aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore4
-rw-r--r--.travis.yml8
-rw-r--r--AUTHORS8
-rw-r--r--NEWS10
-rw-r--r--backends/base-backend.cpp14
-rw-r--r--backends/base-backend.h3
-rw-r--r--backends/cloud/box/boxlistdirectorybyidrequest.cpp195
-rw-r--r--backends/cloud/box/boxlistdirectorybyidrequest.h63
-rw-r--r--backends/cloud/box/boxstorage.cpp345
-rw-r--r--backends/cloud/box/boxstorage.h116
-rw-r--r--backends/cloud/box/boxtokenrefresher.cpp137
-rw-r--r--backends/cloud/box/boxtokenrefresher.h53
-rw-r--r--backends/cloud/box/boxuploadrequest.cpp232
-rw-r--r--backends/cloud/box/boxuploadrequest.h63
-rw-r--r--backends/cloud/cloudmanager.cpp456
-rw-r--r--backends/cloud/cloudmanager.h274
-rw-r--r--backends/cloud/downloadrequest.cpp138
-rw-r--r--backends/cloud/downloadrequest.h64
-rw-r--r--backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp137
-rw-r--r--backends/cloud/dropbox/dropboxcreatedirectoryrequest.h57
-rw-r--r--backends/cloud/dropbox/dropboxinforequest.cpp192
-rw-r--r--backends/cloud/dropbox/dropboxinforequest.h56
-rw-r--r--backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp222
-rw-r--r--backends/cloud/dropbox/dropboxlistdirectoryrequest.h61
-rw-r--r--backends/cloud/dropbox/dropboxstorage.cpp198
-rw-r--r--backends/cloud/dropbox/dropboxstorage.h101
-rw-r--r--backends/cloud/dropbox/dropboxuploadrequest.cpp204
-rw-r--r--backends/cloud/dropbox/dropboxuploadrequest.h60
-rw-r--r--backends/cloud/folderdownloadrequest.cpp197
-rw-r--r--backends/cloud/folderdownloadrequest.h79
-rw-r--r--backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp163
-rw-r--r--backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h63
-rw-r--r--backends/cloud/googledrive/googledrivestorage.cpp348
-rw-r--r--backends/cloud/googledrive/googledrivestorage.h118
-rw-r--r--backends/cloud/googledrive/googledrivetokenrefresher.cpp125
-rw-r--r--backends/cloud/googledrive/googledrivetokenrefresher.h52
-rw-r--r--backends/cloud/googledrive/googledriveuploadrequest.cpp353
-rw-r--r--backends/cloud/googledrive/googledriveuploadrequest.h70
-rw-r--r--backends/cloud/id/idcreatedirectoryrequest.cpp163
-rw-r--r--backends/cloud/id/idcreatedirectoryrequest.h65
-rw-r--r--backends/cloud/id/iddownloadrequest.cpp108
-rw-r--r--backends/cloud/id/iddownloadrequest.h62
-rw-r--r--backends/cloud/id/idlistdirectoryrequest.cpp141
-rw-r--r--backends/cloud/id/idlistdirectoryrequest.h66
-rw-r--r--backends/cloud/id/idresolveidrequest.cpp136
-rw-r--r--backends/cloud/id/idresolveidrequest.h60
-rw-r--r--backends/cloud/id/idstorage.cpp109
-rw-r--r--backends/cloud/id/idstorage.h83
-rw-r--r--backends/cloud/id/idstreamfilerequest.cpp98
-rw-r--r--backends/cloud/id/idstreamfilerequest.h59
-rw-r--r--backends/cloud/iso8601.cpp100
-rw-r--r--backends/cloud/iso8601.h37
-rw-r--r--backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp150
-rw-r--r--backends/cloud/onedrive/onedrivecreatedirectoryrequest.h59
-rw-r--r--backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp193
-rw-r--r--backends/cloud/onedrive/onedrivelistdirectoryrequest.h66
-rw-r--r--backends/cloud/onedrive/onedrivestorage.cpp326
-rw-r--r--backends/cloud/onedrive/onedrivestorage.h113
-rw-r--r--backends/cloud/onedrive/onedrivetokenrefresher.cpp130
-rw-r--r--backends/cloud/onedrive/onedrivetokenrefresher.h52
-rw-r--r--backends/cloud/onedrive/onedriveuploadrequest.cpp191
-rw-r--r--backends/cloud/onedrive/onedriveuploadrequest.h61
-rw-r--r--backends/cloud/savessyncrequest.cpp403
-rw-r--r--backends/cloud/savessyncrequest.h80
-rw-r--r--backends/cloud/storage.cpp342
-rw-r--r--backends/cloud/storage.h238
-rw-r--r--backends/cloud/storagefile.cpp68
-rw-r--r--backends/cloud/storagefile.h65
-rw-r--r--backends/cloud/storageinfo.h53
-rw-r--r--backends/events/androidsdl/androidsdl-events.cpp23
-rw-r--r--backends/fs/abstract-fs.h9
-rw-r--r--backends/fs/amigaos4/amigaos4-fs.cpp5
-rw-r--r--backends/fs/amigaos4/amigaos4-fs.h1
-rw-r--r--backends/fs/chroot/chroot-fs.cpp5
-rw-r--r--backends/fs/chroot/chroot-fs.h1
-rw-r--r--backends/fs/ds/ds-fs.cpp10
-rw-r--r--backends/fs/ds/ds-fs.h2
-rw-r--r--backends/fs/n64/n64-fs.cpp5
-rw-r--r--backends/fs/n64/n64-fs.h1
-rw-r--r--backends/fs/posix/posix-fs.cpp30
-rw-r--r--backends/fs/posix/posix-fs.h1
-rw-r--r--backends/fs/ps2/ps2-fs.cpp5
-rw-r--r--backends/fs/ps2/ps2-fs.h1
-rw-r--r--backends/fs/psp/psp-fs.cpp5
-rw-r--r--backends/fs/psp/psp-fs.h1
-rw-r--r--backends/fs/symbian/symbian-fs.cpp6
-rw-r--r--backends/fs/symbian/symbian-fs.h1
-rw-r--r--backends/fs/wii/wii-fs.cpp5
-rw-r--r--backends/fs/wii/wii-fs.h1
-rw-r--r--backends/fs/windows/windows-fs.cpp32
-rw-r--r--backends/fs/windows/windows-fs.h1
-rw-r--r--backends/graphics/graphics.h4
-rw-r--r--backends/graphics/opengl/opengl-graphics.cpp25
-rw-r--r--backends/graphics/opengl/opengl-graphics.h3
-rw-r--r--backends/graphics/opengl/opengl-sys.h12
-rw-r--r--backends/graphics/surfacesdl/surfacesdl-graphics.cpp257
-rw-r--r--backends/graphics/surfacesdl/surfacesdl-graphics.h20
-rw-r--r--backends/modular-backend.cpp12
-rw-r--r--backends/modular-backend.h3
-rw-r--r--backends/module.mk95
-rw-r--r--backends/networking/browser/openurl-android.cpp (renamed from engines/titanic/game/placeholder/place_holder_item.cpp)18
-rw-r--r--backends/networking/browser/openurl-default.cpp (renamed from engines/titanic/gfx/chev_switch.cpp)22
-rw-r--r--backends/networking/browser/openurl-osx.cpp49
-rw-r--r--backends/networking/browser/openurl-posix.cpp77
-rw-r--r--backends/networking/browser/openurl-windows.cpp42
-rw-r--r--backends/networking/browser/openurl.h41
-rw-r--r--backends/networking/connection/islimited-android.cpp35
-rw-r--r--backends/networking/connection/islimited-default.cpp (renamed from engines/titanic/sound/music_handler.cpp)27
-rw-r--r--backends/networking/connection/islimited.h39
-rw-r--r--backends/networking/curl/cloudicon.cpp171
-rw-r--r--backends/networking/curl/cloudicon.h70
-rw-r--r--backends/networking/curl/cloudicon_data.h111
-rw-r--r--backends/networking/curl/cloudicon_disabled_data.h117
-rw-r--r--backends/networking/curl/connectionmanager.cpp204
-rw-r--r--backends/networking/curl/connectionmanager.h132
-rw-r--r--backends/networking/curl/curljsonrequest.cpp215
-rw-r--r--backends/networking/curl/curljsonrequest.h67
-rw-r--r--backends/networking/curl/curlrequest.cpp162
-rw-r--r--backends/networking/curl/curlrequest.h100
-rw-r--r--backends/networking/curl/networkreadstream.cpp257
-rw-r--r--backends/networking/curl/networkreadstream.h142
-rw-r--r--backends/networking/curl/request.cpp74
-rw-r--r--backends/networking/curl/request.h203
-rw-r--r--backends/networking/make_archive.py37
-rw-r--r--backends/networking/sdl_net/client.cpp190
-rw-r--r--backends/networking/sdl_net/client.h125
-rw-r--r--backends/networking/sdl_net/getclienthandler.cpp162
-rw-r--r--backends/networking/sdl_net/getclienthandler.h57
-rw-r--r--backends/networking/sdl_net/handlers/basehandler.h41
-rw-r--r--backends/networking/sdl_net/handlers/createdirectoryhandler.cpp129
-rw-r--r--backends/networking/sdl_net/handlers/createdirectoryhandler.h42
-rw-r--r--backends/networking/sdl_net/handlers/downloadfilehandler.cpp88
-rw-r--r--backends/networking/sdl_net/handlers/downloadfilehandler.h40
-rw-r--r--backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp81
-rw-r--r--backends/networking/sdl_net/handlers/filesajaxpagehandler.h40
-rw-r--r--backends/networking/sdl_net/handlers/filesbasehandler.cpp91
-rw-r--r--backends/networking/sdl_net/handlers/filesbasehandler.h52
-rw-r--r--backends/networking/sdl_net/handlers/filespagehandler.cpp237
-rw-r--r--backends/networking/sdl_net/handlers/filespagehandler.h62
-rw-r--r--backends/networking/sdl_net/handlers/indexpagehandler.cpp65
-rw-r--r--backends/networking/sdl_net/handlers/indexpagehandler.h45
-rw-r--r--backends/networking/sdl_net/handlers/listajaxhandler.cpp157
-rw-r--r--backends/networking/sdl_net/handlers/listajaxhandler.h63
-rw-r--r--backends/networking/sdl_net/handlers/resourcehandler.cpp75
-rw-r--r--backends/networking/sdl_net/handlers/resourcehandler.h42
-rw-r--r--backends/networking/sdl_net/handlers/uploadfilehandler.cpp79
-rw-r--r--backends/networking/sdl_net/handlers/uploadfilehandler.h40
-rw-r--r--backends/networking/sdl_net/handlerutils.cpp204
-rw-r--r--backends/networking/sdl_net/handlerutils.h50
-rw-r--r--backends/networking/sdl_net/localwebserver.cpp446
-rw-r--r--backends/networking/sdl_net/localwebserver.h115
-rw-r--r--backends/networking/sdl_net/reader.cpp462
-rw-r--r--backends/networking/sdl_net/reader.h143
-rw-r--r--backends/networking/sdl_net/uploadfileclienthandler.cpp212
-rw-r--r--backends/networking/sdl_net/uploadfileclienthandler.h70
-rw-r--r--backends/networking/wwwroot.zipbin0 -> 242704 bytes
-rw-r--r--backends/networking/wwwroot/.files.html60
-rw-r--r--backends/networking/wwwroot/.filesAJAX.html240
-rw-r--r--backends/networking/wwwroot/.index.html18
-rw-r--r--backends/networking/wwwroot/ajax.js48
-rw-r--r--backends/networking/wwwroot/favicon.icobin0 -> 94081 bytes
-rw-r--r--backends/networking/wwwroot/icons/7z.pngbin0 -> 166 bytes
-rw-r--r--backends/networking/wwwroot/icons/dir.pngbin0 -> 150 bytes
-rw-r--r--backends/networking/wwwroot/icons/txt.pngbin0 -> 156 bytes
-rw-r--r--backends/networking/wwwroot/icons/unk.pngbin0 -> 142 bytes
-rw-r--r--backends/networking/wwwroot/icons/up.pngbin0 -> 161 bytes
-rw-r--r--backends/networking/wwwroot/icons/zip.pngbin0 -> 183 bytes
-rw-r--r--backends/networking/wwwroot/logo.pngbin0 -> 132967 bytes
-rw-r--r--backends/networking/wwwroot/style.css113
-rw-r--r--backends/platform/android/asset-archive.cpp480
-rw-r--r--backends/platform/android/asset-archive.h10
-rw-r--r--backends/platform/android/jni.cpp39
-rw-r--r--backends/platform/android/jni.h4
-rw-r--r--backends/platform/android/org/scummvm/scummvm/ScummVM.java2
-rw-r--r--backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java19
-rw-r--r--backends/platform/dc/dc-fs.cpp1
-rw-r--r--backends/platform/dc/vmsave.cpp23
-rw-r--r--backends/platform/ds/arm9/source/gbampsave.cpp13
-rw-r--r--backends/platform/ds/arm9/source/gbampsave.h3
-rw-r--r--backends/platform/n64/framfs_save_manager.h19
-rw-r--r--backends/platform/n64/pakfs_save_manager.h19
-rw-r--r--backends/platform/ps2/savefilemgr.cpp15
-rw-r--r--backends/platform/ps2/savefilemgr.h3
-rw-r--r--backends/platform/sdl/macosx/macosx.cpp11
-rw-r--r--backends/platform/sdl/macosx/macosx.h3
-rw-r--r--backends/platform/sdl/macosx/macosx_wrapper.h31
-rw-r--r--backends/platform/sdl/macosx/macosx_wrapper.mm48
-rw-r--r--backends/platform/sdl/module.mk1
-rw-r--r--backends/platform/sdl/sdl.cpp54
-rw-r--r--backends/platform/sdl/sdl.h9
-rw-r--r--backends/saves/default/default-saves.cpp166
-rw-r--r--backends/saves/default/default-saves.h22
-rw-r--r--backends/saves/savefile.cpp28
-rw-r--r--base/commandLine.cpp4
-rw-r--r--base/main.cpp24
-rw-r--r--base/plugins.h8
-rw-r--r--base/version.cpp21
-rw-r--r--common/callback.h138
-rw-r--r--common/config-manager.cpp26
-rw-r--r--common/config-manager.h9
-rw-r--r--common/debug.cpp23
-rw-r--r--common/debug.h30
-rw-r--r--common/file.cpp17
-rw-r--r--common/file.h2
-rw-r--r--common/gui_options.h2
-rw-r--r--common/json.cpp1099
-rw-r--r--common/json.h166
-rw-r--r--common/memstream.h83
-rw-r--r--common/module.mk1
-rw-r--r--common/savefile.h32
-rw-r--r--common/scummsys.h6
-rw-r--r--common/str.cpp18
-rw-r--r--common/str.h20
-rw-r--r--common/system.h71
-rw-r--r--common/xmlparser.cpp2
-rwxr-xr-xconfigure189
-rw-r--r--devtools/create_project/create_project.cpp21
-rw-r--r--devtools/create_project/create_project.h2
-rw-r--r--devtools/create_project/xcode.cpp3
-rw-r--r--devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj6
-rw-r--r--devtools/create_titanic/create_titanic_dat.cpp495
-rwxr-xr-xdevtools/credits.pl8
-rw-r--r--devtools/scumm-md5.txt1
-rw-r--r--dists/android/AndroidManifest.xml2
-rw-r--r--dists/android/AndroidManifest.xml.in2
-rw-r--r--dists/cloudicon.pngbin0 -> 1242 bytes
-rw-r--r--dists/cloudicon_disabled.pngbin0 -> 1331 bytes
-rw-r--r--dists/debian/control2
-rw-r--r--dists/msvc10/create_msvc10.bat4
-rw-r--r--dists/msvc11/create_msvc11.bat4
-rw-r--r--dists/msvc12/create_msvc12.bat4
-rw-r--r--dists/msvc9/create_msvc9.bat4
-rw-r--r--dists/scummvm.rc3
-rw-r--r--dists/win32/ScummVM.iss2
-rw-r--r--engines/access/detection.cpp3
-rw-r--r--engines/adl/adl.cpp29
-rw-r--r--engines/adl/adl.h4
-rw-r--r--engines/adl/adl_v2.cpp102
-rw-r--r--engines/adl/adl_v2.h11
-rw-r--r--engines/adl/adl_v3.cpp229
-rw-r--r--engines/adl/adl_v3.h29
-rw-r--r--engines/adl/adl_v4.cpp258
-rw-r--r--engines/adl/adl_v4.h (renamed from engines/adl/hires2.h)63
-rw-r--r--engines/adl/detection.cpp33
-rw-r--r--engines/adl/detection.h1
-rw-r--r--engines/adl/disk.cpp196
-rw-r--r--engines/adl/disk.h50
-rw-r--r--engines/adl/display.cpp25
-rw-r--r--engines/adl/display.h1
-rw-r--r--engines/adl/hires0.cpp143
-rw-r--r--engines/adl/hires1.cpp104
-rw-r--r--engines/adl/hires1.h134
-rw-r--r--engines/adl/hires2.cpp102
-rw-r--r--engines/adl/hires4.cpp271
-rw-r--r--engines/adl/hires6.cpp127
-rw-r--r--engines/adl/hires6.h92
-rw-r--r--engines/adl/module.mk3
-rw-r--r--engines/agi/detection.cpp3
-rw-r--r--engines/agos/detection.cpp3
-rw-r--r--engines/avalanche/detection.cpp3
-rw-r--r--engines/bbvs/detection.cpp3
-rw-r--r--engines/cge/detection.cpp3
-rw-r--r--engines/cge2/detection.cpp3
-rw-r--r--engines/director/archive.cpp248
-rw-r--r--engines/director/detection_tables.h13
-rw-r--r--engines/director/director.cpp278
-rw-r--r--engines/director/director.h26
-rw-r--r--engines/director/frame.cpp782
-rw-r--r--engines/director/frame.h149
-rw-r--r--engines/director/images.cpp (renamed from engines/director/dib.cpp)120
-rw-r--r--engines/director/images.h (renamed from engines/director/dib.h)23
-rw-r--r--engines/director/lingo/lingo-builtins.cpp267
-rw-r--r--engines/director/lingo/lingo-code.cpp196
-rw-r--r--engines/director/lingo/lingo-codegen.cpp101
-rw-r--r--engines/director/lingo/lingo-funcs.cpp26
-rw-r--r--engines/director/lingo/lingo-gr.cpp1392
-rw-r--r--engines/director/lingo/lingo-gr.h240
-rw-r--r--engines/director/lingo/lingo-gr.y38
-rw-r--r--engines/director/lingo/lingo-lex.cpp546
-rw-r--r--engines/director/lingo/lingo-lex.l10
-rw-r--r--engines/director/lingo/lingo-the.cpp155
-rw-r--r--engines/director/lingo/lingo-the.h50
-rw-r--r--engines/director/lingo/lingo.cpp121
-rw-r--r--engines/director/lingo/lingo.h93
-rw-r--r--engines/director/lingo/tests/factory2.lingo4
-rw-r--r--engines/director/lingo/tests/math.lingo2
-rw-r--r--engines/director/module.mk5
-rw-r--r--engines/director/movie.cpp5
-rw-r--r--engines/director/movie.h3
-rw-r--r--engines/director/resource.cpp17
-rw-r--r--engines/director/resource.h9
-rw-r--r--engines/director/score.cpp985
-rw-r--r--engines/director/score.h244
-rw-r--r--engines/director/sound.cpp10
-rw-r--r--engines/director/sound.h2
-rw-r--r--engines/director/sprite.cpp96
-rw-r--r--engines/director/sprite.h137
-rw-r--r--engines/drascula/detection.cpp3
-rw-r--r--engines/fullpipe/anihandler.cpp (renamed from engines/fullpipe/mgm.cpp)201
-rw-r--r--engines/fullpipe/anihandler.h (renamed from engines/fullpipe/mgm.h)40
-rw-r--r--engines/fullpipe/behavior.cpp28
-rw-r--r--engines/fullpipe/fullpipe.cpp10
-rw-r--r--engines/fullpipe/fullpipe.h21
-rw-r--r--engines/fullpipe/gameloader.cpp4
-rw-r--r--engines/fullpipe/gfx.cpp34
-rw-r--r--engines/fullpipe/interaction.cpp10
-rw-r--r--engines/fullpipe/inventory.cpp32
-rw-r--r--engines/fullpipe/lift.cpp5
-rw-r--r--engines/fullpipe/messages.cpp8
-rw-r--r--engines/fullpipe/messages.h2
-rw-r--r--engines/fullpipe/modal.cpp34
-rw-r--r--engines/fullpipe/module.mk2
-rw-r--r--engines/fullpipe/motion.cpp567
-rw-r--r--engines/fullpipe/motion.h66
-rw-r--r--engines/fullpipe/objects.h2
-rw-r--r--engines/fullpipe/scene.cpp2
-rw-r--r--engines/fullpipe/scenes.cpp2
-rw-r--r--engines/fullpipe/scenes.h6
-rw-r--r--engines/fullpipe/scenes/scene03.cpp35
-rw-r--r--engines/fullpipe/scenes/scene04.cpp187
-rw-r--r--engines/fullpipe/scenes/scene11.cpp64
-rw-r--r--engines/fullpipe/scenes/scene22.cpp34
-rw-r--r--engines/fullpipe/scenes/scene27.cpp12
-rw-r--r--engines/fullpipe/scenes/scene29.cpp22
-rw-r--r--engines/fullpipe/stateloader.cpp4
-rw-r--r--engines/fullpipe/statics.cpp101
-rw-r--r--engines/fullpipe/utils.cpp2
-rw-r--r--engines/gnap/detection.cpp3
-rw-r--r--engines/hopkins/detection.cpp3
-rw-r--r--engines/kyra/animator_tim.cpp3
-rw-r--r--engines/kyra/detection.cpp3
-rw-r--r--engines/kyra/kyra_v1.h3
-rw-r--r--engines/kyra/saveload.cpp4
-rw-r--r--engines/kyra/saveload_eob.cpp2
-rw-r--r--engines/lab/detection.cpp3
-rw-r--r--engines/mads/detection.cpp3
-rw-r--r--engines/metaengine.h14
-rw-r--r--engines/mohawk/console.cpp1
-rw-r--r--engines/mohawk/cstime.cpp3
-rw-r--r--engines/mohawk/cstime.h1
-rw-r--r--engines/mohawk/livingbooks.cpp3
-rw-r--r--engines/mohawk/livingbooks.h1
-rw-r--r--engines/mohawk/module.mk1
-rw-r--r--engines/mohawk/mohawk.cpp9
-rw-r--r--engines/mohawk/mohawk.h1
-rw-r--r--engines/mohawk/myst.cpp3
-rw-r--r--engines/mohawk/myst.h1
-rw-r--r--engines/mohawk/riven.cpp7
-rw-r--r--engines/mohawk/riven.h2
-rw-r--r--engines/mohawk/riven_external.cpp6
-rw-r--r--engines/mohawk/riven_graphics.cpp2
-rw-r--r--engines/mohawk/riven_scripts.cpp46
-rw-r--r--engines/mohawk/riven_sound.cpp459
-rw-r--r--engines/mohawk/riven_sound.h197
-rw-r--r--engines/mohawk/sound.cpp451
-rw-r--r--engines/mohawk/sound.h37
-rw-r--r--engines/mortevielle/detection.cpp1
-rw-r--r--engines/neverhood/detection.cpp3
-rw-r--r--engines/prince/detection.cpp3
-rw-r--r--engines/saga/detection.cpp3
-rw-r--r--engines/savestate.cpp4
-rw-r--r--engines/savestate.h23
-rw-r--r--engines/sci/console.cpp4
-rw-r--r--engines/sci/detection.cpp4
-rw-r--r--engines/sci/detection_tables.h15
-rw-r--r--engines/sci/engine/features.cpp47
-rw-r--r--engines/sci/engine/features.h14
-rw-r--r--engines/sci/engine/file.cpp2
-rw-r--r--engines/sci/engine/kernel.h38
-rw-r--r--engines/sci/engine/kernel_tables.h154
-rw-r--r--engines/sci/engine/kevent.cpp80
-rw-r--r--engines/sci/engine/kfile.cpp105
-rw-r--r--engines/sci/engine/kgraphics.cpp13
-rw-r--r--engines/sci/engine/kgraphics32.cpp136
-rw-r--r--engines/sci/engine/klists.cpp36
-rw-r--r--engines/sci/engine/kmisc.cpp98
-rw-r--r--engines/sci/engine/kvideo.cpp253
-rw-r--r--engines/sci/engine/savegame.cpp80
-rw-r--r--engines/sci/engine/savegame.h4
-rw-r--r--engines/sci/engine/script_patches.cpp113
-rw-r--r--engines/sci/engine/script_patches.h12
-rw-r--r--engines/sci/engine/seg_manager.cpp12
-rw-r--r--engines/sci/engine/seg_manager.h3
-rw-r--r--engines/sci/engine/segment.h72
-rw-r--r--engines/sci/engine/state.cpp3
-rw-r--r--engines/sci/engine/state.h1
-rw-r--r--engines/sci/engine/vm.cpp15
-rw-r--r--engines/sci/engine/workarounds.cpp21
-rw-r--r--engines/sci/engine/workarounds.h2
-rw-r--r--engines/sci/event.cpp9
-rw-r--r--engines/sci/graphics/cache.cpp10
-rw-r--r--engines/sci/graphics/celobj32.cpp162
-rw-r--r--engines/sci/graphics/celobj32.h9
-rw-r--r--engines/sci/graphics/compare.cpp2
-rw-r--r--engines/sci/graphics/compare.h4
-rw-r--r--engines/sci/graphics/coordadjuster.cpp52
-rw-r--r--engines/sci/graphics/coordadjuster.h48
-rw-r--r--engines/sci/graphics/cursor.cpp43
-rw-r--r--engines/sci/graphics/cursor.h9
-rw-r--r--engines/sci/graphics/cursor32.cpp448
-rw-r--r--engines/sci/graphics/cursor32.h255
-rw-r--r--engines/sci/graphics/frameout.cpp122
-rw-r--r--engines/sci/graphics/frameout.h33
-rw-r--r--engines/sci/graphics/helpers.h6
-rw-r--r--engines/sci/graphics/paint16.cpp2
-rw-r--r--engines/sci/graphics/paint16.h4
-rw-r--r--engines/sci/graphics/palette32.cpp14
-rw-r--r--engines/sci/graphics/picture.cpp2
-rw-r--r--engines/sci/graphics/picture.h6
-rw-r--r--engines/sci/graphics/screen.cpp27
-rw-r--r--engines/sci/graphics/screen_item32.cpp9
-rw-r--r--engines/sci/graphics/screen_item32.h1
-rw-r--r--engines/sci/graphics/text16.cpp2
-rw-r--r--engines/sci/graphics/text32.cpp6
-rw-r--r--engines/sci/graphics/transitions32.cpp4
-rw-r--r--engines/sci/graphics/video32.cpp506
-rw-r--r--engines/sci/graphics/video32.h231
-rw-r--r--engines/sci/graphics/view.cpp12
-rw-r--r--engines/sci/graphics/view.h2
-rw-r--r--engines/sci/module.mk1
-rw-r--r--engines/sci/resource.cpp56
-rw-r--r--engines/sci/resource.h27
-rw-r--r--engines/sci/resource_audio.cpp38
-rw-r--r--engines/sci/resource_intern.h5
-rw-r--r--engines/sci/sci.cpp87
-rw-r--r--engines/sci/sci.h24
-rw-r--r--engines/sci/sound/audio32.cpp164
-rw-r--r--engines/sci/sound/audio32.h24
-rw-r--r--engines/sci/sound/decoders/sol.cpp19
-rw-r--r--engines/sci/video/robot_decoder.cpp1806
-rw-r--r--engines/sci/video/robot_decoder.h1459
-rw-r--r--engines/scumm/detection.cpp3
-rw-r--r--engines/scumm/he/intern_he.h5
-rw-r--r--engines/scumm/he/logic/moonbase_logic.cpp311
-rw-r--r--engines/scumm/he/moonbase/moonbase.cpp9
-rw-r--r--engines/scumm/he/moonbase/moonbase.h4
-rw-r--r--engines/scumm/he/moonbase/net_defines.h66
-rw-r--r--engines/scumm/he/moonbase/net_main.cpp199
-rw-r--r--engines/scumm/he/moonbase/net_main.h89
-rw-r--r--engines/scumm/he/script_v72he.cpp37
-rw-r--r--engines/scumm/module.mk5
-rw-r--r--engines/scumm/object.cpp10
-rw-r--r--engines/scumm/script_v6.cpp11
-rw-r--r--engines/scumm/scumm-md5.h3
-rw-r--r--engines/scumm/scumm.cpp24
-rw-r--r--engines/sherlock/detection.cpp3
-rw-r--r--engines/sky/detection.cpp2
-rw-r--r--engines/sword2/sword2.cpp3
-rw-r--r--engines/testbed/cloud.cpp565
-rw-r--r--engines/testbed/cloud.h83
-rw-r--r--engines/testbed/config-params.h12
-rw-r--r--engines/testbed/misc.cpp25
-rw-r--r--engines/testbed/misc.h3
-rw-r--r--engines/testbed/module.mk10
-rw-r--r--engines/testbed/testbed.cpp16
-rw-r--r--engines/testbed/webserver.cpp237
-rw-r--r--engines/testbed/webserver.h69
-rw-r--r--engines/tinsel/detection.cpp3
-rw-r--r--engines/titanic/carry/arm.cpp4
-rw-r--r--engines/titanic/carry/auditory_centre.cpp10
-rw-r--r--engines/titanic/carry/auditory_centre.h2
-rw-r--r--engines/titanic/carry/bowl_ear.cpp31
-rw-r--r--engines/titanic/carry/bowl_ear.h5
-rw-r--r--engines/titanic/carry/brain.cpp2
-rw-r--r--engines/titanic/carry/bridge_piece.cpp6
-rw-r--r--engines/titanic/carry/carry.cpp4
-rw-r--r--engines/titanic/carry/carry.h9
-rw-r--r--engines/titanic/carry/carry_parrot.cpp14
-rw-r--r--engines/titanic/carry/central_core.cpp56
-rw-r--r--engines/titanic/carry/central_core.h4
-rw-r--r--engines/titanic/carry/chicken.cpp4
-rw-r--r--engines/titanic/carry/chicken.h2
-rw-r--r--engines/titanic/carry/crushed_tv.cpp1
-rw-r--r--engines/titanic/carry/ear.cpp27
-rw-r--r--engines/titanic/carry/ear.h3
-rw-r--r--engines/titanic/carry/eye.cpp103
-rw-r--r--engines/titanic/carry/eye.h8
-rw-r--r--engines/titanic/carry/fruit.cpp47
-rw-r--r--engines/titanic/carry/fruit.h5
-rw-r--r--engines/titanic/carry/glass.cpp116
-rw-r--r--engines/titanic/carry/glass.h9
-rw-r--r--engines/titanic/carry/hammer.cpp23
-rw-r--r--engines/titanic/carry/hammer.h3
-rw-r--r--engines/titanic/carry/head_piece.cpp50
-rw-r--r--engines/titanic/carry/head_piece.h11
-rw-r--r--engines/titanic/carry/hose.cpp71
-rw-r--r--engines/titanic/carry/hose.h15
-rw-r--r--engines/titanic/carry/key.cpp20
-rw-r--r--engines/titanic/carry/key.h3
-rw-r--r--engines/titanic/carry/liftbot_head.cpp67
-rw-r--r--engines/titanic/carry/liftbot_head.h6
-rw-r--r--engines/titanic/carry/long_stick.cpp32
-rw-r--r--engines/titanic/carry/long_stick.h4
-rw-r--r--engines/titanic/carry/magazine.cpp2
-rw-r--r--engines/titanic/carry/maitred_left_arm.cpp33
-rw-r--r--engines/titanic/carry/maitred_left_arm.h7
-rw-r--r--engines/titanic/carry/maitred_right_arm.cpp12
-rw-r--r--engines/titanic/carry/maitred_right_arm.h2
-rw-r--r--engines/titanic/carry/mouth.cpp40
-rw-r--r--engines/titanic/carry/mouth.h4
-rw-r--r--engines/titanic/carry/napkin.cpp3
-rw-r--r--engines/titanic/carry/nose.cpp25
-rw-r--r--engines/titanic/carry/nose.h3
-rw-r--r--engines/titanic/carry/perch.cpp13
-rw-r--r--engines/titanic/carry/perch.h2
-rw-r--r--engines/titanic/carry/phonograph_cylinder.cpp34
-rw-r--r--engines/titanic/carry/phonograph_ear.cpp26
-rw-r--r--engines/titanic/carry/phonograph_ear.h8
-rw-r--r--engines/titanic/carry/photograph.cpp11
-rw-r--r--engines/titanic/carry/plug_in.cpp16
-rw-r--r--engines/titanic/carry/speech_centre.cpp42
-rw-r--r--engines/titanic/carry/speech_centre.h9
-rw-r--r--engines/titanic/carry/vision_centre.cpp29
-rw-r--r--engines/titanic/carry/vision_centre.h4
-rw-r--r--engines/titanic/core/background.cpp14
-rw-r--r--engines/titanic/core/background.h4
-rw-r--r--engines/titanic/core/click_responder.cpp21
-rw-r--r--engines/titanic/core/click_responder.h4
-rw-r--r--engines/titanic/core/drop_target.cpp172
-rw-r--r--engines/titanic/core/drop_target.h32
-rw-r--r--engines/titanic/core/game_object.cpp125
-rw-r--r--engines/titanic/core/game_object.h120
-rw-r--r--engines/titanic/core/game_object_desc_item.cpp4
-rw-r--r--engines/titanic/core/game_object_desc_item.h7
-rw-r--r--engines/titanic/core/mail_man.cpp4
-rw-r--r--engines/titanic/core/multi_drop_target.cpp21
-rw-r--r--engines/titanic/core/multi_drop_target.h2
-rw-r--r--engines/titanic/core/named_item.cpp6
-rw-r--r--engines/titanic/core/named_item.h4
-rw-r--r--engines/titanic/core/project_item.cpp2
-rw-r--r--engines/titanic/core/room_item.cpp4
-rw-r--r--engines/titanic/core/saveable_object.cpp31
-rw-r--r--engines/titanic/core/tree_item.cpp29
-rw-r--r--engines/titanic/core/tree_item.h15
-rw-r--r--engines/titanic/core/turn_on_play_sound.cpp25
-rw-r--r--engines/titanic/core/turn_on_play_sound.h8
-rw-r--r--engines/titanic/core/turn_on_turn_off.cpp49
-rw-r--r--engines/titanic/core/turn_on_turn_off.h13
-rw-r--r--engines/titanic/core/view_item.cpp4
-rw-r--r--engines/titanic/debugger.cpp2
-rw-r--r--engines/titanic/events.cpp10
-rw-r--r--engines/titanic/game/announce.cpp100
-rw-r--r--engines/titanic/game/announce.h12
-rw-r--r--engines/titanic/game/annoy_barbot.cpp13
-rw-r--r--engines/titanic/game/annoy_barbot.h2
-rw-r--r--engines/titanic/game/arboretum_gate.cpp357
-rw-r--r--engines/titanic/game/arboretum_gate.h61
-rw-r--r--engines/titanic/game/auto_animate.cpp30
-rw-r--r--engines/titanic/game/auto_animate.h8
-rw-r--r--engines/titanic/game/bar_bell.cpp83
-rw-r--r--engines/titanic/game/bar_bell.h8
-rw-r--r--engines/titanic/game/bar_menu.cpp70
-rw-r--r--engines/titanic/game/bar_menu.h13
-rw-r--r--engines/titanic/game/bar_menu_button.cpp22
-rw-r--r--engines/titanic/game/bar_menu_button.h3
-rw-r--r--engines/titanic/game/belbot_get_light.cpp39
-rw-r--r--engines/titanic/game/belbot_get_light.h5
-rw-r--r--engines/titanic/game/bomb.cpp306
-rw-r--r--engines/titanic/game/bomb.h15
-rw-r--r--engines/titanic/game/bottom_of_well_monitor.cpp72
-rw-r--r--engines/titanic/game/bottom_of_well_monitor.h9
-rw-r--r--engines/titanic/game/bowl_unlocker.cpp43
-rw-r--r--engines/titanic/game/bowl_unlocker.h9
-rw-r--r--engines/titanic/game/brain_slot.cpp115
-rw-r--r--engines/titanic/game/brain_slot.h11
-rw-r--r--engines/titanic/game/bridge_door.cpp25
-rw-r--r--engines/titanic/game/bridge_door.h4
-rw-r--r--engines/titanic/game/bridge_view.cpp80
-rw-r--r--engines/titanic/game/bridge_view.h7
-rw-r--r--engines/titanic/game/broken_pell_base.cpp2
-rw-r--r--engines/titanic/game/broken_pell_base.h4
-rw-r--r--engines/titanic/game/broken_pellerator.cpp107
-rw-r--r--engines/titanic/game/broken_pellerator.h5
-rw-r--r--engines/titanic/game/broken_pellerator_froz.cpp103
-rw-r--r--engines/titanic/game/broken_pellerator_froz.h5
-rw-r--r--engines/titanic/game/cage.cpp70
-rw-r--r--engines/titanic/game/cage.h8
-rw-r--r--engines/titanic/game/captains_wheel.cpp153
-rw-r--r--engines/titanic/game/captains_wheel.h7
-rw-r--r--engines/titanic/game/cell_point_button.cpp34
-rw-r--r--engines/titanic/game/cell_point_button.h7
-rw-r--r--engines/titanic/game/chev_code.cpp250
-rw-r--r--engines/titanic/game/chev_code.h15
-rw-r--r--engines/titanic/game/chev_panel.cpp88
-rw-r--r--engines/titanic/game/chev_panel.h16
-rw-r--r--engines/titanic/game/chicken_cooler.cpp33
-rw-r--r--engines/titanic/game/chicken_cooler.h2
-rw-r--r--engines/titanic/game/chicken_dispensor.cpp141
-rw-r--r--engines/titanic/game/chicken_dispensor.h8
-rw-r--r--engines/titanic/game/close_broken_pel.cpp14
-rw-r--r--engines/titanic/game/close_broken_pel.h4
-rw-r--r--engines/titanic/game/code_wheel.cpp66
-rw-r--r--engines/titanic/game/code_wheel.h7
-rw-r--r--engines/titanic/game/cookie.cpp17
-rw-r--r--engines/titanic/game/cookie.h3
-rw-r--r--engines/titanic/game/credits.cpp35
-rw-r--r--engines/titanic/game/credits.h3
-rw-r--r--engines/titanic/game/credits_button.cpp20
-rw-r--r--engines/titanic/game/credits_button.h3
-rw-r--r--engines/titanic/game/desk_click_responder.cpp28
-rw-r--r--engines/titanic/game/desk_click_responder.h5
-rw-r--r--engines/titanic/game/doorbot_elevator_handler.cpp16
-rw-r--r--engines/titanic/game/doorbot_elevator_handler.h3
-rw-r--r--engines/titanic/game/doorbot_home_handler.cpp10
-rw-r--r--engines/titanic/game/doorbot_home_handler.h2
-rw-r--r--engines/titanic/game/ear_sweet_bowl.cpp33
-rw-r--r--engines/titanic/game/ear_sweet_bowl.h3
-rw-r--r--engines/titanic/game/eject_phonograph_button.cpp49
-rw-r--r--engines/titanic/game/eject_phonograph_button.h13
-rw-r--r--engines/titanic/game/elevator_action_area.cpp11
-rw-r--r--engines/titanic/game/elevator_action_area.h2
-rw-r--r--engines/titanic/game/emma_control.cpp33
-rw-r--r--engines/titanic/game/emma_control.h11
-rw-r--r--engines/titanic/game/empty_nut_bowl.cpp43
-rw-r--r--engines/titanic/game/empty_nut_bowl.h9
-rw-r--r--engines/titanic/game/end_credit_text.cpp37
-rw-r--r--engines/titanic/game/end_credit_text.h8
-rw-r--r--engines/titanic/game/end_credits.cpp29
-rw-r--r--engines/titanic/game/end_credits.h7
-rw-r--r--engines/titanic/game/end_explode_ship.cpp64
-rw-r--r--engines/titanic/game/end_explode_ship.h5
-rw-r--r--engines/titanic/game/end_game_credits.cpp51
-rw-r--r--engines/titanic/game/end_game_credits.h9
-rw-r--r--engines/titanic/game/end_sequence_control.cpp44
-rw-r--r--engines/titanic/game/end_sequence_control.h4
-rw-r--r--engines/titanic/game/fan.cpp87
-rw-r--r--engines/titanic/game/fan.h8
-rw-r--r--engines/titanic/game/fan_control.cpp141
-rw-r--r--engines/titanic/game/fan_control.h12
-rw-r--r--engines/titanic/game/fan_decrease.cpp11
-rw-r--r--engines/titanic/game/fan_decrease.h2
-rw-r--r--engines/titanic/game/fan_increase.cpp11
-rw-r--r--engines/titanic/game/fan_increase.h2
-rw-r--r--engines/titanic/game/fan_noises.cpp179
-rw-r--r--engines/titanic/game/fan_noises.h19
-rw-r--r--engines/titanic/game/floor_indicator.cpp11
-rw-r--r--engines/titanic/game/floor_indicator.h2
-rw-r--r--engines/titanic/game/games_console.cpp30
-rw-r--r--engines/titanic/game/games_console.h7
-rw-r--r--engines/titanic/game/get_lift_eye2.cpp58
-rw-r--r--engines/titanic/game/get_lift_eye2.h6
-rw-r--r--engines/titanic/game/glass_smasher.cpp19
-rw-r--r--engines/titanic/game/glass_smasher.h3
-rw-r--r--engines/titanic/game/gondolier/gondolier_base.cpp27
-rw-r--r--engines/titanic/game/gondolier/gondolier_base.h10
-rw-r--r--engines/titanic/game/gondolier/gondolier_chest.cpp29
-rw-r--r--engines/titanic/game/gondolier/gondolier_chest.h4
-rw-r--r--engines/titanic/game/gondolier/gondolier_face.cpp23
-rw-r--r--engines/titanic/game/gondolier/gondolier_face.h7
-rw-r--r--engines/titanic/game/gondolier/gondolier_mixer.cpp152
-rw-r--r--engines/titanic/game/gondolier/gondolier_mixer.h17
-rw-r--r--engines/titanic/game/gondolier/gondolier_slider.cpp199
-rw-r--r--engines/titanic/game/gondolier/gondolier_slider.h25
-rw-r--r--engines/titanic/game/hammer_clip.cpp44
-rw-r--r--engines/titanic/game/hammer_clip.h4
-rw-r--r--engines/titanic/game/hammer_dispensor.cpp61
-rw-r--r--engines/titanic/game/hammer_dispensor.h11
-rw-r--r--engines/titanic/game/hammer_dispensor_button.cpp101
-rw-r--r--engines/titanic/game/hammer_dispensor_button.h12
-rw-r--r--engines/titanic/game/head_slot.cpp120
-rw-r--r--engines/titanic/game/head_slot.h11
-rw-r--r--engines/titanic/game/head_smash_event.cpp19
-rw-r--r--engines/titanic/game/head_smash_event.h3
-rw-r--r--engines/titanic/game/head_smash_lever.cpp63
-rw-r--r--engines/titanic/game/head_smash_lever.h11
-rw-r--r--engines/titanic/game/idle_summoner.cpp79
-rw-r--r--engines/titanic/game/idle_summoner.h7
-rw-r--r--engines/titanic/game/lemon_dispensor.cpp78
-rw-r--r--engines/titanic/game/lemon_dispensor.h15
-rw-r--r--engines/titanic/game/light.cpp89
-rw-r--r--engines/titanic/game/light.h8
-rw-r--r--engines/titanic/game/light_switch.cpp102
-rw-r--r--engines/titanic/game/light_switch.h11
-rw-r--r--engines/titanic/game/little_lift_button.cpp25
-rw-r--r--engines/titanic/game/little_lift_button.h3
-rw-r--r--engines/titanic/game/long_stick_dispenser.cpp99
-rw-r--r--engines/titanic/game/long_stick_dispenser.h8
-rw-r--r--engines/titanic/game/maitred/maitred_arm_holder.cpp19
-rw-r--r--engines/titanic/game/maitred/maitred_arm_holder.h3
-rw-r--r--engines/titanic/game/maitred/maitred_body.cpp46
-rw-r--r--engines/titanic/game/maitred/maitred_body.h8
-rw-r--r--engines/titanic/game/maitred/maitred_legs.cpp66
-rw-r--r--engines/titanic/game/maitred/maitred_legs.h7
-rw-r--r--engines/titanic/game/maitred/maitred_prod_receptor.cpp81
-rw-r--r--engines/titanic/game/maitred/maitred_prod_receptor.h9
-rw-r--r--engines/titanic/game/missiveomat.cpp295
-rw-r--r--engines/titanic/game/missiveomat.h27
-rw-r--r--engines/titanic/game/missiveomat_button.cpp30
-rw-r--r--engines/titanic/game/missiveomat_button.h8
-rw-r--r--engines/titanic/game/movie_tester.cpp26
-rw-r--r--engines/titanic/game/movie_tester.h6
-rw-r--r--engines/titanic/game/music_console_button.cpp96
-rw-r--r--engines/titanic/game/music_console_button.h4
-rw-r--r--engines/titanic/game/music_room_stop_phonograph_button.cpp40
-rw-r--r--engines/titanic/game/music_room_stop_phonograph_button.h7
-rw-r--r--engines/titanic/game/music_system_lock.cpp28
-rw-r--r--engines/titanic/game/music_system_lock.h3
-rw-r--r--engines/titanic/game/nav_helmet.cpp99
-rw-r--r--engines/titanic/game/nav_helmet.h13
-rw-r--r--engines/titanic/game/nav_helmet_off.cpp (renamed from engines/titanic/game/parrot/parrot_succubus.cpp)30
-rw-r--r--engines/titanic/game/nav_helmet_off.h53
-rw-r--r--engines/titanic/game/nav_helmet_on.cpp49
-rw-r--r--engines/titanic/game/nav_helmet_on.h (renamed from engines/titanic/game/bilge_succubus.h)22
-rw-r--r--engines/titanic/game/no_nut_bowl.cpp21
-rw-r--r--engines/titanic/game/no_nut_bowl.h4
-rw-r--r--engines/titanic/game/nose_holder.cpp79
-rw-r--r--engines/titanic/game/nose_holder.h8
-rw-r--r--engines/titanic/game/null_port_hole.cpp12
-rw-r--r--engines/titanic/game/nut_replacer.cpp16
-rw-r--r--engines/titanic/game/nut_replacer.h3
-rw-r--r--engines/titanic/game/parrot/parrot_lobby_controller.cpp35
-rw-r--r--engines/titanic/game/parrot/parrot_lobby_controller.h2
-rw-r--r--engines/titanic/game/parrot/parrot_lobby_link_updater.cpp78
-rw-r--r--engines/titanic/game/parrot/parrot_lobby_link_updater.h20
-rw-r--r--engines/titanic/game/parrot/parrot_lobby_object.cpp32
-rw-r--r--engines/titanic/game/parrot/parrot_lobby_object.h8
-rw-r--r--engines/titanic/game/parrot/parrot_lobby_view_object.cpp16
-rw-r--r--engines/titanic/game/parrot/parrot_lobby_view_object.h6
-rw-r--r--engines/titanic/game/parrot/parrot_loser.cpp10
-rw-r--r--engines/titanic/game/parrot/parrot_loser.h2
-rw-r--r--engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp78
-rw-r--r--engines/titanic/game/parrot/parrot_nut_bowl_actor.h11
-rw-r--r--engines/titanic/game/parrot/parrot_nut_eater.cpp52
-rw-r--r--engines/titanic/game/parrot/parrot_nut_eater.h5
-rw-r--r--engines/titanic/game/parrot/parrot_perch_holder.cpp45
-rw-r--r--engines/titanic/game/parrot/parrot_perch_holder.h5
-rw-r--r--engines/titanic/game/parrot/parrot_trigger.cpp13
-rw-r--r--engines/titanic/game/parrot/parrot_trigger.h2
-rw-r--r--engines/titanic/game/parrot/player_meets_parrot.cpp6
-rw-r--r--engines/titanic/game/parrot/player_meets_parrot.h1
-rw-r--r--engines/titanic/game/pet/pet.cpp12
-rw-r--r--engines/titanic/game/pet/pet.h2
-rw-r--r--engines/titanic/game/pet/pet_lift.cpp37
-rw-r--r--engines/titanic/game/pet/pet_lift.h2
-rw-r--r--engines/titanic/game/pet/pet_monitor.cpp20
-rw-r--r--engines/titanic/game/pet/pet_pellerator.cpp24
-rw-r--r--engines/titanic/game/pet/pet_pellerator.h3
-rw-r--r--engines/titanic/game/pet/pet_sentinal.cpp29
-rw-r--r--engines/titanic/game/pet/pet_sentinal.h7
-rw-r--r--engines/titanic/game/pet/pet_sounds.cpp28
-rw-r--r--engines/titanic/game/pet/pet_sounds.h8
-rw-r--r--engines/titanic/game/pet/pet_transition.cpp23
-rw-r--r--engines/titanic/game/pet/pet_transition.h2
-rw-r--r--engines/titanic/game/pet/pet_transport.cpp2
-rw-r--r--engines/titanic/game/pet_disabler.cpp15
-rw-r--r--engines/titanic/game/pet_disabler.h3
-rw-r--r--engines/titanic/game/phonograph.cpp120
-rw-r--r--engines/titanic/game/phonograph.h8
-rw-r--r--engines/titanic/game/phonograph_lid.cpp51
-rw-r--r--engines/titanic/game/phonograph_lid.h9
-rw-r--r--engines/titanic/game/pickup/pick_up.cpp14
-rw-r--r--engines/titanic/game/pickup/pick_up.h8
-rw-r--r--engines/titanic/game/pickup/pick_up_bar_glass.cpp51
-rw-r--r--engines/titanic/game/pickup/pick_up_bar_glass.h4
-rw-r--r--engines/titanic/game/pickup/pick_up_hose.cpp67
-rw-r--r--engines/titanic/game/pickup/pick_up_hose.h9
-rw-r--r--engines/titanic/game/pickup/pick_up_lemon.cpp25
-rw-r--r--engines/titanic/game/pickup/pick_up_lemon.h3
-rw-r--r--engines/titanic/game/pickup/pick_up_speech_centre.cpp36
-rw-r--r--engines/titanic/game/pickup/pick_up_speech_centre.h4
-rw-r--r--engines/titanic/game/pickup/pick_up_vis_centre.cpp23
-rw-r--r--engines/titanic/game/pickup/pick_up_vis_centre.h3
-rw-r--r--engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp36
-rw-r--r--engines/titanic/game/placeholder/bar_shelf_vis_centre.h12
-rw-r--r--engines/titanic/game/placeholder/lemon_on_bar.cpp21
-rw-r--r--engines/titanic/game/placeholder/lemon_on_bar.h8
-rw-r--r--engines/titanic/game/placeholder/place_holder.cpp (renamed from engines/titanic/game/call_pellerator.cpp)15
-rw-r--r--engines/titanic/game/placeholder/place_holder.h (renamed from engines/titanic/game/placeholder/place_holder_item.h)4
-rw-r--r--engines/titanic/game/placeholder/tv_on_bar.cpp22
-rw-r--r--engines/titanic/game/placeholder/tv_on_bar.h8
-rw-r--r--engines/titanic/game/play_music_button.cpp43
-rw-r--r--engines/titanic/game/play_music_button.h9
-rw-r--r--engines/titanic/game/play_on_act.cpp21
-rw-r--r--engines/titanic/game/play_on_act.h3
-rw-r--r--engines/titanic/game/port_hole.cpp62
-rw-r--r--engines/titanic/game/port_hole.h9
-rw-r--r--engines/titanic/game/record_phonograph_button.cpp32
-rw-r--r--engines/titanic/game/record_phonograph_button.h7
-rw-r--r--engines/titanic/game/replacement_ear.cpp11
-rw-r--r--engines/titanic/game/replacement_ear.h2
-rw-r--r--engines/titanic/game/reserved_table.cpp41
-rw-r--r--engines/titanic/game/reserved_table.h8
-rw-r--r--engines/titanic/game/restaurant_cylinder_holder.cpp108
-rw-r--r--engines/titanic/game/restaurant_cylinder_holder.h13
-rw-r--r--engines/titanic/game/restaurant_phonograph.cpp96
-rw-r--r--engines/titanic/game/restaurant_phonograph.h10
-rw-r--r--engines/titanic/game/sauce_dispensor.cpp112
-rw-r--r--engines/titanic/game/sauce_dispensor.h7
-rw-r--r--engines/titanic/game/search_point.cpp21
-rw-r--r--engines/titanic/game/search_point.h2
-rw-r--r--engines/titanic/game/season_background.cpp103
-rw-r--r--engines/titanic/game/season_background.h13
-rw-r--r--engines/titanic/game/season_barrel.cpp27
-rw-r--r--engines/titanic/game/season_barrel.h9
-rw-r--r--engines/titanic/game/seasonal_adjustment.cpp93
-rw-r--r--engines/titanic/game/seasonal_adjustment.h10
-rw-r--r--engines/titanic/game/service_elevator_window.cpp63
-rw-r--r--engines/titanic/game/service_elevator_window.h4
-rw-r--r--engines/titanic/game/sgt/armchair.cpp50
-rw-r--r--engines/titanic/game/sgt/armchair.h4
-rw-r--r--engines/titanic/game/sgt/basin.cpp41
-rw-r--r--engines/titanic/game/sgt/basin.h4
-rw-r--r--engines/titanic/game/sgt/bedfoot.cpp92
-rw-r--r--engines/titanic/game/sgt/bedfoot.h3
-rw-r--r--engines/titanic/game/sgt/bedhead.cpp133
-rw-r--r--engines/titanic/game/sgt/bedhead.h43
-rw-r--r--engines/titanic/game/sgt/chest_of_drawers.cpp43
-rw-r--r--engines/titanic/game/sgt/chest_of_drawers.h4
-rw-r--r--engines/titanic/game/sgt/desk.cpp46
-rw-r--r--engines/titanic/game/sgt/desk.h4
-rw-r--r--engines/titanic/game/sgt/deskchair.cpp52
-rw-r--r--engines/titanic/game/sgt/deskchair.h5
-rw-r--r--engines/titanic/game/sgt/drawer.cpp41
-rw-r--r--engines/titanic/game/sgt/drawer.h4
-rw-r--r--engines/titanic/game/sgt/sgt_doors.cpp61
-rw-r--r--engines/titanic/game/sgt/sgt_doors.h10
-rw-r--r--engines/titanic/game/sgt/sgt_nav.cpp44
-rw-r--r--engines/titanic/game/sgt/sgt_nav.h3
-rw-r--r--engines/titanic/game/sgt/sgt_navigation.cpp87
-rw-r--r--engines/titanic/game/sgt/sgt_navigation.h12
-rw-r--r--engines/titanic/game/sgt/sgt_restaurant_doors.cpp10
-rw-r--r--engines/titanic/game/sgt/sgt_restaurant_doors.h2
-rw-r--r--engines/titanic/game/sgt/sgt_state_control.cpp47
-rw-r--r--engines/titanic/game/sgt/sgt_state_control.h11
-rw-r--r--engines/titanic/game/sgt/sgt_state_room.cpp79
-rw-r--r--engines/titanic/game/sgt/sgt_state_room.h13
-rw-r--r--engines/titanic/game/sgt/sgt_tv.cpp37
-rw-r--r--engines/titanic/game/sgt/sgt_tv.h4
-rw-r--r--engines/titanic/game/sgt/sgt_upper_doors_sound.cpp6
-rw-r--r--engines/titanic/game/sgt/toilet.cpp44
-rw-r--r--engines/titanic/game/sgt/toilet.h4
-rw-r--r--engines/titanic/game/sgt/vase.cpp40
-rw-r--r--engines/titanic/game/sgt/vase.h4
-rw-r--r--engines/titanic/game/sgt/washstand.cpp38
-rw-r--r--engines/titanic/game/sgt/washstand.h4
-rw-r--r--engines/titanic/game/ship_setting.cpp90
-rw-r--r--engines/titanic/game/ship_setting.h10
-rw-r--r--engines/titanic/game/ship_setting_button.cpp58
-rw-r--r--engines/titanic/game/ship_setting_button.h11
-rw-r--r--engines/titanic/game/show_cell_points.cpp37
-rw-r--r--engines/titanic/game/show_cell_points.h9
-rw-r--r--engines/titanic/game/speech_dispensor.cpp106
-rw-r--r--engines/titanic/game/speech_dispensor.h15
-rw-r--r--engines/titanic/game/starling_puret.cpp44
-rw-r--r--engines/titanic/game/starling_puret.h8
-rw-r--r--engines/titanic/game/stop_phonograph_button.cpp19
-rw-r--r--engines/titanic/game/stop_phonograph_button.h2
-rw-r--r--engines/titanic/game/sub_glass.cpp75
-rw-r--r--engines/titanic/game/sub_glass.h16
-rw-r--r--engines/titanic/game/sub_wrapper.cpp44
-rw-r--r--engines/titanic/game/sub_wrapper.h7
-rw-r--r--engines/titanic/game/sweet_bowl.cpp30
-rw-r--r--engines/titanic/game/sweet_bowl.h4
-rw-r--r--engines/titanic/game/television.cpp29
-rw-r--r--engines/titanic/game/television.h12
-rw-r--r--engines/titanic/game/third_class_canal.cpp9
-rw-r--r--engines/titanic/game/third_class_canal.h2
-rw-r--r--engines/titanic/game/throw_tv_down_well.cpp63
-rw-r--r--engines/titanic/game/throw_tv_down_well.h12
-rw-r--r--engines/titanic/game/titania_still_control.cpp16
-rw-r--r--engines/titanic/game/titania_still_control.h3
-rw-r--r--engines/titanic/game/tow_parrot_nav.cpp17
-rw-r--r--engines/titanic/game/tow_parrot_nav.h2
-rw-r--r--engines/titanic/game/transport/gondolier.cpp17
-rw-r--r--engines/titanic/game/transport/gondolier.h5
-rw-r--r--engines/titanic/game/transport/lift.cpp279
-rw-r--r--engines/titanic/game/transport/lift.h19
-rw-r--r--engines/titanic/game/transport/lift_indicator.cpp196
-rw-r--r--engines/titanic/game/transport/lift_indicator.h15
-rw-r--r--engines/titanic/game/transport/pellerator.cpp324
-rw-r--r--engines/titanic/game/transport/pellerator.h10
-rw-r--r--engines/titanic/game/transport/service_elevator.cpp227
-rw-r--r--engines/titanic/game/transport/service_elevator.h16
-rw-r--r--engines/titanic/game/up_lighter.cpp67
-rw-r--r--engines/titanic/game/up_lighter.h7
-rw-r--r--engines/titanic/game/useless_lever.cpp24
-rw-r--r--engines/titanic/game/useless_lever.h3
-rw-r--r--engines/titanic/game/wheel_button.cpp45
-rw-r--r--engines/titanic/game/wheel_button.h8
-rw-r--r--engines/titanic/game/wheel_hotspot.cpp40
-rw-r--r--engines/titanic/game/wheel_hotspot.h3
-rw-r--r--engines/titanic/game/wheel_spin.cpp24
-rw-r--r--engines/titanic/game/wheel_spin.h7
-rw-r--r--engines/titanic/game_manager.cpp4
-rw-r--r--engines/titanic/game_state.cpp6
-rw-r--r--engines/titanic/game_state.h19
-rw-r--r--engines/titanic/gfx/act_button.cpp10
-rw-r--r--engines/titanic/gfx/act_button.h2
-rw-r--r--engines/titanic/gfx/changes_season_button.cpp11
-rw-r--r--engines/titanic/gfx/changes_season_button.h2
-rw-r--r--engines/titanic/gfx/edit_control.cpp194
-rw-r--r--engines/titanic/gfx/edit_control.h22
-rw-r--r--engines/titanic/gfx/move_object_button.cpp15
-rw-r--r--engines/titanic/gfx/move_object_button.h2
-rw-r--r--engines/titanic/gfx/music_control.cpp31
-rw-r--r--engines/titanic/gfx/music_control.h10
-rw-r--r--engines/titanic/gfx/music_slider_pitch.cpp (renamed from engines/titanic/game/bilge_succubus.cpp)50
-rw-r--r--engines/titanic/gfx/music_slider_pitch.h14
-rw-r--r--engines/titanic/gfx/music_slider_speed.cpp67
-rw-r--r--engines/titanic/gfx/music_slider_speed.h38
-rw-r--r--engines/titanic/gfx/music_switch_inversion.cpp67
-rw-r--r--engines/titanic/gfx/music_switch_inversion.h14
-rw-r--r--engines/titanic/gfx/music_switch_reverse.cpp66
-rw-r--r--engines/titanic/gfx/music_switch_reverse.h38
-rw-r--r--engines/titanic/gfx/music_voice_mute.cpp59
-rw-r--r--engines/titanic/gfx/music_voice_mute.h4
-rw-r--r--engines/titanic/gfx/slider_button.cpp43
-rw-r--r--engines/titanic/gfx/slider_button.h6
-rw-r--r--engines/titanic/gfx/st_button.cpp14
-rw-r--r--engines/titanic/gfx/st_button.h6
-rw-r--r--engines/titanic/gfx/status_change_button.cpp11
-rw-r--r--engines/titanic/gfx/status_change_button.h2
-rw-r--r--engines/titanic/gfx/toggle_button.h2
-rw-r--r--engines/titanic/gfx/toggle_switch.cpp32
-rw-r--r--engines/titanic/gfx/toggle_switch.h8
-rw-r--r--engines/titanic/input_handler.cpp2
-rw-r--r--engines/titanic/main_game_window.cpp30
-rw-r--r--engines/titanic/main_game_window.h9
-rw-r--r--engines/titanic/messages/auto_sound_event.cpp13
-rw-r--r--engines/titanic/messages/auto_sound_event.h2
-rw-r--r--engines/titanic/messages/bilge_dispensor_event.cpp35
-rw-r--r--engines/titanic/messages/bilge_dispensor_event.h5
-rw-r--r--engines/titanic/messages/door_auto_sound_event.cpp18
-rw-r--r--engines/titanic/messages/door_auto_sound_event.h4
-rw-r--r--engines/titanic/messages/messages.h98
-rw-r--r--engines/titanic/messages/mouse_messages.cpp1
-rw-r--r--engines/titanic/messages/mouse_messages.h26
-rw-r--r--engines/titanic/messages/pet_messages.h2
-rw-r--r--engines/titanic/messages/service_elevator_door.cpp10
-rw-r--r--engines/titanic/messages/service_elevator_door.h2
-rw-r--r--engines/titanic/module.mk19
-rw-r--r--engines/titanic/moves/call_pellerator.cpp82
-rw-r--r--engines/titanic/moves/call_pellerator.h (renamed from engines/titanic/game/call_pellerator.h)6
-rw-r--r--engines/titanic/moves/enter_bomb_room.cpp10
-rw-r--r--engines/titanic/moves/enter_bomb_room.h2
-rw-r--r--engines/titanic/moves/enter_bridge.cpp17
-rw-r--r--engines/titanic/moves/enter_bridge.h5
-rw-r--r--engines/titanic/moves/enter_exit_first_class_state.cpp40
-rw-r--r--engines/titanic/moves/enter_exit_first_class_state.h16
-rw-r--r--engines/titanic/moves/enter_exit_mini_lift.cpp35
-rw-r--r--engines/titanic/moves/enter_exit_mini_lift.h7
-rw-r--r--engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp51
-rw-r--r--engines/titanic/moves/enter_exit_sec_class_mini_lift.h13
-rw-r--r--engines/titanic/moves/enter_exit_view.cpp49
-rw-r--r--engines/titanic/moves/enter_exit_view.h14
-rw-r--r--engines/titanic/moves/enter_sec_class_state.cpp73
-rw-r--r--engines/titanic/moves/enter_sec_class_state.h8
-rw-r--r--engines/titanic/moves/exit_arboretum.cpp66
-rw-r--r--engines/titanic/moves/exit_arboretum.h9
-rw-r--r--engines/titanic/moves/exit_bridge.cpp20
-rw-r--r--engines/titanic/moves/exit_bridge.h4
-rw-r--r--engines/titanic/moves/exit_lift.cpp86
-rw-r--r--engines/titanic/moves/exit_lift.h5
-rw-r--r--engines/titanic/moves/exit_pellerator.cpp86
-rw-r--r--engines/titanic/moves/exit_pellerator.h6
-rw-r--r--engines/titanic/moves/exit_state_room.cpp11
-rw-r--r--engines/titanic/moves/exit_state_room.h4
-rw-r--r--engines/titanic/moves/exit_tiania.cpp38
-rw-r--r--engines/titanic/moves/exit_tiania.h6
-rw-r--r--engines/titanic/moves/move_player_in_parrot_room.cpp21
-rw-r--r--engines/titanic/moves/move_player_in_parrot_room.h3
-rw-r--r--engines/titanic/moves/move_player_to.cpp19
-rw-r--r--engines/titanic/moves/move_player_to.h3
-rw-r--r--engines/titanic/moves/move_player_to_from.cpp21
-rw-r--r--engines/titanic/moves/move_player_to_from.h6
-rw-r--r--engines/titanic/moves/multi_move.cpp28
-rw-r--r--engines/titanic/moves/multi_move.h8
-rw-r--r--engines/titanic/moves/pan_from_pel.cpp20
-rw-r--r--engines/titanic/moves/pan_from_pel.h6
-rw-r--r--engines/titanic/moves/restaurant_pan_handler.cpp30
-rw-r--r--engines/titanic/moves/restaurant_pan_handler.h11
-rw-r--r--engines/titanic/moves/restricted_move.cpp44
-rw-r--r--engines/titanic/moves/restricted_move.h5
-rw-r--r--engines/titanic/moves/scraliontis_table.cpp47
-rw-r--r--engines/titanic/moves/scraliontis_table.h12
-rw-r--r--engines/titanic/moves/trip_down_canal.cpp14
-rw-r--r--engines/titanic/moves/trip_down_canal.h2
-rw-r--r--engines/titanic/npcs/barbot.cpp744
-rw-r--r--engines/titanic/npcs/barbot.h153
-rw-r--r--engines/titanic/npcs/bellbot.cpp232
-rw-r--r--engines/titanic/npcs/bellbot.h14
-rw-r--r--engines/titanic/npcs/bilge_succubus.cpp467
-rw-r--r--engines/titanic/npcs/bilge_succubus.h65
-rw-r--r--engines/titanic/npcs/callbot.cpp38
-rw-r--r--engines/titanic/npcs/callbot.h7
-rw-r--r--engines/titanic/npcs/character.cpp10
-rw-r--r--engines/titanic/npcs/character.h4
-rw-r--r--engines/titanic/npcs/doorbot.cpp513
-rw-r--r--engines/titanic/npcs/doorbot.h19
-rw-r--r--engines/titanic/npcs/liftbot.cpp145
-rw-r--r--engines/titanic/npcs/liftbot.h15
-rw-r--r--engines/titanic/npcs/maitre_d.cpp157
-rw-r--r--engines/titanic/npcs/maitre_d.h14
-rw-r--r--engines/titanic/npcs/mobile.h2
-rw-r--r--engines/titanic/npcs/parrot.cpp615
-rw-r--r--engines/titanic/npcs/parrot.h19
-rw-r--r--engines/titanic/npcs/parrot_succubus.cpp152
-rw-r--r--engines/titanic/npcs/parrot_succubus.h (renamed from engines/titanic/game/parrot/parrot_succubus.h)6
-rw-r--r--engines/titanic/npcs/robot_controller.cpp22
-rw-r--r--engines/titanic/npcs/robot_controller.h5
-rw-r--r--engines/titanic/npcs/starlings.cpp25
-rw-r--r--engines/titanic/npcs/starlings.h5
-rw-r--r--engines/titanic/npcs/succubus.cpp789
-rw-r--r--engines/titanic/npcs/succubus.h88
-rw-r--r--engines/titanic/npcs/summon_bots.cpp37
-rw-r--r--engines/titanic/npcs/summon_bots.h3
-rw-r--r--engines/titanic/npcs/titania.cpp218
-rw-r--r--engines/titanic/npcs/titania.h30
-rw-r--r--engines/titanic/npcs/true_talk_npc.cpp41
-rw-r--r--engines/titanic/npcs/true_talk_npc.h26
-rw-r--r--engines/titanic/pet_control/pet_control.cpp27
-rw-r--r--engines/titanic/pet_control/pet_control.h6
-rw-r--r--engines/titanic/pet_control/pet_conversations.h20
-rw-r--r--engines/titanic/pet_control/pet_drag_chev.cpp2
-rw-r--r--engines/titanic/pet_control/pet_inventory_glyphs.cpp12
-rw-r--r--engines/titanic/pet_control/pet_load_save.h10
-rw-r--r--engines/titanic/pet_control/pet_remote_glyphs.cpp6
-rw-r--r--engines/titanic/pet_control/pet_rooms.cpp4
-rw-r--r--engines/titanic/pet_control/pet_rooms_glyphs.cpp8
-rw-r--r--engines/titanic/pet_control/pet_save.cpp20
-rw-r--r--engines/titanic/room_flags.cpp6
-rw-r--r--engines/titanic/sound/auto_sound_player.cpp2
-rw-r--r--engines/titanic/sound/music_player.cpp12
-rw-r--r--engines/titanic/sound/music_player.h4
-rw-r--r--engines/titanic/sound/music_room.cpp48
-rw-r--r--engines/titanic/sound/music_room.h35
-rw-r--r--engines/titanic/sound/music_room_handler.cpp138
-rw-r--r--engines/titanic/sound/music_room_handler.h125
-rw-r--r--engines/titanic/sound/music_wave.cpp22
-rw-r--r--engines/titanic/sound/music_wave.h28
-rw-r--r--engines/titanic/sound/proximity.cpp6
-rw-r--r--engines/titanic/sound/proximity.h10
-rw-r--r--engines/titanic/sound/qmixer.cpp118
-rw-r--r--engines/titanic/sound/qmixer.h35
-rw-r--r--engines/titanic/sound/sound.cpp33
-rw-r--r--engines/titanic/sound/sound.h15
-rw-r--r--engines/titanic/sound/sound_manager.cpp58
-rw-r--r--engines/titanic/sound/sound_manager.h49
-rw-r--r--engines/titanic/sound/titania_speech.cpp8
-rw-r--r--engines/titanic/sound/wave_file.cpp26
-rw-r--r--engines/titanic/sound/wave_file.h14
-rw-r--r--engines/titanic/star_control/star_control_sub13.cpp1
-rw-r--r--engines/titanic/star_control/surface_fader.cpp6
-rw-r--r--engines/titanic/support/avi_surface.cpp45
-rw-r--r--engines/titanic/support/avi_surface.h5
-rw-r--r--engines/titanic/support/credit_text.cpp110
-rw-r--r--engines/titanic/support/credit_text.h6
-rw-r--r--engines/titanic/support/direct_draw.cpp21
-rw-r--r--engines/titanic/support/direct_draw.h12
-rw-r--r--engines/titanic/support/exe_resources.cpp1
-rw-r--r--engines/titanic/support/files_manager.cpp15
-rw-r--r--engines/titanic/support/files_manager.h7
-rw-r--r--engines/titanic/support/font.cpp61
-rw-r--r--engines/titanic/support/font.h6
-rw-r--r--engines/titanic/support/image.cpp2
-rw-r--r--engines/titanic/support/mouse_cursor.cpp10
-rw-r--r--engines/titanic/support/mouse_cursor.h7
-rw-r--r--engines/titanic/support/movie.cpp28
-rw-r--r--engines/titanic/support/movie.h10
-rw-r--r--engines/titanic/support/screen_manager.cpp26
-rw-r--r--engines/titanic/support/screen_manager.h20
-rw-r--r--engines/titanic/support/simple_file.h2
-rw-r--r--engines/titanic/support/string.cpp20
-rw-r--r--engines/titanic/support/string.h11
-rw-r--r--engines/titanic/support/string_parser.cpp97
-rw-r--r--engines/titanic/support/string_parser.h76
-rw-r--r--engines/titanic/support/video_surface.cpp14
-rw-r--r--engines/titanic/titanic.cpp2
-rw-r--r--engines/titanic/true_talk/script_handler.cpp21
-rw-r--r--engines/titanic/true_talk/title_engine.cpp4
-rw-r--r--engines/titanic/true_talk/title_engine.h14
-rw-r--r--engines/titanic/true_talk/true_talk_manager.cpp30
-rw-r--r--engines/titanic/true_talk/true_talk_manager.h5
-rw-r--r--engines/titanic/true_talk/tt_npc_script.cpp19
-rw-r--r--engines/titanic/true_talk/tt_parser.cpp8
-rw-r--r--engines/titanic/true_talk/tt_room_script.cpp2
-rw-r--r--engines/titanic/true_talk/tt_sentence.cpp4
-rw-r--r--engines/titanic/true_talk/tt_string_node.cpp2
-rw-r--r--engines/titanic/true_talk/tt_synonym.cpp2
-rw-r--r--engines/titanic/true_talk/tt_vocab.cpp16
-rw-r--r--engines/titanic/true_talk/tt_word.cpp2
-rw-r--r--engines/toltecs/detection.cpp3
-rw-r--r--engines/toon/detection.cpp3
-rw-r--r--engines/tsage/detection.cpp1
-rw-r--r--engines/tsage/stP1kAlMbin0 -> 24604948 bytes
-rw-r--r--engines/voyeur/detection.cpp3
-rw-r--r--engines/wage/design.cpp4
-rw-r--r--engines/wage/detection.cpp3
-rw-r--r--engines/wage/saveload.cpp26
-rw-r--r--engines/wintermute/base/base_game.cpp4
-rw-r--r--engines/wintermute/base/base_game.h4
-rw-r--r--engines/wintermute/base/base_script_holder.cpp2
-rw-r--r--engines/wintermute/base/scriptables/script.cpp4
-rw-r--r--engines/wintermute/base/scriptables/script_engine.cpp2
-rw-r--r--engines/wintermute/debugger.h6
-rw-r--r--engines/zvision/detection.cpp3
-rw-r--r--graphics/VectorRenderer.h26
-rw-r--r--graphics/VectorRendererSpec.cpp44
-rw-r--r--graphics/VectorRendererSpec.h9
-rw-r--r--graphics/primitives.cpp10
-rw-r--r--graphics/transparent_surface.cpp80
-rw-r--r--graphics/transparent_surface.h10
-rw-r--r--gui/ThemeEngine.cpp185
-rw-r--r--gui/ThemeEngine.h41
-rw-r--r--gui/ThemeParser.cpp66
-rw-r--r--gui/ThemeParser.h6
-rw-r--r--gui/animation/AccelerateInterpolator.h (renamed from engines/titanic/gfx/chev_switch.h)35
-rw-r--r--gui/animation/AlphaAnimation.h53
-rw-r--r--gui/animation/Animation.cpp98
-rw-r--r--gui/animation/Animation.h76
-rw-r--r--gui/animation/DeccelerateInterpolator.h41
-rw-r--r--gui/animation/Drawable.h109
-rw-r--r--gui/animation/Interpolator.h44
-rw-r--r--gui/animation/ParallelAnimation.h72
-rw-r--r--gui/animation/RepeatAnimationWrapper.cpp52
-rw-r--r--gui/animation/RepeatAnimationWrapper.h (renamed from engines/titanic/sound/music_handler.h)47
-rw-r--r--gui/animation/ScaleAnimation.h69
-rw-r--r--gui/animation/SequenceAnimationComposite.cpp72
-rw-r--r--gui/animation/SequenceAnimationComposite.h51
-rw-r--r--gui/animation/WaitForConditionAnimation.h71
-rw-r--r--gui/browser.cpp8
-rw-r--r--gui/credits.h8
-rw-r--r--gui/debugger.cpp4
-rw-r--r--gui/downloaddialog.cpp272
-rw-r--r--gui/downloaddialog.h81
-rw-r--r--gui/editgamedialog.cpp550
-rw-r--r--gui/editgamedialog.h97
-rw-r--r--gui/gui-manager.cpp2
-rw-r--r--gui/launcher.cpp668
-rw-r--r--gui/launcher.h2
-rw-r--r--gui/module.mk11
-rw-r--r--gui/object.h6
-rw-r--r--gui/options.cpp484
-rw-r--r--gui/options.h47
-rw-r--r--gui/remotebrowser.cpp232
-rw-r--r--gui/remotebrowser.h84
-rw-r--r--gui/saveload-dialog.cpp214
-rw-r--r--gui/saveload-dialog.h48
-rw-r--r--gui/saveload.cpp4
-rw-r--r--gui/storagewizarddialog.cpp361
-rw-r--r--gui/storagewizarddialog.h107
-rw-r--r--gui/themes/default.inc439
-rw-r--r--gui/themes/scummclassic.zipbin115643 -> 130414 bytes
-rw-r--r--gui/themes/scummclassic/classic_gfx.stx40
-rw-r--r--gui/themes/scummclassic/classic_layout.stx247
-rw-r--r--gui/themes/scummclassic/classic_layout_lowres.stx252
-rw-r--r--gui/themes/scummmodern.zipbin1491681 -> 1650222 bytes
-rw-r--r--gui/themes/scummmodern/box.bmpbin0 -> 35806 bytes
-rw-r--r--gui/themes/scummmodern/dropbox.bmpbin0 -> 35806 bytes
-rw-r--r--gui/themes/scummmodern/googledrive.bmpbin0 -> 35806 bytes
-rw-r--r--gui/themes/scummmodern/onedrive.bmpbin0 -> 35806 bytes
-rw-r--r--gui/themes/scummmodern/scummmodern_gfx.stx50
-rw-r--r--gui/themes/scummmodern/scummmodern_layout.stx247
-rw-r--r--gui/themes/scummmodern/scummmodern_layout_lowres.stx252
-rwxr-xr-xgui/themes/scummtheme.py2
-rw-r--r--gui/widget.cpp140
-rw-r--r--gui/widget.h24
-rw-r--r--gui/widgets/editable.cpp15
-rw-r--r--gui/widgets/scrollcontainer.cpp22
-rw-r--r--gui/widgets/scrollcontainer.h7
-rw-r--r--image/codecs/msrle.cpp5
-rw-r--r--ports.mk2
-rw-r--r--snapcraft.yaml63
1163 files changed, 59248 insertions, 9447 deletions
diff --git a/.gitignore b/.gitignore
index 270fb560fe..faec15b3de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -199,3 +199,7 @@ dists/msvc*/**
#Ignore bison debug output
*.output
+
+#Ignore Xcode output/project files
+out/
+scummvm.xcodeproj
diff --git a/.travis.yml b/.travis.yml
index 0c88ec8f24..0ec65ee859 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,14 +1,14 @@
language:
- cpp
-sudo: false
+sudo: required
addons:
apt:
packages:
- g++ make
- - libsdl1.2-dev
- - libjpeg62-turbo-dev
+ - libsdl2-dev
+ - libjpeg-turbo8-dev
- libmpeg2-4-dev
- libogg-dev
- libvorbis-dev
@@ -32,6 +32,8 @@ compiler:
os:
- linux
+dist: trusty
+
script:
- ./configure --enable-all-engines
- make -j 2
diff --git a/AUTHORS b/AUTHORS
index 59e0d70fcf..102eedd5cd 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -681,7 +681,7 @@ Special thanks to
*****************
Daniel Balsom - For the original Reinherit (SAGA) code
Sander Buskens - For his work on the initial reversing of Monkey2
- Canadacow - For the original MT-32 emulator
+ Dean Beeler - For the original MT-32 emulator
Kevin Carnes - For Scumm16, the basis of ScummVM's older gfx codecs
Curt Coder - For the original TrollVM (preAGI) code
Patrick Combet - For the original Gobliiins ADL player
@@ -693,12 +693,12 @@ Special thanks to
DOSBox Team - For their awesome OPL2 and OPL3 emulator
Yusuke Kamiyamane - For contributing some GUI icons
Till Kresslein - For design of modern ScummVM GUI
- Jezar - For his freeverb filter implementation
+ Jezar Wakefield - For his freeverb filter implementation
Jim Leiterman - Various info on his FM-TOWNS/Marty SCUMM ports
- lloyd - For deep tech details about C64 Zak & MM
+ Lloyd Rosen - For deep tech details about C64 Zak & MM
Sarien Team - Original AGI engine code
Jimmi Thogersen - For ScummRev, and much obscure code/documentation
- Tristan - For additional work on the original MT-32 emulator
+ Tristan Matthews - For additional work on the original MT-32 emulator
James Woodcock - Soundtrack enhancements
Anton Yartsev - For the original re-implementation of the Z-Vision
engine
diff --git a/NEWS b/NEWS
index fc2c10a4d1..8f1800b912 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,7 @@ For a more comprehensive changelog of the latest experimental code, see:
General:
- Fixed audio corruption in the MS ADPCM decoder.
+ - Switched SDL backend to SDL2 by default. SDL1 is still a fallback.
AGI:
- Added support for Hercules rendering. Both green and amber modes are
@@ -44,15 +45,15 @@ For a more comprehensive changelog of the latest experimental code, see:
interacting with the armor in room 37 (main house, downstairs). This bug is also
present in the original game.
- Fixed auto-saving in the fan-made Cascade Quest.
- - Fixed a bug in Conquests of the Longbow: The Adventures of Robin Hood that caused
- the game to crash while wandering through the Sherwood Forest.
- - Added a detection entry for the ImagiNation Network (INN) demo.
+ - Fixed a game bug in the Conquests of Longbow scripts that could cause crashes in Sherwood Forest.
+ - Added support for the ImagiNation Network (INN) demo.
SCUMM:
- Fixed missing translations in the in-game quit and restart dialogs in Pajama Sam 1.
- Fixed visual glitches in DOTT that occured after loading a savegame with the stereo
in Green Tentacle's room turned on.
- Improved timing and pathfinding in Maniac Mansion (C64 and Apple II versions)
+ - Added support for the Dutch demo of Let's Explore the Airport with Buzzy.
WAGE:
- Closed memory leak.
@@ -61,6 +62,9 @@ For a more comprehensive changelog of the latest experimental code, see:
- Fixed taskbar support on Windows 10 onwards.
- Fixed keymapping for non-QWERTY keyboards.
+ Linux port:
+ - Added basic support for the snap packaging system.
+
1.8.1 (2016-05-25)
New ports:
- Added Nintendo 3DS port.
diff --git a/backends/base-backend.cpp b/backends/base-backend.cpp
index 3e95c3e26a..dfb9e284ce 100644
--- a/backends/base-backend.cpp
+++ b/backends/base-backend.cpp
@@ -39,6 +39,20 @@ void BaseBackend::displayMessageOnOSD(const char *msg) {
dialog.runModal();
}
+void BaseBackend::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {
+ warning("BaseBackend::copyRectToOSD not implemented"); //TODO
+}
+
+void BaseBackend::clearOSD() {
+ warning("BaseBackend::clearOSD not implemented"); //TODO
+ //what should I do? Remove all TimedMessageDialogs?
+}
+
+Graphics::PixelFormat BaseBackend::getOSDFormat() {
+ warning("BaseBackend::getOSDFormat not implemented");
+ return Graphics::PixelFormat();
+}
+
void BaseBackend::initBackend() {
// Init Event manager
#ifndef DISABLE_DEFAULT_EVENT_MANAGER
diff --git a/backends/base-backend.h b/backends/base-backend.h
index 598f682b32..2394edaf38 100644
--- a/backends/base-backend.h
+++ b/backends/base-backend.h
@@ -33,6 +33,9 @@ public:
virtual void initBackend();
virtual void displayMessageOnOSD(const char *msg);
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h);
+ virtual void clearOSD();
+ virtual Graphics::PixelFormat getOSDFormat();
virtual void fillScreen(uint32 col);
};
diff --git a/backends/cloud/box/boxlistdirectorybyidrequest.cpp b/backends/cloud/box/boxlistdirectorybyidrequest.cpp
new file mode 100644
index 0000000000..c013f1eb2a
--- /dev/null
+++ b/backends/cloud/box/boxlistdirectorybyidrequest.cpp
@@ -0,0 +1,195 @@
+/* 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 "backends/cloud/box/boxlistdirectorybyidrequest.h"
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/cloud/box/boxtokenrefresher.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Box {
+
+#define BOX_LIST_DIRECTORY_LIMIT 1000
+#define BOX_FOLDERS_API_LINK "https://api.box.com/2.0/folders/%s/items?offset=%u&limit=%u&fields=%s"
+
+BoxListDirectoryByIdRequest::BoxListDirectoryByIdRequest(BoxStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+BoxListDirectoryByIdRequest::~BoxListDirectoryByIdRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest) _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void BoxListDirectoryByIdRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest) _workingRequest->finish();
+ _files.clear();
+ _ignoreCallback = false;
+
+ makeRequest(0);
+}
+
+void BoxListDirectoryByIdRequest::makeRequest(uint32 offset) {
+ Common::String url = Common::String::format(
+ BOX_FOLDERS_API_LINK,
+ _requestedId.c_str(),
+ offset,
+ BOX_LIST_DIRECTORY_LIMIT,
+ "id,type,name,size,modified_at"
+ );
+
+ Networking::JsonCallback callback = new Common::Callback<BoxListDirectoryByIdRequest, Networking::JsonResponse>(this, &BoxListDirectoryByIdRequest::responseCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<BoxListDirectoryByIdRequest, Networking::ErrorResponse>(this, &BoxListDirectoryByIdRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void BoxListDirectoryByIdRequest::responseCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete response.value;
+ return;
+ }
+
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject responseObject = json->asObject();
+ //debug(9, "%s", json->stringify(true).c_str());
+
+ //TODO: handle error messages passed as JSON
+ /*
+ if (responseObject.contains("error") || responseObject.contains("error_summary")) {
+ warning("Box returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
+ error.failed = true;
+ error.response = json->stringify();
+ finishError(error);
+ delete json;
+ return;
+ }
+ */
+
+ //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
+ if (responseObject.contains("entries")) {
+ if (!responseObject.getVal("entries")->isArray()) {
+ error.response = Common::String::format(
+ "\"entries\" found, but that's not an array!\n%s",
+ responseObject.getVal("entries")->stringify(true).c_str()
+ );
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONArray items = responseObject.getVal("entries")->asArray();
+ for (uint32 i = 0; i < items.size(); ++i) {
+ if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "BoxListDirectoryByIdRequest")) continue;
+
+ Common::JSONObject item = items[i]->asObject();
+
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxListDirectoryByIdRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxListDirectoryByIdRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxListDirectoryByIdRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxListDirectoryByIdRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxListDirectoryByIdRequest")) continue;
+
+ Common::String id = item.getVal("id")->asString();
+ Common::String name = item.getVal("name")->asString();
+ bool isDirectory = (item.getVal("type")->asString() == "folder");
+ uint32 size;
+ if (item.getVal("size")->isString()) {
+ size = item.getVal("size")->asString().asUint64();
+ } else {
+ size = item.getVal("size")->asIntegerNumber();
+ }
+ uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString());
+
+ //as we list directory by id, we can't determine full path for the file, so we leave it empty
+ _files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory));
+ }
+ }
+
+ uint32 received = 0;
+ uint32 totalCount = 0;
+ if (responseObject.contains("total_count") && responseObject.getVal("total_count")->isIntegerNumber())
+ totalCount = responseObject.getVal("total_count")->asIntegerNumber();
+ if (responseObject.contains("offset") && responseObject.getVal("offset")->isIntegerNumber())
+ received = responseObject.getVal("offset")->asIntegerNumber();
+ if (responseObject.contains("limit") && responseObject.getVal("limit")->isIntegerNumber())
+ received += responseObject.getVal("limit")->asIntegerNumber();
+ bool hasMore = (received < totalCount);
+
+ if (hasMore) makeRequest(received);
+ else finishListing(_files);
+
+ delete json;
+}
+
+void BoxListDirectoryByIdRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+ if (error.request) _date = error.request->date();
+ finishError(error);
+}
+
+void BoxListDirectoryByIdRequest::handle() {}
+
+void BoxListDirectoryByIdRequest::restart() { start(); }
+
+Common::String BoxListDirectoryByIdRequest::date() const { return _date; }
+
+void BoxListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback) (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace Box
+} // End of namespace Cloud
diff --git a/backends/cloud/box/boxlistdirectorybyidrequest.h b/backends/cloud/box/boxlistdirectorybyidrequest.h
new file mode 100644
index 0000000000..13f1ba056c
--- /dev/null
+++ b/backends/cloud/box/boxlistdirectorybyidrequest.h
@@ -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.
+*
+*/
+
+#ifndef BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H
+#define BACKENDS_CLOUD_BOX_BOXLISTDIRECTORYBYIDREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Box {
+
+class BoxStorage;
+
+class BoxListDirectoryByIdRequest: public Networking::Request {
+ Common::String _requestedId;
+ BoxStorage *_storage;
+
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::Array<StorageFile> _files;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void makeRequest(uint32 offset);
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ BoxListDirectoryByIdRequest(BoxStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb);
+ virtual ~BoxListDirectoryByIdRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Box
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/box/boxstorage.cpp b/backends/cloud/box/boxstorage.cpp
new file mode 100644
index 0000000000..70864679e7
--- /dev/null
+++ b/backends/cloud/box/boxstorage.cpp
@@ -0,0 +1,345 @@
+/* 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.
+*
+*/
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/cloud/box/boxlistdirectorybyidrequest.h"
+#include "backends/cloud/box/boxtokenrefresher.h"
+#include "backends/cloud/box/boxuploadrequest.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+#ifdef ENABLE_RELEASE
+#include "dists/clouds/cloud_keys.h"
+#endif
+
+namespace Cloud {
+namespace Box {
+
+#define BOX_OAUTH2_TOKEN "https://api.box.com/oauth2/token"
+#define BOX_API_FOLDERS "https://api.box.com/2.0/folders"
+#define BOX_API_FILES_CONTENT "https://api.box.com/2.0/files/%s/content"
+#define BOX_API_USERS_ME "https://api.box.com/2.0/users/me"
+
+char *BoxStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth
+char *BoxStorage::SECRET = nullptr;
+
+void BoxStorage::loadKeyAndSecret() {
+#ifdef ENABLE_RELEASE
+ KEY = RELEASE_BOX_KEY;
+ SECRET = RELEASE_BOX_SECRET;
+#else
+ Common::String k = ConfMan.get("BOX_KEY", ConfMan.kCloudDomain);
+ KEY = new char[k.size() + 1];
+ memcpy(KEY, k.c_str(), k.size());
+ KEY[k.size()] = 0;
+
+ k = ConfMan.get("BOX_SECRET", ConfMan.kCloudDomain);
+ SECRET = new char[k.size() + 1];
+ memcpy(SECRET, k.c_str(), k.size());
+ SECRET[k.size()] = 0;
+#endif
+}
+
+BoxStorage::BoxStorage(Common::String accessToken, Common::String refreshToken):
+ _token(accessToken), _refreshToken(refreshToken) {}
+
+BoxStorage::BoxStorage(Common::String code) {
+ getAccessToken(
+ new Common::Callback<BoxStorage, BoolResponse>(this, &BoxStorage::codeFlowComplete),
+ new Common::Callback<BoxStorage, Networking::ErrorResponse>(this, &BoxStorage::codeFlowFailed),
+ code
+ );
+}
+
+BoxStorage::~BoxStorage() {}
+
+void BoxStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
+ if (!KEY || !SECRET)
+ loadKeyAndSecret();
+ bool codeFlow = (code != "");
+
+ if (!codeFlow && _refreshToken == "") {
+ warning("BoxStorage: no refresh token available to get new access token.");
+ if (callback) (*callback)(BoolResponse(nullptr, false));
+ return;
+ }
+
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::tokenRefreshed, callback);
+ if (errorCallback == nullptr)
+ errorCallback = getErrorPrintingCallback();
+
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, BOX_OAUTH2_TOKEN);
+ if (codeFlow) {
+ request->addPostField("grant_type=authorization_code");
+ request->addPostField("code=" + code);
+ } else {
+ request->addPostField("grant_type=refresh_token");
+ request->addPostField("refresh_token=" + _refreshToken);
+ }
+ request->addPostField("client_id=" + Common::String(KEY));
+ request->addPostField("client_secret=" + Common::String(SECRET));
+ /*
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345");
+ } else {
+ request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code");
+ }
+ */
+ addRequest(request);
+}
+
+void BoxStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("BoxStorage: got NULL instead of JSON");
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete callback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage")) {
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete json;
+ delete callback;
+ return;
+ }
+
+ Common::JSONObject result = json->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "BoxStorage") ||
+ !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "BoxStorage")) {
+ warning("BoxStorage: bad response, no token passed");
+ debug(9, "%s", json->stringify().c_str());
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ } else {
+ _token = result.getVal("access_token")->asString();
+ _refreshToken = result.getVal("refresh_token")->asString();
+ CloudMan.save(); //ask CloudManager to save our new refreshToken
+ if (callback)
+ (*callback)(BoolResponse(nullptr, true));
+ }
+ delete json;
+ delete callback;
+}
+
+void BoxStorage::codeFlowComplete(BoolResponse response) {
+ if (!response.value) {
+ warning("BoxStorage: failed to get access token through code flow");
+ CloudMan.removeStorage(this);
+ return;
+ }
+
+ CloudMan.replaceStorage(this, kStorageBoxId);
+ ConfMan.flushToDisk();
+}
+
+void BoxStorage::codeFlowFailed(Networking::ErrorResponse error) {
+ debug(9, "BoxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+ CloudMan.removeStorage(this);
+}
+
+void BoxStorage::saveConfig(Common::String keyPrefix) {
+ ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
+}
+
+Common::String BoxStorage::name() const {
+ return "Box";
+}
+
+void BoxStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("BoxStorage::infoInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::infoInnerCallback")) {
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+
+ Common::String uid, name, email;
+ uint64 quotaUsed = 0, quotaAllocated = 0;
+
+ // can check that "type": "user"
+ // there is also "max_upload_size", "phone" and "avatar_url"
+
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "id", "BoxStorage::infoInnerCallback"))
+ uid = info.getVal("id")->asString();
+
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "name", "BoxStorage::infoInnerCallback"))
+ name = info.getVal("name")->asString();
+
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "login", "BoxStorage::infoInnerCallback"))
+ email = info.getVal("login")->asString();
+
+ if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "space_amount", "BoxStorage::infoInnerCallback"))
+ quotaAllocated = info.getVal("space_amount")->asIntegerNumber();
+
+ if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "space_used", "BoxStorage::infoInnerCallback"))
+ quotaUsed = info.getVal("space_used")->asIntegerNumber();
+
+ Common::String username = email;
+ if (username == "") username = name;
+ if (username == "") username = uid;
+ CloudMan.setStorageUsername(kStorageBoxId, username);
+
+ if (outerCallback) {
+ (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated)));
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+Networking::Request *BoxStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = getPrintFilesCallback();
+ return addRequest(new BoxListDirectoryByIdRequest(this, id, callback, errorCallback));
+}
+
+void BoxStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("BoxStorage::createDirectoryInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (outerCallback) {
+ if (Networking::CurlJsonRequest::jsonIsObject(json, "BoxStorage::createDirectoryInnerCallback")) {
+ Common::JSONObject info = json->asObject();
+ (*outerCallback)(BoolResponse(nullptr, info.contains("id")));
+ } else {
+ (*outerCallback)(BoolResponse(nullptr, false));
+ }
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+Networking::Request *BoxStorage::createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+
+ Common::String url = BOX_API_FOLDERS;
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, BoolResponse, Networking::JsonResponse>(this, &BoxStorage::createDirectoryInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + accessToken());
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject parentObject;
+ parentObject.setVal("id", new Common::JSONValue(parentId));
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+ jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ return addRequest(request);
+}
+
+Networking::Request *BoxStorage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new BoxUploadRequest(this, remotePath, localPath, callback, errorCallback));
+}
+
+Networking::Request *BoxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ warning("BoxStorage::upload(ReadStream) not implemented");
+ if (errorCallback)
+ (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "BoxStorage::upload(ReadStream) not implemented", -1));
+ delete callback;
+ delete errorCallback;
+ return nullptr;
+}
+
+bool BoxStorage::uploadStreamSupported() {
+ return false;
+}
+
+Networking::Request *BoxStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
+ if (callback) {
+ Common::String url = Common::String::format(BOX_API_FILES_CONTENT, id.c_str());
+ Common::String header = "Authorization: Bearer " + _token;
+ curl_slist *headersList = curl_slist_append(nullptr, header.c_str());
+ Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, "");
+ (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream));
+ }
+ delete callback;
+ delete errorCallback;
+ return nullptr;
+}
+
+Networking::Request *BoxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<BoxStorage, StorageInfoResponse, Networking::JsonResponse>(this, &BoxStorage::infoInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new BoxTokenRefresher(this, innerCallback, errorCallback, BOX_API_USERS_ME);
+ request->addHeader("Authorization: Bearer " + _token);
+ return addRequest(request);
+}
+
+Common::String BoxStorage::savesDirectoryPath() { return "scummvm/saves/"; }
+
+BoxStorage *BoxStorage::loadFromConfig(Common::String keyPrefix) {
+ loadKeyAndSecret();
+
+ if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+ warning("BoxStorage: no access_token found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
+ warning("BoxStorage: no refresh_token found");
+ return nullptr;
+ }
+
+ Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
+ Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
+ return new BoxStorage(accessToken, refreshToken);
+}
+
+Common::String BoxStorage::getRootDirectoryId() {
+ return "0";
+}
+
+} // End of namespace Box
+} // End of namespace Cloud
diff --git a/backends/cloud/box/boxstorage.h b/backends/cloud/box/boxstorage.h
new file mode 100644
index 0000000000..2dd516d894
--- /dev/null
+++ b/backends/cloud/box/boxstorage.h
@@ -0,0 +1,116 @@
+/* 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 BACKENDS_CLOUD_BOX_BOXSTORAGE_H
+#define BACKENDS_CLOUD_BOX_BOXSTORAGE_H
+
+#include "backends/cloud/id/idstorage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Box {
+
+class BoxStorage: public Id::IdStorage {
+ static char *KEY, *SECRET;
+
+ static void loadKeyAndSecret();
+
+ Common::String _token, _refreshToken;
+
+ /** This private constructor is called from loadFromConfig(). */
+ BoxStorage(Common::String token, Common::String refreshToken);
+
+ void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response);
+ void codeFlowComplete(BoolResponse response);
+ void codeFlowFailed(Networking::ErrorResponse error);
+
+ /** Constructs StorageInfo based on JSON response from cloud. */
+ void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
+
+ void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response);
+public:
+ /** This constructor uses OAuth code flow to get tokens. */
+ BoxStorage(Common::String code);
+ virtual ~BoxStorage();
+
+ /**
+ * Storage methods, which are used by CloudManager to save
+ * storage in configuration file.
+ */
+
+ /**
+ * Save storage data using ConfMan.
+ * @param keyPrefix all saved keys must start with this prefix.
+ * @note every Storage must write keyPrefix + "type" key
+ * with common value (e.g. "Dropbox").
+ */
+ virtual void saveConfig(Common::String keyPrefix);
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const;
+
+ /** Public Cloud API comes down there. */
+
+ virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns UploadStatus struct with info about uploaded file. */
+ virtual Networking::Request *upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns whether Storage supports upload(ReadStream). */
+ virtual bool uploadStreamSupported();
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns the StorageInfo struct. */
+ virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns storage's saves directory path with the trailing slash. */
+ virtual Common::String savesDirectoryPath();
+
+ /**
+ * Load token and user id from configs and return BoxStorage for those.
+ * @return pointer to the newly created BoxStorage or 0 if some problem occured.
+ */
+ static BoxStorage *loadFromConfig(Common::String keyPrefix);
+
+ virtual Common::String getRootDirectoryId();
+
+ /**
+ * Gets new access_token. If <code> passed is "", refresh_token is used.
+ * Use "" in order to refresh token and pass a callback, so you could
+ * continue your work when new token is available.
+ */
+ void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
+
+ Common::String accessToken() const { return _token; }
+};
+
+} // End of namespace Box
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/box/boxtokenrefresher.cpp b/backends/cloud/box/boxtokenrefresher.cpp
new file mode 100644
index 0000000000..ca05eef838
--- /dev/null
+++ b/backends/cloud/box/boxtokenrefresher.cpp
@@ -0,0 +1,137 @@
+/* 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.
+*
+*/
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/cloud/box/boxtokenrefresher.h"
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+namespace Cloud {
+namespace Box {
+
+BoxTokenRefresher::BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
+ CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {}
+
+BoxTokenRefresher::~BoxTokenRefresher() {}
+
+void BoxTokenRefresher::tokenRefreshed(Storage::BoolResponse response) {
+ if (!response.value) {
+ //failed to refresh token, notify user with NULL in original callback
+ warning("BoxTokenRefresher: failed to refresh token");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ //update headers: first change header with token, then pass those to request
+ for (uint32 i = 0; i < _headers.size(); ++i) {
+ if (_headers[i].contains("Authorization")) {
+ _headers[i] = "Authorization: Bearer " + _parentStorage->accessToken();
+ }
+ }
+ setHeaders(_headers);
+
+ //successfully received refreshed token, can restart the original request now
+ retry(0);
+}
+
+void BoxTokenRefresher::finishJson(Common::JSONValue *json) {
+ if (!json) {
+ //that's probably not an error (200 OK)
+ CurlJsonRequest::finishJson(nullptr);
+ return;
+ }
+
+ if (jsonIsObject(json, "BoxTokenRefresher")) {
+ Common::JSONObject result = json->asObject();
+ if (result.contains("type") && result.getVal("type")->isString() && result.getVal("type")->asString() == "error") {
+ //new token needed => request token & then retry original request
+ long httpCode = -1;
+ if (_stream) {
+ httpCode = _stream->httpResponseCode();
+ debug(9, "BoxTokenRefresher: code %ld", httpCode);
+ }
+
+ bool irrecoverable = true;
+
+ Common::String code, message;
+ if (jsonContainsString(result, "code", "BoxTokenRefresher")) {
+ code = result.getVal("code")->asString();
+ debug(9, "BoxTokenRefresher: code = %s", code.c_str());
+ }
+
+ if (jsonContainsString(result, "message", "BoxTokenRefresher")) {
+ message = result.getVal("message")->asString();
+ debug(9, "BoxTokenRefresher: message = %s", message.c_str());
+ }
+
+ //TODO: decide when token refreshment will help
+ //for now refreshment is used only when HTTP 401 is passed in finishError()
+ //if (code == "unauthenticated") irrecoverable = false;
+
+ if (irrecoverable) {
+ finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpCode));
+ delete json;
+ return;
+ }
+
+ pause();
+ delete json;
+ _parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed));
+ return;
+ }
+ }
+
+ //notify user of success
+ CurlJsonRequest::finishJson(json);
+}
+
+void BoxTokenRefresher::finishError(Networking::ErrorResponse error) {
+ if (error.httpResponseCode == 401) { // invalid_token
+ pause();
+ _parentStorage->getAccessToken(new Common::Callback<BoxTokenRefresher, Storage::BoolResponse>(this, &BoxTokenRefresher::tokenRefreshed));
+ return;
+ }
+
+ // there are also 400 == invalid_request and 403 == insufficient_scope
+ // but TokenRefresher is there to refresh token when it's invalid only
+
+ Request::finishError(error);
+}
+
+void BoxTokenRefresher::setHeaders(Common::Array<Common::String> &headers) {
+ _headers = headers;
+ curl_slist_free_all(_headersList);
+ _headersList = 0;
+ for (uint32 i = 0; i < headers.size(); ++i)
+ CurlJsonRequest::addHeader(headers[i]);
+}
+
+void BoxTokenRefresher::addHeader(Common::String header) {
+ _headers.push_back(header);
+ CurlJsonRequest::addHeader(header);
+}
+
+} // End of namespace Box
+} // End of namespace Cloud
diff --git a/backends/cloud/box/boxtokenrefresher.h b/backends/cloud/box/boxtokenrefresher.h
new file mode 100644
index 0000000000..c08e8468c3
--- /dev/null
+++ b/backends/cloud/box/boxtokenrefresher.h
@@ -0,0 +1,53 @@
+/* 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 BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H
+#define BACKENDS_CLOUD_BOX_BOXTOKENREFRESHER_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Box {
+
+class BoxStorage;
+
+class BoxTokenRefresher: public Networking::CurlJsonRequest {
+ BoxStorage *_parentStorage;
+ Common::Array<Common::String> _headers;
+
+ void tokenRefreshed(Storage::BoolResponse response);
+
+ virtual void finishJson(Common::JSONValue *json);
+ virtual void finishError(Networking::ErrorResponse error);
+public:
+ BoxTokenRefresher(BoxStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
+ virtual ~BoxTokenRefresher();
+
+ virtual void setHeaders(Common::Array<Common::String> &headers);
+ virtual void addHeader(Common::String header);
+};
+
+} // End of namespace Box
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/box/boxuploadrequest.cpp b/backends/cloud/box/boxuploadrequest.cpp
new file mode 100644
index 0000000000..5084aa5652
--- /dev/null
+++ b/backends/cloud/box/boxuploadrequest.cpp
@@ -0,0 +1,232 @@
+/* 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 "backends/cloud/box/boxuploadrequest.h"
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/cloud/box/boxtokenrefresher.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Box {
+
+#define BOX_API_FILES "https://upload.box.com/api/2.0/files"
+
+BoxUploadRequest::BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _localPath(localPath), _uploadCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+BoxUploadRequest::~BoxUploadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _uploadCallback;
+}
+
+void BoxUploadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _resolvedId = ""; //used to update file contents
+ _parentId = ""; //used to create file within parent directory
+ _ignoreCallback = false;
+
+ resolveId();
+}
+
+void BoxUploadRequest::resolveId() {
+ //check whether such file already exists
+ Storage::UploadCallback innerCallback = new Common::Callback<BoxUploadRequest, Storage::UploadResponse>(this, &BoxUploadRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::idResolveFailedCallback);
+ _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback);
+}
+
+void BoxUploadRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+ _resolvedId = response.value.id();
+ upload();
+}
+
+void BoxUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+
+ //not resolved => error or no such file
+ if (error.response.contains("no such file found in its parent directory")) {
+ //parent's id after the '\n'
+ Common::String parentId = error.response;
+ for (uint32 i = 0; i < parentId.size(); ++i)
+ if (parentId[i] == '\n') {
+ parentId.erase(0, i + 1);
+ break;
+ }
+
+ _parentId = parentId;
+ upload();
+ return;
+ }
+
+ finishError(error);
+}
+
+void BoxUploadRequest::upload() {
+ Common::String name = _savePath;
+ for (uint32 i = name.size(); i > 0; --i) {
+ if (name[i - 1] == '/' || name[i - 1] == '\\') {
+ name.erase(0, i);
+ break;
+ }
+ }
+
+ Common::String url = BOX_API_FILES;
+ if (_resolvedId != "")
+ url += "/" + _resolvedId;
+ url += "/content";
+ Networking::JsonCallback callback = new Common::Callback<BoxUploadRequest, Networking::JsonResponse>(this, &BoxUploadRequest::uploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<BoxUploadRequest, Networking::ErrorResponse>(this, &BoxUploadRequest::notUploadedCallback);
+ Networking::CurlJsonRequest *request = new BoxTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+
+ Common::JSONObject jsonRequestParameters;
+ if (_resolvedId == "") {
+ Common::JSONObject parentObject;
+ parentObject.setVal("id", new Common::JSONValue(_parentId));
+ jsonRequestParameters.setVal("parent", new Common::JSONValue(parentObject));
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+ }
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addFormField("attributes", Common::JSON::stringify(&value));
+ request->addFormFile("file", _localPath);
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void BoxUploadRequest::uploadedCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq) {
+ const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
+ if (stream) {
+ long code = stream->httpResponseCode();
+ error.httpResponseCode = code;
+ }
+ }
+
+ if (error.httpResponseCode != 200 && error.httpResponseCode != 201)
+ warning("BoxUploadRequest: looks like an error (bad HTTP code)");
+
+ //check JSON and show warnings if it's malformed
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject object = json->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsArray(object, "entries", "BoxUploadRequest")) {
+ Common::JSONArray entries = object.getVal("entries")->asArray();
+ if (entries.size() == 0) {
+ warning("BoxUploadRequest: 'entries' found, but it's empty");
+ } else if (!Networking::CurlJsonRequest::jsonIsObject(entries[0], "BoxUploadRequest")) {
+ warning("BoxUploadRequest: 'entries' first item is not an object");
+ } else {
+ Common::JSONObject item = entries[0]->asObject();
+
+ if (Networking::CurlJsonRequest::jsonContainsString(item, "id", "BoxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(item, "name", "BoxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(item, "type", "BoxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(item, "modified_at", "BoxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsStringOrIntegerNumber(item, "size", "BoxUploadRequest")) {
+
+ //finished
+ Common::String id = item.getVal("id")->asString();
+ Common::String name = item.getVal("name")->asString();
+ bool isDirectory = (item.getVal("type")->asString() == "folder");
+ uint32 size;
+ if (item.getVal("size")->isString()) {
+ size = item.getVal("size")->asString().asUint64();
+ } else {
+ size = item.getVal("size")->asIntegerNumber();
+ }
+ uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("modified_at")->asString());
+
+ finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
+ delete json;
+ return;
+ }
+ }
+ }
+
+ //TODO: check errors
+ /*
+ if (object.contains("error")) {
+ warning("Box returned error: %s", json->stringify(true).c_str());
+ delete json;
+ error.response = json->stringify(true);
+ finishError(error);
+ return;
+ }
+ */
+
+ warning("BoxUploadRequest: no file info to return");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+
+ delete json;
+}
+
+void BoxUploadRequest::notUploadedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+ finishError(error);
+}
+
+void BoxUploadRequest::handle() {}
+
+void BoxUploadRequest::restart() { start(); }
+
+void BoxUploadRequest::finishUpload(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace Box
+} // End of namespace Cloud
diff --git a/backends/cloud/box/boxuploadrequest.h b/backends/cloud/box/boxuploadrequest.h
new file mode 100644
index 0000000000..1bc8690210
--- /dev/null
+++ b/backends/cloud/box/boxuploadrequest.h
@@ -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.
+*
+*/
+
+#ifndef BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H
+#define BACKENDS_CLOUD_BOX_BOXUPLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Box {
+class BoxStorage;
+
+class BoxUploadRequest: public Networking::Request {
+ BoxStorage *_storage;
+ Common::String _savePath, _localPath;
+ Storage::UploadCallback _uploadCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _resolvedId, _parentId;
+
+ void start();
+ void resolveId();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void upload();
+ void uploadedCallback(Networking::JsonResponse response);
+ void notUploadedCallback(Networking::ErrorResponse error);
+ void finishUpload(StorageFile status);
+
+public:
+ BoxUploadRequest(BoxStorage *storage, Common::String path, Common::String localPath, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
+ virtual ~BoxUploadRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Box
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/cloudmanager.cpp b/backends/cloud/cloudmanager.cpp
new file mode 100644
index 0000000000..826fc6103d
--- /dev/null
+++ b/backends/cloud/cloudmanager.cpp
@@ -0,0 +1,456 @@
+/* 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 "backends/cloud/cloudmanager.h"
+#include "backends/cloud/box/boxstorage.h"
+#include "backends/cloud/dropbox/dropboxstorage.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/googledrive/googledrivestorage.h"
+#include "common/translation.h"
+#include "common/config-manager.h"
+#include "common/str.h"
+#ifdef USE_SDL_NET
+#include "backends/networking/sdl_net/localwebserver.h"
+#endif
+
+namespace Common {
+
+DECLARE_SINGLETON(Cloud::CloudManager);
+
+}
+
+namespace Cloud {
+
+const char *const CloudManager::kStoragePrefix = "storage_";
+
+CloudManager::CloudManager() : _currentStorageIndex(0), _activeStorage(nullptr) {}
+
+CloudManager::~CloudManager() {
+ delete _activeStorage;
+ freeStorages();
+}
+
+Common::String CloudManager::getStorageConfigName(uint32 index) const {
+ switch (index) {
+ case kStorageNoneId: return "<none>";
+ case kStorageDropboxId: return "Dropbox";
+ case kStorageOneDriveId: return "OneDrive";
+ case kStorageGoogleDriveId: return "GoogleDrive";
+ case kStorageBoxId: return "Box";
+ }
+ assert(false); // Unhandled StorageID value
+ return "";
+}
+
+void CloudManager::loadStorage() {
+ switch (_currentStorageIndex) {
+ case kStorageDropboxId:
+ _activeStorage = Dropbox::DropboxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ break;
+ case kStorageOneDriveId:
+ _activeStorage = OneDrive::OneDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ break;
+ case kStorageGoogleDriveId:
+ _activeStorage = GoogleDrive::GoogleDriveStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ break;
+ case kStorageBoxId:
+ _activeStorage = Box::BoxStorage::loadFromConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ break;
+ default:
+ _activeStorage = nullptr;
+ }
+
+ if (!_activeStorage) {
+ _currentStorageIndex = kStorageNoneId;
+ }
+}
+
+void CloudManager::init() {
+ //init configs structs
+ for (uint32 i = 0; i < kStorageTotal; ++i) {
+ Common::String name = getStorageConfigName(i);
+ StorageConfig config;
+ config.name = _(name);
+ config.username = "";
+ config.lastSyncDate = "";
+ config.usedBytes = 0;
+ if (ConfMan.hasKey(kStoragePrefix + name + "_username", ConfMan.kCloudDomain))
+ config.username = ConfMan.get(kStoragePrefix + name + "_username", ConfMan.kCloudDomain);
+ if (ConfMan.hasKey(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain))
+ config.lastSyncDate = ConfMan.get(kStoragePrefix + name + "_lastSync", ConfMan.kCloudDomain);
+ if (ConfMan.hasKey(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain))
+ config.usedBytes = ConfMan.get(kStoragePrefix + name + "_usedBytes", ConfMan.kCloudDomain).asUint64();
+ _storages.push_back(config);
+ }
+
+ //load an active storage if there is any
+ _currentStorageIndex = kStorageNoneId;
+ if (ConfMan.hasKey("current_storage", ConfMan.kCloudDomain))
+ _currentStorageIndex = ConfMan.getInt("current_storage", ConfMan.kCloudDomain);
+
+ loadStorage();
+}
+
+void CloudManager::save() {
+ for (uint32 i = 0; i < _storages.size(); ++i) {
+ if (i == kStorageNoneId)
+ continue;
+ Common::String name = getStorageConfigName(i);
+ ConfMan.set(kStoragePrefix + name + "_username", _storages[i].username, ConfMan.kCloudDomain);
+ ConfMan.set(kStoragePrefix + name + "_lastSync", _storages[i].lastSyncDate, ConfMan.kCloudDomain);
+ ConfMan.set(kStoragePrefix + name + "_usedBytes", Common::String::format("%lu", _storages[i].usedBytes), ConfMan.kCloudDomain);
+ }
+
+ ConfMan.set("current_storage", Common::String::format("%u", _currentStorageIndex), ConfMan.kCloudDomain);
+ if (_activeStorage)
+ _activeStorage->saveConfig(kStoragePrefix + getStorageConfigName(_currentStorageIndex) + "_");
+ ConfMan.flushToDisk();
+}
+
+void CloudManager::replaceStorage(Storage *storage, uint32 index) {
+ freeStorages();
+ if (!storage)
+ error("CloudManager::replaceStorage: NULL storage passed");
+ if (index >= kStorageTotal)
+ error("CloudManager::replaceStorage: invalid index passed");
+ if (_activeStorage != nullptr && _activeStorage->isWorking()) {
+ warning("CloudManager::replaceStorage: replacing Storage while the other is working");
+ if (_activeStorage->isDownloading())
+ _activeStorage->cancelDownload();
+ if (_activeStorage->isSyncing())
+ _activeStorage->cancelSync();
+ removeStorage(_activeStorage);
+ } else {
+ delete _activeStorage;
+ }
+ _activeStorage = storage;
+ _currentStorageIndex = index;
+ save();
+
+ //do what should be done on first Storage connect
+ if (_activeStorage) {
+ _activeStorage->info(nullptr, nullptr); //automatically calls setStorageUsername()
+ _activeStorage->syncSaves(nullptr, nullptr);
+ }
+}
+
+void CloudManager::removeStorage(Storage *storage) {
+ // can't just delete it as it's mostly likely the one who calls the method
+ // it would be freed on freeStorages() call (on next Storage connect or replace)
+ _storagesToRemove.push_back(storage);
+}
+
+void CloudManager::freeStorages() {
+ for (uint32 i = 0; i < _storagesToRemove.size(); ++i)
+ delete _storagesToRemove[i];
+ _storagesToRemove.clear();
+}
+
+void CloudManager::passNoStorageConnected(Networking::ErrorCallback errorCallback) const {
+ if (errorCallback == nullptr)
+ return;
+ (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "No Storage connected!", -1));
+}
+
+Storage *CloudManager::getCurrentStorage() const {
+ return _activeStorage;
+}
+
+uint32 CloudManager::getStorageIndex() const {
+ return _currentStorageIndex;
+}
+
+Common::StringArray CloudManager::listStorages() const {
+ Common::StringArray result;
+ for (uint32 i = 0; i < _storages.size(); ++i) {
+ result.push_back(_storages[i].name);
+ }
+ return result;
+}
+
+bool CloudManager::switchStorage(uint32 index) {
+ if (index >= _storages.size()) {
+ warning("CloudManager::switchStorage: invalid index passed");
+ return false;
+ }
+
+ Storage *storage = getCurrentStorage();
+ if (storage && storage->isWorking()) {
+ warning("CloudManager::switchStorage: another storage is working now");
+ return false;
+ }
+
+ _currentStorageIndex = index;
+ loadStorage();
+ save();
+ return true;
+}
+
+Common::String CloudManager::getStorageUsername(uint32 index) {
+ if (index >= _storages.size())
+ return "";
+ return _storages[index].username;
+}
+
+uint64 CloudManager::getStorageUsedSpace(uint32 index) {
+ if (index >= _storages.size())
+ return 0;
+ return _storages[index].usedBytes;
+}
+
+Common::String CloudManager::getStorageLastSync(uint32 index) {
+ if (index >= _storages.size())
+ return "";
+ if (index == _currentStorageIndex && isSyncing())
+ return "";
+ return _storages[index].lastSyncDate;
+}
+
+void CloudManager::setStorageUsername(uint32 index, Common::String name) {
+ if (index >= _storages.size())
+ return;
+ _storages[index].username = name;
+ save();
+}
+
+void CloudManager::setStorageUsedSpace(uint32 index, uint64 used) {
+ if (index >= _storages.size())
+ return;
+ _storages[index].usedBytes = used;
+ save();
+}
+
+void CloudManager::setStorageLastSync(uint32 index, Common::String date) {
+ if (index >= _storages.size())
+ return;
+ _storages[index].lastSyncDate = date;
+ save();
+}
+
+void CloudManager::connectStorage(uint32 index, Common::String code) {
+ freeStorages();
+
+ Storage *storage = nullptr;
+ switch (index) {
+ case kStorageDropboxId:
+ storage = new Dropbox::DropboxStorage(code);
+ break;
+ case kStorageOneDriveId:
+ storage = new OneDrive::OneDriveStorage(code);
+ break;
+ case kStorageGoogleDriveId:
+ storage = new GoogleDrive::GoogleDriveStorage(code);
+ break;
+ case kStorageBoxId:
+ storage = new Box::BoxStorage(code);
+ break;
+ }
+ // in these constructors Storages request token using the passed code
+ // when the token is received, they call replaceStorage()
+ // or removeStorage(), if some error occurred
+ // thus, no memory leak happens
+}
+
+Networking::Request *CloudManager::listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ Storage *storage = getCurrentStorage();
+ if (storage) {
+ return storage->listDirectory(path, callback, errorCallback, recursive);
+ } else {
+ passNoStorageConnected(errorCallback);
+ delete callback;
+ delete errorCallback;
+ }
+ return nullptr;
+}
+
+Networking::Request *CloudManager::downloadFolder(Common::String remotePath, Common::String localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ Storage *storage = getCurrentStorage();
+ if (storage) {
+ return storage->downloadFolder(remotePath, localPath, callback, errorCallback, recursive);
+ } else {
+ passNoStorageConnected(errorCallback);
+ delete callback;
+ delete errorCallback;
+ }
+ return nullptr;
+}
+
+Networking::Request *CloudManager::info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ Storage *storage = getCurrentStorage();
+ if (storage) {
+ return storage->info(callback, errorCallback);
+ } else {
+ passNoStorageConnected(errorCallback);
+ delete callback;
+ delete errorCallback;
+ }
+ return nullptr;
+}
+
+Common::String CloudManager::savesDirectoryPath() {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->savesDirectoryPath();
+ return "";
+}
+
+SavesSyncRequest *CloudManager::syncSaves(Storage::BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ Storage *storage = getCurrentStorage();
+ if (storage) {
+ setStorageLastSync(_currentStorageIndex, "???"); //TODO get the date
+ return storage->syncSaves(callback, errorCallback);
+ } else {
+ passNoStorageConnected(errorCallback);
+ delete callback;
+ delete errorCallback;
+ }
+ return nullptr;
+}
+
+bool CloudManager::isWorking() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->isWorking();
+ return false;
+}
+
+bool CloudManager::couldUseLocalServer() {
+#ifdef USE_SDL_NET
+ return Networking::LocalWebserver::getPort() == Networking::LocalWebserver::DEFAULT_SERVER_PORT;
+#else
+ return false;
+#endif
+}
+
+///// SavesSyncRequest-related /////
+
+bool CloudManager::isSyncing() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->isSyncing();
+ return false;
+}
+
+double CloudManager::getSyncDownloadingProgress() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getSyncDownloadingProgress();
+ return 1;
+}
+
+double CloudManager::getSyncProgress() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getSyncProgress();
+ return 1;
+}
+
+Common::Array<Common::String> CloudManager::getSyncingFiles() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getSyncingFiles();
+ return Common::Array<Common::String>();
+}
+
+void CloudManager::cancelSync() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ storage->cancelSync();
+}
+
+void CloudManager::setSyncTarget(GUI::CommandReceiver *target) const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ storage->setSyncTarget(target);
+}
+
+///// DownloadFolderRequest-related /////
+
+bool CloudManager::startDownload(Common::String remotePath, Common::String localPath) const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->startDownload(remotePath, localPath);
+ return false;
+}
+
+void CloudManager::cancelDownload() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ storage->cancelDownload();
+}
+
+void CloudManager::setDownloadTarget(GUI::CommandReceiver *target) const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ storage->setDownloadTarget(target);
+}
+
+bool CloudManager::isDownloading() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->isDownloading();
+ return false;
+}
+
+double CloudManager::getDownloadingProgress() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadingProgress();
+ return 1;
+}
+
+uint64 CloudManager::getDownloadBytesNumber() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadBytesNumber();
+ return 0;
+}
+
+uint64 CloudManager::getDownloadTotalBytesNumber() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadTotalBytesNumber();
+ return 0;
+}
+
+uint64 CloudManager::getDownloadSpeed() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadSpeed();
+ return 0;
+}
+
+Common::String CloudManager::getDownloadRemoteDirectory() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadRemoteDirectory();
+ return "";
+}
+
+Common::String CloudManager::getDownloadLocalDirectory() const {
+ Storage *storage = getCurrentStorage();
+ if (storage)
+ return storage->getDownloadLocalDirectory();
+ return "";
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/cloudmanager.h b/backends/cloud/cloudmanager.h
new file mode 100644
index 0000000000..c504ff39cb
--- /dev/null
+++ b/backends/cloud/cloudmanager.h
@@ -0,0 +1,274 @@
+/* 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 CLOUD_CLOUDMANAGER_H
+#define CLOUD_CLOUDMANAGER_H
+
+#include "backends/cloud/storage.h"
+#include "common/array.h"
+#include "common/singleton.h"
+#include "common/str-array.h"
+
+namespace GUI {
+
+class CommandReceiver;
+
+}
+
+namespace Cloud {
+
+// The actual indexes in CloudManager's array
+enum StorageID {
+ kStorageNoneId = 0,
+ kStorageDropboxId = 1,
+ kStorageOneDriveId = 2,
+ kStorageGoogleDriveId = 3,
+ kStorageBoxId = 4,
+
+ kStorageTotal
+};
+
+class CloudManager : public Common::Singleton<CloudManager> {
+ static const char *const kStoragePrefix;
+
+ struct StorageConfig {
+ Common::String name, username;
+ uint64 usedBytes;
+ Common::String lastSyncDate;
+ };
+
+ Common::Array<StorageConfig> _storages;
+ uint _currentStorageIndex;
+ Storage *_activeStorage;
+ Common::Array<Storage *> _storagesToRemove;
+
+ void loadStorage();
+
+ Common::String getStorageConfigName(uint32 index) const;
+
+ /** Frees memory used by storages which failed to connect. */
+ void freeStorages();
+
+ /** Calls the error callback with a special "no storage connected" message. */
+ void passNoStorageConnected(Networking::ErrorCallback errorCallback) const;
+
+public:
+ CloudManager();
+ virtual ~CloudManager();
+
+ /**
+ * Loads all information from configs and creates current Storage instance.
+ *
+ * @note It's called once on startup in scummvm_main().
+ */
+ void init();
+
+ /**
+ * Saves all information into configuration file.
+ */
+ void save();
+
+ /**
+ * Replace active Storage.
+ * @note this method automatically saves the changes with ConfMan.
+ *
+ * @param storage Cloud::Storage to replace active storage with.
+ * @param index one of Cloud::StorageID enum values to indicate what storage type is replaced.
+ */
+ void replaceStorage(Storage *storage, uint32 index);
+
+ /** Adds storage in the list of storages to remove later. */
+ void removeStorage(Storage *storage);
+
+ /**
+ * Returns active Storage, which could be used to interact
+ * with cloud storage.
+ *
+ * @return active Cloud::Storage or null, if there is no active Storage.
+ */
+ Cloud::Storage *getCurrentStorage() const;
+
+ /**
+ * Return active Storage's index.
+ *
+ * @return active Storage's index.
+ */
+ uint32 getStorageIndex() const;
+
+ /**
+ * Return Storages names as list.
+ *
+ * @return a list of Storages names.
+ */
+ Common::StringArray listStorages() const;
+
+ /**
+ * Changes the storage to the one with given index.
+ *
+ * @param new Storage's index.
+ */
+ bool switchStorage(uint32 index);
+
+ /**
+ * Return username used by Storage.
+ *
+ * @param Storage's index.
+ * @returns username or "" if index is invalid (no such Storage).
+ */
+ Common::String getStorageUsername(uint32 index);
+
+ /**
+ * Return space used by Storage.
+ *
+ * @param Storage's index.
+ * @returns used space in bytes or 0 if index is invalid (no such Storage).
+ */
+ uint64 getStorageUsedSpace(uint32 index);
+
+ /**
+ * Return Storage's last sync date.
+ *
+ * @param Storage's index.
+ * @returns last sync date or "" if index is invalid (no such Storage).
+ It also returns "" if there never was any sync
+ or if storage is syncing right now.
+ */
+ Common::String getStorageLastSync(uint32 index);
+
+ /**
+ * Set Storage's username.
+ * Automatically saves changes to the config.
+ *
+ * @param index Storage's index.
+ * @param name username to set
+ */
+ void setStorageUsername(uint32 index, Common::String name);
+
+ /**
+ * Set Storage's used space field.
+ * Automatically saves changes to the config.
+ *
+ * @param index Storage's index.
+ * @param used value to set
+ */
+ void setStorageUsedSpace(uint32 index, uint64 used);
+
+ /**
+ * Set Storage's last sync date.
+ * Automatically saves changes to the config.
+ *
+ * @param index Storage's index.
+ * @param date date to set
+ */
+ void setStorageLastSync(uint32 index, Common::String date);
+
+ /**
+ * Replace Storage which has given index with a
+ * storage created with given code.
+ *
+ * @param index Storage's index
+ * @param code OAuth2 code received from user
+ */
+ void connectStorage(uint32 index, Common::String code);
+
+ /** Returns ListDirectoryResponse with list of files. */
+ Networking::Request *listDirectory(Common::String path, Storage::ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+
+ /** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */
+ Networking::Request *downloadFolder(Common::String remotePath, Common::String localPath, Storage::FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+
+ /** Return the StorageInfo struct. */
+ Networking::Request *info(Storage::StorageInfoCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns storage's saves directory path with the trailing slash. */
+ Common::String savesDirectoryPath();
+
+ /**
+ * Starts saves syncing process in currently active storage if there is any.
+ */
+ SavesSyncRequest *syncSaves(Cloud::Storage::BoolCallback callback = nullptr, Networking::ErrorCallback errorCallback = nullptr);
+
+ /** Returns whether there are any requests running. */
+ bool isWorking() const;
+
+ /** Returns whether LocalWebserver is available to use for auth. */
+ static bool couldUseLocalServer();
+
+ ///// SavesSyncRequest-related /////
+
+ /** Returns whether there is a SavesSyncRequest running. */
+ bool isSyncing() const;
+
+ /** Returns a number in [0, 1] range which represents current sync downloading progress (1 = complete). */
+ double getSyncDownloadingProgress() const;
+
+ /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */
+ double getSyncProgress() const;
+
+ /** Returns an array of saves names which are not yet synced (thus cannot be used). */
+ Common::Array<Common::String> getSyncingFiles() const;
+
+ /** Cancels running sync. */
+ void cancelSync() const;
+
+ /** Sets SavesSyncRequest's target to given CommandReceiver. */
+ void setSyncTarget(GUI::CommandReceiver *target) const;
+
+ ///// DownloadFolderRequest-related /////
+
+ /** Starts a folder download. */
+ bool startDownload(Common::String remotePath, Common::String localPath) const;
+
+ /** Cancels running download. */
+ void cancelDownload() const;
+
+ /** Sets FolderDownloadRequest's target to given CommandReceiver. */
+ void setDownloadTarget(GUI::CommandReceiver *target) const;
+
+ /** Returns whether there is a FolderDownloadRequest running. */
+ bool isDownloading() const;
+
+ /** Returns a number in [0, 1] range which represents current download progress (1 = complete). */
+ double getDownloadingProgress() const;
+
+ /** Returns a number of bytes that is downloaded in current download progress. */
+ uint64 getDownloadBytesNumber() const;
+
+ /** Returns a total number of bytes to be downloaded in current download progress. */
+ uint64 getDownloadTotalBytesNumber() const;
+
+ /** Returns download speed of current download progress. */
+ uint64 getDownloadSpeed() const;
+
+ /** Returns remote directory path. */
+ Common::String getDownloadRemoteDirectory() const;
+
+ /** Returns local directory path. */
+ Common::String getDownloadLocalDirectory() const;
+};
+
+/** Shortcut for accessing the connection manager. */
+#define CloudMan Cloud::CloudManager::instance()
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/downloadrequest.cpp b/backends/cloud/downloadrequest.cpp
new file mode 100644
index 0000000000..e28670fc7b
--- /dev/null
+++ b/backends/cloud/downloadrequest.cpp
@@ -0,0 +1,138 @@
+/* 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 "backends/cloud/downloadrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "common/textconsole.h"
+
+namespace Cloud {
+
+DownloadRequest::DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, Common::String remoteFileId, Common::DumpFile *dumpFile):
+ Request(nullptr, ecb), _boolCallback(callback), _localFile(dumpFile), _remoteFileId(remoteFileId), _storage(storage),
+ _remoteFileStream(nullptr), _workingRequest(nullptr), _ignoreCallback(false), _buffer(new byte[DOWNLOAD_REQUEST_BUFFER_SIZE]) {
+ start();
+}
+
+DownloadRequest::~DownloadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+ delete _localFile;
+ delete[] _buffer;
+}
+
+void DownloadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _remoteFileStream = nullptr;
+ //TODO: add some way to reopen DumpFile, so DownloadRequest could be restarted
+ _ignoreCallback = false;
+
+ _workingRequest = _storage->streamFileById(
+ _remoteFileId,
+ new Common::Callback<DownloadRequest, Networking::NetworkReadStreamResponse>(this, &DownloadRequest::streamCallback),
+ new Common::Callback<DownloadRequest, Networking::ErrorResponse>(this, &DownloadRequest::streamErrorCallback)
+ );
+}
+
+void DownloadRequest::streamCallback(Networking::NetworkReadStreamResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ _remoteFileStream = (Networking::NetworkReadStream *)response.value;
+}
+
+void DownloadRequest::streamErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void DownloadRequest::handle() {
+ if (!_localFile) {
+ warning("DownloadRequest: no file to write");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ if (!_localFile->isOpen()) {
+ warning("DownloadRequest: failed to open file to write");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ if (!_remoteFileStream) {
+ //waiting for callback
+ return;
+ }
+
+ uint32 readBytes = _remoteFileStream->read(_buffer, DOWNLOAD_REQUEST_BUFFER_SIZE);
+
+ if (readBytes != 0)
+ if (_localFile->write(_buffer, readBytes) != readBytes) {
+ warning("DownloadRequest: unable to write all received bytes into output file");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ if (_remoteFileStream->eos()) {
+ if (_remoteFileStream->httpResponseCode() != 200) {
+ warning("DownloadRequest: HTTP response code is not 200 OK (it's %ld)", _remoteFileStream->httpResponseCode());
+ //TODO: do something about it actually
+ // the problem is file's already downloaded, stream is over
+ // so we can't return error message anymore
+ }
+
+ finishDownload(_remoteFileStream->httpResponseCode() == 200);
+
+ _localFile->close(); //yes, I know it's closed automatically in ~DumpFile()
+ }
+}
+
+void DownloadRequest::restart() {
+ warning("DownloadRequest: can't restart as there are no means to reopen DumpFile");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ //start();
+}
+
+void DownloadRequest::finishDownload(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+void DownloadRequest::finishError(Networking::ErrorResponse error) {
+ if (_localFile)
+ _localFile->close();
+ Request::finishError(error);
+}
+
+double DownloadRequest::getProgress() const {
+ if (_remoteFileStream)
+ return _remoteFileStream->getProgress();
+ return 0;
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/downloadrequest.h b/backends/cloud/downloadrequest.h
new file mode 100644
index 0000000000..7e58098849
--- /dev/null
+++ b/backends/cloud/downloadrequest.h
@@ -0,0 +1,64 @@
+/* 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 BACKENDS_CLOUD_DOWNLOADREQUEST_H
+#define BACKENDS_CLOUD_DOWNLOADREQUEST_H
+
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "backends/cloud/storage.h"
+#include "common/file.h"
+
+namespace Cloud {
+
+#define DOWNLOAD_REQUEST_BUFFER_SIZE 1 * 1024 * 1024
+
+class DownloadRequest: public Networking::Request {
+ Storage::BoolCallback _boolCallback;
+ Common::DumpFile *_localFile;
+ Common::String _remoteFileId;
+ Storage *_storage;
+ Networking::NetworkReadStream *_remoteFileStream;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ byte *_buffer;
+
+ void start();
+ void streamCallback(Networking::NetworkReadStreamResponse response);
+ void streamErrorCallback(Networking::ErrorResponse error);
+ void finishDownload(bool success);
+ virtual void finishError(Networking::ErrorResponse error);
+
+public:
+ DownloadRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb, Common::String remoteFileId, Common::DumpFile *dumpFile);
+ virtual ~DownloadRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp
new file mode 100644
index 0000000000..97090b44f8
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.cpp
@@ -0,0 +1,137 @@
+/* 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 "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_API_CREATE_FOLDER "https://api.dropboxapi.com/2/files/create_folder"
+
+DropboxCreateDirectoryRequest::DropboxCreateDirectoryRequest(Common::String token, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _token(token), _path(path), _boolCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+DropboxCreateDirectoryRequest::~DropboxCreateDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void DropboxCreateDirectoryRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _ignoreCallback = false;
+
+ Networking::JsonCallback innerCallback = new Common::Callback<DropboxCreateDirectoryRequest, Networking::JsonResponse>(this, &DropboxCreateDirectoryRequest::responseCallback);
+ Networking::ErrorCallback errorCallback = new Common::Callback<DropboxCreateDirectoryRequest, Networking::ErrorResponse>(this, &DropboxCreateDirectoryRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_CREATE_FOLDER);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("path", new Common::JSONValue(_path));
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxCreateDirectoryRequest::responseCallback(Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+ if (response.request) _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+ if (info.contains("id")) {
+ finishCreation(true);
+ } else {
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "error_summary", "DropboxCreateDirectoryRequest")) {
+ Common::String summary = info.getVal("error_summary")->asString();
+ if (summary.contains("path") && summary.contains("conflict") && summary.contains("folder")) {
+ // existing directory - not an error for CreateDirectoryRequest
+ finishCreation(false);
+ delete json;
+ return;
+ }
+ }
+ error.response = json->stringify(true);
+ finishError(error);
+ }
+
+ delete json;
+}
+
+void DropboxCreateDirectoryRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void DropboxCreateDirectoryRequest::handle() {}
+
+void DropboxCreateDirectoryRequest::restart() { start(); }
+
+Common::String DropboxCreateDirectoryRequest::date() const { return _date; }
+
+void DropboxCreateDirectoryRequest::finishCreation(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.h
new file mode 100644
index 0000000000..0ef6a22a04
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxcreatedirectoryrequest.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 BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_DROPBOX_DROPBOXCREATEDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxCreateDirectoryRequest: public Networking::Request {
+ Common::String _token;
+ Common::String _path;
+ Storage::BoolCallback _boolCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishCreation(bool success);
+public:
+ DropboxCreateDirectoryRequest(Common::String token, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
+ virtual ~DropboxCreateDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxinforequest.cpp b/backends/cloud/dropbox/dropboxinforequest.cpp
new file mode 100644
index 0000000000..6cdbe3321b
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxinforequest.cpp
@@ -0,0 +1,192 @@
+/* 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 "backends/cloud/dropbox/dropboxinforequest.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_API_GET_CURRENT_ACCOUNT "https://api.dropboxapi.com/2/users/get_current_account"
+#define DROPBOX_API_GET_SPACE_USAGE "https://api.dropboxapi.com/2/users/get_space_usage"
+
+DropboxInfoRequest::DropboxInfoRequest(Common::String token, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _token(token), _infoCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+DropboxInfoRequest::~DropboxInfoRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _infoCallback;
+}
+
+void DropboxInfoRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _ignoreCallback = false;
+
+ Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, Networking::JsonResponse>(this, &DropboxInfoRequest::userResponseCallback);
+ Networking::ErrorCallback errorCallback = new Common::Callback<DropboxInfoRequest, Networking::ErrorResponse>(this, &DropboxInfoRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_GET_CURRENT_ACCOUNT);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+ request->addPostField("null"); //use POST
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxInfoRequest::userResponseCallback(Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ //Dropbox documentation states there are no errors for this API method
+ Common::JSONObject info = json->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "name", "DropboxInfoRequest") &&
+ Networking::CurlJsonRequest::jsonIsObject(info.getVal("name"), "DropboxInfoRequest")) {
+ Common::JSONObject nameInfo = info.getVal("name")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsString(nameInfo, "display_name", "DropboxInfoRequest")) {
+ _name = nameInfo.getVal("display_name")->asString();
+ }
+ }
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "account_id", "DropboxInfoRequest")) {
+ _uid = info.getVal("account_id")->asString();
+ }
+ if (Networking::CurlJsonRequest::jsonContainsString(info, "email", "DropboxInfoRequest")) {
+ _email = info.getVal("email")->asString();
+ }
+ CloudMan.setStorageUsername(kStorageDropboxId, _email);
+ delete json;
+
+ Networking::JsonCallback innerCallback = new Common::Callback<DropboxInfoRequest, Networking::JsonResponse>(this, &DropboxInfoRequest::quotaResponseCallback);
+ Networking::ErrorCallback errorCallback = new Common::Callback<DropboxInfoRequest, Networking::ErrorResponse>(this, &DropboxInfoRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, DROPBOX_API_GET_SPACE_USAGE);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+ request->addPostField("null"); //use POST
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxInfoRequest::quotaResponseCallback(Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ //Dropbox documentation states there are no errors for this API method
+ Common::JSONObject info = json->asObject();
+
+ if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "used", "DropboxInfoRequest")) {
+ error.response = "Passed JSON misses 'used' attribute!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ uint64 used = info.getVal("used")->asIntegerNumber(), allocated = 0;
+
+ if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "allocation", "DropboxInfoRequest") &&
+ Networking::CurlJsonRequest::jsonIsObject(info.getVal("allocation"), "DropboxInfoRequest")) {
+ Common::JSONObject allocation = info.getVal("allocation")->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(allocation, "allocated", "DropboxInfoRequest")) {
+ error.response = "Passed JSON misses 'allocation/allocated' attribute!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ allocated = allocation.getVal("allocated")->asIntegerNumber();
+ }
+
+ finishInfo(StorageInfo(_uid, _name, _email, used, allocated));
+ delete json;
+}
+
+void DropboxInfoRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) return;
+ finishError(error);
+}
+
+void DropboxInfoRequest::handle() {}
+
+void DropboxInfoRequest::restart() { start(); }
+
+void DropboxInfoRequest::finishInfo(StorageInfo info) {
+ Request::finishSuccess();
+ if (_infoCallback)
+ (*_infoCallback)(Storage::StorageInfoResponse(this, info));
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxinforequest.h b/backends/cloud/dropbox/dropboxinforequest.h
new file mode 100644
index 0000000000..68a3e135de
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxinforequest.h
@@ -0,0 +1,56 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H
+#define BACKENDS_CLOUD_DROPBOX_DROPBOXINFOREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxInfoRequest: public Networking::Request {
+ Common::String _token;
+ Common::String _uid, _name, _email;
+ Storage::StorageInfoCallback _infoCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void start();
+ void userResponseCallback(Networking::JsonResponse response);
+ void quotaResponseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishInfo(StorageInfo info);
+public:
+ DropboxInfoRequest(Common::String token, Storage::StorageInfoCallback cb, Networking::ErrorCallback ecb);
+ virtual ~DropboxInfoRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp b/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp
new file mode 100644
index 0000000000..8b00f7c2bf
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxlistdirectoryrequest.cpp
@@ -0,0 +1,222 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 "backends/cloud/dropbox/dropboxlistdirectoryrequest.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_API_LIST_FOLDER "https://api.dropboxapi.com/2/files/list_folder"
+#define DROPBOX_API_LIST_FOLDER_CONTINUE "https://api.dropboxapi.com/2/files/list_folder/continue"
+
+DropboxListDirectoryRequest::DropboxListDirectoryRequest(Common::String token, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
+ Networking::Request(nullptr, ecb), _requestedPath(path), _requestedRecursive(recursive), _listDirectoryCallback(cb),
+ _token(token), _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+DropboxListDirectoryRequest::~DropboxListDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void DropboxListDirectoryRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _files.clear();
+ _ignoreCallback = false;
+
+ Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, Networking::JsonResponse>(this, &DropboxListDirectoryRequest::responseCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, Networking::ErrorResponse>(this, &DropboxListDirectoryRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, DROPBOX_API_LIST_FOLDER);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("path", new Common::JSONValue(_requestedPath));
+ jsonRequestParameters.setVal("recursive", new Common::JSONValue(_requestedRecursive));
+ jsonRequestParameters.setVal("include_media_info", new Common::JSONValue(false));
+ jsonRequestParameters.setVal("include_deleted", new Common::JSONValue(false));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxListDirectoryRequest::responseCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+
+ if (_ignoreCallback) {
+ delete response.value;
+ return;
+ }
+
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject responseObject = json->asObject();
+
+ if (responseObject.contains("error") || responseObject.contains("error_summary")) {
+ if (responseObject.contains("error_summary") && responseObject.getVal("error_summary")->isString()) {
+ warning("Dropbox returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
+ }
+ error.failed = true;
+ error.response = json->stringify();
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
+ if (responseObject.contains("entries")) {
+ if (!responseObject.getVal("entries")->isArray()) {
+ error.response = Common::String::format(
+ "\"entries\" found, but that's not an array!\n%s",
+ responseObject.getVal("entries")->stringify(true).c_str()
+ );
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONArray items = responseObject.getVal("entries")->asArray();
+ for (uint32 i = 0; i < items.size(); ++i) {
+ if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "DropboxListDirectoryRequest"))
+ continue;
+
+ Common::JSONObject item = items[i]->asObject();
+
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "path_lower", "DropboxListDirectoryRequest"))
+ continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, ".tag", "DropboxListDirectoryRequest"))
+ continue;
+
+ Common::String path = item.getVal("path_lower")->asString();
+ bool isDirectory = (item.getVal(".tag")->asString() == "folder");
+ uint32 size = 0, timestamp = 0;
+ if (!isDirectory) {
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "server_modified", "DropboxListDirectoryRequest"))
+ continue;
+ if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "DropboxListDirectoryRequest"))
+ continue;
+
+ size = item.getVal("size")->asIntegerNumber();
+ timestamp = ISO8601::convertToTimestamp(item.getVal("server_modified")->asString());
+ }
+ _files.push_back(StorageFile(path, size, timestamp, isDirectory));
+ }
+ }
+
+ bool hasMore = false;
+ if (responseObject.contains("has_more")) {
+ if (!responseObject.getVal("has_more")->isBool()) {
+ warning("DropboxListDirectoryRequest: \"has_more\" is not a boolean");
+ debug(9, "%s", responseObject.getVal("has_more")->stringify(true).c_str());
+ error.response = "\"has_more\" is not a boolean!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ hasMore = responseObject.getVal("has_more")->asBool();
+ }
+
+ if (hasMore) {
+ if (!Networking::CurlJsonRequest::jsonContainsString(responseObject, "cursor", "DropboxListDirectoryRequest")) {
+ error.response = "\"has_more\" found, but \"cursor\" is not (or it's not a string)!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Networking::JsonCallback callback = new Common::Callback<DropboxListDirectoryRequest, Networking::JsonResponse>(this, &DropboxListDirectoryRequest::responseCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<DropboxListDirectoryRequest, Networking::ErrorResponse>(this, &DropboxListDirectoryRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, DROPBOX_API_LIST_FOLDER_CONTINUE);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("cursor", new Common::JSONValue(responseObject.getVal("cursor")->asString()));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+ } else {
+ finishListing(_files);
+ }
+
+ delete json;
+}
+
+void DropboxListDirectoryRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void DropboxListDirectoryRequest::handle() {}
+
+void DropboxListDirectoryRequest::restart() { start(); }
+
+Common::String DropboxListDirectoryRequest::date() const { return _date; }
+
+void DropboxListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback)
+ (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxlistdirectoryrequest.h b/backends/cloud/dropbox/dropboxlistdirectoryrequest.h
new file mode 100644
index 0000000000..5c0d8dfa21
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxlistdirectoryrequest.h
@@ -0,0 +1,61 @@
+/* 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 BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_DROPBOX_DROPBOXLISTDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxListDirectoryRequest: public Networking::Request {
+ Common::String _requestedPath;
+ bool _requestedRecursive;
+
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::String _token;
+ Common::Array<StorageFile> _files;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ DropboxListDirectoryRequest(Common::String token, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
+ virtual ~DropboxListDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxstorage.cpp b/backends/cloud/dropbox/dropboxstorage.cpp
new file mode 100644
index 0000000000..d12070316b
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxstorage.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.
+*
+*/
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/cloud/dropbox/dropboxstorage.h"
+#include "backends/cloud/dropbox/dropboxcreatedirectoryrequest.h"
+#include "backends/cloud/dropbox/dropboxinforequest.h"
+#include "backends/cloud/dropbox/dropboxlistdirectoryrequest.h"
+#include "backends/cloud/dropbox/dropboxuploadrequest.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+#ifdef ENABLE_RELEASE
+#include "dists/clouds/cloud_keys.h"
+#endif
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_OAUTH2_TOKEN "https://api.dropboxapi.com/oauth2/token"
+#define DROPBOX_API_FILES_DOWNLOAD "https://content.dropboxapi.com/2/files/download"
+
+char *DropboxStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth
+char *DropboxStorage::SECRET = nullptr;
+
+void DropboxStorage::loadKeyAndSecret() {
+#ifdef ENABLE_RELEASE
+ KEY = RELEASE_DROPBOX_KEY;
+ SECRET = RELEASE_DROPBOX_SECRET;
+#else
+ Common::String k = ConfMan.get("DROPBOX_KEY", ConfMan.kCloudDomain);
+ KEY = new char[k.size() + 1];
+ memcpy(KEY, k.c_str(), k.size());
+ KEY[k.size()] = 0;
+
+ k = ConfMan.get("DROPBOX_SECRET", ConfMan.kCloudDomain);
+ SECRET = new char[k.size() + 1];
+ memcpy(SECRET, k.c_str(), k.size());
+ SECRET[k.size()] = 0;
+#endif
+}
+
+DropboxStorage::DropboxStorage(Common::String accessToken, Common::String userId): _token(accessToken), _uid(userId) {}
+
+DropboxStorage::DropboxStorage(Common::String code) {
+ getAccessToken(code);
+}
+
+DropboxStorage::~DropboxStorage() {}
+
+void DropboxStorage::getAccessToken(Common::String code) {
+ if (!KEY || !SECRET)
+ loadKeyAndSecret();
+ Networking::JsonCallback callback = new Common::Callback<DropboxStorage, Networking::JsonResponse>(this, &DropboxStorage::codeFlowComplete);
+ Networking::ErrorCallback errorCallback = new Common::Callback<DropboxStorage, Networking::ErrorResponse>(this, &DropboxStorage::codeFlowFailed);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, errorCallback, DROPBOX_OAUTH2_TOKEN);
+ request->addPostField("code=" + code);
+ request->addPostField("grant_type=authorization_code");
+ request->addPostField("client_id=" + Common::String(KEY));
+ request->addPostField("client_secret=" + Common::String(SECRET));
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2F");
+ } else {
+ request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code");
+ }
+ addRequest(request);
+}
+
+void DropboxStorage::codeFlowComplete(Networking::JsonResponse response) {
+ Common::JSONValue *json = (Common::JSONValue *)response.value;
+ if (json == nullptr) {
+ debug(9, "DropboxStorage::codeFlowComplete: got NULL instead of JSON!");
+ CloudMan.removeStorage(this);
+ return;
+ }
+
+ if (!json->isObject()) {
+ debug(9, "DropboxStorage::codeFlowComplete: Passed JSON is not an object!");
+ CloudMan.removeStorage(this);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject result = json->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "DropboxStorage::codeFlowComplete") ||
+ !Networking::CurlJsonRequest::jsonContainsString(result, "uid", "DropboxStorage::codeFlowComplete")) {
+ warning("DropboxStorage: bad response, no token/uid passed");
+ debug(9, "%s", json->stringify(true).c_str());
+ CloudMan.removeStorage(this);
+ } else {
+ _token = result.getVal("access_token")->asString();
+ _uid = result.getVal("uid")->asString();
+ ConfMan.removeKey("dropbox_code", ConfMan.kCloudDomain);
+ CloudMan.replaceStorage(this, kStorageDropboxId);
+ ConfMan.flushToDisk();
+ }
+
+ delete json;
+}
+
+void DropboxStorage::codeFlowFailed(Networking::ErrorResponse error) {
+ debug(9, "DropboxStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+ CloudMan.removeStorage(this);
+}
+
+void DropboxStorage::saveConfig(Common::String keyPrefix) {
+ ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain);
+}
+
+Common::String DropboxStorage::name() const {
+ return "Dropbox";
+}
+
+Networking::Request *DropboxStorage::listDirectory(Common::String path, ListDirectoryCallback outerCallback, Networking::ErrorCallback errorCallback, bool recursive) {
+ return addRequest(new DropboxListDirectoryRequest(_token, path, outerCallback, errorCallback, recursive));
+}
+
+Networking::Request *DropboxStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new DropboxUploadRequest(_token, path, contents, callback, errorCallback));
+}
+
+Networking::Request *DropboxStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("path", new Common::JSONValue(path));
+ Common::JSONValue value(jsonRequestParameters);
+
+ Networking::CurlRequest *request = new Networking::CurlRequest(nullptr, nullptr, DROPBOX_API_FILES_DOWNLOAD); //TODO: is it OK to pass no callbacks?
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value));
+ request->addHeader("Content-Type: "); //required to be empty (as we do POST, it's usually app/form-url-encoded)
+
+ Networking::NetworkReadStreamResponse response = request->execute();
+ if (callback)
+ (*callback)(response);
+ return response.request; // no leak here, response.request == request
+}
+
+Networking::Request *DropboxStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new DropboxCreateDirectoryRequest(_token, path, callback, errorCallback));
+}
+
+Networking::Request *DropboxStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new DropboxInfoRequest(_token, callback, errorCallback));
+}
+
+Common::String DropboxStorage::savesDirectoryPath() { return "/saves/"; }
+
+DropboxStorage *DropboxStorage::loadFromConfig(Common::String keyPrefix) {
+ loadKeyAndSecret();
+
+ if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+ warning("DropboxStorage: no access_token found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "user_id", ConfMan.kCloudDomain)) {
+ warning("DropboxStorage: no user_id found");
+ return nullptr;
+ }
+
+ Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
+ Common::String userId = ConfMan.get(keyPrefix + "user_id", ConfMan.kCloudDomain);
+
+ return new DropboxStorage(accessToken, userId);
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxstorage.h b/backends/cloud/dropbox/dropboxstorage.h
new file mode 100644
index 0000000000..0a0043abee
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxstorage.h
@@ -0,0 +1,101 @@
+/* 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 BACKENDS_CLOUD_DROPBOX_STORAGE_H
+#define BACKENDS_CLOUD_DROPBOX_STORAGE_H
+
+#include "backends/cloud/storage.h"
+#include "common/callback.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxStorage: public Cloud::Storage {
+ static char *KEY, *SECRET;
+
+ static void loadKeyAndSecret();
+
+ Common::String _token, _uid;
+
+ /** This private constructor is called from loadFromConfig(). */
+ DropboxStorage(Common::String token, Common::String uid);
+
+ void getAccessToken(Common::String code);
+ void codeFlowComplete(Networking::JsonResponse response);
+ void codeFlowFailed(Networking::ErrorResponse error);
+
+public:
+ /** This constructor uses OAuth code flow to get tokens. */
+ DropboxStorage(Common::String code);
+ virtual ~DropboxStorage();
+
+ /**
+ * Storage methods, which are used by CloudManager to save
+ * storage in configuration file.
+ */
+
+ /**
+ * Save storage data using ConfMan.
+ * @param keyPrefix all saved keys must start with this prefix.
+ * @note every Storage must write keyPrefix + "type" key
+ * with common value (e.g. "Dropbox").
+ */
+ virtual void saveConfig(Common::String keyPrefix);
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const;
+
+ /** Public Cloud API comes down there. */
+
+ /** Returns ListDirectoryStatus struct with list of files. */
+ virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+
+ /** Returns UploadStatus struct with info about uploaded file. */
+ virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns the StorageInfo struct. */
+ virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns storage's saves directory path with the trailing slash. */
+ virtual Common::String savesDirectoryPath();
+
+ /**
+ * Load token and user id from configs and return DropboxStorage for those.
+ * @return pointer to the newly created DropboxStorage or 0 if some problem occured.
+ */
+ static DropboxStorage *loadFromConfig(Common::String keyPrefix);
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/dropbox/dropboxuploadrequest.cpp b/backends/cloud/dropbox/dropboxuploadrequest.cpp
new file mode 100644
index 0000000000..2c9dcc4109
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxuploadrequest.cpp
@@ -0,0 +1,204 @@
+/* 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 "backends/cloud/dropbox/dropboxuploadrequest.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+#define DROPBOX_API_FILES_UPLOAD "https://content.dropboxapi.com/2/files/upload"
+#define DROPBOX_API_FILES_UPLOAD_SESSION "https://content.dropboxapi.com/2/files/upload_session/"
+
+DropboxUploadRequest::DropboxUploadRequest(Common::String token, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _token(token), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+DropboxUploadRequest::~DropboxUploadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _contentsStream;
+ delete _uploadCallback;
+}
+
+void DropboxUploadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ if (!_contentsStream) {
+ warning("DropboxUploadRequest: cannot start because stream is invalid");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ if (!_contentsStream->seek(0)) {
+ warning("DropboxUploadRequest: cannot restart because stream couldn't seek(0)");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ _ignoreCallback = false;
+
+ uploadNextPart();
+}
+
+void DropboxUploadRequest::uploadNextPart() {
+ const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
+
+ Common::String url = DROPBOX_API_FILES_UPLOAD_SESSION;
+ Common::JSONObject jsonRequestParameters;
+
+ if (_contentsStream->pos() == 0 || _sessionId == "") {
+ if ((uint32)_contentsStream->size() <= UPLOAD_PER_ONE_REQUEST) {
+ url = DROPBOX_API_FILES_UPLOAD;
+ jsonRequestParameters.setVal("path", new Common::JSONValue(_savePath));
+ jsonRequestParameters.setVal("mode", new Common::JSONValue("overwrite"));
+ jsonRequestParameters.setVal("autorename", new Common::JSONValue(false));
+ jsonRequestParameters.setVal("mute", new Common::JSONValue(false));
+ } else {
+ url += "start";
+ jsonRequestParameters.setVal("close", new Common::JSONValue(false));
+ }
+ } else {
+ if ((uint32)(_contentsStream->size() - _contentsStream->pos()) <= UPLOAD_PER_ONE_REQUEST) {
+ url += "finish";
+ Common::JSONObject jsonCursor, jsonCommit;
+ jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId));
+ jsonCursor.setVal("offset", new Common::JSONValue((long long int)_contentsStream->pos()));
+ jsonCommit.setVal("path", new Common::JSONValue(_savePath));
+ jsonCommit.setVal("mode", new Common::JSONValue("overwrite"));
+ jsonCommit.setVal("autorename", new Common::JSONValue(false));
+ jsonCommit.setVal("mute", new Common::JSONValue(false));
+ jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor));
+ jsonRequestParameters.setVal("commit", new Common::JSONValue(jsonCommit));
+ } else {
+ url += "append_v2";
+ Common::JSONObject jsonCursor;
+ jsonCursor.setVal("session_id", new Common::JSONValue(_sessionId));
+ jsonCursor.setVal("offset", new Common::JSONValue((long long int)_contentsStream->pos()));
+ jsonRequestParameters.setVal("cursor", new Common::JSONValue(jsonCursor));
+ jsonRequestParameters.setVal("close", new Common::JSONValue(false));
+ }
+ }
+
+ Common::JSONValue value(jsonRequestParameters);
+ Networking::JsonCallback callback = new Common::Callback<DropboxUploadRequest, Networking::JsonResponse>(this, &DropboxUploadRequest::partUploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<DropboxUploadRequest, Networking::ErrorResponse>(this, &DropboxUploadRequest::partUploadedErrorCallback);
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(callback, failureCallback, url);
+ request->addHeader("Authorization: Bearer " + _token);
+ request->addHeader("Content-Type: application/octet-stream");
+ request->addHeader("Dropbox-API-Arg: " + Common::JSON::stringify(&value));
+
+ byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
+ uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
+ request->setBuffer(buffer, size);
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void DropboxUploadRequest::partUploadedCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ bool needsFinishRequest = false;
+
+ if (json->isObject()) {
+ Common::JSONObject object = json->asObject();
+
+ //debug(9, "%s", json->stringify(true).c_str());
+
+ if (object.contains("error") || object.contains("error_summary")) {
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "error_summary", "DropboxUploadRequest")) {
+ warning("Dropbox returned error: %s", object.getVal("error_summary")->asString().c_str());
+ }
+ error.response = json->stringify(true);
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "path_lower", "DropboxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "server_modified", "DropboxUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "DropboxUploadRequest")) {
+ //finished
+ Common::String path = object.getVal("path_lower")->asString();
+ uint32 size = object.getVal("size")->asIntegerNumber();
+ uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("server_modified")->asString());
+ finishUpload(StorageFile(path, size, timestamp, false));
+ return;
+ }
+
+ if (_sessionId == "") {
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "session_id", "DropboxUploadRequest"))
+ _sessionId = object.getVal("session_id")->asString();
+ needsFinishRequest = true;
+ }
+ }
+
+ if (!needsFinishRequest && (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1)) {
+ warning("DropboxUploadRequest: no file info to return");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+ } else {
+ uploadNextPart();
+ }
+
+ delete json;
+}
+
+void DropboxUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void DropboxUploadRequest::handle() {}
+
+void DropboxUploadRequest::restart() { start(); }
+
+void DropboxUploadRequest::finishUpload(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
diff --git a/backends/cloud/dropbox/dropboxuploadrequest.h b/backends/cloud/dropbox/dropboxuploadrequest.h
new file mode 100644
index 0000000000..5adf5a6df2
--- /dev/null
+++ b/backends/cloud/dropbox/dropboxuploadrequest.h
@@ -0,0 +1,60 @@
+/* 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 BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H
+#define BACKENDS_CLOUD_DROPBOX_DROPBOXUPLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Dropbox {
+
+class DropboxUploadRequest: public Networking::Request {
+ Common::String _token;
+ Common::String _savePath;
+ Common::SeekableReadStream *_contentsStream;
+ Storage::UploadCallback _uploadCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _sessionId;
+
+ void start();
+ void uploadNextPart();
+ void partUploadedCallback(Networking::JsonResponse response);
+ void partUploadedErrorCallback(Networking::ErrorResponse error);
+ void finishUpload(StorageFile status);
+
+public:
+ DropboxUploadRequest(Common::String token, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
+ virtual ~DropboxUploadRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Dropbox
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/folderdownloadrequest.cpp b/backends/cloud/folderdownloadrequest.cpp
new file mode 100644
index 0000000000..7eeee0c6d6
--- /dev/null
+++ b/backends/cloud/folderdownloadrequest.cpp
@@ -0,0 +1,197 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 "backends/cloud/folderdownloadrequest.h"
+#include "backends/cloud/downloadrequest.h"
+#include "backends/cloud/id/iddownloadrequest.h"
+#include "common/debug.h"
+#include "gui/downloaddialog.h"
+#include <backends/networking/curl/connectionmanager.h>
+
+namespace Cloud {
+
+FolderDownloadRequest::FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, Common::String remoteDirectoryPath, Common::String localDirectoryPath, bool recursive):
+ Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _fileArrayCallback(callback),
+ _remoteDirectoryPath(remoteDirectoryPath), _localDirectoryPath(localDirectoryPath), _recursive(recursive),
+ _workingRequest(nullptr), _ignoreCallback(false), _totalFiles(0) {
+ start();
+}
+
+FolderDownloadRequest::~FolderDownloadRequest() {
+ sendCommand(GUI::kDownloadEndedCmd, 0);
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _fileArrayCallback;
+}
+
+void FolderDownloadRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _currentFile = StorageFile();
+ _pendingFiles.clear();
+ _failedFiles.clear();
+ _ignoreCallback = false;
+ _totalFiles = 0;
+ _downloadedBytes = _totalBytes = _wasDownloadedBytes = _currentDownloadSpeed = 0;
+
+ //list directory first
+ _workingRequest = _storage->listDirectory(
+ _remoteDirectoryPath,
+ new Common::Callback<FolderDownloadRequest, Storage::ListDirectoryResponse>(this, &FolderDownloadRequest::directoryListedCallback),
+ new Common::Callback<FolderDownloadRequest, Networking::ErrorResponse>(this, &FolderDownloadRequest::directoryListedErrorCallback),
+ _recursive
+ );
+}
+
+void FolderDownloadRequest::directoryListedCallback(Storage::ListDirectoryResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ _pendingFiles = response.value;
+
+ // remove all directories
+ // non-empty directories would be created by DumpFile, and empty ones are just ignored
+ for (Common::Array<StorageFile>::iterator i = _pendingFiles.begin(); i != _pendingFiles.end();)
+ if (i->isDirectory())
+ _pendingFiles.erase(i);
+ else {
+ _totalBytes += i->size();
+ ++i;
+ }
+
+ _totalFiles = _pendingFiles.size();
+ downloadNextFile();
+}
+
+void FolderDownloadRequest::directoryListedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void FolderDownloadRequest::fileDownloadedCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (!response.value) _failedFiles.push_back(_currentFile);
+ _downloadedBytes += _currentFile.size();
+ downloadNextFile();
+}
+
+void FolderDownloadRequest::fileDownloadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ fileDownloadedCallback(Storage::BoolResponse(error.request, false));
+}
+
+void FolderDownloadRequest::downloadNextFile() {
+ do {
+ if (_pendingFiles.empty()) {
+ sendCommand(GUI::kDownloadEndedCmd, 0);
+ finishDownload(_failedFiles);
+ return;
+ }
+
+ _currentFile = _pendingFiles.back();
+ _pendingFiles.pop_back();
+ } while (_currentFile.isDirectory()); // directories are actually removed earlier, in the directoryListedCallback()
+
+ sendCommand(GUI::kDownloadProgressCmd, (int)(getProgress() * 100));
+
+ Common::String remotePath = _currentFile.path();
+ Common::String localPath = remotePath;
+ if (_remoteDirectoryPath == "" || remotePath.hasPrefix(_remoteDirectoryPath)) {
+ localPath.erase(0, _remoteDirectoryPath.size());
+ if (_remoteDirectoryPath != "" && (_remoteDirectoryPath.lastChar() != '/' && _remoteDirectoryPath.lastChar() != '\\'))
+ localPath.erase(0, 1);
+ } else {
+ warning("FolderDownloadRequest: Can't process the following paths:");
+ warning("remote directory: %s", _remoteDirectoryPath.c_str());
+ warning("remote file under that directory: %s", remotePath.c_str());
+ }
+ if (_localDirectoryPath != "") {
+ if (_localDirectoryPath.lastChar() == '/' || _localDirectoryPath.lastChar() == '\\')
+ localPath = _localDirectoryPath + localPath;
+ else
+ localPath = _localDirectoryPath + "/" + localPath;
+ }
+ debug(9, "FolderDownloadRequest: %s -> %s", remotePath.c_str(), localPath.c_str());
+ _workingRequest = _storage->downloadById(
+ _currentFile.id(), localPath,
+ new Common::Callback<FolderDownloadRequest, Storage::BoolResponse>(this, &FolderDownloadRequest::fileDownloadedCallback),
+ new Common::Callback<FolderDownloadRequest, Networking::ErrorResponse>(this, &FolderDownloadRequest::fileDownloadedErrorCallback)
+ );
+}
+
+void FolderDownloadRequest::handle() {
+ uint32 microsecondsPassed = Networking::ConnectionManager::getCloudRequestsPeriodInMicroseconds();
+ uint64 currentDownloadedBytes = getDownloadedBytes();
+ uint64 downloadedThisPeriod = currentDownloadedBytes - _wasDownloadedBytes;
+ _currentDownloadSpeed = downloadedThisPeriod * (1000000L / microsecondsPassed);
+ _wasDownloadedBytes = currentDownloadedBytes;
+}
+
+void FolderDownloadRequest::restart() { start(); }
+
+void FolderDownloadRequest::finishDownload(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_fileArrayCallback)
+ (*_fileArrayCallback)(Storage::FileArrayResponse(this, files));
+}
+
+double FolderDownloadRequest::getProgress() const {
+ if (_totalFiles == 0 || _totalBytes == 0)
+ return 0;
+ return (double)getDownloadedBytes() / (double)getTotalBytesToDownload();
+}
+
+uint64 FolderDownloadRequest::getDownloadedBytes() const {
+ if (_totalFiles == 0)
+ return 0;
+
+ double currentFileProgress = 0;
+ DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest);
+ if (downloadRequest != nullptr) {
+ currentFileProgress = downloadRequest->getProgress();
+ } else {
+ Id::IdDownloadRequest *idDownloadRequest = dynamic_cast<Id::IdDownloadRequest *>(_workingRequest);
+ if (idDownloadRequest != nullptr)
+ currentFileProgress = idDownloadRequest->getProgress();
+ }
+
+ return _downloadedBytes + (uint64)(currentFileProgress * _currentFile.size());
+}
+
+uint64 FolderDownloadRequest::getTotalBytesToDownload() const {
+ return _totalBytes;
+}
+
+uint64 FolderDownloadRequest::getDownloadSpeed() const {
+ return _currentDownloadSpeed;
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/folderdownloadrequest.h b/backends/cloud/folderdownloadrequest.h
new file mode 100644
index 0000000000..08fa193e07
--- /dev/null
+++ b/backends/cloud/folderdownloadrequest.h
@@ -0,0 +1,79 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H
+#define BACKENDS_CLOUD_FOLDERDOWNLOADREQUEST_H
+
+#include "backends/networking/curl/request.h"
+#include "backends/cloud/storage.h"
+#include "gui/object.h"
+
+namespace Cloud {
+
+class FolderDownloadRequest: public Networking::Request, public GUI::CommandSender {
+ Storage *_storage;
+ Storage::FileArrayCallback _fileArrayCallback;
+ Common::String _remoteDirectoryPath, _localDirectoryPath;
+ bool _recursive;
+ Common::Array<StorageFile> _pendingFiles, _failedFiles;
+ StorageFile _currentFile;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ uint32 _totalFiles;
+ uint64 _downloadedBytes, _totalBytes, _wasDownloadedBytes, _currentDownloadSpeed;
+
+ void start();
+ void directoryListedCallback(Storage::ListDirectoryResponse response);
+ void directoryListedErrorCallback(Networking::ErrorResponse error);
+ void fileDownloadedCallback(Storage::BoolResponse response);
+ void fileDownloadedErrorCallback(Networking::ErrorResponse error);
+ void downloadNextFile();
+ void finishDownload(Common::Array<StorageFile> &files);
+public:
+ FolderDownloadRequest(Storage *storage, Storage::FileArrayCallback callback, Networking::ErrorCallback ecb, Common::String remoteDirectoryPath, Common::String localDirectoryPath, bool recursive);
+ virtual ~FolderDownloadRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+
+ /** Returns a number of downloaded bytes. */
+ uint64 getDownloadedBytes() const;
+
+ /** Returns a total number of bytes to download. */
+ uint64 getTotalBytesToDownload() const;
+
+ /** Returns average download speed for the last second. */
+ uint64 getDownloadSpeed() const;
+
+ /** Returns remote directory path. */
+ Common::String getRemotePath() const { return _remoteDirectoryPath; }
+
+ /** Returns local directory path. */
+ Common::String getLocalPath() const { return _localDirectoryPath; }
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp
new file mode 100644
index 0000000000..52611126a0
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.cpp
@@ -0,0 +1,163 @@
+/* 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 "backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h"
+#include "backends/cloud/googledrive/googledrivestorage.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+#include "googledrivetokenrefresher.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files?spaces=drive&fields=files%28id,mimeType,modifiedTime,name,size%29,nextPageToken&orderBy=folder,name"
+//files(id,mimeType,modifiedTime,name,size),nextPageToken
+
+GoogleDriveListDirectoryByIdRequest::GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _requestedId(id), _storage(storage), _listDirectoryCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+GoogleDriveListDirectoryByIdRequest::~GoogleDriveListDirectoryByIdRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void GoogleDriveListDirectoryByIdRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _files.clear();
+ _ignoreCallback = false;
+
+ makeRequest("");
+}
+
+void GoogleDriveListDirectoryByIdRequest::makeRequest(Common::String pageToken) {
+ Common::String url = GOOGLEDRIVE_API_FILES;
+ if (pageToken != "")
+ url += "&pageToken=" + pageToken;
+ url += "&q=%27" + _requestedId + "%27+in+parents";
+
+ Networking::JsonCallback callback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, Networking::JsonResponse>(this, &GoogleDriveListDirectoryByIdRequest::responseCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveListDirectoryByIdRequest, Networking::ErrorResponse>(this, &GoogleDriveListDirectoryByIdRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void GoogleDriveListDirectoryByIdRequest::responseCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete response.value;
+ return;
+ }
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json) {
+ Common::JSONObject responseObject = json->asObject();
+
+ ///debug("%s", json->stringify(true).c_str());
+
+ if (responseObject.contains("error") || responseObject.contains("error_summary")) {
+ warning("GoogleDrive returned error: %s", responseObject.getVal("error_summary")->asString().c_str());
+ error.failed = true;
+ error.response = json->stringify();
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ //TODO: check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
+
+ if (responseObject.contains("files") && responseObject.getVal("files")->isArray()) {
+ Common::JSONArray items = responseObject.getVal("files")->asArray();
+ for (uint32 i = 0; i < items.size(); ++i) {
+ Common::JSONObject item = items[i]->asObject();
+ Common::String id = item.getVal("id")->asString();
+ Common::String name = item.getVal("name")->asString();
+ bool isDirectory = (item.getVal("mimeType")->asString() == "application/vnd.google-apps.folder");
+ uint32 size = 0, timestamp = 0;
+ if (item.contains("size") && item.getVal("size")->isString())
+ size = item.getVal("size")->asString().asUint64();
+ if (item.contains("modifiedTime") && item.getVal("modifiedTime")->isString())
+ timestamp = ISO8601::convertToTimestamp(item.getVal("modifiedTime")->asString());
+
+ //as we list directory by id, we can't determine full path for the file, so we leave it empty
+ _files.push_back(StorageFile(id, "", name, size, timestamp, isDirectory));
+ }
+ }
+
+ bool hasMore = (responseObject.contains("nextPageToken"));
+
+ if (hasMore) {
+ Common::String token = responseObject.getVal("nextPageToken")->asString();
+ makeRequest(token);
+ } else {
+ finishListing(_files);
+ }
+ } else {
+ warning("null, not json");
+ error.failed = true;
+ finishError(error);
+ }
+
+ delete json;
+}
+
+void GoogleDriveListDirectoryByIdRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void GoogleDriveListDirectoryByIdRequest::handle() {}
+
+void GoogleDriveListDirectoryByIdRequest::restart() { start(); }
+
+Common::String GoogleDriveListDirectoryByIdRequest::date() const { return _date; }
+
+void GoogleDriveListDirectoryByIdRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback)
+ (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h
new file mode 100644
index 0000000000..e94c6b1f4e
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h
@@ -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.
+*
+*/
+
+#ifndef BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H
+#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVELISTDIRECTORYBYIDREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+class GoogleDriveStorage;
+
+class GoogleDriveListDirectoryByIdRequest: public Networking::Request {
+ Common::String _requestedId;
+ GoogleDriveStorage *_storage;
+
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::Array<StorageFile> _files;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void makeRequest(Common::String pageToken);
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ GoogleDriveListDirectoryByIdRequest(GoogleDriveStorage *storage, Common::String id, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb);
+ virtual ~GoogleDriveListDirectoryByIdRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/googledrive/googledrivestorage.cpp b/backends/cloud/googledrive/googledrivestorage.cpp
new file mode 100644
index 0000000000..1b4b8baf56
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivestorage.cpp
@@ -0,0 +1,348 @@
+/* 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.
+*
+*/
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/cloud/googledrive/googledrivestorage.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/cloud/googledrive/googledrivetokenrefresher.h"
+#include "backends/cloud/googledrive/googledrivelistdirectorybyidrequest.h"
+#include "backends/cloud/googledrive/googledriveuploadrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+#ifdef ENABLE_RELEASE
+#include "dists/clouds/cloud_keys.h"
+#endif
+
+namespace Cloud {
+namespace GoogleDrive {
+
+#define GOOGLEDRIVE_OAUTH2_TOKEN "https://accounts.google.com/o/oauth2/token"
+#define GOOGLEDRIVE_API_FILES_ALT_MEDIA "https://www.googleapis.com/drive/v3/files/%s?alt=media"
+#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/drive/v3/files"
+#define GOOGLEDRIVE_API_ABOUT "https://www.googleapis.com/drive/v3/about?fields=storageQuota,user"
+
+char *GoogleDriveStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth
+char *GoogleDriveStorage::SECRET = nullptr;
+
+void GoogleDriveStorage::loadKeyAndSecret() {
+#ifdef ENABLE_RELEASE
+ KEY = RELEASE_GOOGLE_DRIVE_KEY;
+ SECRET = RELEASE_GOOGLE_DRIVE_SECRET;
+#else
+ Common::String k = ConfMan.get("GOOGLE_DRIVE_KEY", ConfMan.kCloudDomain);
+ KEY = new char[k.size() + 1];
+ memcpy(KEY, k.c_str(), k.size());
+ KEY[k.size()] = 0;
+
+ k = ConfMan.get("GOOGLE_DRIVE_SECRET", ConfMan.kCloudDomain);
+ SECRET = new char[k.size() + 1];
+ memcpy(SECRET, k.c_str(), k.size());
+ SECRET[k.size()] = 0;
+#endif
+}
+
+GoogleDriveStorage::GoogleDriveStorage(Common::String accessToken, Common::String refreshToken):
+ _token(accessToken), _refreshToken(refreshToken) {}
+
+GoogleDriveStorage::GoogleDriveStorage(Common::String code) {
+ getAccessToken(
+ new Common::Callback<GoogleDriveStorage, BoolResponse>(this, &GoogleDriveStorage::codeFlowComplete),
+ new Common::Callback<GoogleDriveStorage, Networking::ErrorResponse>(this, &GoogleDriveStorage::codeFlowFailed),
+ code
+ );
+}
+
+GoogleDriveStorage::~GoogleDriveStorage() {}
+
+void GoogleDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
+ if (!KEY || !SECRET) loadKeyAndSecret();
+ bool codeFlow = (code != "");
+
+ if (!codeFlow && _refreshToken == "") {
+ warning("GoogleDriveStorage: no refresh token available to get new access token.");
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ return;
+ }
+
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, BoolResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::tokenRefreshed, callback);
+ if (errorCallback == nullptr)
+ errorCallback = getErrorPrintingCallback();
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, GOOGLEDRIVE_OAUTH2_TOKEN);
+ if (codeFlow) {
+ request->addPostField("code=" + code);
+ request->addPostField("grant_type=authorization_code");
+ } else {
+ request->addPostField("refresh_token=" + _refreshToken);
+ request->addPostField("grant_type=refresh_token");
+ }
+ request->addPostField("client_id=" + Common::String(KEY));
+ request->addPostField("client_secret=" + Common::String(SECRET));
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345");
+ } else {
+ request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code");
+ }
+ addRequest(request);
+}
+
+void GoogleDriveStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("GoogleDriveStorage: got NULL instead of JSON");
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete callback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage")) {
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete json;
+ delete callback;
+ return;
+ }
+
+ Common::JSONObject result = json->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "GoogleDriveStorage")) {
+ warning("GoogleDriveStorage: bad response, no token passed");
+ debug(9, "%s", json->stringify().c_str());
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ } else {
+ _token = result.getVal("access_token")->asString();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "GoogleDriveStorage"))
+ warning("GoogleDriveStorage: no refresh_token passed");
+ else
+ _refreshToken = result.getVal("refresh_token")->asString();
+ CloudMan.save(); //ask CloudManager to save our new refreshToken
+ if (callback)
+ (*callback)(BoolResponse(nullptr, true));
+ }
+ delete json;
+ delete callback;
+}
+
+void GoogleDriveStorage::codeFlowComplete(BoolResponse response) {
+ if (!response.value) {
+ warning("GoogleDriveStorage: failed to get access token through code flow");
+ CloudMan.removeStorage(this);
+ return;
+ }
+
+ ConfMan.removeKey("googledrive_code", ConfMan.kCloudDomain);
+ CloudMan.replaceStorage(this, kStorageGoogleDriveId);
+ ConfMan.flushToDisk();
+}
+
+void GoogleDriveStorage::codeFlowFailed(Networking::ErrorResponse error) {
+ debug(9, "GoogleDriveStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+ CloudMan.removeStorage(this);
+}
+
+void GoogleDriveStorage::saveConfig(Common::String keyPrefix) {
+ ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
+}
+
+Common::String GoogleDriveStorage::name() const {
+ return "Google Drive";
+}
+
+void GoogleDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("GoogleDriveStorage::infoInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage::infoInnerCallback")) {
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+
+ Common::String uid, name, email;
+ uint64 quotaUsed = 0, quotaAllocated = 0;
+
+ if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "user", "GoogleDriveStorage::infoInnerCallback") &&
+ Networking::CurlJsonRequest::jsonIsObject(info.getVal("user"), "GoogleDriveStorage::infoInnerCallback")) {
+ //"me":true, "kind":"drive#user","photoLink": "",
+ //"displayName":"Alexander Tkachev","emailAddress":"alexander@tkachov.ru","permissionId":""
+ Common::JSONObject user = info.getVal("user")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "permissionId", "GoogleDriveStorage::infoInnerCallback"))
+ uid = user.getVal("permissionId")->asString(); //not sure it's user's id, but who cares anyway?
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "displayName", "GoogleDriveStorage::infoInnerCallback"))
+ name = user.getVal("displayName")->asString();
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "emailAddress", "GoogleDriveStorage::infoInnerCallback"))
+ email = user.getVal("emailAddress")->asString();
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsAttribute(info, "storageQuota", "GoogleDriveStorage::infoInnerCallback") &&
+ Networking::CurlJsonRequest::jsonIsObject(info.getVal("storageQuota"), "GoogleDriveStorage::infoInnerCallback")) {
+ //"usageInDrive":"6332462","limit":"18253611008","usage":"6332462","usageInDriveTrash":"0"
+ Common::JSONObject storageQuota = info.getVal("storageQuota")->asObject();
+
+ if (Networking::CurlJsonRequest::jsonContainsString(storageQuota, "usage", "GoogleDriveStorage::infoInnerCallback")) {
+ Common::String usage = storageQuota.getVal("usage")->asString();
+ quotaUsed = usage.asUint64();
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsString(storageQuota, "limit", "GoogleDriveStorage::infoInnerCallback")) {
+ Common::String limit = storageQuota.getVal("limit")->asString();
+ quotaAllocated = limit.asUint64();
+ }
+ }
+
+ CloudMan.setStorageUsername(kStorageGoogleDriveId, email);
+
+ if (outerCallback) {
+ (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated)));
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+void GoogleDriveStorage::createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("GoogleDriveStorage::createDirectoryInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (outerCallback) {
+ if (Networking::CurlJsonRequest::jsonIsObject(json, "GoogleDriveStorage::createDirectoryInnerCallback")) {
+ Common::JSONObject info = json->asObject();
+ (*outerCallback)(BoolResponse(nullptr, info.contains("id")));
+ } else {
+ (*outerCallback)(BoolResponse(nullptr, false));
+ }
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+Networking::Request *GoogleDriveStorage::listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = new Common::Callback<GoogleDriveStorage, FileArrayResponse>(this, &GoogleDriveStorage::printFiles);
+ return addRequest(new GoogleDriveListDirectoryByIdRequest(this, id, callback, errorCallback));
+}
+
+Networking::Request *GoogleDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new GoogleDriveUploadRequest(this, path, contents, callback, errorCallback));
+}
+
+Networking::Request *GoogleDriveStorage::streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
+ if (callback) {
+ Common::String url = Common::String::format(GOOGLEDRIVE_API_FILES_ALT_MEDIA, ConnMan.urlEncode(id).c_str());
+ Common::String header = "Authorization: Bearer " + _token;
+ curl_slist *headersList = curl_slist_append(nullptr, header.c_str());
+ Networking::NetworkReadStream *stream = new Networking::NetworkReadStream(url.c_str(), headersList, "");
+ (*callback)(Networking::NetworkReadStreamResponse(nullptr, stream));
+ }
+ delete callback;
+ delete errorCallback;
+ return nullptr;
+}
+
+void GoogleDriveStorage::printInfo(StorageInfoResponse response) {
+ debug(9, "\nGoogleDriveStorage: user info:");
+ debug(9, "\tname: %s", response.value.name().c_str());
+ debug(9, "\temail: %s", response.value.email().c_str());
+ debug(9, "\tdisk usage: %lu/%lu", response.value.used(), response.value.available());
+}
+
+Networking::Request *GoogleDriveStorage::createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+
+ Common::String url = GOOGLEDRIVE_API_FILES;
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, BoolResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::createDirectoryInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + accessToken());
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONArray parentsArray;
+ parentsArray.push_back(new Common::JSONValue(parentId));
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("mimeType", new Common::JSONValue("application/vnd.google-apps.folder"));
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+ jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ return addRequest(request);
+}
+
+Networking::Request *GoogleDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!callback)
+ callback = new Common::Callback<GoogleDriveStorage, StorageInfoResponse>(this, &GoogleDriveStorage::printInfo);
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<GoogleDriveStorage, StorageInfoResponse, Networking::JsonResponse>(this, &GoogleDriveStorage::infoInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(this, innerCallback, errorCallback, GOOGLEDRIVE_API_ABOUT);
+ request->addHeader("Authorization: Bearer " + _token);
+ return addRequest(request);
+}
+
+Common::String GoogleDriveStorage::savesDirectoryPath() { return "scummvm/saves/"; }
+
+GoogleDriveStorage *GoogleDriveStorage::loadFromConfig(Common::String keyPrefix) {
+ loadKeyAndSecret();
+
+ if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+ warning("GoogleDriveStorage: no access_token found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
+ warning("GoogleDriveStorage: no refresh_token found");
+ return nullptr;
+ }
+
+ Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
+ Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
+ return new GoogleDriveStorage(accessToken, refreshToken);
+}
+
+Common::String GoogleDriveStorage::getRootDirectoryId() {
+ return "root";
+}
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/googledrive/googledrivestorage.h b/backends/cloud/googledrive/googledrivestorage.h
new file mode 100644
index 0000000000..6a834c44ca
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivestorage.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 BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H
+#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVESTORAGE_H
+
+#include "backends/cloud/id/idstorage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+class GoogleDriveStorage: public Id::IdStorage {
+ static char *KEY, *SECRET;
+
+ static void loadKeyAndSecret();
+
+ Common::String _token, _refreshToken;
+
+ /** This private constructor is called from loadFromConfig(). */
+ GoogleDriveStorage(Common::String token, Common::String refreshToken);
+
+ void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response);
+ void codeFlowComplete(BoolResponse response);
+ void codeFlowFailed(Networking::ErrorResponse error);
+
+ /** Constructs StorageInfo based on JSON response from cloud. */
+ void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
+
+ /** Returns bool based on JSON response from cloud. */
+ void createDirectoryInnerCallback(BoolCallback outerCallback, Networking::JsonResponse json);
+
+ void printInfo(StorageInfoResponse response);
+public:
+ /** This constructor uses OAuth code flow to get tokens. */
+ GoogleDriveStorage(Common::String code);
+ virtual ~GoogleDriveStorage();
+
+ /**
+ * Storage methods, which are used by CloudManager to save
+ * storage in configuration file.
+ */
+
+ /**
+ * Save storage data using ConfMan.
+ * @param keyPrefix all saved keys must start with this prefix.
+ * @note every Storage must write keyPrefix + "type" key
+ * with common value (e.g. "Dropbox").
+ */
+ virtual void saveConfig(Common::String keyPrefix);
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const;
+
+ /** Public Cloud API comes down there. */
+
+ /** Returns Array<StorageFile> - the list of files. */
+ virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns UploadStatus struct with info about uploaded file. */
+ virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns the StorageInfo struct. */
+ virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns storage's saves directory path with the trailing slash. */
+ virtual Common::String savesDirectoryPath();
+
+ /**
+ * Load token and user id from configs and return GoogleDriveStorage for those.
+ * @return pointer to the newly created GoogleDriveStorage or 0 if some problem occured.
+ */
+ static GoogleDriveStorage *loadFromConfig(Common::String keyPrefix);
+
+ virtual Common::String getRootDirectoryId();
+
+ /**
+ * Gets new access_token. If <code> passed is "", refresh_token is used.
+ * Use "" in order to refresh token and pass a callback, so you could
+ * continue your work when new token is available.
+ */
+ void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
+
+ Common::String accessToken() const { return _token; }
+};
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/googledrive/googledrivetokenrefresher.cpp b/backends/cloud/googledrive/googledrivetokenrefresher.cpp
new file mode 100644
index 0000000000..8cc492d6b4
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivetokenrefresher.cpp
@@ -0,0 +1,125 @@
+/* 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.
+*
+*/
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/cloud/googledrive/googledrivetokenrefresher.h"
+#include "backends/cloud/googledrive/googledrivestorage.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+namespace Cloud {
+namespace GoogleDrive {
+
+GoogleDriveTokenRefresher::GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
+ CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {}
+
+GoogleDriveTokenRefresher::~GoogleDriveTokenRefresher() {}
+
+void GoogleDriveTokenRefresher::tokenRefreshed(Storage::BoolResponse response) {
+ if (!response.value) {
+ //failed to refresh token, notify user with NULL in original callback
+ warning("GoogleDriveTokenRefresher: failed to refresh token");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ //update headers: first change header with token, then pass those to request
+ for (uint32 i = 0; i < _headers.size(); ++i) {
+ if (_headers[i].contains("Authorization")) {
+ _headers[i] = "Authorization: Bearer " + _parentStorage->accessToken();
+ }
+ }
+ setHeaders(_headers);
+
+ //successfully received refreshed token, can restart the original request now
+ retry(0);
+}
+
+void GoogleDriveTokenRefresher::finishJson(Common::JSONValue *json) {
+ if (!json) {
+ //that's probably not an error (200 OK)
+ CurlJsonRequest::finishJson(nullptr);
+ return;
+ }
+
+ if (jsonIsObject(json, "GoogleDriveTokenRefresher")) {
+ Common::JSONObject result = json->asObject();
+ long httpResponseCode = -1;
+ if (result.contains("error") && jsonIsObject(result.getVal("error"), "GoogleDriveTokenRefresher")) {
+ //new token needed => request token & then retry original request
+ if (_stream) {
+ httpResponseCode = _stream->httpResponseCode();
+ debug(9, "GoogleDriveTokenRefresher: code = %ld", httpResponseCode);
+ }
+
+ Common::JSONObject error = result.getVal("error")->asObject();
+ bool irrecoverable = true;
+
+ uint32 code = -1;
+ Common::String message;
+ if (jsonContainsIntegerNumber(error, "code", "GoogleDriveTokenRefresher")) {
+ code = error.getVal("code")->asIntegerNumber();
+ debug(9, "GoogleDriveTokenRefresher: code = %u", code);
+ }
+
+ if (jsonContainsString(error, "message", "GoogleDriveTokenRefresher")) {
+ message = error.getVal("message")->asString();
+ debug(9, "GoogleDriveTokenRefresher: message = %s", message.c_str());
+ }
+
+ if (code == 401 || message == "Invalid Credentials")
+ irrecoverable = false;
+
+ if (irrecoverable) {
+ finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode));
+ delete json;
+ return;
+ }
+
+ pause();
+ delete json;
+ _parentStorage->getAccessToken(new Common::Callback<GoogleDriveTokenRefresher, Storage::BoolResponse>(this, &GoogleDriveTokenRefresher::tokenRefreshed));
+ return;
+ }
+ }
+
+ //notify user of success
+ CurlJsonRequest::finishJson(json);
+}
+
+void GoogleDriveTokenRefresher::setHeaders(Common::Array<Common::String> &headers) {
+ _headers = headers;
+ curl_slist_free_all(_headersList);
+ _headersList = 0;
+ for (uint32 i = 0; i < headers.size(); ++i)
+ CurlJsonRequest::addHeader(headers[i]);
+}
+
+void GoogleDriveTokenRefresher::addHeader(Common::String header) {
+ _headers.push_back(header);
+ CurlJsonRequest::addHeader(header);
+}
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/googledrive/googledrivetokenrefresher.h b/backends/cloud/googledrive/googledrivetokenrefresher.h
new file mode 100644
index 0000000000..6cb3e41849
--- /dev/null
+++ b/backends/cloud/googledrive/googledrivetokenrefresher.h
@@ -0,0 +1,52 @@
+/* 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 BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H
+#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVETOKENREFRESHER_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+class GoogleDriveStorage;
+
+class GoogleDriveTokenRefresher: public Networking::CurlJsonRequest {
+ GoogleDriveStorage *_parentStorage;
+ Common::Array<Common::String> _headers;
+
+ void tokenRefreshed(Storage::BoolResponse response);
+
+ virtual void finishJson(Common::JSONValue *json);
+public:
+ GoogleDriveTokenRefresher(GoogleDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
+ virtual ~GoogleDriveTokenRefresher();
+
+ virtual void setHeaders(Common::Array<Common::String> &headers);
+ virtual void addHeader(Common::String header);
+};
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/googledrive/googledriveuploadrequest.cpp b/backends/cloud/googledrive/googledriveuploadrequest.cpp
new file mode 100644
index 0000000000..5f61dcd2a8
--- /dev/null
+++ b/backends/cloud/googledrive/googledriveuploadrequest.cpp
@@ -0,0 +1,353 @@
+/* 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 "backends/cloud/googledrive/googledriveuploadrequest.h"
+#include "backends/cloud/googledrive/googledrivestorage.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+#include "googledrivetokenrefresher.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+
+#define GOOGLEDRIVE_API_FILES "https://www.googleapis.com/upload/drive/v3/files"
+
+GoogleDriveUploadRequest::GoogleDriveUploadRequest(GoogleDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+GoogleDriveUploadRequest::~GoogleDriveUploadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _contentsStream;
+ delete _uploadCallback;
+}
+
+void GoogleDriveUploadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ if (_contentsStream == nullptr || !_contentsStream->seek(0)) {
+ warning("GoogleDriveUploadRequest: cannot restart because stream couldn't seek(0)");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ _resolvedId = ""; //used to update file contents
+ _parentId = ""; //used to create file within parent directory
+ _serverReceivedBytes = 0;
+ _ignoreCallback = false;
+
+ resolveId();
+}
+
+void GoogleDriveUploadRequest::resolveId() {
+ //check whether such file already exists
+ Storage::UploadCallback innerCallback = new Common::Callback<GoogleDriveUploadRequest, Storage::UploadResponse>(this, &GoogleDriveUploadRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::idResolveFailedCallback);
+ _workingRequest = _storage->resolveFileId(_savePath, innerCallback, innerErrorCallback);
+}
+
+void GoogleDriveUploadRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ _resolvedId = response.value.id();
+ startUpload();
+}
+
+void GoogleDriveUploadRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //not resolved => error or no such file
+ if (error.response.contains("no such file found in its parent directory")) {
+ //parent's id after the '\n'
+ Common::String parentId = error.response;
+ for (uint32 i = 0; i < parentId.size(); ++i)
+ if (parentId[i] == '\n') {
+ parentId.erase(0, i + 1);
+ break;
+ }
+
+ _parentId = parentId;
+ startUpload();
+ return;
+ }
+
+ finishError(error);
+}
+
+void GoogleDriveUploadRequest::startUpload() {
+ Common::String name = _savePath;
+ for (uint32 i = name.size(); i > 0; --i) {
+ if (name[i - 1] == '/' || name[i - 1] == '\\') {
+ name.erase(0, i);
+ break;
+ }
+ }
+
+ Common::String url = GOOGLEDRIVE_API_FILES;
+ if (_resolvedId != "")
+ url += "/" + ConnMan.urlEncode(_resolvedId);
+ url += "?uploadType=resumable&fields=id,mimeType,modifiedTime,name,size";
+ Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, Networking::JsonResponse>(this, &GoogleDriveUploadRequest::startUploadCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::startUploadErrorCallback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->addHeader("Content-Type: application/json");
+ if (_resolvedId != "")
+ request->usePatch();
+
+ Common::JSONObject jsonRequestParameters;
+ if (_resolvedId != "") {
+ jsonRequestParameters.setVal("id", new Common::JSONValue(_resolvedId));
+ } else {
+ Common::JSONArray parentsArray;
+ parentsArray.push_back(new Common::JSONValue(_parentId));
+ jsonRequestParameters.setVal("parents", new Common::JSONValue(parentsArray));
+ }
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void GoogleDriveUploadRequest::startUploadCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq) {
+ const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
+ if (stream) {
+ long code = stream->httpResponseCode();
+ Common::String headers = stream->responseHeaders();
+ if (code == 200) {
+ const char *cstr = headers.c_str();
+ const char *position = strstr(cstr, "Location: ");
+
+ if (position) {
+ Common::String result = "";
+ char c;
+ for (const char *i = position + 10; c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ _uploadUrl = result;
+ uploadNextPart();
+ return;
+ }
+ }
+
+ error.httpResponseCode = code;
+ }
+ }
+
+ Common::JSONValue *json = response.value;
+ delete json;
+
+ finishError(error);
+}
+
+void GoogleDriveUploadRequest::startUploadErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void GoogleDriveUploadRequest::uploadNextPart() {
+ const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
+ Common::String url = _uploadUrl;
+
+ Networking::JsonCallback callback = new Common::Callback<GoogleDriveUploadRequest, Networking::JsonResponse>(this, &GoogleDriveUploadRequest::partUploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<GoogleDriveUploadRequest, Networking::ErrorResponse>(this, &GoogleDriveUploadRequest::partUploadedErrorCallback);
+ Networking::CurlJsonRequest *request = new GoogleDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->usePut();
+
+ uint32 oldPos = _contentsStream->pos();
+ if (oldPos != _serverReceivedBytes) {
+ if (!_contentsStream->seek(_serverReceivedBytes)) {
+ warning("GoogleDriveUploadRequest: cannot upload because stream couldn't seek(%lu)", _serverReceivedBytes);
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ oldPos = _serverReceivedBytes;
+ }
+
+ byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
+ uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
+ if (size != 0)
+ request->setBuffer(buffer, size);
+
+ if (_uploadUrl != "") {
+ if (_contentsStream->pos() == 0)
+ request->addHeader(Common::String::format("Content-Length: 0"));
+ else
+ request->addHeader(Common::String::format("Content-Range: bytes %u-%u/%u", oldPos, _contentsStream->pos() - 1, _contentsStream->size()));
+ }
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+bool GoogleDriveUploadRequest::handleHttp308(const Networking::NetworkReadStream *stream) {
+ //308 Resume Incomplete, with Range: X-Y header
+ if (!stream)
+ return false;
+ if (stream->httpResponseCode() != 308)
+ return false; //seriously
+
+ Common::String headers = stream->responseHeaders();
+ const char *cstr = headers.c_str();
+ for (int rangeTry = 0; rangeTry < 2; ++rangeTry) {
+ const char *needle = (rangeTry == 0 ? "Range: 0-" : "Range: bytes=0-");
+ uint32 needleLength = (rangeTry == 0 ? 9 : 15);
+
+ const char *position = strstr(cstr, needle); //if it lost the first part, I refuse to talk with it
+
+ if (position) {
+ Common::String result = "";
+ char c;
+ for (const char *i = position + needleLength; c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ _serverReceivedBytes = result.asUint64() + 1;
+ uploadNextPart();
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void GoogleDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq) {
+ const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
+ if (stream) {
+ long code = stream->httpResponseCode();
+ error.httpResponseCode = code;
+ if (code == 308 && handleHttp308(stream)) {
+ delete (Common::JSONValue *)response.value;
+ return;
+ }
+ }
+ }
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (json->isObject()) {
+ Common::JSONObject object = json->asObject();
+
+ if (object.contains("error")) {
+ warning("GoogleDrive returned error: %s", json->stringify(true).c_str());
+ error.response = json->stringify(true);
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "GoogleDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "name", "GoogleDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "mimeType", "GoogleDriveUploadRequest")) {
+ //finished
+ Common::String id = object.getVal("id")->asString();
+ Common::String name = object.getVal("name")->asString();
+ bool isDirectory = (object.getVal("mimeType")->asString() == "application/vnd.google-apps.folder");
+ uint32 size = 0, timestamp = 0;
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "size", "GoogleDriveUploadRequest", true))
+ size = object.getVal("size")->asString().asUint64();
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "modifiedTime", "GoogleDriveUploadRequest", true))
+ timestamp = ISO8601::convertToTimestamp(object.getVal("modifiedTime")->asString());
+
+ finishUpload(StorageFile(id, _savePath, name, size, timestamp, isDirectory));
+ return;
+ }
+ }
+
+ if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) {
+ warning("GoogleDriveUploadRequest: no file info to return");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+ } else {
+ uploadNextPart();
+ }
+
+ delete json;
+}
+
+void GoogleDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)error.request;
+ if (rq) {
+ const Networking::NetworkReadStream *stream = rq->getNetworkReadStream();
+ if (stream) {
+ long code = stream->httpResponseCode();
+ if (code == 308 && handleHttp308(stream)) {
+ return;
+ }
+ }
+ }
+
+ finishError(error);
+}
+
+void GoogleDriveUploadRequest::handle() {}
+
+void GoogleDriveUploadRequest::restart() { start(); }
+
+void GoogleDriveUploadRequest::finishUpload(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/googledrive/googledriveuploadrequest.h b/backends/cloud/googledrive/googledriveuploadrequest.h
new file mode 100644
index 0000000000..73acab5bbd
--- /dev/null
+++ b/backends/cloud/googledrive/googledriveuploadrequest.h
@@ -0,0 +1,70 @@
+/* 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 BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H
+#define BACKENDS_CLOUD_GOOGLEDRIVE_GOOGLEDRIVEUPLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace GoogleDrive {
+class GoogleDriveStorage;
+
+class GoogleDriveUploadRequest: public Networking::Request {
+ GoogleDriveStorage *_storage;
+ Common::String _savePath;
+ Common::SeekableReadStream *_contentsStream;
+ Storage::UploadCallback _uploadCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _resolvedId, _parentId;
+ Common::String _uploadUrl;
+ uint64 _serverReceivedBytes;
+
+ void start();
+ void resolveId();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void startUpload();
+ void startUploadCallback(Networking::JsonResponse response);
+ void startUploadErrorCallback(Networking::ErrorResponse error);
+ void uploadNextPart();
+ void partUploadedCallback(Networking::JsonResponse response);
+ void partUploadedErrorCallback(Networking::ErrorResponse error);
+ bool handleHttp308(const Networking::NetworkReadStream *stream);
+ void finishUpload(StorageFile status);
+
+public:
+ GoogleDriveUploadRequest(GoogleDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
+ virtual ~GoogleDriveUploadRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace GoogleDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idcreatedirectoryrequest.cpp b/backends/cloud/id/idcreatedirectoryrequest.cpp
new file mode 100644
index 0000000000..37f417f806
--- /dev/null
+++ b/backends/cloud/id/idcreatedirectoryrequest.cpp
@@ -0,0 +1,163 @@
+/* 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 "backends/cloud/id/idcreatedirectoryrequest.h"
+#include "backends/cloud/id/idstorage.h"
+#include "common/debug.h"
+
+namespace Cloud {
+namespace Id {
+
+IdCreateDirectoryRequest::IdCreateDirectoryRequest(IdStorage *storage, Common::String parentPath, Common::String directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb),
+ _requestedParentPath(parentPath), _requestedDirectoryName(directoryName), _storage(storage), _boolCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdCreateDirectoryRequest::~IdCreateDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void IdCreateDirectoryRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _ignoreCallback = false;
+
+ //the only exception when we create parent folder - is when it's ScummVM/ base folder
+ Common::String prefix = _requestedParentPath;
+ if (prefix.size() > 7)
+ prefix.erase(7);
+ if (prefix.equalsIgnoreCase("ScummVM")) {
+ Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback);
+ _workingRequest = _storage->createDirectory("ScummVM", callback, failureCallback);
+ return;
+ }
+
+ resolveId();
+}
+
+void IdCreateDirectoryRequest::createdBaseDirectoryCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+ resolveId();
+}
+
+void IdCreateDirectoryRequest::createdBaseDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void IdCreateDirectoryRequest::resolveId() {
+ //check whether such folder already exists
+ Storage::UploadCallback innerCallback = new Common::Callback<IdCreateDirectoryRequest, Storage::UploadResponse>(this, &IdCreateDirectoryRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::idResolveFailedCallback);
+ Common::String path = _requestedParentPath;
+ if (_requestedParentPath != "")
+ path += "/";
+ path += _requestedDirectoryName;
+ _workingRequest = _storage->resolveFileId(path, innerCallback, innerErrorCallback);
+}
+
+void IdCreateDirectoryRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+
+ //resolved => folder already exists
+ finishCreation(false);
+}
+
+void IdCreateDirectoryRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+
+ //not resolved => folder not exists
+ if (error.response.contains("no such file found in its parent directory")) {
+ //parent's id after the '\n'
+ Common::String parentId = error.response;
+ for (uint32 i = 0; i < parentId.size(); ++i)
+ if (parentId[i] == '\n') {
+ parentId.erase(0, i + 1);
+ break;
+ }
+
+ Storage::BoolCallback callback = new Common::Callback<IdCreateDirectoryRequest, Storage::BoolResponse>(this, &IdCreateDirectoryRequest::createdDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<IdCreateDirectoryRequest, Networking::ErrorResponse>(this, &IdCreateDirectoryRequest::createdDirectoryErrorCallback);
+ _workingRequest = _storage->createDirectoryWithParentId(parentId, _requestedDirectoryName, callback, failureCallback);
+ return;
+ }
+
+ finishError(error);
+}
+
+void IdCreateDirectoryRequest::createdDirectoryCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+ finishCreation(response.value);
+}
+
+void IdCreateDirectoryRequest::createdDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void IdCreateDirectoryRequest::handle() {}
+
+void IdCreateDirectoryRequest::restart() { start(); }
+
+Common::String IdCreateDirectoryRequest::date() const { return _date; }
+
+void IdCreateDirectoryRequest::finishCreation(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idcreatedirectoryrequest.h b/backends/cloud/id/idcreatedirectoryrequest.h
new file mode 100644
index 0000000000..241bcd30be
--- /dev/null
+++ b/backends/cloud/id/idcreatedirectoryrequest.h
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_ID_IDCREATEDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdCreateDirectoryRequest: public Networking::Request {
+ Common::String _requestedParentPath;
+ Common::String _requestedDirectoryName;
+ IdStorage *_storage;
+ Storage::BoolCallback _boolCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void createdBaseDirectoryCallback(Storage::BoolResponse response);
+ void createdBaseDirectoryErrorCallback(Networking::ErrorResponse error);
+ void resolveId();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void createdDirectoryCallback(Storage::BoolResponse response);
+ void createdDirectoryErrorCallback(Networking::ErrorResponse error);
+ void finishCreation(bool success);
+public:
+ IdCreateDirectoryRequest(IdStorage *storage, Common::String parentPath, Common::String directoryName, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
+ virtual ~IdCreateDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/iddownloadrequest.cpp b/backends/cloud/id/iddownloadrequest.cpp
new file mode 100644
index 0000000000..2532d611b8
--- /dev/null
+++ b/backends/cloud/id/iddownloadrequest.cpp
@@ -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.
+*
+*/
+
+#include "backends/cloud/id/iddownloadrequest.h"
+#include "backends/cloud/id/idstorage.h"
+#include "backends/cloud/downloadrequest.h"
+
+namespace Cloud {
+namespace Id {
+
+IdDownloadRequest::IdDownloadRequest(IdStorage *storage, Common::String remotePath, Common::String localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _requestedFile(remotePath), _requestedLocalFile(localPath), _storage(storage), _boolCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdDownloadRequest::~IdDownloadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void IdDownloadRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _ignoreCallback = false;
+
+ //find file's id
+ Storage::UploadCallback innerCallback = new Common::Callback<IdDownloadRequest, Storage::UploadResponse>(this, &IdDownloadRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, Networking::ErrorResponse>(this, &IdDownloadRequest::idResolveFailedCallback);
+ _workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback);
+}
+
+void IdDownloadRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Storage::BoolCallback innerCallback = new Common::Callback<IdDownloadRequest, Storage::BoolResponse>(this, &IdDownloadRequest::downloadCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdDownloadRequest, Networking::ErrorResponse>(this, &IdDownloadRequest::downloadErrorCallback);
+ _workingRequest = _storage->downloadById(response.value.id(), _requestedLocalFile, innerCallback, innerErrorCallback);
+}
+
+void IdDownloadRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdDownloadRequest::downloadCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishDownload(response.value);
+}
+
+void IdDownloadRequest::downloadErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdDownloadRequest::handle() {}
+
+void IdDownloadRequest::restart() { start(); }
+
+void IdDownloadRequest::finishDownload(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+double IdDownloadRequest::getProgress() const {
+ DownloadRequest *downloadRequest = dynamic_cast<DownloadRequest *>(_workingRequest);
+ if (downloadRequest == nullptr)
+ return 0; // resolving id still
+
+ // id resolve is 10 % and download is the other 90 %
+ return 0.1 + 0.9 * downloadRequest->getProgress(); // downloading
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/iddownloadrequest.h b/backends/cloud/id/iddownloadrequest.h
new file mode 100644
index 0000000000..65e05c00b3
--- /dev/null
+++ b/backends/cloud/id/iddownloadrequest.h
@@ -0,0 +1,62 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H
+#define BACKENDS_CLOUD_ID_IDDOWNLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdDownloadRequest: public Networking::Request {
+ Common::String _requestedFile, _requestedLocalFile;
+ IdStorage *_storage;
+ Storage::BoolCallback _boolCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void start();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void downloadCallback(Storage::BoolResponse response);
+ void downloadErrorCallback(Networking::ErrorResponse error);
+ void finishDownload(bool success);
+public:
+ IdDownloadRequest(IdStorage *storage, Common::String remotePath, Common::String localPath, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
+ virtual ~IdDownloadRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idlistdirectoryrequest.cpp b/backends/cloud/id/idlistdirectoryrequest.cpp
new file mode 100644
index 0000000000..4e63709984
--- /dev/null
+++ b/backends/cloud/id/idlistdirectoryrequest.cpp
@@ -0,0 +1,141 @@
+/* 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 "backends/cloud/id/idlistdirectoryrequest.h"
+#include "backends/cloud/id/idstorage.h"
+
+namespace Cloud {
+namespace Id {
+
+IdListDirectoryRequest::IdListDirectoryRequest(IdStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
+ Networking::Request(nullptr, ecb),
+ _requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdListDirectoryRequest::~IdListDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void IdListDirectoryRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _files.clear();
+ _directoriesQueue.clear();
+ _currentDirectory = StorageFile();
+ _ignoreCallback = false;
+
+ //find out that directory's id
+ Storage::UploadCallback innerCallback = new Common::Callback<IdListDirectoryRequest, Storage::UploadResponse>(this, &IdListDirectoryRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdListDirectoryRequest, Networking::ErrorResponse>(this, &IdListDirectoryRequest::idResolveErrorCallback);
+ _workingRequest = _storage->resolveFileId(_requestedPath, innerCallback, innerErrorCallback);
+}
+
+void IdListDirectoryRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+
+ StorageFile directory = response.value;
+ directory.setPath(_requestedPath);
+ _directoriesQueue.push_back(directory);
+ listNextDirectory();
+}
+
+void IdListDirectoryRequest::idResolveErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void IdListDirectoryRequest::listNextDirectory() {
+ if (_directoriesQueue.empty()) {
+ finishListing(_files);
+ return;
+ }
+
+ _currentDirectory = _directoriesQueue.back();
+ _directoriesQueue.pop_back();
+
+ Storage::FileArrayCallback callback = new Common::Callback<IdListDirectoryRequest, Storage::FileArrayResponse>(this, &IdListDirectoryRequest::listedDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<IdListDirectoryRequest, Networking::ErrorResponse>(this, &IdListDirectoryRequest::listedDirectoryErrorCallback);
+ _workingRequest = _storage->listDirectoryById(_currentDirectory.id(), callback, failureCallback);
+}
+
+void IdListDirectoryRequest::listedDirectoryCallback(Storage::FileArrayResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (response.request)
+ _date = response.request->date();
+
+ for (uint32 i = 0; i < response.value.size(); ++i) {
+ StorageFile &file = response.value[i];
+ Common::String path = _currentDirectory.path();
+ if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+ path += file.name();
+ file.setPath(path);
+ _files.push_back(file);
+ if (_requestedRecursive && file.isDirectory()) {
+ _directoriesQueue.push_back(file);
+ }
+ }
+
+ listNextDirectory();
+}
+
+void IdListDirectoryRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void IdListDirectoryRequest::handle() {}
+
+void IdListDirectoryRequest::restart() { start(); }
+
+Common::String IdListDirectoryRequest::date() const { return _date; }
+
+void IdListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback)
+ (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idlistdirectoryrequest.h b/backends/cloud/id/idlistdirectoryrequest.h
new file mode 100644
index 0000000000..58c5d2c864
--- /dev/null
+++ b/backends/cloud/id/idlistdirectoryrequest.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 BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_ID_IDLISTDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdListDirectoryRequest: public Networking::Request {
+ Common::String _requestedPath;
+ bool _requestedRecursive;
+ IdStorage *_storage;
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::Array<StorageFile> _files;
+ Common::Array<StorageFile> _directoriesQueue;
+ StorageFile _currentDirectory;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveErrorCallback(Networking::ErrorResponse error);
+ void listNextDirectory();
+ void listedDirectoryCallback(Storage::FileArrayResponse response);
+ void listedDirectoryErrorCallback(Networking::ErrorResponse error);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ IdListDirectoryRequest(IdStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
+ virtual ~IdListDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idresolveidrequest.cpp b/backends/cloud/id/idresolveidrequest.cpp
new file mode 100644
index 0000000000..e8589fc204
--- /dev/null
+++ b/backends/cloud/id/idresolveidrequest.cpp
@@ -0,0 +1,136 @@
+/* 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 "backends/cloud/id/idresolveidrequest.h"
+#include "backends/cloud/id/idstorage.h"
+
+namespace Cloud {
+namespace Id {
+
+IdResolveIdRequest::IdResolveIdRequest(IdStorage *storage, Common::String path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive):
+ Networking::Request(nullptr, ecb),
+ _requestedPath(path), _storage(storage), _uploadCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdResolveIdRequest::~IdResolveIdRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _uploadCallback;
+}
+
+void IdResolveIdRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _currentDirectory = "";
+ _currentDirectoryId = _storage->getRootDirectoryId();
+ _ignoreCallback = false;
+
+ listNextDirectory(StorageFile(_currentDirectoryId, 0, 0, true));
+}
+
+void IdResolveIdRequest::listNextDirectory(StorageFile fileToReturn) {
+ if (_currentDirectory.equalsIgnoreCase(_requestedPath)) {
+ finishFile(fileToReturn);
+ return;
+ }
+
+ Storage::FileArrayCallback callback = new Common::Callback<IdResolveIdRequest, Storage::FileArrayResponse>(this, &IdResolveIdRequest::listedDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<IdResolveIdRequest, Networking::ErrorResponse>(this, &IdResolveIdRequest::listedDirectoryErrorCallback);
+ _workingRequest = _storage->listDirectoryById(_currentDirectoryId, callback, failureCallback);
+}
+
+void IdResolveIdRequest::listedDirectoryCallback(Storage::FileArrayResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Common::String currentLevelName = _requestedPath;
+ ///debug(9, "'%s'", currentLevelName.c_str());
+ if (_currentDirectory.size())
+ currentLevelName.erase(0, _currentDirectory.size());
+ if (currentLevelName.size() && (currentLevelName[0] == '/' || currentLevelName[0] == '\\'))
+ currentLevelName.erase(0, 1);
+ ///debug(9, "'%s'", currentLevelName.c_str());
+ for (uint32 i = 0; i < currentLevelName.size(); ++i) {
+ if (currentLevelName[i] == '/' || currentLevelName[i] == '\\') {
+ currentLevelName.erase(i);
+ ///debug(9, "'%s'", currentLevelName.c_str());
+ break;
+ }
+ }
+
+ Common::String path = _currentDirectory;
+ if (path != "")
+ path += "/";
+ path += currentLevelName;
+ bool lastLevel = (path.equalsIgnoreCase(_requestedPath));
+
+ ///debug(9, "IdResolveIdRequest: searching for '%s' in '%s'", currentLevelName.c_str(), _currentDirectory.c_str());
+
+ Common::Array<StorageFile> &files = response.value;
+ bool found = false;
+ for (uint32 i = 0; i < files.size(); ++i) {
+ if ((files[i].isDirectory() || lastLevel) && files[i].name().equalsIgnoreCase(currentLevelName)) {
+ if (_currentDirectory != "")
+ _currentDirectory += "/";
+ _currentDirectory += files[i].name();
+ _currentDirectoryId = files[i].id();
+ ///debug(9, "IdResolveIdRequest: found it! new directory and its id: '%s', '%s'", _currentDirectory.c_str(), _currentDirectoryId.c_str());
+ listNextDirectory(files[i]);
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ if (lastLevel)
+ finishError(Networking::ErrorResponse(this, false, true, Common::String("no such file found in its parent directory\n") + _currentDirectoryId, 404));
+ else
+ finishError(Networking::ErrorResponse(this, false, true, "subdirectory not found", 400));
+ }
+}
+
+void IdResolveIdRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdResolveIdRequest::handle() {}
+
+void IdResolveIdRequest::restart() { start(); }
+
+void IdResolveIdRequest::finishFile(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idresolveidrequest.h b/backends/cloud/id/idresolveidrequest.h
new file mode 100644
index 0000000000..e735e96385
--- /dev/null
+++ b/backends/cloud/id/idresolveidrequest.h
@@ -0,0 +1,60 @@
+/* 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 BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H
+#define BACKENDS_CLOUD_ID_IDRESOLVEIDREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdResolveIdRequest: public Networking::Request {
+ Common::String _requestedPath;
+ IdStorage *_storage;
+ Storage::UploadCallback _uploadCallback;
+ Common::String _currentDirectory;
+ Common::String _currentDirectoryId;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void start();
+ void listNextDirectory(StorageFile fileToReturn);
+ void listedDirectoryCallback(Storage::FileArrayResponse response);
+ void listedDirectoryErrorCallback(Networking::ErrorResponse error);
+ void finishFile(StorageFile file);
+public:
+ IdResolveIdRequest(IdStorage *storage, Common::String path, Storage::UploadCallback cb, Networking::ErrorCallback ecb, bool recursive = false); //TODO: why upload?
+ virtual ~IdResolveIdRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idstorage.cpp b/backends/cloud/id/idstorage.cpp
new file mode 100644
index 0000000000..0ffa8c068c
--- /dev/null
+++ b/backends/cloud/id/idstorage.cpp
@@ -0,0 +1,109 @@
+/* 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.
+*
+*/
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/cloud/id/idstorage.h"
+#include "backends/cloud/id/idcreatedirectoryrequest.h"
+#include "backends/cloud/id/iddownloadrequest.h"
+#include "backends/cloud/id/idlistdirectoryrequest.h"
+#include "backends/cloud/id/idresolveidrequest.h"
+#include "backends/cloud/id/idstreamfilerequest.h"
+#include "common/debug.h"
+
+namespace Cloud {
+namespace Id {
+
+IdStorage::~IdStorage() {}
+
+void IdStorage::printFiles(FileArrayResponse response) {
+ debug(9, "IdStorage: files:");
+ Common::Array<StorageFile> &files = response.value;
+ for (uint32 i = 0; i < files.size(); ++i) {
+ debug(9, "\t%s%s", files[i].name().c_str(), files[i].isDirectory() ? " (directory)" : "");
+ debug(9, "\t%s", files[i].path().c_str());
+ debug(9, "\t%s", files[i].id().c_str());
+ debug(9, " ");
+ }
+}
+
+void IdStorage::printBool(BoolResponse response) {
+ debug(9, "IdStorage: bool: %s", response.value ? "true" : "false");
+}
+
+void IdStorage::printFile(UploadResponse response) {
+ debug(9, "\nIdStorage: uploaded file info:");
+ debug(9, "\tid: %s", response.value.path().c_str());
+ debug(9, "\tname: %s", response.value.name().c_str());
+ debug(9, "\tsize: %u", response.value.size());
+ debug(9, "\ttimestamp: %u", response.value.timestamp());
+}
+
+Storage::ListDirectoryCallback IdStorage::getPrintFilesCallback() {
+ return new Common::Callback<IdStorage, FileArrayResponse>(this, &IdStorage::printFiles);
+}
+
+Networking::Request *IdStorage::resolveFileId(Common::String path, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = new Common::Callback<IdStorage, UploadResponse>(this, &IdStorage::printFile);
+ return addRequest(new IdResolveIdRequest(this, path, callback, errorCallback));
+}
+
+Networking::Request *IdStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = new Common::Callback<IdStorage, FileArrayResponse>(this, &IdStorage::printFiles);
+ return addRequest(new IdListDirectoryRequest(this, path, callback, errorCallback, recursive));
+}
+
+Networking::Request *IdStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ if (!callback)
+ callback = new Common::Callback<IdStorage, BoolResponse>(this, &IdStorage::printBool);
+
+ //find out the parent path and directory name
+ Common::String parentPath = "", directoryName = path;
+ for (uint32 i = path.size(); i > 0; --i) {
+ if (path[i - 1] == '/' || path[i - 1] == '\\') {
+ parentPath = path;
+ parentPath.erase(i - 1);
+ directoryName.erase(0, i);
+ break;
+ }
+ }
+
+ return addRequest(new IdCreateDirectoryRequest(this, parentPath, directoryName, callback, errorCallback));
+}
+
+Networking::Request *IdStorage::streamFile(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new IdStreamFileRequest(this, path, outerCallback, errorCallback));
+}
+
+Networking::Request *IdStorage::download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new IdDownloadRequest(this, remotePath, localPath, callback, errorCallback));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idstorage.h b/backends/cloud/id/idstorage.h
new file mode 100644
index 0000000000..7e64fd4a37
--- /dev/null
+++ b/backends/cloud/id/idstorage.h
@@ -0,0 +1,83 @@
+/* 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 BACKENDS_CLOUD_ID_IDSTORAGE_H
+#define BACKENDS_CLOUD_ID_IDSTORAGE_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+/*
+ * Id::IdStorage is a special base class, which is created
+ * to simplify adding new storages which use ids instead of
+ * paths in their API.
+ *
+ * Some Requests are already implemented, and Storage based
+ * on IdStorage needs to override/implement a few basic things.
+ *
+ * For example, ListDirectoryRequest and ResolveIdRequests are
+ * based on listDirectoryById() and getRootDirectoryId() methods.
+ * Implementing these you'll get id resolving and directory
+ * listing by path.
+ */
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage: public Cloud::Storage {
+protected:
+ void printFiles(FileArrayResponse response);
+ void printBool(BoolResponse response);
+ void printFile(UploadResponse response);
+
+ ListDirectoryCallback getPrintFilesCallback();
+
+public:
+ virtual ~IdStorage();
+
+ /** Public Cloud API comes down there. */
+
+ /** Returns StorageFile with the resolved file's id. */
+ virtual Networking::Request *resolveFileId(Common::String path, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns ListDirectoryStatus struct with list of files. */
+ virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+ virtual Networking::Request *listDirectoryById(Common::String id, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *createDirectoryWithParentId(Common::String parentId, Common::String name, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ virtual Common::String getRootDirectoryId() = 0;
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/id/idstreamfilerequest.cpp b/backends/cloud/id/idstreamfilerequest.cpp
new file mode 100644
index 0000000000..2e68b15412
--- /dev/null
+++ b/backends/cloud/id/idstreamfilerequest.cpp
@@ -0,0 +1,98 @@
+/* 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 "backends/cloud/id/idstreamfilerequest.h"
+#include "backends/cloud/id/idstorage.h"
+
+namespace Cloud {
+namespace Id {
+
+IdStreamFileRequest::IdStreamFileRequest(IdStorage *storage, Common::String path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _requestedFile(path), _storage(storage), _streamCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+IdStreamFileRequest::~IdStreamFileRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _streamCallback;
+}
+
+void IdStreamFileRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _ignoreCallback = false;
+
+ //find file's id
+ Storage::UploadCallback innerCallback = new Common::Callback<IdStreamFileRequest, Storage::UploadResponse>(this, &IdStreamFileRequest::idResolvedCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, Networking::ErrorResponse>(this, &IdStreamFileRequest::idResolveFailedCallback);
+ _workingRequest = _storage->resolveFileId(_requestedFile, innerCallback, innerErrorCallback);
+}
+
+void IdStreamFileRequest::idResolvedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::NetworkReadStreamCallback innerCallback = new Common::Callback<IdStreamFileRequest, Networking::NetworkReadStreamResponse>(this, &IdStreamFileRequest::streamFileCallback);
+ Networking::ErrorCallback innerErrorCallback = new Common::Callback<IdStreamFileRequest, Networking::ErrorResponse>(this, &IdStreamFileRequest::streamFileErrorCallback);
+ _workingRequest = _storage->streamFileById(response.value.id(), innerCallback, innerErrorCallback);
+}
+
+void IdStreamFileRequest::idResolveFailedCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdStreamFileRequest::streamFileCallback(Networking::NetworkReadStreamResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishStream(response.value);
+}
+
+void IdStreamFileRequest::streamFileErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void IdStreamFileRequest::handle() {}
+
+void IdStreamFileRequest::restart() { start(); }
+
+void IdStreamFileRequest::finishStream(Networking::NetworkReadStream *stream) {
+ Request::finishSuccess();
+ if (_streamCallback)
+ (*_streamCallback)(Networking::NetworkReadStreamResponse(this, stream));
+}
+
+} // End of namespace Id
+} // End of namespace Cloud
diff --git a/backends/cloud/id/idstreamfilerequest.h b/backends/cloud/id/idstreamfilerequest.h
new file mode 100644
index 0000000000..20d7c0c25b
--- /dev/null
+++ b/backends/cloud/id/idstreamfilerequest.h
@@ -0,0 +1,59 @@
+/* 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 BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H
+#define BACKENDS_CLOUD_ID_IDSTREAMFILEREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace Id {
+
+class IdStorage;
+
+class IdStreamFileRequest: public Networking::Request {
+ Common::String _requestedFile;
+ IdStorage *_storage;
+ Networking::NetworkReadStreamCallback _streamCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void start();
+ void idResolvedCallback(Storage::UploadResponse response);
+ void idResolveFailedCallback(Networking::ErrorResponse error);
+ void streamFileCallback(Networking::NetworkReadStreamResponse response);
+ void streamFileErrorCallback(Networking::ErrorResponse error);
+ void finishStream(Networking::NetworkReadStream *stream);
+public:
+ IdStreamFileRequest(IdStorage *storage, Common::String path, Networking::NetworkReadStreamCallback cb, Networking::ErrorCallback ecb);
+ virtual ~IdStreamFileRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace Id
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/iso8601.cpp b/backends/cloud/iso8601.cpp
new file mode 100644
index 0000000000..177ef67f11
--- /dev/null
+++ b/backends/cloud/iso8601.cpp
@@ -0,0 +1,100 @@
+/* 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 "backends/cloud/iso8601.h"
+#include "common/str.h"
+
+namespace {
+
+Common::String getSubstring(const Common::String &s, uint32 beginning, uint32 ending) {
+ //beginning inclusive, ending exclusive
+ Common::String result = s;
+ result.erase(ending);
+ result.erase(0, beginning);
+ return result;
+}
+
+int find(const char *cstr, uint32 startPosition, char needle) {
+ const char *res = strchr(cstr + startPosition, needle);
+ if (res == nullptr)
+ return -1;
+ return res - cstr;
+}
+
+}
+
+namespace Cloud {
+namespace ISO8601 {
+
+uint32 convertToTimestamp(const Common::String &iso8601Date) {
+ //2015-05-12T15:50:38Z
+ const char *cstr = iso8601Date.c_str();
+ int firstHyphen = find(cstr, 0, '-');
+ int secondHyphen = find(cstr, firstHyphen + 1, '-');
+ int tSeparator = find(cstr, secondHyphen + 1, 'T');
+ int firstColon = find(cstr, tSeparator + 1, ':');
+ int secondColon = find(cstr, firstColon + 1, ':');
+ int zSeparator = find(cstr, secondColon + 1, 'Z');
+ if (zSeparator == -1)
+ zSeparator = find(cstr, secondColon + 1, '-'); // Box's RFC 3339
+ //now note '+1' which means if there ever was '-1' result of find(), we still did a valid find() from 0th char
+
+ Common::String year = getSubstring(iso8601Date, 0, firstHyphen);
+ Common::String month = getSubstring(iso8601Date, firstHyphen + 1, secondHyphen);
+ Common::String day = getSubstring(iso8601Date, secondHyphen + 1, tSeparator);
+ Common::String hour = getSubstring(iso8601Date, tSeparator + 1, firstColon);
+ Common::String minute = getSubstring(iso8601Date, firstColon + 1, secondColon);
+ Common::String second = getSubstring(iso8601Date, secondColon + 1, zSeparator);
+ //now note only 'ending' argument was not '+1' (which means I could've make that function such that -1 means 'until the end')
+
+ int Y = atoi(year.c_str());
+ int M = atoi(month.c_str());
+ int D = atoi(day.c_str());
+ int h = atoi(hour.c_str());
+ int m = atoi(minute.c_str());
+ int s = atoi(second.c_str());
+
+ //ok, now I compose a timestamp based on my basic perception of time/date
+ //yeah, I know about leap years and leap seconds and all, but still we don't care there
+
+ uint32 days = D - 1;
+ for (int i = 1970; i < Y; ++i)
+ if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0))
+ days += 366;
+ else
+ days += 365;
+
+ int mdays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ for (int i = 1; i < M; ++i) {
+ days += mdays[i - 1];
+ if (i == 2)
+ if ((Y % 4 == 0 && Y % 100 != 0) || (Y % 400 == 0))
+ days += 1;
+ }
+
+ uint32 hours = days * 24 + h;
+ uint32 minutes = hours * 60 + m;
+ return minutes * 60 + s;
+}
+
+} // End of namespace ISO8601
+} // End of namespace Cloud
diff --git a/backends/cloud/iso8601.h b/backends/cloud/iso8601.h
new file mode 100644
index 0000000000..cdd817bc07
--- /dev/null
+++ b/backends/cloud/iso8601.h
@@ -0,0 +1,37 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 BACKENDS_CLOUD_ISO8601_H
+#define BACKENDS_CLOUD_ISO8601_H
+
+#include "common/str.h"
+
+namespace Cloud {
+namespace ISO8601 {
+
+/** Returns timestamp corresponding to given ISO 8601 date */
+uint32 convertToTimestamp(const Common::String &iso8601Date);
+
+} // End of namespace ISO8601
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp
new file mode 100644
index 0000000000..fc7e4f58b0
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.cpp
@@ -0,0 +1,150 @@
+/* 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 "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot"
+
+OneDriveCreateDirectoryRequest::OneDriveCreateDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _storage(storage), _path(path), _boolCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+OneDriveCreateDirectoryRequest::~OneDriveCreateDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void OneDriveCreateDirectoryRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _ignoreCallback = false;
+
+ Common::String name = _path, parent = _path;
+ if (name.size() != 0) {
+ uint32 i = name.size() - 1;
+ while (true) {
+ parent.deleteLastChar();
+ if (name[i] == '/' || name[i] == '\\') {
+ name.erase(0, i + 1);
+ break;
+ }
+ if (i == 0)
+ break;
+ --i;
+ }
+ }
+
+ Common::String url = ONEDRIVE_API_SPECIAL_APPROOT;
+ if (parent != "")
+ url += ":/" + ConnMan.urlEncode(parent) + ":";
+ url += "/children";
+ Networking::JsonCallback innerCallback = new Common::Callback<OneDriveCreateDirectoryRequest, Networking::JsonResponse>(this, &OneDriveCreateDirectoryRequest::responseCallback);
+ Networking::ErrorCallback errorCallback = new Common::Callback<OneDriveCreateDirectoryRequest, Networking::ErrorResponse>(this, &OneDriveCreateDirectoryRequest::errorCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, innerCallback, errorCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->addHeader("Content-Type: application/json");
+
+ Common::JSONObject jsonRequestParameters;
+ jsonRequestParameters.setVal("name", new Common::JSONValue(name));
+ jsonRequestParameters.setVal("folder", new Common::JSONValue(Common::JSONObject()));
+ Common::JSONValue value(jsonRequestParameters);
+ request->addPostField(Common::JSON::stringify(&value));
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void OneDriveCreateDirectoryRequest::responseCallback(Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ _workingRequest = nullptr;
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+ if (info.contains("id")) {
+ finishCreation(true);
+ } else {
+ error.response = json->stringify(true);
+ finishError(error);
+ }
+
+ delete json;
+}
+
+void OneDriveCreateDirectoryRequest::errorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void OneDriveCreateDirectoryRequest::handle() {}
+
+void OneDriveCreateDirectoryRequest::restart() { start(); }
+
+Common::String OneDriveCreateDirectoryRequest::date() const { return _date; }
+
+void OneDriveCreateDirectoryRequest::finishCreation(bool success) {
+ Request::finishSuccess();
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h
new file mode 100644
index 0000000000..acaca2bf00
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivecreatedirectoryrequest.h
@@ -0,0 +1,59 @@
+/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVECREATEDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+class OneDriveStorage;
+
+class OneDriveCreateDirectoryRequest: public Networking::Request {
+ OneDriveStorage *_storage;
+ Common::String _path;
+ Storage::BoolCallback _boolCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void responseCallback(Networking::JsonResponse response);
+ void errorCallback(Networking::ErrorResponse error);
+ void finishCreation(bool success);
+public:
+ OneDriveCreateDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::BoolCallback cb, Networking::ErrorCallback ecb);
+ virtual ~OneDriveCreateDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp b/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp
new file mode 100644
index 0000000000..a247a9f234
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivelistdirectoryrequest.cpp
@@ -0,0 +1,193 @@
+/* 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 "backends/cloud/onedrive/onedrivelistdirectoryrequest.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+#define ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/children"
+
+OneDriveListDirectoryRequest::OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive):
+ Networking::Request(nullptr, ecb),
+ _requestedPath(path), _requestedRecursive(recursive), _storage(storage), _listDirectoryCallback(cb),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+OneDriveListDirectoryRequest::~OneDriveListDirectoryRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _listDirectoryCallback;
+}
+
+void OneDriveListDirectoryRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _files.clear();
+ _directoriesQueue.clear();
+ _currentDirectory = "";
+ _ignoreCallback = false;
+
+ _directoriesQueue.push_back(_requestedPath);
+ listNextDirectory();
+}
+
+void OneDriveListDirectoryRequest::listNextDirectory() {
+ if (_directoriesQueue.empty()) {
+ finishListing(_files);
+ return;
+ }
+
+ _currentDirectory = _directoriesQueue.back();
+ _directoriesQueue.pop_back();
+
+ if (_currentDirectory != "" && _currentDirectory.lastChar() != '/' && _currentDirectory.lastChar() != '\\')
+ _currentDirectory += '/';
+
+ Common::String dir = _currentDirectory;
+ dir.deleteLastChar();
+ Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CHILDREN, ConnMan.urlEncode(dir).c_str());
+ makeRequest(url);
+}
+
+void OneDriveListDirectoryRequest::makeRequest(Common::String url) {
+ Networking::JsonCallback callback = new Common::Callback<OneDriveListDirectoryRequest, Networking::JsonResponse>(this, &OneDriveListDirectoryRequest::listedDirectoryCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveListDirectoryRequest, Networking::ErrorResponse>(this, &OneDriveListDirectoryRequest::listedDirectoryErrorCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void OneDriveListDirectoryRequest::listedDirectoryCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ Common::JSONValue *json = response.value;
+
+ if (_ignoreCallback) {
+ delete json;
+ return;
+ }
+
+ if (response.request)
+ _date = response.request->date();
+
+ Networking::ErrorResponse error(this);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (!json->isObject()) {
+ error.response = "Passed JSON is not an object!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONObject object = json->asObject();
+
+ //check that ALL keys exist AND HAVE RIGHT TYPE to avoid segfaults
+ if (!Networking::CurlJsonRequest::jsonContainsArray(object, "value", "OneDriveListDirectoryRequest")) {
+ error.response = "\"value\" not found or that's not an array!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ Common::JSONArray items = object.getVal("value")->asArray();
+ for (uint32 i = 0; i < items.size(); ++i) {
+ if (!Networking::CurlJsonRequest::jsonIsObject(items[i], "OneDriveListDirectoryRequest")) continue;
+
+ Common::JSONObject item = items[i]->asObject();
+
+ if (!Networking::CurlJsonRequest::jsonContainsAttribute(item, "folder", "OneDriveListDirectoryRequest", true)) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "name", "OneDriveListDirectoryRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsIntegerNumber(item, "size", "OneDriveListDirectoryRequest")) continue;
+ if (!Networking::CurlJsonRequest::jsonContainsString(item, "lastModifiedDateTime", "OneDriveListDirectoryRequest")) continue;
+
+ Common::String path = _currentDirectory + item.getVal("name")->asString();
+ bool isDirectory = item.contains("folder");
+ uint32 size = item.getVal("size")->asIntegerNumber();
+ uint32 timestamp = ISO8601::convertToTimestamp(item.getVal("lastModifiedDateTime")->asString());
+
+ StorageFile file(path, size, timestamp, isDirectory);
+ _files.push_back(file);
+ if (_requestedRecursive && file.isDirectory()) {
+ _directoriesQueue.push_back(file.path());
+ }
+ }
+
+ bool hasMore = object.contains("@odata.nextLink");
+ if (hasMore) {
+ if (!Networking::CurlJsonRequest::jsonContainsString(object, "@odata.nextLink", "OneDriveListDirectoryRequest")) {
+ error.response = "\"@odata.nextLink\" is not a string!";
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ makeRequest(object.getVal("@odata.nextLink")->asString());
+ } else {
+ listNextDirectory();
+ }
+
+ delete json;
+}
+
+void OneDriveListDirectoryRequest::listedDirectoryErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ if (error.request)
+ _date = error.request->date();
+ finishError(error);
+}
+
+void OneDriveListDirectoryRequest::handle() {}
+
+void OneDriveListDirectoryRequest::restart() { start(); }
+
+Common::String OneDriveListDirectoryRequest::date() const { return _date; }
+
+void OneDriveListDirectoryRequest::finishListing(Common::Array<StorageFile> &files) {
+ Request::finishSuccess();
+ if (_listDirectoryCallback)
+ (*_listDirectoryCallback)(Storage::ListDirectoryResponse(this, files));
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedrivelistdirectoryrequest.h b/backends/cloud/onedrive/onedrivelistdirectoryrequest.h
new file mode 100644
index 0000000000..eb510ab257
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivelistdirectoryrequest.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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVELISTDIRECTORYREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+class OneDriveStorage;
+
+class OneDriveListDirectoryRequest: public Networking::Request {
+ Common::String _requestedPath;
+ bool _requestedRecursive;
+ OneDriveStorage *_storage;
+ Storage::ListDirectoryCallback _listDirectoryCallback;
+ Common::Array<StorageFile> _files;
+ Common::Array<Common::String> _directoriesQueue;
+ Common::String _currentDirectory;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _date;
+
+ void start();
+ void listNextDirectory();
+ void listedDirectoryCallback(Networking::JsonResponse response);
+ void listedDirectoryErrorCallback(Networking::ErrorResponse error);
+ void makeRequest(Common::String url);
+ void finishListing(Common::Array<StorageFile> &files);
+public:
+ OneDriveListDirectoryRequest(OneDriveStorage *storage, Common::String path, Storage::ListDirectoryCallback cb, Networking::ErrorCallback ecb, bool recursive = false);
+ virtual ~OneDriveListDirectoryRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedrivestorage.cpp b/backends/cloud/onedrive/onedrivestorage.cpp
new file mode 100644
index 0000000000..4b70bb73b9
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivestorage.cpp
@@ -0,0 +1,326 @@
+/* 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.
+*
+*/
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/cloud/onedrive/onedrivecreatedirectoryrequest.h"
+#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
+#include "backends/cloud/onedrive/onedrivelistdirectoryrequest.h"
+#include "backends/cloud/onedrive/onedriveuploadrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+#ifdef ENABLE_RELEASE
+#include "dists/clouds/cloud_keys.h"
+#endif
+
+namespace Cloud {
+namespace OneDrive {
+
+#define ONEDRIVE_OAUTH2_TOKEN "https://login.live.com/oauth20_token.srf"
+#define ONEDRIVE_API_SPECIAL_APPROOT_ID "https://api.onedrive.com/v1.0/drive/special/approot:/"
+#define ONEDRIVE_API_SPECIAL_APPROOT "https://api.onedrive.com/v1.0/drive/special/approot"
+
+char *OneDriveStorage::KEY = nullptr; //can't use CloudConfig there yet, loading it on instance creation/auth
+char *OneDriveStorage::SECRET = nullptr;
+
+void OneDriveStorage::loadKeyAndSecret() {
+#ifdef ENABLE_RELEASE
+ KEY = RELEASE_ONEDRIVE_KEY;
+ SECRET = RELEASE_ONEDRIVE_SECRET;
+#else
+ Common::String k = ConfMan.get("ONEDRIVE_KEY", ConfMan.kCloudDomain);
+ KEY = new char[k.size() + 1];
+ memcpy(KEY, k.c_str(), k.size());
+ KEY[k.size()] = 0;
+
+ k = ConfMan.get("ONEDRIVE_SECRET", ConfMan.kCloudDomain);
+ SECRET = new char[k.size() + 1];
+ memcpy(SECRET, k.c_str(), k.size());
+ SECRET[k.size()] = 0;
+#endif
+}
+
+OneDriveStorage::OneDriveStorage(Common::String accessToken, Common::String userId, Common::String refreshToken):
+ _token(accessToken), _uid(userId), _refreshToken(refreshToken) {}
+
+OneDriveStorage::OneDriveStorage(Common::String code) {
+ getAccessToken(
+ new Common::Callback<OneDriveStorage, BoolResponse>(this, &OneDriveStorage::codeFlowComplete),
+ new Common::Callback<OneDriveStorage, Networking::ErrorResponse>(this, &OneDriveStorage::codeFlowFailed),
+ code
+ );
+}
+
+OneDriveStorage::~OneDriveStorage() {}
+
+void OneDriveStorage::getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback, Common::String code) {
+ if (!KEY || !SECRET)
+ loadKeyAndSecret();
+ bool codeFlow = (code != "");
+
+ if (!codeFlow && _refreshToken == "") {
+ warning("OneDriveStorage: no refresh token available to get new access token.");
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ return;
+ }
+
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, BoolResponse, Networking::JsonResponse>(this, &OneDriveStorage::tokenRefreshed, callback);
+ if (errorCallback == nullptr)
+ errorCallback = getErrorPrintingCallback();
+ Networking::CurlJsonRequest *request = new Networking::CurlJsonRequest(innerCallback, errorCallback, ONEDRIVE_OAUTH2_TOKEN);
+ if (codeFlow) {
+ request->addPostField("code=" + code);
+ request->addPostField("grant_type=authorization_code");
+ } else {
+ request->addPostField("refresh_token=" + _refreshToken);
+ request->addPostField("grant_type=refresh_token");
+ }
+ request->addPostField("client_id=" + Common::String(KEY));
+ request->addPostField("client_secret=" + Common::String(SECRET));
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ request->addPostField("&redirect_uri=http%3A%2F%2Flocalhost%3A12345%2F");
+ } else {
+ request->addPostField("&redirect_uri=https%3A%2F%2Fwww.scummvm.org/c/code");
+ }
+ addRequest(request);
+}
+
+void OneDriveStorage::tokenRefreshed(BoolCallback callback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("OneDriveStorage: got NULL instead of JSON");
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete callback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage")) {
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ delete json;
+ delete callback;
+ return;
+ }
+
+ Common::JSONObject result = json->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "access_token", "OneDriveStorage") ||
+ !Networking::CurlJsonRequest::jsonContainsString(result, "user_id", "OneDriveStorage") ||
+ !Networking::CurlJsonRequest::jsonContainsString(result, "refresh_token", "OneDriveStorage")) {
+ warning("OneDriveStorage: bad response, no token or user_id passed");
+ debug(9, "%s", json->stringify().c_str());
+ if (callback)
+ (*callback)(BoolResponse(nullptr, false));
+ } else {
+ _token = result.getVal("access_token")->asString();
+ _uid = result.getVal("user_id")->asString();
+ _refreshToken = result.getVal("refresh_token")->asString();
+ CloudMan.save(); //ask CloudManager to save our new refreshToken
+ if (callback)
+ (*callback)(BoolResponse(nullptr, true));
+ }
+ delete json;
+ delete callback;
+}
+
+void OneDriveStorage::codeFlowComplete(BoolResponse response) {
+ if (!response.value) {
+ warning("OneDriveStorage: failed to get access token through code flow");
+ CloudMan.removeStorage(this);
+ return;
+ }
+
+ ConfMan.removeKey("onedrive_code", ConfMan.kCloudDomain);
+ CloudMan.replaceStorage(this, kStorageOneDriveId);
+ ConfMan.flushToDisk();
+}
+
+void OneDriveStorage::codeFlowFailed(Networking::ErrorResponse error) {
+ debug(9, "OneDriveStorage: code flow failed (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+ CloudMan.removeStorage(this);
+}
+
+void OneDriveStorage::saveConfig(Common::String keyPrefix) {
+ ConfMan.set(keyPrefix + "access_token", _token, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "user_id", _uid, ConfMan.kCloudDomain);
+ ConfMan.set(keyPrefix + "refresh_token", _refreshToken, ConfMan.kCloudDomain);
+}
+
+Common::String OneDriveStorage::name() const {
+ return "OneDrive";
+}
+
+void OneDriveStorage::infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("OneDriveStorage::infoInnerCallback: NULL passed instead of JSON");
+ delete outerCallback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage::infoInnerCallback")) {
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ Common::JSONObject info = json->asObject();
+
+ Common::String uid, name, email;
+ uint64 quotaUsed = 0, quotaAllocated = 26843545600L; // 25 GB, because I actually don't know any way to find out the real one
+
+ if (Networking::CurlJsonRequest::jsonContainsObject(info, "createdBy", "OneDriveStorage::infoInnerCallback")) {
+ Common::JSONObject createdBy = info.getVal("createdBy")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsObject(createdBy, "user", "OneDriveStorage::infoInnerCallback")) {
+ Common::JSONObject user = createdBy.getVal("user")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "id", "OneDriveStorage::infoInnerCallback"))
+ uid = user.getVal("id")->asString();
+ if (Networking::CurlJsonRequest::jsonContainsString(user, "displayName", "OneDriveStorage::infoInnerCallback"))
+ name = user.getVal("displayName")->asString();
+ }
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsIntegerNumber(info, "size", "OneDriveStorage::infoInnerCallback")) {
+ quotaUsed = info.getVal("size")->asIntegerNumber();
+ }
+
+ Common::String username = email;
+ if (username == "")
+ username = name;
+ if (username == "")
+ username = uid;
+ CloudMan.setStorageUsername(kStorageOneDriveId, username);
+
+ if (outerCallback) {
+ (*outerCallback)(StorageInfoResponse(nullptr, StorageInfo(uid, name, email, quotaUsed, quotaAllocated)));
+ delete outerCallback;
+ }
+
+ delete json;
+}
+
+void OneDriveStorage::fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response) {
+ Common::JSONValue *json = response.value;
+ if (!json) {
+ warning("OneDriveStorage::fileInfoCallback: NULL passed instead of JSON");
+ if (outerCallback)
+ (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
+ delete outerCallback;
+ return;
+ }
+
+ if (!Networking::CurlJsonRequest::jsonIsObject(json, "OneDriveStorage::fileInfoCallback")) {
+ if (outerCallback)
+ (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ Common::JSONObject result = response.value->asObject();
+ if (!Networking::CurlJsonRequest::jsonContainsString(result, "@content.downloadUrl", "OneDriveStorage::fileInfoCallback")) {
+ warning("OneDriveStorage: downloadUrl not found in passed JSON");
+ debug(9, "%s", response.value->stringify().c_str());
+ if (outerCallback)
+ (*outerCallback)(Networking::NetworkReadStreamResponse(response.request, nullptr));
+ delete json;
+ delete outerCallback;
+ return;
+ }
+
+ const char *url = result.getVal("@content.downloadUrl")->asString().c_str();
+ if (outerCallback)
+ (*outerCallback)(Networking::NetworkReadStreamResponse(
+ response.request,
+ new Networking::NetworkReadStream(url, nullptr, "")
+ ));
+
+ delete json;
+ delete outerCallback;
+}
+
+Networking::Request *OneDriveStorage::listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ return addRequest(new OneDriveListDirectoryRequest(this, path, callback, errorCallback, recursive));
+}
+
+Networking::Request *OneDriveStorage::upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ return addRequest(new OneDriveUploadRequest(this, path, contents, callback, errorCallback));
+}
+
+Networking::Request *OneDriveStorage::streamFileById(Common::String path, Networking::NetworkReadStreamCallback outerCallback, Networking::ErrorCallback errorCallback) {
+ Common::String url = ONEDRIVE_API_SPECIAL_APPROOT_ID + ConnMan.urlEncode(path);
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, Networking::NetworkReadStreamResponse, Networking::JsonResponse>(this, &OneDriveStorage::fileInfoCallback, outerCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _token);
+ return addRequest(request);
+}
+
+Networking::Request *OneDriveStorage::createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new OneDriveCreateDirectoryRequest(this, path, callback, errorCallback));
+}
+
+Networking::Request *OneDriveStorage::info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) {
+ Networking::JsonCallback innerCallback = new Common::CallbackBridge<OneDriveStorage, StorageInfoResponse, Networking::JsonResponse>(this, &OneDriveStorage::infoInnerCallback, callback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(this, innerCallback, errorCallback, ONEDRIVE_API_SPECIAL_APPROOT);
+ request->addHeader("Authorization: bearer " + _token);
+ return addRequest(request);
+}
+
+Common::String OneDriveStorage::savesDirectoryPath() { return "saves/"; }
+
+OneDriveStorage *OneDriveStorage::loadFromConfig(Common::String keyPrefix) {
+ loadKeyAndSecret();
+
+ if (!ConfMan.hasKey(keyPrefix + "access_token", ConfMan.kCloudDomain)) {
+ warning("OneDriveStorage: no access_token found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "user_id", ConfMan.kCloudDomain)) {
+ warning("OneDriveStorage: no user_id found");
+ return nullptr;
+ }
+
+ if (!ConfMan.hasKey(keyPrefix + "refresh_token", ConfMan.kCloudDomain)) {
+ warning("OneDriveStorage: no refresh_token found");
+ return nullptr;
+ }
+
+ Common::String accessToken = ConfMan.get(keyPrefix + "access_token", ConfMan.kCloudDomain);
+ Common::String userId = ConfMan.get(keyPrefix + "user_id", ConfMan.kCloudDomain);
+ Common::String refreshToken = ConfMan.get(keyPrefix + "refresh_token", ConfMan.kCloudDomain);
+ return new OneDriveStorage(accessToken, userId, refreshToken);
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedrivestorage.h b/backends/cloud/onedrive/onedrivestorage.h
new file mode 100644
index 0000000000..59c61074e3
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivestorage.h
@@ -0,0 +1,113 @@
+/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVESTORAGE_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+class OneDriveStorage: public Cloud::Storage {
+ static char *KEY, *SECRET;
+
+ static void loadKeyAndSecret();
+
+ Common::String _token, _uid, _refreshToken;
+
+ /** This private constructor is called from loadFromConfig(). */
+ OneDriveStorage(Common::String token, Common::String uid, Common::String refreshToken);
+
+ void tokenRefreshed(BoolCallback callback, Networking::JsonResponse response);
+ void codeFlowComplete(BoolResponse response);
+ void codeFlowFailed(Networking::ErrorResponse error);
+
+ /** Constructs StorageInfo based on JSON response from cloud. */
+ void infoInnerCallback(StorageInfoCallback outerCallback, Networking::JsonResponse json);
+
+ void fileInfoCallback(Networking::NetworkReadStreamCallback outerCallback, Networking::JsonResponse response);
+public:
+ /** This constructor uses OAuth code flow to get tokens. */
+ OneDriveStorage(Common::String code);
+ virtual ~OneDriveStorage();
+
+ /**
+ * Storage methods, which are used by CloudManager to save
+ * storage in configuration file.
+ */
+
+ /**
+ * Save storage data using ConfMan.
+ * @param keyPrefix all saved keys must start with this prefix.
+ * @note every Storage must write keyPrefix + "type" key
+ * with common value (e.g. "Dropbox").
+ */
+ virtual void saveConfig(Common::String keyPrefix);
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const;
+
+ /** Public Cloud API comes down there. */
+
+ /** Returns ListDirectoryStatus struct with list of files. */
+ virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+
+ /** Returns UploadStatus struct with info about uploaded file. */
+ virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFileById(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns the StorageInfo struct. */
+ virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns storage's saves directory path with the trailing slash. */
+ virtual Common::String savesDirectoryPath();
+
+ /**
+ * Load token and user id from configs and return OneDriveStorage for those.
+ * @return pointer to the newly created OneDriveStorage or 0 if some problem occured.
+ */
+ static OneDriveStorage *loadFromConfig(Common::String keyPrefix);
+
+ /**
+ * Gets new access_token. If <code> passed is "", refresh_token is used.
+ * Use "" in order to refresh token and pass a callback, so you could
+ * continue your work when new token is available.
+ */
+ void getAccessToken(BoolCallback callback, Networking::ErrorCallback errorCallback = nullptr, Common::String code = "");
+
+ Common::String accessToken() const { return _token; }
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedrivetokenrefresher.cpp b/backends/cloud/onedrive/onedrivetokenrefresher.cpp
new file mode 100644
index 0000000000..ce7895f41c
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivetokenrefresher.cpp
@@ -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.
+*
+*/
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/cloud/onedrive/onedrivetokenrefresher.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+namespace Cloud {
+namespace OneDrive {
+
+OneDriveTokenRefresher::OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url):
+ CurlJsonRequest(callback, ecb, url), _parentStorage(parent) {}
+
+OneDriveTokenRefresher::~OneDriveTokenRefresher() {}
+
+void OneDriveTokenRefresher::tokenRefreshed(Storage::BoolResponse response) {
+ if (!response.value) {
+ //failed to refresh token, notify user with NULL in original callback
+ warning("OneDriveTokenRefresher: failed to refresh token");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ //update headers: first change header with token, then pass those to request
+ for (uint32 i = 0; i < _headers.size(); ++i) {
+ if (_headers[i].contains("Authorization")) {
+ _headers[i] = "Authorization: bearer " + _parentStorage->accessToken();
+ }
+ }
+ setHeaders(_headers);
+
+ //successfully received refreshed token, can restart the original request now
+ retry(0);
+}
+
+void OneDriveTokenRefresher::finishJson(Common::JSONValue *json) {
+ if (!json) {
+ //that's probably not an error (200 OK)
+ CurlJsonRequest::finishJson(nullptr);
+ return;
+ }
+
+ if (jsonIsObject(json, "OneDriveTokenRefresher")) {
+ Common::JSONObject result = json->asObject();
+ long httpResponseCode = -1;
+ if (result.contains("error") && jsonIsObject(result.getVal("error"), "OneDriveTokenRefresher")) {
+ //new token needed => request token & then retry original request
+ if (_stream) {
+ httpResponseCode = _stream->httpResponseCode();
+ debug(9, "OneDriveTokenRefresher: code = %ld", httpResponseCode);
+ }
+
+ Common::JSONObject error = result.getVal("error")->asObject();
+ bool irrecoverable = true;
+
+ Common::String code, message;
+ if (jsonContainsString(error, "code", "OneDriveTokenRefresher")) {
+ code = error.getVal("code")->asString();
+ debug(9, "OneDriveTokenRefresher: code = %s", code.c_str());
+ }
+
+ if (jsonContainsString(error, "message", "OneDriveTokenRefresher")) {
+ message = error.getVal("message")->asString();
+ debug(9, "OneDriveTokenRefresher: message = %s", message.c_str());
+ }
+
+ //determine whether token refreshing would help in this situation
+ if (code == "itemNotFound") {
+ if (message.contains("application ID"))
+ irrecoverable = false;
+ }
+
+ if (code == "unauthenticated")
+ irrecoverable = false;
+
+ if (irrecoverable) {
+ finishError(Networking::ErrorResponse(this, false, true, json->stringify(true), httpResponseCode));
+ delete json;
+ return;
+ }
+
+ pause();
+ delete json;
+ _parentStorage->getAccessToken(new Common::Callback<OneDriveTokenRefresher, Storage::BoolResponse>(this, &OneDriveTokenRefresher::tokenRefreshed));
+ return;
+ }
+ }
+
+ //notify user of success
+ CurlJsonRequest::finishJson(json);
+}
+
+void OneDriveTokenRefresher::setHeaders(Common::Array<Common::String> &headers) {
+ _headers = headers;
+ curl_slist_free_all(_headersList);
+ _headersList = 0;
+ for (uint32 i = 0; i < headers.size(); ++i)
+ CurlJsonRequest::addHeader(headers[i]);
+}
+
+void OneDriveTokenRefresher::addHeader(Common::String header) {
+ _headers.push_back(header);
+ CurlJsonRequest::addHeader(header);
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedrivetokenrefresher.h b/backends/cloud/onedrive/onedrivetokenrefresher.h
new file mode 100644
index 0000000000..77e34d4e03
--- /dev/null
+++ b/backends/cloud/onedrive/onedrivetokenrefresher.h
@@ -0,0 +1,52 @@
+/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVETOKENREFRESHER_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+class OneDriveStorage;
+
+class OneDriveTokenRefresher: public Networking::CurlJsonRequest {
+ OneDriveStorage *_parentStorage;
+ Common::Array<Common::String> _headers;
+
+ void tokenRefreshed(Storage::BoolResponse response);
+
+ virtual void finishJson(Common::JSONValue *json);
+public:
+ OneDriveTokenRefresher(OneDriveStorage *parent, Networking::JsonCallback callback, Networking::ErrorCallback ecb, const char *url);
+ virtual ~OneDriveTokenRefresher();
+
+ virtual void setHeaders(Common::Array<Common::String> &headers);
+ virtual void addHeader(Common::String header);
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/onedrive/onedriveuploadrequest.cpp b/backends/cloud/onedrive/onedriveuploadrequest.cpp
new file mode 100644
index 0000000000..41e6e2a37b
--- /dev/null
+++ b/backends/cloud/onedrive/onedriveuploadrequest.cpp
@@ -0,0 +1,191 @@
+/* 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 "backends/cloud/onedrive/onedriveuploadrequest.h"
+#include "backends/cloud/onedrive/onedrivestorage.h"
+#include "backends/cloud/iso8601.h"
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/json.h"
+#include "onedrivetokenrefresher.h"
+
+namespace Cloud {
+namespace OneDrive {
+
+#define ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/upload.createSession"
+#define ONEDRIVE_API_SPECIAL_APPROOT_CONTENT "https://api.onedrive.com/v1.0/drive/special/approot:/%s:/content"
+
+OneDriveUploadRequest::OneDriveUploadRequest(OneDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb):
+ Networking::Request(nullptr, ecb), _storage(storage), _savePath(path), _contentsStream(contents), _uploadCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+OneDriveUploadRequest::~OneDriveUploadRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _contentsStream;
+ delete _uploadCallback;
+}
+
+void OneDriveUploadRequest::start() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ if (_contentsStream == nullptr) {
+ warning("OneDriveUploadRequest: cannot restart because no stream given");
+ finishError(Networking::ErrorResponse(this, false, true, "No stream given", -1));
+ return;
+ }
+ if (!_contentsStream->seek(0)) {
+ warning("OneDriveUploadRequest: cannot restart because stream couldn't seek(0)");
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+ _ignoreCallback = false;
+
+ uploadNextPart();
+}
+
+void OneDriveUploadRequest::uploadNextPart() {
+ const uint32 UPLOAD_PER_ONE_REQUEST = 10 * 1024 * 1024;
+
+ if (_uploadUrl == "" && (uint32)_contentsStream->size() > UPLOAD_PER_ONE_REQUEST) {
+ Common::String url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_UPLOAD, ConnMan.urlEncode(_savePath).c_str()); //folder must exist
+ Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, Networking::JsonResponse>(this, &OneDriveUploadRequest::partUploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, Networking::ErrorResponse>(this, &OneDriveUploadRequest::partUploadedErrorCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->setBuffer(new byte[1], 0); //use POST
+ _workingRequest = ConnMan.addRequest(request);
+ return;
+ }
+
+ Common::String url;
+ if (_uploadUrl == "") {
+ url = Common::String::format(ONEDRIVE_API_SPECIAL_APPROOT_CONTENT, ConnMan.urlEncode(_savePath).c_str());
+ } else {
+ url = _uploadUrl;
+ }
+
+ Networking::JsonCallback callback = new Common::Callback<OneDriveUploadRequest, Networking::JsonResponse>(this, &OneDriveUploadRequest::partUploadedCallback);
+ Networking::ErrorCallback failureCallback = new Common::Callback<OneDriveUploadRequest, Networking::ErrorResponse>(this, &OneDriveUploadRequest::partUploadedErrorCallback);
+ Networking::CurlJsonRequest *request = new OneDriveTokenRefresher(_storage, callback, failureCallback, url.c_str());
+ request->addHeader("Authorization: Bearer " + _storage->accessToken());
+ request->usePut();
+
+ uint32 oldPos = _contentsStream->pos();
+
+ byte *buffer = new byte[UPLOAD_PER_ONE_REQUEST];
+ uint32 size = _contentsStream->read(buffer, UPLOAD_PER_ONE_REQUEST);
+ request->setBuffer(buffer, size);
+
+ if (_uploadUrl != "") {
+ request->addHeader(Common::String::format("Content-Range: bytes %u-%u/%u", oldPos, _contentsStream->pos() - 1, _contentsStream->size()));
+ } else if (_contentsStream->size() == 0) {
+ warning("\"Sorry, OneDrive can't upload empty files\"");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+ delete request;
+ return;
+ }
+
+ _workingRequest = ConnMan.addRequest(request);
+}
+
+void OneDriveUploadRequest::partUploadedCallback(Networking::JsonResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ Networking::ErrorResponse error(this, false, true, "", -1);
+ Networking::CurlJsonRequest *rq = (Networking::CurlJsonRequest *)response.request;
+ if (rq && rq->getNetworkReadStream())
+ error.httpResponseCode = rq->getNetworkReadStream()->httpResponseCode();
+
+ Common::JSONValue *json = response.value;
+ if (json == nullptr) {
+ error.response = "Failed to parse JSON, null passed!";
+ finishError(error);
+ return;
+ }
+
+ if (json->isObject()) {
+ Common::JSONObject object = json->asObject();
+
+ if (object.contains("error")) {
+ warning("OneDriveUploadRequest: error: %s", json->stringify(true).c_str());
+ error.response = json->stringify(true);
+ finishError(error);
+ delete json;
+ return;
+ }
+
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "id", "OneDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "name", "OneDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsIntegerNumber(object, "size", "OneDriveUploadRequest") &&
+ Networking::CurlJsonRequest::jsonContainsString(object, "lastModifiedDateTime", "OneDriveUploadRequest")) {
+ //finished
+ Common::String path = _savePath;
+ uint32 size = object.getVal("size")->asIntegerNumber();
+ uint32 timestamp = ISO8601::convertToTimestamp(object.getVal("lastModifiedDateTime")->asString());
+ finishUpload(StorageFile(path, size, timestamp, false));
+ return;
+ }
+
+ if (_uploadUrl == "") {
+ if (Networking::CurlJsonRequest::jsonContainsString(object, "uploadUrl", "OneDriveUploadRequest"))
+ _uploadUrl = object.getVal("uploadUrl")->asString();
+ }
+ }
+
+ if (_contentsStream->eos() || _contentsStream->pos() >= _contentsStream->size() - 1) {
+ warning("OneDriveUploadRequest: no file info to return");
+ finishUpload(StorageFile(_savePath, 0, 0, false));
+ } else {
+ uploadNextPart();
+ }
+
+ delete json;
+}
+
+void OneDriveUploadRequest::partUploadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+ finishError(error);
+}
+
+void OneDriveUploadRequest::handle() {}
+
+void OneDriveUploadRequest::restart() { start(); }
+
+void OneDriveUploadRequest::finishUpload(StorageFile file) {
+ Request::finishSuccess();
+ if (_uploadCallback)
+ (*_uploadCallback)(Storage::UploadResponse(this, file));
+}
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
diff --git a/backends/cloud/onedrive/onedriveuploadrequest.h b/backends/cloud/onedrive/onedriveuploadrequest.h
new file mode 100644
index 0000000000..4e2cb24a7f
--- /dev/null
+++ b/backends/cloud/onedrive/onedriveuploadrequest.h
@@ -0,0 +1,61 @@
+/* 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 BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H
+#define BACKENDS_CLOUD_ONEDRIVE_ONEDRIVEUPLOADREQUEST_H
+
+#include "backends/cloud/storage.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/request.h"
+#include "common/callback.h"
+
+namespace Cloud {
+namespace OneDrive {
+class OneDriveStorage;
+
+class OneDriveUploadRequest: public Networking::Request {
+ OneDriveStorage *_storage;
+ Common::String _savePath;
+ Common::SeekableReadStream *_contentsStream;
+ Storage::UploadCallback _uploadCallback;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ Common::String _uploadUrl;
+
+ void start();
+ void uploadNextPart();
+ void partUploadedCallback(Networking::JsonResponse response);
+ void partUploadedErrorCallback(Networking::ErrorResponse error);
+ void finishUpload(StorageFile status);
+
+public:
+ OneDriveUploadRequest(OneDriveStorage *storage, Common::String path, Common::SeekableReadStream *contents, Storage::UploadCallback callback, Networking::ErrorCallback ecb);
+ virtual ~OneDriveUploadRequest();
+
+ virtual void handle();
+ virtual void restart();
+};
+
+} // End of namespace OneDrive
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/savessyncrequest.cpp b/backends/cloud/savessyncrequest.cpp
new file mode 100644
index 0000000000..fff46c3a83
--- /dev/null
+++ b/backends/cloud/savessyncrequest.cpp
@@ -0,0 +1,403 @@
+/* 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 "backends/cloud/savessyncrequest.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/saves/default/default-saves.h"
+#include "common/config-manager.h"
+#include "common/debug.h"
+#include "common/file.h"
+#include "common/json.h"
+#include "common/savefile.h"
+#include "common/system.h"
+#include "gui/saveload-dialog.h"
+
+namespace Cloud {
+
+SavesSyncRequest::SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb):
+ Request(nullptr, ecb), CommandSender(nullptr), _storage(storage), _boolCallback(callback),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ start();
+}
+
+SavesSyncRequest::~SavesSyncRequest() {
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ delete _boolCallback;
+}
+
+void SavesSyncRequest::start() {
+ //cleanup
+ _ignoreCallback = true;
+ if (_workingRequest)
+ _workingRequest->finish();
+ _currentDownloadingFile = StorageFile();
+ _currentUploadingFile = "";
+ _filesToDownload.clear();
+ _filesToUpload.clear();
+ _localFilesTimestamps.clear();
+ _totalFilesToHandle = 0;
+ _ignoreCallback = false;
+
+ //load timestamps
+ _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
+
+ //list saves directory
+ Common::String dir = _storage->savesDirectoryPath();
+ if (dir.lastChar() == '/')
+ dir.deleteLastChar();
+ _workingRequest = _storage->listDirectory(
+ dir,
+ new Common::Callback<SavesSyncRequest, Storage::ListDirectoryResponse>(this, &SavesSyncRequest::directoryListedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryListedErrorCallback)
+ );
+ if (!_workingRequest) finishError(Networking::ErrorResponse(this));
+}
+
+void SavesSyncRequest::directoryListedCallback(Storage::ListDirectoryResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ if (response.request) _date = response.request->date();
+
+ Common::HashMap<Common::String, bool> localFileNotAvailableInCloud;
+ for (Common::HashMap<Common::String, uint32>::iterator i = _localFilesTimestamps.begin(); i != _localFilesTimestamps.end(); ++i) {
+ localFileNotAvailableInCloud[i->_key] = true;
+ }
+
+ //determine which files to download and which files to upload
+ Common::Array<StorageFile> &remoteFiles = response.value;
+ uint64 totalSize = 0;
+ for (uint32 i = 0; i < remoteFiles.size(); ++i) {
+ StorageFile &file = remoteFiles[i];
+ if (file.isDirectory())
+ continue;
+ totalSize += file.size();
+ if (file.name() == DefaultSaveFileManager::TIMESTAMPS_FILENAME)
+ continue;
+
+ Common::String name = file.name();
+ if (!_localFilesTimestamps.contains(name)) {
+ _filesToDownload.push_back(file);
+ } else {
+ localFileNotAvailableInCloud[name] = false;
+
+ if (_localFilesTimestamps[name] == file.timestamp())
+ continue;
+
+ //we actually can have some files not only with timestamp < remote
+ //but also with timestamp > remote (when we have been using ANOTHER CLOUD and then switched back)
+ if (_localFilesTimestamps[name] > file.timestamp() || _localFilesTimestamps[name] == DefaultSaveFileManager::INVALID_TIMESTAMP)
+ _filesToUpload.push_back(file.name());
+ else
+ _filesToDownload.push_back(file);
+ }
+ }
+
+ CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize);
+
+ //upload files which are unavailable in cloud
+ for (Common::HashMap<Common::String, bool>::iterator i = localFileNotAvailableInCloud.begin(); i != localFileNotAvailableInCloud.end(); ++i) {
+ if (i->_key == DefaultSaveFileManager::TIMESTAMPS_FILENAME)
+ continue;
+ if (i->_value)
+ _filesToUpload.push_back(i->_key);
+ }
+
+ debug(9, "\nSavesSyncRequest: download files:");
+ for (uint32 i = 0; i < _filesToDownload.size(); ++i) {
+ debug(9, "%s", _filesToDownload[i].name().c_str());
+ }
+ debug(9, "\nSavesSyncRequest: upload files:");
+ for (uint32 i = 0; i < _filesToUpload.size(); ++i) {
+ debug(9, "%s", _filesToUpload[i].c_str());
+ }
+ _totalFilesToHandle = _filesToDownload.size() + _filesToUpload.size();
+
+ //start downloading files
+ downloadNextFile();
+}
+
+void SavesSyncRequest::directoryListedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ bool irrecoverable = error.interrupted || error.failed;
+ if (error.failed) {
+ Common::JSONValue *value = Common::JSON::parse(error.response.c_str());
+ if (value) {
+ if (value->isObject()) {
+ Common::JSONObject object = value->asObject();
+
+ //Dropbox-related error:
+ if (object.contains("error_summary") && object.getVal("error_summary")->isString()) {
+ Common::String summary = object.getVal("error_summary")->asString();
+ if (summary.contains("not_found")) {
+ irrecoverable = false;
+ }
+ }
+
+ //OneDrive-related error:
+ if (object.contains("error") && object.getVal("error")->isObject()) {
+ Common::JSONObject errorNode = object.getVal("error")->asObject();
+ if (Networking::CurlJsonRequest::jsonContainsString(errorNode, "code", "SavesSyncRequest")) {
+ Common::String code = errorNode.getVal("code")->asString();
+ if (code == "itemNotFound") {
+ irrecoverable = false;
+ }
+ }
+ }
+ }
+ delete value;
+ }
+
+ //Google Drive and Box-related ScummVM-based error
+ if (error.response.contains("subdirectory not found")) {
+ irrecoverable = false; //base "/ScummVM/" folder not found
+ } else if (error.response.contains("no such file found in its parent directory")) {
+ irrecoverable = false; //"Saves" folder within "/ScummVM/" not found
+ }
+ }
+
+ if (irrecoverable) {
+ finishError(error);
+ return;
+ }
+
+ //we're lucky - user just lacks his "/cloud/" folder - let's create one
+ Common::String dir = _storage->savesDirectoryPath();
+ if (dir.lastChar() == '/')
+ dir.deleteLastChar();
+ debug(9, "SavesSyncRequest: creating %s", dir.c_str());
+ _workingRequest = _storage->createDirectory(
+ dir,
+ new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::directoryCreatedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::directoryCreatedErrorCallback)
+ );
+ if (!_workingRequest)
+ finishError(Networking::ErrorResponse(this));
+}
+
+void SavesSyncRequest::directoryCreatedCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //stop syncing if failed to create saves directory
+ if (!response.value) {
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ //continue with empty files list
+ Common::Array<StorageFile> files;
+ directoryListedCallback(Storage::ListDirectoryResponse(response.request, files));
+}
+
+void SavesSyncRequest::directoryCreatedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //stop syncing if failed to create saves directory
+ finishError(error);
+}
+
+void SavesSyncRequest::downloadNextFile() {
+ if (_filesToDownload.empty()) {
+ _currentDownloadingFile = StorageFile("", 0, 0, false); //so getFilesToDownload() would return an empty array
+ sendCommand(GUI::kSavesSyncEndedCmd, 0);
+ uploadNextFile();
+ return;
+ }
+
+ _currentDownloadingFile = _filesToDownload.back();
+ _filesToDownload.pop_back();
+
+ sendCommand(GUI::kSavesSyncProgressCmd, (int)(getDownloadingProgress() * 100));
+
+ debug(9, "SavesSyncRequest: downloading %s (%d %%)", _currentDownloadingFile.name().c_str(), (int)(getProgress() * 100));
+ _workingRequest = _storage->downloadById(
+ _currentDownloadingFile.id(),
+ DefaultSaveFileManager::concatWithSavesPath(_currentDownloadingFile.name()),
+ new Common::Callback<SavesSyncRequest, Storage::BoolResponse>(this, &SavesSyncRequest::fileDownloadedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileDownloadedErrorCallback)
+ );
+ if (!_workingRequest)
+ finishError(Networking::ErrorResponse(this));
+}
+
+void SavesSyncRequest::fileDownloadedCallback(Storage::BoolResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //stop syncing if download failed
+ if (!response.value) {
+ //delete the incomplete file
+ g_system->getSavefileManager()->removeSavefile(_currentDownloadingFile.name());
+ finishError(Networking::ErrorResponse(this, false, true, "", -1));
+ return;
+ }
+
+ //update local timestamp for downloaded file
+ _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
+ _localFilesTimestamps[_currentDownloadingFile.name()] = _currentDownloadingFile.timestamp();
+ DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps);
+
+ //continue downloading files
+ downloadNextFile();
+}
+
+void SavesSyncRequest::fileDownloadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //stop syncing if download failed
+ finishError(error);
+}
+
+void SavesSyncRequest::uploadNextFile() {
+ if (_filesToUpload.empty()) {
+ finishSync(true);
+ return;
+ }
+
+ _currentUploadingFile = _filesToUpload.back();
+ _filesToUpload.pop_back();
+
+ debug(9, "SavesSyncRequest: uploading %s (%d %%)", _currentUploadingFile.c_str(), (int)(getProgress() * 100));
+ if (_storage->uploadStreamSupported()) {
+ _workingRequest = _storage->upload(
+ _storage->savesDirectoryPath() + _currentUploadingFile,
+ g_system->getSavefileManager()->openRawFile(_currentUploadingFile),
+ new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback)
+ );
+ } else {
+ _workingRequest = _storage->upload(
+ _storage->savesDirectoryPath() + _currentUploadingFile,
+ DefaultSaveFileManager::concatWithSavesPath(_currentUploadingFile),
+ new Common::Callback<SavesSyncRequest, Storage::UploadResponse>(this, &SavesSyncRequest::fileUploadedCallback),
+ new Common::Callback<SavesSyncRequest, Networking::ErrorResponse>(this, &SavesSyncRequest::fileUploadedErrorCallback)
+ );
+ }
+ if (!_workingRequest) finishError(Networking::ErrorResponse(this));
+}
+
+void SavesSyncRequest::fileUploadedCallback(Storage::UploadResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //update local timestamp for the uploaded file
+ _localFilesTimestamps = DefaultSaveFileManager::loadTimestamps();
+ _localFilesTimestamps[_currentUploadingFile] = response.value.timestamp();
+ DefaultSaveFileManager::saveTimestamps(_localFilesTimestamps);
+
+ //continue uploading files
+ uploadNextFile();
+}
+
+void SavesSyncRequest::fileUploadedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ //stop syncing if upload failed
+ finishError(error);
+}
+
+void SavesSyncRequest::handle() {}
+
+void SavesSyncRequest::restart() { start(); }
+
+double SavesSyncRequest::getDownloadingProgress() const {
+ if (_totalFilesToHandle == 0) {
+ if (_state == Networking::FINISHED)
+ return 1; //nothing to upload and download => Request ends soon
+ return 0; //directory not listed yet
+ }
+
+ if (_totalFilesToHandle == _filesToUpload.size())
+ return 1; //nothing to download => download complete
+
+ uint32 totalFilesToDownload = _totalFilesToHandle - _filesToUpload.size();
+ uint32 filesLeftToDownload = _filesToDownload.size() + (_currentDownloadingFile.name() != "" ? 1 : 0);
+ return (double)(totalFilesToDownload - filesLeftToDownload) / (double)(totalFilesToDownload);
+}
+
+double SavesSyncRequest::getProgress() const {
+ if (_totalFilesToHandle == 0) {
+ if (_state == Networking::FINISHED)
+ return 1; //nothing to upload and download => Request ends soon
+ return 0; //directory not listed yet
+ }
+
+ return (double)(_totalFilesToHandle - _filesToDownload.size() - _filesToUpload.size()) / (double)(_totalFilesToHandle);
+}
+
+Common::Array<Common::String> SavesSyncRequest::getFilesToDownload() {
+ Common::Array<Common::String> result;
+ for (uint32 i = 0; i < _filesToDownload.size(); ++i)
+ result.push_back(_filesToDownload[i].name());
+ if (_currentDownloadingFile.name() != "")
+ result.push_back(_currentDownloadingFile.name());
+ return result;
+}
+
+void SavesSyncRequest::finishError(Networking::ErrorResponse error) {
+ debug(9, "SavesSync::finishError");
+ //if we were downloading a file - remember the name
+ //and make the Request close() it, so we can delete it
+ Common::String name = _currentDownloadingFile.name();
+ if (_workingRequest) {
+ _ignoreCallback = true;
+ _workingRequest->finish();
+ _workingRequest = nullptr;
+ _ignoreCallback = false;
+ }
+ //unlock all the files by making getFilesToDownload() return empty array
+ _currentDownloadingFile = StorageFile();
+ _filesToDownload.clear();
+ //delete the incomplete file
+ if (name != "")
+ g_system->getSavefileManager()->removeSavefile(name);
+ Request::finishError(error);
+}
+
+void SavesSyncRequest::finishSync(bool success) {
+ Request::finishSuccess();
+
+ //update last successful sync date
+ CloudMan.setStorageLastSync(CloudMan.getStorageIndex(), _date);
+
+ if (_boolCallback)
+ (*_boolCallback)(Storage::BoolResponse(this, success));
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/savessyncrequest.h b/backends/cloud/savessyncrequest.h
new file mode 100644
index 0000000000..e891e93969
--- /dev/null
+++ b/backends/cloud/savessyncrequest.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 BACKENDS_CLOUD_SAVESSYNCREQUEST_H
+#define BACKENDS_CLOUD_SAVESSYNCREQUEST_H
+
+#include "backends/networking/curl/request.h"
+#include "backends/cloud/storage.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "gui/object.h"
+
+namespace Cloud {
+
+class SavesSyncRequest: public Networking::Request, public GUI::CommandSender {
+ Storage *_storage;
+ Storage::BoolCallback _boolCallback;
+ Common::HashMap<Common::String, uint32> _localFilesTimestamps;
+ Common::Array<StorageFile> _filesToDownload;
+ Common::Array<Common::String> _filesToUpload;
+ StorageFile _currentDownloadingFile;
+ Common::String _currentUploadingFile;
+ Request *_workingRequest;
+ bool _ignoreCallback;
+ uint32 _totalFilesToHandle;
+ Common::String _date;
+
+ void start();
+ void directoryListedCallback(Storage::ListDirectoryResponse response);
+ void directoryListedErrorCallback(Networking::ErrorResponse error);
+ void directoryCreatedCallback(Storage::BoolResponse response);
+ void directoryCreatedErrorCallback(Networking::ErrorResponse error);
+ void fileDownloadedCallback(Storage::BoolResponse response);
+ void fileDownloadedErrorCallback(Networking::ErrorResponse error);
+ void fileUploadedCallback(Storage::UploadResponse response);
+ void fileUploadedErrorCallback(Networking::ErrorResponse error);
+ void downloadNextFile();
+ void uploadNextFile();
+ virtual void finishError(Networking::ErrorResponse error);
+ void finishSync(bool success);
+
+public:
+ SavesSyncRequest(Storage *storage, Storage::BoolCallback callback, Networking::ErrorCallback ecb);
+ virtual ~SavesSyncRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getDownloadingProgress() const;
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+
+ /** Returns an array of saves names which are not downloaded yet. */
+ Common::Array<Common::String> getFilesToDownload();
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/storage.cpp b/backends/cloud/storage.cpp
new file mode 100644
index 0000000000..910d80d153
--- /dev/null
+++ b/backends/cloud/storage.cpp
@@ -0,0 +1,342 @@
+/* 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 "backends/cloud/storage.h"
+#include "backends/cloud/downloadrequest.h"
+#include "backends/cloud/folderdownloadrequest.h"
+#include "backends/cloud/savessyncrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "common/debug.h"
+#include "common/file.h"
+#include <common/translation.h>
+
+namespace Cloud {
+
+Storage::Storage():
+ _runningRequestsCount(0), _savesSyncRequest(nullptr), _syncRestartRequestsed(false),
+ _downloadFolderRequest(nullptr) {}
+
+Storage::~Storage() {}
+
+Networking::ErrorCallback Storage::getErrorPrintingCallback() {
+ return new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::printErrorResponse);
+}
+
+void Storage::printErrorResponse(Networking::ErrorResponse error) {
+ debug(9, "Storage: error response (%s, %ld):", (error.failed ? "failed" : "interrupted"), error.httpResponseCode);
+ debug(9, "%s", error.response.c_str());
+}
+
+Networking::Request *Storage::addRequest(Networking::Request *request) {
+ _runningRequestsMutex.lock();
+ ++_runningRequestsCount;
+ if (_runningRequestsCount == 1)
+ debug(9, "Storage is working now");
+ _runningRequestsMutex.unlock();
+ return ConnMan.addRequest(request, new Common::Callback<Storage, Networking::Request *>(this, &Storage::requestFinishedCallback));
+}
+
+void Storage::requestFinishedCallback(Networking::Request *invalidRequestPointer) {
+ bool restartSync = false;
+
+ _runningRequestsMutex.lock();
+ if (invalidRequestPointer == _savesSyncRequest)
+ _savesSyncRequest = nullptr;
+ --_runningRequestsCount;
+ if (_syncRestartRequestsed)
+ restartSync = true;
+ if (_runningRequestsCount == 0 && !restartSync)
+ debug(9, "Storage is not working now");
+ _runningRequestsMutex.unlock();
+
+ if (restartSync)
+ syncSaves(nullptr, nullptr);
+}
+
+Networking::Request *Storage::upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback) errorCallback = getErrorPrintingCallback();
+
+ Common::File *f = new Common::File();
+ if (!f->open(localPath)) {
+ warning("Storage: unable to open file to upload from");
+ if (errorCallback)
+ (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1));
+ delete errorCallback;
+ delete callback;
+ delete f;
+ return nullptr;
+ }
+
+ return upload(remotePath, f, callback, errorCallback);
+}
+
+bool Storage::uploadStreamSupported() {
+ return true;
+}
+
+Networking::Request *Storage::streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) {
+ //most Storages use paths instead of ids, so this should work
+ return streamFileById(path, callback, errorCallback);
+}
+
+Networking::Request *Storage::download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ //most Storages use paths instead of ids, so this should work
+ return downloadById(remotePath, localPath, callback, errorCallback);
+}
+
+Networking::Request *Storage::downloadById(Common::String remoteId, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ if (!errorCallback) errorCallback = getErrorPrintingCallback();
+
+ Common::DumpFile *f = new Common::DumpFile();
+ if (!f->open(localPath, true)) {
+ warning("Storage: unable to open file to download into");
+ if (errorCallback) (*errorCallback)(Networking::ErrorResponse(nullptr, false, true, "", -1));
+ delete errorCallback;
+ delete callback;
+ delete f;
+ return nullptr;
+ }
+
+ return addRequest(new DownloadRequest(this, callback, errorCallback, remoteId, f));
+}
+
+Networking::Request *Storage::downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive) {
+ if (!errorCallback)
+ errorCallback = getErrorPrintingCallback();
+ return addRequest(new FolderDownloadRequest(this, callback, errorCallback, remotePath, localPath, recursive));
+}
+
+SavesSyncRequest *Storage::syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback) {
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest) {
+ warning("Storage::syncSaves: there is a sync in progress already");
+ _syncRestartRequestsed = true;
+ _runningRequestsMutex.unlock();
+ return _savesSyncRequest;
+ }
+ if (!callback)
+ callback = new Common::Callback<Storage, BoolResponse>(this, &Storage::savesSyncDefaultCallback);
+ if (!errorCallback)
+ errorCallback = new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::savesSyncDefaultErrorCallback);
+ _savesSyncRequest = new SavesSyncRequest(this, callback, errorCallback);
+ _syncRestartRequestsed = false;
+ _runningRequestsMutex.unlock();
+ return (SavesSyncRequest *)addRequest(_savesSyncRequest); //who knows what that ConnMan could return in the future
+}
+
+bool Storage::isWorking() {
+ _runningRequestsMutex.lock();
+ bool working = _runningRequestsCount > 0;
+ _runningRequestsMutex.unlock();
+ return working;
+}
+
+///// SavesSyncRequest-related /////
+
+bool Storage::isSyncing() {
+ _runningRequestsMutex.lock();
+ bool syncing = _savesSyncRequest != nullptr;
+ _runningRequestsMutex.unlock();
+ return syncing;
+}
+
+double Storage::getSyncDownloadingProgress() {
+ double result = 1;
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ result = _savesSyncRequest->getDownloadingProgress();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+double Storage::getSyncProgress() {
+ double result = 1;
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ result = _savesSyncRequest->getProgress();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+Common::Array<Common::String> Storage::getSyncingFiles() {
+ Common::Array<Common::String> result;
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ result = _savesSyncRequest->getFilesToDownload();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+void Storage::cancelSync() {
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ _savesSyncRequest->finish();
+ _runningRequestsMutex.unlock();
+}
+
+void Storage::setSyncTarget(GUI::CommandReceiver *target) {
+ _runningRequestsMutex.lock();
+ if (_savesSyncRequest)
+ _savesSyncRequest->setTarget(target);
+ _runningRequestsMutex.unlock();
+}
+
+void Storage::savesSyncDefaultCallback(BoolResponse response) {
+ _runningRequestsMutex.lock();
+ _savesSyncRequest = nullptr;
+ _runningRequestsMutex.unlock();
+
+ if (!response.value)
+ warning("SavesSyncRequest called success callback with `false` argument");
+ g_system->displayMessageOnOSD(_("Saves sync complete."));
+}
+
+void Storage::savesSyncDefaultErrorCallback(Networking::ErrorResponse error) {
+ _runningRequestsMutex.lock();
+ _savesSyncRequest = nullptr;
+ _runningRequestsMutex.unlock();
+
+ printErrorResponse(error);
+
+ if (error.interrupted)
+ g_system->displayMessageOnOSD(_("Saves sync was cancelled."));
+ else
+ g_system->displayMessageOnOSD(_("Saves sync failed.\nCheck your Internet connection."));
+}
+
+///// DownloadFolderRequest-related /////
+
+bool Storage::startDownload(Common::String remotePath, Common::String localPath) {
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest) {
+ warning("Storage::startDownload: there is a download in progress already");
+ _runningRequestsMutex.unlock();
+ return false;
+ }
+ _downloadFolderRequest = (FolderDownloadRequest *)downloadFolder(
+ remotePath, localPath,
+ new Common::Callback<Storage, FileArrayResponse>(this, &Storage::directoryDownloadedCallback),
+ new Common::Callback<Storage, Networking::ErrorResponse>(this, &Storage::directoryDownloadedErrorCallback),
+ true
+ );
+ _runningRequestsMutex.unlock();
+ return true;
+}
+
+void Storage::cancelDownload() {
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ _downloadFolderRequest->finish();
+ _runningRequestsMutex.unlock();
+}
+
+void Storage::setDownloadTarget(GUI::CommandReceiver *target) {
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ _downloadFolderRequest->setTarget(target);
+ _runningRequestsMutex.unlock();
+}
+
+bool Storage::isDownloading() {
+ _runningRequestsMutex.lock();
+ bool syncing = _downloadFolderRequest != nullptr;
+ _runningRequestsMutex.unlock();
+ return syncing;
+}
+
+double Storage::getDownloadingProgress() {
+ double result = 1;
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getProgress();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+uint64 Storage::getDownloadBytesNumber() {
+ uint64 result = 0;
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getDownloadedBytes();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+uint64 Storage::getDownloadTotalBytesNumber() {
+ uint64 result = 0;
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getTotalBytesToDownload();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+uint64 Storage::getDownloadSpeed() {
+ uint64 result = 0;
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getDownloadSpeed();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+Common::String Storage::getDownloadRemoteDirectory() {
+ Common::String result = "";
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getRemotePath();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+Common::String Storage::getDownloadLocalDirectory() {
+ Common::String result = "";
+ _runningRequestsMutex.lock();
+ if (_downloadFolderRequest)
+ result = _downloadFolderRequest->getLocalPath();
+ _runningRequestsMutex.unlock();
+ return result;
+}
+
+void Storage::directoryDownloadedCallback(FileArrayResponse response) {
+ _runningRequestsMutex.lock();
+ _downloadFolderRequest = nullptr;
+ _runningRequestsMutex.unlock();
+
+ Common::String message;
+ if (response.value.size()) {
+ message = Common::String::format(_("Download complete.\nFailed to download %u files."), response.value.size());
+ } else {
+ message = _("Download complete.");
+ }
+ g_system->displayMessageOnOSD(message.c_str());
+}
+
+void Storage::directoryDownloadedErrorCallback(Networking::ErrorResponse error) {
+ _runningRequestsMutex.lock();
+ _downloadFolderRequest = nullptr;
+ _runningRequestsMutex.unlock();
+
+ g_system->displayMessageOnOSD(_("Download failed."));
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/storage.h b/backends/cloud/storage.h
new file mode 100644
index 0000000000..6a5765f13a
--- /dev/null
+++ b/backends/cloud/storage.h
@@ -0,0 +1,238 @@
+/* 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 BACKENDS_CLOUD_STORAGE_H
+#define BACKENDS_CLOUD_STORAGE_H
+
+#include "backends/cloud/storagefile.h"
+#include "backends/cloud/storageinfo.h"
+#include "backends/networking/curl/request.h"
+#include "backends/networking/curl/curlrequest.h"
+#include "common/array.h"
+#include "common/callback.h"
+#include "common/mutex.h"
+#include "common/stream.h"
+#include "common/str.h"
+
+namespace GUI {
+
+class CommandReceiver;
+
+}
+
+namespace Cloud {
+
+class SavesSyncRequest;
+class FolderDownloadRequest;
+
+class Storage {
+public:
+ typedef Networking::Response<Common::Array<StorageFile>&> FileArrayResponse;
+ typedef Networking::Response<StorageInfo> StorageInfoResponse;
+ typedef Networking::Response<bool> BoolResponse;
+ typedef Networking::Response<StorageFile> UploadResponse;
+ typedef Networking::Response<Common::Array<StorageFile> &> ListDirectoryResponse;
+
+ typedef Common::BaseCallback<FileArrayResponse> *FileArrayCallback;
+ typedef Common::BaseCallback<StorageInfoResponse> *StorageInfoCallback;
+ typedef Common::BaseCallback<BoolResponse> *BoolCallback;
+ typedef Common::BaseCallback<UploadResponse> *UploadCallback;
+ typedef Common::BaseCallback<ListDirectoryResponse> *ListDirectoryCallback;
+
+protected:
+ /** Keeps track of running requests. */
+ uint32 _runningRequestsCount;
+ Common::Mutex _runningRequestsMutex;
+
+ /** SavesSyncRequest-related */
+ SavesSyncRequest *_savesSyncRequest;
+ bool _syncRestartRequestsed;
+
+ /** FolderDownloadRequest-related */
+ FolderDownloadRequest *_downloadFolderRequest;
+
+ /** Returns default error callback (printErrorResponse). */
+ virtual Networking::ErrorCallback getErrorPrintingCallback();
+
+ /** Prints ErrorResponse contents with debug(). */
+ virtual void printErrorResponse(Networking::ErrorResponse error);
+
+ /**
+ * Adds request to the ConnMan, but also increases _runningRequestsCount.
+ * This method should be used by Storage implementations instead of
+ * direct ConnMan.addRequest() call.
+ *
+ * @return the same Request pointer, just as a shortcut
+ */
+ virtual Networking::Request *addRequest(Networking::Request *request);
+
+ /**
+ * Decreases _runningRequestCount. It's called from ConnMan automatically.
+ * Passed pointer is dangling, but one can use the address to determine
+ * some special Requests (which addresses were remembered somewhere).
+ */
+ virtual void requestFinishedCallback(Networking::Request *invalidRequestPointer);
+
+public:
+ Storage();
+ virtual ~Storage();
+
+ /**
+ * Storage methods, which are used by CloudManager to save
+ * storage in configuration file.
+ */
+
+ /**
+ * Save storage data using ConfMan.
+ * @param keyPrefix all saved keys must start with this prefix.
+ * @note every Storage must write keyPrefix + "type" key
+ * with common value (e.g. "Dropbox").
+ */
+ virtual void saveConfig(Common::String keyPrefix) = 0;
+
+ /**
+ * Return unique storage name.
+ * @returns some unique storage name (for example, "Dropbox (user@example.com)")
+ */
+ virtual Common::String name() const = 0;
+
+ /**
+ * Public Cloud API comes down there.
+ *
+ * All Cloud API methods return Networking::Request *, which
+ * might be used to control request. All methods also accept
+ * a callback, which is called, when request is complete.
+ */
+
+ /** Returns ListDirectoryResponse with list of files. */
+ virtual Networking::Request *listDirectory(Common::String path, ListDirectoryCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false) = 0;
+
+ /** Returns StorageFile with info about uploaded file. */
+ virtual Networking::Request *upload(Common::String path, Common::SeekableReadStream *contents, UploadCallback callback, Networking::ErrorCallback errorCallback) = 0;
+ virtual Networking::Request *upload(Common::String remotePath, Common::String localPath, UploadCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns whether Storage supports upload(ReadStream). */
+ virtual bool uploadStreamSupported();
+
+ /** Returns pointer to Networking::NetworkReadStream. */
+ virtual Networking::Request *streamFile(Common::String path, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *streamFileById(Common::String id, Networking::NetworkReadStreamCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *download(Common::String remotePath, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback);
+ virtual Networking::Request *downloadById(Common::String remoteId, Common::String localPath, BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Returns Common::Array<StorageFile> with list of files, which were not downloaded. */
+ virtual Networking::Request *downloadFolder(Common::String remotePath, Common::String localPath, FileArrayCallback callback, Networking::ErrorCallback errorCallback, bool recursive = false);
+
+ /** Calls the callback when finished. */
+ virtual SavesSyncRequest *syncSaves(BoolCallback callback, Networking::ErrorCallback errorCallback);
+
+ /** Calls the callback when finished. */
+ virtual Networking::Request *createDirectory(Common::String path, BoolCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /**
+ * Returns the StorageInfo struct via <callback>.
+ * Calls the <errorCallback> if failed to get information.
+ *
+ * @note on success Storage should also call
+ * CloudMan.setStorageUsername().
+ */
+ virtual Networking::Request *info(StorageInfoCallback callback, Networking::ErrorCallback errorCallback) = 0;
+
+ /** Returns storage's saves directory path with the trailing slash. */
+ virtual Common::String savesDirectoryPath() = 0;
+
+ /** Returns whether there are any requests running. */
+ virtual bool isWorking();
+
+ ///// SavesSyncRequest-related /////
+
+ /** Returns whether there is a SavesSyncRequest running. */
+ virtual bool isSyncing();
+
+ /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */
+ virtual double getSyncDownloadingProgress();
+
+ /** Returns a number in [0, 1] range which represents current sync progress (1 = complete). */
+ virtual double getSyncProgress();
+
+ /** Returns an array of saves names which are not yet synced (thus cannot be used). */
+ virtual Common::Array<Common::String> getSyncingFiles();
+
+ /** Cancels running sync. */
+ virtual void cancelSync();
+
+ /** Sets SavesSyncRequest's target to given CommandReceiver. */
+ virtual void setSyncTarget(GUI::CommandReceiver *target);
+
+protected:
+ /** Finishes the sync. Shows an OSD message. */
+ virtual void savesSyncDefaultCallback(BoolResponse response);
+
+ /** Finishes the sync. Shows an OSD message. */
+ virtual void savesSyncDefaultErrorCallback(Networking::ErrorResponse error);
+
+public:
+ ///// DownloadFolderRequest-related /////
+
+ /** Starts a folder download. */
+ virtual bool startDownload(Common::String remotePath, Common::String localPath);
+
+ /** Cancels running download. */
+ virtual void cancelDownload();
+
+ /** Sets FolderDownloadRequest's target to given CommandReceiver. */
+ virtual void setDownloadTarget(GUI::CommandReceiver *target);
+
+ /** Returns whether there is a FolderDownloadRequest running. */
+ virtual bool isDownloading();
+
+ /** Returns a number in [0, 1] range which represents current download progress (1 = complete). */
+ virtual double getDownloadingProgress();
+
+ /** Returns a number of bytes that is downloaded in current download progress. */
+ virtual uint64 getDownloadBytesNumber();
+
+ /** Returns a total number of bytes to be downloaded in current download progress. */
+ virtual uint64 getDownloadTotalBytesNumber();
+
+ /** Returns download speed of current download progress. */
+ virtual uint64 getDownloadSpeed();
+
+ /** Returns remote directory path. */
+ virtual Common::String getDownloadRemoteDirectory();
+
+ /** Returns local directory path. */
+ virtual Common::String getDownloadLocalDirectory();
+
+protected:
+ /** Finishes the download. Shows an OSD message. */
+ virtual void directoryDownloadedCallback(FileArrayResponse response);
+
+ /** Finishes the download. Shows an OSD message. */
+ virtual void directoryDownloadedErrorCallback(Networking::ErrorResponse error);
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/storagefile.cpp b/backends/cloud/storagefile.cpp
new file mode 100644
index 0000000000..62d492292d
--- /dev/null
+++ b/backends/cloud/storagefile.cpp
@@ -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.
+*
+*/
+
+#include "backends/cloud/storagefile.h"
+
+namespace Cloud {
+
+StorageFile::StorageFile() {
+ _id = "";
+ _path = "";
+ _name = "";
+ _size = 0;
+ _timestamp = 0;
+ _isDirectory = false;
+}
+
+StorageFile::StorageFile(Common::String pth, uint32 sz, uint32 ts, bool dir) {
+ _id = pth;
+ _path = pth;
+
+ _name = pth;
+ if (_name.size() != 0) {
+ uint32 i = _name.size() - 1;
+ while (true) {
+ if (_name[i] == '/' || _name[i] == '\\') {
+ _name.erase(0, i + 1);
+ break;
+ }
+ if (i == 0)
+ break;
+ --i;
+ }
+ }
+
+ _size = sz;
+ _timestamp = ts;
+ _isDirectory = dir;
+}
+
+StorageFile::StorageFile(Common::String id, Common::String path, Common::String name, uint32 sz, uint32 ts, bool dir) {
+ _id = id;
+ _path = path;
+ _name = name;
+ _size = sz;
+ _timestamp = ts;
+ _isDirectory = dir;
+}
+
+} // End of namespace Cloud
diff --git a/backends/cloud/storagefile.h b/backends/cloud/storagefile.h
new file mode 100644
index 0000000000..1183524f48
--- /dev/null
+++ b/backends/cloud/storagefile.h
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 BACKENDS_CLOUD_STORAGEFILE_H
+#define BACKENDS_CLOUD_STORAGEFILE_H
+
+#include "common/str.h"
+
+namespace Cloud {
+
+/**
+ * StorageFile represents a file storaged on remote cloud storage.
+ * It contains basic information about a file, and might be used
+ * when listing directories or syncing files.
+ *
+ * Some storages (Google Drive, for example) don't have an actual
+ * path notation to address files. Instead, they are using ids.
+ * As resolving id by path is not a fast operation, it's required
+ * to use ids if they are known, but user-friendly paths are
+ * necessary too, because these are used by Requests.
+ *
+ * If storage supports path notation, id would actually contain path.
+ */
+class StorageFile {
+ Common::String _id, _path, _name;
+ uint32 _size, _timestamp;
+ bool _isDirectory;
+
+public:
+ StorageFile(); //invalid empty file
+ StorageFile(Common::String pth, uint32 sz, uint32 ts, bool dir);
+ StorageFile(Common::String id, Common::String path, Common::String name, uint32 sz, uint32 ts, bool dir);
+
+ Common::String id() const { return _id; }
+ Common::String path() const { return _path; }
+ Common::String name() const { return _name; }
+ uint32 size() const { return _size; }
+ uint32 timestamp() const { return _timestamp; }
+ bool isDirectory() const { return _isDirectory; }
+
+ void setPath(Common::String path_) { _path = path_; }
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/cloud/storageinfo.h b/backends/cloud/storageinfo.h
new file mode 100644
index 0000000000..f1fb540974
--- /dev/null
+++ b/backends/cloud/storageinfo.h
@@ -0,0 +1,53 @@
+/* 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 BACKENDS_CLOUD_STORAGEINFO_H
+#define BACKENDS_CLOUD_STORAGEINFO_H
+
+#include "common/str.h"
+
+namespace Cloud {
+
+/**
+* StorageInfo contains information about remote cloud storage.
+* It's disk quota usage, owner name, and such.
+*/
+
+class StorageInfo {
+ Common::String _uid, _name, _email;
+ uint64 _usedBytes, _allocatedBytes;
+
+public:
+ StorageInfo(Common::String uid_, Common::String name_, Common::String email_, uint64 used_, uint64 allocated):
+ _uid(uid_), _name(name_), _email(email_), _usedBytes(used_), _allocatedBytes(allocated) {}
+
+ Common::String uid() const { return _uid; }
+ Common::String name() const { return _name; }
+ Common::String email() const { return _email; }
+ uint64 used() const { return _usedBytes; }
+ uint64 available() const { return _allocatedBytes; }
+
+};
+
+} // End of namespace Cloud
+
+#endif
diff --git a/backends/events/androidsdl/androidsdl-events.cpp b/backends/events/androidsdl/androidsdl-events.cpp
index bd8045ec62..7ea8ff1dc1 100644
--- a/backends/events/androidsdl/androidsdl-events.cpp
+++ b/backends/events/androidsdl/androidsdl-events.cpp
@@ -43,17 +43,16 @@ bool AndroidSdlEventSource::handleMouseButtonDown(SDL_Event &ev, Common::Event &
else if (ev.button.button == SDL_BUTTON_MIDDLE) {
event.type = Common::EVENT_MBUTTONDOWN;
- static int show_onscreen=0;
- if (show_onscreen==0) {
- SDL_ANDROID_SetScreenKeyboardShown(0);
- show_onscreen++;
+ static int show_onscreen = 0;
+ if (show_onscreen == 0) {
+ SDL_ANDROID_SetScreenKeyboardShown(0);
+ show_onscreen++;
+ } else if (show_onscreen==1) {
+ SDL_ANDROID_SetScreenKeyboardShown(1);
+ show_onscreen++;
}
- else if (show_onscreen==1) {
- SDL_ANDROID_SetScreenKeyboardShown(1);
- show_onscreen++;
- }
- if (show_onscreen==2)
- show_onscreen=0;
+ if (show_onscreen == 2)
+ show_onscreen = 0;
}
#endif
else
@@ -68,7 +67,9 @@ bool AndroidSdlEventSource::remapKey(SDL_Event &ev, Common::Event &event) {
if (false) {}
if (ev.key.keysym.sym == SDLK_LCTRL) {
- ev.key.keysym.sym = SDLK_F5;
+ event.type = Common::EVENT_KEYDOWN;
+ event.kbd.keycode = Common::KEYCODE_F5;
+ return true;
} else {
// Let the events fall through if we didn't change them, this may not be the best way to
// set it up, but i'm not sure how sdl would like it if we let if fall through then redid it though.
diff --git a/backends/fs/abstract-fs.h b/backends/fs/abstract-fs.h
index dcfdc08975..24286b649d 100644
--- a/backends/fs/abstract-fs.h
+++ b/backends/fs/abstract-fs.h
@@ -191,6 +191,15 @@ public:
* @return pointer to the stream object, 0 in case of a failure
*/
virtual Common::WriteStream *createWriteStream() = 0;
+
+ /**
+ * Creates a file referred by this node.
+ *
+ * @param isDirectory true if created file must be a directory
+ *
+ * @return true if file is created successfully
+ */
+ virtual bool create(bool isDirectory) = 0;
};
diff --git a/backends/fs/amigaos4/amigaos4-fs.cpp b/backends/fs/amigaos4/amigaos4-fs.cpp
index 6d5b099736..24a8fb98ad 100644
--- a/backends/fs/amigaos4/amigaos4-fs.cpp
+++ b/backends/fs/amigaos4/amigaos4-fs.cpp
@@ -443,4 +443,9 @@ Common::WriteStream *AmigaOSFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
+bool AmigaOSFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //defined(__amigaos4__)
diff --git a/backends/fs/amigaos4/amigaos4-fs.h b/backends/fs/amigaos4/amigaos4-fs.h
index 408354888e..3ed45d3f77 100644
--- a/backends/fs/amigaos4/amigaos4-fs.h
+++ b/backends/fs/amigaos4/amigaos4-fs.h
@@ -116,6 +116,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
diff --git a/backends/fs/chroot/chroot-fs.cpp b/backends/fs/chroot/chroot-fs.cpp
index 2cbb4af9d6..be6805bbd8 100644
--- a/backends/fs/chroot/chroot-fs.cpp
+++ b/backends/fs/chroot/chroot-fs.cpp
@@ -108,6 +108,11 @@ Common::WriteStream *ChRootFilesystemNode::createWriteStream() {
return _realNode->createWriteStream();
}
+bool ChRootFilesystemNode::create(bool /*isDir*/) {
+ error("Not supported");
+ return false;
+}
+
Common::String ChRootFilesystemNode::addPathComponent(const Common::String &path, const Common::String &component) {
const char sep = '/';
if (path.lastChar() == sep && component.firstChar() == sep) {
diff --git a/backends/fs/chroot/chroot-fs.h b/backends/fs/chroot/chroot-fs.h
index 9ff913be31..e0ecc1c47e 100644
--- a/backends/fs/chroot/chroot-fs.h
+++ b/backends/fs/chroot/chroot-fs.h
@@ -49,6 +49,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
private:
static Common::String addPathComponent(const Common::String &path, const Common::String &component);
diff --git a/backends/fs/ds/ds-fs.cpp b/backends/fs/ds/ds-fs.cpp
index 3782caf432..83b1295838 100644
--- a/backends/fs/ds/ds-fs.cpp
+++ b/backends/fs/ds/ds-fs.cpp
@@ -211,6 +211,11 @@ Common::WriteStream *DSFileSystemNode::createWriteStream() {
return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE);
}
+bool DSFileSystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
//////////////////////////////////////////////////////////////////////////
// GBAMPFileSystemNode - File system using GBA Movie Player and CF card //
//////////////////////////////////////////////////////////////////////////
@@ -393,6 +398,11 @@ Common::WriteStream *GBAMPFileSystemNode::createWriteStream() {
return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE);
}
+bool GBAMPFileSystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
DSFileStream::DSFileStream(void *handle) : _handle(handle) {
diff --git a/backends/fs/ds/ds-fs.h b/backends/fs/ds/ds-fs.h
index b9ccfcbe9c..939d1a1555 100644
--- a/backends/fs/ds/ds-fs.h
+++ b/backends/fs/ds/ds-fs.h
@@ -91,6 +91,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
/**
* Returns the zip file this node points to.
@@ -156,6 +157,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
struct fileHandle {
diff --git a/backends/fs/n64/n64-fs.cpp b/backends/fs/n64/n64-fs.cpp
index fe37dad467..d568500d30 100644
--- a/backends/fs/n64/n64-fs.cpp
+++ b/backends/fs/n64/n64-fs.cpp
@@ -160,4 +160,9 @@ Common::WriteStream *N64FilesystemNode::createWriteStream() {
return RomfsStream::makeFromPath(getPath(), true);
}
+bool N64FilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //#ifdef __N64__
diff --git a/backends/fs/n64/n64-fs.h b/backends/fs/n64/n64-fs.h
index d503a6601e..d520ad5be6 100644
--- a/backends/fs/n64/n64-fs.h
+++ b/backends/fs/n64/n64-fs.h
@@ -73,6 +73,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
#endif
diff --git a/backends/fs/posix/posix-fs.cpp b/backends/fs/posix/posix-fs.cpp
index 1a6c4e40a5..746ae6a5a0 100644
--- a/backends/fs/posix/posix-fs.cpp
+++ b/backends/fs/posix/posix-fs.cpp
@@ -39,6 +39,7 @@
#include <dirent.h>
#include <stdio.h>
#include <errno.h>
+#include <fcntl.h>
#ifdef __OS2__
#define INCL_DOS
@@ -252,6 +253,35 @@ Common::WriteStream *POSIXFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
+bool POSIXFilesystemNode::create(bool isDir) {
+ bool success;
+
+ if (isDir) {
+ success = mkdir(_path.c_str(), 0755) == 0;
+ } else {
+ int fd = open(_path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0755);
+ success = fd >= 0;
+
+ if (fd >= 0) {
+ close(fd);
+ }
+ }
+
+ if (success) {
+ setFlags();
+ if (_isValid) {
+ if (_isDirectory != isDir) warning("failed to create %s: got %s", isDir ? "directory" : "file", _isDirectory ? "directory" : "file");
+ return _isDirectory == isDir;
+ }
+
+ warning("POSIXFilesystemNode: %s() was a success, but stat indicates there is no such %s",
+ isDir ? "mkdir" : "creat", isDir ? "directory" : "file");
+ return false;
+ }
+
+ return false;
+}
+
namespace Posix {
bool assureDirectoryExists(const Common::String &dir, const char *prefix) {
diff --git a/backends/fs/posix/posix-fs.h b/backends/fs/posix/posix-fs.h
index 0703ac5bf5..4ebce7e9d9 100644
--- a/backends/fs/posix/posix-fs.h
+++ b/backends/fs/posix/posix-fs.h
@@ -73,6 +73,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
private:
/**
diff --git a/backends/fs/ps2/ps2-fs.cpp b/backends/fs/ps2/ps2-fs.cpp
index 9b6e1270f1..8391e063a0 100644
--- a/backends/fs/ps2/ps2-fs.cpp
+++ b/backends/fs/ps2/ps2-fs.cpp
@@ -441,4 +441,9 @@ Common::WriteStream *Ps2FilesystemNode::createWriteStream() {
return PS2FileStream::makeFromPath(getPath(), true);
}
+bool Ps2FilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif
diff --git a/backends/fs/ps2/ps2-fs.h b/backends/fs/ps2/ps2-fs.h
index 63b866ba5b..c9da434535 100644
--- a/backends/fs/ps2/ps2-fs.h
+++ b/backends/fs/ps2/ps2-fs.h
@@ -96,6 +96,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
int getDev() { return 0; }
};
diff --git a/backends/fs/psp/psp-fs.cpp b/backends/fs/psp/psp-fs.cpp
index e8aad9fa98..6bd5e93435 100644
--- a/backends/fs/psp/psp-fs.cpp
+++ b/backends/fs/psp/psp-fs.cpp
@@ -239,4 +239,9 @@ Common::WriteStream *PSPFilesystemNode::createWriteStream() {
return Common::wrapBufferedWriteStream(stream, WRITE_BUFFER_SIZE);
}
+bool PSPFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //#ifdef __PSP__
diff --git a/backends/fs/psp/psp-fs.h b/backends/fs/psp/psp-fs.h
index 1bb4543d19..648544650b 100644
--- a/backends/fs/psp/psp-fs.h
+++ b/backends/fs/psp/psp-fs.h
@@ -65,6 +65,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
#endif
diff --git a/backends/fs/symbian/symbian-fs.cpp b/backends/fs/symbian/symbian-fs.cpp
index 8fbc3a402a..e4163caa33 100644
--- a/backends/fs/symbian/symbian-fs.cpp
+++ b/backends/fs/symbian/symbian-fs.cpp
@@ -231,4 +231,10 @@ Common::SeekableReadStream *SymbianFilesystemNode::createReadStream() {
Common::WriteStream *SymbianFilesystemNode::createWriteStream() {
return SymbianStdioStream::makeFromPath(getPath(), true);
}
+
+bool SymbianFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //#if defined(__SYMBIAN32__)
diff --git a/backends/fs/symbian/symbian-fs.h b/backends/fs/symbian/symbian-fs.h
index 339e998a28..1327f9f2e9 100644
--- a/backends/fs/symbian/symbian-fs.h
+++ b/backends/fs/symbian/symbian-fs.h
@@ -66,6 +66,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
#endif
diff --git a/backends/fs/wii/wii-fs.cpp b/backends/fs/wii/wii-fs.cpp
index 43f4f592b7..46041bf499 100644
--- a/backends/fs/wii/wii-fs.cpp
+++ b/backends/fs/wii/wii-fs.cpp
@@ -213,4 +213,9 @@ Common::WriteStream *WiiFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
+bool WiiFilesystemNode::create(bool isDirectory) {
+ error("Not supported");
+ return false;
+}
+
#endif //#if defined(__WII__)
diff --git a/backends/fs/wii/wii-fs.h b/backends/fs/wii/wii-fs.h
index c77c543dae..affb765884 100644
--- a/backends/fs/wii/wii-fs.h
+++ b/backends/fs/wii/wii-fs.h
@@ -69,6 +69,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
};
#endif
diff --git a/backends/fs/windows/windows-fs.cpp b/backends/fs/windows/windows-fs.cpp
index 49549b83cb..b43686f911 100644
--- a/backends/fs/windows/windows-fs.cpp
+++ b/backends/fs/windows/windows-fs.cpp
@@ -244,4 +244,36 @@ Common::WriteStream *WindowsFilesystemNode::createWriteStream() {
return StdioStream::makeFromPath(getPath(), true);
}
+bool WindowsFilesystemNode::create(bool isDirectory) {
+ bool success;
+
+ if (isDirectory) {
+ success = CreateDirectory(toUnicode(_path.c_str()), NULL) != 0;
+ } else {
+ success = CreateFile(toUnicode(_path.c_str()), GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) != INVALID_HANDLE_VALUE;
+ }
+
+ if (success) {
+ //this piece is copied from constructor, it checks that file exists and detects whether it's a directory
+ DWORD fileAttribs = GetFileAttributes(toUnicode(_path.c_str()));
+ if (fileAttribs != INVALID_FILE_ATTRIBUTES) {
+ _isDirectory = ((fileAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0);
+ _isValid = true;
+ // Add a trailing slash, if necessary.
+ if (_isDirectory && _path.lastChar() != '\\') {
+ _path += '\\';
+ }
+
+ if (_isDirectory != isDirectory) warning("failed to create %s: got %s", isDirectory ? "directory" : "file", _isDirectory ? "directory" : "file");
+ return _isDirectory == isDirectory;
+ }
+
+ warning("WindowsFilesystemNode: Create%s() was a success, but GetFileAttributes() indicates there is no such %s",
+ isDirectory ? "Directory" : "File", isDirectory ? "directory" : "file");
+ return false;
+ }
+
+ return false;
+}
+
#endif //#ifdef WIN32
diff --git a/backends/fs/windows/windows-fs.h b/backends/fs/windows/windows-fs.h
index d06044603a..7c9f2c1e1a 100644
--- a/backends/fs/windows/windows-fs.h
+++ b/backends/fs/windows/windows-fs.h
@@ -88,6 +88,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream();
+ virtual bool create(bool isDirectory);
private:
/**
diff --git a/backends/graphics/graphics.h b/backends/graphics/graphics.h
index 3671b9f0b9..921dfca61c 100644
--- a/backends/graphics/graphics.h
+++ b/backends/graphics/graphics.h
@@ -84,6 +84,10 @@ public:
virtual void setCursorPalette(const byte *colors, uint start, uint num) = 0;
virtual void displayMessageOnOSD(const char *msg) {}
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {}
+ virtual void clearOSD() {}
+ virtual Graphics::PixelFormat getOSDFormat() { return Graphics::PixelFormat(); }
+
// Graphics::PaletteManager interface
//virtual void setPalette(const byte *colors, uint start, uint num) = 0;
diff --git a/backends/graphics/opengl/opengl-graphics.cpp b/backends/graphics/opengl/opengl-graphics.cpp
index 4d6a00a3b3..c491b03f1f 100644
--- a/backends/graphics/opengl/opengl-graphics.cpp
+++ b/backends/graphics/opengl/opengl-graphics.cpp
@@ -751,6 +751,31 @@ void OpenGLGraphicsManager::displayMessageOnOSD(const char *msg) {
#endif
}
+void OpenGLGraphicsManager::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {
+#ifdef USE_OSD
+ warning("implement copyRectToOSD"); //TODO
+#endif
+}
+
+void OpenGLGraphicsManager::clearOSD() {
+#ifdef USE_OSD
+ // HACK: Actually no client code should use graphics functions from
+ // another thread. But the MT-32 emulator still does, thus we need to
+ // make sure this doesn't happen while a updateScreen call is done.
+ Common::StackLock lock(_osdMutex);
+
+ Graphics::Surface *dst = _osd->getSurface();
+ _osd->fill(0);
+ _osd->flagDirty();
+
+ // Init the OSD display parameters.
+ _osdAlpha = kOSDInitialAlpha;
+ _osdFadeStartTime = g_system->getMillis() + kOSDFadeOutDelay;
+#endif
+}
+
+Graphics::PixelFormat OpenGLGraphicsManager::getOSDFormat() { return Graphics::PixelFormat(); } //TODO
+
void OpenGLGraphicsManager::setPalette(const byte *colors, uint start, uint num) {
assert(_gameScreen->hasPalette());
diff --git a/backends/graphics/opengl/opengl-graphics.h b/backends/graphics/opengl/opengl-graphics.h
index 35435c156e..55d2c5c826 100644
--- a/backends/graphics/opengl/opengl-graphics.h
+++ b/backends/graphics/opengl/opengl-graphics.h
@@ -115,6 +115,9 @@ public:
virtual void setCursorPalette(const byte *colors, uint start, uint num);
virtual void displayMessageOnOSD(const char *msg);
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h);
+ virtual void clearOSD();
+ virtual Graphics::PixelFormat getOSDFormat();
// PaletteManager interface
virtual void setPalette(const byte *colors, uint start, uint num);
diff --git a/backends/graphics/opengl/opengl-sys.h b/backends/graphics/opengl/opengl-sys.h
index 4495128f32..7b531cc140 100644
--- a/backends/graphics/opengl/opengl-sys.h
+++ b/backends/graphics/opengl/opengl-sys.h
@@ -48,9 +48,15 @@
// 0 - Force OpenGL context
// 1 - Force OpenGL ES context
// 2 - Force OpenGL ES 2.0 context
-#define USE_FORCED_GL (defined(USE_GLES_MODE) && USE_GLES_MODE == 0)
-#define USE_FORCED_GLES (defined(USE_GLES_MODE) && USE_GLES_MODE == 1)
-#define USE_FORCED_GLES2 (defined(USE_GLES_MODE) && USE_GLES_MODE == 2)
+#ifdef USE_GLES_MODE
+ #define USE_FORCED_GL (USE_GLES_MODE == 0)
+ #define USE_FORCED_GLES (USE_GLES_MODE == 1)
+ #define USE_FORCED_GLES2 (USE_GLES_MODE == 2)
+#else
+ #define USE_FORCED_GL 0
+ #define USE_FORCED_GLES 0
+ #define USE_FORCED_GLES2 0
+#endif
// On Tizen we include the toolchain's OpenGL file. This is something we
// actually want to avoid. However, since Tizen uses eglGetProcAddress which
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
index 5b591e77ff..fdf21010e7 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.cpp
@@ -121,7 +121,7 @@ SurfaceSdlGraphicsManager::SurfaceSdlGraphicsManager(SdlEventSource *sdlEventSou
:
SdlGraphicsManager(sdlEventSource, window),
#ifdef USE_OSD
- _osdSurface(0), _osdAlpha(SDL_ALPHA_TRANSPARENT), _osdFadeStartTime(0),
+ _osdSurface(0), _osdMessageSurface(nullptr), _osdMessageAlpha(SDL_ALPHA_TRANSPARENT), _osdMessageFadeStartTime(0),
#endif
_hwscreen(0),
#if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -883,13 +883,16 @@ bool SurfaceSdlGraphicsManager::loadGFXMode() {
_osdSurface = SDL_CreateRGBSurface(SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA,
_hwscreen->w,
_hwscreen->h,
- 16,
- _hwscreen->format->Rmask,
- _hwscreen->format->Gmask,
- _hwscreen->format->Bmask,
- _hwscreen->format->Amask);
+ 32,
+ 0xFF000000,
+ 0x00FF0000,
+ 0x0000FF00,
+ 0x000000FF
+ );
if (_osdSurface == NULL)
error("allocating _osdSurface failed");
+
+ _osdFormat = getSurfaceFormat(_osdSurface);
SDL_SetColorKey(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, kOSDColorKey);
#endif
@@ -946,6 +949,11 @@ void SurfaceSdlGraphicsManager::unloadGFXMode() {
SDL_FreeSurface(_osdSurface);
_osdSurface = NULL;
}
+
+ if (_osdMessageSurface) {
+ SDL_FreeSurface(_osdMessageSurface);
+ _osdMessageSurface = NULL;
+ }
#endif
DestroyScalers();
@@ -1059,21 +1067,33 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
#ifdef USE_OSD
// OSD visible (i.e. non-transparent)?
- if (_osdAlpha != SDL_ALPHA_TRANSPARENT) {
+ if (_osdMessageAlpha != SDL_ALPHA_TRANSPARENT) {
// Updated alpha value
- const int diff = SDL_GetTicks() - _osdFadeStartTime;
+ const int diff = SDL_GetTicks() - _osdMessageFadeStartTime;
if (diff > 0) {
if (diff >= kOSDFadeOutDuration) {
// Back to full transparency
- _osdAlpha = SDL_ALPHA_TRANSPARENT;
+ _osdMessageAlpha = SDL_ALPHA_TRANSPARENT;
} else {
// Do a linear fade out...
const int startAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100;
- _osdAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration;
+ _osdMessageAlpha = startAlpha + diff * (SDL_ALPHA_TRANSPARENT - startAlpha) / kOSDFadeOutDuration;
}
- SDL_SetAlpha(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _osdAlpha);
_forceFull = true;
}
+
+ if (_osdMessageAlpha == SDL_ALPHA_TRANSPARENT) {
+ removeOSDMessage();
+ } else {
+ if (_osdMessageSurface && _osdSurface) {
+ SDL_Rect dstRect;
+ dstRect.x = (_osdSurface->w - _osdMessageSurface->w) / 2;
+ dstRect.y = (_osdSurface->h - _osdMessageSurface->h) / 2;
+ dstRect.w = _osdMessageSurface->w;
+ dstRect.h = _osdMessageSurface->h;
+ blitOSDMessage(dstRect);
+ }
+ }
}
#endif
@@ -1179,9 +1199,7 @@ void SurfaceSdlGraphicsManager::internUpdateScreen() {
drawMouse();
#ifdef USE_OSD
- if (_osdAlpha != SDL_ALPHA_TRANSPARENT) {
- SDL_BlitSurface(_osdSurface, 0, _hwscreen, 0);
- }
+ SDL_BlitSurface(_osdSurface, 0, _hwscreen, 0);
#endif
#ifdef USE_SDL_DEBUG_FOCUSRECT
@@ -2121,26 +2139,11 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) {
Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
- uint i;
-
- // Lock the OSD surface for drawing
- if (SDL_LockSurface(_osdSurface))
- error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
-
- Graphics::Surface dst;
- dst.init(_osdSurface->w, _osdSurface->h, _osdSurface->pitch, _osdSurface->pixels,
- Graphics::PixelFormat(_osdSurface->format->BytesPerPixel,
- 8 - _osdSurface->format->Rloss, 8 - _osdSurface->format->Gloss,
- 8 - _osdSurface->format->Bloss, 8 - _osdSurface->format->Aloss,
- _osdSurface->format->Rshift, _osdSurface->format->Gshift,
- _osdSurface->format->Bshift, _osdSurface->format->Ashift));
+ removeOSDMessage();
// The font we are going to use:
const Graphics::Font *font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
- // Clear everything with the "transparent" color, i.e. the colorkey
- SDL_FillRect(_osdSurface, 0, kOSDColorKey);
-
// Split the message into separate lines.
Common::Array<Common::String> lines;
const char *ptr;
@@ -2159,44 +2162,184 @@ void SurfaceSdlGraphicsManager::displayMessageOnOSD(const char *msg) {
const int lineHeight = font->getFontHeight() + 2 * lineSpacing;
int width = 0;
int height = lineHeight * lines.size() + 2 * vOffset;
+ uint i;
for (i = 0; i < lines.size(); i++) {
width = MAX(width, font->getStringWidth(lines[i]) + 14);
}
// Clip the rect
- if (width > dst.w)
- width = dst.w;
- if (height > dst.h)
- height = dst.h;
+ if (width > _osdSurface->w)
+ width = _osdSurface->w;
+ if (height > _osdSurface->h)
+ height = _osdSurface->h;
+
+ _osdMessageSurface = SDL_CreateRGBSurface(
+ SDL_SWSURFACE | SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA,
+ width, height, 32, 0xFF000000, 0x00FF0000, 0x0000FF00, 0x000000FF
+ );
+
+ // Lock the surface
+ if (SDL_LockSurface(_osdMessageSurface))
+ error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
// Draw a dark gray rect
- // TODO: Rounded corners ? Border?
- SDL_Rect osdRect;
- osdRect.x = (dst.w - width) / 2;
- osdRect.y = (dst.h - height) / 2;
- osdRect.w = width;
- osdRect.h = height;
- SDL_FillRect(_osdSurface, &osdRect, SDL_MapRGB(_osdSurface->format, 64, 64, 64));
+ // TODO: Rounded corners ? Border?
+ SDL_FillRect(_osdMessageSurface, nullptr, SDL_MapRGB(_osdMessageSurface->format, 64, 64, 64));
+
+ Graphics::Surface dst;
+ dst.init(_osdMessageSurface->w, _osdMessageSurface->h, _osdMessageSurface->pitch, _osdMessageSurface->pixels,
+ Graphics::PixelFormat(_osdMessageSurface->format->BytesPerPixel,
+ 8 - _osdMessageSurface->format->Rloss, 8 - _osdMessageSurface->format->Gloss,
+ 8 - _osdMessageSurface->format->Bloss, 8 - _osdMessageSurface->format->Aloss,
+ _osdMessageSurface->format->Rshift, _osdMessageSurface->format->Gshift,
+ _osdMessageSurface->format->Bshift, _osdMessageSurface->format->Ashift));
// Render the message, centered, and in white
for (i = 0; i < lines.size(); i++) {
font->drawString(&dst, lines[i],
- osdRect.x, osdRect.y + i * lineHeight + vOffset + lineSpacing, osdRect.w,
- SDL_MapRGB(_osdSurface->format, 255, 255, 255),
- Graphics::kTextAlignCenter);
+ 0, 0 + i * lineHeight + vOffset + lineSpacing, width,
+ SDL_MapRGB(_osdMessageSurface->format, 255, 255, 255),
+ Graphics::kTextAlignCenter);
+ }
+
+ // Finished drawing, so unlock the OSD message surface
+ SDL_UnlockSurface(_osdMessageSurface);
+
+ // Init the OSD display parameters, and the fade out
+ _osdMessageAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100;
+ _osdMessageFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay;
+
+ // Ensure a full redraw takes place next time the screen is updated
+ _forceFull = true;
+}
+
+void SurfaceSdlGraphicsManager::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {
+ assert(_transactionMode == kTransactionNone);
+ assert(buf);
+
+ Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
+
+ // Lock the OSD surface for drawing
+ if (SDL_LockSurface(_osdSurface))
+ error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
+
+ // Mark that area as "dirty"
+ addDirtyRect(x, y, w, h, true);
+
+#ifdef USE_RGB_COLOR
+ byte *dst = (byte *)_osdSurface->pixels + y * _osdSurface->pitch + x * _osdSurface->format->BytesPerPixel;
+ if (_videoMode.screenWidth == w && pitch == _osdSurface->pitch) {
+ memcpy(dst, buf, h*pitch);
+ } else {
+ const byte *src = (const byte *)buf;
+ do {
+ memcpy(dst, src, w * _osdSurface->format->BytesPerPixel);
+ src += pitch;
+ dst += _osdSurface->pitch;
+ } while (--h);
}
+#else
+ byte *dst = (byte *)_osdSurface->pixels + y * _osdSurface->pitch + x;
+ if (_osdSurface->pitch == pitch && pitch == w) {
+ memcpy(dst, buf, h*w);
+ } else {
+ const byte *src = (const byte *)buf;
+ do {
+ memcpy(dst, src, w);
+ src += pitch;
+ dst += _osdSurface->pitch;
+ } while (--h);
+ }
+#endif
// Finished drawing, so unlock the OSD surface again
SDL_UnlockSurface(_osdSurface);
+}
- // Init the OSD display parameters, and the fade out
- _osdAlpha = SDL_ALPHA_TRANSPARENT + kOSDInitialAlpha * (SDL_ALPHA_OPAQUE - SDL_ALPHA_TRANSPARENT) / 100;
- _osdFadeStartTime = SDL_GetTicks() + kOSDFadeOutDelay;
- SDL_SetAlpha(_osdSurface, SDL_RLEACCEL | SDL_SRCCOLORKEY | SDL_SRCALPHA, _osdAlpha);
+void SurfaceSdlGraphicsManager::clearOSD() {
+ assert(_transactionMode == kTransactionNone);
+
+ Common::StackLock lock(_graphicsMutex); // Lock the mutex until this function ends
+
+ // Lock the OSD surface for drawing
+ if (SDL_LockSurface(_osdSurface))
+ error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
+
+ // Clear everything with the "transparent" color, i.e. the colorkey
+ SDL_FillRect(_osdSurface, 0, kOSDColorKey);
+
+ // Finished drawing, so unlock the OSD surface again
+ SDL_UnlockSurface(_osdSurface);
+
+ // Remove OSD message as well
+ removeOSDMessage();
// Ensure a full redraw takes place next time the screen is updated
_forceFull = true;
}
+
+void SurfaceSdlGraphicsManager::removeOSDMessage() {
+ // Lock the OSD surface for drawing
+ if (SDL_LockSurface(_osdSurface))
+ error("displayMessageOnOSD: SDL_LockSurface failed: %s", SDL_GetError());
+
+ //remove previous message
+ if (_osdMessageSurface) {
+ SDL_Rect osdRect;
+ osdRect.x = (_osdSurface->w - _osdMessageSurface->w) / 2;
+ osdRect.y = (_osdSurface->h - _osdMessageSurface->h) / 2;
+ osdRect.w = _osdMessageSurface->w;
+ osdRect.h = _osdMessageSurface->h;
+ SDL_FillRect(_osdSurface, &osdRect, kOSDColorKey);
+ SDL_FreeSurface(_osdMessageSurface);
+ }
+
+ _osdMessageSurface = NULL;
+ _osdMessageAlpha = SDL_ALPHA_TRANSPARENT;
+
+ // Finished drawing, so unlock the OSD surface again
+ SDL_UnlockSurface(_osdSurface);
+}
+
+void SurfaceSdlGraphicsManager::blitOSDMessage(SDL_Rect dstRect) {
+ SDL_Surface *src = _osdMessageSurface;
+ SDL_Surface *dst = _osdSurface;
+ Graphics::PixelFormat srcFormat = getSurfaceFormat(src);
+ Graphics::PixelFormat dstFormat = _osdFormat;
+ for (int y = 0; y < dstRect.h; y++) {
+ const byte *srcRow = (const byte *)((const byte *)(src->pixels) + y * src->pitch); //src (x, y) == (0, 0)
+ byte *dstRow = (byte *)((byte *)(dst->pixels) + (dstRect.y + y) * dst->pitch + dstRect.x * dstFormat.bytesPerPixel);
+
+ for (int x = 0; x < dstRect.w; x++) {
+ uint32 srcColor;
+ if (dst->format->BytesPerPixel == 2)
+ srcColor = READ_UINT16(srcRow);
+ else if (dst->format->BytesPerPixel == 3)
+ srcColor = READ_UINT24(srcRow);
+ else
+ srcColor = READ_UINT32(srcRow);
+
+ srcRow += srcFormat.bytesPerPixel;
+
+ // Convert that color to the new format
+ byte r, g, b, a;
+ srcFormat.colorToARGB(srcColor, a, r, g, b);
+ a = _osdMessageAlpha; //this is the important line, because apart from that this is plain surface copying
+ uint32 color = dstFormat.ARGBToColor(a, r, g, b);
+
+ if (dstFormat.bytesPerPixel == 2)
+ *((uint16 *)dstRow) = color;
+ else
+ *((uint32 *)dstRow) = color;
+
+ dstRow += dstFormat.bytesPerPixel;
+ }
+ }
+}
+
+Graphics::PixelFormat SurfaceSdlGraphicsManager::getOSDFormat() {
+ return _osdFormat;
+}
#endif
bool SurfaceSdlGraphicsManager::handleScalerHotkeys(Common::KeyCode key) {
@@ -2492,4 +2635,22 @@ void SurfaceSdlGraphicsManager::SDL_UpdateRects(SDL_Surface *screen, int numrect
}
#endif // SDL_VERSION_ATLEAST(2, 0, 0)
+Graphics::PixelFormat SurfaceSdlGraphicsManager::getSurfaceFormat(SDL_Surface *surface) {
+ Graphics::PixelFormat format;
+ if (surface) {
+ format.bytesPerPixel = surface->format->BytesPerPixel;
+
+ format.rLoss = surface->format->Rloss;
+ format.gLoss = surface->format->Gloss;
+ format.bLoss = surface->format->Bloss;
+ format.aLoss = surface->format->Aloss;
+
+ format.rShift = surface->format->Rshift;
+ format.gShift = surface->format->Gshift;
+ format.bShift = surface->format->Bshift;
+ format.aShift = surface->format->Ashift;
+ }
+ return format;
+}
+
#endif
diff --git a/backends/graphics/surfacesdl/surfacesdl-graphics.h b/backends/graphics/surfacesdl/surfacesdl-graphics.h
index 25d6ff041c..d8f826aca0 100644
--- a/backends/graphics/surfacesdl/surfacesdl-graphics.h
+++ b/backends/graphics/surfacesdl/surfacesdl-graphics.h
@@ -145,6 +145,9 @@ public:
#ifdef USE_OSD
virtual void displayMessageOnOSD(const char *msg);
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h);
+ virtual void clearOSD();
+ virtual Graphics::PixelFormat getOSDFormat();
#endif
// Override from Common::EventObserver
@@ -160,12 +163,14 @@ public:
protected:
#ifdef USE_OSD
- /** Surface containing the OSD message */
+ /** Surface containing the OSD */
SDL_Surface *_osdSurface;
- /** Transparency level of the OSD */
- uint8 _osdAlpha;
+ /** Surface containing the OSD message */
+ SDL_Surface *_osdMessageSurface;
+ /** Transparency level of the OSD message */
+ uint8 _osdMessageAlpha;
/** When to start the fade out */
- uint32 _osdFadeStartTime;
+ uint32 _osdMessageFadeStartTime;
/** Enum with OSD options */
enum {
kOSDFadeOutDelay = 2 * 1000, /** < Delay before the OSD is faded out (in milliseconds) */
@@ -173,6 +178,11 @@ protected:
kOSDColorKey = 1, /** < Transparent color key */
kOSDInitialAlpha = 80 /** < Initial alpha level, in percent */
};
+ /** OSD pixel format */
+ Graphics::PixelFormat _osdFormat;
+
+ void removeOSDMessage();
+ void blitOSDMessage(SDL_Rect dstRect);
#endif
/** Hardware screen */
@@ -358,6 +368,8 @@ protected:
Common::Rect _focusRect;
#endif
+ static Graphics::PixelFormat getSurfaceFormat(SDL_Surface *surface);
+
virtual void addDirtyRect(int x, int y, int w, int h, bool realCoordinates = false);
virtual void drawMouse();
diff --git a/backends/modular-backend.cpp b/backends/modular-backend.cpp
index d8be9ca7ed..e1bdf15571 100644
--- a/backends/modular-backend.cpp
+++ b/backends/modular-backend.cpp
@@ -241,6 +241,18 @@ void ModularBackend::displayMessageOnOSD(const char *msg) {
_graphicsManager->displayMessageOnOSD(msg);
}
+void ModularBackend::copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) {
+ _graphicsManager->copyRectToOSD(buf, pitch, x, y, w, h);
+}
+
+void ModularBackend::clearOSD() {
+ _graphicsManager->clearOSD();
+}
+
+Graphics::PixelFormat ModularBackend::getOSDFormat() {
+ return _graphicsManager->getOSDFormat();
+}
+
void ModularBackend::quit() {
exit(0);
}
diff --git a/backends/modular-backend.h b/backends/modular-backend.h
index 20e8b7357d..9cde27915f 100644
--- a/backends/modular-backend.h
+++ b/backends/modular-backend.h
@@ -127,6 +127,9 @@ public:
virtual void quit();
virtual void displayMessageOnOSD(const char *msg);
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h);
+ virtual void clearOSD();
+ virtual Graphics::PixelFormat getOSDFormat();
//@}
diff --git a/backends/module.mk b/backends/module.mk
index 4c1ca42f06..c402a10a90 100644
--- a/backends/module.mk
+++ b/backends/module.mk
@@ -19,6 +19,65 @@ MODULE_OBJS := \
saves/default/default-saves.o \
timer/default/default-timer.o
+ifdef USE_LIBCURL
+MODULE_OBJS += \
+ cloud/cloudmanager.o \
+ cloud/iso8601.o \
+ cloud/storage.o \
+ cloud/storagefile.o \
+ cloud/downloadrequest.o \
+ cloud/folderdownloadrequest.o \
+ cloud/savessyncrequest.o \
+ cloud/box/boxstorage.o \
+ cloud/box/boxlistdirectorybyidrequest.o \
+ cloud/box/boxtokenrefresher.o \
+ cloud/box/boxuploadrequest.o \
+ cloud/dropbox/dropboxstorage.o \
+ cloud/dropbox/dropboxcreatedirectoryrequest.o \
+ cloud/dropbox/dropboxinforequest.o \
+ cloud/dropbox/dropboxlistdirectoryrequest.o \
+ cloud/dropbox/dropboxuploadrequest.o \
+ cloud/googledrive/googledrivelistdirectorybyidrequest.o \
+ cloud/googledrive/googledrivestorage.o \
+ cloud/googledrive/googledrivetokenrefresher.o \
+ cloud/googledrive/googledriveuploadrequest.o \
+ cloud/id/idstorage.o \
+ cloud/id/idcreatedirectoryrequest.o \
+ cloud/id/iddownloadrequest.o \
+ cloud/id/idlistdirectoryrequest.o \
+ cloud/id/idresolveidrequest.o \
+ cloud/id/idstreamfilerequest.o \
+ cloud/onedrive/onedrivestorage.o \
+ cloud/onedrive/onedrivecreatedirectoryrequest.o \
+ cloud/onedrive/onedrivetokenrefresher.o \
+ cloud/onedrive/onedrivelistdirectoryrequest.o \
+ cloud/onedrive/onedriveuploadrequest.o \
+ networking/curl/connectionmanager.o \
+ networking/curl/networkreadstream.o \
+ networking/curl/cloudicon.o \
+ networking/curl/curlrequest.o \
+ networking/curl/curljsonrequest.o \
+ networking/curl/request.o
+endif
+
+ifdef USE_SDL_NET
+MODULE_OBJS += \
+ networking/sdl_net/client.o \
+ networking/sdl_net/getclienthandler.o \
+ networking/sdl_net/handlers/createdirectoryhandler.o \
+ networking/sdl_net/handlers/downloadfilehandler.o \
+ networking/sdl_net/handlers/filesajaxpagehandler.o \
+ networking/sdl_net/handlers/filesbasehandler.o \
+ networking/sdl_net/handlers/filespagehandler.o \
+ networking/sdl_net/handlers/indexpagehandler.o \
+ networking/sdl_net/handlers/listajaxhandler.o \
+ networking/sdl_net/handlers/resourcehandler.o \
+ networking/sdl_net/handlers/uploadfilehandler.o \
+ networking/sdl_net/handlerutils.o \
+ networking/sdl_net/localwebserver.o \
+ networking/sdl_net/reader.o \
+ networking/sdl_net/uploadfileclienthandler.o
+endif
ifdef USE_ELF_LOADER
MODULE_OBJS += \
@@ -91,6 +150,42 @@ MODULE_OBJS += \
endif
endif
+# openUrl
+ifeq ($(BACKEND),android)
+MODULE_OBJS += \
+ networking/browser/openurl-android.o
+else
+ifdef MACOSX
+MODULE_OBJS += \
+ networking/browser/openurl-osx.o
+else
+ifdef WIN32
+MODULE_OBJS += \
+ networking/browser/openurl-windows.o
+else
+ ifdef POSIX
+ MODULE_OBJS += \
+ networking/browser/openurl-posix.o
+ else
+ # create_project doesn't know something about `else`
+ ifndef WIN32
+ MODULE_OBJS += \
+ networking/browser/openurl-default.o
+ endif
+ endif
+endif
+endif
+endif
+
+# Connection::isLimited
+ifeq ($(BACKEND),android)
+MODULE_OBJS += \
+ networking/connection/islimited-android.o
+else
+MODULE_OBJS += \
+ networking/connection/islimited-default.o
+endif
+
ifdef POSIX
MODULE_OBJS += \
fs/posix/posix-fs.o \
diff --git a/engines/titanic/game/placeholder/place_holder_item.cpp b/backends/networking/browser/openurl-android.cpp
index 365e8cbe50..64e683238b 100644
--- a/engines/titanic/game/placeholder/place_holder_item.cpp
+++ b/backends/networking/browser/openurl-android.cpp
@@ -20,18 +20,16 @@
*
*/
-#include "titanic/game/placeholder/place_holder_item.h"
+#include "backends/networking/browser/openurl.h"
+#include "backends/platform/android/jni.h"
-namespace Titanic {
+namespace Networking {
+namespace Browser {
-void CPlaceHolderItem::save(SimpleFile *file, int indent) {
- file->writeNumberLine(1, indent);
- CGameObject::save(file, indent);
+bool openUrl(const Common::String &url) {
+ return JNI::openUrl(url.c_str());
}
-void CPlaceHolderItem::load(SimpleFile *file) {
- file->readNumber();
- CGameObject::load(file);
-}
+} // End of namespace Browser
+} // End of namespace Networking
-} // End of namespace Titanic
diff --git a/engines/titanic/gfx/chev_switch.cpp b/backends/networking/browser/openurl-default.cpp
index a6ce93098c..c430953196 100644
--- a/engines/titanic/gfx/chev_switch.cpp
+++ b/backends/networking/browser/openurl-default.cpp
@@ -20,21 +20,17 @@
*
*/
-#include "titanic/gfx/chev_switch.h"
+#include "backends/networking/browser/openurl.h"
+#include "common/textconsole.h"
-namespace Titanic {
+namespace Networking {
+namespace Browser {
-CChevSwitch::CChevSwitch() : CToggleSwitch() {
+bool openUrl(const Common::String &url) {
+ warning("Networking::Browser::openUrl(): not implemented");
+ return false;
}
-void CChevSwitch::save(SimpleFile *file, int indent) {
- file->writeNumberLine(1, indent);
- CToggleSwitch::save(file, indent);
-}
-
-void CChevSwitch::load(SimpleFile *file) {
- file->readNumber();
- CToggleSwitch::load(file);
-}
+} // End of namespace Browser
+} // End of namespace Networking
-} // End of namespace Titanic
diff --git a/backends/networking/browser/openurl-osx.cpp b/backends/networking/browser/openurl-osx.cpp
new file mode 100644
index 0000000000..8d786d7fd2
--- /dev/null
+++ b/backends/networking/browser/openurl-osx.cpp
@@ -0,0 +1,49 @@
+/* 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.
+ *
+ */
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/browser/openurl.h"
+#include <CoreFoundation/CFBundle.h>
+#include <ApplicationServices/ApplicationServices.h>
+
+namespace Networking {
+namespace Browser {
+
+using namespace std;
+
+bool openUrl(const Common::String &url) {
+ CFURLRef urlRef = CFURLCreateWithBytes (
+ NULL,
+ (UInt8*)url.c_str(),
+ url.size(),
+ kCFStringEncodingASCII,
+ NULL
+ );
+ int result = LSOpenCFURLRef(urlRef, 0);
+ CFRelease(urlRef);
+ return result == 0;
+}
+
+} // End of namespace Browser
+} // End of namespace Networking
+
diff --git a/backends/networking/browser/openurl-posix.cpp b/backends/networking/browser/openurl-posix.cpp
new file mode 100644
index 0000000000..429a379fcf
--- /dev/null
+++ b/backends/networking/browser/openurl-posix.cpp
@@ -0,0 +1,77 @@
+/* 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.
+ *
+ */
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/browser/openurl.h"
+#include "common/textconsole.h"
+#include <stdlib.h>
+
+namespace Networking {
+namespace Browser {
+
+namespace {
+bool launch(const Common::String client, const Common::String &url) {
+ // FIXME: system's input must be heavily escaped
+ // well, when url's specified by user
+ // it's OK now (urls are hardcoded somewhere in GUI)
+ Common::String cmd = client + " " + url;
+ return (system(cmd.c_str()) != -1);
+}
+}
+
+bool openUrl(const Common::String &url) {
+ // inspired by Qt's "qdesktopservices_x11.cpp"
+
+ // try "standards"
+ if (launch("xdg-open", url))
+ return true;
+ if (launch(getenv("DEFAULT_BROWSER"), url))
+ return true;
+ if (launch(getenv("BROWSER"), url))
+ return true;
+
+ // try desktop environment specific tools
+ if (launch("gnome-open", url)) // gnome
+ return true;
+ if (launch("kfmclient openURL", url)) // kde
+ return true;
+ if (launch("exo-open", url)) // xfce
+ return true;
+
+ // try browser names
+ if (launch("firefox", url))
+ return true;
+ if (launch("mozilla", url))
+ return true;
+ if (launch("netscape", url))
+ return true;
+ if (launch("opera", url))
+ return true;
+
+ warning("Networking::Browser::openUrl() (POSIX) failed to open URL");
+ return false;
+}
+
+} // End of namespace Browser
+} // End of namespace Networking
+
diff --git a/backends/networking/browser/openurl-windows.cpp b/backends/networking/browser/openurl-windows.cpp
new file mode 100644
index 0000000000..b78c9fa7ed
--- /dev/null
+++ b/backends/networking/browser/openurl-windows.cpp
@@ -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.
+ *
+ */
+
+#include "backends/networking/browser/openurl.h"
+#include "common/textconsole.h"
+#include <windows.h>
+#include <shellapi.h>
+
+namespace Networking {
+namespace Browser {
+
+bool openUrl(const Common::String &url) {
+ const uint64 result = (uint64)ShellExecute(0, 0, /*(wchar_t*)nativeFilePath.utf16()*/url.c_str(), 0, 0, SW_SHOWNORMAL);
+ // ShellExecute returns a value greater than 32 if successful
+ if (result <= 32) {
+ warning("ShellExecute failed: error = %u", result);
+ return false;
+ }
+ return true;
+}
+
+} // End of namespace Browser
+} // End of namespace Networking
diff --git a/backends/networking/browser/openurl.h b/backends/networking/browser/openurl.h
new file mode 100644
index 0000000000..15b4bf385b
--- /dev/null
+++ b/backends/networking/browser/openurl.h
@@ -0,0 +1,41 @@
+/* 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 NETWORKING_BROWSER_OPENURL_H
+#define NETWORKING_BROWSER_OPENURL_H
+
+#include "common/str.h"
+
+namespace Networking {
+namespace Browser {
+
+/**
+ * Opens URL in default browser (if available on the target system).
+ *
+ * Returns true on success.
+ */
+bool openUrl(const Common::String &url);
+
+} // End of namespace Browser
+} // End of namespace Networking
+
+#endif /*NETWORKING_BROWSER_OPENURL_H*/
diff --git a/backends/networking/connection/islimited-android.cpp b/backends/networking/connection/islimited-android.cpp
new file mode 100644
index 0000000000..8989f218ec
--- /dev/null
+++ b/backends/networking/connection/islimited-android.cpp
@@ -0,0 +1,35 @@
+/* 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 "backends/networking/connection/islimited.h"
+#include "backends/platform/android/jni.h"
+
+namespace Networking {
+namespace Connection {
+
+bool isLimited() {
+ return JNI::isConnectionLimited();
+}
+
+} // End of namespace Connection
+} // End of namespace Networking
+
diff --git a/engines/titanic/sound/music_handler.cpp b/backends/networking/connection/islimited-default.cpp
index 41545347b8..a993077fff 100644
--- a/engines/titanic/sound/music_handler.cpp
+++ b/backends/networking/connection/islimited-default.cpp
@@ -20,26 +20,17 @@
*
*/
-#include "titanic/sound/music_handler.h"
-#include "titanic/sound/sound_manager.h"
-#include "titanic/core/project_item.h"
+#include "backends/networking/connection/islimited.h"
+#include "common/textconsole.h"
-namespace Titanic {
+namespace Networking {
+namespace Connection {
-CMusicHandler::CMusicHandler(CProjectItem *project, CSoundManager *soundManager) :
- _project(project), _soundManager(soundManager),
- _field124(0) {
-
-}
-
-CMusicWave *CMusicHandler::createMusicWave(int v1, int v2) {
- // TODO
- return nullptr;
-}
-
-bool CMusicHandler::isBusy() {
- // TODO
+bool isLimited() {
+ warning("Networking::Connection::isLimited(): not limited by default");
return false;
}
-} // End of namespace Titanic
+} // End of namespace Connection
+} // End of namespace Networking
+
diff --git a/backends/networking/connection/islimited.h b/backends/networking/connection/islimited.h
new file mode 100644
index 0000000000..b23d31d157
--- /dev/null
+++ b/backends/networking/connection/islimited.h
@@ -0,0 +1,39 @@
+/* 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 NETWORKING_CONNECTION_ISLIMITED_H
+#define NETWORKING_CONNECTION_ISLIMITED_H
+
+namespace Networking {
+namespace Connection {
+
+/**
+* Returns whether connection's limited (if available on the target system).
+*
+* Returns true if connection seems limited.
+*/
+bool isLimited();
+
+} // End of namespace Connection
+} // End of namespace Networking
+
+#endif /*NETWORKING_CONNECTION_ISLIMITED_H*/
diff --git a/backends/networking/curl/cloudicon.cpp b/backends/networking/curl/cloudicon.cpp
new file mode 100644
index 0000000000..1c1ecf2f85
--- /dev/null
+++ b/backends/networking/curl/cloudicon.cpp
@@ -0,0 +1,171 @@
+/* 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 "backends/networking/curl/cloudicon.h"
+#include "backends/cloud/cloudmanager.h"
+#include "common/memstream.h"
+#include "gui/ThemeEngine.h"
+#include "gui/gui-manager.h"
+#include "image/png.h"
+
+namespace Networking {
+
+const float CloudIcon::ALPHA_STEP = 0.025;
+const float CloudIcon::ALPHA_MAX = 1;
+const float CloudIcon::ALPHA_MIN = 0.6;
+
+CloudIcon::CloudIcon():
+ _wasVisible(false), _iconsInited(false), _showingDisabled(false),
+ _currentAlpha(0), _alphaRising(true), _disabledFrames(0) {
+ initIcons();
+}
+
+CloudIcon::~CloudIcon() {}
+
+bool CloudIcon::draw() {
+ bool stop = false;
+ initIcons();
+
+ if (CloudMan.isWorking() || _disabledFrames > 0) {
+ if (g_system) {
+ if (!_wasVisible) {
+ g_system->clearOSD();
+ _wasVisible = true;
+ }
+ --_disabledFrames;
+ if (_alphaRising) {
+ if (_currentAlpha < ALPHA_MIN)
+ _currentAlpha += 5 * ALPHA_STEP;
+ else
+ _currentAlpha += ALPHA_STEP;
+ if (_currentAlpha > ALPHA_MAX) {
+ _currentAlpha = ALPHA_MAX;
+ _alphaRising = false;
+ }
+ } else {
+ _currentAlpha -= ALPHA_STEP;
+ if (_currentAlpha < ALPHA_MIN) {
+ _currentAlpha = ALPHA_MIN;
+ _alphaRising = true;
+ }
+ }
+ } else {
+ _wasVisible = false;
+ }
+ } else {
+ _wasVisible = false;
+ _currentAlpha -= 5 * ALPHA_STEP;
+ if (_currentAlpha <= 0) {
+ _currentAlpha = 0;
+ stop = true;
+ }
+ }
+
+ if (g_system) {
+ Graphics::TransparentSurface *surface = &_icon;
+ makeAlphaIcon((_showingDisabled ? _disabledIcon : _icon), _currentAlpha);
+ if (_alphaIcon.getPixels())
+ surface = &_alphaIcon;
+ if (surface && surface->getPixels()) {
+ int x = g_system->getOverlayWidth() - surface->w - 10, y = 10;
+ g_system->copyRectToOSD(surface->getPixels(), surface->pitch, x, y, surface->w, surface->h);
+ }
+ }
+
+ if (stop)
+ _showingDisabled = false;
+ return stop;
+}
+
+void CloudIcon::showDisabled() {
+ _showingDisabled = true;
+ _disabledFrames = 20 * 3; //3 seconds 20 fps
+}
+
+#include "backends/networking/curl/cloudicon_data.h"
+#include "backends/networking/curl/cloudicon_disabled_data.h"
+
+void CloudIcon::initIcons() {
+ if (_iconsInited)
+ return;
+ loadIcon(_icon, cloudicon_data, ARRAYSIZE(cloudicon_data));
+ loadIcon(_disabledIcon, cloudicon_disabled_data, ARRAYSIZE(cloudicon_disabled_data));
+ _iconsInited = true;
+}
+
+void CloudIcon::loadIcon(Graphics::TransparentSurface &icon, byte *data, uint32 size) {
+ Image::PNGDecoder decoder;
+ Common::MemoryReadStream stream(data, size);
+ if (!decoder.loadStream(stream))
+ error("CloudIcon::loadIcon: error decoding PNG");
+
+ Graphics::TransparentSurface *s = new Graphics::TransparentSurface(*decoder.getSurface(), true);
+ if (s) {
+ Graphics::PixelFormat f = g_system->getOSDFormat();
+ if (f != s->format) {
+ Graphics::TransparentSurface *s2 = s->convertTo(f);
+ if (s2)
+ icon.copyFrom(*s2);
+ else
+ warning("CloudIcon::loadIcon: failed converting TransparentSurface");
+ delete s2;
+ } else {
+ icon.copyFrom(*s);
+ }
+ delete s;
+ } else {
+ warning("CloudIcon::loadIcon: failed reading TransparentSurface from PNGDecoder");
+ }
+}
+
+void CloudIcon::makeAlphaIcon(Graphics::TransparentSurface &icon, float alpha) {
+ _alphaIcon.copyFrom(icon);
+
+ byte *pixels = (byte *)_alphaIcon.getPixels();
+ for (int y = 0; y < _alphaIcon.h; y++) {
+ byte *row = pixels + y * _alphaIcon.pitch;
+ for (int x = 0; x < _alphaIcon.w; x++) {
+ uint32 srcColor;
+ if (_alphaIcon.format.bytesPerPixel == 2)
+ srcColor = READ_UINT16(row);
+ else if (_alphaIcon.format.bytesPerPixel == 3)
+ srcColor = READ_UINT24(row);
+ else
+ srcColor = READ_UINT32(row);
+
+ // Update color's alpha
+ byte r, g, b, a;
+ _alphaIcon.format.colorToARGB(srcColor, a, r, g, b);
+ a = (byte)(a * alpha);
+ uint32 color = _alphaIcon.format.ARGBToColor(a, r, g, b);
+
+ if (_alphaIcon.format.bytesPerPixel == 2)
+ *((uint16 *)row) = color;
+ else
+ *((uint32 *)row) = color;
+
+ row += _alphaIcon.format.bytesPerPixel;
+ }
+ }
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/cloudicon.h b/backends/networking/curl/cloudicon.h
new file mode 100644
index 0000000000..7e4d7387a3
--- /dev/null
+++ b/backends/networking/curl/cloudicon.h
@@ -0,0 +1,70 @@
+/* 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 BACKENDS_NETWORKING_CURL_CLOUDICON_H
+#define BACKENDS_NETWORKING_CURL_CLOUDICON_H
+
+#include "graphics/transparent_surface.h"
+
+namespace Networking {
+
+class CloudIcon {
+ static const float ALPHA_STEP, ALPHA_MAX, ALPHA_MIN;
+
+ bool _wasVisible, _iconsInited, _showingDisabled;
+ Graphics::TransparentSurface _icon, _disabledIcon, _alphaIcon;
+ float _currentAlpha;
+ bool _alphaRising;
+ int _disabledFrames;
+
+ void initIcons();
+ void loadIcon(Graphics::TransparentSurface &icon, byte *data, uint32 size);
+ void makeAlphaIcon(Graphics::TransparentSurface &icon, float alpha);
+
+public:
+ CloudIcon();
+ ~CloudIcon();
+
+ /**
+ * This method is called from ConnectionManager every time
+ * its own timer calls the handle() method. The primary
+ * responsibility of this draw() method is to draw cloud icon
+ * on ScummVM's OSD when current cloud Storage is working.
+ *
+ * As we don't want ConnectionManager to work when no
+ * Requests are running, we'd like to stop the timer. But then
+ * this icon wouldn't have time to disappear smoothly. So,
+ * in order to do that, ConnectionManager stop its timer
+ * only when this draw() method returns true, indicating that
+ * the CloudIcon has disappeared and the timer could be stopped.
+ *
+ * @return true if ConnMan's timer could be stopped.
+ */
+ bool draw();
+
+ /** Draw a "cloud disabled" icon instead of "cloud syncing" one. */
+ void showDisabled();
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/cloudicon_data.h b/backends/networking/curl/cloudicon_data.h
new file mode 100644
index 0000000000..21d88182a3
--- /dev/null
+++ b/backends/networking/curl/cloudicon_data.h
@@ -0,0 +1,111 @@
+/* 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.
+ *
+ */
+
+// This is a PNG file dumped into array.
+// $ recode data..d1 <dists/cloudicon.png >cloudicon_data.h
+// The tool is from https://github.com/pinard/Recode
+
+byte cloudicon_data[] = {
+ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68,
+ 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115,
+ 122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8,
+ 124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11,
+ 18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116,
+ 69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109,
+ 101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0,
+ 0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101,
+ 0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107,
+ 115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 50, 73, 68,
+ 65, 84, 88, 133, 197, 151, 109, 104, 150, 101, 20, 199, 127, 247, 227,
+ 179, 105, 51, 23, 65, 181, 150, 224, 154, 214, 132, 194, 249, 33, 165,
+ 22, 189, 231, 194, 210, 250, 16, 171, 180, 55, 42, 152, 68, 65, 100,
+ 52, 233, 5, 146, 144, 144, 26, 249, 169, 62, 164, 80, 89, 152, 25,
+ 18, 226, 42, 49, 87, 88, 180, 94, 96, 96, 246, 234, 180, 70, 50,
+ 66, 214, 55, 247, 22, 133, 247, 255, 244, 225, 58, 247, 158, 107, 143,
+ 219, 243, 60, 186, 192, 3, 135, 251, 220, 215, 117, 223, 231, 127, 238,
+ 235, 252, 239, 235, 58, 39, 105, 250, 206, 56, 147, 146, 55, 85, 252,
+ 108, 13, 112, 59, 176, 12, 88, 2, 204, 7, 230, 248, 220, 48, 208,
+ 15, 244, 2, 221, 64, 23, 48, 86, 137, 211, 228, 146, 158, 178, 43,
+ 80, 7, 172, 3, 218, 129, 179, 43, 241, 9, 140, 0, 155, 129, 87,
+ 128, 193, 146, 15, 47, 248, 178, 100, 0, 237, 64, 39, 112, 14, 96,
+ 174, 20, 217, 153, 228, 162, 0, 50, 25, 2, 58, 128, 45, 83, 1,
+ 228, 53, 121, 10, 170, 129, 183, 128, 213, 126, 47, 215, 49, 224, 39,
+ 224, 19, 160, 7, 56, 2, 204, 0, 154, 128, 27, 128, 229, 110, 215,
+ 120, 64, 181, 132, 149, 184, 30, 120, 4, 248, 183, 24, 40, 185, 248,
+ 243, 147, 86, 160, 154, 144, 195, 91, 252, 43, 5, 140, 2, 31, 2,
+ 27, 129, 195, 83, 125, 141, 75, 19, 240, 2, 129, 47, 179, 61, 144,
+ 4, 216, 7, 172, 44, 14, 34, 105, 232, 62, 41, 128, 109, 192, 189,
+ 14, 126, 2, 24, 0, 30, 3, 246, 150, 1, 46, 150, 54, 15, 184,
+ 1, 200, 251, 216, 123, 192, 253, 19, 2, 152, 183, 119, 66, 0, 237,
+ 192, 27, 110, 159, 0, 250, 128, 187, 128, 67, 167, 8, 158, 201, 98,
+ 224, 3, 160, 209, 131, 72, 128, 53, 68, 156, 200, 153, 192, 181, 206,
+ 68, 167, 219, 50, 49, 96, 226, 78, 19, 135, 162, 103, 138, 117, 169,
+ 137, 46, 19, 163, 38, 82, 19, 63, 152, 120, 220, 196, 12, 159, 63,
+ 104, 98, 149, 137, 99, 238, 211, 28, 163, 46, 243, 145, 147, 192, 117,
+ 157, 68, 173, 219, 195, 18, 143, 74, 28, 137, 230, 139, 181, 77, 162,
+ 71, 98, 165, 68, 141, 68, 78, 98, 145, 196, 107, 18, 59, 252, 30,
+ 137, 3, 18, 207, 72, 140, 249, 125, 173, 99, 33, 65, 114, 209, 110,
+ 131, 192, 218, 65, 2, 105, 4, 108, 245, 165, 74, 125, 165, 206, 5,
+ 214, 2, 151, 3, 7, 128, 119, 128, 95, 253, 189, 169, 228, 105, 224,
+ 85, 183, 171, 128, 29, 192, 29, 4, 82, 142, 18, 246, 151, 177, 164,
+ 126, 151, 1, 220, 3, 188, 79, 32, 222, 40, 97, 167, 235, 243, 151,
+ 207, 2, 190, 5, 154, 35, 231, 223, 0, 45, 101, 242, 127, 12, 152,
+ 75, 97, 191, 88, 12, 124, 237, 254, 18, 96, 21, 176, 35, 227, 192,
+ 50, 207, 15, 38, 126, 49, 113, 56, 202, 243, 221, 38, 154, 139, 114,
+ 223, 82, 130, 23, 153, 214, 155, 88, 20, 221, 255, 104, 226, 104, 116,
+ 223, 106, 42, 144, 112, 169, 137, 196, 131, 248, 56, 10, 166, 222, 196,
+ 141, 21, 128, 77, 165, 223, 155, 120, 42, 34, 246, 158, 200, 247, 18,
+ 19, 228, 21, 178, 60, 223, 151, 41, 1, 190, 112, 251, 60, 224, 171,
+ 104, 238, 116, 36, 33, 240, 224, 32, 240, 25, 176, 31, 120, 194, 199,
+ 27, 161, 112, 26, 102, 167, 154, 1, 127, 186, 253, 98, 9, 240, 81,
+ 2, 97, 43, 149, 231, 61, 128, 129, 104, 108, 78, 28, 64, 44, 25,
+ 105, 218, 74, 56, 156, 13, 252, 76, 248, 43, 42, 145, 140, 176, 249,
+ 226, 137, 188, 133, 20, 12, 123, 68, 9, 225, 171, 127, 7, 46, 40,
+ 227, 180, 82, 112, 128, 153, 126, 189, 148, 194, 105, 57, 12, 5, 18,
+ 246, 71, 68, 185, 214, 237, 191, 166, 65, 190, 98, 237, 243, 235, 205,
+ 209, 88, 127, 188, 19, 246, 250, 53, 39, 177, 194, 237, 157, 37, 118,
+ 193, 83, 213, 183, 37, 102, 73, 92, 39, 145, 196, 152, 57, 75, 193,
+ 82, 246, 249, 21, 75, 89, 104, 41, 205, 150, 178, 222, 82, 250, 163,
+ 241, 211, 213, 253, 150, 178, 201, 82, 174, 180, 148, 185, 150, 146, 248,
+ 120, 183, 165, 133, 20, 116, 153, 24, 113, 123, 150, 137, 103, 77, 28,
+ 55, 113, 141, 137, 173, 38, 134, 60, 61, 167, 178, 236, 3, 38, 54,
+ 152, 184, 213, 255, 253, 39, 221, 55, 142, 213, 101, 42, 252, 5, 99,
+ 132, 202, 101, 45, 97, 175, 94, 14, 172, 0, 118, 1, 15, 185, 78,
+ 71, 86, 19, 138, 217, 196, 117, 179, 99, 146, 204, 126, 125, 188, 30,
+ 168, 35, 236, 255, 181, 132, 3, 233, 15, 224, 54, 202, 87, 64, 229,
+ 164, 5, 216, 9, 92, 232, 224, 67, 192, 66, 188, 88, 205, 69, 185,
+ 26, 180, 148, 14, 207, 81, 206, 82, 230, 89, 202, 110, 75, 185, 108,
+ 26, 249, 191, 202, 82, 222, 181, 148, 243, 163, 220, 119, 56, 22, 150,
+ 78, 172, 7, 144, 216, 34, 177, 205, 237, 188, 196, 2, 137, 61, 18,
+ 15, 158, 6, 243, 31, 144, 216, 37, 209, 224, 190, 18, 137, 237, 142,
+ 49, 254, 92, 50, 115, 211, 164, 69, 233, 71, 64, 43, 140, 23, 165,
+ 127, 19, 26, 142, 231, 8, 117, 192, 84, 82, 69, 56, 118, 215, 3,
+ 55, 17, 54, 160, 172, 40, 253, 148, 80, 168, 78, 44, 74, 171, 59,
+ 39, 237, 11, 170, 129, 55, 129, 251, 40, 108, 205, 2, 254, 1, 126,
+ 243, 96, 122, 129, 163, 62, 223, 0, 92, 237, 160, 141, 17, 112, 38,
+ 219, 129, 135, 139, 193, 1, 146, 170, 151, 43, 106, 76, 106, 139, 198,
+ 229, 192, 73, 20, 96, 12, 152, 177, 253, 56, 101, 26, 147, 36, 191,
+ 177, 226, 214, 108, 13, 133, 214, 172, 220, 75, 35, 14, 90, 190, 53,
+ 203, 189, 84, 113, 119, 156, 53, 167, 173, 192, 21, 252, 95, 205, 105,
+ 178, 225, 204, 182, 231, 255, 1, 200, 91, 112, 221, 160, 249, 68, 42,
+ 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
+};
diff --git a/backends/networking/curl/cloudicon_disabled_data.h b/backends/networking/curl/cloudicon_disabled_data.h
new file mode 100644
index 0000000000..4340a8a37c
--- /dev/null
+++ b/backends/networking/curl/cloudicon_disabled_data.h
@@ -0,0 +1,117 @@
+/* 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.
+ *
+ */
+
+// This is a PNG file dumped into array.
+// $ recode data..d1 <dists/cloudicon_disabled.png >cloudicon_disabled_data.h
+// The tool is from https://github.com/pinard/Recode
+
+byte cloudicon_disabled_data[] = {
+ 137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68,
+ 82, 0, 0, 0, 32, 0, 0, 0, 32, 8, 6, 0, 0, 0, 115,
+ 122, 122, 244, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8, 8, 8,
+ 124, 8, 100, 136, 0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 11,
+ 18, 0, 0, 11, 18, 1, 210, 221, 126, 252, 0, 0, 0, 22, 116,
+ 69, 88, 116, 67, 114, 101, 97, 116, 105, 111, 110, 32, 84, 105, 109,
+ 101, 0, 48, 54, 47, 48, 51, 47, 49, 54, 159, 192, 233, 192, 0,
+ 0, 0, 28, 116, 69, 88, 116, 83, 111, 102, 116, 119, 97, 114, 101,
+ 0, 65, 100, 111, 98, 101, 32, 70, 105, 114, 101, 119, 111, 114, 107,
+ 115, 32, 67, 83, 54, 232, 188, 178, 140, 0, 0, 4, 139, 73, 68,
+ 65, 84, 88, 133, 197, 215, 91, 168, 86, 69, 20, 7, 240, 223, 254,
+ 244, 120, 236, 120, 57, 26, 94, 10, 31, 34, 35, 11, 36, 203, 74,
+ 212, 160, 204, 212, 110, 166, 248, 208, 205, 172, 151, 144, 136, 158, 82,
+ 80, 168, 32, 233, 165, 160, 32, 130, 236, 165, 34, 204, 74, 18, 52,
+ 36, 169, 232, 34, 24, 221, 243, 218, 133, 74, 77, 195, 44, 200, 91,
+ 122, 188, 156, 172, 102, 159, 221, 195, 204, 246, 219, 126, 231, 226, 17,
+ 138, 22, 12, 123, 246, 236, 153, 245, 95, 107, 205, 127, 205, 172, 157,
+ 21, 254, 95, 233, 27, 122, 63, 183, 5, 179, 48, 13, 87, 98, 52,
+ 6, 167, 111, 71, 176, 11, 27, 177, 14, 107, 209, 222, 27, 165, 217,
+ 137, 211, 207, 25, 137, 197, 152, 143, 65, 189, 209, 137, 163, 120, 1,
+ 79, 98, 111, 143, 147, 143, 247, 172, 108, 62, 158, 194, 16, 20, 169,
+ 105, 232, 151, 82, 171, 24, 80, 74, 27, 22, 225, 197, 110, 13, 104,
+ 235, 122, 188, 31, 94, 194, 93, 21, 192, 14, 49, 172, 223, 226, 109,
+ 124, 130, 29, 232, 131, 49, 184, 22, 55, 166, 126, 75, 131, 65, 175,
+ 225, 94, 252, 213, 201, 128, 131, 93, 131, 191, 137, 27, 42, 192, 199,
+ 241, 6, 158, 192, 246, 238, 188, 73, 50, 6, 143, 98, 78, 50, 164,
+ 140, 200, 123, 34, 135, 78, 49, 34, 219, 215, 89, 193, 171, 152, 151,
+ 192, 3, 246, 224, 1, 188, 123, 26, 224, 170, 92, 134, 143, 49, 160,
+ 50, 86, 96, 5, 238, 174, 78, 108, 204, 130, 249, 98, 216, 75, 240,
+ 109, 184, 13, 63, 156, 1, 248, 68, 44, 21, 189, 47, 165, 67, 140,
+ 196, 60, 172, 87, 225, 68, 182, 167, 62, 105, 100, 2, 106, 77, 11,
+ 118, 139, 123, 186, 163, 59, 164, 53, 76, 16, 195, 125, 29, 250, 15,
+ 227, 167, 73, 12, 237, 27, 73, 91, 75, 142, 28, 21, 185, 51, 60,
+ 141, 181, 225, 98, 41, 59, 106, 33, 185, 26, 88, 28, 104, 77, 253,
+ 163, 129, 251, 3, 59, 42, 223, 79, 105, 171, 184, 53, 240, 73, 224,
+ 150, 64, 203, 32, 106, 99, 185, 160, 224, 236, 16, 245, 118, 4, 54,
+ 5, 166, 4, 22, 6, 218, 211, 218, 214, 132, 37, 32, 75, 238, 181,
+ 224, 55, 12, 76, 222, 191, 140, 251, 144, 39, 79, 135, 98, 1, 198,
+ 98, 11, 150, 227, 251, 50, 204, 67, 113, 121, 90, 156, 57, 185, 127,
+ 135, 154, 184, 9, 95, 160, 9, 43, 49, 59, 69, 225, 24, 206, 65,
+ 123, 150, 54, 247, 14, 188, 158, 214, 30, 23, 79, 186, 109, 9, 252,
+ 44, 124, 142, 113, 149, 232, 127, 134, 201, 196, 88, 79, 66, 115, 5,
+ 252, 24, 182, 114, 224, 32, 35, 230, 212, 207, 139, 75, 241, 169, 168,
+ 47, 195, 157, 88, 89, 110, 193, 180, 64, 145, 250, 223, 5, 182, 87,
+ 194, 125, 123, 96, 92, 195, 22, 76, 14, 201, 227, 177, 226, 65, 144,
+ 227, 111, 28, 198, 151, 113, 131, 135, 5, 46, 169, 172, 249, 38, 176,
+ 187, 242, 62, 35, 160, 150, 199, 197, 19, 114, 178, 156, 34, 231, 173,
+ 244, 180, 154, 115, 115, 166, 166, 57, 167, 180, 193, 34, 3, 7, 165,
+ 61, 11, 137, 105, 27, 112, 160, 62, 111, 235, 106, 22, 166, 126, 71,
+ 206, 59, 165, 238, 156, 43, 114, 100, 155, 98, 120, 218, 146, 206, 2,
+ 83, 241, 225, 26, 134, 165, 253, 27, 173, 65, 134, 138, 137, 222, 154,
+ 222, 139, 228, 249, 87, 233, 217, 133, 76, 159, 19, 47, 169, 89, 226,
+ 129, 214, 71, 188, 192, 134, 148, 231, 64, 121, 171, 21, 248, 85, 244,
+ 232, 177, 30, 192, 59, 90, 82, 6, 193, 9, 108, 234, 30, 28, 30,
+ 9, 209, 128, 74, 214, 71, 204, 190, 121, 231, 201, 5, 228, 220, 218,
+ 248, 97, 136, 200, 246, 102, 106, 29, 234, 73, 190, 5, 135, 186, 7,
+ 135, 201, 9, 167, 111, 227, 135, 50, 2, 71, 146, 69, 153, 232, 245,
+ 206, 192, 136, 234, 196, 86, 49, 13, 154, 162, 113, 39, 211, 101, 51,
+ 126, 239, 25, 28, 154, 19, 206, 133, 234, 119, 195, 17, 234, 36, 220,
+ 85, 33, 202, 213, 169, 191, 175, 36, 92, 139, 152, 106, 3, 212, 9,
+ 119, 76, 100, 251, 126, 157, 9, 218, 69, 219, 150, 158, 211, 42, 99,
+ 187, 114, 245, 147, 112, 83, 122, 214, 2, 51, 83, 127, 85, 16, 89,
+ 62, 94, 60, 61, 202, 20, 58, 36, 38, 244, 126, 93, 159, 146, 93,
+ 180, 101, 129, 254, 129, 107, 2, 89, 26, 219, 24, 42, 6, 188, 95,
+ 153, 124, 81, 202, 251, 37, 3, 249, 101, 188, 120, 114, 148, 223, 219,
+ 197, 212, 56, 208, 123, 240, 245, 129, 167, 3, 19, 3, 163, 42, 6,
+ 172, 11, 234, 36, 92, 43, 242, 105, 32, 250, 227, 161, 89, 44, 45,
+ 98, 196, 139, 14, 178, 146, 112, 27, 210, 179, 23, 178, 7, 203, 240,
+ 248, 156, 152, 251, 15, 38, 221, 146, 138, 181, 212, 73, 216, 46, 214,
+ 112, 11, 82, 180, 103, 138, 71, 237, 40, 145, 52, 29, 216, 221, 194,
+ 220, 41, 49, 0, 103, 36, 129, 185, 152, 158, 116, 101, 9, 171, 29,
+ 178, 85, 245, 121, 213, 235, 184, 90, 215, 229, 248, 89, 172, 112, 190,
+ 62, 83, 112, 209, 145, 85, 226, 229, 147, 105, 188, 142, 43, 172, 220,
+ 155, 179, 40, 29, 201, 85, 6, 239, 204, 153, 155, 243, 117, 47, 216,
+ 222, 216, 38, 229, 188, 146, 51, 188, 162, 119, 81, 194, 82, 205, 130,
+ 178, 189, 24, 120, 173, 97, 108, 80, 34, 102, 111, 73, 87, 182, 123,
+ 2, 107, 2, 231, 133, 184, 213, 89, 96, 69, 194, 56, 57, 47, 91,
+ 222, 57, 100, 253, 68, 130, 92, 175, 94, 148, 254, 129, 15, 240, 176,
+ 88, 7, 116, 39, 77, 226, 181, 187, 68, 172, 146, 154, 69, 78, 101,
+ 98, 77, 57, 91, 99, 81, 250, 82, 215, 138, 202, 178, 188, 44, 78,
+ 37, 67, 254, 196, 143, 201, 152, 141, 98, 217, 86, 224, 60, 92, 149,
+ 64, 207, 175, 0, 151, 178, 66, 119, 101, 249, 243, 61, 184, 163, 254,
+ 99, 210, 218, 48, 94, 94, 5, 101, 13, 162, 1, 176, 100, 251, 97,
+ 167, 249, 49, 233, 115, 179, 250, 111, 78, 23, 109, 115, 193, 178, 130,
+ 62, 5, 151, 20, 52, 23, 241, 76, 200, 10, 106, 149, 103, 173, 50,
+ 158, 21, 28, 45, 120, 174, 96, 110, 193, 71, 61, 232, 151, 61, 219,
+ 115, 4, 170, 82, 254, 156, 206, 16, 47, 197, 127, 231, 231, 244, 153,
+ 222, 27, 240, 159, 200, 63, 153, 185, 24, 191, 162, 246, 71, 153, 0,
+ 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
+};
diff --git a/backends/networking/curl/connectionmanager.cpp b/backends/networking/curl/connectionmanager.cpp
new file mode 100644
index 0000000000..f3dc91ad60
--- /dev/null
+++ b/backends/networking/curl/connectionmanager.cpp
@@ -0,0 +1,204 @@
+/* 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.
+*
+*/
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/system.h"
+#include "common/timer.h"
+#include <curl/curl.h>
+
+namespace Common {
+
+DECLARE_SINGLETON(Networking::ConnectionManager);
+
+}
+
+namespace Networking {
+
+ConnectionManager::ConnectionManager(): _multi(0), _timerStarted(false), _frame(0) {
+ curl_global_init(CURL_GLOBAL_ALL);
+ _multi = curl_multi_init();
+}
+
+ConnectionManager::~ConnectionManager() {
+ stopTimer();
+
+ //terminate all requests
+ _handleMutex.lock();
+ for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end(); ++i) {
+ Request *request = i->request;
+ RequestCallback callback = i->onDeleteCallback;
+ if (request)
+ request->finish();
+ delete request;
+ if (callback)
+ (*callback)(request);
+ }
+ _requests.clear();
+
+ //cleanup
+ curl_multi_cleanup(_multi);
+ curl_global_cleanup();
+ _multi = nullptr;
+ _handleMutex.unlock();
+}
+
+void ConnectionManager::registerEasyHandle(CURL *easy) const {
+ curl_multi_add_handle(_multi, easy);
+}
+
+Request *ConnectionManager::addRequest(Request *request, RequestCallback callback) {
+ _addedRequestsMutex.lock();
+ _addedRequests.push_back(RequestWithCallback(request, callback));
+ if (!_timerStarted)
+ startTimer();
+ _addedRequestsMutex.unlock();
+ return request;
+}
+
+void ConnectionManager::showCloudDisabledIcon() {
+ _icon.showDisabled();
+ startTimer();
+}
+
+Common::String ConnectionManager::urlEncode(Common::String s) const {
+ if (!_multi)
+ return "";
+ char *output = curl_easy_escape(_multi, s.c_str(), s.size());
+ if (output) {
+ Common::String result = output;
+ curl_free(output);
+ return result;
+ }
+ return "";
+}
+
+uint32 ConnectionManager::getCloudRequestsPeriodInMicroseconds() {
+ return TIMER_INTERVAL * CLOUD_PERIOD;
+}
+
+//private goes here:
+
+void connectionsThread(void *ignored) {
+ ConnMan.handle();
+}
+
+void ConnectionManager::startTimer(int interval) {
+ Common::TimerManager *manager = g_system->getTimerManager();
+ if (manager->installTimerProc(connectionsThread, interval, 0, "Networking::ConnectionManager's Timer")) {
+ _timerStarted = true;
+ } else {
+ warning("Failed to install Networking::ConnectionManager's timer");
+ }
+}
+
+void ConnectionManager::stopTimer() {
+ debug(9, "timer stopped");
+ Common::TimerManager *manager = g_system->getTimerManager();
+ manager->removeTimerProc(connectionsThread);
+ _timerStarted = false;
+}
+
+bool ConnectionManager::hasAddedRequests() {
+ _addedRequestsMutex.lock();
+ bool hasNewRequests = !_addedRequests.empty();
+ _addedRequestsMutex.unlock();
+ return hasNewRequests;
+}
+
+void ConnectionManager::handle() {
+ //lock mutex here (in case another handle() would be called before this one ends)
+ _handleMutex.lock();
+ ++_frame;
+ if (_frame % CLOUD_PERIOD == 0)
+ interateRequests();
+ if (_frame % CURL_PERIOD == 0)
+ processTransfers();
+
+ if (_icon.draw() && _requests.empty() && !hasAddedRequests())
+ stopTimer();
+ _handleMutex.unlock();
+}
+
+void ConnectionManager::interateRequests() {
+ //add new requests
+ _addedRequestsMutex.lock();
+ for (Common::Array<RequestWithCallback>::iterator i = _addedRequests.begin(); i != _addedRequests.end(); ++i) {
+ _requests.push_back(*i);
+ }
+ _addedRequests.clear();
+ _addedRequestsMutex.unlock();
+
+ //call handle() of all running requests (so they can do their work)
+ debug(9, "handling %d request(s)", _requests.size());
+ for (Common::Array<RequestWithCallback>::iterator i = _requests.begin(); i != _requests.end();) {
+ Request *request = i->request;
+ if (request) {
+ if (request->state() == PROCESSING)
+ request->handle();
+ else if (request->state() == RETRY)
+ request->handleRetry();
+ }
+
+ if (!request || request->state() == FINISHED) {
+ delete (i->request);
+ if (i->onDeleteCallback)
+ (*i->onDeleteCallback)(i->request); //that's not a mistake (we're passing an address and that method knows there is no object anymore)
+ _requests.erase(i);
+ continue;
+ }
+
+ ++i;
+ }
+}
+
+void ConnectionManager::processTransfers() {
+ if (!_multi) return;
+
+ //check libcurl's transfers and notify requests of messages from queue (transfer completion or failure)
+ int transfersRunning;
+ curl_multi_perform(_multi, &transfersRunning);
+
+ int messagesInQueue;
+ CURLMsg *curlMsg;
+ while ((curlMsg = curl_multi_info_read(_multi, &messagesInQueue))) {
+ CURL *easyHandle = curlMsg->easy_handle;
+
+ NetworkReadStream *stream;
+ curl_easy_getinfo(easyHandle, CURLINFO_PRIVATE, &stream);
+ if (stream)
+ stream->finished();
+
+ if (curlMsg->msg == CURLMSG_DONE) {
+ debug(9, "ConnectionManager: SUCCESS (%d - %s)", curlMsg->data.result, curl_easy_strerror(curlMsg->data.result));
+ } else {
+ warning("ConnectionManager: FAILURE (CURLMsg (%d))", curlMsg->msg);
+ }
+
+ curl_multi_remove_handle(_multi, easyHandle);
+ }
+}
+
+} // End of namespace Cloud
diff --git a/backends/networking/curl/connectionmanager.h b/backends/networking/curl/connectionmanager.h
new file mode 100644
index 0000000000..826bef6d36
--- /dev/null
+++ b/backends/networking/curl/connectionmanager.h
@@ -0,0 +1,132 @@
+/* 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 BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H
+#define BACKENDS_NETWORKING_CURL_CONNECTIONMANAGER_H
+
+#include "backends/networking/curl/cloudicon.h"
+#include "backends/networking/curl/request.h"
+#include "common/str.h"
+#include "common/singleton.h"
+#include "common/hashmap.h"
+#include "common/mutex.h"
+
+typedef void CURL;
+typedef void CURLM;
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream;
+
+class ConnectionManager : public Common::Singleton<ConnectionManager> {
+ static const uint32 FRAMES_PER_SECOND = 20;
+ static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
+ static const uint32 CLOUD_PERIOD = 20; //every 20th frame
+ static const uint32 CURL_PERIOD = 1; //every frame
+
+ friend void connectionsThread(void *); //calls handle()
+
+ typedef Common::BaseCallback<Request *> *RequestCallback;
+
+ /**
+ * RequestWithCallback is used by ConnectionManager to
+ * storage the Request and a callback which should be
+ * called on Request delete.
+ *
+ * Usually one won't need to pass such callback, but
+ * in some cases you'd like to know whether Request is
+ * still running.
+ *
+ * For example, Cloud::Storage is keeping track of how
+ * many Requests are running, and thus it needs to know
+ * that Request was destroyed to decrease its counter.
+ *
+ * onDeleteCallback is called with *invalid* pointer.
+ * ConnectionManager deletes Request first and then passes
+ * the pointer to the callback. One may use the address
+ * to find it in own HashMap or Array and remove it.
+ * So, again, this pointer is for information only. One
+ * cannot use it.
+ */
+ struct RequestWithCallback {
+ Request *request;
+ RequestCallback onDeleteCallback;
+
+ RequestWithCallback(Request *rq = nullptr, RequestCallback cb = nullptr): request(rq), onDeleteCallback(cb) {}
+ };
+
+ CURLM *_multi;
+ bool _timerStarted;
+ Common::Array<RequestWithCallback> _requests, _addedRequests;
+ Common::Mutex _handleMutex, _addedRequestsMutex;
+ CloudIcon _icon;
+ uint32 _frame;
+
+ void startTimer(int interval = TIMER_INTERVAL);
+ void stopTimer();
+ void handle();
+ void interateRequests();
+ void processTransfers();
+ bool hasAddedRequests();
+
+public:
+ ConnectionManager();
+ virtual ~ConnectionManager();
+
+ /**
+ * All libcurl transfers are going through this ConnectionManager.
+ * So, if you want to start any libcurl transfer, you must create
+ * an easy handle and register it using this method.
+ */
+ void registerEasyHandle(CURL *easy) const;
+
+ /**
+ * Use this method to add new Request into manager's queue.
+ * Manager will periodically call handle() method of these
+ * Requests until they set their state to FINISHED.
+ *
+ * If Request's state is RETRY, handleRetry() is called instead.
+ *
+ * The passed callback would be called after Request is deleted.
+ *
+ * @note This method starts the timer if it's not started yet.
+ *
+ * @return the same Request pointer, just as a shortcut
+ */
+ Request *addRequest(Request *request, RequestCallback callback = nullptr);
+
+ /** Shows a "cloud disabled" icon for a three seconds. */
+ void showCloudDisabledIcon();
+
+ /** Return URL-encoded version of given string. */
+ Common::String urlEncode(Common::String s) const;
+
+ static uint32 getCloudRequestsPeriodInMicroseconds();
+};
+
+/** Shortcut for accessing the connection manager. */
+#define ConnMan Networking::ConnectionManager::instance()
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/curljsonrequest.cpp b/backends/networking/curl/curljsonrequest.cpp
new file mode 100644
index 0000000000..1899cbd913
--- /dev/null
+++ b/backends/networking/curl/curljsonrequest.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.
+*
+*/
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/curl/curljsonrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/debug.h"
+#include "common/json.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+CurlJsonRequest::CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url) :
+ CurlRequest(nullptr, ecb, url), _jsonCallback(cb), _contentsStream(DisposeAfterUse::YES),
+ _buffer(new byte[CURL_JSON_REQUEST_BUFFER_SIZE]) {}
+
+CurlJsonRequest::~CurlJsonRequest() {
+ delete _jsonCallback;
+ delete[] _buffer;
+}
+
+char *CurlJsonRequest::getPreparedContents() {
+ //write one more byte in the end
+ byte zero[1] = {0};
+ _contentsStream.write(zero, 1);
+
+ //replace all "bad" bytes with '.' character
+ byte *result = _contentsStream.getData();
+ uint32 size = _contentsStream.size();
+ for (uint32 i = 0; i < size; ++i) {
+ if (result[i] == '\n')
+ result[i] = ' '; //yeah, kinda stupid
+ else if (result[i] < 0x20 || result[i] > 0x7f)
+ result[i] = '.';
+ }
+
+ //make it zero-terminated string
+ result[size - 1] = '\0';
+
+ return (char *)result;
+}
+
+void CurlJsonRequest::handle() {
+ if (!_stream) _stream = makeStream();
+
+ if (_stream) {
+ uint32 readBytes = _stream->read(_buffer, CURL_JSON_REQUEST_BUFFER_SIZE);
+ if (readBytes != 0)
+ if (_contentsStream.write(_buffer, readBytes) != readBytes)
+ warning("CurlJsonRequest: unable to write all the bytes into MemoryWriteStreamDynamic");
+
+ if (_stream->eos()) {
+ char *contents = getPreparedContents();
+ Common::JSONValue *json = Common::JSON::parse(contents);
+ if (json) {
+ finishJson(json); //it's JSON even if's not 200 OK? That's fine!..
+ } else {
+ if (_stream->httpResponseCode() == 200) //no JSON, but 200 OK? That's fine!..
+ finishJson(nullptr);
+ else
+ finishError(ErrorResponse(this, false, true, contents, _stream->httpResponseCode()));
+ }
+ }
+ }
+}
+
+void CurlJsonRequest::restart() {
+ if (_stream)
+ delete _stream;
+ _stream = nullptr;
+ _contentsStream = Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES);
+ //with no stream available next handle() will create another one
+}
+
+void CurlJsonRequest::finishJson(Common::JSONValue *json) {
+ Request::finishSuccess();
+ if (_jsonCallback)
+ (*_jsonCallback)(JsonResponse(this, json)); //potential memory leak, free it in your callbacks!
+ else
+ delete json;
+}
+
+bool CurlJsonRequest::jsonIsObject(Common::JSONValue *item, const char *warningPrefix) {
+ if (item == nullptr) {
+ warning("%s: passed item is NULL", warningPrefix);
+ return false;
+ }
+
+ if (item->isObject()) return true;
+
+ warning("%s: passed item is not an object", warningPrefix);
+ debug(9, "%s", item->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isObject()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an object", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isString()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not a string", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isIntegerNumber()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an integer", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isArray()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is not an array", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ if (item.getVal(key)->isString() || item.getVal(key)->isIntegerNumber()) return true;
+
+ warning("%s: passed item's \"%s\" attribute is neither a string or an integer", warningPrefix, key);
+ debug(9, "%s", item.getVal(key)->stringify(true).c_str());
+ return false;
+}
+
+bool CurlJsonRequest::jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional) {
+ if (!item.contains(key)) {
+ if (isOptional) {
+ return true;
+ }
+
+ warning("%s: passed item misses the \"%s\" attribute", warningPrefix, key);
+ return false;
+ }
+
+ return true;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/curljsonrequest.h b/backends/networking/curl/curljsonrequest.h
new file mode 100644
index 0000000000..edd523015a
--- /dev/null
+++ b/backends/networking/curl/curljsonrequest.h
@@ -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.
+*
+*/
+
+#ifndef BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H
+#define BACKENDS_NETWORKING_CURL_CURLJSONREQUEST_H
+
+#include "backends/networking/curl/curlrequest.h"
+#include "common/memstream.h"
+#include "common/json.h"
+
+namespace Networking {
+
+typedef Response<Common::JSONValue *> JsonResponse;
+typedef Common::BaseCallback<JsonResponse> *JsonCallback;
+
+#define CURL_JSON_REQUEST_BUFFER_SIZE 512 * 1024
+
+class CurlJsonRequest: public CurlRequest {
+protected:
+ JsonCallback _jsonCallback;
+ Common::MemoryWriteStreamDynamic _contentsStream;
+ byte *_buffer;
+
+ /** Prepares raw bytes from _contentsStream to be parsed with Common::JSON::parse(). */
+ char *getPreparedContents();
+
+ /** Sets FINISHED state and passes the JSONValue * into user's callback in JsonResponse. */
+ virtual void finishJson(Common::JSONValue *json);
+
+public:
+ CurlJsonRequest(JsonCallback cb, ErrorCallback ecb, Common::String url);
+ virtual ~CurlJsonRequest();
+
+ virtual void handle();
+ virtual void restart();
+
+ static bool jsonIsObject(Common::JSONValue *item, const char *warningPrefix);
+ static bool jsonContainsObject(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsString(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsArray(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsStringOrIntegerNumber(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+ static bool jsonContainsAttribute(Common::JSONObject &item, const char *key, const char *warningPrefix, bool isOptional = false);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/curlrequest.cpp b/backends/networking/curl/curlrequest.cpp
new file mode 100644
index 0000000000..64fa347023
--- /dev/null
+++ b/backends/networking/curl/curlrequest.cpp
@@ -0,0 +1,162 @@
+/* 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.
+*
+*/
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/curl/curlrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "backends/networking/curl/networkreadstream.h"
+#include "common/textconsole.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+CurlRequest::CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url):
+ Request(cb, ecb), _url(url), _stream(nullptr), _headersList(nullptr), _bytesBuffer(nullptr),
+ _bytesBufferSize(0), _uploading(false), _usingPatch(false) {}
+
+CurlRequest::~CurlRequest() {
+ delete _stream;
+ delete _bytesBuffer;
+}
+
+NetworkReadStream *CurlRequest::makeStream() {
+ if (_bytesBuffer)
+ return new NetworkReadStream(_url.c_str(), _headersList, _bytesBuffer, _bytesBufferSize, _uploading, _usingPatch, true);
+ if (!_formFields.empty() || !_formFiles.empty())
+ return new NetworkReadStream(_url.c_str(), _headersList, _formFields, _formFiles);
+ return new NetworkReadStream(_url.c_str(), _headersList, _postFields, _uploading, _usingPatch);
+}
+
+void CurlRequest::handle() {
+ if (!_stream) _stream = makeStream();
+
+ if (_stream && _stream->eos()) {
+ if (_stream->httpResponseCode() != 200) {
+ warning("CurlRequest: HTTP response code is not 200 OK (it's %ld)", _stream->httpResponseCode());
+ ErrorResponse error(this, false, true, "", _stream->httpResponseCode());
+ finishError(error);
+ return;
+ }
+
+ finishSuccess(); //note that this Request doesn't call its callback on success (that's because it has nothing to return)
+ }
+}
+
+void CurlRequest::restart() {
+ if (_stream)
+ delete _stream;
+ _stream = nullptr;
+ //with no stream available next handle() will create another one
+}
+
+Common::String CurlRequest::date() const {
+ if (_stream) {
+ Common::String headers = _stream->responseHeaders();
+ const char *cstr = headers.c_str();
+ const char *position = strstr(cstr, "Date: ");
+
+ if (position) {
+ Common::String result = "";
+ char c;
+ for (const char *i = position + 6; c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ return result;
+ }
+ }
+ return "";
+}
+
+void CurlRequest::setHeaders(Common::Array<Common::String> &headers) {
+ curl_slist_free_all(_headersList);
+ _headersList = nullptr;
+ for (uint32 i = 0; i < headers.size(); ++i)
+ addHeader(headers[i]);
+}
+
+void CurlRequest::addHeader(Common::String header) {
+ _headersList = curl_slist_append(_headersList, header.c_str());
+}
+
+void CurlRequest::addPostField(Common::String keyValuePair) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST fields would be ignored, because there is buffer present");
+
+ if (!_formFields.empty() || !_formFiles.empty())
+ warning("CurlRequest: added POST fields would be ignored, because there are form fields/files present");
+
+ if (_postFields == "")
+ _postFields = keyValuePair;
+ else
+ _postFields += "&" + keyValuePair;
+}
+
+void CurlRequest::addFormField(Common::String name, Common::String value) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST form fields would be ignored, because there is buffer present");
+
+ if (_formFields.contains(name))
+ warning("CurlRequest: form field '%s' already had a value", name.c_str());
+
+ _formFields[name] = value;
+}
+
+void CurlRequest::addFormFile(Common::String name, Common::String filename) {
+ if (_bytesBuffer)
+ warning("CurlRequest: added POST form files would be ignored, because there is buffer present");
+
+ if (_formFields.contains(name))
+ warning("CurlRequest: form file field '%s' already had a value", name.c_str());
+
+ _formFiles[name] = filename;
+}
+
+void CurlRequest::setBuffer(byte *buffer, uint32 size) {
+ if (_postFields != "")
+ warning("CurlRequest: added POST fields would be ignored, because buffer added");
+
+ if (_bytesBuffer)
+ delete _bytesBuffer;
+
+ _bytesBuffer = buffer;
+ _bytesBufferSize = size;
+}
+
+void CurlRequest::usePut() { _uploading = true; }
+
+void CurlRequest::usePatch() { _usingPatch = true; }
+
+NetworkReadStreamResponse CurlRequest::execute() {
+ if (!_stream) {
+ _stream = makeStream();
+ ConnMan.addRequest(this);
+ }
+
+ return NetworkReadStreamResponse(this, _stream);
+}
+
+const NetworkReadStream *CurlRequest::getNetworkReadStream() const { return _stream; }
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/curlrequest.h b/backends/networking/curl/curlrequest.h
new file mode 100644
index 0000000000..6ce94f8983
--- /dev/null
+++ b/backends/networking/curl/curlrequest.h
@@ -0,0 +1,100 @@
+/* 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 BACKENDS_NETWORKING_CURL_CURLREQUEST_H
+#define BACKENDS_NETWORKING_CURL_CURLREQUEST_H
+
+#include "backends/networking/curl/request.h"
+#include "common/str.h"
+#include "common/array.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream;
+
+typedef Response<NetworkReadStream *> NetworkReadStreamResponse;
+typedef Common::BaseCallback<NetworkReadStreamResponse> *NetworkReadStreamCallback;
+
+class CurlRequest: public Request {
+protected:
+ Common::String _url;
+ NetworkReadStream *_stream;
+ curl_slist *_headersList;
+ Common::String _postFields;
+ Common::HashMap<Common::String, Common::String> _formFields;
+ Common::HashMap<Common::String, Common::String> _formFiles;
+ byte *_bytesBuffer;
+ uint32 _bytesBufferSize;
+ bool _uploading; //using PUT method
+ bool _usingPatch; //using PATCH method
+
+ virtual NetworkReadStream *makeStream();
+
+public:
+ CurlRequest(DataCallback cb, ErrorCallback ecb, Common::String url);
+ virtual ~CurlRequest();
+
+ virtual void handle();
+ virtual void restart();
+ virtual Common::String date() const;
+
+ /** Replaces all headers with the passed array of headers. */
+ virtual void setHeaders(Common::Array<Common::String> &headers);
+
+ /** Adds a header into headers list. */
+ virtual void addHeader(Common::String header);
+
+ /** Adds a post field (key=value pair). */
+ virtual void addPostField(Common::String field);
+
+ /** Adds a form/multipart field (name, value). */
+ virtual void addFormField(Common::String name, Common::String value);
+
+ /** Adds a form/multipart file (field name, file name). */
+ virtual void addFormFile(Common::String name, Common::String filename);
+
+ /** Sets bytes buffer. */
+ virtual void setBuffer(byte *buffer, uint32 size);
+
+ /** Remembers to use PUT method when it would create NetworkReadStream. */
+ virtual void usePut();
+
+ /** Remembers to use PATCH method when it would create NetworkReadStream. */
+ virtual void usePatch();
+
+ /**
+ * Starts this Request with ConnMan.
+ * @return its NetworkReadStream in NetworkReadStreamResponse.
+ */
+ virtual NetworkReadStreamResponse execute();
+
+ /** Returns Request's NetworkReadStream. */
+ const NetworkReadStream *getNetworkReadStream() const;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/networkreadstream.cpp b/backends/networking/curl/networkreadstream.cpp
new file mode 100644
index 0000000000..032aef87be
--- /dev/null
+++ b/backends/networking/curl/networkreadstream.cpp
@@ -0,0 +1,257 @@
+/* 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.
+*
+*/
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/curl/networkreadstream.h"
+#include "backends/networking/curl/connectionmanager.h"
+#include "base/version.h"
+#include <curl/curl.h>
+
+namespace Networking {
+
+static size_t curlDataCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->write(d, n * l);
+ return 0;
+}
+
+static size_t curlReadDataCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->fillWithSendingContents(d, n * l);
+ return 0;
+}
+
+static size_t curlHeadersCallback(char *d, size_t n, size_t l, void *p) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ return stream->addResponseHeaders(d, n * l);
+ return 0;
+}
+
+static int curlProgressCallback(void *p, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
+ NetworkReadStream *stream = (NetworkReadStream *)p;
+ if (stream)
+ stream->setProgress(dlnow, dltotal);
+ return 0;
+}
+
+static int curlProgressCallbackOlder(void *p, double dltotal, double dlnow, double ultotal, double ulnow) {
+ // for libcurl older than 7.32.0 (CURLOPT_PROGRESSFUNCTION)
+ return curlProgressCallback(p, (curl_off_t)dltotal, (curl_off_t)dlnow, (curl_off_t)ultotal, (curl_off_t)ulnow);
+}
+
+void NetworkReadStream::init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
+ _eos = _requestComplete = false;
+ _sendingContentsBuffer = nullptr;
+ _sendingContentsSize = _sendingContentsPos = 0;
+ _progressDownloaded = _progressTotal = 0;
+
+ _easy = curl_easy_init();
+ curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
+ curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us
+ curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete
+ curl_easy_setopt(_easy, CURLOPT_HEADER, 0L);
+ curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback);
+ curl_easy_setopt(_easy, CURLOPT_URL, url);
+ curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
+ curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on
+ curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList);
+ curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion);
+ curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
+#if LIBCURL_VERSION_NUM >= 0x072000
+ // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0
+ // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used
+ curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
+ curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
+#endif
+ if (uploading) {
+ curl_easy_setopt(_easy, CURLOPT_UPLOAD, 1L);
+ curl_easy_setopt(_easy, CURLOPT_READDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_READFUNCTION, curlReadDataCallback);
+ _sendingContentsBuffer = buffer;
+ _sendingContentsSize = bufferSize;
+ } else if (usingPatch) {
+ curl_easy_setopt(_easy, CURLOPT_CUSTOMREQUEST, "PATCH");
+ } else {
+ if (post || bufferSize != 0) {
+ curl_easy_setopt(_easy, CURLOPT_POSTFIELDSIZE, bufferSize);
+ curl_easy_setopt(_easy, CURLOPT_COPYPOSTFIELDS, buffer);
+ }
+ }
+ ConnMan.registerEasyHandle(_easy);
+}
+
+void NetworkReadStream::init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
+ _eos = _requestComplete = false;
+ _sendingContentsBuffer = nullptr;
+ _sendingContentsSize = _sendingContentsPos = 0;
+ _progressDownloaded = _progressTotal = 0;
+
+ _easy = curl_easy_init();
+ curl_easy_setopt(_easy, CURLOPT_WRITEFUNCTION, curlDataCallback);
+ curl_easy_setopt(_easy, CURLOPT_WRITEDATA, this); //so callback can call us
+ curl_easy_setopt(_easy, CURLOPT_PRIVATE, this); //so ConnectionManager can call us when request is complete
+ curl_easy_setopt(_easy, CURLOPT_HEADER, 0L);
+ curl_easy_setopt(_easy, CURLOPT_HEADERDATA, this);
+ curl_easy_setopt(_easy, CURLOPT_HEADERFUNCTION, curlHeadersCallback);
+ curl_easy_setopt(_easy, CURLOPT_URL, url);
+ curl_easy_setopt(_easy, CURLOPT_VERBOSE, 0L);
+ curl_easy_setopt(_easy, CURLOPT_FOLLOWLOCATION, 1L); //probably it's OK to have it always on
+ curl_easy_setopt(_easy, CURLOPT_HTTPHEADER, headersList);
+ curl_easy_setopt(_easy, CURLOPT_USERAGENT, gScummVMFullVersion);
+ curl_easy_setopt(_easy, CURLOPT_NOPROGRESS, 0L);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSFUNCTION, curlProgressCallbackOlder);
+ curl_easy_setopt(_easy, CURLOPT_PROGRESSDATA, this);
+#if LIBCURL_VERSION_NUM >= 0x072000
+ // CURLOPT_XFERINFOFUNCTION introduced in libcurl 7.32.0
+ // CURLOPT_PROGRESSFUNCTION is used as a backup plan in case older version is used
+ curl_easy_setopt(_easy, CURLOPT_XFERINFOFUNCTION, curlProgressCallback);
+ curl_easy_setopt(_easy, CURLOPT_XFERINFODATA, this);
+#endif
+
+ // set POST multipart upload form fields/files
+ struct curl_httppost *formpost = nullptr;
+ struct curl_httppost *lastptr = nullptr;
+
+ for (Common::HashMap<Common::String, Common::String>::iterator i = formFields.begin(); i != formFields.end(); ++i) {
+ CURLFORMcode code = curl_formadd(
+ &formpost,
+ &lastptr,
+ CURLFORM_COPYNAME, i->_key.c_str(),
+ CURLFORM_COPYCONTENTS, i->_value.c_str(),
+ CURLFORM_END
+ );
+
+ if (code != CURL_FORMADD_OK)
+ warning("NetworkReadStream: field curl_formadd('%s') failed", i->_key.c_str());
+ }
+
+ for (Common::HashMap<Common::String, Common::String>::iterator i = formFiles.begin(); i != formFiles.end(); ++i) {
+ CURLFORMcode code = curl_formadd(
+ &formpost,
+ &lastptr,
+ CURLFORM_COPYNAME, i->_key.c_str(),
+ CURLFORM_FILE, i->_value.c_str(),
+ CURLFORM_END
+ );
+
+ if (code != CURL_FORMADD_OK)
+ warning("NetworkReadStream: file curl_formadd('%s') failed", i->_key.c_str());
+ }
+
+ curl_easy_setopt(_easy, CURLOPT_HTTPPOST, formpost);
+
+ ConnMan.registerEasyHandle(_easy);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading, bool usingPatch) {
+ init(url, headersList, (const byte *)postFields.c_str(), postFields.size(), uploading, usingPatch, false);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles) {
+ init(url, headersList, formFields, formFiles);
+}
+
+NetworkReadStream::NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post) {
+ init(url, headersList, buffer, bufferSize, uploading, usingPatch, post);
+}
+
+NetworkReadStream::~NetworkReadStream() {
+ if (_easy)
+ curl_easy_cleanup(_easy);
+}
+
+bool NetworkReadStream::eos() const {
+ return _eos;
+}
+
+uint32 NetworkReadStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 actuallyRead = MemoryReadWriteStream::read(dataPtr, dataSize);
+
+ if (actuallyRead == 0) {
+ if (_requestComplete)
+ _eos = true;
+ return 0;
+ }
+
+ return actuallyRead;
+}
+
+void NetworkReadStream::finished() {
+ _requestComplete = true;
+}
+
+long NetworkReadStream::httpResponseCode() const {
+ long responseCode = -1;
+ if (_easy)
+ curl_easy_getinfo(_easy, CURLINFO_RESPONSE_CODE, &responseCode);
+ return responseCode;
+}
+
+Common::String NetworkReadStream::currentLocation() const {
+ Common::String result = "";
+ if (_easy) {
+ char *pointer;
+ curl_easy_getinfo(_easy, CURLINFO_EFFECTIVE_URL, &pointer);
+ result = Common::String(pointer);
+ }
+ return result;
+}
+
+Common::String NetworkReadStream::responseHeaders() const {
+ return _responseHeaders;
+}
+
+uint32 NetworkReadStream::fillWithSendingContents(char *bufferToFill, uint32 maxSize) {
+ uint32 size = _sendingContentsSize - _sendingContentsPos;
+ if (size > maxSize)
+ size = maxSize;
+ for (uint32 i = 0; i < size; ++i) {
+ bufferToFill[i] = _sendingContentsBuffer[_sendingContentsPos + i];
+ }
+ _sendingContentsPos += size;
+ return size;
+}
+
+uint32 NetworkReadStream::addResponseHeaders(char *buffer, uint32 size) {
+ _responseHeaders += Common::String(buffer, size);
+ return size;
+}
+
+double NetworkReadStream::getProgress() const {
+ if (_progressTotal < 1)
+ return 0;
+ return (double)_progressDownloaded / (double)_progressTotal;
+}
+
+void NetworkReadStream::setProgress(uint64 downloaded, uint64 total) {
+ _progressDownloaded = downloaded;
+ _progressTotal = total;
+}
+
+} // End of namespace Cloud
diff --git a/backends/networking/curl/networkreadstream.h b/backends/networking/curl/networkreadstream.h
new file mode 100644
index 0000000000..2be6d591cb
--- /dev/null
+++ b/backends/networking/curl/networkreadstream.h
@@ -0,0 +1,142 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H
+#define BACKENDS_NETWORKING_CURL_NETWORKREADSTREAM_H
+
+#include "common/memstream.h"
+#include "common/stream.h"
+#include "common/str.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+typedef void CURL;
+struct curl_slist;
+
+namespace Networking {
+
+class NetworkReadStream: public Common::MemoryReadWriteStream {
+ CURL *_easy;
+ bool _eos, _requestComplete;
+ const byte *_sendingContentsBuffer;
+ uint32 _sendingContentsSize;
+ uint32 _sendingContentsPos;
+ Common::String _responseHeaders;
+ uint64 _progressDownloaded, _progressTotal;
+ void init(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading, bool usingPatch, bool post);
+ void init(const char *url, curl_slist *headersList, Common::HashMap<Common::String, Common::String> formFields, Common::HashMap<Common::String, Common::String> formFiles);
+
+public:
+ /** Send <postFields>, using POST by default. */
+ NetworkReadStream(const char *url, curl_slist *headersList, Common::String postFields, bool uploading = false, bool usingPatch = false);
+ /** Send <formFields>, <formFiles>, using POST multipart/form. */
+ NetworkReadStream(
+ const char *url, curl_slist *headersList,
+ Common::HashMap<Common::String, Common::String> formFields,
+ Common::HashMap<Common::String, Common::String> formFiles);
+ /** Send <buffer, using POST by default. */
+ NetworkReadStream(const char *url, curl_slist *headersList, const byte *buffer, uint32 bufferSize, bool uploading = false, bool usingPatch = false, bool post = true);
+ virtual ~NetworkReadStream();
+
+ /**
+ * Returns true if a read failed because the stream end has been reached.
+ * This flag is cleared by clearErr().
+ * For a SeekableReadStream, it is also cleared by a successful seek.
+ *
+ * @note The semantics of any implementation of this method are
+ * supposed to match those of ISO C feof(). In particular, in a stream
+ * with N bytes, reading exactly N bytes from the start should *not*
+ * set eos; only reading *beyond* the available data should set it.
+ */
+ virtual bool eos() const;
+
+ /**
+ * Read data from the stream. Subclasses must implement this
+ * method; all other read methods are implemented using it.
+ *
+ * @note The semantics of any implementation of this method are
+ * supposed to match those of ISO C fread(), in particular where
+ * it concerns setting error and end of file/stream flags.
+ *
+ * @param dataPtr pointer to a buffer into which the data is read
+ * @param dataSize number of bytes to be read
+ * @return the number of bytes which were actually read.
+ */
+ virtual uint32 read(void *dataPtr, uint32 dataSize);
+
+ /**
+ * This method is called by ConnectionManager to indicate
+ * that transfer is finished.
+ *
+ * @note It's called on failure too.
+ */
+ void finished();
+
+ /**
+ * Returns HTTP response code from inner CURL handle.
+ * It returns -1 to indicate there is no inner handle.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ long httpResponseCode() const;
+
+ /**
+ * Return current location URL from inner CURL handle.
+ * "" is returned to indicate there is no inner handle.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ Common::String currentLocation() const;
+
+ /**
+ * Return response headers.
+ *
+ * @note This method should be called when eos() == true.
+ */
+ Common::String responseHeaders() const;
+
+ /**
+ * Fills the passed buffer with _sendingContentsBuffer contents.
+ * It works similarly to read(), expect it's not for reading
+ * Stream's contents, but for sending our own data to the server.
+ *
+ * @returns how many bytes were actually read (filled in)
+ */
+ uint32 fillWithSendingContents(char *bufferToFill, uint32 maxSize);
+
+ /**
+ * Remembers headers returned to CURL in server's response.
+ *
+ * @returns how many bytes were actually read
+ */
+ uint32 addResponseHeaders(char *buffer, uint32 size);
+
+ /** Returns a number in range [0, 1], where 1 is "complete". */
+ double getProgress() const;
+
+ /** Used in curl progress callback to pass current downloaded/total values. */
+ void setProgress(uint64 downloaded, uint64 total);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/curl/request.cpp b/backends/networking/curl/request.cpp
new file mode 100644
index 0000000000..30af48a478
--- /dev/null
+++ b/backends/networking/curl/request.cpp
@@ -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.
+*
+*/
+
+#include "backends/networking/curl/request.h"
+
+namespace Networking {
+
+ErrorResponse::ErrorResponse(Request *rq):
+ request(rq), interrupted(false), failed(true), response(""), httpResponseCode(-1) {}
+
+ErrorResponse::ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode):
+ request(rq), interrupted(interrupt), failed(failure), response(resp), httpResponseCode(httpCode) {}
+
+Request::Request(DataCallback cb, ErrorCallback ecb):
+ _callback(cb), _errorCallback(ecb), _state(PROCESSING), _retryInSeconds(0) {}
+
+Request::~Request() {
+ delete _callback;
+ delete _errorCallback;
+}
+
+void Request::handleRetry() {
+ if (_retryInSeconds > 0) {
+ --_retryInSeconds;
+ } else {
+ _state = PROCESSING;
+ restart();
+ }
+}
+
+void Request::pause() { _state = PAUSED; }
+
+void Request::finish() {
+ ErrorResponse error(this, true, false, "", -1);
+ finishError(error);
+}
+
+void Request::retry(uint32 seconds) {
+ _state = RETRY;
+ _retryInSeconds = seconds;
+}
+
+RequestState Request::state() const { return _state; }
+
+Common::String Request::date() const { return ""; }
+
+void Request::finishError(ErrorResponse error) {
+ _state = FINISHED;
+ if (_errorCallback)
+ (*_errorCallback)(error);
+}
+
+void Request::finishSuccess() { _state = FINISHED; }
+
+} // End of namespace Networking
diff --git a/backends/networking/curl/request.h b/backends/networking/curl/request.h
new file mode 100644
index 0000000000..9b366ea40c
--- /dev/null
+++ b/backends/networking/curl/request.h
@@ -0,0 +1,203 @@
+/* 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 BACKENDS_NETWORKING_CURL_REQUEST_H
+#define BACKENDS_NETWORKING_CURL_REQUEST_H
+
+#include "common/callback.h"
+#include "common/scummsys.h"
+#include "common/str.h"
+
+namespace Networking {
+
+class Request;
+
+/**
+ * Response<T> is a struct to be returned from Request
+ * to user's callbacks. It's a type safe way to indicate
+ * which "return value" Request has and user awaits.
+ *
+ * It just keeps a Request pointer together with
+ * some T value (which might be a pointer, a reference
+ * or a plain type (copied by value)).
+ *
+ * To make it more convenient, typedefs are used.
+ * For example, Response<void *> is called DataResponse
+ * and corresponding callback pointer is DataCallback.
+ */
+
+template<typename T> struct Response {
+ Request *request;
+ T value;
+
+ Response(Request *rq, T v) : request(rq), value(v) {}
+};
+
+/**
+ * ErrorResponse is a struct to be returned from Request
+ * to user's failure callbacks.
+ *
+ * It keeps a Request pointer together with some useful
+ * information fields, which would explain why failure
+ * callback was called.
+ *
+ * <interrupted> flag is set when Request was interrupted,
+ * i.e. finished by user with finish() call.
+ *
+ * <failed> flag is set when Request has failed because of
+ * some error (bad server response, for example).
+ *
+ * <response> contains server's original response.
+ *
+ * <httpResponseCode> contains server's HTTP response code.
+ */
+
+struct ErrorResponse {
+ Request *request;
+ bool interrupted;
+ bool failed;
+ Common::String response;
+ long httpResponseCode;
+
+ ErrorResponse(Request *rq);
+ ErrorResponse(Request *rq, bool interrupt, bool failure, Common::String resp, long httpCode);
+};
+
+typedef Response<void *> DataReponse;
+typedef Common::BaseCallback<DataReponse> *DataCallback;
+typedef Common::BaseCallback<ErrorResponse> *ErrorCallback;
+
+/**
+ * RequestState is used to indicate current Request state.
+ * ConnectionManager uses it to decide what to do with the Request.
+ *
+ * PROCESSING state indicates that Request is working.
+ * ConnectionManager calls handle() method of Requests in that state.
+ *
+ * PAUSED state indicates that Request is not working.
+ * ConnectionManager keeps Requests in that state and doesn't call any methods of those.
+ *
+ * RETRY state indicates that Request must restart after a few seconds.
+ * ConnectionManager calls handleRetry() method of Requests in that state.
+ * Default handleRetry() implementation decreases _retryInSeconds value
+ * until it reaches zero. When it does, Request's restart() method is called.
+ *
+ * FINISHED state indicates that Request did the work and might be deleted.
+ * ConnectionManager deletes Requests in that state.
+ * After this state is set, but before ConnectionManager deletes the Request,
+ * Request calls user's callback. User can ask Request to change its state
+ * by calling retry() or pause() methods and Request won't be deleted.
+ *
+ * Request get a success and failure callbacks. Request must call one
+ * (and only one!) of these callbacks when it sets FINISHED state.
+ */
+enum RequestState {
+ PROCESSING,
+ PAUSED,
+ RETRY,
+ FINISHED
+};
+
+class Request {
+protected:
+ /**
+ * Callback, which should be called when Request is finished.
+ * That's the way Requests pass the result to the code which asked to create this request.
+ *
+ * @note some Requests use their own callbacks to return something but void *.
+ * @note callback must be called in finish() or similar method.
+ */
+ DataCallback _callback;
+
+ /**
+ * Callback, which should be called when Request is failed/interrupted.
+ * That's the way Requests pass error information to the code which asked to create this request.
+ * @note callback must be called in finish() or similar method.
+ */
+ ErrorCallback _errorCallback;
+
+ /**
+ * Request state, which is used by ConnectionManager to determine
+ * whether request might be deleted or it's still working.
+ *
+ * State might be changed from outside with finish(), pause() or
+ * retry() methods. Override these if you want to react to these
+ * changes correctly.
+ */
+ RequestState _state;
+
+ /** In RETRY state this indicates whether it's time to call restart(). */
+ uint32 _retryInSeconds;
+
+ /** Sets FINISHED state and calls the _errorCallback with given error. */
+ virtual void finishError(ErrorResponse error);
+
+ /** Sets FINISHED state. Implementations might extend it if needed. */
+ virtual void finishSuccess();
+
+public:
+ Request(DataCallback cb, ErrorCallback ecb);
+ virtual ~Request();
+
+ /** Method, which does actual work. Depends on what this Request is doing. */
+ virtual void handle() = 0;
+
+ /** Method, which is called by ConnectionManager when Request's state is RETRY. */
+ virtual void handleRetry();
+
+ /** Method, which is used to restart the Request. */
+ virtual void restart() = 0;
+
+ /** Method, which is called to pause the Request. */
+ virtual void pause();
+
+ /**
+ * Method, which is called to *interrupt* the Request.
+ * When it's called, Request must stop its work and
+ * call the failure callback to notify user.
+ */
+ virtual void finish();
+
+ /** Method, which is called to retry the Request. */
+ virtual void retry(uint32 seconds);
+
+ /** Returns Request's current state. */
+ RequestState state() const;
+
+ /**
+ * Return date this Request received from server.
+ * It could be extracted from "Date" header,
+ * which is kept in NetworkReadStream.
+ *
+ * @note not all Requests do that, so "" is returned
+ * to indicate the date is unknown. That's also true
+ * if no server response available or no "Date" header
+ * was passed.
+ *
+ * @returns date from "Date" response header.
+ */
+ virtual Common::String date() const;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/make_archive.py b/backends/networking/make_archive.py
new file mode 100644
index 0000000000..64d314bedd
--- /dev/null
+++ b/backends/networking/make_archive.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+# encoding: utf-8
+import sys
+import re
+import os
+import zipfile
+
+ARCHIVE_FILE_EXTENSIONS = ('.html', '.css', '.js', '.ico', '.png')
+
+def buildArchive(archiveName):
+ if not os.path.isdir(archiveName):
+ print ("Invalid archive name: " + archiveName)
+ return
+
+ zf = zipfile.ZipFile(archiveName + ".zip", 'w')
+
+ print ("Building '" + archiveName + "' archive:")
+ os.chdir(archiveName)
+
+ directories = ['.', './icons']
+ for d in directories:
+ filenames = os.listdir(d)
+ filenames.sort()
+ for filename in filenames:
+ if os.path.isfile(d + '/' + filename) and filename.endswith(ARCHIVE_FILE_EXTENSIONS):
+ zf.write(d + '/' + filename, d + '/' + filename)
+ print (" Adding file: " + d + '/' + filename)
+
+ os.chdir('../')
+
+ zf.close()
+
+def main():
+ buildArchive("wwwroot")
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/backends/networking/sdl_net/client.cpp b/backends/networking/sdl_net/client.cpp
new file mode 100644
index 0000000000..dab38ba5c0
--- /dev/null
+++ b/backends/networking/sdl_net/client.cpp
@@ -0,0 +1,190 @@
+/* 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.
+*
+*/
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/sdl_net/client.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/memstream.h"
+#include <SDL/SDL_net.h>
+
+namespace Networking {
+
+Client::Client():
+ _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr),
+ _previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) {}
+
+Client::Client(SDLNet_SocketSet set, TCPsocket socket):
+ _state(INVALID), _set(nullptr), _socket(nullptr), _handler(nullptr),
+ _previousHandler(nullptr), _stream(nullptr), _buffer(new byte[CLIENT_BUFFER_SIZE]) {
+ open(set, socket);
+}
+
+Client::~Client() {
+ close();
+ delete[] _buffer;
+}
+
+void Client::open(SDLNet_SocketSet set, TCPsocket socket) {
+ if (_state != INVALID)
+ close();
+ _state = READING_HEADERS;
+ _socket = socket;
+ _set = set;
+ Reader cleanReader;
+ _reader = cleanReader;
+ if (_handler)
+ delete _handler;
+ _handler = nullptr;
+ if (_previousHandler)
+ delete _previousHandler;
+ _previousHandler = nullptr;
+ _stream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+ if (set) {
+ int numused = SDLNet_TCP_AddSocket(set, socket);
+ if (numused == -1) {
+ error("Client: SDLNet_AddSocket: %s\n", SDLNet_GetError());
+ }
+ }
+}
+
+bool Client::readMoreIfNeeded() {
+ if (_stream == nullptr)
+ return false; //nothing to read into
+ if (_stream->size() - _stream->pos() > 0)
+ return true; //not needed, some data left in the stream
+ if (!_socket)
+ return false;
+ if (!SDLNet_SocketReady(_socket))
+ return false;
+
+ int bytes = SDLNet_TCP_Recv(_socket, _buffer, CLIENT_BUFFER_SIZE);
+ if (bytes <= 0) {
+ warning("Client::readMoreIfNeeded: recv fail");
+ close();
+ return false;
+ }
+
+ if (_stream->write(_buffer, bytes) != bytes) {
+ warning("Client::readMoreIfNeeded: failed to write() into MemoryReadWriteStream");
+ close();
+ return false;
+ }
+
+ return true;
+}
+
+void Client::readHeaders() {
+ if (!readMoreIfNeeded())
+ return;
+ _reader.setContent(_stream);
+ if (_reader.readFirstHeaders())
+ _state = (_reader.badRequest() ? BAD_REQUEST : READ_HEADERS);
+}
+
+bool Client::readContent(Common::WriteStream *stream) {
+ if (!readMoreIfNeeded())
+ return false;
+ _reader.setContent(_stream);
+ return _reader.readFirstContent(stream);
+}
+
+bool Client::readBlockHeaders(Common::WriteStream *stream) {
+ if (!readMoreIfNeeded())
+ return false;
+ _reader.setContent(_stream);
+ return _reader.readBlockHeaders(stream);
+}
+
+bool Client::readBlockContent(Common::WriteStream *stream) {
+ if (!readMoreIfNeeded())
+ return false;
+ _reader.setContent(_stream);
+ return _reader.readBlockContent(stream);
+}
+
+void Client::setHandler(ClientHandler *handler) {
+ if (_handler) {
+ if (_previousHandler)
+ delete _previousHandler;
+ _previousHandler = _handler; //can't just delete it, as setHandler() could've been called by handler itself
+ }
+ _state = BEING_HANDLED;
+ _handler = handler;
+}
+
+void Client::handle() {
+ if (_state != BEING_HANDLED)
+ warning("handle() called in a wrong Client's state");
+ if (!_handler)
+ warning("Client doesn't have handler to be handled by");
+ if (_handler)
+ _handler->handle(this);
+}
+
+void Client::close() {
+ if (_set) {
+ if (_socket) {
+ int numused = SDLNet_TCP_DelSocket(_set, _socket);
+ if (numused == -1)
+ error("Client: SDLNet_DelSocket: %s\n", SDLNet_GetError());
+ }
+ _set = nullptr;
+ }
+
+ if (_socket) {
+ SDLNet_TCP_Close(_socket);
+ _socket = nullptr;
+ }
+
+ if (_stream) {
+ delete _stream;
+ _stream = nullptr;
+ }
+
+ _state = INVALID;
+}
+
+
+ClientState Client::state() const { return _state; }
+
+Common::String Client::headers() const { return _reader.headers(); }
+
+Common::String Client::method() const { return _reader.method(); }
+
+Common::String Client::path() const { return _reader.path(); }
+
+Common::String Client::query() const { return _reader.query(); }
+
+Common::String Client::queryParameter(Common::String name) const { return _reader.queryParameter(name); }
+
+Common::String Client::anchor() const { return _reader.anchor(); }
+
+bool Client::noMoreContent() const { return _reader.noMoreContent(); }
+
+bool Client::socketIsReady() { return SDLNet_SocketReady(_socket); }
+
+int Client::recv(void *data, int maxlen) { return SDLNet_TCP_Recv(_socket, data, maxlen); }
+
+int Client::send(void *data, int len) { return SDLNet_TCP_Send(_socket, data, len); }
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/client.h b/backends/networking/sdl_net/client.h
new file mode 100644
index 0000000000..134c1be05d
--- /dev/null
+++ b/backends/networking/sdl_net/client.h
@@ -0,0 +1,125 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_CLIENT_H
+#define BACKENDS_NETWORKING_SDL_NET_CLIENT_H
+
+#include "backends/networking/sdl_net/reader.h"
+#include "common/str.h"
+
+namespace Common {
+class MemoryReadWriteStream;
+}
+
+typedef struct _SDLNet_SocketSet *SDLNet_SocketSet;
+typedef struct _TCPsocket *TCPsocket;
+
+namespace Networking {
+
+enum ClientState {
+ INVALID,
+ READING_HEADERS,
+ READ_HEADERS,
+ BAD_REQUEST,
+ BEING_HANDLED
+};
+
+class Client;
+
+#define CLIENT_BUFFER_SIZE 1 * 1024 * 1024
+
+class ClientHandler {
+public:
+ virtual ~ClientHandler() {};
+ virtual void handle(Client *client) = 0;
+};
+
+/**
+ * Client class represents one client's HTTP request
+ * to the LocalWebserver.
+ *
+ * While in READING_HEADERS state, it's kept in LocalWebserver.
+ * Client must read the headers and decide whether it's
+ * READ_HEADERS (could be handled) or BAD_REQUEST (failed).
+ *
+ * If it's READ_HEADERS, LocalWebserver searches for a corresponding
+ * BaseHandler. These classes use the information from headers -
+ * like method, path, GET parameters - to build the response
+ * for this client's request. When they do, they call setHandler()
+ * and pass a special ClientHandler. Client becomes BEING_HANDLED.
+ *
+ * While in that state, LocalWebserver calls Client's handle() and
+ * it's passed to ClientHandler. The latter does the job: it commands
+ * Client to read or write bytes with its socket or calls
+ * readContent() methods, so Client reads the request through Reader.
+ */
+
+class Client {
+ ClientState _state;
+ SDLNet_SocketSet _set;
+ TCPsocket _socket;
+ Reader _reader;
+ ClientHandler *_handler, *_previousHandler;
+ Common::MemoryReadWriteStream *_stream;
+ byte *_buffer;
+
+ bool readMoreIfNeeded();
+
+public:
+ Client();
+ Client(SDLNet_SocketSet set, TCPsocket socket);
+ virtual ~Client();
+
+ void open(SDLNet_SocketSet set, TCPsocket socket);
+ void readHeaders();
+ bool readContent(Common::WriteStream *stream);
+ bool readBlockHeaders(Common::WriteStream *stream);
+ bool readBlockContent(Common::WriteStream *stream);
+ void setHandler(ClientHandler *handler);
+ void handle();
+ void close();
+
+ ClientState state() const;
+ Common::String headers() const;
+ Common::String method() const;
+ Common::String path() const;
+ Common::String query() const;
+ Common::String queryParameter(Common::String name) const;
+ Common::String anchor() const;
+
+ bool noMoreContent() const;
+
+ /**
+ * Return SDLNet_SocketReady(_socket).
+ *
+ * It's "ready" when it has something
+ * to read (recv()). You can send()
+ * when this is false.
+ */
+ bool socketIsReady();
+ int recv(void *data, int maxlen);
+ int send(void *data, int len);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/getclienthandler.cpp b/backends/networking/sdl_net/getclienthandler.cpp
new file mode 100644
index 0000000000..1c4f5db8a8
--- /dev/null
+++ b/backends/networking/sdl_net/getclienthandler.cpp
@@ -0,0 +1,162 @@
+/* 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 "backends/networking/sdl_net/getclienthandler.h"
+#include "common/textconsole.h"
+
+namespace Networking {
+
+GetClientHandler::GetClientHandler(Common::SeekableReadStream *stream):
+ _responseCode(200), _headersPrepared(false),
+ _stream(stream), _buffer(new byte[CLIENT_HANDLER_BUFFER_SIZE]) {}
+
+GetClientHandler::~GetClientHandler() {
+ delete _stream;
+ delete[] _buffer;
+}
+
+const char *GetClientHandler::responseMessage(long responseCode) {
+ switch (responseCode) {
+ case 100: return "Continue";
+ case 101: return "Switching Protocols";
+ case 102: return "Processing";
+
+ case 200: return "OK";
+ case 201: return "Created";
+ case 202: return "Accepted";
+ case 203: return "Non-Authoritative Information";
+ case 204: return "No Content";
+ case 205: return "Reset Content";
+ case 206: return "Partial Content";
+ case 207: return "Multi-Status";
+ case 226: return "IM Used";
+
+ case 300: return "Multiple Choices";
+ case 301: return "Moved Permanently";
+ case 302: return "Moved Temporarily"; //case 302: return "Found";
+ case 303: return "See Other";
+ case 304: return "Not Modified";
+ case 305: return "Use Proxy";
+ case 306: return "RESERVED";
+ case 307: return "Temporary Redirect";
+
+ case 400: return "Bad Request";
+ case 401: return "Unauthorized";
+ case 402: return "Payment Required";
+ case 403: return "Forbidden";
+ case 404: return "Not Found";
+ case 405: return "Method Not Allowed";
+ case 406: return "Not Acceptable";
+ case 407: return "Proxy Authentication Required";
+ case 408: return "Request Timeout";
+ case 409: return "Conflict";
+ case 410: return "Gone";
+ case 411: return "Length Required";
+ case 412: return "Precondition Failed";
+ case 413: return "Request Entity Too Large";
+ case 414: return "Request-URI Too Large";
+ case 415: return "Unsupported Media Type";
+ case 416: return "Requested Range Not Satisfiable";
+ case 417: return "Expectation Failed";
+ case 422: return "Unprocessable Entity";
+ case 423: return "Locked";
+ case 424: return "Failed Dependency";
+ case 425: return "Unordered Collection";
+ case 426: return "Upgrade Required";
+ case 428: return "Precondition Required";
+ case 429: return "Too Many Requests";
+ case 431: return "Request Header Fields Too Large";
+ case 434: return "Requested Host Unavailable";
+ case 449: return "Retry With";
+ case 451: return "Unavailable For Legal Reasons";
+
+ case 500: return "Internal Server Error";
+ case 501: return "Not Implemented";
+ case 502: return "Bad Gateway";
+ case 503: return "Service Unavailable";
+ case 504: return "Gateway Timeout";
+ case 505: return "HTTP Version Not Supported";
+ case 506: return "Variant Also Negotiates";
+ case 507: return "Insufficient Storage";
+ case 508: return "Loop Detected";
+ case 509: return "Bandwidth Limit Exceeded";
+ case 510: return "Not Extended";
+ case 511: return "Network Authentication Required";
+ }
+ return "Unknown";
+}
+
+void GetClientHandler::prepareHeaders() {
+ if (!_specialHeaders.contains("Content-Type"))
+ setHeader("Content-Type", "text/html");
+
+ if (!_specialHeaders.contains("Content-Length") && _stream)
+ setHeader("Content-Length", Common::String::format("%u", _stream->size()));
+
+ _headers = Common::String::format("HTTP/1.1 %ld %s\r\n", _responseCode, responseMessage(_responseCode));
+ for (Common::HashMap<Common::String, Common::String>::iterator i = _specialHeaders.begin(); i != _specialHeaders.end(); ++i)
+ _headers += i->_key + ": " + i->_value + "\r\n";
+ _headers += "\r\n";
+
+ _headersPrepared = true;
+}
+
+void GetClientHandler::handle(Client *client) {
+ if (!client)
+ return;
+ if (!_headersPrepared)
+ prepareHeaders();
+
+ uint32 readBytes;
+
+ // send headers first
+ if (_headers.size() > 0) {
+ readBytes = _headers.size();
+ if (readBytes > CLIENT_HANDLER_BUFFER_SIZE)
+ readBytes = CLIENT_HANDLER_BUFFER_SIZE;
+ memcpy(_buffer, _headers.c_str(), readBytes);
+ _headers.erase(0, readBytes);
+ } else {
+ if (!_stream) {
+ client->close();
+ return;
+ }
+
+ readBytes = _stream->read(_buffer, CLIENT_HANDLER_BUFFER_SIZE);
+ }
+
+ if (readBytes != 0)
+ if (client->send(_buffer, readBytes) != readBytes) {
+ warning("GetClientHandler: unable to send all bytes to the client");
+ client->close();
+ return;
+ }
+
+ // we're done here!
+ if (_stream->eos())
+ client->close();
+}
+
+void GetClientHandler::setHeader(Common::String name, Common::String value) { _specialHeaders[name] = value; }
+void GetClientHandler::setResponseCode(long code) { _responseCode = code; }
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/getclienthandler.h b/backends/networking/sdl_net/getclienthandler.h
new file mode 100644
index 0000000000..3486ceef8a
--- /dev/null
+++ b/backends/networking/sdl_net/getclienthandler.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 BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_GETCLIENTHANDLER_H
+
+#include "backends/networking/sdl_net/client.h"
+#include "common/hashmap.h"
+#include "common/stream.h"
+#include "common/hash-str.h"
+
+namespace Networking {
+
+#define CLIENT_HANDLER_BUFFER_SIZE 1 * 1024 * 1024
+
+class GetClientHandler: public ClientHandler {
+ Common::HashMap<Common::String, Common::String> _specialHeaders;
+ long _responseCode;
+ bool _headersPrepared;
+ Common::String _headers;
+ Common::SeekableReadStream *_stream;
+ byte *_buffer;
+
+ static const char *responseMessage(long responseCode);
+ void prepareHeaders();
+
+public:
+ GetClientHandler(Common::SeekableReadStream *stream);
+ virtual ~GetClientHandler();
+
+ virtual void handle(Client *client);
+ void setHeader(Common::String name, Common::String value);
+ void setResponseCode(long code);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/basehandler.h b/backends/networking/sdl_net/handlers/basehandler.h
new file mode 100644
index 0000000000..dec5e955bd
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/basehandler.h
@@ -0,0 +1,41 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_BASEHANDLER_H
+
+#include "backends/networking/sdl_net/client.h"
+
+namespace Networking {
+
+class BaseHandler {
+public:
+ BaseHandler() {}
+ virtual ~BaseHandler() {}
+
+ virtual void handle(Client &) = 0;
+ virtual bool minimalModeSupported() { return false; }
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp b/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
new file mode 100644
index 0000000000..284bf16651
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/createdirectoryhandler.cpp
@@ -0,0 +1,129 @@
+/* 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 "backends/networking/sdl_net/handlers/createdirectoryhandler.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/translation.h"
+#include <common/callback.h>
+
+namespace Networking {
+
+CreateDirectoryHandler::CreateDirectoryHandler() {}
+
+CreateDirectoryHandler::~CreateDirectoryHandler() {}
+
+void CreateDirectoryHandler::handleError(Client &client, Common::String message) const {
+ if (client.queryParameter("answer_json") == "true")
+ setJsonResponseHandler(client, "error", message);
+ else
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, message);
+}
+
+void CreateDirectoryHandler::setJsonResponseHandler(Client &client, Common::String type, Common::String message) const {
+ Common::JSONObject response;
+ response.setVal("type", new Common::JSONValue(type));
+ response.setVal("message", new Common::JSONValue(message));
+
+ Common::JSONValue json = response;
+ LocalWebserver::setClientGetHandler(client, json.stringify(true));
+}
+
+/// public
+
+void CreateDirectoryHandler::handle(Client &client) {
+ Common::String path = client.queryParameter("path");
+ Common::String name = client.queryParameter("directory_name");
+
+ // check that <path> is not an absolute root
+ if (path == "" || path == "/") {
+ handleError(client, _("Can't create directory here!"));
+ return;
+ }
+
+ // check that <path> contains no '../'
+ if (HandlerUtils::hasForbiddenCombinations(path)) {
+ handleError(client, _("Invalid path!"));
+ return;
+ }
+
+ // transform virtual path to actual file system one
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd) || path.empty()) {
+ handleError(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> exists, is directory and isn't forbidden
+ AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path);
+ if (!HandlerUtils::permittedPath(node->getPath())) {
+ handleError(client, _("Invalid path!"));
+ return;
+ }
+ if (!node->exists()) {
+ handleError(client, _("Parent directory doesn't exists!"));
+ return;
+ }
+ if (!node->isDirectory()) {
+ handleError(client, _("Can't create a directory within a file!"));
+ return;
+ }
+
+ // check that <directory_name> doesn't exist or is directory
+ if (path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+ node = g_system->getFilesystemFactory()->makeFileNodePath(path + name);
+ if (node->exists()) {
+ if (!node->isDirectory()) {
+ handleError(client, _("There is a file with that name in the parent directory!"));
+ return;
+ }
+ } else {
+ // create the <directory_name> in <path>
+ if (!node->create(true)) {
+ handleError(client, _("Failed to create the directory!"));
+ return;
+ }
+ }
+
+ // if json requested, respond with it
+ if (client.queryParameter("answer_json") == "true") {
+ setJsonResponseHandler(client, "success", _("Directory created successfully!"));
+ return;
+ }
+
+ // set redirect on success
+ HandlerUtils::setMessageHandler(
+ client,
+ Common::String::format(
+ "%s<br/><a href=\"files?path=%s\">%s</a>",
+ _("Directory created successfully!"),
+ client.queryParameter("path").c_str(),
+ _("Back to parent directory")
+ ),
+ (client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") +
+ LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))
+ );
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/createdirectoryhandler.h b/backends/networking/sdl_net/handlers/createdirectoryhandler.h
new file mode 100644
index 0000000000..2a18d5c4aa
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/createdirectoryhandler.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 BACKENDS_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_CREATEDIRECTORYHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class CreateDirectoryHandler: public FilesBaseHandler {
+ void handleError(Client &client, Common::String message) const;
+ void setJsonResponseHandler(Client &client, Common::String type, Common::String message) const;
+public:
+ CreateDirectoryHandler();
+ virtual ~CreateDirectoryHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/downloadfilehandler.cpp b/backends/networking/sdl_net/handlers/downloadfilehandler.cpp
new file mode 100644
index 0000000000..9e212b1a6c
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/downloadfilehandler.cpp
@@ -0,0 +1,88 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 "backends/networking/sdl_net/handlers/downloadfilehandler.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/networking/sdl_net/getclienthandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+DownloadFileHandler::DownloadFileHandler() {}
+
+DownloadFileHandler::~DownloadFileHandler() {}
+
+/// public
+
+void DownloadFileHandler::handle(Client &client) {
+ Common::String path = client.queryParameter("path");
+
+ // check that <path> is not an absolute root
+ if (path == "" || path == "/") {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> contains no '../'
+ if (HandlerUtils::hasForbiddenCombinations(path)) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // transform virtual path to actual file system one
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> exists, is directory and isn't forbidden
+ AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path);
+ if (!HandlerUtils::permittedPath(node->getPath())) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+ if (!node->exists()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The file doesn't exist!"));
+ return;
+ }
+ if (node->isDirectory()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't download a directory!"));
+ return;
+ }
+ Common::SeekableReadStream *stream = node->createReadStream();
+ if (stream == nullptr) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Failed to read the file!"));
+ return;
+ }
+
+ GetClientHandler *handler = new GetClientHandler(stream);
+ handler->setResponseCode(200);
+ handler->setHeader("Content-Type", "application/force-download");
+ handler->setHeader("Content-Disposition", "attachment; filename=\"" + node->getDisplayName() + "\"");
+ handler->setHeader("Content-Transfer-Encoding", "binary");
+ client.setHandler(handler);
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/downloadfilehandler.h b/backends/networking/sdl_net/handlers/downloadfilehandler.h
new file mode 100644
index 0000000000..5fa5e5d55a
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/downloadfilehandler.h
@@ -0,0 +1,40 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_DOWNLOADFILEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class DownloadFileHandler: public FilesBaseHandler {
+public:
+ DownloadFileHandler();
+ virtual ~DownloadFileHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp b/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp
new file mode 100644
index 0000000000..8c5ee29b70
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filesajaxpagehandler.cpp
@@ -0,0 +1,81 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 "backends/networking/sdl_net/handlers/filesajaxpagehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+#define FILES_PAGE_NAME ".filesAJAX.html"
+
+FilesAjaxPageHandler::FilesAjaxPageHandler() {}
+
+FilesAjaxPageHandler::~FilesAjaxPageHandler() {}
+
+namespace {
+
+Common::String encodeDoubleQuotesAndSlashes(Common::String s) {
+ Common::String result = "";
+ for (uint32 i = 0; i < s.size(); ++i)
+ if (s[i] == '"') {
+ result += "\\\"";
+ } else if (s[i] == '\\') {
+ result += "\\\\";
+ } else {
+ result += s[i];
+ }
+ return result;
+}
+
+}
+
+/// public
+
+void FilesAjaxPageHandler::handle(Client &client) {
+ // load stylish response page from the archive
+ Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME);
+ if (stream == nullptr) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The page is not available without the resources."));
+ return;
+ }
+
+ Common::String response = HandlerUtils::readEverythingFromStream(stream);
+ Common::String path = client.queryParameter("path");
+
+ //these occur twice:
+ replace(response, "{create_directory_button}", _("Create directory"));
+ replace(response, "{create_directory_button}", _("Create directory"));
+ replace(response, "{upload_files_button}", _("Upload files")); //tab
+ replace(response, "{upload_file_button}", _("Upload files")); //button in the tab
+ replace(response, "{create_directory_desc}", _("Type new directory name:"));
+ replace(response, "{upload_file_desc}", _("Select a file to upload:"));
+ replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):"));
+ replace(response, "{index_of}", _("Index of "));
+ replace(response, "{loading}", _("Loading..."));
+ replace(response, "{error}", _("Error occurred"));
+ replace(response, "{start_path}", encodeDoubleQuotesAndSlashes(path));
+ LocalWebserver::setClientGetHandler(client, response);
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/filesajaxpagehandler.h b/backends/networking/sdl_net/handlers/filesajaxpagehandler.h
new file mode 100644
index 0000000000..1d9b125c2e
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filesajaxpagehandler.h
@@ -0,0 +1,40 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_FILESAJAXPAGEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class FilesAjaxPageHandler: public FilesBaseHandler {
+public:
+ FilesAjaxPageHandler();
+ virtual ~FilesAjaxPageHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/filesbasehandler.cpp b/backends/networking/sdl_net/handlers/filesbasehandler.cpp
new file mode 100644
index 0000000000..a585af9b5a
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filesbasehandler.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 "backends/networking/sdl_net/handlers/filesbasehandler.h"
+#include "backends/saves/default/default-saves.h"
+#include "common/config-manager.h"
+#include "common/system.h"
+
+namespace Networking {
+
+FilesBaseHandler::FilesBaseHandler() {}
+
+FilesBaseHandler::~FilesBaseHandler() {}
+
+Common::String FilesBaseHandler::parentPath(Common::String path) {
+ if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\')) path.deleteLastChar();
+ if (!path.empty()) {
+ for (int i = path.size() - 1; i >= 0; --i)
+ if (i == 0 || path[i] == '/' || path[i] == '\\') {
+ path.erase(i);
+ break;
+ }
+ }
+ if (path.size() && path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+ return path;
+}
+
+bool FilesBaseHandler::transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd, bool isDirectory) {
+ // <path> is not empty, but could lack the trailing slash
+ if (isDirectory && path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+
+ if (path.hasPrefix("/root") && ConfMan.hasKey("rootpath", "cloud")) {
+ prefixToAdd = "/root/";
+ prefixToRemove = ConfMan.get("rootpath", "cloud");
+ if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\')
+ prefixToRemove += '/';
+ if (prefixToRemove == "/") prefixToRemove = "";
+ path.erase(0, 5);
+ if (path.size() && (path[0] == '/' || path[0] == '\\'))
+ path.deleteChar(0); // if that was "/root/ab/c", it becomes "/ab/c", but we need "ab/c"
+ path = prefixToRemove + path;
+ if (path == "")
+ path = "/"; // absolute root is '/'
+ return true;
+ }
+
+ if (path.hasPrefix("/saves")) {
+ prefixToAdd = "/saves/";
+
+ // determine savepath (prefix to remove)
+#ifdef USE_LIBCURL
+ DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
+ prefixToRemove = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath"));
+#else
+ prefixToRemove = ConfMan.get("savepath");
+#endif
+ if (prefixToRemove.size() && prefixToRemove.lastChar() != '/' && prefixToRemove.lastChar() != '\\')
+ prefixToRemove += '/';
+
+ path.erase(0, 6);
+ if (path.size() && (path[0] == '/' || path[0] == '\\'))
+ path.deleteChar(0);
+ path = prefixToRemove + path;
+ return true;
+ }
+
+ return false;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/filesbasehandler.h b/backends/networking/sdl_net/handlers/filesbasehandler.h
new file mode 100644
index 0000000000..1c7f4dd799
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filesbasehandler.h
@@ -0,0 +1,52 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_FILESBASEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/basehandler.h"
+
+namespace Networking {
+
+class FilesBaseHandler: public BaseHandler {
+protected:
+ Common::String parentPath(Common::String path);
+
+ /**
+ * Transforms virtual <path> into actual file system path.
+ *
+ * Fills prefixes with actual file system prefix ("to remove")
+ * and virtual path prefix ("to add").
+ *
+ * Returns true on success.
+ */
+ bool transformPath(Common::String &path, Common::String &prefixToRemove, Common::String &prefixToAdd, bool isDirectory = true);
+public:
+ FilesBaseHandler();
+ virtual ~FilesBaseHandler();
+
+ virtual void handle(Client &client) = 0;
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/filespagehandler.cpp b/backends/networking/sdl_net/handlers/filespagehandler.cpp
new file mode 100644
index 0000000000..e117b4922c
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filespagehandler.cpp
@@ -0,0 +1,237 @@
+/* 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 "backends/networking/sdl_net/handlers/filespagehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/config-manager.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+#define INDEX_PAGE_NAME ".index.html"
+#define FILES_PAGE_NAME ".files.html"
+
+FilesPageHandler::FilesPageHandler() {}
+
+FilesPageHandler::~FilesPageHandler() {}
+
+namespace {
+Common::String encodeDoubleQuotes(Common::String s) {
+ Common::String result = "";
+ for (uint32 i = 0; i < s.size(); ++i)
+ if (s[i] == '"') {
+ result += "\\\"";
+ } else {
+ result += s[i];
+ }
+ return result;
+}
+
+Common::String encodeHtmlEntities(Common::String s) {
+ Common::String result = "";
+ for (uint32 i = 0; i < s.size(); ++i)
+ if (s[i] == '<')
+ result += "&lt;";
+ else if (s[i] == '>')
+ result += "&gt;";
+ else if (s[i] == '&')
+ result += "&amp;";
+ else if (s[i] > 0x7F)
+ result += Common::String::format("&#%d;", (int)s[i]);
+ else result += s[i];
+ return result;
+}
+
+Common::String getDisplayPath(Common::String s) {
+ Common::String result = "";
+ for (uint32 i = 0; i < s.size(); ++i)
+ if (s[i] == '\\')
+ result += '/';
+ else
+ result += s[i];
+ if (result == "")
+ return "/";
+ return result;
+}
+}
+
+bool FilesPageHandler::listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate) {
+ if (path == "" || path == "/") {
+ if (ConfMan.hasKey("rootpath", "cloud"))
+ addItem(content, itemTemplate, IT_DIRECTORY, "/root/", _("File system root"));
+ addItem(content, itemTemplate, IT_DIRECTORY, "/saves/", _("Saved games"));
+ return true;
+ }
+
+ if (HandlerUtils::hasForbiddenCombinations(path))
+ return false;
+
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd))
+ return false;
+
+ Common::FSNode node = Common::FSNode(path);
+ if (path == "/")
+ node = node.getParent(); // absolute root
+
+ if (!HandlerUtils::permittedPath(node.getPath()))
+ return false;
+
+ if (!node.isDirectory())
+ return false;
+
+ // list directory
+ Common::FSList _nodeContent;
+ if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files
+ _nodeContent.clear();
+ else
+ Common::sort(_nodeContent.begin(), _nodeContent.end());
+
+ // add parent directory link
+ {
+ Common::String filePath = path;
+ if (filePath.hasPrefix(prefixToRemove))
+ filePath.erase(0, prefixToRemove.size());
+ if (filePath == "" || filePath == "/" || filePath == "\\")
+ filePath = "/";
+ else
+ filePath = parentPath(prefixToAdd + filePath);
+ addItem(content, itemTemplate, IT_PARENT_DIRECTORY, filePath, _("Parent directory"));
+ }
+
+ // fill the content
+ for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) {
+ Common::String name = i->getDisplayName();
+ if (i->isDirectory())
+ name += "/";
+
+ Common::String filePath = i->getPath();
+ if (filePath.hasPrefix(prefixToRemove))
+ filePath.erase(0, prefixToRemove.size());
+ filePath = prefixToAdd + filePath;
+
+ addItem(content, itemTemplate, detectType(i->isDirectory(), name), filePath, name);
+ }
+
+ return true;
+}
+
+FilesPageHandler::ItemType FilesPageHandler::detectType(bool isDirectory, const Common::String &name) {
+ if (isDirectory)
+ return IT_DIRECTORY;
+ if (name.hasSuffix(".txt"))
+ return IT_TXT;
+ if (name.hasSuffix(".zip"))
+ return IT_ZIP;
+ if (name.hasSuffix(".7z"))
+ return IT_7Z;
+ return IT_UNKNOWN;
+}
+
+void FilesPageHandler::addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, Common::String path, Common::String name, Common::String size) const {
+ Common::String item = itemTemplate, icon;
+ bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY);
+ switch (itemType) {
+ case IT_DIRECTORY:
+ icon = "dir.png";
+ break;
+ case IT_PARENT_DIRECTORY:
+ icon = "up.png";
+ break;
+ case IT_TXT:
+ icon = "txt.png";
+ break;
+ case IT_ZIP:
+ icon = "zip.png";
+ break;
+ case IT_7Z:
+ icon = "7z.png";
+ break;
+ default:
+ icon = "unk.png";
+ }
+ replace(item, "{icon}", icon);
+ replace(item, "{link}", (isDirectory ? "files?path=" : "download?path=") + LocalWebserver::urlEncodeQueryParameterValue(path));
+ replace(item, "{name}", encodeHtmlEntities(name));
+ replace(item, "{size}", size);
+ content += item;
+}
+
+/// public
+
+void FilesPageHandler::handle(Client &client) {
+ Common::String response =
+ "<html>" \
+ "<head><title>ScummVM</title></head>" \
+ "<body>" \
+ "<p>{create_directory_desc}</p>" \
+ "<form action=\"create\">" \
+ "<input type=\"hidden\" name=\"path\" value=\"{path}\"/>" \
+ "<input type=\"text\" name=\"directory_name\" value=\"\"/>" \
+ "<input type=\"submit\" value=\"{create_directory_button}\"/>" \
+ "</form>" \
+ "<hr/>" \
+ "<p>{upload_file_desc}</p>" \
+ "<form action=\"upload?path={path}\" method=\"post\" enctype=\"multipart/form-data\">" \
+ "<input type=\"file\" name=\"upload_file-f\" allowdirs multiple/>" \
+ "<span>{or_upload_directory_desc}</span>" \
+ "<input type=\"file\" name=\"upload_file-d\" directory webkitdirectory multiple/>" \
+ "<input type=\"submit\" value=\"{upload_file_button}\"/>" \
+ "</form>"
+ "<hr/>" \
+ "<h1>{index_of_directory}</h1>" \
+ "<table>{content}</table>" \
+ "</body>" \
+ "</html>";
+ Common::String itemTemplate = "<tr><td><img src=\"icons/{icon}\"/></td><td><a href=\"{link}\">{name}</a></td><td>{size}</td></tr>\n"; //TODO: load this template too?
+
+ // load stylish response page from the archive
+ Common::SeekableReadStream *const stream = HandlerUtils::getArchiveFile(FILES_PAGE_NAME);
+ if (stream)
+ response = HandlerUtils::readEverythingFromStream(stream);
+
+ Common::String path = client.queryParameter("path");
+ Common::String content = "";
+
+ // show an error message if failed to list directory
+ if (!listDirectory(path, content, itemTemplate)) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("ScummVM couldn't list the directory you specified."));
+ return;
+ }
+
+ //these occur twice:
+ replace(response, "{create_directory_button}", _("Create directory"));
+ replace(response, "{create_directory_button}", _("Create directory"));
+ replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path")));
+ replace(response, "{path}", encodeDoubleQuotes(client.queryParameter("path")));
+ replace(response, "{upload_files_button}", _("Upload files")); //tab
+ replace(response, "{upload_file_button}", _("Upload files")); //button in the tab
+ replace(response, "{create_directory_desc}", _("Type new directory name:"));
+ replace(response, "{upload_file_desc}", _("Select a file to upload:"));
+ replace(response, "{or_upload_directory_desc}", _("Or select a directory (works in Chrome only):"));
+ replace(response, "{index_of_directory}", Common::String::format(_("Index of %s"), encodeHtmlEntities(getDisplayPath(client.queryParameter("path"))).c_str()));
+ replace(response, "{content}", content);
+ LocalWebserver::setClientGetHandler(client, response);
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/filespagehandler.h b/backends/networking/sdl_net/handlers/filespagehandler.h
new file mode 100644
index 0000000000..e404036cb6
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/filespagehandler.h
@@ -0,0 +1,62 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_FILESPAGEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class FilesPageHandler: public FilesBaseHandler {
+ enum ItemType {
+ IT_DIRECTORY,
+ IT_PARENT_DIRECTORY,
+ IT_TXT,
+ IT_ZIP,
+ IT_7Z,
+ IT_UNKNOWN
+ };
+
+ /**
+ * Lists the directory <path>.
+ *
+ * Returns true on success.
+ */
+ bool listDirectory(Common::String path, Common::String &content, const Common::String &itemTemplate);
+
+ /** Helper method for detecting items' type. */
+ static ItemType detectType(bool isDirectory, const Common::String &name);
+
+ /** Helper method for adding items into the files list. */
+ void addItem(Common::String &content, const Common::String &itemTemplate, ItemType itemType, Common::String path, Common::String name, Common::String size = "") const;
+
+public:
+ FilesPageHandler();
+ virtual ~FilesPageHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/indexpagehandler.cpp b/backends/networking/sdl_net/handlers/indexpagehandler.cpp
new file mode 100644
index 0000000000..985bd6635e
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/indexpagehandler.cpp
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 "backends/networking/sdl_net/handlers/indexpagehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/translation.h"
+#include "gui/storagewizarddialog.h"
+
+namespace Networking {
+
+IndexPageHandler::IndexPageHandler(): CommandSender(nullptr) {}
+
+IndexPageHandler::~IndexPageHandler() {}
+
+/// public
+
+Common::String IndexPageHandler::code() const { return _code; }
+
+void IndexPageHandler::handle(Client &client) {
+ Common::String code = client.queryParameter("code");
+
+ if (code == "") {
+ // redirect to "/filesAJAX"
+ HandlerUtils::setMessageHandler(
+ client,
+ Common::String::format(
+ "%s<br/><a href=\"files\">%s</a>",
+ _("This is a local webserver index page."),
+ _("Open Files manager")
+ ),
+ "/filesAJAX"
+ );
+ return;
+ }
+
+ _code = code;
+ sendCommand(GUI::kStorageCodePassedCmd, 0);
+ HandlerUtils::setMessageHandler(client, _("ScummVM got the code and already connects to your cloud storage!"));
+}
+
+bool IndexPageHandler::minimalModeSupported() {
+ return true;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/indexpagehandler.h b/backends/networking/sdl_net/handlers/indexpagehandler.h
new file mode 100644
index 0000000000..8065954b27
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/indexpagehandler.h
@@ -0,0 +1,45 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_INDEXPAGEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/basehandler.h"
+#include "gui/object.h"
+
+namespace Networking {
+class LocalWebserver;
+
+class IndexPageHandler: public BaseHandler, public GUI::CommandSender {
+ Common::String _code;
+public:
+ IndexPageHandler();
+ virtual ~IndexPageHandler();
+
+ Common::String code() const;
+ virtual void handle(Client &client);
+ virtual bool minimalModeSupported();
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/listajaxhandler.cpp b/backends/networking/sdl_net/handlers/listajaxhandler.cpp
new file mode 100644
index 0000000000..f94b674a3c
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/listajaxhandler.cpp
@@ -0,0 +1,157 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 "backends/networking/sdl_net/handlers/listajaxhandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/config-manager.h"
+#include "common/json.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+ListAjaxHandler::ListAjaxHandler() {}
+
+ListAjaxHandler::~ListAjaxHandler() {}
+
+Common::JSONObject ListAjaxHandler::listDirectory(Common::String path) {
+ Common::JSONArray itemsList;
+ Common::JSONObject errorResult;
+ Common::JSONObject successResult;
+ successResult.setVal("type", new Common::JSONValue("success"));
+ errorResult.setVal("type", new Common::JSONValue("error"));
+
+ if (path == "" || path == "/") {
+ if (ConfMan.hasKey("rootpath", "cloud"))
+ addItem(itemsList, IT_DIRECTORY, "/root/", _("File system root"));
+ addItem(itemsList, IT_DIRECTORY, "/saves/", _("Saved games"));
+ successResult.setVal("items", new Common::JSONValue(itemsList));
+ return successResult;
+ }
+
+ if (HandlerUtils::hasForbiddenCombinations(path))
+ return errorResult;
+
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd))
+ return errorResult;
+
+ Common::FSNode node = Common::FSNode(path);
+ if (path == "/")
+ node = node.getParent(); // absolute root
+
+ if (!HandlerUtils::permittedPath(node.getPath()))
+ return errorResult;
+
+ if (!node.isDirectory())
+ return errorResult;
+
+ // list directory
+ Common::FSList _nodeContent;
+ if (!node.getChildren(_nodeContent, Common::FSNode::kListAll, false)) // do not show hidden files
+ _nodeContent.clear();
+ else
+ Common::sort(_nodeContent.begin(), _nodeContent.end());
+
+ // add parent directory link
+ {
+ Common::String filePath = path;
+ if (filePath.hasPrefix(prefixToRemove))
+ filePath.erase(0, prefixToRemove.size());
+ if (filePath == "" || filePath == "/" || filePath == "\\")
+ filePath = "/";
+ else
+ filePath = parentPath(prefixToAdd + filePath);
+ addItem(itemsList, IT_PARENT_DIRECTORY, filePath, _("Parent directory"));
+ }
+
+ // fill the content
+ for (Common::FSList::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) {
+ Common::String name = i->getDisplayName();
+ if (i->isDirectory()) name += "/";
+
+ Common::String filePath = i->getPath();
+ if (filePath.hasPrefix(prefixToRemove))
+ filePath.erase(0, prefixToRemove.size());
+ filePath = prefixToAdd + filePath;
+
+ addItem(itemsList, detectType(i->isDirectory(), name), filePath, name);
+ }
+
+ successResult.setVal("items", new Common::JSONValue(itemsList));
+ return successResult;
+}
+
+ListAjaxHandler::ItemType ListAjaxHandler::detectType(bool isDirectory, const Common::String &name) {
+ if (isDirectory)
+ return IT_DIRECTORY;
+ if (name.hasSuffix(".txt"))
+ return IT_TXT;
+ if (name.hasSuffix(".zip"))
+ return IT_ZIP;
+ if (name.hasSuffix(".7z"))
+ return IT_7Z;
+ return IT_UNKNOWN;
+}
+
+void ListAjaxHandler::addItem(Common::JSONArray &responseItemsList, ItemType itemType, Common::String path, Common::String name, Common::String size) {
+ Common::String icon;
+ bool isDirectory = (itemType == IT_DIRECTORY || itemType == IT_PARENT_DIRECTORY);
+ switch (itemType) {
+ case IT_DIRECTORY:
+ icon = "dir.png";
+ break;
+ case IT_PARENT_DIRECTORY:
+ icon = "up.png";
+ break;
+ case IT_TXT:
+ icon = "txt.png";
+ break;
+ case IT_ZIP:
+ icon = "zip.png";
+ break;
+ case IT_7Z:
+ icon = "7z.png";
+ break;
+ default:
+ icon = "unk.png";
+ }
+
+ Common::JSONObject item;
+ item.setVal("name", new Common::JSONValue(name));
+ item.setVal("path", new Common::JSONValue(path));
+ item.setVal("isDirectory", new Common::JSONValue(isDirectory));
+ item.setVal("size", new Common::JSONValue(size));
+ item.setVal("icon", new Common::JSONValue(icon));
+ responseItemsList.push_back(new Common::JSONValue(item));
+}
+
+/// public
+
+void ListAjaxHandler::handle(Client &client) {
+ Common::String path = client.queryParameter("path");
+ Common::JSONValue jsonResponse = listDirectory(path);
+ Common::String response = jsonResponse.stringify(true);
+ LocalWebserver::setClientGetHandler(client, response);
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/listajaxhandler.h b/backends/networking/sdl_net/handlers/listajaxhandler.h
new file mode 100644
index 0000000000..40840ad6c3
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/listajaxhandler.h
@@ -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.
+*
+*/
+
+#ifndef BACKENDS_NETWORKING_SDL_NET_LISTAJAXHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_LISTAJAXHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+#include "common/json.h"
+
+namespace Networking {
+
+class ListAjaxHandler: public FilesBaseHandler {
+ enum ItemType {
+ IT_DIRECTORY,
+ IT_PARENT_DIRECTORY,
+ IT_TXT,
+ IT_ZIP,
+ IT_7Z,
+ IT_UNKNOWN
+ };
+
+ /**
+ * Lists the directory <path>.
+ *
+ * Returns JSON with either listed directory or error response.
+ */
+ Common::JSONObject listDirectory(Common::String path);
+
+ /** Helper method for detecting items' type. */
+ static ItemType detectType(bool isDirectory, const Common::String &name);
+
+ /** Helper method for adding items into the files list. */
+ static void addItem(Common::JSONArray &responseItemsList, ItemType itemType, Common::String path, Common::String name, Common::String size = "");
+
+public:
+ ListAjaxHandler();
+ virtual ~ListAjaxHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/resourcehandler.cpp b/backends/networking/sdl_net/handlers/resourcehandler.cpp
new file mode 100644
index 0000000000..631eb63351
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/resourcehandler.cpp
@@ -0,0 +1,75 @@
+/* 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 "backends/networking/sdl_net/handlers/resourcehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+
+namespace Networking {
+
+ResourceHandler::ResourceHandler() {}
+
+ResourceHandler::~ResourceHandler() {}
+
+const char *ResourceHandler::determineMimeType(Common::String &filename) {
+ // text
+ if (filename.hasSuffix(".html")) return "text/html";
+ if (filename.hasSuffix(".css")) return "text/css";
+ if (filename.hasSuffix(".txt")) return "text/plain";
+ if (filename.hasSuffix(".js")) return "application/javascript";
+
+ // images
+ if (filename.hasSuffix(".jpeg") || filename.hasSuffix(".jpg") || filename.hasSuffix(".jpe")) return "image/jpeg";
+ if (filename.hasSuffix(".gif")) return "image/gif";
+ if (filename.hasSuffix(".png")) return "image/png";
+ if (filename.hasSuffix(".svg")) return "image/svg+xml";
+ if (filename.hasSuffix(".tiff")) return "image/tiff";
+ if (filename.hasSuffix(".ico")) return "image/vnd.microsoft.icon";
+ if (filename.hasSuffix(".wbmp")) return "image/vnd.wap.wbmp";
+
+ if (filename.hasSuffix(".zip")) return "application/zip";
+ return "application/octet-stream";
+}
+
+/// public
+
+void ResourceHandler::handle(Client &client) {
+ Common::String filename = client.path();
+ filename.deleteChar(0);
+
+ // if archive hidden file is requested, ignore
+ if (filename.size() && filename[0] == '.')
+ return;
+
+ // if file not found, don't set handler either
+ Common::SeekableReadStream *file = HandlerUtils::getArchiveFile(filename);
+ if (file == nullptr)
+ return;
+
+ LocalWebserver::setClientGetHandler(client, file, 200, determineMimeType(filename));
+}
+
+bool ResourceHandler::minimalModeSupported() {
+ return true;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/resourcehandler.h b/backends/networking/sdl_net/handlers/resourcehandler.h
new file mode 100644
index 0000000000..2ec4c5bb19
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/resourcehandler.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 BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_RESOURCEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/basehandler.h"
+
+namespace Networking {
+
+class ResourceHandler: public BaseHandler {
+ static const char *determineMimeType(Common::String &filename);
+public:
+ ResourceHandler();
+ virtual ~ResourceHandler();
+
+ virtual void handle(Client &client);
+ virtual bool minimalModeSupported();
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.cpp b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp
new file mode 100644
index 0000000000..a0e992c25e
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/uploadfilehandler.cpp
@@ -0,0 +1,79 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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 "backends/networking/sdl_net/handlers/uploadfilehandler.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/uploadfileclienthandler.h"
+#include "backends/fs/fs-factory.h"
+#include "common/system.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+UploadFileHandler::UploadFileHandler() {}
+
+UploadFileHandler::~UploadFileHandler() {}
+
+/// public
+
+void UploadFileHandler::handle(Client &client) {
+ Common::String path = client.queryParameter("path");
+
+ // check that <path> is not an absolute root
+ if (path == "" || path == "/" || path == "\\") {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> contains no '../'
+ if (HandlerUtils::hasForbiddenCombinations(path)) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // transform virtual path to actual file system one
+ Common::String prefixToRemove = "", prefixToAdd = "";
+ if (!transformPath(path, prefixToRemove, prefixToAdd, false) || path.empty()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+
+ // check that <path> exists, is directory and isn't forbidden
+ AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(path);
+ if (!HandlerUtils::permittedPath(node->getPath())) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Invalid path!"));
+ return;
+ }
+ if (!node->exists()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("The parent directory doesn't exist!"));
+ return;
+ }
+ if (!node->isDirectory()) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, _("Can't upload into a file!"));
+ return;
+ }
+
+ // if all OK, set special handler
+ client.setHandler(new UploadFileClientHandler(path));
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlers/uploadfilehandler.h b/backends/networking/sdl_net/handlers/uploadfilehandler.h
new file mode 100644
index 0000000000..cbff215156
--- /dev/null
+++ b/backends/networking/sdl_net/handlers/uploadfilehandler.h
@@ -0,0 +1,40 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILEHANDLER_H
+
+#include "backends/networking/sdl_net/handlers/filesbasehandler.h"
+
+namespace Networking {
+
+class UploadFileHandler: public FilesBaseHandler {
+public:
+ UploadFileHandler();
+ virtual ~UploadFileHandler();
+
+ virtual void handle(Client &client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/handlerutils.cpp b/backends/networking/sdl_net/handlerutils.cpp
new file mode 100644
index 0000000000..fba00aef59
--- /dev/null
+++ b/backends/networking/sdl_net/handlerutils.cpp
@@ -0,0 +1,204 @@
+/* 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 "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "backends/saves/default/default-saves.h"
+#include "common/archive.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+#include "common/translation.h"
+#include "common/unzip.h"
+
+namespace Networking {
+
+#define ARCHIVE_NAME "wwwroot.zip"
+
+#define INDEX_PAGE_NAME ".index.html"
+
+Common::Archive *HandlerUtils::getZipArchive() {
+ // first search in themepath
+ if (ConfMan.hasKey("themepath")) {
+ const Common::FSNode &node = Common::FSNode(ConfMan.get("themepath"));
+ if (!node.exists() || !node.isReadable() || !node.isDirectory())
+ return nullptr;
+
+ Common::FSNode fileNode = node.getChild(ARCHIVE_NAME);
+ if (fileNode.exists() && fileNode.isReadable() && !fileNode.isDirectory()) {
+ Common::SeekableReadStream *const stream = fileNode.createReadStream();
+ Common::Archive *zipArchive = Common::makeZipArchive(stream);
+ if (zipArchive)
+ return zipArchive;
+ }
+ }
+
+ // then use SearchMan to find it
+ Common::ArchiveMemberList fileList;
+ SearchMan.listMatchingMembers(fileList, ARCHIVE_NAME);
+ for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) {
+ Common::ArchiveMember const &m = **it;
+ Common::SeekableReadStream *const stream = m.createReadStream();
+ Common::Archive *zipArchive = Common::makeZipArchive(stream);
+ if (zipArchive)
+ return zipArchive;
+ }
+
+ return nullptr;
+}
+
+Common::ArchiveMemberList HandlerUtils::listArchive() {
+ Common::ArchiveMemberList resultList;
+ Common::Archive *zipArchive = getZipArchive();
+ if (zipArchive) {
+ zipArchive->listMembers(resultList);
+ delete zipArchive;
+ }
+ return resultList;
+}
+
+Common::SeekableReadStream *HandlerUtils::getArchiveFile(Common::String name) {
+ Common::SeekableReadStream *result = nullptr;
+ Common::Archive *zipArchive = getZipArchive();
+ if (zipArchive) {
+ const Common::ArchiveMemberPtr ptr = zipArchive->getMember(name);
+ if (ptr.get() == nullptr)
+ return nullptr;
+ result = ptr->createReadStream();
+ delete zipArchive;
+ }
+ return result;
+}
+
+Common::String HandlerUtils::readEverythingFromStream(Common::SeekableReadStream *const stream) {
+ Common::String result;
+ char buf[1024];
+ uint32 readBytes;
+ while (!stream->eos()) {
+ readBytes = stream->read(buf, 1024);
+ result += Common::String(buf, readBytes);
+ }
+ return result;
+}
+
+Common::String HandlerUtils::normalizePath(const Common::String &path) {
+ Common::String normalized;
+ bool slash = false;
+ for (uint32 i = 0; i < path.size(); ++i) {
+ char c = path[i];
+ if (c == '\\' || c == '/') {
+ slash = true;
+ continue;
+ }
+
+ if (slash) {
+ normalized += '/';
+ slash = false;
+ }
+
+ if ('A' <= c && c <= 'Z') {
+ normalized += c - 'A' + 'a';
+ } else {
+ normalized += c;
+ }
+ }
+ if (slash) normalized += '/';
+ return normalized;
+}
+
+bool HandlerUtils::hasForbiddenCombinations(const Common::String &path) {
+ return (path.contains("/../") || path.contains("\\..\\") || path.contains("\\../") || path.contains("/..\\"));
+}
+
+bool HandlerUtils::isBlacklisted(const Common::String &path) {
+ const char *blacklist[] = {
+ "/etc",
+ "/bin",
+ "c:/windows" // just saying: I know guys who install windows on another drives
+ };
+
+ // normalize path
+ Common::String normalized = normalizePath(path);
+
+ uint32 size = sizeof(blacklist) / sizeof(const char *);
+ for (uint32 i = 0; i < size; ++i)
+ if (normalized.hasPrefix(blacklist[i]))
+ return true;
+
+ return false;
+}
+
+bool HandlerUtils::hasPermittedPrefix(const Common::String &path) {
+ // normalize path
+ Common::String normalized = normalizePath(path);
+
+ // prefix for /root/
+ Common::String prefix;
+ if (ConfMan.hasKey("rootpath", "cloud")) {
+ prefix = normalizePath(ConfMan.get("rootpath", "cloud"));
+ if (prefix == "/" || normalized.hasPrefix(prefix))
+ return true;
+ }
+
+ // prefix for /saves/
+#ifdef USE_LIBCURL
+ DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
+ prefix = (manager ? manager->concatWithSavesPath("") : ConfMan.get("savepath"));
+#else
+ prefix = ConfMan.get("savepath");
+#endif
+ return (normalized.hasPrefix(normalizePath(prefix)));
+}
+
+bool HandlerUtils::permittedPath(const Common::String path) {
+ return hasPermittedPrefix(path) && !isBlacklisted(path);
+}
+
+void HandlerUtils::setMessageHandler(Client &client, Common::String message, Common::String redirectTo) {
+ Common::String response = "<html><head><title>ScummVM</title></head><body>{message}</body></html>";
+
+ // load stylish response page from the archive
+ Common::SeekableReadStream *const stream = getArchiveFile(INDEX_PAGE_NAME);
+ if (stream)
+ response = readEverythingFromStream(stream);
+
+ replace(response, "{message}", message);
+ if (redirectTo.empty())
+ LocalWebserver::setClientGetHandler(client, response);
+ else
+ LocalWebserver::setClientRedirectHandler(client, response, redirectTo);
+}
+
+void HandlerUtils::setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo) {
+ setMessageHandler(
+ client,
+ Common::String::format(
+ "%s<br/><a href=\"files%s?path=%s\">%s</a>",
+ message.c_str(),
+ client.queryParameter("ajax") == "true" ? "AJAX" : "",
+ "%2F", //that's encoded "/"
+ _("Back to the files manager")
+ ),
+ redirectTo
+ );
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/handlerutils.h b/backends/networking/sdl_net/handlerutils.h
new file mode 100644
index 0000000000..4c2eff49b6
--- /dev/null
+++ b/backends/networking/sdl_net/handlerutils.h
@@ -0,0 +1,50 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H
+#define BACKENDS_NETWORKING_SDL_NET_HANDLERUTILS_H
+
+#include "backends/networking/sdl_net/client.h"
+#include "common/archive.h"
+
+namespace Networking {
+
+class HandlerUtils {
+public:
+ static Common::Archive *getZipArchive();
+ static Common::ArchiveMemberList listArchive();
+ static Common::SeekableReadStream *getArchiveFile(Common::String name);
+ static Common::String readEverythingFromStream(Common::SeekableReadStream *const stream);
+
+ static Common::String normalizePath(const Common::String &path);
+ static bool hasForbiddenCombinations(const Common::String &path);
+ static bool isBlacklisted(const Common::String &path);
+ static bool hasPermittedPrefix(const Common::String &path);
+ static bool permittedPath(const Common::String path);
+
+ static void setMessageHandler(Client &client, Common::String message, Common::String redirectTo = "");
+ static void setFilesManagerErrorMessageHandler(Client &client, Common::String message, Common::String redirectTo = "");
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/localwebserver.cpp b/backends/networking/sdl_net/localwebserver.cpp
new file mode 100644
index 0000000000..6557c7a88d
--- /dev/null
+++ b/backends/networking/sdl_net/localwebserver.cpp
@@ -0,0 +1,446 @@
+/* ScummVM - Graphic Adventure Engine
+*
+* ScummVM is the legal property of its developers, whose names
+* are too numerous to list here. Please refer to the COPYRIGHT
+* file distributed with this source distribution.
+*
+* This program is free software; you can redistribute it and/or
+* modify it under the terms of the GNU General Public License
+* as published by the Free Software Foundation; either version 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.
+*
+*/
+
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "backends/networking/sdl_net/getclienthandler.h"
+#include "common/memstream.h"
+#include "common/str.h"
+#include "common/system.h"
+#include "common/timer.h"
+#include "common/translation.h"
+#include <SDL/SDL_net.h>
+#include <common/config-manager.h>
+
+#ifdef POSIX
+#include <sys/types.h>
+#include <ifaddrs.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#endif
+
+namespace Common {
+class MemoryReadWriteStream;
+
+DECLARE_SINGLETON(Networking::LocalWebserver);
+
+}
+
+namespace Networking {
+
+LocalWebserver::LocalWebserver(): _set(nullptr), _serverSocket(nullptr), _timerStarted(false),
+ _stopOnIdle(false), _minimalMode(false), _clients(0), _idlingFrames(0), _serverPort(DEFAULT_SERVER_PORT) {
+ addPathHandler("/", &_indexPageHandler);
+ addPathHandler("/files", &_filesPageHandler);
+ addPathHandler("/create", &_createDirectoryHandler);
+ addPathHandler("/download", &_downloadFileHandler);
+ addPathHandler("/upload", &_uploadFileHandler);
+ addPathHandler("/list", &_listAjaxHandler);
+ addPathHandler("/filesAJAX", &_filesAjaxPageHandler);
+ _defaultHandler = &_resourceHandler;
+}
+
+LocalWebserver::~LocalWebserver() {
+ stop();
+}
+
+void localWebserverTimer(void *ignored) {
+ LocalServer.handle();
+}
+
+void LocalWebserver::startTimer(int interval) {
+ Common::TimerManager *manager = g_system->getTimerManager();
+ if (manager->installTimerProc(localWebserverTimer, interval, 0, "Networking::LocalWebserver's Timer")) {
+ _timerStarted = true;
+ } else {
+ warning("Failed to install Networking::LocalWebserver's timer");
+ }
+}
+
+void LocalWebserver::stopTimer() {
+ Common::TimerManager *manager = g_system->getTimerManager();
+ manager->removeTimerProc(localWebserverTimer);
+ _timerStarted = false;
+}
+
+void LocalWebserver::start(bool useMinimalMode) {
+ _handleMutex.lock();
+ _serverPort = getPort();
+ _stopOnIdle = false;
+ if (_timerStarted) {
+ _handleMutex.unlock();
+ return;
+ }
+ _minimalMode = useMinimalMode;
+ startTimer();
+
+ // Create a listening TCP socket
+ IPaddress ip;
+ if (SDLNet_ResolveHost(&ip, NULL, _serverPort) == -1) {
+ error("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
+ }
+
+ resolveAddress(&ip);
+
+ _serverSocket = SDLNet_TCP_Open(&ip);
+ if (!_serverSocket) {
+ warning("LocalWebserver: SDLNet_TCP_Open: %s", SDLNet_GetError());
+ stopTimer();
+ g_system->displayMessageOnOSD(_("Failed to start local webserver.\nCheck whether selected port is not used by another application and try again."));
+ _handleMutex.unlock();
+ return;
+ }
+
+ // Create a socket set
+ _set = SDLNet_AllocSocketSet(MAX_CONNECTIONS + 1); //one more for our server socket
+ if (!_set) {
+ error("LocalWebserver: SDLNet_AllocSocketSet: %s\n", SDLNet_GetError());
+ }
+
+ int numused = SDLNet_TCP_AddSocket(_set, _serverSocket);
+ if (numused == -1) {
+ error("LocalWebserver: SDLNet_AddSocket: %s\n", SDLNet_GetError());
+ }
+ _handleMutex.unlock();
+}
+
+void LocalWebserver::stop() {
+ _handleMutex.lock();
+ if (_timerStarted)
+ stopTimer();
+
+ if (_serverSocket) {
+ SDLNet_TCP_Close(_serverSocket);
+ _serverSocket = nullptr;
+ }
+
+ for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
+ _client[i].close();
+
+ _clients = 0;
+
+ if (_set) {
+ SDLNet_FreeSocketSet(_set);
+ _set = nullptr;
+ }
+ _handleMutex.unlock();
+}
+
+void LocalWebserver::stopOnIdle() { _stopOnIdle = true; }
+
+void LocalWebserver::addPathHandler(Common::String path, BaseHandler *handler) {
+ if (_pathHandlers.contains(path))
+ warning("LocalWebserver::addPathHandler: path already had a handler");
+ _pathHandlers[path] = handler;
+}
+
+Common::String LocalWebserver::getAddress() { return _address; }
+
+IndexPageHandler &LocalWebserver::indexPageHandler() { return _indexPageHandler; }
+
+bool LocalWebserver::isRunning() {
+ bool result = false;
+ _handleMutex.lock();
+ result = _timerStarted;
+ _handleMutex.unlock();
+ return result;
+}
+
+uint32 LocalWebserver::getPort() {
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ if (ConfMan.hasKey("local_server_port"))
+ return ConfMan.getInt("local_server_port");
+#endif
+ return DEFAULT_SERVER_PORT;
+}
+
+void LocalWebserver::handle() {
+ _handleMutex.lock();
+ int numready = SDLNet_CheckSockets(_set, 0);
+ if (numready == -1) {
+ error("LocalWebserver: SDLNet_CheckSockets: %s\n", SDLNet_GetError());
+ } else if (numready) {
+ acceptClient();
+ }
+
+ for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
+ handleClient(i);
+
+ _clients = 0;
+ for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
+ if (_client[i].state() != INVALID)
+ ++_clients;
+
+ if (_clients == 0)
+ ++_idlingFrames;
+ else
+ _idlingFrames = 0;
+
+ if (_idlingFrames > FRAMES_PER_SECOND && _stopOnIdle) {
+ _handleMutex.unlock();
+ stop();
+ return;
+ }
+
+ _handleMutex.unlock();
+}
+
+void LocalWebserver::handleClient(uint32 i) {
+ switch (_client[i].state()) {
+ case INVALID:
+ return;
+ case READING_HEADERS:
+ _client[i].readHeaders();
+ break;
+ case READ_HEADERS: {
+ // decide what to do next with that client
+ // check whether we know a handler for such URL
+ BaseHandler *handler = nullptr;
+ if (_pathHandlers.contains(_client[i].path())) {
+ handler = _pathHandlers[_client[i].path()];
+ } else {
+ // try default handler
+ handler = _defaultHandler;
+ }
+
+ // if server's in "minimal mode", only handlers which support it are used
+ if (handler && (!_minimalMode || handler->minimalModeSupported()))
+ handler->handle(_client[i]);
+
+ if (_client[i].state() == BEING_HANDLED || _client[i].state() == INVALID)
+ break;
+
+ // if no handler, answer with default BAD REQUEST
+ // fallthrough
+ }
+
+ case BAD_REQUEST:
+ setClientGetHandler(_client[i], "<html><head><title>ScummVM - Bad Request</title></head><body>BAD REQUEST</body></html>", 400);
+ break;
+ case BEING_HANDLED:
+ _client[i].handle();
+ break;
+ }
+}
+
+void LocalWebserver::acceptClient() {
+ if (!SDLNet_SocketReady(_serverSocket))
+ return;
+
+ TCPsocket client = SDLNet_TCP_Accept(_serverSocket);
+ if (!client)
+ return;
+
+ if (_clients == MAX_CONNECTIONS) { //drop the connection
+ SDLNet_TCP_Close(client);
+ return;
+ }
+
+ ++_clients;
+ for (uint32 i = 0; i < MAX_CONNECTIONS; ++i)
+ if (_client[i].state() == INVALID) {
+ _client[i].open(_set, client);
+ break;
+ }
+}
+
+void LocalWebserver::resolveAddress(void *ipAddress) {
+ IPaddress *ip = (IPaddress *)ipAddress;
+
+ // not resolved
+ _address = Common::String::format("http://127.0.0.1:%u/ (unresolved)", _serverPort);
+
+ // default way (might work everywhere, surely works on Windows)
+ const char *name = SDLNet_ResolveIP(ip);
+ if (name == NULL) {
+ warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
+ } else {
+ IPaddress localIp;
+ if (SDLNet_ResolveHost(&localIp, name, _serverPort) == -1) {
+ warning("LocalWebserver: SDLNet_ResolveHost: %s\n", SDLNet_GetError());
+ } else {
+ _address = Common::String::format(
+ "http://%u.%u.%u.%u:%u/",
+ localIp.host & 0xFF, (localIp.host >> 8) & 0xFF, (localIp.host >> 16) & 0xFF, (localIp.host >> 24) & 0xFF,
+ _serverPort
+ );
+ }
+ }
+
+ // check that our trick worked
+ if (_address.contains("/127.0.0.1:") || _address.contains("localhost") || _address.contains("/0.0.0.0:"))
+ warning("LocalWebserver: Failed to resolve IP with the default way");
+ else
+ return;
+
+ // if not - try platform-specific
+#ifdef POSIX
+ struct ifaddrs *ifAddrStruct = NULL;
+ void *tmpAddrPtr = NULL;
+
+ getifaddrs(&ifAddrStruct);
+
+ for (struct ifaddrs *i = ifAddrStruct; i != NULL; i = i->ifa_next) {
+ if (!i->ifa_addr) {
+ continue;
+ }
+
+ Common::String addr;
+
+ // IPv4
+ if (i->ifa_addr->sa_family == AF_INET) {
+ tmpAddrPtr = &((struct sockaddr_in *)i->ifa_addr)->sin_addr;
+ char addressBuffer[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
+ debug(9, "%s IP Address %s", i->ifa_name, addressBuffer);
+ addr = addressBuffer;
+ }
+
+ // IPv6
+ /*
+ if (i->ifa_addr->sa_family == AF_INET6) {
+ tmpAddrPtr = &((struct sockaddr_in6 *)i->ifa_addr)->sin6_addr;
+ char addressBuffer[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
+ debug(9, "%s IP Address %s", i->ifa_name, addressBuffer);
+ addr = addressBuffer;
+ }
+ */
+
+ if (addr.empty())
+ continue;
+
+ // ignored IPv4 addresses
+ if (addr.equals("127.0.0.1") || addr.equals("0.0.0.0") || addr.equals("localhost"))
+ continue;
+
+ // ignored IPv6 addresses
+ /*
+ if (addr.equals("::1"))
+ continue;
+ */
+
+ // use the address found
+ _address = "http://" + addr + Common::String::format(":%u/", _serverPort);
+ }
+
+ if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
+#endif
+}
+
+void LocalWebserver::setClientGetHandler(Client &client, Common::String response, long code, const char *mimeType) {
+ byte *data = new byte[response.size()];
+ memcpy(data, response.c_str(), response.size());
+ Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
+ setClientGetHandler(client, stream, code, mimeType);
+}
+
+void LocalWebserver::setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code, const char *mimeType) {
+ GetClientHandler *handler = new GetClientHandler(responseStream);
+ handler->setResponseCode(code);
+ if (mimeType)
+ handler->setHeader("Content-Type", mimeType);
+ client.setHandler(handler);
+}
+
+void LocalWebserver::setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType) {
+ byte *data = new byte[response.size()];
+ memcpy(data, response.c_str(), response.size());
+ Common::MemoryReadStream *stream = new Common::MemoryReadStream(data, response.size(), DisposeAfterUse::YES);
+ setClientRedirectHandler(client, stream, location, mimeType);
+}
+
+void LocalWebserver::setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType) {
+ GetClientHandler *handler = new GetClientHandler(responseStream);
+ handler->setResponseCode(302); //redirect
+ handler->setHeader("Location", location);
+ if (mimeType)
+ handler->setHeader("Content-Type", mimeType);
+ client.setHandler(handler);
+}
+
+namespace {
+int hexDigit(char c) {
+ if ('0' <= c && c <= '9') return c - '0';
+ if ('A' <= c && c <= 'F') return c - 'A' + 10;
+ if ('a' <= c && c <= 'f') return c - 'a' + 10;
+ return -1;
+}
+}
+
+Common::String LocalWebserver::urlDecode(Common::String value) {
+ Common::String result = "";
+ uint32 size = value.size();
+ for (uint32 i = 0; i < size; ++i) {
+ if (value[i] == '+') {
+ result += ' ';
+ continue;
+ }
+
+ if (value[i] == '%' && i + 2 < size) {
+ int d1 = hexDigit(value[i + 1]);
+ int d2 = hexDigit(value[i + 2]);
+ if (0 <= d1 && d1 < 16 && 0 <= d2 && d2 < 16) {
+ result += (char)(d1 * 16 + d2);
+ i = i + 2;
+ continue;
+ }
+ }
+
+ result += value[i];
+ }
+ return result;
+}
+
+namespace {
+bool isQueryUnreserved(char c) {
+ return (
+ ('0' <= c && c <= '9') ||
+ ('A' <= c && c <= 'Z') ||
+ ('a' <= c && c <= 'z') ||
+ c == '-' || c == '_' || c == '.' || c == '!' ||
+ c == '~' || c == '*' || c == '\'' || c == '(' || c == ')'
+ );
+}
+}
+
+Common::String LocalWebserver::urlEncodeQueryParameterValue(Common::String value) {
+ //OK chars = alphanum | "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+ //reserved for query are ";", "/", "?", ":", "@", "&", "=", "+", ","
+ //that means these must be encoded too or otherwise they could malform the query
+ Common::String result = "";
+ char hexChar[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+ for (uint32 i = 0; i < value.size(); ++i) {
+ char c = value[i];
+ if (isQueryUnreserved(c))
+ result += c;
+ else {
+ result += '%';
+ result += hexChar[(c >> 4) & 0xF];
+ result += hexChar[c & 0xF];
+ }
+ }
+ return result;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/localwebserver.h b/backends/networking/sdl_net/localwebserver.h
new file mode 100644
index 0000000000..c6cf8485c3
--- /dev/null
+++ b/backends/networking/sdl_net/localwebserver.h
@@ -0,0 +1,115 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H
+#define BACKENDS_NETWORKING_SDL_NET_LOCALWEBSERVER_H
+
+#include "backends/networking/sdl_net/client.h"
+#include "backends/networking/sdl_net/handlers/basehandler.h"
+#include "backends/networking/sdl_net/handlers/createdirectoryhandler.h"
+#include "backends/networking/sdl_net/handlers/downloadfilehandler.h"
+#include "backends/networking/sdl_net/handlers/filesajaxpagehandler.h"
+#include "backends/networking/sdl_net/handlers/filespagehandler.h"
+#include "backends/networking/sdl_net/handlers/indexpagehandler.h"
+#include "backends/networking/sdl_net/handlers/listajaxhandler.h"
+#include "backends/networking/sdl_net/handlers/resourcehandler.h"
+#include "backends/networking/sdl_net/handlers/uploadfilehandler.h"
+#include "common/hash-str.h"
+#include "common/mutex.h"
+#include "common/singleton.h"
+#include "common/scummsys.h"
+
+namespace Common {
+class SeekableReadStream;
+}
+
+typedef struct _SDLNet_SocketSet *SDLNet_SocketSet;
+typedef struct _TCPsocket *TCPsocket;
+
+namespace Networking {
+
+#define NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+
+class LocalWebserver : public Common::Singleton<LocalWebserver> {
+ static const uint32 FRAMES_PER_SECOND = 20;
+ static const uint32 TIMER_INTERVAL = 1000000 / FRAMES_PER_SECOND;
+ static const uint32 MAX_CONNECTIONS = 10;
+
+ friend void localWebserverTimer(void *); //calls handle()
+
+ SDLNet_SocketSet _set;
+ TCPsocket _serverSocket;
+ Client _client[MAX_CONNECTIONS];
+ int _clients;
+ bool _timerStarted, _stopOnIdle, _minimalMode;
+ Common::HashMap<Common::String, BaseHandler*> _pathHandlers;
+ BaseHandler *_defaultHandler;
+ IndexPageHandler _indexPageHandler;
+ FilesPageHandler _filesPageHandler;
+ CreateDirectoryHandler _createDirectoryHandler;
+ DownloadFileHandler _downloadFileHandler;
+ UploadFileHandler _uploadFileHandler;
+ ListAjaxHandler _listAjaxHandler;
+ FilesAjaxPageHandler _filesAjaxPageHandler;
+ ResourceHandler _resourceHandler;
+ uint32 _idlingFrames;
+ Common::Mutex _handleMutex;
+ Common::String _address;
+ uint32 _serverPort;
+
+ void startTimer(int interval = TIMER_INTERVAL);
+ void stopTimer();
+ void handle();
+ void handleClient(uint32 i);
+ void acceptClient();
+ void resolveAddress(void *ipAddress);
+ void addPathHandler(Common::String path, BaseHandler *handler);
+
+public:
+ static const uint32 DEFAULT_SERVER_PORT = 12345;
+
+ LocalWebserver();
+ virtual ~LocalWebserver();
+
+ void start(bool useMinimalMode = false);
+ void stop();
+ void stopOnIdle();
+
+ Common::String getAddress();
+ IndexPageHandler &indexPageHandler();
+ bool isRunning();
+ static uint32 getPort();
+
+ static void setClientGetHandler(Client &client, Common::String response, long code = 200, const char *mimeType = nullptr);
+ static void setClientGetHandler(Client &client, Common::SeekableReadStream *responseStream, long code = 200, const char *mimeType = nullptr);
+ static void setClientRedirectHandler(Client &client, Common::String response, Common::String location, const char *mimeType = nullptr);
+ static void setClientRedirectHandler(Client &client, Common::SeekableReadStream *responseStream, Common::String location, const char *mimeType = nullptr);
+ static Common::String urlDecode(Common::String value);
+ static Common::String urlEncodeQueryParameterValue(Common::String value);
+};
+
+/** Shortcut for accessing the local webserver. */
+#define LocalServer Networking::LocalWebserver::instance()
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/reader.cpp b/backends/networking/sdl_net/reader.cpp
new file mode 100644
index 0000000000..8f3199f51c
--- /dev/null
+++ b/backends/networking/sdl_net/reader.cpp
@@ -0,0 +1,462 @@
+/* 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 "backends/networking/sdl_net/reader.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/memstream.h"
+#include "common/stream.h"
+
+namespace Networking {
+
+Reader::Reader() {
+ _state = RS_NONE;
+ _content = nullptr;
+ _bytesLeft = 0;
+
+ _window = nullptr;
+ _windowUsed = 0;
+ _windowSize = 0;
+
+ _headersStream = nullptr;
+ _firstBlock = true;
+
+ _contentLength = 0;
+ _availableBytes = 0;
+ _isBadRequest = false;
+ _allContentRead = false;
+}
+
+Reader::~Reader() {
+ cleanup();
+}
+
+Reader &Reader::operator=(Reader &r) {
+ if (this == &r)
+ return *this;
+ cleanup();
+
+ _state = r._state;
+ _content = r._content;
+ _bytesLeft = r._bytesLeft;
+ r._state = RS_NONE;
+
+ _window = r._window;
+ _windowUsed = r._windowUsed;
+ _windowSize = r._windowSize;
+ r._window = nullptr;
+
+ _headersStream = r._headersStream;
+ r._headersStream = nullptr;
+
+ _headers = r._headers;
+ _method = r._method;
+ _path = r._path;
+ _query = r._query;
+ _anchor = r._anchor;
+ _queryParameters = r._queryParameters;
+ _contentLength = r._contentLength;
+ _boundary = r._boundary;
+ _availableBytes = r._availableBytes;
+ _firstBlock = r._firstBlock;
+ _isBadRequest = r._isBadRequest;
+ _allContentRead = r._allContentRead;
+
+ return *this;
+}
+
+void Reader::cleanup() {
+ //_content is not to be freed, it's not owned by Reader
+
+ if (_headersStream != nullptr)
+ delete _headersStream;
+
+ if (_window != nullptr)
+ freeWindow();
+}
+
+bool Reader::readAndHandleFirstHeaders() {
+ Common::String boundary = "\r\n\r\n";
+ if (_window == nullptr) {
+ makeWindow(boundary.size());
+ }
+ if (_headersStream == nullptr) {
+ _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+ }
+
+ while (readOneByteInStream(_headersStream, boundary)) {
+ if (_headersStream->size() > SUSPICIOUS_HEADERS_SIZE) {
+ _isBadRequest = true;
+ return true;
+ }
+ if (!bytesLeft())
+ return false;
+ }
+ handleFirstHeaders(_headersStream);
+
+ freeWindow();
+ _state = RS_READING_CONTENT;
+ return true;
+}
+
+bool Reader::readBlockHeadersIntoStream(Common::WriteStream *stream) {
+ Common::String boundary = "\r\n\r\n";
+ if (_window == nullptr) makeWindow(boundary.size());
+
+ while (readOneByteInStream(stream, boundary)) {
+ if (!bytesLeft())
+ return false;
+ }
+ if (stream) stream->flush();
+
+ freeWindow();
+ _state = RS_READING_CONTENT;
+ return true;
+}
+
+namespace {
+void readFromThatUntilLineEnd(const char *cstr, Common::String needle, Common::String &result) {
+ const char *position = strstr(cstr, needle.c_str());
+
+ if (position) {
+ char c;
+ for (const char *i = position + needle.size(); c = *i, c != 0; ++i) {
+ if (c == '\n' || c == '\r')
+ break;
+ result += c;
+ }
+ }
+}
+}
+
+void Reader::handleFirstHeaders(Common::MemoryReadWriteStream *headersStream) {
+ if (!_boundary.empty()) {
+ warning("Reader: handleFirstHeaders() called when first headers were already handled");
+ return;
+ }
+
+ //parse method, path, query, fragment
+ _headers = readEverythingFromMemoryStream(headersStream);
+ parseFirstLine(_headers);
+
+ //find boundary
+ _boundary = "";
+ readFromThatUntilLineEnd(_headers.c_str(), "boundary=", _boundary);
+
+ //find content length
+ Common::String contentLength = "";
+ readFromThatUntilLineEnd(_headers.c_str(), "Content-Length: ", contentLength);
+ _contentLength = contentLength.asUint64();
+ _availableBytes = _contentLength;
+}
+
+void Reader::parseFirstLine(const Common::String &headers) {
+ uint32 headersSize = headers.size();
+ bool bad = false;
+
+ if (headersSize > 0) {
+ const char *cstr = headers.c_str();
+ const char *position = strstr(cstr, "\r\n");
+ if (position) { //we have at least one line - and we want the first one
+ //"<METHOD> <path> HTTP/<VERSION>\r\n"
+ Common::String method, path, http, buf;
+ uint32 length = position - cstr;
+ if (headersSize > length)
+ headersSize = length;
+ for (uint32 i = 0; i < headersSize; ++i) {
+ if (headers[i] != ' ')
+ buf += headers[i];
+ if (headers[i] == ' ' || i == headersSize - 1) {
+ if (method == "") {
+ method = buf;
+ } else if (path == "") {
+ path = buf;
+ } else if (http == "") {
+ http = buf;
+ } else {
+ bad = true;
+ break;
+ }
+ buf = "";
+ }
+ }
+
+ //check that method is supported
+ if (method != "GET" && method != "PUT" && method != "POST")
+ bad = true;
+
+ //check that HTTP/<VERSION> is OK
+ if (!http.hasPrefix("HTTP/"))
+ bad = true;
+
+ _method = method;
+ parsePathQueryAndAnchor(path);
+ }
+ }
+
+ if (bad) _isBadRequest = true;
+}
+
+void Reader::parsePathQueryAndAnchor(Common::String path) {
+ //<path>[?query][#anchor]
+ bool readingPath = true;
+ bool readingQuery = false;
+ _path = "";
+ _query = "";
+ _anchor = "";
+ for (uint32 i = 0; i < path.size(); ++i) {
+ if (readingPath) {
+ if (path[i] == '?') {
+ readingPath = false;
+ readingQuery = true;
+ } else {
+ _path += path[i];
+ }
+ } else if (readingQuery) {
+ if (path[i] == '#') {
+ readingQuery = false;
+ } else {
+ _query += path[i];
+ }
+ } else {
+ _anchor += path[i];
+ }
+ }
+
+ parseQueryParameters();
+}
+
+void Reader::parseQueryParameters() {
+ Common::String key = "";
+ Common::String value = "";
+ bool readingKey = true;
+ for (uint32 i = 0; i < _query.size(); ++i) {
+ if (readingKey) {
+ if (_query[i] == '=') {
+ readingKey = false;
+ value = "";
+ } else {
+ key += _query[i];
+ }
+ } else {
+ if (_query[i] == '&') {
+ if (_queryParameters.contains(key))
+ warning("Reader: query parameter \"%s\" is already set!", key.c_str());
+ else
+ _queryParameters[key] = LocalWebserver::urlDecode(value);
+ readingKey = true;
+ key = "";
+ } else {
+ value += _query[i];
+ }
+ }
+ }
+
+ if (!key.empty()) {
+ if (_queryParameters.contains(key))
+ warning("Reader: query parameter \"%s\" is already set!", key.c_str());
+ else
+ _queryParameters[key] = LocalWebserver::urlDecode(value);
+ }
+}
+
+bool Reader::readContentIntoStream(Common::WriteStream *stream) {
+ Common::String boundary = "--" + _boundary;
+ if (!_firstBlock)
+ boundary = "\r\n" + boundary;
+ if (_boundary.empty())
+ boundary = "\r\n";
+ if (_window == nullptr)
+ makeWindow(boundary.size());
+
+ while (readOneByteInStream(stream, boundary)) {
+ if (!bytesLeft())
+ return false;
+ }
+
+ _firstBlock = false;
+ if (stream)
+ stream->flush();
+
+ freeWindow();
+ _state = RS_READING_HEADERS;
+ return true;
+}
+
+void Reader::makeWindow(uint32 size) {
+ freeWindow();
+
+ _window = new byte[size];
+ _windowUsed = 0;
+ _windowSize = size;
+}
+
+void Reader::freeWindow() {
+ delete[] _window;
+ _window = nullptr;
+ _windowUsed = _windowSize = 0;
+}
+
+namespace {
+bool windowEqualsString(const byte *window, uint32 windowSize, const Common::String &boundary) {
+ if (boundary.size() != windowSize)
+ return false;
+
+ for (uint32 i = 0; i < windowSize; ++i) {
+ if (window[i] != boundary[i])
+ return false;
+ }
+
+ return true;
+}
+}
+
+bool Reader::readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary) {
+ byte b = readOne();
+ _window[_windowUsed++] = b;
+ if (_windowUsed < _windowSize)
+ return true;
+
+ //when window is filled, check whether that's the boundary
+ if (windowEqualsString(_window, _windowSize, boundary))
+ return false;
+
+ //if not, add the first byte of the window to the string
+ if (stream)
+ stream->writeByte(_window[0]);
+ for (uint32 i = 1; i < _windowSize; ++i)
+ _window[i - 1] = _window[i];
+ --_windowUsed;
+ return true;
+}
+
+byte Reader::readOne() {
+ byte b;
+ _content->read(&b, 1);
+ --_availableBytes;
+ --_bytesLeft;
+ return b;
+}
+
+/// public
+
+bool Reader::readFirstHeaders() {
+ if (_state == RS_NONE)
+ _state = RS_READING_HEADERS;
+
+ if (!bytesLeft())
+ return false;
+
+ if (_state == RS_READING_HEADERS)
+ return readAndHandleFirstHeaders();
+
+ warning("Reader::readFirstHeaders(): bad state");
+ return false;
+}
+
+bool Reader::readFirstContent(Common::WriteStream *stream) {
+ if (_state != RS_READING_CONTENT) {
+ warning("Reader::readFirstContent(): bad state");
+ return false;
+ }
+
+ // no difference, actually
+ return readBlockContent(stream);
+}
+
+bool Reader::readBlockHeaders(Common::WriteStream *stream) {
+ if (_state != RS_READING_HEADERS) {
+ warning("Reader::readBlockHeaders(): bad state");
+ return false;
+ }
+
+ if (!bytesLeft())
+ return false;
+
+ return readBlockHeadersIntoStream(stream);
+}
+
+bool Reader::readBlockContent(Common::WriteStream *stream) {
+ if (_state != RS_READING_CONTENT) {
+ warning("Reader::readBlockContent(): bad state");
+ return false;
+ }
+
+ if (!bytesLeft())
+ return false;
+
+ if (!readContentIntoStream(stream))
+ return false;
+
+ if (_availableBytes >= 2) {
+ Common::String bts;
+ bts += readOne();
+ bts += readOne();
+ if (bts == "--")
+ _allContentRead = true;
+ else if (bts != "\r\n")
+ warning("Reader: strange bytes: \"%s\"", bts.c_str());
+ } else {
+ warning("Reader: strange ending");
+ _allContentRead = true;
+ }
+
+ return true;
+}
+
+uint32 Reader::bytesLeft() const { return _bytesLeft; }
+
+void Reader::setContent(Common::MemoryReadWriteStream *stream) {
+ _content = stream;
+ _bytesLeft = stream->size() - stream->pos();
+}
+
+bool Reader::badRequest() const { return _isBadRequest; }
+
+bool Reader::noMoreContent() const { return _allContentRead; }
+
+Common::String Reader::headers() const { return _headers; }
+
+Common::String Reader::method() const { return _method; }
+
+Common::String Reader::path() const { return _path; }
+
+Common::String Reader::query() const { return _query; }
+
+Common::String Reader::queryParameter(Common::String name) const { return _queryParameters[name]; }
+
+Common::String Reader::anchor() const { return _anchor; }
+
+Common::String Reader::readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream) {
+ Common::String result;
+ char buf[1024];
+ uint32 readBytes;
+ while (true) {
+ readBytes = stream->read(buf, 1024);
+ if (readBytes == 0)
+ break;
+ result += Common::String(buf, readBytes);
+ }
+ return result;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/reader.h b/backends/networking/sdl_net/reader.h
new file mode 100644
index 0000000000..16d62a27eb
--- /dev/null
+++ b/backends/networking/sdl_net/reader.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 BACKENDS_NETWORKING_SDL_NET_READER_H
+#define BACKENDS_NETWORKING_SDL_NET_READER_H
+
+#include "common/str.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+
+namespace Common {
+class MemoryReadWriteStream;
+class WriteStream;
+}
+
+namespace Networking {
+
+enum ReaderState {
+ RS_NONE,
+ RS_READING_HEADERS,
+ RS_READING_CONTENT
+};
+
+/**
+ * This is a helper class for Client.
+ *
+ * It parses HTTP request and finds headers
+ * and content. It also supports POST form/multipart.
+ *
+ * One might pass the request even byte by byte,
+ * Reader will always be able to continue from the
+ * state it stopped on.
+ *
+ * Main headers/content must be read with
+ * readFirstHeaders() and readFirstContent() methods.
+ * Further headers/content blocks (POST form/multipart)
+ * must be read with readBlockHeaders() and readBlockContent().
+ *
+ * Main headers and parsed URL components could be accessed
+ * with special methods after reading.
+ *
+ * To use the object, call setContent() and then one of those
+ * reading methods. It would return whether reading is over
+ * or not. If reading is over, content stream still could
+ * contain bytes to read with other methods.
+ *
+ * If reading is not over, Reader awaits you to call the
+ * same reading method when you'd get more content.
+ *
+ * If it's over, you should check whether Reader awaits
+ * more content with noMoreContent() and call the other
+ * reading method, if it is. When headers are read, one
+ * must read contents, and vice versa.
+ */
+
+class Reader {
+ ReaderState _state;
+ Common::MemoryReadWriteStream *_content;
+ uint32 _bytesLeft;
+
+ byte *_window;
+ uint32 _windowUsed, _windowSize;
+
+ Common::MemoryReadWriteStream *_headersStream;
+
+ Common::String _headers;
+ Common::String _method, _path, _query, _anchor;
+ Common::HashMap<Common::String, Common::String> _queryParameters;
+ uint32 _contentLength;
+ Common::String _boundary;
+ uint32 _availableBytes;
+ bool _firstBlock;
+ bool _isBadRequest;
+ bool _allContentRead;
+
+ void cleanup();
+
+ bool readAndHandleFirstHeaders(); //true when ended reading
+ bool readBlockHeadersIntoStream(Common::WriteStream *stream); //true when ended reading
+ bool readContentIntoStream(Common::WriteStream *stream); //true when ended reading
+
+ void handleFirstHeaders(Common::MemoryReadWriteStream *headers);
+ void parseFirstLine(const Common::String &headers);
+ void parsePathQueryAndAnchor(Common::String path);
+ void parseQueryParameters();
+
+ void makeWindow(uint32 size);
+ void freeWindow();
+ bool readOneByteInStream(Common::WriteStream *stream, const Common::String &boundary);
+
+ byte readOne();
+ uint32 bytesLeft() const;
+
+public:
+ static const uint32 SUSPICIOUS_HEADERS_SIZE = 1024 * 1024; // 1 MB is really a lot
+
+ Reader();
+ ~Reader();
+
+ Reader &operator=(Reader &r);
+
+ bool readFirstHeaders(); //true when ended reading
+ bool readFirstContent(Common::WriteStream *stream); //true when ended reading
+ bool readBlockHeaders(Common::WriteStream *stream); //true when ended reading
+ bool readBlockContent(Common::WriteStream *stream); //true when ended reading
+
+ void setContent(Common::MemoryReadWriteStream *stream);
+
+ bool badRequest() const;
+ bool noMoreContent() const;
+
+ Common::String headers() const;
+ Common::String method() const;
+ Common::String path() const;
+ Common::String query() const;
+ Common::String queryParameter(Common::String name) const;
+ Common::String anchor() const;
+
+ static Common::String readEverythingFromMemoryStream(Common::MemoryReadWriteStream *stream);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/sdl_net/uploadfileclienthandler.cpp b/backends/networking/sdl_net/uploadfileclienthandler.cpp
new file mode 100644
index 0000000000..ebf341682c
--- /dev/null
+++ b/backends/networking/sdl_net/uploadfileclienthandler.cpp
@@ -0,0 +1,212 @@
+/* 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 "backends/networking/sdl_net/uploadfileclienthandler.h"
+#include "backends/fs/fs-factory.h"
+#include "backends/networking/sdl_net/handlerutils.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "backends/networking/sdl_net/reader.h"
+#include "common/file.h"
+#include "common/memstream.h"
+#include "common/translation.h"
+
+namespace Networking {
+
+UploadFileClientHandler::UploadFileClientHandler(Common::String parentDirectoryPath):
+ _state(UFH_READING_CONTENT), _headersStream(nullptr), _contentStream(nullptr),
+ _parentDirectoryPath(parentDirectoryPath), _uploadedFiles(0) {}
+
+UploadFileClientHandler::~UploadFileClientHandler() {
+ delete _headersStream;
+ delete _contentStream;
+}
+
+void UploadFileClientHandler::handle(Client *client) {
+ if (client == nullptr) {
+ warning("UploadFileClientHandler::handle(): empty client pointer");
+ return;
+ }
+
+ while (true) {
+ switch (_state) {
+ case UFH_READING_CONTENT:
+ if (client->readContent(nullptr)) {
+ _state = UFH_READING_BLOCK_HEADERS;
+ continue;
+ }
+ break;
+
+ case UFH_READING_BLOCK_HEADERS:
+ if (_headersStream == nullptr)
+ _headersStream = new Common::MemoryReadWriteStream(DisposeAfterUse::YES);
+
+ if (client->readBlockHeaders(_headersStream)) {
+ handleBlockHeaders(client);
+ continue;
+ }
+
+ // fail on suspicious headers
+ if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) {
+ setErrorMessageHandler(*client, _("Invalid request: headers are too long!"));
+ }
+ break;
+
+ case UFH_READING_BLOCK_CONTENT:
+ // _contentStream is created by handleBlockHeaders() if needed
+
+ if (client->readBlockContent(_contentStream)) {
+ handleBlockContent(client);
+ continue;
+ }
+ break;
+
+ case UFH_ERROR:
+ case UFH_STOP:
+ return;
+ }
+
+ break;
+ }
+}
+
+namespace {
+void readFromThatUntilDoubleQuote(const char *cstr, Common::String needle, Common::String &result) {
+ const char *position = strstr(cstr, needle.c_str());
+
+ if (position) {
+ char c;
+ for (const char *i = position + needle.size(); c = *i, c != 0; ++i) {
+ if (c == '"')
+ break;
+ result += c;
+ }
+ }
+}
+}
+
+void UploadFileClientHandler::handleBlockHeaders(Client *client) {
+ _state = UFH_READING_BLOCK_CONTENT;
+
+ // fail on suspicious headers
+ if (_headersStream->size() > Reader::SUSPICIOUS_HEADERS_SIZE) {
+ setErrorMessageHandler(*client, _("Invalid request: headers are too long!"));
+ }
+
+ // search for "upload_file" field
+ Common::String headers = Reader::readEverythingFromMemoryStream(_headersStream);
+ Common::String fieldName = "";
+ readFromThatUntilDoubleQuote(headers.c_str(), "name=\"", fieldName);
+ if (!fieldName.hasPrefix("upload_file"))
+ return;
+
+ Common::String filename = "";
+ readFromThatUntilDoubleQuote(headers.c_str(), "filename=\"", filename);
+
+ // skip block if <filename> is empty
+ if (filename.empty())
+ return;
+
+ if (HandlerUtils::hasForbiddenCombinations(filename))
+ return;
+
+ // check that <path>/<filename> doesn't exist
+ Common::String path = _parentDirectoryPath;
+ if (path.lastChar() != '/' && path.lastChar() != '\\')
+ path += '/';
+ AbstractFSNode *originalNode = g_system->getFilesystemFactory()->makeFileNodePath(path + filename);
+ if (!HandlerUtils::permittedPath(originalNode->getPath())) {
+ setErrorMessageHandler(*client, _("Invalid path!"));
+ return;
+ }
+ if (originalNode->exists()) {
+ setErrorMessageHandler(*client, _("There is a file with that name in the parent directory!"));
+ return;
+ }
+
+ // remove previous stream (if there is one)
+ if (_contentStream) {
+ delete _contentStream;
+ _contentStream = nullptr;
+ }
+
+ // create file stream (and necessary subdirectories)
+ Common::DumpFile *f = new Common::DumpFile();
+ if (!f->open(originalNode->getPath(), true)) {
+ delete f;
+ setErrorMessageHandler(*client, _("Failed to upload the file!"));
+ return;
+ }
+
+ _contentStream = f;
+}
+
+void UploadFileClientHandler::handleBlockContent(Client *client) {
+ _state = UFH_READING_BLOCK_HEADERS;
+
+ // if previous block headers were file-related and created a stream
+ if (_contentStream) {
+ _contentStream->flush();
+ ++_uploadedFiles;
+
+ delete _contentStream;
+ _contentStream = nullptr;
+
+ if (client->noMoreContent()) {
+ // success - redirect back to directory listing
+ setSuccessHandler(*client);
+ return;
+ }
+ }
+
+ // no more content avaiable
+ if (client->noMoreContent()) {
+ // if no file field was found - failure
+ if (_uploadedFiles == 0) {
+ setErrorMessageHandler(*client, _("No file was passed!"));
+ } else {
+ setSuccessHandler(*client);
+ }
+ }
+}
+
+void UploadFileClientHandler::setErrorMessageHandler(Client &client, Common::String message) {
+ HandlerUtils::setFilesManagerErrorMessageHandler(client, message);
+ _state = UFH_ERROR;
+}
+
+void UploadFileClientHandler::setSuccessHandler(Client &client) {
+ // success - redirect back to directory listing
+ HandlerUtils::setMessageHandler(
+ client,
+ Common::String::format(
+ "%s<br/><a href=\"files?path=%s\">%s</a>",
+ _("Uploaded successfully!"),
+ client.queryParameter("path").c_str(),
+ _("Back to parent directory")
+ ),
+ (client.queryParameter("ajax") == "true" ? "/filesAJAX?path=" : "/files?path=") +
+ LocalWebserver::urlEncodeQueryParameterValue(client.queryParameter("path"))
+ );
+ _state = UFH_STOP;
+}
+
+} // End of namespace Networking
diff --git a/backends/networking/sdl_net/uploadfileclienthandler.h b/backends/networking/sdl_net/uploadfileclienthandler.h
new file mode 100644
index 0000000000..b6481cf18f
--- /dev/null
+++ b/backends/networking/sdl_net/uploadfileclienthandler.h
@@ -0,0 +1,70 @@
+/* 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 BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H
+#define BACKENDS_NETWORKING_SDL_NET_UPLOADFILECLIENTHANDLER_H
+
+#include "backends/networking/sdl_net/client.h"
+#include "common/stream.h"
+
+namespace Networking {
+
+enum UploadFileHandlerState {
+ UFH_READING_CONTENT,
+ UFH_READING_BLOCK_HEADERS,
+ UFH_READING_BLOCK_CONTENT,
+ UFH_ERROR,
+ UFH_STOP
+};
+
+/**
+ * This class handles POST form/multipart upload.
+ *
+ * handleBlockHeaders() looks for filename and, if it's found,
+ * handleBlockContent() saves content into the file with such name.
+ *
+ * If no file found or other error occurs, it sets
+ * default error message handler.
+ */
+
+class UploadFileClientHandler: public ClientHandler {
+ UploadFileHandlerState _state;
+ Common::MemoryReadWriteStream *_headersStream;
+ Common::WriteStream *_contentStream;
+ Common::String _parentDirectoryPath;
+ uint32 _uploadedFiles;
+
+ void handleBlockHeaders(Client *client);
+ void handleBlockContent(Client *client);
+ void setErrorMessageHandler(Client &client, Common::String message);
+ void setSuccessHandler(Client &client);
+
+public:
+ UploadFileClientHandler(Common::String parentDirectoryPath);
+ virtual ~UploadFileClientHandler();
+
+ virtual void handle(Client *client);
+};
+
+} // End of namespace Networking
+
+#endif
diff --git a/backends/networking/wwwroot.zip b/backends/networking/wwwroot.zip
new file mode 100644
index 0000000000..b767d7c5d7
--- /dev/null
+++ b/backends/networking/wwwroot.zip
Binary files differ
diff --git a/backends/networking/wwwroot/.files.html b/backends/networking/wwwroot/.files.html
new file mode 100644
index 0000000000..f05c0113f8
--- /dev/null
+++ b/backends/networking/wwwroot/.files.html
@@ -0,0 +1,60 @@
+<!doctype html>
+<html>
+ <head>
+ <title>ScummVM</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+ </head>
+ <body>
+ <div class="container">
+ <div class='header'>
+ <center><img src="logo.png"/></center>
+ </div>
+ <div class="controls">
+ <table class="buttons"><tr>
+ <td><a href="javascript:show('create_directory');">{create_directory_button}</a></td>
+ <td><a href="javascript:show('upload_file');">{upload_files_button}</a></td>
+ </tr></table>
+ <div id="create_directory" class="modal">
+ <p>{create_directory_desc}</p>
+ <form action="create">
+ <input type="hidden" name="path" value="{path}"/>
+ <input type="text" name="directory_name" value=""/>
+ <input type="submit" value="{create_directory_button}"/>
+ </form>
+ </div>
+ <div id="upload_file" class="modal">
+ <p>{upload_file_desc}</p>
+ <form action="upload?path={path}" method="post" enctype="multipart/form-data">
+ <!-- we don't need "[]" in the name, as our webserver is not using PHP -->
+ <!-- "allowdirs" is a proposal, not implemented yet -->
+ <input type="file" name="upload_file-f" allowdirs multiple/>
+ <br/><br/>
+ <p>{or_upload_directory_desc}</p>
+ <!-- "directory"/"webkitdirectory" works in Chrome only yet, "multiple" is just in case here -->
+ <input type="file" name="upload_file-d" directory webkitdirectory multiple/>
+ <input type="submit" value="{upload_file_button}"/>
+ </form>
+ </div>
+ </div>
+ <div class="content">
+ <table class="files_list">
+ <td></td><td><b class="directory_name">{index_of_directory}</b></td><td></td>
+ {content}
+ </table>
+ </div>
+ </div>
+ <script>
+ function show(id) {
+ var e = document.getElementById(id);
+ var visible = (e.style.display == "block");
+ if (visible) id = ""; //hide
+
+ e = document.getElementById("create_directory");
+ e.style.display = (e.id == id ? "block" : "none");
+ e = document.getElementById("upload_file");
+ e.style.display = (e.id == id ? "block" : "none");
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/backends/networking/wwwroot/.filesAJAX.html b/backends/networking/wwwroot/.filesAJAX.html
new file mode 100644
index 0000000000..d45c73069d
--- /dev/null
+++ b/backends/networking/wwwroot/.filesAJAX.html
@@ -0,0 +1,240 @@
+<!doctype html>
+<html>
+ <head>
+ <title>ScummVM</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+ </head>
+ <body>
+ <div class="container">
+ <div class='header'>
+ <center><img src="logo.png"/></center>
+ </div>
+ <div class="controls">
+ <table class="buttons"><tr>
+ <td><a href="javascript:show('create_directory');">{create_directory_button}</a></td>
+ <td><a href="javascript:show('upload_file');">{upload_files_button}</a></td>
+ </tr></table>
+ <div id="create_directory" class="modal">
+ <p>{create_directory_desc}</p>
+ <form action="create" id="create_directory_form" onsubmit="return createDirectory();">
+ <input type="hidden" name="path" value="{path}"/>
+ <input type="hidden" name="ajax" value="true"/>
+ <input type="text" name="directory_name" value=""/>
+ <input type="submit" value="{create_directory_button}"/>
+ </form>
+ </div>
+ <div id="upload_file" class="modal">
+ <p>{upload_file_desc}</p>
+ <form action="upload?path={path}&ajax=true" method="post" enctype="multipart/form-data" id="files_upload_form">
+ <!-- we don't need "[]" in the name, as our webserver is not using PHP -->
+ <!-- "allowdirs" is a proposal, not implemented yet -->
+ <input type="file" name="upload_file-f" allowdirs multiple/>
+ <br/><br/>
+ <p>{or_upload_directory_desc}</p>
+ <!-- "directory"/"webkitdirectory" works in Chrome only yet, "multiple" is just in case here -->
+ <input type="file" name="upload_file-d" directory webkitdirectory multiple/>
+ <input type="submit" value="{upload_file_button}"/>
+ </form>
+ </div>
+ </div>
+ <div class="content">
+ <div id="loading_message">{loading}</div>
+ <div id="error_message">{error}</div>
+ <table class="files_list" id="files_list">
+ </table>
+ </div>
+ </div>
+ <script>
+ function show(id) {
+ var e = document.getElementById(id);
+ var visible = (e.style.display == "block");
+ if (visible) id = ""; //hide
+
+ e = document.getElementById("create_directory");
+ e.style.display = (e.id == id ? "block" : "none");
+ e = document.getElementById("upload_file");
+ e.style.display = (e.id == id ? "block" : "none");
+ }
+ </script>
+ <script src="ajax.js"></script>
+ <script>
+ window.onload = function () {
+ showDirectory("{start_path}");
+ }
+
+ function showDirectory(path) {
+ if (isLoading) return;
+ showLoading();
+ ajax.getAndParseJson("./list", {"path": path}, getCallback(path));
+ }
+
+ function getCallback(path) {
+ return function (jsonResponse) {
+ if (jsonResponse.type == "error") {
+ showError();
+ return;
+ }
+
+ openDirectory(path, jsonResponse.items);
+ hideLoading();
+ };
+ }
+
+ function createDirectory() {
+ if (isLoading) return;
+ showLoading();
+
+ var data = {"answer_json": "true"};
+ var elements = document.getElementById("create_directory_form").elements;
+ for (var el in elements)
+ data[elements[el].name] = elements[el].value;
+
+ ajax.getAndParseJson("./create", data, getCreateDirectoryCallback(data["path"]));
+ show("create_directory");
+ return false; // invalidate form, so it won't submit
+ }
+
+ function getCreateDirectoryCallback(path) {
+ return function (jsonResponse) {
+ console.log(jsonResponse);
+
+ if (jsonResponse.type == "error") {
+ showError();
+ return;
+ }
+
+ hideLoading();
+ showDirectory(path);
+ };
+ }
+
+ var isLoading = false;
+
+ function showLoading() {
+ isLoading = true;
+ var e = document.getElementById("loading_message");
+ e.style.display = "block";
+ e = document.getElementById("error_message");
+ e.style.display = "none";
+ }
+
+ function showError() {
+ isLoading = false;
+ var e = document.getElementById("loading_message");
+ e.style.display = "none";
+ e = document.getElementById("error_message");
+ e.style.display = "block";
+ //TODO: pass the actual message there?
+ }
+
+ function hideLoading() {
+ isLoading = false;
+ var e = document.getElementById("loading_message");
+ e.style.display = "none";
+ e = document.getElementById("error_message");
+ e.style.display = "none";
+ }
+
+ function openDirectory(path, items) {
+ // update path
+ document.getElementById("create_directory_form").elements["path"].value = path;
+ document.getElementById("files_upload_form").action = "upload?path=" + path + "&ajax=true";
+
+ // update table contents
+ listDirectory(path, items);
+ }
+
+ function makeBreadcrumb(name, path) {
+ var a = createElementWithContents("a", name);
+ a.onclick = function () { showDirectory(path); };
+ a.href = "javascript:void(0);";
+ return a;
+ }
+
+ function makeBreadcrumbs(path) {
+ var b = document.createElement("b");
+ b.className = "directory_name";
+
+ b.appendChild(createElementWithContents("span", "{index_of}"));
+ var slashes = true;
+ var crumb = "";
+ var currentPath = "";
+ path += ' '; //so the last slash is added
+ for (var i=0; i<path.length; ++i) {
+ if (path[i] == '/' || path[i] == '\\') {
+ if (!slashes) {
+ currentPath += crumb;
+ b.appendChild(makeBreadcrumb(crumb, currentPath+'/'));
+ slashes = true;
+ }
+ } else {
+ if (slashes) {
+ currentPath += "/";
+ if (currentPath == "/") { //make special '/' crumb here
+ b.appendChild(makeBreadcrumb('/', '/'));
+ } else {
+ b.appendChild(createElementWithContents("span", "/"));
+ }
+ slashes = false;
+ crumb = "";
+ }
+ crumb += path[i];
+ }
+ }
+ return b;
+ }
+
+ function listDirectory(path, items) {
+ // cleanup the list
+ var files_list = document.getElementById("files_list");
+ while (files_list.hasChildNodes())
+ files_list.removeChild(files_list.firstChild);
+ var tbody = document.createElement("tbody");
+
+ // add header item
+ var tr = document.createElement("tr");
+ tr.appendChild(createElementWithContents("td", ""));
+ var td = document.createElement("td");
+ td.appendChild(makeBreadcrumbs(path));
+ tr.appendChild(td);
+ tr.appendChild(createElementWithContents("td", ""));
+ tbody.appendChild(tr);
+
+ // add items
+ for (var i in items)
+ addItem(tbody, items[i]);
+
+ files_list.appendChild(tbody);
+ }
+
+ function addItem(tbody, item) {
+ var tr = document.createElement("tr");
+ var td = document.createElement("td");
+ var img = document.createElement("img");
+ img.src = "./icons/" + item.icon;
+ td.appendChild(img);
+ tr.appendChild(td);
+
+ td = document.createElement("td");
+ var a = createElementWithContents("a", item.name);
+ if (item.isDirectory) {
+ a.onclick = function () { showDirectory(item.path); };
+ a.href = "javascript:void(0);";
+ } else
+ a.href = "./download?path=" + encodeURIComponent(item.path);
+ td.appendChild(a);
+ tr.appendChild(td);
+
+ tr.appendChild(createElementWithContents("td", ""));
+ tbody.appendChild(tr);
+ }
+
+ function createElementWithContents(type, innerHTML) {
+ var e = document.createElement(type);
+ e.innerHTML = innerHTML;
+ return e;
+ }
+ </script>
+ </body>
+</html> \ No newline at end of file
diff --git a/backends/networking/wwwroot/.index.html b/backends/networking/wwwroot/.index.html
new file mode 100644
index 0000000000..2a3d9d382d
--- /dev/null
+++ b/backends/networking/wwwroot/.index.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html>
+ <head>
+ <title>ScummVM</title>
+ <meta charset="utf-8"/>
+ <link rel="stylesheet" type="text/css" href="style.css"/>
+ </head>
+ <body>
+ <div class="container">
+ <div class='header'>
+ <center><img src="logo.png"/></center>
+ </div>
+ <div class="content">
+ <p>{message}</p>
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/backends/networking/wwwroot/ajax.js b/backends/networking/wwwroot/ajax.js
new file mode 100644
index 0000000000..c01d7e93fc
--- /dev/null
+++ b/backends/networking/wwwroot/ajax.js
@@ -0,0 +1,48 @@
+// the following is snippet from http://stackoverflow.com/a/18078705
+// I changed a few things though
+
+var ajax = {};
+ajax.x = function () { return new XMLHttpRequest(); }; // "no one uses IE6"
+
+ajax.send = function (url, callback, errorCallback, method, data, async) {
+ if (async === undefined) async = true;
+
+ var x = ajax.x();
+ x.open(method, url, async);
+ x.onreadystatechange = function () {
+ if (x.readyState == XMLHttpRequest.DONE) {
+ if (x.status == 200)
+ callback(x.responseText);
+ else
+ errorCallback(x);
+ }
+ };
+ if (method == 'POST') {
+ x.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
+ }
+ x.send(data)
+};
+
+ajax.get = function (url, data, callback, errorCallback, async) {
+ var query = [];
+ for (var key in data) {
+ query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
+ }
+ ajax.send(url + (query.length ? '?' + query.join('&') : ''), callback, errorCallback, 'GET', null, async)
+};
+
+ajax.post = function (url, data, callback, errorCallback, async) {
+ var query = [];
+ for (var key in data) {
+ query.push(encodeURIComponent(key) + '=' + encodeURIComponent(data[key]));
+ }
+ ajax.send(url, callback, errorCallback, 'POST', query.join('&'), async)
+};
+
+ajax.getAndParseJson = function (url, data, callback) {
+ ajax.get(
+ url, data,
+ function (responseText) { callback(JSON.parse(responseText)); },
+ function (x) { console.log("error: " + x.status); }
+ );
+}; \ No newline at end of file
diff --git a/backends/networking/wwwroot/favicon.ico b/backends/networking/wwwroot/favicon.ico
new file mode 100644
index 0000000000..0283e8432e
--- /dev/null
+++ b/backends/networking/wwwroot/favicon.ico
Binary files differ
diff --git a/backends/networking/wwwroot/icons/7z.png b/backends/networking/wwwroot/icons/7z.png
new file mode 100644
index 0000000000..656e7e7c62
--- /dev/null
+++ b/backends/networking/wwwroot/icons/7z.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/dir.png b/backends/networking/wwwroot/icons/dir.png
new file mode 100644
index 0000000000..bcdec04a57
--- /dev/null
+++ b/backends/networking/wwwroot/icons/dir.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/txt.png b/backends/networking/wwwroot/icons/txt.png
new file mode 100644
index 0000000000..023d2ee24a
--- /dev/null
+++ b/backends/networking/wwwroot/icons/txt.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/unk.png b/backends/networking/wwwroot/icons/unk.png
new file mode 100644
index 0000000000..346eebecc3
--- /dev/null
+++ b/backends/networking/wwwroot/icons/unk.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/up.png b/backends/networking/wwwroot/icons/up.png
new file mode 100644
index 0000000000..2dc3df022b
--- /dev/null
+++ b/backends/networking/wwwroot/icons/up.png
Binary files differ
diff --git a/backends/networking/wwwroot/icons/zip.png b/backends/networking/wwwroot/icons/zip.png
new file mode 100644
index 0000000000..cdfc5763dd
--- /dev/null
+++ b/backends/networking/wwwroot/icons/zip.png
Binary files differ
diff --git a/backends/networking/wwwroot/logo.png b/backends/networking/wwwroot/logo.png
new file mode 100644
index 0000000000..9fdd2d0d1e
--- /dev/null
+++ b/backends/networking/wwwroot/logo.png
Binary files differ
diff --git a/backends/networking/wwwroot/style.css b/backends/networking/wwwroot/style.css
new file mode 100644
index 0000000000..ba31587c4d
--- /dev/null
+++ b/backends/networking/wwwroot/style.css
@@ -0,0 +1,113 @@
+html {
+ background: rgb(212, 117, 11);
+ background: linear-gradient(to bottom, rgb(212, 117, 11) 0%, rgb(212, 117, 11) 36%, rgb(239, 196, 24) 100%);
+ min-height: 100vh;
+}
+
+.container {
+ width: 80%;
+ margin: 0 auto;
+}
+
+.header {
+ padding: 10pt;
+ margin-bottom: 0;
+}
+
+.content {
+ padding: 8pt;
+ background: rgb(251, 241, 206);
+ font-family: Tahoma;
+ font-size: 16pt;
+}
+
+.content p { margin: 0 0 6pt 0; }
+
+.controls {
+ padding: 8pt;
+ background: #FFF;
+ font-family: Tahoma;
+ font-size: 16pt;
+}
+
+.controls .buttons {
+ width: 100%;
+ max-width: 500pt;
+ margin: -8pt auto;
+ border: 0;
+ border-spacing: 0;
+}
+
+.controls .buttons td {
+ width: 50%;
+ text-align: center;
+ margin: 0;
+ padding: 0;
+}
+
+.controls .buttons a {
+ display: block;
+ height: 40pt;
+ line-height: 38pt;
+ vertical-align: middle;
+ color: #000;
+ text-decoration: none;
+}
+
+.controls .buttons a:hover {
+ background: #F3F3F3;
+}
+
+.modal {
+ margin-top: 10pt;
+ display: none;
+}
+
+.modal p { margin: 0 0 6pt 0; }
+
+#create_directory input[type="text"], #upload_file input[type="file"] {
+ width: calc(100% - 2 * 5pt);
+}
+
+.modal input {
+ border: 1px solid #EEE;
+ padding: 5pt;
+ font-size: 12pt;
+}
+
+.modal input[type="submit"] {
+ display: block;
+ margin: 6pt auto;
+ background: #DDD;
+ border: 0;
+}
+
+.modal input[type="submit"]:hover {
+ background: #F3F3F3;
+ cursor: pointer;
+}
+
+td img { vertical-align: middle; height: 20px; }
+
+.directory_name {
+ display: block;
+ padding-bottom: 6px;
+}
+
+.directory_name a { color: black; }
+.directory_name a:hover { color: blue; }
+
+#loading_message, #error_message {
+ margin: -8pt;
+ margin-bottom: 5pt;
+ padding: 4pt;
+ text-align: center;
+}
+
+#loading_message {
+ background: #99FF99;
+}
+
+#error_message {
+ background: #FF9999;
+}
diff --git a/backends/platform/android/asset-archive.cpp b/backends/platform/android/asset-archive.cpp
index 6680081c16..0ee6efc111 100644
--- a/backends/platform/android/asset-archive.cpp
+++ b/backends/platform/android/asset-archive.cpp
@@ -22,8 +22,6 @@
#if defined(__ANDROID__)
-#include <jni.h>
-
#include <sys/types.h>
#include <unistd.h>
@@ -37,443 +35,112 @@
#include "backends/platform/android/jni.h"
#include "backends/platform/android/asset-archive.h"
-// Must match android.content.res.AssetManager.ACCESS_*
-const jint ACCESS_UNKNOWN = 0;
-const jint ACCESS_RANDOM = 1;
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
-// This might be useful to someone else. Assumes markSupported() == true.
-class JavaInputStream : public Common::SeekableReadStream {
+class AssetInputStream : public Common::SeekableReadStream {
public:
- JavaInputStream(JNIEnv *env, jobject is);
- virtual ~JavaInputStream();
-
- virtual bool eos() const {
- return _eos;
- }
+ AssetInputStream(AAssetManager *as, const Common::String &path);
+ virtual ~AssetInputStream();
- virtual bool err() const {
- return _err;
- }
+ virtual bool eos() const { return _eos; }
- virtual void clearErr() {
- _eos = _err = false;
- }
+ virtual void clearErr() {_eos = false; }
virtual uint32 read(void *dataPtr, uint32 dataSize);
- virtual int32 pos() const {
- return _pos;
- }
+ virtual int32 pos() const { return _pos; }
- virtual int32 size() const {
- return _len;
- }
+ virtual int32 size() const { return _len; }
virtual bool seek(int32 offset, int whence = SEEK_SET);
private:
- void close(JNIEnv *env);
-
- jmethodID MID_mark;
- jmethodID MID_available;
- jmethodID MID_close;
- jmethodID MID_read;
- jmethodID MID_reset;
- jmethodID MID_skip;
+ void close();
+ AAsset *_asset;
- jobject _input_stream;
- jsize _buflen;
- jbyteArray _buf;
uint32 _pos;
- jint _len;
+ uint32 _len;
bool _eos;
- bool _err;
};
-JavaInputStream::JavaInputStream(JNIEnv *env, jobject is) :
- _eos(false),
- _err(false),
- _pos(0)
-{
- _input_stream = env->NewGlobalRef(is);
- _buflen = 8192;
- jobject buf = env->NewByteArray(_buflen);
- _buf = (jbyteArray)env->NewGlobalRef(buf);
- env->DeleteLocalRef(buf);
-
- jclass cls = env->GetObjectClass(_input_stream);
- MID_mark = env->GetMethodID(cls, "mark", "(I)V");
- assert(MID_mark);
- MID_available = env->GetMethodID(cls, "available", "()I");
- assert(MID_available);
- MID_close = env->GetMethodID(cls, "close", "()V");
- assert(MID_close);
- MID_read = env->GetMethodID(cls, "read", "([BII)I");
- assert(MID_read);
- MID_reset = env->GetMethodID(cls, "reset", "()V");
- assert(MID_reset);
- MID_skip = env->GetMethodID(cls, "skip", "(J)J");
- assert(MID_skip);
- env->DeleteLocalRef(cls);
-
- // Mark start of stream, so we can reset back to it.
- // readlimit is set to something bigger than anything we might
- // want to seek within.
- env->CallVoidMethod(_input_stream, MID_mark, 10 * 1024 * 1024);
- _len = env->CallIntMethod(_input_stream, MID_available);
+AssetInputStream::AssetInputStream(AAssetManager *as, const Common::String &path) :
+ _eos(false), _pos(0) {
+ _asset = AAssetManager_open(as, path.c_str(), AASSET_MODE_RANDOM);
+ _len = AAsset_getLength(_asset);
}
-JavaInputStream::~JavaInputStream() {
- JNIEnv *env = JNI::getEnv();
- close(env);
-
- env->DeleteGlobalRef(_buf);
- env->DeleteGlobalRef(_input_stream);
+AssetInputStream::~AssetInputStream() {
}
-void JavaInputStream::close(JNIEnv *env) {
- env->CallVoidMethod(_input_stream, MID_close);
-
- if (env->ExceptionCheck())
- env->ExceptionClear();
+void AssetInputStream::close() {
+ AAsset_close(_asset);
}
-uint32 JavaInputStream::read(void *dataPtr, uint32 dataSize) {
- JNIEnv *env = JNI::getEnv();
-
- if (_buflen < jint(dataSize)) {
- _buflen = dataSize;
-
- env->DeleteGlobalRef(_buf);
- jobject buf = env->NewByteArray(_buflen);
- _buf = static_cast<jbyteArray>(env->NewGlobalRef(buf));
- env->DeleteLocalRef(buf);
- }
-
- jint ret = env->CallIntMethod(_input_stream, MID_read, _buf, 0, dataSize);
-
- if (env->ExceptionCheck()) {
- warning("Exception during JavaInputStream::read(%p, %d)",
- dataPtr, dataSize);
-
- env->ExceptionDescribe();
- env->ExceptionClear();
-
- _err = true;
- ret = -1;
- } else if (ret == -1) {
+uint32 AssetInputStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 readlen = AAsset_read(_asset, dataPtr, dataSize);
+ _pos += readlen;
+ if (readlen != dataSize) {
_eos = true;
- ret = 0;
- } else {
- env->GetByteArrayRegion(_buf, 0, ret, static_cast<jbyte *>(dataPtr));
- _pos += ret;
}
-
- return ret;
+ return readlen;
}
-bool JavaInputStream::seek(int32 offset, int whence) {
- JNIEnv *env = JNI::getEnv();
- uint32 newpos;
-
- switch (whence) {
- case SEEK_SET:
- newpos = offset;
- break;
- case SEEK_CUR:
- newpos = _pos + offset;
- break;
- case SEEK_END:
- newpos = _len + offset;
- break;
- default:
- debug("Unknown 'whence' arg %d", whence);
+bool AssetInputStream::seek(int32 offset, int whence) {
+ int res = AAsset_seek(_asset, offset, whence);
+ if (res == -1) {
return false;
}
-
- jlong skip_bytes;
- if (newpos > _pos) {
- skip_bytes = newpos - _pos;
- } else {
- // Can't skip backwards, so jump back to start and skip from there.
- env->CallVoidMethod(_input_stream, MID_reset);
-
- if (env->ExceptionCheck()) {
- warning("Failed to rewind to start of asset stream");
-
- env->ExceptionDescribe();
- env->ExceptionClear();
-
- return false;
- }
-
- _pos = 0;
- skip_bytes = newpos;
- }
-
- while (skip_bytes > 0) {
- jlong ret = env->CallLongMethod(_input_stream, MID_skip, skip_bytes);
-
- if (env->ExceptionCheck()) {
- warning("Failed to skip %ld bytes into asset stream",
- static_cast<long>(skip_bytes));
-
- env->ExceptionDescribe();
- env->ExceptionClear();
-
- return false;
- } else if (ret == 0) {
- warning("InputStream->skip(%ld) didn't skip any bytes. Aborting seek.",
- static_cast<long>(skip_bytes));
-
- // No point looping forever...
- return false;
- }
-
- _pos += ret;
- skip_bytes -= ret;
- }
-
- _eos = false;
- return true;
-}
-
-// Must match android.content.res.AssetFileDescriptor.UNKNOWN_LENGTH
-const jlong UNKNOWN_LENGTH = -1;
-
-// Reading directly from a fd is so much more efficient, that it is
-// worth optimising for.
-class AssetFdReadStream : public Common::SeekableReadStream {
-public:
- AssetFdReadStream(JNIEnv *env, jobject assetfd);
- virtual ~AssetFdReadStream();
-
- virtual bool eos() const {
- return _eos;
- }
-
- virtual bool err() const {
- return _err;
- }
-
- virtual void clearErr() {
- _eos = _err = false;
- }
-
- virtual uint32 read(void *dataPtr, uint32 dataSize);
-
- virtual int32 pos() const {
- return _pos;
- }
-
- virtual int32 size() const {
- return _declared_len;
- }
-
- virtual bool seek(int32 offset, int whence = SEEK_SET);
-
-private:
- void close(JNIEnv *env);
-
- int _fd;
- jmethodID MID_close;
- jobject _assetfd;
- jlong _start_off;
- jlong _declared_len;
- uint32 _pos;
- bool _eos;
- bool _err;
-};
-
-AssetFdReadStream::AssetFdReadStream(JNIEnv *env, jobject assetfd) :
- _eos(false),
- _err(false),
- _pos(0)
-{
- _assetfd = env->NewGlobalRef(assetfd);
-
- jclass cls = env->GetObjectClass(_assetfd);
- MID_close = env->GetMethodID(cls, "close", "()V");
- assert(MID_close);
-
- jmethodID MID_getStartOffset =
- env->GetMethodID(cls, "getStartOffset", "()J");
- assert(MID_getStartOffset);
- _start_off = env->CallLongMethod(_assetfd, MID_getStartOffset);
-
- jmethodID MID_getDeclaredLength =
- env->GetMethodID(cls, "getDeclaredLength", "()J");
- assert(MID_getDeclaredLength);
- _declared_len = env->CallLongMethod(_assetfd, MID_getDeclaredLength);
-
- jmethodID MID_getFileDescriptor =
- env->GetMethodID(cls, "getFileDescriptor",
- "()Ljava/io/FileDescriptor;");
- assert(MID_getFileDescriptor);
- jobject javafd = env->CallObjectMethod(_assetfd, MID_getFileDescriptor);
- assert(javafd);
-
- jclass fd_cls = env->GetObjectClass(javafd);
- jfieldID FID_descriptor = env->GetFieldID(fd_cls, "descriptor", "I");
- assert(FID_descriptor);
- env->DeleteLocalRef(fd_cls);
-
- _fd = env->GetIntField(javafd, FID_descriptor);
- env->DeleteLocalRef(javafd);
-
- env->DeleteLocalRef(cls);
-}
-
-AssetFdReadStream::~AssetFdReadStream() {
- JNIEnv *env = JNI::getEnv();
- env->CallVoidMethod(_assetfd, MID_close);
-
- if (env->ExceptionCheck())
- env->ExceptionClear();
-
- env->DeleteGlobalRef(_assetfd);
-}
-
-uint32 AssetFdReadStream::read(void *dataPtr, uint32 dataSize) {
- if (_declared_len != UNKNOWN_LENGTH) {
- jlong cap = _declared_len - _pos;
- if (dataSize > cap)
- dataSize = cap;
- }
-
- int ret = ::read(_fd, dataPtr, dataSize);
-
- if (ret == 0)
- _eos = true;
- else if (ret == -1)
- _err = true;
- else
- _pos += ret;
-
- return ret;
-}
-
-bool AssetFdReadStream::seek(int32 offset, int whence) {
- if (whence == SEEK_SET) {
- if (_declared_len != UNKNOWN_LENGTH && offset > _declared_len)
- offset = _declared_len;
-
- offset += _start_off;
- } else if (whence == SEEK_END && _declared_len != UNKNOWN_LENGTH) {
- whence = SEEK_SET;
- offset = _start_off + _declared_len + offset;
+ if (whence == SEEK_CUR) {
+ _pos += offset;
+ } else if (whence == SEEK_SET) {
+ _pos = offset;
+ } else if (whence == SEEK_END) {
+ _pos = _len + offset;
}
-
- int ret = lseek(_fd, offset, whence);
-
- if (ret == -1)
- return false;
-
- _pos = ret - _start_off;
+ assert(_pos <= _len);
_eos = false;
-
return true;
}
-AndroidAssetArchive::AndroidAssetArchive(jobject am) {
+AndroidAssetArchive::AndroidAssetArchive(jobject am) : _hasCached(false) {
JNIEnv *env = JNI::getEnv();
- _am = env->NewGlobalRef(am);
- jclass cls = env->GetObjectClass(_am);
- MID_open = env->GetMethodID(cls, "open",
- "(Ljava/lang/String;I)Ljava/io/InputStream;");
- assert(MID_open);
-
- MID_openFd = env->GetMethodID(cls, "openFd", "(Ljava/lang/String;)"
- "Landroid/content/res/AssetFileDescriptor;");
- assert(MID_openFd);
-
- MID_list = env->GetMethodID(cls, "list",
- "(Ljava/lang/String;)[Ljava/lang/String;");
- assert(MID_list);
- env->DeleteLocalRef(cls);
+ _am = AAssetManager_fromJava(env, am);
}
AndroidAssetArchive::~AndroidAssetArchive() {
- JNIEnv *env = JNI::getEnv();
- env->DeleteGlobalRef(_am);
}
bool AndroidAssetArchive::hasFile(const Common::String &name) const {
- JNIEnv *env = JNI::getEnv();
- jstring path = env->NewStringUTF(name.c_str());
- jobject result = env->CallObjectMethod(_am, MID_open, path, ACCESS_UNKNOWN);
- if (env->ExceptionCheck()) {
- // Assume FileNotFoundException
- //warning("Error while calling AssetManager->open(%s)", name.c_str());
- //env->ExceptionDescribe();
- env->ExceptionClear();
- env->DeleteLocalRef(path);
-
- return false;
+ AAsset *asset = AAssetManager_open(_am, name.c_str(), AASSET_MODE_RANDOM);
+ bool exists = false;
+ if (asset != NULL) {
+ exists = true;
+ AAsset_close(asset);
}
-
- env->DeleteLocalRef(result);
- env->DeleteLocalRef(path);
-
- return true;
+ return exists;
}
int AndroidAssetArchive::listMembers(Common::ArchiveMemberList &member_list) const {
- JNIEnv *env = JNI::getEnv();
- Common::List<Common::String> dirlist;
- dirlist.push_back("");
+ if (_hasCached) {
+ member_list.insert(member_list.end(), _cachedMembers.begin(), _cachedMembers.end());
+ return _cachedMembers.size();
+ }
int count = 0;
- while (!dirlist.empty()) {
- const Common::String dir = dirlist.back();
- dirlist.pop_back();
-
- jstring jpath = env->NewStringUTF(dir.c_str());
- jobjectArray jpathlist =
- (jobjectArray)env->CallObjectMethod(_am, MID_list, jpath);
-
- if (env->ExceptionCheck()) {
- warning("Error while calling AssetManager->list(%s). Ignoring.",
- dir.c_str());
- env->ExceptionDescribe();
- env->ExceptionClear();
-
- // May as well keep going ...
- continue;
- }
-
- env->DeleteLocalRef(jpath);
+ AAssetDir *dir = AAssetManager_openDir(_am, "");
+ const char *file = AAssetDir_getNextFileName(dir);
- for (jsize i = 0; i < env->GetArrayLength(jpathlist); ++i) {
- jstring elem = (jstring)env->GetObjectArrayElement(jpathlist, i);
- const char *p = env->GetStringUTFChars(elem, 0);
-
- if (strlen(p)) {
- Common::String thispath = dir;
-
- if (!thispath.empty())
- thispath += "/";
-
- thispath += p;
-
- // Assume files have a . in them, and directories don't
- if (strchr(p, '.')) {
- member_list.push_back(getMember(thispath));
- ++count;
- } else {
- // AssetManager is ridiculously slow and we don't care
- // about subdirectories at the moment, so ignore them.
- // dirlist.push_back(thispath);
- }
- }
-
- env->ReleaseStringUTFChars(elem, p);
- env->DeleteLocalRef(elem);
- }
-
- env->DeleteLocalRef(jpathlist);
+ while (file) {
+ member_list.push_back(getMember(file));
+ ++count;
+ file = AAssetDir_getNextFileName(dir);
}
+ AAssetDir_close(dir);
+
+ _cachedMembers = Common::ArchiveMemberList(member_list);
+ _hasCached = true;
return count;
}
@@ -483,39 +150,10 @@ const Common::ArchiveMemberPtr AndroidAssetArchive::getMember(const Common::Stri
}
Common::SeekableReadStream *AndroidAssetArchive::createReadStreamForMember(const Common::String &path) const {
- JNIEnv *env = JNI::getEnv();
- jstring jpath = env->NewStringUTF(path.c_str());
-
- // Try openFd() first ...
- jobject afd = env->CallObjectMethod(_am, MID_openFd, jpath);
-
- if (env->ExceptionCheck())
- env->ExceptionClear();
- else if (afd != 0) {
- // success :)
- Common::SeekableReadStream *stream = new AssetFdReadStream(env, afd);
- env->DeleteLocalRef(jpath);
- env->DeleteLocalRef(afd);
- return stream;
+ if (!hasFile(path)) {
+ return nullptr;
}
-
- // ... and fallback to normal open() if that doesn't work
- jobject is = env->CallObjectMethod(_am, MID_open, jpath, ACCESS_RANDOM);
-
- if (env->ExceptionCheck()) {
- // Assume FileNotFoundException
- //warning("Error opening %s", path.c_str());
- //env->ExceptionDescribe();
- env->ExceptionClear();
- env->DeleteLocalRef(jpath);
-
- return 0;
- }
-
- Common::SeekableReadStream *stream = new JavaInputStream(env, is);
- env->DeleteLocalRef(jpath);
- env->DeleteLocalRef(is);
- return stream;
+ return new AssetInputStream(_am, path);
}
#endif
diff --git a/backends/platform/android/asset-archive.h b/backends/platform/android/asset-archive.h
index 6a0033d24e..8ae55b22c9 100644
--- a/backends/platform/android/asset-archive.h
+++ b/backends/platform/android/asset-archive.h
@@ -32,6 +32,8 @@
#include "common/util.h"
#include "common/archive.h"
+#include <android/asset_manager.h>
+
class AndroidAssetArchive : public Common::Archive {
public:
AndroidAssetArchive(jobject am);
@@ -43,11 +45,9 @@ public:
virtual Common::SeekableReadStream *createReadStreamForMember(const Common::String &name) const;
private:
- jmethodID MID_open;
- jmethodID MID_openFd;
- jmethodID MID_list;
-
- jobject _am;
+ AAssetManager *_am;
+ mutable Common::ArchiveMemberList _cachedMembers;
+ mutable bool _hasCached;
};
#endif
diff --git a/backends/platform/android/jni.cpp b/backends/platform/android/jni.cpp
index 22e6a749c2..256ae09ef8 100644
--- a/backends/platform/android/jni.cpp
+++ b/backends/platform/android/jni.cpp
@@ -76,6 +76,8 @@ bool JNI::_ready_for_events = 0;
jmethodID JNI::_MID_getDPI = 0;
jmethodID JNI::_MID_displayMessageOnOSD = 0;
+jmethodID JNI::_MID_openUrl = 0;
+jmethodID JNI::_MID_isConnectionLimited = 0;
jmethodID JNI::_MID_setWindowCaption = 0;
jmethodID JNI::_MID_showVirtualKeyboard = 0;
jmethodID JNI::_MID_getSysArchives = 0;
@@ -232,6 +234,41 @@ void JNI::displayMessageOnOSD(const char *msg) {
env->DeleteLocalRef(java_msg);
}
+bool JNI::openUrl(const char *url) {
+ bool success = true;
+ JNIEnv *env = JNI::getEnv();
+ jstring javaUrl = env->NewStringUTF(url);
+
+ env->CallVoidMethod(_jobj, _MID_openUrl, javaUrl);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Failed to open URL");
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ success = false;
+ }
+
+ env->DeleteLocalRef(javaUrl);
+ return success;
+}
+
+bool JNI::isConnectionLimited() {
+ bool limited = false;
+ JNIEnv *env = JNI::getEnv();
+ limited = env->CallBooleanMethod(_jobj, _MID_isConnectionLimited);
+
+ if (env->ExceptionCheck()) {
+ LOGE("Failed to check whether connection's limited");
+
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ limited = true;
+ }
+
+ return limited;
+}
+
void JNI::setWindowCaption(const char *caption) {
JNIEnv *env = JNI::getEnv();
jstring java_caption = env->NewStringUTF(caption);
@@ -411,6 +448,8 @@ void JNI::create(JNIEnv *env, jobject self, jobject asset_manager,
FIND_METHOD(, setWindowCaption, "(Ljava/lang/String;)V");
FIND_METHOD(, getDPI, "([F)V");
FIND_METHOD(, displayMessageOnOSD, "(Ljava/lang/String;)V");
+ FIND_METHOD(, openUrl, "(Ljava/lang/String;)V");
+ FIND_METHOD(, isConnectionLimited, "()Z");
FIND_METHOD(, showVirtualKeyboard, "(Z)V");
FIND_METHOD(, getSysArchives, "()[Ljava/lang/String;");
FIND_METHOD(, initSurface, "()Ljavax/microedition/khronos/egl/EGLSurface;");
diff --git a/backends/platform/android/jni.h b/backends/platform/android/jni.h
index 70feaaf72a..0798db448a 100644
--- a/backends/platform/android/jni.h
+++ b/backends/platform/android/jni.h
@@ -58,6 +58,8 @@ public:
static void setWindowCaption(const char *caption);
static void getDPI(float *values);
static void displayMessageOnOSD(const char *msg);
+ static bool openUrl(const char *url);
+ static bool isConnectionLimited();
static void showVirtualKeyboard(bool enable);
static void addSysArchivesToSearchSet(Common::SearchSet &s, int priority);
@@ -89,6 +91,8 @@ private:
static jmethodID _MID_getDPI;
static jmethodID _MID_displayMessageOnOSD;
+ static jmethodID _MID_openUrl;
+ static jmethodID _MID_isConnectionLimited;
static jmethodID _MID_setWindowCaption;
static jmethodID _MID_showVirtualKeyboard;
static jmethodID _MID_getSysArchives;
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVM.java b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
index 3b370a583d..47dcb32b22 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVM.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVM.java
@@ -53,6 +53,8 @@ public abstract class ScummVM implements SurfaceHolder.Callback, Runnable {
// Callbacks from C++ peer instance
abstract protected void getDPI(float[] values);
abstract protected void displayMessageOnOSD(String msg);
+ abstract protected void openUrl(String url);
+ abstract protected boolean isConnectionLimited();
abstract protected void setWindowCaption(String caption);
abstract protected void showVirtualKeyboard(boolean enable);
abstract protected String[] getSysArchives();
diff --git a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
index 5b2dcae175..225496ca0d 100644
--- a/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
+++ b/backends/platform/android/org/scummvm/scummvm/ScummVMActivity.java
@@ -2,9 +2,13 @@ package org.scummvm.scummvm;
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.media.AudioManager;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
@@ -75,6 +79,21 @@ public class ScummVMActivity extends Activity {
}
@Override
+ protected void openUrl(String url) {
+ startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
+ }
+
+ @Override
+ protected boolean isConnectionLimited() {
+ WifiManager wifiMgr = (WifiManager)getSystemService(Context.WIFI_SERVICE);
+ if (wifiMgr != null && wifiMgr.isWifiEnabled()) {
+ WifiInfo wifiInfo = wifiMgr.getConnectionInfo();
+ return (wifiInfo == null || wifiInfo.getNetworkId() == -1); //WiFi is on, but it's not connected to any network
+ }
+ return true;
+ }
+
+ @Override
protected void setWindowCaption(final String caption) {
runOnUiThread(new Runnable() {
public void run() {
diff --git a/backends/platform/dc/dc-fs.cpp b/backends/platform/dc/dc-fs.cpp
index 77fe4143dd..4bd7e5a777 100644
--- a/backends/platform/dc/dc-fs.cpp
+++ b/backends/platform/dc/dc-fs.cpp
@@ -57,6 +57,7 @@ public:
virtual Common::SeekableReadStream *createReadStream();
virtual Common::WriteStream *createWriteStream() { return 0; }
+ virtual bool create(bool isDirectory) { return false; }
static AbstractFSNode *makeFileNodePath(const Common::String &path);
};
diff --git a/backends/platform/dc/vmsave.cpp b/backends/platform/dc/vmsave.cpp
index d896ba1299..5e8f50ca89 100644
--- a/backends/platform/dc/vmsave.cpp
+++ b/backends/platform/dc/vmsave.cpp
@@ -266,7 +266,7 @@ public:
{ return ::readSaveGame(buffer, _size, filename); }
};
-class OutVMSave : public Common::OutSaveFile {
+class OutVMSave : public Common::WriteStream {
private:
char *buffer;
int _pos, size, committed;
@@ -293,11 +293,24 @@ public:
class VMSaveManager : public Common::SaveFileManager {
public:
+ virtual void updateSavefilesList(Common::StringArray &lockedFiles) {
+ // TODO: implement this (locks files, preventing them from being listed, saved or loaded)
+ }
- virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) {
- OutVMSave *s = new OutVMSave(filename.c_str());
- return compress ? Common::wrapCompressedWriteStream(s) : s;
- }
+ virtual Common::InSaveFile *openRawFile(const Common::String &filename) {
+ InVMSave *s = new InVMSave();
+ if (s->readSaveGame(filename.c_str())) {
+ return s;
+ } else {
+ delete s;
+ return NULL;
+ }
+ }
+
+ virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) {
+ OutVMSave *s = new OutVMSave(filename.c_str());
+ return new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s);
+ }
virtual Common::InSaveFile *openForLoading(const Common::String &filename) {
InVMSave *s = new InVMSave();
diff --git a/backends/platform/ds/arm9/source/gbampsave.cpp b/backends/platform/ds/arm9/source/gbampsave.cpp
index ef6091e2a2..9991a3253a 100644
--- a/backends/platform/ds/arm9/source/gbampsave.cpp
+++ b/backends/platform/ds/arm9/source/gbampsave.cpp
@@ -45,6 +45,17 @@ static Common::String getSavePath() {
// GBAMP Save File Manager
//////////////////////////
+void GBAMPSaveFileManager::updateSavefilesList(Common::StringArray &lockedFiles) {
+ // TODO: implement this
+ // in this method manager should remember lockedFiles
+ // these files must not be opened for loading or saving, or listed by listSavefiles()
+}
+
+Common::InSaveFile *GBAMPSaveFileManager::openRawFile(const Common::String &filename) {
+ // TODO: make sure it returns raw file, not uncompressed save contents
+ return openForLoading(filename);
+}
+
Common::OutSaveFile *GBAMPSaveFileManager::openForSaving(const Common::String &filename, bool compress) {
Common::String fileSpec = getSavePath();
if (fileSpec.lastChar() != '/')
@@ -56,7 +67,7 @@ Common::OutSaveFile *GBAMPSaveFileManager::openForSaving(const Common::String &f
Common::WriteStream *stream = DS::DSFileStream::makeFromPath(fileSpec, true);
// Use a write buffer
stream = Common::wrapBufferedWriteStream(stream, SAVE_BUFFER_SIZE);
- return stream;
+ return new Common::OutSaveFile(stream);
}
Common::InSaveFile *GBAMPSaveFileManager::openForLoading(const Common::String &filename) {
diff --git a/backends/platform/ds/arm9/source/gbampsave.h b/backends/platform/ds/arm9/source/gbampsave.h
index d86db2ec70..d30e3ab177 100644
--- a/backends/platform/ds/arm9/source/gbampsave.h
+++ b/backends/platform/ds/arm9/source/gbampsave.h
@@ -27,6 +27,9 @@
class GBAMPSaveFileManager : public Common::SaveFileManager {
public:
+ virtual void updateSavefilesList(Common::StringArray &lockedFiles);
+ virtual Common::InSaveFile *openRawFile(const Common::String &filename);
+
virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true);
virtual Common::InSaveFile *openForLoading(const Common::String &filename);
diff --git a/backends/platform/n64/framfs_save_manager.h b/backends/platform/n64/framfs_save_manager.h
index 9bd4ee579e..24f9bf10ce 100644
--- a/backends/platform/n64/framfs_save_manager.h
+++ b/backends/platform/n64/framfs_save_manager.h
@@ -65,7 +65,7 @@ public:
}
};
-class OutFRAMSave : public Common::OutSaveFile {
+class OutFRAMSave : public Common::WriteStream {
private:
FRAMFILE *fd;
@@ -102,11 +102,26 @@ public:
class FRAMSaveManager : public Common::SaveFileManager {
public:
+ virtual void updateSavefilesList(Common::StringArray &lockedFiles) {
+ // this method is used to lock saves while cloud syncing
+ // as there is no network on N64, this method wouldn't be used
+ // thus it's not implemtented
+ }
+
+ virtual Common::InSaveFile *openRawFile(const Common::String &filename) {
+ InFRAMSave *s = new InFRAMSave();
+ if (s->readSaveGame(filename.c_str())) {
+ return s;
+ } else {
+ delete s;
+ return 0;
+ }
+ }
virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) {
OutFRAMSave *s = new OutFRAMSave(filename.c_str());
if (!s->err()) {
- return compress ? Common::wrapCompressedWriteStream(s) : s;
+ return new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s);
} else {
delete s;
return 0;
diff --git a/backends/platform/n64/pakfs_save_manager.h b/backends/platform/n64/pakfs_save_manager.h
index 0c08f0c506..8e16d1fce4 100644
--- a/backends/platform/n64/pakfs_save_manager.h
+++ b/backends/platform/n64/pakfs_save_manager.h
@@ -65,7 +65,7 @@ public:
}
};
-class OutPAKSave : public Common::OutSaveFile {
+class OutPAKSave : public Common::WriteStream {
private:
PAKFILE *fd;
@@ -104,11 +104,26 @@ public:
class PAKSaveManager : public Common::SaveFileManager {
public:
+ virtual void updateSavefilesList(Common::StringArray &lockedFiles) {
+ // this method is used to lock saves while cloud syncing
+ // as there is no network on N64, this method wouldn't be used
+ // thus it's not implemtented
+ }
+
+ virtual Common::InSaveFile *openRawFile(const Common::String &filename) {
+ InPAKSave *s = new InPAKSave();
+ if (s->readSaveGame(filename.c_str())) {
+ return s;
+ } else {
+ delete s;
+ return NULL;
+ }
+ }
virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true) {
OutPAKSave *s = new OutPAKSave(filename.c_str());
if (!s->err()) {
- return compress ? Common::wrapCompressedWriteStream(s) : s;
+ return new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(s) : s);
} else {
delete s;
return NULL;
diff --git a/backends/platform/ps2/savefilemgr.cpp b/backends/platform/ps2/savefilemgr.cpp
index 4fd2b1c72b..4cd988074e 100644
--- a/backends/platform/ps2/savefilemgr.cpp
+++ b/backends/platform/ps2/savefilemgr.cpp
@@ -82,7 +82,11 @@ void Ps2SaveFileManager::mcSplit(char *full, char *game, char *ext) {
// TODO
}
-Common::InSaveFile *Ps2SaveFileManager::openForLoading(const Common::String &filename) {
+void Ps2SaveFileManager::updateSavefilesList(Common::StringArray &lockedFiles) {
+ // TODO: implement this (locks files, preventing them from being listed, saved or loaded)
+}
+
+Common::InSaveFile *Ps2SaveFileManager::openRawFile(const Common::String &filename) {
Common::FSNode savePath(ConfMan.get("savepath")); // TODO: is this fast?
Common::SeekableReadStream *sf;
@@ -141,7 +145,12 @@ Common::InSaveFile *Ps2SaveFileManager::openForLoading(const Common::String &fil
// _screen->wantAnim(false);
- return Common::wrapCompressedReadStream(sf);
+ return sf;
+}
+
+Common::InSaveFile *Ps2SaveFileManager::openForLoading(const Common::String &filename) {
+ Common::SeekableReadStream *sf = openRawFile(filename);
+ return (sf == NULL ? NULL : Common::wrapCompressedReadStream(sf));
}
Common::OutSaveFile *Ps2SaveFileManager::openForSaving(const Common::String &filename, bool compress) {
@@ -192,7 +201,7 @@ Common::OutSaveFile *Ps2SaveFileManager::openForSaving(const Common::String &fil
}
_screen->wantAnim(false);
- return compress ? Common::wrapCompressedWriteStream(sf) : sf;
+ return new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf);
}
bool Ps2SaveFileManager::removeSavefile(const Common::String &filename) {
diff --git a/backends/platform/ps2/savefilemgr.h b/backends/platform/ps2/savefilemgr.h
index 547f16fa77..3d45382c64 100644
--- a/backends/platform/ps2/savefilemgr.h
+++ b/backends/platform/ps2/savefilemgr.h
@@ -34,6 +34,9 @@ public:
Ps2SaveFileManager(OSystem_PS2 *system, Gs2dScreen *screen);
virtual ~Ps2SaveFileManager();
+ virtual void updateSavefilesList(Common::StringArray &lockedFiles);
+ virtual Common::InSaveFile *openRawFile(const Common::String &filename);
+
virtual Common::InSaveFile *openForLoading(const Common::String &filename);
virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true);
virtual Common::StringArray listSavefiles(const Common::String &pattern);
diff --git a/backends/platform/sdl/macosx/macosx.cpp b/backends/platform/sdl/macosx/macosx.cpp
index 7652c0d833..c4e83dc9d3 100644
--- a/backends/platform/sdl/macosx/macosx.cpp
+++ b/backends/platform/sdl/macosx/macosx.cpp
@@ -33,6 +33,7 @@
#include "backends/platform/sdl/macosx/macosx.h"
#include "backends/updates/macosx/macosx-updates.h"
#include "backends/taskbar/macosx/macosx-taskbar.h"
+#include "backends/platform/sdl/macosx/macosx_wrapper.h"
#include "common/archive.h"
#include "common/config-manager.h"
@@ -106,7 +107,7 @@ void OSystem_MacOSX::addSysArchivesToSearchSet(Common::SearchSet &s, int priorit
}
bool OSystem_MacOSX::hasFeature(Feature f) {
- if (f == kFeatureDisplayLogFile)
+ if (f == kFeatureDisplayLogFile || f == kFeatureClipboardSupport)
return true;
return OSystem_POSIX::hasFeature(f);
}
@@ -124,6 +125,14 @@ bool OSystem_MacOSX::displayLogFile() {
return err != noErr;
}
+bool OSystem_MacOSX::hasTextInClipboard() {
+ return hasTextInClipboardMacOSX();
+}
+
+Common::String OSystem_MacOSX::getTextFromClipboard() {
+ return getTextFromClipboardMacOSX();
+}
+
Common::String OSystem_MacOSX::getSystemLanguage() const {
#if defined(USE_DETECTLANG) && defined(USE_TRANSLATION)
CFArrayRef availableLocalizations = CFBundleCopyBundleLocalizations(CFBundleGetMainBundle());
diff --git a/backends/platform/sdl/macosx/macosx.h b/backends/platform/sdl/macosx/macosx.h
index 6905284a5f..0c755cbf6d 100644
--- a/backends/platform/sdl/macosx/macosx.h
+++ b/backends/platform/sdl/macosx/macosx.h
@@ -33,6 +33,9 @@ public:
virtual bool displayLogFile();
+ virtual bool hasTextInClipboard();
+ virtual Common::String getTextFromClipboard();
+
virtual Common::String getSystemLanguage() const;
virtual void init();
diff --git a/backends/platform/sdl/macosx/macosx_wrapper.h b/backends/platform/sdl/macosx/macosx_wrapper.h
new file mode 100644
index 0000000000..3b346fc486
--- /dev/null
+++ b/backends/platform/sdl/macosx/macosx_wrapper.h
@@ -0,0 +1,31 @@
+/* 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 PLATFORM_SDL_MACOSX_WRAPPER_H
+#define PLATFORM_SDL_MACOSX_WRAPPER_H
+
+#include <common/str.h>
+
+bool hasTextInClipboardMacOSX();
+Common::String getTextFromClipboardMacOSX();
+
+#endif
diff --git a/backends/platform/sdl/macosx/macosx_wrapper.mm b/backends/platform/sdl/macosx/macosx_wrapper.mm
new file mode 100644
index 0000000000..8ec9eac5ac
--- /dev/null
+++ b/backends/platform/sdl/macosx/macosx_wrapper.mm
@@ -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.
+ *
+ */
+
+// Disable symbol overrides so that we can use system headers.
+#define FORBIDDEN_SYMBOL_ALLOW_ALL
+
+#include "backends/platform/sdl/macosx/macosx_wrapper.h"
+
+#include <AppKit/NSPasteboard.h>
+#include <Foundation/NSArray.h>
+
+bool hasTextInClipboardMacOSX() {
+ return [[NSPasteboard generalPasteboard] availableTypeFromArray:[NSArray arrayWithObject:NSStringPboardType]] != nil;
+}
+
+Common::String getTextFromClipboardMacOSX() {
+ if (!hasTextInClipboardMacOSX())
+ return Common::String();
+ // Note: on OS X 10.6 and above it is recommanded to use NSPasteboardTypeString rather than NSStringPboardType.
+ // But since we still target older version use NSStringPboardType.
+ NSPasteboard *pb = [NSPasteboard generalPasteboard];
+ NSString* str = [pb stringForType:NSStringPboardType];
+ if (str == nil)
+ return Common::String();
+ // If the string cannot be represented using the requested encoding we get a null pointer below.
+ // This is fine as ScummVM would not know what to do with non-ASCII characters (although maybe
+ // we should use NSISOLatin1StringEncoding?).
+ return Common::String([str cStringUsingEncoding:NSASCIIStringEncoding]);
+}
diff --git a/backends/platform/sdl/module.mk b/backends/platform/sdl/module.mk
index 74dd506d31..84ce272d3c 100644
--- a/backends/platform/sdl/module.mk
+++ b/backends/platform/sdl/module.mk
@@ -14,6 +14,7 @@ ifdef MACOSX
MODULE_OBJS += \
macosx/macosx-main.o \
macosx/macosx.o \
+ macosx/macosx_wrapper.o \
macosx/appmenu_osx.o
endif
diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp
index dca6891fef..6862bb349f 100644
--- a/backends/platform/sdl/sdl.cpp
+++ b/backends/platform/sdl/sdl.cpp
@@ -60,6 +60,15 @@
#endif // !WIN32
#endif
+#ifdef USE_SDL_NET
+#include <SDL/SDL_net.h>
+#endif
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_clipboard.h>
+#endif
+
OSystem_SDL::OSystem_SDL()
:
#ifdef USE_OPENGL
@@ -73,6 +82,9 @@ OSystem_SDL::OSystem_SDL()
#endif
_inited(false),
_initedSDL(false),
+#ifdef USE_SDL_NET
+ _initedSDLnet(false),
+#endif
_logger(0),
_mixerManager(0),
_eventSource(0),
@@ -120,6 +132,10 @@ OSystem_SDL::~OSystem_SDL() {
delete _logger;
_logger = 0;
+#ifdef USE_SDL_NET
+ if (_initedSDLnet) SDLNet_Quit();
+#endif
+
SDL_Quit();
}
@@ -160,6 +176,13 @@ void OSystem_SDL::init() {
}
+bool OSystem_SDL::hasFeature(Feature f) {
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ if (f == kFeatureClipboardSupport) return true;
+#endif
+ return ModularBackend::hasFeature(f);
+}
+
void OSystem_SDL::initBackend() {
// Check if backend has not been initialized
assert(!_inited);
@@ -294,6 +317,17 @@ void OSystem_SDL::initSDL() {
_initedSDL = true;
}
+
+#ifdef USE_SDL_NET
+ // Check if SDL_net has not been initialized
+ if (!_initedSDLnet) {
+ // Initialize SDL_net
+ if (SDLNet_Init() == -1)
+ error("Could not initialize SDL_net: %s", SDLNet_GetError());
+
+ _initedSDLnet = true;
+ }
+#endif
}
void OSystem_SDL::addSysArchivesToSearchSet(Common::SearchSet &s, int priority) {
@@ -431,6 +465,26 @@ Common::String OSystem_SDL::getSystemLanguage() const {
#endif // USE_DETECTLANG
}
+bool OSystem_SDL::hasTextInClipboard() {
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ return SDL_HasClipboardText() == SDL_TRUE;
+#else
+ return false;
+#endif
+}
+
+Common::String OSystem_SDL::getTextFromClipboard() {
+ if (!hasTextInClipboard()) return "";
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+ char *text = SDL_GetClipboardText();
+ if (text == nullptr) return "";
+ return text;
+#else
+ return "";
+#endif
+}
+
uint32 OSystem_SDL::getMillis(bool skipRecord) {
uint32 millis = SDL_GetTicks();
diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h
index 1fe670c5c3..17b4e9b001 100644
--- a/backends/platform/sdl/sdl.h
+++ b/backends/platform/sdl/sdl.h
@@ -55,6 +55,8 @@ public:
*/
virtual SdlMixerManager *getMixerManager();
+ virtual bool hasFeature(Feature f);
+
// Override functions from ModularBackend and OSystem
virtual void initBackend();
#if defined(USE_TASKBAR)
@@ -69,6 +71,10 @@ public:
virtual Common::String getSystemLanguage() const;
+ // Clipboard
+ virtual bool hasTextInClipboard();
+ virtual Common::String getTextFromClipboard();
+
virtual void setWindowCaption(const char *caption);
virtual void addSysArchivesToSearchSet(Common::SearchSet &s, int priority = 0);
virtual uint32 getMillis(bool skipRecord = false);
@@ -81,6 +87,9 @@ public:
protected:
bool _inited;
bool _initedSDL;
+#ifdef USE_SDL_NET
+ bool _initedSDLnet;
+#endif
/**
* Mixer manager that configures and setups SDL for
diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp
index daec36ae72..8a7fba46f7 100644
--- a/backends/saves/default/default-saves.cpp
+++ b/backends/saves/default/default-saves.cpp
@@ -27,6 +27,11 @@
#include "common/scummsys.h"
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#include "common/file.h"
+#endif
+
#if !defined(DISABLE_DEFAULT_SAVEFILEMANAGER)
#include "backends/saves/default/default-saves.h"
@@ -42,6 +47,10 @@
#include <errno.h> // for removeSavefile()
#endif
+#ifdef USE_LIBCURL
+const char *DefaultSaveFileManager::TIMESTAMPS_FILENAME = "timestamps";
+#endif
+
DefaultSaveFileManager::DefaultSaveFileManager() {
}
@@ -59,28 +68,62 @@ void DefaultSaveFileManager::checkPath(const Common::FSNode &dir) {
}
}
+void DefaultSaveFileManager::updateSavefilesList(Common::StringArray &lockedFiles) {
+ //make it refresh the cache next time it lists the saves
+ _cachedDirectory = "";
+
+ //remember the locked files list because some of these files don't exist yet
+ _lockedFiles = lockedFiles;
+}
+
Common::StringArray DefaultSaveFileManager::listSavefiles(const Common::String &pattern) {
// Assure the savefile name cache is up-to-date.
assureCached(getSavePath());
if (getError().getCode() != Common::kNoError)
return Common::StringArray();
+ Common::HashMap<Common::String, bool> locked;
+ for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) {
+ locked[*i] = true;
+ }
+
Common::StringArray results;
for (SaveFileCache::const_iterator file = _saveFileCache.begin(), end = _saveFileCache.end(); file != end; ++file) {
- if (file->_key.matchString(pattern, true)) {
+ if (!locked.contains(file->_key) && file->_key.matchString(pattern, true)) {
results.push_back(file->_key);
}
}
-
return results;
}
+Common::InSaveFile *DefaultSaveFileManager::openRawFile(const Common::String &filename) {
+ // Assure the savefile name cache is up-to-date.
+ assureCached(getSavePath());
+ if (getError().getCode() != Common::kNoError)
+ return nullptr;
+
+ SaveFileCache::const_iterator file = _saveFileCache.find(filename);
+ if (file == _saveFileCache.end()) {
+ return nullptr;
+ } else {
+ // Open the file for loading.
+ Common::SeekableReadStream *sf = file->_value.createReadStream();
+ return sf;
+ }
+}
+
Common::InSaveFile *DefaultSaveFileManager::openForLoading(const Common::String &filename) {
// Assure the savefile name cache is up-to-date.
assureCached(getSavePath());
if (getError().getCode() != Common::kNoError)
return nullptr;
+ for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) {
+ if (filename == *i) {
+ return nullptr; //file is locked, no loading available
+ }
+ }
+
SaveFileCache::const_iterator file = _saveFileCache.find(filename);
if (file == _saveFileCache.end()) {
return nullptr;
@@ -98,6 +141,19 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const Common::String
if (getError().getCode() != Common::kNoError)
return nullptr;
+ for (Common::StringArray::const_iterator i = _lockedFiles.begin(), end = _lockedFiles.end(); i != end; ++i) {
+ if (filename == *i) {
+ return nullptr; //file is locked, no saving available
+ }
+ }
+
+#ifdef USE_LIBCURL
+ // Update file's timestamp
+ Common::HashMap<Common::String, uint32> timestamps = loadTimestamps();
+ timestamps[filename] = INVALID_TIMESTAMP;
+ saveTimestamps(timestamps);
+#endif
+
// Obtain node.
SaveFileCache::const_iterator file = _saveFileCache.find(filename);
Common::FSNode fileNode;
@@ -112,7 +168,7 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const Common::String
// Open the file for saving.
Common::WriteStream *const sf = fileNode.createWriteStream();
- Common::OutSaveFile *const result = compress ? Common::wrapCompressedWriteStream(sf) : sf;
+ Common::OutSaveFile *const result = new Common::OutSaveFile(compress ? Common::wrapCompressedWriteStream(sf) : sf);
// Add file to cache now that it exists.
_saveFileCache[filename] = Common::FSNode(fileNode.getPath());
@@ -181,6 +237,12 @@ void DefaultSaveFileManager::assureCached(const Common::String &savePathName) {
// Check that path exists and is usable.
checkPath(Common::FSNode(savePathName));
+#ifdef USE_LIBCURL
+ Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing
+ if (!files.empty()) updateSavefilesList(files); //makes this cache invalid
+ else _lockedFiles = files;
+#endif
+
if (_cachedDirectory == savePathName) {
return;
}
@@ -216,4 +278,102 @@ void DefaultSaveFileManager::assureCached(const Common::String &savePathName) {
_cachedDirectory = savePathName;
}
+#ifdef USE_LIBCURL
+
+Common::HashMap<Common::String, uint32> DefaultSaveFileManager::loadTimestamps() {
+ Common::HashMap<Common::String, uint32> timestamps;
+
+ //refresh the files list
+ Common::Array<Common::String> files;
+ g_system->getSavefileManager()->updateSavefilesList(files);
+
+ //start with listing all the files in saves/ directory and setting invalid timestamp to them
+ Common::StringArray localFiles = g_system->getSavefileManager()->listSavefiles("*");
+ for (uint32 i = 0; i < localFiles.size(); ++i)
+ timestamps[localFiles[i]] = INVALID_TIMESTAMP;
+
+ //now actually load timestamps from file
+ Common::InSaveFile *file = g_system->getSavefileManager()->openRawFile(TIMESTAMPS_FILENAME);
+ if (!file) {
+ warning("DefaultSaveFileManager: failed to open '%s' file to load timestamps", TIMESTAMPS_FILENAME);
+ return timestamps;
+ }
+
+ while (!file->eos()) {
+ //read filename into buffer (reading until the first ' ')
+ Common::String buffer;
+ while (!file->eos()) {
+ byte b = file->readByte();
+ if (b == ' ') break;
+ buffer += (char)b;
+ }
+
+ //read timestamp info buffer (reading until ' ' or some line ending char)
+ Common::String filename = buffer;
+ while (true) {
+ bool lineEnded = false;
+ buffer = "";
+ while (!file->eos()) {
+ byte b = file->readByte();
+ if (b == ' ' || b == '\n' || b == '\r') {
+ lineEnded = (b == '\n');
+ break;
+ }
+ buffer += (char)b;
+ }
+
+ if (buffer == "" && file->eos()) break;
+ if (!lineEnded) filename += " " + buffer;
+ else break;
+ }
+
+ //parse timestamp
+ uint32 timestamp = buffer.asUint64();
+ if (buffer == "" || timestamp == 0) break;
+ timestamps[filename] = timestamp;
+ }
+
+ delete file;
+ return timestamps;
+}
+
+void DefaultSaveFileManager::saveTimestamps(Common::HashMap<Common::String, uint32> &timestamps) {
+ Common::DumpFile f;
+ Common::String filename = concatWithSavesPath(TIMESTAMPS_FILENAME);
+ if (!f.open(filename, true)) {
+ warning("DefaultSaveFileManager: failed to open '%s' file to save timestamps", filename.c_str());
+ return;
+ }
+
+ for (Common::HashMap<Common::String, uint32>::iterator i = timestamps.begin(); i != timestamps.end(); ++i) {
+ Common::String data = i->_key + Common::String::format(" %u\n", i->_value);
+ if (f.write(data.c_str(), data.size()) != data.size()) {
+ warning("DefaultSaveFileManager: failed to write timestamps data into '%s'", filename.c_str());
+ return;
+ }
+ }
+
+ f.flush();
+ f.finalize();
+ f.close();
+}
+
+Common::String DefaultSaveFileManager::concatWithSavesPath(Common::String name) {
+ DefaultSaveFileManager *manager = dynamic_cast<DefaultSaveFileManager *>(g_system->getSavefileManager());
+ Common::String path = (manager ? manager->getSavePath() : ConfMan.get("savepath"));
+ if (path.size() > 0 && (path.lastChar() == '/' || path.lastChar() == '\\'))
+ return path + name;
+
+ //simple heuristic to determine which path separator to use
+ int backslashes = 0;
+ for (uint32 i = 0; i < path.size(); ++i)
+ if (path[i] == '/') --backslashes;
+ else if (path[i] == '\\') ++backslashes;
+
+ if (backslashes > 0) return path + '\\' + name;
+ return path + '/' + name;
+}
+
+#endif // ifdef USE_LIBCURL
+
#endif // !defined(DISABLE_DEFAULT_SAVEFILEMANAGER)
diff --git a/backends/saves/default/default-saves.h b/backends/saves/default/default-saves.h
index bf4ca0229d..f2453810bf 100644
--- a/backends/saves/default/default-saves.h
+++ b/backends/saves/default/default-saves.h
@@ -27,7 +27,8 @@
#include "common/savefile.h"
#include "common/str.h"
#include "common/fs.h"
-#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include <limits.h>
/**
* Provides a default savefile manager implementation for common platforms.
@@ -37,11 +38,24 @@ public:
DefaultSaveFileManager();
DefaultSaveFileManager(const Common::String &defaultSavepath);
+ virtual void updateSavefilesList(Common::StringArray &lockedFiles);
virtual Common::StringArray listSavefiles(const Common::String &pattern);
+ virtual Common::InSaveFile *openRawFile(const Common::String &filename);
virtual Common::InSaveFile *openForLoading(const Common::String &filename);
virtual Common::OutSaveFile *openForSaving(const Common::String &filename, bool compress = true);
virtual bool removeSavefile(const Common::String &filename);
+#ifdef USE_LIBCURL
+
+ static const uint32 INVALID_TIMESTAMP = UINT_MAX;
+ static const char *TIMESTAMPS_FILENAME;
+
+ static Common::HashMap<Common::String, uint32> loadTimestamps();
+ static void saveTimestamps(Common::HashMap<Common::String, uint32> &timestamps);
+ static Common::String concatWithSavesPath(Common::String name);
+
+#endif
+
protected:
/**
* Get the path to the savegame directory.
@@ -74,6 +88,12 @@ protected:
*/
SaveFileCache _saveFileCache;
+ /**
+ * List of "locked" files. These cannot be used for saving/loading
+ * because CloudManager is downloading those.
+ */
+ Common::StringArray _lockedFiles;
+
private:
/**
* The currently cached directory.
diff --git a/backends/saves/savefile.cpp b/backends/saves/savefile.cpp
index b04c53d832..e21ea16ad8 100644
--- a/backends/saves/savefile.cpp
+++ b/backends/saves/savefile.cpp
@@ -23,9 +23,37 @@
#include "common/util.h"
#include "common/savefile.h"
#include "common/str.h"
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#endif
namespace Common {
+OutSaveFile::OutSaveFile(WriteStream *w): _wrapped(w) {}
+
+OutSaveFile::~OutSaveFile() {}
+
+bool OutSaveFile::err() const { return _wrapped->err(); }
+
+void OutSaveFile::clearErr() { _wrapped->clearErr(); }
+
+void OutSaveFile::finalize() {
+ _wrapped->finalize();
+#ifdef USE_LIBCURL
+ CloudMan.syncSaves();
+#endif
+}
+
+bool OutSaveFile::flush() { return _wrapped->flush(); }
+
+uint32 OutSaveFile::write(const void *dataPtr, uint32 dataSize) {
+ return _wrapped->write(dataPtr, dataSize);
+}
+
+int32 OutSaveFile::pos() const {
+ return _wrapped->pos();
+}
+
bool SaveFileManager::copySavefile(const String &oldFilename, const String &newFilename) {
InSaveFile *inFile = 0;
OutSaveFile *outFile = 0;
diff --git a/base/commandLine.cpp b/base/commandLine.cpp
index 2c24c018ee..c2b4ea765f 100644
--- a/base/commandLine.cpp
+++ b/base/commandLine.cpp
@@ -97,6 +97,7 @@ static const char HELP_STRING[] =
" -d, --debuglevel=NUM Set debug verbosity level\n"
" --debugflags=FLAGS Enable engine specific debug flags\n"
" (separated by commas)\n"
+ " --debug-channels-only Show only the specified debug channels\n"
" -u, --dump-scripts Enable script dumping if a directory called 'dumps'\n"
" exists in the current directory\n"
"\n"
@@ -426,6 +427,9 @@ Common::String parseCommandLine(Common::StringMap &settings, int argc, const cha
DO_LONG_OPTION("debugflags")
END_OPTION
+ DO_LONG_OPTION_BOOL("debug-channels-only")
+ END_OPTION
+
DO_OPTION('e', "music-driver")
END_OPTION
diff --git a/base/main.cpp b/base/main.cpp
index 349f719ed5..6b6868b78a 100644
--- a/base/main.cpp
+++ b/base/main.cpp
@@ -66,6 +66,13 @@
#endif
#include "backends/keymapper/keymapper.h"
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#include "backends/networking/curl/connectionmanager.h"
+#endif
+#ifdef USE_SDL_NET
+#include "backends/networking/sdl_net/localwebserver.h"
+#endif
#if defined(_WIN32_WCE)
#include "backends/platform/wince/CELauncherDialog.h"
@@ -391,6 +398,10 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
} else if (ConfMan.hasKey("debugflags"))
specialDebug = ConfMan.get("debugflags");
+ if (settings.contains("debug-channels-only"))
+ gDebugChannelsOnly = true;
+
+
PluginManager::instance().init();
PluginManager::instance().loadAllPlugins(); // load plugins for cached plugin manager
@@ -471,6 +482,11 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
dlg.runModal();
}
#endif
+
+#ifdef USE_LIBCURL
+ CloudMan.init();
+ CloudMan.syncSaves();
+#endif
// Unless a game was specified, show the launcher dialog
if (0 == ConfMan.getActiveDomain())
@@ -580,6 +596,14 @@ extern "C" int scummvm_main(int argc, const char * const argv[]) {
launcherDialog();
}
}
+#ifdef USE_SDL_NET
+ Networking::LocalWebserver::destroy();
+#endif
+#ifdef USE_LIBCURL
+ Networking::ConnectionManager::destroy();
+ //I think it's important to destroy it after ConnectionManager
+ Cloud::CloudManager::destroy();
+#endif
PluginManager::instance().unloadAllPlugins();
PluginManager::destroy();
GUI::GuiManager::destroy();
diff --git a/base/plugins.h b/base/plugins.h
index 6037fc2d71..2793ff3ffd 100644
--- a/base/plugins.h
+++ b/base/plugins.h
@@ -79,8 +79,12 @@ extern int pluginTypeVersions[PLUGIN_TYPE_MAX];
#define PLUGIN_ENABLED_STATIC(ID) \
(ENABLE_##ID && !PLUGIN_ENABLED_DYNAMIC(ID))
-#define PLUGIN_ENABLED_DYNAMIC(ID) \
- (ENABLE_##ID && (ENABLE_##ID == DYNAMIC_PLUGIN) && defined(DYNAMIC_MODULES))
+#ifdef DYNAMIC_MODULES
+ #define PLUGIN_ENABLED_DYNAMIC(ID) \
+ (ENABLE_##ID && (ENABLE_##ID == DYNAMIC_PLUGIN))
+#else
+ #define PLUGIN_ENABLED_DYNAMIC(ID) 0
+#endif
// see comments in backends/plugins/elf/elf-provider.cpp
#if defined(USE_ELF_LOADER) && defined(ELF_LOADER_CXA_ATEXIT)
diff --git a/base/version.cpp b/base/version.cpp
index 299e4ce325..d40dd573f0 100644
--- a/base/version.cpp
+++ b/base/version.cpp
@@ -154,4 +154,25 @@ const char *gScummVMFeatures = ""
#ifdef ENABLE_VKEYBD
"virtual keyboard "
#endif
+
+#ifdef USE_CLOUD
+ "cloud ("
+#ifdef USE_LIBCURL
+ "servers"
+#ifdef USE_SDL_NET
+ ", "
+#endif
+#endif
+#ifdef USE_SDL_NET
+ "local"
+#endif
+ ") "
+#else
+#ifdef USE_LIBCURL
+ "libcurl "
+#endif
+#ifdef USE_SDL_NET
+ "SDL_net "
+#endif
+#endif
;
diff --git a/common/callback.h b/common/callback.h
new file mode 100644
index 0000000000..c6c249a511
--- /dev/null
+++ b/common/callback.h
@@ -0,0 +1,138 @@
+/* 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 COMMON_CALLBACK_H
+#define COMMON_CALLBACK_H
+
+namespace Common {
+
+/**
+ * BaseCallback<S> is a simple base class for object-oriented callbacks.
+ *
+ * Object-oriented callbacks are such callbacks that know exact instance
+ * which method must be called.
+ *
+ * For backwards compatibility purposes, there is a GlobalFunctionCallback,
+ * which is BaseCallback<void *>, so it can be used with global C-like
+ * functions too.
+ *
+ * <S> is the type, which is passed to operator() of this callback.
+ * This allows you to specify that you accept a callback, which wants
+ * to receive an <S> object.
+ */
+template<typename S = void *> class BaseCallback {
+public:
+ BaseCallback() {}
+ virtual ~BaseCallback() {}
+ virtual void operator()(S data) = 0;
+};
+
+/**
+ * GlobalFunctionCallback<T> is a simple wrapper for global C functions.
+ *
+ * If there is a method, which accepts BaseCallback<T>, you can
+ * easily pass your C function by passing
+ * new GlobalFunctionCallback<T>(yourFunction)
+ */
+template<typename T> class GlobalFunctionCallback: public BaseCallback<T> {
+ typedef void(*GlobalFunction)(T result);
+ GlobalFunction _callback;
+
+public:
+ GlobalFunctionCallback(GlobalFunction cb): _callback(cb) {}
+ virtual ~GlobalFunctionCallback() {}
+ virtual void operator()(T data) {
+ if (_callback) _callback(data);
+ }
+};
+
+/**
+ * Callback<T, S> implements an object-oriented callback.
+ *
+ * <T> stands for a class which method you want to call.
+ * <S>, again, is the type of an object passed to operator().
+ *
+ * So, if you have void MyClass::myMethod(AnotherClass) method,
+ * the corresponding callback is Callback<MyClass, AnotherClass>.
+ * You create it similarly to this:
+ * new Callback<MyClass, AnotherClass>(
+ * pointerToMyClassObject,
+ * &MyClass::myMethod
+ * )
+ */
+template<class T, typename S = void *> class Callback: public BaseCallback<S> {
+protected:
+ typedef void(T::*TMethod)(S);
+ T *_object;
+ TMethod _method;
+public:
+ Callback(T *object, TMethod method): _object(object), _method(method) {}
+ virtual ~Callback() {}
+ void operator()(S data) { (_object->*_method)(data); }
+};
+
+/**
+ * CallbackBridge<T, OS, S> helps you to chain callbacks.
+ *
+ * CallbackBridge keeps a pointer to BaseCallback<OS>.
+ * When its operator() is called, it passes this pointer
+ * along with the actual data (of type <S>) to the method
+ * of <T> class.
+ *
+ * This is needed when you have to call a callback only
+ * when your own callback is called. So, your callback
+ * is "inner" and the other one is "outer".
+ *
+ * CallbackBridge implements the "inner" one and calls
+ * the method you wanted. It passes the "outer", so you
+ * can call it from your method. You can ignore it, but
+ * probably there is no point in using CallbackBridge then.
+ *
+ * So, if you receive a BaseCallback<SomeClass> callback
+ * and you want to call it from your MyClass::myMethod method,
+ * you should create CallbackBridge<MyClass, SomeClass, S>,
+ * where <S> is data type you want to receive in MyClass::myMethod.
+ *
+ * You create it similarly to this:
+ * new Callback<MyClass, SomeClass, AnotherClass>(
+ * pointerToMyClassObject,
+ * &MyClass::myMethod,
+ * outerCallback
+ * )
+ * where `outerCallback` is BaseCallback<SomeClass> and `myMethod` is:
+ * void MyClass::myMethod(BaseCallback<SomeClass> *, AnotherClass)
+ */
+template<class T, typename OS, typename S = void *> class CallbackBridge: public BaseCallback<S> {
+ typedef void(T::*TCallbackMethod)(BaseCallback<OS> *, S);
+ T *_object;
+ TCallbackMethod _method;
+ BaseCallback<OS> *_outerCallback;
+public:
+ CallbackBridge(T *object, TCallbackMethod method, BaseCallback<OS> *outerCallback):
+ _object(object), _method(method), _outerCallback(outerCallback) {}
+ virtual ~CallbackBridge() {}
+ void operator()(S data) { (_object->*_method)(_outerCallback, data); }
+};
+
+} // End of namespace Common
+
+#endif
diff --git a/common/config-manager.cpp b/common/config-manager.cpp
index 24c877a4e9..fdd0c6f033 100644
--- a/common/config-manager.cpp
+++ b/common/config-manager.cpp
@@ -45,6 +45,10 @@ char const *const ConfigManager::kTransientDomain = "__TRANSIENT";
char const *const ConfigManager::kKeymapperDomain = "keymapper";
#endif
+#ifdef USE_CLOUD
+char const *const ConfigManager::kCloudDomain = "cloud";
+#endif
+
#pragma mark -
@@ -67,6 +71,9 @@ void ConfigManager::copyFrom(ConfigManager &source) {
#ifdef ENABLE_KEYMAPPER
_keymapperDomain = source._keymapperDomain;
#endif
+#ifdef USE_CLOUD
+ _cloudDomain = source._cloudDomain;
+#endif
_domainSaveOrder = source._domainSaveOrder;
_activeDomainName = source._activeDomainName;
_activeDomain = &_gameDomains[_activeDomainName];
@@ -121,6 +128,10 @@ void ConfigManager::addDomain(const String &domainName, const ConfigManager::Dom
} else if (domainName == kKeymapperDomain) {
_keymapperDomain = domain;
#endif
+#ifdef USE_CLOUD
+ } else if (domainName == kCloudDomain) {
+ _cloudDomain = domain;
+#endif
} else if (domain.contains("gameid")) {
// If the domain contains "gameid" we assume it's a game domain
if (_gameDomains.contains(domainName))
@@ -160,6 +171,9 @@ void ConfigManager::loadFromStream(SeekableReadStream &stream) {
#ifdef ENABLE_KEYMAPPER
_keymapperDomain.clear();
#endif
+#ifdef USE_CLOUD
+ _cloudDomain.clear();
+#endif
// TODO: Detect if a domain occurs multiple times (or likewise, if
// a key occurs multiple times inside one domain).
@@ -272,6 +286,10 @@ void ConfigManager::flushToDisk() {
// Write the keymapper domain
writeDomain(*stream, kKeymapperDomain, _keymapperDomain);
#endif
+#ifdef USE_CLOUD
+ // Write the cloud domain
+ writeDomain(*stream, kCloudDomain, _cloudDomain);
+#endif
DomainMap::const_iterator d;
@@ -359,6 +377,10 @@ const ConfigManager::Domain *ConfigManager::getDomain(const String &domName) con
if (domName == kKeymapperDomain)
return &_keymapperDomain;
#endif
+#ifdef USE_CLOUD
+ if (domName == kCloudDomain)
+ return &_cloudDomain;
+#endif
if (_gameDomains.contains(domName))
return &_gameDomains[domName];
if (_miscDomains.contains(domName))
@@ -379,6 +401,10 @@ ConfigManager::Domain *ConfigManager::getDomain(const String &domName) {
if (domName == kKeymapperDomain)
return &_keymapperDomain;
#endif
+#ifdef USE_CLOUD
+ if (domName == kCloudDomain)
+ return &_cloudDomain;
+#endif
if (_gameDomains.contains(domName))
return &_gameDomains[domName];
if (_miscDomains.contains(domName))
diff --git a/common/config-manager.h b/common/config-manager.h
index 14f911f69d..669faaaf88 100644
--- a/common/config-manager.h
+++ b/common/config-manager.h
@@ -94,6 +94,11 @@ public:
static char const *const kKeymapperDomain;
#endif
+#ifdef USE_CLOUD
+ /** The name of cloud domain used to store user's tokens */
+ static char const *const kCloudDomain;
+#endif
+
void loadDefaultConfigFile();
void loadConfigFile(const String &filename);
@@ -188,6 +193,10 @@ private:
Domain _keymapperDomain;
#endif
+#ifdef USE_CLOUD
+ Domain _cloudDomain;
+#endif
+
Array<String> _domainSaveOrder;
String _activeDomainName;
diff --git a/common/debug.cpp b/common/debug.cpp
index 182b28afdf..c61fc63dea 100644
--- a/common/debug.cpp
+++ b/common/debug.cpp
@@ -30,6 +30,7 @@
// TODO: Move gDebugLevel into namespace Common.
int gDebugLevel = -1;
+bool gDebugChannelsOnly = false;
namespace Common {
@@ -119,6 +120,18 @@ bool DebugManager::isDebugChannelEnabled(uint32 channel) {
} // End of namespace Common
+bool debugLevelSet(int level) {
+ return level <= gDebugLevel;
+}
+
+bool debugChannelSet(int level, uint32 debugChannels) {
+ if (gDebugLevel != 11)
+ if (level > gDebugLevel || !(DebugMan.isDebugChannelEnabled(debugChannels)))
+ return false;
+
+ return true;
+}
+
#ifndef DISABLE_TEXT_CONSOLE
@@ -137,6 +150,9 @@ static void debugHelper(const char *s, va_list va, bool caret = true) {
void debug(const char *s, ...) {
va_list va;
+ if (gDebugChannelsOnly)
+ return;
+
va_start(va, s);
debugHelper(s, va);
va_end(va);
@@ -145,7 +161,7 @@ void debug(const char *s, ...) {
void debug(int level, const char *s, ...) {
va_list va;
- if (level > gDebugLevel)
+ if (level > gDebugLevel || gDebugChannelsOnly)
return;
va_start(va, s);
@@ -157,6 +173,9 @@ void debug(int level, const char *s, ...) {
void debugN(const char *s, ...) {
va_list va;
+ if (gDebugChannelsOnly)
+ return;
+
va_start(va, s);
debugHelper(s, va, false);
va_end(va);
@@ -165,7 +184,7 @@ void debugN(const char *s, ...) {
void debugN(int level, const char *s, ...) {
va_list va;
- if (level > gDebugLevel)
+ if (level > gDebugLevel || gDebugChannelsOnly)
return;
va_start(va, s);
diff --git a/common/debug.h b/common/debug.h
index b6e0679a12..883a0bf29d 100644
--- a/common/debug.h
+++ b/common/debug.h
@@ -31,11 +31,10 @@ inline void debug(const char *s, ...) {}
inline void debug(int level, const char *s, ...) {}
inline void debugN(const char *s, ...) {}
inline void debugN(int level, const char *s, ...) {}
-inline void debugC(int level, uint32 engineChannel, const char *s, ...) {}
-inline void debugC(uint32 engineChannel, const char *s, ...) {}
-inline void debugCN(int level, uint32 engineChannel, const char *s, ...) {}
-inline void debugCN(uint32 engineChannel, const char *s, ...) {}
-
+inline void debugC(int level, uint32 debugChannels, const char *s, ...) {}
+inline void debugC(uint32 debugChannels, const char *s, ...) {}
+inline void debugCN(int level, uint32 debugChannels, const char *s, ...) {}
+inline void debugCN(uint32 debugChannels, const char *s, ...) {}
#else
@@ -111,6 +110,18 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3);
#endif
/**
+ * Returns true if the debug level is set to the specified level
+ */
+bool debugLevelSet(int level);
+
+/**
+ * Returns true if the debug level and channel are active
+ *
+ * @see enableDebugChannel
+ */
+bool debugChannelSet(int level, uint32 debugChannels);
+
+/**
* The debug level. Initially set to -1, indicating that no debug output
* should be shown. Positive values usually imply an increasing number of
* debug output shall be generated, the higher the value, the more verbose the
@@ -118,6 +129,15 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3);
*/
extern int gDebugLevel;
+/**
+ * Specify if we want to show only the debug channels and suppress
+ * the non-channeled output.
+ *
+ * This option is useful when you want to have higher levels of channels
+ * visible without the noise from other subsystems or OSystem.
+ */
+extern bool gDebugChannelsOnly;
+
//Global constant for EventRecorder debug channel
enum GlobalDebugLevels {
kDebugLevelEventRec = 1 << 30
diff --git a/common/file.cpp b/common/file.cpp
index 4d9c630076..9797bcaa69 100644
--- a/common/file.cpp
+++ b/common/file.cpp
@@ -25,6 +25,8 @@
#include "common/file.h"
#include "common/fs.h"
#include "common/textconsole.h"
+#include "common/system.h"
+#include "backends/fs/fs-factory.h"
namespace Common {
@@ -149,10 +151,23 @@ DumpFile::~DumpFile() {
close();
}
-bool DumpFile::open(const String &filename) {
+bool DumpFile::open(const String &filename, bool createPath) {
assert(!filename.empty());
assert(!_handle);
+ if (createPath) {
+ for (uint32 i = 0; i < filename.size(); ++i) {
+ if (filename[i] == '/' || filename[i] == '\\') {
+ Common::String subpath = filename;
+ subpath.erase(i);
+ if (subpath.empty()) continue;
+ AbstractFSNode *node = g_system->getFilesystemFactory()->makeFileNodePath(subpath);
+ if (node->exists()) continue;
+ if (!node->create(true)) warning("DumpFile: unable to create directories from path prefix");
+ }
+ }
+ }
+
FSNode node(filename);
return open(node);
}
diff --git a/common/file.h b/common/file.h
index 3d174834e9..8ad6249d6d 100644
--- a/common/file.h
+++ b/common/file.h
@@ -143,7 +143,7 @@ public:
DumpFile();
virtual ~DumpFile();
- virtual bool open(const String &filename);
+ virtual bool open(const String &filename, bool createPath = false);
virtual bool open(const FSNode &node);
virtual void close();
diff --git a/common/gui_options.h b/common/gui_options.h
index ec3eccd161..d17f45cac1 100644
--- a/common/gui_options.h
+++ b/common/gui_options.h
@@ -68,7 +68,7 @@
#define GUIO_GAMEOPTIONS6 "\055"
#define GUIO_GAMEOPTIONS7 "\056"
#define GUIO_GAMEOPTIONS8 "\057"
-#define GUIO_GAMEOPTIONS9 "\058"
+#define GUIO_GAMEOPTIONS9 "\060"
#define GUIO0() (GUIO_NONE)
#define GUIO1(a) (a)
diff --git a/common/json.cpp b/common/json.cpp
new file mode 100644
index 0000000000..f198e30b76
--- /dev/null
+++ b/common/json.cpp
@@ -0,0 +1,1099 @@
+/* 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.
+*
+*/
+
+/*
+* Files JSON.cpp and JSONValue.cpp part of the SimpleJSON Library - https://github.com/MJPA/SimpleJSON
+*
+* Copyright (C) 2010 Mike Anchor
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+#include "common/json.h"
+
+#ifdef __MINGW32__
+#define wcsncasecmp wcsnicmp
+#endif
+
+// Macros to free an array/object
+#define FREE_ARRAY(x) { JSONArray::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete *iter; } }
+#define FREE_OBJECT(x) { JSONObject::iterator iter; for (iter = x.begin(); iter != x.end(); iter++) { delete (*iter)._value; } }
+
+namespace Common {
+
+/**
+* Blocks off the public constructor
+*
+* @access private
+*
+*/
+JSON::JSON() {}
+
+/**
+* Parses a complete JSON encoded string (UNICODE input version)
+*
+* @access public
+*
+* @param char* data The JSON text
+*
+* @return JSONValue* Returns a JSON Value representing the root, or NULL on error
+*/
+JSONValue *JSON::parse(const char *data) {
+ // Skip any preceding whitespace, end of data = no JSON = fail
+ if (!skipWhitespace(&data))
+ return NULL;
+
+ // We need the start of a value here now...
+ JSONValue *value = JSONValue::parse(&data);
+ if (value == NULL)
+ return NULL;
+
+ // Can be white space now and should be at the end of the string then...
+ if (skipWhitespace(&data)) {
+ delete value;
+ return NULL;
+ }
+
+ // We're now at the end of the string
+ return value;
+}
+
+/**
+* Turns the passed in JSONValue into a JSON encode string
+*
+* @access public
+*
+* @param JSONValue* value The root value
+*
+* @return String Returns a JSON encoded string representation of the given value
+*/
+String JSON::stringify(const JSONValue *value) {
+ if (value != NULL)
+ return value->stringify();
+ else
+ return "";
+}
+
+/**
+* Skips over any whitespace characters (space, tab, \r or \n) defined by the JSON spec
+*
+* @access protected
+*
+* @param char** data Pointer to a char* that contains the JSON text
+*
+* @return bool Returns true if there is more data, or false if the end of the text was reached
+*/
+bool JSON::skipWhitespace(const char **data) {
+ while (**data != 0 && (**data == ' ' || **data == '\t' || **data == '\r' || **data == '\n'))
+ (*data)++;
+
+ return **data != 0;
+}
+
+/**
+* Extracts a JSON String as defined by the spec - "<some chars>"
+* Any escaped characters are swapped out for their unescaped values
+*
+* @access protected
+*
+* @param char** data Pointer to a char* that contains the JSON text
+* @param String& str Reference to a String to receive the extracted string
+*
+* @return bool Returns true on success, false on failure
+*/
+bool JSON::extractString(const char **data, String &str) {
+ str = "";
+
+ while (**data != 0) {
+ // Save the char so we can change it if need be
+ char next_char = **data;
+
+ // Escaping something?
+ if (next_char == '\\') {
+ // Move over the escape char
+ (*data)++;
+
+ // Deal with the escaped char
+ switch (**data) {
+ case '"': next_char = '"';
+ break;
+ case '\\': next_char = '\\';
+ break;
+ case '/': next_char = '/';
+ break;
+ case 'b': next_char = '\b';
+ break;
+ case 'f': next_char = '\f';
+ break;
+ case 'n': next_char = '\n';
+ break;
+ case 'r': next_char = '\r';
+ break;
+ case 't': next_char = '\t';
+ break;
+ case 'u': {
+ // We need 5 chars (4 hex + the 'u') or its not valid
+ if (!simplejson_wcsnlen(*data, 5))
+ return false;
+
+ // Deal with the chars
+ next_char = 0;
+ for (int i = 0; i < 4; i++) {
+ // Do it first to move off the 'u' and leave us on the
+ // final hex digit as we move on by one later on
+ (*data)++;
+
+ next_char <<= 4;
+
+ // Parse the hex digit
+ if (**data >= '0' && **data <= '9')
+ next_char |= (**data - '0');
+ else if (**data >= 'A' && **data <= 'F')
+ next_char |= (10 + (**data - 'A'));
+ else if (**data >= 'a' && **data <= 'f')
+ next_char |= (10 + (**data - 'a'));
+ else {
+ // Invalid hex digit = invalid JSON
+ return false;
+ }
+ }
+ break;
+ }
+
+ // By the spec, only the above cases are allowed
+ default:
+ return false;
+ }
+ }
+
+ // End of the string?
+ else if (next_char == '"') {
+ (*data)++;
+ //str.reserve(); // Remove unused capacity //TODO
+ return true;
+ }
+
+ // Disallowed char?
+ else if (next_char < ' ' && next_char != '\t') {
+ // SPEC Violation: Allow tabs due to real world cases
+ return false;
+ }
+
+ // Add the next char
+ str += next_char;
+
+ // Move on
+ (*data)++;
+ }
+
+ // If we're here, the string ended incorrectly
+ return false;
+}
+
+/**
+* Parses some text as though it is an integer
+*
+* @access protected
+*
+* @param char** data Pointer to a char* that contains the JSON text
+*
+* @return double Returns the double value of the number found
+*/
+double JSON::parseInt(const char **data) {
+ double integer = 0;
+ while (**data != 0 && **data >= '0' && **data <= '9')
+ integer = integer * 10 + (*(*data)++ - '0');
+
+ return integer;
+}
+
+/**
+* Parses some text as though it is a decimal
+*
+* @access protected
+*
+* @param char** data Pointer to a char* that contains the JSON text
+*
+* @return double Returns the double value of the decimal found
+*/
+double JSON::parseDecimal(const char **data) {
+ double decimal = 0.0;
+ double factor = 0.1;
+ while (**data != 0 && **data >= '0' && **data <= '9') {
+ int digit = (*(*data)++ - '0');
+ decimal = decimal + digit * factor;
+ factor *= 0.1;
+ }
+ return decimal;
+}
+
+/**
+* Parses a JSON encoded value to a JSONValue object
+*
+* @access protected
+*
+* @param char** data Pointer to a char* that contains the data
+*
+* @return JSONValue* Returns a pointer to a JSONValue object on success, NULL on error
+*/
+JSONValue *JSONValue::parse(const char **data) {
+ // Is it a string?
+ if (**data == '"') {
+ String str;
+ if (!JSON::extractString(&(++(*data)), str))
+ return NULL;
+ else
+ return new JSONValue(str);
+ }
+
+ // Is it a boolean?
+ else if ((simplejson_wcsnlen(*data, 4) && scumm_strnicmp(*data, "true", 4) == 0) || (simplejson_wcsnlen(*data, 5) && scumm_strnicmp(*data, "false", 5) == 0)) {
+ bool value = scumm_strnicmp(*data, "true", 4) == 0;
+ (*data) += value ? 4 : 5;
+ return new JSONValue(value);
+ }
+
+ // Is it a null?
+ else if (simplejson_wcsnlen(*data, 4) && scumm_strnicmp(*data, "null", 4) == 0) {
+ (*data) += 4;
+ return new JSONValue();
+ }
+
+ // Is it a number?
+ else if (**data == '-' || (**data >= '0' && **data <= '9')) {
+ // Negative?
+ bool neg = **data == '-';
+ if (neg) (*data)++;
+
+ long long int integer = 0;
+ double number = 0.0;
+ bool onlyInteger = true;
+
+ // Parse the whole part of the number - only if it wasn't 0
+ if (**data == '0')
+ (*data)++;
+ else if (**data >= '1' && **data <= '9')
+ number = integer = JSON::parseInt(data);
+ else
+ return NULL;
+
+ // Could be a decimal now...
+ if (**data == '.') {
+ (*data)++;
+
+ // Not get any digits?
+ if (!(**data >= '0' && **data <= '9'))
+ return NULL;
+
+ // Find the decimal and sort the decimal place out
+ // Use ParseDecimal as ParseInt won't work with decimals less than 0.1
+ // thanks to Javier Abadia for the report & fix
+ double decimal = JSON::parseDecimal(data);
+
+ // Save the number
+ number += decimal;
+ onlyInteger = false;
+ }
+
+ // Could be an exponent now...
+ if (**data == 'E' || **data == 'e') {
+ (*data)++;
+
+ // Check signage of expo
+ bool neg_expo = false;
+ if (**data == '-' || **data == '+') {
+ neg_expo = **data == '-';
+ (*data)++;
+ }
+
+ // Not get any digits?
+ if (!(**data >= '0' && **data <= '9'))
+ return NULL;
+
+ // Sort the expo out
+ double expo = JSON::parseInt(data);
+ for (double i = 0.0; i < expo; i++)
+ number = neg_expo ? (number / 10.0) : (number * 10.0);
+ onlyInteger = false;
+ }
+
+ // Was it neg?
+ if (neg) number *= -1;
+
+ if (onlyInteger)
+ return new JSONValue(neg ? -integer : integer);
+
+ return new JSONValue(number);
+ }
+
+ // An object?
+ else if (**data == '{') {
+ JSONObject object;
+
+ (*data)++;
+
+ while (**data != 0) {
+ // Whitespace at the start?
+ if (!JSON::skipWhitespace(data)) {
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ // Special case - empty object
+ if (object.size() == 0 && **data == '}') {
+ (*data)++;
+ return new JSONValue(object);
+ }
+
+ // We want a string now...
+ String name;
+ if (!JSON::extractString(&(++(*data)), name)) {
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ // More whitespace?
+ if (!JSON::skipWhitespace(data)) {
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ // Need a : now
+ if (*((*data)++) != ':') {
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ // More whitespace?
+ if (!JSON::skipWhitespace(data)) {
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ // The value is here
+ JSONValue *value = parse(data);
+ if (value == NULL) {
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ // Add the name:value
+ if (object.find(name) != object.end())
+ delete object[name];
+ object[name] = value;
+
+ // More whitespace?
+ if (!JSON::skipWhitespace(data)) {
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ // End of object?
+ if (**data == '}') {
+ (*data)++;
+ return new JSONValue(object);
+ }
+
+ // Want a , now
+ if (**data != ',') {
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ (*data)++;
+ }
+
+ // Only here if we ran out of data
+ FREE_OBJECT(object);
+ return NULL;
+ }
+
+ // An array?
+ else if (**data == '[') {
+ JSONArray array;
+
+ (*data)++;
+
+ while (**data != 0) {
+ // Whitespace at the start?
+ if (!JSON::skipWhitespace(data)) {
+ FREE_ARRAY(array);
+ return NULL;
+ }
+
+ // Special case - empty array
+ if (array.size() == 0 && **data == ']') {
+ (*data)++;
+ return new JSONValue(array);
+ }
+
+ // Get the value
+ JSONValue *value = parse(data);
+ if (value == NULL) {
+ FREE_ARRAY(array);
+ return NULL;
+ }
+
+ // Add the value
+ array.push_back(value);
+
+ // More whitespace?
+ if (!JSON::skipWhitespace(data)) {
+ FREE_ARRAY(array);
+ return NULL;
+ }
+
+ // End of array?
+ if (**data == ']') {
+ (*data)++;
+ return new JSONValue(array);
+ }
+
+ // Want a , now
+ if (**data != ',') {
+ FREE_ARRAY(array);
+ return NULL;
+ }
+
+ (*data)++;
+ }
+
+ // Only here if we ran out of data
+ FREE_ARRAY(array);
+ return NULL;
+ }
+
+ // Ran out of possibilites, it's bad!
+ else {
+ return NULL;
+ }
+}
+
+/**
+* Basic constructor for creating a JSON Value of type NULL
+*
+* @access public
+*/
+JSONValue::JSONValue(/*NULL*/) {
+ _type = JSONType_Null;
+}
+
+/**
+* Basic constructor for creating a JSON Value of type String
+*
+* @access public
+*
+* @param char* m_char_value The string to use as the value
+*/
+JSONValue::JSONValue(const char *charValue) {
+ _type = JSONType_String;
+ _stringValue = new String(String(charValue));
+}
+
+/**
+* Basic constructor for creating a JSON Value of type String
+*
+* @access public
+*
+* @param String m_string_value The string to use as the value
+*/
+JSONValue::JSONValue(const String &stringValue) {
+ _type = JSONType_String;
+ _stringValue = new String(stringValue);
+}
+
+/**
+* Basic constructor for creating a JSON Value of type Bool
+*
+* @access public
+*
+* @param bool m_bool_value The bool to use as the value
+*/
+JSONValue::JSONValue(bool boolValue) {
+ _type = JSONType_Bool;
+ _boolValue = boolValue;
+}
+
+/**
+* Basic constructor for creating a JSON Value of type Number
+*
+* @access public
+*
+* @param double m_number_value The number to use as the value
+*/
+JSONValue::JSONValue(double numberValue) {
+ _type = JSONType_Number;
+ _numberValue = numberValue;
+}
+
+/**
+* Basic constructor for creating a JSON Value of type Number (Integer)
+*
+* @access public
+*
+* @param int numberValue The number to use as the value
+*/
+JSONValue::JSONValue(long long int numberValue) {
+ _type = JSONType_IntegerNumber;
+ _integerValue = numberValue;
+}
+
+/**
+* Basic constructor for creating a JSON Value of type Array
+*
+* @access public
+*
+* @param JSONArray m_array_value The JSONArray to use as the value
+*/
+JSONValue::JSONValue(const JSONArray &arrayValue) {
+ _type = JSONType_Array;
+ _arrayValue = new JSONArray(arrayValue);
+}
+
+/**
+* Basic constructor for creating a JSON Value of type Object
+*
+* @access public
+*
+* @param JSONObject m_object_value The JSONObject to use as the value
+*/
+JSONValue::JSONValue(const JSONObject &objectValue) {
+ _type = JSONType_Object;
+ _objectValue = new JSONObject(objectValue);
+}
+
+/**
+* Copy constructor to perform a deep copy of array / object values
+*
+* @access public
+*
+* @param JSONValue m_source The source JSONValue that is being copied
+*/
+JSONValue::JSONValue(const JSONValue &source) {
+ _type = source._type;
+
+ switch (_type) {
+ case JSONType_String:
+ _stringValue = new String(*source._stringValue);
+ break;
+
+ case JSONType_Bool:
+ _boolValue = source._boolValue;
+ break;
+
+ case JSONType_Number:
+ _numberValue = source._numberValue;
+ break;
+
+ case JSONType_IntegerNumber:
+ _integerValue = source._integerValue;
+ break;
+
+ case JSONType_Array: {
+ JSONArray source_array = *source._arrayValue;
+ JSONArray::iterator iter;
+ _arrayValue = new JSONArray();
+ for (iter = source_array.begin(); iter != source_array.end(); iter++)
+ _arrayValue->push_back(new JSONValue(**iter));
+ break;
+ }
+
+ case JSONType_Object: {
+ JSONObject source_object = *source._objectValue;
+ _objectValue = new JSONObject();
+ JSONObject::iterator iter;
+ for (iter = source_object.begin(); iter != source_object.end(); iter++) {
+ String name = (*iter)._key;
+ (*_objectValue)[name] = new JSONValue(*((*iter)._value));
+ }
+ break;
+ }
+
+ case JSONType_Null:
+ // Nothing to do.
+ break;
+ }
+}
+
+/**
+* The destructor for the JSON Value object
+* Handles deleting the objects in the array or the object value
+*
+* @access public
+*/
+JSONValue::~JSONValue() {
+ if (_type == JSONType_Array) {
+ JSONArray::iterator iter;
+ for (iter = _arrayValue->begin(); iter != _arrayValue->end(); iter++)
+ delete *iter;
+ delete _arrayValue;
+ } else if (_type == JSONType_Object) {
+ JSONObject::iterator iter;
+ for (iter = _objectValue->begin(); iter != _objectValue->end(); iter++) {
+ delete (*iter)._value;
+ }
+ delete _objectValue;
+ } else if (_type == JSONType_String) {
+ delete _stringValue;
+ }
+}
+
+/**
+* Checks if the value is a NULL
+*
+* @access public
+*
+* @return bool Returns true if it is a NULL value, false otherwise
+*/
+bool JSONValue::isNull() const {
+ return _type == JSONType_Null;
+}
+
+/**
+* Checks if the value is a String
+*
+* @access public
+*
+* @return bool Returns true if it is a String value, false otherwise
+*/
+bool JSONValue::isString() const {
+ return _type == JSONType_String;
+}
+
+/**
+* Checks if the value is a Bool
+*
+* @access public
+*
+* @return bool Returns true if it is a Bool value, false otherwise
+*/
+bool JSONValue::isBool() const {
+ return _type == JSONType_Bool;
+}
+
+/**
+* Checks if the value is a Number
+*
+* @access public
+*
+* @return bool Returns true if it is a Number value, false otherwise
+*/
+bool JSONValue::isNumber() const {
+ return _type == JSONType_Number;
+}
+
+/**
+* Checks if the value is an Integer
+*
+* @access public
+*
+* @return bool Returns true if it is an Integer value, false otherwise
+*/
+bool JSONValue::isIntegerNumber() const {
+ return _type == JSONType_IntegerNumber;
+}
+
+/**
+* Checks if the value is an Array
+*
+* @access public
+*
+* @return bool Returns true if it is an Array value, false otherwise
+*/
+bool JSONValue::isArray() const {
+ return _type == JSONType_Array;
+}
+
+/**
+* Checks if the value is an Object
+*
+* @access public
+*
+* @return bool Returns true if it is an Object value, false otherwise
+*/
+bool JSONValue::isObject() const {
+ return _type == JSONType_Object;
+}
+
+/**
+* Retrieves the String value of this JSONValue
+* Use isString() before using this method.
+*
+* @access public
+*
+* @return String Returns the string value
+*/
+const String &JSONValue::asString() const {
+ return (*_stringValue);
+}
+
+/**
+* Retrieves the Bool value of this JSONValue
+* Use isBool() before using this method.
+*
+* @access public
+*
+* @return bool Returns the bool value
+*/
+bool JSONValue::asBool() const {
+ return _boolValue;
+}
+
+/**
+* Retrieves the Number value of this JSONValue
+* Use isNumber() before using this method.
+*
+* @access public
+*
+* @return double Returns the number value
+*/
+double JSONValue::asNumber() const {
+ return _numberValue;
+}
+
+/**
+* Retrieves the Integer value of this JSONValue
+* Use isIntegerNumber() before using this method.
+*
+* @access public
+*
+* @return int Returns the number value
+*/
+long long int JSONValue::asIntegerNumber() const {
+ return _integerValue;
+}
+
+/**
+* Retrieves the Array value of this JSONValue
+* Use isArray() before using this method.
+*
+* @access public
+*
+* @return JSONArray Returns the array value
+*/
+const JSONArray &JSONValue::asArray() const {
+ return (*_arrayValue);
+}
+
+/**
+* Retrieves the Object value of this JSONValue
+* Use isObject() before using this method.
+*
+* @access public
+*
+* @return JSONObject Returns the object value
+*/
+const JSONObject &JSONValue::asObject() const {
+ return (*_objectValue);
+}
+
+/**
+* Retrieves the number of children of this JSONValue.
+* This number will be 0 or the actual number of children
+* if isArray() or isObject().
+*
+* @access public
+*
+* @return The number of children.
+*/
+std::size_t JSONValue::countChildren() const {
+ switch (_type) {
+ case JSONType_Array:
+ return _arrayValue->size();
+ case JSONType_Object:
+ return _objectValue->size();
+ default:
+ return 0;
+ }
+}
+
+/**
+* Checks if this JSONValue has a child at the given index.
+* Use isArray() before using this method.
+*
+* @access public
+*
+* @return bool Returns true if the array has a value at the given index.
+*/
+bool JSONValue::hasChild(std::size_t index) const {
+ if (_type == JSONType_Array) {
+ return index < _arrayValue->size();
+ } else {
+ return false;
+ }
+}
+
+/**
+* Retrieves the child of this JSONValue at the given index.
+* Use isArray() before using this method.
+*
+* @access public
+*
+* @return JSONValue* Returns JSONValue at the given index or NULL
+* if it doesn't exist.
+*/
+JSONValue *JSONValue::child(std::size_t index) {
+ if (index < _arrayValue->size()) {
+ return (*_arrayValue)[index];
+ } else {
+ return NULL;
+ }
+}
+
+/**
+* Checks if this JSONValue has a child at the given key.
+* Use isObject() before using this method.
+*
+* @access public
+*
+* @return bool Returns true if the object has a value at the given key.
+*/
+bool JSONValue::hasChild(const char *name) const {
+ if (_type == JSONType_Object) {
+ return _objectValue->find(name) != _objectValue->end();
+ } else {
+ return false;
+ }
+}
+
+/**
+* Retrieves the child of this JSONValue at the given key.
+* Use isObject() before using this method.
+*
+* @access public
+*
+* @return JSONValue* Returns JSONValue for the given key in the object
+* or NULL if it doesn't exist.
+*/
+JSONValue *JSONValue::child(const char *name) {
+ JSONObject::const_iterator it = _objectValue->find(name);
+ if (it != _objectValue->end()) {
+ return it->_value;
+ } else {
+ return NULL;
+ }
+}
+
+/**
+* Retrieves the keys of the JSON Object or an empty vector
+* if this value is not an object.
+*
+* @access public
+*
+* @return std::vector<String> A vector containing the keys.
+*/
+Array<String> JSONValue::objectKeys() const {
+ Array<String> keys;
+
+ if (_type == JSONType_Object) {
+ JSONObject::const_iterator iter = _objectValue->begin();
+ while (iter != _objectValue->end()) {
+ keys.push_back(iter->_key);
+
+ iter++;
+ }
+ }
+
+ return keys;
+}
+
+/**
+* Creates a JSON encoded string for the value with all necessary characters escaped
+*
+* @access public
+*
+* @param bool prettyprint Enable prettyprint
+*
+* @return String Returns the JSON string
+*/
+String JSONValue::stringify(bool const prettyprint) const {
+ size_t const indentDepth = prettyprint ? 1 : 0;
+ return stringifyImpl(indentDepth);
+}
+
+
+/**
+* Creates a JSON encoded string for the value with all necessary characters escaped
+*
+* @access private
+*
+* @param size_t indentDepth The prettyprint indentation depth (0 : no prettyprint)
+*
+* @return String Returns the JSON string
+*/
+String JSONValue::stringifyImpl(size_t const indentDepth) const {
+ String ret_string;
+ size_t const indentDepth1 = indentDepth ? indentDepth + 1 : 0;
+ String const indentStr = indent(indentDepth);
+ String const indentStr1 = indent(indentDepth1);
+
+ switch (_type) {
+ case JSONType_Null:
+ ret_string = "null";
+ break;
+
+ case JSONType_String:
+ ret_string = stringifyString(*_stringValue);
+ break;
+
+ case JSONType_Bool:
+ ret_string = _boolValue ? "true" : "false";
+ break;
+
+ case JSONType_Number: {
+ if (isinf(_numberValue) || isnan(_numberValue))
+ ret_string = "null";
+ else {
+ char str[80];
+ sprintf(str, "%lg", _numberValue);
+ ret_string = str;
+ }
+ break;
+ }
+
+ case JSONType_IntegerNumber: {
+ char str[80];
+ sprintf(str, "%lld", _integerValue);
+ ret_string = str;
+ break;
+ }
+
+ case JSONType_Array: {
+ ret_string = indentDepth ? "[\n" + indentStr1 : "[";
+ JSONArray::const_iterator iter = _arrayValue->begin();
+ while (iter != _arrayValue->end()) {
+ ret_string += (*iter)->stringifyImpl(indentDepth1);
+
+ // Not at the end - add a separator
+ if (++iter != _arrayValue->end())
+ ret_string += ",";
+ }
+ ret_string += indentDepth ? "\n" + indentStr + "]" : "]";
+ break;
+ }
+
+ case JSONType_Object: {
+ ret_string = indentDepth ? "{\n" + indentStr1 : "{";
+ JSONObject::const_iterator iter = _objectValue->begin();
+ while (iter != _objectValue->end()) {
+ ret_string += stringifyString((*iter)._key);
+ ret_string += ":";
+ ret_string += (*iter)._value->stringifyImpl(indentDepth1);
+
+ // Not at the end - add a separator
+ if (++iter != _objectValue->end())
+ ret_string += ",";
+ }
+ ret_string += indentDepth ? "\n" + indentStr + "}" : "}";
+ break;
+ }
+ }
+
+ return ret_string;
+}
+
+/**
+* Creates a JSON encoded string with all required fields escaped
+* Works from http://www.ecma-internationl.org/publications/files/ECMA-ST/ECMA-262.pdf
+* Section 15.12.3.
+*
+* @access private
+*
+* @param String str The string that needs to have the characters escaped
+*
+* @return String Returns the JSON string
+*/
+String JSONValue::stringifyString(const String &str) {
+ String str_out = "\"";
+
+ String::const_iterator iter = str.begin();
+ while (iter != str.end()) {
+ char chr = *iter;
+
+ if (chr == '"' || chr == '\\' || chr == '/') {
+ str_out += '\\';
+ str_out += chr;
+ } else if (chr == '\b') {
+ str_out += "\\b";
+ } else if (chr == '\f') {
+ str_out += "\\f";
+ } else if (chr == '\n') {
+ str_out += "\\n";
+ } else if (chr == '\r') {
+ str_out += "\\r";
+ } else if (chr == '\t') {
+ str_out += "\\t";
+ } else if (chr < ' ' || chr > 126) {
+ str_out += "\\u";
+ for (int i = 0; i < 4; i++) {
+ int value = (chr >> 12) & 0xf;
+ if (value >= 0 && value <= 9)
+ str_out += (char)('0' + value);
+ else if (value >= 10 && value <= 15)
+ str_out += (char)('A' + (value - 10));
+ chr <<= 4;
+ }
+ } else {
+ str_out += chr;
+ }
+
+ iter++;
+ }
+
+ str_out += "\"";
+ return str_out;
+}
+
+/**
+* Creates the indentation string for the depth given
+*
+* @access private
+*
+* @param size_t indent The prettyprint indentation depth (0 : no indentation)
+*
+* @return String Returns the string
+*/
+String JSONValue::indent(size_t depth) {
+ const size_t indent_step = 2;
+ depth ? --depth : 0;
+ String indentStr;
+ for (size_t i = 0; i < depth * indent_step; ++i) indentStr += ' ';
+ return indentStr;
+}
+
+} // End of namespace Common
diff --git a/common/json.h b/common/json.h
new file mode 100644
index 0000000000..8e2eade669
--- /dev/null
+++ b/common/json.h
@@ -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.
+*
+*/
+
+/*
+* Files JSON.h and JSONValue.h part of the SimpleJSON Library - https://github.com/MJPA/SimpleJSON
+*
+* Copyright (C) 2010 Mike Anchor
+*
+* Permission is hereby granted, free of charge, to any person obtaining a copy
+* of this software and associated documentation files (the "Software"), to deal
+* in the Software without restriction, including without limitation the rights
+* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the Software is
+* furnished to do so, subject to the following conditions:
+*
+* The above copyright notice and this permission notice shall be included in
+* all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+* THE SOFTWARE.
+*/
+
+#ifndef COMMON_JSON_H
+#define COMMON_JSON_H
+
+#include "common/array.h"
+#include "common/hashmap.h"
+#include "common/hash-str.h"
+#include "common/str.h"
+
+// Win32 incompatibilities
+#if defined(WIN32) && !defined(__GNUC__)
+static inline bool isnan(double x) {
+ return x != x;
+}
+
+static inline bool isinf(double x) {
+ return !isnan(x) && isnan(x - x);
+}
+#endif
+
+// Simple function to check a string 's' has at least 'n' characters
+static inline bool simplejson_wcsnlen(const char *s, size_t n) {
+ if (s == 0)
+ return false;
+
+ const char *save = s;
+ while (n-- > 0) {
+ if (*(save++) == 0) return false;
+ }
+
+ return true;
+}
+
+namespace Common {
+
+// Custom types
+class JSONValue;
+typedef Array<JSONValue*> JSONArray;
+typedef HashMap<String, JSONValue*> JSONObject;
+
+class JSON;
+
+enum JSONType { JSONType_Null, JSONType_String, JSONType_Bool, JSONType_Number, JSONType_IntegerNumber, JSONType_Array, JSONType_Object };
+
+class JSONValue {
+ friend class JSON;
+
+public:
+ JSONValue(/*NULL*/);
+ JSONValue(const char *charValue);
+ JSONValue(const String &stringValue);
+ JSONValue(bool boolValue);
+ JSONValue(double numberValue);
+ JSONValue(long long int numberValue);
+ JSONValue(const JSONArray &arrayValue);
+ JSONValue(const JSONObject &objectValue);
+ JSONValue(const JSONValue &source);
+ ~JSONValue();
+
+ bool isNull() const;
+ bool isString() const;
+ bool isBool() const;
+ bool isNumber() const;
+ bool isIntegerNumber() const;
+ bool isArray() const;
+ bool isObject() const;
+
+ const String &asString() const;
+ bool asBool() const;
+ double asNumber() const;
+ long long int asIntegerNumber() const;
+ const JSONArray &asArray() const;
+ const JSONObject &asObject() const;
+
+ size_t countChildren() const;
+ bool hasChild(size_t index) const;
+ JSONValue *child(size_t index);
+ bool hasChild(const char *name) const;
+ JSONValue *child(const char *name);
+ Array<String> objectKeys() const;
+
+ String stringify(bool const prettyprint = false) const;
+protected:
+ static JSONValue *parse(const char **data);
+
+private:
+ static String stringifyString(const String &str);
+ String stringifyImpl(size_t const indentDepth) const;
+ static String indent(size_t depth);
+
+ JSONType _type;
+
+ union {
+ bool _boolValue;
+ double _numberValue;
+ long long int _integerValue;
+ String *_stringValue;
+ JSONArray *_arrayValue;
+ JSONObject *_objectValue;
+ };
+
+};
+
+class JSON {
+ friend class JSONValue;
+
+public:
+ static JSONValue *parse(const char *data);
+ static String stringify(const JSONValue *value);
+protected:
+ static bool skipWhitespace(const char **data);
+ static bool extractString(const char **data, String &str);
+ static double parseInt(const char **data);
+ static double parseDecimal(const char **data);
+private:
+ JSON();
+};
+
+} // End of namespace Common
+
+#endif
diff --git a/common/memstream.h b/common/memstream.h
index 59d5f15b1a..25fdde91c7 100644
--- a/common/memstream.h
+++ b/common/memstream.h
@@ -209,6 +209,89 @@ public:
bool seek(int32 offset, int whence = SEEK_SET);
};
+/**
+* MemoryStream based on RingBuffer. Grows if has insufficient buffer size.
+*/
+class MemoryReadWriteStream : public WriteStream {
+private:
+ uint32 _capacity;
+ uint32 _size;
+ byte *_data;
+ uint32 _writePos, _readPos, _pos, _length;
+ DisposeAfterUse::Flag _disposeMemory;
+
+ void ensureCapacity(uint32 new_len) {
+ if (new_len <= _capacity)
+ return;
+
+ byte *old_data = _data;
+ uint32 oldCapacity = _capacity;
+
+ _capacity = MAX(new_len + 32, _capacity * 2);
+ _data = (byte *)malloc(_capacity);
+
+ if (old_data) {
+ // Copy old data
+ if (_readPos < _writePos) {
+ memcpy(_data, old_data + _readPos, _writePos - _readPos);
+ _writePos = _length;
+ _readPos = 0;
+ } else {
+ memcpy(_data, old_data + _readPos, oldCapacity - _readPos);
+ memcpy(_data + oldCapacity - _readPos, old_data, _writePos);
+ _writePos = _length;
+ _readPos = 0;
+ }
+ free(old_data);
+ }
+ }
+public:
+ MemoryReadWriteStream(DisposeAfterUse::Flag disposeMemory = DisposeAfterUse::NO) : _capacity(0), _size(0), _data(0), _writePos(0), _readPos(0), _pos(0), _length(0), _disposeMemory(disposeMemory) {}
+
+ ~MemoryReadWriteStream() {
+ if (_disposeMemory)
+ free(_data);
+ }
+
+ uint32 write(const void *dataPtr, uint32 dataSize) {
+ ensureCapacity(_length + dataSize);
+ if (_writePos + dataSize < _capacity) {
+ memcpy(_data + _writePos, dataPtr, dataSize);
+ } else {
+ memcpy(_data + _writePos, dataPtr, _capacity - _writePos);
+ const byte *shiftedPtr = (const byte *)dataPtr + _capacity - _writePos;
+ memcpy(_data, shiftedPtr, dataSize - (_capacity - _writePos));
+ }
+ _writePos = (_writePos + dataSize) % _capacity;
+ _pos += dataSize;
+ _length += dataSize;
+ if (_pos > _size)
+ _size = _pos;
+ return dataSize;
+ }
+
+ virtual uint32 read(void *dataPtr, uint32 dataSize) {
+ uint32 length = _length;
+ if (length < dataSize) dataSize = length;
+ if (dataSize == 0 || _capacity == 0) return 0;
+ if (_readPos + dataSize < _capacity) {
+ memcpy(dataPtr, _data + _readPos, dataSize);
+ } else {
+ memcpy(dataPtr, _data + _readPos, _capacity - _readPos);
+ byte *shiftedPtr = (byte *)dataPtr + _capacity - _readPos;
+ memcpy(shiftedPtr, _data, dataSize - (_capacity - _readPos));
+ }
+ _readPos = (_readPos + dataSize) % _capacity;
+ _length -= dataSize;
+ return dataSize;
+ }
+
+ int32 pos() const { return _pos - _length; } //'read' position in the stream
+ uint32 size() const { return _size; } //that's also 'write' position in the stream, as it's append-only
+
+ byte *getData() { return _data; }
+};
+
} // End of namespace Common
#endif
diff --git a/common/module.mk b/common/module.mk
index 570040c8e1..54aa16f557 100644
--- a/common/module.mk
+++ b/common/module.mk
@@ -16,6 +16,7 @@ MODULE_OBJS := \
iff_container.o \
ini-file.o \
installshield_cab.o \
+ json.o \
language.o \
localization.o \
macresman.o \
diff --git a/common/savefile.h b/common/savefile.h
index 9fca07f9d5..eb7e6f946e 100644
--- a/common/savefile.h
+++ b/common/savefile.h
@@ -28,6 +28,7 @@
#include "common/stream.h"
#include "common/str-array.h"
#include "common/error.h"
+#include "common/ptr.h"
namespace Common {
@@ -44,8 +45,21 @@ typedef SeekableReadStream InSaveFile;
* That typically means "save games", but also includes things like the
* IQ points in Indy3.
*/
-typedef WriteStream OutSaveFile;
+class OutSaveFile: public WriteStream {
+protected:
+ ScopedPtr<WriteStream> _wrapped;
+public:
+ OutSaveFile(WriteStream *w);
+ virtual ~OutSaveFile();
+
+ virtual bool err() const;
+ virtual void clearErr();
+ virtual void finalize();
+ virtual bool flush();
+ virtual uint32 write(const void *dataPtr, uint32 dataSize);
+ virtual int32 pos() const;
+};
/**
* The SaveFileManager is serving as a factory for InSaveFile
@@ -137,6 +151,15 @@ public:
virtual InSaveFile *openForLoading(const String &name) = 0;
/**
+ * Open the file with the specified name in the given directory for loading.
+ * In contrast to openForLoading(), it returns raw file instead of unpacked.
+ *
+ * @param name The name of the savefile.
+ * @return Pointer to an InSaveFile, or NULL if an error occurred.
+ */
+ virtual InSaveFile *openRawFile(const String &name) = 0;
+
+ /**
* Removes the given savefile from the system.
*
* @param name The name of the savefile to be removed.
@@ -174,6 +197,13 @@ public:
* @see Common::matchString()
*/
virtual StringArray listSavefiles(const String &pattern) = 0;
+
+ /**
+ * Refreshes the save files list (because some new files could've been added)
+ * and remembers the "locked" files list. These files could not be used
+ * for saving or loading because they are being synced by CloudManager.
+ */
+ virtual void updateSavefilesList(StringArray &lockedFiles) = 0;
};
} // End of namespace Common
diff --git a/common/scummsys.h b/common/scummsys.h
index 5e1069fb46..959c67a404 100644
--- a/common/scummsys.h
+++ b/common/scummsys.h
@@ -29,7 +29,11 @@
// This is a convenience macro to test whether the compiler used is a GCC
// version, which is at least major.minor.
-#define GCC_ATLEAST(major, minor) (defined(__GNUC__) && (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor))))
+#ifdef __GNUC__
+ #define GCC_ATLEAST(major, minor) (__GNUC__ > (major) || (__GNUC__ == (major) && __GNUC_MINOR__ >= (minor)))
+#else
+ #define GCC_ATLEAST(major, minor) 0
+#endif
#if defined(_WIN32_WCE) && _WIN32_WCE < 300
#define NONSTANDARD_PORT
diff --git a/common/str.cpp b/common/str.cpp
index 3ff49a90c6..90bd539790 100644
--- a/common/str.cpp
+++ b/common/str.cpp
@@ -335,6 +335,15 @@ bool String::contains(char x) const {
return strchr(c_str(), x) != NULL;
}
+uint64 String::asUint64() const {
+ uint64 result = 0;
+ for (uint32 i = 0; i < _size; ++i) {
+ if (_str[i] < '0' || _str[i] > '9') break;
+ result = result * 10L + (_str[i] - '0');
+ }
+ return result;
+}
+
bool String::matchString(const char *pat, bool ignoreCase, bool pathMode) const {
return Common::matchString(c_str(), pat, ignoreCase, pathMode);
}
@@ -829,6 +838,15 @@ bool matchString(const char *str, const char *pat, bool ignoreCase, bool pathMod
}
}
+void replace(Common::String &source, const Common::String &what, const Common::String &with) {
+ const char *cstr = source.c_str();
+ const char *position = strstr(cstr, what.c_str());
+ if (position) {
+ uint32 index = position - cstr;
+ source.replace(index, what.size(), with);
+ }
+}
+
String tag2string(uint32 tag) {
char str[5];
str[0] = (char)(tag >> 24);
diff --git a/common/str.h b/common/str.h
index 9ada8aaaa0..d55ba072a9 100644
--- a/common/str.h
+++ b/common/str.h
@@ -162,6 +162,9 @@ public:
bool contains(const char *x) const;
bool contains(char x) const;
+ /** Return uint64 corrensponding to String's contents. */
+ uint64 asUint64() const;
+
/**
* Simple DOS-style pattern matching function (understands * and ? like used in DOS).
* Taken from exult/files/listfiles.cc
@@ -233,12 +236,12 @@ public:
void trim();
uint hash() const;
-
+
/**@{
* Functions to replace some amount of chars with chars from some other string.
*
* @note The implementation follows that of the STL's std::string:
- * http://www.cplusplus.com/reference/string/string/replace/
+ * http://www.cplusplus.com/reference/string/string/replace/
*
* @param pos Starting position for the replace in the original string.
* @param count Number of chars to replace from the original string.
@@ -247,7 +250,7 @@ public:
* @param countOri Same as count
* @param posDest Initial position to read str from.
* @param countDest Number of chars to read from str. npos by default.
- */
+ */
// Replace 'count' bytes, starting from 'pos' with str.
void replace(uint32 pos, uint32 count, const String &str);
// The same as above, but accepts a C-like array of characters.
@@ -264,7 +267,7 @@ public:
// str[posDest, posDest + countDest)
void replace(uint32 posOri, uint32 countOri, const char *str,
uint32 posDest, uint32 countDest);
- /**@}*/
+ /**@}*/
/**
* Print formatted data into a String object. Similar to sprintf,
@@ -387,6 +390,15 @@ String normalizePath(const String &path, const char sep);
*/
bool matchString(const char *str, const char *pat, bool ignoreCase = false, bool pathMode = false);
+/**
+ * Function which replaces substring with the other. It happens in place.
+ * If there is no substring found, original string is not changed.
+ *
+ * @param source String to search and replace substring in.
+ * @param what Substring to replace.
+ * @param with String to replace with.
+ */
+void replace(Common::String &source, const Common::String &what, const Common::String &with);
/**
* Take a 32 bit value and turn it into a four character string, where each of
diff --git a/common/system.h b/common/system.h
index 6d185d3075..805eba68ed 100644
--- a/common/system.h
+++ b/common/system.h
@@ -314,7 +314,15 @@ public:
*
* This feature has no associated state.
*/
- kFeatureDisplayLogFile
+ kFeatureDisplayLogFile,
+
+ /**
+ * The presence of this feature indicates whether the hasTextInClipboard()
+ * and getTextFromClipboard() calls are supported.
+ *
+ * This feature has no associated state.
+ */
+ kFeatureClipboardSupport
};
/**
@@ -1086,6 +1094,45 @@ public:
virtual void displayMessageOnOSD(const char *msg) = 0;
/**
+ * Blit a bitmap to the 'on screen display'.
+ *
+ * If the current pixel format has one byte per pixel, the graphics data
+ * uses 8 bits per pixel, using the palette specified via setPalette.
+ * If more than one byte per pixel is in use, the graphics data uses the
+ * pixel format returned by getScreenFormat.
+ *
+ * @param buf the buffer containing the graphics data source
+ * @param pitch the pitch of the buffer (number of bytes in a scanline)
+ * @param x the x coordinate of the destination rectangle
+ * @param y the y coordinate of the destination rectangle
+ * @param w the width of the destination rectangle
+ * @param h the height of the destination rectangle
+ *
+ * @note The specified destination rectangle must be completly contained
+ * in the visible screen space, and must be non-empty. If not, a
+ * backend may or may not perform clipping, trigger an assert or
+ * silently corrupt memory.
+ *
+ * @see updateScreen
+ * @see getScreenFormat
+ * @see copyRectToScreen
+ */
+
+ virtual void copyRectToOSD(const void *buf, int pitch, int x, int y, int w, int h) = 0;
+
+ /**
+ * Clears 'on screen display' from everything drawn on it.
+ */
+
+ virtual void clearOSD() = 0;
+
+ /**
+ * Returns 'on screen display' pixel format.
+ */
+
+ virtual Graphics::PixelFormat getOSDFormat() = 0;
+
+ /**
* Return the SaveFileManager, used to store and load savestates
* and other modifiable persistent game data. For more information,
* refer to the SaveFileManager documentation.
@@ -1200,6 +1247,28 @@ public:
virtual bool displayLogFile() { return false; }
/**
+ * Returns whether there is text available in the clipboard.
+ *
+ * The kFeatureClipboardSupport feature flag can be used to
+ * test whether this call has been implemented by the active
+ * backend.
+ *
+ * @return true if there is text in the clipboard, false otherwise
+ */
+ virtual bool hasTextInClipboard() { return false; }
+
+ /**
+ * Returns clipboard contents as a String.
+ *
+ * The kFeatureClipboardSupport feature flag can be used to
+ * test whether this call has been implemented by the active
+ * backend.
+ *
+ * @return clipboard contents ("" if hasTextInClipboard() == false)
+ */
+ virtual Common::String getTextFromClipboard() { return ""; }
+
+ /**
* Returns the locale of the system.
*
* This returns the currently set up locale of the system, on which
diff --git a/common/xmlparser.cpp b/common/xmlparser.cpp
index da4f577e3c..4470180710 100644
--- a/common/xmlparser.cpp
+++ b/common/xmlparser.cpp
@@ -128,7 +128,7 @@ bool XMLParser::parserError(const String &errStr) {
while (currentPosition--)
errorMessage += (char)_stream->readByte();
}
-
+
errorMessage += "\n\nParser error: ";
errorMessage += errStr;
errorMessage += "\n\n";
diff --git a/configure b/configure
index 9e2a19de34..e74b7485ec 100755
--- a/configure
+++ b/configure
@@ -117,6 +117,8 @@ done
#
# Default lib behavior yes/no/auto
_vorbis=auto
+_sdlnet=auto
+_libcurl=auto
_tremor=auto
_tremolo=no
_flac=auto
@@ -153,6 +155,7 @@ _build_hq_scalers=yes
_enable_prof=no
_global_constructors=no
_bink=yes
+_cloud=auto
# Default vkeybd/keymapper/eventrec options
_vkeybd=no
_keymapper=no
@@ -184,10 +187,12 @@ _amigaospath="Games:ScummVM"
_staticlibpath=
_xcodetoolspath=
_sparklepath=
-_sdlconfig=sdl-config
+_sdlconfig=sdl2-config
+_libcurlconfig=curl-config
_freetypeconfig=freetype-config
_sdlpath="$PATH"
_freetypepath="$PATH"
+_libcurlpath="$PATH"
_nasmpath="$PATH"
NASMFLAGS=""
NASM=""
@@ -201,6 +206,7 @@ _have_x86=no
# Add (virtual) features
add_feature 16bit "16bit color" "_16bit"
+add_feature cloud "cloud" "_cloud"
add_feature faad "libfaad" "_faad"
add_feature flac "FLAC" "_flac"
add_feature freetype2 "FreeType2" "_freetype2"
@@ -368,7 +374,7 @@ define_in_config_if_yes() {
# TODO: small bit of code to test sdl usability
find_sdlconfig() {
echo_n "Looking for sdl-config... "
- sdlconfigs="$SDL_CONFIG:$_sdlconfig:sdl-config:sdl11-config:sdl12-config"
+ sdlconfigs="$SDL_CONFIG:$_sdlconfig:sdl2-config:sdl12-config:sdl11-config:sdl-config"
_sdlconfig=
IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="$SEPARATOR"
@@ -433,6 +439,40 @@ find_freetypeconfig() {
}
#
+# Determine curl-config
+#
+find_libcurlconfig() {
+ echo_n "Looking for curl-config... "
+ libcurlconfigs="$_libcurlconfig"
+ _libcurlconfig=
+
+ IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="$SEPARATOR"
+ for path_dir in $_libcurlpath; do
+ #reset separator to parse sdlconfigs
+ IFS=":"
+ for libcurlconfig in $libcurlconfigs; do
+ if test -f "$path_dir/$libcurlconfig" ; then
+ _libcurlconfig="$path_dir/$libcurlconfig"
+ echo $_libcurlconfig
+ # Save the prefix
+ _libcurlpath=$path_dir
+ if test `basename $path_dir` = bin ; then
+ _libcurlpath=`dirname $path_dir`
+ fi
+ # break at first curl-config found in path
+ break 2
+ fi
+ done
+ done
+
+ IFS="$ac_save_ifs"
+
+ if test -z "$_libcurlconfig"; then
+ echo "none found!"
+ fi
+}
+
+#
# Determine extension used for executables
#
get_system_exe_extension() {
@@ -922,6 +962,7 @@ Optional Features:
--disable-hq-scalers exclude HQ2x and HQ3x scalers
--disable-translation don't build support for translated messages
--disable-taskbar don't build support for taskbar and launcher integration
+ --disable-cloud don't build cloud support
--enable-vkeybd build virtual keyboard support
--enable-keymapper build key mapper support
--enable-eventrecorder enable event recording functionality
@@ -1004,6 +1045,11 @@ Optional Libraries:
--with-sndio-prefix=DIR Prefix where sndio is installed (optional)
--disable-sndio disable sndio MIDI driver [autodetect]
+ --with-sdlnet-prefix=DIR Prefix where SDL_Net is
+ installed (optional)
+ --disable-sdlnet disable SDL_Net networking library [autodetect]
+ --disable-libcurl disable libcurl networking library [autodetect]
+
Some influential environment variables:
LDFLAGS linker flags, e.g. -L<lib dir> if you have libraries in a
nonstandard directory <lib dir>
@@ -1082,6 +1128,12 @@ for ac_option in $@; do
--disable-freetype2) _freetype2=no ;;
--enable-taskbar) _taskbar=yes ;;
--disable-taskbar) _taskbar=no ;;
+ --enable-sdlnet) _sdlnet=yes ;;
+ --disable-sdlnet) _sdlnet=no ;;
+ --enable-libcurl) _libcurl=yes ;;
+ --disable-libcurl) _libcurl=no ;;
+ --enable-cloud) _cloud=yes ;;
+ --disable-cloud) _cloud=no ;;
--enable-updates) _updates=yes ;;
--disable-updates) _updates=no ;;
--enable-libunity) _libunity=yes ;;
@@ -1190,6 +1242,11 @@ for ac_option in $@; do
LIBUNITY_CFLAGS="-I$arg/include"
LIBUNITY_LIBS="-L$arg/lib"
;;
+ --with-sdlnet-prefix=*)
+ arg=`echo $ac_option | cut -d '=' -f 2`
+ SDL_NET_CFLAGS="-I$arg/include"
+ SDL_NET_LIBS="-L$arg/lib"
+ ;;
--backend=*)
_backend=`echo $ac_option | cut -d '=' -f 2`
;;
@@ -2214,7 +2271,7 @@ case $_host_os in
append_var CXXFLAGS "-mtune=xscale"
append_var CXXFLAGS "-msoft-float"
ABI="armeabi"
- ANDROID_PLATFORM=4
+ ANDROID_PLATFORM=9
ANDROID_PLATFORM_ARCH="arm"
;;
android-v7a | android-arm-v7a)
@@ -2223,7 +2280,7 @@ case $_host_os in
append_var CXXFLAGS "-mfpu=vfp"
append_var LDFLAGS "-Wl,--fix-cortex-a8"
ABI="armeabi-v7a"
- ANDROID_PLATFORM=4
+ ANDROID_PLATFORM=9
ANDROID_PLATFORM_ARCH="arm"
;;
android-mips)
@@ -2248,7 +2305,7 @@ case $_host_os in
append_var CXXFLAGS "-mfloat-abi=softfp"
append_var CXXFLAGS "-mfpu=neon"
ABI="armeabi-v7a"
- ANDROID_PLATFORM=4
+ ANDROID_PLATFORM=16
ANDROID_PLATFORM_ARCH="arm"
;;
esac
@@ -2540,9 +2597,9 @@ case $_host_os in
;;
ps3)
# Force use of SDL and freetype from the ps3 toolchain
- _sdlconfig=sdl2-config
_sdlpath="$PS3DEV/portlibs/ppu:$PS3DEV/portlibs/ppu/bin"
_freetypepath="$PS3DEV/portlibs/ppu:$PS3DEV/portlibs/ppu/bin"
+ _libcurlpath="$PS3DEV/portlibs/ppu:$PS3DEV/portlibs/ppu/bin"
append_var DEFINES "-DPLAYSTATION3"
append_var CXXFLAGS "-mcpu=cell -mminimal-toc -I$PSL1GHT/ppu/include -I$PS3DEV/portlibs/ppu/include"
@@ -2814,6 +2871,7 @@ if test -n "$_host"; then
_vkeybd=yes
_keymapper=yes
_vorbis=no
+ _sdlconfig=sdl-config
_port_mk="backends/platform/dingux/dingux.mk"
;;
gp2x)
@@ -3122,7 +3180,7 @@ case $_backend in
append_var INCLUDES "-I$ANDROID_NDK/sources/cxx-stl/system/include"
;;
androidsdl)
- ;;
+ ;;
dc)
append_var INCLUDES '-I$(srcdir)/backends/platform/dc'
append_var INCLUDES '-isystem $(ronindir)/include'
@@ -4071,6 +4129,115 @@ EOF
esac
#
+# Check for SDL_Net
+#
+echocheck "SDL_Net"
+if test "$_sdlnet" = auto ; then
+ _sdlnet=no
+ cat > $TMPC << EOF
+#include "SDL/SDL_net.h"
+int main(int argc, char *argv[]) { SDLNet_Init(); return 0; }
+EOF
+ cc_check $LIBS $INCLUDES $SDL_NET_CFLAGS $SDL_NET_LIBS -lSDL_net && _sdlnet=yes
+fi
+if test "$_sdlnet" = yes ; then
+ append_var LIBS "$SDL_NET_LIBS -lSDL_net"
+ append_var INCLUDES "$SDL_NET_CFLAGS"
+fi
+define_in_config_if_yes "$_sdlnet" 'USE_SDL_NET'
+echo "$_sdlnet"
+
+#
+# Check for libcurl to be present
+#
+if test "$_libcurl" != "no"; then
+
+ # Look for the curl-config script
+ find_libcurlconfig
+
+ if test -z "$_libcurlconfig"; then
+ _libcurl=no
+ else
+ LIBCURL_LIBS=`$_libcurlconfig --libs`
+ LIBCURL_CFLAGS=`$_libcurlconfig --cflags`
+
+ if test "$_libcurl" = "auto"; then
+ _libcurl=no
+
+ cat > $TMPC << EOF
+ #include <curl/curl.h>
+ int main(int argc, char *argv[]) {
+ int x;
+ curl_easy_setopt(NULL,CURLOPT_URL,NULL);
+ x=CURL_ERROR_SIZE;
+ x=CURLOPT_WRITEFUNCTION;
+ x=CURLOPT_WRITEDATA;
+ x=CURLOPT_ERRORBUFFER;
+ x=CURLOPT_STDERR;
+ x=CURLOPT_VERBOSE;
+
+ curl_version_info_data *data = curl_version_info(CURLVERSION_NOW);
+ if (data->features & CURL_VERSION_SSL)
+ return 0;
+ return 1;
+ }
+EOF
+
+ cc_check_no_clean $LIBCURL_CFLAGS $LIBCURL_LIBS
+ if test "$?" -eq 0; then
+ if test -n "$_host"; then
+ # In cross-compiling mode, we cannot run the result, assume SSL is available
+ _libcurl=yes
+ else
+ $TMPO$HOSTEXEEXT
+ if test "$?" -eq 0; then
+ _libcurl=yes
+ else
+ _libcurl="no SSL support"
+ fi
+ fi
+ fi
+ cc_check_clean
+ fi
+
+ if test "$_libcurl" = "yes"; then
+ append_var LIBS "$LIBCURL_LIBS"
+ append_var INCLUDES "$LIBCURL_CFLAGS"
+ fi
+ fi
+
+fi
+
+echocheck "libcurl"
+echo "$_libcurl"
+
+define_in_config_if_yes "$_libcurl" "USE_LIBCURL"
+
+#
+# Check whether to build cloud integration support
+#
+echo_n "Cloud integration... "
+if test "$_cloud" = "no"; then
+ echo "no"
+else
+ _cloud=no
+ if test "$_sdlnet" = "yes"; then
+ _cloud=yes
+ echo_n "local"
+ fi
+ if test "$_libcurl" = "yes"; then
+ if test "$_cloud" = "yes"; then echo_n ", "; fi
+ _cloud=yes
+ echo_n "servers"
+ fi
+ if test "$_cloud" = "no"; then
+ echo_n "no"
+ fi
+ echo # newline
+fi
+define_in_config_if_yes $_cloud 'USE_CLOUD'
+
+#
# Check is NSDockTilePlugIn protocol is supported
#
case $_host_os in
@@ -4651,7 +4818,11 @@ if test "$_keymapper" = yes ; then
fi
if test "$_eventrec" = yes ; then
- echo ", event recorder"
+ echo_n ", event recorder"
+fi
+
+if test "$_cloud" = yes ; then
+ echo ", cloud"
else
echo
fi
@@ -4693,7 +4864,7 @@ case $_backend in
# -lgcc is carefully placed here - we want to catch
# all toolchain symbols in *our* libraries rather
# than pick up anything unhygenic from the Android libs.
- LIBS="-Wl,-Bstatic $static_libs -Wl,-Bdynamic -lgcc $system_libs -llog -lGLESv1_CM"
+ LIBS="-Wl,-Bstatic $static_libs -Wl,-Bdynamic -lgcc $system_libs -llog -landroid -lGLESv1_CM"
;;
n64)
# Move some libs down here, otherwise some symbols requires by libvorbis aren't found
diff --git a/devtools/create_project/create_project.cpp b/devtools/create_project/create_project.cpp
index 7e2cad0901..91690c2128 100644
--- a/devtools/create_project/create_project.cpp
+++ b/devtools/create_project/create_project.cpp
@@ -284,8 +284,8 @@ int main(int argc, char *argv[]) {
setup.devTools = true;
} else if (!std::strcmp(argv[i], "--tests")) {
setup.tests = true;
- } else if (!std::strcmp(argv[i], "--sdl2")) {
- setup.useSDL2 = true;
+ } else if (!std::strcmp(argv[i], "--sdl1")) {
+ setup.useSDL2 = false;
} else {
std::cerr << "ERROR: Unknown parameter \"" << argv[i] << "\"\n";
return -1;
@@ -524,6 +524,8 @@ int main(int argc, char *argv[]) {
// 4355 ('this' : used in base member initializer list)
// only disabled for specific engines where it is used in a safe way
//
+ // 4373 (previous versions of the compiler did not override when parameters only differed by const/volatile qualifiers)
+ //
// 4510 ('class' : default constructor could not be generated)
//
// 4511 ('class' : copy constructor could not be generated)
@@ -573,6 +575,8 @@ int main(int argc, char *argv[]) {
projectWarnings["m4"].push_back("4355");
+ projectWarnings["sci"].push_back("4373");
+
if (msvcVersion == 9)
provider = new CreateProjectTool::VisualStudioProvider(globalWarnings, projectWarnings, msvcVersion);
else
@@ -696,7 +700,7 @@ void displayHelp(const char *exe) {
" --disable-<name> disable inclusion of the feature \"name\"\n"
"\n"
"SDL settings:\n"
- " --sdl2 link to SDL 2.0, instead of SDL 1.2\n"
+ " --sdl1 link to SDL 1.2, instead of SDL 2.0\n"
"\n"
" There are the following features available:\n"
"\n";
@@ -996,10 +1000,12 @@ const Feature s_features[] = {
{ "png", "USE_PNG", "libpng16", true, "libpng support" },
{ "faad", "USE_FAAD", "libfaad", false, "AAC support" },
{ "mpeg2", "USE_MPEG2", "libmpeg2", false, "MPEG-2 support" },
- { "theora", "USE_THEORADEC", "libtheora_static", true, "Theora decoding support" },
- { "freetype", "USE_FREETYPE2", "freetype", true, "FreeType support" },
- { "jpeg", "USE_JPEG", "jpeg-static", true, "libjpeg support" },
- {"fluidsynth", "USE_FLUIDSYNTH", "libfluidsynth", true, "FluidSynth support" },
+ { "theora", "USE_THEORADEC", "libtheora_static", true, "Theora decoding support" },
+ { "freetype", "USE_FREETYPE2", "freetype", true, "FreeType support" },
+ { "jpeg", "USE_JPEG", "jpeg-static", true, "libjpeg support" },
+ {"fluidsynth", "USE_FLUIDSYNTH", "libfluidsynth", true, "FluidSynth support" },
+ { "libcurl", "USE_LIBCURL", "libcurl", true, "libcurl support" },
+ { "sdlnet", "USE_SDL_NET", "SDL_net", true, "SDL_net support" },
// Feature flags
{ "bink", "USE_BINK", "", true, "Bink video support" },
@@ -1011,6 +1017,7 @@ const Feature s_features[] = {
{ "opengl", "USE_OPENGL", "", true, "OpenGL support" },
{ "opengles", "USE_GLES", "", true, "forced OpenGL ES mode" },
{ "taskbar", "USE_TASKBAR", "", true, "Taskbar integration support" },
+ { "cloud", "USE_CLOUD", "", true, "Cloud integration support" },
{ "translation", "USE_TRANSLATION", "", true, "Translation support" },
{ "vkeybd", "ENABLE_VKEYBD", "", false, "Virtual keyboard support"},
{ "keymapper", "ENABLE_KEYMAPPER", "", false, "Keymapper support"},
diff --git a/devtools/create_project/create_project.h b/devtools/create_project/create_project.h
index 1e417d485b..bd0dcf6be9 100644
--- a/devtools/create_project/create_project.h
+++ b/devtools/create_project/create_project.h
@@ -239,7 +239,7 @@ struct BuildSetup {
tests = false;
runBuildEvents = false;
createInstaller = false;
- useSDL2 = false;
+ useSDL2 = true;
}
};
diff --git a/devtools/create_project/xcode.cpp b/devtools/create_project/xcode.cpp
index 62969d1436..07dc24e315 100644
--- a/devtools/create_project/xcode.cpp
+++ b/devtools/create_project/xcode.cpp
@@ -786,6 +786,8 @@ void XcodeProvider::setupBuildConfiguration(const BuildSetup &setup) {
ADD_SETTING(scummvm_Debug, "GCC_ENABLE_CPP_RTTI", "YES");
ADD_SETTING(scummvm_Debug, "GCC_INPUT_FILETYPE", "automatic");
ADD_SETTING(scummvm_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
+ ADD_SETTING(scummvm_Debug, "GCC_WARN_SIGN_COMPARE", "YES");
+ ADD_SETTING(scummvm_Debug, "WARNING_CFLAGS", "-Wno-multichar");
ValueList scummvm_defines(_defines);
REMOVE_DEFINE(scummvm_defines, "MACOSX");
REMOVE_DEFINE(scummvm_defines, "IPHONE");
@@ -844,7 +846,6 @@ void XcodeProvider::setupBuildConfiguration(const BuildSetup &setup) {
ADD_SETTING(iPhone_Debug, "GCC_OPTIMIZATION_LEVEL", "0");
ADD_SETTING(iPhone_Debug, "GCC_PRECOMPILE_PREFIX_HEADER", "NO");
ADD_SETTING(iPhone_Debug, "GCC_WARN_64_TO_32_BIT_CONVERSION", "NO");
- ADD_SETTING(iPhone_Debug, "WARNING_CFLAGS", "-Wno-multichar");
ADD_SETTING_QUOTE(iPhone_Debug, "GCC_PREFIX_HEADER", "");
ADD_SETTING(iPhone_Debug, "GCC_UNROLL_LOOPS", "YES");
ValueList iPhone_HeaderSearchPaths;
diff --git a/devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj b/devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj
index 4f06a5e469..55266a875f 100644
--- a/devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj
+++ b/devtools/create_project/xcode/create_project.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 076583601D660492006CBB9B /* cmake.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0765835E1D660492006CBB9B /* cmake.cpp */; };
F9A66C691396D4DF00CEE494 /* codeblocks.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A66C5F1396D4DF00CEE494 /* codeblocks.cpp */; };
F9A66C6A1396D4DF00CEE494 /* create_project.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A66C621396D4DF00CEE494 /* create_project.cpp */; };
F9A66C6B1396D4DF00CEE494 /* msbuild.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F9A66C651396D4DF00CEE494 /* msbuild.cpp */; };
@@ -41,6 +42,8 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 0765835E1D660492006CBB9B /* cmake.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = cmake.cpp; path = ../cmake.cpp; sourceTree = "<group>"; };
+ 0765835F1D660492006CBB9B /* cmake.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = cmake.h; path = ../cmake.h; sourceTree = "<group>"; };
F9A66C271396D36100CEE494 /* create_project */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = create_project; sourceTree = BUILT_PRODUCTS_DIR; };
F9A66C491396D47500CEE494 /* installer.vbs */ = {isa = PBXFileReference; lastKnownFileType = text; name = installer.vbs; path = ../scripts/installer.vbs; sourceTree = "<group>"; };
F9A66C4A1396D47500CEE494 /* postbuild.cmd */ = {isa = PBXFileReference; lastKnownFileType = text; name = postbuild.cmd; path = ../scripts/postbuild.cmd; sourceTree = "<group>"; };
@@ -76,6 +79,8 @@
F9A66C1C1396D36100CEE494 = {
isa = PBXGroup;
children = (
+ 0765835E1D660492006CBB9B /* cmake.cpp */,
+ 0765835F1D660492006CBB9B /* cmake.h */,
F9A66C861396E2F500CEE494 /* xcode.cpp */,
F9A66C841396E2D800CEE494 /* xcode.h */,
F9A66C6D1396D4E800CEE494 /* visualstudio.cpp */,
@@ -169,6 +174,7 @@
F9A66C6A1396D4DF00CEE494 /* create_project.cpp in Sources */,
F9A66C6B1396D4DF00CEE494 /* msbuild.cpp in Sources */,
F9A66C6C1396D4DF00CEE494 /* msvc.cpp in Sources */,
+ 076583601D660492006CBB9B /* cmake.cpp in Sources */,
F9A66C6F1396D4E800CEE494 /* visualstudio.cpp in Sources */,
F9A66C871396E2F500CEE494 /* xcode.cpp in Sources */,
);
diff --git a/devtools/create_titanic/create_titanic_dat.cpp b/devtools/create_titanic/create_titanic_dat.cpp
index 72c7f1ef46..61e5de1149 100644
--- a/devtools/create_titanic/create_titanic_dat.cpp
+++ b/devtools/create_titanic/create_titanic_dat.cpp
@@ -61,9 +61,25 @@ Common::File inputFile, outputFile;
Common::PEResources res;
uint headerOffset = 6;
uint dataOffset = HEADER_SIZE;
-#define SEGMENT_OFFSET 0x401C00
-const int FILE_DIFF = 0x401C00;
+#define ENGLISH_10042C_FILESIZE 4099072
+#define ENGLISH_10042B_FILESIZE 4095488
+#define ENGLISH_10042_FILESIZE 4094976
+enum {
+ ENGLISH_10042C_DIFF = 0x401C00,
+ ENGLISH_10042B_DIFF = 0x401400,
+ ENGLISH_10042_DIFF = 0x402000
+};
+enum Version {
+ ENGLISH_10042C = 0,
+ ENGLISH_10042B = 1,
+ ENGLISH_10042 = 2
+};
+Version _version;
+
+const int FILE_DIFF[3] = {
+ ENGLISH_10042C_DIFF, ENGLISH_10042B_DIFF, ENGLISH_10042_DIFF
+};
static const char *const ITEM_NAMES[46] = {
"LeftArmWith", "LeftArmWithout", "RightArmWith", "RightArmWithout", "BridgeRed",
@@ -249,6 +265,155 @@ static const CommonPhrase BELLBOT_COMMON_PHRASES[] = {
{ nullptr, 0, 0, 0 }
};
+struct FrameRange {
+ int _startFrame;
+ int _endFrame;
+};
+
+static const FrameRange BARBOT_FRAME_RANGES[60] = {
+ { 558, 585 }, { 659, 692 }, { 802, 816 }, { 1941, 1977 }, { 1901, 1941 },
+ { 810, 816 }, { 857, 865}, { 842, 857 }, { 821, 842 }, { 682, 692 },
+ { 1977, 2018 }, { 2140, 2170 }, { 2101, 2139 }, { 2018, 2099}, { 1902, 2015 },
+ { 1811, 1901 }, { 1751, 1810 }, { 1703, 1750 }, { 1681, 1702 }, { 1642, 1702 },
+ { 1571, 1641 }, { 1499, 1570 }, { 1403, 1463 }, { 1464, 1499 }, { 1288, 1295 },
+ { 1266, 1287 }, { 1245, 1265 }, { 1208, 1244 }, { 1171, 1207 }, { 1120, 1170 },
+ { 1092, 1120 }, { 1092, 1092 }, { 1044, 1091 }, { 1011, 1043 }, { 1001, 1010 },
+ { 985, 1001 }, { 927, 984 }, { 912, 926 }, { 898, 906 }, { 802, 896 },
+ { 865, 896 }, { 842, 865 }, { 816, 842 }, { 802, 842 }, { 740, 801 },
+ { 692, 740 }, { 610, 692 }, { 558, 610 }, { 500, 558 }, { 467, 500 },
+ { 421, 466 }, { 349, 420 }, { 306, 348 }, { 305, 306 }, { 281, 305 },
+ { 202, 281 }, { 182, 202 }, { 165, 182 }, { 96, 165 }, { 0, 95 }
+};
+
+static const char *const MISSIVEOMAT_MESSAGES[3] = {
+ "Welcome, Leovinus.\n"
+ "\n"
+ "This is your Missive-O-Mat.\n"
+ "\n"
+ "You have received 1827 Electric Missives.\n"
+ "\n"
+ "For your convenience I have deleted:\n"
+ " 453 things that people you don't know thought it would be "
+ "terribly witty to forward to you,\n"
+ " 63 Missives containing double or triple exclamation marks,\n"
+ " 846 Missives from mailing-lists you once thought might be quite "
+ "interesting and now can't figure out how to cancel,\n"
+ " 962 Chain Missives,\n"
+ " 1034 instructions on how to become a millionaire using butter,\n"
+ " 3 Yassaccan Death Threats (slightly down on last week which is"
+ " pleasing news),\n"
+ " and a Missive from your Mother which I have answered reassuringly.\n"
+ "\n"
+ "I have selected the following Missives for your particular attention. "
+ "You will not need to run Fib-Finder to see why. Something Is Up and I "
+ "suspect those two slippery urchins Brobostigon and Scraliontis are behind it.",
+
+ "Hello Droot. I have evaluated your recent missives.\n"
+ "Contents break down as follows:\n"
+ "\n"
+ "Good news 49%\n"
+ "Bad news 48%\n"
+ "Indifferent news 4%\n"
+ "Petty mailings and Family Missives 5%\n"
+ "Special Offers from the Blerontin Sand Society 1% (note - there's"
+ " a rather pretty dune for hire on p4)\n"
+ "\n"
+ "In general terms you Thrive. You continue to Prosper. Your shares are"
+ " Secure. Your hair, as always, looks Good. Carpet 14 needs cleaning. \n"
+ "\n"
+ "I am pleased to report there have been no further comments about "
+ "foot odor.\n"
+ "\n"
+ "Recommend urgently you sell all fish paste shares as Market jittery.\n"
+ "\n"
+ "As your Great Scheme nears completion I have taken the liberty of"
+ " replying to all non-urgent Missives and list below only communic"
+ "ations with Manager Brobostigon and His Pain in the Ass Loftiness"
+ " Leovinus. \n"
+ "\n"
+ "Beware - Leovinus grows suspicious. Don't take your eye off B"
+ "robostigon. \n"
+ "\n"
+ "Weather for the Launch tomorrow is bright and sunny. Hazy clouds"
+ " will be turned on at eleven. I suggest the red suit with the st"
+ "reamers.\n"
+ "\n"
+ "All money transfers will be completed through alias accounts by m"
+ "oonsup.\n"
+ "\n"
+ "Eat well. Your fish levels are down and you may suffer indecisio"
+ "n flutters mid-morning.\n"
+ "\n"
+ "Here are your Missives...",
+
+ "Hello Antar, this is your Missive-o-Mat.\n"
+ "Not that you need reminding but today is the Glorious Dawning of "
+ "a New Age in Luxury Space Travel.\n"
+ "\n"
+ "Generally my assessment of your position this morning is that you"
+ " are well, albeit not as rich as you would like to be. I hope yo"
+ "ur interesting collaboration with Mr Scraliontis will soon bear f"
+ "ruit. \n"
+ "\n"
+ "I trust your flatulence has eased during the night. Such a distr"
+ "essing condition for a man in your position.\n"
+ "\n"
+ "Most of your Missives are routine construction matters which I ha"
+ "ve dealt with and deleted. All Missives from Mr Scraliontis and "
+ "His Loftiness Leovinus are here."
+};
+
+struct BedheadEntry {
+ const char *_name1;
+ const char *_name2;
+ const char *_name3;
+ const char *_name4;
+ int _startFrame;
+ int _endFrame;
+};
+
+static const BedheadEntry ON_CLOSED[4] = {
+ { "Closed", "Closed", "Open", "Open", 0, 12 },
+ { "Open", "Any", "Any", "RestingUTV", 0, 4 },
+ { "Closed", "Open", "Any", "RestingV", 0, 6 },
+ { "Closed", "Closed", "Closed", "RestingG", 0, 21 }
+};
+static const BedheadEntry ON_RESTING_TV[2] = {
+ { "Any", "Closed", "Open", "Open", 6, 12 },
+ { "Any", "Closed", "Closed", "RestingG", 6, 21 }
+};
+static const BedheadEntry ON_RESTING_UV[2] = {
+ { "Any", "Any", "Open", "Open", 8, 12 },
+ { "Any", "Any", "Closed", "RestingG", 8, 21 }
+};
+static const BedheadEntry ON_CLOSED_WRONG[2] = {
+ { "Any", "Any", "Closed", "OpenWrong", 42, 56 },
+ { "Any", "Any", "Open", "RestingDWrong", 42, 52 }
+};
+
+static const BedheadEntry OFF_OPEN[3] = {
+ { "Closed", "Closed", "Open", "Closed", 27, 41 },
+ { "Any", "Open", "Any", "RestingUV", 27, 29 },
+ { "Open", "Closed", "Any", "RestingTV", 27, 33 }
+};
+static const BedheadEntry OFF_RESTING_UTV[1] = {
+ { "Any", "Any", "Any", "Closed", 36, 41 }
+};
+static const BedheadEntry OFF_RESTING_V[1] = {
+ { "Closed", "Any", "Any", "Closed", 32, 41 }
+};
+static const BedheadEntry OFF_RESTING_G[3] = {
+ { "Closed", "Closed", "Closed", "Closed", 21, 41 },
+ { "Any", "Open", "Closed", "RestingUV", 21, 29 },
+ { "Open", "Closed", "Closed", "RestingTV", 21, 33 }
+};
+static const BedheadEntry OFF_OPEN_WRONG[1] = {
+ { "Any", "Any", "Any", "ClosedWrong", 56, 70 }
+};
+static const BedheadEntry OFF_RESTING_D_WRONG[1] = {
+ { "Any", "Any", "Any", "ClosedWrong", 59, 70 }
+};
+
void NORETURN_PRE error(const char *s, ...) {
printf("%s\n", s);
@@ -275,7 +440,7 @@ void writeFinalEntryHeader() {
void writeStringArray(const char *name, uint offset, int count) {
outputFile.seek(dataOffset);
- inputFile.seek(offset);
+ inputFile.seek(offset - FILE_DIFF[_version]);
uint *offsets = new uint[count];
for (int idx = 0; idx < count; ++idx)
offsets[idx] = inputFile.readLong();
@@ -283,7 +448,7 @@ void writeStringArray(const char *name, uint offset, int count) {
// Iterate through reading each string
for (int idx = 0; idx < count; ++idx) {
if (offsets[idx]) {
- inputFile.seek(offsets[idx] - SEGMENT_OFFSET);
+ inputFile.seek(offsets[idx] - FILE_DIFF[_version]);
outputFile.writeString(inputFile);
} else {
outputFile.writeString("");
@@ -359,7 +524,7 @@ void writeBitmap(const char *name, Common::File *file) {
outputFile.writeLong(0); // res1 & res2
outputFile.writeLong(0x436); // image offset
- outputFile.write(*file, file->size() + 14);
+ outputFile.write(*file, file->size());
writeEntryHeader(name, dataOffset, file->size() + 14);
dataOffset += file->size() + 14;
@@ -402,7 +567,7 @@ void writeNumbers() {
}
void writeString(uint offset) {
- inputFile.seek(offset - FILE_DIFF);
+ inputFile.seek(offset - FILE_DIFF[_version]);
char c;
do {
c = inputFile.readByte();
@@ -413,26 +578,19 @@ void writeString(uint offset) {
void writeResponseTree() {
outputFile.seek(dataOffset);
- inputFile.seek(0x619500 - FILE_DIFF);
- char buffer[32];
- inputFile.read(buffer, 32);
- if (strcmp(buffer, "ReadInt(): No number to read")) {
- printf("Could not find tree data at expected position\n");
- exit(1);
- }
-
+ const int OFFSETS[3] = { 0x619520, 0x618340, 0x617380 };
for (int idx = 0; idx < 1022; ++idx) {
- inputFile.seek(0x619520 - FILE_DIFF + idx * 8);
+ inputFile.seek(OFFSETS[_version] - FILE_DIFF[_version] + idx * 8);
uint id = inputFile.readLong();
uint offset = inputFile.readLong();
outputFile.writeLong(id);
if (!id) {
// An end of list id
- } else if (offset >= 0x619520 && offset <= 0x61B510) {
+ } else if (offset >= OFFSETS[_version] && offset <= (OFFSETS[_version] + 0x1FF0)) {
// Offset to another table
outputFile.writeByte(0);
- outputFile.writeLong((offset - 0x619520) / 8);
+ outputFile.writeLong((offset - OFFSETS[_version]) / 8);
} else {
// Offset to ASCIIZ string
outputFile.writeByte(1);
@@ -452,7 +610,7 @@ void writeSentenceEntries(const char *name, uint tableOffset) {
uint offset3, offset5, offset6, offset7, offset8, offset10;
for (uint idx = 0; ; ++idx) {
- inputFile.seek(tableOffset - FILE_DIFF + idx * 0x34);
+ inputFile.seek(tableOffset - FILE_DIFF[_version] + idx * 0x34);
v1 = inputFile.readLong();
if (!v1)
// Reached end of list
@@ -498,7 +656,7 @@ void writeWords(const char *name, uint tableOffset, int recordCount = 2) {
uint val, strOffset;
for (uint idx = 0; ; ++idx) {
- inputFile.seek(tableOffset - FILE_DIFF + idx * recordSize);
+ inputFile.seek(tableOffset - FILE_DIFF[_version] + idx * recordSize);
val = inputFile.readLong();
strOffset = inputFile.readLong();
@@ -516,7 +674,7 @@ void writeWords(const char *name, uint tableOffset, int recordCount = 2) {
}
void writeSentenceMappings(const char *name, uint offset, int numValues) {
- inputFile.seek(offset - FILE_DIFF);
+ inputFile.seek(offset - FILE_DIFF[_version]);
outputFile.seek(dataOffset);
uint id;
@@ -535,7 +693,8 @@ void writeSentenceMappings(const char *name, uint offset, int numValues) {
void writeStarfieldPoints() {
outputFile.seek(dataOffset);
- inputFile.seek(0x59DE4C - FILE_DIFF);
+ const int OFFSETS[3] = { 0x59DE4C, 0x59DBEC, 0x59CC1C };
+ inputFile.seek(OFFSETS[_version] - FILE_DIFF[_version]);
uint size = 876 * 12;
outputFile.write(inputFile, size);
@@ -546,13 +705,14 @@ void writeStarfieldPoints() {
void writeStarfieldPoints2() {
outputFile.seek(dataOffset);
+ const int OFFSETS[3] = { 0x5A2F28, 0x5A2CC8, 0x5A1CF8 };
for (int rootCtr = 0; rootCtr < 80; ++rootCtr) {
- inputFile.seek(0x5A2F28 - FILE_DIFF + rootCtr * 8);
+ inputFile.seek(OFFSETS[_version] - FILE_DIFF[_version] + rootCtr * 8);
uint offset = inputFile.readUint32LE();
uint count = inputFile.readUint32LE();
outputFile.writeLong(count);
- inputFile.seek(offset - FILE_DIFF);
+ inputFile.seek(offset - FILE_DIFF[_version]);
outputFile.write(inputFile, count * 4 * 4);
}
@@ -576,6 +736,99 @@ void writePhrases(const char *name, const CommonPhrase *phrases) {
dataOffset += size;
}
+void writeBarbotFrameRanges() {
+ outputFile.seek(dataOffset);
+
+ for (int idx = 0; idx < 60; ++idx) {
+ outputFile.writeLong(BARBOT_FRAME_RANGES[idx]._startFrame);
+ outputFile.writeLong(BARBOT_FRAME_RANGES[idx]._endFrame);
+ }
+
+ uint size = outputFile.size() - dataOffset;
+ writeEntryHeader("FRAMES/BARBOT", dataOffset, size);
+ dataOffset += size;
+}
+
+void writeMissiveOMatMessages() {
+ outputFile.seek(dataOffset);
+
+ for (int idx = 0; idx < 3; ++idx)
+ outputFile.writeString(MISSIVEOMAT_MESSAGES[idx]);
+
+ uint size = outputFile.size() - dataOffset;
+ writeEntryHeader("TEXT/MISSIVEOMAT/WELCOME", dataOffset, size);
+ dataOffset += size;
+
+ static const int MESSAGES[3] = { 0x5A63C0, 0x5A5BA8, 0x5A4A18 };
+ writeStringArray("TEXT/MISSIVEOMAT/MESSAGES", MESSAGES[_version], 58);
+ static const int FROM[3] = { 0x5A61F0, 0x5A59D8, 0x5A4BE8 };
+ writeStringArray("TEXT/MISSIVEOMAT/FROM", FROM[_version], 58);
+ static const int TO[3] = { 0x5A62D8, 0x5A5AC0, 0x5A4B00 };
+ writeStringArray("TEXT/MISSIVEOMAT/TO", TO[_version], 58);
+}
+
+void writeBedheadGroup(const BedheadEntry *data, int count) {
+ for (int idx = 0; idx < count; ++idx, ++data) {
+ outputFile.writeString(data->_name1);
+ outputFile.writeString(data->_name2);
+ outputFile.writeString(data->_name3);
+ outputFile.writeString(data->_name4);
+ outputFile.writeLong(data->_startFrame);
+ outputFile.writeLong(data->_endFrame);
+ }
+}
+
+void writeBedheadData() {
+ outputFile.seek(dataOffset);
+
+ writeBedheadGroup(ON_CLOSED, 4);
+ writeBedheadGroup(ON_RESTING_TV, 2);
+ writeBedheadGroup(ON_RESTING_UV, 2);
+ writeBedheadGroup(ON_CLOSED_WRONG, 2);
+ writeBedheadGroup(OFF_OPEN, 3);
+ writeBedheadGroup(OFF_RESTING_UTV, 1);
+ writeBedheadGroup(OFF_RESTING_V, 1);
+ writeBedheadGroup(OFF_RESTING_G, 3);
+ writeBedheadGroup(OFF_OPEN_WRONG, 1);
+ writeBedheadGroup(OFF_RESTING_D_WRONG, 1);
+
+ uint size = outputFile.size() - dataOffset;
+ writeEntryHeader("DATA/BEDHEAD", dataOffset, size);
+ dataOffset += size;
+}
+
+void writeParrotLobbyLinkUpdaterEntries() {
+ static const int OFFSETS[3] = { 0x5A5B38, 0x5A5320, 0x5A4360 };
+ static const int COUNTS[5] = { 7, 5, 6, 9, 1 };
+ static const int SKIP[5] = { 36, 36, 40, 36, 0 };
+ uint recordOffset = OFFSETS[_version], linkOffset;
+ byte vals[8];
+
+ outputFile.seek(dataOffset);
+
+ for (int groupNum = 0; groupNum < 4; ++groupNum) {
+ for (int entryNum = 0; entryNum < COUNTS[groupNum];
+ ++entryNum, recordOffset += 36) {
+ inputFile.seek(recordOffset - FILE_DIFF[_version]);
+ linkOffset = inputFile.readUint32LE();
+ for (int idx = 0; idx < 8; ++idx)
+ vals[idx] = inputFile.readUint32LE();
+
+ // Write out the entry
+ inputFile.seek(linkOffset - FILE_DIFF[_version]);
+ outputFile.writeString(inputFile);
+ outputFile.write(vals, 8);
+ }
+
+ // Skip space between groups
+ recordOffset += SKIP[groupNum];
+ }
+
+ uint size = outputFile.size() - dataOffset;
+ writeEntryHeader("DATA/PARROT_LOBBY_LINK_UPDATOR", dataOffset, size);
+ dataOffset += size;
+}
+
void writeHeader() {
// Write out magic string
const char *MAGIC_STR = "SVTN";
@@ -617,71 +870,123 @@ void writeData() {
writeStringArray("TEXT/ITEM_IDS", ITEM_IDS, 40);
writeStringArray("TEXT/ROOM_NAMES", ROOM_NAMES, 34);
- writeStringArray("TEXT/PHRASES", 0x21B7C8, 376);
- writeStringArray("TEXT/REPLACEMENTS1", 0x21BDB0, 218);
- writeStringArray("TEXT/REPLACEMENTS2", 0x21C120, 1576);
- writeStringArray("TEXT/REPLACEMENTS3", 0x21D9C8, 82);
- writeStringArray("TEXT/PRONOUNS", 0x22F718, 15);
-
- writeSentenceEntries("Sentences/Default", 0x5C0130);
- writeSentenceEntries("Sentences/Barbot", 0x5ABE60);
- writeSentenceEntries("Sentences/Barbot2", 0x5BD4E8);
- writeSentenceEntries("Sentences/Bellbot", 0x5C2230);
- writeSentenceEntries("Sentences/Bellbot/1", 0x5D1670);
- writeSentenceEntries("Sentences/Bellbot/2", 0x5D1A80);
- writeSentenceEntries("Sentences/Bellbot/3", 0x5D1AE8);
- writeSentenceEntries("Sentences/Bellbot/4", 0x5D1B88);
- writeSentenceEntries("Sentences/Bellbot/5", 0x5D2A60);
- writeSentenceEntries("Sentences/Bellbot/6", 0x5D2CD0);
- writeSentenceEntries("Sentences/Bellbot/7", 0x5D3488);
- writeSentenceEntries("Sentences/Bellbot/8", 0x5D3900);
- writeSentenceEntries("Sentences/Bellbot/9", 0x5D3968);
- writeSentenceEntries("Sentences/Bellbot/10", 0x5D4668);
- writeSentenceEntries("Sentences/Bellbot/11", 0x5D47A0);
- writeSentenceEntries("Sentences/Bellbot/12", 0x5D4EC0);
- writeSentenceEntries("Sentences/Bellbot/13", 0x5D5100);
- writeSentenceEntries("Sentences/Bellbot/14", 0x5D5370);
- writeSentenceEntries("Sentences/Bellbot/15", 0x5D5548);
- writeSentenceEntries("Sentences/Bellbot/16", 0x5D56B8);
- writeSentenceEntries("Sentences/Bellbot/17", 0x5D57C0);
- writeSentenceEntries("Sentences/Bellbot/18", 0x5D5B38);
- writeSentenceEntries("Sentences/Bellbot/19", 0x5D61B8);
-
- writeSentenceEntries("Sentences/Deskbot", 0x5DCD10);
- writeSentenceEntries("Sentences/Deskbot/2", 0x5E8E18);
- writeSentenceEntries("Sentences/Deskbot/3", 0x5E8BA8);
+ const int TEXT_PHRASES[3] = { 0x61D3C8, 0x618340, 0x61B1E0 };
+ const int TEXT_REPLACEMENTS1[3] = { 0x61D9B0, 0x61C788, 0x61B7C8 };
+ const int TEXT_REPLACEMENTS2[3] = { 0x61DD20, 0x61CAF8, 0x61BB38 };
+ const int TEXT_REPLACEMENTS3[3] = { 0x61F5C8, 0x61E3A0, 0x61D3E0 };
+ const int TEXT_PRONOUNS[3] = { 0x631318, 0x6300F8, 0x62F138 };
+ writeStringArray("TEXT/PHRASES", TEXT_PHRASES[_version], 376);
+ writeStringArray("TEXT/REPLACEMENTS1", TEXT_REPLACEMENTS1[_version], 218);
+ writeStringArray("TEXT/REPLACEMENTS2", TEXT_REPLACEMENTS2[_version], 1576);
+ writeStringArray("TEXT/REPLACEMENTS3", TEXT_REPLACEMENTS3[_version], 82);
+ writeStringArray("TEXT/PRONOUNS", TEXT_PRONOUNS[_version], 15);
+
+ const int SENTENCES_DEFAULT[3] = { 0x5C0130, 0x5BEFC8, 0x5BE008 };
+ const int SENTENCES_BARBOT[2][3] = {
+ { 0x5ABE60, 0x5AACF8, 0x5A9D38 }, { 0x5BD4E8, 0x5BC380, 0x5BB3C0 }
+ };
+ const int SENTENCES_BELLBOT[20][3] = {
+ { 0x5C2230, 0x5C10C8, 0X5C0108 }, { 0x5D1670, 0x5D0508, 0x5CF548 },
+ { 0x5D1A80, 0x5D0918, 0x5CF958 }, { 0x5D1AE8, 0x5D0980, 0x5CF9C0 },
+ { 0x5D1B88, 0x5D0A20, 0x5CFA60 }, { 0x5D2A60, 0x5D18F8, 0x5D0938 },
+ { 0x5D2CD0, 0x5D1B68, 0x5D0BA8 }, { 0x5D3488, 0x5D2320, 0x5D1360 },
+ { 0x5D3900, 0x5D2798, 0x5D17D8 }, { 0x5D3968, 0x5D2800, 0x5D1840 },
+ { 0x5D4668, 0x5D3500, 0x5D2540 }, { 0x5D47A0, 0x5D3638, 0x5D2678 },
+ { 0x5D4EC0, 0x5D3D58, 0x5D2D98 }, { 0x5D5100, 0x5D3F98, 0x5D2FD8 },
+ { 0x5D5370, 0x5D4208, 0x5D3248 }, { 0x5D5548, 0x5D43E0, 0x5D3420 },
+ { 0x5D56B8, 0x5D4550, 0x5D3590 }, { 0x5D57C0, 0x5D4658, 0x5D3698 },
+ { 0x5D5B38, 0x5D49D0, 0x5D3A10 }, { 0x5D61B8, 0x5D5050, 0x5D4090 }
+ };
+ writeSentenceEntries("Sentences/Default", SENTENCES_DEFAULT[_version]);
+ writeSentenceEntries("Sentences/Barbot", SENTENCES_BARBOT[0][_version]);
+ writeSentenceEntries("Sentences/Barbot2", SENTENCES_BARBOT[1][_version]);
+ writeSentenceEntries("Sentences/Bellbot", SENTENCES_BELLBOT[0][_version]);
+ writeSentenceEntries("Sentences/Bellbot/1", SENTENCES_BELLBOT[1][_version]);
+ writeSentenceEntries("Sentences/Bellbot/2", SENTENCES_BELLBOT[2][_version]);
+ writeSentenceEntries("Sentences/Bellbot/3", SENTENCES_BELLBOT[3][_version]);
+ writeSentenceEntries("Sentences/Bellbot/4", SENTENCES_BELLBOT[4][_version]);
+ writeSentenceEntries("Sentences/Bellbot/5", SENTENCES_BELLBOT[5][_version]);
+ writeSentenceEntries("Sentences/Bellbot/6", SENTENCES_BELLBOT[6][_version]);
+ writeSentenceEntries("Sentences/Bellbot/7", SENTENCES_BELLBOT[7][_version]);
+ writeSentenceEntries("Sentences/Bellbot/8", SENTENCES_BELLBOT[8][_version]);
+ writeSentenceEntries("Sentences/Bellbot/9", SENTENCES_BELLBOT[9][_version]);
+ writeSentenceEntries("Sentences/Bellbot/10", SENTENCES_BELLBOT[10][_version]);
+ writeSentenceEntries("Sentences/Bellbot/11", SENTENCES_BELLBOT[11][_version]);
+ writeSentenceEntries("Sentences/Bellbot/12", SENTENCES_BELLBOT[12][_version]);
+ writeSentenceEntries("Sentences/Bellbot/13", SENTENCES_BELLBOT[13][_version]);
+ writeSentenceEntries("Sentences/Bellbot/14", SENTENCES_BELLBOT[14][_version]);
+ writeSentenceEntries("Sentences/Bellbot/15", SENTENCES_BELLBOT[15][_version]);
+ writeSentenceEntries("Sentences/Bellbot/16", SENTENCES_BELLBOT[16][_version]);
+ writeSentenceEntries("Sentences/Bellbot/17", SENTENCES_BELLBOT[17][_version]);
+ writeSentenceEntries("Sentences/Bellbot/18", SENTENCES_BELLBOT[18][_version]);
+ writeSentenceEntries("Sentences/Bellbot/19", SENTENCES_BELLBOT[19][_version]);
+
+ const int SENTENCES_DESKBOT[3][3] = {
+ { 0x5DCD10, 0x5DBBA8, 0x5DABE8 }, { 0x5E8E18, 0x5E7CB0, 0x5E6CF0 },
+ { 0x5E8BA8, 0x5E7A40, 0x5E6A80 }
+ };
+ writeSentenceEntries("Sentences/Deskbot", SENTENCES_DESKBOT[0][_version]);
+ writeSentenceEntries("Sentences/Deskbot/2", SENTENCES_DESKBOT[1][_version]);
+ writeSentenceEntries("Sentences/Deskbot/3", SENTENCES_DESKBOT[2][_version]);
- writeSentenceEntries("Sentences/Doorbot", 0x5EC110);
- writeSentenceEntries("Sentences/Doorbot/2", 0x5FD930);
- writeSentenceEntries("Sentences/Doorbot/100", 0x5FD930);
- writeSentenceEntries("Sentences/Doorbot/101", 0x5FE668);
- writeSentenceEntries("Sentences/Doorbot/102", 0x5FDD40);
- writeSentenceEntries("Sentences/Doorbot/107", 0x5FFF08);
- writeSentenceEntries("Sentences/Doorbot/110", 0x5FE3C0);
- writeSentenceEntries("Sentences/Doorbot/111", 0x5FF0C8);
- writeSentenceEntries("Sentences/Doorbot/124", 0x5FF780);
- writeSentenceEntries("Sentences/Doorbot/129", 0x5FFAC0);
- writeSentenceEntries("Sentences/Doorbot/131", 0x5FFC30);
- writeSentenceEntries("Sentences/Doorbot/132", 0x6000E0);
-
- writeSentenceEntries("Sentences/Liftbot", 0x6026B0);
- writeSentenceEntries("Sentences/MaitreD", 0x60CFD8);
- writeSentenceEntries("Sentences/MaitreD/1", 0x614288);
- writeSentenceEntries("Sentences/Parrot", 0x615858);
- writeSentenceEntries("Sentences/SuccUBus", 0x616698);
- writeSentenceMappings("Mappings/Barbot", 0x5B28A0, 8);
- writeSentenceMappings("Mappings/Bellbot", 0x5CD830, 1);
- writeSentenceMappings("Mappings/Deskbot", 0x5E2BB8, 4);
- writeSentenceMappings("Mappings/Doorbot", 0x5F7950, 4);
- writeSentenceMappings("Mappings/Liftbot", 0x608660, 4);
- writeSentenceMappings("Mappings/MaitreD", 0x6125C8, 1);
- writeSentenceMappings("Mappings/Parrot", 0x615B68, 1);
- writeSentenceMappings("Mappings/SuccUBus", 0x6189F0, 1);
- writeWords("Words/Barbot", 0x5BE2E0);
- writeWords("Words/Bellbot", 0x5D8230);
- writeWords("Words/Deskbot", 0x5EAAA8);
- writeWords("Words/Doorbot", 0x601098, 3);
- writeWords("Words/Liftbot", 0x60C788);
+ const int SENTENCES_DOORBOT[12][3] = {
+ { 0x5EC110, 0x5EAFA8, 0x5E9FE8 }, { 0x5FD930, 0x5FC7C8, 0x5FB808 },
+ { 0x5FDD0C, 0x5FCBA4, 0x5FBBE4 }, { 0x5FE668, 0x5FD500, 0x5FC540 },
+ { 0x5FDD40, 0x5FCBD8, 0X5FBC18 }, { 0x5FFF08, 0x5FEDA0, 0x5FDDE0 },
+ { 0x5FE3C0, 0x5FD258, 0x5FC298 }, { 0x5FF0C8, 0x5FDF60, 0x5FCFA0 },
+ { 0x5FF780, 0x5FE618, 0x5FD658 }, { 0x5FFAC0, 0x5FE958, 0x5FD998 },
+ { 0x5FFC30, 0x5FEAC8, 0x5FDB08 }, { 0x6000E0, 0x5FEF78, 0x5FDFB8 }
+ };
+ writeSentenceEntries("Sentences/Doorbot", SENTENCES_DOORBOT[0][_version]);
+ writeSentenceEntries("Sentences/Doorbot/2", SENTENCES_DOORBOT[1][_version]);
+ writeSentenceEntries("Sentences/Doorbot/100", SENTENCES_DOORBOT[2][_version]);
+ writeSentenceEntries("Sentences/Doorbot/101", SENTENCES_DOORBOT[3][_version]);
+ writeSentenceEntries("Sentences/Doorbot/102", SENTENCES_DOORBOT[4][_version]);
+ writeSentenceEntries("Sentences/Doorbot/107", SENTENCES_DOORBOT[5][_version]);
+ writeSentenceEntries("Sentences/Doorbot/110", SENTENCES_DOORBOT[6][_version]);
+ writeSentenceEntries("Sentences/Doorbot/111", SENTENCES_DOORBOT[7][_version]);
+ writeSentenceEntries("Sentences/Doorbot/124", SENTENCES_DOORBOT[8][_version]);
+ writeSentenceEntries("Sentences/Doorbot/129", SENTENCES_DOORBOT[9][_version]);
+ writeSentenceEntries("Sentences/Doorbot/131", SENTENCES_DOORBOT[10][_version]);
+ writeSentenceEntries("Sentences/Doorbot/132", SENTENCES_DOORBOT[11][_version]);
+
+ const int SENTENCES_LIFTBOT[3] = { 0x6026B0, 0x601548, 0x600588 };
+ const int SENTENCES_MAITRED[2][3] = {
+ { 0x60CFD8, 0x60BE70, 0x60AEB0 }, { 0x614288, 0x613120, 0x612160 }
+ };
+ const int SENTENCES_PARROT[3] = { 0x615858, 0x6146F0, 0x613730 };
+ const int SENTENCES_SUCCUBUS[3] = { 0x616698, 0x615530, 0x614570 };
+ const int MAPPINGS_BARBOT[3] = { 0x5B28A0, 0x5B173E, 0x5B0778 };
+ const int MAPPINGS_BELLBOT[3] = { 0x5CD830, 0x5CC6C8, 0x5CB708 };
+ const int MAPPINGS_DESKBOT[3] = { 0x5E2BB8, 0x5E1A50, 0x5E0A90 };
+ const int MAPPINGS_DOORBOT[3] = { 0x5F7950, 0x5F67E8, 0x5F5828 };
+ const int MAPPINGS_LIFTBOT[3] = { 0x608660, 0x6074F8, 0x606538 };
+ const int MAPPINGS_MAITRED[3] = { 0x6125C8, 0x611460, 0x6104A0 };
+ const int MAPPINGS_PARROT[3] = { 0x615B68, 0x614A00, 0x613A40 };
+ const int MAPPINGS_SUCCUBUS[3] = { 0x6189F0, 0x617888, 0x6168C8 };
+ const int WORDS_BARBOT[3] = { 0x5BE2E0, 0x5BD178, 0x5BC1B8 };
+ const int WORDS_BELLBOT[3] = { 0x5D8230, 0x5D70C8, 0x5D6108 };
+ const int WORDS_DESKBOT[3] = { 0x5EAAA8, 0x5E9940, 0x5E8980 };
+ const int WORDS_DOORBOT[3] = { 0x601098, 0x5FFF30, 0x5FEF70 };
+ const int WORDS_LIFTBOT[3] = { 0x60C788, 0x60B620, 0x60A660 };
+ writeSentenceEntries("Sentences/Liftbot", SENTENCES_LIFTBOT[_version]);
+ writeSentenceEntries("Sentences/MaitreD", SENTENCES_MAITRED[0][_version]);
+ writeSentenceEntries("Sentences/MaitreD/1", SENTENCES_MAITRED[1][_version]);
+ writeSentenceEntries("Sentences/Parrot", SENTENCES_PARROT[_version]);
+ writeSentenceEntries("Sentences/SuccUBus", SENTENCES_SUCCUBUS[_version]);
+ writeSentenceMappings("Mappings/Barbot", MAPPINGS_BARBOT[_version], 8);
+ writeSentenceMappings("Mappings/Bellbot", MAPPINGS_BELLBOT[_version], 1);
+ writeSentenceMappings("Mappings/Deskbot", MAPPINGS_DESKBOT[_version], 4);
+ writeSentenceMappings("Mappings/Doorbot", MAPPINGS_DOORBOT[_version], 4);
+ writeSentenceMappings("Mappings/Liftbot", MAPPINGS_LIFTBOT[_version], 4);
+ writeSentenceMappings("Mappings/MaitreD", MAPPINGS_MAITRED[_version], 1);
+ writeSentenceMappings("Mappings/Parrot", MAPPINGS_PARROT[_version], 1);
+ writeSentenceMappings("Mappings/SuccUBus", MAPPINGS_SUCCUBUS[_version], 1);
+ writeWords("Words/Barbot", WORDS_BARBOT[_version]);
+ writeWords("Words/Bellbot", WORDS_BELLBOT[_version]);
+ writeWords("Words/Deskbot", WORDS_DESKBOT[_version]);
+ writeWords("Words/Doorbot", WORDS_DOORBOT[_version], 3);
+ writeWords("Words/Liftbot", WORDS_LIFTBOT[_version]);
writePhrases("Phrases/Bellbot", BELLBOT_COMMON_PHRASES);
writeResponseTree();
@@ -689,9 +994,14 @@ void writeData() {
writeAllScriptQuotes();
writeAllScriptResponses();
writeAllScriptRanges();
+
writeAllTagMappings();
writeAllUpdateStates();
writeAllScriptPreResponses();
+ writeBarbotFrameRanges();
+ writeMissiveOMatMessages();
+ writeBedheadData();
+ writeParrotLobbyLinkUpdaterEntries();
}
void createScriptMap() {
@@ -745,6 +1055,17 @@ int main(int argc, char *argv[]) {
}
res.loadFromEXE(argv[1]);
+ if (inputFile.size() == ENGLISH_10042C_FILESIZE)
+ _version = ENGLISH_10042C;
+ else if (inputFile.size() == ENGLISH_10042B_FILESIZE)
+ _version = ENGLISH_10042B;
+ else if (inputFile.size() == ENGLISH_10042_FILESIZE)
+ _version = ENGLISH_10042;
+ else {
+ printf("Unknown version of ST.exe specified");
+ exit(0);
+ }
+
if (!outputFile.open(argv[2], Common::kFileWriteMode)) {
error("Could not open output file");
}
diff --git a/devtools/credits.pl b/devtools/credits.pl
index 9cc5ad4227..3f1511c399 100755
--- a/devtools/credits.pl
+++ b/devtools/credits.pl
@@ -1244,7 +1244,7 @@ begin_credits("Credits");
begin_persons();
add_person("Daniel Balsom", "DanielFox", "For the original Reinherit (SAGA) code");
add_person("Sander Buskens", "", "For his work on the initial reversing of Monkey2");
- add_person("", "Canadacow", "For the original MT-32 emulator");
+ add_person("Dean Beeler", "Canadacow", "For the original MT-32 emulator");
add_person("Kevin Carnes", "", "For Scumm16, the basis of ScummVM's older gfx codecs");
add_person("Curt Coder", "", "For the original TrollVM (preAGI) code");
add_person("Patrick Combet", "Dorian Gray", "For the original Gobliiins ADL player");
@@ -1253,12 +1253,12 @@ begin_credits("Credits");
add_person("DOSBox Team", "", "For their awesome OPL2 and OPL3 emulator");
add_person("Yusuke Kamiyamane", "", "For contributing some GUI icons");
add_person("Till Kresslein", "Krest", "For design of modern ScummVM GUI");
- add_person("", "Jezar", "For his freeverb filter implementation");
+ add_person("Jezar Wakefield", "", "For his freeverb filter implementation");
add_person("Jim Leiterman", "", "Various info on his FM-TOWNS/Marty SCUMM ports");
- add_person("", "lloyd", "For deep tech details about C64 Zak &amp; MM");
+ add_person("Lloyd Rosen", "", "For deep tech details about C64 Zak &amp; MM");
add_person("Sarien Team", "", "Original AGI engine code");
add_person("Jimmi Th&oslash;gersen", "", "For ScummRev, and much obscure code/documentation");
- add_person("", "Tristan", "For additional work on the original MT-32 emulator");
+ add_person("Tristan Matthews", "", "For additional work on the original MT-32 emulator");
add_person("James Woodcock", "", "Soundtrack enhancements");
add_person("Anton Yartsev", "Zidane", "For the original re-implementation of the Z-Vision engine");
end_persons();
diff --git a/devtools/scumm-md5.txt b/devtools/scumm-md5.txt
index 92754a27b4..98f8774dd7 100644
--- a/devtools/scumm-md5.txt
+++ b/devtools/scumm-md5.txt
@@ -633,6 +633,7 @@ airport Let's Explore the Airport with Buzzy
8ffd618a776a4c0d8922bb28b09f8ce8 -1 en Windows - Demo - khalek
e144f5f49d9241d2a9dee2576b3d09cb 51152 en Windows - Demo - khalek
86c9902b7bec1a17926d4dae85beaa45 -1 en Windows HE 71 Demo - khalek
+ 3c90d2a39cafa60b8ebce70a34a59a41 51152 nl Windows - Demo - Ben Castricum
farm Let's Explore the Farm with Buzzy
a5c5388da9bf0e6662fdca8813a79d13 86962 en Windows - - - George Kormendi
diff --git a/dists/android/AndroidManifest.xml b/dists/android/AndroidManifest.xml
index c091039266..64870a459b 100644
--- a/dists/android/AndroidManifest.xml
+++ b/dists/android/AndroidManifest.xml
@@ -40,6 +40,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" android:required="true"/>
+
<uses-feature android:name="android.hardware.screen.landscape"
android:required="false" />
diff --git a/dists/android/AndroidManifest.xml.in b/dists/android/AndroidManifest.xml.in
index 7eaece9d1f..9601425c74 100644
--- a/dists/android/AndroidManifest.xml.in
+++ b/dists/android/AndroidManifest.xml.in
@@ -40,6 +40,8 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" android:required="true"/>
+
<uses-feature android:name="android.hardware.screen.landscape"
android:required="false" />
diff --git a/dists/cloudicon.png b/dists/cloudicon.png
new file mode 100644
index 0000000000..21373aea1a
--- /dev/null
+++ b/dists/cloudicon.png
Binary files differ
diff --git a/dists/cloudicon_disabled.png b/dists/cloudicon_disabled.png
new file mode 100644
index 0000000000..27c9600b0a
--- /dev/null
+++ b/dists/cloudicon_disabled.png
Binary files differ
diff --git a/dists/debian/control b/dists/debian/control
index 40c0e53470..f101ce3ef8 100644
--- a/dists/debian/control
+++ b/dists/debian/control
@@ -3,7 +3,7 @@ Section: games
Priority: optional
Maintainer: Debian Games Team <pkg-games-devel@lists.alioth.debian.org>
Uploaders: David Weinehall <tao@debian.org>, Moritz Muehlenhoff <jmm@debian.org>
-Build-Depends: debhelper (>= 7.0.50~), nasm [i386], libsdl1.2-dev, libmad0-dev, libasound2-dev [linux-any], libvorbis-dev, libmpeg2-4-dev, libflac-dev, libz-dev, libfluidsynth-dev, python
+Build-Depends: debhelper (>= 7.0.50~), nasm [i386], libsdl2-dev, libmad0-dev, libasound2-dev [linux-any], libvorbis-dev, libmpeg2-4-dev, libflac-dev, libz-dev, libfluidsynth-dev, python
Standards-Version: 3.9.2
Homepage: http://www.scummvm.org
diff --git a/dists/msvc10/create_msvc10.bat b/dists/msvc10/create_msvc10.bat
index be0434fc50..53acbff42e 100644
--- a/dists/msvc10/create_msvc10.bat
+++ b/dists/msvc10/create_msvc10.bat
@@ -55,14 +55,14 @@ goto done
echo.
echo Creating project files with all engines enabled (stable and unstable)
echo.
-create_project ..\.. --enable-all-engines --msvc --msvc-version 10 --build-events
+create_project ..\.. --enable-all-engines --disable-fluidsynth --msvc --msvc-version 10 --build-events
goto done
:stable
echo.
echo Creating normal project files, with only the stable engines enabled
echo.
-create_project ..\.. --msvc --msvc-version 10
+create_project ..\.. --disable-fluidsynth --msvc --msvc-version 10
goto done
:tools
diff --git a/dists/msvc11/create_msvc11.bat b/dists/msvc11/create_msvc11.bat
index fc5471f46f..3c3052a5b0 100644
--- a/dists/msvc11/create_msvc11.bat
+++ b/dists/msvc11/create_msvc11.bat
@@ -55,14 +55,14 @@ goto done
echo.
echo Creating project files with all engines enabled (stable and unstable)
echo.
-create_project ..\.. --enable-all-engines --msvc --msvc-version 11 --build-events
+create_project ..\.. --enable-all-engines --disable-fluidsynth --msvc --msvc-version 11 --build-events
goto done
:stable
echo.
echo Creating normal project files, with only the stable engines enabled
echo.
-create_project ..\.. --msvc --msvc-version 11
+create_project ..\.. --disable-fluidsynth --msvc --msvc-version 11
goto done
:tools
diff --git a/dists/msvc12/create_msvc12.bat b/dists/msvc12/create_msvc12.bat
index d99001edb1..449b50ea54 100644
--- a/dists/msvc12/create_msvc12.bat
+++ b/dists/msvc12/create_msvc12.bat
@@ -55,14 +55,14 @@ goto done
echo.
echo Creating project files with all engines enabled (stable and unstable)
echo.
-create_project ..\.. --enable-all-engines --msvc --msvc-version 12 --build-events
+create_project ..\.. --enable-all-engines --disable-fluidsynth --msvc --msvc-version 12 --build-events
goto done
:stable
echo.
echo Creating normal project files, with only the stable engines enabled
echo.
-create_project ..\.. --msvc --msvc-version 12
+create_project ..\.. --disable-fluidsynth --msvc --msvc-version 12
goto done
:tools
diff --git a/dists/msvc9/create_msvc9.bat b/dists/msvc9/create_msvc9.bat
index 34bcccdd7b..005bc084db 100644
--- a/dists/msvc9/create_msvc9.bat
+++ b/dists/msvc9/create_msvc9.bat
@@ -55,14 +55,14 @@ goto done
echo.
echo Creating project files with all engines enabled (stable and unstable)
echo.
-create_project ..\.. --enable-all-engines --msvc --msvc-version 9
+create_project ..\.. --enable-all-engines --disable-fluidsynth --msvc --msvc-version 9
goto done
:stable
echo.
echo Creating normal project files, with only the stable engines enabled
echo.
-create_project ..\.. --msvc --msvc-version 9
+create_project ..\.. --disable-fluidsynth --msvc --msvc-version 9
goto done
:tools
diff --git a/dists/scummvm.rc b/dists/scummvm.rc
index 873feaa419..00d71ef717 100644
--- a/dists/scummvm.rc
+++ b/dists/scummvm.rc
@@ -19,6 +19,9 @@ scummmodern.zip FILE "gui/themes/scummmodern.zip"
#ifdef USE_TRANSLATION
translations.dat FILE "gui/themes/translations.dat"
#endif
+#ifdef USE_SDL_NET
+wwwroot.zip FILE "backends/networking/wwwroot.zip"
+#endif
#if ENABLE_ACCESS == STATIC_PLUGIN
access.dat FILE "dists/engine-data/access.dat"
diff --git a/dists/win32/ScummVM.iss b/dists/win32/ScummVM.iss
index 740b30d071..6a5b7d91da 100644
--- a/dists/win32/ScummVM.iss
+++ b/dists/win32/ScummVM.iss
@@ -105,7 +105,7 @@ Source: doc/de/LIESMICH.txt; DestDir: {app}; Flags: ignoreversion isreadme; Lang
Source: doc/se/LasMig.txt; DestDir: {app}; Flags: ignoreversion isreadme; Languages: se
Source: README-SDL.txt; DestDir: {app}; Flags: ignoreversion
Source: scummvm.exe; DestDir: {app}; Flags: ignoreversion
-Source: SDL.dll; DestDir: {app}; Flags: replacesameversion
+Source: SDL2.dll; DestDir: {app}; Flags: replacesameversion
;Mirgration script for saved games in Windows NT4 onwards
Source: migration.bat; DestDir: {app}; Flags: ignoreversion; MinVersion: 0, 1
Source: migration.txt; DestDir: {app}; Flags: ignoreversion; MinVersion: 0, 1
diff --git a/engines/access/detection.cpp b/engines/access/detection.cpp
index 368753f117..3e70de3635 100644
--- a/engines/access/detection.cpp
+++ b/engines/access/detection.cpp
@@ -111,7 +111,8 @@ bool AccessMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
- (f == kSavesSupportThumbnail);
+ (f == kSavesSupportThumbnail) ||
+ (f == kSimpleSavesNames);
}
bool Access::AccessEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/adl/adl.cpp b/engines/adl/adl.cpp
index 83181c9847..9afb2c6700 100644
--- a/engines/adl/adl.cpp
+++ b/engines/adl/adl.cpp
@@ -402,6 +402,15 @@ byte AdlEngine::roomArg(byte room) const {
return room;
}
+void AdlEngine::loadDroppedItemOffsets(Common::ReadStream &stream, byte count) {
+ for (uint i = 0; i < count; ++i) {
+ Common::Point p;
+ p.x = stream.readByte();
+ p.y = stream.readByte();
+ _itemOffsets.push_back(p);
+ }
+}
+
void AdlEngine::clearScreen() const {
_display->setMode(DISPLAY_MODE_MIXED);
_display->clear(0x00);
@@ -661,6 +670,11 @@ Common::Error AdlEngine::loadGameState(int slot) {
_state.rooms[i].isFirstTime = inFile->readByte();
}
+ // NOTE: _state.curPicture is part of the save state in the original engine. We
+ // reconstruct it instead. This is believed to be safe for at least hires 0-2, but
+ // this may need to be re-evaluated for later games.
+ _state.curPicture = _state.rooms[_state.room].curPicture;
+
size = inFile->readUint32BE();
if (size != _state.items.size())
error("Item count mismatch (expected %i; found %i)", _state.items.size(), size);
@@ -951,7 +965,7 @@ int AdlEngine::o1_isVarEQ(ScriptEnv &e) {
int AdlEngine::o1_isCurPicEQ(ScriptEnv &e) {
OP_DEBUG_1("\t&& GET_CURPIC() == %d", e.arg(1));
- if (getCurRoom().curPicture == e.arg(1))
+ if (_state.curPicture == e.arg(1))
return 1;
return -1;
@@ -1258,16 +1272,17 @@ Common::String AdlEngine::toAscii(const Common::String &str) {
}
Common::String AdlEngine::itemStr(uint i) const {
- byte desc = getItem(i).description;
- byte noun = getItem(i).noun;
+ const Item &item(getItem(i));
+
Common::String name = Common::String::format("%d", i);
- if (noun > 0) {
+ if (item.noun > 0) {
name += "/";
- name += _priNouns[noun - 1];
+ name += _priNouns[item.noun - 1];
}
- if (desc > 0) {
+ Common::String desc = getItemDescription(item);
+ if (!desc.empty()) {
name += "/";
- name += toAscii(loadMessage(desc));
+ name += toAscii(desc);
}
return name;
}
diff --git a/engines/adl/adl.h b/engines/adl/adl.h
index c9d77fcc62..971336ef50 100644
--- a/engines/adl/adl.h
+++ b/engines/adl/adl.h
@@ -165,11 +165,12 @@ struct State {
Common::Array<byte> vars;
byte room;
+ byte curPicture;
uint16 moves;
bool isDark;
Time time;
- State() : room(1), moves(1), isDark(false) { }
+ State() : room(1), curPicture(0), moves(1), isDark(false) { }
};
typedef Common::List<Command> Commands;
@@ -246,6 +247,7 @@ protected:
virtual void initState();
virtual byte roomArg(byte room) const;
virtual void advanceClock() { }
+ void loadDroppedItemOffsets(Common::ReadStream &stream, byte count);
// Opcodes
int o1_isItemInRoom(ScriptEnv &e);
diff --git a/engines/adl/adl_v2.cpp b/engines/adl/adl_v2.cpp
index 4fdf796701..979d794146 100644
--- a/engines/adl/adl_v2.cpp
+++ b/engines/adl/adl_v2.cpp
@@ -79,9 +79,9 @@ void AdlEngine_v2::setupOpcodeTables() {
Opcode(o1_listInv);
Opcode(o2_moveItem);
Opcode(o1_setRoom);
- Opcode(o1_setCurPic);
+ Opcode(o2_setCurPic);
// 0x08
- Opcode(o1_setPic);
+ Opcode(o2_setPic);
Opcode(o1_printMsg);
Opcode(o1_setLight);
Opcode(o1_setDark);
@@ -250,6 +250,8 @@ void AdlEngine_v2::loadRoom(byte roomNr) {
void AdlEngine_v2::showRoom() {
bool redrawPic = false;
+ _state.curPicture = getCurRoom().curPicture;
+
if (_state.room != _roomOnScreen) {
loadRoom(_state.room);
clearScreen();
@@ -257,15 +259,15 @@ void AdlEngine_v2::showRoom() {
if (!_state.isDark)
redrawPic = true;
} else {
- if (getCurRoom().curPicture != _picOnScreen || _itemRemoved)
+ if (_state.curPicture != _picOnScreen || _itemRemoved)
redrawPic = true;
}
if (redrawPic) {
_roomOnScreen = _state.room;
- _picOnScreen = getCurRoom().curPicture;
+ _picOnScreen = _state.curPicture;
- drawPic(getCurRoom().curPicture);
+ drawPic(_state.curPicture);
_itemRemoved = false;
_itemsOnScreen = 0;
@@ -336,7 +338,7 @@ void AdlEngine_v2::drawItems() {
Common::Array<byte>::const_iterator pic;
for (pic = item->roomPictures.begin(); pic != item->roomPictures.end(); ++pic) {
- if (*pic == getCurRoom().curPicture || *pic == IDI_ANY) {
+ if (*pic == _state.curPicture || *pic == IDI_ANY) {
drawItem(*item, item->position);
break;
}
@@ -357,9 +359,83 @@ DataBlockPtr AdlEngine_v2::readDataBlockPtr(Common::ReadStream &f) const {
if (track == 0 && sector == 0 && offset == 0 && size == 0)
return DataBlockPtr();
+ adjustDataBlockPtr(track, sector, offset, size);
+
return _disk->getDataBlock(track, sector, offset, size);
}
+void AdlEngine_v2::loadItems(Common::ReadStream &stream) {
+ byte id;
+ while ((id = stream.readByte()) != 0xff && !stream.eos() && !stream.err()) {
+ Item item = Item();
+ item.id = id;
+ item.noun = stream.readByte();
+ item.room = stream.readByte();
+ item.picture = stream.readByte();
+ item.isLineArt = stream.readByte(); // Disk number in later games
+ item.position.x = stream.readByte();
+ item.position.y = stream.readByte();
+ item.state = stream.readByte();
+ item.description = stream.readByte();
+
+ stream.readByte(); // Struct size
+
+ byte picListSize = stream.readByte();
+
+ // Flag to keep track of what has been drawn on the screen
+ stream.readByte();
+
+ for (uint i = 0; i < picListSize; ++i)
+ item.roomPictures.push_back(stream.readByte());
+
+ _state.items.push_back(item);
+ }
+
+ if (stream.eos() || stream.err())
+ error("Error loading items");
+}
+
+void AdlEngine_v2::loadRooms(Common::ReadStream &stream, byte count) {
+ for (uint i = 0; i < count; ++i) {
+ Room room;
+
+ stream.readByte(); // number
+ for (uint j = 0; j < 6; ++j)
+ room.connections[j] = stream.readByte();
+ room.data = readDataBlockPtr(stream);
+ room.picture = stream.readByte();
+ room.curPicture = stream.readByte();
+ room.isFirstTime = stream.readByte();
+
+ _state.rooms.push_back(room);
+ }
+
+ if (stream.eos() || stream.err())
+ error("Error loading rooms");
+}
+
+void AdlEngine_v2::loadMessages(Common::ReadStream &stream, byte count) {
+ for (uint i = 0; i < count; ++i)
+ _messages.push_back(readDataBlockPtr(stream));
+}
+
+void AdlEngine_v2::loadPictures(Common::ReadStream &stream) {
+ byte picNr;
+ while ((picNr = stream.readByte()) != 0xff) {
+ if (stream.eos() || stream.err())
+ error("Error reading global pic list");
+
+ _pictures[picNr] = readDataBlockPtr(stream);
+ }
+}
+
+void AdlEngine_v2::loadItemPictures(Common::ReadStream &stream, byte count) {
+ for (uint i = 0; i < count; ++i) {
+ stream.readByte(); // number
+ _itemPics.push_back(readDataBlockPtr(stream));
+ }
+}
+
int AdlEngine_v2::o2_isFirstTime(ScriptEnv &e) {
OP_DEBUG_0("\t&& IS_FIRST_TIME()");
@@ -425,6 +501,20 @@ int AdlEngine_v2::o2_moveItem(ScriptEnv &e) {
return 2;
}
+int AdlEngine_v2::o2_setCurPic(ScriptEnv &e) {
+ OP_DEBUG_1("\tSET_CURPIC(%d)", e.arg(1));
+
+ getCurRoom().curPicture = _state.curPicture = e.arg(1);
+ return 1;
+}
+
+int AdlEngine_v2::o2_setPic(ScriptEnv &e) {
+ OP_DEBUG_1("\tSET_PIC(%d)", e.arg(1));
+
+ getCurRoom().picture = getCurRoom().curPicture = _state.curPicture = e.arg(1);
+ return 1;
+}
+
int AdlEngine_v2::o2_moveAllItems(ScriptEnv &e) {
OP_DEBUG_2("\tMOVE_ALL_ITEMS(%s, %s)", itemRoomStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
diff --git a/engines/adl/adl_v2.h b/engines/adl/adl_v2.h
index f18972b74b..8f36b5cdb8 100644
--- a/engines/adl/adl_v2.h
+++ b/engines/adl/adl_v2.h
@@ -25,9 +25,6 @@
#include "adl/adl.h"
-// Note: this version of ADL redraws only when necessary, but
-// this is not currently implemented.
-
namespace Common {
class RandomSource;
}
@@ -55,6 +52,12 @@ protected:
void takeItem(byte noun);
virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
+ virtual void adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const { }
+ void loadItems(Common::ReadStream &stream);
+ void loadRooms(Common::ReadStream &stream, byte count);
+ void loadMessages(Common::ReadStream &stream, byte count);
+ void loadPictures(Common::ReadStream &stream);
+ void loadItemPictures(Common::ReadStream &stream, byte count);
void checkTextOverflow(char c);
@@ -64,6 +67,8 @@ protected:
int o2_isCarryingSomething(ScriptEnv &e);
int o2_moveItem(ScriptEnv &e);
+ int o2_setCurPic(ScriptEnv &e);
+ int o2_setPic(ScriptEnv &e);
int o2_moveAllItems(ScriptEnv &e);
int o2_save(ScriptEnv &e);
int o2_restore(ScriptEnv &e);
diff --git a/engines/adl/adl_v3.cpp b/engines/adl/adl_v3.cpp
index 623db661bc..ba9e4a063e 100644
--- a/engines/adl/adl_v3.cpp
+++ b/engines/adl/adl_v3.cpp
@@ -20,162 +20,50 @@
*
*/
-#include "common/random.h"
-#include "common/error.h"
-
#include "adl/adl_v3.h"
-#include "adl/display.h"
-#include "adl/graphics.h"
namespace Adl {
AdlEngine_v3::AdlEngine_v3(OSystem *syst, const AdlGameDescription *gd) :
- AdlEngine_v2(syst, gd),
- _curDisk(0) {
-}
-
-Common::String AdlEngine_v3::loadMessage(uint idx) const {
- Common::String str = AdlEngine_v2::loadMessage(idx);
-
- for (uint i = 0; i < str.size(); ++i) {
- const char *xorStr = "AVISDURGAN";
- str.setChar(str[i] ^ xorStr[i % strlen(xorStr)], i);
- }
-
- return str;
+ AdlEngine_v2(syst, gd) {
}
Common::String AdlEngine_v3::getItemDescription(const Item &item) const {
- return _itemDesc[item.id - 1];
+ return _itemDesc[item.description - 1];
}
-void AdlEngine_v3::applyDiskOffset(byte &track, byte &sector) const {
- sector += _diskOffsets[_curDisk].sector;
- if (sector >= 16) {
- sector -= 16;
- ++track;
- }
+void AdlEngine_v3::loadItemDescriptions(Common::SeekableReadStream &stream, byte count) {
+ int32 startPos = stream.pos();
+ uint16 baseAddr = stream.readUint16LE();
- track += _diskOffsets[_curDisk].track;
-}
-
-DataBlockPtr AdlEngine_v3::readDataBlockPtr(Common::ReadStream &f) const {
- byte track = f.readByte();
- byte sector = f.readByte();
- byte offset = f.readByte();
- byte size = f.readByte();
-
- if (f.eos() || f.err())
- error("Error reading DataBlockPtr");
+ // This code assumes that the first pointer points to a string that
+ // directly follows the pointer table
+ assert(baseAddr != 0);
+ baseAddr -= count * 2;
- if (track == 0 && sector == 0 && offset == 0 && size == 0)
- return DataBlockPtr();
+ for (uint i = 0; i < count; ++i) {
+ stream.seek(startPos + i * 2);
+ uint16 offset = stream.readUint16LE();
- applyDiskOffset(track, sector);
+ if (offset > 0) {
+ stream.seek(startPos + offset - baseAddr);
+ _itemDesc.push_back(readString(stream, 0xff));
+ } else
+ _itemDesc.push_back(Common::String());
+ }
- return _disk->getDataBlock(track, sector, offset, size);
+ if (stream.eos() || stream.err())
+ error("Error loading item descriptions");
}
typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v3> OpcodeV3;
-#define SetOpcodeTable(x) table = &x;
-#define Opcode(x) table->push_back(new OpcodeV3(this, &AdlEngine_v3::x))
-#define OpcodeUnImpl() table->push_back(new OpcodeV3(this, 0))
void AdlEngine_v3::setupOpcodeTables() {
- Common::Array<const Opcode *> *table = 0;
-
- SetOpcodeTable(_condOpcodes);
- // 0x00
- OpcodeUnImpl();
- Opcode(o2_isFirstTime);
- Opcode(o2_isRandomGT);
- Opcode(o3_isItemInRoom);
- // 0x04
- Opcode(o3_isNounNotInRoom);
- Opcode(o1_isMovesGT);
- Opcode(o1_isVarEQ);
- Opcode(o2_isCarryingSomething);
- // 0x08
- Opcode(o3_isVarGT);
- Opcode(o1_isCurPicEQ);
- Opcode(o3_skipOneCommand);
-
- SetOpcodeTable(_actOpcodes);
- // 0x00
- OpcodeUnImpl();
- Opcode(o1_varAdd);
- Opcode(o1_varSub);
- Opcode(o1_varSet);
- // 0x04
- Opcode(o1_listInv);
- Opcode(o3_moveItem);
- Opcode(o1_setRoom);
- Opcode(o1_setCurPic);
- // 0x08
- Opcode(o1_setPic);
- Opcode(o1_printMsg);
- Opcode(o3_dummy);
- Opcode(o3_setTextMode);
- // 0x0c
- Opcode(o2_moveAllItems);
- Opcode(o1_quit);
- Opcode(o3_dummy);
- Opcode(o2_save);
- // 0x10
- Opcode(o2_restore);
- Opcode(o1_restart);
- Opcode(o3_setDisk);
- Opcode(o3_dummy);
- // 0x14
- Opcode(o1_resetPic);
- Opcode(o1_goDirection<IDI_DIR_NORTH>);
- Opcode(o1_goDirection<IDI_DIR_SOUTH>);
- Opcode(o1_goDirection<IDI_DIR_EAST>);
- // 0x18
- Opcode(o1_goDirection<IDI_DIR_WEST>);
- Opcode(o1_goDirection<IDI_DIR_UP>);
- Opcode(o1_goDirection<IDI_DIR_DOWN>);
- Opcode(o1_takeItem);
- // 0x1c
- Opcode(o1_dropItem);
- Opcode(o1_setRoomPic);
- Opcode(o3_sound);
- OpcodeUnImpl();
- // 0x20
- Opcode(o2_initDisk);
-}
-
-int AdlEngine_v3::o3_isVarGT(ScriptEnv &e) {
- OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2));
-
- if (getVar(e.arg(1)) > e.arg(2))
- return 2;
-
- return -1;
-}
-
-int AdlEngine_v3::o3_skipOneCommand(ScriptEnv &e) {
- OP_DEBUG_0("\t&& SKIP_ONE_COMMAND()");
-
- _skipOneCommand = true;
- setVar(2, 0);
-
- return -1;
-}
-
-// FIXME: Rename "isLineArt" and look at code duplication
-int AdlEngine_v3::o3_isItemInRoom(ScriptEnv &e) {
- OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
-
- const Item &item = getItem(e.arg(1));
-
- if (e.arg(2) != IDI_ANY && item.isLineArt != _curDisk)
- return -1;
-
- if (item.room == roomArg(e.arg(2)))
- return 2;
-
- return -1;
+ AdlEngine_v2::setupOpcodeTables();
+ delete _condOpcodes[0x04];
+ _condOpcodes[0x04] = new OpcodeV3(this, &AdlEngine_v3::o3_isNounNotInRoom);
+ delete _actOpcodes[0x04];
+ _actOpcodes[0x04] = new OpcodeV3(this, &AdlEngine_v3::o3_listInv);
}
int AdlEngine_v3::o3_isNounNotInRoom(ScriptEnv &e) {
@@ -183,75 +71,28 @@ int AdlEngine_v3::o3_isNounNotInRoom(ScriptEnv &e) {
Common::List<Item>::const_iterator item;
- setVar(24, 0);
+ bool isAnItem = false;
- for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ for (item = _state.items.begin(); item != _state.items.end(); ++item) {
if (item->noun == e.getNoun()) {
- setVar(24, 1);
+ isAnItem = true;
if (item->room == roomArg(e.arg(1)))
return -1;
}
-
- return 1;
-}
-
-int AdlEngine_v3::o3_moveItem(ScriptEnv &e) {
- OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
-
- byte room = roomArg(e.arg(2));
-
- Item &item = getItem(e.arg(1));
-
- if (item.room == _roomOnScreen)
- _picOnScreen = 0;
-
- // Set items that move from inventory to a room to state "dropped"
- if (item.room == IDI_ANY && room != IDI_VOID_ROOM)
- item.state = IDI_ITEM_DROPPED;
-
- item.room = room;
- item.isLineArt = _curDisk;
- return 2;
-}
-
-int AdlEngine_v3::o3_dummy(ScriptEnv &e) {
- OP_DEBUG_0("\tDUMMY()");
-
- return 0;
-}
-
-int AdlEngine_v3::o3_setTextMode(ScriptEnv &e) {
- OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1));
-
- // TODO
- // 1: 4-line mode
- // 2: 24-line mode
-
- switch (e.arg(1)) {
- case 3:
- // We re-use the restarting flag here, to simulate a long jump
- _isRestarting = true;
- return -1;
}
- return 1;
+ return (isAnItem ? 1 : -1);
}
-int AdlEngine_v3::o3_setDisk(ScriptEnv &e) {
- OP_DEBUG_2("\tSET_DISK(%d, %d)", e.arg(1), e.arg(2));
-
- // TODO
- // Arg 1: disk
- // Arg 2: room
-
- return 2;
-}
+int AdlEngine_v3::o3_listInv(ScriptEnv &e) {
+ OP_DEBUG_0("\tLIST_INVENTORY()");
-int AdlEngine_v3::o3_sound(ScriptEnv &e) {
- OP_DEBUG_0("\tSOUND()");
+ Common::List<Item>::const_iterator item;
- // TODO
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->room == IDI_ANY)
+ printString(_itemDesc[item->description - 1]);
return 0;
}
diff --git a/engines/adl/adl_v3.h b/engines/adl/adl_v3.h
index 61dd5852e7..b0d40f3993 100644
--- a/engines/adl/adl_v3.h
+++ b/engines/adl/adl_v3.h
@@ -25,18 +25,6 @@
#include "adl/adl_v2.h"
-// Note: this version of ADL redraws only when necessary, but
-// this is not currently implemented.
-
-namespace Common {
-class RandomSource;
-}
-
-struct DiskOffset {
- byte track;
- byte sector;
-};
-
namespace Adl {
class AdlEngine_v3 : public AdlEngine_v2 {
@@ -48,27 +36,14 @@ protected:
// AdlEngine
virtual void setupOpcodeTables();
- virtual Common::String loadMessage(uint idx) const;
Common::String getItemDescription(const Item &item) const;
- // AdlEngine_v2
- virtual DataBlockPtr readDataBlockPtr(Common::ReadStream &f) const;
-
- void applyDiskOffset(byte &track, byte &sector) const;
+ void loadItemDescriptions(Common::SeekableReadStream &stream, byte count);
- int o3_isVarGT(ScriptEnv &e);
- int o3_isItemInRoom(ScriptEnv &e);
int o3_isNounNotInRoom(ScriptEnv &e);
- int o3_skipOneCommand(ScriptEnv &e);
- int o3_moveItem(ScriptEnv &e);
- int o3_dummy(ScriptEnv &e);
- int o3_setTextMode(ScriptEnv &e);
- int o3_setDisk(ScriptEnv &e);
- int o3_sound(ScriptEnv &e);
+ int o3_listInv(ScriptEnv &e);
Common::Array<Common::String> _itemDesc;
- byte _curDisk;
- Common::Array<DiskOffset> _diskOffsets;
};
} // End of namespace Adl
diff --git a/engines/adl/adl_v4.cpp b/engines/adl/adl_v4.cpp
new file mode 100644
index 0000000000..ed20c82513
--- /dev/null
+++ b/engines/adl/adl_v4.cpp
@@ -0,0 +1,258 @@
+/* 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/random.h"
+#include "common/error.h"
+
+#include "adl/adl_v4.h"
+#include "adl/display.h"
+#include "adl/graphics.h"
+
+namespace Adl {
+
+AdlEngine_v4::AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine_v3(syst, gd),
+ _curDisk(0) {
+}
+
+Common::String AdlEngine_v4::loadMessage(uint idx) const {
+ Common::String str = AdlEngine_v2::loadMessage(idx);
+
+ for (uint i = 0; i < str.size(); ++i) {
+ const char *xorStr = "AVISDURGAN";
+ str.setChar(str[i] ^ xorStr[i % strlen(xorStr)], i);
+ }
+
+ return str;
+}
+
+Common::String AdlEngine_v4::getItemDescription(const Item &item) const {
+ return _itemDesc[item.id - 1];
+}
+
+void AdlEngine_v4::applyDiskOffset(byte &track, byte &sector) const {
+ sector += _diskOffsets[_curDisk].sector;
+ if (sector >= 16) {
+ sector -= 16;
+ ++track;
+ }
+
+ track += _diskOffsets[_curDisk].track;
+}
+
+void AdlEngine_v4::adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const {
+ applyDiskOffset(track, sector);
+}
+
+typedef Common::Functor1Mem<ScriptEnv &, int, AdlEngine_v4> OpcodeV4;
+#define SetOpcodeTable(x) table = &x;
+#define Opcode(x) table->push_back(new OpcodeV4(this, &AdlEngine_v4::x))
+#define OpcodeUnImpl() table->push_back(new OpcodeV4(this, 0))
+
+void AdlEngine_v4::setupOpcodeTables() {
+ Common::Array<const Opcode *> *table = 0;
+
+ SetOpcodeTable(_condOpcodes);
+ // 0x00
+ OpcodeUnImpl();
+ Opcode(o2_isFirstTime);
+ Opcode(o2_isRandomGT);
+ Opcode(o4_isItemInRoom);
+ // 0x04
+ Opcode(o4_isNounNotInRoom);
+ Opcode(o1_isMovesGT);
+ Opcode(o1_isVarEQ);
+ Opcode(o2_isCarryingSomething);
+ // 0x08
+ Opcode(o4_isVarGT);
+ Opcode(o1_isCurPicEQ);
+ Opcode(o4_skipOneCommand);
+
+ SetOpcodeTable(_actOpcodes);
+ // 0x00
+ OpcodeUnImpl();
+ Opcode(o1_varAdd);
+ Opcode(o1_varSub);
+ Opcode(o1_varSet);
+ // 0x04
+ Opcode(o4_listInv);
+ Opcode(o4_moveItem);
+ Opcode(o1_setRoom);
+ Opcode(o2_setCurPic);
+ // 0x08
+ Opcode(o2_setPic);
+ Opcode(o1_printMsg);
+ Opcode(o4_dummy);
+ Opcode(o4_setTextMode);
+ // 0x0c
+ Opcode(o2_moveAllItems);
+ Opcode(o1_quit);
+ Opcode(o4_dummy);
+ Opcode(o2_save);
+ // 0x10
+ Opcode(o2_restore);
+ Opcode(o1_restart);
+ Opcode(o4_setDisk);
+ Opcode(o4_dummy);
+ // 0x14
+ Opcode(o1_resetPic);
+ Opcode(o1_goDirection<IDI_DIR_NORTH>);
+ Opcode(o1_goDirection<IDI_DIR_SOUTH>);
+ Opcode(o1_goDirection<IDI_DIR_EAST>);
+ // 0x18
+ Opcode(o1_goDirection<IDI_DIR_WEST>);
+ Opcode(o1_goDirection<IDI_DIR_UP>);
+ Opcode(o1_goDirection<IDI_DIR_DOWN>);
+ Opcode(o1_takeItem);
+ // 0x1c
+ Opcode(o1_dropItem);
+ Opcode(o1_setRoomPic);
+ Opcode(o4_sound);
+ OpcodeUnImpl();
+ // 0x20
+ Opcode(o2_initDisk);
+}
+
+int AdlEngine_v4::o4_isVarGT(ScriptEnv &e) {
+ OP_DEBUG_2("\t&& VARS[%d] > %d", e.arg(1), e.arg(2));
+
+ if (getVar(e.arg(1)) > e.arg(2))
+ return 2;
+
+ return -1;
+}
+
+int AdlEngine_v4::o4_skipOneCommand(ScriptEnv &e) {
+ OP_DEBUG_0("\t&& SKIP_ONE_COMMAND()");
+
+ _skipOneCommand = true;
+ setVar(2, 0);
+
+ return -1;
+}
+
+// FIXME: Rename "isLineArt" and look at code duplication
+int AdlEngine_v4::o4_isItemInRoom(ScriptEnv &e) {
+ OP_DEBUG_2("\t&& GET_ITEM_ROOM(%s) == %s", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
+
+ const Item &item = getItem(e.arg(1));
+
+ if (e.arg(2) != IDI_ANY && item.isLineArt != _curDisk)
+ return -1;
+
+ if (item.room == roomArg(e.arg(2)))
+ return 2;
+
+ return -1;
+}
+
+int AdlEngine_v4::o4_isNounNotInRoom(ScriptEnv &e) {
+ OP_DEBUG_1("\t&& NO_SUCH_ITEMS_IN_ROOM(%s)", itemRoomStr(e.arg(1)).c_str());
+
+ Common::List<Item>::const_iterator item;
+
+ setVar(24, 0);
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->noun == e.getNoun()) {
+ setVar(24, 1);
+
+ if (item->room == roomArg(e.arg(1)))
+ return -1;
+ }
+
+ return 1;
+}
+
+int AdlEngine_v4::o4_listInv(ScriptEnv &e) {
+ OP_DEBUG_0("\tLIST_INVENTORY()");
+
+ Common::List<Item>::const_iterator item;
+
+ for (item = _state.items.begin(); item != _state.items.end(); ++item)
+ if (item->room == IDI_ANY)
+ printString(_itemDesc[item->id - 1]);
+
+ return 0;
+}
+
+int AdlEngine_v4::o4_moveItem(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_ITEM_ROOM(%s, %s)", itemStr(e.arg(1)).c_str(), itemRoomStr(e.arg(2)).c_str());
+
+ byte room = roomArg(e.arg(2));
+
+ Item &item = getItem(e.arg(1));
+
+ if (item.room == _roomOnScreen)
+ _picOnScreen = 0;
+
+ // Set items that move from inventory to a room to state "dropped"
+ if (item.room == IDI_ANY && room != IDI_VOID_ROOM)
+ item.state = IDI_ITEM_DROPPED;
+
+ item.room = room;
+ item.isLineArt = _curDisk;
+ return 2;
+}
+
+int AdlEngine_v4::o4_dummy(ScriptEnv &e) {
+ OP_DEBUG_0("\tDUMMY()");
+
+ return 0;
+}
+
+int AdlEngine_v4::o4_setTextMode(ScriptEnv &e) {
+ OP_DEBUG_1("\tSET_TEXT_MODE(%d)", e.arg(1));
+
+ // TODO
+ // 1: 4-line mode
+ // 2: 24-line mode
+
+ switch (e.arg(1)) {
+ case 3:
+ // We re-use the restarting flag here, to simulate a long jump
+ _isRestarting = true;
+ return -1;
+ }
+
+ return 1;
+}
+
+int AdlEngine_v4::o4_setDisk(ScriptEnv &e) {
+ OP_DEBUG_2("\tSET_DISK(%d, %d)", e.arg(1), e.arg(2));
+
+ // TODO
+ // Arg 1: disk
+ // Arg 2: room
+
+ return 2;
+}
+
+int AdlEngine_v4::o4_sound(ScriptEnv &e) {
+ OP_DEBUG_0("\tSOUND()");
+
+ // TODO
+
+ return 0;
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/hires2.h b/engines/adl/adl_v4.h
index 50016725d6..79aa824d92 100644
--- a/engines/adl/hires2.h
+++ b/engines/adl/adl_v4.h
@@ -20,45 +20,52 @@
*
*/
-#ifndef ADL_HIRES2_H
-#define ADL_HIRES2_H
+#ifndef ADL_ADL_V4_H
+#define ADL_ADL_V4_H
-#include "common/str.h"
-
-#include "adl/adl_v2.h"
-#include "adl/disk.h"
+#include "adl/adl_v3.h"
namespace Common {
-class ReadStream;
-struct Point;
+class RandomSource;
}
+struct DiskOffset {
+ byte track;
+ byte sector;
+};
+
namespace Adl {
-#define IDS_HR2_DISK_IMAGE "WIZARD.DSK"
+class AdlEngine_v4 : public AdlEngine_v3 {
+public:
+ virtual ~AdlEngine_v4() { }
-#define IDI_HR2_NUM_ROOMS 135
-#define IDI_HR2_NUM_MESSAGES 255
-#define IDI_HR2_NUM_VARS 40
-#define IDI_HR2_NUM_ITEM_PICS 38
-#define IDI_HR2_NUM_ITEM_OFFSETS 16
+protected:
+ AdlEngine_v4(OSystem *syst, const AdlGameDescription *gd);
-// Messages used outside of scripts
-#define IDI_HR2_MSG_CANT_GO_THERE 123
-#define IDI_HR2_MSG_DONT_UNDERSTAND 19
-#define IDI_HR2_MSG_ITEM_DOESNT_MOVE 242
-#define IDI_HR2_MSG_ITEM_NOT_HERE 4
-#define IDI_HR2_MSG_THANKS_FOR_PLAYING 239
+ // AdlEngine
+ virtual void setupOpcodeTables();
+ virtual Common::String loadMessage(uint idx) const;
+ Common::String getItemDescription(const Item &item) const;
-class HiRes2Engine : public AdlEngine_v2 {
-public:
- HiRes2Engine(OSystem *syst, const AdlGameDescription *gd) : AdlEngine_v2(syst, gd) { }
+ // AdlEngine_v2
+ virtual void adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const;
-private:
- // AdlEngine
- void runIntro() const;
- void init();
- void initGameState();
+ void applyDiskOffset(byte &track, byte &sector) const;
+
+ int o4_isVarGT(ScriptEnv &e);
+ int o4_isItemInRoom(ScriptEnv &e);
+ int o4_isNounNotInRoom(ScriptEnv &e);
+ int o4_skipOneCommand(ScriptEnv &e);
+ int o4_listInv(ScriptEnv &e);
+ int o4_moveItem(ScriptEnv &e);
+ int o4_dummy(ScriptEnv &e);
+ int o4_setTextMode(ScriptEnv &e);
+ int o4_setDisk(ScriptEnv &e);
+ int o4_sound(ScriptEnv &e);
+
+ byte _curDisk;
+ Common::Array<DiskOffset> _diskOffsets;
};
} // End of namespace Adl
diff --git a/engines/adl/detection.cpp b/engines/adl/detection.cpp
index 6a85f98cf0..10812d79ea 100644
--- a/engines/adl/detection.cpp
+++ b/engines/adl/detection.cpp
@@ -76,6 +76,7 @@ static const PlainGameDescriptor adlGames[] = {
{ "hires0", "Hi-Res Adventure #0: Mission Asteroid" },
{ "hires1", "Hi-Res Adventure #1: Mystery House" },
{ "hires2", "Hi-Res Adventure #2: Wizard and the Princess" },
+ { "hires4", "Hi-Res Adventure #4: Ulysses and the Golden Fleece" },
{ "hires6", "Hi-Res Adventure #6: The Dark Crystal" },
{ 0, 0 }
};
@@ -92,7 +93,7 @@ static const AdlGameDescription gameDescriptions[] = {
},
Common::EN_ANY,
Common::kPlatformApple2,
- ADGF_UNSTABLE,
+ ADGF_TESTING,
GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES)
},
GAME_TYPE_HIRES1
@@ -106,7 +107,7 @@ static const AdlGameDescription gameDescriptions[] = {
},
Common::EN_ANY,
Common::kPlatformApple2,
- ADGF_UNSTABLE,
+ ADGF_TESTING,
GUIO2(GAMEOPTION_COLOR_DEFAULT_OFF, GAMEOPTION_SCANLINES)
},
GAME_TYPE_HIRES1
@@ -120,7 +121,7 @@ static const AdlGameDescription gameDescriptions[] = {
},
Common::EN_ANY,
Common::kPlatformApple2,
- ADGF_UNSTABLE,
+ ADGF_TESTING,
GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
},
GAME_TYPE_HIRES2
@@ -134,11 +135,26 @@ static const AdlGameDescription gameDescriptions[] = {
},
Common::EN_ANY,
Common::kPlatformApple2,
- ADGF_UNSTABLE,
+ ADGF_TESTING,
GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
},
GAME_TYPE_HIRES0
},
+ { // Hi-Res Adventure #4: Ulysses and the Golden Fleece - Atari 8-bit - Re-release
+ {
+ "hires4", 0,
+ {
+ { "ULYS1A.XFD", 0, "26365d2b06509fd21e7a7919e33f7199", 92160 },
+ // FIXME: Add sides 1B and 2C
+ AD_LISTEND
+ },
+ Common::EN_ANY,
+ Common::kPlatformAtariST, // FIXME
+ ADGF_UNSTABLE,
+ GUIO2(GAMEOPTION_COLOR_DEFAULT_ON, GAMEOPTION_SCANLINES)
+ },
+ GAME_TYPE_HIRES4
+ },
{ // Hi-Res Adventure #6: The Dark Crystal - Apple II - Roberta Williams Anthology
{
"hires6", 0,
@@ -189,6 +205,7 @@ bool AdlMetaEngine::hasFeature(MetaEngineFeature f) const {
case kSavesSupportThumbnail:
case kSavesSupportCreationDate:
case kSavesSupportPlayTime:
+ case kSimpleSavesNames:
return true;
default:
return false;
@@ -296,6 +313,8 @@ void AdlMetaEngine::removeSaveState(const char *target, int slot) const {
Engine *HiRes1Engine_create(OSystem *syst, const AdlGameDescription *gd);
Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd);
+Engine *HiRes0Engine_create(OSystem *syst, const AdlGameDescription *gd);
+Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd);
Engine *HiRes6Engine_create(OSystem *syst, const AdlGameDescription *gd);
bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *gd) const {
@@ -311,6 +330,12 @@ bool AdlMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameD
case GAME_TYPE_HIRES2:
*engine = HiRes2Engine_create(syst, adlGd);
break;
+ case GAME_TYPE_HIRES0:
+ *engine = HiRes0Engine_create(syst, adlGd);
+ break;
+ case GAME_TYPE_HIRES4:
+ *engine = HiRes4Engine_create(syst, adlGd);
+ break;
case GAME_TYPE_HIRES6:
*engine = HiRes6Engine_create(syst, adlGd);
break;
diff --git a/engines/adl/detection.h b/engines/adl/detection.h
index 533466c094..b4dc3c430f 100644
--- a/engines/adl/detection.h
+++ b/engines/adl/detection.h
@@ -35,6 +35,7 @@ enum GameType {
GAME_TYPE_HIRES0,
GAME_TYPE_HIRES1,
GAME_TYPE_HIRES2,
+ GAME_TYPE_HIRES4,
GAME_TYPE_HIRES6
};
diff --git a/engines/adl/disk.cpp b/engines/adl/disk.cpp
index 214f76aeae..d429556670 100644
--- a/engines/adl/disk.cpp
+++ b/engines/adl/disk.cpp
@@ -28,98 +28,54 @@
namespace Adl {
-const DataBlockPtr DiskImage_DSK::getDataBlock(uint track, uint sector, uint offset, uint size) const {
- return Common::SharedPtr<DiskImage::DataBlock>(new DiskImage::DataBlock(this, track, sector, offset, size));
-}
-
-Common::SeekableReadStream *DiskImage_DSK::createReadStream(uint track, uint sector, uint offset, uint size) const {
- _f->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
- Common::SeekableReadStream *stream = _f->readStream(size * _bytesPerSector + _bytesPerSector - offset);
-
- if (_f->eos() || _f->err())
- error("Error reading disk image");
-
- return stream;
-}
-
-bool DiskImage_DSK::open(const Common::String &filename) {
- assert(!_f->isOpen());
-
- if (!_f->open(filename))
- return false;
+static Common::SeekableReadStream *readImage(const Common::String &filename) {
+ Common::File *f = new Common::File;
- uint filesize = _f->size();
- switch (filesize) {
- case 143360:
- _tracks = 35;
- _sectorsPerTrack = 16;
- _bytesPerSector = 256;
- break;
- default:
- warning("Unrecognized disk image '%s' of size %d bytes", filename.c_str(), filesize);
- return false;
+ if (!f->open(filename)) {
+ delete f;
+ return nullptr;
}
- return true;
-}
-
-const DataBlockPtr DiskImage_NIB::getDataBlock(uint track, uint sector, uint offset, uint size) const {
- return Common::SharedPtr<DiskImage::DataBlock>(new DiskImage::DataBlock(this, track, sector, offset, size));
-}
-
-Common::SeekableReadStream *DiskImage_NIB::createReadStream(uint track, uint sector, uint offset, uint size) const {
- _memStream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
- Common::SeekableReadStream *stream = _memStream->readStream(size * _bytesPerSector + _bytesPerSector - offset);
-
- if (_memStream->eos() || _memStream->err())
- error("Error reading NIB image");
-
- return stream;
+ return f;
}
// 4-and-4 encoding (odd-even)
-static uint8 read44(Common::SeekableReadStream *f) {
+static uint8 read44(Common::SeekableReadStream &f) {
// 1s in the other fields, so we can just AND
- uint8 ret = f->readByte();
- return ((ret << 1) | 1) & f->readByte();
+ uint8 ret = f.readByte();
+ return ((ret << 1) | 1) & f.readByte();
}
-bool DiskImage_NIB::open(const Common::String &filename) {
- assert(!_f->isOpen());
+static Common::SeekableReadStream *readImage_NIB(const Common::String &filename) {
+ Common::File f;
- if (!_f->open(filename))
- return false;
+ if (!f.open(filename))
+ return nullptr;
- uint filesize = _f->size();
- switch (filesize) {
- case 232960:
- _tracks = 35;
- _sectorsPerTrack = 16; // we always pad it out
- _bytesPerSector = 256;
- break;
- default:
- error("Unrecognized NIB image '%s' of size %d bytes", filename.c_str(), filesize);
- }
+ if (f.size() != 232960)
+ error("Unrecognized NIB image '%s' of size %d bytes", filename.c_str(), f.size());
// starting at 0xaa, 32 is invalid (see below)
const byte c_5and3_lookup[] = { 32, 0, 32, 1, 2, 3, 32, 32, 32, 32, 32, 4, 5, 6, 32, 32, 7, 8, 32, 9, 10, 11, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 12, 13, 32, 32, 14, 15, 32, 16, 17, 18, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 19, 20, 32, 21, 22, 23, 32, 32, 32, 32, 32, 24, 25, 26, 32, 32, 27, 28, 32, 29, 30, 31 };
// starting at 0x96, 64 is invalid (see below)
const byte c_6and2_lookup[] = { 0, 1, 64, 64, 2, 3, 64, 4, 5, 6, 64, 64, 64, 64, 64, 64, 7, 8, 64, 64, 64, 9, 10, 11, 12, 13, 64, 64, 14, 15, 16, 17, 18, 19, 64, 20, 21, 22, 23, 24, 25, 26, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 27, 64, 28, 29, 30, 64, 64, 64, 31, 64, 64, 32, 33, 64, 34, 35, 36, 37, 38, 39, 40, 64, 64, 64, 64, 64, 41, 42, 43, 64, 44, 45, 46, 47, 48, 49, 50, 64, 64, 51, 52, 53, 54, 55, 56, 64, 57, 58, 59, 60, 61, 62, 63 };
- uint32 diskSize = _tracks * _sectorsPerTrack * _bytesPerSector;
- byte *diskImage = (byte *)calloc(diskSize, 1);
- _memStream = new Common::MemoryReadStream(diskImage, diskSize, DisposeAfterUse::YES);
+ // we always pad it out
+ const uint sectorsPerTrack = 16;
+ const uint bytesPerSector = 256;
+ const uint imageSize = 35 * sectorsPerTrack * bytesPerSector;
+ byte *const diskImage = (byte *)calloc(imageSize, 1);
bool sawAddress = false;
uint8 volNo, track, sector;
bool newStyle;
- while (_f->pos() < _f->size()) {
+ while (f.pos() < f.size()) {
// Read until we find two sync bytes.
- if (_f->readByte() != 0xd5 || _f->readByte() != 0xaa)
+ if (f.readByte() != 0xd5 || f.readByte() != 0xaa)
continue;
- byte prologue = _f->readByte();
+ byte prologue = f.readByte();
if (sawAddress && (prologue == 0xb5 || prologue == 0x96)) {
warning("NIB: data for %02x/%02x/%02x missing", volNo, track, sector);
@@ -140,21 +96,13 @@ bool DiskImage_NIB::open(const Common::String &filename) {
}
}
- volNo = read44(_f);
- track = read44(_f);
- sector = read44(_f);
- uint8 checksum = read44(_f);
+ volNo = read44(f);
+ track = read44(f);
+ sector = read44(f);
+ uint8 checksum = read44(f);
if ((volNo ^ track ^ sector) != checksum)
error("invalid NIB checksum");
- // FIXME: This is a hires0/hires2-specific hack.
- if (volNo == 0xfe) {
- if (track == 1)
- track = 2;
- else if (track == 2)
- track = 1;
- }
-
// Epilogue is de/aa plus a gap, but we don't care.
continue;
}
@@ -163,17 +111,17 @@ bool DiskImage_NIB::open(const Common::String &filename) {
// We should always find the data field after an address field.
// TODO: we ignore volNo?
- byte *output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector;
+ byte *output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector;
if (newStyle) {
// We hardcode the DOS 3.3 mapping here. TODO: Do we also need raw/prodos?
int raw2dos[16] = { 0, 7, 14, 6, 13, 5, 12, 4, 11, 3, 10, 2, 9, 1, 8, 15 };
sector = raw2dos[sector];
- output = diskImage + (track * _sectorsPerTrack + sector) * _bytesPerSector;
+ output = diskImage + (track * sectorsPerTrack + sector) * bytesPerSector;
// 6-and-2 uses 342 on-disk bytes
byte inbuffer[342];
- _f->read(inbuffer, 342);
+ f.read(inbuffer, 342);
byte oldVal = 0;
for (uint n = 0; n < 342; ++n) {
@@ -188,7 +136,7 @@ bool DiskImage_NIB::open(const Common::String &filename) {
inbuffer[n] = oldVal;
}
- byte checksum = _f->readByte();
+ byte checksum = f.readByte();
if (checksum < 0x96 || oldVal != c_6and2_lookup[checksum - 0x96])
warning("NIB: checksum mismatch @ (%x, %x)", track, sector);
@@ -208,7 +156,7 @@ bool DiskImage_NIB::open(const Common::String &filename) {
} else {
// 5-and-3 uses 410 on-disk bytes, decoding to just over 256 bytes
byte inbuffer[410];
- _f->read(inbuffer, 410);
+ f.read(inbuffer, 410);
bool truncated = false;
byte oldVal = 0;
@@ -218,16 +166,16 @@ bool DiskImage_NIB::open(const Common::String &filename) {
if (inbuffer[n] == 0xd5) {
// Early end of block.
truncated = true;
- _f->seek(-(410 - n), SEEK_CUR);
- warning("NIB: early end of block @ 0x%x (%x, %x)", _f->pos(), track, sector);
+ f.seek(-(410 - n), SEEK_CUR);
+ warning("NIB: early end of block @ 0x%x (%x, %x)", f.pos(), track, sector);
break;
}
byte val = c_5and3_lookup[inbuffer[n] - 0xaa];
if (val == 0x20) {
// Badly-encoded nibbles, stop trying to decode here.
truncated = true;
- warning("NIB: bad nibble %02x @ 0x%x (%x, %x)", inbuffer[n], _f->pos(), track, sector);
- _f->seek(-(410 - n), SEEK_CUR);
+ warning("NIB: bad nibble %02x @ 0x%x (%x, %x)", inbuffer[n], f.pos(), track, sector);
+ f.seek(-(410 - n), SEEK_CUR);
break;
}
// undo checksum
@@ -235,7 +183,7 @@ bool DiskImage_NIB::open(const Common::String &filename) {
inbuffer[n] = oldVal;
}
if (!truncated) {
- byte checksum = _f->readByte();
+ byte checksum = f.readByte();
if (checksum < 0xaa || oldVal != c_5and3_lookup[checksum - 0xaa])
warning("NIB: checksum mismatch @ (%x, %x)", track, sector);
}
@@ -259,9 +207,77 @@ bool DiskImage_NIB::open(const Common::String &filename) {
}
}
+ return new Common::MemoryReadStream(diskImage, imageSize, DisposeAfterUse::YES);
+}
+
+bool DiskImage::open(const Common::String &filename) {
+ Common::String lcName(filename);
+ lcName.toLowercase();
+
+ if (lcName.hasSuffix(".dsk")) {
+ _stream = readImage(filename);
+ _tracks = 35;
+ _sectorsPerTrack = 16;
+ _bytesPerSector = 256;
+ } else if (lcName.hasSuffix(".nib")) {
+ _stream = readImage_NIB(filename);
+ _tracks = 35;
+ _sectorsPerTrack = 16;
+ _bytesPerSector = 256;
+ } else if (lcName.hasSuffix(".xfd")) {
+ _stream = readImage(filename);
+ _tracks = 40;
+ _sectorsPerTrack = 18;
+ _bytesPerSector = 128;
+ }
+
+ int expectedSize = _tracks * _sectorsPerTrack * _bytesPerSector;
+
+ if (!_stream)
+ return false;
+
+ if (_stream->size() != expectedSize)
+ error("Unrecognized disk image '%s' of size %d bytes (expected %d bytes)", filename.c_str(), _stream->size(), expectedSize);
+
return true;
}
+const DataBlockPtr DiskImage::getDataBlock(uint track, uint sector, uint offset, uint size) const {
+ return DataBlockPtr(new DiskImage::DataBlock(this, track, sector, offset, size, _sectorLimit));
+}
+
+Common::SeekableReadStream *DiskImage::createReadStream(uint track, uint sector, uint offset, uint size, uint sectorLimit) const {
+ const uint bytesToRead = size * _bytesPerSector + _bytesPerSector - offset;
+ byte *const data = (byte *)malloc(bytesToRead);
+ uint dataOffset = 0;
+
+ if (sectorLimit == 0)
+ sectorLimit = _sectorsPerTrack;
+
+ if (sector >= sectorLimit)
+ error("Sector %i is out of bounds for %i-sector reading", sector, sectorLimit);
+
+ while (dataOffset < bytesToRead) {
+ uint bytesRemInTrack = (sectorLimit - 1 - sector) * _bytesPerSector + _bytesPerSector - offset;
+ _stream->seek((track * _sectorsPerTrack + sector) * _bytesPerSector + offset);
+
+ if (bytesToRead - dataOffset < bytesRemInTrack)
+ bytesRemInTrack = bytesToRead - dataOffset;
+
+ if (_stream->read(data + dataOffset, bytesRemInTrack) < bytesRemInTrack)
+ error("Error reading disk image at track %d; sector %d", track, sector);
+
+ ++track;
+
+ sector = 0;
+ offset = 0;
+
+ dataOffset += bytesRemInTrack;
+ }
+
+ return new Common::MemoryReadStream(data, bytesToRead, DisposeAfterUse::YES);
+}
+
const DataBlockPtr Files_Plain::getDataBlock(const Common::String &filename, uint offset) const {
return Common::SharedPtr<Files::DataBlock>(new Files::DataBlock(this, filename, offset));
}
@@ -449,7 +465,7 @@ Common::SeekableReadStream *Files_DOS33::createReadStream(const Common::String &
}
bool Files_DOS33::open(const Common::String &filename) {
- _disk = new DiskImage_DSK();
+ _disk = new DiskImage();
if (!_disk->open(filename))
return false;
diff --git a/engines/adl/disk.h b/engines/adl/disk.h
index 43b9e387ba..653d76ff10 100644
--- a/engines/adl/disk.h
+++ b/engines/adl/disk.h
@@ -73,41 +73,45 @@ protected:
class DiskImage {
public:
DiskImage() :
+ _stream(nullptr),
_tracks(0),
_sectorsPerTrack(0),
- _bytesPerSector(0) {
- _f = new Common::File();
- }
+ _bytesPerSector(0),
+ _sectorLimit(0) { }
- virtual ~DiskImage() {
- delete _f;
+ ~DiskImage() {
+ delete _stream;
}
- virtual bool open(const Common::String &filename) = 0;
- virtual const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const = 0;
- virtual Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const = 0;
+ bool open(const Common::String &filename);
+ const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
+ Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0, uint sectorsUsed = 0) const;
+ void setSectorLimit(uint sectorLimit) { _sectorLimit = sectorLimit; } // Maximum number of sectors to read per track before stepping
protected:
class DataBlock : public Adl::DataBlock {
public:
- DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size) :
+ DataBlock(const DiskImage *disk, uint track, uint sector, uint offset, uint size, uint sectorLimit) :
_track(track),
_sector(sector),
_offset(offset),
_size(size),
+ _sectorLimit(sectorLimit),
_disk(disk) { }
Common::SeekableReadStream *createReadStream() const {
- return _disk->createReadStream(_track, _sector, _offset, _size);
+ return _disk->createReadStream(_track, _sector, _offset, _size, _sectorLimit);
}
private:
uint _track, _sector, _offset, _size;
+ uint _sectorLimit;
const DiskImage *_disk;
};
- Common::File *_f;
+ Common::SeekableReadStream *_stream;
uint _tracks, _sectorsPerTrack, _bytesPerSector;
+ uint _sectorLimit;
};
// Data in plain files
@@ -117,30 +121,6 @@ public:
Common::SeekableReadStream *createReadStream(const Common::String &filename, uint offset = 0) const;
};
-// .DSK disk image - 35 tracks, 16 sectors per track, 256 bytes per sector
-class DiskImage_DSK : public DiskImage {
-public:
- bool open(const Common::String &filename);
- const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
- Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const;
-};
-
-// .NIB disk image
-class DiskImage_NIB : public DiskImage {
-public:
- DiskImage_NIB() : _memStream(nullptr) { }
- virtual ~DiskImage_NIB() {
- delete _memStream;
- }
-
- bool open(const Common::String &filename);
- const DataBlockPtr getDataBlock(uint track, uint sector, uint offset = 0, uint size = 0) const;
- Common::SeekableReadStream *createReadStream(uint track, uint sector, uint offset = 0, uint size = 0) const;
-
-private:
- Common::SeekableReadStream *_memStream;
-};
-
// Data in files contained in Apple DOS 3.3 disk image
class Files_DOS33 : public Files {
public:
diff --git a/engines/adl/display.cpp b/engines/adl/display.cpp
index 858d3ac20b..2cf50f72fc 100644
--- a/engines/adl/display.cpp
+++ b/engines/adl/display.cpp
@@ -55,6 +55,9 @@ static const byte colorPalette[COLOR_PALETTE_ENTRIES * 3] = {
0xf2, 0x5e, 0x00
};
+// Opacity of the optional scanlines (percentage)
+#define SCANLINE_OPACITY 75
+
// Corresponding color in second palette
#define PAL2(X) ((X) | 0x04)
@@ -133,6 +136,8 @@ Display::Display() :
_textBufSurface->create(DISPLAY_WIDTH * 2, DISPLAY_HEIGHT * 2, Graphics::PixelFormat::createFormatCLUT8());
createFont();
+
+ _startMillis = g_system->getMillis();
}
Display::~Display() {
@@ -332,14 +337,16 @@ void Display::writeFrameBuffer(const Common::Point &p, byte color, byte mask) {
}
void Display::showScanlines(bool enable) {
- byte pal[COLOR_PALETTE_ENTRIES * 3] = { };
+ byte pal[COLOR_PALETTE_ENTRIES * 3];
- if (enable)
- g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES);
- else {
- g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES);
- g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES);
+ g_system->getPaletteManager()->grabPalette(pal, 0, COLOR_PALETTE_ENTRIES);
+
+ if (enable) {
+ for (uint i = 0; i < ARRAYSIZE(pal); ++i)
+ pal[i] = pal[i] * (100 - SCANLINE_OPACITY) / 100;
}
+
+ g_system->getPaletteManager()->setPalette(pal, COLOR_PALETTE_ENTRIES, COLOR_PALETTE_ENTRIES);
}
static byte processColorBits(uint16 &bits, bool &odd, bool secondPal) {
@@ -487,7 +494,11 @@ void Display::updateTextSurface() {
r.translate(((c & 0x3f) % 16) * 7 * 2, (c & 0x3f) / 16 * 8 * 2);
if (!(c & 0x80)) {
- if (!(c & 0x40) || ((g_system->getMillis() / 270) & 1))
+ // Blink text. We subtract _startMillis to make this compatible
+ // with the event recorder, which returns offsetted values on
+ // playback.
+ const uint32 millisPassed = g_system->getMillis() - _startMillis;
+ if (!(c & 0x40) || ((millisPassed / 270) & 1))
r.translate(0, 4 * 8 * 2);
}
diff --git a/engines/adl/display.h b/engines/adl/display.h
index bc27b7cb6b..e761e63f2e 100644
--- a/engines/adl/display.h
+++ b/engines/adl/display.h
@@ -102,6 +102,7 @@ private:
Graphics::Surface *_font;
uint _cursorPos;
bool _showCursor;
+ uint32 _startMillis;
};
} // End of namespace Adl
diff --git a/engines/adl/hires0.cpp b/engines/adl/hires0.cpp
new file mode 100644
index 0000000000..9a0af05d20
--- /dev/null
+++ b/engines/adl/hires0.cpp
@@ -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.
+ *
+ */
+
+#include "common/textconsole.h"
+
+#include "adl/adl_v2.h"
+#include "adl/graphics.h"
+#include "adl/disk.h"
+
+namespace Adl {
+
+#define IDS_HR0_DISK_IMAGE "MISSION.NIB"
+
+#define IDI_HR0_NUM_ROOMS 43
+#define IDI_HR0_NUM_MESSAGES 142
+#define IDI_HR0_NUM_VARS 40
+#define IDI_HR0_NUM_ITEM_PICS 2
+#define IDI_HR0_NUM_ITEM_OFFSETS 16
+
+// Messages used outside of scripts
+#define IDI_HR0_MSG_CANT_GO_THERE 110
+#define IDI_HR0_MSG_DONT_UNDERSTAND 112
+#define IDI_HR0_MSG_ITEM_DOESNT_MOVE 114
+#define IDI_HR0_MSG_ITEM_NOT_HERE 115
+#define IDI_HR0_MSG_THANKS_FOR_PLAYING 113
+
+class HiRes0Engine : public AdlEngine_v2 {
+public:
+ HiRes0Engine(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine_v2(syst, gd) { }
+ ~HiRes0Engine() { }
+
+private:
+ // AdlEngine
+ void init();
+ void initGameState();
+};
+
+void HiRes0Engine::init() {
+ _graphics = new Graphics_v2(*_display);
+
+ _disk = new DiskImage();
+ if (!_disk->open(IDS_HR0_DISK_IMAGE))
+ error("Failed to open disk image '" IDS_HR0_DISK_IMAGE "'");
+
+ _disk->setSectorLimit(13);
+
+ // TODO: all these strings/offsets/etc are the same as hires2
+
+ StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 2));
+ loadMessages(*stream, IDI_HR0_NUM_MESSAGES);
+
+ // Read parser messages
+ stream.reset(_disk->createReadStream(0x1a, 0x1));
+ _strings.verbError = readStringAt(*stream, 0x4f);
+ _strings.nounError = readStringAt(*stream, 0x8e);
+ _strings.enterCommand = readStringAt(*stream, 0xbc);
+
+ // Read time string
+ stream.reset(_disk->createReadStream(0x19, 0x7, 0xd7));
+ _strings_v2.time = readString(*stream, 0xff);
+
+ // Read line feeds
+ stream.reset(_disk->createReadStream(0x19, 0xb, 0xf8, 1));
+ _strings.lineFeeds = readString(*stream);
+
+ // Read opcode strings
+ stream.reset(_disk->createReadStream(0x1a, 0x6, 0x00, 2));
+ _strings_v2.saveInsert = readStringAt(*stream, 0x5f);
+ _strings_v2.saveReplace = readStringAt(*stream, 0xe5);
+ _strings_v2.restoreInsert = readStringAt(*stream, 0x132);
+ _strings_v2.restoreReplace = readStringAt(*stream, 0x1c2);
+ _strings.playAgain = readStringAt(*stream, 0x225);
+ _strings.pressReturn = readStringAt(*stream, 0x25f);
+
+ _messageIds.cantGoThere = IDI_HR0_MSG_CANT_GO_THERE;
+ _messageIds.dontUnderstand = IDI_HR0_MSG_DONT_UNDERSTAND;
+ _messageIds.itemDoesntMove = IDI_HR0_MSG_ITEM_DOESNT_MOVE;
+ _messageIds.itemNotHere = IDI_HR0_MSG_ITEM_NOT_HERE;
+ _messageIds.thanksForPlaying = IDI_HR0_MSG_THANKS_FOR_PLAYING;
+
+ // Load global picture data
+ stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0));
+ loadPictures(*stream);
+
+ // Load item picture data
+ stream.reset(_disk->createReadStream(0x1e, 0x9, 0x05));
+ loadItemPictures(*stream, IDI_HR0_NUM_ITEM_PICS);
+
+ // Load commands from executable
+ stream.reset(_disk->createReadStream(0x1d, 0x7, 0x00, 2));
+ readCommands(*stream, _roomCommands);
+
+ stream.reset(_disk->createReadStream(0x1f, 0x7, 0x00, 3));
+ readCommands(*stream, _globalCommands);
+
+ // Load dropped item offsets
+ stream.reset(_disk->createReadStream(0x1b, 0x4, 0x15));
+ loadDroppedItemOffsets(*stream, IDI_HR0_NUM_ITEM_OFFSETS);
+
+ // Load verbs
+ stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 3));
+ loadWords(*stream, _verbs, _priVerbs);
+
+ // Load nouns
+ stream.reset(_disk->createReadStream(0x22, 0x2, 0x00, 2));
+ loadWords(*stream, _nouns, _priNouns);
+}
+
+void HiRes0Engine::initGameState() {
+ _state.vars.resize(IDI_HR0_NUM_VARS);
+
+ StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 2));
+ loadRooms(*stream, IDI_HR0_NUM_ROOMS);
+
+ stream.reset(_disk->createReadStream(0x21, 0x0));
+ loadItems(*stream);
+}
+
+Engine *HiRes0Engine_create(OSystem *syst, const AdlGameDescription *gd) {
+ return new HiRes0Engine(syst, gd);
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/hires1.cpp b/engines/adl/hires1.cpp
index 096d8ef496..217a9013ba 100644
--- a/engines/adl/hires1.cpp
+++ b/engines/adl/hires1.cpp
@@ -27,11 +27,105 @@
#include "common/stream.h"
#include "common/ptr.h"
-#include "adl/hires1.h"
+#include "adl/adl.h"
+#include "adl/graphics.h"
#include "adl/display.h"
namespace Adl {
+#define IDS_HR1_EXE_0 "AUTO LOAD OBJ"
+#define IDS_HR1_EXE_1 "ADVENTURE"
+#define IDS_HR1_LOADER "MYSTERY.HELLO"
+#define IDS_HR1_MESSAGES "MESSAGES"
+
+#define IDI_HR1_NUM_ROOMS 41
+#define IDI_HR1_NUM_PICS 97
+#define IDI_HR1_NUM_VARS 20
+#define IDI_HR1_NUM_ITEM_OFFSETS 21
+#define IDI_HR1_NUM_MESSAGES 168
+
+// Messages used outside of scripts
+#define IDI_HR1_MSG_CANT_GO_THERE 137
+#define IDI_HR1_MSG_DONT_UNDERSTAND 37
+#define IDI_HR1_MSG_ITEM_DOESNT_MOVE 151
+#define IDI_HR1_MSG_ITEM_NOT_HERE 152
+#define IDI_HR1_MSG_THANKS_FOR_PLAYING 140
+#define IDI_HR1_MSG_DONT_HAVE_IT 127
+#define IDI_HR1_MSG_GETTING_DARK 7
+
+#define IDI_HR1_OFS_STR_ENTER_COMMAND 0x5bbc
+#define IDI_HR1_OFS_STR_VERB_ERROR 0x5b4f
+#define IDI_HR1_OFS_STR_NOUN_ERROR 0x5b8e
+#define IDI_HR1_OFS_STR_PLAY_AGAIN 0x5f1e
+#define IDI_HR1_OFS_STR_CANT_GO_THERE 0x6c0a
+#define IDI_HR1_OFS_STR_DONT_HAVE_IT 0x6c31
+#define IDI_HR1_OFS_STR_DONT_UNDERSTAND 0x6c51
+#define IDI_HR1_OFS_STR_GETTING_DARK 0x6c7c
+#define IDI_HR1_OFS_STR_PRESS_RETURN 0x5f68
+#define IDI_HR1_OFS_STR_LINE_FEEDS 0x59d4
+
+#define IDI_HR1_OFS_PD_TEXT_0 0x005d
+#define IDI_HR1_OFS_PD_TEXT_1 0x012b
+#define IDI_HR1_OFS_PD_TEXT_2 0x016d
+#define IDI_HR1_OFS_PD_TEXT_3 0x0259
+
+#define IDI_HR1_OFS_INTRO_TEXT 0x0066
+#define IDI_HR1_OFS_GAME_OR_HELP 0x000f
+
+#define IDI_HR1_OFS_LOGO_0 0x1003
+#define IDI_HR1_OFS_LOGO_1 0x1800
+
+#define IDI_HR1_OFS_ITEMS 0x0100
+#define IDI_HR1_OFS_ROOMS 0x050a
+#define IDI_HR1_OFS_PICS 0x4b03
+#define IDI_HR1_OFS_CMDS_0 0x3c00
+#define IDI_HR1_OFS_CMDS_1 0x3d00
+#define IDI_HR1_OFS_MSGS 0x4d00
+
+#define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff
+#define IDI_HR1_OFS_CORNERS 0x4f00
+
+#define IDI_HR1_OFS_VERBS 0x3800
+#define IDI_HR1_OFS_NOUNS 0x0f00
+
+class HiRes1Engine : public AdlEngine {
+public:
+ HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine(syst, gd),
+ _files(nullptr),
+ _messageDelay(true) { }
+ ~HiRes1Engine() { delete _files; }
+
+private:
+ // AdlEngine
+ void runIntro() const;
+ void init();
+ void initGameState();
+ void restartGame();
+ void printString(const Common::String &str);
+ Common::String loadMessage(uint idx) const;
+ void printMessage(uint idx);
+ void drawItems();
+ void drawItem(Item &item, const Common::Point &pos);
+ void loadRoom(byte roomNr);
+ void showRoom();
+
+ void wordWrap(Common::String &str) const;
+
+ Files *_files;
+ Common::File _exe;
+ Common::Array<DataBlockPtr> _corners;
+ Common::Array<byte> _roomDesc;
+ bool _messageDelay;
+
+ struct {
+ Common::String cantGoThere;
+ Common::String dontHaveIt;
+ Common::String dontUnderstand;
+ Common::String gettingDark;
+ } _gameStrings;
+};
+
void HiRes1Engine::runIntro() const {
StreamPtr stream(_files->createReadStream(IDS_HR1_EXE_0));
@@ -183,12 +277,7 @@ void HiRes1Engine::init() {
// Load dropped item offsets
stream->seek(IDI_HR1_OFS_ITEM_OFFSETS);
- for (uint i = 0; i < IDI_HR1_NUM_ITEM_OFFSETS; ++i) {
- Common::Point p;
- p.x = stream->readByte();
- p.y = stream->readByte();
- _itemOffsets.push_back(p);
- }
+ loadDroppedItemOffsets(*stream, IDI_HR1_NUM_ITEM_OFFSETS);
// Load right-angle line art
stream->seek(IDI_HR1_OFS_CORNERS);
@@ -338,6 +427,7 @@ void HiRes1Engine::loadRoom(byte roomNr) {
}
void HiRes1Engine::showRoom() {
+ _state.curPicture = getCurRoom().curPicture;
clearScreen();
loadRoom(_state.room);
diff --git a/engines/adl/hires1.h b/engines/adl/hires1.h
deleted file mode 100644
index c060bc892e..0000000000
--- a/engines/adl/hires1.h
+++ /dev/null
@@ -1,134 +0,0 @@
-/* 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 ADL_HIRES1_H
-#define ADL_HIRES1_H
-
-#include "common/str.h"
-
-#include "adl/adl.h"
-#include "adl/graphics.h"
-#include "adl/disk.h"
-
-namespace Common {
-class ReadStream;
-struct Point;
-}
-
-namespace Adl {
-
-#define IDS_HR1_EXE_0 "AUTO LOAD OBJ"
-#define IDS_HR1_EXE_1 "ADVENTURE"
-#define IDS_HR1_LOADER "MYSTERY.HELLO"
-#define IDS_HR1_MESSAGES "MESSAGES"
-
-#define IDI_HR1_NUM_ROOMS 41
-#define IDI_HR1_NUM_PICS 97
-#define IDI_HR1_NUM_VARS 20
-#define IDI_HR1_NUM_ITEM_OFFSETS 21
-#define IDI_HR1_NUM_MESSAGES 168
-
-// Messages used outside of scripts
-#define IDI_HR1_MSG_CANT_GO_THERE 137
-#define IDI_HR1_MSG_DONT_UNDERSTAND 37
-#define IDI_HR1_MSG_ITEM_DOESNT_MOVE 151
-#define IDI_HR1_MSG_ITEM_NOT_HERE 152
-#define IDI_HR1_MSG_THANKS_FOR_PLAYING 140
-#define IDI_HR1_MSG_DONT_HAVE_IT 127
-#define IDI_HR1_MSG_GETTING_DARK 7
-
-#define IDI_HR1_OFS_STR_ENTER_COMMAND 0x5bbc
-#define IDI_HR1_OFS_STR_VERB_ERROR 0x5b4f
-#define IDI_HR1_OFS_STR_NOUN_ERROR 0x5b8e
-#define IDI_HR1_OFS_STR_PLAY_AGAIN 0x5f1e
-#define IDI_HR1_OFS_STR_CANT_GO_THERE 0x6c0a
-#define IDI_HR1_OFS_STR_DONT_HAVE_IT 0x6c31
-#define IDI_HR1_OFS_STR_DONT_UNDERSTAND 0x6c51
-#define IDI_HR1_OFS_STR_GETTING_DARK 0x6c7c
-#define IDI_HR1_OFS_STR_PRESS_RETURN 0x5f68
-#define IDI_HR1_OFS_STR_LINE_FEEDS 0x59d4
-
-#define IDI_HR1_OFS_PD_TEXT_0 0x005d
-#define IDI_HR1_OFS_PD_TEXT_1 0x012b
-#define IDI_HR1_OFS_PD_TEXT_2 0x016d
-#define IDI_HR1_OFS_PD_TEXT_3 0x0259
-
-#define IDI_HR1_OFS_INTRO_TEXT 0x0066
-#define IDI_HR1_OFS_GAME_OR_HELP 0x000f
-
-#define IDI_HR1_OFS_LOGO_0 0x1003
-#define IDI_HR1_OFS_LOGO_1 0x1800
-
-#define IDI_HR1_OFS_ITEMS 0x0100
-#define IDI_HR1_OFS_ROOMS 0x050a
-#define IDI_HR1_OFS_PICS 0x4b03
-#define IDI_HR1_OFS_CMDS_0 0x3c00
-#define IDI_HR1_OFS_CMDS_1 0x3d00
-#define IDI_HR1_OFS_MSGS 0x4d00
-
-#define IDI_HR1_OFS_ITEM_OFFSETS 0x68ff
-#define IDI_HR1_OFS_CORNERS 0x4f00
-
-#define IDI_HR1_OFS_VERBS 0x3800
-#define IDI_HR1_OFS_NOUNS 0x0f00
-
-class HiRes1Engine : public AdlEngine {
-public:
- HiRes1Engine(OSystem *syst, const AdlGameDescription *gd) :
- AdlEngine(syst, gd),
- _files(nullptr),
- _messageDelay(true) { }
- ~HiRes1Engine() { delete _files; }
-
-private:
- // AdlEngine
- void runIntro() const;
- void init();
- void initGameState();
- void restartGame();
- void printString(const Common::String &str);
- Common::String loadMessage(uint idx) const;
- void printMessage(uint idx);
- void drawItems();
- void drawItem(Item &item, const Common::Point &pos);
- void loadRoom(byte roomNr);
- void showRoom();
-
- void wordWrap(Common::String &str) const;
-
- Files *_files;
- Common::File _exe;
- Common::Array<DataBlockPtr> _corners;
- Common::Array<byte> _roomDesc;
- bool _messageDelay;
-
- struct {
- Common::String cantGoThere;
- Common::String dontHaveIt;
- Common::String dontUnderstand;
- Common::String gettingDark;
- } _gameStrings;
-};
-
-} // End of namespace Adl
-
-#endif
diff --git a/engines/adl/hires2.cpp b/engines/adl/hires2.cpp
index d8e8a65e29..199f457b4f 100644
--- a/engines/adl/hires2.cpp
+++ b/engines/adl/hires2.cpp
@@ -26,14 +26,44 @@
#include "common/file.h"
#include "common/stream.h"
-#include "adl/hires2.h"
+#include "adl/adl_v2.h"
#include "adl/display.h"
#include "adl/graphics.h"
#include "adl/disk.h"
namespace Adl {
+#define IDS_HR2_DISK_IMAGE "WIZARD.DSK"
+
+#define IDI_HR2_NUM_ROOMS 135
+#define IDI_HR2_NUM_MESSAGES 255
+#define IDI_HR2_NUM_VARS 40
+#define IDI_HR2_NUM_ITEM_PICS 38
+#define IDI_HR2_NUM_ITEM_OFFSETS 16
+
+// Messages used outside of scripts
+#define IDI_HR2_MSG_CANT_GO_THERE 123
+#define IDI_HR2_MSG_DONT_UNDERSTAND 19
+#define IDI_HR2_MSG_ITEM_DOESNT_MOVE 242
+#define IDI_HR2_MSG_ITEM_NOT_HERE 4
+#define IDI_HR2_MSG_THANKS_FOR_PLAYING 239
+
+class HiRes2Engine : public AdlEngine_v2 {
+public:
+ HiRes2Engine(OSystem *syst, const AdlGameDescription *gd) : AdlEngine_v2(syst, gd) { }
+
+private:
+ // AdlEngine
+ void runIntro() const;
+ void init();
+ void initGameState();
+};
+
void HiRes2Engine::runIntro() const {
+ // This only works for the 16-sector re-release. The original
+ // release is not supported at this time, because we don't have
+ // access to it.
+ _disk->setSectorLimit(0);
StreamPtr stream(_disk->createReadStream(0x00, 0xd, 0x17, 1));
_display->setMode(DISPLAY_MODE_TEXT);
@@ -45,19 +75,21 @@ void HiRes2Engine::runIntro() const {
_display->printString(str);
delay(2000);
+
+ _disk->setSectorLimit(13);
}
void HiRes2Engine::init() {
_graphics = new Graphics_v2(*_display);
- _disk = new DiskImage_DSK();
+ _disk = new DiskImage();
if (!_disk->open(IDS_HR2_DISK_IMAGE))
error("Failed to open disk image '" IDS_HR2_DISK_IMAGE "'");
- StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4));
+ _disk->setSectorLimit(13);
- for (uint i = 0; i < IDI_HR2_NUM_MESSAGES; ++i)
- _messages.push_back(readDataBlockPtr(*stream));
+ StreamPtr stream(_disk->createReadStream(0x1f, 0x2, 0x00, 4));
+ loadMessages(*stream, IDI_HR2_NUM_MESSAGES);
// Read parser messages
stream.reset(_disk->createReadStream(0x1a, 0x1));
@@ -90,20 +122,11 @@ void HiRes2Engine::init() {
// Load global picture data
stream.reset(_disk->createReadStream(0x19, 0xa, 0x80, 0));
- byte picNr;
- while ((picNr = stream->readByte()) != 0xff) {
- if (stream->eos() || stream->err())
- error("Error reading global pic list");
-
- _pictures[picNr] = readDataBlockPtr(*stream);
- }
+ loadPictures(*stream);
// Load item picture data
stream.reset(_disk->createReadStream(0x1e, 0x9, 0x05));
- for (uint i = 0; i < IDI_HR2_NUM_ITEM_PICS; ++i) {
- stream->readByte(); // number
- _itemPics.push_back(readDataBlockPtr(*stream));
- }
+ loadItemPictures(*stream, IDI_HR2_NUM_ITEM_PICS);
// Load commands from executable
stream.reset(_disk->createReadStream(0x1d, 0x7, 0x00, 4));
@@ -114,12 +137,7 @@ void HiRes2Engine::init() {
// Load dropped item offsets
stream.reset(_disk->createReadStream(0x1b, 0x4, 0x15));
- for (uint i = 0; i < IDI_HR2_NUM_ITEM_OFFSETS; ++i) {
- Common::Point p;
- p.x = stream->readByte();
- p.y = stream->readByte();
- _itemOffsets.push_back(p);
- }
+ loadDroppedItemOffsets(*stream, IDI_HR2_NUM_ITEM_OFFSETS);
// Load verbs
stream.reset(_disk->createReadStream(0x19, 0x0, 0x00, 3));
@@ -134,46 +152,10 @@ void HiRes2Engine::initGameState() {
_state.vars.resize(IDI_HR2_NUM_VARS);
StreamPtr stream(_disk->createReadStream(0x21, 0x5, 0x0e, 7));
-
- for (uint i = 0; i < IDI_HR2_NUM_ROOMS; ++i) {
- Room room;
- stream->readByte(); // number
- for (uint j = 0; j < 6; ++j)
- room.connections[j] = stream->readByte();
- room.data = readDataBlockPtr(*stream);
- room.picture = stream->readByte();
- room.curPicture = stream->readByte();
- room.isFirstTime = stream->readByte();
- _state.rooms.push_back(room);
- }
+ loadRooms(*stream, IDI_HR2_NUM_ROOMS);
stream.reset(_disk->createReadStream(0x21, 0x0, 0x00, 2));
-
- byte id;
- while ((id = stream->readByte()) != 0xff) {
- Item item = Item();
- item.id = id;
- item.noun = stream->readByte();
- item.room = stream->readByte();
- item.picture = stream->readByte();
- item.isLineArt = stream->readByte(); // Is this still used in this way?
- item.position.x = stream->readByte();
- item.position.y = stream->readByte();
- item.state = stream->readByte();
- item.description = stream->readByte();
-
- stream->readByte(); // Struct size
-
- byte picListSize = stream->readByte();
-
- // Flag to keep track of what has been drawn on the screen
- stream->readByte();
-
- for (uint i = 0; i < picListSize; ++i)
- item.roomPictures.push_back(stream->readByte());
-
- _state.items.push_back(item);
- }
+ loadItems(*stream);
}
Engine *HiRes2Engine_create(OSystem *syst, const AdlGameDescription *gd) {
diff --git a/engines/adl/hires4.cpp b/engines/adl/hires4.cpp
new file mode 100644
index 0000000000..ddfc868e9a
--- /dev/null
+++ b/engines/adl/hires4.cpp
@@ -0,0 +1,271 @@
+/* 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/debug.h"
+#include "common/error.h"
+#include "common/file.h"
+#include "common/stream.h"
+
+#include "adl/adl_v3.h"
+#include "adl/detection.h"
+#include "adl/display.h"
+#include "adl/graphics.h"
+#include "adl/disk.h"
+
+namespace Adl {
+
+#define IDI_HR4_NUM_ROOMS 164
+#define IDI_HR4_NUM_MESSAGES 255
+#define IDI_HR4_NUM_VARS 40
+#define IDI_HR4_NUM_ITEM_DESCS 44
+#define IDI_HR4_NUM_ITEM_PICS 41
+#define IDI_HR4_NUM_ITEM_OFFSETS 40
+
+// Messages used outside of scripts
+#define IDI_HR4_MSG_CANT_GO_THERE 110
+#define IDI_HR4_MSG_DONT_UNDERSTAND 112
+#define IDI_HR4_MSG_ITEM_DOESNT_MOVE 114
+#define IDI_HR4_MSG_ITEM_NOT_HERE 115
+#define IDI_HR4_MSG_THANKS_FOR_PLAYING 113
+
+class HiRes4Engine_Atari : public AdlEngine_v3 {
+public:
+ HiRes4Engine_Atari(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine_v3(syst, gd),
+ _boot(nullptr),
+ _curDisk(0) { }
+ ~HiRes4Engine_Atari();
+
+private:
+ // AdlEngine
+ void init();
+ void initGameState();
+ void loadRoom(byte roomNr);
+ Common::String formatVerbError(const Common::String &verb) const;
+ Common::String formatNounError(const Common::String &verb, const Common::String &noun) const;
+
+ // AdlEngine_v2
+ void adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const;
+
+ Common::SeekableReadStream *createReadStream(DiskImage *disk, byte track, byte sector, byte offset = 0, byte size = 0) const;
+ void loadCommonData();
+ void insertDisk(byte diskNr);
+ void rebindDisk();
+
+ DiskImage *_boot;
+ byte _curDisk;
+};
+
+static const char *const atariDisks[] = { "ULYS1A.XFD", "ULYS1B.XFD", "ULYS2C.XFD" };
+
+HiRes4Engine_Atari::~HiRes4Engine_Atari() {
+ delete _boot;
+}
+
+void HiRes4Engine_Atari::init() {
+ _graphics = new Graphics_v2(*_display);
+
+ _boot = new DiskImage();
+ if (!_boot->open(atariDisks[0]))
+ error("Failed to open disk image '%s'", atariDisks[0]);
+
+ insertDisk(1);
+ loadCommonData();
+
+ StreamPtr stream(createReadStream(_boot, 0x06, 0x2));
+ _strings.verbError = readStringAt(*stream, 0x4f);
+ _strings.nounError = readStringAt(*stream, 0x83);
+ _strings.enterCommand = readStringAt(*stream, 0xa6);
+
+ stream.reset(createReadStream(_boot, 0x05, 0xb, 0xd7));
+ _strings_v2.time = readString(*stream, 0xff);
+
+ stream.reset(createReadStream(_boot, 0x06, 0x7, 0x00, 2));
+ _strings_v2.saveInsert = readStringAt(*stream, 0x62);
+ _strings_v2.saveReplace = readStringAt(*stream, 0xdd);
+ _strings_v2.restoreInsert = readStringAt(*stream, 0x12a);
+ _strings_v2.restoreReplace = readStringAt(*stream, 0x1b8);
+ _strings.playAgain = readStringAt(*stream, 0x21b);
+ // TODO: restart sequence has "insert side a/b" strings
+
+ _messageIds.cantGoThere = IDI_HR4_MSG_CANT_GO_THERE;
+ _messageIds.dontUnderstand = IDI_HR4_MSG_DONT_UNDERSTAND;
+ _messageIds.itemDoesntMove = IDI_HR4_MSG_ITEM_DOESNT_MOVE;
+ _messageIds.itemNotHere = IDI_HR4_MSG_ITEM_NOT_HERE;
+ _messageIds.thanksForPlaying = IDI_HR4_MSG_THANKS_FOR_PLAYING;
+
+ stream.reset(createReadStream(_boot, 0x06, 0xd, 0x12, 2));
+ loadItemDescriptions(*stream, IDI_HR4_NUM_ITEM_DESCS);
+
+ stream.reset(createReadStream(_boot, 0x07, 0x1, 0xf4));
+ loadDroppedItemOffsets(*stream, IDI_HR4_NUM_ITEM_OFFSETS);
+
+ stream.reset(createReadStream(_boot, 0x08, 0xe, 0xa5, 5));
+ readCommands(*stream, _roomCommands);
+
+ stream.reset(createReadStream(_boot, 0x0a, 0x9, 0x00, 3));
+ readCommands(*stream, _globalCommands);
+
+ stream.reset(createReadStream(_boot, 0x05, 0x4, 0x00, 3));
+ loadWords(*stream, _verbs, _priVerbs);
+
+ stream.reset(createReadStream(_boot, 0x03, 0xb, 0x00, 6));
+ loadWords(*stream, _nouns, _priNouns);
+}
+
+void HiRes4Engine_Atari::loadRoom(byte roomNr) {
+ if (roomNr >= 59 && roomNr < 113) {
+ if (_curDisk != 2) {
+ insertDisk(2);
+ rebindDisk();
+ }
+ } else if (_curDisk != 1) {
+ insertDisk(1);
+ rebindDisk();
+ }
+
+ if (roomNr == 121) {
+ // Room 121 is not present in the Atari version. This causes
+ // problems when we're dumping scripts with the debugger, so
+ // we intercept this room load here.
+ // FIXME: Find out if the Apple II version does have this room
+ // FIXME: Implement more generic handling of invalid rooms?
+ debug("Warning: attempt to load non-existent room 121");
+ _roomData.description.clear();
+ _roomData.pictures.clear();
+ _roomData.commands.clear();
+ return;
+ }
+
+ AdlEngine_v3::loadRoom(roomNr);
+}
+
+Common::String HiRes4Engine_Atari::formatVerbError(const Common::String &verb) const {
+ Common::String err = _strings.verbError;
+ for (uint i = 0; i < verb.size(); ++i)
+ err.setChar(verb[i], i + 8);
+ return err;
+}
+
+Common::String HiRes4Engine_Atari::formatNounError(const Common::String &verb, const Common::String &noun) const {
+ Common::String err = _strings.nounError;
+ for (uint i = 0; i < verb.size(); ++i)
+ err.setChar(verb[i], i + 8);
+ for (uint i = 0; i < noun.size(); ++i)
+ err.setChar(noun[i], i + 19);
+ return err;
+}
+
+void HiRes4Engine_Atari::insertDisk(byte diskNr) {
+ if (_curDisk == diskNr)
+ return;
+
+ _curDisk = diskNr;
+
+ delete _disk;
+ _disk = new DiskImage();
+ if (!_disk->open(atariDisks[diskNr]))
+ error("Failed to open disk image '%s'", atariDisks[diskNr]);
+}
+
+void HiRes4Engine_Atari::rebindDisk() {
+ // As room.data is bound to the DiskImage, we need to rebind them here
+ // We cannot simply reload the rooms as that would reset their state
+
+ // FIXME: Remove DataBlockPtr-DiskImage coupling?
+
+ StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
+ for (uint i = 0; i < IDI_HR4_NUM_ROOMS; ++i) {
+ stream->skip(7);
+ _state.rooms[i].data = readDataBlockPtr(*stream);
+ stream->skip(3);
+ }
+
+ // Rebind data that is on both side B and C
+ loadCommonData();
+}
+
+void HiRes4Engine_Atari::loadCommonData() {
+ _messages.clear();
+ StreamPtr stream(createReadStream(_boot, 0x0a, 0x4, 0x00, 3));
+ loadMessages(*stream, IDI_HR4_NUM_MESSAGES);
+
+ _pictures.clear();
+ stream.reset(createReadStream(_boot, 0x05, 0xe, 0x80));
+ loadPictures(*stream);
+
+ _itemPics.clear();
+ stream.reset(createReadStream(_boot, 0x09, 0xe, 0x05));
+ loadItemPictures(*stream, IDI_HR4_NUM_ITEM_PICS);
+}
+
+void HiRes4Engine_Atari::initGameState() {
+ _state.vars.resize(IDI_HR4_NUM_VARS);
+
+ StreamPtr stream(createReadStream(_boot, 0x03, 0x1, 0x0e, 9));
+ loadRooms(*stream, IDI_HR4_NUM_ROOMS);
+
+ stream.reset(createReadStream(_boot, 0x02, 0xc, 0x00, 12));
+ loadItems(*stream);
+
+ // FIXME
+ _display->moveCursorTo(Common::Point(0, 23));
+}
+
+Common::SeekableReadStream *HiRes4Engine_Atari::createReadStream(DiskImage *disk, byte track, byte sector, byte offset, byte size) const {
+ adjustDataBlockPtr(track, sector, offset, size);
+ return disk->createReadStream(track, sector, offset, size);
+}
+
+void HiRes4Engine_Atari::adjustDataBlockPtr(byte &track, byte &sector, byte &offset, byte &size) const {
+ // Convert the Apple II disk offsets in the game, to Atari disk offsets
+ uint sectorIndex = (track * 16 + sector + 1) << 1;
+
+ // Atari uses 128 bytes per sector vs. 256 on the Apple II
+ // Note that size indicates *additional* sectors to read after reading one sector
+ size *= 2;
+
+ if (offset >= 128) {
+ // Offset in the second half of an Apple II sector, skip one sector and adjust offset
+ ++sectorIndex;
+ offset -= 128;
+ } else {
+ // Offset in the first half of an Apple II sector, we need to read one additional sector
+ ++size;
+ }
+
+ // Compute track/sector for Atari's 18 sectors per track (sectorIndex is 1-based)
+ track = (sectorIndex - 1) / 18;
+ sector = (sectorIndex - 1) % 18;
+}
+
+Engine *HiRes4Engine_create(OSystem *syst, const AdlGameDescription *gd) {
+ switch (gd->desc.platform) {
+ case Common::kPlatformAtariST:
+ return new HiRes4Engine_Atari(syst, gd);
+ default:
+ error("Unsupported platform");
+ }
+}
+
+} // End of namespace Adl
diff --git a/engines/adl/hires6.cpp b/engines/adl/hires6.cpp
index c42b4165a6..a1fea05f19 100644
--- a/engines/adl/hires6.cpp
+++ b/engines/adl/hires6.cpp
@@ -27,13 +27,65 @@
#include "common/stream.h"
#include "common/memstream.h"
-#include "adl/hires6.h"
+#include "adl/adl_v4.h"
#include "adl/display.h"
#include "adl/graphics.h"
#include "adl/disk.h"
namespace Adl {
+#define IDI_HR6_NUM_ROOMS 35
+#define IDI_HR6_NUM_MESSAGES 256
+#define IDI_HR6_NUM_VARS 40
+#define IDI_HR6_NUM_ITEM_DESCS 15
+#define IDI_HR6_NUM_ITEM_PICS 15
+#define IDI_HR6_NUM_ITEM_OFFSETS 16
+
+// Messages used outside of scripts
+#define IDI_HR6_MSG_CANT_GO_THERE 249
+#define IDI_HR6_MSG_DONT_UNDERSTAND 247
+#define IDI_HR6_MSG_ITEM_DOESNT_MOVE 253
+#define IDI_HR6_MSG_ITEM_NOT_HERE 254
+#define IDI_HR6_MSG_THANKS_FOR_PLAYING 252
+
+struct DiskDataDesc {
+ byte track;
+ byte sector;
+ byte offset;
+ byte volume;
+};
+
+class HiRes6Engine : public AdlEngine_v4 {
+public:
+ HiRes6Engine(OSystem *syst, const AdlGameDescription *gd) :
+ AdlEngine_v4(syst, gd),
+ _boot(nullptr),
+ _currVerb(0),
+ _currNoun(0) {
+ }
+
+ ~HiRes6Engine() { delete _boot; }
+
+private:
+ // AdlEngine
+ void runIntro() const;
+ void init();
+ void initGameState();
+ void printRoomDescription();
+ void showRoom();
+ Common::String formatVerbError(const Common::String &verb) const;
+ Common::String formatNounError(const Common::String &verb, const Common::String &noun) const;
+
+ // AdlEngine_v2
+ void printString(const Common::String &str);
+
+ void loadDisk(byte disk);
+
+ DiskImage *_boot;
+ byte _currVerb, _currNoun;
+ Common::Array<DiskDataDesc> _diskDataDesc;
+};
+
static const char *disks[] = { "DARK1A.DSK", "DARK1B.NIB", "DARK2A.NIB", "DARK2B.NIB" };
#define SECTORS_PER_TRACK 16
@@ -69,7 +121,7 @@ static Common::MemoryReadStream *loadSectors(DiskImage *disk, byte track, byte s
}
void HiRes6Engine::runIntro() const {
- DiskImage_DSK *boot(new DiskImage_DSK());
+ DiskImage *boot(new DiskImage());
if (!boot->open(disks[0]))
error("Failed to open disk image '%s'", disks[0]);
@@ -109,7 +161,7 @@ void HiRes6Engine::runIntro() const {
}
void HiRes6Engine::init() {
- _boot = new DiskImage_DSK();
+ _boot = new DiskImage();
_graphics = new Graphics_v2(*_display);
if (!_boot->open(disks[0]))
@@ -141,18 +193,12 @@ void HiRes6Engine::init() {
// Item descriptions
stream.reset(loadSectors(_boot, 0x6, 0xb, 2));
- stream->seek(0x34);
- for (uint i = 0; i < IDI_HR6_NUM_ITEM_DESCS; ++i)
- _itemDesc.push_back(readString(*stream, 0xff));
+ stream->seek(0x16);
+ loadItemDescriptions(*stream, IDI_HR6_NUM_ITEM_DESCS);
// Load dropped item offsets
stream.reset(_boot->createReadStream(0x8, 0x9, 0x16));
- for (uint i = 0; i < IDI_HR6_NUM_ITEM_OFFSETS; ++i) {
- Common::Point p;
- p.x = stream->readByte();
- p.y = stream->readByte();
- _itemOffsets.push_back(p);
- }
+ loadDroppedItemOffsets(*stream, IDI_HR6_NUM_ITEM_OFFSETS);
// Location of game data for each disc
stream.reset(_boot->createReadStream(0x5, 0xa, 0x03));
@@ -177,7 +223,7 @@ void HiRes6Engine::init() {
void HiRes6Engine::loadDisk(byte disk) {
delete _disk;
- _disk = new DiskImage_NIB();
+ _disk = new DiskImage();
if (!_disk->open(disks[disk]))
error("Failed to open disk image '%s'", disks[disk]);
@@ -187,10 +233,7 @@ void HiRes6Engine::loadDisk(byte disk) {
// Load item picture data (indexed on boot disk)
StreamPtr stream(_boot->createReadStream(0xb, 0xd, 0x08));
_itemPics.clear();
- for (uint i = 0; i < IDI_HR6_NUM_ITEM_PICS; ++i) {
- stream->readByte();
- _itemPics.push_back(readDataBlockPtr(*stream));
- }
+ loadItemPictures(*stream, IDI_HR6_NUM_ITEM_PICS);
_curDisk = disk;
@@ -214,19 +257,13 @@ void HiRes6Engine::loadDisk(byte disk) {
// Messages
_messages.clear();
uint count = size / 4;
- for (uint i = 0; i < count; ++i)
- _messages.push_back(readDataBlockPtr(*stream));
+ loadMessages(*stream, count);
break;
}
case 0x4a80: {
// Global pics
_pictures.clear();
- byte picNr;
- while ((picNr = stream->readByte()) != 0xff) {
- if (stream->eos() || stream->err())
- error("Error reading global pic list");
- _pictures[picNr] = readDataBlockPtr(*stream);
- }
+ loadPictures(*stream);
break;
}
case 0x4000:
@@ -243,17 +280,7 @@ void HiRes6Engine::loadDisk(byte disk) {
stream->skip(14); // Skip invalid room 0
_state.rooms.clear();
- for (uint i = 0; i < count; ++i) {
- Room room;
- stream->readByte(); // number
- for (uint j = 0; j < 6; ++j)
- room.connections[j] = stream->readByte();
- room.data = readDataBlockPtr(*stream);
- room.picture = stream->readByte();
- room.curPicture = stream->readByte();
- room.isFirstTime = stream->readByte();
- _state.rooms.push_back(room);
- }
+ loadRooms(*stream, count);
break;
}
case 0x7b00:
@@ -287,36 +314,14 @@ void HiRes6Engine::initGameState() {
StreamPtr stream(_boot->createReadStream(0x3, 0xe, 0x03));
- byte id;
- while ((id = stream->readByte()) != 0xff) {
- Item item = Item();
- item.id = id;
- item.noun = stream->readByte();
- item.room = stream->readByte();
- item.picture = stream->readByte();
- item.isLineArt = stream->readByte(); // Now seems to be disk number
- item.position.x = stream->readByte();
- item.position.y = stream->readByte();
- item.state = stream->readByte();
- item.description = stream->readByte();
-
- stream->readByte(); // Struct size
-
- byte picListSize = stream->readByte();
-
- // Flag to keep track of what has been drawn on the screen
- stream->readByte();
-
- for (uint i = 0; i < picListSize; ++i)
- item.roomPictures.push_back(stream->readByte());
-
- _state.items.push_back(item);
- }
+ loadItems(*stream);
_currVerb = _currNoun = 0;
}
void HiRes6Engine::showRoom() {
+ _state.curPicture = getCurRoom().curPicture;
+
bool redrawPic = false;
if (getVar(26) == 0xfe)
diff --git a/engines/adl/hires6.h b/engines/adl/hires6.h
deleted file mode 100644
index 4bd2bcc7cc..0000000000
--- a/engines/adl/hires6.h
+++ /dev/null
@@ -1,92 +0,0 @@
-/* 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 ADL_HIRES6_H
-#define ADL_HIRES6_H
-
-#include "common/str.h"
-
-#include "adl/adl_v3.h"
-#include "adl/disk.h"
-
-namespace Common {
-class ReadStream;
-struct Point;
-}
-
-namespace Adl {
-
-#define IDI_HR6_NUM_ROOMS 35
-#define IDI_HR6_NUM_MESSAGES 256
-#define IDI_HR6_NUM_VARS 40
-#define IDI_HR6_NUM_ITEM_DESCS 15
-#define IDI_HR6_NUM_ITEM_PICS 15
-#define IDI_HR6_NUM_ITEM_OFFSETS 16
-
-// Messages used outside of scripts
-#define IDI_HR6_MSG_CANT_GO_THERE 249
-#define IDI_HR6_MSG_DONT_UNDERSTAND 247
-#define IDI_HR6_MSG_ITEM_DOESNT_MOVE 253
-#define IDI_HR6_MSG_ITEM_NOT_HERE 254
-#define IDI_HR6_MSG_THANKS_FOR_PLAYING 252
-
-struct DiskDataDesc {
- byte track;
- byte sector;
- byte offset;
- byte volume;
-};
-
-class HiRes6Engine : public AdlEngine_v3 {
-public:
- HiRes6Engine(OSystem *syst, const AdlGameDescription *gd) :
- AdlEngine_v3(syst, gd),
- _boot(nullptr),
- _currVerb(0),
- _currNoun(0) {
- }
-
- ~HiRes6Engine() { delete _boot; }
-
-private:
- // AdlEngine
- void runIntro() const;
- void init();
- void initGameState();
- void printRoomDescription();
- void showRoom();
- Common::String formatVerbError(const Common::String &verb) const;
- Common::String formatNounError(const Common::String &verb, const Common::String &noun) const;
-
- // AdlEngine_v2
- void printString(const Common::String &str);
-
- void loadDisk(byte disk);
-
- DiskImage_DSK *_boot;
- byte _currVerb, _currNoun;
- Common::Array<DiskDataDesc> _diskDataDesc;
-};
-
-} // End of namespace Adl
-
-#endif
diff --git a/engines/adl/module.mk b/engines/adl/module.mk
index 7ab37efc67..d1de2a6c02 100644
--- a/engines/adl/module.mk
+++ b/engines/adl/module.mk
@@ -4,6 +4,7 @@ MODULE_OBJS := \
adl.o \
adl_v2.o \
adl_v3.o \
+ adl_v4.o \
console.o \
detection.o \
disk.o \
@@ -11,8 +12,10 @@ MODULE_OBJS := \
graphics.o \
graphics_v1.o \
graphics_v2.o \
+ hires0.o \
hires1.o \
hires2.o \
+ hires4.o \
hires6.o \
speaker.o
diff --git a/engines/agi/detection.cpp b/engines/agi/detection.cpp
index 9f66d78d80..5cb239f8d8 100644
--- a/engines/agi/detection.cpp
+++ b/engines/agi/detection.cpp
@@ -231,7 +231,8 @@ bool AgiMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
- (f == kSavesSupportPlayTime);
+ (f == kSavesSupportPlayTime) ||
+ (f == kSimpleSavesNames);
}
bool AgiBase::hasFeature(EngineFeature f) const {
diff --git a/engines/agos/detection.cpp b/engines/agos/detection.cpp
index 2c89522089..dbc4ee9145 100644
--- a/engines/agos/detection.cpp
+++ b/engines/agos/detection.cpp
@@ -125,7 +125,8 @@ public:
bool AgosMetaEngine::hasFeature(MetaEngineFeature f) const {
return
- (f == kSupportsListSaves);
+ (f == kSupportsListSaves) ||
+ (f == kSimpleSavesNames);
}
bool AGOS::AGOSEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/avalanche/detection.cpp b/engines/avalanche/detection.cpp
index e35c5d2cac..def395b77f 100644
--- a/engines/avalanche/detection.cpp
+++ b/engines/avalanche/detection.cpp
@@ -99,7 +99,8 @@ bool AvalancheMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsDeleteSave) ||
(f == kSupportsLoadingDuringStartup) ||
(f == kSavesSupportMetaInfo) ||
- (f == kSavesSupportThumbnail);
+ (f == kSavesSupportThumbnail) ||
+ (f == kSimpleSavesNames);
}
SaveStateList AvalancheMetaEngine::listSaves(const char *target) const {
diff --git a/engines/bbvs/detection.cpp b/engines/bbvs/detection.cpp
index 7c0045ee73..fa735c9ec3 100644
--- a/engines/bbvs/detection.cpp
+++ b/engines/bbvs/detection.cpp
@@ -97,7 +97,8 @@ bool BbvsMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsLoadingDuringStartup) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
- (f == kSavesSupportCreationDate);
+ (f == kSavesSupportCreationDate) ||
+ (f == kSimpleSavesNames);
}
void BbvsMetaEngine::removeSaveState(const char *target, int slot) const {
diff --git a/engines/cge/detection.cpp b/engines/cge/detection.cpp
index 82d27f8d54..0df1e8711e 100644
--- a/engines/cge/detection.cpp
+++ b/engines/cge/detection.cpp
@@ -180,7 +180,8 @@ bool CGEMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
- (f == kSavesSupportCreationDate);
+ (f == kSavesSupportCreationDate) ||
+ (f == kSimpleSavesNames);
}
void CGEMetaEngine::removeSaveState(const char *target, int slot) const {
diff --git a/engines/cge2/detection.cpp b/engines/cge2/detection.cpp
index 2b84d167c7..3701baa40f 100644
--- a/engines/cge2/detection.cpp
+++ b/engines/cge2/detection.cpp
@@ -185,7 +185,8 @@ bool CGE2MetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
(f == kSupportsListSaves) ||
- (f == kSupportsLoadingDuringStartup);
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSimpleSavesNames);
}
int CGE2MetaEngine::getMaximumSaveSlot() const {
diff --git a/engines/director/archive.cpp b/engines/director/archive.cpp
new file mode 100644
index 0000000000..5b22b45734
--- /dev/null
+++ b/engines/director/archive.cpp
@@ -0,0 +1,248 @@
+/* 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/macresman.h"
+
+#include "director/director.h"
+#include "director/resource.h"
+#include "director/lingo/lingo.h"
+
+namespace Director {
+
+Archive *DirectorEngine::createArchive() {
+ if (getPlatform() == Common::kPlatformMacintosh) {
+ if (getVersion() < 4)
+ return new MacArchive();
+ else
+ return new RIFXArchive();
+ } else {
+ return new RIFFArchive();
+ }
+}
+
+void DirectorEngine::loadMainArchive() {
+ if (getPlatform() == Common::kPlatformWindows)
+ loadEXE();
+ else
+ loadMac();
+}
+
+void DirectorEngine::cleanupMainArchive() {
+ delete _mainArchive;
+ delete _macBinary;
+}
+
+void DirectorEngine::loadEXE() {
+ Common::SeekableReadStream *exeStream = SearchMan.createReadStreamForMember(getEXEName());
+ if (!exeStream)
+ error("Failed to open EXE '%s'", getEXEName().c_str());
+
+ _lingo->processEvent(kEventStart, 0);
+
+ exeStream->seek(-4, SEEK_END);
+ exeStream->seek(exeStream->readUint32LE());
+
+ switch (getVersion()) {
+ case 3:
+ loadEXEv3(exeStream);
+ break;
+ case 4:
+ loadEXEv4(exeStream);
+ break;
+ case 5:
+ loadEXEv5(exeStream);
+ break;
+ case 7:
+ loadEXEv7(exeStream);
+ break;
+ default:
+ error("Unhandled Windows EXE version %d", getVersion());
+ }
+}
+
+void DirectorEngine::loadEXEv3(Common::SeekableReadStream *stream) {
+ uint16 entryCount = stream->readUint16LE();
+ if (entryCount != 1)
+ error("Unhandled multiple entry v3 EXE");
+
+ stream->skip(5); // unknown
+
+ stream->readUint32LE(); // Main MMM size
+ Common::String mmmFileName = readPascalString(*stream);
+ Common::String directoryName = readPascalString(*stream);
+
+ debugC(1, kDebugLoading, "Main MMM: '%s'", mmmFileName.c_str());
+ debugC(1, kDebugLoading, "Directory Name: '%s'", directoryName.c_str());
+
+ _mainArchive = new RIFFArchive();
+
+ if (!_mainArchive->openFile(mmmFileName))
+ error("Could not open '%s'", mmmFileName.c_str());
+
+ delete stream;
+}
+
+void DirectorEngine::loadEXEv4(Common::SeekableReadStream *stream) {
+ if (stream->readUint32BE() != MKTAG('P', 'J', '9', '3'))
+ error("Invalid projector tag found in v4 EXE");
+
+ uint32 rifxOffset = stream->readUint32LE();
+ /* uint32 fontMapOffset = */ stream->readUint32LE();
+ /* uint32 resourceForkOffset1 = */ stream->readUint32LE();
+ /* uint32 resourceForkOffset2 = */ stream->readUint32LE();
+ stream->readUint32LE(); // graphics DLL offset
+ stream->readUint32LE(); // sound DLL offset
+ /* uint32 rifxOffsetAlt = */ stream->readUint32LE(); // equivalent to rifxOffset
+
+ loadEXERIFX(stream, rifxOffset);
+}
+
+void DirectorEngine::loadEXEv5(Common::SeekableReadStream *stream) {
+ if (stream->readUint32LE() != MKTAG('P', 'J', '9', '5'))
+ error("Invalid projector tag found in v5 EXE");
+
+ uint32 rifxOffset = stream->readUint32LE();
+ stream->readUint32LE(); // unknown
+ stream->readUint32LE(); // unknown
+ stream->readUint32LE(); // unknown
+ /* uint16 screenWidth = */ stream->readUint16LE();
+ /* uint16 screenHeight = */ stream->readUint16LE();
+ stream->readUint32LE(); // unknown
+ stream->readUint32LE(); // unknown
+ /* uint32 fontMapOffset = */ stream->readUint32LE();
+
+ loadEXERIFX(stream, rifxOffset);
+}
+
+void DirectorEngine::loadEXEv7(Common::SeekableReadStream *stream) {
+ if (stream->readUint32LE() != MKTAG('P', 'J', '0', '0'))
+ error("Invalid projector tag found in v7 EXE");
+
+ uint32 rifxOffset = stream->readUint32LE();
+ stream->readUint32LE(); // unknown
+ stream->readUint32LE(); // unknown
+ stream->readUint32LE(); // unknown
+ stream->readUint32LE(); // unknown
+ stream->readUint32LE(); // some DLL offset
+
+ loadEXERIFX(stream, rifxOffset);
+}
+
+void DirectorEngine::loadEXERIFX(Common::SeekableReadStream *stream, uint32 offset) {
+ _mainArchive = new RIFXArchive();
+
+ if (!_mainArchive->openStream(stream, offset))
+ error("Failed to load RIFX from EXE");
+}
+
+void DirectorEngine::loadMac() {
+ if (getVersion() < 4) {
+ // The data is part of the resource fork of the executable
+ _mainArchive = new MacArchive();
+
+ if (!_mainArchive->openFile(getEXEName()))
+ error("Failed to open Mac binary '%s'", getEXEName().c_str());
+ } else {
+ // The RIFX is located in the data fork of the executable
+ _macBinary = new Common::MacResManager();
+
+ if (!_macBinary->open(getEXEName()) || !_macBinary->hasDataFork())
+ error("Failed to open Mac binary '%s'", getEXEName().c_str());
+
+ Common::SeekableReadStream *dataFork = _macBinary->getDataFork();
+ _mainArchive = new RIFXArchive();
+
+ // First we need to detect PPC vs. 68k
+
+ uint32 tag = dataFork->readUint32BE();
+ uint32 startOffset;
+
+ if (SWAP_BYTES_32(tag) == MKTAG('P', 'J', '9', '3') || tag == MKTAG('P', 'J', '9', '5') || tag == MKTAG('P', 'J', '0', '0')) {
+ // PPC: The RIFX shares the data fork with the binary
+ startOffset = dataFork->readUint32BE();
+ } else {
+ // 68k: The RIFX is the only thing in the data fork
+ startOffset = 0;
+ }
+
+ if (!_mainArchive->openStream(dataFork, startOffset))
+ error("Failed to load RIFX from Mac binary");
+ }
+}
+
+void DirectorEngine::loadSharedCastsFrom(Common::String filename) {
+ Archive *shardcst = createArchive();
+
+ debugC(1, kDebugLoading, "Loading Shared cast '%s'", filename.c_str());
+
+ shardcst->openFile(filename);
+
+ _sharedDIB = new Common::HashMap<int, Common::SeekableSubReadStreamEndian *>;
+ _sharedSTXT = new Common::HashMap<int, Common::SeekableSubReadStreamEndian *>;
+ _sharedSound = new Common::HashMap<int, Common::SeekableSubReadStreamEndian *>;
+ _sharedBMP = new Common::HashMap<int, Common::SeekableSubReadStreamEndian *>;
+
+ Score *castScore = new Score(this, shardcst);
+
+ castScore->loadConfig(*shardcst->getResource(MKTAG('V','W','C','F'), 1024));
+ castScore->loadCastData(*shardcst->getResource(MKTAG('V','W','C','R'), 1024));
+
+ _sharedCasts = &castScore->_casts;
+
+ Common::Array<uint16> dib = shardcst->getResourceIDList(MKTAG('D','I','B',' '));
+ if (dib.size() != 0) {
+ debugC(3, kDebugLoading, "Loading %d DIBs", dib.size());
+
+ for (Common::Array<uint16>::iterator iterator = dib.begin(); iterator != dib.end(); ++iterator) {
+ debugC(3, kDebugLoading, "Shared DIB %d", *iterator);
+ _sharedDIB->setVal(*iterator, shardcst->getResource(MKTAG('D','I','B',' '), *iterator));
+ }
+ }
+
+ Common::Array<uint16> stxt = shardcst->getResourceIDList(MKTAG('S','T','X','T'));
+ if (stxt.size() != 0) {
+ debugC(3, kDebugLoading, "Loading %d STXTs", stxt.size());
+
+ for (Common::Array<uint16>::iterator iterator = stxt.begin(); iterator != stxt.end(); ++iterator) {
+ debugC(3, kDebugLoading, "Shared STXT %d", *iterator);
+ _sharedSTXT->setVal(*iterator, shardcst->getResource(MKTAG('S','T','X','T'), *iterator));
+ }
+ }
+
+ Common::Array<uint16> bmp = shardcst->getResourceIDList(MKTAG('B','I','T','D'));
+ if (bmp.size() != 0) {
+ debugC(3, kDebugLoading, "Loading %d BITDs", bmp.size());
+ for (Common::Array<uint16>::iterator iterator = bmp.begin(); iterator != bmp.end(); ++iterator) {
+ _sharedBMP->setVal(*iterator, shardcst->getResource(MKTAG('B','I','T','D'), *iterator));
+ }
+ }
+
+ Common::Array<uint16> sound = shardcst->getResourceIDList(MKTAG('S','N','D',' '));
+ if (stxt.size() != 0) {
+ debugC(3, kDebugLoading, "Loading %d SNDs", sound.size());
+ for (Common::Array<uint16>::iterator iterator = sound.begin(); iterator != sound.end(); ++iterator) {
+ _sharedSound->setVal(*iterator, shardcst->getResource(MKTAG('S','N','D',' '), *iterator));
+ }
+ }
+}
+
+} // End of namespace Director
diff --git a/engines/director/detection_tables.h b/engines/director/detection_tables.h
index 65eff50fc9..e03b831fa3 100644
--- a/engines/director/detection_tables.h
+++ b/engines/director/detection_tables.h
@@ -40,6 +40,19 @@ static const DirectorGameDescription gameDescriptions[] = {
3
},
+ { // Generic D3 entry
+ {
+ "director",
+ "",
+ AD_ENTRY1("D3", 0),
+ Common::EN_ANY,
+ Common::kPlatformMacintosh,
+ ADGF_MACRESFORK,
+ GUIO1(GUIO_NOASPECT)
+ },
+ GID_GENERIC,
+ 3
+ },
{
{
"theapartment",
diff --git a/engines/director/director.cpp b/engines/director/director.cpp
index 469aeb80cb..c6b51bc452 100644
--- a/engines/director/director.cpp
+++ b/engines/director/director.cpp
@@ -20,35 +20,26 @@
*
*/
-#include "audio/mixer.h"
-
#include "common/config-manager.h"
-#include "common/debug.h"
-#include "common/scummsys.h"
+#include "common/debug-channels.h"
#include "common/error.h"
-#include "common/events.h"
-#include "common/macresman.h"
-#include "common/stream.h"
-#include "common/system.h"
-#include "common/textconsole.h"
-#include "common/fs.h"
-
-#include "engines/util.h"
-#include "graphics/surface.h"
#include "graphics/macgui/macwindowmanager.h"
#include "director/director.h"
-#include "director/dib.h"
#include "director/resource.h"
-#include "director/score.h"
-#include "director/lingo/lingo.h"
#include "director/sound.h"
+#include "director/lingo/lingo.h"
namespace Director {
DirectorEngine::DirectorEngine(OSystem *syst, const DirectorGameDescription *gameDesc) : Engine(syst), _gameDescription(gameDesc),
_rnd("director") {
+ DebugMan.addDebugChannel(kDebugLingoExec, "lingoexec", "Lingo Execution");
+ DebugMan.addDebugChannel(kDebugLingoCompile, "lingocompile", "Lingo Compilation");
+ DebugMan.addDebugChannel(kDebugLoading, "loading", "Loading");
+ DebugMan.addDebugChannel(kDebugImages, "images", "Image drawing");
+
if (!_mixer->isReady())
error("Sound initialization failed");
@@ -56,6 +47,14 @@ DirectorEngine::DirectorEngine(OSystem *syst, const DirectorGameDescription *gam
syncSoundSettings();
_sharedCasts = nullptr;
+
+ _currentScore = nullptr;
+ _soundManager = nullptr;
+ _currentPalette = nullptr;
+ _currentPaletteLength = 0;
+ _lingo = nullptr;
+
+ _sharedCasts = nullptr;
_sharedSound = nullptr;
_sharedBMP = nullptr;
_sharedSTXT = nullptr;
@@ -71,30 +70,28 @@ DirectorEngine::DirectorEngine(OSystem *syst, const DirectorGameDescription *gam
const Common::FSNode gameDataDir(ConfMan.get("path"));
SearchMan.addSubDirectoryMatching(gameDataDir, "data");
SearchMan.addSubDirectoryMatching(gameDataDir, "install");
+
+ _colorDepth = 8; // FIXME. Check if it is 8-bit
+ _keyCode = 0;
}
DirectorEngine::~DirectorEngine() {
- delete _sharedCasts;
delete _sharedSound;
delete _sharedBMP;
delete _sharedSTXT;
delete _sharedDIB;
- delete _movies;
- delete _mainArchive;
- delete _macBinary;
+ delete _currentScore;
+
+ cleanupMainArchive();
+
delete _soundManager;
delete _lingo;
- delete _currentScore;
- delete _currentPalette;
}
Common::Error DirectorEngine::run() {
debug("Starting v%d Director game", getVersion());
- //FIXME
- _sharedMMM = "SHARDCST.MMM";
-
_currentPalette = nullptr;
_macBinary = nullptr;
@@ -118,12 +115,12 @@ Common::Error DirectorEngine::run() {
//_mainArchive = new RIFFArchive();
//_mainArchive->openFile("bookshelf_example.mmm");
- if (getPlatform() == Common::kPlatformWindows)
- loadEXE();
- else
- loadMac();
+ scanMovies(ConfMan.get("path"));
+
+ loadSharedCastsFrom(_sharedCastFile);
+ loadMainArchive();
- _currentScore = new Score(this);
+ _currentScore = new Score(this, _mainArchive);
debug(0, "Score name %s", _currentScore->getMacName().c_str());
_currentScore->loadArchive();
@@ -132,168 +129,43 @@ Common::Error DirectorEngine::run() {
return Common::kNoError;
}
-Common::HashMap<Common::String, Score *> DirectorEngine::loadMMMNames(Common::String folder) {
+Common::HashMap<Common::String, Score *> DirectorEngine::scanMovies(const Common::String &folder) {
Common::FSNode directory(folder);
Common::FSList movies;
+ const char *sharedMMMname;
+
+ if (getPlatform() == Common::kPlatformWindows)
+ sharedMMMname = "SHARDCST.MMM";
+ else
+ sharedMMMname = "Shared Cast*";
+
Common::HashMap<Common::String, Score *> nameMap;
- directory.getChildren(movies, Common::FSNode::kListFilesOnly);
+ if (!directory.getChildren(movies, Common::FSNode::kListFilesOnly))
+ return nameMap;
if (!movies.empty()) {
for (Common::FSList::const_iterator i = movies.begin(); i != movies.end(); ++i) {
- if (i->getName() == _sharedMMM) {
- loadSharedCastsFrom(i->getPath());
+ debugC(2, kDebugLoading, "File: %s", i->getName().c_str());
+
+ if (Common::matchString(i->getName().c_str(), sharedMMMname, true)) {
+ _sharedCastFile = i->getName();
continue;
}
- RIFFArchive *arc = new RIFFArchive();
- arc->openFile(i->getPath());
- Score *sc = new Score(this);
+ Archive *arc = createArchive();
+
+ arc->openFile(i->getName());
+ Score *sc = new Score(this, arc);
nameMap[sc->getMacName()] = sc;
+
+ debugC(2, kDebugLoading, "Movie name: \"%s\"", sc->getMacName().c_str());
}
}
return nameMap;
}
-void DirectorEngine::loadEXE() {
- Common::SeekableReadStream *exeStream = SearchMan.createReadStreamForMember(getEXEName());
- if (!exeStream)
- error("Failed to open EXE '%s'", getEXEName().c_str());
-
- _lingo->processEvent(kEventStart, 0);
-
- exeStream->seek(-4, SEEK_END);
- exeStream->seek(exeStream->readUint32LE());
-
- switch (getVersion()) {
- case 3:
- loadEXEv3(exeStream);
- break;
- case 4:
- loadEXEv4(exeStream);
- break;
- case 5:
- loadEXEv5(exeStream);
- break;
- case 7:
- loadEXEv7(exeStream);
- break;
- default:
- error("Unhandled Windows EXE version %d", getVersion());
- }
-}
-
-void DirectorEngine::loadEXEv3(Common::SeekableReadStream *stream) {
- uint16 entryCount = stream->readUint16LE();
- if (entryCount != 1)
- error("Unhandled multiple entry v3 EXE");
-
- stream->skip(5); // unknown
-
- stream->readUint32LE(); // Main MMM size
- Common::String mmmFileName = readPascalString(*stream);
- Common::String directoryName = readPascalString(*stream);
-
- debug("Main MMM: '%s'", mmmFileName.c_str());
- debug("Directory Name: '%s'", directoryName.c_str());
-
- _mainArchive = new RIFFArchive();
-
- if (!_mainArchive->openFile(mmmFileName))
- error("Could not open '%s'", mmmFileName.c_str());
-
- delete stream;
-}
-
-void DirectorEngine::loadEXEv4(Common::SeekableReadStream *stream) {
- if (stream->readUint32BE() != MKTAG('P', 'J', '9', '3'))
- error("Invalid projector tag found in v4 EXE");
-
- uint32 rifxOffset = stream->readUint32LE();
- /* uint32 fontMapOffset = */ stream->readUint32LE();
- /* uint32 resourceForkOffset1 = */ stream->readUint32LE();
- /* uint32 resourceForkOffset2 = */ stream->readUint32LE();
- stream->readUint32LE(); // graphics DLL offset
- stream->readUint32LE(); // sound DLL offset
- /* uint32 rifxOffsetAlt = */ stream->readUint32LE(); // equivalent to rifxOffset
-
- loadEXERIFX(stream, rifxOffset);
-}
-
-void DirectorEngine::loadEXEv5(Common::SeekableReadStream *stream) {
- if (stream->readUint32LE() != MKTAG('P', 'J', '9', '5'))
- error("Invalid projector tag found in v5 EXE");
-
- uint32 rifxOffset = stream->readUint32LE();
- stream->readUint32LE(); // unknown
- stream->readUint32LE(); // unknown
- stream->readUint32LE(); // unknown
- /* uint16 screenWidth = */ stream->readUint16LE();
- /* uint16 screenHeight = */ stream->readUint16LE();
- stream->readUint32LE(); // unknown
- stream->readUint32LE(); // unknown
- /* uint32 fontMapOffset = */ stream->readUint32LE();
-
- loadEXERIFX(stream, rifxOffset);
-}
-
-void DirectorEngine::loadEXEv7(Common::SeekableReadStream *stream) {
- if (stream->readUint32LE() != MKTAG('P', 'J', '0', '0'))
- error("Invalid projector tag found in v7 EXE");
-
- uint32 rifxOffset = stream->readUint32LE();
- stream->readUint32LE(); // unknown
- stream->readUint32LE(); // unknown
- stream->readUint32LE(); // unknown
- stream->readUint32LE(); // unknown
- stream->readUint32LE(); // some DLL offset
-
- loadEXERIFX(stream, rifxOffset);
-}
-
-void DirectorEngine::loadEXERIFX(Common::SeekableReadStream *stream, uint32 offset) {
- _mainArchive = new RIFXArchive();
-
- if (!_mainArchive->openStream(stream, offset))
- error("Failed to load RIFX from EXE");
-}
-
-void DirectorEngine::loadMac() {
- if (getVersion() < 4) {
- // The data is part of the resource fork of the executable
- _mainArchive = new MacArchive();
-
- if (!_mainArchive->openFile(getEXEName()))
- error("Failed to open Mac binary '%s'", getEXEName().c_str());
- } else {
- // The RIFX is located in the data fork of the executable
- _macBinary = new Common::MacResManager();
-
- if (!_macBinary->open(getEXEName()) || !_macBinary->hasDataFork())
- error("Failed to open Mac binary '%s'", getEXEName().c_str());
-
- Common::SeekableReadStream *dataFork = _macBinary->getDataFork();
- _mainArchive = new RIFXArchive();
-
- // First we need to detect PPC vs. 68k
-
- uint32 tag = dataFork->readUint32BE();
- uint32 startOffset;
-
- if (SWAP_BYTES_32(tag) == MKTAG('P', 'J', '9', '3') || tag == MKTAG('P', 'J', '9', '5') || tag == MKTAG('P', 'J', '0', '0')) {
- // PPC: The RIFX shares the data fork with the binary
- startOffset = dataFork->readUint32BE();
- } else {
- // 68k: The RIFX is the only thing in the data fork
- startOffset = 0;
- }
-
- if (!_mainArchive->openStream(dataFork, startOffset))
- error("Failed to load RIFX from Mac binary");
- }
-}
-
Common::String DirectorEngine::readPascalString(Common::SeekableReadStream &stream) {
byte length = stream.readByte();
Common::String x;
@@ -309,60 +181,4 @@ void DirectorEngine::setPalette(byte *palette, uint16 count) {
_currentPaletteLength = count;
}
-void DirectorEngine::loadSharedCastsFrom(Common::String filename) {
- Archive *shardcst;
-
- if (getVersion() < 4) {
- shardcst = new RIFFArchive();
- } else {
- shardcst = new RIFXArchive();
- }
-
- shardcst->openFile(filename);
-
- Score *castScore = new Score(this);
- Common::SeekableSubReadStreamEndian *castStream = shardcst->getResource(MKTAG('V','W','C','R'), 1024);
-
- castScore->loadCastData(*castStream);
- *_sharedCasts = castScore->_casts;
-
- Common::Array<uint16> dib = shardcst->getResourceIDList(MKTAG('D','I','B',' '));
-
- if (dib.size() != 0) {
- Common::Array<uint16>::iterator iterator;
- for (iterator = dib.begin(); iterator != dib.end(); ++iterator) {
- debug(3, "Shared DIB %d", *iterator);
- _sharedDIB->setVal(*iterator, shardcst->getResource(MKTAG('D','I','B',' '), *iterator));
- }
- }
-
- Common::Array<uint16> stxt = shardcst->getResourceIDList(MKTAG('S','T','X','T'));
-
- if (stxt.size() != 0) {
- Common::Array<uint16>::iterator iterator;
- for (iterator = stxt.begin(); iterator != stxt.end(); ++iterator) {
- debug(3, "Shared STXT %d", *iterator);
- _sharedSTXT->setVal(*iterator, shardcst->getResource(MKTAG('S','T','X','T'), *iterator));
- }
- }
-
- Common::Array<uint16> bmp = shardcst->getResourceIDList(MKTAG('B','I','T','D'));
-
- if (bmp.size() != 0) {
- Common::Array<uint16>::iterator iterator;
- for (iterator = bmp.begin(); iterator != bmp.end(); ++iterator) {
- _sharedBMP->setVal(*iterator, shardcst->getResource(MKTAG('B','I','T','D'), *iterator));
- }
- }
-
- Common::Array<uint16> sound = shardcst->getResourceIDList(MKTAG('S','N','D',' '));
-
- if (stxt.size() != 0) {
- Common::Array<uint16>::iterator iterator;
- for (iterator = sound.begin(); iterator != sound.end(); ++iterator) {
- _sharedSound->setVal(*iterator, shardcst->getResource(MKTAG('S','N','D',' '), *iterator));
- }
- }
-}
-
} // End of namespace Director
diff --git a/engines/director/director.h b/engines/director/director.h
index 6208df2197..5a28a9e267 100644
--- a/engines/director/director.h
+++ b/engines/director/director.h
@@ -23,14 +23,11 @@
#ifndef DIRECTOR_DIRECTOR_H
#define DIRECTOR_DIRECTOR_H
-#include "common/scummsys.h"
#include "common/random.h"
#include "common/substream.h"
-#include "common/str.h"
#include "common/hashmap.h"
#include "engines/engine.h"
-#include "engines/director/sound.h"
namespace Common {
class MacResManager;
@@ -49,10 +46,19 @@ enum DirectorGameID {
class Archive;
struct DirectorGameDescription;
+class DirectorSound;
class Lingo;
class Score;
struct Cast;
+enum {
+ kDebugLingoExec = 1 << 0,
+ kDebugLingoCompile = 1 << 1,
+ kDebugLoading = 1 << 2,
+ kDebugImages = 1 << 3
+};
+
+
class DirectorEngine : public ::Engine {
public:
DirectorEngine(OSystem *syst, const DirectorGameDescription *gameDesc);
@@ -74,6 +80,11 @@ public:
const byte *getPalette() const { return _currentPalette; }
uint16 getPaletteColorCount() const { return _currentPaletteLength; }
void loadSharedCastsFrom(Common::String filename);
+
+ void loadMainArchive();
+ Archive *createArchive();
+ void cleanupMainArchive();
+
Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *getSharedDIB() const { return _sharedDIB; }
Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *getSharedBMP() const { return _sharedBMP; }
Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *getSharedSTXT() const { return _sharedSTXT; }
@@ -85,13 +96,17 @@ public:
Common::RandomSource _rnd;
Graphics::MacWindowManager *_wm;
+public:
+ int _colorDepth;
+ int _keyCode;
+
protected:
virtual Common::Error run();
private:
const DirectorGameDescription *_gameDescription;
- Common::HashMap<Common::String, Score *> loadMMMNames(Common::String folder);
+ Common::HashMap<Common::String, Score *> scanMovies(const Common::String &folder);
void loadEXE();
void loadEXEv3(Common::SeekableReadStream *stream);
void loadEXEv4(Common::SeekableReadStream *stream);
@@ -102,7 +117,6 @@ private:
Common::String readPascalString(Common::SeekableReadStream &stream);
- Common::String _sharedMMM;
Common::HashMap<int, Cast *> *_sharedCasts;
Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *_sharedDIB;
Common::HashMap<int, Common::SeekableSubReadStreamEndian *> *_sharedSTXT;
@@ -115,6 +129,8 @@ private:
byte *_currentPalette;
uint16 _currentPaletteLength;
Lingo *_lingo;
+
+ Common::String _sharedCastFile;
};
} // End of namespace Director
diff --git a/engines/director/frame.cpp b/engines/director/frame.cpp
new file mode 100644
index 0000000000..342e524805
--- /dev/null
+++ b/engines/director/frame.cpp
@@ -0,0 +1,782 @@
+/* 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 "graphics/font.h"
+#include "graphics/macgui/macwindowmanager.h"
+#include "image/bmp.h"
+
+#include "director/director.h"
+#include "director/frame.h"
+#include "director/images.h"
+#include "director/resource.h"
+#include "director/score.h"
+#include "director/sprite.h"
+
+namespace Director {
+
+Frame::Frame(DirectorEngine *vm) {
+ _vm = vm;
+ _transDuration = 0;
+ _transType = kTransNone;
+ _transArea = 0;
+ _transChunkSize = 0;
+ _tempo = 0;
+
+ _sound1 = 0;
+ _sound2 = 0;
+ _soundType1 = 0;
+ _soundType2 = 0;
+
+ _actionId = 0;
+ _skipFrameFlag = 0;
+ _blend = 0;
+
+ _palette = NULL;
+
+ _sprites.resize(CHANNEL_COUNT);
+
+ for (uint16 i = 0; i < _sprites.size(); i++) {
+ Sprite *sp = new Sprite();
+ _sprites[i] = sp;
+ }
+}
+
+Frame::Frame(const Frame &frame) {
+ _vm = frame._vm;
+ _actionId = frame._actionId;
+ _transArea = frame._transArea;
+ _transDuration = frame._transDuration;
+ _transType = frame._transType;
+ _transChunkSize = frame._transChunkSize;
+ _tempo = frame._tempo;
+ _sound1 = frame._sound1;
+ _sound2 = frame._sound2;
+ _soundType1 = frame._soundType1;
+ _soundType2 = frame._soundType2;
+ _skipFrameFlag = frame._skipFrameFlag;
+ _blend = frame._blend;
+ _palette = new PaletteInfo();
+
+ debugC(1, kDebugLoading, "Frame. action: %d transType: %d transDuration: %d", _actionId, _transType, _transDuration);
+
+ _sprites.resize(CHANNEL_COUNT);
+
+ for (uint16 i = 0; i < CHANNEL_COUNT; i++) {
+ _sprites[i] = new Sprite(*frame._sprites[i]);
+ }
+}
+
+Frame::~Frame() {
+ delete _palette;
+}
+
+void Frame::readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) {
+ if (offset >= 32) {
+ if (size <= 16)
+ readSprite(stream, offset, size);
+ else {
+ // read > 1 sprites channel
+ while (size > 16) {
+ byte spritePosition = (offset - 32) / 16;
+ uint16 nextStart = (spritePosition + 1) * 16 + 32;
+ uint16 needSize = nextStart - offset;
+ readSprite(stream, offset, needSize);
+ offset += needSize;
+ size -= needSize;
+ }
+ readSprite(stream, offset, size);
+ }
+ } else {
+ readMainChannels(stream, offset, size);
+ }
+}
+
+void Frame::readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) {
+ uint16 finishPosition = offset + size;
+
+ while (offset < finishPosition) {
+ switch(offset) {
+ case kScriptIdPosition:
+ _actionId = stream.readByte();
+ offset++;
+ break;
+ case kSoundType1Position:
+ _soundType1 = stream.readByte();
+ offset++;
+ break;
+ case kTransFlagsPosition: {
+ uint8 transFlags = stream.readByte();
+ if (transFlags & 0x80)
+ _transArea = 1;
+ else
+ _transArea = 0;
+ _transDuration = transFlags & 0x7f;
+ offset++;
+ }
+ break;
+ case kTransChunkSizePosition:
+ _transChunkSize = stream.readByte();
+ offset++;
+ break;
+ case kTempoPosition:
+ _tempo = stream.readByte();
+ offset++;
+ break;
+ case kTransTypePosition:
+ _transType = static_cast<TransitionType>(stream.readByte());
+ offset++;
+ break;
+ case kSound1Position:
+ _sound1 = stream.readUint16();
+ offset+=2;
+ break;
+ case kSkipFrameFlagsPosition:
+ _skipFrameFlag = stream.readByte();
+ offset++;
+ break;
+ case kBlendPosition:
+ _blend = stream.readByte();
+ offset++;
+ break;
+ case kSound2Position:
+ _sound2 = stream.readUint16();
+ offset += 2;
+ break;
+ case kSound2TypePosition:
+ _soundType2 = stream.readByte();
+ offset += 1;
+ break;
+ case kPaletePosition:
+ if (stream.readUint16())
+ readPaletteInfo(stream);
+ offset += 16;
+ break;
+ default:
+ offset++;
+ stream.readByte();
+ debugC(kDebugLoading, "Frame::readMainChannels: Field Position %d, Finish Position %d", offset, finishPosition);
+ break;
+ }
+ }
+}
+
+void Frame::readPaletteInfo(Common::SeekableSubReadStreamEndian &stream) {
+ _palette->firstColor = stream.readByte();
+ _palette->lastColor = stream.readByte();
+ _palette->flags = stream.readByte();
+ _palette->speed = stream.readByte();
+ _palette->frameCount = stream.readUint16();
+ stream.skip(8); //unknown
+}
+
+void Frame::readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) {
+ uint16 spritePosition = (offset - 32) / 16;
+ uint16 spriteStart = spritePosition * 16 + 32;
+
+ uint16 fieldPosition = offset - spriteStart;
+ uint16 finishPosition = fieldPosition + size;
+
+ Sprite &sprite = *_sprites[spritePosition];
+
+ while (fieldPosition < finishPosition) {
+ switch (fieldPosition) {
+ case kSpritePositionUnk1:
+ /*byte x1 = */ stream.readByte();
+ fieldPosition++;
+ break;
+ case kSpritePositionEnabled:
+ sprite._enabled = (stream.readByte() != 0);
+ fieldPosition++;
+ break;
+ case kSpritePositionUnk2:
+ /*byte x2 = */ stream.readUint16();
+ fieldPosition += 2;
+ break;
+ case kSpritePositionFlags:
+ sprite._flags = stream.readUint16();
+ sprite._ink = static_cast<InkType>(sprite._flags & 0x3f);
+
+ if (sprite._flags & 0x40)
+ sprite._trails = 1;
+ else
+ sprite._trails = 0;
+
+ fieldPosition += 2;
+ break;
+ case kSpritePositionCastId:
+ sprite._castId = stream.readUint16();
+ fieldPosition += 2;
+ break;
+ case kSpritePositionY:
+ sprite._startPoint.y = stream.readUint16();
+ fieldPosition += 2;
+ break;
+ case kSpritePositionX:
+ sprite._startPoint.x = stream.readUint16();
+ fieldPosition += 2;
+ break;
+ case kSpritePositionWidth:
+ sprite._width = stream.readUint16();
+ fieldPosition += 2;
+ break;
+ case kSpritePositionHeight:
+ sprite._height = stream.readUint16();
+ fieldPosition += 2;
+ break;
+ default:
+ // end of channel, go to next sprite channel
+ readSprite(stream, spriteStart + 16, finishPosition - fieldPosition);
+ fieldPosition = finishPosition;
+ break;
+ }
+ }
+}
+
+void Frame::prepareFrame(Score *score) {
+ renderSprites(*score->_surface, false);
+ renderSprites(*score->_trailSurface, true);
+
+ if (_transType != 0)
+ //T ODO Handle changing area case
+ playTransition(score);
+
+ if (_sound1 != 0 || _sound2 != 0) {
+ playSoundChannel();
+ }
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, score->_surface->getBounds().width(), score->_surface->getBounds().height());
+}
+
+void Frame::playSoundChannel() {
+ debug(0, "Sound1 %d", _sound1);
+ debug(0, "Sound2 %d", _sound2);
+}
+
+void Frame::playTransition(Score *score) {
+ uint16 duration = _transDuration * 250; // _transDuration in 1/4 of sec
+ duration = (duration == 0 ? 250 : duration); // director supports transition duration = 0, but animation play like value = 1, idk.
+
+ if (_transChunkSize == 0)
+ _transChunkSize = 1; // equal to 1 step
+
+ uint16 stepDuration = duration / _transChunkSize;
+ uint16 steps = duration / stepDuration;
+
+ switch (_transType) {
+ case kTransCoverDown:
+ {
+ uint16 stepSize = score->_movieRect.height() / steps;
+ Common::Rect r = score->_movieRect;
+
+ for (uint16 i = 1; i < steps; i++) {
+ r.setHeight(stepSize * i);
+
+ g_system->delayMillis(stepDuration);
+ score->processEvents();
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height());
+ g_system->updateScreen();
+ }
+ }
+ break;
+ case kTransCoverUp:
+ {
+ uint16 stepSize = score->_movieRect.height() / steps;
+ Common::Rect r = score->_movieRect;
+
+ for (uint16 i = 1; i < steps; i++) {
+ r.setHeight(stepSize * i);
+
+ g_system->delayMillis(stepDuration);
+ score->processEvents();
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, score->_movieRect.height() - stepSize * i, r.width(), r.height());
+ g_system->updateScreen();
+ }
+ }
+ break;
+ case kTransCoverRight: {
+ uint16 stepSize = score->_movieRect.width() / steps;
+ Common::Rect r = score->_movieRect;
+
+ for (uint16 i = 1; i < steps; i++) {
+ r.setWidth(stepSize * i);
+
+ g_system->delayMillis(stepDuration);
+ score->processEvents();
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height());
+ g_system->updateScreen();
+ }
+ }
+ break;
+ case kTransCoverLeft: {
+ uint16 stepSize = score->_movieRect.width() / steps;
+ Common::Rect r = score->_movieRect;
+
+ for (uint16 i = 1; i < steps; i++) {
+ r.setWidth(stepSize * i);
+
+ g_system->delayMillis(stepDuration);
+ score->processEvents();
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, 0, r.width(), r.height());
+ g_system->updateScreen();
+ }
+ }
+ break;
+ case kTransCoverUpLeft: {
+ uint16 stepSize = score->_movieRect.width() / steps;
+ Common::Rect r = score->_movieRect;
+
+ for (uint16 i = 1; i < steps; i++) {
+ r.setWidth(stepSize * i);
+ r.setHeight(stepSize * i);
+
+ g_system->delayMillis(stepDuration);
+ score->processEvents();
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, score->_movieRect.height() - stepSize * i, r.width(), r.height());
+ g_system->updateScreen();
+ }
+ }
+ break;
+ case kTransCoverUpRight: {
+ uint16 stepSize = score->_movieRect.width() / steps;
+ Common::Rect r = score->_movieRect;
+
+ for (uint16 i = 1; i < steps; i++) {
+ r.setWidth(stepSize * i);
+ r.setHeight(stepSize * i);
+
+ g_system->delayMillis(stepDuration);
+ score->processEvents();
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, score->_movieRect.height() - stepSize * i, r.width(), r.height());
+ g_system->updateScreen();
+ }
+ }
+ break;
+ case kTransCoverDownLeft: {
+ uint16 stepSize = score->_movieRect.width() / steps;
+ Common::Rect r = score->_movieRect;
+
+ for (uint16 i = 1; i < steps; i++) {
+ r.setWidth(stepSize * i);
+ r.setHeight(stepSize * i);
+
+ g_system->delayMillis(stepDuration);
+ score->processEvents();
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, 0, r.width(), r.height());
+ g_system->updateScreen();
+ }
+ }
+ break;
+ case kTransCoverDownRight: {
+ uint16 stepSize = score->_movieRect.width() / steps;
+ Common::Rect r = score->_movieRect;
+
+ for (uint16 i = 1; i < steps; i++) {
+ r.setWidth(stepSize * i);
+ r.setHeight(stepSize * i);
+
+ g_system->delayMillis(stepDuration);
+ score->processEvents();
+
+ g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height());
+ g_system->updateScreen();
+ }
+ }
+ break;
+ default:
+ warning("Unhandled transition type %d %d %d", _transType, duration, _transChunkSize);
+ break;
+
+ }
+}
+
+void Frame::renderSprites(Graphics::ManagedSurface &surface, bool renderTrail) {
+ for (uint16 i = 0; i < CHANNEL_COUNT; i++) {
+ if (_sprites[i]->_enabled) {
+ if ((_sprites[i]->_trails == 0 && renderTrail) || (_sprites[i]->_trails == 1 && !renderTrail))
+ continue;
+
+ Cast *cast;
+ if (!_vm->_currentScore->_casts.contains(_sprites[i]->_castId)) {
+ if (!_vm->getSharedCasts()->contains(_sprites[i]->_castId)) {
+ warning("Cast id %d not found", _sprites[i]->_castId);
+ continue;
+ } else {
+ warning("Getting cast id %d from shared cast", _sprites[i]->_castId);
+ cast = _vm->getSharedCasts()->getVal(_sprites[i]->_castId);
+ }
+ } else {
+ cast = _vm->_currentScore->_casts[_sprites[i]->_castId];
+ }
+
+ if (cast->type == kCastText) {
+ renderText(surface, i);
+ continue;
+ }
+
+ Image::ImageDecoder *img = getImageFrom(_sprites[i]->_castId);
+
+ if (!img) {
+ warning("Image with id %d not found", _sprites[i]->_castId);
+ continue;
+ }
+
+ if (!img->getSurface()) {
+ warning("Frame::renderSprites: Could not load image %d", _sprites[i]->_castId);
+ continue;
+ }
+
+ uint32 regX = static_cast<BitmapCast *>(_sprites[i]->_cast)->regX;
+ uint32 regY = static_cast<BitmapCast *>(_sprites[i]->_cast)->regY;
+ uint32 rectLeft = static_cast<BitmapCast *>(_sprites[i]->_cast)->initialRect.left;
+ uint32 rectTop = static_cast<BitmapCast *>(_sprites[i]->_cast)->initialRect.top;
+
+ int x = _sprites[i]->_startPoint.x - regX + rectLeft;
+ int y = _sprites[i]->_startPoint.y - regY + rectTop;
+ int height = _sprites[i]->_height;
+ int width = _sprites[i]->_width;
+
+ Common::Rect drawRect = Common::Rect(x, y, x + width, y + height);
+ _drawRects.push_back(drawRect);
+
+ switch (_sprites[i]->_ink) {
+ case kInkTypeCopy:
+ surface.blitFrom(*img->getSurface(), Common::Point(x, y));
+ break;
+ case kInkTypeTransparent:
+ // FIXME: is it always white (last entry in pallette)?
+ surface.transBlitFrom(*img->getSurface(), Common::Point(x, y), _vm->getPaletteColorCount() - 1);
+ break;
+ case kInkTypeBackgndTrans:
+ drawBackgndTransSprite(surface, *img->getSurface(), drawRect);
+ break;
+ case kInkTypeMatte:
+ drawMatteSprite(surface, *img->getSurface(), drawRect);
+ break;
+ case kInkTypeGhost:
+ drawGhostSprite(surface, *img->getSurface(), drawRect);
+ break;
+ case kInkTypeReverse:
+ drawReverseSprite(surface, *img->getSurface(), drawRect);
+ break;
+ default:
+ warning("Unhandled ink type %d", _sprites[i]->_ink);
+ surface.blitFrom(*img->getSurface(), Common::Point(x, y));
+ break;
+ }
+ }
+ }
+}
+
+void Frame::renderButton(Graphics::ManagedSurface &surface, uint16 spriteId) {
+ renderText(surface, spriteId);
+
+ uint16 castID = _sprites[spriteId]->_castId;
+ ButtonCast *button = static_cast<ButtonCast *>(_vm->_currentScore->_casts[castID]);
+
+ uint32 rectLeft = button->initialRect.left;
+ uint32 rectTop = button->initialRect.top;
+
+ int x = _sprites[spriteId]->_startPoint.x + rectLeft;
+ int y = _sprites[spriteId]->_startPoint.y + rectTop;
+ int height = _sprites[spriteId]->_height;
+ int width = _sprites[spriteId]->_width;
+
+ switch (button->buttonType) {
+ case kTypeCheckBox:
+ // Magic numbers: checkbox square need to move left about 5px from text and 12px side size (D4)
+ surface.frameRect(Common::Rect(x - 17, y, x + 12, y + 12), 0);
+ break;
+ case kTypeButton:
+ surface.frameRect(Common::Rect(x, y, x + width, y + height), 0);
+ break;
+ case kTypeRadio:
+ warning("STUB: renderButton: kTypeRadio");
+ break;
+ }
+}
+
+Image::ImageDecoder *Frame::getImageFrom(uint16 spriteId) {
+ uint16 imgId = spriteId + 1024;
+ Image::ImageDecoder *img = NULL;
+
+ if (_vm->_currentScore->getArchive()->hasResource(MKTAG('D', 'I', 'B', ' '), imgId)) {
+ img = new DIBDecoder();
+ img->loadStream(*_vm->_currentScore->getArchive()->getResource(MKTAG('D', 'I', 'B', ' '), imgId));
+ return img;
+ }
+
+ if (_vm->getSharedDIB() != NULL && _vm->getSharedDIB()->contains(imgId)) {
+ img = new DIBDecoder();
+ img->loadStream(*_vm->getSharedDIB()->getVal(imgId));
+ return img;
+ }
+
+ if (_vm->_currentScore->getArchive()->hasResource(MKTAG('B', 'I', 'T', 'D'), imgId)) {
+ Common::SeekableReadStream *pic = _vm->_currentScore->getArchive()->getResource(MKTAG('B', 'I', 'T', 'D'), imgId);
+
+ if (_vm->getVersion() < 4) {
+ BitmapCast *bc = static_cast<BitmapCast *>(_vm->_currentScore->_casts[spriteId]);
+ int w = bc->initialRect.width(), h = bc->initialRect.height();
+
+ debugC(2, kDebugImages, "id: %d, w: %d, h: %d, flags: %x, some: %x, unk1: %d, unk2: %d",
+ imgId, w, h, bc->flags, bc->someFlaggyThing, bc->unk1, bc->unk2);
+ img = new BITDDecoder(w, h);
+ } else {
+ img = new Image::BitmapDecoder();
+ }
+
+ if (debugChannelSet(8, kDebugLoading)) {
+ Common::SeekableReadStream *s = pic;
+ byte buf[1024];
+ int n = s->read(buf, 1024);
+ Common::hexdump(buf, n);
+ }
+
+ img->loadStream(*pic);
+ return img;
+ }
+
+ if (_vm->getSharedBMP() != NULL && _vm->getSharedBMP()->contains(imgId)) {
+ img = new Image::BitmapDecoder();
+ img->loadStream(*_vm->getSharedBMP()->getVal(imgId));
+ return img;
+ }
+
+ warning("Image %d not found", spriteId);
+ return img;
+}
+
+
+void Frame::renderText(Graphics::ManagedSurface &surface, uint16 spriteID) {
+ uint16 castID = _sprites[spriteID]->_castId;
+
+ TextCast *textCast = static_cast<TextCast *>(_vm->_currentScore->_casts[castID]);
+ Common::SeekableSubReadStreamEndian *textStream;
+
+ if (_vm->_currentScore->_movieArchive->hasResource(MKTAG('S','T','X','T'), castID + 1024)) {
+ textStream = _vm->_currentScore->_movieArchive->getResource(MKTAG('S','T','X','T'), castID + 1024);
+ } else {
+ textStream = _vm->getSharedSTXT()->getVal(spriteID + 1024);
+ }
+ /*uint32 unk1 = */ textStream->readUint32();
+ uint32 strLen = textStream->readUint32();
+ /*uin32 dataLen = */ textStream->readUint32();
+ Common::String text;
+
+ for (uint32 i = 0; i < strLen; i++) {
+ byte ch = textStream->readByte();
+ if (ch == 0x0d) {
+ ch = '\n';
+ }
+ text += ch;
+ }
+
+ uint32 rectLeft = static_cast<TextCast *>(_sprites[spriteID]->_cast)->initialRect.left;
+ uint32 rectTop = static_cast<TextCast *>(_sprites[spriteID]->_cast)->initialRect.top;
+
+ int x = _sprites[spriteID]->_startPoint.x + rectLeft;
+ int y = _sprites[spriteID]->_startPoint.y + rectTop;
+ int height = _sprites[spriteID]->_height;
+ int width = _sprites[spriteID]->_width;
+
+ const char *fontName;
+
+ if (_vm->_currentScore->_fontMap.contains(textCast->fontId)) {
+ fontName = _vm->_currentScore->_fontMap[textCast->fontId].c_str();
+ } else if ((fontName = _vm->_wm->getFontName(textCast->fontId, textCast->fontSize)) == NULL) {
+ warning("Unknown font id %d, falling back to default", textCast->fontId);
+ fontName = _vm->_wm->getFontName(0, 12);
+ }
+
+ const Graphics::Font *font = _vm->_wm->getFont(fontName, Graphics::FontManager::kBigGUIFont);
+
+ font->drawString(&surface, text, x, y, width, 0);
+
+ if (textCast->borderSize != kSizeNone) {
+ uint16 size = textCast->borderSize;
+
+ // Indent from borders, measured in d4
+ x -= 1;
+ y -= 4;
+
+ height += 4;
+ width += 1;
+
+ while (size) {
+ surface.frameRect(Common::Rect(x, y, x + height, y + width), 0);
+ x--;
+ y--;
+ height += 2;
+ width += 2;
+ size--;
+ }
+ }
+
+ if (textCast->gutterSize != kSizeNone) {
+ x -= 1;
+ y -= 4;
+
+ height += 4;
+ width += 1;
+ uint16 size = textCast->gutterSize;
+
+ surface.frameRect(Common::Rect(x, y, x + height, y + width), 0);
+
+ while (size) {
+ surface.drawLine(x + width, y, x + width, y + height, 0);
+ surface.drawLine(x, y + height, x + width, y + height, 0);
+ x++;
+ y++;
+ size--;
+ }
+ }
+}
+
+void Frame::drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
+ uint8 skipColor = _vm->getPaletteColorCount() - 1; // FIXME is it always white (last entry in pallette) ?
+
+ for (int ii = 0; ii < sprite.h; ii++) {
+ const byte *src = (const byte *)sprite.getBasePtr(0, ii);
+ byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
+
+ for (int j = 0; j < drawRect.width(); j++) {
+ if (*src != skipColor)
+ *dst = *src;
+
+ src++;
+ dst++;
+ }
+ }
+}
+
+void Frame::drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
+ uint8 skipColor = _vm->getPaletteColorCount() - 1;
+ for (int ii = 0; ii < sprite.h; ii++) {
+ const byte *src = (const byte *)sprite.getBasePtr(0, ii);
+ byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
+
+ for (int j = 0; j < drawRect.width(); j++) {
+ if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && (*src != skipColor))
+ *dst = (_vm->getPaletteColorCount() - 1) - *src; // Oposite color
+
+ src++;
+ dst++;
+ }
+ }
+}
+
+void Frame::drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
+ uint8 skipColor = _vm->getPaletteColorCount() - 1;
+ for (int ii = 0; ii < sprite.h; ii++) {
+ const byte *src = (const byte *)sprite.getBasePtr(0, ii);
+ byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
+
+ for (int j = 0; j < drawRect.width(); j++) {
+ if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0))
+ *dst = (_vm->getPaletteColorCount() - 1) - *src;
+ else if (*src != skipColor)
+ *dst = *src;
+ src++;
+ dst++;
+ }
+ }
+}
+
+void Frame::drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
+ // Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent
+ Graphics::Surface tmp;
+ tmp.copyFrom(sprite);
+
+ // Searching white color in the corners
+ int whiteColor = -1;
+
+ for (int corner = 0; corner < 4; corner++) {
+ int x = (corner & 0x1) ? tmp.w - 1 : 0;
+ int y = (corner & 0x2) ? tmp.h - 1 : 0;
+
+ byte color = *(byte *)tmp.getBasePtr(x, y);
+
+ if (_vm->getPalette()[color * 3 + 0] == 0xff &&
+ _vm->getPalette()[color * 3 + 1] == 0xff &&
+ _vm->getPalette()[color * 3 + 2] == 0xff) {
+ whiteColor = color;
+ break;
+ }
+ }
+
+ if (whiteColor == -1) {
+ debugC(1, kDebugImages, "No white color for Matte image");
+
+ for (int yy = 0; yy < tmp.h; yy++) {
+ const byte *src = (const byte *)tmp.getBasePtr(0, yy);
+ byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy);
+
+ for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++)
+ *dst = *src;
+ }
+ } else {
+ Graphics::FloodFill ff(&tmp, whiteColor, 0, true);
+
+ for (int yy = 0; yy < tmp.h; yy++) {
+ ff.addSeed(0, yy);
+ ff.addSeed(tmp.w - 1, yy);
+ }
+
+ for (int xx = 0; xx < tmp.w; xx++) {
+ ff.addSeed(xx, 0);
+ ff.addSeed(xx, tmp.h - 1);
+ }
+ ff.fillMask();
+
+ for (int yy = 0; yy < tmp.h; yy++) {
+ const byte *src = (const byte *)tmp.getBasePtr(0, yy);
+ const byte *mask = (const byte *)ff.getMask()->getBasePtr(0, yy);
+ byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy);
+
+ for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++, mask++)
+ if (*mask == 0)
+ *dst = *src;
+ }
+ }
+
+ tmp.free();
+}
+
+uint16 Frame::getSpriteIDFromPos(Common::Point pos) {
+ // Find first from top to bottom
+ for (uint16 i = _drawRects.size() - 1; i > 0; i--) {
+ if (_drawRects[i].contains(pos))
+ return i;
+ }
+
+ return 0;
+}
+
+} // End of namespace Director
diff --git a/engines/director/frame.h b/engines/director/frame.h
new file mode 100644
index 0000000000..8c6f82f493
--- /dev/null
+++ b/engines/director/frame.h
@@ -0,0 +1,149 @@
+/* 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 DIRECTOR_FRAME_H
+#define DIRECTOR_FRAME_H
+
+#include "graphics/managed_surface.h"
+
+namespace Image {
+ class ImageDecoder;
+}
+
+namespace Director {
+
+class Sprite;
+
+#define CHANNEL_COUNT 24
+
+enum TransitionType {
+ kTransNone,
+ kTransWipeRight,
+ kTransWipeLeft,
+ kTransWipeDown,
+ kTransWipeUp,
+ kTransCenterOutHorizontal,
+ kTransEdgesInHorizontal,
+ kTransCenterOutVertical,
+ kTransEdgesInVertical,
+ kTransCenterOutSquare,
+ kTransEdgesInSquare,
+ kTransPushLeft,
+ kTransPushRight,
+ kTransPushDown,
+ kTransPushUp,
+ kTransRevealUp,
+ kTransRevealUpRight,
+ kTransRevealRight,
+ kTransRevealDown,
+ kTransRevealDownRight,
+ kTransRevealDownLeft,
+ kTransRevealLeft,
+ kTransRevealUpLeft,
+ kTransDissolvePixelsFast,
+ kTransDissolveBoxyRects,
+ kTransDissolveBoxySquares,
+ kTransDissolvePatterns,
+ kTransRandomRows,
+ kTransRandomColumns,
+ kTransCoverDown,
+ kTransCoverDownLeft,
+ kTransCoverDownRight,
+ kTransCoverLeft,
+ kTransCoverRight,
+ kTransCoverUp,
+ kTransCoverUpLeft,
+ kTransCoverUpRight,
+ kTransTypeVenitianBlind,
+ kTransTypeCheckerboard,
+ kTransTypeStripsBottomBuildLeft,
+ kTransTypeStripsBottomBuildRight,
+ kTransTypeStripsLeftBuildDown,
+ kTransTypeStripsLeftBuildUp,
+ kTransTypeStripsRightBuildDown,
+ kTransTypeStripsRightBuildUp,
+ kTransTypeStripsTopBuildLeft,
+ kTransTypeStripsTopBuildRight,
+ kTransZoomOpen,
+ kTransZoomClose,
+ kTransVerticalBinds,
+ kTransDissolveBitsTrans,
+ kTransDissolvePixels,
+ kTransDissolveBits
+};
+
+struct PaletteInfo {
+ uint8 firstColor;
+ uint8 lastColor;
+ uint8 flags;
+ uint8 speed;
+ uint16 frameCount;
+};
+
+
+class Frame {
+public:
+ Frame(DirectorEngine *vm);
+ Frame(const Frame &frame);
+ ~Frame();
+ void readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
+ void prepareFrame(Score *score);
+ uint16 getSpriteIDFromPos(Common::Point pos);
+
+private:
+ void playTransition(Score *score);
+ void playSoundChannel();
+ void renderSprites(Graphics::ManagedSurface &surface, bool renderTrail);
+ void renderText(Graphics::ManagedSurface &surface, uint16 spriteId);
+ void renderButton(Graphics::ManagedSurface &surface, uint16 spriteId);
+ void readPaletteInfo(Common::SeekableSubReadStreamEndian &stream);
+ void readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
+ void readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
+ Image::ImageDecoder *getImageFrom(uint16 spriteID);
+ void drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
+ void drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
+ void drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
+ void drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
+public:
+ uint8 _actionId;
+ uint8 _transDuration;
+ uint8 _transArea; //1 - Whole Stage, 0 - Changing Area
+ uint8 _transChunkSize;
+ TransitionType _transType;
+ PaletteInfo *_palette;
+ uint8 _tempo;
+
+ uint16 _sound1;
+ uint8 _soundType1;
+ uint16 _sound2;
+ uint8 _soundType2;
+
+ uint8 _skipFrameFlag;
+ uint8 _blend;
+ Common::Array<Sprite *> _sprites;
+ Common::Array<Common::Rect > _drawRects;
+ DirectorEngine *_vm;
+};
+
+} //End of namespace Director
+
+#endif
diff --git a/engines/director/dib.cpp b/engines/director/images.cpp
index 8c54ba5363..cd8223ae8e 100644
--- a/engines/director/dib.cpp
+++ b/engines/director/images.cpp
@@ -20,19 +20,12 @@
*
*/
-#include "director/dib.h"
-
-#include "common/stream.h"
#include "common/substream.h"
-#include "common/textconsole.h"
-#include "graphics/pixelformat.h"
-#include "graphics/surface.h"
-#include "graphics/palette.h"
-#include "image/codecs/codec.h"
-#include "common/util.h"
#include "common/debug.h"
-#include "image/codecs/bmp_raw.h"
-#include "common/system.h"
+#include "common/textconsole.h"
+
+#include "director/director.h"
+#include "director/images.h"
namespace Director {
@@ -62,7 +55,7 @@ void DIBDecoder::loadPalette(Common::SeekableReadStream &stream) {
uint16 steps = stream.size() / 6;
uint16 index = (steps * 3) - 1;
_paletteColorCount = steps;
- _palette = new byte[index];
+ _palette = new byte[index + 1];
for (uint8 i = 0; i < steps; i++) {
_palette[index - 2] = stream.readByte();
@@ -87,7 +80,7 @@ bool DIBDecoder::loadStream(Common::SeekableReadStream &stream) {
stream.readUint16LE(); // planes
uint16 bitsPerPixel = stream.readUint16LE();
uint32 compression = stream.readUint32BE();
- uint32 imageSize = stream.readUint32LE();
+ /* uint32 imageSize = */ stream.readUint32LE();
/* uint32 pixelsPerMeterX = */ stream.readUint32LE();
/* uint32 pixelsPerMeterY = */ stream.readUint32LE();
_paletteColorCount = stream.readUint32LE();
@@ -95,7 +88,6 @@ bool DIBDecoder::loadStream(Common::SeekableReadStream &stream) {
_paletteColorCount = (_paletteColorCount == 0) ? 255: _paletteColorCount;
- uint16 imageRawSize = stream.size() - 40;
Common::SeekableSubReadStream subStream(&stream, 40, stream.size());
_codec = Image::createBitmapCodec(compression, width, height, bitsPerPixel);
@@ -108,4 +100,104 @@ bool DIBDecoder::loadStream(Common::SeekableReadStream &stream) {
return true;
}
+/****************************
+ * BITD
+ ****************************/
+
+BITDDecoder::BITDDecoder(int w, int h) {
+ _surface = new Graphics::Surface();
+
+ // We make the surface pitch a multiple of 16.
+ int pitch = w;
+ if (w % 16)
+ pitch += 16 - (w % 16);
+
+ // HACK: Create a padded surface by adjusting w after create()
+ _surface->create(pitch, h, Graphics::PixelFormat::createFormatCLUT8());
+ _surface->w = w;
+
+ _palette = new byte[256 * 3];
+
+ _palette[0] = _palette[1] = _palette[2] = 0;
+ _palette[255 * 3 + 0] = _palette[255 * 3 + 1] = _palette[255 * 3 + 2] = 0xff;
+
+ _paletteColorCount = 2;
+}
+
+BITDDecoder::~BITDDecoder() {
+ destroy();
+}
+
+void BITDDecoder::destroy() {
+ _surface = 0;
+
+ delete[] _palette;
+ _palette = 0;
+ _paletteColorCount = 0;
+}
+
+void BITDDecoder::loadPalette(Common::SeekableReadStream &stream) {
+ // no op
+}
+
+bool BITDDecoder::loadStream(Common::SeekableReadStream &stream) {
+ int x = 0, y = 0;
+
+ // If the stream has exactly the required number of bits for this image,
+ // we assume it is uncompressed.
+ if (stream.size() * 8 == _surface->pitch * _surface->h) {
+ debugC(3, kDebugImages, "Skipping compression");
+ for (y = 0; y < _surface->h; y++) {
+ for (x = 0; x < _surface->pitch; ) {
+ byte color = stream.readByte();
+ for (int c = 0; c < 8; c++)
+ *((byte *)_surface->getBasePtr(x++, y)) = (color & (1 << (7 - c))) ? 0 : 0xff;
+ }
+ }
+
+ return true;
+ }
+
+ while (y < _surface->h) {
+ int n = stream.readSByte();
+ int count;
+ int b = 0;
+ int state = 0;
+
+ if (stream.eos())
+ break;
+
+ if ((n >= 0) && (n <= 127)) { // If n is between 0 and 127 inclusive, copy the next n+1 bytes literally.
+ count = n + 1;
+ state = 1;
+ } else if ((n >= -127) && (n <= -1)) { // Else if n is between -127 and -1 inclusive, copy the next byte -n+1 times.
+ b = stream.readByte();
+ count = -n + 1;
+ state = 2;
+ } else { // Else if n is -128, noop.
+ count = 0;
+ }
+
+ for (int i = 0; i < count && y < _surface->h; i++) {
+ byte color = 0;
+ if (state == 1) {
+ color = stream.readByte();
+ } else if (state == 2)
+ color = b;
+
+ for (int c = 0; c < 8; c++) {
+ *((byte *)_surface->getBasePtr(x, y)) = (color & (1 << (7 - c))) ? 0 : 0xff;
+ x++;
+ if (x == _surface->pitch) {
+ y++;
+ x = 0;
+ break;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Director
diff --git a/engines/director/dib.h b/engines/director/images.h
index e3763be2bf..54e824588f 100644
--- a/engines/director/dib.h
+++ b/engines/director/images.h
@@ -20,8 +20,8 @@
*
*/
-#ifndef DIRECTOR_DIB_H
-#define DIRECTOR_DIB_H
+#ifndef DIRECTOR_IMAGES_H
+#define DIRECTOR_IMAGES_H
#include "common/scummsys.h"
#include "common/str.h"
@@ -62,6 +62,25 @@ private:
uint8 _paletteColorCount;
};
+class BITDDecoder : public Image::ImageDecoder {
+public:
+ BITDDecoder(int w, int h);
+ virtual ~BITDDecoder();
+
+ // ImageDecoder API
+ void destroy();
+ virtual bool loadStream(Common::SeekableReadStream &stream);
+ virtual const Graphics::Surface *getSurface() const { return _surface; }
+ const byte *getPalette() const { return _palette; }
+ void loadPalette(Common::SeekableReadStream &stream);
+ uint16 getPaletteColorCount() const { return _paletteColorCount; }
+
+private:
+ Graphics::Surface *_surface;
+ byte *_palette;
+ uint8 _paletteColorCount;
+};
+
} // End of namespace Director
#endif
diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp
index 5001eeabb9..bad585cfe1 100644
--- a/engines/director/lingo/lingo-builtins.cpp
+++ b/engines/director/lingo/lingo-builtins.cpp
@@ -20,7 +20,7 @@
*
*/
-#include "engines/director/lingo/lingo.h"
+#include "director/lingo/lingo.h"
namespace Director {
@@ -32,23 +32,29 @@ static struct BuiltinProto {
bool parens;
} builtins[] = {
// Math
- { "abs", Lingo::b_abs, 1, 1, true }, // D2
- { "atan", Lingo::b_atan, 1, 1, true }, // D4
- { "cos", Lingo::b_cos, 1, 1, true }, // D4
- { "exp", Lingo::b_exp, 1, 1, true }, // D4
- { "float", Lingo::b_float, 1, 1, true }, // D4
- { "integer",Lingo::b_integer, 1, 1, true },
- { "log", Lingo::b_log, 1, 1, true }, // D4
- { "pi", Lingo::b_pi, 0, 0, true }, // D4
- { "power", Lingo::b_power, 2, 2, true }, // D4
- { "random", Lingo::b_random, 1, 1, true }, // D2
- { "sin", Lingo::b_sin, 1, 1, true },
- { "sqrt", Lingo::b_sqrt, 1, 1, true }, // D2
- { "tan", Lingo::b_tan, 1, 1, true }, // D4
+ { "abs", Lingo::b_abs, 1, 1, true }, // D2
+ { "atan", Lingo::b_atan, 1, 1, true }, // D4
+ { "cos", Lingo::b_cos, 1, 1, true }, // D4
+ { "exp", Lingo::b_exp, 1, 1, true }, // D4
+ { "float", Lingo::b_float, 1, 1, true }, // D4
+ { "integer", Lingo::b_integer, 1, 1, true },
+ { "integerp", Lingo::b_integerp, 1, 1, true },
+ { "log", Lingo::b_log, 1, 1, true }, // D4
+ { "pi", Lingo::b_pi, 0, 0, true }, // D4
+ { "power", Lingo::b_power, 2, 2, true }, // D4
+ { "random", Lingo::b_random, 1, 1, true }, // D2
+ { "sin", Lingo::b_sin, 1, 1, true },
+ { "sqrt", Lingo::b_sqrt, 1, 1, true }, // D2
+ { "tan", Lingo::b_tan, 1, 1, true }, // D4
// String
- { "chars", Lingo::b_chars, 3, 3, true }, // D2
- { "length", Lingo::b_length, 1, 1, true }, // D2
- { "string", Lingo::b_string, 1, 1, true }, // D2
+ { "chars", Lingo::b_chars, 3, 3, true }, // D2
+ { "charToNum", Lingo::b_charToNum, 1, 1, true }, // D2
+ { "length", Lingo::b_length, 1, 1, true }, // D2
+ { "numToChar", Lingo::b_numToChar, 1, 1, true }, // D2
+ { "offset", Lingo::b_offset, 2, 2, true }, // D2
+ { "string", Lingo::b_string, 1, 1, true }, // D2
+ { "stringp", Lingo::b_stringp, 1, 1, true }, // D2
+ { "value", Lingo::b_value, 1, 1, true }, // D2
// Files
{ "closeDA", Lingo::b_closeDA, 0, 0, false }, // D2
{ "closeResFile", Lingo::b_closeResFile, 0, 1, false }, // D2
@@ -84,18 +90,25 @@ static struct BuiltinProto {
{ "ilk", Lingo::b_ilk, 1, 2, true }, // D4
// put // D2
// set // D2
+ { "objectp", Lingo::b_objectp, 1, 1, true },
{ "showGlobals", Lingo::b_showGlobals, 0, 0, false }, // D2
{ "showLocals", Lingo::b_showLocals, 0, 0, false }, // D2
+ { "symbolp", Lingo::b_symbolp, 1, 1, true }, // D2
// Score
+ { "constrainH", Lingo::b_constrainH, 2, 2, true }, // D2
+ { "constrainV", Lingo::b_constrainV, 2, 2, true }, // D2
{ "editableText", Lingo::b_editableText, 0, 0, false }, // D2
// go // D2
{ "installMenu", Lingo::b_installMenu, 1, 1, false }, // D2
+ { "label", Lingo::b_label, 1, 1, true }, // D2
+ { "marker", Lingo::b_marker, 1, 1, true }, // D2
{ "moveableSprite", Lingo::b_moveableSprite,0, 0, false }, // D2
{ "puppetPalette", Lingo::b_puppetPalette, -1,0, false }, // D2
{ "puppetSound", Lingo::b_puppetSound, -1,0, false }, // D2
{ "puppetSprite", Lingo::b_puppetSprite, -1,0, false }, // D2
{ "puppetTempo", Lingo::b_puppetTempo, 1, 1, false }, // D2
{ "puppetTransition",Lingo::b_puppetTransition,-1,0, false },// D2
+ { "rollOver", Lingo::b_rollOver, 1, 1, true }, // D2
{ "spriteBox", Lingo::b_spriteBox, -1,0, false }, // D2
{ "updateStage", Lingo::b_updateStage, 0, 0, false }, // D2
{ "zoomBox", Lingo::b_zoomBox, -1,0, false }, // D2
@@ -104,7 +117,17 @@ static struct BuiltinProto {
// Sound
{ "beep", Lingo::b_beep, 0, 1, false }, // D2
{ "mci", Lingo::b_mci, 1, 1, false },
- { "mciwait", Lingo::b_mciwait, 1, 1, false },
+ { "mciwait", Lingo::b_mciwait, 1, 1, false },
+ // Constants
+ { "backspace", Lingo::b_backspace, 0, 0, false }, // D2
+ { "empty", Lingo::b_empty, 0, 0, false }, // D2
+ { "enter", Lingo::b_enter, 0, 0, false }, // D2
+ { "false", Lingo::b_false, 0, 0, false }, // D2
+ { "quote", Lingo::b_quote, 0, 0, false }, // D2
+ { "return", Lingo::b_return, 0, 0, false }, // D2
+ { "tab", Lingo::b_tab, 0, 0, false }, // D2
+ { "true", Lingo::b_true, 0, 0, false }, // D2
+
{ 0, 0, 0, 0, false }
};
@@ -121,6 +144,8 @@ void Lingo::initBuiltIns() {
sym->u.bltin = blt->func;
_handlers[blt->name] = sym;
+
+ _functions[(void *)sym->u.s] = new FuncDesc(blt->name, "");
}
}
@@ -158,6 +183,14 @@ void Lingo::dropStack(int nargs) {
pop();
}
+void Lingo::drop(int num) {
+ if (num > _stack.size() - 1) {
+ warning("Incorrect number of elements to drop from stack: %d > %d", num, _stack.size() - 1);
+ return;
+ }
+ _stack.remove_at(_stack.size() - 1 - num);
+}
+
///////////////////
// Math
@@ -207,6 +240,14 @@ void Lingo::b_integer(int nargs) {
g_lingo->push(d);
}
+void Lingo::b_integerp(int nargs) {
+ Datum d = g_lingo->pop();
+ int res = (d.type == INT) ? 1 : 0;
+ d.toInt();
+ d.u.i = res;
+ g_lingo->push(d);
+}
+
void Lingo::b_log(int nargs) {
Datum d = g_lingo->pop();
d.toFloat();
@@ -290,6 +331,20 @@ void Lingo::b_chars(int nargs) {
g_lingo->push(s);
}
+void Lingo::b_charToNum(int nargs) {
+ Datum d = g_lingo->pop();
+
+ if (d.type != STRING)
+ error("Incorrect type for 'charToNum' function: %s", d.type2str());
+
+ byte chr = d.u.s->c_str()[0];
+ delete d.u.s;
+
+ d.u.i = chr;
+ d.type = INT;
+ g_lingo->push(d);
+}
+
void Lingo::b_length(int nargs) {
Datum d = g_lingo->pop();
@@ -304,12 +359,48 @@ void Lingo::b_length(int nargs) {
g_lingo->push(d);
}
+void Lingo::b_numToChar(int nargs) {
+ Datum d = g_lingo->pop();
+
+ d.toInt();
+
+ g_lingo->push(Datum((char)d.u.i));
+}
+
+void Lingo::b_offset(int nargs) {
+ Datum target = g_lingo->pop();
+ Datum source = g_lingo->pop();
+
+ target.toString();
+ source.toString();
+
+ warning("STUB: b_offset()");
+
+ g_lingo->push(Datum(0));
+}
+
void Lingo::b_string(int nargs) {
Datum d = g_lingo->pop();
d.toString();
g_lingo->push(d);
}
+void Lingo::b_stringp(int nargs) {
+ Datum d = g_lingo->pop();
+ int res = (d.type == STRING) ? 1 : 0;
+ d.toInt();
+ d.u.i = res;
+ g_lingo->push(d);
+}
+
+void Lingo::b_value(int nargs) {
+ Datum d = g_lingo->pop();
+ d.toInt();
+ warning("STUB: b_value()");
+ g_lingo->push(d);
+}
+
+
///////////////////
// Files
///////////////////
@@ -462,7 +553,7 @@ void Lingo::b_alert(int nargs) {
d.toString();
- warning("STUB: b_alert");
+ warning("STUB: b_alert(%s)", d.u.s->c_str());
delete d.u.s;
}
@@ -473,6 +564,14 @@ void Lingo::b_cursor(int nargs) {
warning("STUB: b_cursor(%d)", d.u.i);
}
+void Lingo::b_objectp(int nargs) {
+ Datum d = g_lingo->pop();
+ int res = (d.type == OBJECT) ? 1 : 0;
+ d.toInt();
+ d.u.i = res;
+ g_lingo->push(d);
+}
+
void Lingo::b_showGlobals(int nargs) {
warning("STUB: b_showGlobals");
}
@@ -481,13 +580,40 @@ void Lingo::b_showLocals(int nargs) {
warning("STUB: b_showLocals");
}
+void Lingo::b_symbolp(int nargs) {
+ Datum d = g_lingo->pop();
+ int res = (d.type == SYMBOL) ? 1 : 0;
+ d.toInt();
+ d.u.i = res;
+ g_lingo->push(d);
+}
///////////////////
// Score
///////////////////
-void Lingo::b_updateStage(int nargs) {
- warning("STUB: b_updateStage");
+void Lingo::b_constrainH(int nargs) {
+ Datum num = g_lingo->pop();
+ Datum sprite = g_lingo->pop();
+
+ num.toInt();
+ sprite.toInt();
+
+ warning("STUB: b_constrainH(%d, %d)", sprite.u.i, num.u.i);
+
+ g_lingo->push(Datum(0));
+}
+
+void Lingo::b_constrainV(int nargs) {
+ Datum num = g_lingo->pop();
+ Datum sprite = g_lingo->pop();
+
+ num.toInt();
+ sprite.toInt();
+
+ warning("STUB: b_constrainV(%d, %d)", sprite.u.i, num.u.i);
+
+ g_lingo->push(Datum(0));
}
void Lingo::b_editableText(int nargs) {
@@ -499,6 +625,22 @@ void Lingo::b_installMenu(int nargs) {
warning("STUB: b_installMenu(%d)", d.u.i);
}
+void Lingo::b_label(int nargs) {
+ Datum d = g_lingo->pop();
+ d.toInt();
+ warning("STUB: b_label(%d)", d.u.i);
+
+ g_lingo->push(Datum(0));
+}
+
+void Lingo::b_marker(int nargs) {
+ Datum d = g_lingo->pop();
+ d.toInt();
+ warning("STUB: b_marker(%d)", d.u.i);
+
+ g_lingo->push(Datum(0));
+}
+
void Lingo::b_moveableSprite(int nargs) {
Datum d = g_lingo->pop();
warning("STUB: b_moveableSprite(%d)", d.u.i);
@@ -513,6 +655,8 @@ void Lingo::b_puppetPalette(int nargs) {
}
void Lingo::b_puppetSound(int nargs) {
+ g_lingo->convertVOIDtoString(0, nargs);
+
g_lingo->printStubWithArglist("b_puppetSound", nargs);
g_lingo->dropStack(nargs);
@@ -535,6 +679,13 @@ void Lingo::b_puppetTransition(int nargs) {
g_lingo->dropStack(nargs);
}
+void Lingo::b_rollOver(int nargs) {
+ Datum d = g_lingo->pop();
+ warning("STUB: b_puppetTempo(%d)", d.u.i);
+
+ g_lingo->push(Datum(0));
+}
+
void Lingo::b_spriteBox(int nargs) {
g_lingo->printStubWithArglist("b_spriteBox", nargs);
@@ -547,6 +698,10 @@ void Lingo::b_zoomBox(int nargs) {
g_lingo->dropStack(nargs);
}
+void Lingo::b_updateStage(int nargs) {
+ warning("STUB: b_updateStage");
+}
+
///////////////////
@@ -594,5 +749,75 @@ void Lingo::b_mciwait(int nargs) {
g_lingo->func_mciwait(*d.u.s);
}
+///////////////////
+// Constants
+///////////////////
+void Lingo::b_backspace(int nargs) {
+ g_lingo->push(Datum(new Common::String("\b")));
+}
+
+void Lingo::b_empty(int nargs) {
+ g_lingo->push(Datum(new Common::String("")));
+}
+
+void Lingo::b_enter(int nargs) {
+ g_lingo->push(Datum(new Common::String("\n")));
+}
+
+void Lingo::b_false(int nargs) {
+ g_lingo->push(Datum(0));
+}
+
+void Lingo::b_quote(int nargs) {
+ g_lingo->push(Datum(new Common::String("\"")));
+}
+
+void Lingo::b_return(int nargs) {
+ g_lingo->push(Datum(new Common::String("\r")));
+}
+
+void Lingo::b_tab(int nargs) {
+ g_lingo->push(Datum(new Common::String("\t")));
+}
+
+void Lingo::b_true(int nargs) {
+ g_lingo->push(Datum(1));
+}
+
+///////////////////
+// Factory
+///////////////////
+void Lingo::b_factory(int nargs) {
+ // This is intentionally empty
+}
+
+void Lingo::factoryCall(Common::String &name, int nargs) {
+ Common::String s("factoryCall: ");
+
+ s += name;
+
+ convertVOIDtoString(0, nargs);
+
+ printStubWithArglist(s.c_str(), nargs);
+
+ Datum method = _stack[_stack.size() - nargs + 0];
+
+ drop(nargs - 1);
+
+ s = name + "-" + *method.u.s;
+
+ debugC(3, kDebugLingoExec, "Stack size before call: %d, nargs: %d", _stack.size(), nargs);
+ call(s, nargs);
+ debugC(3, kDebugLingoExec, "Stack size after call: %d", _stack.size());
+
+ if (!method.u.s->compareToIgnoreCase("mNew")) {
+ Datum d;
+
+ d.type = OBJECT;
+ d.u.s = new Common::String(name);
+
+ g_lingo->push(d);
+ }
+}
} // End of namespace Director
diff --git a/engines/director/lingo/lingo-code.cpp b/engines/director/lingo/lingo-code.cpp
index fe1c62f3df..66f16536f8 100644
--- a/engines/director/lingo/lingo-code.cpp
+++ b/engines/director/lingo/lingo-code.cpp
@@ -43,14 +43,77 @@
// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
// THIS SOFTWARE.
-#include "engines/director/lingo/lingo.h"
-#include "common/file.h"
-#include "audio/decoders/wave.h"
-
+#include "director/lingo/lingo.h"
#include "director/lingo/lingo-gr.h"
namespace Director {
+static struct FuncDescr {
+ const inst func;
+ const char *name;
+ const char *args;
+} funcDescr[] = {
+ { 0, "STOP", "" },
+ { Lingo::c_xpop, "c_xpop", "" },
+ { Lingo::c_printtop, "c_printtop", "" },
+ { Lingo::c_constpush, "c_constpush", "i" },
+ { Lingo::c_voidpush, "c_voidpush", "" },
+ { Lingo::c_fconstpush, "c_fconstpush", "f" },
+ { Lingo::c_stringpush, "c_stringpush", "s" },
+ { Lingo::c_varpush, "c_varpush", "s" },
+ { Lingo::c_assign, "c_assign", "" },
+ { Lingo::c_eval, "c_eval", "s" },
+ { Lingo::c_theentitypush,"c_theentitypush","ii" }, // entity, field
+ { Lingo::c_theentityassign,"c_theentityassign","ii" },
+ { Lingo::c_swap, "c_swap", "" },
+ { Lingo::c_add, "c_add", "" },
+ { Lingo::c_sub, "c_sub", "" },
+ { Lingo::c_mul, "c_mul", "" },
+ { Lingo::c_div, "c_div", "" },
+ { Lingo::c_mod, "c_mod", "" },
+ { Lingo::c_negate, "c_negate", "" },
+ { Lingo::c_ampersand, "c_ampersand", "" },
+ { Lingo::c_concat, "c_concat", "" },
+ { Lingo::c_contains, "c_contains", "" },
+ { Lingo::c_starts, "c_starts", "" },
+ { Lingo::c_intersects, "c_intersects", "" },
+ { Lingo::c_within, "c_within", "" },
+ { Lingo::c_and, "c_and", "" },
+ { Lingo::c_or, "c_or", "" },
+ { Lingo::c_not, "c_not", "" },
+ { Lingo::c_eq, "c_eq", "" },
+ { Lingo::c_neq, "c_neq", "" },
+ { Lingo::c_gt, "c_gt", "" },
+ { Lingo::c_lt, "c_lt", "" },
+ { Lingo::c_ge, "c_ge", "" },
+ { Lingo::c_le, "c_le", "" },
+ { Lingo::c_repeatwhilecode,"c_repeatwhilecode","oo" },
+ { Lingo::c_repeatwithcode,"c_repeatwithcode","ooooos" },
+ { Lingo::c_exitRepeat, "c_exitRepeat", "" },
+ { Lingo::c_ifcode, "c_ifcode", "oooi" },
+ { Lingo::c_whencode, "c_whencode", "os" },
+ { Lingo::c_goto, "c_goto", "" },
+ { Lingo::c_gotoloop, "c_gotoloop", "" },
+ { Lingo::c_gotonext, "c_gotonext", "" },
+ { Lingo::c_gotoprevious,"c_gotoprevious","" },
+ { Lingo::c_play, "c_play", "" },
+ { Lingo::c_playdone, "c_playdone", "" },
+ { Lingo::c_call, "c_call", "si" },
+ { Lingo::c_procret, "c_procret", "" },
+ { Lingo::c_global, "c_global", "s" },
+ { Lingo::c_instance, "c_instance", "s" },
+ { Lingo::c_open, "c_open", "" },
+ { 0, 0, 0 }
+};
+
+void Lingo::initFuncs() {
+ Symbol sym;
+ for (FuncDescr *fnc = funcDescr; fnc->name; fnc++) {
+ sym.u.func = fnc->func;
+ _functions[(void *)sym.u.s] = new FuncDesc(fnc->name, fnc->args);
+ }
+}
+
void Lingo::push(Datum d) {
_stack.push_back(d);
}
@@ -108,6 +171,9 @@ void Lingo::c_printtop(void) {
case SYMBOL:
warning("%s", d.type2str(true));
break;
+ case OBJECT:
+ warning("#%s", d.u.s->c_str());
+ break;
default:
warning("--unknown--");
}
@@ -140,19 +206,25 @@ void Lingo::c_fconstpush() {
}
void Lingo::c_stringpush() {
- Datum d;
char *s = (char *)&(*g_lingo->_currentScript)[g_lingo->_pc];
g_lingo->_pc += g_lingo->calcStringAlignment(s);
- d.u.s = new Common::String(s);
- d.type = STRING;
- g_lingo->push(d);
+ g_lingo->push(Datum(new Common::String(s)));
}
void Lingo::c_varpush() {
char *name = (char *)&(*g_lingo->_currentScript)[g_lingo->_pc];
Datum d;
+ g_lingo->_pc += g_lingo->calcStringAlignment(name);
+
+ if (g_lingo->_handlers.contains(name)) {
+ d.type = HANDLER;
+ d.u.s = new Common::String(name);
+ g_lingo->push(d);
+ return;
+ }
+
d.u.sym = g_lingo->lookupVar(name);
if (d.u.sym->type == CASTREF) {
d.type = INT;
@@ -165,8 +237,6 @@ void Lingo::c_varpush() {
d.type = VAR;
}
- g_lingo->_pc += g_lingo->calcStringAlignment(name);
-
g_lingo->push(d);
}
@@ -204,8 +274,11 @@ void Lingo::c_assign() {
delete d2.u.arr;
} else if (d2.type == SYMBOL) {
d1.u.sym->u.i = d2.u.i;
+ } else if (d2.type == OBJECT) {
+ d1.u.sym->u.s = d2.u.s;
} else {
warning("c_assign: unhandled type: %s", d2.type2str());
+ d1.u.sym->u.s = d2.u.s;
}
d1.u.sym->type = d2.type;
@@ -232,6 +305,12 @@ void Lingo::c_eval() {
Datum d;
d = g_lingo->pop();
+ if (d.type == HANDLER) {
+ g_lingo->call(*d.u.s, 0);
+ delete d.u.s;
+ return;
+ }
+
if (d.type != VAR) { // It could be cast ref
g_lingo->push(d);
return;
@@ -253,7 +332,7 @@ void Lingo::c_eval() {
else if (d.u.sym->type == SYMBOL)
d.u.i = d.u.sym->u.i;
else if (d.u.sym->type == VOID)
- d.u.s = new Common::String(*d.u.sym->name);
+ d.u.s = new Common::String(d.u.sym->name);
else
warning("c_eval: unhandled type: %s", d.type2str());
@@ -346,6 +425,21 @@ void Lingo::c_div() {
g_lingo->push(d1);
}
+void Lingo::c_mod() {
+ Datum d2 = g_lingo->pop();
+ d2.toInt();
+
+ if (d2.u.i == 0)
+ error("division by zero");
+
+ Datum d1 = g_lingo->pop();
+ d1.toInt();
+
+ d1.u.i %= d2.u.i;
+
+ g_lingo->push(d1);
+}
+
void Lingo::c_negate() {
Datum d = g_lingo->pop();
@@ -578,6 +672,11 @@ void Lingo::c_repeatwhilecode(void) {
if (g_lingo->_returning)
break;
+ if (g_lingo->_exitRepeat) {
+ g_lingo->_exitRepeat = false;
+ break;
+ }
+
g_lingo->execute(savepc + 2); /* condition */
d = g_lingo->pop();
d.toInt();
@@ -614,6 +713,11 @@ void Lingo::c_repeatwithcode(void) {
if (g_lingo->_returning)
break;
+ if (g_lingo->_exitRepeat) {
+ g_lingo->_exitRepeat = false;
+ break;
+ }
+
counter->u.i += inc;
g_lingo->execute(finish); /* condition */
d = g_lingo->pop();
@@ -627,6 +731,10 @@ void Lingo::c_repeatwithcode(void) {
g_lingo->_pc = end; /* next stmt */
}
+void Lingo::c_exitRepeat(void) {
+ g_lingo->_exitRepeat = true;
+}
+
void Lingo::c_ifcode() {
Datum d;
int savepc = g_lingo->_pc; /* then part */
@@ -636,24 +744,48 @@ void Lingo::c_ifcode() {
int end = READ_UINT32(&(*g_lingo->_currentScript)[savepc + 2]);
int skipEnd = READ_UINT32(&(*g_lingo->_currentScript)[savepc + 3]);
- debug(8, "executing cond (have to %s end)", skipEnd ? "skip" : "execute");
+ debugC(8, kDebugLingoExec, "executing cond (have to %s end)", skipEnd ? "skip" : "execute");
g_lingo->execute(savepc + 4); /* condition */
d = g_lingo->pop();
if (d.toInt()) {
- debug(8, "executing then");
+ debugC(8, kDebugLingoExec, "executing then");
g_lingo->execute(then);
} else if (elsep) { /* else part? */
- debug(8, "executing else");
+ debugC(8, kDebugLingoExec, "executing else");
g_lingo->execute(elsep);
}
if (!g_lingo->_returning && !skipEnd) {
g_lingo->_pc = end; /* next stmt */
- debug(8, "executing end");
- } else
- debug(8, "Skipped end");
+ debugC(8, kDebugLingoExec, "executing end");
+ } else {
+ debugC(8, kDebugLingoExec, "Skipped end");
+ }
+}
+
+void Lingo::c_whencode() {
+ Datum d;
+ int start = g_lingo->_pc;
+ int end = READ_UINT32(&(*g_lingo->_currentScript)[start]);
+ Common::String eventname((char *)&(*g_lingo->_currentScript)[start + 1]);
+
+ start += g_lingo->calcStringAlignment(eventname.c_str()) + 1;
+
+ debugC(3, kDebugLingoExec, "c_whencode([%5d][%5d], %s)", start, end, eventname.c_str());
+
+ g_lingo->define(eventname, start, 0, NULL, end);
+
+ if (debugChannelSet(3, kDebugLingoExec)) {
+ int pc = start;
+ while (pc <= end) {
+ Common::String instr = g_lingo->decodeInstruction(pc, &pc);
+ debugC(3, kDebugLingoExec, "[%5d] %s", pc, instr.c_str());
+ }
+ }
+
+ g_lingo->_pc = end;
}
//************************
@@ -718,11 +850,24 @@ void Lingo::c_call() {
g_lingo->_pc += g_lingo->calcStringAlignment(name.c_str());
int nargs = READ_UINT32(&(*g_lingo->_currentScript)[g_lingo->_pc++]);
+
+ g_lingo->call(name, nargs);
+}
+
+void Lingo::call(Common::String name, int nargs) {
bool drop = false;
Symbol *sym;
if (!g_lingo->_handlers.contains(name)) {
+ Symbol *s = g_lingo->lookupVar(name.c_str(), false);
+ if (s && s->type == OBJECT) {
+ debugC(3, kDebugLingoExec, "Dereferencing object reference: %s to %s", name.c_str(), s->u.s->c_str());
+ name = *s->u.s;
+ }
+ }
+
+ if (!g_lingo->_handlers.contains(name)) {
warning("Call to undefined handler '%s'. Dropping %d stack items", name.c_str(), nargs);
drop = true;
} else {
@@ -755,7 +900,10 @@ void Lingo::c_call() {
}
if (sym->type == BLTIN) {
- (*sym->u.bltin)(nargs);
+ if (sym->u.bltin == b_factory)
+ g_lingo->factoryCall(name, nargs);
+ else
+ (*sym->u.bltin)(nargs);
return;
}
@@ -768,6 +916,7 @@ void Lingo::c_call() {
g_lingo->push(d);
}
+ debugC(5, kDebugLingoExec, "Pushing frame %d", g_lingo->_callstack.size() + 1);
CFrame *fp = new CFrame;
fp->sp = sym;
@@ -793,7 +942,10 @@ void Lingo::c_procret() {
return;
}
+ debugC(5, kDebugLingoExec, "Popping frame %d", g_lingo->_callstack.size() + 1);
+
CFrame *fp = g_lingo->_callstack.back();
+ g_lingo->_callstack.pop_back();
g_lingo->_currentScript = fp->retscript;
g_lingo->_pc = fp->retpc;
@@ -822,6 +974,14 @@ void Lingo::c_global() {
g_lingo->_pc += g_lingo->calcStringAlignment(name.c_str());
}
+void Lingo::c_instance() {
+ Common::String name((char *)&(*g_lingo->_currentScript)[g_lingo->_pc]);
+
+ warning("STUB: c_instance(%s)", name.c_str());
+
+ g_lingo->_pc += g_lingo->calcStringAlignment(name.c_str());
+}
+
void Lingo::c_open() {
Datum d2 = g_lingo->pop();
Datum d1 = g_lingo->pop();
diff --git a/engines/director/lingo/lingo-codegen.cpp b/engines/director/lingo/lingo-codegen.cpp
index 4284fa7452..440efb5b44 100644
--- a/engines/director/lingo/lingo-codegen.cpp
+++ b/engines/director/lingo/lingo-codegen.cpp
@@ -43,7 +43,7 @@
// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
// THIS SOFTWARE.
-#include "engines/director/lingo/lingo.h"
+#include "director/lingo/lingo.h"
#include "common/file.h"
#include "audio/decoders/wave.h"
@@ -53,17 +53,85 @@ namespace Director {
void Lingo::execute(int pc) {
for(_pc = pc; (*_currentScript)[_pc] != STOP && !_returning;) {
+ Common::String instr = decodeInstruction(_pc);
- for (uint i = 0; i < _stack.size(); i++) {
- debugN(5, "%d ", _stack[i].u.i);
- }
- debug(5, "%s", "");
+ if (debugChannelSet(5, kDebugLingoExec))
+ printStack("Stack before: ");
+
+ debugC(1, kDebugLingoExec, "[%3d]: %s", _pc, instr.c_str());
_pc++;
(*((*_currentScript)[_pc - 1]))();
+
+ if (debugChannelSet(5, kDebugLingoExec))
+ printStack("Stack after: ");
}
}
+void Lingo::printStack(const char *s) {
+ Common::String stack(s);
+
+ for (uint i = 0; i < _stack.size(); i++) {
+ Datum d = _stack[i];
+ d.toString();
+ stack += Common::String::format("<%s> ", d.u.s->c_str());
+ }
+ debugC(5, kDebugLingoExec, "%s", stack.c_str());
+}
+
+Common::String Lingo::decodeInstruction(int pc, int *newPc) {
+ Symbol sym;
+ Common::String res;
+
+ sym.u.func = (*_currentScript)[pc++];
+ if (_functions.contains((void *)sym.u.s)) {
+ res = _functions[(void *)sym.u.s]->name;
+ const char *pars = _functions[(void *)sym.u.s]->proto;
+ inst i;
+
+ while (*pars) {
+ switch (*pars++) {
+ case 'i':
+ {
+ i = (*_currentScript)[pc++];
+ int v = READ_UINT32(&i);
+
+ res += Common::String::format(" %d", v);
+ break;
+ }
+ case 'o':
+ {
+ i = (*_currentScript)[pc++];
+ int v = READ_UINT32(&i);
+
+ res += Common::String::format(" [%5d]", v);
+ break;
+ }
+ case 's':
+ {
+ char *s = (char *)&(*_currentScript)[pc];
+ pc += calcStringAlignment(s);
+
+ res += Common::String::format(" \"%s\"", s);
+ break;
+ }
+ default:
+ warning("decodeInstruction: Unknown parameter type: %c", pars[-1]);
+ }
+
+ if (*pars)
+ res += ',';
+ }
+ } else {
+ res = "<unknown>";
+ }
+
+ if (newPc)
+ *newPc = pc;
+
+ return res;
+}
+
Symbol *Lingo::lookupVar(const char *name, bool create, bool putInGlobalList) {
Symbol *sym;
@@ -123,13 +191,13 @@ void Lingo::cleanLocalVars() {
delete g_lingo->_localvars;
}
-void Lingo::define(Common::String &name, int start, int nargs, Common::String *prefix) {
+void Lingo::define(Common::String &name, int start, int nargs, Common::String *prefix, int end) {
Symbol *sym;
if (prefix)
name = *prefix + "-" + name;
- debug(3, "define(\"%s\", %d, %d, %d)", name.c_str(), start, _currentScript->size() - 1, nargs);
+ debugC(3, kDebugLingoCompile, "define(\"%s\", %d, %d, %d)", name.c_str(), start, _currentScript->size() - 1, nargs);
if (!_handlers.contains(name)) { // Create variable if it was not defined
sym = new Symbol;
@@ -146,7 +214,10 @@ void Lingo::define(Common::String &name, int start, int nargs, Common::String *p
delete sym->u.defn;
}
- sym->u.defn = new ScriptData(&(*_currentScript)[start], _currentScript->size() - start + 1);
+ if (end == -1)
+ end = _currentScript->size();
+
+ sym->u.defn = new ScriptData(&(*_currentScript)[start], end - start + 1);
sym->nargs = nargs;
}
@@ -221,7 +292,7 @@ int Lingo::codeFunc(Common::String *s, int numpar) {
if (s->equalsIgnoreCase("me")) {
if (!g_lingo->_currentFactory.empty()) {
g_lingo->codeString(g_lingo->_currentFactory.c_str());
- debug(2, "Repaced 'me' with %s", g_lingo->_currentFactory.c_str());
+ debugC(2, kDebugLingoCompile, "Replaced 'me' with %s", g_lingo->_currentFactory.c_str());
} else {
warning("'me' out of factory method");
g_lingo->codeString(s->c_str());
@@ -270,6 +341,18 @@ void Lingo::processIf(int elselabel, int endlabel) {
void Lingo::codeFactory(Common::String &name) {
_currentFactory = name;
+
+ Symbol *sym = new Symbol;
+
+ sym->name = (char *)calloc(name.size() + 1, 1);
+ Common::strlcpy(sym->name, name.c_str(), name.size());
+ sym->type = BLTIN;
+ sym->nargs = -1;
+ sym->maxArgs = 0;
+ sym->parens = true;
+ sym->u.bltin = g_lingo->b_factory;
+
+ _handlers[name] = sym;
}
}
diff --git a/engines/director/lingo/lingo-funcs.cpp b/engines/director/lingo/lingo-funcs.cpp
index da2cd5f358..e22044c1e9 100644
--- a/engines/director/lingo/lingo-funcs.cpp
+++ b/engines/director/lingo/lingo-funcs.cpp
@@ -20,34 +20,12 @@
*
*/
-// Heavily inspired by hoc
-// Copyright (C) AT&T 1995
-// All Rights Reserved
-//
-// Permission to use, copy, modify, and distribute this software and
-// its documentation for any purpose and without fee is hereby
-// granted, provided that the above copyright notice appear in all
-// copies and that both that the copyright notice and this
-// permission notice and warranty disclaimer appear in supporting
-// documentation, and that the name of AT&T or any of its entities
-// not be used in advertising or publicity pertaining to
-// distribution of the software without specific, written prior
-// permission.
-//
-// AT&T DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
-// INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
-// IN NO EVENT SHALL AT&T OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
-// SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
-// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
-// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
-// ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
-// THIS SOFTWARE.
-
-#include "engines/director/lingo/lingo.h"
+#include "director/lingo/lingo.h"
#include "common/file.h"
#include "audio/decoders/wave.h"
#include "common/util.h"
#include "director/lingo/lingo-gr.h"
+#include "director/sound.h"
namespace Director {
diff --git a/engines/director/lingo/lingo-gr.cpp b/engines/director/lingo/lingo-gr.cpp
index 1758d5c5d6..f92e030c88 100644
--- a/engines/director/lingo/lingo-gr.cpp
+++ b/engines/director/lingo/lingo-gr.cpp
@@ -74,64 +74,67 @@
RECT = 263,
ARRAY = 264,
SYMBOL = 265,
- INT = 266,
- THEENTITY = 267,
- THEENTITYWITHID = 268,
- FLOAT = 269,
- BLTIN = 270,
- BLTINNOARGS = 271,
- BLTINNOARGSORONE = 272,
- BLTINONEARG = 273,
- BLTINARGLIST = 274,
- ID = 275,
- STRING = 276,
- HANDLER = 277,
- tDOWN = 278,
- tELSE = 279,
- tNLELSIF = 280,
- tEND = 281,
- tEXIT = 282,
- tFRAME = 283,
- tGLOBAL = 284,
- tGO = 285,
- tIF = 286,
- tINTO = 287,
- tLOOP = 288,
- tMACRO = 289,
- tMOVIE = 290,
- tNEXT = 291,
- tOF = 292,
- tPREVIOUS = 293,
- tPUT = 294,
- tREPEAT = 295,
- tSET = 296,
- tTHEN = 297,
- tTO = 298,
- tWHEN = 299,
- tWITH = 300,
- tWHILE = 301,
- tNLELSE = 302,
- tFACTORY = 303,
- tMETHOD = 304,
- tOPEN = 305,
- tPLAY = 306,
- tDONE = 307,
- tPLAYACCEL = 308,
- tGE = 309,
- tLE = 310,
- tGT = 311,
- tLT = 312,
- tEQ = 313,
- tNEQ = 314,
- tAND = 315,
- tOR = 316,
- tNOT = 317,
- tCONCAT = 318,
- tCONTAINS = 319,
- tSTARTS = 320,
- tSPRITE = 321,
- tINTERSECTS = 322,
- tWITHIN = 323
+ OBJECT = 266,
+ INT = 267,
+ THEENTITY = 268,
+ THEENTITYWITHID = 269,
+ FLOAT = 270,
+ BLTIN = 271,
+ BLTINNOARGS = 272,
+ BLTINNOARGSORONE = 273,
+ BLTINONEARG = 274,
+ BLTINARGLIST = 275,
+ ID = 276,
+ STRING = 277,
+ HANDLER = 278,
+ tDOWN = 279,
+ tELSE = 280,
+ tNLELSIF = 281,
+ tEND = 282,
+ tEXIT = 283,
+ tFRAME = 284,
+ tGLOBAL = 285,
+ tGO = 286,
+ tIF = 287,
+ tINTO = 288,
+ tLOOP = 289,
+ tMACRO = 290,
+ tMOVIE = 291,
+ tNEXT = 292,
+ tOF = 293,
+ tPREVIOUS = 294,
+ tPUT = 295,
+ tREPEAT = 296,
+ tSET = 297,
+ tTHEN = 298,
+ tTO = 299,
+ tWHEN = 300,
+ tWITH = 301,
+ tWHILE = 302,
+ tNLELSE = 303,
+ tFACTORY = 304,
+ tMETHOD = 305,
+ tOPEN = 306,
+ tPLAY = 307,
+ tDONE = 308,
+ tPLAYACCEL = 309,
+ tINSTANCE = 310,
+ tGE = 311,
+ tLE = 312,
+ tGT = 313,
+ tLT = 314,
+ tEQ = 315,
+ tNEQ = 316,
+ tAND = 317,
+ tOR = 318,
+ tNOT = 319,
+ tMOD = 320,
+ tCONCAT = 321,
+ tCONTAINS = 322,
+ tSTARTS = 323,
+ tSPRITE = 324,
+ tINTERSECTS = 325,
+ tWITHIN = 326
};
#endif
/* Tokens. */
@@ -143,64 +146,67 @@
#define RECT 263
#define ARRAY 264
#define SYMBOL 265
-#define INT 266
-#define THEENTITY 267
-#define THEENTITYWITHID 268
-#define FLOAT 269
-#define BLTIN 270
-#define BLTINNOARGS 271
-#define BLTINNOARGSORONE 272
-#define BLTINONEARG 273
-#define BLTINARGLIST 274
-#define ID 275
-#define STRING 276
-#define HANDLER 277
-#define tDOWN 278
-#define tELSE 279
-#define tNLELSIF 280
-#define tEND 281
-#define tEXIT 282
-#define tFRAME 283
-#define tGLOBAL 284
-#define tGO 285
-#define tIF 286
-#define tINTO 287
-#define tLOOP 288
-#define tMACRO 289
-#define tMOVIE 290
-#define tNEXT 291
-#define tOF 292
-#define tPREVIOUS 293
-#define tPUT 294
-#define tREPEAT 295
-#define tSET 296
-#define tTHEN 297
-#define tTO 298
-#define tWHEN 299
-#define tWITH 300
-#define tWHILE 301
-#define tNLELSE 302
-#define tFACTORY 303
-#define tMETHOD 304
-#define tOPEN 305
-#define tPLAY 306
-#define tDONE 307
-#define tPLAYACCEL 308
-#define tGE 309
-#define tLE 310
-#define tGT 311
-#define tLT 312
-#define tEQ 313
-#define tNEQ 314
-#define tAND 315
-#define tOR 316
-#define tNOT 317
-#define tCONCAT 318
-#define tCONTAINS 319
-#define tSTARTS 320
-#define tSPRITE 321
-#define tINTERSECTS 322
-#define tWITHIN 323
+#define OBJECT 266
+#define INT 267
+#define THEENTITY 268
+#define THEENTITYWITHID 269
+#define FLOAT 270
+#define BLTIN 271
+#define BLTINNOARGS 272
+#define BLTINNOARGSORONE 273
+#define BLTINONEARG 274
+#define BLTINARGLIST 275
+#define ID 276
+#define STRING 277
+#define HANDLER 278
+#define tDOWN 279
+#define tELSE 280
+#define tNLELSIF 281
+#define tEND 282
+#define tEXIT 283
+#define tFRAME 284
+#define tGLOBAL 285
+#define tGO 286
+#define tIF 287
+#define tINTO 288
+#define tLOOP 289
+#define tMACRO 290
+#define tMOVIE 291
+#define tNEXT 292
+#define tOF 293
+#define tPREVIOUS 294
+#define tPUT 295
+#define tREPEAT 296
+#define tSET 297
+#define tTHEN 298
+#define tTO 299
+#define tWHEN 300
+#define tWITH 301
+#define tWHILE 302
+#define tNLELSE 303
+#define tFACTORY 304
+#define tMETHOD 305
+#define tOPEN 306
+#define tPLAY 307
+#define tDONE 308
+#define tPLAYACCEL 309
+#define tINSTANCE 310
+#define tGE 311
+#define tLE 312
+#define tGT 313
+#define tLT 314
+#define tEQ 315
+#define tNEQ 316
+#define tAND 317
+#define tOR 318
+#define tNOT 319
+#define tMOD 320
+#define tCONCAT 321
+#define tCONTAINS 322
+#define tSTARTS 323
+#define tSPRITE 324
+#define tINTERSECTS 325
+#define tWITHIN 326
@@ -219,7 +225,7 @@ extern int yylex();
extern int yyparse();
using namespace Director;
-void yyerror(char *s) {
+void yyerror(const char *s) {
g_lingo->_hadError = true;
warning("%s at line %d col %d", s, g_lingo->_linenumber, g_lingo->_colnumber);
}
@@ -258,7 +264,7 @@ typedef union YYSTYPE
Common::Array<double> *arr;
}
/* Line 193 of yacc.c. */
-#line 262 "engines/director/lingo/lingo-gr.cpp"
+#line 268 "engines/director/lingo/lingo-gr.cpp"
YYSTYPE;
# define yystype YYSTYPE /* obsolescent; will be withdrawn */
# define YYSTYPE_IS_DECLARED 1
@@ -271,7 +277,7 @@ typedef union YYSTYPE
/* Line 216 of yacc.c. */
-#line 275 "engines/director/lingo/lingo-gr.cpp"
+#line 281 "engines/director/lingo/lingo-gr.cpp"
#ifdef short
# undef short
@@ -484,22 +490,22 @@ union yyalloc
#endif
/* YYFINAL -- State number of the termination state. */
-#define YYFINAL 87
+#define YYFINAL 92
/* YYLAST -- Last index in YYTABLE. */
-#define YYLAST 921
+#define YYLAST 995
/* YYNTOKENS -- Number of terminals. */
-#define YYNTOKENS 82
+#define YYNTOKENS 85
/* YYNNTS -- Number of nonterminals. */
-#define YYNNTS 35
+#define YYNNTS 37
/* YYNRULES -- Number of rules. */
-#define YYNRULES 124
+#define YYNRULES 130
/* YYNRULES -- Number of states. */
-#define YYNSTATES 259
+#define YYNSTATES 269
/* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */
#define YYUNDEFTOK 2
-#define YYMAXUTOK 323
+#define YYMAXUTOK 326
#define YYTRANSLATE(YYX) \
((unsigned int) (YYX) <= YYMAXUTOK ? yytranslate[YYX] : YYUNDEFTOK)
@@ -508,12 +514,12 @@ union yyalloc
static const yytype_uint8 yytranslate[] =
{
0, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 75, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 79, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 2, 2, 2, 2, 2, 2, 2, 74, 80, 2,
- 76, 77, 72, 70, 81, 71, 2, 73, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 78, 73, 2,
+ 80, 81, 76, 74, 84, 75, 2, 77, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
- 79, 69, 78, 2, 2, 2, 2, 2, 2, 2,
+ 83, 72, 82, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
@@ -539,7 +545,7 @@ static const yytype_uint8 yytranslate[] =
35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
55, 56, 57, 58, 59, 60, 61, 62, 63, 64,
- 65, 66, 67, 68
+ 65, 66, 67, 68, 69, 70, 71
};
#if YYDEBUG
@@ -549,87 +555,91 @@ static const yytype_uint16 yyprhs[] =
{
0, 0, 3, 7, 9, 12, 14, 15, 17, 19,
21, 23, 25, 30, 35, 40, 46, 51, 56, 62,
- 64, 66, 68, 70, 79, 91, 104, 109, 118, 130,
- 142, 149, 160, 171, 172, 176, 179, 181, 184, 186,
- 193, 195, 201, 203, 207, 211, 214, 218, 220, 222,
- 223, 224, 225, 228, 231, 233, 235, 237, 239, 244,
- 246, 248, 251, 253, 257, 261, 265, 269, 273, 277,
- 281, 285, 289, 293, 297, 300, 304, 308, 312, 316,
- 319, 322, 326, 331, 336, 339, 341, 343, 345, 348,
- 351, 354, 356, 359, 364, 367, 369, 373, 376, 379,
- 382, 385, 389, 392, 395, 397, 401, 404, 407, 410,
- 414, 417, 418, 427, 430, 431, 440, 441, 443, 447,
- 452, 453, 457, 458, 460
+ 64, 66, 68, 70, 79, 91, 104, 108, 117, 129,
+ 141, 148, 159, 170, 171, 175, 178, 180, 183, 185,
+ 192, 194, 200, 202, 206, 210, 213, 217, 219, 221,
+ 222, 223, 224, 227, 230, 234, 236, 238, 240, 242,
+ 247, 249, 251, 254, 256, 260, 264, 268, 272, 276,
+ 280, 284, 288, 292, 296, 300, 304, 307, 311, 315,
+ 319, 323, 326, 329, 333, 338, 343, 346, 348, 350,
+ 353, 355, 358, 361, 364, 367, 369, 372, 377, 380,
+ 382, 386, 388, 392, 395, 398, 401, 404, 408, 411,
+ 414, 416, 420, 423, 426, 429, 433, 436, 437, 446,
+ 449, 450, 459, 460, 462, 466, 471, 472, 476, 477,
+ 479
};
/* YYRHS -- A `-1'-separated list of the rules' RHS. */
static const yytype_int8 yyrhs[] =
{
- 83, 0, -1, 83, 84, 85, -1, 85, -1, 1,
- 84, -1, 75, -1, -1, 110, -1, 104, -1, 115,
- -1, 86, -1, 88, -1, 39, 103, 32, 20, -1,
- 41, 20, 69, 103, -1, 41, 12, 69, 103, -1,
- 41, 13, 103, 69, 103, -1, 41, 20, 43, 103,
- -1, 41, 12, 43, 103, -1, 41, 13, 103, 43,
- 103, -1, 103, -1, 104, -1, 87, -1, 89, -1,
- 96, 76, 95, 77, 102, 101, 26, 40, -1, 97,
- 69, 103, 101, 43, 103, 101, 102, 101, 26, 40,
- -1, 97, 69, 103, 101, 23, 43, 103, 101, 102,
- 101, 26, 40, -1, 44, 20, 42, 103, -1, 98,
- 95, 42, 84, 102, 101, 26, 31, -1, 98, 95,
- 42, 84, 102, 101, 47, 102, 101, 26, 31, -1,
- 98, 95, 42, 84, 102, 101, 100, 91, 101, 26,
- 31, -1, 98, 95, 42, 100, 87, 101, -1, 98,
- 95, 42, 100, 87, 101, 47, 100, 87, 101, -1,
- 98, 95, 42, 100, 87, 101, 92, 101, 90, 101,
- -1, -1, 47, 100, 87, -1, 91, 94, -1, 94,
- -1, 92, 93, -1, 93, -1, 99, 95, 42, 100,
- 88, 101, -1, 92, -1, 99, 95, 42, 102, 101,
- -1, 103, -1, 103, 69, 103, -1, 76, 95, 77,
- -1, 40, 46, -1, 40, 45, 20, -1, 31, -1,
- 25, -1, -1, -1, -1, 102, 84, -1, 102, 88,
- -1, 11, -1, 14, -1, 21, -1, 16, -1, 20,
- 76, 116, 77, -1, 20, -1, 12, -1, 13, 103,
- -1, 86, -1, 103, 70, 103, -1, 103, 71, 103,
- -1, 103, 72, 103, -1, 103, 73, 103, -1, 103,
- 78, 103, -1, 103, 79, 103, -1, 103, 59, 103,
- -1, 103, 54, 103, -1, 103, 55, 103, -1, 103,
- 60, 103, -1, 103, 61, 103, -1, 62, 103, -1,
- 103, 80, 103, -1, 103, 63, 103, -1, 103, 64,
- 103, -1, 103, 65, 103, -1, 70, 103, -1, 71,
- 103, -1, 76, 103, 77, -1, 66, 103, 67, 103,
- -1, 66, 103, 68, 103, -1, 39, 103, -1, 106,
- -1, 109, -1, 27, -1, 29, 105, -1, 18, 103,
- -1, 17, 103, -1, 17, -1, 19, 116, -1, 50,
- 103, 45, 103, -1, 50, 103, -1, 20, -1, 105,
- 81, 20, -1, 30, 33, -1, 30, 36, -1, 30,
- 38, -1, 30, 107, -1, 30, 107, 108, -1, 30,
- 108, -1, 28, 103, -1, 103, -1, 37, 35, 103,
- -1, 35, 103, -1, 51, 52, -1, 51, 107, -1,
- 51, 107, 108, -1, 51, 108, -1, -1, 34, 20,
- 111, 100, 113, 84, 114, 102, -1, 48, 20, -1,
- -1, 49, 20, 112, 100, 113, 84, 114, 102, -1,
- -1, 20, -1, 113, 81, 20, -1, 113, 84, 81,
- 20, -1, -1, 20, 100, 116, -1, -1, 103, -1,
- 116, 81, 103, -1
+ 86, 0, -1, 86, 87, 88, -1, 88, -1, 1,
+ 87, -1, 79, -1, -1, 115, -1, 108, -1, 120,
+ -1, 89, -1, 91, -1, 40, 107, 33, 21, -1,
+ 42, 21, 72, 107, -1, 42, 13, 72, 107, -1,
+ 42, 14, 107, 72, 107, -1, 42, 21, 44, 107,
+ -1, 42, 13, 44, 107, -1, 42, 14, 107, 44,
+ 107, -1, 107, -1, 108, -1, 90, -1, 92, -1,
+ 99, 80, 98, 81, 105, 104, 27, 41, -1, 100,
+ 72, 107, 104, 44, 107, 104, 105, 104, 27, 41,
+ -1, 100, 72, 107, 104, 24, 44, 107, 104, 105,
+ 104, 27, 41, -1, 106, 107, 104, -1, 101, 98,
+ 43, 87, 105, 104, 27, 32, -1, 101, 98, 43,
+ 87, 105, 104, 48, 105, 104, 27, 32, -1, 101,
+ 98, 43, 87, 105, 104, 103, 94, 104, 27, 32,
+ -1, 101, 98, 43, 103, 90, 104, -1, 101, 98,
+ 43, 103, 90, 104, 48, 103, 90, 104, -1, 101,
+ 98, 43, 103, 90, 104, 95, 104, 93, 104, -1,
+ -1, 48, 103, 90, -1, 94, 97, -1, 97, -1,
+ 95, 96, -1, 96, -1, 102, 98, 43, 103, 91,
+ 104, -1, 95, -1, 102, 98, 43, 105, 104, -1,
+ 107, -1, 107, 72, 107, -1, 80, 98, 81, -1,
+ 41, 47, -1, 41, 46, 21, -1, 32, -1, 26,
+ -1, -1, -1, -1, 105, 87, -1, 105, 91, -1,
+ 45, 21, 43, -1, 12, -1, 15, -1, 22, -1,
+ 17, -1, 21, 80, 121, 81, -1, 21, -1, 13,
+ -1, 14, 107, -1, 89, -1, 107, 74, 107, -1,
+ 107, 75, 107, -1, 107, 76, 107, -1, 107, 77,
+ 107, -1, 107, 65, 107, -1, 107, 82, 107, -1,
+ 107, 83, 107, -1, 107, 61, 107, -1, 107, 56,
+ 107, -1, 107, 57, 107, -1, 107, 62, 107, -1,
+ 107, 63, 107, -1, 64, 107, -1, 107, 73, 107,
+ -1, 107, 66, 107, -1, 107, 67, 107, -1, 107,
+ 68, 107, -1, 74, 107, -1, 75, 107, -1, 80,
+ 107, 81, -1, 69, 107, 70, 107, -1, 69, 107,
+ 71, 107, -1, 40, 107, -1, 111, -1, 114, -1,
+ 28, 41, -1, 28, -1, 30, 109, -1, 55, 110,
+ -1, 19, 107, -1, 18, 107, -1, 18, -1, 20,
+ 121, -1, 51, 107, 46, 107, -1, 51, 107, -1,
+ 21, -1, 109, 84, 21, -1, 21, -1, 110, 84,
+ 21, -1, 31, 34, -1, 31, 37, -1, 31, 39,
+ -1, 31, 112, -1, 31, 112, 113, -1, 31, 113,
+ -1, 29, 107, -1, 107, -1, 38, 36, 107, -1,
+ 36, 107, -1, 52, 53, -1, 52, 112, -1, 52,
+ 112, 113, -1, 52, 113, -1, -1, 35, 21, 116,
+ 103, 118, 87, 119, 105, -1, 49, 21, -1, -1,
+ 50, 21, 117, 103, 118, 87, 119, 105, -1, -1,
+ 21, -1, 118, 84, 21, -1, 118, 87, 84, 21,
+ -1, -1, 21, 103, 121, -1, -1, 107, -1, 121,
+ 84, 107, -1
};
/* YYRLINE[YYN] -- source line where rule number YYN was defined. */
static const yytype_uint16 yyrline[] =
{
- 0, 103, 103, 104, 105, 108, 113, 114, 115, 116,
- 117, 118, 121, 127, 133, 141, 149, 155, 163, 172,
- 173, 175, 176, 181, 192, 208, 220, 225, 232, 241,
- 250, 260, 270, 281, 282, 285, 286, 289, 290, 293,
- 301, 302, 310, 311, 312, 314, 316, 322, 328, 335,
- 337, 339, 340, 341, 344, 345, 348, 351, 355, 358,
- 362, 369, 375, 376, 377, 378, 379, 380, 381, 382,
- 383, 384, 385, 386, 387, 388, 389, 390, 391, 392,
- 393, 394, 395, 396, 399, 400, 401, 402, 404, 405,
- 408, 411, 414, 415, 416, 419, 420, 431, 432, 433,
- 434, 437, 440, 445, 446, 449, 450, 453, 454, 457,
- 460, 490, 490, 496, 499, 499, 505, 506, 507, 508,
- 510, 514, 522, 523, 524
+ 0, 105, 105, 106, 107, 110, 115, 116, 117, 118,
+ 119, 120, 123, 129, 135, 143, 151, 157, 165, 174,
+ 175, 177, 178, 183, 194, 210, 222, 230, 237, 246,
+ 255, 265, 275, 286, 287, 290, 291, 294, 295, 298,
+ 306, 307, 315, 316, 317, 319, 321, 327, 333, 340,
+ 342, 344, 345, 346, 349, 355, 356, 359, 362, 365,
+ 368, 372, 379, 385, 386, 387, 388, 389, 390, 391,
+ 392, 393, 394, 395, 396, 397, 398, 399, 400, 401,
+ 402, 403, 404, 405, 406, 407, 410, 411, 412, 413,
+ 414, 416, 417, 418, 421, 424, 427, 428, 429, 432,
+ 433, 436, 437, 448, 449, 450, 451, 454, 457, 462,
+ 463, 466, 467, 470, 471, 474, 477, 507, 507, 513,
+ 516, 516, 521, 522, 523, 524, 526, 530, 538, 539,
+ 540
};
#endif
@@ -639,23 +649,24 @@ static const yytype_uint16 yyrline[] =
static const char *const yytname[] =
{
"$end", "error", "$undefined", "UNARY", "CASTREF", "VOID", "VAR",
- "POINT", "RECT", "ARRAY", "SYMBOL", "INT", "THEENTITY",
+ "POINT", "RECT", "ARRAY", "SYMBOL", "OBJECT", "INT", "THEENTITY",
"THEENTITYWITHID", "FLOAT", "BLTIN", "BLTINNOARGS", "BLTINNOARGSORONE",
"BLTINONEARG", "BLTINARGLIST", "ID", "STRING", "HANDLER", "tDOWN",
"tELSE", "tNLELSIF", "tEND", "tEXIT", "tFRAME", "tGLOBAL", "tGO", "tIF",
"tINTO", "tLOOP", "tMACRO", "tMOVIE", "tNEXT", "tOF", "tPREVIOUS",
"tPUT", "tREPEAT", "tSET", "tTHEN", "tTO", "tWHEN", "tWITH", "tWHILE",
"tNLELSE", "tFACTORY", "tMETHOD", "tOPEN", "tPLAY", "tDONE",
- "tPLAYACCEL", "tGE", "tLE", "tGT", "tLT", "tEQ", "tNEQ", "tAND", "tOR",
- "tNOT", "tCONCAT", "tCONTAINS", "tSTARTS", "tSPRITE", "tINTERSECTS",
- "tWITHIN", "'='", "'+'", "'-'", "'*'", "'/'", "'%'", "'\\n'", "'('",
- "')'", "'>'", "'<'", "'&'", "','", "$accept", "program", "nl",
- "programline", "asgn", "stmtoneliner", "stmt", "ifstmt",
+ "tPLAYACCEL", "tINSTANCE", "tGE", "tLE", "tGT", "tLT", "tEQ", "tNEQ",
+ "tAND", "tOR", "tNOT", "tMOD", "tCONCAT", "tCONTAINS", "tSTARTS",
+ "tSPRITE", "tINTERSECTS", "tWITHIN", "'='", "'&'", "'+'", "'-'", "'*'",
+ "'/'", "'%'", "'\\n'", "'('", "')'", "'>'", "'<'", "','", "$accept",
+ "program", "nl", "programline", "asgn", "stmtoneliner", "stmt", "ifstmt",
"elsestmtoneliner", "elseifstmt", "elseifstmtoneliner",
"elseifstmtoneliner1", "elseifstmt1", "cond", "repeatwhile",
- "repeatwith", "if", "elseif", "begin", "end", "stmtlist", "expr", "func",
- "globallist", "gotofunc", "gotoframe", "gotomovie", "playfunc", "defn",
- "@1", "@2", "argdef", "argstore", "macro", "arglist", 0
+ "repeatwith", "if", "elseif", "begin", "end", "stmtlist", "when", "expr",
+ "func", "globallist", "instancelist", "gotofunc", "gotoframe",
+ "gotomovie", "playfunc", "defn", "@1", "@2", "argdef", "argstore",
+ "macro", "arglist", 0
};
#endif
@@ -670,28 +681,29 @@ static const yytype_uint16 yytoknum[] =
285, 286, 287, 288, 289, 290, 291, 292, 293, 294,
295, 296, 297, 298, 299, 300, 301, 302, 303, 304,
305, 306, 307, 308, 309, 310, 311, 312, 313, 314,
- 315, 316, 317, 318, 319, 320, 321, 322, 323, 61,
- 43, 45, 42, 47, 37, 10, 40, 41, 62, 60,
- 38, 44
+ 315, 316, 317, 318, 319, 320, 321, 322, 323, 324,
+ 325, 326, 61, 38, 43, 45, 42, 47, 37, 10,
+ 40, 41, 62, 60, 44
};
# endif
/* YYR1[YYN] -- Symbol number of symbol that rule YYN derives. */
static const yytype_uint8 yyr1[] =
{
- 0, 82, 83, 83, 83, 84, 85, 85, 85, 85,
- 85, 85, 86, 86, 86, 86, 86, 86, 86, 87,
- 87, 88, 88, 88, 88, 88, 88, 89, 89, 89,
- 89, 89, 89, 90, 90, 91, 91, 92, 92, 93,
- 94, 94, 95, 95, 95, 96, 97, 98, 99, 100,
- 101, 102, 102, 102, 103, 103, 103, 103, 103, 103,
- 103, 103, 103, 103, 103, 103, 103, 103, 103, 103,
- 103, 103, 103, 103, 103, 103, 103, 103, 103, 103,
- 103, 103, 103, 103, 104, 104, 104, 104, 104, 104,
- 104, 104, 104, 104, 104, 105, 105, 106, 106, 106,
- 106, 106, 106, 107, 107, 108, 108, 109, 109, 109,
- 109, 111, 110, 110, 112, 110, 113, 113, 113, 113,
- 114, 115, 116, 116, 116
+ 0, 85, 86, 86, 86, 87, 88, 88, 88, 88,
+ 88, 88, 89, 89, 89, 89, 89, 89, 89, 90,
+ 90, 91, 91, 91, 91, 91, 91, 92, 92, 92,
+ 92, 92, 92, 93, 93, 94, 94, 95, 95, 96,
+ 97, 97, 98, 98, 98, 99, 100, 101, 102, 103,
+ 104, 105, 105, 105, 106, 107, 107, 107, 107, 107,
+ 107, 107, 107, 107, 107, 107, 107, 107, 107, 107,
+ 107, 107, 107, 107, 107, 107, 107, 107, 107, 107,
+ 107, 107, 107, 107, 107, 107, 108, 108, 108, 108,
+ 108, 108, 108, 108, 108, 108, 108, 108, 108, 109,
+ 109, 110, 110, 111, 111, 111, 111, 111, 111, 112,
+ 112, 113, 113, 114, 114, 114, 114, 116, 115, 115,
+ 117, 115, 118, 118, 118, 118, 119, 120, 121, 121,
+ 121
};
/* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */
@@ -699,17 +711,18 @@ static const yytype_uint8 yyr2[] =
{
0, 2, 3, 1, 2, 1, 0, 1, 1, 1,
1, 1, 4, 4, 4, 5, 4, 4, 5, 1,
- 1, 1, 1, 8, 11, 12, 4, 8, 11, 11,
+ 1, 1, 1, 8, 11, 12, 3, 8, 11, 11,
6, 10, 10, 0, 3, 2, 1, 2, 1, 6,
1, 5, 1, 3, 3, 2, 3, 1, 1, 0,
- 0, 0, 2, 2, 1, 1, 1, 1, 4, 1,
- 1, 2, 1, 3, 3, 3, 3, 3, 3, 3,
- 3, 3, 3, 3, 2, 3, 3, 3, 3, 2,
- 2, 3, 4, 4, 2, 1, 1, 1, 2, 2,
- 2, 1, 2, 4, 2, 1, 3, 2, 2, 2,
- 2, 3, 2, 2, 1, 3, 2, 2, 2, 3,
- 2, 0, 8, 2, 0, 8, 0, 1, 3, 4,
- 0, 3, 0, 1, 3
+ 0, 0, 2, 2, 3, 1, 1, 1, 1, 4,
+ 1, 1, 2, 1, 3, 3, 3, 3, 3, 3,
+ 3, 3, 3, 3, 3, 3, 2, 3, 3, 3,
+ 3, 2, 2, 3, 4, 4, 2, 1, 1, 2,
+ 1, 2, 2, 2, 2, 1, 2, 4, 2, 1,
+ 3, 1, 3, 2, 2, 2, 2, 3, 2, 2,
+ 1, 3, 2, 2, 2, 3, 2, 0, 8, 2,
+ 0, 8, 0, 1, 3, 4, 0, 3, 0, 1,
+ 3
};
/* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state
@@ -717,28 +730,29 @@ static const yytype_uint8 yyr2[] =
means the default is an error. */
static const yytype_uint8 yydefact[] =
{
- 0, 0, 54, 60, 0, 55, 57, 91, 0, 122,
- 49, 56, 87, 0, 0, 47, 0, 0, 0, 0,
+ 0, 0, 55, 61, 0, 56, 58, 95, 0, 128,
+ 49, 57, 90, 0, 0, 47, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 3, 62, 21, 11, 22, 0, 0, 0, 19,
- 8, 85, 86, 7, 9, 5, 4, 59, 0, 62,
- 61, 90, 89, 123, 92, 122, 122, 95, 88, 0,
- 97, 0, 98, 0, 99, 104, 100, 102, 111, 84,
- 0, 45, 0, 0, 0, 0, 113, 114, 94, 107,
- 108, 110, 74, 0, 79, 80, 0, 1, 6, 0,
- 0, 0, 0, 42, 0, 0, 0, 0, 0, 0,
+ 0, 0, 3, 63, 21, 11, 22, 0, 0, 0,
+ 0, 19, 8, 87, 88, 7, 9, 5, 4, 60,
+ 0, 63, 62, 94, 93, 129, 96, 128, 128, 89,
+ 99, 91, 0, 103, 0, 104, 0, 105, 110, 106,
+ 108, 117, 86, 0, 45, 0, 0, 0, 0, 119,
+ 120, 98, 113, 114, 116, 101, 92, 76, 0, 81,
+ 82, 0, 1, 6, 0, 0, 0, 0, 42, 50,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 121, 0, 103, 106, 0, 101, 49, 0,
- 46, 0, 0, 0, 0, 0, 0, 49, 0, 109,
- 0, 0, 81, 2, 0, 50, 0, 0, 49, 0,
- 70, 71, 69, 72, 73, 76, 77, 78, 63, 64,
- 65, 66, 67, 68, 75, 124, 58, 96, 105, 116,
- 12, 17, 14, 0, 0, 16, 13, 26, 116, 93,
- 82, 83, 51, 0, 44, 51, 0, 43, 117, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 127,
+ 0, 109, 112, 0, 107, 49, 0, 46, 0, 0,
+ 0, 0, 0, 54, 49, 0, 115, 0, 0, 0,
+ 83, 2, 0, 50, 0, 0, 49, 0, 26, 72,
+ 73, 71, 74, 75, 68, 78, 79, 80, 77, 64,
+ 65, 66, 67, 69, 70, 130, 59, 100, 111, 122,
+ 12, 17, 14, 0, 0, 16, 13, 122, 97, 102,
+ 84, 85, 51, 0, 44, 51, 0, 43, 123, 0,
18, 15, 0, 50, 0, 0, 50, 50, 20, 0,
- 120, 120, 52, 53, 0, 0, 50, 49, 30, 118,
+ 126, 126, 52, 53, 0, 0, 50, 49, 30, 124,
0, 51, 51, 0, 50, 51, 0, 51, 0, 48,
- 49, 50, 38, 0, 119, 112, 115, 23, 51, 50,
+ 49, 50, 38, 0, 125, 118, 121, 23, 51, 50,
27, 50, 50, 40, 36, 0, 0, 37, 33, 0,
50, 0, 0, 35, 0, 0, 50, 49, 50, 49,
0, 0, 0, 0, 49, 31, 0, 32, 0, 0,
@@ -748,283 +762,299 @@ static const yytype_uint8 yydefact[] =
/* YYDEFGOTO[NTERM-NUM]. */
static const yytype_int16 yydefgoto[] =
{
- -1, 30, 192, 31, 49, 33, 193, 35, 238, 222,
- 223, 212, 224, 92, 36, 37, 38, 213, 248, 173,
- 183, 39, 188, 58, 41, 66, 67, 42, 43, 118,
- 127, 179, 201, 44, 54
+ -1, 31, 202, 32, 51, 34, 203, 36, 248, 232,
+ 233, 222, 234, 97, 37, 38, 39, 223, 258, 148,
+ 193, 40, 41, 198, 61, 86, 43, 69, 70, 44,
+ 45, 125, 134, 189, 211, 46, 56
};
/* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing
STATE-NUM. */
-#define YYPACT_NINF -198
+#define YYPACT_NINF -206
static const yytype_int16 yypact[] =
{
- 244, -52, -198, -198, 386, -198, -198, 386, 386, 386,
- 792, -198, -198, 19, 574, -198, 23, 386, 11, 7,
- 27, 42, 43, 386, 614, 386, 386, 386, 386, 386,
- 5, -198, 6, -198, -198, -198, -47, -8, 513, 770,
- -198, -198, -198, -198, -198, -198, -198, -5, 386, -198,
- 770, 770, 770, 770, -6, 386, 386, -198, 1, 386,
- -198, 386, -198, 37, -198, 770, 9, -198, -198, 152,
- 56, -198, -36, 386, -28, 36, -198, -198, 650, -198,
- 9, -198, 841, 672, 841, 841, 721, -198, 342, 513,
- 386, 513, 44, 748, 386, 386, 386, 386, 386, 386,
- 386, 386, 386, 386, 386, 386, 386, 386, 386, 152,
- 386, -56, -6, 63, 770, 770, 386, -198, -198, 64,
- -198, 386, 386, 628, 386, 386, 386, -198, 386, -198,
- 386, 386, -198, -198, 8, 770, 10, 694, -52, 386,
- 770, 770, 770, 770, 770, 770, 770, 770, 819, 819,
- 841, 841, 770, 770, 770, 770, -198, -198, 770, 72,
- -198, 770, 770, 386, 386, 770, 770, 770, 72, 770,
- 770, 770, -198, -11, -198, -198, 530, 770, -198, -57,
- 770, 770, -57, 403, 50, 386, 403, -198, -198, 74,
- 14, 14, -198, -198, 73, 386, 770, -10, -16, -198,
- 78, -198, -198, 61, 770, -198, 75, -198, 79, -198,
- -198, 79, -198, 513, -198, 403, 403, -198, -198, 403,
- -198, 403, 79, 79, -198, 513, 530, -198, 58, 65,
- 403, 77, 82, -198, 84, 69, -198, -198, -198, -198,
- 86, 76, 88, 89, -15, -198, 530, -198, 469, 81,
- -198, -198, -198, 403, -198, -198, -198, -198, -198
+ 263, -61, -206, -206, 671, -206, -206, 671, 671, 671,
+ 912, -206, -17, 9, 593, -206, 14, 671, 8, 6,
+ 20, 24, 29, 671, 634, 31, 671, 671, 671, 671,
+ 671, 3, -206, 4, -206, -206, -206, -21, -8, 707,
+ 671, 889, -206, -206, -206, -206, -206, -206, -206, -20,
+ 671, -206, 889, 889, 889, 889, 1, 671, 671, -206,
+ -206, 7, 671, -206, 671, -206, 37, -206, 889, 13,
+ -206, -206, 723, 54, -206, -38, 671, -34, 41, -206,
+ -206, 774, -206, 13, -206, -206, 10, -43, 797, -43,
+ -43, 843, -206, 332, 707, 671, 707, 43, 866, 889,
+ 671, 671, 671, 671, 671, 671, 671, 671, 671, 671,
+ 671, 671, 671, 671, 671, 671, 723, 671, -59, 1,
+ 67, 889, 889, 671, -206, -206, 69, -206, 671, 671,
+ 751, 671, 671, -206, -206, 671, -206, 71, 671, 671,
+ -206, -206, 18, 889, 19, 820, -61, 671, -206, 44,
+ 44, 44, -43, -43, -43, 889, 44, 44, 166, 224,
+ 224, -43, -43, 889, 889, 889, -206, -206, 889, 81,
+ -206, 889, 889, 671, 671, 889, 889, 81, 889, -206,
+ 889, 889, -206, -7, -206, -206, 529, 889, -206, -58,
+ 889, 889, -58, 396, 60, 671, 396, -206, -206, 84,
+ 27, 27, -206, -206, 85, 671, 889, -16, -12, -206,
+ 87, -206, -206, 73, 889, -206, 90, -206, 97, -206,
+ -206, 97, -206, 707, -206, 396, 396, -206, -206, 396,
+ -206, 396, 97, 97, -206, 707, 529, -206, 76, 86,
+ 396, 101, 104, -206, 105, 93, -206, -206, -206, -206,
+ 111, 114, 126, 127, -18, -206, 529, -206, 465, 120,
+ -206, -206, -206, 396, -206, -206, -206, -206, -206
};
/* YYPGOTO[NTERM-NUM]. */
static const yytype_int16 yypgoto[] =
{
- -198, -198, 12, 25, 2, -172, 0, -198, -198, -198,
- -83, -197, -105, -61, -198, -198, -198, -186, -9, 113,
- -167, 41, 3, -198, -198, 98, -7, -198, -198, -198,
- -198, -45, -67, -198, -3
+ -206, -206, 11, 70, 2, -179, 0, -206, -206, -206,
+ -44, -205, -67, -63, -206, -206, -206, -203, -9, -13,
+ -141, -206, 39, 5, -206, -206, -206, 142, -11, -206,
+ -206, -206, -206, -4, -32, -206, 23
};
/* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If
positive, shift that token. If negative, reduce the rule which
number is the opposite. If zero, do what YYDEFACT says.
If YYTABLE_NINF, syntax error. */
-#define YYTABLE_NINF -60
+#define YYTABLE_NINF -61
static const yytype_int16 yytable[] =
{
- 34, 56, 32, 40, 187, 87, -10, 121, 186, 209,
- -51, -51, 184, 46, 227, 124, 206, 81, 45, 72,
- 73, 156, 225, 45, 189, 110, 227, 74, 134, 89,
- 136, 210, 185, 122, 215, 216, 225, 207, 219, 57,
- 221, 125, 88, 68, 61, 50, 63, 75, 51, 52,
- 53, 230, 111, 112, 236, 65, 70, 71, 69, 117,
- -51, 90, 76, 77, 78, 65, 82, 83, 84, 85,
- 86, 55, 116, 129, 254, 110, 120, 253, 126, 93,
- 45, -10, 113, 157, 160, 172, 138, 174, 34, 109,
- 32, 40, 178, 195, 199, 200, 53, 53, 214, 203,
- 114, 217, 115, 241, 209, 237, 220, 239, 242, 159,
- 243, 244, 249, 133, 123, 211, 250, 233, 168, 251,
- 252, 256, 80, 182, 202, 0, 0, 0, 0, 176,
- 93, 135, 137, 0, 0, 140, 141, 142, 143, 144,
- 145, 146, 147, 148, 149, 150, 151, 152, 153, 154,
- 175, 155, 229, 0, 0, 0, 0, 158, 0, 0,
- 0, 0, 161, 162, 235, 165, 166, 167, 0, 169,
- 0, 170, 171, 0, 0, 0, 0, 0, 0, 0,
- 177, 0, 0, 0, 119, 0, 0, 0, 208, 0,
- 0, 190, 0, 0, 191, 0, 0, 0, 0, 0,
- 0, 226, 0, 0, 180, 181, 94, 95, 0, 0,
- 0, 96, 97, 98, 0, 99, 100, 101, 0, 0,
- 0, 0, 102, 103, 104, 105, 196, 0, 246, 0,
- 106, 107, 108, 0, 0, 0, 204, 0, 0, 0,
- 0, 0, 0, 0, -6, 1, 0, 0, 255, 0,
- 0, 0, 0, 0, 93, 2, 3, 4, 5, 0,
- 6, 7, 8, 9, 10, 11, 93, 0, 0, 0,
- 0, 12, 0, 13, 14, 15, 0, 0, 16, 0,
- 0, 0, 0, 17, 18, 19, 0, 0, 20, 0,
- 0, 0, 21, 22, 23, 24, 194, 0, 0, 197,
- 198, 0, 0, 0, 0, 0, 25, 0, 0, 205,
- 26, 0, 0, 0, 27, 28, 0, 218, 0, -6,
- 29, 0, 0, 0, 228, 0, 0, 0, 0, 0,
- 0, 0, 231, 0, 232, 234, 0, 0, 0, 0,
- 0, 0, 0, 240, 0, 0, 0, 0, 0, 245,
- 0, 247, 0, 2, 3, 4, 5, 0, 6, 7,
- 8, 9, 10, 11, 0, 0, 257, 0, 258, 12,
- 0, 13, 14, 15, 0, 0, 16, 0, 0, 0,
- 0, 17, 18, 19, 0, 0, 20, 0, 0, 0,
- 21, 22, 23, 24, 0, 0, 0, 2, 3, 4,
- 5, 0, 6, 0, 25, 0, 47, 11, 26, 0,
- 0, 0, 27, 28, 2, 3, 4, 5, 29, 6,
- 7, 8, 9, 47, 11, 48, 0, 19, 0, 0,
- 12, 0, 13, 14, 15, 0, 0, 0, 0, 0,
- 0, 0, 17, 18, 19, 0, 0, 20, 25, 0,
- 0, 0, 26, 23, 24, 0, 27, 28, 0, 0,
- 0, 0, 29, 0, 0, 25, 0, 0, 0, 26,
- 0, 0, 0, 27, 28, 0, 0, 0, 45, 29,
- 2, 3, 4, 5, 0, 6, 7, 8, 9, 47,
- 11, 0, 0, 0, 0, 0, 12, 0, 13, 14,
- 15, 0, 0, 0, 0, 0, 0, 0, 17, 18,
- 19, 0, 0, 20, 0, 0, 0, 0, 0, 23,
- 24, 0, 0, 0, 2, 3, 4, 5, 0, 6,
- 0, 25, 0, 47, 11, 26, 0, 0, 0, 27,
- 28, 2, 3, 4, 5, 29, 6, 7, 8, 9,
- 47, 11, 48, 0, 19, 0, 0, 12, 0, 13,
+ 35, 58, 33, 92, -10, 42, 128, 197, -51, -51,
+ 131, 216, 48, 84, 219, 235, 237, 194, 47, 75,
+ 76, 47, 166, 106, 59, 117, 199, 77, 237, 235,
+ 60, 142, 217, 144, 129, 71, 220, 195, 132, 114,
+ 115, 78, 93, 52, 196, 79, 53, 54, 55, 64,
+ 80, 66, 85, 68, 73, 74, 72, 246, 124, 94,
+ 57, -51, 81, 68, 95, 87, 88, 89, 90, 91,
+ 225, 226, 136, 123, 229, 127, 231, 264, 98, 99,
+ 118, 119, 47, -10, 133, 117, 146, 240, 167, 116,
+ 170, 120, 179, 35, 137, 33, 55, 55, 42, 182,
+ 184, 121, 188, 122, 205, 209, 103, 104, 224, 105,
+ 106, 210, 213, 263, 227, 130, 169, 109, 110, 111,
+ 112, 113, 230, 219, 247, 177, 114, 115, 251, 249,
+ 183, 252, 253, 98, 143, 145, 254, 186, 259, 149,
+ 150, 151, 152, 153, 154, 155, 156, 157, 158, 159,
+ 160, 161, 162, 163, 164, 260, 165, 185, 261, 262,
+ 239, 266, 168, 141, 221, 243, 83, 171, 172, 212,
+ 175, 176, 245, 192, 178, 0, 0, 180, 181, 0,
+ 204, 0, 0, 207, 208, 0, 187, 0, 0, 0,
+ 0, 0, 0, 215, 0, 0, 0, 0, 218, 0,
+ 200, 228, 0, 201, 0, 0, 0, 0, 238, 0,
+ 0, 236, 190, 191, 0, 0, 241, 0, 242, 244,
+ 0, 0, 0, 0, 0, 0, 0, 250, 103, 104,
+ 0, 105, 106, 255, 206, 257, 0, 0, 256, 0,
+ 110, 111, 112, 113, 214, 0, 0, 0, 114, 115,
+ 267, 0, 268, 0, 0, 0, 0, 0, 265, 0,
+ 0, 0, 98, -6, 1, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 98, 2, 3, 4, 5, 0,
+ 6, 7, 8, 9, 10, 11, 103, 104, 0, 105,
+ 106, 12, 0, 13, 14, 15, 0, 0, 16, 0,
+ 112, 113, 0, 17, 18, 19, 114, 115, 20, 0,
+ 0, 0, 21, 22, 23, 24, 0, 0, 25, 0,
+ 0, 0, 0, 0, 0, 0, 0, 26, 0, 0,
+ 0, 0, 27, 0, 0, 0, 0, 28, 29, 0,
+ 0, 0, -6, 30, 2, 3, 4, 5, 0, 6,
+ 7, 8, 9, 10, 11, 0, 0, 0, 0, 0,
+ 12, 0, 13, 14, 15, 0, 0, 16, 0, 0,
+ 0, 0, 17, 18, 19, 0, 0, 20, 0, 0,
+ 0, 21, 22, 23, 24, 0, 0, 25, 0, 0,
+ 0, 0, 0, 0, 0, 0, 26, 0, 0, 0,
+ 0, 27, 0, 0, 0, 0, 28, 29, 2, 3,
+ 4, 5, 30, 6, 7, 8, 9, 49, 11, 0,
+ 0, 0, 0, 0, 12, 0, 13, 14, 15, 0,
+ 0, 0, 0, 0, 0, 0, 17, 18, 19, 0,
+ 0, 20, 0, 0, 0, 0, 0, 23, 24, 0,
+ 0, 25, 0, 0, 0, 0, 0, 0, 0, 0,
+ 26, 0, 0, 0, 0, 27, 0, 0, 0, 0,
+ 28, 29, 0, 0, 0, 47, 30, 2, 3, 4,
+ 5, 0, 6, 7, 8, 9, 49, 11, 0, 0,
+ 0, 0, 0, 12, 0, 13, 14, 15, 0, 0,
+ 0, 0, 0, 0, 0, 17, 18, 19, 0, 0,
+ 20, 0, 0, 0, 0, 0, 23, 24, 0, 0,
+ 25, 0, 0, 0, 0, 0, 0, 0, 0, 26,
+ 0, 0, 0, 0, 27, 0, 0, 0, 0, 28,
+ 29, 2, 3, 4, 5, 30, 6, 7, 8, 9,
+ 49, 11, 0, 0, 0, 0, 0, 12, 0, 13,
14, 0, 0, 0, 0, 0, 0, 0, 0, 17,
- 0, 19, 0, 0, 0, 25, 0, 0, 0, 26,
- 23, 24, 0, 27, 28, 2, 3, 4, 5, 91,
- 6, 0, 25, 0, 47, 11, 26, 0, 0, 0,
- 27, 28, 59, 0, 0, 0, 29, 60, 0, 61,
- 62, 63, 64, 48, 0, 19, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 2, 3, 4, 5, 0,
- 6, 0, 0, 0, 47, 11, 25, 0, 0, 0,
- 26, 0, 59, 0, 27, 28, 0, 0, 0, 61,
- 29, 63, 0, 48, 0, 19, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 79, 0, 0, 0,
- 0, 163, 0, 0, 0, 0, 25, 0, 0, 0,
- 26, 0, 94, 95, 27, 28, 0, 96, 97, 98,
- 29, 99, 100, 101, 0, 128, 0, 164, 102, 103,
- 104, 105, 0, 0, 94, 95, 106, 107, 108, 96,
- 97, 98, 0, 99, 100, 101, 0, 0, 0, 0,
- 102, 103, 104, 105, 0, 0, 94, 95, 106, 107,
- 108, 96, 97, 98, 0, 99, 100, 101, 0, 130,
- 131, 0, 102, 103, 104, 105, 0, 0, 94, 95,
- 106, 107, 108, 96, 97, 98, 0, 99, 100, 101,
- 0, 0, 0, 139, 102, 103, 104, 105, 0, 0,
- 0, 132, 106, 107, 108, 94, 95, 0, 0, 0,
- 96, 97, 98, 0, 99, 100, 101, 0, 0, 0,
- 0, 102, 103, 104, 105, 0, 0, 0, 132, 106,
- 107, 108, 94, 95, 0, 0, 0, 96, 97, 98,
- 0, 99, 100, 101, 0, 0, 0, 139, 102, 103,
- 104, 105, 0, 0, 94, 95, 106, 107, 108, 96,
- 97, 98, 0, 99, 100, 101, 0, 0, 0, 0,
- 102, 103, 104, 105, 0, 0, -59, -59, 106, 107,
- 108, -59, -59, -59, 0, -59, -59, -59, 0, 0,
- 0, 0, 0, 0, -59, -59, 0, 0, 55, 0,
- -59, -59, -59, 94, 95, 0, 0, 0, 96, 97,
- 98, 0, 99, 100, 101, 0, 0, 0, 0, 0,
- 0, 104, 105, 0, 0, 94, 95, 106, 107, 108,
- 96, 97, 98, 0, 99, 100, 101, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 106,
- 107, 108
+ 0, 19, 0, 0, 0, 0, 0, 0, 0, 0,
+ 23, 24, 0, 0, 25, 0, 0, 0, 0, 0,
+ 0, 0, 0, 26, 0, 0, 0, 0, 27, 0,
+ 0, 0, 0, 28, 29, 2, 3, 4, 5, 30,
+ 6, 0, 0, 0, 49, 11, 0, 0, 0, 0,
+ 0, 0, 62, 0, 0, 0, 0, 63, 0, 64,
+ 65, 66, 67, 50, 0, 19, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 2, 3, 4, 5,
+ 0, 6, 0, 0, 0, 49, 11, 26, 0, 0,
+ 0, 0, 27, 62, 0, 0, 0, 28, 29, 0,
+ 64, 0, 66, 30, 50, 0, 19, 0, 0, 0,
+ 0, 0, 0, 2, 3, 4, 5, 82, 6, 0,
+ 0, 0, 49, 11, 0, 0, 0, 0, 26, 0,
+ 0, 0, 0, 27, 0, 0, 0, 0, 28, 29,
+ 0, 50, 0, 19, 30, 0, 0, 0, 0, 2,
+ 3, 4, 5, 0, 6, 0, 0, 0, 49, 11,
+ 0, 0, 0, 0, 0, 26, 0, 0, 0, 0,
+ 27, 0, 0, 0, 0, 28, 29, 50, 0, 19,
+ 0, 30, 0, 0, 0, 0, 126, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 26, 0, 0, 0, 0, 27, 0, 0, 100,
+ 101, 28, 29, 0, 102, 103, 104, 96, 105, 106,
+ 107, 108, 0, 0, 0, 173, 109, 110, 111, 112,
+ 113, 0, 0, 0, 0, 114, 115, 100, 101, 0,
+ 0, 0, 102, 103, 104, 0, 105, 106, 107, 108,
+ 135, 0, 0, 174, 109, 110, 111, 112, 113, 0,
+ 100, 101, 0, 114, 115, 102, 103, 104, 0, 105,
+ 106, 107, 108, 0, 0, 0, 0, 109, 110, 111,
+ 112, 113, 0, 100, 101, 0, 114, 115, 102, 103,
+ 104, 0, 105, 106, 107, 108, 0, 138, 139, 0,
+ 109, 110, 111, 112, 113, 0, 100, 101, 0, 114,
+ 115, 102, 103, 104, 0, 105, 106, 107, 108, 0,
+ 0, 0, 147, 109, 110, 111, 112, 113, 0, 100,
+ 101, 140, 114, 115, 102, 103, 104, 0, 105, 106,
+ 107, 108, 0, 0, 0, 0, 109, 110, 111, 112,
+ 113, 0, 100, 101, 140, 114, 115, 102, 103, 104,
+ 0, 105, 106, 107, 108, 0, 0, 0, 147, 109,
+ 110, 111, 112, 113, 0, 100, 101, 0, 114, 115,
+ 102, 103, 104, 0, 105, 106, 107, 108, 0, 0,
+ 0, 0, 109, 110, 111, 112, 113, 0, -60, -60,
+ 0, 114, 115, -60, -60, -60, 0, -60, -60, -60,
+ -60, 0, 0, 0, 0, -60, 0, 0, -60, -60,
+ 0, 0, 57, 0, -60, -60
};
static const yytype_int16 yycheck[] =
{
- 0, 10, 0, 0, 176, 0, 0, 43, 175, 25,
- 25, 26, 23, 1, 211, 43, 26, 24, 75, 12,
- 13, 77, 208, 75, 81, 81, 223, 20, 89, 76,
- 91, 47, 43, 69, 201, 202, 222, 47, 205, 20,
- 207, 69, 30, 20, 35, 4, 37, 20, 7, 8,
- 9, 218, 55, 56, 226, 14, 45, 46, 17, 66,
- 75, 69, 20, 20, 23, 24, 25, 26, 27, 28,
- 29, 76, 35, 80, 246, 81, 20, 244, 42, 38,
- 75, 75, 81, 20, 20, 77, 42, 77, 88, 48,
- 88, 88, 20, 43, 20, 81, 55, 56, 20, 26,
- 59, 40, 61, 26, 25, 47, 31, 42, 26, 118,
- 26, 42, 26, 88, 73, 198, 40, 222, 127, 31,
- 31, 40, 24, 168, 191, -1, -1, -1, -1, 138,
- 89, 90, 91, -1, -1, 94, 95, 96, 97, 98,
- 99, 100, 101, 102, 103, 104, 105, 106, 107, 108,
- 138, 110, 213, -1, -1, -1, -1, 116, -1, -1,
- -1, -1, 121, 122, 225, 124, 125, 126, -1, 128,
- -1, 130, 131, -1, -1, -1, -1, -1, -1, -1,
- 139, -1, -1, -1, 32, -1, -1, -1, 197, -1,
- -1, 179, -1, -1, 182, -1, -1, -1, -1, -1,
- -1, 210, -1, -1, 163, 164, 54, 55, -1, -1,
- -1, 59, 60, 61, -1, 63, 64, 65, -1, -1,
- -1, -1, 70, 71, 72, 73, 185, -1, 237, -1,
- 78, 79, 80, -1, -1, -1, 195, -1, -1, -1,
- -1, -1, -1, -1, 0, 1, -1, -1, 248, -1,
- -1, -1, -1, -1, 213, 11, 12, 13, 14, -1,
- 16, 17, 18, 19, 20, 21, 225, -1, -1, -1,
- -1, 27, -1, 29, 30, 31, -1, -1, 34, -1,
- -1, -1, -1, 39, 40, 41, -1, -1, 44, -1,
- -1, -1, 48, 49, 50, 51, 183, -1, -1, 186,
- 187, -1, -1, -1, -1, -1, 62, -1, -1, 196,
- 66, -1, -1, -1, 70, 71, -1, 204, -1, 75,
- 76, -1, -1, -1, 211, -1, -1, -1, -1, -1,
- -1, -1, 219, -1, 221, 222, -1, -1, -1, -1,
- -1, -1, -1, 230, -1, -1, -1, -1, -1, 236,
- -1, 238, -1, 11, 12, 13, 14, -1, 16, 17,
- 18, 19, 20, 21, -1, -1, 253, -1, 255, 27,
- -1, 29, 30, 31, -1, -1, 34, -1, -1, -1,
- -1, 39, 40, 41, -1, -1, 44, -1, -1, -1,
- 48, 49, 50, 51, -1, -1, -1, 11, 12, 13,
- 14, -1, 16, -1, 62, -1, 20, 21, 66, -1,
- -1, -1, 70, 71, 11, 12, 13, 14, 76, 16,
- 17, 18, 19, 20, 21, 39, -1, 41, -1, -1,
- 27, -1, 29, 30, 31, -1, -1, -1, -1, -1,
- -1, -1, 39, 40, 41, -1, -1, 44, 62, -1,
- -1, -1, 66, 50, 51, -1, 70, 71, -1, -1,
- -1, -1, 76, -1, -1, 62, -1, -1, -1, 66,
- -1, -1, -1, 70, 71, -1, -1, -1, 75, 76,
- 11, 12, 13, 14, -1, 16, 17, 18, 19, 20,
- 21, -1, -1, -1, -1, -1, 27, -1, 29, 30,
- 31, -1, -1, -1, -1, -1, -1, -1, 39, 40,
- 41, -1, -1, 44, -1, -1, -1, -1, -1, 50,
- 51, -1, -1, -1, 11, 12, 13, 14, -1, 16,
- -1, 62, -1, 20, 21, 66, -1, -1, -1, 70,
- 71, 11, 12, 13, 14, 76, 16, 17, 18, 19,
- 20, 21, 39, -1, 41, -1, -1, 27, -1, 29,
- 30, -1, -1, -1, -1, -1, -1, -1, -1, 39,
- -1, 41, -1, -1, -1, 62, -1, -1, -1, 66,
- 50, 51, -1, 70, 71, 11, 12, 13, 14, 76,
- 16, -1, 62, -1, 20, 21, 66, -1, -1, -1,
- 70, 71, 28, -1, -1, -1, 76, 33, -1, 35,
- 36, 37, 38, 39, -1, 41, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, 11, 12, 13, 14, -1,
- 16, -1, -1, -1, 20, 21, 62, -1, -1, -1,
- 66, -1, 28, -1, 70, 71, -1, -1, -1, 35,
- 76, 37, -1, 39, -1, 41, -1, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, 52, -1, -1, -1,
- -1, 43, -1, -1, -1, -1, 62, -1, -1, -1,
- 66, -1, 54, 55, 70, 71, -1, 59, 60, 61,
- 76, 63, 64, 65, -1, 45, -1, 69, 70, 71,
- 72, 73, -1, -1, 54, 55, 78, 79, 80, 59,
- 60, 61, -1, 63, 64, 65, -1, -1, -1, -1,
- 70, 71, 72, 73, -1, -1, 54, 55, 78, 79,
- 80, 59, 60, 61, -1, 63, 64, 65, -1, 67,
- 68, -1, 70, 71, 72, 73, -1, -1, 54, 55,
- 78, 79, 80, 59, 60, 61, -1, 63, 64, 65,
- -1, -1, -1, 69, 70, 71, 72, 73, -1, -1,
- -1, 77, 78, 79, 80, 54, 55, -1, -1, -1,
- 59, 60, 61, -1, 63, 64, 65, -1, -1, -1,
- -1, 70, 71, 72, 73, -1, -1, -1, 77, 78,
- 79, 80, 54, 55, -1, -1, -1, 59, 60, 61,
- -1, 63, 64, 65, -1, -1, -1, 69, 70, 71,
- 72, 73, -1, -1, 54, 55, 78, 79, 80, 59,
- 60, 61, -1, 63, 64, 65, -1, -1, -1, -1,
- 70, 71, 72, 73, -1, -1, 54, 55, 78, 79,
- 80, 59, 60, 61, -1, 63, 64, 65, -1, -1,
- -1, -1, -1, -1, 72, 73, -1, -1, 76, -1,
- 78, 79, 80, 54, 55, -1, -1, -1, 59, 60,
- 61, -1, 63, 64, 65, -1, -1, -1, -1, -1,
- -1, 72, 73, -1, -1, 54, 55, 78, 79, 80,
- 59, 60, 61, -1, 63, 64, 65, -1, -1, -1,
- -1, -1, -1, -1, -1, -1, -1, -1, -1, 78,
- 79, 80
+ 0, 10, 0, 0, 0, 0, 44, 186, 26, 27,
+ 44, 27, 1, 24, 26, 218, 221, 24, 79, 13,
+ 14, 79, 81, 66, 41, 84, 84, 21, 233, 232,
+ 21, 94, 48, 96, 72, 21, 48, 44, 72, 82,
+ 83, 21, 31, 4, 185, 21, 7, 8, 9, 36,
+ 21, 38, 21, 14, 46, 47, 17, 236, 69, 80,
+ 80, 79, 23, 24, 72, 26, 27, 28, 29, 30,
+ 211, 212, 83, 36, 215, 21, 217, 256, 39, 40,
+ 57, 58, 79, 79, 43, 84, 43, 228, 21, 50,
+ 21, 84, 21, 93, 84, 93, 57, 58, 93, 81,
+ 81, 62, 21, 64, 44, 21, 62, 63, 21, 65,
+ 66, 84, 27, 254, 41, 76, 125, 73, 74, 75,
+ 76, 77, 32, 26, 48, 134, 82, 83, 27, 43,
+ 143, 27, 27, 94, 95, 96, 43, 146, 27, 100,
+ 101, 102, 103, 104, 105, 106, 107, 108, 109, 110,
+ 111, 112, 113, 114, 115, 41, 117, 146, 32, 32,
+ 223, 41, 123, 93, 208, 232, 24, 128, 129, 201,
+ 131, 132, 235, 177, 135, -1, -1, 138, 139, -1,
+ 193, -1, -1, 196, 197, -1, 147, -1, -1, -1,
+ -1, -1, -1, 206, -1, -1, -1, -1, 207, -1,
+ 189, 214, -1, 192, -1, -1, -1, -1, 221, -1,
+ -1, 220, 173, 174, -1, -1, 229, -1, 231, 232,
+ -1, -1, -1, -1, -1, -1, -1, 240, 62, 63,
+ -1, 65, 66, 246, 195, 248, -1, -1, 247, -1,
+ 74, 75, 76, 77, 205, -1, -1, -1, 82, 83,
+ 263, -1, 265, -1, -1, -1, -1, -1, 258, -1,
+ -1, -1, 223, 0, 1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, 235, 12, 13, 14, 15, -1,
+ 17, 18, 19, 20, 21, 22, 62, 63, -1, 65,
+ 66, 28, -1, 30, 31, 32, -1, -1, 35, -1,
+ 76, 77, -1, 40, 41, 42, 82, 83, 45, -1,
+ -1, -1, 49, 50, 51, 52, -1, -1, 55, -1,
+ -1, -1, -1, -1, -1, -1, -1, 64, -1, -1,
+ -1, -1, 69, -1, -1, -1, -1, 74, 75, -1,
+ -1, -1, 79, 80, 12, 13, 14, 15, -1, 17,
+ 18, 19, 20, 21, 22, -1, -1, -1, -1, -1,
+ 28, -1, 30, 31, 32, -1, -1, 35, -1, -1,
+ -1, -1, 40, 41, 42, -1, -1, 45, -1, -1,
+ -1, 49, 50, 51, 52, -1, -1, 55, -1, -1,
+ -1, -1, -1, -1, -1, -1, 64, -1, -1, -1,
+ -1, 69, -1, -1, -1, -1, 74, 75, 12, 13,
+ 14, 15, 80, 17, 18, 19, 20, 21, 22, -1,
+ -1, -1, -1, -1, 28, -1, 30, 31, 32, -1,
+ -1, -1, -1, -1, -1, -1, 40, 41, 42, -1,
+ -1, 45, -1, -1, -1, -1, -1, 51, 52, -1,
+ -1, 55, -1, -1, -1, -1, -1, -1, -1, -1,
+ 64, -1, -1, -1, -1, 69, -1, -1, -1, -1,
+ 74, 75, -1, -1, -1, 79, 80, 12, 13, 14,
+ 15, -1, 17, 18, 19, 20, 21, 22, -1, -1,
+ -1, -1, -1, 28, -1, 30, 31, 32, -1, -1,
+ -1, -1, -1, -1, -1, 40, 41, 42, -1, -1,
+ 45, -1, -1, -1, -1, -1, 51, 52, -1, -1,
+ 55, -1, -1, -1, -1, -1, -1, -1, -1, 64,
+ -1, -1, -1, -1, 69, -1, -1, -1, -1, 74,
+ 75, 12, 13, 14, 15, 80, 17, 18, 19, 20,
+ 21, 22, -1, -1, -1, -1, -1, 28, -1, 30,
+ 31, -1, -1, -1, -1, -1, -1, -1, -1, 40,
+ -1, 42, -1, -1, -1, -1, -1, -1, -1, -1,
+ 51, 52, -1, -1, 55, -1, -1, -1, -1, -1,
+ -1, -1, -1, 64, -1, -1, -1, -1, 69, -1,
+ -1, -1, -1, 74, 75, 12, 13, 14, 15, 80,
+ 17, -1, -1, -1, 21, 22, -1, -1, -1, -1,
+ -1, -1, 29, -1, -1, -1, -1, 34, -1, 36,
+ 37, 38, 39, 40, -1, 42, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, 12, 13, 14, 15,
+ -1, 17, -1, -1, -1, 21, 22, 64, -1, -1,
+ -1, -1, 69, 29, -1, -1, -1, 74, 75, -1,
+ 36, -1, 38, 80, 40, -1, 42, -1, -1, -1,
+ -1, -1, -1, 12, 13, 14, 15, 53, 17, -1,
+ -1, -1, 21, 22, -1, -1, -1, -1, 64, -1,
+ -1, -1, -1, 69, -1, -1, -1, -1, 74, 75,
+ -1, 40, -1, 42, 80, -1, -1, -1, -1, 12,
+ 13, 14, 15, -1, 17, -1, -1, -1, 21, 22,
+ -1, -1, -1, -1, -1, 64, -1, -1, -1, -1,
+ 69, -1, -1, -1, -1, 74, 75, 40, -1, 42,
+ -1, 80, -1, -1, -1, -1, 33, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, 64, -1, -1, -1, -1, 69, -1, -1, 56,
+ 57, 74, 75, -1, 61, 62, 63, 80, 65, 66,
+ 67, 68, -1, -1, -1, 44, 73, 74, 75, 76,
+ 77, -1, -1, -1, -1, 82, 83, 56, 57, -1,
+ -1, -1, 61, 62, 63, -1, 65, 66, 67, 68,
+ 46, -1, -1, 72, 73, 74, 75, 76, 77, -1,
+ 56, 57, -1, 82, 83, 61, 62, 63, -1, 65,
+ 66, 67, 68, -1, -1, -1, -1, 73, 74, 75,
+ 76, 77, -1, 56, 57, -1, 82, 83, 61, 62,
+ 63, -1, 65, 66, 67, 68, -1, 70, 71, -1,
+ 73, 74, 75, 76, 77, -1, 56, 57, -1, 82,
+ 83, 61, 62, 63, -1, 65, 66, 67, 68, -1,
+ -1, -1, 72, 73, 74, 75, 76, 77, -1, 56,
+ 57, 81, 82, 83, 61, 62, 63, -1, 65, 66,
+ 67, 68, -1, -1, -1, -1, 73, 74, 75, 76,
+ 77, -1, 56, 57, 81, 82, 83, 61, 62, 63,
+ -1, 65, 66, 67, 68, -1, -1, -1, 72, 73,
+ 74, 75, 76, 77, -1, 56, 57, -1, 82, 83,
+ 61, 62, 63, -1, 65, 66, 67, 68, -1, -1,
+ -1, -1, 73, 74, 75, 76, 77, -1, 56, 57,
+ -1, 82, 83, 61, 62, 63, -1, 65, 66, 67,
+ 68, -1, -1, -1, -1, 73, -1, -1, 76, 77,
+ -1, -1, 80, -1, 82, 83
};
/* YYSTOS[STATE-NUM] -- The (internal number of the) accessing
symbol of state STATE-NUM. */
static const yytype_uint8 yystos[] =
{
- 0, 1, 11, 12, 13, 14, 16, 17, 18, 19,
- 20, 21, 27, 29, 30, 31, 34, 39, 40, 41,
- 44, 48, 49, 50, 51, 62, 66, 70, 71, 76,
- 83, 85, 86, 87, 88, 89, 96, 97, 98, 103,
- 104, 106, 109, 110, 115, 75, 84, 20, 39, 86,
- 103, 103, 103, 103, 116, 76, 100, 20, 105, 28,
- 33, 35, 36, 37, 38, 103, 107, 108, 20, 103,
- 45, 46, 12, 13, 20, 20, 20, 20, 103, 52,
- 107, 108, 103, 103, 103, 103, 103, 0, 84, 76,
- 69, 76, 95, 103, 54, 55, 59, 60, 61, 63,
- 64, 65, 70, 71, 72, 73, 78, 79, 80, 103,
- 81, 116, 116, 81, 103, 103, 35, 108, 111, 32,
- 20, 43, 69, 103, 43, 69, 42, 112, 45, 108,
- 67, 68, 77, 85, 95, 103, 95, 103, 42, 69,
- 103, 103, 103, 103, 103, 103, 103, 103, 103, 103,
- 103, 103, 103, 103, 103, 103, 77, 20, 103, 100,
- 20, 103, 103, 43, 69, 103, 103, 103, 100, 103,
- 103, 103, 77, 101, 77, 84, 100, 103, 20, 113,
- 103, 103, 113, 102, 23, 43, 102, 87, 104, 81,
- 84, 84, 84, 88, 101, 43, 103, 101, 101, 20,
- 81, 114, 114, 26, 103, 101, 26, 47, 100, 25,
- 47, 92, 93, 99, 20, 102, 102, 40, 101, 102,
- 31, 102, 91, 92, 94, 99, 100, 93, 101, 95,
- 102, 101, 101, 94, 101, 95, 87, 47, 90, 42,
- 101, 26, 26, 26, 42, 101, 100, 101, 100, 26,
- 40, 31, 31, 102, 87, 88, 40, 101, 101
+ 0, 1, 12, 13, 14, 15, 17, 18, 19, 20,
+ 21, 22, 28, 30, 31, 32, 35, 40, 41, 42,
+ 45, 49, 50, 51, 52, 55, 64, 69, 74, 75,
+ 80, 86, 88, 89, 90, 91, 92, 99, 100, 101,
+ 106, 107, 108, 111, 114, 115, 120, 79, 87, 21,
+ 40, 89, 107, 107, 107, 107, 121, 80, 103, 41,
+ 21, 109, 29, 34, 36, 37, 38, 39, 107, 112,
+ 113, 21, 107, 46, 47, 13, 14, 21, 21, 21,
+ 21, 107, 53, 112, 113, 21, 110, 107, 107, 107,
+ 107, 107, 0, 87, 80, 72, 80, 98, 107, 107,
+ 56, 57, 61, 62, 63, 65, 66, 67, 68, 73,
+ 74, 75, 76, 77, 82, 83, 107, 84, 121, 121,
+ 84, 107, 107, 36, 113, 116, 33, 21, 44, 72,
+ 107, 44, 72, 43, 117, 46, 113, 84, 70, 71,
+ 81, 88, 98, 107, 98, 107, 43, 72, 104, 107,
+ 107, 107, 107, 107, 107, 107, 107, 107, 107, 107,
+ 107, 107, 107, 107, 107, 107, 81, 21, 107, 103,
+ 21, 107, 107, 44, 72, 107, 107, 103, 107, 21,
+ 107, 107, 81, 104, 81, 87, 103, 107, 21, 118,
+ 107, 107, 118, 105, 24, 44, 105, 90, 108, 84,
+ 87, 87, 87, 91, 104, 44, 107, 104, 104, 21,
+ 84, 119, 119, 27, 107, 104, 27, 48, 103, 26,
+ 48, 95, 96, 102, 21, 105, 105, 41, 104, 105,
+ 32, 105, 94, 95, 97, 102, 103, 96, 104, 98,
+ 105, 104, 104, 97, 104, 98, 90, 48, 93, 43,
+ 104, 27, 27, 27, 43, 104, 103, 104, 103, 27,
+ 41, 32, 32, 105, 90, 91, 41, 104, 104
};
#define yyerrok (yyerrstatus = 0)
@@ -1839,12 +1869,12 @@ yyreduce:
switch (yyn)
{
case 4:
-#line 105 "engines/director/lingo/lingo-gr.y"
+#line 107 "engines/director/lingo/lingo-gr.y"
{ yyerrok; ;}
break;
case 5:
-#line 108 "engines/director/lingo/lingo-gr.y"
+#line 110 "engines/director/lingo/lingo-gr.y"
{
g_lingo->_linenumber++;
g_lingo->_colnumber = 1;
@@ -1852,12 +1882,12 @@ yyreduce:
break;
case 10:
-#line 117 "engines/director/lingo/lingo-gr.y"
+#line 119 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_xpop); ;}
break;
case 12:
-#line 121 "engines/director/lingo/lingo-gr.y"
+#line 123 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code1(g_lingo->c_varpush);
g_lingo->codeString((yyvsp[(4) - (4)].s)->c_str());
@@ -1867,7 +1897,7 @@ yyreduce:
break;
case 13:
-#line 127 "engines/director/lingo/lingo-gr.y"
+#line 129 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code1(g_lingo->c_varpush);
g_lingo->codeString((yyvsp[(2) - (4)].s)->c_str());
@@ -1877,7 +1907,7 @@ yyreduce:
break;
case 14:
-#line 133 "engines/director/lingo/lingo-gr.y"
+#line 135 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(0); // Put dummy id
g_lingo->code1(g_lingo->c_theentityassign);
@@ -1889,7 +1919,7 @@ yyreduce:
break;
case 15:
-#line 141 "engines/director/lingo/lingo-gr.y"
+#line 143 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code1(g_lingo->c_swap);
g_lingo->code1(g_lingo->c_theentityassign);
@@ -1901,7 +1931,7 @@ yyreduce:
break;
case 16:
-#line 149 "engines/director/lingo/lingo-gr.y"
+#line 151 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code1(g_lingo->c_varpush);
g_lingo->codeString((yyvsp[(2) - (4)].s)->c_str());
@@ -1911,7 +1941,7 @@ yyreduce:
break;
case 17:
-#line 155 "engines/director/lingo/lingo-gr.y"
+#line 157 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(0); // Put dummy id
g_lingo->code1(g_lingo->c_theentityassign);
@@ -1923,7 +1953,7 @@ yyreduce:
break;
case 18:
-#line 163 "engines/director/lingo/lingo-gr.y"
+#line 165 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code1(g_lingo->c_swap);
g_lingo->code1(g_lingo->c_theentityassign);
@@ -1935,12 +1965,12 @@ yyreduce:
break;
case 19:
-#line 172 "engines/director/lingo/lingo-gr.y"
+#line 174 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_xpop); ;}
break;
case 23:
-#line 181 "engines/director/lingo/lingo-gr.y"
+#line 183 "engines/director/lingo/lingo-gr.y"
{
inst body = 0, end = 0;
WRITE_UINT32(&body, (yyvsp[(5) - (8)].code));
@@ -1950,7 +1980,7 @@ yyreduce:
break;
case 24:
-#line 192 "engines/director/lingo/lingo-gr.y"
+#line 194 "engines/director/lingo/lingo-gr.y"
{
inst init = 0, finish = 0, body = 0, end = 0, inc = 0;
WRITE_UINT32(&init, (yyvsp[(3) - (11)].code));
@@ -1966,7 +1996,7 @@ yyreduce:
break;
case 25:
-#line 208 "engines/director/lingo/lingo-gr.y"
+#line 210 "engines/director/lingo/lingo-gr.y"
{
inst init = 0, finish = 0, body = 0, end = 0, inc = 0;
WRITE_UINT32(&init, (yyvsp[(3) - (12)].code));
@@ -1982,14 +2012,17 @@ yyreduce:
break;
case 26:
-#line 220 "engines/director/lingo/lingo-gr.y"
+#line 222 "engines/director/lingo/lingo-gr.y"
{
- g_lingo->code1(g_lingo->c_ifcode);
+ inst end = 0;
+ WRITE_UINT32(&end, (yyvsp[(3) - (3)].code));
+ g_lingo->code1(STOP);
+ (*g_lingo->_currentScript)[(yyvsp[(1) - (3)].code) + 1] = end;
;}
break;
case 27:
-#line 225 "engines/director/lingo/lingo-gr.y"
+#line 230 "engines/director/lingo/lingo-gr.y"
{
inst then = 0, end = 0;
WRITE_UINT32(&then, (yyvsp[(5) - (8)].code));
@@ -2000,7 +2033,7 @@ yyreduce:
break;
case 28:
-#line 232 "engines/director/lingo/lingo-gr.y"
+#line 237 "engines/director/lingo/lingo-gr.y"
{
inst then = 0, else1 = 0, end = 0;
WRITE_UINT32(&then, (yyvsp[(5) - (11)].code));
@@ -2013,7 +2046,7 @@ yyreduce:
break;
case 29:
-#line 241 "engines/director/lingo/lingo-gr.y"
+#line 246 "engines/director/lingo/lingo-gr.y"
{
inst then = 0, else1 = 0, end = 0;
WRITE_UINT32(&then, (yyvsp[(5) - (11)].code));
@@ -2026,7 +2059,7 @@ yyreduce:
break;
case 30:
-#line 250 "engines/director/lingo/lingo-gr.y"
+#line 255 "engines/director/lingo/lingo-gr.y"
{
inst then = 0, else1 = 0, end = 0;
WRITE_UINT32(&then, (yyvsp[(4) - (6)].code));
@@ -2040,7 +2073,7 @@ yyreduce:
break;
case 31:
-#line 260 "engines/director/lingo/lingo-gr.y"
+#line 265 "engines/director/lingo/lingo-gr.y"
{
inst then = 0, else1 = 0, end = 0;
WRITE_UINT32(&then, (yyvsp[(4) - (10)].code));
@@ -2054,7 +2087,7 @@ yyreduce:
break;
case 32:
-#line 270 "engines/director/lingo/lingo-gr.y"
+#line 275 "engines/director/lingo/lingo-gr.y"
{
inst then = 0, else1 = 0, end = 0;
WRITE_UINT32(&then, (yyvsp[(4) - (10)].code));
@@ -2068,17 +2101,17 @@ yyreduce:
break;
case 33:
-#line 281 "engines/director/lingo/lingo-gr.y"
+#line 286 "engines/director/lingo/lingo-gr.y"
{ (yyval.code) = 0; ;}
break;
case 34:
-#line 282 "engines/director/lingo/lingo-gr.y"
+#line 287 "engines/director/lingo/lingo-gr.y"
{ (yyval.code) = (yyvsp[(2) - (3)].code); ;}
break;
case 39:
-#line 293 "engines/director/lingo/lingo-gr.y"
+#line 298 "engines/director/lingo/lingo-gr.y"
{
inst then = 0;
WRITE_UINT32(&then, (yyvsp[(4) - (6)].code));
@@ -2088,7 +2121,7 @@ yyreduce:
break;
case 41:
-#line 302 "engines/director/lingo/lingo-gr.y"
+#line 307 "engines/director/lingo/lingo-gr.y"
{
inst then = 0;
WRITE_UINT32(&then, (yyvsp[(4) - (5)].code));
@@ -2098,22 +2131,22 @@ yyreduce:
break;
case 42:
-#line 310 "engines/director/lingo/lingo-gr.y"
+#line 315 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(STOP); ;}
break;
case 43:
-#line 311 "engines/director/lingo/lingo-gr.y"
+#line 316 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code2(g_lingo->c_eq, STOP); ;}
break;
case 45:
-#line 314 "engines/director/lingo/lingo-gr.y"
+#line 319 "engines/director/lingo/lingo-gr.y"
{ (yyval.code) = g_lingo->code3(g_lingo->c_repeatwhilecode, STOP, STOP); ;}
break;
case 46:
-#line 316 "engines/director/lingo/lingo-gr.y"
+#line 321 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->code3(g_lingo->c_repeatwithcode, STOP, STOP);
g_lingo->code3(STOP, STOP, STOP);
@@ -2122,7 +2155,7 @@ yyreduce:
break;
case 47:
-#line 322 "engines/director/lingo/lingo-gr.y"
+#line 327 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->code1(g_lingo->c_ifcode);
g_lingo->code3(STOP, STOP, STOP);
@@ -2131,7 +2164,7 @@ yyreduce:
break;
case 48:
-#line 328 "engines/director/lingo/lingo-gr.y"
+#line 333 "engines/director/lingo/lingo-gr.y"
{
inst skipEnd;
WRITE_UINT32(&skipEnd, 1); // We have to skip end to avoid multiple executions
@@ -2141,64 +2174,72 @@ yyreduce:
break;
case 49:
-#line 335 "engines/director/lingo/lingo-gr.y"
+#line 340 "engines/director/lingo/lingo-gr.y"
{ (yyval.code) = g_lingo->_currentScript->size(); ;}
break;
case 50:
-#line 337 "engines/director/lingo/lingo-gr.y"
+#line 342 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(STOP); (yyval.code) = g_lingo->_currentScript->size(); ;}
break;
case 51:
-#line 339 "engines/director/lingo/lingo-gr.y"
+#line 344 "engines/director/lingo/lingo-gr.y"
{ (yyval.code) = g_lingo->_currentScript->size(); ;}
break;
case 54:
-#line 344 "engines/director/lingo/lingo-gr.y"
- { (yyval.code) = g_lingo->codeConst((yyvsp[(1) - (1)].i)); ;}
+#line 349 "engines/director/lingo/lingo-gr.y"
+ {
+ (yyval.code) = g_lingo->code1(g_lingo->c_whencode);
+ g_lingo->code1(STOP);
+ g_lingo->codeString((yyvsp[(2) - (3)].s)->c_str());
+ delete (yyvsp[(2) - (3)].s); ;}
break;
case 55:
-#line 345 "engines/director/lingo/lingo-gr.y"
+#line 355 "engines/director/lingo/lingo-gr.y"
+ { (yyval.code) = g_lingo->codeConst((yyvsp[(1) - (1)].i)); ;}
+ break;
+
+ case 56:
+#line 356 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->code1(g_lingo->c_fconstpush);
g_lingo->codeFloat((yyvsp[(1) - (1)].f)); ;}
break;
- case 56:
-#line 348 "engines/director/lingo/lingo-gr.y"
+ case 57:
+#line 359 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->code1(g_lingo->c_stringpush);
g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); ;}
break;
- case 57:
-#line 351 "engines/director/lingo/lingo-gr.y"
+ case 58:
+#line 362 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (1)].s)]->u.func);
- g_lingo->codeConst(0); // Put dummy value
delete (yyvsp[(1) - (1)].s); ;}
break;
- case 58:
-#line 355 "engines/director/lingo/lingo-gr.y"
+ case 59:
+#line 365 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->codeFunc((yyvsp[(1) - (4)].s), (yyvsp[(3) - (4)].narg));
delete (yyvsp[(1) - (4)].s); ;}
break;
- case 59:
-#line 358 "engines/director/lingo/lingo-gr.y"
+ case 60:
+#line 368 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->code1(g_lingo->c_eval);
g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str());
delete (yyvsp[(1) - (1)].s); ;}
break;
- case 60:
-#line 362 "engines/director/lingo/lingo-gr.y"
+ case 61:
+#line 372 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->codeConst(0); // Put dummy id
g_lingo->code1(g_lingo->c_theentitypush);
@@ -2208,8 +2249,8 @@ yyreduce:
g_lingo->code2(e, f); ;}
break;
- case 61:
-#line 369 "engines/director/lingo/lingo-gr.y"
+ case 62:
+#line 379 "engines/director/lingo/lingo-gr.y"
{
(yyval.code) = g_lingo->code1(g_lingo->c_theentitypush);
inst e = 0, f = 0;
@@ -2218,237 +2259,257 @@ yyreduce:
g_lingo->code2(e, f); ;}
break;
- case 63:
-#line 376 "engines/director/lingo/lingo-gr.y"
+ case 64:
+#line 386 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_add); ;}
break;
- case 64:
-#line 377 "engines/director/lingo/lingo-gr.y"
+ case 65:
+#line 387 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_sub); ;}
break;
- case 65:
-#line 378 "engines/director/lingo/lingo-gr.y"
+ case 66:
+#line 388 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_mul); ;}
break;
- case 66:
-#line 379 "engines/director/lingo/lingo-gr.y"
+ case 67:
+#line 389 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_div); ;}
break;
- case 67:
-#line 380 "engines/director/lingo/lingo-gr.y"
+ case 68:
+#line 390 "engines/director/lingo/lingo-gr.y"
+ { g_lingo->code1(g_lingo->c_mod); ;}
+ break;
+
+ case 69:
+#line 391 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_gt); ;}
break;
- case 68:
-#line 381 "engines/director/lingo/lingo-gr.y"
+ case 70:
+#line 392 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_lt); ;}
break;
- case 69:
-#line 382 "engines/director/lingo/lingo-gr.y"
+ case 71:
+#line 393 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_neq); ;}
break;
- case 70:
-#line 383 "engines/director/lingo/lingo-gr.y"
+ case 72:
+#line 394 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_ge); ;}
break;
- case 71:
-#line 384 "engines/director/lingo/lingo-gr.y"
+ case 73:
+#line 395 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_le); ;}
break;
- case 72:
-#line 385 "engines/director/lingo/lingo-gr.y"
+ case 74:
+#line 396 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_and); ;}
break;
- case 73:
-#line 386 "engines/director/lingo/lingo-gr.y"
+ case 75:
+#line 397 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_or); ;}
break;
- case 74:
-#line 387 "engines/director/lingo/lingo-gr.y"
+ case 76:
+#line 398 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_not); ;}
break;
- case 75:
-#line 388 "engines/director/lingo/lingo-gr.y"
+ case 77:
+#line 399 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_ampersand); ;}
break;
- case 76:
-#line 389 "engines/director/lingo/lingo-gr.y"
+ case 78:
+#line 400 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_concat); ;}
break;
- case 77:
-#line 390 "engines/director/lingo/lingo-gr.y"
+ case 79:
+#line 401 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_contains); ;}
break;
- case 78:
-#line 391 "engines/director/lingo/lingo-gr.y"
+ case 80:
+#line 402 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_starts); ;}
break;
- case 79:
-#line 392 "engines/director/lingo/lingo-gr.y"
+ case 81:
+#line 403 "engines/director/lingo/lingo-gr.y"
{ (yyval.code) = (yyvsp[(2) - (2)].code); ;}
break;
- case 80:
-#line 393 "engines/director/lingo/lingo-gr.y"
+ case 82:
+#line 404 "engines/director/lingo/lingo-gr.y"
{ (yyval.code) = (yyvsp[(2) - (2)].code); g_lingo->code1(g_lingo->c_negate); ;}
break;
- case 81:
-#line 394 "engines/director/lingo/lingo-gr.y"
+ case 83:
+#line 405 "engines/director/lingo/lingo-gr.y"
{ (yyval.code) = (yyvsp[(2) - (3)].code); ;}
break;
- case 82:
-#line 395 "engines/director/lingo/lingo-gr.y"
+ case 84:
+#line 406 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_intersects); ;}
break;
- case 83:
-#line 396 "engines/director/lingo/lingo-gr.y"
+ case 85:
+#line 407 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_within); ;}
break;
- case 84:
-#line 399 "engines/director/lingo/lingo-gr.y"
+ case 86:
+#line 410 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_printtop); ;}
break;
- case 87:
-#line 402 "engines/director/lingo/lingo-gr.y"
+ case 89:
+#line 413 "engines/director/lingo/lingo-gr.y"
+ { g_lingo->code1(g_lingo->c_exitRepeat); ;}
+ break;
+
+ case 90:
+#line 414 "engines/director/lingo/lingo-gr.y"
{ g_lingo->codeConst(0); // Push fake value on stack
g_lingo->code1(g_lingo->c_procret); ;}
break;
- case 89:
-#line 405 "engines/director/lingo/lingo-gr.y"
+ case 93:
+#line 418 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (2)].s)]->u.func);
delete (yyvsp[(1) - (2)].s); ;}
break;
- case 90:
-#line 408 "engines/director/lingo/lingo-gr.y"
+ case 94:
+#line 421 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (2)].s)]->u.func);
delete (yyvsp[(1) - (2)].s); ;}
break;
- case 91:
-#line 411 "engines/director/lingo/lingo-gr.y"
+ case 95:
+#line 424 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code2(g_lingo->c_voidpush, g_lingo->_handlers[*(yyvsp[(1) - (1)].s)]->u.func);
delete (yyvsp[(1) - (1)].s); ;}
break;
- case 92:
-#line 414 "engines/director/lingo/lingo-gr.y"
+ case 96:
+#line 427 "engines/director/lingo/lingo-gr.y"
{ g_lingo->codeFunc((yyvsp[(1) - (2)].s), (yyvsp[(2) - (2)].narg)); ;}
break;
- case 93:
-#line 415 "engines/director/lingo/lingo-gr.y"
+ case 97:
+#line 428 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_open); ;}
break;
- case 94:
-#line 416 "engines/director/lingo/lingo-gr.y"
+ case 98:
+#line 429 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code2(g_lingo->c_voidpush, g_lingo->c_open); ;}
break;
- case 95:
-#line 419 "engines/director/lingo/lingo-gr.y"
+ case 99:
+#line 432 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_global); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); delete (yyvsp[(1) - (1)].s); ;}
break;
- case 96:
-#line 420 "engines/director/lingo/lingo-gr.y"
+ case 100:
+#line 433 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_global); g_lingo->codeString((yyvsp[(3) - (3)].s)->c_str()); delete (yyvsp[(3) - (3)].s); ;}
break;
- case 97:
-#line 431 "engines/director/lingo/lingo-gr.y"
+ case 101:
+#line 436 "engines/director/lingo/lingo-gr.y"
+ { g_lingo->code1(g_lingo->c_instance); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); delete (yyvsp[(1) - (1)].s); ;}
+ break;
+
+ case 102:
+#line 437 "engines/director/lingo/lingo-gr.y"
+ { g_lingo->code1(g_lingo->c_instance); g_lingo->codeString((yyvsp[(3) - (3)].s)->c_str()); delete (yyvsp[(3) - (3)].s); ;}
+ break;
+
+ case 103:
+#line 448 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_gotoloop); ;}
break;
- case 98:
-#line 432 "engines/director/lingo/lingo-gr.y"
+ case 104:
+#line 449 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_gotonext); ;}
break;
- case 99:
-#line 433 "engines/director/lingo/lingo-gr.y"
+ case 105:
+#line 450 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_gotoprevious); ;}
break;
- case 100:
-#line 434 "engines/director/lingo/lingo-gr.y"
+ case 106:
+#line 451 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(1);
g_lingo->code1(g_lingo->c_goto); ;}
break;
- case 101:
-#line 437 "engines/director/lingo/lingo-gr.y"
+ case 107:
+#line 454 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(3);
g_lingo->code1(g_lingo->c_goto); ;}
break;
- case 102:
-#line 440 "engines/director/lingo/lingo-gr.y"
+ case 108:
+#line 457 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(2);
g_lingo->code1(g_lingo->c_goto); ;}
break;
- case 107:
-#line 453 "engines/director/lingo/lingo-gr.y"
+ case 113:
+#line 470 "engines/director/lingo/lingo-gr.y"
{ g_lingo->code1(g_lingo->c_playdone); ;}
break;
- case 108:
-#line 454 "engines/director/lingo/lingo-gr.y"
+ case 114:
+#line 471 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(1);
g_lingo->code1(g_lingo->c_play); ;}
break;
- case 109:
-#line 457 "engines/director/lingo/lingo-gr.y"
+ case 115:
+#line 474 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(3);
g_lingo->code1(g_lingo->c_play); ;}
break;
- case 110:
-#line 460 "engines/director/lingo/lingo-gr.y"
+ case 116:
+#line 477 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(2);
g_lingo->code1(g_lingo->c_play); ;}
break;
- case 111:
-#line 490 "engines/director/lingo/lingo-gr.y"
+ case 117:
+#line 507 "engines/director/lingo/lingo-gr.y"
{ g_lingo->_indef = true; g_lingo->_currentFactory.clear(); ;}
break;
- case 112:
-#line 491 "engines/director/lingo/lingo-gr.y"
+ case 118:
+#line 508 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeConst(0); // Push fake value on stack
g_lingo->code1(g_lingo->c_procret);
@@ -2456,54 +2517,53 @@ yyreduce:
g_lingo->_indef = false; ;}
break;
- case 113:
-#line 496 "engines/director/lingo/lingo-gr.y"
+ case 119:
+#line 513 "engines/director/lingo/lingo-gr.y"
{
g_lingo->codeFactory(*(yyvsp[(2) - (2)].s));
;}
break;
- case 114:
-#line 499 "engines/director/lingo/lingo-gr.y"
+ case 120:
+#line 516 "engines/director/lingo/lingo-gr.y"
{ g_lingo->_indef = true; ;}
break;
- case 115:
-#line 500 "engines/director/lingo/lingo-gr.y"
+ case 121:
+#line 517 "engines/director/lingo/lingo-gr.y"
{
- g_lingo->codeConst(0); // Push fake value on stack
g_lingo->code1(g_lingo->c_procret);
- g_lingo->define(*(yyvsp[(2) - (8)].s), (yyvsp[(4) - (8)].code), (yyvsp[(5) - (8)].narg), &g_lingo->_currentFactory);
+ g_lingo->define(*(yyvsp[(2) - (8)].s), (yyvsp[(4) - (8)].code), (yyvsp[(5) - (8)].narg) + 1, &g_lingo->_currentFactory);
g_lingo->_indef = false; ;}
break;
- case 116:
-#line 505 "engines/director/lingo/lingo-gr.y"
+ case 122:
+#line 521 "engines/director/lingo/lingo-gr.y"
{ (yyval.narg) = 0; ;}
break;
- case 117:
-#line 506 "engines/director/lingo/lingo-gr.y"
+ case 123:
+#line 522 "engines/director/lingo/lingo-gr.y"
{ g_lingo->codeArg((yyvsp[(1) - (1)].s)); (yyval.narg) = 1; ;}
break;
- case 118:
-#line 507 "engines/director/lingo/lingo-gr.y"
+ case 124:
+#line 523 "engines/director/lingo/lingo-gr.y"
{ g_lingo->codeArg((yyvsp[(3) - (3)].s)); (yyval.narg) = (yyvsp[(1) - (3)].narg) + 1; ;}
break;
- case 119:
-#line 508 "engines/director/lingo/lingo-gr.y"
+ case 125:
+#line 524 "engines/director/lingo/lingo-gr.y"
{ g_lingo->codeArg((yyvsp[(4) - (4)].s)); (yyval.narg) = (yyvsp[(1) - (4)].narg) + 1; ;}
break;
- case 120:
-#line 510 "engines/director/lingo/lingo-gr.y"
+ case 126:
+#line 526 "engines/director/lingo/lingo-gr.y"
{ g_lingo->codeArgStore(); ;}
break;
- case 121:
-#line 514 "engines/director/lingo/lingo-gr.y"
+ case 127:
+#line 530 "engines/director/lingo/lingo-gr.y"
{
g_lingo->code1(g_lingo->c_call);
g_lingo->codeString((yyvsp[(1) - (3)].s)->c_str());
@@ -2512,24 +2572,24 @@ yyreduce:
g_lingo->code1(numpar); ;}
break;
- case 122:
-#line 522 "engines/director/lingo/lingo-gr.y"
+ case 128:
+#line 538 "engines/director/lingo/lingo-gr.y"
{ (yyval.narg) = 0; ;}
break;
- case 123:
-#line 523 "engines/director/lingo/lingo-gr.y"
+ case 129:
+#line 539 "engines/director/lingo/lingo-gr.y"
{ (yyval.narg) = 1; ;}
break;
- case 124:
-#line 524 "engines/director/lingo/lingo-gr.y"
+ case 130:
+#line 540 "engines/director/lingo/lingo-gr.y"
{ (yyval.narg) = (yyvsp[(1) - (3)].narg) + 1; ;}
break;
/* Line 1267 of yacc.c. */
-#line 2533 "engines/director/lingo/lingo-gr.cpp"
+#line 2593 "engines/director/lingo/lingo-gr.cpp"
default: break;
}
YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc);
@@ -2743,6 +2803,6 @@ yyreturn:
}
-#line 527 "engines/director/lingo/lingo-gr.y"
+#line 543 "engines/director/lingo/lingo-gr.y"
diff --git a/engines/director/lingo/lingo-gr.h b/engines/director/lingo/lingo-gr.h
index de14709531..46097085fe 100644
--- a/engines/director/lingo/lingo-gr.h
+++ b/engines/director/lingo/lingo-gr.h
@@ -47,64 +47,67 @@
RECT = 263,
ARRAY = 264,
SYMBOL = 265,
- INT = 266,
- THEENTITY = 267,
- THEENTITYWITHID = 268,
- FLOAT = 269,
- BLTIN = 270,
- BLTINNOARGS = 271,
- BLTINNOARGSORONE = 272,
- BLTINONEARG = 273,
- BLTINARGLIST = 274,
- ID = 275,
- STRING = 276,
- HANDLER = 277,
- tDOWN = 278,
- tELSE = 279,
- tNLELSIF = 280,
- tEND = 281,
- tEXIT = 282,
- tFRAME = 283,
- tGLOBAL = 284,
- tGO = 285,
- tIF = 286,
- tINTO = 287,
- tLOOP = 288,
- tMACRO = 289,
- tMOVIE = 290,
- tNEXT = 291,
- tOF = 292,
- tPREVIOUS = 293,
- tPUT = 294,
- tREPEAT = 295,
- tSET = 296,
- tTHEN = 297,
- tTO = 298,
- tWHEN = 299,
- tWITH = 300,
- tWHILE = 301,
- tNLELSE = 302,
- tFACTORY = 303,
- tMETHOD = 304,
- tOPEN = 305,
- tPLAY = 306,
- tDONE = 307,
- tPLAYACCEL = 308,
- tGE = 309,
- tLE = 310,
- tGT = 311,
- tLT = 312,
- tEQ = 313,
- tNEQ = 314,
- tAND = 315,
- tOR = 316,
- tNOT = 317,
- tCONCAT = 318,
- tCONTAINS = 319,
- tSTARTS = 320,
- tSPRITE = 321,
- tINTERSECTS = 322,
- tWITHIN = 323
+ OBJECT = 266,
+ INT = 267,
+ THEENTITY = 268,
+ THEENTITYWITHID = 269,
+ FLOAT = 270,
+ BLTIN = 271,
+ BLTINNOARGS = 272,
+ BLTINNOARGSORONE = 273,
+ BLTINONEARG = 274,
+ BLTINARGLIST = 275,
+ ID = 276,
+ STRING = 277,
+ HANDLER = 278,
+ tDOWN = 279,
+ tELSE = 280,
+ tNLELSIF = 281,
+ tEND = 282,
+ tEXIT = 283,
+ tFRAME = 284,
+ tGLOBAL = 285,
+ tGO = 286,
+ tIF = 287,
+ tINTO = 288,
+ tLOOP = 289,
+ tMACRO = 290,
+ tMOVIE = 291,
+ tNEXT = 292,
+ tOF = 293,
+ tPREVIOUS = 294,
+ tPUT = 295,
+ tREPEAT = 296,
+ tSET = 297,
+ tTHEN = 298,
+ tTO = 299,
+ tWHEN = 300,
+ tWITH = 301,
+ tWHILE = 302,
+ tNLELSE = 303,
+ tFACTORY = 304,
+ tMETHOD = 305,
+ tOPEN = 306,
+ tPLAY = 307,
+ tDONE = 308,
+ tPLAYACCEL = 309,
+ tINSTANCE = 310,
+ tGE = 311,
+ tLE = 312,
+ tGT = 313,
+ tLT = 314,
+ tEQ = 315,
+ tNEQ = 316,
+ tAND = 317,
+ tOR = 318,
+ tNOT = 319,
+ tMOD = 320,
+ tCONCAT = 321,
+ tCONTAINS = 322,
+ tSTARTS = 323,
+ tSPRITE = 324,
+ tINTERSECTS = 325,
+ tWITHIN = 326
};
#endif
/* Tokens. */
@@ -116,64 +119,67 @@
#define RECT 263
#define ARRAY 264
#define SYMBOL 265
-#define INT 266
-#define THEENTITY 267
-#define THEENTITYWITHID 268
-#define FLOAT 269
-#define BLTIN 270
-#define BLTINNOARGS 271
-#define BLTINNOARGSORONE 272
-#define BLTINONEARG 273
-#define BLTINARGLIST 274
-#define ID 275
-#define STRING 276
-#define HANDLER 277
-#define tDOWN 278
-#define tELSE 279
-#define tNLELSIF 280
-#define tEND 281
-#define tEXIT 282
-#define tFRAME 283
-#define tGLOBAL 284
-#define tGO 285
-#define tIF 286
-#define tINTO 287
-#define tLOOP 288
-#define tMACRO 289
-#define tMOVIE 290
-#define tNEXT 291
-#define tOF 292
-#define tPREVIOUS 293
-#define tPUT 294
-#define tREPEAT 295
-#define tSET 296
-#define tTHEN 297
-#define tTO 298
-#define tWHEN 299
-#define tWITH 300
-#define tWHILE 301
-#define tNLELSE 302
-#define tFACTORY 303
-#define tMETHOD 304
-#define tOPEN 305
-#define tPLAY 306
-#define tDONE 307
-#define tPLAYACCEL 308
-#define tGE 309
-#define tLE 310
-#define tGT 311
-#define tLT 312
-#define tEQ 313
-#define tNEQ 314
-#define tAND 315
-#define tOR 316
-#define tNOT 317
-#define tCONCAT 318
-#define tCONTAINS 319
-#define tSTARTS 320
-#define tSPRITE 321
-#define tINTERSECTS 322
-#define tWITHIN 323
+#define OBJECT 266
+#define INT 267
+#define THEENTITY 268
+#define THEENTITYWITHID 269
+#define FLOAT 270
+#define BLTIN 271
+#define BLTINNOARGS 272
+#define BLTINNOARGSORONE 273
+#define BLTINONEARG 274
+#define BLTINARGLIST 275
+#define ID 276
+#define STRING 277
+#define HANDLER 278
+#define tDOWN 279
+#define tELSE 280
+#define tNLELSIF 281
+#define tEND 282
+#define tEXIT 283
+#define tFRAME 284
+#define tGLOBAL 285
+#define tGO 286
+#define tIF 287
+#define tINTO 288
+#define tLOOP 289
+#define tMACRO 290
+#define tMOVIE 291
+#define tNEXT 292
+#define tOF 293
+#define tPREVIOUS 294
+#define tPUT 295
+#define tREPEAT 296
+#define tSET 297
+#define tTHEN 298
+#define tTO 299
+#define tWHEN 300
+#define tWITH 301
+#define tWHILE 302
+#define tNLELSE 303
+#define tFACTORY 304
+#define tMETHOD 305
+#define tOPEN 306
+#define tPLAY 307
+#define tDONE 308
+#define tPLAYACCEL 309
+#define tINSTANCE 310
+#define tGE 311
+#define tLE 312
+#define tGT 313
+#define tLT 314
+#define tEQ 315
+#define tNEQ 316
+#define tAND 317
+#define tOR 318
+#define tNOT 319
+#define tMOD 320
+#define tCONCAT 321
+#define tCONTAINS 322
+#define tSTARTS 323
+#define tSPRITE 324
+#define tINTERSECTS 325
+#define tWITHIN 326
@@ -191,7 +197,7 @@ typedef union YYSTYPE
Common::Array<double> *arr;
}
/* Line 1529 of yacc.c. */
-#line 195 "engines/director/lingo/lingo-gr.hpp"
+#line 201 "engines/director/lingo/lingo-gr.hpp"
YYSTYPE;
# define yystype YYSTYPE /* obsolescent; will be withdrawn */
# define YYSTYPE_IS_DECLARED 1
diff --git a/engines/director/lingo/lingo-gr.y b/engines/director/lingo/lingo-gr.y
index 131507880f..9077f44876 100644
--- a/engines/director/lingo/lingo-gr.y
+++ b/engines/director/lingo/lingo-gr.y
@@ -58,7 +58,7 @@ extern int yylex();
extern int yyparse();
using namespace Director;
-void yyerror(char *s) {
+void yyerror(const char *s) {
g_lingo->_hadError = true;
warning("%s at line %d col %d", s, g_lingo->_linenumber, g_lingo->_colnumber);
}
@@ -77,7 +77,7 @@ void yyerror(char *s) {
}
%token UNARY
-%token CASTREF VOID VAR POINT RECT ARRAY SYMBOL
+%token CASTREF VOID VAR POINT RECT ARRAY SYMBOL OBJECT
%token<i> INT
%token<e> THEENTITY THEENTITYWITHID
%token<f> FLOAT
@@ -85,17 +85,19 @@ void yyerror(char *s) {
%token<s> ID STRING HANDLER
%token tDOWN tELSE tNLELSIF tEND tEXIT tFRAME tGLOBAL tGO tIF tINTO tLOOP tMACRO
%token tMOVIE tNEXT tOF tPREVIOUS tPUT tREPEAT tSET tTHEN tTO tWHEN
-%token tWITH tWHILE tNLELSE tFACTORY tMETHOD tOPEN tPLAY tDONE tPLAYACCEL
-%token tGE tLE tGT tLT tEQ tNEQ tAND tOR tNOT
+%token tWITH tWHILE tNLELSE tFACTORY tMETHOD tOPEN tPLAY tDONE tPLAYACCEL tINSTANCE
+%token tGE tLE tGT tLT tEQ tNEQ tAND tOR tNOT tMOD
%token tCONCAT tCONTAINS tSTARTS
%token tSPRITE tINTERSECTS tWITHIN
-%type<code> asgn begin elseif elsestmtoneliner end expr if repeatwhile repeatwith stmtlist
+%type<code> asgn begin elseif elsestmtoneliner end expr if when repeatwhile repeatwith stmtlist
%type<narg> argdef arglist
%right '='
+%left tLT tLE tGT tGE tNEQ tCONTAINS tSTARTS
+%left '&'
%left '+' '-'
-%left '*' '/' '%'
+%left '*' '/' '%' tAND tOR tMOD
%right UNARY
%%
@@ -217,8 +219,11 @@ stmt: stmtoneliner
(*g_lingo->_currentScript)[$1 + 3] = body; /* body of loop */
(*g_lingo->_currentScript)[$1 + 4] = inc; /* increment */
(*g_lingo->_currentScript)[$1 + 5] = end; } /* end, if cond fails */
- | tWHEN ID tTHEN expr {
- g_lingo->code1(g_lingo->c_ifcode);
+ | when expr end {
+ inst end = 0;
+ WRITE_UINT32(&end, $3);
+ g_lingo->code1(STOP);
+ (*g_lingo->_currentScript)[$1 + 1] = end;
}
;
@@ -341,6 +346,12 @@ stmtlist: /* nothing */ { $$ = g_lingo->_currentScript->size(); }
| stmtlist stmt
;
+when: tWHEN ID tTHEN {
+ $$ = g_lingo->code1(g_lingo->c_whencode);
+ g_lingo->code1(STOP);
+ g_lingo->codeString($2->c_str());
+ delete $2; }
+
expr: INT { $$ = g_lingo->codeConst($1); }
| FLOAT {
$$ = g_lingo->code1(g_lingo->c_fconstpush);
@@ -350,7 +361,6 @@ expr: INT { $$ = g_lingo->codeConst($1); }
g_lingo->codeString($1->c_str()); }
| BLTINNOARGS {
$$ = g_lingo->code1(g_lingo->_handlers[*$1]->u.func);
- g_lingo->codeConst(0); // Put dummy value
delete $1; }
| ID '(' arglist ')' {
$$ = g_lingo->codeFunc($1, $3);
@@ -377,6 +387,7 @@ expr: INT { $$ = g_lingo->codeConst($1); }
| expr '-' expr { g_lingo->code1(g_lingo->c_sub); }
| expr '*' expr { g_lingo->code1(g_lingo->c_mul); }
| expr '/' expr { g_lingo->code1(g_lingo->c_div); }
+ | expr tMOD expr { g_lingo->code1(g_lingo->c_mod); }
| expr '>' expr { g_lingo->code1(g_lingo->c_gt); }
| expr '<' expr { g_lingo->code1(g_lingo->c_lt); }
| expr tNEQ expr { g_lingo->code1(g_lingo->c_neq); }
@@ -399,9 +410,11 @@ expr: INT { $$ = g_lingo->codeConst($1); }
func: tPUT expr { g_lingo->code1(g_lingo->c_printtop); }
| gotofunc
| playfunc
+ | tEXIT tREPEAT { g_lingo->code1(g_lingo->c_exitRepeat); }
| tEXIT { g_lingo->codeConst(0); // Push fake value on stack
g_lingo->code1(g_lingo->c_procret); }
| tGLOBAL globallist
+ | tINSTANCE instancelist
| BLTINONEARG expr {
g_lingo->code1(g_lingo->_handlers[*$1]->u.func);
delete $1; }
@@ -420,6 +433,10 @@ globallist: ID { g_lingo->code1(g_lingo->c_global); g_lingo->codeString($1->c
| globallist ',' ID { g_lingo->code1(g_lingo->c_global); g_lingo->codeString($3->c_str()); delete $3; }
;
+instancelist: ID { g_lingo->code1(g_lingo->c_instance); g_lingo->codeString($1->c_str()); delete $1; }
+ | instancelist ',' ID { g_lingo->code1(g_lingo->c_instance); g_lingo->codeString($3->c_str()); delete $3; }
+ ;
+
// go {to} {frame} whichFrame {of movie whichMovie}
// go {to} {frame "Open23" of} movie whichMovie
// go loop
@@ -498,9 +515,8 @@ defn: tMACRO ID { g_lingo->_indef = true; g_lingo->_currentFactory.clear(); }
}
| tMETHOD ID { g_lingo->_indef = true; }
begin argdef nl argstore stmtlist {
- g_lingo->codeConst(0); // Push fake value on stack
g_lingo->code1(g_lingo->c_procret);
- g_lingo->define(*$2, $4, $5, &g_lingo->_currentFactory);
+ g_lingo->define(*$2, $4, $5 + 1, &g_lingo->_currentFactory);
g_lingo->_indef = false; } ;
argdef: /* nothing */ { $$ = 0; }
| ID { g_lingo->codeArg($1); $$ = 1; }
diff --git a/engines/director/lingo/lingo-lex.cpp b/engines/director/lingo/lingo-lex.cpp
index 5fbd8d8653..ebdb169a65 100644
--- a/engines/director/lingo/lingo-lex.cpp
+++ b/engines/director/lingo/lingo-lex.cpp
@@ -364,8 +364,8 @@ static void yy_fatal_error (yyconst char msg[] );
*yy_cp = '\0'; \
(yy_c_buf_p) = yy_cp;
-#define YY_NUM_RULES 56
-#define YY_END_OF_BUFFER 57
+#define YY_NUM_RULES 59
+#define YY_END_OF_BUFFER 60
/* This struct is not used in this scanner,
but its presence is necessary. */
struct yy_trans_info
@@ -373,28 +373,31 @@ struct yy_trans_info
flex_int32_t yy_verify;
flex_int32_t yy_nxt;
};
-static yyconst flex_int16_t yy_accept[191] =
+static yyconst flex_int16_t yy_accept[206] =
{ 0,
- 0, 0, 57, 55, 3, 53, 53, 55, 55, 52,
- 52, 52, 51, 52, 52, 49, 49, 49, 49, 49,
- 49, 49, 49, 49, 49, 49, 49, 49, 49, 49,
- 49, 49, 2, 2, 3, 53, 0, 0, 0, 0,
- 0, 54, 48, 1, 50, 51, 47, 45, 46, 49,
- 49, 49, 49, 49, 49, 49, 49, 49, 49, 18,
- 8, 49, 49, 49, 49, 49, 49, 49, 27, 49,
- 29, 49, 49, 49, 49, 49, 49, 49, 49, 39,
- 49, 49, 2, 2, 0, 1, 50, 4, 49, 49,
- 49, 49, 12, 49, 49, 49, 49, 0, 49, 49,
-
- 49, 49, 49, 49, 26, 49, 49, 49, 32, 49,
- 34, 49, 49, 49, 49, 49, 49, 0, 49, 6,
- 7, 11, 14, 49, 49, 49, 0, 49, 20, 21,
- 49, 49, 49, 25, 28, 30, 49, 49, 49, 49,
- 0, 38, 43, 49, 41, 10, 49, 49, 15, 49,
- 17, 49, 22, 49, 24, 49, 49, 49, 49, 37,
- 44, 49, 0, 49, 49, 16, 49, 23, 49, 33,
- 40, 35, 0, 42, 0, 49, 13, 49, 49, 0,
- 9, 5, 49, 31, 0, 49, 0, 19, 36, 0
+ 0, 0, 60, 58, 3, 56, 56, 58, 58, 55,
+ 55, 55, 54, 55, 55, 52, 52, 52, 52, 52,
+ 52, 52, 52, 52, 52, 52, 52, 52, 52, 52,
+ 52, 52, 2, 2, 3, 56, 0, 0, 0, 0,
+ 0, 57, 51, 1, 53, 54, 50, 48, 49, 52,
+ 52, 52, 52, 52, 52, 52, 52, 52, 52, 18,
+ 8, 52, 52, 52, 52, 52, 52, 52, 29, 52,
+ 31, 52, 52, 52, 52, 52, 52, 52, 52, 42,
+ 52, 52, 2, 2, 0, 1, 53, 4, 52, 52,
+ 52, 52, 12, 52, 52, 52, 52, 0, 52, 52,
+
+ 52, 52, 52, 25, 52, 52, 28, 52, 52, 52,
+ 34, 52, 36, 52, 52, 52, 52, 52, 52, 0,
+ 52, 6, 7, 11, 14, 52, 52, 52, 0, 52,
+ 52, 21, 22, 52, 52, 52, 27, 30, 32, 52,
+ 52, 52, 52, 0, 41, 46, 52, 44, 10, 52,
+ 52, 15, 52, 17, 52, 52, 23, 52, 26, 52,
+ 52, 52, 52, 40, 40, 47, 52, 0, 52, 52,
+ 16, 52, 52, 24, 52, 35, 43, 37, 0, 40,
+ 45, 0, 52, 13, 52, 52, 52, 0, 40, 9,
+ 5, 19, 52, 33, 0, 40, 52, 0, 0, 20,
+
+ 39, 0, 0, 38, 0
} ;
static yyconst flex_int32_t yy_ec[256] =
@@ -407,12 +410,12 @@ static yyconst flex_int32_t yy_ec[256] =
11, 11, 11, 11, 11, 11, 11, 7, 1, 12,
13, 14, 1, 1, 15, 16, 17, 18, 19, 20,
21, 22, 23, 24, 24, 25, 26, 27, 28, 29,
- 24, 30, 31, 32, 33, 34, 35, 36, 37, 24,
- 1, 1, 1, 7, 38, 1, 39, 40, 41, 42,
+ 30, 31, 32, 33, 34, 35, 36, 37, 38, 24,
+ 1, 1, 1, 7, 39, 1, 40, 41, 42, 43,
- 43, 44, 45, 46, 47, 24, 24, 48, 49, 50,
- 51, 52, 24, 53, 54, 55, 56, 57, 58, 59,
- 60, 24, 1, 1, 1, 1, 1, 1, 1, 1,
+ 44, 45, 46, 47, 48, 24, 24, 49, 50, 51,
+ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
+ 62, 24, 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,
@@ -429,130 +432,140 @@ static yyconst flex_int32_t yy_ec[256] =
1, 1, 1, 1, 1
} ;
-static yyconst flex_int32_t yy_meta[61] =
+static yyconst flex_int32_t yy_meta[63] =
{ 0,
1, 2, 3, 3, 2, 1, 1, 1, 1, 1,
4, 1, 1, 1, 5, 5, 5, 5, 5, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
- 5, 5, 5, 5, 5, 5, 5, 4, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5, 4, 5,
5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
- 5, 5, 5, 5, 5, 5, 5, 5, 5, 5
+ 5, 5, 5, 5, 5, 5, 5, 5, 5, 5,
+ 5, 5
} ;
-static yyconst flex_int16_t yy_base[196] =
+static yyconst flex_int16_t yy_base[211] =
{ 0,
- 0, 59, 203, 452, 63, 67, 71, 75, 167, 452,
- 157, 154, 52, 68, 143, 56, 0, 56, 57, 67,
- 72, 68, 68, 69, 85, 102, 102, 104, 80, 119,
- 113, 120, 173, 177, 181, 452, 185, 189, 193, 102,
- 99, 452, 452, 0, 80, 129, 452, 452, 452, 0,
- 91, 120, 165, 118, 126, 146, 184, 187, 171, 96,
- 0, 172, 177, 189, 178, 177, 178, 184, 0, 188,
- 0, 202, 199, 188, 192, 192, 199, 220, 219, 0,
- 226, 208, 251, 262, 217, 0, 78, 0, 219, 227,
- 230, 239, 0, 228, 229, 242, 256, 273, 257, 250,
-
- 251, 255, 263, 256, 0, 262, 253, 258, 0, 274,
- 0, 271, 267, 304, 271, 274, 273, 284, 299, 0,
- 0, 0, 0, 279, 297, 308, 297, 296, 0, 0,
- 301, 304, 314, 0, 0, 0, 311, 320, 305, 307,
- 156, 0, 0, 322, 319, 341, 321, 320, 0, 326,
- 452, 322, 0, 327, 0, 328, 329, 344, 336, 370,
- 0, 343, 375, 344, 341, 0, 345, 0, 348, 0,
- 0, 0, 380, 0, 363, 355, 0, 372, 360, 372,
- 452, 0, 363, 0, 394, 366, 398, 0, 400, 452,
- 431, 433, 438, 442, 446
-
+ 0, 61, 156, 521, 65, 69, 73, 77, 148, 521,
+ 104, 99, 54, 70, 91, 58, 0, 58, 59, 69,
+ 74, 70, 70, 71, 88, 105, 105, 110, 82, 118,
+ 117, 130, 177, 181, 185, 521, 189, 193, 197, 106,
+ 94, 521, 521, 0, 82, 132, 521, 521, 521, 0,
+ 84, 119, 169, 116, 120, 174, 186, 191, 180, 171,
+ 0, 177, 183, 196, 182, 201, 181, 188, 0, 204,
+ 0, 209, 206, 194, 201, 207, 212, 231, 228, 0,
+ 233, 222, 271, 280, 230, 0, 80, 0, 230, 234,
+ 238, 247, 0, 235, 236, 244, 264, 293, 255, 271,
+
+ 267, 266, 278, 0, 279, 271, 0, 279, 269, 273,
+ 0, 290, 0, 287, 282, 309, 289, 292, 296, 300,
+ 307, 0, 0, 0, 0, 296, 310, 318, 310, 324,
+ 311, 0, 0, 317, 318, 330, 0, 0, 0, 328,
+ 337, 322, 323, 363, 0, 0, 334, 334, 202, 336,
+ 330, 0, 338, 521, 340, 340, 0, 355, 0, 347,
+ 348, 364, 354, 212, 387, 0, 361, 388, 367, 359,
+ 0, 383, 382, 0, 369, 0, 0, 0, 402, 404,
+ 0, 393, 383, 0, 401, 405, 384, 403, 427, 521,
+ 0, 0, 395, 0, 256, 429, 401, 441, 448, 0,
+
+ 453, 404, 459, 460, 521, 500, 502, 507, 511, 515
} ;
-static yyconst flex_int16_t yy_def[196] =
+static yyconst flex_int16_t yy_def[211] =
{ 0,
- 190, 1, 190, 190, 190, 190, 190, 190, 191, 190,
- 190, 190, 190, 190, 190, 192, 192, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 190, 190, 190, 190, 190, 190, 190, 190,
- 191, 190, 190, 193, 190, 190, 190, 190, 190, 192,
- 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 190, 190, 190, 193, 190, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 190, 192, 192,
-
- 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 190, 192, 192,
- 192, 192, 192, 192, 192, 192, 190, 192, 192, 192,
- 192, 192, 192, 192, 192, 192, 192, 192, 192, 192,
- 194, 192, 192, 192, 192, 190, 192, 192, 192, 192,
- 190, 192, 192, 192, 192, 192, 192, 192, 192, 194,
- 192, 192, 190, 192, 192, 192, 192, 192, 192, 192,
- 192, 192, 190, 192, 190, 192, 192, 192, 192, 190,
- 190, 192, 192, 192, 190, 192, 195, 192, 195, 0,
- 190, 190, 190, 190, 190
-
+ 205, 1, 205, 205, 205, 205, 205, 205, 206, 205,
+ 205, 205, 205, 205, 205, 207, 207, 207, 207, 207,
+ 207, 207, 207, 207, 207, 207, 207, 207, 207, 207,
+ 207, 207, 205, 205, 205, 205, 205, 205, 205, 205,
+ 206, 205, 205, 208, 205, 205, 205, 205, 205, 207,
+ 207, 207, 207, 207, 207, 207, 207, 207, 207, 207,
+ 207, 207, 207, 207, 207, 207, 207, 207, 207, 207,
+ 207, 207, 207, 207, 207, 207, 207, 207, 207, 207,
+ 207, 207, 205, 205, 205, 208, 205, 207, 207, 207,
+ 207, 207, 207, 207, 207, 207, 207, 205, 207, 207,
+
+ 207, 207, 207, 207, 207, 207, 207, 207, 207, 207,
+ 207, 207, 207, 207, 207, 207, 207, 207, 207, 205,
+ 207, 207, 207, 207, 207, 207, 207, 207, 205, 207,
+ 207, 207, 207, 207, 207, 207, 207, 207, 207, 207,
+ 207, 207, 207, 209, 207, 207, 207, 207, 205, 207,
+ 207, 207, 207, 205, 207, 207, 207, 207, 207, 207,
+ 207, 207, 207, 209, 209, 207, 207, 205, 207, 207,
+ 207, 207, 207, 207, 207, 207, 207, 207, 205, 209,
+ 207, 205, 207, 207, 207, 207, 207, 205, 209, 205,
+ 207, 207, 207, 207, 205, 209, 207, 210, 205, 207,
+
+ 210, 205, 205, 210, 0, 205, 205, 205, 205, 205
} ;
-static yyconst flex_int16_t yy_nxt[513] =
+static yyconst flex_int16_t yy_nxt[584] =
{ 0,
4, 5, 6, 7, 8, 9, 10, 11, 12, 4,
13, 14, 10, 15, 16, 17, 18, 19, 20, 21,
- 22, 17, 23, 17, 24, 25, 26, 27, 28, 29,
- 30, 31, 17, 17, 32, 17, 17, 17, 16, 17,
- 18, 19, 20, 21, 22, 17, 23, 24, 25, 26,
- 27, 28, 29, 30, 31, 17, 17, 32, 17, 17,
- 33, 45, 46, 34, 35, 36, 36, 37, 38, 39,
- 39, 38, 38, 39, 39, 38, 37, 36, 36, 37,
- 47, 48, 51, 52, 53, 40, 57, 61, 87, 40,
- 87, 54, 59, 55, 62, 60, 63, 98, 75, 64,
-
- 98, 58, 56, 65, 42, 51, 52, 53, 88, 40,
- 57, 61, 66, 40, 54, 59, 55, 62, 60, 63,
- 67, 69, 75, 64, 58, 56, 85, 65, 72, 68,
- 70, 71, 88, 73, 79, 66, 74, 76, 45, 46,
- 80, 81, 82, 93, 67, 69, 89, 77, 92, 85,
- 78, 72, 68, 70, 71, 49, 73, 141, 79, 74,
- 141, 76, 44, 80, 43, 81, 82, 93, 94, 89,
- 77, 92, 42, 78, 83, 36, 36, 84, 84, 36,
- 36, 84, 35, 36, 36, 37, 37, 36, 36, 37,
- 38, 90, 94, 38, 38, 39, 39, 38, 97, 91,
-
- 95, 96, 190, 99, 100, 101, 106, 40, 190, 102,
- 103, 40, 190, 104, 90, 105, 107, 108, 190, 109,
- 110, 97, 91, 111, 95, 96, 99, 100, 112, 101,
- 106, 40, 102, 103, 113, 40, 104, 114, 105, 117,
- 107, 108, 109, 110, 115, 120, 111, 118, 116, 190,
- 119, 112, 83, 36, 36, 84, 121, 122, 113, 123,
- 124, 114, 117, 84, 36, 36, 84, 125, 115, 120,
- 118, 126, 116, 119, 98, 128, 132, 98, 130, 121,
- 131, 122, 123, 124, 129, 133, 190, 134, 135, 136,
- 125, 137, 138, 139, 145, 126, 140, 143, 144, 128,
-
- 132, 130, 146, 131, 127, 141, 148, 129, 141, 133,
- 134, 135, 136, 147, 137, 149, 138, 139, 145, 140,
- 143, 144, 150, 190, 151, 152, 146, 127, 153, 148,
- 142, 154, 155, 156, 157, 190, 158, 147, 159, 149,
- 161, 162, 163, 164, 168, 163, 150, 151, 152, 165,
- 166, 153, 167, 142, 154, 169, 155, 156, 157, 158,
- 170, 159, 171, 178, 161, 162, 172, 164, 168, 174,
- 176, 173, 165, 166, 173, 167, 163, 177, 169, 163,
- 179, 173, 181, 170, 173, 182, 171, 178, 183, 172,
- 184, 185, 174, 176, 186, 187, 188, 175, 187, 187,
-
- 177, 190, 187, 179, 190, 190, 181, 180, 182, 190,
- 190, 190, 183, 184, 190, 185, 190, 186, 190, 188,
- 190, 175, 190, 190, 190, 190, 190, 190, 190, 190,
- 180, 41, 41, 190, 41, 41, 50, 50, 86, 86,
- 190, 86, 86, 160, 190, 190, 160, 189, 190, 190,
- 189, 3, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
-
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190
+ 22, 17, 23, 17, 24, 25, 26, 27, 28, 17,
+ 29, 30, 31, 17, 17, 32, 17, 17, 17, 16,
+ 17, 18, 19, 20, 21, 22, 17, 23, 24, 25,
+ 26, 27, 28, 17, 29, 30, 31, 17, 17, 32,
+ 17, 17, 33, 45, 46, 34, 35, 36, 36, 37,
+ 38, 39, 39, 38, 38, 39, 39, 38, 37, 36,
+ 36, 37, 47, 48, 51, 52, 53, 40, 57, 61,
+ 87, 40, 87, 54, 59, 55, 62, 60, 63, 42,
+
+ 75, 88, 64, 49, 58, 56, 65, 44, 51, 52,
+ 53, 43, 40, 57, 61, 66, 40, 54, 59, 55,
+ 62, 60, 63, 67, 69, 75, 88, 64, 58, 56,
+ 85, 65, 68, 70, 72, 71, 76, 93, 79, 66,
+ 73, 45, 46, 74, 80, 89, 77, 92, 67, 69,
+ 78, 81, 82, 42, 85, 205, 68, 70, 72, 71,
+ 205, 76, 93, 79, 73, 205, 205, 74, 80, 89,
+ 77, 92, 98, 205, 78, 98, 81, 82, 83, 36,
+ 36, 84, 84, 36, 36, 84, 35, 36, 36, 37,
+ 37, 36, 36, 37, 38, 90, 94, 38, 38, 39,
+
+ 39, 38, 95, 168, 91, 96, 168, 97, 99, 100,
+ 101, 40, 102, 179, 103, 40, 179, 106, 104, 90,
+ 107, 94, 108, 109, 110, 205, 111, 95, 91, 112,
+ 96, 97, 99, 100, 101, 105, 40, 102, 103, 113,
+ 40, 106, 114, 104, 107, 115, 116, 108, 109, 110,
+ 111, 117, 122, 112, 119, 118, 205, 198, 205, 105,
+ 198, 120, 121, 113, 123, 124, 114, 125, 126, 127,
+ 115, 116, 83, 36, 36, 84, 117, 122, 119, 128,
+ 118, 84, 36, 36, 84, 120, 121, 130, 123, 131,
+ 124, 125, 126, 127, 98, 133, 134, 98, 132, 135,
+
+ 205, 136, 205, 137, 128, 138, 139, 140, 141, 142,
+ 144, 130, 143, 144, 131, 146, 147, 148, 149, 133,
+ 134, 150, 132, 151, 135, 129, 136, 137, 152, 138,
+ 139, 140, 153, 141, 142, 145, 143, 154, 155, 146,
+ 147, 156, 148, 149, 157, 158, 150, 151, 159, 129,
+ 160, 161, 166, 152, 162, 163, 167, 153, 169, 145,
+ 170, 154, 171, 155, 144, 156, 172, 144, 157, 158,
+ 205, 173, 174, 159, 175, 160, 161, 166, 162, 163,
+ 176, 167, 177, 169, 170, 178, 171, 181, 179, 168,
+ 172, 179, 168, 183, 165, 173, 184, 174, 175, 185,
+
+ 186, 205, 187, 179, 176, 179, 179, 177, 179, 178,
+ 182, 181, 190, 205, 191, 194, 180, 183, 165, 192,
+ 184, 193, 195, 203, 185, 186, 187, 197, 179, 188,
+ 199, 179, 200, 199, 189, 182, 205, 190, 191, 194,
+ 180, 205, 198, 205, 192, 198, 193, 195, 203, 199,
+ 205, 197, 199, 188, 205, 205, 200, 205, 189, 196,
+ 204, 204, 205, 204, 204, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 202, 205, 205, 205, 205,
+ 205, 205, 205, 196, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 202,
+
+ 41, 41, 205, 41, 41, 50, 50, 86, 86, 205,
+ 86, 86, 164, 205, 205, 164, 201, 205, 205, 201,
+ 3, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205
} ;
-static yyconst flex_int16_t yy_chk[513] =
+static yyconst flex_int16_t yy_chk[584] =
{ 0,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
@@ -560,57 +573,64 @@ static yyconst flex_int16_t yy_chk[513] =
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,
- 2, 13, 13, 2, 5, 5, 5, 5, 6, 6,
- 6, 6, 7, 7, 7, 7, 8, 8, 8, 8,
- 14, 14, 16, 18, 19, 6, 21, 23, 87, 7,
- 45, 20, 22, 20, 23, 22, 24, 60, 29, 25,
-
- 60, 21, 20, 25, 41, 16, 18, 19, 51, 6,
- 21, 23, 25, 7, 20, 22, 20, 23, 22, 24,
- 26, 27, 29, 25, 21, 20, 40, 25, 28, 26,
- 27, 27, 51, 28, 31, 25, 28, 30, 46, 46,
- 31, 32, 32, 55, 26, 27, 52, 30, 54, 40,
- 30, 28, 26, 27, 27, 15, 28, 141, 31, 28,
- 141, 30, 12, 31, 11, 32, 32, 55, 56, 52,
- 30, 54, 9, 30, 33, 33, 33, 33, 34, 34,
- 34, 34, 35, 35, 35, 35, 37, 37, 37, 37,
- 38, 53, 56, 38, 39, 39, 39, 39, 59, 53,
-
- 57, 58, 3, 62, 63, 64, 70, 38, 0, 65,
- 66, 39, 0, 67, 53, 68, 72, 73, 0, 74,
- 75, 59, 53, 76, 57, 58, 62, 63, 77, 64,
- 70, 38, 65, 66, 78, 39, 67, 79, 68, 82,
- 72, 73, 74, 75, 81, 90, 76, 85, 81, 0,
- 89, 77, 83, 83, 83, 83, 91, 92, 78, 94,
- 95, 79, 82, 84, 84, 84, 84, 96, 81, 90,
- 85, 97, 81, 89, 98, 99, 102, 98, 100, 91,
- 101, 92, 94, 95, 99, 103, 0, 104, 106, 107,
- 96, 108, 110, 112, 117, 97, 113, 115, 116, 99,
-
- 102, 100, 118, 101, 98, 114, 124, 99, 114, 103,
- 104, 106, 107, 119, 108, 125, 110, 112, 117, 113,
- 115, 116, 126, 0, 127, 128, 118, 98, 131, 124,
- 114, 132, 133, 137, 138, 0, 139, 119, 140, 125,
- 144, 145, 146, 147, 154, 146, 126, 127, 128, 148,
- 150, 131, 152, 114, 132, 156, 133, 137, 138, 139,
- 157, 140, 158, 167, 144, 145, 159, 147, 154, 162,
- 164, 160, 148, 150, 160, 152, 163, 165, 156, 163,
- 169, 173, 175, 157, 173, 176, 158, 167, 178, 159,
- 179, 180, 162, 164, 183, 185, 186, 163, 185, 187,
-
- 165, 189, 187, 169, 189, 0, 175, 173, 176, 0,
- 0, 0, 178, 179, 0, 180, 0, 183, 0, 186,
- 0, 163, 0, 0, 0, 0, 0, 0, 0, 0,
- 173, 191, 191, 0, 191, 191, 192, 192, 193, 193,
- 0, 193, 193, 194, 0, 0, 194, 195, 0, 0,
- 195, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
-
- 190, 190, 190, 190, 190, 190, 190, 190, 190, 190,
- 190, 190
+ 1, 1, 2, 13, 13, 2, 5, 5, 5, 5,
+ 6, 6, 6, 6, 7, 7, 7, 7, 8, 8,
+ 8, 8, 14, 14, 16, 18, 19, 6, 21, 23,
+ 87, 7, 45, 20, 22, 20, 23, 22, 24, 41,
+
+ 29, 51, 25, 15, 21, 20, 25, 12, 16, 18,
+ 19, 11, 6, 21, 23, 25, 7, 20, 22, 20,
+ 23, 22, 24, 26, 27, 29, 51, 25, 21, 20,
+ 40, 25, 26, 27, 28, 27, 30, 55, 31, 25,
+ 28, 46, 46, 28, 31, 52, 30, 54, 26, 27,
+ 30, 32, 32, 9, 40, 3, 26, 27, 28, 27,
+ 0, 30, 55, 31, 28, 0, 0, 28, 31, 52,
+ 30, 54, 60, 0, 30, 60, 32, 32, 33, 33,
+ 33, 33, 34, 34, 34, 34, 35, 35, 35, 35,
+ 37, 37, 37, 37, 38, 53, 56, 38, 39, 39,
+
+ 39, 39, 57, 149, 53, 58, 149, 59, 62, 62,
+ 63, 38, 64, 164, 65, 39, 164, 67, 66, 53,
+ 68, 56, 70, 72, 73, 0, 74, 57, 53, 75,
+ 58, 59, 62, 62, 63, 66, 38, 64, 65, 76,
+ 39, 67, 77, 66, 68, 78, 79, 70, 72, 73,
+ 74, 81, 90, 75, 82, 81, 0, 195, 0, 66,
+ 195, 85, 89, 76, 91, 92, 77, 94, 95, 96,
+ 78, 79, 83, 83, 83, 83, 81, 90, 82, 97,
+ 81, 84, 84, 84, 84, 85, 89, 99, 91, 100,
+ 92, 94, 95, 96, 98, 101, 102, 98, 100, 103,
+
+ 0, 105, 0, 106, 97, 108, 109, 110, 112, 114,
+ 116, 99, 115, 116, 100, 117, 118, 119, 120, 101,
+ 102, 121, 100, 126, 103, 98, 105, 106, 127, 108,
+ 109, 110, 128, 112, 114, 116, 115, 129, 130, 117,
+ 118, 131, 119, 120, 134, 135, 121, 126, 136, 98,
+ 140, 141, 147, 127, 142, 143, 148, 128, 150, 116,
+ 151, 129, 153, 130, 144, 131, 155, 144, 134, 135,
+ 0, 156, 158, 136, 160, 140, 141, 147, 142, 143,
+ 161, 148, 162, 150, 151, 163, 153, 167, 165, 168,
+ 155, 165, 168, 169, 144, 156, 170, 158, 160, 172,
+
+ 173, 0, 175, 179, 161, 180, 179, 162, 180, 163,
+ 168, 167, 182, 0, 183, 187, 165, 169, 144, 185,
+ 170, 186, 188, 202, 172, 173, 175, 193, 189, 179,
+ 196, 189, 197, 196, 180, 168, 0, 182, 183, 187,
+ 165, 0, 198, 0, 185, 198, 186, 188, 202, 199,
+ 0, 193, 199, 179, 201, 0, 197, 201, 180, 189,
+ 203, 204, 0, 203, 204, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 199, 0, 0, 0, 0,
+ 0, 0, 0, 189, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 199,
+
+ 206, 206, 0, 206, 206, 207, 207, 208, 208, 0,
+ 208, 208, 209, 0, 0, 209, 210, 0, 0, 210,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205, 205, 205, 205, 205, 205, 205, 205,
+ 205, 205, 205
} ;
static yy_state_type yy_last_accepting_state;
@@ -684,7 +704,7 @@ static void countnl() {
g_lingo->_colnumber = strlen(p);
}
-#line 688 "engines/director/lingo/lingo-lex.cpp"
+#line 708 "engines/director/lingo/lingo-lex.cpp"
#define INITIAL 0
@@ -872,7 +892,7 @@ YY_DECL
#line 69 "engines/director/lingo/lingo-lex.l"
-#line 876 "engines/director/lingo/lingo-lex.cpp"
+#line 896 "engines/director/lingo/lingo-lex.cpp"
if ( !(yy_init) )
{
@@ -926,13 +946,13 @@ yy_match:
while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
{
yy_current_state = (int) yy_def[yy_current_state];
- if ( yy_current_state >= 191 )
+ if ( yy_current_state >= 206 )
yy_c = yy_meta[(unsigned int) yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
++yy_cp;
}
- while ( yy_base[yy_current_state] != 452 );
+ while ( yy_base[yy_current_state] != 521 );
yy_find_action:
yy_act = yy_accept[yy_current_state];
@@ -1051,91 +1071,113 @@ YY_RULE_SETUP
case 19:
YY_RULE_SETUP
#line 90 "engines/director/lingo/lingo-lex.l"
-{ count(); return tINTERSECTS; }
+{ count(); return tINSTANCE; }
YY_BREAK
case 20:
YY_RULE_SETUP
#line 91 "engines/director/lingo/lingo-lex.l"
-{ count(); return tINTO; }
+{ count(); return tINTERSECTS; }
YY_BREAK
case 21:
YY_RULE_SETUP
#line 92 "engines/director/lingo/lingo-lex.l"
-{ count(); return tLOOP; }
+{ count(); return tINTO; }
YY_BREAK
case 22:
YY_RULE_SETUP
#line 93 "engines/director/lingo/lingo-lex.l"
-{ count(); return tMACRO; }
+{ count(); return tLOOP; }
YY_BREAK
case 23:
YY_RULE_SETUP
#line 94 "engines/director/lingo/lingo-lex.l"
-{ count(); return tMETHOD; }
+{ count(); return tMACRO; }
YY_BREAK
case 24:
YY_RULE_SETUP
#line 95 "engines/director/lingo/lingo-lex.l"
-{ count(); return tMOVIE; }
+{ count(); return tMETHOD; }
YY_BREAK
case 25:
YY_RULE_SETUP
#line 96 "engines/director/lingo/lingo-lex.l"
-{ count(); return tNEXT; }
+{ count(); return tMOD; }
YY_BREAK
case 26:
YY_RULE_SETUP
#line 97 "engines/director/lingo/lingo-lex.l"
-{ count(); return tNOT; }
+{ count(); return tMOVIE; }
YY_BREAK
case 27:
YY_RULE_SETUP
#line 98 "engines/director/lingo/lingo-lex.l"
-{ count(); return tOF; }
+{ count(); return tNEXT; }
YY_BREAK
case 28:
YY_RULE_SETUP
#line 99 "engines/director/lingo/lingo-lex.l"
-{ count(); return tOPEN; }
+{ count(); return tNOT; }
YY_BREAK
case 29:
YY_RULE_SETUP
#line 100 "engines/director/lingo/lingo-lex.l"
-{ count(); return tOR; }
+{ count(); return tOF; }
YY_BREAK
case 30:
YY_RULE_SETUP
#line 101 "engines/director/lingo/lingo-lex.l"
-{ count(); return tPLAY; }
+{ count(); return tOPEN; }
YY_BREAK
case 31:
YY_RULE_SETUP
#line 102 "engines/director/lingo/lingo-lex.l"
-{ count(); return tPREVIOUS; }
+{ count(); return tOR; }
YY_BREAK
case 32:
YY_RULE_SETUP
#line 103 "engines/director/lingo/lingo-lex.l"
-{ count(); return tPUT; }
+{ count(); return tPLAY; }
YY_BREAK
case 33:
YY_RULE_SETUP
#line 104 "engines/director/lingo/lingo-lex.l"
-{ count(); return tREPEAT; }
+{ count(); return tPREVIOUS; }
YY_BREAK
case 34:
YY_RULE_SETUP
#line 105 "engines/director/lingo/lingo-lex.l"
-{ count(); return tSET; }
+{ count(); return tPUT; }
YY_BREAK
case 35:
YY_RULE_SETUP
#line 106 "engines/director/lingo/lingo-lex.l"
-{ count(); return tSTARTS; }
+{ count(); return tREPEAT; }
YY_BREAK
case 36:
YY_RULE_SETUP
#line 107 "engines/director/lingo/lingo-lex.l"
+{ count(); return tSET; }
+ YY_BREAK
+case 37:
+YY_RULE_SETUP
+#line 108 "engines/director/lingo/lingo-lex.l"
+{ count(); return tSTARTS; }
+ YY_BREAK
+case 38:
+YY_RULE_SETUP
+#line 109 "engines/director/lingo/lingo-lex.l"
+{
+ count();
+
+ yylval.e[0] = g_lingo->_theEntities["sqrt"]->entity;
+ yylval.e[1] = 0; // No field
+
+ return THEENTITYWITHID;
+ }
+ YY_BREAK
+case 39:
+YY_RULE_SETUP
+#line 117 "engines/director/lingo/lingo-lex.l"
{
count();
@@ -1177,9 +1219,9 @@ YY_RULE_SETUP
warning("Unhandled the entity %s", ptr);
}
YY_BREAK
-case 37:
+case 40:
YY_RULE_SETUP
-#line 147 "engines/director/lingo/lingo-lex.l"
+#line 157 "engines/director/lingo/lingo-lex.l"
{
count();
@@ -1200,64 +1242,64 @@ YY_RULE_SETUP
warning("Unhandled the entity %s", ptr);
}
YY_BREAK
-case 38:
+case 41:
YY_RULE_SETUP
-#line 166 "engines/director/lingo/lingo-lex.l"
+#line 176 "engines/director/lingo/lingo-lex.l"
{ count(); return tTHEN; }
YY_BREAK
-case 39:
+case 42:
YY_RULE_SETUP
-#line 167 "engines/director/lingo/lingo-lex.l"
+#line 177 "engines/director/lingo/lingo-lex.l"
{ count(); return tTO; }
YY_BREAK
-case 40:
+case 43:
YY_RULE_SETUP
-#line 168 "engines/director/lingo/lingo-lex.l"
+#line 178 "engines/director/lingo/lingo-lex.l"
{ count(); return tSPRITE; }
YY_BREAK
-case 41:
+case 44:
YY_RULE_SETUP
-#line 169 "engines/director/lingo/lingo-lex.l"
+#line 179 "engines/director/lingo/lingo-lex.l"
{ count(); return tWITH; }
YY_BREAK
-case 42:
+case 45:
YY_RULE_SETUP
-#line 170 "engines/director/lingo/lingo-lex.l"
+#line 180 "engines/director/lingo/lingo-lex.l"
{ count(); return tWITHIN; }
YY_BREAK
-case 43:
+case 46:
YY_RULE_SETUP
-#line 171 "engines/director/lingo/lingo-lex.l"
+#line 181 "engines/director/lingo/lingo-lex.l"
{ count(); return tWHEN; }
YY_BREAK
-case 44:
+case 47:
YY_RULE_SETUP
-#line 172 "engines/director/lingo/lingo-lex.l"
+#line 182 "engines/director/lingo/lingo-lex.l"
{ count(); return tWHILE; }
YY_BREAK
-case 45:
+case 48:
YY_RULE_SETUP
-#line 174 "engines/director/lingo/lingo-lex.l"
+#line 184 "engines/director/lingo/lingo-lex.l"
{ count(); return tNEQ; }
YY_BREAK
-case 46:
+case 49:
YY_RULE_SETUP
-#line 175 "engines/director/lingo/lingo-lex.l"
+#line 185 "engines/director/lingo/lingo-lex.l"
{ count(); return tGE; }
YY_BREAK
-case 47:
+case 50:
YY_RULE_SETUP
-#line 176 "engines/director/lingo/lingo-lex.l"
+#line 186 "engines/director/lingo/lingo-lex.l"
{ count(); return tLE; }
YY_BREAK
-case 48:
+case 51:
YY_RULE_SETUP
-#line 177 "engines/director/lingo/lingo-lex.l"
+#line 187 "engines/director/lingo/lingo-lex.l"
{ count(); return tCONCAT; }
YY_BREAK
-case 49:
+case 52:
YY_RULE_SETUP
-#line 179 "engines/director/lingo/lingo-lex.l"
+#line 189 "engines/director/lingo/lingo-lex.l"
{
count();
yylval.s = new Common::String(yytext);
@@ -1285,43 +1327,43 @@ YY_RULE_SETUP
return ID;
}
YY_BREAK
-case 50:
+case 53:
YY_RULE_SETUP
-#line 205 "engines/director/lingo/lingo-lex.l"
+#line 215 "engines/director/lingo/lingo-lex.l"
{ count(); yylval.f = atof(yytext); return FLOAT; }
YY_BREAK
-case 51:
+case 54:
YY_RULE_SETUP
-#line 206 "engines/director/lingo/lingo-lex.l"
+#line 216 "engines/director/lingo/lingo-lex.l"
{ count(); yylval.i = strtol(yytext, NULL, 10); return INT; }
YY_BREAK
-case 52:
+case 55:
YY_RULE_SETUP
-#line 207 "engines/director/lingo/lingo-lex.l"
+#line 217 "engines/director/lingo/lingo-lex.l"
{ count(); return *yytext; }
YY_BREAK
-case 53:
-/* rule 53 can match eol */
+case 56:
+/* rule 56 can match eol */
YY_RULE_SETUP
-#line 208 "engines/director/lingo/lingo-lex.l"
+#line 218 "engines/director/lingo/lingo-lex.l"
{ return '\n'; }
YY_BREAK
-case 54:
+case 57:
YY_RULE_SETUP
-#line 209 "engines/director/lingo/lingo-lex.l"
+#line 219 "engines/director/lingo/lingo-lex.l"
{ count(); yylval.s = new Common::String(&yytext[1]); yylval.s->deleteLastChar(); return STRING; }
YY_BREAK
-case 55:
+case 58:
YY_RULE_SETUP
-#line 210 "engines/director/lingo/lingo-lex.l"
+#line 220 "engines/director/lingo/lingo-lex.l"
YY_BREAK
-case 56:
+case 59:
YY_RULE_SETUP
-#line 212 "engines/director/lingo/lingo-lex.l"
+#line 222 "engines/director/lingo/lingo-lex.l"
ECHO;
YY_BREAK
-#line 1325 "engines/director/lingo/lingo-lex.cpp"
+#line 1367 "engines/director/lingo/lingo-lex.cpp"
case YY_STATE_EOF(INITIAL):
yyterminate();
@@ -1614,7 +1656,7 @@ static int yy_get_next_buffer (void)
while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
{
yy_current_state = (int) yy_def[yy_current_state];
- if ( yy_current_state >= 191 )
+ if ( yy_current_state >= 206 )
yy_c = yy_meta[(unsigned int) yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
@@ -1642,11 +1684,11 @@ static int yy_get_next_buffer (void)
while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
{
yy_current_state = (int) yy_def[yy_current_state];
- if ( yy_current_state >= 191 )
+ if ( yy_current_state >= 206 )
yy_c = yy_meta[(unsigned int) yy_c];
}
yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
- yy_is_jam = (yy_current_state == 190);
+ yy_is_jam = (yy_current_state == 205);
return yy_is_jam ? 0 : yy_current_state;
}
@@ -2321,7 +2363,7 @@ void yyfree (void * ptr )
#define YYTABLES_NAME "yytables"
-#line 212 "engines/director/lingo/lingo-lex.l"
+#line 222 "engines/director/lingo/lingo-lex.l"
diff --git a/engines/director/lingo/lingo-lex.l b/engines/director/lingo/lingo-lex.l
index 8c89a99764..c2a5b19fb9 100644
--- a/engines/director/lingo/lingo-lex.l
+++ b/engines/director/lingo/lingo-lex.l
@@ -87,11 +87,13 @@ whitespace [\t ]
(?i:global) { count(); return tGLOBAL; }
(?i:go[\t ]+to) { count(); return tGO; }
(?i:go) { count(); return tGO; }
+(?i:instance) { count(); return tINSTANCE; }
(?i:intersects) { count(); return tINTERSECTS; }
(?i:into) { count(); return tINTO; }
(?i:loop) { count(); return tLOOP; }
(?i:macro) { count(); return tMACRO; }
(?i:method) { count(); return tMETHOD; }
+(?i:mod) { count(); return tMOD; }
(?i:movie) { count(); return tMOVIE; }
(?i:next) { count(); return tNEXT; }
(?i:not) { count(); return tNOT; }
@@ -104,6 +106,14 @@ whitespace [\t ]
(?i:repeat) { count(); return tREPEAT; }
(?i:set) { count(); return tSET; }
(?i:starts) { count(); return tSTARTS; }
+(?i:the[ \t]+sqrt[\t ]+of[\t ]+) {
+ count();
+
+ yylval.e[0] = g_lingo->_theEntities["sqrt"]->entity;
+ yylval.e[1] = 0; // No field
+
+ return THEENTITYWITHID;
+ }
(?i:the[ \t]+[[:alpha:]]+[\t ]+of[\t ]+[[:alpha:]]+) {
count();
diff --git a/engines/director/lingo/lingo-the.cpp b/engines/director/lingo/lingo-the.cpp
index 3f4f9cc432..9751d06900 100644
--- a/engines/director/lingo/lingo-the.cpp
+++ b/engines/director/lingo/lingo-the.cpp
@@ -20,88 +20,123 @@
*
*/
-#include "engines/director/lingo/lingo.h"
+#include "director/lingo/lingo.h"
+#include "director/sprite.h"
namespace Director {
class Sprite;
TheEntity entities[] = {
+ { kTheBeepOn, "beepOn", false }, // D2 property
+ { kTheButtonStyle, "buttonStyle", false }, // D2 p
{ kTheCast, "cast", true },
- { kTheClickOn, "clickOn", false },
- { kTheColorDepth, "colorDepth", false },
- { kTheColorQD, "colorQD", false },
- { kTheCommandDown, "commandDown", false },
- { kTheControlDown, "controlDown", false },
- { kTheDoubleClick, "doubleClick", false },
- { kTheExitLock, "exitlock", false },
+ { kTheCenterStage, "centerStage", false }, // D2 p
+ { kTheCheckBoxAccess, "checkBoxAccess", false }, // D2 p
+ { kTheCheckBoxType, "checkBoxType", false }, // D2 p
+ { kTheClickOn, "clickOn", false }, // D2 function
+ { kTheColorDepth, "colorDepth", false }, // D2 p
+ { kTheColorQD, "colorQD", false }, // D2 f
+ { kTheCommandDown, "commandDown", false }, // D2 f
+ { kTheControlDown, "controlDown", false }, // D2 f
+ { kTheDoubleClick, "doubleClick", false }, // D2 f
+ { kTheExitLock, "exitLock", false }, // D2 p
+ { kTheFixStageSize, "fixStageSize", false }, // D2 p
{ kTheFloatPrecision, "floatPrecision", false },
- { kTheFrame, "frame", false },
+ { kTheFrame, "frame", false }, // D2 f
+ { kTheFreeBlock, "freeBlock", false }, // D2 f
+ { kTheFreeBytes, "freeBytes", false }, // D2 f
+ { kTheFullColorPermit, "fullColorPermit", false }, // D2 p
+ { kTheImageDirect, "imageDirect", false }, // D2 p
{ kTheItemDelimiter, "itemDelimiter", false },
- { kTheKey, "key", false },
- { kTheKeyCode, "keycode", false },
- { kTheLastClick, "lastClick", false },
- { kTheLastEvent, "lastEvent", false },
+ { kTheKey, "key", false }, // D2 f
+ { kTheKeyCode, "keyCode", false }, // D2 f
+ { kTheKeyDownScript, "keyDownScript", false }, // D2 p
+ { kTheLastClick, "lastClick", false }, // D2 f
+ { kTheLastEvent, "lastEvent", false }, // D2 f
{ kTheLastFrame, "lastFrame", false },
+ { kTheLastKey, "lastKey", false }, // D2 f
+ { kTheLastRoll, "lastRoll", false }, // D2 f
+ { kTheMachineType, "machineType", false }, // D2 f
+ { kTheMemorySize, "memorySize", false }, // D2 f
{ kTheMenu, "menu", true },
{ kTheMenus, "menus", false },
{ kTheMenuItem, "menuitem", true },
{ kTheMenuItems, "menuitems", false },
- { kTheMouseDown, "mouseDown", false },
- { kTheMouseDownScript, "mouseDownScript", false },
- { kTheMouseH, "mouseh", false },
- { kTheMouseUp, "mouseUp", false },
- { kTheMouseUpScript, "mouseUpScript", false },
- { kTheMouseV, "mousev", false },
- { kTheMovie, "movie", false },
+ { kTheMouseDown, "mouseDown", false }, // D2 f
+ { kTheMouseDownScript, "mouseDownScript", false }, // D2 p
+ { kTheMouseH, "mouseH", false }, // D2 f
+ { kTheMouseUp, "mouseUp", false }, // D2 f
+ { kTheMouseUpScript, "mouseUpScript", false }, // D2 p
+ { kTheMouseV, "mouseV", false }, // D2 f
+ { kTheMovie, "movie", false }, // D2 f
{ kTheMultiSound, "multiSound", false },
- { kTheOptionDown, "optionDown", false },
- { kThePathName, "pathname", false },
- { kThePerFrameHook, "perframehook", false },
+ { kTheOptionDown, "optionDown", false }, // D2 f
+ { kThePathName, "pathName", false }, // D2 f
+ { kThePauseState, "pauseState", false }, // D2 f
+ { kThePerFrameHook, "perFrameHook", false }, // D2 p
{ kThePreloadEventAbort,"preloadEventAbort",false },
+ { kTheResult, "result", false }, // D2 f
{ kTheRightMouseDown, "rightMouseDown", false },
{ kTheRightMouseUp, "rightMouseUp", false },
{ kTheRomanLingo, "romanLingo", false },
- { kTheShiftDown, "shiftDown", false },
+ { kTheSelection, "selection", false }, // D2 f
+ { kTheShiftDown, "shiftDown", false }, // D2 f
+ { kTheSoundEnabled, "soundEnabled", false }, // D2 p
+ { kTheSoundLevel, "soundLevel", false }, // D2 p
{ kTheSprite, "sprite", true },
+ { kTheSqrt, "sqrt", false }, // D2 f
{ kTheStage, "stage", false },
- { kTheStillDown, "stillDown", false },
- { kTheTicks, "ticks", false },
- { kTheTimeoutLength, "timeoutlength", false },
- { kTheTimer, "timer", false },
+ { kTheStageBottom, "stageBottom", false }, // D2 f
+ { kTheStageLeft, "stageLeft", false }, // D2 f
+ { kTheStageRight, "stageRight", false }, // D2 f
+ { kTheStageTop, "stageTop", false }, // D2 f
+ { kTheStillDown, "stillDown", false }, // D2 f
+ { kTheSwitchColorDepth, "switchColorDepth", false }, // D2 p
+ { kTheTicks, "ticks", false }, // D2 f
+ { kTheTimeoutKeydown, "timeoutKeydown", false }, // D2 p
+ { kTheTimeoutLapsed, "timeoutLapsed", false }, // D2 p
+ { kTheTimeoutLength, "timeoutLength", false }, // D2 p
+ { kTheTimeoutMouse, "timeoutMouse", false }, // D2 p
+ { kTheTimeoutPlay, "timeoutPlay", false }, // D2 p
+ { kTheTimeoutScript, "timeoutScript", false }, // D2 p
+ { kTheTimer, "timer", false }, // D2 p
{ kTheWindow, "window", false },
{ kTheNOEntity, NULL, false }
};
TheEntityField fields[] = {
- { kTheSprite, "backColor", kTheBackColor },
+ { kTheSprite, "backColor", kTheBackColor }, // D2 p
{ kTheSprite, "blend", kTheBlend },
- { kTheSprite, "bottom", kTheBottom },
- { kTheSprite, "castnum", kTheCastNum },
- { kTheSprite, "constraint", kTheConstraint },
- { kTheSprite, "cursor", kTheCursor },
+ { kTheSprite, "bottom", kTheBottom }, // D2 p
+ { kTheSprite, "castNum", kTheCastNum }, // D2 p
+ { kTheSprite, "constraint", kTheConstraint }, // D2 p
+ { kTheSprite, "cursor", kTheCursor }, // D2 p
{ kTheSprite, "editableText", kTheEditableText },
- { kTheSprite, "foreColor", kTheForeColor },
- { kTheSprite, "height", kTheHeight },
- { kTheSprite, "ink", kTheInk },
- { kTheSprite, "left", kTheLeft },
- { kTheSprite, "lineSize", kTheLineSize },
- { kTheSprite, "loch", kTheLocH },
- { kTheSprite, "locv", kTheLocV },
+ { kTheSprite, "foreColor", kTheForeColor }, // D2 p
+ { kTheSprite, "height", kTheHeight }, // D2 p
+ { kTheSprite, "immediate", kTheImmediate }, // D2 p
+ { kTheSprite, "ink", kTheInk }, // D2 p
+ { kTheSprite, "left", kTheLeft }, // D2 p
+ { kTheSprite, "lineSize", kTheLineSize }, // D2 p
+ { kTheSprite, "locH", kTheLocH }, // D2 p
+ { kTheSprite, "locV", kTheLocV }, // D2 p
{ kTheSprite, "moveable", kTheMoveable },
{ kTheSprite, "movieRate", kTheMovieRate },
{ kTheSprite, "movieTime", kTheMovieTime },
- { kTheSprite, "right", kTheRight },
+ { kTheSprite, "pattern", kThePattern }, // D2 p
+ { kTheSprite, "puppet", kThePuppet }, // D2 p
+ { kTheSprite, "right", kTheRight }, // D2 p
{ kTheSprite, "scriptNum", kTheScriptNum },
{ kTheSprite, "startTime", kTheStartTime },
- { kTheSprite, "stretch", kTheStrech },
+ { kTheSprite, "stretch", kTheStrech }, // D2 p
{ kTheSprite, "stopTime", kTheStopTime },
- { kTheSprite, "top", kTheTop },
+ { kTheSprite, "top", kTheTop }, // D2 p
{ kTheSprite, "trails", kTheTrails },
- { kTheSprite, "type", kTheType },
+ { kTheSprite, "type", kTheType }, // D2 p
{ kTheSprite, "visible", kTheVisible },
{ kTheSprite, "volume", kTheVolume },
- { kTheSprite, "width", kTheWidth },
+ { kTheSprite, "width", kTheWidth }, // D2 p
// Common cast fields
{ kTheCast, "castType", kTheCastType },
@@ -136,9 +171,11 @@ TheEntityField fields[] = {
{ kTheCast, "picture", kThePicture },
// TextCast fields
- { kTheCast, "hilite", kTheHilite },
+ { kTheCast, "hilite", kTheHilite }, // D2 p
+ { kTheCast, "selEnd", kTheSelEnd }, // D2 p
+ { kTheCast, "selStart", kTheSelStart }, // D2 p
{ kTheCast, "size", kTheSize },
- { kTheCast, "text", kTheText },
+ { kTheCast, "text", kTheText }, // D2 p
{ kTheWindow, "drawRect", kTheDrawRect },
{ kTheWindow, "filename", kTheFilename },
@@ -188,6 +225,10 @@ void Lingo::setTheEntity(int entity, Datum &id, int field, Datum &d) {
_floatPrecisionFormat = Common::String::format("%%.%df", _floatPrecision);
warning("set to %d: %s", _floatPrecision, _floatPrecisionFormat.c_str());
break;
+ case kTheColorDepth:
+ _vm->_colorDepth = d.toInt();
+ warning("STUB: Set color depth to %d", _vm->_colorDepth);
+ break;
default:
warning("Unprocessed setting field %d of entity %d", field, entity);
}
@@ -311,6 +352,7 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
break;
case kTheCast:
d = getTheCast(id, field);
+ break;
case kThePerFrameHook:
warning("STUB: getting the perframehook");
break;
@@ -318,6 +360,23 @@ Datum Lingo::getTheEntity(int entity, Datum &id, int field) {
d.type = INT;
d.u.i = _floatPrecision;
break;
+ case kTheSqrt:
+ id.toFloat();
+ d.type = FLOAT;
+ d.u.f = sqrt(id.u.f);
+ break;
+ case kTheKeyCode:
+ d.type = INT;
+ d.u.i = _vm->_keyCode;
+ break;
+ case kTheColorQD:
+ d.type = INT;
+ d.u.i = 1;
+ break;
+ case kTheColorDepth:
+ d.type = INT;
+ d.u.i = _vm->_colorDepth;
+ break;
default:
warning("Unprocessed getting field %d of entity %d", field, entity);
d.type = VOID;
@@ -464,8 +523,8 @@ Datum Lingo::getTheCast(Datum &id1, int field) {
return d;
} else {
warning("The cast %d found", id);
- return d;
}
+
cast = _vm->_currentScore->_casts[id];
castInfo = _vm->_currentScore->_castsInfo[id];
diff --git a/engines/director/lingo/lingo-the.h b/engines/director/lingo/lingo-the.h
index 5fea4ba009..f68a81d363 100644
--- a/engines/director/lingo/lingo-the.h
+++ b/engines/director/lingo/lingo-the.h
@@ -28,6 +28,8 @@ namespace Director {
enum TheEntityType {
kTheNOEntity = 0,
kTheFrame = 1,
+ kTheFreeBlock,
+ kTheFreeBytes,
kThePathName,
kTheMenu,
kTheMenuItem,
@@ -44,25 +46,50 @@ enum TheEntityType {
kThePerFrameHook,
kTheTicks,
kTheTimer,
+ kTheTimeoutKeydown,
+ kTheTimeoutLapsed,
kTheTimeoutLength,
+ kTheTimeoutMouse,
+ kTheTimeoutPlay,
+ kTheTimeoutScript,
kTheWindow,
+ kTheBeepOn,
+ kTheButtonStyle,
+ kTheCenterStage,
+ kTheCheckBoxAccess,
+ kTheCheckBoxType,
kTheClickOn,
+ kTheControlDown,
+ kTheCommandDown,
kTheDoubleClick,
+ kTheFixStageSize,
+ kTheFullColorPermit,
+ kTheImageDirect,
+ kTheKey,
+ kTheKeyDownScript,
+ kTheKeyCode,
kTheLastClick,
- kTheLastFrame,
kTheLastEvent,
+ kTheLastFrame,
+ kTheLastKey,
+ kTheLastRoll,
+ kTheMachineType,
+ kTheMemorySize,
kTheMouseDown,
kTheMouseUp,
+ kTheOptionDown,
+ kThePauseState,
kTheRightMouseUp,
kTheRightMouseDown,
+ kTheSoundEnabled,
+ kTheSoundLevel,
kTheStillDown,
- kTheKey,
- kTheKeyCode,
- kTheControlDown,
- kTheCommandDown,
+ kTheSwitchColorDepth,
+ kTheResult,
+ kTheSelection,
kTheShiftDown,
- kTheOptionDown,
+ kTheSqrt,
kTheColorDepth,
kTheColorQD,
@@ -72,7 +99,11 @@ enum TheEntityType {
kTheMultiSound,
kThePreloadEventAbort,
kTheRomanLingo,
- kTheStage
+ kTheStage,
+ kTheStageBottom,
+ kTheStageLeft,
+ kTheStageRight,
+ kTheStageTop
};
enum TheFieldType {
@@ -98,6 +129,7 @@ enum TheFieldType {
kTheFilename,
kTheHeight,
kTheHilite,
+ kTheImmediate,
kTheInk,
kTheLeft,
kTheLineSize,
@@ -110,9 +142,11 @@ enum TheFieldType {
kTheNumber,
kTheName,
kThePalette,
+ kThePattern,
kThePausedAtStart,
kThePicture,
kThePreload,
+ kThePuppet,
kThePurgePriority,
kTheRect,
kTheRegPoint,
@@ -123,6 +157,8 @@ enum TheFieldType {
kTheScript,
kTheScriptNum,
kTheScriptText,
+ kTheSelEnd,
+ kTheSelStart,
kTheSize,
kTheStrech,
kTheSound,
diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp
index 7076ac2bc3..30714deec1 100644
--- a/engines/director/lingo/lingo.cpp
+++ b/engines/director/lingo/lingo.cpp
@@ -20,6 +20,10 @@
*
*/
+#include "common/archive.h"
+#include "common/file.h"
+#include "common/str-array.h"
+
#include "director/lingo/lingo.h"
#include "director/lingo/lingo-gr.h"
@@ -54,9 +58,9 @@ struct EventHandlerType {
{ kEventStart, "start" },
{ kEventKeyUp, "keyUp" },
- { kEventKeyDown, "keyDown" },
- { kEventMouseUp, "mouseUp" },
- { kEventMouseDown, "mouseDown" },
+ { kEventKeyDown, "keyDown" }, // D2 as when
+ { kEventMouseUp, "mouseUp" }, // D2 as when
+ { kEventMouseDown, "mouseDown" }, // D2 as when
{ kEventRightMouseDown, "rightMouseDown" },
{ kEventRightMouseUp, "rightMouseUp" },
{ kEventMouseEnter, "mouseEnter" },
@@ -64,6 +68,8 @@ struct EventHandlerType {
{ kEventMouseUpOutSide, "mouseUpOutSide" },
{ kEventMouseWithin, "mouseWithin" },
+ { kEventTimeout, "timeout" }, // D2 as when
+
{ kEventNone, 0 },
};
@@ -84,6 +90,7 @@ Lingo::Lingo(DirectorEngine *vm) : _vm(vm) {
_eventHandlerTypes[t->handler] = t->name;
initBuiltIns();
+ initFuncs();
initTheEntities();
_currentScript = 0;
@@ -101,6 +108,10 @@ Lingo::Lingo(DirectorEngine *vm) : _vm(vm) {
_floatPrecision = 4;
_floatPrecisionFormat = "%.4f";
+ _exitRepeat = false;
+
+ _localvars = NULL;
+
warning("Lingo Inited");
}
@@ -118,17 +129,17 @@ const char *Lingo::findNextDefinition(const char *s) {
return NULL;
if (!strncmp(res, "macro ", 6)) {
- warning("See macro");
+ debugC(3, kDebugLingoCompile, "See macro");
return res;
}
if (!strncmp(res, "factory ", 8)) {
- warning("See factory");
+ debugC(3, kDebugLingoCompile, "See factory");
return res;
}
if (!strncmp(res, "method ", 7)) {
- warning("See method");
+ debugC(3, kDebugLingoCompile, "See method");
return res;
}
@@ -140,7 +151,7 @@ const char *Lingo::findNextDefinition(const char *s) {
}
void Lingo::addCode(const char *code, ScriptType type, uint16 id) {
- debug(2, "Add code \"%s\" for type %d with id %d", code, type, id);
+ debugC(2, kDebugLingoCompile, "Add code \"%s\" for type %d with id %d", code, type, id);
if (_scripts[type].contains(id)) {
delete _scripts[type][id];
@@ -155,6 +166,13 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) {
const char *begin, *end;
+ if (!strncmp(code, "menu:", 5)) {
+ debugC(2, kDebugLingoCompile, "Parsing menu");
+ parseMenu(code);
+
+ return;
+ }
+
// macros and factories have conflicting grammar. Thus we ease life for the parser.
if ((begin = findNextDefinition(code))) {
bool first = true;
@@ -174,10 +192,18 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) {
else
_inFactory = false;
- debug(2, "Code chunk:\n#####\n%s#####", chunk.c_str());
+ debugC(2, kDebugLingoCompile, "Code chunk:\n#####\n%s#####", chunk.c_str());
parse(chunk.c_str());
+ if (debugChannelSet(3, kDebugLingoCompile)) {
+ int pc = 0;
+ while (pc < _currentScript->size()) {
+ Common::String instr = decodeInstruction(pc, &pc);
+ debugC(3, kDebugLingoCompile, "[%5d] %s", pc, instr.c_str());
+ }
+ }
+
_currentScript->clear();
begin = end;
@@ -185,7 +211,7 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) {
_hadError = true; // HACK: This is for preventing test execution
- debug(2, "Code chunk:\n#####\n%s#####", begin);
+ debugC(2, kDebugLingoCompile, "Code chunk:\n#####\n%s#####", begin);
parse(begin);
} else {
parse(code);
@@ -195,8 +221,16 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) {
_inFactory = false;
- if (_currentScript->size() && !_hadError)
- Common::hexdump((byte *)&_currentScript->front(), _currentScript->size() * sizeof(inst));
+ if (debugChannelSet(3, kDebugLingoCompile)) {
+ if (_currentScript->size() && !_hadError)
+ Common::hexdump((byte *)&_currentScript->front(), _currentScript->size() * sizeof(inst));
+
+ int pc = 0;
+ while (pc < _currentScript->size()) {
+ Common::String instr = decodeInstruction(pc, &pc);
+ debugC(3, kDebugLingoCompile, "[%5d] %s", pc, instr.c_str());
+ }
+ }
}
void Lingo::executeScript(ScriptType type, uint16 id) {
@@ -205,7 +239,7 @@ void Lingo::executeScript(ScriptType type, uint16 id) {
return;
}
- debug(2, "Executing script type: %d, id: %d", type, id);
+ debugC(2, kDebugLingoExec, "Executing script type: %d, id: %d", type, id);
_currentScript = _scripts[type][id];
_pc = 0;
@@ -218,11 +252,36 @@ void Lingo::executeScript(ScriptType type, uint16 id) {
cleanLocalVars();
}
+ScriptType Lingo::event2script(LEvent ev) {
+ if (_vm->getVersion() < 4) {
+ switch (ev) {
+ //case kEventStartMovie: // We are precompiling it now
+ // return kMovieScript;
+ case kEventEnterFrame:
+ return kFrameScript;
+ default:
+ return kNoneScript;
+ }
+ }
+
+ return kNoneScript;
+}
+
void Lingo::processEvent(LEvent event, int entityId) {
if (!_eventHandlerTypes.contains(event))
error("processEvent: Unknown event %d for entity %d", event, entityId);
- debug(2, "processEvent(%s) for %d", _eventHandlerTypes[event], entityId);
+ ScriptType st = event2script(event);
+
+ if (st != kNoneScript) {
+ executeScript(st, entityId + 1);
+ } else if (_handlers.contains(_eventHandlerTypes[event])) {
+ call(_eventHandlerTypes[event], 0);
+ pop();
+ } else {
+ warning("---- Handler %s is not set", _eventHandlerTypes[event]);
+ debugC(8, kDebugLingoExec, "STUB: processEvent(%s) for %d", _eventHandlerTypes[event], entityId);
+ }
}
int Lingo::alignTypes(Datum &d1, Datum &d2) {
@@ -282,6 +341,15 @@ Common::String *Datum::toString() {
delete s;
s = u.s;
break;
+ case OBJECT:
+ *s = Common::String::format("#%s", u.s->c_str());
+ break;
+ case VOID:
+ *s = "#void";
+ break;
+ case VAR:
+ *s = Common::String::format("var: #%s", u.sym->name);
+ break;
default:
warning("Incorrect operation toString() for type: %s", type2str());
}
@@ -310,6 +378,10 @@ const char *Datum::type2str(bool isk) {
return isk ? "#point" : "POINT";
case SYMBOL:
return isk ? "#symbol" : "SYMBOL";
+ case OBJECT:
+ return isk ? "#object" : "OBJECT";
+ case VAR:
+ return isk ? "#var" : "VAR";
default:
snprintf(res, 20, "-- (%d) --", type);
return res;
@@ -358,16 +430,25 @@ Common::String *Lingo::toLowercaseMac(Common::String *s) {
return res;
}
+void Lingo::parseMenu(const char *code) {
+ warning("STUB: parseMenu");
+}
+
void Lingo::runTests() {
Common::File inFile;
- Common::ArchiveMemberList fileList;
- SearchMan.listMatchingMembers(fileList, "*.lingo");
+ Common::ArchiveMemberList fsList;
+ SearchMan.listMatchingMembers(fsList, "*.lingo");
+ Common::StringArray fileList;
int counter = 1;
- for (Common::ArchiveMemberList::iterator it = fileList.begin(); it != fileList.end(); ++it) {
- Common::ArchiveMember const &m = **it;
- Common::SeekableReadStream *const stream = m.createReadStream();
+ for (Common::ArchiveMemberList::iterator it = fsList.begin(); it != fsList.end(); ++it)
+ fileList.push_back((*it)->getName());
+
+ Common::sort(fileList.begin(), fileList.end());
+
+ for (uint i = 0; i < fileList.size(); i++) {
+ Common::SeekableReadStream *const stream = SearchMan.createReadStreamForMember(fileList[i]);
if (stream) {
uint size = stream->size();
@@ -375,7 +456,7 @@ void Lingo::runTests() {
stream->read(script, size);
- warning("Compiling file %s of size %d, id: %d", m.getName().c_str(), size, counter);
+ debugC(2, kDebugLingoCompile, "Compiling file %s of size %d, id: %d", fileList[i].c_str(), size, counter);
_hadError = false;
addCode(script, kMovieScript, counter);
@@ -383,7 +464,7 @@ void Lingo::runTests() {
if (!_hadError)
executeScript(kMovieScript, counter);
else
- warning("Skipping execution");
+ debugC(2, kDebugLingoCompile, "Skipping execution");
free(script);
diff --git a/engines/director/lingo/lingo.h b/engines/director/lingo/lingo.h
index a42b796014..05c73f9886 100644
--- a/engines/director/lingo/lingo.h
+++ b/engines/director/lingo/lingo.h
@@ -23,13 +23,11 @@
#ifndef DIRECTOR_LINGO_LINGO_H
#define DIRECTOR_LINGO_LINGO_H
-#include "common/debug.h"
-#include "common/hashmap.h"
-#include "common/hash-str.h"
#include "audio/audiostream.h"
-#include "common/str.h"
-#include "engines/director/director.h"
-#include "engines/director/score.h"
+#include "common/hash-str.h"
+
+#include "director/director.h"
+#include "director/score.h"
#include "director/lingo/lingo-gr.h"
#include "director/lingo/lingo-the.h"
@@ -50,6 +48,7 @@ enum LEvent {
kEventIdle,
kEventStepFrame,
kEventExitFrame,
+ kEventTimeout,
kEventActivateWindow,
kEventDeactivateWindow,
@@ -78,6 +77,30 @@ typedef void (*inst)(void);
typedef Common::Array<inst> ScriptData;
typedef Common::Array<double> FloatArray;
+struct FuncDesc {
+ Common::String name;
+ const char *proto;
+
+ FuncDesc(Common::String n, const char *p) { name = n; proto = p; }
+};
+
+struct Pointer_EqualTo {
+ bool operator()(const void *x, const void *y) const { return x == y; }
+};
+
+struct Pointer_Hash {
+ uint operator()(const void *x) const {
+#ifdef SCUMM_64BITS
+ uint64 v = (uint64)x;
+ return (v >> 32) ^ (v & 0xffffffff);
+#else
+ return (uint)x;
+#endif
+ }
+};
+
+typedef Common::HashMap<void *, FuncDesc *, Pointer_Hash, Pointer_EqualTo> FuncHash;
+
struct Symbol { /* symbol table entry */
char *name;
int type;
@@ -111,6 +134,9 @@ struct Datum { /* interpreter stack type */
} u;
Datum() { u.sym = NULL; type = VOID; }
+ Datum(int val) { u.i = val; type = INT; }
+ Datum(double val) { u.f = val; type = FLOAT; }
+ Datum(Common::String *val) { u.s = val; type = STRING; }
double toFloat();
int toInt();
@@ -148,10 +174,15 @@ public:
void addCode(const char *code, ScriptType type, uint16 id);
void executeScript(ScriptType type, uint16 id);
+ void printStack(const char *s);
+ Common::String decodeInstruction(int pc, int *newPC = NULL);
+
+ ScriptType event2script(LEvent ev);
void processEvent(LEvent event, int entityId);
void initBuiltIns();
+ void initFuncs();
void initTheEntities();
Common::String *toLowercaseMac(Common::String *s);
@@ -167,7 +198,7 @@ public:
void popContext();
Symbol *lookupVar(const char *name, bool create = true, bool putInGlobalList = false);
void cleanLocalVars();
- void define(Common::String &s, int start, int nargs, Common::String *prefix = NULL);
+ void define(Common::String &s, int start, int nargs, Common::String *prefix = NULL, int end = -1);
void processIf(int elselabel, int endlabel);
int alignTypes(Datum &d1, Datum &d2);
@@ -202,6 +233,7 @@ public:
static void c_sub();
static void c_mul();
static void c_div();
+ static void c_mod();
static void c_negate();
static void c_and();
@@ -233,6 +265,8 @@ public:
static void c_repeatwhilecode();
static void c_repeatwithcode();
static void c_ifcode();
+ static void c_whencode();
+ static void c_exitRepeat();
static void c_eq();
static void c_neq();
static void c_gt();
@@ -240,6 +274,9 @@ public:
static void c_ge();
static void c_le();
static void c_call();
+
+ void call(Common::String name, int nargs);
+
static void c_procret();
static void c_mci();
@@ -249,6 +286,7 @@ public:
static void c_gotonext();
static void c_gotoprevious();
static void c_global();
+ static void c_instance();
static void c_play();
static void c_playdone();
@@ -258,41 +296,56 @@ public:
void printStubWithArglist(const char *funcname, int nargs);
void convertVOIDtoString(int arg, int nargs);
void dropStack(int nargs);
+ void drop(int num);
static void b_abs(int nargs);
static void b_atan(int nargs);
- static void b_chars(int nargs);
static void b_cos(int nargs);
static void b_exp(int nargs);
static void b_float(int nargs);
static void b_integer(int nargs);
- static void b_length(int nargs);
+ static void b_integerp(int nargs);
static void b_log(int nargs);
static void b_pi(int nargs);
static void b_power(int nargs);
static void b_random(int nargs);
static void b_sin(int nargs);
static void b_sqrt(int nargs);
- static void b_string(int nargs);
static void b_tan(int nargs);
+ static void b_chars(int nargs);
+ static void b_charToNum(int nargs);
+ static void b_length(int nargs);
+ static void b_numToChar(int nargs);
+ static void b_offset(int nargs);
+ static void b_string(int nargs);
+ static void b_stringp(int nargs);
+
static void b_ilk(int nargs);
static void b_alert(int nargs);
static void b_cursor(int nargs);
+ static void b_objectp(int nargs);
static void b_printFrom(int nargs);
static void b_showGlobals(int nargs);
static void b_showLocals(int nargs);
+ static void b_symbolp(int nargs);
+ static void b_value(int nargs);
+ static void b_constrainH(int nargs);
+ static void b_constrainV(int nargs);
static void b_editableText(int nargs);
static void b_installMenu(int nargs);
- static void b_updateStage(int nargs);
+ static void b_label(int nargs);
+ static void b_marker(int nargs);
static void b_moveableSprite(int nargs);
static void b_puppetPalette(int nargs);
static void b_puppetSound(int nargs);
static void b_puppetSprite(int nargs);
static void b_puppetTempo(int nargs);
static void b_puppetTransition(int nargs);
+ static void b_rollOver(int nargs);
static void b_spriteBox(int nargs);
+ static void b_updateStage(int nargs);
static void b_zoomBox(int nargs);
static void b_continue(int nargs);
@@ -322,6 +375,18 @@ public:
static void b_mci(int nargs);
static void b_mciwait(int nargs);
+ static void b_backspace(int nargs);
+ static void b_empty(int nargs);
+ static void b_enter(int nargs);
+ static void b_false(int nargs);
+ static void b_quote(int nargs);
+ static void b_return(int nargs);
+ static void b_tab(int nargs);
+ static void b_true(int nargs);
+
+ static void b_factory(int nargs);
+ void factoryCall(Common::String &name, int nargs);
+
void func_mci(Common::String &s);
void func_mciwait(Common::String &s);
void func_goto(Datum &frame, Datum &movie);
@@ -361,8 +426,12 @@ public:
bool _inFactory;
Common::String _currentFactory;
+ bool _exitRepeat;
+
private:
int parse(const char *code);
+ void parseMenu(const char *code);
+
void push(Datum d);
Datum pop(void);
@@ -374,6 +443,8 @@ private:
SymbolHash _globalvars;
SymbolHash *_localvars;
+ FuncHash _functions;
+
int _pc;
StackData _stack;
diff --git a/engines/director/lingo/tests/factory2.lingo b/engines/director/lingo/tests/factory2.lingo
new file mode 100644
index 0000000000..a7b2317e17
--- /dev/null
+++ b/engines/director/lingo/tests/factory2.lingo
@@ -0,0 +1,4 @@
+global aim1
+AimGun2
+
+aim1(mDispose)
diff --git a/engines/director/lingo/tests/math.lingo b/engines/director/lingo/tests/math.lingo
index 6f8ecc374f..f38b061b6a 100644
--- a/engines/director/lingo/tests/math.lingo
+++ b/engines/director/lingo/tests/math.lingo
@@ -20,3 +20,5 @@ updatestage
put (1024/4096)*100 -- 0
put (1024/4096)*100.0 -- 0.0
put ((1024*1.0)/4096)*100.0 -- 25.0
+
+put the sqrt of 9
diff --git a/engines/director/module.mk b/engines/director/module.mk
index 2499528304..1ea361590a 100644
--- a/engines/director/module.mk
+++ b/engines/director/module.mk
@@ -1,13 +1,16 @@
MODULE := engines/director
MODULE_OBJS = \
+ archive.o \
detection.o \
- dib.o \
director.o \
+ frame.o \
+ images.o \
movie.o \
resource.o \
score.o \
sound.o \
+ sprite.o \
lingo/lingo-gr.o \
lingo/lingo.o \
lingo/lingo-builtins.o \
diff --git a/engines/director/movie.cpp b/engines/director/movie.cpp
index 3c34e2d432..fef2b57ff3 100644
--- a/engines/director/movie.cpp
+++ b/engines/director/movie.cpp
@@ -21,11 +21,12 @@
*
*/
+#include "common/system.h"
#include "video/qt_decoder.h"
+
#include "director/movie.h"
#include "director/score.h"
-#include "common/debug.h"
-#include "common/system.h"
+
namespace Director {
Movie::Movie(Common::String fileName, DirectorEngine *vm) {
diff --git a/engines/director/movie.h b/engines/director/movie.h
index e26d10a7c7..84bc116134 100644
--- a/engines/director/movie.h
+++ b/engines/director/movie.h
@@ -24,9 +24,8 @@
#ifndef DIRECTOR_MOVIE_H
#define DIRECTOR_MOVIE_H
-#include "common/str.h"
-#include "common/rect.h"
#include "graphics/managed_surface.h"
+
#include "director/director.h"
namespace Video {
diff --git a/engines/director/resource.cpp b/engines/director/resource.cpp
index fdb0712cb9..7bb73289dd 100644
--- a/engines/director/resource.cpp
+++ b/engines/director/resource.cpp
@@ -24,9 +24,6 @@
#include "common/debug.h"
#include "common/macresman.h"
-#include "common/substream.h"
-#include "common/util.h"
-#include "common/textconsole.h"
namespace Director {
@@ -54,12 +51,18 @@ bool Archive::openFile(const Common::String &fileName) {
return false;
}
+ _fileName = fileName;
+
return true;
}
void Archive::close() {
_types.clear();
- delete _stream; _stream = 0;
+
+ if (_stream)
+ delete _stream;
+
+ _stream = 0;
}
bool Archive::hasResource(uint32 tag, uint16 id) const {
@@ -189,6 +192,12 @@ bool MacArchive::openFile(const Common::String &fileName) {
return false;
}
+ _fileName = _resFork->getBaseFileName();
+ if (_fileName.hasSuffix(".bin")) {
+ for (int i = 0; i < 4; i++)
+ _fileName.deleteLastChar();
+ }
+
Common::MacResTagArray tagArray = _resFork->getResTagArray();
for (uint32 i = 0; i < tagArray.size(); i++) {
diff --git a/engines/director/resource.h b/engines/director/resource.h
index fda8b79d82..8e2ceaeaa5 100644
--- a/engines/director/resource.h
+++ b/engines/director/resource.h
@@ -23,12 +23,7 @@
#ifndef DIRECTOR_RESOURCE_H
#define DIRECTOR_RESOURCE_H
-#include "common/scummsys.h"
-#include "common/endian.h"
-#include "common/func.h"
-#include "common/hashmap.h"
#include "common/file.h"
-#include "common/str.h"
#include "common/substream.h"
namespace Common {
@@ -48,6 +43,8 @@ public:
virtual bool openStream(Common::SeekableReadStream *stream, uint32 offset = 0) = 0;
virtual void close();
+ Common::String getFileName() const { return _fileName; }
+
bool isOpen() const { return _stream != 0; }
bool hasResource(uint32 tag, uint16 id) const;
@@ -72,6 +69,8 @@ protected:
typedef Common::HashMap<uint16, Resource> ResourceMap;
typedef Common::HashMap<uint32, ResourceMap> TypeMap;
TypeMap _types;
+
+ Common::String _fileName;
};
class MacArchive : public Archive {
diff --git a/engines/director/score.cpp b/engines/director/score.cpp
index 6587270641..49ec050dc1 100644
--- a/engines/director/score.cpp
+++ b/engines/director/score.cpp
@@ -20,28 +20,20 @@
*
*/
-#include "director/score.h"
-#include "common/stream.h"
-#include "common/debug.h"
-#include "common/file.h"
-#include "common/archive.h"
+#include "common/system.h"
#include "common/config-manager.h"
-#include "common/unzip.h"
+#include "common/events.h"
-#include "common/system.h"
-#include "director/dib.h"
+#include "engines/util.h"
+#include "graphics/font.h"
+#include "graphics/palette.h"
+
+#include "director/score.h"
+#include "director/frame.h"
#include "director/resource.h"
-#include "director/lingo/lingo.h"
#include "director/sound.h"
-
-#include "graphics/palette.h"
-#include "common/events.h"
-#include "engines/util.h"
-#include "graphics/managed_surface.h"
-#include "graphics/macgui/macwindowmanager.h"
-#include "image/bmp.h"
-#include "graphics/fontman.h"
-#include "graphics/fonts/bdf.h"
+#include "director/sprite.h"
+#include "director/lingo/lingo.h"
namespace Director {
@@ -95,23 +87,35 @@ static byte defaultPalette[768] = {
204, 51, 255, 204, 102, 255, 204, 153, 255, 204, 204, 255, 204, 255, 255, 255,
0, 255, 255, 51, 255, 255, 102, 255, 255, 153, 255, 255, 204, 255, 255, 255 };
-Score::Score(DirectorEngine *vm) {
+Score::Score(DirectorEngine *vm, Archive *archive) {
_vm = vm;
_surface = new Graphics::ManagedSurface;
_trailSurface = new Graphics::ManagedSurface;
- _movieArchive = _vm->getMainArchive();
+ _movieArchive = archive;
_lingo = _vm->getLingo();
_soundManager = _vm->getSoundManager();
_lingo->processEvent(kEventPrepareMovie, 0);
_movieScriptCount = 0;
_labels = NULL;
+ _font = NULL;
+
+ _versionMinor = _versionMajor = 0;
+ _currentFrameRate = 20;
+ _castArrayStart = _castArrayEnd = 0;
+ _currentFrame = 0;
+ _nextFrameTime = 0;
+ _flags = 0;
+ _stopPlay = false;
+ _stageColor = 0;
- if (_movieArchive->hasResource(MKTAG('M','C','N','M'), 0)) {
- _macName = _movieArchive->getName(MKTAG('M','C','N','M'), 0).c_str();
+ if (archive->hasResource(MKTAG('M','C','N','M'), 0)) {
+ _macName = archive->getName(MKTAG('M','C','N','M'), 0).c_str();
+ } else {
+ _macName = archive->getFileName();
}
- if (_movieArchive->hasResource(MKTAG('V','W','L','B'), 1024)) {
- loadLabels(*_movieArchive->getResource(MKTAG('V','W','L','B'), 1024));
+ if (archive->hasResource(MKTAG('V','W','L','B'), 1024)) {
+ loadLabels(*archive->getResource(MKTAG('V','W','L','B'), 1024));
}
}
@@ -156,7 +160,6 @@ void Score::loadArchive() {
}
Common::Array<uint16> vwci = _movieArchive->getResourceIDList(MKTAG('V','W','C','I'));
-
if (vwci.size() > 0) {
Common::Array<uint16>::iterator iterator;
@@ -165,13 +168,8 @@ void Score::loadArchive() {
}
Common::Array<uint16> stxt = _movieArchive->getResourceIDList(MKTAG('S','T','X','T'));
-
if (stxt.size() > 0) {
- Common::Array<uint16>::iterator iterator;
-
- for (iterator = stxt.begin(); iterator != stxt.end(); ++iterator) {
- loadScriptText(*_movieArchive->getResource(MKTAG('S','T','X','T'), *iterator));
- }
+ loadScriptText(*_movieArchive->getResource(MKTAG('S','T','X','T'), *stxt.begin()));
}
}
@@ -188,12 +186,7 @@ Score::~Score() {
if (_movieArchive)
_movieArchive->close();
- delete _surface;
- delete _trailSurface;
-
delete _font;
- delete _movieArchive;
-
delete _labels;
}
@@ -201,7 +194,7 @@ void Score::loadPalette(Common::SeekableSubReadStreamEndian &stream) {
uint16 steps = stream.size() / 6;
uint16 index = (steps * 3) - 1;
uint16 _paletteColorCount = steps;
- byte *_palette = new byte[index];
+ byte *_palette = new byte[index + 1];
for (uint8 i = 0; i < steps; i++) {
_palette[index - 2] = stream.readByte();
@@ -223,6 +216,8 @@ void Score::loadFrames(Common::SeekableSubReadStreamEndian &stream) {
if (_vm->getVersion() > 3) {
stream.skip(16);
+
+ warning("STUB: Score::loadFrames. Skipping initial bytes");
//Unknown, some bytes - constant (refer to contuinity).
}
@@ -249,7 +244,6 @@ void Score::loadFrames(Common::SeekableSubReadStreamEndian &stream) {
frameSize -= channelSize + 4;
}
frame->readChannel(stream, channelOffset, channelSize);
-
}
_frames.push_back(frame);
@@ -279,6 +273,8 @@ void Score::readVersion(uint32 rid) {
}
void Score::loadCastData(Common::SeekableSubReadStreamEndian &stream) {
+ debugC(1, kDebugLoading, "Score::loadCastData(). start: %d, end: %d", _castArrayStart, _castArrayEnd);
+
for (uint16 id = _castArrayStart; id <= _castArrayEnd; id++) {
byte size = stream.readByte();
if (size == 0)
@@ -349,7 +345,7 @@ void Score::loadLabels(Common::SeekableSubReadStreamEndian &stream) {
Common::SortedArray<Label *>::iterator j;
for (j = _labels->begin(); j != _labels->end(); ++j) {
- debug("Frame %d, Label %s", (*j)->number, (*j)->name.c_str());
+ debugC(2, kDebugLoading, "Frame %d, Label %s", (*j)->number, (*j)->name.c_str());
}
}
@@ -412,10 +408,10 @@ void Score::loadScriptText(Common::SeekableSubReadStreamEndian &stream) {
for (uint32 i = 0; i < strLen; i++) {
byte ch = stream.readByte();
- if (ch == 0x0d) {
- //in old Mac systems \r was the code for end-of-line instead.
+ // Convert Mac line endings
+ if (ch == 0x0d)
ch = '\n';
- }
+
script += ch;
}
@@ -451,6 +447,8 @@ void Score::dumpScript(const char *script, ScriptType type, uint16 id) {
char buf[256];
switch (type) {
+ case kNoneScript:
+ error("Incorrect dumpScript() call");
case kFrameScript:
typeName = "frame";
break;
@@ -497,8 +495,8 @@ void Score::loadCastInfo(Common::SeekableSubReadStreamEndian &stream, uint16 id)
}
void Score::gotoloop() {
- //This command has the playback head contonuously return to the first marker to to the left and then loop back.
- //If no marker are to the left of the playback head, the playback head continues to the right.
+ // This command has the playback head contonuously return to the first marker to to the left and then loop back.
+ // If no marker are to the left of the playback head, the playback head continues to the right.
Common::SortedArray<Label *>::iterator i;
for (i = _labels->begin(); i != _labels->end(); ++i) {
@@ -515,25 +513,25 @@ void Score::gotonext() {
for (i = _labels->begin(); i != _labels->end(); ++i) {
if ((*i)->name == _currentLabel) {
if (i != _labels->end()) {
- //return to the first marker to to the right
+ // return to the first marker to to the right
++i;
_currentFrame = (*i)->number;
return;
} else {
- //if no markers are to the right of the playback head,
- //the playback head goes to the first marker to the left
+ // if no markers are to the right of the playback head,
+ // the playback head goes to the first marker to the left
_currentFrame = (*i)->number;
return;
}
}
}
- //If there are not markers to the left,
- //the playback head goes to frame 1, (Director frame array start from 1, engine from 0)
+ // If there are not markers to the left,
+ // the playback head goes to frame 1, (Director frame array start from 1, engine from 0)
_currentFrame = 0;
}
void Score::gotoprevious() {
- //One label
+ // One label
if (_labels->begin() == _labels->end()) {
_currentFrame = (*_labels->begin())->number;
return;
@@ -603,7 +601,7 @@ Common::Array<Common::String> Score::loadStrings(Common::SeekableSubReadStreamEn
}
uint16 count = stream.readUint16();
- offset += (count + 1) * 4 + 2; //positions info + uint16 count
+ offset += (count + 1) * 4 + 2; // positions info + uint16 count
uint32 startPos = stream.readUint32() + offset;
for (uint16 i = 0; i < count; i++) {
@@ -653,16 +651,17 @@ void Score::loadFontMap(Common::SeekableSubReadStreamEndian &stream) {
}
BitmapCast::BitmapCast(Common::SeekableSubReadStreamEndian &stream) {
- /*byte flags = */ stream.readByte();
- uint16 someFlaggyThing = stream.readUint16();
+ flags = stream.readByte();
+ someFlaggyThing = stream.readUint16();
initialRect = Score::readRect(stream);
boundingRect = Score::readRect(stream);
regY = stream.readUint16();
regX = stream.readUint16();
+ unk1 = unk2 = 0;
if (someFlaggyThing & 0x8000) {
- /*uint16 unk1 =*/ stream.readUint16();
- /*uint16 unk2 =*/ stream.readUint16();
+ unk1 = stream.readUint16();
+ unk2 = stream.readUint16();
}
modified = 0;
}
@@ -675,8 +674,9 @@ TextCast::TextCast(Common::SeekableSubReadStreamEndian &stream) {
textType = static_cast<TextType>(stream.readByte());
textAlign = static_cast<TextAlignType>(stream.readUint16());
stream.skip(6); //palinfo
- //for now, just supposition
- fontId = stream.readUint32();
+
+ int t = stream.readUint32();
+ assert(t == 0); // So far we saw only 0 here
initialRect = Score::readRect(stream);
textShadow = static_cast<SizeType>(stream.readByte());
@@ -687,8 +687,11 @@ TextCast::TextCast(Common::SeekableSubReadStreamEndian &stream) {
textFlags.push_back(kTextFlagAutoTab);
if (flags & 0x4)
textFlags.push_back(kTextFlagDoNotWrap);
- //again supposition
- fontSize = stream.readUint16();
+
+ // TODO: FIXME: guesswork
+ fontId = stream.readByte();
+ fontSize = stream.readByte();
+
modified = 0;
}
@@ -735,11 +738,9 @@ void Score::startLoop() {
_frames[_currentFrame]->prepareFrame(this);
while (!_stopPlay && _currentFrame < _frames.size() - 2) {
+ debugC(1, kDebugImages, "Current frame: %d", _currentFrame);
update();
processEvents();
-
- g_system->updateScreen();
- g_system->delayMillis(10);
}
}
@@ -750,20 +751,24 @@ void Score::update() {
_surface->clear();
_surface->copyFrom(*_trailSurface);
- //Enter and exit from previous frame (Director 4)
- _lingo->processEvent(kEventEnterFrame, _currentFrame);
- _lingo->processEvent(kEventExitFrame, _currentFrame);
- //TODO Director 6 - another order
+ // Enter and exit from previous frame (Director 4)
+ _lingo->processEvent(kEventEnterFrame, _frames[_currentFrame]->_actionId);
+ _lingo->processEvent(kEventExitFrame, _frames[_currentFrame]->_actionId);
+ // TODO Director 6 - another order
+ // TODO Director 6 step: send beginSprite event to any sprites whose span begin in the upcoming frame
+ if (_vm->getVersion() >= 6) {
+ for (uint16 i = 0; i < CHANNEL_COUNT; i++) {
+ if (_frames[_currentFrame]->_sprites[i]->_enabled) {
+ _lingo->processEvent(kEventBeginSprite, i);
+ }
+ }
+ }
- //TODO Director 6 step: send beginSprite event to any sprites whose span begin in the upcoming frame
- //for (uint16 i = 0; i < CHANNEL_COUNT; i++) {
- // if (_frames[_currentFrame]->_sprites[i]->_enabled)
- // _lingo->processEvent(kEventBeginSprite, i);
- //}
+ // TODO Director 6 step: send prepareFrame event to all sprites and the script channel in upcoming frame
+ if (_vm->getVersion() >= 6)
+ _lingo->processEvent(kEventPrepareFrame, _currentFrame);
- //TODO Director 6 step: send prepareFrame event to all sprites and the script channel in upcoming frame
- //_lingo->processEvent(kEventPrepareFrame, _currentFrame);
_currentFrame++;
Common::SortedArray<Label *>::iterator i;
@@ -774,35 +779,35 @@ void Score::update() {
}
_frames[_currentFrame]->prepareFrame(this);
- //Stage is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell)
+ // Stage is drawn between the prepareFrame and enterFrame events (Lingo in a Nutshell)
byte tempo = _frames[_currentFrame]->_tempo;
if (tempo) {
if (tempo > 161) {
- //Delay
+ // Delay
_nextFrameTime = g_system->getMillis() + (256 - tempo) * 1000;
return;
} else if (tempo <= 60) {
- //FPS
+ // FPS
_nextFrameTime = g_system->getMillis() + (float)tempo / 60 * 1000;
_currentFrameRate = tempo;
} else if (tempo >= 136) {
- //TODO Wait for channel tempo - 135
+ // TODO Wait for channel tempo - 135
+ warning("STUB: tempo >= 136");
} else if (tempo == 128) {
- //TODO Wait for Click/Key
+ // TODO Wait for Click/Key
+ warning("STUB: tempo == 128");
} else if (tempo == 135) {
- //Wait for sound channel 1
+ // Wait for sound channel 1
while (_soundManager->isChannelActive(1)) {
processEvents();
- g_system->delayMillis(10);
}
} else if (tempo == 134) {
- //Wait for sound channel 2
+ // Wait for sound channel 2
while (_soundManager->isChannelActive(2)) {
processEvents();
- g_system->delayMillis(10);
}
}
}
@@ -815,803 +820,61 @@ void Score::processEvents() {
Common::Event event;
- while (g_system->getEventManager()->pollEvent(event)) {
- if (event.type == Common::EVENT_QUIT)
- _stopPlay = true;
-
- if (event.type == Common::EVENT_LBUTTONDOWN) {
- Common::Point pos = g_system->getEventManager()->getMousePos();
-
- //TODO there is dont send frame id
- _lingo->processEvent(kEventMouseDown, _frames[_currentFrame]->getSpriteIDFromPos(pos));
- }
-
- if (event.type == Common::EVENT_LBUTTONUP) {
- Common::Point pos = g_system->getEventManager()->getMousePos();
-
- _lingo->processEvent(kEventMouseUp, _frames[_currentFrame]->getSpriteIDFromPos(pos));
- }
- }
-}
-
-Sprite *Score::getSpriteById(uint16 id) {
- if (_frames[_currentFrame]->_sprites[id]) {
- return _frames[_currentFrame]->_sprites[id];
- } else {
- warning("Sprite on frame %d width id %d not found", _currentFrame, id);
- return nullptr;
- }
-}
-
-Frame::Frame(DirectorEngine *vm) {
- _vm = vm;
- _transDuration = 0;
- _transType = kTransNone;
- _transArea = 0;
- _transChunkSize = 0;
- _tempo = 0;
-
- _sound1 = 0;
- _sound2 = 0;
- _soundType1 = 0;
- _soundType2 = 0;
-
- _actionId = 0;
- _skipFrameFlag = 0;
- _blend = 0;
-
- _sprites.resize(CHANNEL_COUNT);
-
- for (uint16 i = 0; i < _sprites.size(); i++) {
- Sprite *sp = new Sprite();
- _sprites[i] = sp;
- }
-}
+ int endTime = g_system->getMillis() + 200;
-Frame::Frame(const Frame &frame) {
- _vm = frame._vm;
- _actionId = frame._actionId;
- _transArea = frame._transArea;
- _transDuration = frame._transDuration;
- _transType = frame._transType;
- _transChunkSize = frame._transChunkSize;
- _tempo = frame._tempo;
- _sound1 = frame._sound1;
- _sound2 = frame._sound2;
- _soundType1 = frame._soundType1;
- _soundType2 = frame._soundType2;
- _skipFrameFlag = frame._skipFrameFlag;
- _blend = frame._blend;
- _palette = new PaletteInfo();
-
- _sprites.resize(CHANNEL_COUNT);
-
- for (uint16 i = 0; i < CHANNEL_COUNT; i++) {
- _sprites[i] = new Sprite(*frame._sprites[i]);
- }
-}
+ while (g_system->getMillis() < endTime) {
+ while (g_system->getEventManager()->pollEvent(event)) {
+ if (event.type == Common::EVENT_QUIT)
+ _stopPlay = true;
-Frame::~Frame() {
- delete[] &_sprites;
- delete[] &_drawRects;
- delete _palette;
-}
+ if (event.type == Common::EVENT_LBUTTONDOWN) {
+ Common::Point pos = g_system->getEventManager()->getMousePos();
-void Frame::readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) {
- if (offset >= 32) {
- if (size <= 16)
- readSprite(stream, offset, size);
- else {
- //read > 1 sprites channel
- while (size > 16) {
- byte spritePosition = (offset - 32) / 16;
- uint16 nextStart = (spritePosition + 1) * 16 + 32;
- uint16 needSize = nextStart - offset;
- readSprite(stream, offset, needSize);
- offset += needSize;
- size -= needSize;
+ // TODO there is dont send frame id
+ _lingo->processEvent(kEventMouseDown, _frames[_currentFrame]->getSpriteIDFromPos(pos));
}
- readSprite(stream, offset, size);
- }
- } else {
- readMainChannels(stream, offset, size);
- }
-}
-void Frame::readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) {
- uint16 finishPosition = offset + size;
+ if (event.type == Common::EVENT_LBUTTONUP) {
+ Common::Point pos = g_system->getEventManager()->getMousePos();
- while (offset < finishPosition) {
- switch(offset) {
- case kScriptIdPosition:
- _actionId = stream.readByte();
- offset++;
- break;
- case kSoundType1Position:
- _soundType1 = stream.readByte();
- offset++;
- break;
- case kTransFlagsPosition: {
- uint8 transFlags = stream.readByte();
- if (transFlags & 0x80)
- _transArea = 1;
- else
- _transArea = 0;
- _transDuration = transFlags & 0x7f;
- offset++;
+ _lingo->processEvent(kEventMouseUp, _frames[_currentFrame]->getSpriteIDFromPos(pos));
}
- break;
- case kTransChunkSizePosition:
- _transChunkSize = stream.readByte();
- offset++;
- break;
- case kTempoPosition:
- _tempo = stream.readByte();
- offset++;
- break;
- case kTransTypePosition:
- _transType = static_cast<TransitionType>(stream.readByte());
- offset++;
- break;
- case kSound1Position:
- _sound1 = stream.readUint16();
- offset+=2;
- break;
- case kSkipFrameFlagsPosition:
- _skipFrameFlag = stream.readByte();
- offset++;
- break;
- case kBlendPosition:
- _blend = stream.readByte();
- offset++;
- break;
- case kSound2Position:
- _sound2 = stream.readUint16();
- offset += 2;
- break;
- case kSound2TypePosition:
- _soundType2 = stream.readByte();
- offset += 1;
- break;
- case kPaletePosition:
- if (stream.readUint16())
- readPaletteInfo(stream);
- offset += 16;
- default:
- offset++;
- stream.readByte();
- debug("Field Position %d, Finish Position %d", offset, finishPosition);
- break;
- }
- }
-}
-
-void Frame::readPaletteInfo(Common::SeekableSubReadStreamEndian &stream) {
- _palette->firstColor = stream.readByte();
- _palette->lastColor = stream.readByte();
- _palette->flags = stream.readByte();
- _palette->speed = stream.readByte();
- _palette->frameCount = stream.readUint16();
- stream.skip(8); //unknown
-}
-void Frame::readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size) {
- uint16 spritePosition = (offset - 32) / 16;
- uint16 spriteStart = spritePosition * 16 + 32;
-
- uint16 fieldPosition = offset - spriteStart;
- uint16 finishPosition = fieldPosition + size;
-
- Sprite &sprite = *_sprites[spritePosition];
-
- while (fieldPosition < finishPosition) {
- switch (fieldPosition) {
- case kSpritePositionUnk1:
- /*byte x1 = */ stream.readByte();
- fieldPosition++;
- break;
- case kSpritePositionEnabled:
- sprite._enabled = (stream.readByte() != 0);
- fieldPosition++;
- break;
- case kSpritePositionUnk2:
- /*byte x2 = */ stream.readUint16();
- fieldPosition += 2;
- break;
- case kSpritePositionFlags:
- sprite._flags = stream.readUint16();
- sprite._ink = static_cast<InkType>(sprite._flags & 0x3f);
-
- if (sprite._flags & 0x40)
- sprite._trails = 1;
- else
- sprite._trails = 0;
-
- fieldPosition += 2;
- break;
- case kSpritePositionCastId:
- sprite._castId = stream.readUint16();
- fieldPosition += 2;
- break;
- case kSpritePositionY:
- sprite._startPoint.y = stream.readUint16();
- fieldPosition += 2;
- break;
- case kSpritePositionX:
- sprite._startPoint.x = stream.readUint16();
- fieldPosition += 2;
- break;
- case kSpritePositionWidth:
- sprite._width = stream.readUint16();
- fieldPosition += 2;
- break;
- case kSpritePositionHeight:
- sprite._height = stream.readUint16();
- fieldPosition += 2;
- break;
- default:
- //end cycle, go to next sprite channel
- readSprite(stream, spriteStart + 16, finishPosition - fieldPosition);
- fieldPosition = finishPosition;
- break;
- }
- }
-}
-
-void Frame::prepareFrame(Score *score) {
- renderSprites(*score->_surface, false);
- renderSprites(*score->_trailSurface, true);
-
- if (_transType != 0)
- //TODO Handle changing area case
- playTransition(score);
-
- if (_sound1 != 0 || _sound2 != 0) {
- playSoundChannel();
- }
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, score->_surface->getBounds().width(), score->_surface->getBounds().height());
-}
-
-void Frame::playSoundChannel() {
- debug(0, "Sound2 %d", _sound2);
- debug(0, "Sound1 %d", _sound1);
-}
-
-void Frame::playTransition(Score *score) {
- uint16 duration = _transDuration * 250; // _transDuration in 1/4 of sec
- duration = (duration == 0 ? 250 : duration); // director support transition duration = 0, but animation play like value = 1, idk.
-
- if (_transChunkSize == 0)
- _transChunkSize = 1; //equal 1 step
-
- uint16 stepDuration = duration / _transChunkSize;
- uint16 steps = duration / stepDuration;
-
- switch (_transType) {
- case kTransCoverDown:
- {
- uint16 stepSize = score->_movieRect.height() / steps;
- Common::Rect r = score->_movieRect;
-
- for (uint16 i = 1; i < steps; i++) {
- r.setHeight(stepSize * i);
-
- g_system->delayMillis(stepDuration);
- score->processEvents();
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height());
- g_system->updateScreen();
- }
- }
- break;
- case kTransCoverUp:
- {
- uint16 stepSize = score->_movieRect.height() / steps;
- Common::Rect r = score->_movieRect;
-
- for (uint16 i = 1; i < steps; i++) {
- r.setHeight(stepSize * i);
-
- g_system->delayMillis(stepDuration);
- score->processEvents();
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, score->_movieRect.height() - stepSize * i, r.width(), r.height());
- g_system->updateScreen();
- }
- }
- break;
- case kTransCoverRight: {
- uint16 stepSize = score->_movieRect.width() / steps;
- Common::Rect r = score->_movieRect;
-
- for (uint16 i = 1; i < steps; i++) {
- r.setWidth(stepSize * i);
-
- g_system->delayMillis(stepDuration);
- score->processEvents();
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height());
- g_system->updateScreen();
- }
- }
- break;
- case kTransCoverLeft: {
- uint16 stepSize = score->_movieRect.width() / steps;
- Common::Rect r = score->_movieRect;
-
- for (uint16 i = 1; i < steps; i++) {
- r.setWidth(stepSize * i);
-
- g_system->delayMillis(stepDuration);
- score->processEvents();
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, 0, r.width(), r.height());
- g_system->updateScreen();
- }
- }
- break;
- case kTransCoverUpLeft: {
- uint16 stepSize = score->_movieRect.width() / steps;
- Common::Rect r = score->_movieRect;
-
- for (uint16 i = 1; i < steps; i++) {
- r.setWidth(stepSize * i);
- r.setHeight(stepSize * i);
-
- g_system->delayMillis(stepDuration);
- score->processEvents();
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, score->_movieRect.height() - stepSize * i, r.width(), r.height());
- g_system->updateScreen();
- }
- }
- break;
- case kTransCoverUpRight: {
- uint16 stepSize = score->_movieRect.width() / steps;
- Common::Rect r = score->_movieRect;
-
- for (uint16 i = 1; i < steps; i++) {
- r.setWidth(stepSize * i);
- r.setHeight(stepSize * i);
-
- g_system->delayMillis(stepDuration);
- score->processEvents();
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, score->_movieRect.height() - stepSize * i, r.width(), r.height());
- g_system->updateScreen();
- }
- }
- break;
- case kTransCoverDownLeft: {
- uint16 stepSize = score->_movieRect.width() / steps;
- Common::Rect r = score->_movieRect;
-
- for (uint16 i = 1; i < steps; i++) {
- r.setWidth(stepSize * i);
- r.setHeight(stepSize * i);
-
- g_system->delayMillis(stepDuration);
- score->processEvents();
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, score->_movieRect.width() - stepSize * i, 0, r.width(), r.height());
- g_system->updateScreen();
- }
- }
- break;
- case kTransCoverDownRight: {
- uint16 stepSize = score->_movieRect.width() / steps;
- Common::Rect r = score->_movieRect;
-
- for (uint16 i = 1; i < steps; i++) {
- r.setWidth(stepSize * i);
- r.setHeight(stepSize * i);
-
- g_system->delayMillis(stepDuration);
- score->processEvents();
-
- g_system->copyRectToScreen(score->_surface->getPixels(), score->_surface->pitch, 0, 0, r.width(), r.height());
- g_system->updateScreen();
- }
- }
- break;
- default:
- warning("Unhandled transition type %d %d %d", _transType, duration, _transChunkSize);
- break;
-
- }
-}
-
-void Frame::renderSprites(Graphics::ManagedSurface &surface, bool renderTrail) {
- for (uint16 i = 0; i < CHANNEL_COUNT; i++) {
- if (_sprites[i]->_enabled) {
- if ((_sprites[i]->_trails == 0 && renderTrail) || (_sprites[i]->_trails == 1 && !renderTrail))
- continue;
-
- Cast *cast;
- if (!_vm->_currentScore->_casts.contains(_sprites[i]->_castId)) {
- if (!_vm->getSharedCasts()->contains(_sprites[i]->_castId)) {
- warning("Cast id %d not found", _sprites[i]->_castId);
- continue;
- } else {
- cast = _vm->getSharedCasts()->getVal(_sprites[i]->_castId);
+ if (event.type == Common::EVENT_KEYDOWN) {
+ _vm->_keyCode = event.kbd.keycode;
+ switch (_vm->_keyCode) {
+ case Common::KEYCODE_LEFT:
+ _vm->_keyCode = 123;
+ break;
+ case Common::KEYCODE_RIGHT:
+ _vm->_keyCode = 124;
+ break;
+ case Common::KEYCODE_DOWN:
+ _vm->_keyCode = 125;
+ break;
+ case Common::KEYCODE_UP:
+ _vm->_keyCode = 126;
+ break;
+ default:
+ warning("Keycode: %d", _vm->_keyCode);
}
- } else {
- cast = _vm->_currentScore->_casts[_sprites[i]->_castId];
- }
-
- if (cast->type == kCastText) {
- renderText(surface, i);
- continue;
- }
-
- Image::ImageDecoder *img = getImageFrom(_sprites[i]->_castId);
- if (!img) {
- warning("Image with id %d not found", _sprites[i]->_castId);
- continue;
- }
-
- if (!img->getSurface()) {
- //TODO
- //BMPDecoder doesnt cover all BITD resources (not all have first two bytes 'BM')
- //Some BITD's first two bytes 0x6 0x0
- warning("Can not load image %d", _sprites[i]->_castId);
- continue;
- }
-
- uint32 regX = static_cast<BitmapCast *>(_sprites[i]->_cast)->regX;
- uint32 regY = static_cast<BitmapCast *>(_sprites[i]->_cast)->regY;
- uint32 rectLeft = static_cast<BitmapCast *>(_sprites[i]->_cast)->initialRect.left;
- uint32 rectTop = static_cast<BitmapCast *>(_sprites[i]->_cast)->initialRect.top;
-
- int x = _sprites[i]->_startPoint.x - regX + rectLeft;
- int y = _sprites[i]->_startPoint.y - regY + rectTop;
- int height = _sprites[i]->_height;
- int width = _sprites[i]->_width;
-
- Common::Rect drawRect = Common::Rect(x, y, x + width, y + height);
- _drawRects.push_back(drawRect);
-
- switch (_sprites[i]->_ink) {
- case kInkTypeCopy:
- surface.blitFrom(*img->getSurface(), Common::Point(x, y));
- break;
- case kInkTypeBackgndTrans:
- drawBackgndTransSprite(surface, *img->getSurface(), drawRect);
- break;
- case kInkTypeMatte:
- drawMatteSprite(surface, *img->getSurface(), drawRect);
- break;
- case kInkTypeGhost:
- drawGhostSprite(surface, *img->getSurface(), drawRect);
- break;
- case kInkTypeReverse:
- drawReverseSprite(surface, *img->getSurface(), drawRect);
- break;
- default:
- warning("Unhandled ink type %d", _sprites[i]->_ink);
- surface.blitFrom(*img->getSurface(), Common::Point(x, y));
- break;
+ _lingo->processEvent(kEventKeyDown, 0);
}
}
- }
-}
-
-void Frame::renderButton(Graphics::ManagedSurface &surface, uint16 spriteId) {
- renderText(surface, spriteId);
-
- uint16 castID = _sprites[spriteId]->_castId;
- ButtonCast *button = static_cast<ButtonCast *>(_vm->_currentScore->_casts[castID]);
-
- uint32 rectLeft = button->initialRect.left;
- uint32 rectTop = button->initialRect.top;
- int x = _sprites[spriteId]->_startPoint.x + rectLeft;
- int y = _sprites[spriteId]->_startPoint.y + rectTop;
- int height = _sprites[spriteId]->_height;
- int width = _sprites[spriteId]->_width;
-
- switch (button->buttonType) {
- case kTypeCheckBox:
- //Magic numbers: checkbox square need to move left about 5px from text and 12px side size (d4)
- surface.frameRect(Common::Rect(x - 17, y, x + 12, y + 12), 0);
- break;
- case kTypeButton:
- surface.frameRect(Common::Rect(x, y, x + width, y + height), 0);
- break;
- case kTypeRadio:
- warning("STUB: renderButton: kTypeRadio");
- break;
- }
-}
-
-Image::ImageDecoder *Frame::getImageFrom(uint16 spriteId) {
- uint16 imgId = spriteId + 1024;
- Image::ImageDecoder *img = NULL;
-
- if (_vm->_currentScore->getArchive()->hasResource(MKTAG('D', 'I', 'B', ' '), imgId)) {
- img = new DIBDecoder();
- img->loadStream(*_vm->_currentScore->getArchive()->getResource(MKTAG('D', 'I', 'B', ' '), imgId));
- return img;
- }
-
- if (_vm->getSharedDIB() != NULL && _vm->getSharedDIB()->contains(imgId)) {
- img = new DIBDecoder();
- img->loadStream(*_vm->getSharedDIB()->getVal(imgId));
- return img;
- }
-
- if (_vm->_currentScore->getArchive()->hasResource(MKTAG('B', 'I', 'T', 'D'), imgId)) {
- img = new Image::BitmapDecoder();
- img->loadStream(*_vm->_currentScore->getArchive()->getResource(MKTAG('B', 'I', 'T', 'D'), imgId));
- return img;
- }
-
- if (_vm->getSharedBMP() != NULL && _vm->getSharedBMP()->contains(imgId)) {
- img = new Image::BitmapDecoder();
- img->loadStream(*_vm->getSharedBMP()->getVal(imgId));
- return img;
+ g_system->updateScreen();
+ g_system->delayMillis(10);
}
-
- warning("Image %d not found", spriteId);
- return img;
}
-
-void Frame::renderText(Graphics::ManagedSurface &surface, uint16 spriteID) {
- uint16 castID = _sprites[spriteID]->_castId;
-
- TextCast *textCast = static_cast<TextCast *>(_vm->_currentScore->_casts[castID]);
- Common::SeekableSubReadStreamEndian *textStream;
-
- if (_vm->_currentScore->_movieArchive->hasResource(MKTAG('S','T','X','T'), castID + 1024)) {
- textStream = _vm->_currentScore->_movieArchive->getResource(MKTAG('S','T','X','T'), castID + 1024);
+Sprite *Score::getSpriteById(uint16 id) {
+ if (_frames[_currentFrame]->_sprites[id]) {
+ return _frames[_currentFrame]->_sprites[id];
} else {
- textStream = _vm->getSharedSTXT()->getVal(spriteID + 1024);
- }
- /*uint32 unk1 = */ textStream->readUint32();
- uint32 strLen = textStream->readUint32();
- /*uin32 dataLen = */ textStream->readUint32();
- Common::String text;
-
- for (uint32 i = 0; i < strLen; i++) {
- byte ch = textStream->readByte();
- if (ch == 0x0d) {
- ch = '\n';
- }
- text += ch;
- }
-
- uint32 rectLeft = static_cast<TextCast *>(_sprites[spriteID]->_cast)->initialRect.left;
- uint32 rectTop = static_cast<TextCast *>(_sprites[spriteID]->_cast)->initialRect.top;
-
- int x = _sprites[spriteID]->_startPoint.x + rectLeft;
- int y = _sprites[spriteID]->_startPoint.y + rectTop;
- int height = _sprites[spriteID]->_height;
- int width = _sprites[spriteID]->_width;
-
- const char *fontName;
-
- if (_vm->_currentScore->_fontMap.contains(textCast->fontId)) {
- fontName = _vm->_currentScore->_fontMap[textCast->fontId].c_str();
- } else if ((fontName = _vm->_wm->getFontName(textCast->fontId, textCast->fontSize)) == NULL) {
- warning("Unknown font id %d, falling back to default", textCast->fontId);
- fontName = _vm->_wm->getFontName(0, 12);
- }
-
- const Graphics::Font *font = _vm->_wm->getFont(fontName, Graphics::FontManager::kBigGUIFont);
-
- font->drawString(&surface, text, x, y, width, 0);
-
- if (textCast->borderSize != kSizeNone) {
- uint16 size = textCast->borderSize;
-
- //Indent from borders, measured in d4
- x -= 1;
- y -= 4;
-
- height += 4;
- width += 1;
-
- while (size) {
- surface.frameRect(Common::Rect(x, y, x + height, y + width), 0);
- x--;
- y--;
- height += 2;
- width += 2;
- size--;
- }
- }
-
- if (textCast->gutterSize != kSizeNone) {
- x -= 1;
- y -= 4;
-
- height += 4;
- width += 1;
- uint16 size = textCast->gutterSize;
-
- surface.frameRect(Common::Rect(x, y, x + height, y + width), 0);
-
- while (size) {
- surface.drawLine(x + width, y, x + width, y + height, 0);
- surface.drawLine(x, y + height, x + width, y + height, 0);
- x++;
- y++;
- size--;
- }
- }
-}
-
-void Frame::drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
- uint8 skipColor = _vm->getPaletteColorCount() - 1; //FIXME is it always white (last entry in pallette) ?
-
- for (int ii = 0; ii < sprite.h; ii++) {
- const byte *src = (const byte *)sprite.getBasePtr(0, ii);
- byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
-
- for (int j = 0; j < drawRect.width(); j++) {
- if (*src != skipColor)
- *dst = *src;
-
- src++;
- dst++;
- }
- }
-}
-
-void Frame::drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
- uint8 skipColor = _vm->getPaletteColorCount() - 1;
- for (int ii = 0; ii < sprite.h; ii++) {
- const byte *src = (const byte *)sprite.getBasePtr(0, ii);
- byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
-
- for (int j = 0; j < drawRect.width(); j++) {
- if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0) && (*src != skipColor))
- *dst = (_vm->getPaletteColorCount() - 1) - *src; //Oposite color
-
- src++;
- dst++;
- }
- }
-}
-
-void Frame::drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
- uint8 skipColor = _vm->getPaletteColorCount() - 1;
- for (int ii = 0; ii < sprite.h; ii++) {
- const byte *src = (const byte *)sprite.getBasePtr(0, ii);
- byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + ii);
-
- for (int j = 0; j < drawRect.width(); j++) {
- if ((getSpriteIDFromPos(Common::Point(drawRect.left + j, drawRect.top + ii)) != 0))
- *dst = (_vm->getPaletteColorCount() - 1) - *src;
- else if (*src != skipColor)
- *dst = *src;
- src++;
- dst++;
- }
- }
-}
-
-void Frame::drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect) {
- //Like background trans, but all white pixels NOT ENCLOSED by coloured pixels are transparent
- Graphics::Surface tmp;
- tmp.copyFrom(sprite);
-
- // Searching white color in the corners
- int whiteColor = -1;
-
- for (int corner = 0; corner < 4; corner++) {
- int x = (corner & 0x1) ? tmp.w - 1 : 0;
- int y = (corner & 0x2) ? tmp.h - 1 : 0;
-
- byte color = *(byte *)tmp.getBasePtr(x, y);
-
- if (_vm->getPalette()[color * 3 + 0] == 0xff &&
- _vm->getPalette()[color * 3 + 1] == 0xff &&
- _vm->getPalette()[color * 3 + 2] == 0xff) {
- whiteColor = color;
- break;
- }
- }
-
- if (whiteColor == -1) {
- warning("No white color for Matte image");
- whiteColor = *(byte *)tmp.getBasePtr(0, 0);
- }
-
- Graphics::FloodFill ff(&tmp, whiteColor, 0, true);
-
- for (int yy = 0; yy < tmp.h; yy++) {
- ff.addSeed(0, yy);
- ff.addSeed(tmp.w - 1, yy);
- }
-
- for (int xx = 0; xx < tmp.w; xx++) {
- ff.addSeed(xx, 0);
- ff.addSeed(xx, tmp.h - 1);
- }
- ff.fillMask();
-
- for (int yy = 0; yy < tmp.h; yy++) {
- const byte *src = (const byte *)tmp.getBasePtr(0, yy);
- const byte *mask = (const byte *)ff.getMask()->getBasePtr(0, yy);
- byte *dst = (byte *)target.getBasePtr(drawRect.left, drawRect.top + yy);
-
- for (int xx = 0; xx < drawRect.width(); xx++, src++, dst++, mask++)
- if (*mask == 0)
- *dst = *src;
- }
-
- tmp.free();
-}
-
-uint16 Frame::getSpriteIDFromPos(Common::Point pos) {
- //Find first from top to bottom
- for (uint16 i = _drawRects.size() - 1; i > 0; i--) {
- if (_drawRects[i].contains(pos))
- return i;
+ warning("Sprite on frame %d width id %d not found", _currentFrame, id);
+ return nullptr;
}
-
- return 0;
-}
-
-Sprite::Sprite() {
- _enabled = false;
- _trails = 0;
- _width = 0;
- _ink = kInkTypeCopy;
- _flags = 0;
- _height = 0;
- _castId = 0;
- _constraint = 0;
- _moveable = 0;
- _castId = 0;
- _backColor = 0;
- _foreColor = 0;
- _left = 0;
- _right = 0;
- _top = 0;
- _bottom = 0;
- _visible = false;
- _movieRate = 0;
- _movieTime = 0;
- _startTime = 0;
- _stopTime = 0;
- _volume = 0;
- _stretch = 0;
- _type = kInactiveSprite;
-}
-
-Sprite::Sprite(const Sprite &sprite) {
- _enabled = sprite._enabled;
- _castId = sprite._castId;
- _flags = sprite._flags;
- _trails = sprite._trails;
- _ink = sprite._ink;
- _width = sprite._width;
- _height = sprite._height;
- _startPoint.x = sprite._startPoint.x;
- _startPoint.y = sprite._startPoint.y;
- _backColor = sprite._backColor;
- _foreColor = sprite._foreColor;
- _left = sprite._left;
- _right = sprite._right;
- _top = sprite._top;
- _bottom = sprite._bottom;
- _visible = sprite._visible;
- _movieRate = sprite._movieRate;
- _movieTime = sprite._movieTime;
- _stopTime = sprite._stopTime;
- _volume = sprite._volume;
- _stretch = sprite._stretch;
- _type = sprite._type;
-}
-
-Sprite::~Sprite() {
- delete _cast;
- delete &_startPoint;
}
} //End of namespace Director
diff --git a/engines/director/score.h b/engines/director/score.h
index a7ca59b475..9d929adc6a 100644
--- a/engines/director/score.h
+++ b/engines/director/score.h
@@ -23,23 +23,22 @@
#ifndef DIRECTOR_SCORE_H
#define DIRECTOR_SCORE_H
+#include "common/substream.h"
#include "common/rect.h"
-#include "common/stream.h"
-#include "common/array.h"
-#include "director/resource.h"
-#include "graphics/managed_surface.h"
-#include "common/str.h"
-#include "image/image_decoder.h"
-#include "graphics/font.h"
+
+namespace Graphics {
+ class ManagedSurface;
+ class Font;
+}
namespace Director {
-class Lingo;
-class DirectorSound;
-class Score;
+class Archive;
class DirectorEngine;
-
-#define CHANNEL_COUNT 24
+class DirectorSound;
+class Frame;
+class Lingo;
+class Sprite;
enum CastType {
kCastBitmap = 1,
@@ -55,134 +54,14 @@ enum CastType {
kCastScript
};
-//Director v4
-enum SpriteType {
- kInactiveSprite, //turns the sprite off
- kBitmapSprite,
- kRectangleSprite,
- kRoundedRectangleSprite,
- kOvalSprite,
- kLineTopBottomSprite, //line from top left to bottom right
- kLineBottomTopSprite, //line from bottom left to top right
- kTextSprite,
- kButtonSprite,
- kCheckboxSprite,
- kRadioButtonSprite,
- kUndeterminedSprite = 16 //use castType property to examine the type of cast member associated with sprite
-};
-
-enum SpritePosition {
- kSpritePositionUnk1 = 0,
- kSpritePositionEnabled,
- kSpritePositionUnk2,
- kSpritePositionFlags = 4,
- kSpritePositionCastId = 6,
- kSpritePositionY = 8,
- kSpritePositionX = 10,
- kSpritePositionHeight = 12,
- kSpritePositionWidth = 14
-};
-
-enum MainChannelsPosition {
- kScriptIdPosition = 0,
- kSoundType1Position,
- kTransFlagsPosition,
- kTransChunkSizePosition,
- kTempoPosition,
- kTransTypePosition,
- kSound1Position,
- kSkipFrameFlagsPosition = 8,
- kBlendPosition,
- kSound2Position,
- kSound2TypePosition = 11,
- kPaletePosition = 15
-};
-
-enum InkType {
- kInkTypeCopy,
- kInkTypeTransparent,
- kInkTypeReverse,
- kInkTypeGhost,
- kInkTypeNotCopy,
- kInkTypeNotTrans,
- kInkTypeNotReverse,
- kInkTypeNotGhost,
- kInkTypeMatte,
- kInkTypeMask,
- //10-31 Not used (Lingo in a Nutshell)
- kInkTypeBlend = 32,
- kInkTypeAddPin,
- kInkTypeAdd,
- kInkTypeSubPin,
- kInkTypeBackgndTrans,
- kInkTypeLight,
- kInkTypeSub,
- kInkTypeDark
-};
-
enum ScriptType {
kMovieScript = 0,
kSpriteScript = 1,
kFrameScript = 2,
+ kNoneScript = -1,
kMaxScriptType = 2
};
-enum TransitionType {
- kTransNone,
- kTransWipeRight,
- kTransWipeLeft,
- kTransWipeDown,
- kTransWipeUp,
- kTransCenterOutHorizontal,
- kTransEdgesInHorizontal,
- kTransCenterOutVertical,
- kTransEdgesInVertical,
- kTransCenterOutSquare,
- kTransEdgesInSquare,
- kTransPushLeft,
- kTransPushRight,
- kTransPushDown,
- kTransPushUp,
- kTransRevealUp,
- kTransRevealUpRight,
- kTransRevealRight,
- kTransRevealDown,
- kTransRevealDownRight,
- kTransRevealDownLeft,
- kTransRevealLeft,
- kTransRevealUpLeft,
- kTransDissolvePixelsFast,
- kTransDissolveBoxyRects,
- kTransDissolveBoxySquares,
- kTransDissolvePatterns,
- kTransRandomRows,
- kTransRandomColumns,
- kTransCoverDown,
- kTransCoverDownLeft,
- kTransCoverDownRight,
- kTransCoverLeft,
- kTransCoverRight,
- kTransCoverUp,
- kTransCoverUpLeft,
- kTransCoverUpRight,
- kTransTypeVenitianBlind,
- kTransTypeCheckerboard,
- kTransTypeStripsBottomBuildLeft,
- kTransTypeStripsBottomBuildRight,
- kTransTypeStripsLeftBuildDown,
- kTransTypeStripsLeftBuildUp,
- kTransTypeStripsRightBuildDown,
- kTransTypeStripsRightBuildUp,
- kTransTypeStripsTopBuildLeft,
- kTransTypeStripsTopBuildRight,
- kTransZoomOpen,
- kTransZoomClose,
- kTransVerticalBinds,
- kTransDissolveBitsTrans,
- kTransDissolvePixels,
- kTransDissolveBits
-};
-
struct Cast {
CastType type;
Common::Rect initialRect;
@@ -196,6 +75,8 @@ struct BitmapCast : Cast {
uint16 regX;
uint16 regY;
uint8 flags;
+ uint16 someFlaggyThing;
+ uint16 unk1, unk2;
};
enum ShapeType {
@@ -281,98 +162,6 @@ struct CastInfo {
Common::String type;
};
-struct PaletteInfo {
- uint8 firstColor;
- uint8 lastColor;
- uint8 flags;
- uint8 speed;
- uint16 frameCount;
-};
-
-class Sprite {
-public:
- Sprite();
- Sprite(const Sprite &sprite);
- ~Sprite();
- bool _enabled;
- byte _castId;
- InkType _ink;
- uint16 _trails;
- Cast *_cast;
- uint16 _flags;
- Common::Point _startPoint;
- uint16 _width;
- uint16 _height;
- //TODO: default constraint = 0, if turned on, sprite is constrainted to the bounding rect
- //As i know, constrainted != 0 only if sprite moveable
- byte _constraint;
- byte _moveable;
- byte _backColor;
- byte _foreColor;
- uint16 _left;
- uint16 _right;
- uint16 _top;
- uint16 _bottom;
- byte _blend;
- bool _visible;
- SpriteType _type;
- //Using in digital movie sprites
- byte _movieRate;
- uint16 _movieTime;
- uint16 _startTime;
- uint16 _stopTime;
- byte _volume;
- byte _stretch;
- //Using in shape sprites
- byte _lineSize;
- //Using in text sprites
- Common::String _editableText;
-};
-
-class Frame {
-public:
- Frame(DirectorEngine *vm);
- Frame(const Frame &frame);
- ~Frame();
- void readChannel(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
- void prepareFrame(Score *score);
- uint16 getSpriteIDFromPos(Common::Point pos);
-
-private:
- void playTransition(Score *score);
- void playSoundChannel();
- void renderSprites(Graphics::ManagedSurface &surface, bool renderTrail);
- void renderText(Graphics::ManagedSurface &surface, uint16 spriteId);
- void renderButton(Graphics::ManagedSurface &surface, uint16 spriteId);
- void readPaletteInfo(Common::SeekableSubReadStreamEndian &stream);
- void readSprite(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
- void readMainChannels(Common::SeekableSubReadStreamEndian &stream, uint16 offset, uint16 size);
- Image::ImageDecoder *getImageFrom(uint16 spriteID);
- void drawBackgndTransSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
- void drawMatteSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
- void drawGhostSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
- void drawReverseSprite(Graphics::ManagedSurface &target, const Graphics::Surface &sprite, Common::Rect &drawRect);
-public:
- uint8 _actionId;
- uint8 _transDuration;
- uint8 _transArea; //1 - Whole Stage, 0 - Changing Area
- uint8 _transChunkSize;
- TransitionType _transType;
- PaletteInfo *_palette;
- uint8 _tempo;
-
- uint16 _sound1;
- uint8 _soundType1;
- uint16 _sound2;
- uint8 _soundType2;
-
- uint8 _skipFrameFlag;
- uint8 _blend;
- Common::Array<Sprite *> _sprites;
- Common::Array<Common::Rect > _drawRects;
- DirectorEngine *_vm;
-};
-
struct Label {
Common::String name;
uint16 number;
@@ -381,7 +170,7 @@ struct Label {
class Score {
public:
- Score(DirectorEngine *vm);
+ Score(DirectorEngine *vm, Archive *);
~Score();
static Common::Rect readRect(Common::SeekableSubReadStreamEndian &stream);
@@ -394,14 +183,15 @@ public:
void startLoop();
void processEvents();
Archive *getArchive() const { return _movieArchive; };
+ void loadConfig(Common::SeekableSubReadStreamEndian &stream);
void loadCastData(Common::SeekableSubReadStreamEndian &stream);
void setCurrentFrame(uint16 frameId) { _currentFrame = frameId; }
Common::String getMacName() const { return _macName; }
Sprite *getSpriteById(uint16 id);
+
private:
void update();
void readVersion(uint32 rid);
- void loadConfig(Common::SeekableSubReadStreamEndian &stream);
void loadPalette(Common::SeekableSubReadStreamEndian &stream);
void loadFrames(Common::SeekableSubReadStreamEndian &stream);
void loadLabels(Common::SeekableSubReadStreamEndian &stream);
diff --git a/engines/director/sound.cpp b/engines/director/sound.cpp
index 5f6d435392..af6e0d2e1c 100644
--- a/engines/director/sound.cpp
+++ b/engines/director/sound.cpp
@@ -20,12 +20,12 @@
*
*/
-#include "director/sound.h"
#include "audio/decoders/wave.h"
#include "common/file.h"
#include "audio/decoders/aiff.h"
#include "common/system.h"
-#include "common/debug.h"
+
+#include "director/sound.h"
namespace Director {
@@ -36,6 +36,12 @@ DirectorSound::DirectorSound() {
_mixer = g_system->getMixer();
}
+DirectorSound::~DirectorSound() {
+ delete _sound1;
+ delete _sound2;
+ delete _scriptSound;
+}
+
void DirectorSound::playWAV(Common::String filename, uint8 soundChannel) {
Common::File *file = new Common::File();
diff --git a/engines/director/sound.h b/engines/director/sound.h
index 87a989c596..850842be21 100644
--- a/engines/director/sound.h
+++ b/engines/director/sound.h
@@ -22,7 +22,6 @@
#include "audio/audiostream.h"
#include "audio/mixer.h"
-#include "common/str.h"
#ifndef DIRECTOR_SOUND_H
#define DIRECTOR_SOUND_H
@@ -39,6 +38,7 @@ private:
public:
DirectorSound();
+ ~DirectorSound();
void playWAV(Common::String filename, uint8 channelID);
void playAIFF(Common::String filename, uint8 channelID);
diff --git a/engines/director/sprite.cpp b/engines/director/sprite.cpp
new file mode 100644
index 0000000000..77d53ae1da
--- /dev/null
+++ b/engines/director/sprite.cpp
@@ -0,0 +1,96 @@
+/* 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 "director/director.h"
+#include "director/score.h"
+#include "director/sprite.h"
+
+namespace Director {
+
+Sprite::Sprite() {
+ _enabled = false;
+ _trails = 0;
+ _width = 0;
+ _ink = kInkTypeCopy;
+ _flags = 0;
+ _height = 0;
+ _castId = 0;
+ _constraint = 0;
+ _moveable = 0;
+ _castId = 0;
+ _backColor = 0;
+ _foreColor = 0;
+ _left = 0;
+ _right = 0;
+ _top = 0;
+ _bottom = 0;
+ _visible = false;
+ _movieRate = 0;
+ _movieTime = 0;
+ _startTime = 0;
+ _stopTime = 0;
+ _volume = 0;
+ _stretch = 0;
+ _type = kInactiveSprite;
+
+ _cast = nullptr;
+ _blend = 0;
+ _lineSize = 1;
+}
+
+Sprite::Sprite(const Sprite &sprite) {
+ _enabled = sprite._enabled;
+ _castId = sprite._castId;
+ _flags = sprite._flags;
+ _trails = sprite._trails;
+ _ink = sprite._ink;
+ _width = sprite._width;
+ _height = sprite._height;
+ _startPoint.x = sprite._startPoint.x;
+ _startPoint.y = sprite._startPoint.y;
+ _backColor = sprite._backColor;
+ _foreColor = sprite._foreColor;
+ _left = sprite._left;
+ _right = sprite._right;
+ _top = sprite._top;
+ _bottom = sprite._bottom;
+ _visible = sprite._visible;
+ _movieRate = sprite._movieRate;
+ _movieTime = sprite._movieTime;
+ _stopTime = sprite._stopTime;
+ _volume = sprite._volume;
+ _stretch = sprite._stretch;
+ _type = sprite._type;
+
+ _cast = sprite._cast;
+ _constraint = sprite._constraint;
+ _moveable = sprite._moveable;
+ _blend = sprite._blend;
+ _startTime = sprite._startTime;
+ _lineSize = sprite._lineSize;
+}
+
+Sprite::~Sprite() {
+ delete _cast;
+}
+
+} //End of namespace Director
diff --git a/engines/director/sprite.h b/engines/director/sprite.h
new file mode 100644
index 0000000000..c66c66d6e4
--- /dev/null
+++ b/engines/director/sprite.h
@@ -0,0 +1,137 @@
+/* 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 DIRECTOR_SPRITE_H
+#define DIRECTOR_SPRITE_H
+
+#include "common/rect.h"
+
+namespace Director {
+
+enum InkType {
+ kInkTypeCopy,
+ kInkTypeTransparent,
+ kInkTypeReverse,
+ kInkTypeGhost,
+ kInkTypeNotCopy,
+ kInkTypeNotTrans,
+ kInkTypeNotReverse,
+ kInkTypeNotGhost,
+ kInkTypeMatte,
+ kInkTypeMask,
+ //10-31 Not used (Lingo in a Nutshell)
+ kInkTypeBlend = 32,
+ kInkTypeAddPin,
+ kInkTypeAdd,
+ kInkTypeSubPin,
+ kInkTypeBackgndTrans,
+ kInkTypeLight,
+ kInkTypeSub,
+ kInkTypeDark
+};
+
+//Director v4
+enum SpriteType {
+ kInactiveSprite, //turns the sprite off
+ kBitmapSprite,
+ kRectangleSprite,
+ kRoundedRectangleSprite,
+ kOvalSprite,
+ kLineTopBottomSprite, //line from top left to bottom right
+ kLineBottomTopSprite, //line from bottom left to top right
+ kTextSprite,
+ kButtonSprite,
+ kCheckboxSprite,
+ kRadioButtonSprite,
+ kUndeterminedSprite = 16 //use castType property to examine the type of cast member associated with sprite
+};
+
+enum SpritePosition {
+ kSpritePositionUnk1 = 0,
+ kSpritePositionEnabled,
+ kSpritePositionUnk2,
+ kSpritePositionFlags = 4,
+ kSpritePositionCastId = 6,
+ kSpritePositionY = 8,
+ kSpritePositionX = 10,
+ kSpritePositionHeight = 12,
+ kSpritePositionWidth = 14
+};
+
+enum MainChannelsPosition {
+ kScriptIdPosition = 0,
+ kSoundType1Position,
+ kTransFlagsPosition,
+ kTransChunkSizePosition,
+ kTempoPosition,
+ kTransTypePosition,
+ kSound1Position,
+ kSkipFrameFlagsPosition = 8,
+ kBlendPosition,
+ kSound2Position,
+ kSound2TypePosition = 11,
+ kPaletePosition = 15
+};
+
+class Sprite {
+public:
+ Sprite();
+ Sprite(const Sprite &sprite);
+ ~Sprite();
+ bool _enabled;
+ byte _castId;
+ InkType _ink;
+ uint16 _trails;
+ Cast *_cast;
+ uint16 _flags;
+ Common::Point _startPoint;
+ uint16 _width;
+ uint16 _height;
+ //TODO: default constraint = 0, if turned on, sprite is constrainted to the bounding rect
+ //As i know, constrainted != 0 only if sprite moveable
+ byte _constraint;
+ byte _moveable;
+ byte _backColor;
+ byte _foreColor;
+ uint16 _left;
+ uint16 _right;
+ uint16 _top;
+ uint16 _bottom;
+ byte _blend;
+ bool _visible;
+ SpriteType _type;
+ //Using in digital movie sprites
+ byte _movieRate;
+ uint16 _movieTime;
+ uint16 _startTime;
+ uint16 _stopTime;
+ byte _volume;
+ byte _stretch;
+ //Using in shape sprites
+ byte _lineSize;
+ //Using in text sprites
+ Common::String _editableText;
+};
+
+} //End of namespace Director
+
+#endif
diff --git a/engines/drascula/detection.cpp b/engines/drascula/detection.cpp
index ffec393a0a..3bc8069b76 100644
--- a/engines/drascula/detection.cpp
+++ b/engines/drascula/detection.cpp
@@ -339,7 +339,8 @@ bool DrasculaMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
- (f == kSavesSupportPlayTime);
+ (f == kSavesSupportPlayTime) ||
+ (f == kSimpleSavesNames);
}
const ExtraGuiOptions DrasculaMetaEngine::getExtraGuiOptions(const Common::String &target) const {
diff --git a/engines/fullpipe/mgm.cpp b/engines/fullpipe/anihandler.cpp
index c9eeebb224..126abbf247 100644
--- a/engines/fullpipe/mgm.cpp
+++ b/engines/fullpipe/anihandler.cpp
@@ -29,14 +29,14 @@
namespace Fullpipe {
-void MGM::clear() {
+void AniHandler::detachAllObjects() {
_items.clear();
}
-MessageQueue *MGM::genMQ(StaticANIObject *ani, int staticsIndex, int staticsId, int *resStatId, Common::Point **pointArr) {
- debugC(4, kDebugPathfinding, "MGM::genMQ(*%d, %d, %d, res, point)", ani->_id, staticsIndex, staticsId);
+MessageQueue *AniHandler::makeQueue(StaticANIObject *ani, int staticsIndex, int staticsId, int *resStatId, Common::Point **pointArr) {
+ debugC(4, kDebugPathfinding, "AniHandler::makeQueue(*%d, %d, %d, res, point)", ani->_id, staticsIndex, staticsId);
- int idx = getItemIndexById(ani->_id);
+ int idx = getIndex(ani->_id);
if (idx == -1)
return 0;
@@ -62,8 +62,8 @@ MessageQueue *MGM::genMQ(StaticANIObject *ani, int staticsIndex, int staticsId,
int subidx = startidx + endidx * _items[idx]->statics.size();
if (!_items[idx]->subItems[subidx]->movement) {
- clearMovements2(idx);
- recalcOffsets(idx, startidx, endidx, 0, 1);
+ clearVisitsList(idx);
+ seekWay(idx, startidx, endidx, 0, 1);
}
if (!_items[idx]->subItems[subidx]->movement)
@@ -131,25 +131,25 @@ MGMSubItem::MGMSubItem() {
y = 0;
}
-void MGM::addItem(int objId) {
- debugC(4, kDebugPathfinding, "MGM::addItem(%d)", objId);
+void AniHandler::attachObject(int objId) {
+ debugC(4, kDebugPathfinding, "AniHandler::addItem(%d)", objId);
- if (getItemIndexById(objId) == -1) {
+ if (getIndex(objId) == -1) {
MGMItem *item = new MGMItem();
item->objId = objId;
_items.push_back(item);
}
- rebuildTables(objId);
+ resetData(objId);
}
-void MGM::rebuildTables(int objId) {
- int idx = getItemIndexById(objId);
+void AniHandler::resetData(int objId) {
+ int idx = getIndex(objId);
if (idx == -1)
return;
- debugC(3, kDebugPathfinding, "MGM::rebuildTables. (1) movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size());
+ debugC(3, kDebugPathfinding, "AniHandler::resetData. (1) movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size());
_items[idx]->subItems.clear();
_items[idx]->statics.clear();
@@ -174,10 +174,10 @@ void MGM::rebuildTables(int objId) {
_items[idx]->movements2.push_back(0);
}
- debugC(3, kDebugPathfinding, "MGM::rebuildTables. (2) movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size());
+ debugC(3, kDebugPathfinding, "AniHandler::resetData. (2) movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size());
}
-int MGM::getItemIndexById(int objId) {
+int AniHandler::getIndex(int objId) {
for (uint i = 0; i < _items.size(); i++)
if (_items[i]->objId == objId)
return i;
@@ -185,62 +185,62 @@ int MGM::getItemIndexById(int objId) {
return -1;
}
-MessageQueue *MGM::genMovement(MGMInfo *mgminfo) {
- debugC(4, kDebugPathfinding, "MGM::genMovement(*%d)", mgminfo->ani ? mgminfo->ani->_id : -1);
+MessageQueue *AniHandler::makeRunQueue(MakeQueueStruct *mkQueue) {
+ debugC(4, kDebugPathfinding, "AniHandler::makeRunQueue(*%d)", mkQueue->ani ? mkQueue->ani->_id : -1);
- if (!mgminfo->ani)
+ if (!mkQueue->ani)
return 0;
- Movement *mov = mgminfo->ani->_movement;
+ Movement *mov = mkQueue->ani->_movement;
- if (!mov && !mgminfo->ani->_statics)
+ if (!mov && !mkQueue->ani->_statics)
return 0;
- if (!(mgminfo->flags & 1)) {
+ if (!(mkQueue->flags & 1)) {
if (mov)
- mgminfo->staticsId1 = mov->_staticsObj2->_staticsId;
+ mkQueue->staticsId1 = mov->_staticsObj2->_staticsId;
else
- mgminfo->staticsId1 = mgminfo->ani->_statics->_staticsId;
+ mkQueue->staticsId1 = mkQueue->ani->_statics->_staticsId;
}
Common::Point point;
- if (!(mgminfo->flags & 0x10) || !(mgminfo->flags & 0x20)) {
- int nx = mgminfo->ani->_ox;
- int ny = mgminfo->ani->_oy;
+ if (!(mkQueue->flags & 0x10) || !(mkQueue->flags & 0x20)) {
+ int nx = mkQueue->ani->_ox;
+ int ny = mkQueue->ani->_oy;
- if (mgminfo->ani->_movement) {
- mgminfo->ani->calcNextStep(&point);
+ if (mkQueue->ani->_movement) {
+ mkQueue->ani->calcNextStep(&point);
nx += point.x;
ny += point.y;
}
- if (!(mgminfo->flags & 0x10))
- mgminfo->x2 = nx;
+ if (!(mkQueue->flags & 0x10))
+ mkQueue->x2 = nx;
- if (!(mgminfo->flags & 0x20))
- mgminfo->y2 = ny;
+ if (!(mkQueue->flags & 0x20))
+ mkQueue->y2 = ny;
}
- mov = mgminfo->ani->getMovementById(mgminfo->movementId);
+ mov = mkQueue->ani->getMovementById(mkQueue->movementId);
if (!mov)
return 0;
- int itemIdx = getItemIndexById(mgminfo->ani->_id);
- int subIdx = getStaticsIndexById(itemIdx, mgminfo->staticsId1);
+ int itemIdx = getIndex(mkQueue->ani->_id);
+ int subIdx = getStaticsIndexById(itemIdx, mkQueue->staticsId1);
int st2idx = getStaticsIndexById(itemIdx, mov->_staticsObj1->_staticsId);
int st1idx = getStaticsIndexById(itemIdx, mov->_staticsObj2->_staticsId);
- int subOffset = getStaticsIndexById(itemIdx, mgminfo->staticsId2);
+ int subOffset = getStaticsIndexById(itemIdx, mkQueue->staticsId2);
- debugC(3, kDebugPathfinding, "MGM::genMovement. (1) movements1 sz: %d movements2 sz: %d", _items[itemIdx]->movements1.size(), _items[itemIdx]->movements2.size());
+ debugC(3, kDebugPathfinding, "AniHandler::genMovement. (1) movements1 sz: %d movements2 sz: %d", _items[itemIdx]->movements1.size(), _items[itemIdx]->movements2.size());
- clearMovements2(itemIdx);
- recalcOffsets(itemIdx, subIdx, st2idx, 0, 1);
- clearMovements2(itemIdx);
- recalcOffsets(itemIdx, st1idx, subOffset, 0, 1);
+ clearVisitsList(itemIdx);
+ seekWay(itemIdx, subIdx, st2idx, 0, 1);
+ clearVisitsList(itemIdx);
+ seekWay(itemIdx, st1idx, subOffset, 0, 1);
MGMSubItem *sub1 = _items[itemIdx]->subItems[subIdx + st2idx * _items[itemIdx]->statics.size()];
MGMSubItem *sub2 = _items[itemIdx]->subItems[st1idx + subOffset * _items[itemIdx]->statics.size()];
@@ -251,8 +251,8 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) {
if (st1idx != subOffset && !sub2->movement)
return 0;
- int n1x = mgminfo->x1 - mgminfo->x2 - sub1->x - sub2->x;
- int n1y = mgminfo->y1 - mgminfo->y2 - sub1->y - sub2->y;
+ int n1x = mkQueue->x1 - mkQueue->x2 - sub1->x - sub2->x;
+ int n1y = mkQueue->y1 - mkQueue->y2 - sub1->y - sub2->y;
Common::Point point1;
@@ -263,37 +263,37 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) {
int mult;
int len = -1;
- if (mgminfo->flags & 0x40) {
- mult = mgminfo->field_10;
+ if (mkQueue->flags & 0x40) {
+ mult = mkQueue->field_10;
len = -1;
n2x *= mult;
n2y *= mult;
} else {
- calcLength(&point, mov, n1x, n1y, &mult, &len, 1);
+ getNumCycles(&point, mov, n1x, n1y, &mult, &len, 1);
n2x = point.x;
n2y = point.y;
}
- if (!(mgminfo->flags & 2)) {
+ if (!(mkQueue->flags & 2)) {
len = -1;
n2x = mult * point1.x;
n1x = mult * point1.x;
- mgminfo->x1 = mgminfo->x2 + mult * point1.x + sub1->x + sub2->x;
+ mkQueue->x1 = mkQueue->x2 + mult * point1.x + sub1->x + sub2->x;
}
- if (!(mgminfo->flags & 4)) {
+ if (!(mkQueue->flags & 4)) {
n2y = mult * point1.y;
n1y = mult * point1.y;
len = -1;
- mgminfo->y1 = mgminfo->y2 + mult * point1.y + sub1->y + sub2->y;
+ mkQueue->y1 = mkQueue->y2 + mult * point1.y + sub1->y + sub2->y;
}
int px = 0;
int py = 0;
if (sub1->movement) {
- px = countPhases(itemIdx, subIdx, st2idx, 1);
- py = countPhases(itemIdx, subIdx, st2idx, 2);
+ px = getFramesCount(itemIdx, subIdx, st2idx, 1);
+ py = getFramesCount(itemIdx, subIdx, st2idx, 2);
}
if (mult > 1) {
@@ -307,8 +307,8 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) {
}
if (sub2->movement) {
- px += countPhases(itemIdx, st1idx, subOffset, 1);
- py += countPhases(itemIdx, st1idx, subOffset, 2);
+ px += getFramesCount(itemIdx, st1idx, subOffset, 1);
+ py += getFramesCount(itemIdx, st1idx, subOffset, 2);
}
int dx1 = n1x - n2x;
@@ -348,9 +348,9 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) {
for (int i = subIdx; i != st2idx;) {
MGMSubItem *s = _items[itemIdx]->subItems[i + subOffset * _items[itemIdx]->statics.size()];
- ex2 = buildExCommand2(s->movement, mgminfo->ani->_id, x1, y1, &x2, &y2, -1);
+ ex2 = createCommand(s->movement, mkQueue->ani->_id, x1, y1, &x2, &y2, -1);
ex2->_parId = mq->_id;
- ex2->_keyCode = mgminfo->ani->_okeyCode;
+ ex2->_keyCode = mkQueue->ani->_okeyCode;
mq->addExCommandToEnd(ex2);
@@ -365,9 +365,9 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) {
else
plen = -1;
- ex2 = buildExCommand2(mov, mgminfo->ani->_id, x1, y1, &x2, &y2, plen);
+ ex2 = createCommand(mov, mkQueue->ani->_id, x1, y1, &x2, &y2, plen);
ex2->_parId = mq->_id;
- ex2->_keyCode = mgminfo->ani->_okeyCode;
+ ex2->_keyCode = mkQueue->ani->_okeyCode;
mq->addExCommandToEnd(ex2);
}
@@ -375,30 +375,30 @@ MessageQueue *MGM::genMovement(MGMInfo *mgminfo) {
for (int j = st1idx; j != subOffset;) {
MGMSubItem *s = _items[itemIdx]->subItems[j + subOffset * _items[itemIdx]->statics.size()];
- ex2 = buildExCommand2(s->movement, mgminfo->ani->_id, x1, y1, &x2, &y2, -1);
+ ex2 = createCommand(s->movement, mkQueue->ani->_id, x1, y1, &x2, &y2, -1);
ex2->_parId = mq->_id;
- ex2->_keyCode = mgminfo->ani->_okeyCode;
+ ex2->_keyCode = mkQueue->ani->_okeyCode;
mq->addExCommandToEnd(ex2);
j = s->staticsIndex;
}
- ExCommand *ex = new ExCommand(mgminfo->ani->_id, 5, -1, mgminfo->x1, mgminfo->y1, 0, 1, 0, 0, 0);
+ ExCommand *ex = new ExCommand(mkQueue->ani->_id, 5, -1, mkQueue->x1, mkQueue->y1, 0, 1, 0, 0, 0);
- ex->_field_14 = mgminfo->field_1C;
- ex->_keyCode = mgminfo->ani->_okeyCode;
+ ex->_field_14 = mkQueue->field_1C;
+ ex->_keyCode = mkQueue->ani->_okeyCode;
ex->_field_24 = 0;
ex->_excFlags |= 3;
mq->addExCommandToEnd(ex);
- debugC(3, kDebugPathfinding, "MGM::genMovement. (2) movements1 sz: %d movements2 sz: %d", _items[itemIdx]->movements1.size(), _items[itemIdx]->movements2.size());
+ debugC(3, kDebugPathfinding, "AniHandler::genMovement. (2) movements1 sz: %d movements2 sz: %d", _items[itemIdx]->movements1.size(), _items[itemIdx]->movements2.size());
return mq;
}
-int MGM::countPhases(int idx, int subIdx, int endIdx, int flag) {
+int AniHandler::getFramesCount(int idx, int subIdx, int endIdx, int flag) {
int res = 0;
if (endIdx < 0)
@@ -415,10 +415,10 @@ int MGM::countPhases(int idx, int subIdx, int endIdx, int flag) {
return res;
}
-void MGM::updateAnimStatics(StaticANIObject *ani, int staticsId) {
- debugC(4, kDebugPathfinding, "MGM::updateAnimStatics(*%d, %d)", ani->_id, staticsId);
+void AniHandler::putObjectToStatics(StaticANIObject *ani, int staticsId) {
+ debugC(4, kDebugPathfinding, "AniHandler::putObjectToStatics(*%d, %d)", ani->_id, staticsId);
- if (getItemIndexById(ani->_id) == -1)
+ if (getIndex(ani->_id) == -1)
return;
if (ani->_movement) {
@@ -437,7 +437,7 @@ void MGM::updateAnimStatics(StaticANIObject *ani, int staticsId) {
if (ani->_statics) {
Common::Point point;
- getPoint(&point, ani->_id, ani->_statics->_staticsId, staticsId);
+ getTransitionSize(&point, ani->_id, ani->_statics->_staticsId, staticsId);
ani->setOXY(ani->_ox + point.x, ani->_oy + point.y);
@@ -445,10 +445,10 @@ void MGM::updateAnimStatics(StaticANIObject *ani, int staticsId) {
}
}
-Common::Point *MGM::getPoint(Common::Point *point, int objectId, int staticsId1, int staticsId2) {
- debugC(4, kDebugPathfinding, "MGM::getPoint([%d, %d], %d, %d, %d)", point->x, point->y, objectId, staticsId1, staticsId2);
+Common::Point *AniHandler::getTransitionSize(Common::Point *point, int objectId, int staticsId1, int staticsId2) {
+ debugC(4, kDebugPathfinding, "AniHandler::getTransitionSize([%d, %d], %d, %d, %d)", point->x, point->y, objectId, staticsId1, staticsId2);
- int idx = getItemIndexById(objectId);
+ int idx = getIndex(objectId);
if (idx == -1) {
point->x = -1;
@@ -464,12 +464,12 @@ Common::Point *MGM::getPoint(Common::Point *point, int objectId, int staticsId1,
int subidx = st1idx + st2idx * _items[idx]->statics.size();
if (!_items[idx]->subItems[subidx]->movement) {
- clearMovements2(idx);
- recalcOffsets(idx, st1idx, st2idx, false, true);
+ clearVisitsList(idx);
+ seekWay(idx, st1idx, st2idx, false, true);
if (!_items[idx]->subItems[subidx]->movement) {
- clearMovements2(idx);
- recalcOffsets(idx, st1idx, st2idx, true, false);
+ clearVisitsList(idx);
+ seekWay(idx, st1idx, st2idx, true, false);
}
}
@@ -488,7 +488,7 @@ Common::Point *MGM::getPoint(Common::Point *point, int objectId, int staticsId1,
return point;
}
-int MGM::getStaticsIndexById(int idx, int16 id) {
+int AniHandler::getStaticsIndexById(int idx, int16 id) {
if (!_items[idx]->statics.size())
return -1;
@@ -500,7 +500,7 @@ int MGM::getStaticsIndexById(int idx, int16 id) {
return -1;
}
-int MGM::getStaticsIndex(int idx, Statics *st) {
+int AniHandler::getStaticsIndex(int idx, Statics *st) {
if (!_items[idx]->statics.size())
return -1;
@@ -512,23 +512,23 @@ int MGM::getStaticsIndex(int idx, Statics *st) {
return -1;
}
-void MGM::clearMovements2(int idx) {
- debugC(2, kDebugPathfinding, "MGM::clearMovements2(%d)", idx);
+void AniHandler::clearVisitsList(int idx) {
+ debugC(2, kDebugPathfinding, "AniHandler::clearVisitsList(%d)", idx);
for (uint i = 0; i < _items[idx]->movements2.size(); i++)
_items[idx]->movements2[i] = 0;
- debugC(3, kDebugPathfinding, "MGM::clearMovements2. movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size());
+ debugC(3, kDebugPathfinding, "AniHandler::clearVisitsList. movements1 sz: %d movements2 sz: %d", _items[idx]->movements1.size(), _items[idx]->movements2.size());
}
-int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) {
+int AniHandler::seekWay(int idx, int st1idx, int st2idx, bool flip, bool flop) {
MGMItem *item = _items[idx];
int subIdx = st1idx + st2idx * item->statics.size();
- debugC(2, kDebugPathfinding, "MGM::recalcOffsets(%d, %d, %d, %d, %d)", idx, st1idx, st2idx, flip, flop);
+ debugC(2, kDebugPathfinding, "AniHandler::seekWay(%d, %d, %d, %d, %d)", idx, st1idx, st2idx, flip, flop);
if (st1idx == st2idx) {
- memset(&item->subItems[subIdx], 0, sizeof(item->subItems[subIdx]));
+ memset(item->subItems[subIdx], 0, sizeof(*(item->subItems[subIdx])));
return 0;
}
@@ -537,7 +537,7 @@ int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) {
Common::Point point;
- debugC(3, kDebugPathfinding, "MGM::recalcOffsets. movements1 sz: %d movements2 sz: %d", item->movements1.size(), item->movements2.size());
+ debugC(3, kDebugPathfinding, "AniHandler::seekWay. movements1 sz: %d movements2 sz: %d", item->movements1.size(), item->movements2.size());
for (uint i = 0; i < item->movements1.size(); i++) {
Movement *mov = item->movements1[i];
@@ -549,9 +549,9 @@ int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) {
item->movements2[i] = 1;
int stidx = getStaticsIndex(idx, mov->_staticsObj2);
- int recalc = recalcOffsets(idx, stidx, st2idx, flip, flop);
+ int recalc = seekWay(idx, stidx, st2idx, flip, flop);
int sz = mov->_currMovement ? mov->_currMovement->_dynamicPhases.size() : mov->_dynamicPhases.size();
- debugC(1, kDebugPathfinding, "MGM::recalcOffsets, want idx: %d, off: %d (%d + %d), sz: %d", idx, stidx + st2idx * _items[idx]->statics.size(), stidx, st2idx, item->subItems.size());
+ debugC(1, kDebugPathfinding, "AniHandler::seekWay, want idx: %d, off: %d (%d + %d), sz: %d", idx, stidx + st2idx * _items[idx]->statics.size(), stidx, st2idx, item->subItems.size());
int newsz = sz + item->subItems[stidx + st2idx * _items[idx]->statics.size()]->field_C;
@@ -580,7 +580,7 @@ int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) {
item->movements2[i] = 1;
int stidx = getStaticsIndex(idx, mov->_staticsObj1);
- int recalc = recalcOffsets(idx, stidx, st2idx, flip, flop);
+ int recalc = seekWay(idx, stidx, st2idx, flip, flop);
if (recalc < 0)
continue;
@@ -608,10 +608,10 @@ int MGM::recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop) {
return -1;
}
-int MGM::refreshOffsets(int objectId, int idx1, int idx2) {
- debugC(4, kDebugPathfinding, "MGM::refreshOffsets(%d, %d, %d)", objectId, idx1, idx2);
+int AniHandler::getNumMovements(int objectId, int idx1, int idx2) {
+ debugC(4, kDebugPathfinding, "AniHandler::getNumMovements(%d, %d, %d)", objectId, idx1, idx2);
- int idx = getItemIndexById(objectId);
+ int idx = getIndex(objectId);
if (idx != -1) {
int from = getStaticsIndexById(idx, idx1);
@@ -623,15 +623,15 @@ int MGM::refreshOffsets(int objectId, int idx1, int idx2) {
if (sub->movement) {
idx = sub->field_8;
} else {
- clearMovements2(idx);
- idx = recalcOffsets(idx, from, to, 0, 1);
+ clearVisitsList(idx);
+ idx = seekWay(idx, from, to, 0, 1);
}
}
return idx;
}
-Common::Point *MGM::calcLength(Common::Point *pRes, Movement *mov, int x, int y, int *mult, int *len, int flag) {
+Common::Point *AniHandler::getNumCycles(Common::Point *pRes, Movement *mov, int x, int y, int *mult, int *len, int flag) {
Common::Point point;
mov->calcSomeXY(point, 0, -1);
@@ -639,7 +639,6 @@ Common::Point *MGM::calcLength(Common::Point *pRes, Movement *mov, int x, int y,
int p1y = point.y;
int newmult = 0;
- int oldlen = *len;
if (abs(p1y) > abs(p1x)) {
if (mov->calcSomeXY(point, 0, -1)->y)
@@ -685,13 +684,13 @@ Common::Point *MGM::calcLength(Common::Point *pRes, Movement *mov, int x, int y,
int p2x = 0;
int p2y = 0;
- if (!oldlen)
- oldlen = -1;
+ if (!*len)
+ *len = -1;
- if (oldlen > 0) {
+ if (*len > 0) {
++*mult;
- mov->calcSomeXY(point, 0, oldlen);
+ mov->calcSomeXY(point, 0, *len);
p2x = point.x;
p2y = point.y;
@@ -707,8 +706,8 @@ Common::Point *MGM::calcLength(Common::Point *pRes, Movement *mov, int x, int y,
return pRes;
}
-ExCommand2 *MGM::buildExCommand2(Movement *mov, int objId, int x1, int y1, Common::Point *x2, Common::Point *y2, int len) {
- debugC(2, kDebugPathfinding, "MGM::buildExCommand2(mov, %d, %d, %d, [%d, %d], [%d, %d], %d)", objId, x1, y1, x2->x, x2->y, y2->x, y2->y, len);
+ExCommand2 *AniHandler::createCommand(Movement *mov, int objId, int x1, int y1, Common::Point *x2, Common::Point *y2, int len) {
+ debugC(2, kDebugPathfinding, "AniHandler::createCommand(mov, %d, %d, %d, [%d, %d], [%d, %d], %d)", objId, x1, y1, x2->x, x2->y, y2->x, y2->y, len);
uint cnt;
diff --git a/engines/fullpipe/mgm.h b/engines/fullpipe/anihandler.h
index 13195891da..ae16f91ba8 100644
--- a/engines/fullpipe/mgm.h
+++ b/engines/fullpipe/anihandler.h
@@ -20,8 +20,8 @@
*
*/
-#ifndef FULLPIPE_MGM_H
-#define FULLPIPE_MGM_H
+#ifndef FULLPIPE_ANIHANDLER_H
+#define FULLPIPE_ANIHANDLER_H
namespace Fullpipe {
@@ -50,7 +50,7 @@ struct MGMItem {
MGMItem();
};
-struct MGMInfo {
+struct MakeQueueStruct {
StaticANIObject *ani;
int staticsId1;
int staticsId2;
@@ -63,33 +63,33 @@ struct MGMInfo {
int y2;
int flags;
- MGMInfo() { memset(this, 0, sizeof(MGMInfo)); }
+ MakeQueueStruct() { memset(this, 0, sizeof(MakeQueueStruct)); }
};
-class MGM : public CObject {
+class AniHandler : public CObject {
public:
Common::Array<MGMItem *> _items;
public:
- void clear();
- void addItem(int objId);
- void rebuildTables(int objId);
- int getItemIndexById(int objId);
+ void detachAllObjects();
+ void attachObject(int objId);
+ void resetData(int objId);
+ int getIndex(int objId);
- MessageQueue *genMovement(MGMInfo *mgminfo);
- void updateAnimStatics(StaticANIObject *ani, int staticsId);
- Common::Point *getPoint(Common::Point *point, int aniId, int staticsId1, int staticsId2);
+ MessageQueue *makeRunQueue(MakeQueueStruct *mkQueue);
+ void putObjectToStatics(StaticANIObject *ani, int staticsId);
+ Common::Point *getTransitionSize(Common::Point *point, int aniId, int staticsId1, int staticsId2);
int getStaticsIndexById(int idx, int16 id);
int getStaticsIndex(int idx, Statics *st);
- void clearMovements2(int idx);
- int recalcOffsets(int idx, int st1idx, int st2idx, bool flip, bool flop);
- Common::Point *calcLength(Common::Point *point, Movement *mov, int x, int y, int *mult, int *len, int flag);
- ExCommand2 *buildExCommand2(Movement *mov, int objId, int x1, int y1, Common::Point *x2, Common::Point *y2, int len);
- MessageQueue *genMQ(StaticANIObject *ani, int staticsIndex, int staticsId, int *resStatId, Common::Point **pointArr);
- int countPhases(int idx, int subIdx, int subOffset, int flag);
- int refreshOffsets(int objectId, int idx1, int idx2);
+ void clearVisitsList(int idx);
+ int seekWay(int idx, int st1idx, int st2idx, bool flip, bool flop);
+ Common::Point *getNumCycles(Common::Point *point, Movement *mov, int x, int y, int *mult, int *len, int flag);
+ ExCommand2 *createCommand(Movement *mov, int objId, int x1, int y1, Common::Point *x2, Common::Point *y2, int len);
+ MessageQueue *makeQueue(StaticANIObject *ani, int staticsIndex, int staticsId, int *resStatId, Common::Point **pointArr);
+ int getFramesCount(int idx, int subIdx, int subOffset, int flag);
+ int getNumMovements(int objectId, int idx1, int idx2);
};
} // End of namespace Fullpipe
-#endif /* FULLPIPE_MGM_H */
+#endif /* FULLPIPE_ANIHANDLER_H */
diff --git a/engines/fullpipe/behavior.cpp b/engines/fullpipe/behavior.cpp
index 75b1c78143..92fb952605 100644
--- a/engines/fullpipe/behavior.cpp
+++ b/engines/fullpipe/behavior.cpp
@@ -49,6 +49,8 @@ void BehaviorManager::clear() {
}
void BehaviorManager::initBehavior(Scene *sc, GameVar *var) {
+ debugC(2, kDebugBehavior, "BehaviorManager::initBehavior(%d, %s)", sc->_sceneId, transCyrillic((byte *)var->_varName));
+
clear();
_scene = sc;
@@ -58,7 +60,10 @@ void BehaviorManager::initBehavior(Scene *sc, GameVar *var) {
if (!behvar)
return;
+ debugC(3, kDebugBehavior, "BehaviorManager::initBehavior. have Variable");
+
for (GameVar *subvar = behvar->_subVars; subvar; subvar = subvar->_nextVarObj) {
+ debugC(3, kDebugBehavior, "BehaviorManager::initBehavior. subVar %s", transCyrillic((byte *)subvar->_varName));
if (!strcmp(subvar->_varName, "AMBIENT")) {
behinfo = new BehaviorInfo;
behinfo->initAmbientBehavior(subvar, sc);
@@ -66,8 +71,8 @@ void BehaviorManager::initBehavior(Scene *sc, GameVar *var) {
_behaviors.push_back(behinfo);
} else {
StaticANIObject *ani = sc->getStaticANIObject1ByName(subvar->_varName, -1);
- if (ani)
- for (uint i = 0; i < sc->_staticANIObjectList1.size(); i++)
+ if (ani) {
+ for (uint i = 0; i < sc->_staticANIObjectList1.size(); i++) {
if (((StaticANIObject *)sc->_staticANIObjectList1[i])->_id == ani->_id) {
behinfo = new BehaviorInfo;
behinfo->initObjectBehavior(subvar, sc, ani);
@@ -75,6 +80,8 @@ void BehaviorManager::initBehavior(Scene *sc, GameVar *var) {
_behaviors.push_back(behinfo);
}
+ }
+ }
}
}
}
@@ -83,7 +90,7 @@ void BehaviorManager::updateBehaviors() {
if (!_isActive)
return;
- debugC(4, kDebugAnimation, "BehaviorManager::updateBehaviors()");
+ debugC(6, kDebugBehavior, "BehaviorManager::updateBehaviors()");
for (uint i = 0; i < _behaviors.size(); i++) {
BehaviorInfo *beh = _behaviors[i];
@@ -122,7 +129,7 @@ void BehaviorManager::updateBehaviors() {
}
void BehaviorManager::updateBehavior(BehaviorInfo *behaviorInfo, BehaviorAnim *entry) {
- debugC(4, kDebugAnimation, "BehaviorManager::updateBehavior() %d", entry->_movesCount);
+ debugC(7, kDebugBehavior, "BehaviorManager::updateBehavior() moves: %d", entry->_movesCount);
for (int i = 0; i < entry->_movesCount; i++) {
BehaviorMove *bhi = entry->_behaviorMoves[i];
if (!(bhi->_flags & 1)) {
@@ -144,7 +151,7 @@ void BehaviorManager::updateBehavior(BehaviorInfo *behaviorInfo, BehaviorAnim *e
}
void BehaviorManager::updateStaticAniBehavior(StaticANIObject *ani, int delay, BehaviorAnim *bhe) {
- debugC(4, kDebugAnimation, "BehaviorManager::updateStaticAniBehavior(%s)", transCyrillic((byte *)ani->_objectName));
+ debugC(6, kDebugBehavior, "BehaviorManager::updateStaticAniBehavior(%s)", transCyrillic((byte *)ani->_objectName));
MessageQueue *mq = 0;
@@ -199,7 +206,7 @@ void BehaviorManager::setFlagByStaticAniObject(StaticANIObject *ani, int flag) {
if (ani == beh->_ani) {
if (flag)
- beh->_flags &= 0xfe;
+ beh->_flags &= 0xfffffffe;
else
beh->_flags |= 1;
}
@@ -224,7 +231,7 @@ BehaviorMove *BehaviorManager::getBehaviorMoveByMessageQueueDataId(StaticANIObje
}
void BehaviorInfo::clear() {
- _ani = 0;
+ _ani = NULL;
_staticsId = 0;
_counter = 0;
_counterMax = 0;
@@ -236,7 +243,7 @@ void BehaviorInfo::clear() {
}
void BehaviorInfo::initAmbientBehavior(GameVar *var, Scene *sc) {
- debugC(4, kDebugAnimation, "BehaviorInfo::initAmbientBehavior(%s)", transCyrillic((byte *)var->_varName));
+ debugC(4, kDebugBehavior, "BehaviorInfo::initAmbientBehavior(%s)", transCyrillic((byte *)var->_varName));
clear();
_animsCount = 1;
@@ -260,7 +267,8 @@ void BehaviorInfo::initAmbientBehavior(GameVar *var, Scene *sc) {
}
void BehaviorInfo::initObjectBehavior(GameVar *var, Scene *sc, StaticANIObject *ani) {
- debugC(4, kDebugAnimation, "BehaviorInfo::initObjectBehavior(%s)", transCyrillic((byte *)var->_varName));
+ Common::String s((char *)transCyrillic((byte *)var->_varName));
+ debugC(4, kDebugBehavior, "BehaviorInfo::initObjectBehavior(%s, %d, %s)", s.c_str(), sc->_sceneId, transCyrillic((byte *)ani->_objectName));
clear();
@@ -303,7 +311,7 @@ BehaviorAnim::BehaviorAnim(GameVar *var, Scene *sc, StaticANIObject *ani, int *m
_staticsId = 0;
_movesCount = 0;
- *minDelay = 100000000;
+ *minDelay = 0xffffffff;
int totalPercent = 0;
_flags = 0;
diff --git a/engines/fullpipe/fullpipe.cpp b/engines/fullpipe/fullpipe.cpp
index 5f06d8ad79..54a77938c9 100644
--- a/engines/fullpipe/fullpipe.cpp
+++ b/engines/fullpipe/fullpipe.cpp
@@ -52,8 +52,11 @@ FullpipeEngine::FullpipeEngine(OSystem *syst, const ADGameDescription *gameDesc)
DebugMan.addDebugChannel(kDebugDrawing, "drawing", "Drawing");
DebugMan.addDebugChannel(kDebugLoading, "loading", "Scene loading");
DebugMan.addDebugChannel(kDebugAnimation, "animation", "Animation");
+ DebugMan.addDebugChannel(kDebugBehavior, "behavior", "Behavior");
DebugMan.addDebugChannel(kDebugMemory, "memory", "Memory management");
DebugMan.addDebugChannel(kDebugEvents, "events", "Event handling");
+ DebugMan.addDebugChannel(kDebugInventory, "inventory", "Inventory");
+ DebugMan.addDebugChannel(kDebugSceneLogic, "scenelogic", "Scene Logic");
// Setup mixer
if (!_mixer->isReady()) {
@@ -139,7 +142,7 @@ FullpipeEngine::FullpipeEngine(OSystem *syst, const ADGameDescription *gameDesc)
_scene3 = 0;
_movTable = 0;
_floaters = 0;
- _mgm = 0;
+ _aniHandler = 0;
_globalMessageQueueList = 0;
_messageHandlers = 0;
@@ -216,7 +219,7 @@ void FullpipeEngine::initialize() {
_sceneRect.bottom = 599;
_floaters = new Floaters;
- _mgm = new MGM;
+ _aniHandler = new AniHandler;
}
void FullpipeEngine::restartGame() {
@@ -403,6 +406,7 @@ void FullpipeEngine::updateEvents() {
_lastInputTicks = _updateTicks;
ex->handle();
}
+ _mouseScreenPos = event.mouse;
break;
case Common::EVENT_LBUTTONDOWN:
if (!_inputArFlag && (_updateTicks - _lastInputTicks) >= 2) {
@@ -415,6 +419,7 @@ void FullpipeEngine::updateEvents() {
_lastInputTicks = _updateTicks;
ex->handle();
}
+ _mouseScreenPos = event.mouse;
break;
case Common::EVENT_LBUTTONUP:
if (!_inputArFlag && (_updateTicks - _lastButtonUpTicks) >= 2) {
@@ -423,6 +428,7 @@ void FullpipeEngine::updateEvents() {
_lastButtonUpTicks = _updateTicks;
ex->handle();
}
+ _mouseScreenPos = event.mouse;
break;
default:
break;
diff --git a/engines/fullpipe/fullpipe.h b/engines/fullpipe/fullpipe.h
index 1c85536a9a..09c9559199 100644
--- a/engines/fullpipe/fullpipe.h
+++ b/engines/fullpipe/fullpipe.h
@@ -48,13 +48,16 @@ namespace Fullpipe {
enum FullpipeGameFeatures {
};
-enum AccessDebugChannels {
- kDebugPathfinding = 1 << 0,
- kDebugDrawing = 1 << 1,
- kDebugLoading = 1 << 2,
- kDebugAnimation = 1 << 3,
- kDebugMemory = 1 << 4,
- kDebugEvents = 1 << 5
+enum {
+ kDebugPathfinding = 1 << 0,
+ kDebugDrawing = 1 << 1,
+ kDebugLoading = 1 << 2,
+ kDebugAnimation = 1 << 3,
+ kDebugMemory = 1 << 4,
+ kDebugEvents = 1 << 5,
+ kDebugBehavior = 1 << 6,
+ kDebugInventory = 1 << 7,
+ kDebugSceneLogic = 1 << 8
};
class BehaviorManager;
@@ -73,7 +76,7 @@ class GlobalMessageQueueList;
struct MessageHandler;
class MessageQueue;
struct MovTable;
-class MGM;
+class AniHandler;
class NGIArchive;
class PictureObject;
struct PreloadItem;
@@ -209,7 +212,7 @@ public:
MovTable *_movTable;
Floaters *_floaters;
- MGM *_mgm;
+ AniHandler *_aniHandler;
Common::Array<Common::Point *> _arcadeKeys;
diff --git a/engines/fullpipe/gameloader.cpp b/engines/fullpipe/gameloader.cpp
index 1cd51036d0..270e69aa42 100644
--- a/engines/fullpipe/gameloader.cpp
+++ b/engines/fullpipe/gameloader.cpp
@@ -88,10 +88,10 @@ GameLoader::~GameLoader() {
for (uint i = 0; i < _sc2array.size(); i++) {
if (_sc2array[i]._defPicAniInfos)
- delete _sc2array[i]._defPicAniInfos;
+ free(_sc2array[i]._defPicAniInfos);
if (_sc2array[i]._picAniInfos)
- delete _sc2array[i]._picAniInfos;
+ free(_sc2array[i]._picAniInfos);
if (_sc2array[i]._motionController)
delete _sc2array[i]._motionController;
diff --git a/engines/fullpipe/gfx.cpp b/engines/fullpipe/gfx.cpp
index dcd5c33740..eba5d442d5 100644
--- a/engines/fullpipe/gfx.cpp
+++ b/engines/fullpipe/gfx.cpp
@@ -290,8 +290,8 @@ bool GameObject::load(MfcArchive &file) {
_id = file.readUint16LE();
_objectName = file.readPascalString();
- _ox = file.readUint32LE();
- _oy = file.readUint32LE();
+ _ox = file.readSint32LE();
+ _oy = file.readSint32LE();
_priority = file.readUint16LE();
if (g_fp->_gameProjectVersion >= 11) {
@@ -353,7 +353,6 @@ bool GameObject::getPicAniInfo(PicAniInfo *info) {
info->ox = _ox;
info->oy = _oy;
info->priority = _priority;
- warning("Yep %d", _id);
return true;
}
@@ -495,8 +494,8 @@ bool Picture::load(MfcArchive &file) {
debugC(5, kDebugLoading, "Picture::load()");
MemoryObject::load(file);
- _x = file.readUint32LE();
- _y = file.readUint32LE();
+ _x = file.readSint32LE();
+ _y = file.readSint32LE();
_field_44 = file.readUint16LE();
assert(g_fp->_gameProjectVersion >= 2);
@@ -786,8 +785,8 @@ Bitmap::~Bitmap() {
void Bitmap::load(Common::ReadStream *s) {
debugC(5, kDebugLoading, "Bitmap::load()");
- _x = s->readUint32LE();
- _y = s->readUint32LE();
+ _x = s->readSint32LE();
+ _y = s->readSint32LE();
_width = s->readUint32LE();
_height = s->readUint32LE();
s->readUint32LE(); // pixels
@@ -806,7 +805,7 @@ bool Bitmap::isPixelHitAtPos(int x, int y) {
if (!_surface)
return false;
- return ((*((int32 *)_surface->getBasePtr(x, y)) & 0xff000000) != 0);
+ return ((*((int32 *)_surface->getBasePtr(x - _x, y - _y)) & 0xff) != 0);
}
void Bitmap::decode(int32 *palette) {
@@ -1122,28 +1121,31 @@ void Bitmap::copier(uint32 *dest, byte *src, int len, int32 *palette, bool cb05_
}
Bitmap *Bitmap::reverseImage(bool flip) {
+ Bitmap *b = new Bitmap(this);
+
if (flip)
- _flipping = Graphics::FLIP_H;
- else
- _flipping = Graphics::FLIP_NONE;
+ b->_flipping ^= Graphics::FLIP_H;
- return this;
+ return b;
}
Bitmap *Bitmap::flipVertical() {
- _flipping = Graphics::FLIP_V;
+ Bitmap *b = new Bitmap(this);
+
+ b->_flipping ^= Graphics::FLIP_V;
- return this;
+ return b;
}
void Bitmap::drawShaded(int type, int x, int y, byte *palette, int alpha) {
- warning("STUB: Bitmap::drawShaded(%d, %d, %d)", type, x, y);
+ if (alpha != 255)
+ warning("STUB: Bitmap::drawShaded(%d, %d, %d, %d)", type, x, y, alpha);
putDib(x, y, (int32 *)palette, alpha);
}
void Bitmap::drawRotated(int x, int y, int angle, byte *palette, int alpha) {
- warning("STUB: Bitmap::drawShaded(%d, %d, %d)", x, y, angle);
+ warning("STUB: Bitmap::drawRotated(%d, %d, %d, %d)", x, y, angle, alpha);
putDib(x, y, (int32 *)palette, alpha);
}
diff --git a/engines/fullpipe/interaction.cpp b/engines/fullpipe/interaction.cpp
index 484b1e150e..dc40750fe6 100644
--- a/engines/fullpipe/interaction.cpp
+++ b/engines/fullpipe/interaction.cpp
@@ -143,8 +143,8 @@ bool InteractionController::handleInteraction(StaticANIObject *subj, GameObject
obj->setPicAniInfo(&aniInfo);
if (abs(xpos - subj->_ox) > 1 || abs(ypos - subj->_oy) > 1) {
- debugC(0, kDebugPathfinding, "Calling doWalkTo() at [%d, %d]", xpos, ypos);
- mq = getSc2MctlCompoundBySceneId(g_fp->_currentScene->_sceneId)->doWalkTo(subj, xpos, ypos, 1, cinter->_staticsId2);
+ debugC(0, kDebugPathfinding, "Calling makeQueue() at [%d, %d]", xpos, ypos);
+ mq = getSc2MctlCompoundBySceneId(g_fp->_currentScene->_sceneId)->makeQueue(subj, xpos, ypos, 1, cinter->_staticsId2);
if (mq) {
dur = mq->calcDuration(subj);
delete mq;
@@ -305,7 +305,7 @@ LABEL_38:
ani->changeStatics2(inter->_staticsId1);
}
- int xpos = inter->_yOffs + obj->_ox;
+ int xpos = inter->_xOffs + obj->_ox;
int ypos = inter->_yOffs + obj->_oy;
obj->setPicAniInfo(&aniInfo);
@@ -450,8 +450,8 @@ bool Interaction::load(MfcArchive &file) {
_objectId3 = file.readUint16LE();
_objectState2 = file.readUint32LE();
_objectState1 = file.readUint32LE();
- _xOffs = file.readUint32LE();
- _yOffs = file.readUint32LE();
+ _xOffs = file.readSint32LE();
+ _yOffs = file.readSint32LE();
_sceneId = file.readUint32LE();
_flags = file.readUint32LE();
_actionName = file.readPascalString();
diff --git a/engines/fullpipe/inventory.cpp b/engines/fullpipe/inventory.cpp
index aa229d55d7..f1dafeba7d 100644
--- a/engines/fullpipe/inventory.cpp
+++ b/engines/fullpipe/inventory.cpp
@@ -35,7 +35,7 @@ Inventory::~Inventory() {
}
bool Inventory::load(MfcArchive &file) {
- debugC(5, kDebugLoading, "Inventory::load()");
+ debugC(5, kDebugLoading | kDebugInventory, "Inventory::load()");
_sceneId = file.readUint16LE();
int numInvs = file.readUint32LE();
@@ -119,12 +119,36 @@ void Inventory2::addItem2(StaticANIObject *obj) {
}
void Inventory2::removeItem(int itemId, int count) {
- warning("STUB: Inventory2::removeItem(%d, %d)", itemId, count);
+ debugC(2, kDebugInventory, "Inventory2::removeItem(%d, %d)", itemId, count);
+
+ while (count) {
+ int i;
+ for (i = _inventoryItems.size() - 1; i >= 0; i--) {
+ if (_inventoryItems[i]->itemId == itemId) {
+ if (_selectedId == itemId)
+ unselectItem(false);
+
+ if (_inventoryItems[i]->count > count) {
+ _inventoryItems[i]->count -= count;
+ } else {
+ count -= _inventoryItems[i]->count;
+ _inventoryItems.remove_at(i);
+ }
+
+ if (getCountItemsWithId(itemId) < 0)
+ getInventoryPoolItemFieldCById(itemId);
+
+ break;
+ }
+ }
+ }
}
void Inventory2::removeItem2(Scene *sceneObj, int itemId, int x, int y, int priority) {
int idx = getInventoryItemIndexById(itemId);
+ debugC(2, kDebugInventory, "removeItem2(*, %d, %d, %d, %d)", itemId, x, y, priority);
+
if (idx >= 0) {
if (_inventoryItems[idx]->count) {
removeItem(itemId, 1);
@@ -187,11 +211,15 @@ int Inventory2::getItemFlags(int itemId) {
}
void Inventory2::rebuildItemRects() {
+ debugC(2, kDebugInventory, "rebuildItemRects()");
+
_scene = g_fp->accessScene(_sceneId);
if (!_scene)
return;
+ _inventoryIcons.clear();
+
_picture = _scene->getBigPicture(0, 0);
_picture->setAlpha(50);
diff --git a/engines/fullpipe/lift.cpp b/engines/fullpipe/lift.cpp
index d066c89d4a..93bfbaaa24 100644
--- a/engines/fullpipe/lift.cpp
+++ b/engines/fullpipe/lift.cpp
@@ -392,7 +392,8 @@ void FullpipeEngine::lift_clickButton() {
lift_walkAndGo();
}
-void FullpipeEngine::lift_goAnimation() { if (_lastLiftButton) {
+void FullpipeEngine::lift_goAnimation() {
+ if (_lastLiftButton) {
int parentId = _currentScene->_sceneId;
int buttonId = lift_getButtonIdN(_lastLiftButton->_statics->_staticsId);
@@ -428,6 +429,8 @@ void FullpipeEngine::lift_goAnimation() { if (_lastLiftButton) {
delete mq;
_aniMan->_flags |= 1;
+
+ return;
}
}
}
diff --git a/engines/fullpipe/messages.cpp b/engines/fullpipe/messages.cpp
index 8652223ed1..9ddf0fda4e 100644
--- a/engines/fullpipe/messages.cpp
+++ b/engines/fullpipe/messages.cpp
@@ -61,8 +61,8 @@ bool ExCommand::load(MfcArchive &file) {
_parentId = file.readUint16LE();
_messageKind = file.readUint32LE();
- _x = file.readUint32LE();
- _y = file.readUint32LE();
+ _x = file.readSint32LE();
+ _y = file.readSint32LE();
_field_14 = file.readUint32LE();
_sceneClickX = file.readUint32LE();
_sceneClickY = file.readUint32LE();
@@ -457,7 +457,7 @@ void MessageQueue::deleteExCommandByIndex(uint idx, bool doFree) {
_exCommands.erase(it);
}
-void MessageQueue::transferExCommands(MessageQueue *mq) {
+void MessageQueue::mergeQueue(MessageQueue *mq) { // Original belongs to AniHandler
while (mq->_exCommands.size()) {
_exCommands.push_back(mq->_exCommands.front());
mq->_exCommands.pop_front();
@@ -682,7 +682,7 @@ int GlobalMessageQueueList::compact() {
useList[i] = 0;
for (uint i = 0; i < size();) {
- if (((MessageQueue *)_storage[i])->_isFinished) {
+ if (_storage[i]->_isFinished) {
disableQueueById(_storage[i]->_id);
remove_at(i);
} else {
diff --git a/engines/fullpipe/messages.h b/engines/fullpipe/messages.h
index e6f7f05150..67fbb2a6cd 100644
--- a/engines/fullpipe/messages.h
+++ b/engines/fullpipe/messages.h
@@ -142,7 +142,7 @@ class MessageQueue : public CObject {
ExCommand *getExCommandByIndex(uint idx);
void deleteExCommandByIndex(uint idx, bool doFree);
- void transferExCommands(MessageQueue *mq);
+ void mergeQueue(MessageQueue *mq);
void replaceKeyCode(int key1, int key2);
diff --git a/engines/fullpipe/modal.cpp b/engines/fullpipe/modal.cpp
index 096323781f..aab4843067 100644
--- a/engines/fullpipe/modal.cpp
+++ b/engines/fullpipe/modal.cpp
@@ -309,7 +309,7 @@ bool ModalMap::init(int counterdiff) {
_rect2.right = _rect2.left + 800;
_rect2.bottom = _rect2.top + 600;
- g_fp->_sceneRect =_rect2;
+ g_fp->_sceneRect = _rect2;
_mapScene->updateScrolling2();
@@ -344,19 +344,19 @@ bool ModalMap::handleMessage(ExCommand *cmd) {
case 29:
_flag = 1;
_mouseX = g_fp->_mouseScreenPos.x;
- _mouseY = g_fp->_mouseScreenPos.x;
+ _mouseY = g_fp->_mouseScreenPos.y;
- _field_3C = _rect2.top;
_field_38 = _rect2.left;
+ _field_3C = _rect2.top;
- break;
+ return false;
case 30:
_flag = 0;
- break;
+ return false;
case 36:
- if (cmd->_keyCode != 9 && cmd->_keyCode != 27 )
+ if (cmd->_keyCode != 9 && cmd->_keyCode != 27)
return false;
break;
@@ -431,20 +431,20 @@ PictureObject *ModalMap::getScenePicture() {
switch (g_fp->_currentScene->_sceneId) {
case SC_1:
- picId = PIC_MAP_S01;
- break;
+ picId = PIC_MAP_S01;
+ break;
case SC_2:
- picId = PIC_MAP_S02;
- break;
+ picId = PIC_MAP_S02;
+ break;
case SC_3:
- picId = PIC_MAP_S03;
- break;
+ picId = PIC_MAP_S03;
+ break;
case SC_4:
- picId = PIC_MAP_S04;
- break;
+ picId = PIC_MAP_S04;
+ break;
case SC_5:
- picId = PIC_MAP_S05;
- break;
+ picId = PIC_MAP_S05;
+ break;
case SC_6:
picId = PIC_MAP_S06;
break;
@@ -489,7 +489,7 @@ PictureObject *ModalMap::getScenePicture() {
picId = PIC_MAP_S20;
break;
case SC_21:
- picId = PIC_MAP_S21;
+ picId = PIC_MAP_S21;
break;
case SC_22:
picId = PIC_MAP_S22;
diff --git a/engines/fullpipe/module.mk b/engines/fullpipe/module.mk
index 96bd91fd39..01aba1bd82 100644
--- a/engines/fullpipe/module.mk
+++ b/engines/fullpipe/module.mk
@@ -1,6 +1,7 @@
MODULE := engines/fullpipe
MODULE_OBJS = \
+ anihandler.o \
behavior.o \
console.o \
detection.o \
@@ -15,7 +16,6 @@ MODULE_OBJS = \
lift.o \
messagehandlers.o \
messages.o \
- mgm.o \
modal.o \
motion.o \
ngiarchive.o \
diff --git a/engines/fullpipe/motion.cpp b/engines/fullpipe/motion.cpp
index fbfa602e62..81d92ccac8 100644
--- a/engines/fullpipe/motion.cpp
+++ b/engines/fullpipe/motion.cpp
@@ -132,11 +132,11 @@ int MctlCompound::detachObject(StaticANIObject *obj) {
return 1;
}
-void MctlCompound::initMovGraph2() {
+void MctlCompound::initMctlGraph() {
if (_objtype != kObjTypeMctlCompound)
return;
- debugC(4, kDebugPathfinding, "MctlCompound::initMovGraph2()");
+ debugC(4, kDebugPathfinding, "MctlCompound::initMctlGraph()");
for (uint i = 0; i < _motionControllers.size(); i++) {
if (_motionControllers[i]->_motionControllerObj->_objtype != kObjTypeMovGraph)
@@ -144,7 +144,7 @@ void MctlCompound::initMovGraph2() {
MovGraph *gr = (MovGraph *)_motionControllers[i]->_motionControllerObj;
- MovGraph2 *newgr = new MovGraph2();
+ MctlGraph *newgr = new MctlGraph();
newgr->_links = gr->_links;
newgr->_nodes = gr->_nodes;
@@ -208,7 +208,7 @@ MessageQueue *MctlCompound::startMove(StaticANIObject *ani, int sourceX, int sou
if (!cp)
return 0;
- MessageQueue *mq = _motionControllers[idx]->_motionControllerObj->doWalkTo(ani, cp->_connectionX, cp->_connectionY, 1, cp->_mctlmirror);
+ MessageQueue *mq = _motionControllers[idx]->_motionControllerObj->makeQueue(ani, cp->_connectionX, cp->_connectionY, 1, cp->_mctlmirror);
if (!mq)
return 0;
@@ -237,11 +237,11 @@ MessageQueue *MctlCompound::startMove(StaticANIObject *ani, int sourceX, int sou
return mq;
}
-MessageQueue *MctlCompound::doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) {
+MessageQueue *MctlCompound::makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) {
int match1 = -1;
int match2 = -1;
- debugC(1, kDebugPathfinding, "MctlCompound::doWalkTo(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId);
+ debugC(1, kDebugPathfinding, "MctlCompound::makeQueue(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId);
if (!subj)
return 0;
@@ -271,7 +271,7 @@ MessageQueue *MctlCompound::doWalkTo(StaticANIObject *subj, int xpos, int ypos,
return 0;
if (match1 == match2)
- return _motionControllers[match1]->_motionControllerObj->doWalkTo(subj, xpos, ypos, fuzzyMatch, staticsId);
+ return _motionControllers[match1]->_motionControllerObj->makeQueue(subj, xpos, ypos, fuzzyMatch, staticsId);
double dist;
MctlConnectionPoint *closestP = findClosestConnectionPoint(subj->_ox, subj->_oy, match1, xpos, ypos, match2, &dist);
@@ -279,7 +279,7 @@ MessageQueue *MctlCompound::doWalkTo(StaticANIObject *subj, int xpos, int ypos,
if (!closestP)
return 0;
- MessageQueue *mq = _motionControllers[match1]->_motionControllerObj->doWalkTo(subj, closestP->_connectionX, closestP->_connectionY, 1, closestP->_mctlmirror);
+ MessageQueue *mq = _motionControllers[match1]->_motionControllerObj->makeQueue(subj, closestP->_connectionX, closestP->_connectionY, 1, closestP->_mctlmirror);
ExCommand *ex;
@@ -348,7 +348,7 @@ void MctlLadder::attachObject(StaticANIObject *obj) {
MctlLadderMovement *movement = new MctlLadderMovement;
if (initMovement(obj, movement)) {
- _mgm.addItem(obj->_id);
+ _aniHandler.attachObject(obj->_id);
_ladmovements.push_back(movement);
} else {
delete movement;
@@ -372,16 +372,16 @@ bool MctlLadder::initMovement(StaticANIObject *ani, MctlLadderMovement *movement
if (!v)
return false;
- v = v->getSubVarByName("Test_Ladder");
+ GameVar *l = v->getSubVarByName("Test_Ladder");
- if (!v)
+ if (!l)
return false;
movement->staticIdsSize = 6;
movement->movVars = new MctlLadderMovementVars;
movement->staticIds = new int[movement->staticIdsSize];
- v = v->getSubVarByName("Up");
+ v = l->getSubVarByName("Up");
if (!v)
return false;
@@ -393,7 +393,7 @@ bool MctlLadder::initMovement(StaticANIObject *ani, MctlLadderMovement *movement
movement->staticIds[0] = ani->getMovementById(movement->movVars->varUpStart)->_staticsObj1->_staticsId;
movement->staticIds[2] = ani->getMovementById(movement->movVars->varUpGo)->_staticsObj1->_staticsId;
- v = v->getSubVarByName("Down");
+ v = l->getSubVarByName("Down");
if (!v)
return false;
@@ -413,7 +413,7 @@ bool MctlLadder::initMovement(StaticANIObject *ani, MctlLadderMovement *movement
void MctlLadder::detachAllObjects() {
debugC(4, kDebugPathfinding, "MctlLadder::detachAllObjects()");
- _mgm.clear();
+ _aniHandler.detachAllObjects();
for (uint i = 0; i < _ladmovements.size(); i++) {
delete _ladmovements[i]->movVars;
@@ -426,7 +426,7 @@ void MctlLadder::detachAllObjects() {
MessageQueue *MctlLadder::startMove(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) {
debugC(4, kDebugPathfinding, "MctlLadder::startMove(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId);
- MessageQueue *mq = doWalkTo(subj, xpos, ypos, fuzzyMatch, staticsId);
+ MessageQueue *mq = makeQueue(subj, xpos, ypos, fuzzyMatch, staticsId);
if (mq) {
if (mq->chain(subj))
@@ -436,8 +436,8 @@ MessageQueue *MctlLadder::startMove(StaticANIObject *subj, int xpos, int ypos, i
return 0;
}
-MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int fuzzyMatch, int staticsId) {
- debugC(1, kDebugPathfinding, "MctlLadder::doWalkTo(*%d, %d, %d, %d, %d)", (ani ? ani->_id : -1), xpos, ypos, fuzzyMatch, staticsId);
+MessageQueue *MctlLadder::makeQueue(StaticANIObject *ani, int xpos, int ypos, int fuzzyMatch, int staticsId) {
+ debugC(1, kDebugPathfinding, "MctlLadder::makeQueue(*%d, %d, %d, %d, %d)", (ani ? ani->_id : -1), xpos, ypos, fuzzyMatch, staticsId);
int pos = findObjectPos(ani);
@@ -459,7 +459,7 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
int direction = (normy - ani->_oy) < 0 ? 0 : 1;
- MGMInfo mgminfo;
+ MakeQueueStruct mkQueue;
PicAniInfo picinfo;
MessageQueue *mq;
ExCommand *ex;
@@ -476,7 +476,7 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
ani->_movement = 0;
ani->setOXY(point.x + ox, point.y + oy);
- mq = doWalkTo(ani, normx, normy, fuzzyMatch, staticsId);
+ mq = makeQueue(ani, normx, normy, fuzzyMatch, staticsId);
ani->setPicAniInfo(&picinfo);
@@ -484,38 +484,38 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
}
if (ani->_statics->_staticsId == _ladmovements[pos]->staticIds[0]) {
- mgminfo.ani = ani;
+ mkQueue.ani = ani;
if (staticsId)
- mgminfo.staticsId2 = staticsId;
+ mkQueue.staticsId2 = staticsId;
else
- mgminfo.staticsId2 = _ladmovements[pos]->staticIds[direction];
+ mkQueue.staticsId2 = _ladmovements[pos]->staticIds[direction];
- mgminfo.x1 = normx;
- mgminfo.y1 = normy;
- mgminfo.field_1C = _ladder_field_14;
- mgminfo.flags = 14;
- mgminfo.movementId = direction ? _ladmovements[pos]->movVars->varDownGo : _ladmovements[pos]->movVars->varUpGo;
+ mkQueue.x1 = normx;
+ mkQueue.y1 = normy;
+ mkQueue.field_1C = _ladder_field_14;
+ mkQueue.flags = 14;
+ mkQueue.movementId = direction ? _ladmovements[pos]->movVars->varDownGo : _ladmovements[pos]->movVars->varUpGo;
- return _mgm.genMovement(&mgminfo);
+ return _aniHandler.makeRunQueue(&mkQueue);
}
if (ani->_statics->_staticsId == _ladmovements[pos]->staticIds[2]) {
if (!direction) {
- mgminfo.ani = ani;
+ mkQueue.ani = ani;
if (staticsId)
- mgminfo.staticsId2 = staticsId;
+ mkQueue.staticsId2 = staticsId;
else
- mgminfo.staticsId2 = _ladmovements[pos]->staticIds[0];
+ mkQueue.staticsId2 = _ladmovements[pos]->staticIds[0];
- mgminfo.x1 = normx;
- mgminfo.y1 = normy;
- mgminfo.field_1C = _ladder_field_14;
- mgminfo.flags = 14;
- mgminfo.movementId = _ladmovements[pos]->movVars->varUpGo;
+ mkQueue.x1 = normx;
+ mkQueue.y1 = normy;
+ mkQueue.field_1C = _ladder_field_14;
+ mkQueue.flags = 14;
+ mkQueue.movementId = _ladmovements[pos]->movVars->varUpGo;
- return _mgm.genMovement(&mgminfo);
+ return _aniHandler.makeRunQueue(&mkQueue);
}
int ox = ani->_ox;
@@ -523,23 +523,23 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
ani->getMovementById(_ladmovements[pos]->movVars->varUpStop)->calcSomeXY(point, 0, -1);
- mgminfo.ani = ani;
+ mkQueue.ani = ani;
if (staticsId)
- mgminfo.staticsId2 = staticsId;
+ mkQueue.staticsId2 = staticsId;
else
- mgminfo.staticsId2 = _ladmovements[pos]->staticIds[1];
+ mkQueue.staticsId2 = _ladmovements[pos]->staticIds[1];
- mgminfo.field_1C = _ladder_field_14;
- mgminfo.x1 = normx;
- mgminfo.y1 = normy;
- mgminfo.y2 = point.y + oy;
- mgminfo.x2 = point.x + ox;
- mgminfo.flags = 63;
- mgminfo.staticsId1 = _ladmovements[pos]->staticIds[0];
- mgminfo.movementId = _ladmovements[pos]->movVars->varDownGo;
+ mkQueue.field_1C = _ladder_field_14;
+ mkQueue.x1 = normx;
+ mkQueue.y1 = normy;
+ mkQueue.y2 = point.y + oy;
+ mkQueue.x2 = point.x + ox;
+ mkQueue.flags = 63;
+ mkQueue.staticsId1 = _ladmovements[pos]->staticIds[0];
+ mkQueue.movementId = _ladmovements[pos]->movVars->varDownGo;
- mq = _mgm.genMovement(&mgminfo);
+ mq = _aniHandler.makeRunQueue(&mkQueue);
ex = new ExCommand(ani->_id, 1, _ladmovements[pos]->movVars->varUpStop, 0, 0, 0, 1, 0, 0, 0);
ex->_keyCode = ani->_okeyCode;
@@ -551,7 +551,7 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
}
if (ani->_statics->_staticsId != _ladmovements[pos]->staticIds[3]) {
- mq = _mgm.genMQ(ani, _ladmovements[pos]->staticIds[0], 0, 0, 0);
+ mq = _aniHandler.makeQueue(ani, _ladmovements[pos]->staticIds[0], 0, 0, 0);
if (!mq)
return 0;
@@ -559,7 +559,7 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
int nx = ani->_ox;
int ny = ani->_oy;
- _mgm.getPoint(&point, ani->_id, ani->_statics->_staticsId, _ladmovements[pos]->staticIds[0]);
+ _aniHandler.getTransitionSize(&point, ani->_id, ani->_statics->_staticsId, _ladmovements[pos]->staticIds[0]);
nx += point.x;
ny += point.y;
@@ -570,9 +570,9 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
ani->_movement = 0;
ani->setOXY(nx, ny);
- MessageQueue *newmq = doWalkTo(ani, normx, normy, fuzzyMatch, staticsId);
+ MessageQueue *newmq = makeQueue(ani, normx, normy, fuzzyMatch, staticsId);
- mq->transferExCommands(newmq);
+ mq->mergeQueue(newmq);
delete newmq;
@@ -590,22 +590,22 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
nx += point.x;
ny += point.y;
- mgminfo.ani = ani;
+ mkQueue.ani = ani;
if (staticsId)
- mgminfo.staticsId2 = staticsId;
+ mkQueue.staticsId2 = staticsId;
else
- mgminfo.staticsId2 = _ladmovements[pos]->staticIds[0];
+ mkQueue.staticsId2 = _ladmovements[pos]->staticIds[0];
- mgminfo.field_1C = _ladder_field_14;
- mgminfo.x1 = normx;
- mgminfo.y1 = normy;
- mgminfo.y2 = ny;
- mgminfo.x2 = nx;
- mgminfo.flags = 63;
- mgminfo.staticsId1 = _ladmovements[pos]->staticIds[1];
- mgminfo.movementId = _ladmovements[pos]->movVars->varUpGo;
+ mkQueue.field_1C = _ladder_field_14;
+ mkQueue.x1 = normx;
+ mkQueue.y1 = normy;
+ mkQueue.y2 = ny;
+ mkQueue.x2 = nx;
+ mkQueue.flags = 63;
+ mkQueue.staticsId1 = _ladmovements[pos]->staticIds[1];
+ mkQueue.movementId = _ladmovements[pos]->movVars->varUpGo;
- mq = _mgm.genMovement(&mgminfo);
+ mq = _aniHandler.makeRunQueue(&mkQueue);
ex = new ExCommand(ani->_id, 1, _ladmovements[pos]->movVars->varDownStop, 0, 0, 0, 1, 0, 0, 0);
ex->_keyCode = ani->_okeyCode;
@@ -617,24 +617,24 @@ MessageQueue *MctlLadder::doWalkTo(StaticANIObject *ani, int xpos, int ypos, int
}
- mgminfo.ani = ani;
+ mkQueue.ani = ani;
if (staticsId)
- mgminfo.staticsId2 = staticsId;
+ mkQueue.staticsId2 = staticsId;
else
- mgminfo.staticsId2 = _ladmovements[pos]->staticIds[1];
+ mkQueue.staticsId2 = _ladmovements[pos]->staticIds[1];
- mgminfo.x1 = normx;
- mgminfo.y1 = normy;
- mgminfo.field_1C = _ladder_field_14;
- mgminfo.flags = 14;
- mgminfo.movementId = _ladmovements[pos]->movVars->varDownGo;
+ mkQueue.x1 = normx;
+ mkQueue.y1 = normy;
+ mkQueue.field_1C = _ladder_field_14;
+ mkQueue.flags = 14;
+ mkQueue.movementId = _ladmovements[pos]->movVars->varDownGo;
- return _mgm.genMovement(&mgminfo);
+ return _aniHandler.makeRunQueue(&mkQueue);
}
MessageQueue *MctlLadder::controllerWalkTo(StaticANIObject *ani, int off) {
- return doWalkTo(ani, _ladderX + off * _width, _ladderY + off * _height, 1, 0);
+ return makeQueue(ani, _ladderX + off * _width, _ladderY + off * _height, 1, 0);
}
MctlConnectionPoint *MctlCompound::findClosestConnectionPoint(int ox, int oy, int destIndex, int connectionX, int connectionY, int sourceIdx, double *minDistancePtr) {
@@ -705,7 +705,7 @@ MctlConnectionPoint::~MctlConnectionPoint() {
delete _messageQueueObj;
}
-MovInfo1::MovInfo1(MovInfo1 *src) {
+MctlMQ::MctlMQ(MctlMQ *src) {
index = src->index;
pt1 = src->pt1;
pt2 = src->pt2;
@@ -718,7 +718,7 @@ MovInfo1::MovInfo1(MovInfo1 *src) {
flags = src->flags;
}
-void MovInfo1::clear() {
+void MctlMQ::clear() {
index = 0;
pt1.x = pt1.y = 0;
pt2.x = pt2.y = 0;
@@ -806,8 +806,8 @@ bool MovGraph::load(MfcArchive &file) {
void MovGraph::attachObject(StaticANIObject *obj) {
debugC(4, kDebugPathfinding, "MovGraph::attachObject(*%d)", obj->_id);
- _mgm.clear();
- _mgm.addItem(obj->_id);
+ _aniHandler.detachAllObjects();
+ _aniHandler.attachObject(obj->_id);
for (uint i = 0; i < _items.size(); i++)
if (_items[i]->ani == obj)
@@ -819,7 +819,7 @@ void MovGraph::attachObject(StaticANIObject *obj) {
_items.push_back(item);
- _mgm.addItem(obj->_id); // FIXME: Is it really needed?
+ _aniHandler.attachObject(obj->_id); // FIXME: Is it really needed?
}
int MovGraph::detachObject(StaticANIObject *obj) {
@@ -1008,8 +1008,8 @@ bool MovGraph::resetPosition(StaticANIObject *ani, int flag) {
Statics *st;
if (ani->_statics) {
- int t = _mgm.refreshOffsets(ani->_id, ani->_statics->_staticsId, movarr._link->_dwordArray2[_field_44]);
- if (t > _mgm.refreshOffsets(ani->_id, ani->_statics->_staticsId, movarr._link->_dwordArray2[_field_44 + 1]))
+ int t = _aniHandler.getNumMovements(ani->_id, ani->_statics->_staticsId, movarr._link->_dwordArray2[_field_44]);
+ if (t > _aniHandler.getNumMovements(ani->_id, ani->_statics->_staticsId, movarr._link->_dwordArray2[_field_44 + 1]))
st = ani->getStaticsById(movarr._link->_dwordArray2[_field_44 + 1]);
else
st = ani->getStaticsById(movarr._link->_dwordArray2[_field_44]);
@@ -1051,8 +1051,8 @@ bool MovGraph::canDropInventory(StaticANIObject *ani, int x, int y) {
return false;
}
-MessageQueue *MovGraph::doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) {
- debugC(1, kDebugPathfinding, "MovGraph::doWalkTo(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId);
+MessageQueue *MovGraph::makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) {
+ debugC(1, kDebugPathfinding, "MovGraph::makeQueue(*%d, %d, %d, %d, %d)", (subj ? subj->_id : -1), xpos, ypos, fuzzyMatch, staticsId);
PicAniInfo picAniInfo;
int ss;
@@ -1230,28 +1230,28 @@ MessageQueue *MovGraph::makeWholeQueue(StaticANIObject *ani, MovArr *movarr, int
}
}
- MGMInfo mgminfo;
+ MakeQueueStruct mkQueue;
- memset(&mgminfo, 0, sizeof(mgminfo));
- mgminfo.ani = ani;
- mgminfo.staticsId2 = id2;
- mgminfo.staticsId1 = id1;
- mgminfo.x1 = nx;
- mgminfo.x2 = ox;
- mgminfo.y2 = oy;
- mgminfo.y1 = ny;
- mgminfo.field_1C = nd;
- mgminfo.movementId = st->link->_dwordArray1[_field_44 + st->sfield_0];
+ memset(&mkQueue, 0, sizeof(mkQueue));
+ mkQueue.ani = ani;
+ mkQueue.staticsId2 = id2;
+ mkQueue.staticsId1 = id1;
+ mkQueue.x1 = nx;
+ mkQueue.x2 = ox;
+ mkQueue.y2 = oy;
+ mkQueue.y1 = ny;
+ mkQueue.field_1C = nd;
+ mkQueue.movementId = st->link->_dwordArray1[_field_44 + st->sfield_0];
- mgminfo.flags = 0xe;
+ mkQueue.flags = 0xe;
if (mq)
- mgminfo.flags |= 0x31;
+ mkQueue.flags |= 0x31;
- MessageQueue *newmq = _mgm.genMovement(&mgminfo);
+ MessageQueue *newmq = _aniHandler.makeRunQueue(&mkQueue);
if (mq) {
if (newmq) {
- mq->transferExCommands(newmq);
+ mq->mergeQueue(newmq);
delete newmq;
}
@@ -1332,9 +1332,9 @@ double MovGraph::putToLink(Common::Point *point, MovGraphLink *link, int fuzzyMa
int n2x = link->_graphDst->_x;
int n2y = link->_graphDst->_y;
double dist1x = (double)(point->x - n1x);
- double dist1y = (double)(point->y - n1y);
+ double dist1y = (double)(n1y - point->y);
double dist2x = (double)(n2x - n1x);
- double dist2y = (double)(n2y - n1y);
+ double dist2y = (double)(n1y - n2y);
double dist1 = sqrt(dist1x * dist1x + dist1y * dist1y);
double dist2 = (dist2y * dist1y + dist2x * dist1x) / link->_length / dist1;
double distm = dist2 * dist1;
@@ -1355,8 +1355,8 @@ double MovGraph::putToLink(Common::Point *point, MovGraphLink *link, int fuzzyMa
return -1.0;
}
} else {
- point->x = (int)(n1x + (dist2x * distm / link->_length));
- point->y = (int)(n1y + (dist2y * distm / link->_length));
+ point->x = n1x + (int)((double)(n2x - n1x) * distm / link->_length);
+ point->y = n1y + (int)((double)(n2y - n1y) * distm / link->_length);
}
return res;
@@ -1608,14 +1608,14 @@ bool MovGraph::getHitPoint(int idx, int x, int y, MovArr *arr, int a6) {
int offmin = 100;
for (int i = 0; i < arrSize; i++) {
- int off = _mgm.refreshOffsets(_items[idx]->ani->_id, staticsId, (*movarr)[i]->_link->_dwordArray2[_field_44]);
+ int off = _aniHandler.getNumMovements(_items[idx]->ani->_id, staticsId, (*movarr)[i]->_link->_dwordArray2[_field_44]);
if (off < offmin) {
offmin = off;
idxmin = i;
}
- off = _mgm.refreshOffsets(_items[idx]->ani->_id, staticsId, (*movarr)[i]->_link->_dwordArray2[_field_44 + 1]);
+ off = _aniHandler.getNumMovements(_items[idx]->ani->_id, staticsId, (*movarr)[i]->_link->_dwordArray2[_field_44 + 1]);
if (off < offmin) {
offmin = off;
idxmin = i;
@@ -1657,7 +1657,7 @@ void MovGraph::setEnds(MovStep *step1, MovStep *step2) {
}
}
-int MovGraph2::getItemIndexByGameObjectId(int objectId) {
+int MctlGraph::getObjIndex(int objectId) {
for (uint i = 0; i < _items2.size(); i++)
if (_items2[i]->_objectId == objectId)
return i;
@@ -1665,7 +1665,7 @@ int MovGraph2::getItemIndexByGameObjectId(int objectId) {
return -1;
}
-int MovGraph2::getItemSubIndexByStaticsId(int idx, int staticsId) {
+int MctlGraph::getDirByStatics(int idx, int staticsId) {
for (int i = 0; i < 4; i++)
if (_items2[idx]->_subItems[i]._staticsId1 == staticsId || _items2[idx]->_subItems[i]._staticsId2 == staticsId)
return i;
@@ -1673,7 +1673,7 @@ int MovGraph2::getItemSubIndexByStaticsId(int idx, int staticsId) {
return -1;
}
-int MovGraph2::getItemSubIndexByMovementId(int idx, int movId) {
+int MctlGraph::getDirByMovement(int idx, int movId) {
for (int i = 0; i < 4; i++)
if (_items2[idx]->_subItems[i]._walk[0]._movementId == movId || _items2[idx]->_subItems[i]._turn[0]._movementId == movId ||
_items2[idx]->_subItems[i]._turnS[0]._movementId == movId)
@@ -1682,14 +1682,14 @@ int MovGraph2::getItemSubIndexByMovementId(int idx, int movId) {
return -1;
}
-int MovGraph2::getItemSubIndexByMGM(int index, StaticANIObject *ani) {
- if (findNode(ani->_ox, ani->_oy, 0) || findLink1(ani->_ox, ani->_oy, -1, 0) || findLink2(ani->_ox, ani->_oy)) {
+int MctlGraph::getDirByPoint(int index, StaticANIObject *ani) {
+ if (getHitNode(ani->_ox, ani->_oy, 0) || getHitLink(ani->_ox, ani->_oy, -1, 0) || getNearestLink(ani->_ox, ani->_oy)) {
int minidx = -1;
int min = 0;
for (int i = 0; i < 4; i++) {
debugC(1, kDebugPathfinding, "WWW 5");
- int tmp = _mgm.refreshOffsets(ani->_id, ani->_statics->_staticsId, _items2[index]->_subItems[i]._staticsId1);
+ int tmp = _aniHandler.getNumMovements(ani->_id, ani->_statics->_staticsId, _items2[index]->_subItems[i]._staticsId1);
if (tmp >= 0 && (minidx == -1 || tmp < min)) {
minidx = i;
@@ -1703,8 +1703,8 @@ int MovGraph2::getItemSubIndexByMGM(int index, StaticANIObject *ani) {
return -1;
}
-bool MovGraph2::initDirections(StaticANIObject *obj, MovGraph2Item *item) {
- debugC(4, kDebugPathfinding, "MovGraph::initDirections(%d, ...)", obj->_id);
+bool MctlGraph::fillData(StaticANIObject *obj, MctlAni *item) {
+ debugC(4, kDebugPathfinding, "MovGraph::fillData(%d, ...)", obj->_id);
item->_obj = obj;
item->_objectId = obj->_id;
@@ -1834,19 +1834,19 @@ bool MovGraph2::initDirections(StaticANIObject *obj, MovGraph2Item *item) {
return true;
}
-void MovGraph2::attachObject(StaticANIObject *obj) {
- debugC(4, kDebugPathfinding, "MovGraph2::attachObject(*%d)", obj->_id);
+void MctlGraph::attachObject(StaticANIObject *obj) {
+ debugC(4, kDebugPathfinding, "MctlGraph::attachObject(*%d)", obj->_id);
MovGraph::attachObject(obj);
- int id = getItemIndexByGameObjectId(obj->_id);
+ int id = getObjIndex(obj->_id);
if (id >= 0) {
_items2[id]->_obj = obj;
} else {
- MovGraph2Item *item = new MovGraph2Item;
+ MctlAni *item = new MctlAni;
- if (initDirections(obj, item)) {
+ if (fillData(obj, item)) {
_items2.push_back(item);
} else {
delete item;
@@ -1854,10 +1854,10 @@ void MovGraph2::attachObject(StaticANIObject *obj) {
}
}
-void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst) {
- debugC(4, kDebugPathfinding, "MovGraph2::buildMovInfo1SubItems(...)");
+void MctlGraph::generateList(MctlMQ *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst) {
+ debugC(4, kDebugPathfinding, "MctlGraph::generateList(...)");
- MovInfo1Sub *elem;
+ MctlMQSub *elem;
Common::Point point;
Common::Rect rect;
@@ -1865,7 +1865,7 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
movinfo->items.clear();
- elem = new MovInfo1Sub;
+ elem = new MctlMQSub;
elem->subIndex = subIndex;
elem->x = movinfo->pt1.x;
elem->y = movinfo->pt1.y;
@@ -1880,9 +1880,9 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
if (linkList->size() <= 1) {
if (linkList->size() == 1)
- idx1 = getShortSide((*linkList)[0], movinfo->pt2.x - movinfo->pt1.x, movinfo->pt2.y - movinfo->pt1.y);
+ idx1 = getDirBySize((*linkList)[0], movinfo->pt2.x - movinfo->pt1.x, movinfo->pt2.y - movinfo->pt1.y);
else
- idx1 = getShortSide(0, movinfo->pt2.x - movinfo->pt1.x, movinfo->pt2.y - movinfo->pt1.y);
+ idx1 = getDirBySize(0, movinfo->pt2.x - movinfo->pt1.x, movinfo->pt2.y - movinfo->pt1.y);
point.y = -1;
rect.bottom = -1;
@@ -1890,14 +1890,14 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
rect.top = -1;
rect.left = -1;
} else {
- idx1 = findLink(linkList, i, &rect, &point);
+ idx1 = getLinkDir(linkList, i, &rect, &point);
}
if (idx1 != prevSubIndex) {
prevSubIndex = idx1;
subIndex = idx1;
- elem = new MovInfo1Sub;
+ elem = new MctlMQSub;
elem->subIndex = subIndex;
elem->x = rect.left;
elem->y = rect.top;
@@ -1909,9 +1909,9 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
if (i != linkList->size() - 1) {
while (1) {
i++;
- if (findLink(linkList, i, &rect, 0) != prevSubIndex) {
+ if (getLinkDir(linkList, i, &rect, 0) != prevSubIndex) {
i--;
- findLink(linkList, i, &rect, &point);
+ getLinkDir(linkList, i, &rect, &point);
break;
}
@@ -1924,7 +1924,7 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
if (movinfo->items.back()->subIndex != 10) {
subIndex = prevSubIndex;
- elem = new MovInfo1Sub;
+ elem = new MctlMQSub;
elem->subIndex = 10;
elem->x = -1;
elem->y = -1;
@@ -1932,8 +1932,8 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
movinfo->items.push_back(elem);
- if (i == linkList->size()) {
- elem = new MovInfo1Sub;
+ if (i == linkList->size() - 1) {
+ elem = new MctlMQSub;
elem->subIndex = prevSubIndex;
elem->x = movinfo->pt2.x;
elem->y = movinfo->pt2.y;
@@ -1941,7 +1941,7 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
movinfo->items.push_back(elem);
} else {
- elem = new MovInfo1Sub;
+ elem = new MctlMQSub;
elem->subIndex = prevSubIndex;
elem->x = rect.right;
elem->y = rect.bottom;
@@ -1953,7 +1953,7 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
}
if (subIndex != movinfo->item1Index) {
- elem = new MovInfo1Sub;
+ elem = new MctlMQSub;
elem->subIndex = movinfo->item1Index;
elem->x = movinfo->pt2.x;
elem->y = movinfo->pt2.y;
@@ -1965,40 +1965,40 @@ void MovGraph2::buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphL
movinfo->itemsCount = movinfo->items.size();
}
-MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) {
- debugC(4, kDebugPathfinding, "MovGraph2::buildMovInfo1MessageQueue(...)");
+MessageQueue *MctlGraph::makeWholeQueue(MctlMQ *mctlMQ) {
+ debugC(4, kDebugPathfinding, "MctlGraph::makeWholeQueue(...)");
- MovInfo1 movinfo(movInfo);
+ MctlMQ movinfo(mctlMQ);
- int curX = movInfo->pt1.x;
- int curY = movInfo->pt1.y;
- int curDistance = movInfo->distance1;
+ int curX = mctlMQ->pt1.x;
+ int curY = mctlMQ->pt1.y;
+ int curDistance = mctlMQ->distance1;
MessageQueue *mq = new MessageQueue(g_fp->_globalMessageQueueList->compact());
- for (int i = 0; i < movInfo->itemsCount - 1; i++) {
- if (movInfo->items[i + 1]->subIndex != 10) {
+ for (int i = 0; i < mctlMQ->itemsCount - 1; i++) {
+ if (mctlMQ->items[i + 1]->subIndex != 10) {
MG2I *mg2i;
- if (i >= movInfo->itemsCount - 2 || movInfo->items[i + 2]->subIndex != 10) {
+ if (i >= mctlMQ->itemsCount - 2 || mctlMQ->items[i + 2]->subIndex != 10) {
movinfo.flags = 0;
- mg2i = &_items2[movInfo->index]->_subItems[movInfo->items[i]->subIndex]._turnS[movInfo->items[i + 1]->subIndex];
+ mg2i = &_items2[mctlMQ->index]->_subItems[mctlMQ->items[i]->subIndex]._turnS[mctlMQ->items[i + 1]->subIndex];
} else {
movinfo.flags = 2;
- mg2i = &_items2[movInfo->index]->_subItems[movInfo->items[i]->subIndex]._turn[movInfo->items[i + 1]->subIndex];
+ mg2i = &_items2[mctlMQ->index]->_subItems[mctlMQ->items[i]->subIndex]._turn[mctlMQ->items[i + 1]->subIndex];
}
- if (i < movInfo->itemsCount - 2
- || (movInfo->items[i]->x == movInfo->items[i + 1]->x
- && movInfo->items[i]->y == movInfo->items[i + 1]->y)
- || movInfo->items[i]->x == -1
- || movInfo->items[i]->y == -1
- || movInfo->items[i + 1]->x == -1
- || movInfo->items[i + 1]->y == -1) {
+ if (i < mctlMQ->itemsCount - 2
+ || (mctlMQ->items[i]->x == mctlMQ->items[i + 1]->x
+ && mctlMQ->items[i]->y == mctlMQ->items[i + 1]->y)
+ || mctlMQ->items[i]->x == -1
+ || mctlMQ->items[i]->y == -1
+ || mctlMQ->items[i + 1]->x == -1
+ || mctlMQ->items[i + 1]->y == -1) {
- ExCommand *ex = new ExCommand(_items2[movInfo->index]->_objectId, 1, mg2i->_movementId, 0, 0, 0, 1, 0, 0, 0);
+ ExCommand *ex = new ExCommand(_items2[mctlMQ->index]->_objectId, 1, mg2i->_movementId, 0, 0, 0, 1, 0, 0, 0);
ex->_excFlags |= 2;
- ex->_keyCode = _items2[movInfo->index]->_obj->_okeyCode;
+ ex->_keyCode = _items2[mctlMQ->index]->_obj->_okeyCode;
ex->_field_24 = 1;
ex->_field_14 = -1;
mq->addExCommandToEnd(ex);
@@ -2006,60 +2006,49 @@ MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) {
curX += mg2i->_mx;
curY += mg2i->_my;
} else {
- MGMInfo mgminfo;
+ MakeQueueStruct mkQueue;
- memset(&mgminfo, 0, sizeof(mgminfo));
+ memset(&mkQueue, 0, sizeof(mkQueue));
- mgminfo.ani = _items2[movInfo->index]->_obj;
- mgminfo.staticsId2 = mg2i->_mov->_staticsObj2->_staticsId;
- mgminfo.x1 = movInfo->items[i + 1]->x;
- mgminfo.y1 = movInfo->items[i + 1]->y;
- mgminfo.field_1C = movInfo->items[i + 1]->distance;
- mgminfo.staticsId1 = mg2i->_mov->_staticsObj1->_staticsId;
+ mkQueue.ani = _items2[mctlMQ->index]->_obj;
+ mkQueue.staticsId2 = mg2i->_mov->_staticsObj2->_staticsId;
+ mkQueue.x1 = mctlMQ->items[i + 1]->x;
+ mkQueue.y1 = mctlMQ->items[i + 1]->y;
+ mkQueue.field_1C = mctlMQ->items[i + 1]->distance;
+ mkQueue.staticsId1 = mg2i->_mov->_staticsObj1->_staticsId;
- mgminfo.x2 = movInfo->items[i]->x;
- mgminfo.y2 = movInfo->items[i]->y;
- mgminfo.field_10 = 1;
- mgminfo.flags = 0x7f;
- mgminfo.movementId = mg2i->_movementId;
+ mkQueue.x2 = mctlMQ->items[i]->x;
+ mkQueue.y2 = mctlMQ->items[i]->y;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 0x7f;
+ mkQueue.movementId = mg2i->_movementId;
- MessageQueue *mq2 = _mgm.genMovement(&mgminfo);
- mq->transferExCommands(mq2);
+ MessageQueue *mq2 = _aniHandler.makeRunQueue(&mkQueue);
+ mq->mergeQueue(mq2);
delete mq2;
- curX = movInfo->items[i + 1]->x;
- curY = movInfo->items[i + 1]->y;
+ curX = mctlMQ->items[i + 1]->x;
+ curY = mctlMQ->items[i + 1]->y;
}
} else {
- movinfo.item1Index = movInfo->items[i]->subIndex;
+ movinfo.item1Index = mctlMQ->items[i]->subIndex;
movinfo.subIndex = movinfo.item1Index;
movinfo.pt1.y = curY;
movinfo.pt1.x = curX;
movinfo.distance1 = curDistance;
- movinfo.pt2.x = movInfo->items[i + 2]->x;
- movinfo.pt2.y = movInfo->items[i + 2]->y;
- movinfo.distance2 = movInfo->items[i + 2]->distance;
-
- if (i >= movInfo->itemsCount - 4
- || movInfo->items[i + 2]->subIndex == 10
- || movInfo->items[i + 3]->subIndex == 10
- || movInfo->items[i + 2]->subIndex == movInfo->items[i + 3]->subIndex
- || movInfo->items[i + 4]->subIndex != 10) {
- if (i >= movInfo->itemsCount - 3
- || movInfo->items[i + 2]->subIndex == 10
- || movInfo->items[i + 3]->subIndex == 10
- || movInfo->items[i + 2]->subIndex == movInfo->items[i + 3]->subIndex) {
- movinfo.flags &= 3;
- } else {
- MG2I *m = &_items2[movInfo->index]->_subItems[movInfo->items[i + 2]->subIndex]._turnS[movInfo->items[i + 3]->subIndex];
- movinfo.pt2.x -= m->_mx;
- movinfo.pt2.y -= m->_my;
- movinfo.flags &= 3;
- }
- } else {
- MG2I *m = &_items2[movInfo->index]->_subItems[movInfo->items[i + 2]->subIndex]._turn[movInfo->items[i + 3]->subIndex];
+ movinfo.pt2.x = mctlMQ->items[i + 2]->x;
+ movinfo.pt2.y = mctlMQ->items[i + 2]->y;
+ movinfo.distance2 = mctlMQ->items[i + 2]->distance;
+
+ if (i < mctlMQ->itemsCount - 4
+ && mctlMQ->items[i + 2]->subIndex != 10
+ && mctlMQ->items[i + 3]->subIndex != 10
+ && mctlMQ->items[i + 2]->subIndex != mctlMQ->items[i + 3]->subIndex
+ && mctlMQ->items[i + 4]->subIndex == 10) {
+
+ MG2I *m = &_items2[mctlMQ->index]->_subItems[mctlMQ->items[i + 2]->subIndex]._turn[mctlMQ->items[i + 3]->subIndex];
if (movinfo.item1Index && movinfo.item1Index != 1) {
movinfo.pt2.y -= m->_my;
@@ -2068,17 +2057,31 @@ MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) {
movinfo.pt2.x -= m->_mx;
movinfo.flags = (movinfo.flags & 2) | 1;
}
+
+ } else if (i < mctlMQ->itemsCount - 3
+ && mctlMQ->items[i + 2]->subIndex != 10
+ && mctlMQ->items[i + 3]->subIndex != 10
+ && mctlMQ->items[i + 2]->subIndex != mctlMQ->items[i + 3]->subIndex) {
+
+ MG2I *m = &_items2[mctlMQ->index]->_subItems[mctlMQ->items[i + 2]->subIndex]._turnS[mctlMQ->items[i + 3]->subIndex];
+ movinfo.pt2.x -= m->_mx;
+ movinfo.pt2.y -= m->_my;
+ movinfo.flags = (movinfo.flags & 2) | (mctlMQ->flags & 1);
+
+ } else {
+ movinfo.flags = (movinfo.flags & 2) | (mctlMQ->flags & 1);
}
+
i++; // intentional
- MessageQueue *mq2 = genMovement(&movinfo);
+ MessageQueue *mq2 = makeLineQueue(&movinfo);
if (!mq2) {
delete mq;
return 0;
}
- mq->transferExCommands(mq2);
+ mq->mergeQueue(mq2);
delete mq2;
@@ -2088,20 +2091,20 @@ MessageQueue *MovGraph2::buildMovInfo1MessageQueue(MovInfo1 *movInfo) {
}
}
- movInfo->pt2.x = movinfo.pt2.x;
- movInfo->pt2.y = movinfo.pt2.y;
+ mctlMQ->pt2.x = movinfo.pt2.x;
+ mctlMQ->pt2.y = movinfo.pt2.y;
return mq;
}
-int MovGraph2::detachObject(StaticANIObject *obj) {
- warning("STUB: MovGraph2::detachObject()");
+int MctlGraph::detachObject(StaticANIObject *obj) {
+ warning("STUB: MctlGraph::detachObject()");
return 0;
}
-void MovGraph2::detachAllObjects() {
- debugC(4, kDebugPathfinding, "MovGraph2::detachAllObjects()");
+void MctlGraph::detachAllObjects() {
+ debugC(4, kDebugPathfinding, "MctlGraph::detachAllObjects()");
for (uint i = 0; i < _items2.size(); i++)
delete _items2[i];
@@ -2109,8 +2112,8 @@ void MovGraph2::detachAllObjects() {
_items2.clear();
}
-MessageQueue *MovGraph2::startMove(StaticANIObject *ani, int xpos, int ypos, int fuzzyMatch, int staticsId) {
- debugC(4, kDebugPathfinding, "MovGraph2::startMove(*%d, %d, %d, %d, %d)", ani->_id, xpos, ypos, fuzzyMatch, staticsId);
+MessageQueue *MctlGraph::startMove(StaticANIObject *ani, int xpos, int ypos, int fuzzyMatch, int staticsId) {
+ debugC(4, kDebugPathfinding, "MctlGraph::startMove(*%d, %d, %d, %d, %d)", ani->_id, xpos, ypos, fuzzyMatch, staticsId);
if (!ani->isIdle())
return 0;
@@ -2119,7 +2122,7 @@ MessageQueue *MovGraph2::startMove(StaticANIObject *ani, int xpos, int ypos, int
return 0;
debugC(1, kDebugPathfinding, "WWW 3");
- MessageQueue *mq = doWalkTo(ani, xpos, ypos, fuzzyMatch, staticsId);
+ MessageQueue *mq = makeQueue(ani, xpos, ypos, fuzzyMatch, staticsId);
if (!mq)
return 0;
@@ -2130,7 +2133,7 @@ MessageQueue *MovGraph2::startMove(StaticANIObject *ani, int xpos, int ypos, int
ani->getPicAniInfo(&picAniInfo);
ani->updateStepPos();
- MessageQueue *mq1 = doWalkTo(ani, xpos, ypos, fuzzyMatch, staticsId);
+ MessageQueue *mq1 = makeQueue(ani, xpos, ypos, fuzzyMatch, staticsId);
ani->setPicAniInfo(&picAniInfo);
@@ -2153,16 +2156,16 @@ MessageQueue *MovGraph2::startMove(StaticANIObject *ani, int xpos, int ypos, int
return mq;
}
-MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int fuzzyMatch, int staticsId) {
+MessageQueue *MctlGraph::makeQueue(StaticANIObject *obj, int xpos, int ypos, int fuzzyMatch, int staticsId) {
LinkInfo linkInfoDest;
LinkInfo linkInfoSource;
- MovInfo1 movInfo1;
+ MctlMQ mctlMQ1;
PicAniInfo picAniInfo;
Common::Point point;
- debugC(1, kDebugPathfinding, "MovGraph2::doWalkTo(%d, %d, %d, %d, %d)", obj->_id, xpos, ypos, fuzzyMatch, staticsId);
+ debugC(1, kDebugPathfinding, "MctlGraph::makeQueue(%d, %d, %d, %d, %d)", obj->_id, xpos, ypos, fuzzyMatch, staticsId);
- int idx = getItemIndexByGameObjectId(obj->_id);
+ int idx = getObjIndex(obj->_id);
if (idx < 0)
return 0;
@@ -2180,15 +2183,15 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int
int idxsub;
if (obj->_movement)
- idxsub = getItemSubIndexByMovementId(idx, obj->_movement->_id);
+ idxsub = getDirByMovement(idx, obj->_movement->_id);
else
- idxsub = getItemSubIndexByStaticsId(idx, obj->_statics->_staticsId);
+ idxsub = getDirByStatics(idx, obj->_statics->_staticsId);
bool subMgm = false;
if (idxsub == -1) {
debugC(1, kDebugPathfinding, "WWW 4");
- idxsub = getItemSubIndexByMGM(idx, obj);
+ idxsub = getDirByPoint(idx, obj);
subMgm = true;
if (idxsub == -1)
@@ -2226,7 +2229,7 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int
MessageQueue *mq = new MessageQueue();
if (staticsId && obj->_statics->_staticsId != staticsId) {
- int idxwalk = getItemSubIndexByStaticsId(idx, staticsId);
+ int idxwalk = getDirByStatics(idx, staticsId);
if (idxwalk == -1) {
obj->setPicAniInfo(&picAniInfo);
@@ -2262,13 +2265,13 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int
return mq;
}
- linkInfoSource.node = findNode(obj->_ox, obj->_oy, 0);
+ linkInfoSource.node = getHitNode(obj->_ox, obj->_oy, 0);
if (!linkInfoSource.node) {
- linkInfoSource.link = findLink1(obj->_ox, obj->_oy, idxsub, 0);
+ linkInfoSource.link = getHitLink(obj->_ox, obj->_oy, idxsub, 0);
if (!linkInfoSource.link) {
- linkInfoSource.link = findLink2(obj->_ox, obj->_oy);
+ linkInfoSource.link = getNearestLink(obj->_ox, obj->_oy);
if (!linkInfoSource.link) {
obj->setPicAniInfo(&picAniInfo);
@@ -2278,10 +2281,10 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int
}
}
- linkInfoDest.node = findNode(xpos, ypos, fuzzyMatch);
+ linkInfoDest.node = getHitNode(xpos, ypos, fuzzyMatch);
if (!linkInfoDest.node) {
- linkInfoDest.link = findLink1(xpos, ypos, idxsub, fuzzyMatch);
+ linkInfoDest.link = getHitLink(xpos, ypos, idxsub, fuzzyMatch);
if (!linkInfoDest.link) {
obj->setPicAniInfo(&picAniInfo);
@@ -2291,78 +2294,78 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int
}
Common::Array<MovGraphLink *> tempLinkList;
- double minPath = findMinPath(&linkInfoSource, &linkInfoDest, &tempLinkList);
+ double minPath = iterate(&linkInfoSource, &linkInfoDest, &tempLinkList);
- debugC(0, kDebugPathfinding, "MovGraph2::doWalkTo(): path: %g parts: %d", minPath, tempLinkList.size());
+ debugC(0, kDebugPathfinding, "MctlGraph::makeQueue(): path: %g parts: %d", minPath, tempLinkList.size());
if (minPath < 0.0 || ((linkInfoSource.node != linkInfoDest.node || !linkInfoSource.node) && !tempLinkList.size()))
return 0;
- movInfo1.clear();
+ mctlMQ1.clear();
- movInfo1.subIndex = idxsub;
- movInfo1.pt1.x = obj->_ox;
- movInfo1.pt1.y = obj->_oy;
+ mctlMQ1.subIndex = idxsub;
+ mctlMQ1.pt1.x = obj->_ox;
+ mctlMQ1.pt1.y = obj->_oy;
int dx1 = obj->_ox;
int dy1 = obj->_oy;
int dx2, dy2;
if (linkInfoSource.node)
- movInfo1.distance1 = linkInfoSource.node->_z;
+ mctlMQ1.distance1 = linkInfoSource.node->_z;
else
- movInfo1.distance1 = linkInfoSource.link->_graphSrc->_z;
+ mctlMQ1.distance1 = linkInfoSource.link->_graphSrc->_z;
if (linkInfoDest.node) {
dx2 = linkInfoDest.node->_x;
dy2 = linkInfoDest.node->_y;
- movInfo1.pt2.x = linkInfoDest.node->_x;
- movInfo1.pt2.y = linkInfoDest.node->_y;
+ mctlMQ1.pt2.x = linkInfoDest.node->_x;
+ mctlMQ1.pt2.y = linkInfoDest.node->_y;
- movInfo1.distance2 = linkInfoDest.node->_z;
+ mctlMQ1.distance2 = linkInfoDest.node->_z;
} else {
- movInfo1.pt2.x = xpos;
- movInfo1.pt2.y = ypos;
+ mctlMQ1.pt2.x = xpos;
+ mctlMQ1.pt2.y = ypos;
MovGraphNode *nod = linkInfoDest.link->_graphSrc;
double dst1 = sqrt((double)((ypos - nod->_y) * (ypos - nod->_y) + (xpos - nod->_x) * (xpos - nod->_x)));
int dst = linkInfoDest.link->_graphDst->_z - nod->_z;
- movInfo1.distance2 = (int)(nod->_z + (dst1 * (double)dst / linkInfoDest.link->_length));
+ mctlMQ1.distance2 = (int)(nod->_z + (dst1 * (double)dst / linkInfoDest.link->_length));
- putToLink(&movInfo1.pt2, linkInfoDest.link, 1);
+ putToLink(&mctlMQ1.pt2, linkInfoDest.link, 1);
- dx1 = movInfo1.pt1.x;
- dy1 = movInfo1.pt1.y;
- dx2 = movInfo1.pt2.x;
- dy2 = movInfo1.pt2.y;
+ dx1 = mctlMQ1.pt1.x;
+ dy1 = mctlMQ1.pt1.y;
+ dx2 = mctlMQ1.pt2.x;
+ dy2 = mctlMQ1.pt2.y;
}
if (staticsId) {
- movInfo1.item1Index = getItemSubIndexByStaticsId(idx, staticsId);
+ mctlMQ1.item1Index = getDirByStatics(idx, staticsId);
} else if (tempLinkList.size() <= 1) {
if (tempLinkList.size() == 1)
- movInfo1.item1Index = getShortSide(tempLinkList[0], dx2 - dx1, dy2 - dy1);
+ mctlMQ1.item1Index = getDirBySize(tempLinkList[0], dx2 - dx1, dy2 - dy1);
else
- movInfo1.item1Index = getShortSide(0, dx2 - dx1, dy2 - dy1);
+ mctlMQ1.item1Index = getDirBySize(0, dx2 - dx1, dy2 - dy1);
} else {
- movInfo1.item1Index = findLink(&tempLinkList, tempLinkList.size() - 1, 0, 0);
+ mctlMQ1.item1Index = getLinkDir(&tempLinkList, tempLinkList.size() - 1, 0, 0);
}
- movInfo1.flags = fuzzyMatch != 0;
+ mctlMQ1.flags = fuzzyMatch != 0;
if (_items2[idx]->_subItems[idxsub]._staticsId1 != obj->_statics->_staticsId)
- movInfo1.flags |= 2;
+ mctlMQ1.flags |= 2;
- buildMovInfo1SubItems(&movInfo1, &tempLinkList, &linkInfoSource, &linkInfoDest);
+ generateList(&mctlMQ1, &tempLinkList, &linkInfoSource, &linkInfoDest);
- MessageQueue *mq = buildMovInfo1MessageQueue(&movInfo1);
+ MessageQueue *mq = makeWholeQueue(&mctlMQ1);
- linkInfoDest.node = findNode(movInfo1.pt2.x, movInfo1.pt2.y, fuzzyMatch);
+ linkInfoDest.node = getHitNode(mctlMQ1.pt2.x, mctlMQ1.pt2.y, fuzzyMatch);
if (!linkInfoDest.node)
- linkInfoDest.link = findLink1(movInfo1.pt2.x, movInfo1.pt2.y, movInfo1.item1Index, fuzzyMatch);
+ linkInfoDest.link = getHitLink(mctlMQ1.pt2.x, mctlMQ1.pt2.y, mctlMQ1.item1Index, fuzzyMatch);
if (fuzzyMatch || linkInfoDest.link || linkInfoDest.node) {
if (mq && mq->getCount() > 0 && picAniInfo.movementId) {
@@ -2397,13 +2400,13 @@ MessageQueue *MovGraph2::doWalkTo(StaticANIObject *obj, int xpos, int ypos, int
return mq;
}
-MovGraphNode *MovGraph2::findNode(int x, int y, int fuzzyMatch) {
+MovGraphNode *MctlGraph::getHitNode(int x, int y, int strictMatch) {
for (ObList::iterator i = _nodes.begin(); i != _nodes.end(); ++i) {
assert(((CObject *)*i)->_objtype == kObjTypeMovGraphNode);
MovGraphNode *node = (MovGraphNode *)*i;
- if (fuzzyMatch) {
+ if (!strictMatch) {
if (abs(node->_x - x) < 15 && abs(node->_y - y) < 15)
return node;
} else {
@@ -2415,7 +2418,7 @@ MovGraphNode *MovGraph2::findNode(int x, int y, int fuzzyMatch) {
return 0;
}
-int MovGraph2::getShortSide(MovGraphLink *lnk, int x, int y) {
+int MctlGraph::getDirBySize(MovGraphLink *lnk, int x, int y) {
bool cond;
if (lnk)
@@ -2429,8 +2432,8 @@ int MovGraph2::getShortSide(MovGraphLink *lnk, int x, int y) {
return ((y > 0) + 2);
}
-int MovGraph2::findLink(Common::Array<MovGraphLink *> *linkList, int idx, Common::Rect *rect, Common::Point *point) {
- debugC(4, kDebugPathfinding, "MovGraph2::findLink(...)");
+int MctlGraph::getLinkDir(Common::Array<MovGraphLink *> *linkList, int idx, Common::Rect *rect, Common::Point *point) {
+ debugC(4, kDebugPathfinding, "MctlGraph::getLinkDir(...)");
MovGraphNode *node1 = (*linkList)[idx]->_graphSrc;
MovGraphNode *node2 = (*linkList)[idx]->_graphDst;
@@ -2473,13 +2476,13 @@ int MovGraph2::findLink(Common::Array<MovGraphLink *> *linkList, int idx, Common
}
if (abs(node3->_x - node2->_x) <= abs(node3->_y - node2->_y))
- return (node3->_y < node2->_x) + 2;
+ return (node3->_y < node2->_y) + 2;
else
return node3->_x >= node2->_x;
}
-MessageQueue *MovGraph2::genMovement(MovInfo1 *info) {
- debugC(4, kDebugPathfinding, "MovGraph2::genMovement(...)");
+MessageQueue *MctlGraph::makeLineQueue(MctlMQ *info) {
+ debugC(4, kDebugPathfinding, "MctlGraph::makeLineQueue(...)");
int mx1 = 0;
int my1 = 0;
@@ -2504,7 +2507,7 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) {
int a2 = 0;
int mgmLen;
- _mgm.calcLength(&point, _items2[info->index]->_subItems[info->subIndex]._walk[1]._mov, x, y, &mgmLen, &a2, info->flags & 1);
+ _aniHandler.getNumCycles(&point, _items2[info->index]->_subItems[info->subIndex]._walk[1]._mov, x, y, &mgmLen, &a2, info->flags & 1);
int x1 = point.x;
int y1 = point.y;
@@ -2619,7 +2622,7 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) {
ex->_excFlags |= 2;
mq->addExCommandToEnd(ex);
- ex = _mgm.buildExCommand2(
+ ex = _aniHandler.createCommand(
_items2[info->index]->_subItems[info->subIndex]._walk[0]._mov,
_items2[info->index]->_objectId,
x1,
@@ -2641,7 +2644,7 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) {
else
par = -1;
- ex = _mgm.buildExCommand2(
+ ex = _aniHandler.createCommand(
_items2[info->index]->_subItems[info->subIndex]._walk[1]._mov,
_items2[info->index]->_objectId,
x1,
@@ -2655,7 +2658,7 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) {
}
if (!(info->flags & 4)) {
- ex = _mgm.buildExCommand2(
+ ex = _aniHandler.createCommand(
_items2[info->index]->_subItems[info->subIndex]._walk[2]._mov,
_items2[info->index]->_objectId,
x1,
@@ -2681,8 +2684,8 @@ MessageQueue *MovGraph2::genMovement(MovInfo1 *info) {
return mq;
}
-MovGraphLink *MovGraph2::findLink1(int x, int y, int idx, int fuzzyMatch) {
- debugC(4, kDebugPathfinding, "MovGraph2::findLink1(...)");
+MovGraphLink *MctlGraph::getHitLink(int x, int y, int idx, int fuzzyMatch) {
+ debugC(4, kDebugPathfinding, "MctlGraph::getHitLink(...)");
Common::Point point;
MovGraphLink *res = 0;
@@ -2717,8 +2720,8 @@ MovGraphLink *MovGraph2::findLink1(int x, int y, int idx, int fuzzyMatch) {
return res;
}
-MovGraphLink *MovGraph2::findLink2(int x, int y) {
- debugC(4, kDebugPathfinding, "MovGraph2::findLink2(...)");
+MovGraphLink *MctlGraph::getNearestLink(int x, int y) {
+ debugC(4, kDebugPathfinding, "MctlGraph::getNearestLink(...)");
double mindist = 1.0e20;
MovGraphLink *res = 0;
@@ -2761,8 +2764,8 @@ MovGraphLink *MovGraph2::findLink2(int x, int y) {
return 0;
}
-double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj) {
- debugC(4, kDebugPathfinding, "MovGraph2::findMinPath(...)");
+double MctlGraph::iterate(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj) {
+ debugC(4, kDebugPathfinding, "MctlGraph::iterate(...)");
LinkInfo linkInfoWorkSource;
@@ -2781,7 +2784,7 @@ double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest,
lnk->_flags |= 0x80000000;
- double newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList);
+ double newDistance = iterate(&linkInfoWorkSource, linkInfoDest, &tmpList);
if (newDistance >= 0.0 && (minDistance < 0.0 || newDistance + lnk->_length < minDistance)) {
listObj->clear();
@@ -2799,7 +2802,7 @@ double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest,
Common::Array<MovGraphLink *> tmpList;
- double newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList);
+ double newDistance = iterate(&linkInfoWorkSource, linkInfoDest, &tmpList);
if (newDistance >= 0.0) {
listObj->clear();
@@ -2815,9 +2818,11 @@ double MovGraph2::findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest,
tmpList.clear();
- newDistance = findMinPath(&linkInfoWorkSource, linkInfoDest, &tmpList);
+ newDistance = iterate(&linkInfoWorkSource, linkInfoDest, &tmpList);
if (newDistance >= 0 && (minDistance < 0.0 || newDistance < minDistance)) {
+ listObj->clear();
+
listObj->push_back(linkInfoSource->link);
listObj->push_back(tmpList);
@@ -2905,7 +2910,7 @@ void MovGraphLink::recalcLength() {
double dy = _graphDst->_y - _graphSrc->_y;
_length = sqrt(dy * dy + dx * dx);
- _angle = atan2(dx, dy);
+ _angle = atan2(dy, dx);
}
}
@@ -2913,9 +2918,9 @@ bool MovGraphNode::load(MfcArchive &file) {
debugC(5, kDebugLoading, "MovGraphNode::load()");
_field_14 = file.readUint32LE();
- _x = file.readUint32LE();
- _y = file.readUint32LE();
- _z = file.readUint32LE();
+ _x = file.readSint32LE();
+ _y = file.readSint32LE();
+ _z = file.readSint32LE();
return true;
}
@@ -2932,12 +2937,12 @@ ReactParallel::ReactParallel() {
bool ReactParallel::load(MfcArchive &file) {
debugC(5, kDebugLoading, "ReactParallel::load()");
- _x1 = file.readUint32LE();
- _y1 = file.readUint32LE();
- _x2 = file.readUint32LE();
- _y2 = file.readUint32LE();
- _dx = file.readUint32LE();
- _dy = file.readUint32LE();
+ _x1 = file.readSint32LE();
+ _y1 = file.readSint32LE();
+ _x2 = file.readSint32LE();
+ _y2 = file.readSint32LE();
+ _dx = file.readSint32LE();
+ _dy = file.readSint32LE();
createRegion();
@@ -2950,7 +2955,7 @@ void ReactParallel::createRegion() {
for (int i = 0; i < 4; i++)
_points[i] = new Common::Point;
- double at = atan2((double)(_x1 - _x2), (double)(_y1 - _y2)) + 1.570796; // pi/2
+ double at = atan2((double)(_y1 - _y2), (double)(_x1 - _x2)) + 1.570796; // pi/2
double sn = sin(at);
double cs = cos(at);
@@ -2990,8 +2995,8 @@ ReactPolygonal::~ReactPolygonal() {
bool ReactPolygonal::load(MfcArchive &file) {
debugC(5, kDebugLoading, "ReactPolygonal::load()");
- _centerX = file.readUint32LE();
- _centerY = file.readUint32LE();
+ _centerX = file.readSint32LE();
+ _centerY = file.readSint32LE();
_pointCount = file.readUint32LE();
if (_pointCount > 0) {
diff --git a/engines/fullpipe/motion.h b/engines/fullpipe/motion.h
index 41860e32d0..93b57c7aaa 100644
--- a/engines/fullpipe/motion.h
+++ b/engines/fullpipe/motion.h
@@ -23,7 +23,7 @@
#ifndef FULLPIPE_MOTION_H
#define FULLPIPE_MOTION_H
-#include "fullpipe/mgm.h"
+#include "fullpipe/anihandler.h"
namespace Fullpipe {
@@ -62,7 +62,7 @@ public:
virtual int method40() { return 0; }
virtual bool canDropInventory(StaticANIObject *ani, int x, int y) { return false; }
virtual int method48() { return -1; }
- virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { return 0; }
+ virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId) { return 0; }
void enableLinks(const char *linkName, bool enable);
MovGraphLink *getLinkByName(const char *name);
@@ -113,9 +113,9 @@ public:
virtual int detachObject(StaticANIObject *obj);
virtual void detachAllObjects();
virtual MessageQueue *startMove(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
- virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
+ virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
- void initMovGraph2();
+ void initMctlGraph();
MctlConnectionPoint *findClosestConnectionPoint(int ox, int oy, int destIndex, int connectionX, int connectionY, int sourceIndex, double *minDistancePtr);
void replaceNodeX(int from, int to);
@@ -149,7 +149,7 @@ public:
int _ladder_field_20;
int _ladder_field_24;
Common::Array<MctlLadderMovement *> _ladmovements;
- MGM _mgm;
+ AniHandler _aniHandler;
public:
MctlLadder();
@@ -160,7 +160,7 @@ public:
virtual int detachObject(StaticANIObject *obj) { return 1; }
virtual void detachAllObjects();
virtual MessageQueue *startMove(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
- virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
+ virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
MessageQueue *controllerWalkTo(StaticANIObject *ani, int off);
@@ -282,7 +282,7 @@ public:
int _field_44;
Common::Array<MovGraphItem *> _items;
MovArr *(*_callback1)(StaticANIObject *ani, Common::Array<MovItem *> *items, signed int counter);
- MGM _mgm;
+ AniHandler _aniHandler;
public:
MovGraph();
@@ -299,7 +299,7 @@ public:
virtual void setSelFunc(MovArr *(*_callback1)(StaticANIObject *ani, Common::Array<MovItem *> *items, signed int counter));
virtual bool resetPosition(StaticANIObject *ani, int flag);
virtual bool canDropInventory(StaticANIObject *ani, int x, int y);
- virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
+ virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
virtual MessageQueue *method50(StaticANIObject *ani, MovArr *movarr, int staticsId);
double putToLink(Common::Point *point, MovGraphLink *link, int fuzzyMatch);
@@ -326,7 +326,7 @@ struct MG2I {
int _my;
};
-struct MovGraph2ItemSub {
+struct MctlAniSub {
int _staticsId2;
int _staticsId1;
MG2I _walk[3];
@@ -339,14 +339,14 @@ struct LinkInfo {
MovGraphNode *node;
};
-struct MovInfo1Sub {
+struct MctlMQSub {
int subIndex;
int x;
int y;
int distance;
};
-struct MovInfo1 {
+struct MctlMQ {
int index;
Common::Point pt1;
Common::Point pt2;
@@ -354,50 +354,50 @@ struct MovInfo1 {
int distance2;
int subIndex;
int item1Index;
- Common::Array<MovInfo1Sub *> items;
+ Common::Array<MctlMQSub *> items;
int itemsCount;
int flags;
- MovInfo1() { clear(); }
- MovInfo1(MovInfo1 *src);
+ MctlMQ() { clear(); }
+ MctlMQ(MctlMQ *src);
void clear();
};
-struct MovGraph2Item { // 744
+struct MctlAni { // 744
int _objectId;
StaticANIObject *_obj;
- MovGraph2ItemSub _subItems[4]; // 184
+ MctlAniSub _subItems[4]; // 184
};
-class MovGraph2 : public MovGraph {
+class MctlGraph : public MovGraph {
public:
- Common::Array<MovGraph2Item *> _items2;
+ Common::Array<MctlAni *> _items2;
public:
virtual void attachObject(StaticANIObject *obj);
virtual int detachObject(StaticANIObject *obj);
virtual void detachAllObjects();
virtual MessageQueue *startMove(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
- virtual MessageQueue *doWalkTo(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
+ virtual MessageQueue *makeQueue(StaticANIObject *subj, int xpos, int ypos, int fuzzyMatch, int staticsId);
- int getItemIndexByGameObjectId(int objectId);
- int getItemSubIndexByStaticsId(int index, int staticsId);
- int getItemSubIndexByMovementId(int index, int movId);
- int getItemSubIndexByMGM(int idx, StaticANIObject *ani);
+ int getObjIndex(int objectId);
+ int getDirByStatics(int index, int staticsId);
+ int getDirByMovement(int index, int movId);
+ int getDirByPoint(int idx, StaticANIObject *ani);
- int getShortSide(MovGraphLink *lnk, int x, int y);
- int findLink(Common::Array<MovGraphLink *> *linkList, int idx, Common::Rect *a3, Common::Point *a4);
+ int getDirBySize(MovGraphLink *lnk, int x, int y);
+ int getLinkDir(Common::Array<MovGraphLink *> *linkList, int idx, Common::Rect *a3, Common::Point *a4);
- bool initDirections(StaticANIObject *obj, MovGraph2Item *item);
- void buildMovInfo1SubItems(MovInfo1 *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst);
- MessageQueue *buildMovInfo1MessageQueue(MovInfo1 *movInfo);
+ bool fillData(StaticANIObject *obj, MctlAni *item);
+ void generateList(MctlMQ *movinfo, Common::Array<MovGraphLink *> *linkList, LinkInfo *lnkSrc, LinkInfo *lnkDst);
+ MessageQueue *makeWholeQueue(MctlMQ *mctlMQ);
- MovGraphNode *findNode(int x, int y, int fuzzyMatch);
- MovGraphLink *findLink1(int x, int y, int idx, int fuzzyMatch);
- MovGraphLink *findLink2(int x, int y);
- double findMinPath(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj);
+ MovGraphNode *getHitNode(int x, int y, int strictMatch);
+ MovGraphLink *getHitLink(int x, int y, int idx, int fuzzyMatch);
+ MovGraphLink *getNearestLink(int x, int y);
+ double iterate(LinkInfo *linkInfoSource, LinkInfo *linkInfoDest, Common::Array<MovGraphLink *> *listObj);
- MessageQueue *genMovement(MovInfo1 *movinfo);
+ MessageQueue *makeLineQueue(MctlMQ *movinfo);
};
class MctlConnectionPoint : public CObject {
diff --git a/engines/fullpipe/objects.h b/engines/fullpipe/objects.h
index 1849bcb96e..f9a641d562 100644
--- a/engines/fullpipe/objects.h
+++ b/engines/fullpipe/objects.h
@@ -61,6 +61,8 @@ struct PicAniInfo {
int32 someDynamicPhaseIndex;
bool load(MfcArchive &file);
+
+ PicAniInfo() { memset(this, 0, sizeof(PicAniInfo)); }
};
union VarValue {
diff --git a/engines/fullpipe/scene.cpp b/engines/fullpipe/scene.cpp
index b47988d768..70f5f1aa81 100644
--- a/engines/fullpipe/scene.cpp
+++ b/engines/fullpipe/scene.cpp
@@ -601,7 +601,7 @@ StaticANIObject *Scene::getStaticANIObjectAtPos(int x, int y) {
if ((p->_field_8 & 0x100) && (p->_flags & 4) &&
p->getPixelAtPos(x, y, &pixel) &&
- (!res || res->_priority >= p->_priority))
+ (!res || res->_priority > p->_priority))
res = p;
}
diff --git a/engines/fullpipe/scenes.cpp b/engines/fullpipe/scenes.cpp
index 32aa955a61..cb9f8c4c01 100644
--- a/engines/fullpipe/scenes.cpp
+++ b/engines/fullpipe/scenes.cpp
@@ -582,7 +582,7 @@ bool FullpipeEngine::sceneSwitcher(EntranceInfo *entrance) {
_aniMan2 = _aniMan;
MctlCompound *cmp = getSc2MctlCompoundBySceneId(entrance->_sceneId);
- cmp->initMovGraph2();
+ cmp->initMctlGraph();
cmp->attachObject(_aniMan);
cmp->activate();
getGameLoaderInteractionController()->enableFlag24();
diff --git a/engines/fullpipe/scenes.h b/engines/fullpipe/scenes.h
index 17ef5c3140..fd90b5f972 100644
--- a/engines/fullpipe/scenes.h
+++ b/engines/fullpipe/scenes.h
@@ -28,7 +28,7 @@ namespace Fullpipe {
struct Bat;
struct BehaviorMove;
struct Hanger;
-class MGM;
+class AniHandler;
class MctlLadder;
struct Ring;
class StaticANIObject;
@@ -396,7 +396,7 @@ public:
StaticANIObject *scene11_boots;
StaticANIObject *scene11_dudeOnSwing;
PictureObject *scene11_hint;
- MGM scene11_mgm;
+ AniHandler scene11_aniHandler;
bool scene11_arcadeIsOn;
bool scene11_scrollIsEnabled;
bool scene11_scrollIsMaximized;
@@ -612,7 +612,7 @@ public:
Common::Array<WalkingBearder *> scene29_bearders;
int scene29_manX;
int scene29_manY;
- MGM scene29_mgm;
+ AniHandler scene29_aniHandler;
StaticANIObject *scene30_leg;
int scene30_liftFlag;
diff --git a/engines/fullpipe/scenes/scene03.cpp b/engines/fullpipe/scenes/scene03.cpp
index e6c9fa3bbd..9c2d5e75cc 100644
--- a/engines/fullpipe/scenes/scene03.cpp
+++ b/engines/fullpipe/scenes/scene03.cpp
@@ -48,6 +48,18 @@ void FullpipeEngine::setSwallowedEggsState() {
}
void scene03_initScene(Scene *sc) {
+ debugC(1, kDebugSceneLogic, "scene03_initScene()");
+
+#if 0
+ Inventory2 *inv = getGameLoaderInventory();
+ inv->addItem(ANI_INV_EGGAPL, 1);
+ inv->addItem(ANI_INV_EGGDOM, 1);
+ inv->addItem(ANI_INV_EGGCOIN, 1);
+ inv->addItem(ANI_INV_EGGBOOT, 1);
+ inv->addItem(ANI_INV_EGGGLS, 1);
+ inv->rebuildItemRects();
+#endif
+
g_vars->scene03_eggeater = sc->getStaticANIObject1ById(ANI_EGGEATER, -1);
g_vars->scene03_domino = sc->getStaticANIObject1ById(ANI_DOMINO_3, -1);
@@ -60,6 +72,9 @@ void scene03_initScene(Scene *sc) {
g_fp->lift_setButton(sO_Level2, ST_LBN_2N);
g_fp->lift_init(sc, QU_SC3_ENTERLIFT, QU_SC3_EXITLIFT);
+
+ debugC(2, kDebugSceneLogic, "scene03: egg1: %d egg2: %d egg3: %d", g_vars->swallowedEgg1->_value.intValue,
+ g_vars->swallowedEgg2->_value.intValue, g_vars->swallowedEgg3->_value.intValue);
}
void scene03_setEaterState() {
@@ -82,18 +97,25 @@ int scene03_updateCursor() {
}
void sceneHandler03_eaterFat() {
+ debugC(2, kDebugSceneLogic, "scene03: eaterFat");
+
g_vars->scene03_eggeater->_flags &= 0xFF7F;
g_vars->scene03_eggeater->startAnim(MV_EGTR_FATASK, 0, -1);
}
void sceneHandler03_swallowEgg(int item) {
+ debugC(2, kDebugSceneLogic, "scene03: swallowEgg");
+
if (!g_vars->swallowedEgg1->_value.intValue) {
g_vars->swallowedEgg1->_value.intValue = item;
+ debugC(2, kDebugSceneLogic, "scene03: setting egg1: %d", g_vars->swallowedEgg1->_value.intValue);
} else if (!g_vars->swallowedEgg2->_value.intValue) {
g_vars->swallowedEgg2->_value.intValue = item;
+ debugC(2, kDebugSceneLogic, "scene03: setting egg2: %d", g_vars->swallowedEgg2->_value.intValue);
} else if (!g_vars->swallowedEgg3->_value.intValue) {
g_vars->swallowedEgg3->_value.intValue = item;
+ debugC(2, kDebugSceneLogic, "scene03: setting egg3: %d", g_vars->swallowedEgg3->_value.intValue);
g_fp->setObjectState(sO_EggGulperGaveCoin, g_fp->getObjectEnumState(sO_EggGulperGaveCoin, sO_Yes));
@@ -102,6 +124,8 @@ void sceneHandler03_swallowEgg(int item) {
}
void sceneHandler03_giveItem(ExCommand *ex) {
+ debugC(2, kDebugSceneLogic, "scene03: giveItem");
+
if (ex->_parentId == ANI_INV_EGGAPL || ex->_parentId == ANI_INV_EGGDOM ||
ex->_parentId == ANI_INV_EGGCOIN || ex->_parentId == ANI_INV_EGGBOOT ||
ex->_parentId == ANI_INV_EGGGLS)
@@ -113,6 +137,8 @@ int sceneHandler03_swallowedEgg1State() {
}
void sceneHandler03_giveCoin(ExCommand *ex) {
+ debugC(2, kDebugSceneLogic, "scene03: giveCoin");
+
MessageQueue *mq = g_fp->_globalMessageQueueList->getMessageQueueById(ex->_parId);
if (mq && mq->getCount() > 0) {
@@ -141,6 +167,8 @@ void sceneHandler03_goLadder() {
}
void sceneHandler03_pushEggStack() {
+ debugC(2, kDebugSceneLogic, "scene03: pushEggStack");
+
g_vars->swallowedEgg1->_value.intValue = g_vars->swallowedEgg2->_value.intValue;
g_vars->swallowedEgg2->_value.intValue = g_vars->swallowedEgg3->_value.intValue;
g_vars->swallowedEgg3->_value.intValue = 0;
@@ -153,12 +181,16 @@ void sceneHandler03_pushEggStack() {
}
void sceneHandler03_releaseEgg() {
+ debugC(2, kDebugSceneLogic, "scene03: releaseEgg");
+
g_vars->scene03_eggeater->_flags &= 0xFF7F;
g_vars->scene03_eggeater->show1(-1, -1, -1, 0);
}
void sceneHandler03_takeEgg(ExCommand *ex) {
+ debugC(2, kDebugSceneLogic, "scene03: taking egg");
+
MessageQueue *mq = g_fp->_globalMessageQueueList->getMessageQueueById(ex->_parId);
if (mq && mq->getCount() > 0) {
@@ -187,6 +219,9 @@ void sceneHandler03_takeEgg(ExCommand *ex) {
}
int sceneHandler03(ExCommand *ex) {
+ if (ex->_messageKind != 17 && ex->_messageNum != 33)
+ debugC(3, kDebugSceneLogic, "scene03: got message: kind %d, num: %d", ex->_messageKind, ex->_messageNum);
+
if (ex->_messageKind != 17) {
if (ex->_messageKind == 57)
sceneHandler03_giveItem(ex);
diff --git a/engines/fullpipe/scenes/scene04.cpp b/engines/fullpipe/scenes/scene04.cpp
index 6c361d6f1a..b6239c219f 100644
--- a/engines/fullpipe/scenes/scene04.cpp
+++ b/engines/fullpipe/scenes/scene04.cpp
@@ -60,6 +60,10 @@ void scene04_speakerCallback(int *phase) {
}
}
+void scene04_springCallback(int *phase) {
+ // do nothing
+}
+
void scene04_initScene(Scene *sc) {
g_vars->scene04_dudeOnLadder = false;
g_vars->scene04_bottle = sc->getPictureObjectById(PIC_SC4_BOTTLE, 0);
@@ -127,7 +131,7 @@ void scene04_initScene(Scene *sc) {
StaticANIObject *spring = sc->getStaticANIObject1ById(ANI_SPRING, -1);
if (spring)
- spring->_callback2 = 0;
+ spring->_callback2 = scene04_springCallback;
g_vars->scene04_bottleObjList.clear();
g_vars->scene04_bottleObjList.push_back(sc->getPictureObjectById(PIC_SC4_BOTTLE, 0));
@@ -395,21 +399,21 @@ void sceneHandler04_jumpOnLadder() {
g_fp->_aniMan->_flags |= 1;
- MGM mgm;
- MGMInfo mgminfo;
+ AniHandler aniHandler;
+ MakeQueueStruct mkQueue;
- mgm.addItem(ANI_MAN);
+ aniHandler.attachObject(ANI_MAN);
- mgminfo.ani = g_fp->_aniMan;
- mgminfo.staticsId2 = ST_MAN_ONPLANK;
- mgminfo.x1 = 938;
- mgminfo.y1 = 442;
- mgminfo.field_1C = 10;
- mgminfo.field_10 = 1;
- mgminfo.flags = 78;
- mgminfo.movementId = MV_MAN_JUMPONPLANK;
+ mkQueue.ani = g_fp->_aniMan;
+ mkQueue.staticsId2 = ST_MAN_ONPLANK;
+ mkQueue.x1 = 938;
+ mkQueue.y1 = 442;
+ mkQueue.field_1C = 10;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 78;
+ mkQueue.movementId = MV_MAN_JUMPONPLANK;
- MessageQueue *mq = mgm.genMovement(&mgminfo);
+ MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue);
if (mq) {
mq->_flags |= 1;
@@ -460,21 +464,21 @@ void sceneHandler04_dropBottle() {
}
void sceneHandler04_gotoLadder(ExCommand *ex) {
- MGM mgm;
- MGMInfo mgminfo;
+ AniHandler aniHandler;
+ MakeQueueStruct mkQueue;
- mgm.addItem(ANI_MAN);
+ aniHandler.attachObject(ANI_MAN);
- mgminfo.ani = g_fp->_aniMan;
- mgminfo.staticsId2 = ST_MAN_UP;
- mgminfo.x1 = 1095;
- mgminfo.y1 = 434;
- mgminfo.field_1C = 12;
- mgminfo.field_10 = 1;
- mgminfo.flags = 78;
- mgminfo.movementId = MV_MAN_PLANKTOLADDER;
+ mkQueue.ani = g_fp->_aniMan;
+ mkQueue.staticsId2 = ST_MAN_UP;
+ mkQueue.x1 = 1095;
+ mkQueue.y1 = 434;
+ mkQueue.field_1C = 12;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 78;
+ mkQueue.movementId = MV_MAN_PLANKTOLADDER;
- MessageQueue *mq = mgm.genMovement(&mgminfo);
+ MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue);
if (mq) {
mq->deleteExCommandByIndex(mq->getCount() - 1, 1);
@@ -550,21 +554,21 @@ void sceneHandler04_raisePlank() {
}
MessageQueue *sceneHandler04_kozFly3(StaticANIObject *ani, double phase) {
- MGM mgm;
- MGMInfo mgminfo;
+ AniHandler aniHandler;
+ MakeQueueStruct mkQueue;
- mgm.addItem(ANI_KOZAWKA);
+ aniHandler.attachObject(ANI_KOZAWKA);
- mgminfo.ani = ani;
- mgminfo.staticsId2 = ST_KZW_SIT;
- mgminfo.x1 = (int)(723.0 - phase * 185.0);
- mgminfo.y1 = 486;
- mgminfo.field_1C = 10;
- mgminfo.field_10 = 1;
- mgminfo.flags = 78;
- mgminfo.movementId = MV_KZW_JUMP;
+ mkQueue.ani = ani;
+ mkQueue.staticsId2 = ST_KZW_SIT;
+ mkQueue.x1 = (int)(723.0 - phase * 185.0);
+ mkQueue.y1 = 486;
+ mkQueue.field_1C = 10;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 78;
+ mkQueue.movementId = MV_KZW_JUMP;
- MessageQueue *mq = mgm.genMovement(&mgminfo);
+ MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue);
if (mq) {
ExCommand *ex = new ExCommand(ANI_KOZAWKA, 1, MV_KZW_STANDUP, 0, 0, 0, 1, 0, 0, 0);
@@ -599,35 +603,35 @@ MessageQueue *sceneHandler04_kozFly3(StaticANIObject *ani, double phase) {
}
MessageQueue *sceneHandler04_kozFly5(StaticANIObject *ani, double phase) {
- MGM mgm;
- MGMInfo mgminfo;
-
- mgm.addItem(ANI_KOZAWKA);
-
- mgminfo.ani = ani;
- mgminfo.staticsId2 = ST_KZW_JUMPOUT;
- mgminfo.x1 = 525;
- mgminfo.y1 = (int)(344.0 - (double)(320 - g_vars->scene04_bottle->_oy) * phase);
- mgminfo.field_1C = 10;
- mgminfo.field_10 = 1;
- mgminfo.flags = 78;
- mgminfo.movementId = MV_KZW_JUMPHIT;
-
- MessageQueue *mq1 = mgm.genMovement(&mgminfo);
-
- memset(&mgminfo, 0, sizeof(mgminfo));
- mgminfo.ani = ani;
- mgminfo.staticsId1 = ST_KZW_JUMPOUT;
- mgminfo.staticsId2 = ST_KZW_SIT;
- mgminfo.x2 = 525;
- mgminfo.y2 = (int)(344.0 - (double)(320 - g_vars->scene04_bottle->_oy) * phase);
- mgminfo.y1 = 486;
- mgminfo.field_1C = 10;
- mgminfo.field_10 = 1;
- mgminfo.flags = 117;
- mgminfo.movementId = MV_KZW_JUMPOUT;
-
- MessageQueue *mq2 = mgm.genMovement(&mgminfo);
+ AniHandler aniHandler;
+ MakeQueueStruct mkQueue;
+
+ aniHandler.attachObject(ANI_KOZAWKA);
+
+ mkQueue.ani = ani;
+ mkQueue.staticsId2 = ST_KZW_JUMPOUT;
+ mkQueue.x1 = 525;
+ mkQueue.y1 = (int)(344.0 - (double)(320 - g_vars->scene04_bottle->_oy) * phase);
+ mkQueue.field_1C = 10;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 78;
+ mkQueue.movementId = MV_KZW_JUMPHIT;
+
+ MessageQueue *mq1 = aniHandler.makeRunQueue(&mkQueue);
+
+ memset(&mkQueue, 0, sizeof(mkQueue));
+ mkQueue.ani = ani;
+ mkQueue.staticsId1 = ST_KZW_JUMPOUT;
+ mkQueue.staticsId2 = ST_KZW_SIT;
+ mkQueue.x2 = 525;
+ mkQueue.y2 = (int)(344.0 - (double)(320 - g_vars->scene04_bottle->_oy) * phase);
+ mkQueue.y1 = 486;
+ mkQueue.field_1C = 10;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 117;
+ mkQueue.movementId = MV_KZW_JUMPOUT;
+
+ MessageQueue *mq2 = aniHandler.makeRunQueue(&mkQueue);
if (mq1 && mq2) {
mq1->addExCommandToEnd(mq2->getExCommandByIndex(0)->createClone());
@@ -670,21 +674,21 @@ MessageQueue *sceneHandler04_kozFly5(StaticANIObject *ani, double phase) {
}
MessageQueue *sceneHandler04_kozFly6(StaticANIObject *ani) {
- MGM mgm;
- MGMInfo mgminfo;
+ AniHandler aniHandler;
+ MakeQueueStruct mkQueue;
- mgm.addItem(ANI_KOZAWKA);
+ aniHandler.attachObject(ANI_KOZAWKA);
- mgminfo.ani = ani;
- mgminfo.staticsId2 = ST_KZW_SIT;
- mgminfo.x1 = 397 - 4 * g_fp->_rnd->getRandomNumber(1);
- mgminfo.field_1C = ani->_priority;
- mgminfo.y1 = g_vars->scene04_bottle->_oy - 4 * g_fp->_rnd->getRandomNumber(1) + 109;
- mgminfo.field_10 = 1;
- mgminfo.flags = 78;
- mgminfo.movementId = MV_KZW_JUMPROTATE;
+ mkQueue.ani = ani;
+ mkQueue.staticsId2 = ST_KZW_SIT;
+ mkQueue.x1 = 397 - 4 * g_fp->_rnd->getRandomNumber(1);
+ mkQueue.field_1C = ani->_priority;
+ mkQueue.y1 = g_vars->scene04_bottle->_oy - 4 * g_fp->_rnd->getRandomNumber(1) + 109;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 78;
+ mkQueue.movementId = MV_KZW_JUMPROTATE;
- MessageQueue *mq = mgm.genMovement(&mgminfo);
+ MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue);
if (mq) {
mq->deleteExCommandByIndex(mq->getCount() - 1, 1);
@@ -728,21 +732,21 @@ void sceneHandler04_kozMove(Movement *mov, int from, int to, Common::Point *poin
}
MessageQueue *sceneHandler04_kozFly7(StaticANIObject *ani, double phase) {
- MGM mgm;
- MGMInfo mgminfo;
+ AniHandler aniHandler;
+ MakeQueueStruct mkQueue;
- mgm.addItem(ANI_KOZAWKA);
+ aniHandler.attachObject(ANI_KOZAWKA);
- mgminfo.ani = ani;
- mgminfo.staticsId2 = 560;
- mgminfo.x1 = (int)(250.0 - phase * 100.0);
- mgminfo.y1 = 455;
- mgminfo.field_1C = 10;
- mgminfo.field_10 = 1;
- mgminfo.flags = 78;
- mgminfo.movementId = MV_KZW_JUMPROTATE;
+ mkQueue.ani = ani;
+ mkQueue.staticsId2 = 560;
+ mkQueue.x1 = (int)(250.0 - phase * 100.0);
+ mkQueue.y1 = 455;
+ mkQueue.field_1C = 10;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 78;
+ mkQueue.movementId = MV_KZW_JUMPROTATE;
- MessageQueue *mq = mgm.genMovement(&mgminfo);
+ MessageQueue *mq = aniHandler.makeRunQueue(&mkQueue);
if (mq) {
sceneHandler04_kozMove(ani->getMovementById(MV_KZW_JUMPROTATE), 1, 9, g_vars->scene04_jumpRotateKozyawki, phase * 0.5 + 1.5);
@@ -949,7 +953,8 @@ void sceneHandler04_springWobble() {
if (g_vars->scene04_bottleWeight < newdelta)
g_vars->scene04_springOffset--;
- if ((oldDynIndex <= g_vars->scene04_bottleWeight && newdelta > g_vars->scene04_bottleWeight) || newdelta <= g_vars->scene04_bottleWeight) {
+ if ((oldDynIndex <= g_vars->scene04_bottleWeight && newdelta > g_vars->scene04_bottleWeight)
+ || (oldDynIndex > g_vars->scene04_bottleWeight && newdelta <= g_vars->scene04_bottleWeight)) {
g_vars->scene04_springDelay++;
if (g_vars->scene04_springOffset && g_vars->scene04_springDelay > 1) {
diff --git a/engines/fullpipe/scenes/scene11.cpp b/engines/fullpipe/scenes/scene11.cpp
index 1fa5cabc15..1a8fa8ed67 100644
--- a/engines/fullpipe/scenes/scene11.cpp
+++ b/engines/fullpipe/scenes/scene11.cpp
@@ -96,7 +96,7 @@ void scene11_setupMusic() {
void scene11_initScene(Scene *sc) {
g_vars->scene11_swingie = sc->getStaticANIObject1ById(ANI_SWINGER, -1);
g_vars->scene11_boots = sc->getStaticANIObject1ById(ANI_BOOTS_11, -1);
- g_vars->scene11_mgm.clear();
+ g_vars->scene11_aniHandler.detachAllObjects();
g_vars->scene11_dudeOnSwing = sc->getStaticANIObject1ById(ANI_MAN11, -1);
g_vars->scene11_dudeOnSwing->_callback2 = scene11_dudeSwingCallback;
g_vars->scene11_dudeOnSwing = sc->getStaticANIObject1ById(ANI_KACHELI, -1);
@@ -251,7 +251,7 @@ void sceneHandler11_manToSwing() {
g_vars->scene11_dudeOnSwing->startAnim(MV_MAN11_SWING_0, 0, -1);
g_vars->scene11_dudeOnSwing->_movement->setDynamicPhaseIndex(45);
- g_vars->scene11_mgm.addItem(g_fp->_aniMan->_id);
+ g_vars->scene11_aniHandler.attachObject(g_fp->_aniMan->_id);
g_fp->_currentScene->_x = 1400 - g_fp->_sceneRect.right;
@@ -385,7 +385,7 @@ void sceneHandler11_emptySwing() {
}
void sceneHandler11_jumpHitAndWin() {
- MGMInfo mgminfo;
+ MakeQueueStruct mkQueue;
sceneHandler11_emptySwing();
@@ -393,16 +393,16 @@ void sceneHandler11_jumpHitAndWin() {
MV_MAN11_JUMPHIT, 0);
g_fp->_aniMan->_priority = 10;
- mgminfo.field_1C = 10;
- mgminfo.ani = g_fp->_aniMan;
- mgminfo.staticsId2 = ST_MAN_1PIX;
- mgminfo.x1 = 1400;
- mgminfo.y1 = 0;
- mgminfo.field_10 = 1;
- mgminfo.flags = 66;
- mgminfo.movementId = MV_MAN11_JUMPHIT;
+ mkQueue.field_1C = 10;
+ mkQueue.ani = g_fp->_aniMan;
+ mkQueue.staticsId2 = ST_MAN_1PIX;
+ mkQueue.x1 = 1400;
+ mkQueue.y1 = 0;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 66;
+ mkQueue.movementId = MV_MAN11_JUMPHIT;
- MessageQueue *mq = g_vars->scene11_mgm.genMovement(&mgminfo);
+ MessageQueue *mq = g_vars->scene11_aniHandler.makeRunQueue(&mkQueue);
if (mq) {
g_vars->scene11_crySound = SND_11_024;
@@ -430,7 +430,7 @@ void sceneHandler11_jumpHitAndWin() {
}
void sceneHandler11_jumpOver(double angle) {
- MGMInfo mgminfo;
+ MakeQueueStruct mkQueue;
sceneHandler11_emptySwing();
@@ -438,16 +438,16 @@ void sceneHandler11_jumpOver(double angle) {
MV_MAN11_JUMPOVER, 0);
g_fp->_aniMan->_priority = 0;
- mgminfo.staticsId2 = ST_MAN_1PIX;
- mgminfo.ani = g_fp->_aniMan;
- mgminfo.x1 = 1163;
- mgminfo.y1 = 837 - (int)(angle * 153.0);
- mgminfo.field_1C = 0;
- mgminfo.field_10 = 1;
- mgminfo.flags = 78;
- mgminfo.movementId = MV_MAN11_JUMPOVER;
+ mkQueue.staticsId2 = ST_MAN_1PIX;
+ mkQueue.ani = g_fp->_aniMan;
+ mkQueue.x1 = 1163;
+ mkQueue.y1 = 837 - (int)(angle * 153.0);
+ mkQueue.field_1C = 0;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 78;
+ mkQueue.movementId = MV_MAN11_JUMPOVER;
- MessageQueue *mq = g_vars->scene11_mgm.genMovement(&mgminfo);
+ MessageQueue *mq = g_vars->scene11_aniHandler.makeRunQueue(&mkQueue);
if (mq) {
g_vars->scene11_crySound = SND_11_022;
@@ -463,7 +463,7 @@ void sceneHandler11_jumpOver(double angle) {
}
void sceneHandler11_jumpHit(double angle) {
- MGMInfo mgminfo;
+ MakeQueueStruct mkQueue;
sceneHandler11_emptySwing();
@@ -478,16 +478,16 @@ void sceneHandler11_jumpHit(double angle) {
MV_MAN11_JUMPOVER, 0);
g_fp->_aniMan->_priority = 0;
- mgminfo.staticsId2 = ST_MAN_1PIX;
- mgminfo.ani = g_fp->_aniMan;
- mgminfo.x1 = 1017 - (int)(angle * -214.0);
- mgminfo.y1 = 700;
- mgminfo.field_1C = 0;
- mgminfo.field_10 = 1;
- mgminfo.flags = 78;
- mgminfo.movementId = MV_MAN11_JUMPHIT;
+ mkQueue.staticsId2 = ST_MAN_1PIX;
+ mkQueue.ani = g_fp->_aniMan;
+ mkQueue.x1 = 1017 - (int)(angle * -214.0);
+ mkQueue.y1 = 700;
+ mkQueue.field_1C = 0;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 78;
+ mkQueue.movementId = MV_MAN11_JUMPHIT;
- MessageQueue *mq = g_vars->scene11_mgm.genMovement(&mgminfo);
+ MessageQueue *mq = g_vars->scene11_aniHandler.makeRunQueue(&mkQueue);
if (mq) {
g_vars->scene11_crySound = SND_11_022;
diff --git a/engines/fullpipe/scenes/scene22.cpp b/engines/fullpipe/scenes/scene22.cpp
index f51469da69..84cd5f9a9a 100644
--- a/engines/fullpipe/scenes/scene22.cpp
+++ b/engines/fullpipe/scenes/scene22.cpp
@@ -239,23 +239,23 @@ void sceneHandler22_stoolLogic(ExCommand *cmd) {
goto LABEL_31;
}
- MGM mgm;
- MGMInfo mgminfo;
-
- mgm.addItem(ANI_MAN);
- mgminfo.ani = g_fp->_aniMan;
- mgminfo.staticsId2 = ST_MAN_RIGHT;
- mgminfo.x1 = 934;
- mgminfo.y1 = 391;
- mgminfo.field_1C = 10;
- mgminfo.staticsId1 = 0x4145;
- mgminfo.x2 = 981;
- mgminfo.y2 = 390;
- mgminfo.field_10 = 1;
- mgminfo.flags = 127;
- mgminfo.movementId = rMV_MAN_TURN_SRL;
-
- mq = mgm.genMovement(&mgminfo);
+ AniHandler mgm;
+ MakeQueueStruct mkQueue;
+
+ mgm.attachObject(ANI_MAN);
+ mkQueue.ani = g_fp->_aniMan;
+ mkQueue.staticsId2 = ST_MAN_RIGHT;
+ mkQueue.x1 = 934;
+ mkQueue.y1 = 391;
+ mkQueue.field_1C = 10;
+ mkQueue.staticsId1 = 0x4145;
+ mkQueue.x2 = 981;
+ mkQueue.y2 = 390;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = 127;
+ mkQueue.movementId = rMV_MAN_TURN_SRL;
+
+ mq = mgm.makeRunQueue(&mkQueue);
ExCommand *ex = mq->getExCommandByIndex(0);
diff --git a/engines/fullpipe/scenes/scene27.cpp b/engines/fullpipe/scenes/scene27.cpp
index 9570d30913..b23f29ad4c 100644
--- a/engines/fullpipe/scenes/scene27.cpp
+++ b/engines/fullpipe/scenes/scene27.cpp
@@ -331,7 +331,7 @@ void sceneHandler27_aimDude() {
void sceneHandler27_wipeDo() {
for (uint i = 0; i < g_vars->scene27_bats.size(); i++) {
if (g_vars->scene27_bats[i]->currX < 800.0) {
- g_vars->scene27_bats[i]->field_10 = atan2(800.0 - g_vars->scene27_bats[i]->currX, 520.0 - g_vars->scene27_bats[i]->currY);
+ g_vars->scene27_bats[i]->field_10 = atan2(520.0 - g_vars->scene27_bats[i]->currY, 800.0 - g_vars->scene27_bats[i]->currX);
g_vars->scene27_bats[i]->power += 1.0;
}
}
@@ -360,7 +360,7 @@ bool sceneHandler27_batFallLogic(uint batn) {
}
bool sceneHandler27_batCalcDistance(int bat1, int bat2) {
- double at = atan2(g_vars->scene27_bats[bat1]->currX - g_vars->scene27_bats[bat2]->currX, g_vars->scene27_bats[bat1]->currY - g_vars->scene27_bats[bat2]->currY);
+ double at = atan2(g_vars->scene27_bats[bat1]->currY - g_vars->scene27_bats[bat2]->currY, g_vars->scene27_bats[bat1]->currX - g_vars->scene27_bats[bat2]->currX);
double dy = g_vars->scene27_bats[bat1]->currY - g_vars->scene27_bats[bat2]->currY;
double dx = g_vars->scene27_bats[bat1]->currX - g_vars->scene27_bats[bat2]->currX;
double ay = cos(at);
@@ -375,7 +375,7 @@ void sceneHandler27_knockBats(int bat1n, int bat2n) {
if (0.0 != bat1->power) {
double rndF = (double)g_fp->_rnd->getRandomNumber(32767) * 0.0000009155552842799158 - 0.015
- + atan2(bat2->currX - bat1->currX, bat2->currY - bat1->currY);
+ + atan2(bat2->currY - bat1->currY, bat2->currX - bat1->currX);
double rndCos = cos(rndF);
double rndSin = sin(rndF);
@@ -386,7 +386,7 @@ void sceneHandler27_knockBats(int bat1n, int bat2n) {
bat1->powerSin -= pow1y * 1.1;
rndF = ((double)g_fp->_rnd->getRandomNumber(32767) * 0.0000009155552842799158 - 0.015
- + atan2(bat1->currX - bat2->currX, bat1->currY - bat2->currY));
+ + atan2(bat1->currY - bat2->currY, bat1->currX - bat2->currX));
double pow2x = cos(bat2->field_10 - rndF) * (double)((int)(bat1->currX - bat2->currX) >= 0 ? 1 : -1) * bat2->power;
double pow2y = sin(bat2->field_10 - rndF) * (double)((int)(bat1->currY - bat2->currY) >= 0 ? 1 : -1) * bat2->power;
@@ -405,7 +405,7 @@ void sceneHandler27_knockBats(int bat1n, int bat2n) {
else
bat1->powerSin += pow2y * 0.64;
- bat1->field_10 = atan2(bat1->powerCos, bat1->powerSin);
+ bat1->field_10 = atan2(bat1->powerSin, bat1->powerCos);
bat1->power = sqrt(bat1->powerCos * bat1->powerCos + bat1->powerSin * bat1->powerSin);
bat2->powerCos += pow1x * 0.64;
@@ -414,7 +414,7 @@ void sceneHandler27_knockBats(int bat1n, int bat2n) {
else
bat2->powerSin += pow1y * 0.64;
- bat2->field_10 = atan2(bat2->powerCos, bat2->powerSin);
+ bat2->field_10 = atan2(bat2->powerSin, bat2->powerCos);
bat2->power = sqrt(bat2->powerCos * bat2->powerCos + bat2->powerSin * bat2->powerSin);
g_fp->playSound(SND_27_026, 0);
diff --git a/engines/fullpipe/scenes/scene29.cpp b/engines/fullpipe/scenes/scene29.cpp
index a03671a4d0..28d06964a9 100644
--- a/engines/fullpipe/scenes/scene29.cpp
+++ b/engines/fullpipe/scenes/scene29.cpp
@@ -487,23 +487,23 @@ bool sceneHandler29_checkGreenBallHit(StaticANIObject *ani, int maxx) {
}
void sceneHandler29_manHit() {
- MGMInfo mgminfo;
+ MakeQueueStruct mkQueue;
g_vars->scene29_manIsHit = true;
g_fp->_aniMan->changeStatics2(ST_MAN29_RUNR);
g_fp->_aniMan->setOXY(g_vars->scene29_manX, g_vars->scene29_manY);
- mgminfo.ani = g_fp->_aniMan;
- mgminfo.staticsId2 = ST_MAN29_SITR;
- mgminfo.y1 = 463;
- mgminfo.x1 = g_vars->scene29_manX <= 638 ? 351 : 0;
- mgminfo.field_1C = 10;
- mgminfo.field_10 = 1;
- mgminfo.flags = (g_vars->scene29_manX <= 638 ? 2 : 0) | 0x44;
- mgminfo.movementId = MV_MAN29_HIT;
+ mkQueue.ani = g_fp->_aniMan;
+ mkQueue.staticsId2 = ST_MAN29_SITR;
+ mkQueue.y1 = 463;
+ mkQueue.x1 = g_vars->scene29_manX <= 638 ? 351 : 0;
+ mkQueue.field_1C = 10;
+ mkQueue.field_10 = 1;
+ mkQueue.flags = (g_vars->scene29_manX <= 638 ? 2 : 0) | 0x44;
+ mkQueue.movementId = MV_MAN29_HIT;
- MessageQueue *mq = g_vars->scene29_mgm.genMovement(&mgminfo);
+ MessageQueue *mq = g_vars->scene29_aniHandler.makeRunQueue(&mkQueue);
ExCommand *ex;
if (mq) {
@@ -728,7 +728,7 @@ void sceneHandler29_manToL() {
g_vars->scene29_arcadeIsOn = true;
- g_vars->scene29_mgm.addItem(g_fp->_aniMan->_id);
+ g_vars->scene29_aniHandler.attachObject(g_fp->_aniMan->_id);
g_fp->_updateScreenCallback = sceneHandler29_updateScreenCallback;
diff --git a/engines/fullpipe/stateloader.cpp b/engines/fullpipe/stateloader.cpp
index c95f6c67f3..02053aa94e 100644
--- a/engines/fullpipe/stateloader.cpp
+++ b/engines/fullpipe/stateloader.cpp
@@ -350,8 +350,8 @@ bool PicAniInfo::load(MfcArchive &file) {
field_8 = file.readUint32LE();
sceneId = file.readUint16LE();
field_E = file.readUint16LE();
- ox = file.readUint32LE();
- oy = file.readUint32LE();
+ ox = file.readSint32LE();
+ oy = file.readSint32LE();
priority = file.readUint32LE();
staticsId = file.readUint16LE();
movementId = file.readUint16LE();
diff --git a/engines/fullpipe/statics.cpp b/engines/fullpipe/statics.cpp
index 14245d5eaf..1e43720c32 100644
--- a/engines/fullpipe/statics.cpp
+++ b/engines/fullpipe/statics.cpp
@@ -69,7 +69,8 @@ Common::Point *StepArray::getCurrPoint(Common::Point *point) {
point->x = 0;
point->y = 0;
} else {
- point = _points[_currPointIndex];
+ point->x = _points[_currPointIndex]->x;
+ point->y = _points[_currPointIndex]->y;
}
return point;
}
@@ -96,7 +97,7 @@ Common::Point *StepArray::getPoint(Common::Point *point, int index, int offset)
}
bool StepArray::gotoNextPoint() {
- if (_currPointIndex < _maxPointIndex) {
+ if (_currPointIndex < _maxPointIndex - 1) {
_currPointIndex++;
return true;
} else {
@@ -107,20 +108,22 @@ bool StepArray::gotoNextPoint() {
void StepArray::insertPoints(Common::Point **points, int pointsCount) {
if (_currPointIndex + pointsCount >= _pointsCount) {
- _points = (Common::Point **)realloc(_points, sizeof(Common::Point *) * (_currPointIndex + pointsCount));
+ _points = (Common::Point **)realloc(_points, sizeof(Common::Point *) * (_pointsCount + pointsCount));
if (!_points) {
error("Out of memory at StepArray::insertPoints()");
}
+
+ for(int i = 0; i < pointsCount; i++)
+ _points[_pointsCount + i] = new Common::Point;
+
+ _pointsCount += pointsCount;
}
_maxPointIndex = _currPointIndex + pointsCount;
- for (int i = 0; i < pointsCount; i++) {
- _points[_currPointIndex + i] = new Common::Point;
-
+ for (int i = 0; i < pointsCount; i++)
*_points[_currPointIndex + i] = *points[i];
- }
}
StaticANIObject::StaticANIObject() {
@@ -156,7 +159,7 @@ StaticANIObject::~StaticANIObject() {
_movements.clear();
- g_fp->_mgm->clear();
+ g_fp->_aniHandler->detachAllObjects();
}
StaticANIObject::StaticANIObject(StaticANIObject *src) : GameObject(src) {
@@ -830,7 +833,7 @@ void StaticANIObject::update(int counterdiff) {
}
}
- if (dyn->_initialCountdown != dyn->_countdown || dyn->_field_68 == 0) {
+ if (dyn->_initialCountdown == dyn->_countdown && dyn->_field_68 != 0) {
newex = new ExCommand(_id, 17, dyn->_field_68, 0, 0, 0, 1, 0, 0, 0);
newex->_excFlags = 2;
newex->_keyCode = _okeyCode;
@@ -856,15 +859,15 @@ void StaticANIObject::update(int counterdiff) {
}
}
}
- if (!_movement)
- return;
-
- _stepArray.getCurrPoint(&point);
- setOXY(point.x + _ox, point.y + _oy);
- _stepArray.gotoNextPoint();
- if (_someDynamicPhaseIndex == _movement->_currDynamicPhaseIndex)
- adjustSomeXY();
}
+ if (!_movement)
+ return;
+
+ _stepArray.getCurrPoint(&point);
+ setOXY(point.x + _ox, point.y + _oy);
+ _stepArray.gotoNextPoint();
+ if (_someDynamicPhaseIndex == _movement->_currDynamicPhaseIndex)
+ adjustSomeXY();
} else if (_flags & 0x20) {
_flags ^= 0x20;
_flags |= 1;
@@ -951,7 +954,7 @@ Common::Point *StaticANIObject::calcNextStep(Common::Point *pRes) {
}
void StaticANIObject::stopAnim_maybe() {
- debugC(6, kDebugAnimation, "StaticANIObject::stopAnim_maybe()");
+ debugC(2, kDebugAnimation, "StaticANIObject::stopAnim_maybe()");
if (!(_flags & 1))
return;
@@ -966,7 +969,10 @@ void StaticANIObject::stopAnim_maybe() {
setOXY(_movement->_ox, _movement->_oy);
if (_flags & 0x40) {
- if (!_movement->_currMovement && !_movement->_currDynamicPhaseIndex) {
+ if (!_movement->_currMovement) {
+ if (_movement->_currDynamicPhaseIndex)
+ goto L11;
+L8:
_statics = _movement->_staticsObj1;
_movement->getCurrDynamicPhaseXY(point);
_ox -= point.x;
@@ -984,13 +990,14 @@ void StaticANIObject::stopAnim_maybe() {
_ox += point.x;
_oy += point.y;
}
- } else {
- _statics = _movement->_staticsObj2;
+ goto L12;
}
- } else {
- _statics = _movement->_staticsObj2;
+ if (!_movement->_currDynamicPhaseIndex)
+ goto L8;
}
-
+L11:
+ _statics = _movement->_staticsObj2;
+L12:
_statics->getSomeXY(point);
_statics->_x = _ox - point.x;
@@ -1040,9 +1047,9 @@ void StaticANIObject::adjustSomeXY() {
}
MessageQueue *StaticANIObject::changeStatics1(int msgNum) {
- g_fp->_mgm->addItem(_id);
+ g_fp->_aniHandler->attachObject(_id);
- MessageQueue *mq = g_fp->_mgm->genMQ(this, msgNum, 0, 0, 0);
+ MessageQueue *mq = g_fp->_aniHandler->makeQueue(this, msgNum, 0, 0, 0);
if (!mq)
return 0;
@@ -1071,8 +1078,8 @@ void StaticANIObject::changeStatics2(int objId) {
deleteFromGlobalMessageQueue();
if (_movement || _statics) {
- g_fp->_mgm->addItem(_id);
- g_fp->_mgm->updateAnimStatics(this, objId);
+ g_fp->_aniHandler->attachObject(_id);
+ g_fp->_aniHandler->putObjectToStatics(this, objId);
} else {
_statics = getStaticsById(objId);
}
@@ -1696,8 +1703,8 @@ bool Movement::load(MfcArchive &file, StaticANIObject *ani) {
_staticsObj1 = ani->addReverseStatics(s);
}
- _mx = file.readUint32LE();
- _my = file.readUint32LE();
+ _mx = file.readSint32LE();
+ _my = file.readSint32LE();
staticsid = file.readUint16LE();
@@ -1708,8 +1715,8 @@ bool Movement::load(MfcArchive &file, StaticANIObject *ani) {
_staticsObj2 = ani->addReverseStatics(s);
}
- _m2x = file.readUint32LE();
- _m2y = file.readUint32LE();
+ _m2x = file.readSint32LE();
+ _m2y = file.readSint32LE();
if (_staticsObj2) {
_dynamicPhases.push_back(_staticsObj2);
@@ -2147,11 +2154,17 @@ void Movement::gotoFirstFrame() {
void Movement::gotoLastFrame() {
if (_currMovement) {
- while ((uint)_currDynamicPhaseIndex != _currMovement->_dynamicPhases.size() - 1)
- gotoNextFrame(0, 0);
+ if ((uint)_currDynamicPhaseIndex != _currMovement->_dynamicPhases.size() - 1) {
+ do {
+ gotoNextFrame(0, 0);
+ } while ((uint)_currDynamicPhaseIndex != _currMovement->_dynamicPhases.size() - 1);
+ }
} else {
- while ((uint)_currDynamicPhaseIndex != _dynamicPhases.size() - 1)
- gotoNextFrame(0, 0);
+ if ((uint)_currDynamicPhaseIndex != _dynamicPhases.size() - 1) {
+ do {
+ gotoNextFrame(0, 0);
+ } while ((uint)_currDynamicPhaseIndex != _dynamicPhases.size() - 1);
+ }
}
}
@@ -2260,17 +2273,17 @@ bool DynamicPhase::load(MfcArchive &file) {
_field_7C = file.readUint16LE();
_rect = new Common::Rect();
- _rect->left = file.readUint32LE();
- _rect->top = file.readUint32LE();
- _rect->right = file.readUint32LE();
- _rect->bottom = file.readUint32LE();
+ _rect->left = file.readSint32LE();
+ _rect->top = file.readSint32LE();
+ _rect->right = file.readSint32LE();
+ _rect->bottom = file.readSint32LE();
- assert (g_fp->_gameProjectVersion >= 1);
+ assert(g_fp->_gameProjectVersion >= 1);
- _someX = file.readUint32LE();
- _someY = file.readUint32LE();
+ _someX = file.readSint32LE();
+ _someY = file.readSint32LE();
- assert (g_fp->_gameProjectVersion >= 12);
+ assert(g_fp->_gameProjectVersion >= 12);
_dynFlags = file.readUint32LE();
diff --git a/engines/fullpipe/utils.cpp b/engines/fullpipe/utils.cpp
index a8e00468b5..148f779d1e 100644
--- a/engines/fullpipe/utils.cpp
+++ b/engines/fullpipe/utils.cpp
@@ -84,7 +84,7 @@ bool DWordArray::load(MfcArchive &file) {
resize(count);
for (int i = 0; i < count; i++) {
- int32 t = file.readUint32LE();
+ int32 t = file.readSint32LE();
push_back(t);
}
diff --git a/engines/gnap/detection.cpp b/engines/gnap/detection.cpp
index 7e4ab56d1f..d92a037232 100644
--- a/engines/gnap/detection.cpp
+++ b/engines/gnap/detection.cpp
@@ -89,7 +89,8 @@ bool GnapMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
- (f == kSavesSupportCreationDate);
+ (f == kSavesSupportCreationDate) ||
+ (f == kSimpleSavesNames);
}
bool Gnap::GnapEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/hopkins/detection.cpp b/engines/hopkins/detection.cpp
index cfdbf8030c..041afecaa8 100644
--- a/engines/hopkins/detection.cpp
+++ b/engines/hopkins/detection.cpp
@@ -128,7 +128,8 @@ bool HopkinsMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
- (f == kSavesSupportThumbnail);
+ (f == kSavesSupportThumbnail) ||
+ (f == kSimpleSavesNames);
}
bool Hopkins::HopkinsEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/kyra/animator_tim.cpp b/engines/kyra/animator_tim.cpp
index 1d65ba7b1a..b1cfc6a6a8 100644
--- a/engines/kyra/animator_tim.cpp
+++ b/engines/kyra/animator_tim.cpp
@@ -202,6 +202,9 @@ void TimAnimator::playPart(int animIndex, int firstFrame, int lastFrame, int del
return;
Animation *anim = &_animations[animIndex];
+ // WORKAROUND for some bugged scripts that will try to play invalid animations
+ if (!anim->wsa)
+ return;
int step = (lastFrame >= firstFrame) ? 1 : -1;
for (int i = firstFrame; i != (lastFrame + step); i += step) {
diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp
index 989a45b420..70c7e7c93c 100644
--- a/engines/kyra/detection.cpp
+++ b/engines/kyra/detection.cpp
@@ -173,7 +173,8 @@ bool KyraMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
- (f == kSavesSupportThumbnail);
+ (f == kSavesSupportThumbnail) ||
+ (f == kSimpleSavesNames);
}
bool Kyra::KyraEngine_v1::hasFeature(EngineFeature f) const {
diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h
index c801829855..4de7494510 100644
--- a/engines/kyra/kyra_v1.h
+++ b/engines/kyra/kyra_v1.h
@@ -38,6 +38,7 @@
#include "kyra/item.h"
namespace Common {
+class OutSaveFile;
class SeekableReadStream;
class WriteStream;
} // End of namespace Common
@@ -423,7 +424,7 @@ protected:
virtual Common::Error saveGameStateIntern(int slot, const char *saveName, const Graphics::Surface *thumbnail) = 0;
Common::SeekableReadStream *openSaveForReading(const char *filename, SaveHeader &header, bool checkID = true);
- Common::WriteStream *openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const;
+ Common::OutSaveFile *openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const;
// TODO: Consider moving this to Screen
virtual Graphics::Surface *generateSaveThumbnail() const { return 0; }
diff --git a/engines/kyra/saveload.cpp b/engines/kyra/saveload.cpp
index 2ae6420bd7..81ea796fe9 100644
--- a/engines/kyra/saveload.cpp
+++ b/engines/kyra/saveload.cpp
@@ -184,7 +184,7 @@ Common::SeekableReadStream *KyraEngine_v1::openSaveForReading(const char *filena
return in;
}
-Common::WriteStream *KyraEngine_v1::openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const {
+Common::OutSaveFile *KyraEngine_v1::openSaveForWriting(const char *filename, const char *saveName, const Graphics::Surface *thumbnail) const {
if (shouldQuit())
return 0;
@@ -226,7 +226,7 @@ Common::WriteStream *KyraEngine_v1::openSaveForWriting(const char *filename, con
delete genThumbnail;
}
- return out;
+ return new Common::OutSaveFile(out);
}
const char *KyraEngine_v1::getSavegameFilename(int num) {
diff --git a/engines/kyra/saveload_eob.cpp b/engines/kyra/saveload_eob.cpp
index cca8f3a0a4..9329d14255 100644
--- a/engines/kyra/saveload_eob.cpp
+++ b/engines/kyra/saveload_eob.cpp
@@ -995,7 +995,7 @@ bool EoBCoreEngine::saveAsOriginalSaveFile(int slot) {
return false;
Common::FSNode nf = nd.getChild(_flags.gameID == GI_EOB1 ? "EOBDATA.SAV" : Common::String::format("EOBDATA%d.SAV", slot));
- Common::WriteStream *out = nf.createWriteStream();
+ Common::OutSaveFile *out = new Common::OutSaveFile(nf.createWriteStream());
if (_flags.gameID == GI_EOB2) {
static const char tempStr[20] = "SCUMMVM EXPORT ";
diff --git a/engines/lab/detection.cpp b/engines/lab/detection.cpp
index 30890b5acf..bf6d4563b5 100644
--- a/engines/lab/detection.cpp
+++ b/engines/lab/detection.cpp
@@ -151,7 +151,8 @@ bool LabMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
- (f == kSavesSupportPlayTime);
+ (f == kSavesSupportPlayTime) ||
+ (f == kSimpleSavesNames);
}
bool Lab::LabEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/mads/detection.cpp b/engines/mads/detection.cpp
index 4736503a38..4fb8b82eb3 100644
--- a/engines/mads/detection.cpp
+++ b/engines/mads/detection.cpp
@@ -166,7 +166,8 @@ bool MADSMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
- (f == kSavesSupportThumbnail);
+ (f == kSavesSupportThumbnail) ||
+ (f == kSimpleSavesNames);
}
bool MADS::MADSEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/metaengine.h b/engines/metaengine.h
index e7bfebab71..568b66ac51 100644
--- a/engines/metaengine.h
+++ b/engines/metaengine.h
@@ -236,7 +236,19 @@ public:
* the game till the save.
* This flag may only be set when 'kSavesSupportMetaInfo' is set.
*/
- kSavesSupportPlayTime
+ kSavesSupportPlayTime,
+
+ /**
+ * Feature is available if engine's saves could be detected
+ * with "<target>.###" pattern and "###" corresponds to slot
+ * number.
+ *
+ * If that's not true or engine is using some unusual way
+ * of detecting saves and slot numbers, this should be
+ * unavailable. In that case Save/Load dialog for engine's
+ * games is locked during cloud saves sync.
+ */
+ kSimpleSavesNames
};
/**
diff --git a/engines/mohawk/console.cpp b/engines/mohawk/console.cpp
index fd79e53b07..f08eee9677 100644
--- a/engines/mohawk/console.cpp
+++ b/engines/mohawk/console.cpp
@@ -42,6 +42,7 @@
#ifdef ENABLE_RIVEN
#include "mohawk/riven.h"
#include "mohawk/riven_external.h"
+#include "mohawk/riven_sound.h"
#endif
namespace Mohawk {
diff --git a/engines/mohawk/cstime.cpp b/engines/mohawk/cstime.cpp
index 3b26378819..b2889be714 100644
--- a/engines/mohawk/cstime.cpp
+++ b/engines/mohawk/cstime.cpp
@@ -54,6 +54,7 @@ MohawkEngine_CSTime::MohawkEngine_CSTime(OSystem *syst, const MohawkGameDescript
_console = 0;
_gfx = 0;
+ _sound = 0;
_cursor = 0;
_interface = 0;
_view = 0;
@@ -66,6 +67,7 @@ MohawkEngine_CSTime::~MohawkEngine_CSTime() {
delete _interface;
delete _view;
delete _console;
+ delete _sound;
delete _gfx;
delete _rnd;
}
@@ -75,6 +77,7 @@ Common::Error MohawkEngine_CSTime::run() {
_console = new CSTimeConsole(this);
_gfx = new CSTimeGraphics(this);
+ _sound = new Sound(this);
_cursor = new DefaultCursorManager(this, ID_CURS);
_interface = new CSTimeInterface(this);
diff --git a/engines/mohawk/cstime.h b/engines/mohawk/cstime.h
index bfb7daf945..393032aaa9 100644
--- a/engines/mohawk/cstime.h
+++ b/engines/mohawk/cstime.h
@@ -136,6 +136,7 @@ public:
Common::RandomSource *_rnd;
+ Sound *_sound;
CSTimeGraphics *_gfx;
bool _needsUpdate;
diff --git a/engines/mohawk/livingbooks.cpp b/engines/mohawk/livingbooks.cpp
index 5af8fac901..579e3792b3 100644
--- a/engines/mohawk/livingbooks.cpp
+++ b/engines/mohawk/livingbooks.cpp
@@ -144,6 +144,7 @@ MohawkEngine_LivingBooks::MohawkEngine_LivingBooks(OSystem *syst, const MohawkGa
_rnd = new Common::RandomSource("livingbooks");
+ _sound = NULL;
_page = NULL;
const Common::FSNode gameDataDir(ConfMan.get("path"));
@@ -158,6 +159,7 @@ MohawkEngine_LivingBooks::~MohawkEngine_LivingBooks() {
destroyPage();
delete _console;
+ delete _sound;
delete _gfx;
delete _rnd;
_bookInfoFile.clear();
@@ -182,6 +184,7 @@ Common::Error MohawkEngine_LivingBooks::run() {
error("Could not find xRes/yRes variables");
_gfx = new LBGraphics(this, _screenWidth, _screenHeight);
+ _sound = new Sound(this);
if (getGameType() != GType_LIVINGBOOKSV1)
_cursor = new LivingBooksCursorManager_v2();
diff --git a/engines/mohawk/livingbooks.h b/engines/mohawk/livingbooks.h
index 1a265a1a02..cf67c1ee8e 100644
--- a/engines/mohawk/livingbooks.h
+++ b/engines/mohawk/livingbooks.h
@@ -714,6 +714,7 @@ public:
Common::RandomSource *_rnd;
+ Sound *_sound;
LBGraphics *_gfx;
bool _needsRedraw, _needsUpdate;
diff --git a/engines/mohawk/module.mk b/engines/mohawk/module.mk
index 83e541e3e4..3fc118d2b6 100644
--- a/engines/mohawk/module.mk
+++ b/engines/mohawk/module.mk
@@ -57,6 +57,7 @@ MODULE_OBJS += \
riven_graphics.o \
riven_saveload.o \
riven_scripts.o \
+ riven_sound.o \
riven_vars.o
endif
diff --git a/engines/mohawk/mohawk.cpp b/engines/mohawk/mohawk.cpp
index d740d9479a..b38409f9f1 100644
--- a/engines/mohawk/mohawk.cpp
+++ b/engines/mohawk/mohawk.cpp
@@ -40,14 +40,12 @@ MohawkEngine::MohawkEngine(OSystem *syst, const MohawkGameDescription *gamedesc)
// Setup mixer
syncSoundSettings();
- _sound = 0;
_video = 0;
_pauseDialog = 0;
_cursor = 0;
}
MohawkEngine::~MohawkEngine() {
- delete _sound;
delete _video;
delete _pauseDialog;
delete _cursor;
@@ -58,7 +56,6 @@ MohawkEngine::~MohawkEngine() {
}
Common::Error MohawkEngine::run() {
- _sound = new Sound(this);
_video = new VideoManager(this);
_pauseDialog = new PauseDialog(this, "The game is paused. Press any key to continue.");
@@ -66,14 +63,12 @@ Common::Error MohawkEngine::run() {
}
void MohawkEngine::pauseEngineIntern(bool pause) {
+ Engine::pauseEngineIntern(pause);
+
if (pause) {
_video->pauseVideos();
- _sound->pauseSound();
- _sound->pauseSLST();
} else {
_video->resumeVideos();
- _sound->resumeSound();
- _sound->resumeSLST();
_system->updateScreen();
}
}
diff --git a/engines/mohawk/mohawk.h b/engines/mohawk/mohawk.h
index ac91dca971..bc0d642bce 100644
--- a/engines/mohawk/mohawk.h
+++ b/engines/mohawk/mohawk.h
@@ -100,7 +100,6 @@ public:
bool hasFeature(EngineFeature f) const;
- Sound *_sound;
VideoManager *_video;
CursorManager *_cursor;
diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp
index a1c6d0e748..3c00c1e11b 100644
--- a/engines/mohawk/myst.cpp
+++ b/engines/mohawk/myst.cpp
@@ -75,6 +75,7 @@ MohawkEngine_Myst::MohawkEngine_Myst(OSystem *syst, const MohawkGameDescription
_curResource = -1;
_hoverResource = nullptr;
+ _sound = nullptr;
_gfx = nullptr;
_console = nullptr;
_scriptParser = nullptr;
@@ -88,6 +89,7 @@ MohawkEngine_Myst::~MohawkEngine_Myst() {
DebugMan.clearAllDebugChannels();
delete _gfx;
+ delete _sound;
delete _console;
delete _scriptParser;
delete _gameState;
@@ -220,6 +222,7 @@ Common::Error MohawkEngine_Myst::run() {
MohawkEngine::run();
_gfx = new MystGraphics(this);
+ _sound = new Sound(this);
_console = new MystConsole(this);
_gameState = new MystGameState(this, _saveFileMan);
_optionsDialog = new MystOptionsDialog(this);
diff --git a/engines/mohawk/myst.h b/engines/mohawk/myst.h
index 0b249e5499..0491e853b6 100644
--- a/engines/mohawk/myst.h
+++ b/engines/mohawk/myst.h
@@ -200,6 +200,7 @@ public:
bool _showResourceRects;
+ Sound *_sound;
MystGraphics *_gfx;
MystGameState *_gameState;
MystScriptParser *_scriptParser;
diff --git a/engines/mohawk/riven.cpp b/engines/mohawk/riven.cpp
index b05b76da30..b7c83c0ff8 100644
--- a/engines/mohawk/riven.cpp
+++ b/engines/mohawk/riven.cpp
@@ -34,8 +34,8 @@
#include "mohawk/riven_external.h"
#include "mohawk/riven_graphics.h"
#include "mohawk/riven_saveload.h"
+#include "mohawk/riven_sound.h"
#include "mohawk/dialogs.h"
-#include "mohawk/sound.h"
#include "mohawk/video.h"
#include "mohawk/console.h"
@@ -59,6 +59,7 @@ MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescriptio
_curStack = kStackUnknown;
_hotspots = nullptr;
_gfx = nullptr;
+ _sound = nullptr;
_externalScriptHandler = nullptr;
_rnd = nullptr;
_scriptMan = nullptr;
@@ -92,6 +93,7 @@ MohawkEngine_Riven::MohawkEngine_Riven(OSystem *syst, const MohawkGameDescriptio
}
MohawkEngine_Riven::~MohawkEngine_Riven() {
+ delete _sound;
delete _gfx;
delete _console;
delete _externalScriptHandler;
@@ -123,6 +125,7 @@ Common::Error MohawkEngine_Riven::run() {
SearchMan.add("arcriven.z", &_installerArchive, 0, false);
_gfx = new RivenGraphics(this);
+ _sound = new RivenSoundManager(this);
_console = new RivenConsole(this);
_saveLoad = new RivenSaveLoad(this, _saveFileMan);
_externalScriptHandler = new RivenExternal(this);
@@ -199,6 +202,7 @@ Common::Error MohawkEngine_Riven::run() {
void MohawkEngine_Riven::handleEvents() {
// Update background running things
checkTimer();
+ _sound->updateSLST();
bool needsUpdate = _gfx->runScheduledWaterEffects();
needsUpdate |= _video->updateMovies();
@@ -710,6 +714,7 @@ void MohawkEngine_Riven::delayAndUpdate(uint32 ms) {
uint32 startTime = _system->getMillis();
while (_system->getMillis() < startTime + ms && !shouldQuit()) {
+ _sound->updateSLST();
bool needsUpdate = _gfx->runScheduledWaterEffects();
needsUpdate |= _video->updateMovies();
diff --git a/engines/mohawk/riven.h b/engines/mohawk/riven.h
index 3ea50bb38d..ce819ac970 100644
--- a/engines/mohawk/riven.h
+++ b/engines/mohawk/riven.h
@@ -41,6 +41,7 @@ class RivenExternal;
class RivenConsole;
class RivenSaveLoad;
class RivenOptionsDialog;
+class RivenSoundManager;
// Riven Stack Types
enum {
@@ -121,6 +122,7 @@ public:
MohawkEngine_Riven(OSystem *syst, const MohawkGameDescription *gamedesc);
virtual ~MohawkEngine_Riven();
+ RivenSoundManager *_sound;
RivenGraphics *_gfx;
RivenExternal *_externalScriptHandler;
Common::RandomSource *_rnd;
diff --git a/engines/mohawk/riven_external.cpp b/engines/mohawk/riven_external.cpp
index 00075039fe..125630445e 100644
--- a/engines/mohawk/riven_external.cpp
+++ b/engines/mohawk/riven_external.cpp
@@ -24,7 +24,7 @@
#include "mohawk/riven.h"
#include "mohawk/riven_external.h"
#include "mohawk/riven_graphics.h"
-#include "mohawk/sound.h"
+#include "mohawk/riven_sound.h"
#include "mohawk/video.h"
#include "gui/message.h"
@@ -2429,7 +2429,7 @@ void RivenExternal::xtexterior300_telescopedown(uint16 argc, uint16 *argv) {
// Play the sound of not being able to move
_vm->_cursor->setCursor(kRivenHideCursor);
_vm->_system->updateScreen();
- _vm->_sound->playSoundBlocking(13);
+ _vm->_sound->playSound(13);
}
} else {
// We're not at the bottom, and we can move down again
@@ -2463,7 +2463,7 @@ void RivenExternal::xtexterior300_telescopeup(uint16 argc, uint16 *argv) {
// Play the sound of not being able to move
_vm->_cursor->setCursor(kRivenHideCursor);
_vm->_system->updateScreen();
- _vm->_sound->playSoundBlocking(13);
+ _vm->_sound->playSound(13);
return;
}
diff --git a/engines/mohawk/riven_graphics.cpp b/engines/mohawk/riven_graphics.cpp
index db22dde22d..b583bc9710 100644
--- a/engines/mohawk/riven_graphics.cpp
+++ b/engines/mohawk/riven_graphics.cpp
@@ -23,6 +23,7 @@
#include "mohawk/resource.h"
#include "mohawk/riven.h"
#include "mohawk/riven_graphics.h"
+#include "mohawk/riven_sound.h"
#include "common/system.h"
#include "engines/util.h"
@@ -111,6 +112,7 @@ void RivenGraphics::drawPLST(uint16 x) {
void RivenGraphics::updateScreen(Common::Rect updateRect) {
if (_updatesEnabled) {
_vm->runUpdateScreenScript();
+ _vm->_sound->triggerDrawSound();
if (_dirtyScreen) {
_activatedPLSTs.clear();
diff --git a/engines/mohawk/riven_scripts.cpp b/engines/mohawk/riven_scripts.cpp
index caa235ec8b..3655452603 100644
--- a/engines/mohawk/riven_scripts.cpp
+++ b/engines/mohawk/riven_scripts.cpp
@@ -25,7 +25,7 @@
#include "mohawk/riven_external.h"
#include "mohawk/riven_graphics.h"
#include "mohawk/riven_scripts.h"
-#include "mohawk/sound.h"
+#include "mohawk/riven_sound.h"
#include "mohawk/video.h"
#include "common/memstream.h"
@@ -309,54 +309,44 @@ void RivenScript::switchCard(uint16 op, uint16 argc, uint16 *argv) {
// Command 3: play an SLST from the script
void RivenScript::playScriptSLST(uint16 op, uint16 argc, uint16 *argv) {
- SLSTRecord slstRecord;
int offset = 0, j = 0;
+ uint16 soundCount = argv[offset++];
+ SLSTRecord slstRecord;
slstRecord.index = 0; // not set by the scripts, so we set it to 0
- slstRecord.sound_count = argv[0];
- slstRecord.sound_ids = new uint16[slstRecord.sound_count];
-
- offset = slstRecord.sound_count;
+ slstRecord.soundIds.resize(soundCount);
- for (j = 0; j < slstRecord.sound_count; j++)
- slstRecord.sound_ids[j] = argv[offset++];
- slstRecord.fade_flags = argv[offset++];
+ for (j = 0; j < soundCount; j++)
+ slstRecord.soundIds[j] = argv[offset++];
+ slstRecord.fadeFlags = argv[offset++];
slstRecord.loop = argv[offset++];
- slstRecord.global_volume = argv[offset++];
+ slstRecord.globalVolume = argv[offset++];
slstRecord.u0 = argv[offset++];
- slstRecord.u1 = argv[offset++];
+ slstRecord.suspend = argv[offset++];
- slstRecord.volumes = new uint16[slstRecord.sound_count];
- slstRecord.balances = new int16[slstRecord.sound_count];
- slstRecord.u2 = new uint16[slstRecord.sound_count];
+ slstRecord.volumes.resize(soundCount);
+ slstRecord.balances.resize(soundCount);
+ slstRecord.u2.resize(soundCount);
- for (j = 0; j < slstRecord.sound_count; j++)
+ for (j = 0; j < soundCount; j++)
slstRecord.volumes[j] = argv[offset++];
- for (j = 0; j < slstRecord.sound_count; j++)
+ for (j = 0; j < soundCount; j++)
slstRecord.balances[j] = argv[offset++]; // negative = left, 0 = center, positive = right
- for (j = 0; j < slstRecord.sound_count; j++)
+ for (j = 0; j < soundCount; j++)
slstRecord.u2[j] = argv[offset++]; // Unknown
// Play the requested sound list
_vm->_sound->playSLST(slstRecord);
- _vm->_activatedSLST = true;
-
- delete[] slstRecord.sound_ids;
- delete[] slstRecord.volumes;
- delete[] slstRecord.balances;
- delete[] slstRecord.u2;
}
// Command 4: play local tWAV resource (twav_id, volume, block)
void RivenScript::playSound(uint16 op, uint16 argc, uint16 *argv) {
- byte volume = Sound::convertRivenVolume(argv[1]);
+ uint16 volume = argv[1];
+ bool playOnDraw = argv[2] == 1;
- if (argv[2] == 1)
- _vm->_sound->playSoundBlocking(argv[0], volume);
- else
- _vm->_sound->playSound(argv[0], volume);
+ _vm->_sound->playSound(argv[0], volume, playOnDraw);
}
// Command 7: set variable value (variable, value)
diff --git a/engines/mohawk/riven_sound.cpp b/engines/mohawk/riven_sound.cpp
new file mode 100644
index 0000000000..10a23a0719
--- /dev/null
+++ b/engines/mohawk/riven_sound.cpp
@@ -0,0 +1,459 @@
+/* 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/debug.h"
+#include "common/system.h"
+
+#include "audio/audiostream.h"
+
+#include "mohawk/riven_sound.h"
+#include "mohawk/sound.h"
+
+namespace Mohawk {
+
+RivenSoundManager::RivenSoundManager(MohawkEngine *vm) :
+ _vm(vm),
+ _effect(nullptr),
+ _mainAmbientSoundId(-1),
+ _effectPlayOnDraw(false),
+ _nextFadeUpdate(0) {
+
+}
+
+RivenSoundManager::~RivenSoundManager() {
+ stopSound();
+ stopAllSLST(false);
+}
+
+Audio::RewindableAudioStream *RivenSoundManager::makeAudioStream(uint16 id) {
+ return makeMohawkWaveStream(_vm->getResource(ID_TWAV, id));
+}
+
+void RivenSoundManager::playSound(uint16 id, uint16 volume, bool playOnDraw) {
+ debug (0, "Playing sound %d", id);
+
+ stopSound();
+
+ Audio::RewindableAudioStream *rewindStream = makeAudioStream(id);
+ if (!rewindStream) {
+ warning("Unable to play sound with id %d", id);
+ return;
+ }
+
+ _effect = new RivenSound(_vm, rewindStream);
+ _effect->setVolume(volume);
+
+ _effectPlayOnDraw = playOnDraw;
+ if (!playOnDraw) {
+ _effect->play();
+ }
+}
+
+void RivenSoundManager::playSLST(uint16 index, uint16 card) {
+ Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card);
+
+ uint16 recordCount = slstStream->readUint16BE();
+
+ for (uint16 i = 0; i < recordCount; i++) {
+ SLSTRecord slstRecord;
+ slstRecord.index = slstStream->readUint16BE();
+
+ uint16 soundCount = slstStream->readUint16BE();
+ slstRecord.soundIds.resize(soundCount);
+
+ for (uint16 j = 0; j < soundCount; j++)
+ slstRecord.soundIds[j] = slstStream->readUint16BE();
+
+ slstRecord.fadeFlags = slstStream->readUint16BE();
+ slstRecord.loop = slstStream->readUint16BE();
+ slstRecord.globalVolume = slstStream->readUint16BE();
+ slstRecord.u0 = slstStream->readUint16BE(); // Unknown
+
+ if (slstRecord.u0 > 1)
+ warning("slstRecord.u0: %d non-boolean", slstRecord.u0);
+
+ slstRecord.suspend = slstStream->readUint16BE();
+
+ if (slstRecord.suspend != 0)
+ warning("slstRecord.u1: %d non-zero", slstRecord.suspend);
+
+ slstRecord.volumes.resize(soundCount);
+ slstRecord.balances.resize(soundCount);
+ slstRecord.u2.resize(soundCount);
+
+ for (uint16 j = 0; j < soundCount; j++)
+ slstRecord.volumes[j] = slstStream->readUint16BE();
+
+ for (uint16 j = 0; j < soundCount; j++)
+ slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right
+
+ for (uint16 j = 0; j < soundCount; j++) {
+ slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown
+
+ if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256)
+ warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]);
+ }
+
+ if (slstRecord.index == index) {
+ playSLST(slstRecord);
+ delete slstStream;
+ return;
+ }
+ }
+
+ delete slstStream;
+
+ // If we have no matching entries, we do nothing and just let
+ // the previous ambient sounds continue.
+}
+
+void RivenSoundManager::playSLST(const SLSTRecord &slstRecord) {
+ if (slstRecord.soundIds.empty()) {
+ return;
+ }
+
+ if (slstRecord.soundIds[0] == _mainAmbientSoundId) {
+ if (slstRecord.soundIds.size() > _ambientSounds.sounds.size()) {
+ addAmbientSounds(slstRecord);
+ }
+ setAmbientLooping(slstRecord.loop);
+ setTargetVolumes(slstRecord);
+ _ambientSounds.suspend = slstRecord.suspend;
+ if (slstRecord.suspend) {
+ freePreviousAmbientSounds();
+ pauseAmbientSounds();
+ applyTargetVolumes();
+ } else {
+ playAmbientSounds();
+ }
+ } else {
+ _mainAmbientSoundId = slstRecord.soundIds[0];
+ freePreviousAmbientSounds();
+ moveAmbientSoundsToPreviousSounds();
+ addAmbientSounds(slstRecord);
+ setAmbientLooping(slstRecord.loop);
+ setTargetVolumes(slstRecord);
+ _ambientSounds.suspend = slstRecord.suspend;
+ if (slstRecord.suspend) {
+ freePreviousAmbientSounds();
+ applyTargetVolumes();
+ } else {
+ startFadingAmbientSounds(slstRecord.fadeFlags);
+ }
+ }
+}
+
+void RivenSoundManager::stopAllSLST(bool fade) {
+ _mainAmbientSoundId = -1;
+ freePreviousAmbientSounds();
+ moveAmbientSoundsToPreviousSounds();
+ startFadingAmbientSounds(fade ? kFadeOutPreviousSounds : 0);
+}
+
+void RivenSoundManager::stopSound() {
+ if (_effect) {
+ delete _effect;
+ }
+ _effect = nullptr;
+ _effectPlayOnDraw = false;
+}
+
+void RivenSoundManager::addAmbientSounds(const SLSTRecord &record) {
+ if (record.soundIds.size() > _ambientSounds.sounds.size()) {
+ uint oldSize = _ambientSounds.sounds.size();
+
+ // Resize the list to the new size
+ _ambientSounds.sounds.resize(record.soundIds.size());
+
+ // Add new elements to the list
+ for (uint i = oldSize; i < _ambientSounds.sounds.size(); i++) {
+ Audio::RewindableAudioStream *stream = makeAudioStream(record.soundIds[i]);
+
+ RivenSound *sound = new RivenSound(_vm, stream);
+ sound->setVolume(record.volumes[i]);
+ sound->setBalance(record.balances[i]);
+
+ _ambientSounds.sounds[i].sound = sound;
+ _ambientSounds.sounds[i].targetVolume = record.volumes[i];
+ _ambientSounds.sounds[i].targetBalance = record.balances[i];
+ }
+ }
+}
+
+void RivenSoundManager::setTargetVolumes(const SLSTRecord &record) {
+ for (uint i = 0; i < record.volumes.size(); i++) {
+ _ambientSounds.sounds[i].targetVolume = record.volumes[i] * record.globalVolume / 256;
+ _ambientSounds.sounds[i].targetBalance = record.balances[i];
+ }
+ _ambientSounds.fading = true;
+}
+
+void RivenSoundManager::freePreviousAmbientSounds() {
+ for (uint i = 0; i < _previousAmbientSounds.sounds.size(); i++) {
+ delete _previousAmbientSounds.sounds[i].sound;
+ }
+ _previousAmbientSounds = AmbientSoundList();
+}
+
+void RivenSoundManager::moveAmbientSoundsToPreviousSounds() {
+ _previousAmbientSounds = _ambientSounds;
+ _ambientSounds = AmbientSoundList();
+}
+
+void RivenSoundManager::applyTargetVolumes() {
+ for (uint i = 0; i < _ambientSounds.sounds.size(); i++) {
+ AmbientSound &ambientSound = _ambientSounds.sounds[i];
+ RivenSound *sound = ambientSound.sound;
+ sound->setVolume(ambientSound.targetVolume);
+ sound->setBalance(ambientSound.targetBalance);
+ }
+ _ambientSounds.fading = false;
+}
+
+void RivenSoundManager::startFadingAmbientSounds(uint16 flags) {
+ for (uint i = 0; i < _ambientSounds.sounds.size(); i++) {
+ AmbientSound &ambientSound = _ambientSounds.sounds[i];
+ uint16 volume;
+ if (flags & kFadeInNewSounds) {
+ volume = 0;
+ } else {
+ volume = ambientSound.targetVolume;
+ }
+ ambientSound.sound->setVolume(volume);
+ }
+ _ambientSounds.fading = true;
+ playAmbientSounds();
+
+ if (!_previousAmbientSounds.sounds.empty()) {
+ if (flags) {
+ _previousAmbientSounds.fading = true;
+ } else {
+ freePreviousAmbientSounds();
+ }
+
+ for (uint i = 0; i < _previousAmbientSounds.sounds.size(); i++) {
+ AmbientSound &ambientSound = _previousAmbientSounds.sounds[i];
+ if (flags & kFadeOutPreviousSounds) {
+ ambientSound.targetVolume = 0;
+ } else {
+ ambientSound.sound->setVolume(ambientSound.targetVolume);
+ }
+ }
+ }
+}
+
+void RivenSoundManager::playAmbientSounds() {
+ for (uint i = 0; i < _ambientSounds.sounds.size(); i++) {
+ _ambientSounds.sounds[i].sound->play();
+ }
+}
+
+void RivenSoundManager::setAmbientLooping(bool loop) {
+ for (uint i = 0; i < _ambientSounds.sounds.size(); i++) {
+ _ambientSounds.sounds[i].sound->setLooping(loop);
+ }
+}
+
+void RivenSoundManager::triggerDrawSound() {
+ if (_effectPlayOnDraw && _effect) {
+ _effect->play();
+ }
+ _effectPlayOnDraw = false;
+}
+
+void RivenSoundManager::pauseAmbientSounds() {
+ for (uint i = 0; i < _ambientSounds.sounds.size(); i++) {
+ _ambientSounds.sounds[i].sound->pause();
+ }
+}
+
+void RivenSoundManager::updateSLST() {
+ uint32 time = _vm->_system->getMillis();
+ int32 delta = CLIP<int32>(time - _nextFadeUpdate, -50, 50);
+ if (_nextFadeUpdate == 0 || delta > 0) {
+ _nextFadeUpdate = time + 50 - delta;
+
+ if (_ambientSounds.fading) {
+ fadeAmbientSoundList(_ambientSounds);
+ }
+
+ if (_previousAmbientSounds.fading) {
+ fadeAmbientSoundList(_previousAmbientSounds);
+ }
+
+ if (!_previousAmbientSounds.sounds.empty() && !_ambientSounds.fading && !_previousAmbientSounds.fading) {
+ freePreviousAmbientSounds();
+ }
+ }
+}
+
+void RivenSoundManager::fadeAmbientSoundList(AmbientSoundList &list) {
+ list.fading = false;
+
+ for (uint i = 0; i < list.sounds.size(); i++) {
+ AmbientSound &ambientSound = list.sounds[i];
+ list.fading |= fadeVolume(ambientSound);
+ list.fading |= fadeBalance(ambientSound);
+ }
+}
+
+bool RivenSoundManager::fadeVolume(AmbientSound &ambientSound) {
+ uint16 volume = ambientSound.sound->getVolume();
+ float delta = (ambientSound.targetVolume - volume) / 30.0f;
+
+ if (ABS<float>(delta) < 0.01f) {
+ ambientSound.sound->setVolume(ambientSound.targetVolume);
+ return false;
+ } else {
+ // Make sure the increment is not zero once converted to an integer
+ if (delta > 0 && delta < 1) {
+ delta = 1;
+ } else if (delta < 0 && delta > -1) {
+ delta = -1;
+ }
+
+ ambientSound.sound->setVolume(volume + delta);
+ return true;
+ }
+}
+
+bool RivenSoundManager::fadeBalance(RivenSoundManager::AmbientSound &ambientSound) {
+ int16 balance = ambientSound.sound->getBalance();
+ float delta = (ambientSound.targetBalance - balance) / 10.0f;
+
+ if (ABS<float>(delta) < 0.01) {
+ ambientSound.sound->setBalance(ambientSound.targetBalance);
+ return false;
+ } else {
+ // Make sure the increment is not zero once converted to an integer
+ if (delta > 0 && delta < 1) {
+ delta = 1;
+ } else if (delta < 0 && delta > -1) {
+ delta = -1;
+ }
+
+ ambientSound.sound->setBalance(balance + delta);
+ return true;
+ }
+}
+
+RivenSound::RivenSound(MohawkEngine *vm, Audio::RewindableAudioStream *rewindStream) :
+ _vm(vm),
+ _volume(Audio::Mixer::kMaxChannelVolume),
+ _balance(0),
+ _looping(false),
+ _stream(rewindStream) {
+
+}
+
+bool RivenSound::isPlaying() const {
+ return _vm->_mixer->isSoundHandleActive(_handle);
+}
+
+void RivenSound::pause() {
+ _vm->_mixer->pauseHandle(_handle, true);
+}
+
+void RivenSound::setVolume(uint16 volume) {
+ _volume = volume;
+ if (isPlaying()) {
+ byte mixerVolume = convertVolume(volume);
+ _vm->_mixer->setChannelVolume(_handle, mixerVolume);
+ }
+}
+
+void RivenSound::setBalance(int16 balance) {
+ _balance = balance;
+ if (isPlaying()) {
+ int8 mixerBalance = convertBalance(balance);
+ _vm->_mixer->setChannelBalance(_handle, mixerBalance);
+ }
+}
+
+void RivenSound::setLooping(bool loop) {
+ if (isPlaying() && _looping != loop) {
+ warning("Changing loop state while a sound is playing is not implemented.");
+ }
+ _looping = loop;
+}
+
+void RivenSound::play() {
+ if (isPlaying()) {
+ // If the sound is already playing, make sure it is not paused
+ _vm->_mixer->pauseHandle(_handle, false);
+ return;
+ }
+
+ if (!_stream) {
+ warning("Trying to play a sound without a stream");
+ return;
+ }
+
+ Audio::AudioStream *playStream;
+ if (_looping) {
+ playStream = new Audio::LoopingAudioStream(_stream, 0);
+ } else {
+ playStream = _stream;
+ }
+
+ int8 mixerBalance = convertBalance(_balance);
+ byte mixerVolume = convertVolume(_volume);
+ _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, &_handle, playStream, -1, mixerVolume, mixerBalance);
+ _stream = nullptr;
+}
+
+byte RivenSound::convertVolume(uint16 volume) {
+ // The volume is a fixed point value in the Mohawk part of the original engine.
+ // It's not clear what happens when it is higher than one.
+ return (volume > 255) ? 255 : volume;
+}
+
+int8 RivenSound::convertBalance(int16 balance) {
+ return (int8)(balance >> 8);
+}
+
+RivenSound::~RivenSound() {
+ _vm->_mixer->stopHandle(_handle);
+ delete _stream;
+}
+
+int16 RivenSound::getBalance() const {
+ return _balance;
+}
+
+uint16 RivenSound::getVolume() const {
+ return _volume;
+}
+
+RivenSoundManager::AmbientSound::AmbientSound() :
+ sound(nullptr),
+ targetVolume(0),
+ targetBalance(0) {
+
+}
+
+RivenSoundManager::AmbientSoundList::AmbientSoundList() :
+ fading(false),
+ suspend(false) {
+}
+
+} // End of namespace Mohawk
diff --git a/engines/mohawk/riven_sound.h b/engines/mohawk/riven_sound.h
new file mode 100644
index 0000000000..f673d1ee3f
--- /dev/null
+++ b/engines/mohawk/riven_sound.h
@@ -0,0 +1,197 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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 MOHAWK_RIVEN_SOUND_H
+#define MOHAWK_RIVEN_SOUND_H
+
+#include "common/array.h"
+#include "common/str.h"
+
+#include "audio/mixer.h"
+
+namespace Audio {
+class RewindableAudioStream;
+}
+
+namespace Mohawk {
+
+class MohawkEngine;
+class RivenSound;
+
+/**
+ * Ambient sound list
+ */
+struct SLSTRecord {
+ uint16 index;
+ Common::Array<uint16> soundIds;
+ uint16 fadeFlags;
+ uint16 loop;
+ uint16 globalVolume;
+ uint16 u0;
+ uint16 suspend;
+ Common::Array<uint16> volumes;
+ Common::Array<int16> balances;
+ Common::Array<uint16> u2;
+};
+
+/**
+ * Sound manager for Riven
+ *
+ * The sound manager can play simulteaneously:
+ * - An effect sound
+ * - A list of ambient sounds
+ *
+ * The list of ambient sounds can be cross faded
+ * with the previously running ambient sounds.
+ */
+class RivenSoundManager {
+public:
+ RivenSoundManager(MohawkEngine *vm);
+ ~RivenSoundManager();
+
+ /**
+ * Play an effect sound
+ *
+ * @param id Sound ID in the stack
+ * @param volume Playback volume, between 0 and 255
+ * @param playOnDraw Start playing when the current card is drawn instead of immediatly
+ */
+ void playSound(uint16 id, uint16 volume = 255, bool playOnDraw = false);
+
+ /** Start playing the scheduled on-draw effect sound, if any. Called by the GraphicsManager. */
+ void triggerDrawSound();
+
+ /** Stop playing the current effect sound, if any */
+ void stopSound();
+
+ /** Start playing an ambient sound list */
+ void playSLST(const SLSTRecord &slstRecord);
+
+ /** Start playing an ambient sound list from a resource */
+ void playSLST(uint16 index, uint16 card);
+
+ /** Stop playing the current ambient sounds */
+ void stopAllSLST(bool fade = false);
+
+ /** Update the ambient sounds for fading. Called once per frame. */
+ void updateSLST();
+
+private:
+ struct AmbientSound {
+ RivenSound *sound;
+ uint16 targetVolume;
+ int16 targetBalance;
+
+ AmbientSound();
+ };
+
+ struct AmbientSoundList {
+ bool fading;
+ bool suspend;
+ Common::Array<AmbientSound> sounds;
+
+ AmbientSoundList();
+ };
+
+ enum FadeFlags {
+ kFadeOutPreviousSounds = 1,
+ kFadeInNewSounds = 2
+ };
+
+ MohawkEngine *_vm;
+
+ int16 _mainAmbientSoundId;
+ AmbientSoundList _ambientSounds;
+ AmbientSoundList _previousAmbientSounds;
+ uint32 _nextFadeUpdate;
+
+ RivenSound *_effect;
+ bool _effectPlayOnDraw;
+
+ Audio::RewindableAudioStream *makeAudioStream(uint16 id);
+
+ // Ambient sound management
+ void addAmbientSounds(const SLSTRecord &record);
+ void playAmbientSounds();
+ void pauseAmbientSounds();
+ void moveAmbientSoundsToPreviousSounds();
+ void freePreviousAmbientSounds();
+
+ // Ambient sound fading
+ void setTargetVolumes(const SLSTRecord &record);
+ void applyTargetVolumes();
+ void startFadingAmbientSounds(uint16 flags);
+ void fadeAmbientSoundList(AmbientSoundList &list);
+ bool fadeVolume(AmbientSound &ambientSound);
+ bool fadeBalance(AmbientSound &ambientSound);
+ void setAmbientLooping(bool loop);
+};
+
+/**
+ * A sound used internally by the SoundManager
+ */
+class RivenSound {
+public:
+ RivenSound(MohawkEngine *vm, Audio::RewindableAudioStream *rewindStream);
+ ~RivenSound();
+
+ /** Start playing the sound stream passed to the constructor */
+ void play();
+
+ /** Is the sound currently playing ar paused? */
+ bool isPlaying() const;
+
+ /** Pause the playback, the play method resumes */
+ void pause();
+
+ /** Get the current volume */
+ uint16 getVolume() const;
+
+ /** Change the playback volume */
+ void setVolume(uint16 volume);
+
+ /** Get the current balance */
+ int16 getBalance() const;
+
+ /** Change the balance */
+ void setBalance(int16 balance);
+
+ /** Set the sound to indefinitely loop. Must be called before startting the playback */
+ void setLooping(bool loop);
+
+private:
+ static byte convertVolume(uint16 volume);
+ static int8 convertBalance(int16 balance);
+
+ MohawkEngine *_vm;
+
+ Audio::SoundHandle _handle;
+ Audio::RewindableAudioStream *_stream;
+
+ uint16 _volume;
+ int16 _balance;
+ bool _looping;
+};
+
+} // End of namespace Mohawk
+
+#endif
diff --git a/engines/mohawk/sound.cpp b/engines/mohawk/sound.cpp
index 38cb0b3608..0711561068 100644
--- a/engines/mohawk/sound.cpp
+++ b/engines/mohawk/sound.cpp
@@ -37,6 +37,150 @@
namespace Mohawk {
+Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList) {
+ uint32 tag = 0;
+ ADPCMStatus adpcmStatus;
+ DataChunk dataChunk;
+ uint32 dataSize = 0;
+
+ memset(&dataChunk, 0, sizeof(DataChunk));
+
+ if (stream->readUint32BE() != ID_MHWK) // MHWK tag again
+ error ("Could not find tag 'MHWK'");
+
+ stream->readUint32BE(); // Skip size
+
+ if (stream->readUint32BE() != ID_WAVE)
+ error ("Could not find tag 'WAVE'");
+
+ while (!dataChunk.audioData) {
+ tag = stream->readUint32BE();
+
+ switch (tag) {
+ case ID_ADPC:
+ debug(2, "Found Tag ADPC");
+ // ADPCM Sound Only
+ //
+ // This is useful for seeking in the stream, and is actually quite brilliant
+ // considering some of the other things Broderbund did with the engine.
+ // Only Riven and CSTime are known to use ADPCM audio and only CSTime
+ // actually requires this for seeking. On the other hand, it may be interesting
+ // to look at that one Riven sample that uses the cue points.
+ //
+ // Basically, the sample frame from the cue list is looked up here and then
+ // sets the starting sample and step index at the point specified. Quite
+ // an elegant/efficient system, really.
+
+ adpcmStatus.size = stream->readUint32BE();
+ adpcmStatus.itemCount = stream->readUint16BE();
+ adpcmStatus.channels = stream->readUint16BE();
+ adpcmStatus.statusItems = new ADPCMStatus::StatusItem[adpcmStatus.itemCount];
+
+ assert(adpcmStatus.channels <= 2);
+
+ for (uint16 i = 0; i < adpcmStatus.itemCount; i++) {
+ adpcmStatus.statusItems[i].sampleFrame = stream->readUint32BE();
+
+ for (uint16 j = 0; j < adpcmStatus.channels; j++) {
+ adpcmStatus.statusItems[i].channelStatus[j].last = stream->readSint16BE();
+ adpcmStatus.statusItems[i].channelStatus[j].stepIndex = stream->readUint16BE();
+ }
+ }
+
+ // TODO: Actually use this chunk. For now, just delete the status items...
+ delete[] adpcmStatus.statusItems;
+ break;
+ case ID_CUE:
+ debug(2, "Found Tag Cue#");
+ // Cues are used for animation sync. There are a couple in Myst and
+ // Riven but are not used there at all.
+
+ if (!cueList) {
+ uint32 size = stream->readUint32BE();
+ stream->skip(size);
+ break;
+ }
+
+ cueList->size = stream->readUint32BE();
+ cueList->pointCount = stream->readUint16BE();
+
+ if (cueList->pointCount == 0)
+ debug(2, "Cue# chunk found with no points!");
+ else
+ debug(2, "Cue# chunk found with %d point(s)!", cueList->pointCount);
+
+ cueList->points.resize(cueList->pointCount);
+ for (uint16 i = 0; i < cueList->pointCount; i++) {
+ cueList->points[i].sampleFrame = stream->readUint32BE();
+
+ byte nameLength = stream->readByte();
+ cueList->points[i].name.clear();
+ for (byte j = 0; j < nameLength; j++)
+ cueList->points[i].name += stream->readByte();
+
+ // Realign to an even boundary
+ if (!(nameLength & 1))
+ stream->readByte();
+
+ debug (3, "Cue# chunk point %d (frame %d): %s", i, cueList->points[i].sampleFrame, cueList->points[i].name.c_str());
+ }
+ break;
+ case ID_DATA:
+ debug(2, "Found Tag DATA");
+ // We subtract 20 from the actual chunk size, which is the total size
+ // of the chunk's header
+ dataSize = stream->readUint32BE() - 20;
+ dataChunk.sampleRate = stream->readUint16BE();
+ dataChunk.sampleCount = stream->readUint32BE();
+ dataChunk.bitsPerSample = stream->readByte();
+ dataChunk.channels = stream->readByte();
+ dataChunk.encoding = stream->readUint16BE();
+ dataChunk.loopCount = stream->readUint16BE();
+ dataChunk.loopStart = stream->readUint32BE();
+ dataChunk.loopEnd = stream->readUint32BE();
+
+ // NOTE: We currently ignore all of the loop parameters here. Myst uses the
+ // loopCount variable but the loopStart and loopEnd are always 0 and the size of
+ // the sample. Myst ME doesn't use the Mohawk Sound format and just standard WAVE
+ // files and therefore does not contain any of this metadata and we have to specify
+ // whether or not to loop elsewhere.
+
+ dataChunk.audioData = stream->readStream(dataSize);
+ break;
+ default:
+ error ("Unknown tag found in 'tWAV' chunk -- '%s'", tag2str(tag));
+ }
+ }
+
+ // makeMohawkWaveStream always takes control of the original stream
+ delete stream;
+
+ // The sound in Myst uses raw unsigned 8-bit data
+ // The sound in the CD version of Riven is encoded in Intel DVI ADPCM
+ // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM
+ if (dataChunk.encoding == kCodecRaw) {
+ byte flags = Audio::FLAG_UNSIGNED;
+
+ if (dataChunk.channels == 2)
+ flags |= Audio::FLAG_STEREO;
+
+ return Audio::makeRawStream(dataChunk.audioData, dataChunk.sampleRate, flags);
+ } else if (dataChunk.encoding == kCodecADPCM) {
+ uint32 blockAlign = dataChunk.channels * dataChunk.bitsPerSample / 8;
+ return Audio::makeADPCMStream(dataChunk.audioData, DisposeAfterUse::YES, dataSize, Audio::kADPCMDVI, dataChunk.sampleRate, dataChunk.channels, blockAlign);
+ } else if (dataChunk.encoding == kCodecMPEG2) {
+#ifdef USE_MAD
+ return Audio::makeMP3Stream(dataChunk.audioData, DisposeAfterUse::YES);
+#else
+ warning ("MAD library not included - unable to play MP2 audio");
+#endif
+ } else {
+ error ("Unknown Mohawk WAVE encoding %d", dataChunk.encoding);
+ }
+
+ return nullptr;
+}
+
Sound::Sound(MohawkEngine* vm) : _vm(vm) {
_midiDriver = NULL;
_midiParser = NULL;
@@ -47,7 +191,6 @@ Sound::Sound(MohawkEngine* vm) : _vm(vm) {
Sound::~Sound() {
stopSound();
- stopAllSLST();
stopBackgroundMyst();
if (_midiParser) {
@@ -234,300 +377,6 @@ void Sound::stopMidi() {
_midiParser->unloadMusic();
}
-byte Sound::convertRivenVolume(uint16 volume) {
- return (volume == 256) ? 255 : volume;
-}
-
-void Sound::playSLST(uint16 index, uint16 card) {
- Common::SeekableReadStream *slstStream = _vm->getResource(ID_SLST, card);
- SLSTRecord slstRecord;
- uint16 recordCount = slstStream->readUint16BE();
-
- for (uint16 i = 0; i < recordCount; i++) {
- slstRecord.index = slstStream->readUint16BE();
- slstRecord.sound_count = slstStream->readUint16BE();
- slstRecord.sound_ids = new uint16[slstRecord.sound_count];
-
- for (uint16 j = 0; j < slstRecord.sound_count; j++)
- slstRecord.sound_ids[j] = slstStream->readUint16BE();
-
- slstRecord.fade_flags = slstStream->readUint16BE();
- slstRecord.loop = slstStream->readUint16BE();
- slstRecord.global_volume = slstStream->readUint16BE();
- slstRecord.u0 = slstStream->readUint16BE(); // Unknown
-
- if (slstRecord.u0 > 1)
- warning("slstRecord.u0: %d non-boolean", slstRecord.u0);
-
- slstRecord.u1 = slstStream->readUint16BE(); // Unknown
-
- if (slstRecord.u1 != 0)
- warning("slstRecord.u1: %d non-zero", slstRecord.u1);
-
- slstRecord.volumes = new uint16[slstRecord.sound_count];
- slstRecord.balances = new int16[slstRecord.sound_count];
- slstRecord.u2 = new uint16[slstRecord.sound_count];
-
- for (uint16 j = 0; j < slstRecord.sound_count; j++)
- slstRecord.volumes[j] = slstStream->readUint16BE();
-
- for (uint16 j = 0; j < slstRecord.sound_count; j++)
- slstRecord.balances[j] = slstStream->readSint16BE(); // negative = left, 0 = center, positive = right
-
- for (uint16 j = 0; j < slstRecord.sound_count; j++) {
- slstRecord.u2[j] = slstStream->readUint16BE(); // Unknown
-
- if (slstRecord.u2[j] != 255 && slstRecord.u2[j] != 256)
- warning("slstRecord.u2[%d]: %d not 255 or 256", j, slstRecord.u2[j]);
- }
-
- if (slstRecord.index == index) {
- playSLST(slstRecord);
- delete[] slstRecord.sound_ids;
- delete[] slstRecord.volumes;
- delete[] slstRecord.balances;
- delete[] slstRecord.u2;
- delete slstStream;
- return;
- }
-
- delete[] slstRecord.sound_ids;
- delete[] slstRecord.volumes;
- delete[] slstRecord.balances;
- delete[] slstRecord.u2;
- }
-
- delete slstStream;
-
- // If we have no matching entries, we do nothing and just let
- // the previous ambient sounds continue.
-}
-
-void Sound::playSLST(SLSTRecord slstRecord) {
- // End old sounds
- for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) {
- bool noLongerPlay = true;
- for (uint16 j = 0; j < slstRecord.sound_count; j++)
- if (_currentSLSTSounds[i].id == slstRecord.sound_ids[j])
- noLongerPlay = false;
- if (noLongerPlay)
- stopSLSTSound(i, (slstRecord.fade_flags & 1) != 0);
- }
-
- // Start new sounds
- for (uint16 i = 0; i < slstRecord.sound_count; i++) {
- bool alreadyPlaying = false;
- for (uint16 j = 0; j < _currentSLSTSounds.size(); j++) {
- if (_currentSLSTSounds[j].id == slstRecord.sound_ids[i])
- alreadyPlaying = true;
- }
- if (!alreadyPlaying) {
- playSLSTSound(slstRecord.sound_ids[i],
- (slstRecord.fade_flags & (1 << 1)) != 0,
- slstRecord.loop != 0,
- slstRecord.volumes[i],
- slstRecord.balances[i]);
- }
- }
-}
-
-void Sound::stopAllSLST(bool fade) {
- for (uint16 i = 0; i < _currentSLSTSounds.size(); i++) {
- // TODO: Fade out, if requested
- _vm->_mixer->stopHandle(*_currentSLSTSounds[i].handle);
- delete _currentSLSTSounds[i].handle;
- }
-
- _currentSLSTSounds.clear();
-}
-
-static int8 convertBalance(int16 balance) {
- return (int8)(balance >> 8);
-}
-
-void Sound::playSLSTSound(uint16 id, bool fade, bool loop, uint16 volume, int16 balance) {
- // WORKAROUND: Some Riven SLST entries have a volume of 0, so we just ignore them.
- if (volume == 0)
- return;
-
- SLSTSndHandle sndHandle;
- sndHandle.handle = new Audio::SoundHandle();
- sndHandle.id = id;
- _currentSLSTSounds.push_back(sndHandle);
-
- Audio::RewindableAudioStream *rewindStream = makeMohawkWaveStream(_vm->getResource(ID_TWAV, id));
-
- // Loop here if necessary
- Audio::AudioStream *audStream = rewindStream;
- if (loop)
- audStream = Audio::makeLoopingAudioStream(rewindStream, 0);
-
- // TODO: Handle fading, possibly just raise the volume of the channel in increments?
-
- _vm->_mixer->playStream(Audio::Mixer::kPlainSoundType, sndHandle.handle, audStream, -1, convertRivenVolume(volume), convertBalance(balance));
-}
-
-void Sound::stopSLSTSound(uint16 index, bool fade) {
- // TODO: Fade out, if requested
- _vm->_mixer->stopHandle(*_currentSLSTSounds[index].handle);
- delete _currentSLSTSounds[index].handle;
- _currentSLSTSounds.remove_at(index);
-}
-
-void Sound::pauseSLST() {
- for (uint16 i = 0; i < _currentSLSTSounds.size(); i++)
- _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, true);
-}
-
-void Sound::resumeSLST() {
- for (uint16 i = 0; i < _currentSLSTSounds.size(); i++)
- _vm->_mixer->pauseHandle(*_currentSLSTSounds[i].handle, false);
-}
-
-Audio::RewindableAudioStream *Sound::makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList) {
- uint32 tag = 0;
- ADPCMStatus adpcmStatus;
- DataChunk dataChunk;
- uint32 dataSize = 0;
-
- memset(&dataChunk, 0, sizeof(DataChunk));
-
- if (stream->readUint32BE() != ID_MHWK) // MHWK tag again
- error ("Could not find tag 'MHWK'");
-
- stream->readUint32BE(); // Skip size
-
- if (stream->readUint32BE() != ID_WAVE)
- error ("Could not find tag 'WAVE'");
-
- while (!dataChunk.audioData) {
- tag = stream->readUint32BE();
-
- switch (tag) {
- case ID_ADPC:
- debug(2, "Found Tag ADPC");
- // ADPCM Sound Only
- //
- // This is useful for seeking in the stream, and is actually quite brilliant
- // considering some of the other things Broderbund did with the engine.
- // Only Riven and CSTime are known to use ADPCM audio and only CSTime
- // actually requires this for seeking. On the other hand, it may be interesting
- // to look at that one Riven sample that uses the cue points.
- //
- // Basically, the sample frame from the cue list is looked up here and then
- // sets the starting sample and step index at the point specified. Quite
- // an elegant/efficient system, really.
-
- adpcmStatus.size = stream->readUint32BE();
- adpcmStatus.itemCount = stream->readUint16BE();
- adpcmStatus.channels = stream->readUint16BE();
- adpcmStatus.statusItems = new ADPCMStatus::StatusItem[adpcmStatus.itemCount];
-
- assert(adpcmStatus.channels <= 2);
-
- for (uint16 i = 0; i < adpcmStatus.itemCount; i++) {
- adpcmStatus.statusItems[i].sampleFrame = stream->readUint32BE();
-
- for (uint16 j = 0; j < adpcmStatus.channels; j++) {
- adpcmStatus.statusItems[i].channelStatus[j].last = stream->readSint16BE();
- adpcmStatus.statusItems[i].channelStatus[j].stepIndex = stream->readUint16BE();
- }
- }
-
- // TODO: Actually use this chunk. For now, just delete the status items...
- delete[] adpcmStatus.statusItems;
- break;
- case ID_CUE:
- debug(2, "Found Tag Cue#");
- // Cues are used for animation sync. There are a couple in Myst and
- // Riven but are not used there at all.
-
- if (!cueList) {
- uint32 size = stream->readUint32BE();
- stream->skip(size);
- break;
- }
-
- cueList->size = stream->readUint32BE();
- cueList->pointCount = stream->readUint16BE();
-
- if (cueList->pointCount == 0)
- debug(2, "Cue# chunk found with no points!");
- else
- debug(2, "Cue# chunk found with %d point(s)!", cueList->pointCount);
-
- cueList->points.resize(cueList->pointCount);
- for (uint16 i = 0; i < cueList->pointCount; i++) {
- cueList->points[i].sampleFrame = stream->readUint32BE();
-
- byte nameLength = stream->readByte();
- cueList->points[i].name.clear();
- for (byte j = 0; j < nameLength; j++)
- cueList->points[i].name += stream->readByte();
-
- // Realign to an even boundary
- if (!(nameLength & 1))
- stream->readByte();
-
- debug (3, "Cue# chunk point %d (frame %d): %s", i, cueList->points[i].sampleFrame, cueList->points[i].name.c_str());
- }
- break;
- case ID_DATA:
- debug(2, "Found Tag DATA");
- // We subtract 20 from the actual chunk size, which is the total size
- // of the chunk's header
- dataSize = stream->readUint32BE() - 20;
- dataChunk.sampleRate = stream->readUint16BE();
- dataChunk.sampleCount = stream->readUint32BE();
- dataChunk.bitsPerSample = stream->readByte();
- dataChunk.channels = stream->readByte();
- dataChunk.encoding = stream->readUint16BE();
- dataChunk.loopCount = stream->readUint16BE();
- dataChunk.loopStart = stream->readUint32BE();
- dataChunk.loopEnd = stream->readUint32BE();
-
- // NOTE: We currently ignore all of the loop parameters here. Myst uses the
- // loopCount variable but the loopStart and loopEnd are always 0 and the size of
- // the sample. Myst ME doesn't use the Mohawk Sound format and just standard WAVE
- // files and therefore does not contain any of this metadata and we have to specify
- // whether or not to loop elsewhere.
-
- dataChunk.audioData = stream->readStream(dataSize);
- break;
- default:
- error ("Unknown tag found in 'tWAV' chunk -- '%s'", tag2str(tag));
- }
- }
-
- // makeMohawkWaveStream always takes control of the original stream
- delete stream;
-
- // The sound in Myst uses raw unsigned 8-bit data
- // The sound in the CD version of Riven is encoded in Intel DVI ADPCM
- // The sound in the DVD version of Riven is encoded in MPEG-2 Layer II or Intel DVI ADPCM
- if (dataChunk.encoding == kCodecRaw) {
- byte flags = Audio::FLAG_UNSIGNED;
-
- if (dataChunk.channels == 2)
- flags |= Audio::FLAG_STEREO;
-
- return Audio::makeRawStream(dataChunk.audioData, dataChunk.sampleRate, flags);
- } else if (dataChunk.encoding == kCodecADPCM) {
- uint32 blockAlign = dataChunk.channels * dataChunk.bitsPerSample / 8;
- return Audio::makeADPCMStream(dataChunk.audioData, DisposeAfterUse::YES, dataSize, Audio::kADPCMDVI, dataChunk.sampleRate, dataChunk.channels, blockAlign);
- } else if (dataChunk.encoding == kCodecMPEG2) {
-#ifdef USE_MAD
- return Audio::makeMP3Stream(dataChunk.audioData, DisposeAfterUse::YES);
-#else
- warning ("MAD library not included - unable to play MP2 audio");
-#endif
- } else {
- error ("Unknown Mohawk WAVE encoding %d", dataChunk.encoding);
- }
-
- return NULL;
-}
-
Audio::RewindableAudioStream *Sound::makeLivingBooksWaveStream_v1(Common::SeekableReadStream *stream) {
uint16 header = stream->readUint16BE();
uint16 rate = 0;
@@ -591,18 +440,6 @@ void Sound::stopSound(uint16 id) {
}
}
-void Sound::pauseSound() {
- for (uint32 i = 0; i < _handles.size(); i++)
- if (_handles[i].type == kUsedHandle)
- _vm->_mixer->pauseHandle(_handles[i].handle, true);
-}
-
-void Sound::resumeSound() {
- for (uint32 i = 0; i < _handles.size(); i++)
- if (_handles[i].type == kUsedHandle)
- _vm->_mixer->pauseHandle(_handles[i].handle, false);
-}
-
bool Sound::isPlaying(uint16 id) {
for (uint32 i = 0; i < _handles.size(); i++)
if (_handles[i].type == kUsedHandle && _handles[i].id == id)
diff --git a/engines/mohawk/sound.h b/engines/mohawk/sound.h
index f09706e155..2b4b1ce091 100644
--- a/engines/mohawk/sound.h
+++ b/engines/mohawk/sound.h
@@ -42,20 +42,6 @@ namespace Mohawk {
#define MAX_CHANNELS 2 // Can there be more than 2?
-struct SLSTRecord {
- uint16 index;
- uint16 sound_count;
- uint16 *sound_ids;
- uint16 fade_flags;
- uint16 loop;
- uint16 global_volume;
- uint16 u0;
- uint16 u1;
- uint16 *volumes;
- int16 *balances;
- uint16 *u2;
-};
-
enum SndHandleType {
kFreeHandle,
kUsedHandle
@@ -68,11 +54,6 @@ struct SndHandle {
uint16 id;
};
-struct SLSTSndHandle {
- Audio::SoundHandle *handle;
- uint16 id;
-};
-
struct ADPCMStatus { // Holds ADPCM status data, but is irrelevant for us.
uint32 size;
uint16 itemCount;
@@ -116,6 +97,8 @@ struct DataChunk {
Common::SeekableReadStream *audioData;
};
+Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList = nullptr);
+
class MohawkEngine;
class Sound {
@@ -130,8 +113,6 @@ public:
void stopMidi();
void stopSound();
void stopSound(uint16 id);
- void pauseSound();
- void resumeSound();
bool isPlaying(uint16 id);
bool isPlaying();
uint getNumSamplesPlayed(uint16 id);
@@ -144,21 +125,12 @@ public:
void stopBackgroundMyst();
void changeBackgroundVolumeMyst(uint16 vol);
- // Riven-specific sound functions
- void playSLST(uint16 index, uint16 card);
- void playSLST(SLSTRecord slstRecord);
- void pauseSLST();
- void resumeSLST();
- void stopAllSLST(bool fade = false);
- static byte convertRivenVolume(uint16 volume);
-
private:
MohawkEngine *_vm;
MidiDriver *_midiDriver;
MidiParser *_midiParser;
byte *_midiData;
- static Audio::RewindableAudioStream *makeMohawkWaveStream(Common::SeekableReadStream *stream, CueList *cueList = NULL);
static Audio::RewindableAudioStream *makeLivingBooksWaveStream_v1(Common::SeekableReadStream *stream);
void initMidi();
@@ -169,11 +141,6 @@ private:
// Myst-specific
SndHandle _mystBackgroundSound;
-
- // Riven-specific
- void playSLSTSound(uint16 index, bool fade, bool loop, uint16 volume, int16 balance);
- void stopSLSTSound(uint16 id, bool fade);
- Common::Array<SLSTSndHandle> _currentSLSTSounds;
};
} // End of namespace Mohawk
diff --git a/engines/mortevielle/detection.cpp b/engines/mortevielle/detection.cpp
index 6791707dd5..7bc5339179 100644
--- a/engines/mortevielle/detection.cpp
+++ b/engines/mortevielle/detection.cpp
@@ -90,6 +90,7 @@ bool MortevielleMetaEngine::hasFeature(MetaEngineFeature f) const {
case kSavesSupportMetaInfo:
case kSavesSupportThumbnail:
case kSavesSupportCreationDate:
+ case kSimpleSavesNames:
return true;
default:
return false;
diff --git a/engines/neverhood/detection.cpp b/engines/neverhood/detection.cpp
index 0f409a6435..0c0347ef13 100644
--- a/engines/neverhood/detection.cpp
+++ b/engines/neverhood/detection.cpp
@@ -228,7 +228,8 @@ bool NeverhoodMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
- (f == kSavesSupportPlayTime);
+ (f == kSavesSupportPlayTime) ||
+ (f == kSimpleSavesNames);
}
bool Neverhood::NeverhoodEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/prince/detection.cpp b/engines/prince/detection.cpp
index 1c6f63aff3..ad759823d8 100644
--- a/engines/prince/detection.cpp
+++ b/engines/prince/detection.cpp
@@ -56,7 +56,8 @@ bool PrinceMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
(f == kSupportsListSaves) ||
- (f == kSupportsLoadingDuringStartup);
+ (f == kSupportsLoadingDuringStartup) ||
+ (f == kSimpleSavesNames);
}
bool Prince::PrinceEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/saga/detection.cpp b/engines/saga/detection.cpp
index 0677e84d67..6fe4277c27 100644
--- a/engines/saga/detection.cpp
+++ b/engines/saga/detection.cpp
@@ -157,7 +157,8 @@ bool SagaMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
- (f == kSavesSupportPlayTime);
+ (f == kSavesSupportPlayTime) ||
+ (f == kSimpleSavesNames);
}
bool Saga::SagaEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/savestate.cpp b/engines/savestate.cpp
index 186d7bc5f2..7366aa6a61 100644
--- a/engines/savestate.cpp
+++ b/engines/savestate.cpp
@@ -27,12 +27,12 @@
SaveStateDescriptor::SaveStateDescriptor()
// FIXME: default to 0 (first slot) or to -1 (invalid slot) ?
: _slot(-1), _description(), _isDeletable(true), _isWriteProtected(false),
- _saveDate(), _saveTime(), _playTime(), _thumbnail() {
+ _isLocked(false), _saveDate(), _saveTime(), _playTime(), _thumbnail() {
}
SaveStateDescriptor::SaveStateDescriptor(int s, const Common::String &d)
: _slot(s), _description(d), _isDeletable(true), _isWriteProtected(false),
- _saveDate(), _saveTime(), _playTime(), _thumbnail() {
+ _isLocked(false), _saveDate(), _saveTime(), _playTime(), _thumbnail() {
}
void SaveStateDescriptor::setThumbnail(Graphics::Surface *t) {
diff --git a/engines/savestate.h b/engines/savestate.h
index 21ade602fa..3244d61fdb 100644
--- a/engines/savestate.h
+++ b/engines/savestate.h
@@ -90,6 +90,24 @@ public:
bool getWriteProtectedFlag() const { return _isWriteProtected; }
/**
+ * Defines whether the save state is "locked" because is being synced.
+ */
+ void setLocked(bool state) {
+ _isLocked = state;
+
+ //just in case:
+ if (state) {
+ setDeletableFlag(false);
+ setWriteProtectedFlag(true);
+ }
+ }
+
+ /**
+ * Queries whether the save state is "locked" because is being synced.
+ */
+ bool getLocked() const { return _isLocked; }
+
+ /**
* Return a thumbnail graphics surface representing the savestate visually.
* This is usually a scaled down version of the game graphics. The size
* should be either 160x100 or 160x120 pixels, depending on the aspect
@@ -180,6 +198,11 @@ private:
bool _isWriteProtected;
/**
+ * Whether the save state is "locked" because is being synced.
+ */
+ bool _isLocked;
+
+ /**
* Human readable description of the date the save state was created.
*/
Common::String _saveDate;
diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp
index 3e67a1819a..b20ed3f8be 100644
--- a/engines/sci/console.cpp
+++ b/engines/sci/console.cpp
@@ -54,7 +54,6 @@
#include "sci/graphics/frameout.h"
#include "sci/graphics/paint32.h"
#include "video/coktel_decoder.h"
-#include "sci/video/robot_decoder.h"
#endif
#include "common/file.h"
@@ -266,8 +265,6 @@ void Console::postEnter() {
#ifdef ENABLE_SCI32
} else if (_videoFile.hasSuffix(".vmd")) {
videoDecoder = new Video::AdvancedVMDDecoder();
- } else if (_videoFile.hasSuffix(".rbt")) {
- videoDecoder = new RobotDecoder(_engine->getPlatform() == Common::kPlatformMacintosh);
} else if (_videoFile.hasSuffix(".duk")) {
duckMode = true;
videoDecoder = new Video::AVIDecoder();
@@ -489,6 +486,7 @@ bool Console::cmdGetVersion(int argc, const char **argv) {
debugPrintf("Lofs type: %s\n", getSciVersionDesc(_engine->_features->detectLofsType()));
debugPrintf("Move count type: %s\n", (_engine->_features->handleMoveCount()) ? "increment" : "ignore");
debugPrintf("SetCursor type: %s\n", getSciVersionDesc(_engine->_features->detectSetCursorType()));
+ debugPrintf("PseudoMouse ability: %s\n", _engine->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue ? "yes" : "no");
#ifdef ENABLE_SCI32
if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE))
debugPrintf("SCI2.1 kernel table: %s\n", (_engine->_features->detectSci21KernelType() == SCI_VERSION_2) ? "modified SCI2 (old)" : "SCI2.1 (new)");
diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp
index f5797dc106..ad2b0f31a5 100644
--- a/engines/sci/detection.cpp
+++ b/engines/sci/detection.cpp
@@ -565,8 +565,8 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles,
// the file should be over 10MB, as it contains all the game speech and is usually
// around 450MB+. The size check is for some floppy game versions like KQ6 floppy, which
// also have a small resource.aud file
- if (allFiles.contains("resource.aud") || allFiles.contains("audio001.002")) {
- Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : allFiles["audio001.002"];
+ if (allFiles.contains("resource.aud") || allFiles.contains("resaud.001") || allFiles.contains("audio001.002")) {
+ Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : (allFiles.contains("resaud.001") ? allFiles["resaud.001"] : allFiles["audio001.002"]);
Common::SeekableReadStream *tmpStream = file.createReadStream();
if (tmpStream->size() > 10 * 1024 * 1024) {
// We got a CD version, so set the CD flag accordingly
diff --git a/engines/sci/detection_tables.h b/engines/sci/detection_tables.h
index de342a3afc..eda6bfae64 100644
--- a/engines/sci/detection_tables.h
+++ b/engines/sci/detection_tables.h
@@ -22,15 +22,7 @@
namespace Sci {
-#define GAMEOPTION_PREFER_DIGITAL_SFX GUIO_GAMEOPTIONS1
-#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS2
-#define GAMEOPTION_FB01_MIDI GUIO_GAMEOPTIONS3
-#define GAMEOPTION_JONES_CDAUDIO GUIO_GAMEOPTIONS4
-#define GAMEOPTION_KQ6_WINDOWS_CURSORS GUIO_GAMEOPTIONS5
-#define GAMEOPTION_SQ4_SILVER_CURSORS GUIO_GAMEOPTIONS6
-#define GAMEOPTION_EGA_UNDITHER GUIO_GAMEOPTIONS7
-#define GAMEOPTION_HIGH_RESOLUTION_GRAPHICS GUIO_GAMEOPTIONS8
-#define GAMEOPTION_ENABLE_BLACK_LINED_VIDEO GUIO_GAMEOPTIONS9
+#include "sci/sci.h"
// SCI3 games have a different script format (in CSC files) and are currently unsupported
#define ENABLE_SCI3_GAMES
@@ -836,7 +828,10 @@ static const struct ADGameDescription SciGameDescriptions[] = {
AD_LISTEND},
Common::ES_ESP, Common::kPlatformWindows, ADGF_CD | ADGF_UNSTABLE, GUIO_GK1_CD },
- // Gabriel Knight - English Macintosh
+ // Gabriel Knight - English Macintosh (Floppy!)
+ // This version is hi-res ONLY, so it should NOT get GAMEOPTION_HIGH_RESOLUTION_GRAPHICS
+ // (which is meant for enforcing hi-res graphics), but instead hi-res mode should be enabled all the time.
+ // Confirmed by [md5] and originally by clone2727.
{"gk1", "", {
{"Data1", 0, "044d3bcd7e5b5bb0393d954ade8053fe", 5814918},
{"Data2", 0, "99a0c63febf9e44e12a00f99c00eae0f", 6685352},
diff --git a/engines/sci/engine/features.cpp b/engines/sci/engine/features.cpp
index a993506f7a..e37a1651ef 100644
--- a/engines/sci/engine/features.cpp
+++ b/engines/sci/engine/features.cpp
@@ -45,6 +45,7 @@ GameFeatures::GameFeatures(SegManager *segMan, Kernel *kernel) : _segMan(segMan)
if (!ConfMan.getBool("use_cdaudio"))
_usesCdTrack = false;
_forceDOSTracks = false;
+ _pseudoMouseAbility = kPseudoMouseAbilityUninitialized;
}
reg_t GameFeatures::getDetectionAddr(const Common::String &objName, Selector slc, int methodNum) {
@@ -605,4 +606,50 @@ bool GameFeatures::useAltWinGMSound() {
}
}
+// PseudoMouse was added during SCI1
+// PseudoMouseAbility is about a tiny difference in the keyboard driver, which sets the event type to either
+// 40h (old behaviour) or 44h (the keyboard driver actually added 40h to the existing value).
+// See engine/kevent.cpp, kMapKeyToDir - also script 933
+
+// SCI1EGA:
+// Quest for Glory 2 still used the old way.
+//
+// SCI1EARLY:
+// King's Quest 5 0.000.062 uses the old way.
+// Leisure Suit Larry 1 demo uses the new way, but no PseudoMouse class.
+// Fairy Tales uses the new way.
+// X-Mas 1990 uses the old way, no PseudoMouse class.
+// Space Quest 4 floppy (1.1) uses the new way.
+// Mixed Up Mother Goose uses the old way, no PseudoMouse class.
+//
+// SCI1MIDDLE:
+// Leisure Suit Larry 5 demo uses the new way.
+// Conquests of the Longbow demo uses the new way.
+// Leisure Suit Larry 1 (2.0) uses the new way.
+// Astro Chicken II uses the new way.
+PseudoMouseAbilityType GameFeatures::detectPseudoMouseAbility() {
+ if (_pseudoMouseAbility == kPseudoMouseAbilityUninitialized) {
+ if (getSciVersion() < SCI_VERSION_1_EARLY) {
+ // SCI1 EGA or earlier -> pseudo mouse ability is always disabled
+ _pseudoMouseAbility = kPseudoMouseAbilityFalse;
+
+ } else if (getSciVersion() == SCI_VERSION_1_EARLY) {
+ // For SCI1 early some games had it enabled, some others didn't.
+ // We try to find an object called "PseudoMouse". If it's found, we enable the ability otherwise we don't.
+ reg_t pseudoMouseAddr = _segMan->findObjectByName("PseudoMouse", 0);
+
+ if (pseudoMouseAddr != NULL_REG) {
+ _pseudoMouseAbility = kPseudoMouseAbilityTrue;
+ } else {
+ _pseudoMouseAbility = kPseudoMouseAbilityFalse;
+ }
+
+ } else {
+ // SCI1 middle or later -> pseudo mouse ability is always enabled
+ _pseudoMouseAbility = kPseudoMouseAbilityTrue;
+ }
+ }
+ return _pseudoMouseAbility;
+}
+
} // End of namespace Sci
diff --git a/engines/sci/engine/features.h b/engines/sci/engine/features.h
index 1c410267e6..b2d40f400f 100644
--- a/engines/sci/engine/features.h
+++ b/engines/sci/engine/features.h
@@ -34,6 +34,12 @@ enum MoveCountType {
kIncrementMoveCount
};
+enum PseudoMouseAbilityType {
+ kPseudoMouseAbilityUninitialized,
+ kPseudoMouseAbilityFalse,
+ kPseudoMouseAbilityTrue
+};
+
class GameFeatures {
public:
GameFeatures(SegManager *segMan, Kernel *kernel);
@@ -110,6 +116,12 @@ public:
*/
void forceDOSTracks() { _forceDOSTracks = true; }
+ /**
+ * Autodetects, if Pseudo Mouse ability is enabled (different behavior in keyboard driver)
+ * @return kPseudoMouseAbilityTrue or kPseudoMouseAbilityFalse
+ */
+ PseudoMouseAbilityType detectPseudoMouseAbility();
+
private:
reg_t getDetectionAddr(const Common::String &objName, Selector slc, int methodNum = -1);
@@ -130,6 +142,8 @@ private:
bool _usesCdTrack;
bool _forceDOSTracks;
+ PseudoMouseAbilityType _pseudoMouseAbility;
+
SegManager *_segMan;
Kernel *_kernel;
};
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp
index 156f6f51f7..8cecd8c82c 100644
--- a/engines/sci/engine/file.cpp
+++ b/engines/sci/engine/file.cpp
@@ -200,7 +200,7 @@ reg_t file_open(EngineState *s, const Common::String &filename, int mode, bool u
#ifdef ENABLE_SCI32
if (mode != _K_FILE_MODE_OPEN_OR_FAIL && (
- (g_sci->getGameId() == GID_PHANTASMAGORIA && filename == "phantsg.dir") ||
+ (g_sci->getGameId() == GID_PHANTASMAGORIA && (filename == "phantsg.dir" || filename == "chase.dat")) ||
(g_sci->getGameId() == GID_PQSWAT && filename == "swat.dat"))) {
debugC(kDebugLevelFile, " -> file_open opening %s for rewriting", wrappedName.c_str());
diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h
index 5ff4f932be..45477e1153 100644
--- a/engines/sci/engine/kernel.h
+++ b/engines/sci/engine/kernel.h
@@ -421,6 +421,14 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv);
#ifdef ENABLE_SCI32
// SCI2 Kernel Functions
+reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv);
+reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv);
+reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv);
+reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv);
+reg_t kPlatform32(EngineState *s, int argc, reg_t *argv);
+reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv);
+reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv);
+
reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv);
reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv);
reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv);
@@ -441,15 +449,40 @@ reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv);
reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv);
reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv);
+reg_t kRobot(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotClose(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotPause(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv);
+reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv);
+
reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv);
+reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv);
reg_t kPlayVMDRestrictPalette(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv);
+reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv);
+
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv);
reg_t kArray(EngineState *s, int argc, reg_t *argv);
reg_t kListAt(EngineState *s, int argc, reg_t *argv);
@@ -550,6 +583,7 @@ reg_t kSetScroll(EngineState *s, int argc, reg_t *argv);
reg_t kPaletteSetFromResource32(EngineState *s, int argc, reg_t *argv);
reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv);
reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv);
+reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv);
reg_t kPalCycle(EngineState *s, int argc, reg_t *argv);
reg_t kPalCycleSetCycle(EngineState *s, int argc, reg_t *argv);
@@ -576,9 +610,9 @@ reg_t kTextWidth(EngineState *s, int argc, reg_t *argv);
reg_t kSave(EngineState *s, int argc, reg_t *argv);
reg_t kAutoSave(EngineState *s, int argc, reg_t *argv);
reg_t kList(EngineState *s, int argc, reg_t *argv);
-reg_t kRobot(EngineState *s, int argc, reg_t *argv);
-reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv);
reg_t kCD(EngineState *s, int argc, reg_t *argv);
+reg_t kCheckCD(EngineState *s, int argc, reg_t *argv);
+reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv);
reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv);
reg_t kAddBefore(EngineState *s, int argc, reg_t *argv);
reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv);
diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h
index e0e4dcc233..6e141e7f3b 100644
--- a/engines/sci/engine/kernel_tables.h
+++ b/engines/sci/engine/kernel_tables.h
@@ -183,8 +183,8 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
};
#ifdef ENABLE_SCI32
-// NOTE: In SSCI, some 'unused' kDoAudio subops are actually
-// called indirectly by kDoSound:
+// NOTE: In SSCI, some 'unused' kDoAudio subops are actually called indirectly
+// by kDoSound:
//
// kDoSoundGetAudioCapability -> kDoAudioGetCapability
// kDoSoundPlay -> kDoAudioPlay, kDoAudioStop
@@ -194,23 +194,26 @@ static const SciKernelMapSubEntry kDoSound_subops[] = {
// kDoSoundSetLoop -> kDoAudioSetLoop
// kDoSoundUpdateCues -> kDoAudioPosition
//
-// In ScummVM, logic inside these kernel functions has been
-// moved to methods of Audio32, and direct calls to Audio32
-// are made from kDoSound instead.
+// In ScummVM, logic inside these kernel functions has been moved to methods of
+// Audio32, and direct calls to Audio32 are made from kDoSound instead.
//
-// Some kDoAudio methods are esoteric and appear to be used
-// only by one or two games:
+// Some kDoAudio methods are esoteric and appear to be used only by one or two
+// games:
//
-// kDoAudioMixing: Phantasmagoria (other games call this
-// function, but only to disable the feature)
-// kDoAudioHasSignal: SQ6 TalkRandCycle
-// kDoAudioPan: Rama RegionSFX::pan method
+// - kDoAudioMixing: Phantasmagoria (other games call this function, but only
+// to disable the feature)
+// - kDoAudioHasSignal: SQ6 TalkRandCycle
+// - kDoAudioPan: Rama RegionSFX::pan method
+// - kDoAudioCritical: Phantasmagoria, chapter 3, nursery (room 14200), during
+// the "ghost lullaby" event. It is used to make the
+// lullaby sound exclusive, but it really doesn't make any
+// major difference. Returning 0 means "non-critical", i.e.
+// normal audio behavior.
//
-// Finally, there is a split in SCI2.1mid audio code.
-// QFG4CD & SQ6 do not have opcodes 18 and 19, but they
-// exist in GK2, KQ7 2.00b, Phantasmagoria 1, PQ:SWAT, and
-// Torin. (It is unknown if they exist in MUMG Deluxe or
-// Shivers 1; they are not used in either of these games.)
+// Finally, there is a split in SCI2.1mid audio code. QFG4CD & SQ6 do not have
+// opcodes 18 and 19, but they exist in GK2, KQ7 2.00b, Phantasmagoria 1,
+// PQ:SWAT, and Torin. It is unknown if they exist in MUMG Deluxe or Shivers 1;
+// they are not used in either of these games.
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kDoAudio_subops[] = {
@@ -235,7 +238,7 @@ static const SciKernelMapSubEntry kDoAudio_subops[] = {
{ SIG_SINCE_SCI21MID, 15, MAP_CALL(DoAudioFade), "(iiii)(i)(i)", NULL },
{ SIG_SINCE_SCI21MID, 16, MAP_DUMMY(DoAudioFade36), "iiiii(iii)(i)", NULL },
{ SIG_SINCE_SCI21MID, 17, MAP_CALL(DoAudioHasSignal), "", NULL },
- { SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical), "", NULL },
+ { SIG_SINCE_SCI21MID, 18, MAP_EMPTY(DoAudioCritical), "(i)", NULL },
{ SIG_SINCE_SCI21MID, 19, MAP_CALL(DoAudioSetLoop), "iii(o)", NULL },
{ SIG_SCI3, 20, MAP_DUMMY(DoAudioPan), "", NULL },
{ SIG_SCI3, 21, MAP_DUMMY(DoAudioPanOff), "", NULL },
@@ -294,16 +297,17 @@ static const SciKernelMapSubEntry kPalette_subops[] = {
{ SIG_SCI16, 1, MAP_CALL(PaletteSetFromResource), "i(i)", NULL },
{ SIG_SCI16, 2, MAP_CALL(PaletteSetFlag), "iii", NULL },
{ SIG_SCI16, 3, MAP_CALL(PaletteUnsetFlag), "iii", kPaletteUnsetFlag_workarounds },
-#ifdef ENABLE_SCI32
- { SIG_SCI32, 1, MAP_CALL(PaletteSetFromResource32), "i(i)", NULL },
- { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL },
- { SIG_SCI32, 3, MAP_CALL(PaletteFindColor32), "iii", NULL },
-#endif
{ SIG_SCI16, 4, MAP_CALL(PaletteSetIntensity), "iii(i)", NULL },
{ SIG_SCI16, 5, MAP_CALL(PaletteFindColor), "iii", NULL },
{ SIG_SCI16, 6, MAP_CALL(PaletteAnimate), "i*", NULL },
{ SIG_SCI16, 7, MAP_CALL(PaletteSave), "", NULL },
{ SIG_SCI16, 8, MAP_CALL(PaletteRestore), "[r0]", NULL },
+#ifdef ENABLE_SCI32
+ { SIG_SCI32, 1, MAP_CALL(PaletteSetFromResource32), "i(i)", NULL },
+ { SIG_SCI32, 2, MAP_CALL(PaletteSetFade), "iii", NULL },
+ { SIG_SCI32, 3, MAP_CALL(PaletteFindColor32), "iii", NULL },
+ { SIG_SCI3, 4, MAP_CALL(PaletteSetGamma), "i", NULL },
+#endif
SCI_SUBOPENTRY_TERMINATOR
};
@@ -376,7 +380,7 @@ static const SciKernelMapSubEntry kText_subops[] = {
// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kBitmap_subops[] = {
{ SIG_SINCE_SCI21, 0, MAP_CALL(BitmapCreate), "iiii(i)(i)(i)", NULL },
- { SIG_SINCE_SCI21, 1, MAP_CALL(BitmapDestroy), "r", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(BitmapDestroy), "[r!]", NULL },
{ SIG_SINCE_SCI21, 2, MAP_CALL(BitmapDrawLine), "riiiii(i)(i)", NULL },
{ SIG_SINCE_SCI21, 3, MAP_CALL(BitmapDrawView), "riii(i)(i)(0)(i)(i)", NULL },
{ SIG_SINCE_SCI21, 4, MAP_CALL(BitmapDrawText), "rriiiiiiiiiii", NULL },
@@ -396,6 +400,13 @@ static const SciKernelMapSubEntry kBitmap_subops[] = {
};
// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kCD_subops[] = {
+ { SIG_SINCE_SCI21MID, 0, MAP_CALL(CheckCD), "(i)", NULL },
+ { SIG_SINCE_SCI21MID, 1, MAP_CALL(GetSavedCD), "", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
static const SciKernelMapSubEntry kList_subops[] = {
{ SIG_SINCE_SCI21, 0, MAP_CALL(NewList), "", NULL },
{ SIG_SINCE_SCI21, 1, MAP_CALL(DisposeList), "l", NULL },
@@ -406,8 +417,8 @@ static const SciKernelMapSubEntry kList_subops[] = {
{ SIG_SINCE_SCI21, 6, MAP_CALL(NextNode), "n", NULL },
{ SIG_SINCE_SCI21, 7, MAP_CALL(PrevNode), "n", NULL },
{ SIG_SINCE_SCI21, 8, MAP_CALL(NodeValue), "[n0]", NULL },
- { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn.", NULL },
- { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln.", NULL },
+ { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn(.)", NULL },
+ { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln(.)", NULL },
{ SIG_SINCE_SCI21, 11, MAP_CALL(AddToEnd), "ln(.)", NULL },
{ SIG_SINCE_SCI21, 12, MAP_CALL(AddBefore), "ln.", NULL },
{ SIG_SINCE_SCI21, 13, MAP_CALL(MoveToFront), "ln", NULL },
@@ -423,6 +434,27 @@ static const SciKernelMapSubEntry kList_subops[] = {
SCI_SUBOPENTRY_TERMINATOR
};
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kShowMovieWin_subops[] = {
+ { SIG_SCI2, 0, MAP_CALL(ShowMovieWinOpen), "r", NULL },
+ { SIG_SCI2, 1, MAP_CALL(ShowMovieWinInit), "ii(ii)", NULL },
+ { SIG_SCI2, 2, MAP_CALL(ShowMovieWinPlay), "i", NULL },
+ { SIG_SCI2, 6, MAP_CALL(ShowMovieWinClose), "", NULL },
+ { SIG_SINCE_SCI21, 0, MAP_CALL(ShowMovieWinOpen), "ir", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(ShowMovieWinInit), "iii(ii)", NULL },
+ { SIG_SINCE_SCI21, 2, MAP_CALL(ShowMovieWinPlay), "i(ii)(i)(i)", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(ShowMovieWinClose), "i", NULL },
+ // Since movies are rendered within the graphics engine in ScummVM,
+ // it is not necessary to copy the palette from SCI to MCI, so this
+ // can be a no-op
+ { SIG_SINCE_SCI21, 7, MAP_EMPTY(ShowMovieWinSetPalette), "i", NULL },
+ { SIG_SINCE_SCI21, 8, MAP_CALL(ShowMovieWinGetDuration), "i", NULL },
+ { SIG_SINCE_SCI21, 11, MAP_CALL(ShowMovieWinCue), "ii", NULL },
+ { SIG_SINCE_SCI21, 14, MAP_CALL(ShowMovieWinPlayUntilEvent), "i(i)", NULL },
+ { SIG_SINCE_SCI21, 15, MAP_CALL(ShowMovieWinInitDouble), "iii", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
// There are a lot of subops to PlayVMD, but only a few of them are ever
// actually used by games
// version, subId, function-mapping, signature, workarounds
@@ -430,12 +462,30 @@ static const SciKernelMapSubEntry kPlayVMD_subops[] = {
{ SIG_SINCE_SCI21, 0, MAP_CALL(PlayVMDOpen), "r(i)(i)", NULL },
{ SIG_SINCE_SCI21, 1, MAP_CALL(PlayVMDInit), "ii(i)(i)(ii)", NULL },
{ SIG_SINCE_SCI21, 6, MAP_CALL(PlayVMDClose), "", NULL },
+ { SIG_SINCE_SCI21, 10, MAP_CALL(PlayVMDGetStatus), "", NULL },
{ SIG_SINCE_SCI21, 14, MAP_CALL(PlayVMDPlayUntilEvent), "i(i)(i)", NULL },
{ SIG_SINCE_SCI21, 16, MAP_CALL(PlayVMDShowCursor), "i", NULL },
{ SIG_SINCE_SCI21, 17, MAP_DUMMY(PlayVMDStartBlob), "", NULL },
{ SIG_SINCE_SCI21, 18, MAP_DUMMY(PlayVMDStopBlobs), "", NULL },
{ SIG_SINCE_SCI21, 21, MAP_CALL(PlayVMDSetBlackoutArea), "iiii", NULL },
{ SIG_SINCE_SCI21, 23, MAP_CALL(PlayVMDRestrictPalette), "ii", NULL },
+ { SIG_SCI3, 28, MAP_EMPTY(PlayVMDSetPreload), "i", NULL },
+ SCI_SUBOPENTRY_TERMINATOR
+};
+
+// version, subId, function-mapping, signature, workarounds
+static const SciKernelMapSubEntry kRobot_subops[] = {
+ { SIG_SINCE_SCI21, 0, MAP_CALL(RobotOpen), "ioiii(i)", NULL },
+ { SIG_SINCE_SCI21, 1, MAP_CALL(RobotShowFrame), "i(ii)", NULL },
+ { SIG_SINCE_SCI21, 2, MAP_CALL(RobotGetFrameSize), "r", NULL },
+ { SIG_SINCE_SCI21, 4, MAP_CALL(RobotPlay), "", NULL },
+ { SIG_SINCE_SCI21, 5, MAP_CALL(RobotGetIsFinished), "", NULL },
+ { SIG_SINCE_SCI21, 6, MAP_CALL(RobotGetIsPlaying), "", NULL },
+ { SIG_SINCE_SCI21, 7, MAP_CALL(RobotClose), "", NULL },
+ { SIG_SINCE_SCI21, 8, MAP_CALL(RobotGetCue), "o", NULL },
+ { SIG_SINCE_SCI21, 10, MAP_CALL(RobotPause), "", NULL },
+ { SIG_SINCE_SCI21, 11, MAP_CALL(RobotGetFrameNo), "", NULL },
+ { SIG_SINCE_SCI21, 12, MAP_CALL(RobotSetPriority), "i", NULL },
SCI_SUBOPENTRY_TERMINATOR
};
@@ -455,7 +505,7 @@ static const SciKernelMapSubEntry kString_subops[] = {
{ SIG_SCI32, 0, MAP_CALL(StringNew), "i(i)", NULL },
{ SIG_SCI32, 1, MAP_CALL(StringSize), "[or]", NULL },
{ SIG_SCI32, 2, MAP_CALL(StringAt), "[or]i", NULL },
- { SIG_SCI32, 3, MAP_CALL(StringPutAt), "[or]i(i*)", NULL },
+ { SIG_SCI32, 3, MAP_CALL(StringPutAt), "[or]i(i*)", kStringPutAt_workarounds },
// StringFree accepts invalid references
{ SIG_SCI32, 4, MAP_CALL(StringFree), "[or0!]", NULL },
{ SIG_SCI32, 5, MAP_CALL(StringFill), "[or]ii", NULL },
@@ -548,7 +598,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(Animate), SIG_EVERYWHERE, "(l0)(i)", NULL, NULL },
{ MAP_CALL(AssertPalette), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL },
- { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL },
+ { MAP_CALL(BaseSetter), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "BaseSetter", kBaseSetter32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL },
+#endif
{ MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL },
{ MAP_CALL(CantBeHere), SIG_SCI16, SIGFOR_ALL, "o(l)", NULL, NULL },
#ifdef ENABLE_SCI32
@@ -617,8 +670,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL },
{ MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL },
{ MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL },
- { MAP_CALL(GlobalToLocal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL },
- { MAP_CALL(GlobalToLocal), SIG_EVERYWHERE, "o", NULL, NULL },
+ { MAP_CALL(GlobalToLocal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "GlobalToLocal", kGlobalToLocal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL },
+#endif
{ MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL },
{ MAP_CALL(HaveMouse), SIG_EVERYWHERE, "", NULL, NULL },
{ MAP_CALL(HiliteControl), SIG_EVERYWHERE, "o", NULL, NULL },
@@ -629,8 +684,10 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(Joystick), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop
{ MAP_CALL(LastNode), SIG_EVERYWHERE, "l", NULL, NULL },
{ MAP_CALL(Load), SIG_EVERYWHERE, "ii(i*)", NULL, NULL },
- { MAP_CALL(LocalToGlobal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL },
- { MAP_CALL(LocalToGlobal), SIG_EVERYWHERE, "o", NULL, NULL },
+ { MAP_CALL(LocalToGlobal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "LocalToGlobal", kLocalToGlobal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL },
+#endif
{ MAP_CALL(Lock), SIG_EVERYWHERE, "ii(i)", NULL, NULL },
{ MAP_CALL(MapKeyToDir), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(Memory), SIG_EVERYWHERE, "i(.*)", NULL, kMemory_workarounds }, // subop
@@ -655,11 +712,15 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(Palette), SIG_EVERYWHERE, "i(.*)", kPalette_subops, NULL },
{ MAP_CALL(Parse), SIG_EVERYWHERE, "ro", NULL, NULL },
{ MAP_CALL(PicNotValid), SIG_EVERYWHERE, "(i)", NULL, NULL },
- { MAP_CALL(Platform), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(Platform), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "Platform", kPlatform32, SIG_SCI32, SIGFOR_MAC, "(.*)", NULL, NULL },
+ { "Platform", kPlatform32, SIG_SCI32, SIGFOR_ALL, "(i)", NULL, NULL },
+#endif
{ MAP_CALL(Portrait), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop
{ MAP_CALL(PrevNode), SIG_EVERYWHERE, "n", NULL, NULL },
{ MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL },
- { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, NULL },
+ { MAP_CALL(Random), SIG_EVERYWHERE, "i(i)(i)", NULL, kRandom_workarounds },
{ MAP_CALL(ReadNumber), SIG_EVERYWHERE, "r", NULL, kReadNumber_workarounds },
{ MAP_CALL(RemapColors), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, NULL },
#ifdef ENABLE_SCI32
@@ -672,23 +733,32 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL },
{ MAP_CALL(SaveGame), SIG_EVERYWHERE, "[r0]i[r0](r0)", NULL, NULL },
{ MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL },
- { MAP_CALL(SetCursor), SIG_SINCE_SCI21, SIGFOR_ALL, "i(i)([io])(i*)", NULL, NULL },
- // TODO: SCI2.1 may supply an object optionally (mother goose sci21 right on startup) - find out why
{ MAP_CALL(SetCursor), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(iiiiii)", NULL, NULL },
- { MAP_CALL(SetCursor), SIG_EVERYWHERE, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds },
+ { MAP_CALL(SetCursor), SIG_SCI16, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds },
+#ifdef ENABLE_SCI32
+ { "SetCursor", kSetCursor32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)", NULL, kSetCursor_workarounds },
+#endif
{ MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL },
{ MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL },
{ MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
{ MAP_CALL(SetNowSeen), SIG_SCI16, SIGFOR_ALL, "o(i)", NULL, NULL },
#ifdef ENABLE_SCI32
- { MAP_CALL(SetNowSeen), SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL },
+ { "SetNowSeen", kSetNowSeen32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL },
#endif
{ MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iiiii)(i)", NULL, kSetPort_workarounds },
{ MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL },
{ MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL },
- { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL },
- { MAP_CALL(ShowMovie), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(ShakeScreen), SIG_SCI16, SIGFOR_ALL, "(i)(i)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "ShakeScreen", kShakeScreen32, SIG_SCI32, SIGFOR_ALL, "i(i)", NULL, NULL },
+#endif
+ { MAP_CALL(ShowMovie), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL },
+#ifdef ENABLE_SCI32
+ { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_DOS, "ri(i)(i)", NULL, NULL },
+ { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_MAC, "ri(i)(i)", NULL, NULL },
+ { "ShowMovie", kShowMovieWin, SIG_SCI32, SIGFOR_WIN, "(.*)", kShowMovieWin_subops, NULL },
+#endif
{ MAP_CALL(Show), SIG_EVERYWHERE, "i", NULL, NULL },
{ MAP_CALL(SinDiv), SIG_EVERYWHERE, "ii", NULL, NULL },
{ MAP_CALL(Sort), SIG_EVERYWHERE, "ooo", NULL, NULL },
@@ -745,7 +815,7 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_CALL(CreateTextBitmap), SIG_EVERYWHERE, "i(.*)", NULL, NULL },
{ MAP_CALL(DeletePlane), SIG_EVERYWHERE, "o", NULL, NULL },
{ MAP_CALL(DeleteScreenItem), SIG_EVERYWHERE, "o", NULL, NULL },
- { "DisposeTextBitmap", kBitmapDestroy, SIG_SCI2, SIGFOR_ALL, "r", NULL, NULL },
+ { "DisposeTextBitmap", kBitmapDestroy, SIG_SCI2, SIGFOR_ALL, "[r!]", NULL, NULL },
{ MAP_CALL(FrameOut), SIG_EVERYWHERE, "(i)", NULL, NULL },
{ MAP_CALL(GetHighPlanePri), SIG_EVERYWHERE, "", NULL, NULL },
{ MAP_CALL(InPolygon), SIG_EVERYWHERE, "iio", NULL, NULL },
@@ -815,12 +885,12 @@ static SciKernelMapEntry s_kernelMap[] = {
{ MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL },
// SCI2.1 Kernel Functions
- { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(CD), SIG_SINCE_SCI21MID, SIGFOR_ALL, "(.*)", kCD_subops, NULL },
{ MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iioi", NULL, NULL },
{ MAP_CALL(List), SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL },
{ MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL },
{ MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL },
- { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL },
+ { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", kRobot_subops, NULL },
{ MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL },
{ MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL },
{ MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii(i)(i)", NULL, NULL },
diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp
index 534d9ce713..9250e0fc13 100644
--- a/engines/sci/engine/kevent.cpp
+++ b/engines/sci/engine/kevent.cpp
@@ -34,6 +34,9 @@
#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/cursor.h"
#include "sci/graphics/maciconbar.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/frameout.h"
+#endif
namespace Sci {
@@ -58,10 +61,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
if (g_debug_simulated_key && (mask & SCI_EVENT_KEYBOARD)) {
// In case we use a simulated event we query the current mouse position
mousePos = g_sci->_gfxCursor->getPosition();
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY)
- g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x);
-#endif
+
// Limit the mouse cursor position, if necessary
g_sci->_gfxCursor->refreshPosition();
@@ -86,11 +86,14 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) {
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2)
mousePos = curEvent.mousePosSci;
- else
+ else {
#endif
mousePos = curEvent.mousePos;
- // Limit the mouse cursor position, if necessary
- g_sci->_gfxCursor->refreshPosition();
+ // Limit the mouse cursor position, if necessary
+ g_sci->_gfxCursor->refreshPosition();
+#ifdef ENABLE_SCI32
+ }
+#endif
if (g_sci->getVocabulary())
g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event
@@ -258,11 +261,12 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) {
if (readSelectorValue(segMan, obj, SELECTOR(type)) == SCI_EVENT_KEYBOARD) { // Keyboard
uint16 message = readSelectorValue(segMan, obj, SELECTOR(message));
uint16 eventType = SCI_EVENT_DIRECTION;
- // Check if the game is using cursor views. These games allowed control
- // of the mouse cursor via the keyboard controls (the so called
- // "PseudoMouse" functionality in script 933).
- if (g_sci->_features->detectSetCursorType() == SCI_VERSION_1_1)
+ // It seems with SCI1 Sierra started to add the SCI_EVENT_DIRECTION bit instead of setting it directly.
+ // It was done inside the keyboard driver and is required for the PseudoMouse functionality and class
+ // to work (script 933).
+ if (g_sci->_features->detectPseudoMouseAbility() == kPseudoMouseAbilityTrue) {
eventType |= SCI_EVENT_KEYBOARD;
+ }
for (int i = 0; i < 9; i++) {
if (keyToDirMap[i].key == message) {
@@ -280,14 +284,13 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) {
reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
- reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32
SegManager *segMan = s->_segMan;
if (obj.getSegment()) {
int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
- g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y, planeObject);
+ g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y);
writeSelectorValue(segMan, obj, SELECTOR(x), x);
writeSelectorValue(segMan, obj, SELECTOR(y), y);
@@ -299,14 +302,13 @@ reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) {
reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) {
reg_t obj = argv[0];
- reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32
SegManager *segMan = s->_segMan;
if (obj.getSegment()) {
int16 x = readSelectorValue(segMan, obj, SELECTOR(x));
int16 y = readSelectorValue(segMan, obj, SELECTOR(y));
- g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y, planeObject);
+ g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y);
writeSelectorValue(segMan, obj, SELECTOR(x), x);
writeSelectorValue(segMan, obj, SELECTOR(y), y);
@@ -321,4 +323,52 @@ reg_t kJoystick(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+#ifdef ENABLE_SCI32
+reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv) {
+ const reg_t result = argv[0];
+ const reg_t planeObj = argv[1];
+
+ bool visible = true;
+ Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
+ if (plane == nullptr) {
+ plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
+ visible = false;
+ }
+ if (plane == nullptr) {
+ error("kGlobalToLocal: Plane %04x:%04x not found", PRINT_REG(planeObj));
+ }
+
+ const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) - plane->_gameRect.left;
+ const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) - plane->_gameRect.top;
+
+ writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
+ writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
+
+ return make_reg(0, visible);
+}
+
+reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) {
+ const reg_t result = argv[0];
+ const reg_t planeObj = argv[1];
+
+ bool visible = true;
+ Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj);
+ if (plane == nullptr) {
+ plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
+ visible = false;
+ }
+ if (plane == nullptr) {
+ error("kLocalToGlobal: Plane %04x:%04x not found", PRINT_REG(planeObj));
+ }
+
+ const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) + plane->_gameRect.left;
+ const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) + plane->_gameRect.top;
+
+ writeSelectorValue(s->_segMan, result, SELECTOR(x), x);
+ writeSelectorValue(s->_segMan, result, SELECTOR(y), y);
+
+ return make_reg(0, visible);
+}
+#endif
+
} // End of namespace Sci
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp
index 4508a481a0..e8b9d0461d 100644
--- a/engines/sci/engine/kfile.cpp
+++ b/engines/sci/engine/kfile.cpp
@@ -40,6 +40,9 @@
#include "sci/engine/savegame.h"
#include "sci/sound/audio.h"
#include "sci/console.h"
+#ifdef ENABLE_SCI32
+#include "sci/resource.h"
+#endif
namespace Sci {
@@ -196,26 +199,25 @@ reg_t kValidPath(EngineState *s, int argc, reg_t *argv) {
#ifdef ENABLE_SCI32
reg_t kCD(EngineState *s, int argc, reg_t *argv) {
- // TODO: Stub
- switch (argv[0].toUint16()) {
- case 0:
- if (argc == 1) {
- // Check if a disc is in the drive
- return TRUE_REG;
- } else {
- // Check if the specified disc is in the drive
- // and return the current disc number. We just
- // return the requested disc number.
- return argv[1];
- }
- case 1:
- // Return the current CD number
- return make_reg(0, 1);
- default:
- warning("CD(%d)", argv[0].toUint16());
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kCheckCD(EngineState *s, int argc, reg_t *argv) {
+ const int16 cdNo = argc > 0 ? argv[0].toSint16() : 0;
+
+ if (cdNo) {
+ g_sci->getResMan()->findDisc(cdNo);
}
- return NULL_REG;
+ return make_reg(0, g_sci->getResMan()->getCurrentDiscNo());
+}
+
+reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) {
+ // TODO: This is wrong, CD number needs to be available prior to
+ // the save game being loaded
+ return make_reg(0, g_sci->getResMan()->getCurrentDiscNo());
}
#endif
@@ -251,6 +253,28 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {
return SIGNAL_REG;
}
+ // Torin's autosave system checks for the presence of autosave.cat
+ // by opening it. Since we don't use .cat files, we instead check
+ // for autosave.000 or autosave.001.
+ //
+ // The same logic is being followed for torinsg.cat - this shows
+ // the "Open..." button when continuing a game.
+ //
+ // This has the added benefit of not detecting an SSCI autosave.cat
+ // accompanying SSCI autosave files that we wouldn't be able to load.
+ if (g_sci->getGameId() == GID_TORIN && (name == "autosave.cat" || name == "torinsg.cat")) {
+ Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
+ const Common::String pattern = (name == "autosave.cat") ? g_sci->wrapFilename("autosave.###") : g_sci->getSavegamePattern();
+ bool exists = !saveFileMan->listSavefiles(pattern).empty();
+ if (exists) {
+ // Dummy handle. Torin only checks if this is SIGNAL_REG,
+ // and calls kFileIOClose on it.
+ return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE);
+ } else {
+ return SIGNAL_REG;
+ }
+ }
+
if (name.empty()) {
// Happens many times during KQ1 (e.g. when typing something)
debugC(kDebugLevelFile, "Attempted to open a file with an empty filename");
@@ -608,6 +632,15 @@ reg_t kFileIORename(EngineState *s, int argc, reg_t *argv) {
Common::String oldName = s->_segMan->getString(argv[0]);
Common::String newName = s->_segMan->getString(argv[1]);
+ // We don't fully implement all cases that could occur here, and
+ // assume the file to be renamed is a wrapped filename.
+ // Known usage: In Phant1 and KQ7 while deleting savegames.
+ // The scripts rewrite the dir file as a temporary file, and then
+ // rename it to the actual dir file.
+
+ oldName = g_sci->wrapFilename(oldName);
+ newName = g_sci->wrapFilename(newName);
+
// SCI1.1 returns 0 on success and a DOS error code on fail. SCI32
// returns -1 on fail. We just return -1 for all versions.
if (g_sci->getSaveFileManager()->renameSavefile(oldName, newName))
@@ -688,7 +721,7 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv) {
#endif
reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
- Common::String game_id;
+ Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : "";
int16 virtualId = argv[1].toSint16();
int16 savegameId = -1;
Common::String game_description;
@@ -703,6 +736,13 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+ // Torin has two sets of saves: autosave.### and torinsg.###, both with
+ // their own slots and .cat file.
+ // The autosave system uses autosave.000 and autosave.001.
+ // It also checks the presence of autosave.cat to determine if it should
+ // show the chapter selection menu on startup. (See kFileIOOpen.)
+ bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave";
+
if (argv[0].isNull()) {
// Direct call, from a patched Game::save
if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull()))
@@ -722,9 +762,15 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
if (savegameId < 0)
return NULL_REG;
+ } else if (torinAutosave) {
+ if (argv[2].isNull())
+ error("kSaveGame: called with description being NULL");
+ game_description = s->_segMan->getString(argv[2]);
+ savegameId = virtualId;
+
+ debug(3, "kSaveGame(%s,%d,%s,%s) [Torin autosave]", game_id.c_str(), virtualId, game_description.c_str(), version.c_str());
} else {
// Real call from script
- game_id = s->_segMan->getString(argv[0]);
if (argv[2].isNull())
error("kSaveGame: called with description being NULL");
game_description = s->_segMan->getString(argv[2]);
@@ -798,6 +844,10 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {
Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
Common::OutSaveFile *out;
+ if (torinAutosave) {
+ filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId));
+ }
+
out = saveFileMan->openForSaving(filename);
if (!out) {
warning("Error opening savegame \"%s\" for writing", filename.c_str());
@@ -826,6 +876,10 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId);
+
+ // See comment in kSaveGame
+ bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave";
+
if (argv[0].isNull()) {
// Direct call, either from launcher or from a patched Game::restore
if (savegameId == -1) {
@@ -841,7 +895,7 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
pausedMusic = true;
}
// don't adjust ID of the saved game, it's already correct
- } else {
+ } else if (!torinAutosave) {
if (g_sci->getGameId() == GID_JONES) {
// Jones has one save slot only
savegameId = 0;
@@ -858,8 +912,9 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
s->r_acc = NULL_REG; // signals success
Common::Array<SavegameDesc> saves;
- listSavegames(saves);
- if (findSavegame(saves, savegameId) == -1) {
+ if (!torinAutosave)
+ listSavegames(saves);
+ if (!torinAutosave && findSavegame(saves, savegameId) == -1) {
s->r_acc = TRUE_REG;
warning("Savegame ID %d not found", savegameId);
} else {
@@ -867,6 +922,10 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {
Common::String filename = g_sci->getSavegameName(savegameId);
Common::SeekableReadStream *in;
+ if (torinAutosave) {
+ filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId));
+ }
+
in = saveFileMan->openForLoading(filename);
if (in) {
// found a savegame file
diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp
index cae5a09789..d375a27954 100644
--- a/engines/sci/engine/kgraphics.cpp
+++ b/engines/sci/engine/kgraphics.cpp
@@ -579,17 +579,8 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) {
}
reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) {
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2) {
- g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]);
- return NULL_REG;
- } else {
-#endif
- g_sci->_gfxCompare->kernelSetNowSeen(argv[0]);
- return s->r_acc;
-#ifdef ENABLE_SCI32
- }
-#endif
+ g_sci->_gfxCompare->kernelSetNowSeen(argv[0]);
+ return s->r_acc;
}
reg_t kPalette(EngineState *s, int argc, reg_t *argv) {
diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp
index e458109cc2..a33fcf3167 100644
--- a/engines/sci/engine/kgraphics32.cpp
+++ b/engines/sci/engine/kgraphics32.cpp
@@ -37,8 +37,6 @@
#include "sci/graphics/cache.h"
#include "sci/graphics/compare.h"
#include "sci/graphics/controls16.h"
-#include "sci/graphics/coordadjuster.h"
-#include "sci/graphics/cursor.h"
#include "sci/graphics/palette.h"
#include "sci/graphics/paint16.h"
#include "sci/graphics/picture.h"
@@ -48,6 +46,7 @@
#include "sci/graphics/text16.h"
#include "sci/graphics/view.h"
#ifdef ENABLE_SCI32
+#include "sci/graphics/cursor32.h"
#include "sci/graphics/celobj32.h"
#include "sci/graphics/controls32.h"
#include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class
@@ -64,6 +63,107 @@ namespace Sci {
extern void showScummVMDialog(const Common::String &message);
+reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv) {
+ reg_t object = argv[0];
+
+ const GuiResourceId viewId = readSelectorValue(s->_segMan, object, SELECTOR(view));
+ const int16 loopNo = readSelectorValue(s->_segMan, object, SELECTOR(loop));
+ const int16 celNo = readSelectorValue(s->_segMan, object, SELECTOR(cel));
+ const int16 x = readSelectorValue(s->_segMan, object, SELECTOR(x));
+ const int16 y = readSelectorValue(s->_segMan, object, SELECTOR(y));
+
+ CelObjView celObj(viewId, loopNo, celNo);
+
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ const Ratio scaleX(scriptWidth, celObj._scaledWidth);
+ const Ratio scaleY(scriptHeight, celObj._scaledHeight);
+
+ int16 brLeft;
+
+ if (celObj._mirrorX) {
+ brLeft = x - ((celObj._width - celObj._displace.x) * scaleX).toInt();
+ } else {
+ brLeft = x - (celObj._displace.x * scaleX).toInt();
+ }
+
+ const int16 brRight = brLeft + (celObj._width * scaleX).toInt() - 1;
+
+ writeSelectorValue(s->_segMan, object, SELECTOR(brLeft), brLeft);
+ writeSelectorValue(s->_segMan, object, SELECTOR(brRight), brRight);
+ writeSelectorValue(s->_segMan, object, SELECTOR(brBottom), y + 1);
+ writeSelectorValue(s->_segMan, object, SELECTOR(brTop), y + 1 - readSelectorValue(s->_segMan, object, SELECTOR(yStep)));
+
+ return s->r_acc;
+}
+
+reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv) {
+ const bool found = g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]);
+
+ // NOTE: MGDX is assumed to use the older kSetNowSeen since it was
+ // released before SQ6, but this has not been verified since it cannot be
+ // disassembled at the moment (Phar Lap Windows-only release)
+ if (getSciVersion() <= SCI_VERSION_2_1_EARLY ||
+ g_sci->getGameId() == GID_SQ6 ||
+ g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
+
+ if (!found) {
+ error("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0]));
+ }
+ return s->r_acc;
+ }
+
+ if (!found) {
+ warning("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0]));
+ }
+
+ return make_reg(0, found);
+}
+
+reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv) {
+ switch (argc) {
+ case 1: {
+ if (argv[0].toSint16() == -2) {
+ g_sci->_gfxCursor32->clearRestrictedArea();
+ } else {
+ if (argv[0].isNull()) {
+ g_sci->_gfxCursor32->hide();
+ } else {
+ g_sci->_gfxCursor32->show();
+ }
+ }
+ break;
+ }
+ case 2: {
+ const Common::Point position(argv[0].toSint16(), argv[1].toSint16());
+ g_sci->_gfxCursor32->setPosition(position);
+ break;
+ }
+ case 3: {
+ g_sci->_gfxCursor32->setView(argv[0].toUint16(), argv[1].toSint16(), argv[2].toSint16());
+ break;
+ }
+ case 4: {
+ const Common::Rect restrictRect(argv[0].toSint16(),
+ argv[1].toSint16(),
+ argv[2].toSint16() + 1,
+ argv[3].toSint16() + 1);
+ g_sci->_gfxCursor32->setRestrictedArea(restrictRect);
+ break;
+ }
+ default:
+ error("kSetCursor: Invalid number of arguments (%d)", argc);
+ }
+
+ return s->r_acc;
+}
+
+reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_gfxFrameout->shakeScreen(argv[0].toSint16(), (ShakeDirection)argv[1].toSint16());
+ return s->r_acc;
+}
+
reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) {
const Buffer &buffer = g_sci->_gfxFrameout->getCurrentBuffer();
if (buffer.screenWidth < 640 || buffer.screenHeight < 400)
@@ -266,7 +366,7 @@ reg_t kMessageBox(EngineState *s, int argc, reg_t *argv) {
* effect
*/
reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
- ShowStyleType type = (ShowStyleType)argv[0].toUint16();
+ const uint16 type = argv[0].toUint16();
reg_t planeObj = argv[1];
int16 seconds = argv[2].toSint16();
// NOTE: This value seems to indicate whether the transition is an
@@ -301,6 +401,10 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
divisions = argc > 9 ? argv[9].toSint16() : -1;
}
+ if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() != GID_KQ7 && type == 15) || type > 15) {
+ error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj));
+ }
+
// TODO: Reuse later for SCI2 and SCI3 implementation and then discard
// warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, "
// "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, "
@@ -312,7 +416,7 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) {
// NOTE: The order of planeObj and showStyle are reversed
// because this is how SCI3 called the corresponding method
// on the KernelMgr
- g_sci->_gfxTransitions32->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen);
+ g_sci->_gfxTransitions32->kernelSetShowStyle(argc, planeObj, (ShowStyleType)type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen);
return s->r_acc;
}
@@ -543,7 +647,16 @@ reg_t kBitmapCreate(EngineState *s, int argc, reg_t *argv) {
}
reg_t kBitmapDestroy(EngineState *s, int argc, reg_t *argv) {
- s->_segMan->freeBitmap(argv[0]);
+ const reg_t &addr = argv[0];
+ const SegmentObj *const segment = s->_segMan->getSegmentObj(addr.getSegment());
+
+ if (segment != nullptr &&
+ segment->getType() == SEG_TYPE_BITMAP &&
+ segment->isValidOffset(addr.getOffset())) {
+
+ s->_segMan->freeBitmap(addr);
+ }
+
return s->r_acc;
}
@@ -797,6 +910,19 @@ reg_t kPaletteFindColor32(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, g_sci->_gfxPalette32->matchColor(r, g, b));
}
+/*
+ * Used in SCI3. SCI3 contains 6 gamma look-up tables, with the first
+ * table (gamma = 0) being the default one.
+ */
+reg_t kPaletteSetGamma(EngineState *s, int argc, reg_t *argv) {
+ const uint8 gamma = argv[0].toUint16();
+ assert(gamma <= 6);
+
+ warning("TODO: kPaletteSetGamma(%d)", gamma);
+
+ return s->r_acc;
+}
+
reg_t kPaletteSetFade(EngineState *s, int argc, reg_t *argv) {
uint16 fromColor = argv[0].toUint16();
uint16 toColor = argv[1].toUint16();
diff --git a/engines/sci/engine/klists.cpp b/engines/sci/engine/klists.cpp
index c0da2daaeb..e780d3cdc3 100644
--- a/engines/sci/engine/klists.cpp
+++ b/engines/sci/engine/klists.cpp
@@ -374,13 +374,21 @@ reg_t kFindKey(EngineState *s, int argc, reg_t *argv) {
reg_t kDeleteKey(EngineState *s, int argc, reg_t *argv) {
reg_t node_pos = kFindKey(s, 2, argv);
- Node *n;
List *list = s->_segMan->lookupList(argv[0]);
if (node_pos.isNull())
return NULL_REG; // Signal failure
- n = s->_segMan->lookupNode(node_pos);
+ Node *n = s->_segMan->lookupNode(node_pos);
+
+#ifdef ENABLE_SCI32
+ for (int i = 1; i <= list->numRecursions; ++i) {
+ if (list->nextNodes[i] == node_pos) {
+ list->nextNodes[i] = n->succ;
+ }
+ }
+#endif
+
if (list->first == node_pos)
list->first = n->succ;
if (list->last == node_pos)
@@ -486,7 +494,7 @@ reg_t kListAt(EngineState *s, int argc, reg_t *argv) {
List *list = s->_segMan->lookupList(argv[0]);
reg_t curAddress = list->first;
if (list->first.isNull()) {
- error("kListAt tried to reference empty list (%04x:%04x)", PRINT_REG(argv[0]));
+ // Happens in Torin when examining Di's locket in chapter 3
return NULL_REG;
}
Node *curNode = s->_segMan->lookupNode(curAddress);
@@ -544,9 +552,18 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) {
ObjVarRef address;
+ ++list->numRecursions;
+
+ if (list->numRecursions > ARRAYSIZE(list->nextNodes)) {
+ error("Too much recursion in kListEachElementDo");
+ }
+
while (curNode) {
- // We get the next node here as the current node might be gone after the invoke
- reg_t nextNode = curNode->succ;
+ // We get the next node here as the current node might be deleted by the
+ // invoke. In the case that the next node is also deleted, kDeleteKey
+ // needs to be able to adjust the location of the next node, which is
+ // why it is stored on the list instead of on the stack
+ list->nextNodes[list->numRecursions] = curNode->succ;
curObject = curNode->value;
// First, check if the target selector is a variable
@@ -559,11 +576,18 @@ reg_t kListEachElementDo(EngineState *s, int argc, reg_t *argv) {
}
} else {
invokeSelector(s, curObject, slc, argc, argv, argc - 2, argv + 2);
+ // Check if the call above leads to a game restore, in which case
+ // the segment manager will be reset, and the original list will
+ // be invalidated
+ if (s->abortScriptProcessing == kAbortLoadGame)
+ return s->r_acc;
}
- curNode = s->_segMan->lookupNode(nextNode);
+ curNode = s->_segMan->lookupNode(list->nextNodes[list->numRecursions]);
}
+ --list->numRecursions;
+
return s->r_acc;
}
diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp
index 1924848717..f2a3c6b0f7 100644
--- a/engines/sci/engine/kmisc.cpp
+++ b/engines/sci/engine/kmisc.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/system.h"
#include "sci/sci.h"
@@ -29,6 +30,9 @@
#include "sci/engine/kernel.h"
#include "sci/engine/gc.h"
#include "sci/graphics/cursor.h"
+#ifdef ENABLE_SCI32
+#include "sci/graphics/cursor32.h"
+#endif
#include "sci/graphics/maciconbar.h"
#include "sci/console.h"
@@ -510,9 +514,12 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) {
// In SCI1, its usage is still unknown
// In SCI1.1, it's NOP
// In SCI32, it's used for remapping cursor ID's
+#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2_1_EARLY) // Set Mac cursor remap
- g_sci->_gfxCursor->setMacCursorRemapList(argc - 1, argv + 1);
- else if (getSciVersion() != SCI_VERSION_1_1)
+ g_sci->_gfxCursor32->setMacCursorRemapList(argc - 1, argv + 1);
+ else
+#endif
+ if (getSciVersion() != SCI_VERSION_1_1)
warning("Unknown SCI1 kMacPlatform(0) call");
break;
case 4: // Handle icon bar code
@@ -535,31 +542,28 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) {
}
enum kSciPlatforms {
+ kSciPlatformMacintosh = 0,
kSciPlatformDOS = 1,
kSciPlatformWindows = 2
};
-enum kPlatformOps {
- kPlatformUnk0 = 0,
- kPlatformCDSpeed = 1,
- kPlatformUnk2 = 2,
- kPlatformCDCheck = 3,
- kPlatformGetPlatform = 4,
- kPlatformUnk5 = 5,
- kPlatformIsHiRes = 6,
- kPlatformIsItWindows = 7
-};
-
reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
+ enum Operation {
+ kPlatformUnknown = 0,
+ kPlatformGetPlatform = 4,
+ kPlatformUnknown5 = 5,
+ kPlatformIsHiRes = 6,
+ kPlatformWin311OrHigher = 7
+ };
+
bool isWindows = g_sci->getPlatform() == Common::kPlatformWindows;
- if (argc == 0 && getSciVersion() < SCI_VERSION_2) {
+ if (argc == 0) {
// This is called in KQ5CD with no parameters, where it seems to do some
// graphics driver check. This kernel function didn't have subfunctions
// then. If 0 is returned, the game functions normally, otherwise all
// the animations show up like a slideshow (e.g. in the intro). So we
- // return 0. However, the behavior changed for kPlatform with no
- // parameters in SCI32.
+ // return 0.
return NULL_REG;
}
@@ -571,30 +575,23 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
uint16 operation = (argc == 0) ? 0 : argv[0].toUint16();
switch (operation) {
- case kPlatformCDSpeed:
- // TODO: Returns CD Speed?
- warning("STUB: kPlatform(CDSpeed)");
- break;
- case kPlatformUnk2:
- // Always returns 2
- return make_reg(0, 2);
- case kPlatformCDCheck:
- // TODO: Some sort of CD check?
- warning("STUB: kPlatform(CDCheck)");
- break;
- case kPlatformUnk0:
+ case kPlatformUnknown:
// For Mac versions, kPlatform(0) with other args has more functionality
if (g_sci->getPlatform() == Common::kPlatformMacintosh && argc > 1)
return kMacPlatform(s, argc - 1, argv + 1);
// Otherwise, fall through
case kPlatformGetPlatform:
- return make_reg(0, (isWindows) ? kSciPlatformWindows : kSciPlatformDOS);
- case kPlatformUnk5:
+ if (isWindows)
+ return make_reg(0, kSciPlatformWindows);
+ else if (g_sci->getPlatform() == Common::kPlatformMacintosh)
+ return make_reg(0, kSciPlatformMacintosh);
+ else
+ return make_reg(0, kSciPlatformDOS);
+ case kPlatformUnknown5:
// This case needs to return the opposite of case 6 to get hires graphics
return make_reg(0, !isWindows);
case kPlatformIsHiRes:
- return make_reg(0, isWindows);
- case kPlatformIsItWindows:
+ case kPlatformWin311OrHigher:
return make_reg(0, isWindows);
default:
error("Unsupported kPlatform operation %d", operation);
@@ -603,6 +600,43 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) {
return NULL_REG;
}
+#ifdef ENABLE_SCI32
+reg_t kPlatform32(EngineState *s, int argc, reg_t *argv) {
+ enum Operation {
+ kGetPlatform = 0,
+ kGetCDSpeed = 1,
+ kGetColorDepth = 2,
+ kGetCDDrive = 3
+ };
+
+ const Operation operation = argc > 0 ? (Operation)argv[0].toSint16() : kGetPlatform;
+
+ switch (operation) {
+ case kGetPlatform:
+ switch (g_sci->getPlatform()) {
+ case Common::kPlatformDOS:
+ return make_reg(0, kSciPlatformDOS);
+ case Common::kPlatformWindows:
+ return make_reg(0, kSciPlatformWindows);
+ case Common::kPlatformMacintosh:
+ // For Mac versions, kPlatform(0) with other args has more functionality
+ if (argc > 1)
+ return kMacPlatform(s, argc - 1, argv + 1);
+ else
+ return make_reg(0, kSciPlatformMacintosh);
+ default:
+ error("Unknown platform %d", g_sci->getPlatform());
+ }
+ case kGetColorDepth:
+ return make_reg(0, /* 256 color */ 2);
+ case kGetCDSpeed:
+ case kGetCDDrive:
+ default:
+ return make_reg(0, 0);
+ }
+}
+#endif
+
reg_t kEmpty(EngineState *s, int argc, reg_t *argv) {
// Placeholder for empty kernel functions which are still called from the
// engine scripts (like the empty kSetSynonyms function in SCI1.1). This
diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp
index de4d4a282c..b539c84f5d 100644
--- a/engines/sci/engine/kvideo.cpp
+++ b/engines/sci/engine/kvideo.cpp
@@ -61,40 +61,21 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
uint16 screenWidth = g_sci->_gfxScreen->getDisplayWidth();
uint16 screenHeight = g_sci->_gfxScreen->getDisplayHeight();
- videoState.fileName.toLowercase();
- bool isVMD = videoState.fileName.hasSuffix(".vmd");
-
- if (screenWidth == 640 && width <= 320 && height <= 240 && ((videoState.flags & kDoubled) || !isVMD)) {
+ if (screenWidth == 640 && width <= 320 && height <= 240) {
width *= 2;
height *= 2;
pitch *= 2;
scaleBuffer = new byte[width * height * bytesPerPixel];
}
- uint16 x, y;
-
- // Sanity check...
- if (videoState.x > 0 && videoState.y > 0 && isVMD) {
- x = videoState.x;
- y = videoState.y;
-
- if (x + width > screenWidth || y + height > screenHeight) {
- // Happens in the Lighthouse demo
- warning("VMD video won't fit on screen, centering it instead");
- x = (screenWidth - width) / 2;
- y = (screenHeight - height) / 2;
- }
- } else {
- x = (screenWidth - width) / 2;
- y = (screenHeight - height) / 2;
- }
+ uint16 x = (screenWidth - width) / 2;
+ uint16 y = (screenHeight - height) / 2;
bool skipVideo = false;
- EngineState *s = g_sci->getEngineState();
if (videoDecoder->hasDirtyPalette()) {
- const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3;
- g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart);
+ const byte *palette = videoDecoder->getPalette();
+ g_system->getPaletteManager()->setPalette(palette, 0, 255);
}
while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) {
@@ -103,7 +84,7 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
if (frame) {
if (scaleBuffer) {
- // TODO: Probably should do aspect ratio correction in e.g. GK1 Windows
+ // TODO: Probably should do aspect ratio correction in KQ6
g_sci->_gfxScreen->scale2x((const byte *)frame->getPixels(), scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight(), bytesPerPixel);
g_system->copyRectToScreen(scaleBuffer, pitch, x, y, width, height);
} else {
@@ -111,8 +92,8 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) {
}
if (videoDecoder->hasDirtyPalette()) {
- const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3;
- g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart);
+ const byte *palette = videoDecoder->getPalette();
+ g_system->getPaletteManager()->setPalette(palette, 0, 255);
}
g_system->updateScreen();
@@ -181,16 +162,6 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
// TODO: This appears to be some sort of subop. case 0 contains the string
// for the video, so we'll just play it from there for now.
-#ifdef ENABLE_SCI32
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY) {
- // SCI2.1 always has argv[0] as 1, the rest of the arguments seem to
- // follow SCI1.1/2.
- if (argv[0].toUint16() != 1)
- error("SCI2.1 kShowMovie argv[0] not 1");
- argv++;
- argc--;
- }
-#endif
switch (argv[0].toUint16()) {
case 0: {
Common::String filename = s->_segMan->getString(argv[1]);
@@ -243,52 +214,176 @@ reg_t kShowMovie(EngineState *s, int argc, reg_t *argv) {
}
#ifdef ENABLE_SCI32
+reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv) {
+ Common::String fileName = s->_segMan->getString(argv[0]);
+ const int16 numTicks = argv[1].toSint16();
+ const int16 x = argc > 3 ? argv[2].toSint16() : 0;
+ const int16 y = argc > 3 ? argv[3].toSint16() : 0;
+
+ g_sci->_video32->getSEQPlayer().play(fileName, numTicks, x, y);
+
+ return s->r_acc;
+}
reg_t kRobot(EngineState *s, int argc, reg_t *argv) {
- int16 subop = argv[0].toUint16();
-
- switch (subop) {
- case 0: { // init
- int id = argv[1].toUint16();
- reg_t obj = argv[2];
- int16 flag = argv[3].toSint16();
- int16 x = argv[4].toUint16();
- int16 y = argv[5].toUint16();
- warning("kRobot(init), id %d, obj %04x:%04x, flag %d, x=%d, y=%d", id, PRINT_REG(obj), flag, x, y);
- g_sci->_robotDecoder->load(id);
- g_sci->_robotDecoder->start();
- g_sci->_robotDecoder->setPos(x, y);
- }
- break;
- case 1: // LSL6 hires (startup)
- // TODO
- return NULL_REG; // an integer is expected
- case 4: { // start - we don't really have a use for this one
- //int id = argv[1].toUint16();
- //warning("kRobot(start), id %d", id);
- }
- break;
- case 7: // unknown, called e.g. by Phantasmagoria
- warning("kRobot(%d)", subop);
- break;
- case 8: // sync
- //if (true) { // debug: automatically skip all robot videos
- if (g_sci->_robotDecoder->endOfVideo()) {
- g_sci->_robotDecoder->close();
- // Signal the engine scripts that the video is done
- writeSelector(s->_segMan, argv[1], SELECTOR(signal), SIGNAL_REG);
- } else {
- writeSelector(s->_segMan, argv[1], SELECTOR(signal), NULL_REG);
- }
- break;
- default:
- warning("kRobot(%d)", subop);
- break;
- }
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv) {
+ const GuiResourceId robotId = argv[0].toUint16();
+ const reg_t plane = argv[1];
+ const int16 priority = argv[2].toSint16();
+ const int16 x = argv[3].toSint16();
+ const int16 y = argv[4].toSint16();
+ const int16 scale = argc > 5 ? argv[5].toSint16() : 128;
+ g_sci->_video32->getRobotPlayer().open(robotId, plane, priority, x, y, scale);
+ return make_reg(0, 0);
+}
+reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv) {
+ const uint16 frameNo = argv[0].toUint16();
+ const uint16 newX = argc > 1 ? argv[1].toUint16() : (uint16)RobotDecoder::kUnspecified;
+ const uint16 newY = argc > 1 ? argv[2].toUint16() : (uint16)RobotDecoder::kUnspecified;
+ g_sci->_video32->getRobotPlayer().showFrame(frameNo, newX, newY, RobotDecoder::kUnspecified);
return s->r_acc;
}
+reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv) {
+ Common::Rect frameRect;
+ const uint16 numFramesTotal = g_sci->_video32->getRobotPlayer().getFrameSize(frameRect);
+
+ reg_t *outRect = s->_segMan->derefRegPtr(argv[0], 4);
+ outRect[0] = make_reg(0, frameRect.left);
+ outRect[1] = make_reg(0, frameRect.top);
+ outRect[2] = make_reg(0, frameRect.right - 1);
+ outRect[3] = make_reg(0, frameRect.bottom - 1);
+
+ return make_reg(0, numFramesTotal);
+}
+
+reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getRobotPlayer().resume();
+ return s->r_acc;
+}
+
+reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusEnd);
+}
+
+reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusPlaying);
+}
+
+reg_t kRobotClose(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getRobotPlayer().close();
+ return s->r_acc;
+}
+
+reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv) {
+ writeSelectorValue(s->_segMan, argv[0], SELECTOR(signal), g_sci->_video32->getRobotPlayer().getCue());
+ return s->r_acc;
+}
+
+reg_t kRobotPause(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getRobotPlayer().pause();
+ return s->r_acc;
+}
+
+reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getRobotPlayer().getFrameNo());
+}
+
+reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv) {
+ g_sci->_video32->getRobotPlayer().setPriority(argv[0].toSint16());
+ return s->r_acc;
+}
+
+reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv) {
+ if (!s)
+ return make_reg(0, getSciVersion());
+ error("not supposed to call this");
+}
+
+reg_t kShowMovieWinOpen(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
+ }
+
+ const Common::String fileName = s->_segMan->getString(argv[0]);
+ return make_reg(0, g_sci->_video32->getAVIPlayer().open(fileName));
+}
+
+reg_t kShowMovieWinInit(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
+ }
+
+ const int16 x = argv[0].toSint16();
+ const int16 y = argv[1].toSint16();
+ const int16 width = argc > 3 ? argv[2].toSint16() : 0;
+ const int16 height = argc > 3 ? argv[3].toSint16() : 0;
+ return make_reg(0, g_sci->_video32->getAVIPlayer().init1x(x, y, width, height));
+}
+
+reg_t kShowMovieWinPlay(EngineState *s, int argc, reg_t *argv) {
+ if (getSciVersion() == SCI_VERSION_2) {
+ AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)argv[0].toUint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags));
+ } else {
+ // argv[0] is a broken movie ID
+ const int16 from = argc > 2 ? argv[1].toSint16() : 0;
+ const int16 to = argc > 2 ? argv[2].toSint16() : 0;
+ const int16 showStyle = argc > 3 ? argv[3].toSint16() : 0;
+ const bool cue = argc > 4 ? (bool)argv[4].toSint16() : false;
+ return make_reg(0, g_sci->_video32->getAVIPlayer().play(from, to, showStyle, cue));
+ }
+}
+
+reg_t kShowMovieWinClose(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getAVIPlayer().close());
+}
+
+reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getAVIPlayer().getDuration());
+}
+
+reg_t kShowMovieWinCue(EngineState *s, int argc, reg_t *argv) {
+ // SCI2.1 adds a movie ID to the call, but the movie ID is broken,
+ // so just ignore it
+ if (getSciVersion() > SCI_VERSION_2) {
+ ++argv;
+ --argc;
+ }
+
+ const uint16 frameNo = argv[0].toUint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().cue(frameNo));
+}
+
+reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv) {
+ const int defaultFlags =
+ AVIPlayer::kEventFlagEnd |
+ AVIPlayer::kEventFlagEscapeKey;
+
+ // argv[0] is the movie number, which is not used by this method
+ const AVIPlayer::EventFlags flags = (AVIPlayer::EventFlags)(argc > 1 ? argv[1].toUint16() : defaultFlags);
+
+ return make_reg(0, g_sci->_video32->getAVIPlayer().playUntilEvent(flags));
+}
+
+reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv) {
+ // argv[0] is a broken movie ID
+ const int16 x = argv[1].toSint16();
+ const int16 y = argv[2].toSint16();
+ return make_reg(0, g_sci->_video32->getAVIPlayer().init2x(x, y));
+}
+
reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv) {
if (!s)
return make_reg(0, getSciVersion());
@@ -330,6 +425,10 @@ reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv) {
return make_reg(0, g_sci->_video32->getVMDPlayer().close());
}
+reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv) {
+ return make_reg(0, g_sci->_video32->getVMDPlayer().getStatus());
+}
+
reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv) {
const VMDPlayer::EventFlags flags = (VMDPlayer::EventFlags)argv[0].toUint16();
const int16 lastFrameNo = argc > 1 ? argv[1].toSint16() : -1;
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp
index c4d53a2dc9..be2d7660cb 100644
--- a/engines/sci/engine/savegame.cpp
+++ b/engines/sci/engine/savegame.cpp
@@ -48,6 +48,7 @@
#include "sci/sound/music.h"
#ifdef ENABLE_SCI32
+#include "sci/graphics/cursor32.h"
#include "sci/graphics/frameout.h"
#include "sci/graphics/palette32.h"
#include "sci/graphics/remap32.h"
@@ -159,23 +160,6 @@ void syncWithSerializer(Common::Serializer &s, SciString &obj) {
}
}
-void syncWithSerializer(Common::Serializer &s, SciBitmap *&obj) {
- bool hasEntry;
- if (s.isSaving()) {
- hasEntry = obj != nullptr;
- }
- s.syncAsByte(hasEntry);
-
- if (hasEntry) {
- if (s.isLoading()) {
- obj = new SciBitmap;
- }
-
- obj->saveLoadWithSerializer(s);
- } else {
- obj = nullptr;
- }
-}
#endif
#pragma mark -
@@ -183,7 +167,7 @@ void syncWithSerializer(Common::Serializer &s, SciBitmap *&obj) {
// By default, sync using syncWithSerializer, which in turn can easily be overloaded.
template<typename T>
struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> {
- void operator()(Common::Serializer &s, T &obj) const {
+ void operator()(Common::Serializer &s, T &obj, int) const {
syncWithSerializer(s, obj);
}
};
@@ -191,10 +175,31 @@ struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> {
// Syncer for entries in a segment obj table
template<typename T>
struct SegmentObjTableEntrySyncer : Common::BinaryFunction<Common::Serializer, typename T::Entry &, void> {
- void operator()(Common::Serializer &s, typename T::Entry &entry) const {
+ void operator()(Common::Serializer &s, typename T::Entry &entry, int index) const {
s.syncAsSint32LE(entry.next_free);
- syncWithSerializer(s, entry.data);
+ bool hasData;
+ if (s.getVersion() >= 37) {
+ if (s.isSaving()) {
+ hasData = entry.data != nullptr;
+ }
+ s.syncAsByte(hasData);
+ } else {
+ hasData = (entry.next_free == index);
+ }
+
+ if (hasData) {
+ if (s.isLoading()) {
+ entry.data = new typename T::value_type;
+ }
+ syncWithSerializer(s, *entry.data);
+ } else if (s.isLoading()) {
+ if (s.getVersion() < 37) {
+ typename T::value_type dummy;
+ syncWithSerializer(s, dummy);
+ }
+ entry.data = nullptr;
+ }
}
};
@@ -222,9 +227,8 @@ struct ArraySyncer : Common::BinaryFunction<Common::Serializer, T, void> {
if (s.isLoading())
arr.resize(len);
- typename Common::Array<T>::iterator i;
- for (i = arr.begin(); i != arr.end(); ++i) {
- sync(s, *i);
+ for (uint i = 0; i < len; ++i) {
+ sync(s, arr[i], i);
}
}
};
@@ -423,6 +427,7 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) {
if (getSciVersion() >= SCI_VERSION_2) {
g_sci->_gfxPalette32->saveLoadWithSerializer(s);
g_sci->_gfxRemap32->saveLoadWithSerializer(s);
+ g_sci->_gfxCursor32->saveLoadWithSerializer(s);
} else
#endif
g_sci->_gfxPalette16->saveLoadWithSerializer(s);
@@ -886,6 +891,35 @@ void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) {
_needsUpdate = true;
}
}
+
+void GfxCursor32::saveLoadWithSerializer(Common::Serializer &s) {
+ if (s.getVersion() < 38) {
+ return;
+ }
+
+ int32 hideCount;
+ if (s.isSaving()) {
+ hideCount = _hideCount;
+ }
+ s.syncAsSint32LE(hideCount);
+ s.syncAsSint16LE(_restrictedArea.left);
+ s.syncAsSint16LE(_restrictedArea.top);
+ s.syncAsSint16LE(_restrictedArea.right);
+ s.syncAsSint16LE(_restrictedArea.bottom);
+ s.syncAsUint16LE(_cursorInfo.resourceId);
+ s.syncAsUint16LE(_cursorInfo.loopNo);
+ s.syncAsUint16LE(_cursorInfo.celNo);
+
+ if (s.isLoading()) {
+ hide();
+ setView(_cursorInfo.resourceId, _cursorInfo.loopNo, _cursorInfo.celNo);
+ if (!hideCount) {
+ show();
+ } else {
+ _hideCount = hideCount;
+ }
+ }
+}
#endif
void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) {
diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h
index c5c2bcef08..6616081a20 100644
--- a/engines/sci/engine/savegame.h
+++ b/engines/sci/engine/savegame.h
@@ -37,6 +37,8 @@ struct EngineState;
*
* Version - new/changed feature
* =============================
+ * 38 - SCI32 cursor
+ * 37 - Segment entry data changed to pointers
* 36 - SCI32 bitmap segment
* 35 - SCI32 remap
* 34 - SCI32 palettes, and store play time in ticks
@@ -61,7 +63,7 @@ struct EngineState;
*/
enum {
- CURRENT_SAVEGAME_VERSION = 36,
+ CURRENT_SAVEGAME_VERSION = 38,
MINIMUM_SAVEGAME_VERSION = 14
};
diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp
index e6eed0b4b7..d45c689985 100644
--- a/engines/sci/engine/script_patches.cpp
+++ b/engines/sci/engine/script_patches.cpp
@@ -746,12 +746,115 @@ static const uint16 gk1PatchInterrogationBug[] = {
PATCH_END
};
-// script, description, signature patch
+// On day 10 nearly at the end of the game, Gabriel Knight dresses up and right after that
+// someone will be at the door. Gabriel turns around to see what's going on.
+//
+// In ScummVM Gabriel turning around plays endlessly. This is caused by the loop of Gabriel
+// being kept at 1, but view + cel were changed accordingly. The view used - which is view 859 -
+// does not have a loop 1. kNumCels is called on that, BUT kNumCels in SSCI is broken in that
+// regard. It checks for loop > count and not loop >= count and will return basically random data
+// in case loop == count.
+//
+// In SSCI this simply worked by accident. kNumCels returned 0x53 in this case, but later script code
+// fixed that up somehow, so it worked out in the end.
+//
+// The setup for this is done in SDJEnters::changeState(0). The cycler will never reach the goal
+// because the goal will be cel -1, so it loops endlessly.
+//
+// We fix this by adding a setLoop(0).
+//
+// Applies to at least: English PC-CD, German PC-CD
+// Responsible method: sDJEnters::changeState
+static const uint16 gk1SignatureDay10GabrielDressUp[] = {
+ 0x87, 0x01, // lap param[1]
+ 0x65, 0x14, // aTop state
+ 0x36, // push
+ 0x3c, // dup
+ 0x35, 0x00, // ldi 0
+ 0x1a, // eq?
+ 0x30, SIG_UINT16(0x006f), // bnt [next state 1]
+ SIG_ADDTOOFFSET(+84),
+ 0x39, 0x0e, // pushi 0Eh (view)
+ 0x78, // push1
+ SIG_MAGICDWORD,
+ 0x38, SIG_UINT16(0x035B), // pushi 035Bh (859d)
+ 0x38, SIG_UINT16(0x0141), // pushi 0141h (setCel)
+ 0x78, // push1
+ 0x76, // push0
+ 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle)
+ 0x7a, // push2
+ 0x51, 0x18, // class End
+ 0x36, // push
+ 0x7c, // pushSelf
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x14, 0x00, // send 14h
+ // GKEgo::view(859)
+ // GKEgo::setCel(0)
+ // GKEgo::setCycle(End, sDJEnters)
+ 0x32, SIG_UINT16(0x0233), // jmp [ret]
+ // next state
+ 0x3c, // dup
+ 0x35, 0x01, // ldi 01
+ 0x1a, // eq?
+ 0x31, 0x07, // bnt [next state 2]
+ 0x35, 0x02, // ldi 02
+ 0x65, 0x1a, // aTop cycles
+ 0x32, SIG_UINT16(0x0226), // jmp [ret]
+ // next state
+ 0x3c, // dup
+ 0x35, 0x02, // ldi 02
+ 0x1a, // eq?
+ 0x31, 0x2a, // bnt [next state 3]
+ 0x78, // push1
+ SIG_ADDTOOFFSET(+34),
+ // part of state 2 code, delays for 1 cycle
+ 0x35, 0x01, // ldi 1
+ 0x65, 0x1a, // aTop cycles
+ SIG_END
+};
+
+static const uint16 gk1PatchDay10GabrielDressUp[] = {
+ PATCH_ADDTOOFFSET(+9),
+ 0x30, SIG_UINT16(0x0073), // bnt [next state 1] - offset adjusted
+ SIG_ADDTOOFFSET(+84 + 11),
+ // added by us: setting loop to 0 (5 bytes needed)
+ 0x38, SIG_UINT16(0x00FB), // pushi 00FBh (setLoop)
+ 0x78, // push1
+ 0x76, // push0
+ // original code, but offset changed
+ 0x38, SIG_UINT16(0x00E9), // pushi 00E9h (setCycle)
+ 0x7a, // push2
+ 0x51, 0x18, // class End
+ 0x36, // push
+ 0x7c, // pushSelf
+ 0x81, 0x00, // lag global[0]
+ 0x4a, 0x1a, 0x00, // send 1Ah - adjusted
+ // GKEgo::view(859)
+ // GKEgo::setCel(0)
+ // GKEgo::setLoop(0) <-- new, by us
+ // GKEgo::setCycle(End, sDJEnters)
+ // end of original code
+ 0x3a, // toss
+ 0x48, // ret (saves 1 byte)
+ // state 1 code
+ 0x3c, // dup
+ 0x34, SIG_UINT16(0x0001), // ldi 0001 (waste 1 byte)
+ 0x1a, // eq?
+ 0x31, 2, // bnt [next state 2]
+ 0x33, 41, // jmp to state 2 delay code
+ SIG_ADDTOOFFSET(+41),
+ // wait 2 cycles instead of only 1
+ 0x35, 0x02, // ldi 2
+ PATCH_END
+};
+
+// script, description, signature patch
static const SciScriptPatcherEntry gk1Signatures[] = {
- { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug },
- { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze },
- { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet },
- { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep },
+ { true, 51, "interrogation bug", 1, gk1SignatureInterrogationBug, gk1PatchInterrogationBug },
+ { true, 212, "day 5 phone freeze", 1, gk1SignatureDay5PhoneFreeze, gk1PatchDay5PhoneFreeze },
+ { true, 230, "day 6 police beignet timer issue", 1, gk1SignatureDay6PoliceBeignet, gk1PatchDay6PoliceBeignet },
+ { true, 230, "day 6 police sleep timer issue", 1, gk1SignatureDay6PoliceSleep, gk1PatchDay6PoliceSleep },
+ { true, 808, "day 10 gabriel dress up infinite turning", 1, gk1SignatureDay10GabrielDressUp, gk1PatchDay10GabrielDressUp },
SCI_SIGNATUREENTRY_TERMINATOR
};
diff --git a/engines/sci/engine/script_patches.h b/engines/sci/engine/script_patches.h
index 645e0946b3..f95806a3f3 100644
--- a/engines/sci/engine/script_patches.h
+++ b/engines/sci/engine/script_patches.h
@@ -35,13 +35,13 @@ namespace Sci {
#define SIG_BYTEMASK 0x00FF
#define SIG_MAGICDWORD 0xF000
#define SIG_CODE_ADDTOOFFSET 0xE000
-#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | _offset_
+#define SIG_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_)
#define SIG_CODE_SELECTOR16 0x9000
#define SIG_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_
#define SIG_CODE_SELECTOR8 0x8000
#define SIG_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_
#define SIG_CODE_UINT16 0x1000
-#define SIG_UINT16(_value_) SIG_CODE_UINT16 | (_value_ & 0xFF), (_value_ >> 8)
+#define SIG_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8)
#define SIG_CODE_BYTE 0x0000
#define PATCH_END SIG_END
@@ -49,17 +49,17 @@ namespace Sci {
#define PATCH_VALUEMASK SIG_VALUEMASK
#define PATCH_BYTEMASK SIG_BYTEMASK
#define PATCH_CODE_ADDTOOFFSET SIG_CODE_ADDTOOFFSET
-#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | _offset_
+#define PATCH_ADDTOOFFSET(_offset_) SIG_CODE_ADDTOOFFSET | (_offset_)
#define PATCH_CODE_GETORIGINALBYTE 0xD000
-#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | _offset_
+#define PATCH_GETORIGINALBYTE(_offset_) PATCH_CODE_GETORIGINALBYTE | (_offset_)
#define PATCH_CODE_GETORIGINALBYTEADJUST 0xC000
-#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTEADJUST | _offset_, (uint16)(_adjustValue_)
+#define PATCH_GETORIGINALBYTEADJUST(_offset_, _adjustValue_) PATCH_CODE_GETORIGINALBYTEADJUST | (_offset_), (uint16)(_adjustValue_)
#define PATCH_CODE_SELECTOR16 SIG_CODE_SELECTOR16
#define PATCH_SELECTOR16(_selectorID_) SIG_CODE_SELECTOR16 | SELECTOR_##_selectorID_
#define PATCH_CODE_SELECTOR8 SIG_CODE_SELECTOR8
#define PATCH_SELECTOR8(_selectorID_) SIG_CODE_SELECTOR8 | SELECTOR_##_selectorID_
#define PATCH_CODE_UINT16 SIG_CODE_UINT16
-#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | (_value_ & 0xFF), (_value_ >> 8)
+#define PATCH_UINT16(_value_) SIG_CODE_UINT16 | ((_value_) & 0xFF), ((_value_) >> 8)
#define PATCH_CODE_BYTE SIG_CODE_BYTE
// defines maximum scratch area for getting original bytes from unpatched script data
diff --git a/engines/sci/engine/seg_manager.cpp b/engines/sci/engine/seg_manager.cpp
index 608452983a..5cf8d6162d 100644
--- a/engines/sci/engine/seg_manager.cpp
+++ b/engines/sci/engine/seg_manager.cpp
@@ -959,15 +959,11 @@ SciBitmap *SegManager::allocateBitmap(reg_t *addr, const int16 width, const int1
offset = table->allocEntry();
*addr = make_reg(_bitmapSegId, offset);
- SciBitmap *bitmap = table->at(offset);
+ SciBitmap &bitmap = table->at(offset);
- if (bitmap == nullptr) {
- *addr = NULL_REG;
- }
-
- bitmap->create(width, height, skipColor, displaceX, displaceY, scaledWidth, scaledHeight, paletteSize, remap, gc);
+ bitmap.create(width, height, skipColor, displaceX, displaceY, scaledWidth, scaledHeight, paletteSize, remap, gc);
- return bitmap;
+ return &bitmap;
}
SciBitmap *SegManager::lookupBitmap(const reg_t addr) {
@@ -979,7 +975,7 @@ SciBitmap *SegManager::lookupBitmap(const reg_t addr) {
if (!bitmapTable.isValidEntry(addr.getOffset()))
error("Attempt to use invalid entry %04x:%04x as bitmap", PRINT_REG(addr));
- return (bitmapTable.at(addr.getOffset()));
+ return &(bitmapTable.at(addr.getOffset()));
}
void SegManager::freeBitmap(const reg_t addr) {
diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h
index acebecea97..8ed1c3a143 100644
--- a/engines/sci/engine/seg_manager.h
+++ b/engines/sci/engine/seg_manager.h
@@ -30,8 +30,7 @@
#include "sci/engine/vm_types.h"
#include "sci/engine/segment.h"
#ifdef ENABLE_SCI32
-// TODO: Baaaad?
-#include "sci/graphics/celobj32.h"
+#include "sci/graphics/celobj32.h" // kLowResX, kLowResY
#endif
namespace Sci {
diff --git a/engines/sci/engine/segment.h b/engines/sci/engine/segment.h
index 7c39050f18..add5f4c57c 100644
--- a/engines/sci/engine/segment.h
+++ b/engines/sci/engine/segment.h
@@ -200,6 +200,21 @@ struct Node {
struct List {
reg_t first;
reg_t last;
+
+#ifdef ENABLE_SCI32
+ /**
+ * The next node for each level of recursion during iteration over this list
+ * by kListEachElementDo.
+ */
+ reg_t nextNodes[10];
+
+ /**
+ * The current level of recursion of kListEachElementDo for this list.
+ */
+ int numRecursions;
+
+ List() : numRecursions(0) {}
+#endif
};
struct Hunk {
@@ -212,7 +227,7 @@ template<typename T>
struct SegmentObjTable : public SegmentObj {
typedef T value_type;
struct Entry {
- T data;
+ T *data;
int next_free; /* Only used for free entries */
};
enum { HEAPENTRY_INVALID = -1 };
@@ -228,6 +243,14 @@ public:
initTable();
}
+ ~SegmentObjTable() {
+ for (uint i = 0; i < _table.size(); i++) {
+ if (isValidEntry(i)) {
+ freeEntry(i);
+ }
+ }
+ }
+
void initTable() {
entries_used = 0;
first_free = HEAPENTRY_INVALID;
@@ -241,10 +264,13 @@ public:
first_free = _table[oldff].next_free;
_table[oldff].next_free = oldff;
+ assert(_table[oldff].data == nullptr);
+ _table[oldff].data = new T;
return oldff;
} else {
uint newIdx = _table.size();
_table.push_back(Entry());
+ _table.back().data = new T;
_table[newIdx].next_free = newIdx; // Tag as 'valid'
return newIdx;
}
@@ -263,6 +289,8 @@ public:
::error("Table::freeEntry: Attempt to release invalid table index %d", idx);
_table[idx].next_free = first_free;
+ delete _table[idx].data;
+ _table[idx].data = nullptr;
first_free = idx;
entries_used--;
}
@@ -277,8 +305,8 @@ public:
uint size() const { return _table.size(); }
- T &at(uint index) { return _table[index].data; }
- const T &at(uint index) const { return _table[index].data; }
+ T &at(uint index) { return *_table[index].data; }
+ const T &at(uint index) const { return *_table[index].data; }
T &operator[](uint index) { return at(index); }
const T &operator[](uint index) const { return at(index); }
@@ -338,8 +366,8 @@ struct HunkTable : public SegmentObjTable<Hunk> {
}
virtual void freeEntry(int idx) {
- SegmentObjTable<Hunk>::freeEntry(idx);
freeEntryContents(idx);
+ SegmentObjTable<Hunk>::freeEntry(idx);
}
virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) {
@@ -777,42 +805,14 @@ public:
virtual void saveLoadWithSerializer(Common::Serializer &ser);
};
-struct BitmapTable : public SegmentObjTable<SciBitmap *> {
- BitmapTable() : SegmentObjTable<SciBitmap *>(SEG_TYPE_BITMAP) {}
-
- virtual ~BitmapTable() {
- for (uint i = 0; i < _table.size(); i++) {
- if (isValidEntry(i)) {
- freeEntryContents(i);
- }
- }
- }
-
- int allocEntry() {
- int offset = SegmentObjTable<SciBitmap *>::allocEntry();
- at(offset) = new SciBitmap;
- return offset;
- }
-
- void freeEntryContents(const int offset) {
- delete at(offset);
- at(offset) = nullptr;
- }
-
- virtual void freeEntry(const int offset) override {
- SegmentObjTable<SciBitmap *>::freeEntry(offset);
- freeEntryContents(offset);
- }
-
- virtual void freeAtAddress(SegManager *segMan, reg_t sub_addr) override {
- freeEntry(sub_addr.getOffset());
- }
+struct BitmapTable : public SegmentObjTable<SciBitmap> {
+ BitmapTable() : SegmentObjTable<SciBitmap>(SEG_TYPE_BITMAP) {}
SegmentRef dereference(reg_t pointer) {
SegmentRef ret;
ret.isRaw = true;
- ret.maxSize = at(pointer.getOffset())->getRawSize();
- ret.raw = at(pointer.getOffset())->getRawData();
+ ret.maxSize = at(pointer.getOffset()).getRawSize();
+ ret.raw = at(pointer.getOffset()).getRawData();
return ret;
}
diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp
index 2c85907628..a338beffc9 100644
--- a/engines/sci/engine/state.cpp
+++ b/engines/sci/engine/state.cpp
@@ -121,9 +121,6 @@ void EngineState::reset(bool isRestoring) {
_videoState.reset();
_syncedAudioOptions = false;
-
- _vmdPalStart = 0;
- _vmdPalEnd = 256;
}
void EngineState::speedThrottler(uint32 neededSleep) {
diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h
index dd8d76f002..baa912b60e 100644
--- a/engines/sci/engine/state.h
+++ b/engines/sci/engine/state.h
@@ -203,7 +203,6 @@ public:
// TODO: Excise video code from the state manager
VideoState _videoState;
- uint16 _vmdPalStart, _vmdPalEnd;
bool _syncedAudioOptions;
/**
diff --git a/engines/sci/engine/vm.cpp b/engines/sci/engine/vm.cpp
index 3e12084ed6..548fd477bf 100644
--- a/engines/sci/engine/vm.cpp
+++ b/engines/sci/engine/vm.cpp
@@ -405,6 +405,21 @@ static void callKernelFunc(EngineState *s, int kernelCallNr, int argc) {
error("[VM] k%s[%x]: no subfunction ID parameter given", kernelCall.name, kernelCallNr);
if (argv[0].isPointer())
error("[VM] k%s[%x]: given subfunction ID is actually a pointer", kernelCall.name, kernelCallNr);
+
+#ifdef ENABLE_SCI32
+ // The Windows version of kShowMovie has subops, but the subop number
+ // is put in the second parameter in SCI2.1+, even though every other
+ // kcall with subops puts the subop in the first parameter. To allow use
+ // of the normal subops system, we swap the arguments so the subop
+ // number is in the usual place.
+ if (getSciVersion() > SCI_VERSION_2 &&
+ g_sci->getPlatform() == Common::kPlatformWindows &&
+ strcmp(kernelCall.name, "ShowMovie") == 0) {
+ assert(argc > 1);
+ SWAP(argv[0], argv[1]);
+ }
+#endif
+
const uint16 subId = argv[0].toUint16();
// Skip over subfunction-id
argc--;
diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp
index 9b3b329418..7aaea0902a 100644
--- a/engines/sci/engine/workarounds.cpp
+++ b/engines/sci/engine/workarounds.cpp
@@ -88,6 +88,7 @@ const SciWorkaroundEntry arithmeticWorkarounds[] = {
{ GID_QFG2, 200, 200, 0, "astro", "messages", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_lsi: when getting asked for your name by the astrologer - bug #5152
{ GID_QFG3, 780, 999, 0, "", "export 6", NULL, 0, { WORKAROUND_FAKE, 0 } }, // op_add: trying to talk to yourself at the top of the giant tree - bug #6692
{ GID_QFG4, 710,64941, 0, "RandCycle", "doit", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_gt: when the tentacle appears in the third room of the caves
+ { GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, { WORKAROUND_FAKE, 1 } }, // op_div: when Lycentia knocks Torin out after he removes her collar
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -329,7 +330,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ GID_PEPPER, -1, 894, 0, "Package", "doVerb", NULL, 3, { WORKAROUND_FAKE, 0 } }, // using the hand on the book in the inventory - bug #5154
{ GID_PEPPER, 150, 928, 0, "Narrator", "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // happens during the non-interactive demo of Pepper
{ GID_PQ4, -1, 25, 0, "iconToggle", "select", NULL, 1, { WORKAROUND_FAKE, 0 } }, // when toggling the icon bar to auto-hide or not
- { GID_PQSWAT, -1, 64950, 0, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using the menu in the beginning
+ { GID_PQSWAT, -1, 64950, 0, NULL, "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using any menus in-game
{ GID_QFG1, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1/hq1: going to the brigands hideout
{ GID_QFG1VGA, 16, 16, 0, "lassoFailed", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // qfg1vga: casting the "fetch" spell in the screen with the flowers, temps 0 and 1 - bug #5309
{ GID_QFG1VGA, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1vga_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1vga: going to the brigands hideout - bug #5515
@@ -379,7 +380,8 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = {
{ GID_SQ6, -1, 64950, -1, "Feature", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // called when pressing "Start game" in the main menu, when entering the Orion's Belt bar (room 300), and perhaps other places
{ GID_SQ6, -1, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // during the game
{ GID_TORIN, -1, 64017, 0, "oFlags", "clear", NULL, 0, { WORKAROUND_FAKE, 0 } }, // entering Torin's home in the French version
- { GID_TORIN, 10000, 64029, 0, "oMessager", "nextMsg", NULL, 3, { WORKAROUND_FAKE, 0 } }, // start of chapter one
+ { GID_TORIN, 10000, 64029, 0, "oMessager", "nextMsg", NULL, 3, { WORKAROUND_FAKE, 0 } }, // start of chapter one
+ { GID_TORIN, 20100, 64964, 0, "DPath", "init", NULL, 1, { WORKAROUND_FAKE, 0 } }, // going down the cliff at the first screen of chapter 2 (washing area)
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -519,6 +521,7 @@ const SciWorkaroundEntry kDoSoundPlay_workarounds[] = {
{ GID_LSL6HIRES, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
{ GID_QFG4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
{ GID_PQ4, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // always passes an extra null argument
+ { GID_GK1, -1, 64989, 0, NULL, "play", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // Mac version always passes an extra null argument
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -673,6 +676,14 @@ const SciWorkaroundEntry kPalVarySetPercent_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kRandom_workarounds[] = {
+ { GID_TORIN, 51400,64928, 0, "Blink", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Lycentia knocks Torin out after he removes her collar
+ { GID_TORIN, 51400,64928, 0, "Blink", "cycleDone", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // when Lycentia knocks Torin out after he removes her collar
+ SCI_WORKAROUNDENTRY_TERMINATOR
+};
+
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kReadNumber_workarounds[] = {
{ GID_CNICK_LAURABOW,100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425
{ GID_HOYLE3, 100, 101, 0, "dominoes.opt", "doit", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // When dominoes.opt is present, the game scripts call kReadNumber with an extra integer parameter - bug #6425
@@ -688,6 +699,7 @@ const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = {
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kSetCursor_workarounds[] = {
{ GID_KQ5, -1, 768, 0, "KQCursor", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: gets called with 4 additional "900d" parameters
+ { GID_MOTHERGOOSEHIRES,0, 0, -1, "MG", "setCursor", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // At the start of the game, an object is passed as the cel number
SCI_WORKAROUNDENTRY_TERMINATOR
};
@@ -755,6 +767,11 @@ const SciWorkaroundEntry kUnLoad_workarounds[] = {
};
// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
+const SciWorkaroundEntry kStringPutAt_workarounds[] = {
+ { GID_PHANTASMAGORIA,902, 64918, 0, "Str", "callKernel", NULL, 0, { WORKAROUND_IGNORE, 0 } }, // When starting a new game from after chapter 1, the game tries to save ego's object in a string
+};
+
+// gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround
const SciWorkaroundEntry kScrollWindowAdd_workarounds[] = {
{ GID_PHANTASMAGORIA, 45, 64907, 0, "ScrollableWindow", "addString", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // ScrollWindow interface passes the last two parameters twice
};
diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h
index 248d37fc6c..2cccd05475 100644
--- a/engines/sci/engine/workarounds.h
+++ b/engines/sci/engine/workarounds.h
@@ -91,6 +91,7 @@ extern const SciWorkaroundEntry kMemory_workarounds[];
extern const SciWorkaroundEntry kMoveCursor_workarounds[];
extern const SciWorkaroundEntry kNewWindow_workarounds[];
extern const SciWorkaroundEntry kPalVarySetPercent_workarounds[];
+extern const SciWorkaroundEntry kRandom_workarounds[];
extern const SciWorkaroundEntry kReadNumber_workarounds[];
extern const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[];
extern const SciWorkaroundEntry kSetCursor_workarounds[];
@@ -99,6 +100,7 @@ extern const SciWorkaroundEntry kStrAt_workarounds[];
extern const SciWorkaroundEntry kStrCpy_workarounds[];
extern const SciWorkaroundEntry kStrLen_workarounds[];
extern const SciWorkaroundEntry kUnLoad_workarounds[];
+extern const SciWorkaroundEntry kStringPutAt_workarounds[];
extern const SciWorkaroundEntry kScrollWindowAdd_workarounds[];
extern SciWorkaroundSolution trackOriginAndFindWorkaround(int index, const SciWorkaroundEntry *workaroundList, SciTrackOriginReply *trackOrigin);
diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp
index 4ad2a0cfa3..b267d2ebc2 100644
--- a/engines/sci/event.cpp
+++ b/engines/sci/event.cpp
@@ -30,6 +30,7 @@
#include "sci/engine/state.h"
#include "sci/engine/kernel.h"
#ifdef ENABLE_SCI32
+#include "sci/graphics/cursor32.h"
#include "sci/graphics/frameout.h"
#endif
#include "sci/graphics/screen.h"
@@ -168,9 +169,17 @@ SciEvent EventManager::getScummVMEvent() {
if (getSciVersion() >= SCI_VERSION_2) {
const Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer();
+ if (ev.type == Common::EVENT_MOUSEMOVE) {
+ // This will clamp `mousePos` according to the restricted zone,
+ // so any cursor or screen item associated with the mouse position
+ // does not bounce when it hits the edge (or ignore the edge)
+ g_sci->_gfxCursor32->deviceMoved(mousePos);
+ }
+
Common::Point mousePosSci = mousePos;
mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight));
noEvent.mousePosSci = input.mousePosSci = mousePosSci;
+
} else {
#endif
g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x);
diff --git a/engines/sci/graphics/cache.cpp b/engines/sci/graphics/cache.cpp
index fb1f557ad6..9c77f31a14 100644
--- a/engines/sci/graphics/cache.cpp
+++ b/engines/sci/graphics/cache.cpp
@@ -95,10 +95,20 @@ int16 GfxCache::kernelViewGetCelHeight(GuiResourceId viewId, int16 loopNo, int16
}
int16 GfxCache::kernelViewGetLoopCount(GuiResourceId viewId) {
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ return CelObjView::getNumLoops(viewId);
+ }
+#endif
return getView(viewId)->getLoopCount();
}
int16 GfxCache::kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo) {
+#ifdef ENABLE_SCI32
+ if (getSciVersion() >= SCI_VERSION_2) {
+ return CelObjView::getNumCels(viewId, loopNo);
+ }
+#endif
return getView(viewId)->getCelCount(loopNo);
}
diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp
index 311684d595..d67a4dc03c 100644
--- a/engines/sci/graphics/celobj32.cpp
+++ b/engines/sci/graphics/celobj32.cpp
@@ -45,7 +45,7 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
}
}
- int i = 1 - _activeIndex;
+ const int i = 1 - _activeIndex;
_activeIndex = i;
CelScalerTable &table = _scaleTables[i];
@@ -65,7 +65,7 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) {
void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) {
int value = 0;
int remainder = 0;
- int num = ratio.getNumerator();
+ const int num = ratio.getNumerator();
for (int i = 0; i < size; ++i) {
*table++ = value;
remainder += ratio.getDenominator();
@@ -164,8 +164,8 @@ struct SCALER_Scale {
const byte *_row;
READER _reader;
int16 _x;
- static int16 _valuesX[1024];
- static int16 _valuesY[1024];
+ static int16 _valuesX[4096];
+ static int16 _valuesY[4096];
SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) :
_row(nullptr),
@@ -204,7 +204,7 @@ struct SCALER_Scale {
if (g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth == kLowResX) {
const int16 unscaledX = (scaledPosition.x / scaleX).toInt();
if (FLIP) {
- int lastIndex = celObj._width - 1;
+ const int lastIndex = celObj._width - 1;
for (int16 x = targetRect.left; x < targetRect.right; ++x) {
_valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX);
}
@@ -220,7 +220,7 @@ struct SCALER_Scale {
}
} else {
if (FLIP) {
- int lastIndex = celObj._width - 1;
+ const int lastIndex = celObj._width - 1;
for (int16 x = 0; x < targetRect.width(); ++x) {
_valuesX[targetRect.left + x] = lastIndex - table->valuesX[x];
}
@@ -249,9 +249,9 @@ struct SCALER_Scale {
};
template<bool FLIP, typename READER>
-int16 SCALER_Scale<FLIP, READER>::_valuesX[1024];
+int16 SCALER_Scale<FLIP, READER>::_valuesX[4096];
template<bool FLIP, typename READER>
-int16 SCALER_Scale<FLIP, READER>::_valuesY[1024];
+int16 SCALER_Scale<FLIP, READER>::_valuesY[4096];
#pragma mark -
#pragma mark CelObj - Resource readers
@@ -261,7 +261,7 @@ private:
#ifndef NDEBUG
const int16 _sourceHeight;
#endif
- byte *_pixels;
+ const byte *_pixels;
const int16 _sourceWidth;
public:
@@ -270,7 +270,7 @@ public:
_sourceHeight(celObj._height),
#endif
_sourceWidth(celObj._width) {
- byte *resource = celObj.getResPointer();
+ const byte *resource = celObj.getResPointer();
_pixels = resource + READ_SCI11ENDIAN_UINT32(resource + celObj._celHeaderOffset + 24);
}
@@ -282,8 +282,8 @@ public:
struct READER_Compressed {
private:
- byte *_resource;
- byte _buffer[1024];
+ const byte *const _resource;
+ byte _buffer[4096];
uint32 _controlOffset;
uint32 _dataOffset;
uint32 _uncompressedDataOffset;
@@ -301,7 +301,7 @@ public:
_maxWidth(maxWidth) {
assert(maxWidth <= celObj._width);
- byte *celHeader = _resource + celObj._celHeaderOffset;
+ const byte *const celHeader = _resource + celObj._celHeaderOffset;
_dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24);
_uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28);
_controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32);
@@ -311,14 +311,14 @@ public:
assert(y >= 0 && y < _sourceHeight);
if (y != _y) {
// compressed data segment for row
- byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4);
+ const byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4);
// uncompressed data segment for row
- byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4);
+ const byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4);
uint8 length;
for (int16 i = 0; i < _maxWidth; i += length) {
- byte controlByte = *row++;
+ const byte controlByte = *row++;
length = controlByte;
// Run-length encoded
@@ -581,7 +581,7 @@ void CelObj::submitPalette() const {
int CelObj::_nextCacheId = 1;
CelCache *CelObj::_cache = nullptr;
-int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const {
+int CelObj::searchCache(const CelInfo32 &celInfo, int *const nextInsertIndex) const {
*nextInsertIndex = -1;
int oldestId = _nextCacheId + 1;
int oldestIndex = 0;
@@ -791,6 +791,49 @@ void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Rati
#pragma mark -
#pragma mark CelObjView
+int16 CelObjView::getNumLoops(const GuiResourceId viewId) {
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+
+ if (!resource) {
+ return 0;
+ }
+
+ assert(resource->size >= 3);
+ return resource->data[2];
+}
+
+int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) {
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+
+ if (!resource) {
+ return 0;
+ }
+
+ const byte *const data = resource->data;
+
+ const uint16 loopCount = data[2];
+ if (loopNo >= loopCount || loopNo < 0) {
+ return 0;
+ }
+
+ const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data);
+ const uint8 loopHeaderSize = data[12];
+ const uint8 viewHeaderFieldSize = 2;
+
+#ifndef NDEBUG
+ const byte *const dataMax = data + resource->size;
+#endif
+ const byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * loopNo);
+ assert(loopHeader + 3 <= dataMax);
+
+ if ((int8)loopHeader[0] != -1) {
+ loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]);
+ assert(loopHeader >= data && loopHeader + 3 <= dataMax);
+ }
+
+ return loopHeader[2];
+}
+
CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
_info.type = kCelTypeView;
_info.resourceId = viewId;
@@ -801,7 +844,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
_transparent = true;
int cacheInsertIndex;
- int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ const int cacheIndex = searchCache(_info, &cacheInsertIndex);
if (cacheIndex != -1) {
CelCacheEntry &entry = (*_cache)[cacheIndex];
const CelObjView *const cachedCelObj = dynamic_cast<CelObjView *>(entry.celObj);
@@ -817,20 +860,19 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
// generates view resource metadata for both SCI16 and SCI32
// implementations
- Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false);
// NOTE: SCI2.1/SQ6 just silently returns here.
if (!resource) {
- warning("View resource %d not loaded", viewId);
- return;
+ error("View resource %d not found", viewId);
}
- byte *data = resource->data;
+ const byte *const data = resource->data;
_scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14);
_scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16);
- if (_scaledWidth == 0 || _scaledHeight == 0) {
+ if (_scaledWidth == 0 && _scaledHeight == 0) {
byte sizeFlag = data[5];
if (sizeFlag == 0) {
_scaledWidth = kLowResX;
@@ -844,7 +886,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
}
}
- uint16 loopCount = data[2];
+ const uint16 loopCount = data[2];
if (_info.loopNo >= loopCount) {
_info.loopNo = loopCount - 1;
}
@@ -852,14 +894,14 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
// NOTE: This is the actual check, in the actual location,
// from SCI engine.
if (loopNo < 0) {
- error("Loop is less than 0!");
+ error("Loop is less than 0");
}
const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data);
const uint8 loopHeaderSize = data[12];
const uint8 viewHeaderFieldSize = 2;
- byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo);
+ const byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo);
if ((int8)loopHeader[0] != -1) {
if (loopHeader[1] == 1) {
@@ -874,10 +916,23 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
_info.celNo = celCount - 1;
}
+ // A celNo can be negative and still valid. At least PQ4CD uses this strange
+ // arrangement to load its high-resolution main menu resource. In PQ4CD, the
+ // low-resolution menu is at view 23, loop 9, cel 0, and the high-resolution
+ // menu is at view 2300, loop 0, cel 0. View 2300 is specially crafted to
+ // have 2 loops, with the second loop having 0 cels. When in high-resolution
+ // mode, the game scripts only change the view resource ID from 23 to 2300,
+ // leaving loop 9 and cel 0 the same. The code in CelObjView constructor
+ // auto-corrects loop 9 to loop 1, and then auto-corrects the cel number
+ // from 0 to -1, which effectively causes loop 0, cel 0 to be read.
+ if (_info.celNo < 0 && _info.loopNo == 0) {
+ error("Cel is less than 0 on loop 0");
+ }
+
_hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8);
_celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo);
- byte *celHeader = data + _celHeaderOffset;
+ const byte *const celHeader = data + _celHeaderOffset;
_width = READ_SCI11ENDIAN_UINT16(celHeader);
_height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
@@ -906,7 +961,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int
}
bool CelObjView::analyzeUncompressedForRemap() const {
- byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
+ const byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24);
for (int i = 0; i < _width * _height; ++i) {
const byte pixel = pixels[i];
if (
@@ -923,7 +978,7 @@ bool CelObjView::analyzeUncompressedForRemap() const {
bool CelObjView::analyzeForRemap() const {
READER_Compressed reader(*this, _width);
for (int y = 0; y < _height; y++) {
- const byte *curRow = reader.getRow(y);
+ const byte *const curRow = reader.getRow(y);
for (int x = 0; x < _width; x++) {
const byte pixel = curRow[x];
if (
@@ -948,7 +1003,7 @@ CelObjView *CelObjView::duplicate() const {
}
byte *CelObjView::getResPointer() const {
- const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false);
+ Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false);
if (resource == nullptr) {
error("Failed to load view %d from resource manager", _info.resourceId);
}
@@ -969,7 +1024,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_remap = false;
int cacheInsertIndex;
- int cacheIndex = searchCache(_info, &cacheInsertIndex);
+ const int cacheIndex = searchCache(_info, &cacheInsertIndex);
if (cacheIndex != -1) {
CelCacheEntry &entry = (*_cache)[cacheIndex];
const CelObjPic *const cachedCelObj = dynamic_cast<CelObjPic *>(entry.celObj);
@@ -981,15 +1036,14 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
return;
}
- Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
+ const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false);
// NOTE: SCI2.1/SQ6 just silently returns here.
if (!resource) {
- warning("Pic resource %d not loaded", picId);
- return;
+ error("Pic resource %d not found", picId);
}
- byte *data = resource->data;
+ const byte *const data = resource->data;
_celCount = data[2];
@@ -1000,7 +1054,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo);
_hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6);
- byte *celHeader = data + _celHeaderOffset;
+ const byte *const celHeader = data + _celHeaderOffset;
_width = READ_SCI11ENDIAN_UINT16(celHeader);
_height = READ_SCI11ENDIAN_UINT16(celHeader + 2);
@@ -1012,8 +1066,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
_relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38);
_relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40);
- uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10);
- uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12);
+ const uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10);
+ const uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12);
if (sizeFlag2) {
_scaledWidth = sizeFlag1;
@@ -1032,7 +1086,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
if (celHeader[10] & 128) {
// NOTE: This is correct according to SCI2.1/SQ6/DOS;
// the engine re-reads the byte value as a word value
- uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
+ const uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10);
_transparent = flags & 1 ? true : false;
_remap = flags & 2 ? true : false;
} else {
@@ -1047,8 +1101,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) {
}
bool CelObjPic::analyzeUncompressedForSkip() const {
- byte *resource = getResPointer();
- byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24);
+ const byte *const resource = getResPointer();
+ const byte *const pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24);
for (int i = 0; i < _width * _height; ++i) {
uint8 pixel = pixels[i];
if (pixel == _transparentColor) {
@@ -1060,7 +1114,7 @@ bool CelObjPic::analyzeUncompressedForSkip() const {
}
void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) {
- Ratio square;
+ const Ratio square;
_drawMirrored = mirrorX;
drawTo(target, targetRect, scaledPosition, square, square);
}
@@ -1088,15 +1142,21 @@ CelObjMem::CelObjMem(const reg_t bitmapObject) {
_celHeaderOffset = 0;
_transparent = true;
- SciBitmap &bitmap = *g_sci->getEngineState()->_segMan->lookupBitmap(bitmapObject);
- _width = bitmap.getWidth();
- _height = bitmap.getHeight();
- _displace = bitmap.getDisplace();
- _transparentColor = bitmap.getSkipColor();
- _scaledWidth = bitmap.getScaledWidth();
- _scaledHeight = bitmap.getScaledHeight();
- _hunkPaletteOffset = bitmap.getHunkPaletteOffset();
- _remap = bitmap.getRemap();
+ SciBitmap *bitmap = g_sci->getEngineState()->_segMan->lookupBitmap(bitmapObject);
+
+ // NOTE: SSCI did no error checking here at all.
+ if (!bitmap) {
+ error("Bitmap %04x:%04x not found", PRINT_REG(bitmapObject));
+ }
+
+ _width = bitmap->getWidth();
+ _height = bitmap->getHeight();
+ _displace = bitmap->getDisplace();
+ _transparentColor = bitmap->getSkipColor();
+ _scaledWidth = bitmap->getScaledWidth();
+ _scaledHeight = bitmap->getScaledHeight();
+ _hunkPaletteOffset = bitmap->getHunkPaletteOffset();
+ _remap = bitmap->getRemap();
}
CelObjMem *CelObjMem::duplicate() const {
diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h
index eb6ce3a3c9..21e86d03e0 100644
--- a/engines/sci/graphics/celobj32.h
+++ b/engines/sci/graphics/celobj32.h
@@ -147,7 +147,7 @@ struct CelScalerTable {
* the correct column to read from the source bitmap
* when drawing a scaled version of the source bitmap.
*/
- int valuesX[1024];
+ int valuesX[4096];
/**
* The ratio used to generate the x-values.
@@ -159,7 +159,7 @@ struct CelScalerTable {
* the correct row to read from a source bitmap when
* drawing a scaled version of the source bitmap.
*/
- int valuesY[1024];
+ int valuesY[4096];
/**
* The ratio used to generate the y-values.
@@ -400,7 +400,7 @@ public:
* Reads the pixel at the given coordinates. This method
* is valid only for CelObjView and CelObjPic.
*/
- virtual uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const;
+ virtual uint8 readPixel(const uint16 x, const uint16 y, const bool mirrorX) const;
/**
* Submits the palette from this cel to the palette
@@ -505,6 +505,9 @@ public:
using CelObj::draw;
+ static int16 getNumLoops(const GuiResourceId viewId);
+ static int16 getNumCels(const GuiResourceId viewId, const int16 loopNo);
+
/**
* Draws the cel to the target buffer using the
* positioning, mirroring, and scaling information from
diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp
index 130416ff60..36026a8134 100644
--- a/engines/sci/graphics/compare.cpp
+++ b/engines/sci/graphics/compare.cpp
@@ -37,7 +37,7 @@
namespace Sci {
-GfxCompare::GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster *coordAdjuster)
+GfxCompare::GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster16 *coordAdjuster)
: _segMan(segMan), _cache(cache), _screen(screen), _coordAdjuster(coordAdjuster) {
}
diff --git a/engines/sci/graphics/compare.h b/engines/sci/graphics/compare.h
index c7005980d0..dd65b90bea 100644
--- a/engines/sci/graphics/compare.h
+++ b/engines/sci/graphics/compare.h
@@ -34,7 +34,7 @@ class Screen;
*/
class GfxCompare {
public:
- GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster *coordAdjuster);
+ GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster16 *coordAdjuster);
~GfxCompare();
uint16 kernelOnControl(byte screenMask, const Common::Rect &rect);
@@ -50,7 +50,7 @@ private:
SegManager *_segMan;
GfxCache *_cache;
GfxScreen *_screen;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
uint16 isOnControl(uint16 screenMask, const Common::Rect &rect);
diff --git a/engines/sci/graphics/coordadjuster.cpp b/engines/sci/graphics/coordadjuster.cpp
index 93dff10382..2f22d191d0 100644
--- a/engines/sci/graphics/coordadjuster.cpp
+++ b/engines/sci/graphics/coordadjuster.cpp
@@ -32,9 +32,6 @@
namespace Sci {
-GfxCoordAdjuster::GfxCoordAdjuster() {
-}
-
GfxCoordAdjuster16::GfxCoordAdjuster16(GfxPorts *ports)
: _ports(ports) {
}
@@ -83,53 +80,4 @@ Common::Rect GfxCoordAdjuster16::pictureGetDisplayArea() {
return displayArea;
}
-#ifdef ENABLE_SCI32
-GfxCoordAdjuster32::GfxCoordAdjuster32(SegManager *segMan)
- : _segMan(segMan) {
- _scriptsRunningWidth = 0;
- _scriptsRunningHeight = 0;
-}
-
-GfxCoordAdjuster32::~GfxCoordAdjuster32() {
-}
-
-void GfxCoordAdjuster32::kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject) {
- uint16 planeTop = readSelectorValue(_segMan, planeObject, SELECTOR(top));
- uint16 planeLeft = readSelectorValue(_segMan, planeObject, SELECTOR(left));
-
- y -= planeTop;
- x -= planeLeft;
-}
-void GfxCoordAdjuster32::kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject) {
- uint16 planeTop = readSelectorValue(_segMan, planeObject, SELECTOR(top));
- uint16 planeLeft = readSelectorValue(_segMan, planeObject, SELECTOR(left));
-
- x += planeLeft;
- y += planeTop;
-}
-
-void GfxCoordAdjuster32::setScriptsResolution(uint16 width, uint16 height) {
- _scriptsRunningWidth = width;
- _scriptsRunningHeight = height;
-}
-
-void GfxCoordAdjuster32::fromDisplayToScript(int16 &y, int16 &x) {
- y = ((y * _scriptsRunningHeight) / g_sci->_gfxScreen->getHeight());
- x = ((x * _scriptsRunningWidth) / g_sci->_gfxScreen->getWidth());
-}
-
-void GfxCoordAdjuster32::fromScriptToDisplay(int16 &y, int16 &x) {
- y = ((y * g_sci->_gfxScreen->getHeight()) / _scriptsRunningHeight);
- x = ((x * g_sci->_gfxScreen->getWidth()) / _scriptsRunningWidth);
-}
-
-void GfxCoordAdjuster32::pictureSetDisplayArea(Common::Rect displayArea) {
- _pictureDisplayArea = displayArea;
-}
-
-Common::Rect GfxCoordAdjuster32::pictureGetDisplayArea() {
- return _pictureDisplayArea;
-}
-#endif
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/coordadjuster.h b/engines/sci/graphics/coordadjuster.h
index cb0227fbe4..f7ebd3ec75 100644
--- a/engines/sci/graphics/coordadjuster.h
+++ b/engines/sci/graphics/coordadjuster.h
@@ -35,27 +35,7 @@ class GfxPorts;
* most of the time sci32 doesn't do any coordinate adjustment at all
* sci16 does a lot of port adjustment on given coordinates
*/
-class GfxCoordAdjuster {
-public:
- GfxCoordAdjuster();
- virtual ~GfxCoordAdjuster() { }
-
- virtual void kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject = NULL_REG) { }
- virtual void kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject = NULL_REG) { }
-
- virtual Common::Rect onControl(Common::Rect rect) { return rect; }
- virtual void setCursorPos(Common::Point &pos) { }
- virtual void moveCursor(Common::Point &pos) { }
-
- virtual void setScriptsResolution(uint16 width, uint16 height) { }
- virtual void fromScriptToDisplay(int16 &y, int16 &x) { }
- virtual void fromDisplayToScript(int16 &y, int16 &x) { }
-
- virtual Common::Rect pictureGetDisplayArea() { return Common::Rect(0, 0); }
-private:
-};
-
-class GfxCoordAdjuster16 : public GfxCoordAdjuster {
+class GfxCoordAdjuster16 {
public:
GfxCoordAdjuster16(GfxPorts *ports);
~GfxCoordAdjuster16();
@@ -73,32 +53,6 @@ private:
GfxPorts *_ports;
};
-#ifdef ENABLE_SCI32
-class GfxCoordAdjuster32 : public GfxCoordAdjuster {
-public:
- GfxCoordAdjuster32(SegManager *segMan);
- ~GfxCoordAdjuster32();
-
- void kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject = NULL_REG);
- void kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject = NULL_REG);
-
- void setScriptsResolution(uint16 width, uint16 height);
- void fromScriptToDisplay(int16 &y, int16 &x);
- void fromDisplayToScript(int16 &y, int16 &x);
-
- void pictureSetDisplayArea(Common::Rect displayArea);
- Common::Rect pictureGetDisplayArea();
-
-private:
- SegManager *_segMan;
-
- Common::Rect _pictureDisplayArea;
-
- uint16 _scriptsRunningWidth;
- uint16 _scriptsRunningHeight;
-};
-#endif
-
} // End of namespace Sci
#endif
diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp
index f5dd473959..7cf9a574ef 100644
--- a/engines/sci/graphics/cursor.cpp
+++ b/engines/sci/graphics/cursor.cpp
@@ -80,7 +80,7 @@ GfxCursor::~GfxCursor() {
kernelClearZoomZone();
}
-void GfxCursor::init(GfxCoordAdjuster *coordAdjuster, EventManager *event) {
+void GfxCursor::init(GfxCoordAdjuster16 *coordAdjuster, EventManager *event) {
_coordAdjuster = coordAdjuster;
_event = event;
}
@@ -512,32 +512,18 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu
// automatically. The view resources may exist, but none of the games actually
// use them.
- if (_macCursorRemap.empty()) {
- // QFG1/Freddy/Hoyle4 use a straight viewNum->cursor ID mapping
- // KQ6 uses this mapping for its cursors
- if (g_sci->getGameId() == GID_KQ6) {
- if (viewNum == 990) // Inventory Cursors
- viewNum = loopNum * 16 + celNum + 2000;
- else if (viewNum == 998) // Regular Cursors
- viewNum = celNum + 1000;
- else // Unknown cursor, ignored
- return;
- }
- if (g_sci->hasMacIconBar())
- g_sci->_gfxMacIconBar->setInventoryIcon(viewNum);
- } else {
- // If we do have the list, we'll be using a remap based on what the
- // scripts have given us.
- for (uint32 i = 0; i < _macCursorRemap.size(); i++) {
- if (viewNum == _macCursorRemap[i]) {
- viewNum = (i + 1) * 0x100 + loopNum * 0x10 + celNum;
- break;
- }
-
- if (i == _macCursorRemap.size())
- error("Unmatched Mac cursor %d", viewNum);
- }
+ // QFG1/Freddy/Hoyle4 use a straight viewNum->cursor ID mapping
+ // KQ6 uses this mapping for its cursors
+ if (g_sci->getGameId() == GID_KQ6) {
+ if (viewNum == 990) // Inventory Cursors
+ viewNum = loopNum * 16 + celNum + 2000;
+ else if (viewNum == 998) // Regular Cursors
+ viewNum = celNum + 1000;
+ else // Unknown cursor, ignored
+ return;
}
+ if (g_sci->hasMacIconBar())
+ g_sci->_gfxMacIconBar->setInventoryIcon(viewNum);
Resource *resource = _resMan->findResource(ResourceId(kResourceTypeCursor, viewNum), false);
@@ -568,9 +554,4 @@ void GfxCursor::kernelSetMacCursor(GuiResourceId viewNum, int loopNum, int celNu
kernelShow();
}
-void GfxCursor::setMacCursorRemapList(int cursorCount, reg_t *cursors) {
- for (int i = 0; i < cursorCount; i++)
- _macCursorRemap.push_back(cursors[i].toUint16());
-}
-
} // End of namespace Sci
diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h
index 5125469cfe..36518ea5db 100644
--- a/engines/sci/graphics/cursor.h
+++ b/engines/sci/graphics/cursor.h
@@ -55,7 +55,7 @@ public:
GfxCursor(ResourceManager *resMan, GfxPalette *palette, GfxScreen *screen);
~GfxCursor();
- void init(GfxCoordAdjuster *coordAdjuster, EventManager *event);
+ void init(GfxCoordAdjuster16 *coordAdjuster, EventManager *event);
void kernelShow();
void kernelHide();
@@ -95,15 +95,13 @@ public:
void kernelSetPos(Common::Point pos);
void kernelMoveCursor(Common::Point pos);
- void setMacCursorRemapList(int cursorCount, reg_t *cursors);
-
private:
void purgeCache();
ResourceManager *_resMan;
GfxScreen *_screen;
GfxPalette *_palette;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
EventManager *_event;
int _upscaledHires;
@@ -136,9 +134,6 @@ private:
// these instead and replace the game's gold cursors with their silver
// equivalents.
bool _useSilverSQ4CDCursors;
-
- // Mac versions of games use a remap list to remap their cursors
- Common::Array<uint16> _macCursorRemap;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/cursor32.cpp b/engines/sci/graphics/cursor32.cpp
new file mode 100644
index 0000000000..88150db6e6
--- /dev/null
+++ b/engines/sci/graphics/cursor32.cpp
@@ -0,0 +1,448 @@
+/* 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/rational.h" // for Rational, operator*
+#include "common/system.h" // for OSystem, g_system
+#include "common/memstream.h"
+#include "graphics/cursorman.h" // for CursorMan
+#include "graphics/maccursor.h"
+#include "sci/graphics/celobj32.h" // for CelObjView, CelInfo32, Ratio
+#include "sci/graphics/cursor32.h"
+#include "sci/graphics/frameout.h" // for GfxFrameout
+
+namespace Sci {
+
+GfxCursor32::GfxCursor32() :
+ _hideCount(0),
+ _position(0, 0),
+ _writeToVMAP(false) {
+ CursorMan.showMouse(false);
+}
+
+void GfxCursor32::init(const Buffer &vmap) {
+ _vmap = vmap;
+ _vmapRegion.rect = Common::Rect(_vmap.screenWidth, _vmap.screenHeight);
+ _vmapRegion.data = (byte *)_vmap.getPixels();
+ _restrictedArea = _vmapRegion.rect;
+}
+
+GfxCursor32::~GfxCursor32() {
+ CursorMan.showMouse(true);
+ free(_cursor.data);
+ free(_cursorBack.data);
+ free(_drawBuff1.data);
+ free(_drawBuff2.data);
+ free(_savedVmapRegion.data);
+}
+
+void GfxCursor32::hide() {
+ if (_hideCount++) {
+ return;
+ }
+
+ if (!_cursorBack.rect.isEmpty()) {
+ drawToHardware(_cursorBack);
+ }
+}
+
+void GfxCursor32::revealCursor() {
+ _cursorBack.rect = _cursor.rect;
+ _cursorBack.rect.clip(_vmapRegion.rect);
+ if (_cursorBack.rect.isEmpty()) {
+ return;
+ }
+
+ readVideo(_cursorBack);
+ _drawBuff1.rect = _cursor.rect;
+ copy(_drawBuff1, _cursorBack);
+ paint(_drawBuff1, _cursor);
+ drawToHardware(_drawBuff1);
+}
+
+void GfxCursor32::paint(DrawRegion &target, const DrawRegion &source) {
+ if (source.rect.isEmpty()) {
+ return;
+ }
+
+ Common::Rect drawRect(source.rect);
+ drawRect.clip(target.rect);
+ if (drawRect.isEmpty()) {
+ return;
+ }
+
+ const int16 sourceXOffset = drawRect.left - source.rect.left;
+ const int16 sourceYOffset = drawRect.top - source.rect.top;
+ const int16 drawRectWidth = drawRect.width();
+ const int16 drawRectHeight = drawRect.height();
+
+ byte *targetPixel = target.data + ((drawRect.top - target.rect.top) * target.rect.width()) + (drawRect.left - target.rect.left);
+ const byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset;
+ const uint8 skipColor = source.skipColor;
+
+ const int16 sourceStride = source.rect.width() - drawRectWidth;
+ const int16 targetStride = target.rect.width() - drawRectWidth;
+
+ for (int16 y = 0; y < drawRectHeight; ++y) {
+ for (int16 x = 0; x < drawRectWidth; ++x) {
+ if (*sourcePixel != skipColor) {
+ *targetPixel = *sourcePixel;
+ }
+ ++targetPixel;
+ ++sourcePixel;
+ }
+ sourcePixel += sourceStride;
+ targetPixel += targetStride;
+ }
+}
+
+void GfxCursor32::drawToHardware(const DrawRegion &source) {
+ Common::Rect drawRect(source.rect);
+ drawRect.clip(_vmapRegion.rect);
+ const int16 sourceXOffset = drawRect.left - source.rect.left;
+ const int16 sourceYOffset = drawRect.top - source.rect.top;
+ byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset;
+
+ g_system->copyRectToScreen(sourcePixel, source.rect.width(), drawRect.left, drawRect.top, drawRect.width(), drawRect.height());
+}
+
+void GfxCursor32::unhide() {
+ if (_hideCount == 0 || --_hideCount) {
+ return;
+ }
+
+ _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y);
+ revealCursor();
+}
+
+void GfxCursor32::show() {
+ if (_hideCount) {
+ _hideCount = 0;
+ _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y);
+ revealCursor();
+ }
+}
+
+void GfxCursor32::setRestrictedArea(const Common::Rect &rect) {
+ _restrictedArea = rect;
+
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ mulru(_restrictedArea, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight), 0);
+
+ if (_position.x < rect.left) {
+ _position.x = rect.left;
+ }
+ if (_position.x >= rect.right) {
+ _position.x = rect.right - 1;
+ }
+ if (_position.y < rect.top) {
+ _position.y = rect.top;
+ }
+ if (_position.y >= rect.bottom) {
+ _position.y = rect.bottom - 1;
+ }
+
+ g_system->warpMouse(_position.x, _position.y);
+}
+
+void GfxCursor32::clearRestrictedArea() {
+ _restrictedArea = _vmapRegion.rect;
+}
+
+void GfxCursor32::setView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) {
+ hide();
+
+ _cursorInfo.resourceId = viewId;
+ _cursorInfo.loopNo = loopNo;
+ _cursorInfo.celNo = celNo;
+
+ if (_macCursorRemap.empty() && viewId != -1) {
+ CelObjView view(viewId, loopNo, celNo);
+
+ _hotSpot = view._displace;
+ _width = view._width;
+ _height = view._height;
+
+ // SSCI never increased the size of cursors, but some of the cursors
+ // in early SCI32 games were designed for low-resolution display mode
+ // and so are kind of hard to pick out when running in high-resolution
+ // mode.
+ // To address this, we make some slight adjustments to cursor display
+ // in these early games:
+ // GK1: All the cursors are increased in size since they all appear to
+ // be designed for low-res display.
+ // PQ4: We only make the cursors bigger if they are above a set
+ // threshold size because inventory items usually have a
+ // high-resolution cursor representation.
+ bool pixelDouble = false;
+ if (g_sci->_gfxFrameout->_isHiRes &&
+ (g_sci->getGameId() == GID_GK1 ||
+ (g_sci->getGameId() == GID_PQ4 && _width <= 22 && _height <= 22))) {
+
+ _width *= 2;
+ _height *= 2;
+ _hotSpot.x *= 2;
+ _hotSpot.y *= 2;
+ pixelDouble = true;
+ }
+
+ _cursor.data = (byte *)realloc(_cursor.data, _width * _height);
+ _cursor.rect = Common::Rect(_width, _height);
+ memset(_cursor.data, 255, _width * _height);
+ _cursor.skipColor = 255;
+
+ Buffer target(_width, _height, _cursor.data);
+ if (pixelDouble) {
+ view.draw(target, _cursor.rect, Common::Point(0, 0), false, 2, 2);
+ } else {
+ view.draw(target, _cursor.rect, Common::Point(0, 0), false);
+ }
+ } else if (!_macCursorRemap.empty() && viewId != -1) {
+ // Mac cursor handling
+ GuiResourceId viewNum = viewId;
+
+ // Remap cursor view based on what the scripts have given us.
+ for (uint32 i = 0; i < _macCursorRemap.size(); i++) {
+ if (viewNum == _macCursorRemap[i]) {
+ viewNum = (i + 1) * 0x100 + loopNo * 0x10 + celNo;
+ break;
+ }
+
+ if (i == _macCursorRemap.size())
+ error("Unmatched Mac cursor %d", viewNum);
+ }
+
+ _cursorInfo.resourceId = viewNum;
+
+ Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeCursor, viewNum), false);
+
+ if (!resource) {
+ // The cursor resources often don't exist, this is normal behavior
+ debug(0, "Mac cursor %d not found", viewNum);
+ return;
+ }
+ Common::MemoryReadStream resStream(resource->data, resource->size);
+ Graphics::MacCursor *macCursor = new Graphics::MacCursor();
+
+ if (!macCursor->readFromStream(resStream)) {
+ warning("Failed to load Mac cursor %d", viewNum);
+ delete macCursor;
+ return;
+ }
+
+ _hotSpot = Common::Point(macCursor->getHotspotX(), macCursor->getHotspotY());
+ _width = macCursor->getWidth();
+ _height = macCursor->getHeight();
+
+ _cursor.data = (byte *)realloc(_cursor.data, _width * _height);
+ memcpy(_cursor.data, macCursor->getSurface(), _width * _height);
+ _cursor.rect = Common::Rect(_width, _height);
+ _cursor.skipColor = macCursor->getKeyColor();
+
+ // The cursor will be drawn on next refresh
+ delete macCursor;
+ } else {
+ _hotSpot = Common::Point(0, 0);
+ _width = _height = 1;
+ _cursor.data = (byte *)realloc(_cursor.data, _width * _height);
+ _cursor.rect = Common::Rect(_width, _height);
+ *_cursor.data = _cursor.skipColor;
+ _cursorBack.rect = _cursor.rect;
+ _cursorBack.rect.clip(_vmapRegion.rect);
+ if (!_cursorBack.rect.isEmpty()) {
+ readVideo(_cursorBack);
+ }
+ }
+
+ _cursorBack.data = (byte *)realloc(_cursorBack.data, _width * _height);
+ _drawBuff1.data = (byte *)realloc(_drawBuff1.data, _width * _height);
+ _drawBuff2.data = (byte *)realloc(_drawBuff2.data, _width * _height * 4);
+ _savedVmapRegion.data = (byte *)realloc(_savedVmapRegion.data, _width * _height);
+
+ unhide();
+}
+
+void GfxCursor32::readVideo(DrawRegion &target) {
+ if (g_sci->_gfxFrameout->_frameNowVisible) {
+ copy(target, _vmapRegion);
+ } else {
+ // NOTE: SSCI would read the background for the cursor directly out of
+ // video memory here, but as far as can be determined, this does not
+ // seem to actually be necessary for proper cursor rendering
+ }
+}
+
+void GfxCursor32::copy(DrawRegion &target, const DrawRegion &source) {
+ if (source.rect.isEmpty()) {
+ return;
+ }
+
+ Common::Rect drawRect(source.rect);
+ drawRect.clip(target.rect);
+ if (drawRect.isEmpty()) {
+ return;
+ }
+
+ const int16 sourceXOffset = drawRect.left - source.rect.left;
+ const int16 sourceYOffset = drawRect.top - source.rect.top;
+ const int16 drawWidth = drawRect.width();
+ const int16 drawHeight = drawRect.height();
+
+ byte *targetPixel = target.data + ((drawRect.top - target.rect.top) * target.rect.width()) + (drawRect.left - target.rect.left);
+ const byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset;
+
+ const int16 sourceStride = source.rect.width();
+ const int16 targetStride = target.rect.width();
+
+ for (int y = 0; y < drawHeight; ++y) {
+ memcpy(targetPixel, sourcePixel, drawWidth);
+ targetPixel += targetStride;
+ sourcePixel += sourceStride;
+ }
+}
+
+void GfxCursor32::setPosition(const Common::Point &position) {
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ _position.x = (position.x * Ratio(screenWidth, scriptWidth)).toInt();
+ _position.y = (position.y * Ratio(screenHeight, scriptHeight)).toInt();
+
+ g_system->warpMouse(_position.x, _position.y);
+}
+
+void GfxCursor32::gonnaPaint(Common::Rect paintRect) {
+ if (!_hideCount && !_writeToVMAP && !_cursorBack.rect.isEmpty()) {
+ paintRect.left &= ~3;
+ paintRect.right |= 3;
+ if (_cursorBack.rect.intersects(paintRect)) {
+ _writeToVMAP = true;
+ }
+ }
+}
+
+void GfxCursor32::paintStarting() {
+ if (_writeToVMAP) {
+ _savedVmapRegion.rect = _cursor.rect;
+ copy(_savedVmapRegion, _vmapRegion);
+ paint(_vmapRegion, _cursor);
+ }
+}
+
+void GfxCursor32::donePainting() {
+ if (_writeToVMAP) {
+ copy(_vmapRegion, _savedVmapRegion);
+ _savedVmapRegion.rect = Common::Rect();
+ _writeToVMAP = false;
+ }
+
+ if (!_hideCount && !_cursorBack.rect.isEmpty()) {
+ copy(_cursorBack, _vmapRegion);
+ }
+}
+
+void GfxCursor32::deviceMoved(Common::Point &position) {
+ if (position.x < _restrictedArea.left) {
+ position.x = _restrictedArea.left;
+ }
+ if (position.x >= _restrictedArea.right) {
+ position.x = _restrictedArea.right - 1;
+ }
+ if (position.y < _restrictedArea.top) {
+ position.y = _restrictedArea.top;
+ }
+ if (position.y >= _restrictedArea.bottom) {
+ position.y = _restrictedArea.bottom - 1;
+ }
+
+ _position = position;
+
+ g_system->warpMouse(position.x, position.y);
+ move();
+}
+
+void GfxCursor32::move() {
+ if (_hideCount) {
+ return;
+ }
+
+ // Cursor moved onto the screen after being offscreen
+ _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y);
+ if (_cursorBack.rect.isEmpty()) {
+ revealCursor();
+ return;
+ }
+
+ // Cursor moved offscreen
+ if (!_cursor.rect.intersects(_vmapRegion.rect)) {
+ drawToHardware(_cursorBack);
+ return;
+ }
+
+ if (!_cursor.rect.intersects(_cursorBack.rect)) {
+ // Cursor moved to a completely different part of the screen
+ _drawBuff1.rect = _cursor.rect;
+ _drawBuff1.rect.clip(_vmapRegion.rect);
+ readVideo(_drawBuff1);
+
+ _drawBuff2.rect = _drawBuff1.rect;
+ copy(_drawBuff2, _drawBuff1);
+
+ paint(_drawBuff1, _cursor);
+ drawToHardware(_drawBuff1);
+
+ drawToHardware(_cursorBack);
+
+ _cursorBack.rect = _cursor.rect;
+ _cursorBack.rect.clip(_vmapRegion.rect);
+ copy(_cursorBack, _drawBuff2);
+ } else {
+ // Cursor moved, but still overlaps the previous cursor location
+ Common::Rect mergedRect(_cursorBack.rect);
+ mergedRect.extend(_cursor.rect);
+ mergedRect.clip(_vmapRegion.rect);
+
+ _drawBuff2.rect = mergedRect;
+ readVideo(_drawBuff2);
+
+ copy(_drawBuff2, _cursorBack);
+
+ _cursorBack.rect = _cursor.rect;
+ _cursorBack.rect.clip(_vmapRegion.rect);
+ copy(_cursorBack, _drawBuff2);
+
+ paint(_drawBuff2, _cursor);
+ drawToHardware(_drawBuff2);
+ }
+}
+
+void GfxCursor32::setMacCursorRemapList(int cursorCount, reg_t *cursors) {
+ for (int i = 0; i < cursorCount; i++)
+ _macCursorRemap.push_back(cursors[i].toUint16());
+}
+
+} // End of namespace Sci
diff --git a/engines/sci/graphics/cursor32.h b/engines/sci/graphics/cursor32.h
new file mode 100644
index 0000000000..88a75beb7f
--- /dev/null
+++ b/engines/sci/graphics/cursor32.h
@@ -0,0 +1,255 @@
+/* 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 SCI_GRAPHICS_CURSOR32_H
+#define SCI_GRAPHICS_CURSOR32_H
+
+#include "common/rect.h" // for Point, Rect
+#include "common/scummsys.h" // for int16, byte, uint8
+#include "common/serializer.h" // for Serializable, Serializer (ptr only)
+#include "sci/graphics/celobj32.h" // for CelInfo32
+#include "sci/graphics/helpers.h" // for GuiResourceId
+
+namespace Sci {
+
+class GfxCursor32 : Common::Serializable {
+public:
+ GfxCursor32();
+ ~GfxCursor32();
+
+ /**
+ * Initialises the cursor system with the given
+ * buffer to use as the output buffer for
+ * rendering the cursor.
+ */
+ void init(const Buffer &vmap);
+
+ /**
+ * Called when the hardware mouse moves.
+ */
+ void deviceMoved(Common::Point &position);
+
+ /**
+ * Called by GfxFrameout once for each show
+ * rectangle that is going to be drawn to
+ * hardware.
+ */
+ void gonnaPaint(Common::Rect paintRect);
+
+ /**
+ * Called by GfxFrameout when the rendering to
+ * hardware begins.
+ */
+ void paintStarting();
+
+ /**
+ * Called by GfxFrameout when the output buffer
+ * has finished rendering to hardware.
+ */
+ void donePainting();
+
+ /**
+ * Hides the cursor. Each call to `hide` will
+ * increment a hide counter, which must be
+ * returned to 0 before the cursor will be
+ * shown again.
+ */
+ void hide();
+
+ /**
+ * Shows the cursor, if the hide counter is
+ * returned to 0.
+ */
+ void unhide();
+
+ /**
+ * Shows the cursor regardless of the state of
+ * the hide counter.
+ */
+ void show();
+
+ /**
+ * Sets the view used to render the cursor.
+ */
+ void setView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo);
+
+ /**
+ * Explicitly sets the position of the cursor,
+ * in game script coordinates.
+ */
+ void setPosition(const Common::Point &position);
+
+ /**
+ * Sets the region that the mouse is allowed
+ * to move within.
+ */
+ void setRestrictedArea(const Common::Rect &rect);
+
+ /**
+ * Removes restrictions on mouse movement.
+ */
+ void clearRestrictedArea();
+
+ void setMacCursorRemapList(int cursorCount, reg_t *cursors);
+
+ virtual void saveLoadWithSerializer(Common::Serializer &ser);
+
+private:
+ struct DrawRegion {
+ Common::Rect rect;
+ byte *data;
+ uint8 skipColor;
+
+ DrawRegion() : rect(), data(nullptr) {}
+ };
+
+ /**
+ * Information about the current cursor.
+ * Used to restore cursor when loading a
+ * savegame.
+ */
+ CelInfo32 _cursorInfo;
+
+ /**
+ * Content behind the cursor? TODO
+ */
+ DrawRegion _cursorBack;
+
+ /**
+ * Scratch buffer.
+ */
+ DrawRegion _drawBuff1;
+
+ /**
+ * Scratch buffer 2.
+ */
+ DrawRegion _drawBuff2;
+
+ /**
+ * A draw region representing the current
+ * output buffer.
+ */
+ DrawRegion _vmapRegion;
+
+ /**
+ * The content behind the cursor in the
+ * output buffer.
+ */
+ DrawRegion _savedVmapRegion;
+
+ /**
+ * The cursor bitmap.
+ */
+ DrawRegion _cursor;
+
+ /**
+ * The width and height of the cursor,
+ * in screen coordinates.
+ */
+ int16 _width, _height;
+
+ /**
+ * The output buffer where the cursor is
+ * rendered.
+ */
+ Buffer _vmap;
+
+ /**
+ * The number of times the cursor has been
+ * hidden.
+ */
+ int _hideCount;
+
+ /**
+ * The rendered position of the cursor, in
+ * screen coordinates.
+ */
+ Common::Point _position;
+
+ /**
+ * The position of the cursor hot spot, relative
+ * to the cursor origin, in screen pixels.
+ */
+ Common::Point _hotSpot;
+
+ /**
+ * The area within which the cursor is allowed
+ * to move, in screen pixels.
+ */
+ Common::Rect _restrictedArea;
+
+ /**
+ * Indicates whether or not the cursor needs to
+ * be repainted on the output buffer due to a
+ * change of graphics in the area underneath the
+ * cursor.
+ */
+ bool _writeToVMAP;
+
+ // Mac versions of games use a remap list to remap their cursors
+ Common::Array<uint16> _macCursorRemap;
+
+ /**
+ * Reads data from the output buffer or hardware
+ * to the given draw region.
+ */
+ void readVideo(DrawRegion &target);
+
+ /**
+ * Reads data from the output buffer to the
+ * given draw region.
+ */
+ void readVideoFromVmap(DrawRegion &target);
+
+ /**
+ * Copies pixel data from the given source to
+ * the given target.
+ */
+ void copy(DrawRegion &target, const DrawRegion &source);
+
+ /**
+ * Draws from the given source onto the given
+ * target, skipping pixels in the source that
+ * match the `skipColor` property.
+ */
+ void paint(DrawRegion &target, const DrawRegion &source);
+
+ /**
+ * Draws the cursor to the position it was
+ * drawn to prior to moving offscreen or being
+ * hidden by a call to `hide`.
+ */
+ void revealCursor();
+
+ /**
+ * Draws the given source to the output buffer.
+ */
+ void drawToHardware(const DrawRegion &source);
+
+ /**
+ * Renders the cursor at its new location.
+ */
+ void move();
+};
+
+} // End of namespace Sci
+#endif
diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp
index 1decfa03a9..4e0aa22669 100644
--- a/engines/sci/graphics/frameout.cpp
+++ b/engines/sci/graphics/frameout.cpp
@@ -29,6 +29,7 @@
#include "common/system.h"
#include "common/textconsole.h"
#include "engines/engine.h"
+#include "engines/util.h"
#include "graphics/palette.h"
#include "graphics/surface.h"
@@ -39,50 +40,52 @@
#include "sci/engine/selector.h"
#include "sci/engine/vm.h"
#include "sci/graphics/cache.h"
-#include "sci/graphics/coordadjuster.h"
#include "sci/graphics/compare.h"
+#include "sci/graphics/cursor32.h"
#include "sci/graphics/font.h"
-#include "sci/graphics/screen.h"
+#include "sci/graphics/frameout.h"
#include "sci/graphics/paint32.h"
#include "sci/graphics/palette32.h"
#include "sci/graphics/plane32.h"
#include "sci/graphics/remap32.h"
+#include "sci/graphics/screen.h"
#include "sci/graphics/screen_item32.h"
#include "sci/graphics/text32.h"
#include "sci/graphics/frameout.h"
-#include "sci/video/robot_decoder.h"
#include "sci/graphics/transitions32.h"
+#include "sci/graphics/video32.h"
namespace Sci {
-GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette, GfxTransitions32 *transitions) :
- _isHiRes(false),
+GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor) :
+ _isHiRes(ConfMan.getBool("enable_high_resolution_graphics")),
_palette(palette),
- _resMan(resMan),
- _screen(screen),
+ _cursor(cursor),
_segMan(segMan),
_transitions(transitions),
_benchmarkingFinished(false),
_throttleFrameOut(true),
_throttleState(0),
- // TODO: Stop using _gfxScreen
- _currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr),
_remapOccurred(false),
_frameNowVisible(false),
- _screenRect(screen->getDisplayWidth(), screen->getDisplayHeight()),
_overdrawThreshold(0),
_palMorphIsOn(false) {
- _currentBuffer.setPixels(calloc(1, screen->getDisplayWidth() * screen->getDisplayHeight()));
+ // QFG4 is the only SCI32 game that doesn't have a high-resolution version
+ if (g_sci->getGameId() == GID_QFG4) {
+ _isHiRes = false;
+ }
- // TODO: Make hires detection work uniformly across all SCI engine
- // versions (this flag is normally passed by SCI::MakeGraphicsMgr
- // to the GraphicsMgr constructor depending upon video configuration,
- // so should be handled upstream based on game configuration instead
- // of here)
- if (getSciVersion() >= SCI_VERSION_2_1_EARLY && _resMan->detectHires()) {
- _isHiRes = true;
+ if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
+ _currentBuffer = Buffer(630, 450, nullptr);
+ } else if (_isHiRes) {
+ _currentBuffer = Buffer(640, 480, nullptr);
+ } else {
+ _currentBuffer = Buffer(320, 200, nullptr);
}
+ _currentBuffer.setPixels(calloc(1, _currentBuffer.screenWidth * _currentBuffer.screenHeight));
+ _screenRect = Common::Rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight);
+ initGraphics(_currentBuffer.screenWidth, _currentBuffer.screenHeight, _isHiRes);
switch (g_sci->getGameId()) {
case GID_HOYLE5:
@@ -100,20 +103,6 @@ GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAd
// default script width for other games is 320x200
break;
}
-
- // TODO: Nothing in the renderer really uses this. Currently,
- // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal
- // do, but in the real engine (1) the cursor is handled in
- // frameOut, and (2) functions do a very simple lookup of the
- // plane and arithmetic with the plane's gameRect. In
- // principle, CoordAdjuster could be reused for
- // convertGameRectToPlaneRect, but it is not super clear yet
- // what the benefit would be to do that.
- _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster;
-
- // TODO: Script resolution is hard-coded per game;
- // also this must be set or else the engine will crash
- _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight);
}
GfxFrameout::~GfxFrameout() {
@@ -497,10 +486,12 @@ void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pi
#pragma mark Rendering
void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) {
-// TODO: Robot
-// if (_robot != nullptr) {
-// _robot.doRobot();
-// }
+ RobotDecoder &robotPlayer = g_sci->_video32->getRobotPlayer();
+ const bool robotIsActive = robotPlayer.getStatus() != RobotDecoder::kRobotStatusUninitialized;
+
+ if (robotIsActive) {
+ robotPlayer.doRobot();
+ }
// NOTE: The original engine allocated these as static arrays of 100
// pointers to ScreenItemList / RectList
@@ -538,10 +529,9 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseR
drawScreenItemList(screenItemLists[i]);
}
-// TODO: Robot
-// if (_robot != nullptr) {
-// _robot->frameAlmostVisible();
-// }
+ if (robotIsActive) {
+ robotPlayer.frameAlmostVisible();
+ }
_palette->updateHardware(!shouldShowBits);
@@ -551,10 +541,9 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseR
_frameNowVisible = true;
-// TODO: Robot
-// if (_robot != nullptr) {
-// robot->frameNowVisible();
-// }
+ if (robotIsActive) {
+ robotPlayer.frameNowVisible();
+ }
}
void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *showStyle) {
@@ -563,7 +552,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *show
int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16();
- Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight());
+ Common::Rect rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight);
_showList.add(rect);
showBits();
@@ -1118,6 +1107,10 @@ void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showLi
}
void GfxFrameout::showBits() {
+ if (!_showList.size()) {
+ return;
+ }
+
for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
Common::Rect rounded(**rect);
// NOTE: SCI engine used BR-inclusive rects so used slightly
@@ -1125,13 +1118,10 @@ void GfxFrameout::showBits() {
// was always even.
rounded.left &= ~1;
rounded.right = (rounded.right + 1) & ~1;
-
- // TODO:
- // _cursor->GonnaPaint(rounded);
+ _cursor->gonnaPaint(rounded);
}
- // TODO:
- // _cursor->PaintStarting();
+ _cursor->paintStarting();
for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) {
Common::Rect rounded(**rect);
@@ -1153,8 +1143,7 @@ void GfxFrameout::showBits() {
g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height());
}
- // TODO:
- // _cursor->DonePainting();
+ _cursor->donePainting();
_showList.clear();
}
@@ -1270,6 +1259,30 @@ void GfxFrameout::showRect(const Common::Rect &rect) {
}
}
+void GfxFrameout::shakeScreen(int16 numShakes, const ShakeDirection direction) {
+ if (direction & kShakeHorizontal) {
+ // Used by QFG4 room 750
+ warning("TODO: Horizontal shake not implemented");
+ return;
+ }
+
+ while (numShakes--) {
+ if (direction & kShakeVertical) {
+ g_system->setShakePos(_isHiRes ? 8 : 4);
+ }
+
+ g_system->updateScreen();
+ g_sci->getEngineState()->wait(3);
+
+ if (direction & kShakeVertical) {
+ g_system->setShakePos(0);
+ }
+
+ g_system->updateScreen();
+ g_sci->getEngineState()->wait(3);
+ }
+}
+
#pragma mark -
#pragma mark Mouse cursor
@@ -1328,7 +1341,7 @@ bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const
return true;
}
-void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const {
+bool GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const {
const reg_t planeObject = readSelector(_segMan, screenItemObject, SELECTOR(plane));
Plane *plane = _planes.findByObject(planeObject);
@@ -1338,7 +1351,7 @@ void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const {
ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject);
if (screenItem == nullptr) {
- error("kSetNowSeen: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItemObject), PRINT_REG(planeObject));
+ return false;
}
Common::Rect result = screenItem->getNowSeenRect(*plane);
@@ -1346,6 +1359,7 @@ void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const {
writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsTop), result.top);
writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsRight), result.right - 1);
writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsBottom), result.bottom - 1);
+ return true;
}
void GfxFrameout::remapMarkRedraw() {
diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h
index 42f1d80101..e4caffd9e5 100644
--- a/engines/sci/graphics/frameout.h
+++ b/engines/sci/graphics/frameout.h
@@ -30,8 +30,7 @@ namespace Sci {
typedef Common::Array<DrawList> ScreenItemListList;
typedef Common::Array<RectList> EraseListList;
-class GfxCoordAdjuster32;
-class GfxScreen;
+class GfxCursor32;
class GfxTransitions32;
struct PlaneShowStyle;
@@ -41,17 +40,16 @@ struct PlaneShowStyle;
*/
class GfxFrameout {
private:
- bool _isHiRes;
- GfxCoordAdjuster32 *_coordAdjuster;
+ GfxCursor32 *_cursor;
GfxPalette32 *_palette;
- ResourceManager *_resMan;
- GfxScreen *_screen;
SegManager *_segMan;
public:
- GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette, GfxTransitions32 *transitions);
+ GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor);
~GfxFrameout();
+ bool _isHiRes;
+
void clear();
void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in
void run();
@@ -112,7 +110,7 @@ public:
void kernelAddScreenItem(const reg_t object);
void kernelUpdateScreenItem(const reg_t object);
void kernelDeleteScreenItem(const reg_t object);
- void kernelSetNowSeen(const reg_t screenItemObject) const;
+ bool kernelSetNowSeen(const reg_t screenItemObject) const;
#pragma mark -
#pragma mark Planes
@@ -197,13 +195,6 @@ private:
bool _remapOccurred;
/**
- * Whether or not the data in the current buffer is what
- * is visible to the user. During rendering updates,
- * this flag is set to false.
- */
- bool _frameNowVisible;
-
- /**
* TODO: Document
* TODO: Depending upon if the engine ever modifies this
* rect, it may be stupid to store it separately instead
@@ -310,6 +301,13 @@ private:
public:
/**
+ * Whether or not the data in the current buffer is what
+ * is visible to the user. During rendering updates,
+ * this flag is set to false.
+ */
+ bool _frameNowVisible;
+
+ /**
* Whether palMorphFrameOut should be used instead of
* frameOut for rendering. Used by kMorphOn to
* explicitly enable palMorphFrameOut for one frame.
@@ -365,6 +363,11 @@ public:
*/
void showRect(const Common::Rect &rect);
+ /**
+ * Shakes the screen.
+ */
+ void shakeScreen(const int16 numShakes, const ShakeDirection direction);
+
#pragma mark -
#pragma mark Mouse cursor
private:
diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h
index 3fcc83c5e2..1da3749c90 100644
--- a/engines/sci/graphics/helpers.h
+++ b/engines/sci/graphics/helpers.h
@@ -40,8 +40,10 @@ namespace Sci {
#define MAX_CACHED_FONTS 20
#define MAX_CACHED_VIEWS 50
-#define SCI_SHAKE_DIRECTION_VERTICAL 1
-#define SCI_SHAKE_DIRECTION_HORIZONTAL 2
+enum ShakeDirection {
+ kShakeVertical = 1,
+ kShakeHorizontal = 2
+};
typedef int GuiResourceId; // is a resource-number and -1 means no parameter given
diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp
index 6004e9ce7a..91817d4060 100644
--- a/engines/sci/graphics/paint16.cpp
+++ b/engines/sci/graphics/paint16.cpp
@@ -41,7 +41,7 @@
namespace Sci {
-GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio)
+GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster16 *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio)
: _resMan(resMan), _segMan(segMan), _cache(cache), _ports(ports),
_coordAdjuster(coordAdjuster), _screen(screen), _palette(palette),
_transitions(transitions), _audio(audio), _EGAdrawingVisualize(false) {
diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h
index 317388b2df..6fc9cbbdfc 100644
--- a/engines/sci/graphics/paint16.h
+++ b/engines/sci/graphics/paint16.h
@@ -36,7 +36,7 @@ class GfxView;
*/
class GfxPaint16 {
public:
- GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio);
+ GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster16 *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio);
~GfxPaint16();
void init(GfxAnimate *animate, GfxText16 *text16);
@@ -91,7 +91,7 @@ private:
GfxAnimate *_animate;
GfxCache *_cache;
GfxPorts *_ports;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
GfxScreen *_screen;
GfxPalette *_palette;
GfxText16 *_text16;
diff --git a/engines/sci/graphics/palette32.cpp b/engines/sci/graphics/palette32.cpp
index 2a98c237b0..c7098bc3e4 100644
--- a/engines/sci/graphics/palette32.cpp
+++ b/engines/sci/graphics/palette32.cpp
@@ -282,10 +282,16 @@ void GfxPalette32::updateHardware(const bool updateScreen) {
bpal[i * 3 + 2] = _currentPalette.colors[i].b;
}
- // The last color must always be white
- bpal[255 * 3 ] = 255;
- bpal[255 * 3 + 1] = 255;
- bpal[255 * 3 + 2] = 255;
+ if (g_sci->getPlatform() != Common::kPlatformMacintosh) {
+ // The last color must always be white
+ bpal[255 * 3 ] = 255;
+ bpal[255 * 3 + 1] = 255;
+ bpal[255 * 3 + 2] = 255;
+ } else {
+ bpal[255 * 3 ] = 0;
+ bpal[255 * 3 + 1] = 0;
+ bpal[255 * 3 + 2] = 0;
+ }
g_system->getPaletteManager()->setPalette(bpal, 0, 256);
if (updateScreen) {
diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp
index 2eab391afd..0025b24476 100644
--- a/engines/sci/graphics/picture.cpp
+++ b/engines/sci/graphics/picture.cpp
@@ -35,7 +35,7 @@ namespace Sci {
//#define DEBUG_PICTURE_DRAW
-GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize)
+GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster16 *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize)
: _resMan(resMan), _coordAdjuster(coordAdjuster), _ports(ports), _screen(screen), _palette(palette), _resourceId(resourceId), _EGAdrawingVisualize(EGAdrawingVisualize) {
assert(resourceId != -1);
initData(resourceId);
diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h
index 942fa0f107..1be1ae3004 100644
--- a/engines/sci/graphics/picture.h
+++ b/engines/sci/graphics/picture.h
@@ -38,7 +38,7 @@ enum {
class GfxPorts;
class GfxScreen;
class GfxPalette;
-class GfxCoordAdjuster;
+class GfxCoordAdjuster16;
class ResourceManager;
class Resource;
@@ -48,7 +48,7 @@ class Resource;
*/
class GfxPicture {
public:
- GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize = false);
+ GfxPicture(ResourceManager *resMan, GfxCoordAdjuster16 *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize = false);
~GfxPicture();
GuiResourceId getResourceId();
@@ -84,7 +84,7 @@ private:
void vectorPatternTexturedCircle(Common::Rect box, byte size, byte color, byte prio, byte control, byte texture);
ResourceManager *_resMan;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
GfxPorts *_ports;
GfxScreen *_screen;
GfxPalette *_palette;
diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp
index c977a93817..601ab9f09f 100644
--- a/engines/sci/graphics/screen.cpp
+++ b/engines/sci/graphics/screen.cpp
@@ -53,12 +53,6 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) {
if (g_sci->getGameId() == GID_KQ6)
_upscaledHires = GFX_SCREEN_UPSCALED_640x440;
-#ifdef ENABLE_SCI32
- if (g_sci->getGameId() == GID_GK1)
- _upscaledHires = GFX_SCREEN_UPSCALED_640x480;
- if (g_sci->getGameId() == GID_PQ4)
- _upscaledHires = GFX_SCREEN_UPSCALED_640x480;
-#endif
}
// Japanese versions of games use hi-res font on upscaled version of the game.
@@ -90,28 +84,11 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) {
}
}
-#ifdef ENABLE_SCI32
- // GK1 Mac uses a 640x480 resolution too
- if (g_sci->getPlatform() == Common::kPlatformMacintosh) {
- if (g_sci->getGameId() == GID_GK1)
- _upscaledHires = GFX_SCREEN_UPSCALED_640x480;
- }
-#endif
-
if (_resMan->detectHires()) {
_scriptWidth = 640;
_scriptHeight = 480;
}
-#ifdef ENABLE_SCI32
- // Phantasmagoria 1 effectively outputs 630x450
- // Coordinate translation has to use this resolution as well
- if (g_sci->getGameId() == GID_PHANTASMAGORIA) {
- _width = 630;
- _height = 450;
- }
-#endif
-
// if not yet set, set those to script-width/height
if (!_width)
_width = _scriptWidth;
@@ -632,13 +609,13 @@ void GfxScreen::setVerticalShakePos(uint16 shakePos) {
void GfxScreen::kernelShakeScreen(uint16 shakeCount, uint16 directions) {
while (shakeCount--) {
- if (directions & SCI_SHAKE_DIRECTION_VERTICAL)
+ if (directions & kShakeVertical)
setVerticalShakePos(10);
// TODO: horizontal shakes
g_system->updateScreen();
g_sci->getEngineState()->wait(3);
- if (directions & SCI_SHAKE_DIRECTION_VERTICAL)
+ if (directions & kShakeVertical)
setVerticalShakePos(0);
g_system->updateScreen();
diff --git a/engines/sci/graphics/screen_item32.cpp b/engines/sci/graphics/screen_item32.cpp
index 7383dc222e..f4ed269265 100644
--- a/engines/sci/graphics/screen_item32.cpp
+++ b/engines/sci/graphics/screen_item32.cpp
@@ -178,7 +178,9 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo
const uint8 loopCount = view->data[2];
const uint8 loopSize = view->data[12];
- if (_celInfo.loopNo >= loopCount) {
+ // loopNo is set to be an unsigned integer in SSCI, so if it's a
+ // negative value, it'll be fixed accordingly
+ if ((uint16)_celInfo.loopNo >= loopCount) {
const int maxLoopNo = loopCount - 1;
_celInfo.loopNo = maxLoopNo;
writeSelectorValue(segMan, object, SELECTOR(loop), maxLoopNo);
@@ -189,8 +191,11 @@ void ScreenItem::setFromObject(SegManager *segMan, const reg_t object, const boo
if (seekEntry != -1) {
loopData = view->data + headerSize + (seekEntry * loopSize);
}
+
+ // celNo is set to be an unsigned integer in SSCI, so if it's a
+ // negative value, it'll be fixed accordingly
const uint8 celCount = loopData[2];
- if (_celInfo.celNo >= celCount) {
+ if ((uint16)_celInfo.celNo >= celCount) {
const int maxCelNo = celCount - 1;
_celInfo.celNo = maxCelNo;
writeSelectorValue(segMan, object, SELECTOR(cel), maxCelNo);
diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h
index 3d9d5ef3d7..4221c0ea52 100644
--- a/engines/sci/graphics/screen_item32.h
+++ b/engines/sci/graphics/screen_item32.h
@@ -31,6 +31,7 @@ namespace Sci {
enum ScaleSignals32 {
kScaleSignalNone = 0,
+ // TODO: rename to 'manual'
kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY)
kScaleSignalUseVanishingPoint = 2,
// TODO: Is this actually a thing? I have not seen it and
diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp
index b0f2c52791..cb6e614657 100644
--- a/engines/sci/graphics/text16.cpp
+++ b/engines/sci/graphics/text16.cpp
@@ -633,7 +633,7 @@ reg_t GfxText16::allocAndFillReferenceRectArray() {
if (rectCount) {
reg_t rectArray;
byte *rectArrayPtr = g_sci->getEngineState()->_segMan->allocDynmem(4 * 2 * (rectCount + 1), "text code reference rects", &rectArray);
- GfxCoordAdjuster *coordAdjuster = g_sci->_gfxCoordAdjuster;
+ GfxCoordAdjuster16 *coordAdjuster = g_sci->_gfxCoordAdjuster;
for (uint curRect = 0; curRect < rectCount; curRect++) {
coordAdjuster->kernelLocalToGlobal(_codeRefRects[curRect].left, _codeRefRects[curRect].top);
coordAdjuster->kernelLocalToGlobal(_codeRefRects[curRect].right, _codeRefRects[curRect].bottom);
diff --git a/engines/sci/graphics/text32.cpp b/engines/sci/graphics/text32.cpp
index f81d50946b..11572581ff 100644
--- a/engines/sci/graphics/text32.cpp
+++ b/engines/sci/graphics/text32.cpp
@@ -211,12 +211,12 @@ void GfxText32::drawChar(const char charIndex) {
SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
byte *pixels = bitmap.getPixels();
- _font->drawToBuffer(charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height);
- _drawPosition.x += _font->getCharWidth(charIndex);
+ _font->drawToBuffer((unsigned char)charIndex, _drawPosition.y, _drawPosition.x, _foreColor, _dimmed, pixels, _width, _height);
+ _drawPosition.x += _font->getCharWidth((unsigned char)charIndex);
}
uint16 GfxText32::getCharWidth(const char charIndex, const bool doScaling) const {
- uint16 width = _font->getCharWidth(charIndex);
+ uint16 width = _font->getCharWidth((unsigned char)charIndex);
if (doScaling) {
width = scaleUpWidth(width);
}
diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp
index bceb0fa84d..37f608da85 100644
--- a/engines/sci/graphics/transitions32.cpp
+++ b/engines/sci/graphics/transitions32.cpp
@@ -203,10 +203,6 @@ void GfxTransitions32::kernelSetShowStyle(const uint16 argc, const reg_t planeOb
color = 0;
}
- if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() != GID_KQ7 && type == 15) || type > 15) {
- error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj));
- }
-
Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj);
if (plane == nullptr) {
error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj));
diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp
index dc2641c92a..8b1d4ef32b 100644
--- a/engines/sci/graphics/video32.cpp
+++ b/engines/sci/graphics/video32.cpp
@@ -20,20 +20,484 @@
*
*/
-#include "audio/mixer.h"
-#include "common/config-manager.h"
-#include "sci/console.h"
-#include "sci/event.h"
-#include "sci/graphics/cursor.h"
-#include "sci/graphics/frameout.h"
-#include "sci/graphics/palette32.h"
-#include "sci/graphics/text32.h"
+#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType
+#include "common/config-manager.h" // for ConfMan
+#include "common/textconsole.h" // for warning, error
+#include "common/util.h" // for ARRAYSIZE
+#include "common/system.h" // for g_system
+#include "engine.h" // for Engine, g_engine
+#include "engines/util.h" // for initGraphics
+#include "sci/console.h" // for Console
+#include "sci/engine/state.h" // for EngineState
+#include "sci/engine/vm_types.h" // for reg_t
+#include "sci/event.h" // for SciEvent, EventManager, SCI_...
+#include "sci/graphics/celobj32.h" // for CelInfo32, ::kLowResX, ::kLo...
+#include "sci/graphics/cursor32.h" // for GfxCursor32
+#include "sci/graphics/frameout.h" // for GfxFrameout
+#include "sci/graphics/helpers.h" // for Color, Palette
+#include "sci/graphics/palette32.h" // for GfxPalette32
+#include "sci/graphics/plane32.h" // for Plane, PlanePictureCodes::kP...
+#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem, Scale...
+#include "sci/sci.h" // for SciEngine, g_sci, getSciVersion
#include "sci/graphics/video32.h"
-#include "sci/sci.h"
-#include "video/coktel_decoder.h"
+#include "sci/video/seq_decoder.h" // for SEQDecoder
+#include "video/avi_decoder.h" // for AVIDecoder
+#include "video/coktel_decoder.h" // for AdvancedVMDDecoder
+namespace Graphics { struct Surface; }
namespace Sci {
+#pragma mark SEQPlayer
+
+SEQPlayer::SEQPlayer(SegManager *segMan) :
+ _segMan(segMan),
+ _decoder(nullptr),
+ _plane(nullptr),
+ _screenItem(nullptr) {}
+
+void SEQPlayer::play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y) {
+ delete _decoder;
+ _decoder = new SEQDecoder(numTicks);
+ _decoder->loadFile(fileName);
+
+ // NOTE: In the original engine, video was output directly to the hardware,
+ // bypassing the game's rendering engine. Instead of doing this, we use a
+ // mechanism that is very similar to that used by the VMD player, which
+ // allows the SEQ to be drawn into a bitmap ScreenItem and displayed using
+ // the normal graphics system.
+ _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, kLowResX, kLowResY, 0, false, false);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _bitmap;
+
+ _plane = new Plane(Common::Rect(kLowResX, kLowResY), kPlanePicColored);
+ g_sci->_gfxFrameout->addPlane(*_plane);
+
+ // Normally we would use the x, y coordinates passed into the play function
+ // to position the screen item, but because the video frame bitmap is
+ // drawn in low-resolution coordinates, it gets automatically scaled up by
+ // the engine (pixel doubling with aspect ratio correction). As a result,
+ // the animation does not need the extra offsets from the game in order to
+ // be correctly positioned in the middle of the window, so we ignore them.
+ _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(0, 0), ScaleInfo());
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ _decoder->start();
+
+ while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) {
+ renderFrame();
+ g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
+ g_sci->getEngineState()->_throttleTrigger = true;
+ }
+
+ _segMan->freeBitmap(_screenItem->_celInfo.bitmap);
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ g_sci->_gfxFrameout->frameOut(true);
+ _screenItem = nullptr;
+ _plane = nullptr;
+}
+
+void SEQPlayer::renderFrame() const {
+ const Graphics::Surface *surface = _decoder->decodeNextFrame();
+
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
+ bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h));
+
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette) {
+ Palette palette;
+ const byte *rawPalette = _decoder->getPalette();
+ for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) {
+ palette.colors[i].r = *rawPalette++;
+ palette.colors[i].g = *rawPalette++;
+ palette.colors[i].b = *rawPalette++;
+ palette.colors[i].used = true;
+ }
+
+ g_sci->_gfxPalette32->submit(palette);
+ }
+
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->frameOut(true);
+}
+
+#pragma mark -
+#pragma mark AVIPlayer
+
+AVIPlayer::AVIPlayer(SegManager *segMan, EventManager *eventMan) :
+ _segMan(segMan),
+ _eventMan(eventMan),
+ _decoder(new Video::AVIDecoder(Audio::Mixer::kSFXSoundType)),
+ _scaleBuffer(nullptr),
+ _plane(nullptr),
+ _screenItem(nullptr),
+ _status(kAVINotOpen) {}
+
+AVIPlayer::~AVIPlayer() {
+ close();
+ delete _decoder;
+}
+
+AVIPlayer::IOStatus AVIPlayer::open(const Common::String &fileName) {
+ if (_status != kAVINotOpen) {
+ close();
+ }
+
+ if (!_decoder->loadFile(fileName)) {
+ return kIOFileNotFound;
+ }
+
+ _status = kAVIOpen;
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::init1x(const int16 x, const int16 y, int16 width, int16 height) {
+ if (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ _pixelDouble = false;
+
+ if (!width || !height) {
+ width = _decoder->getWidth();
+ height = _decoder->getHeight();
+ } else if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) {
+ // KQ7 1.51 provides an explicit width and height when it wants scaling,
+ // though the width and height it provides are not scaled
+ _pixelDouble = true;
+ width *= 2;
+ height *= 2;
+ }
+
+ // QFG4CD gives non-multiple-of-2 values for width and height,
+ // which would normally be OK except the source video is a pixel bigger
+ // in each dimension
+ width = (width + 1) & ~1;
+ height = (height + 1) & ~1;
+
+ _drawRect.left = x;
+ _drawRect.top = y;
+ _drawRect.right = x + width;
+ _drawRect.bottom = y + height;
+
+ // SCI2.1mid uses init2x to draw a pixel-doubled AVI, but SCI2 has only the
+ // one play routine which automatically pixel-doubles in hi-res mode
+ if (getSciVersion() == SCI_VERSION_2) {
+ // This is somewhat of a hack; credits.avi from GK1 is not
+ // rendered correctly in SSCI because it is a 640x480 video, but the
+ // game script gives the wrong dimensions. Since this is the only
+ // high-resolution AVI ever used, just set the draw rectangle to draw
+ // the entire screen
+ if (_decoder->getWidth() > 320) {
+ _drawRect.left = 0;
+ _drawRect.top = 0;
+ _drawRect.right = 320;
+ _drawRect.bottom = 200;
+ }
+
+ // In hi-res mode, video will be pixel doubled, so the origin (which
+ // corresponds to the correct position without pixel doubling) needs to
+ // be corrected
+ if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() <= 320) {
+ _drawRect.left /= 2;
+ _drawRect.top /= 2;
+ }
+ }
+
+ init();
+
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::init2x(const int16 x, const int16 y) {
+ if (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ _drawRect.left = x;
+ _drawRect.top = y;
+ _drawRect.right = x + _decoder->getWidth() * 2;
+ _drawRect.bottom = y + _decoder->getHeight() * 2;
+
+ _pixelDouble = true;
+ init();
+
+ return kIOSuccess;
+}
+
+void AVIPlayer::init() {
+ int16 xRes;
+ int16 yRes;
+
+ bool useScreenDimensions = false;
+ if (g_sci->_gfxFrameout->_isHiRes && _decoder->getWidth() > 320) {
+ useScreenDimensions = true;
+ }
+
+ // KQ7 1.51 gives video position in screen coordinates, not game
+ // coordinates, because in SSCI they are passed to Video for Windows, which
+ // renders as an overlay on the game video. Because we put the video into a
+ // ScreenItem instead of rendering directly to the hardware surface, the
+ // coordinates need to be converted to game script coordinates
+ if (g_sci->getGameId() == GID_KQ7 && getSciVersion() == SCI_VERSION_2_1_EARLY) {
+ useScreenDimensions = !_pixelDouble;
+ // This y-translation is arbitrary, based on what roughly centers the
+ // videos in the game window
+ _drawRect.translate(-_drawRect.left / 2, -_drawRect.top * 2 / 3);
+ }
+
+ if (useScreenDimensions) {
+ xRes = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ yRes = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ } else {
+ xRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ yRes = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ }
+
+ _plane = new Plane(_drawRect);
+ g_sci->_gfxFrameout->addPlane(*_plane);
+
+ if (_decoder->getPixelFormat().bytesPerPixel == 1) {
+ _segMan->allocateBitmap(&_bitmap, _decoder->getWidth(), _decoder->getHeight(), kDefaultSkipColor, 0, 0, xRes, yRes, 0, false, false);
+
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _bitmap;
+
+ _screenItem = new ScreenItem(_plane->_object, celInfo, Common::Point(_drawRect.left, _drawRect.top), ScaleInfo());
+ g_sci->_gfxFrameout->addScreenItem(*_screenItem);
+ g_sci->_gfxFrameout->frameOut(true);
+ } else {
+ // Attempting to draw a palettized cursor into a 24bpp surface will
+ // cause memory corruption, so hide the cursor in this mode (SCI did not
+ // have a 24bpp mode but just directed VFW to display videos instead)
+ g_sci->_gfxCursor32->hide();
+
+ const Buffer &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = _decoder->getPixelFormat();
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, g_sci->_gfxFrameout->_isHiRes, &format);
+
+ if (_pixelDouble) {
+ const int16 width = _drawRect.width();
+ const int16 height = _drawRect.height();
+ _scaleBuffer = calloc(1, width * height * format.bytesPerPixel);
+ }
+ }
+}
+
+AVIPlayer::IOStatus AVIPlayer::play(const int16 from, const int16 to, const int16, const bool async) {
+ if (_status == kAVINotOpen) {
+ return kIOFileNotFound;
+ }
+
+ if (from >= 0 && to > 0 && from <= to) {
+ _decoder->seekToFrame(from);
+ _decoder->setEndFrame(to);
+ }
+
+ if (!async) {
+ renderVideo();
+ } else if (getSciVersion() == SCI_VERSION_2_1_EARLY) {
+ playUntilEvent((EventFlags)(kEventFlagEnd | kEventFlagEscapeKey));
+ } else {
+ _status = kAVIPlaying;
+ }
+
+ return kIOSuccess;
+}
+
+void AVIPlayer::renderVideo() const {
+ _decoder->start();
+ while (!g_engine->shouldQuit() && !_decoder->endOfVideo()) {
+ g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
+ g_sci->getEngineState()->_throttleTrigger = true;
+ if (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+ }
+}
+
+AVIPlayer::IOStatus AVIPlayer::close() {
+ if (_status == kAVINotOpen) {
+ return kIOSuccess;
+ }
+
+ free(_scaleBuffer);
+ _scaleBuffer = nullptr;
+
+ if (_decoder->getPixelFormat().bytesPerPixel != 1) {
+ const bool isHiRes = g_sci->_gfxFrameout->_isHiRes;
+ const Buffer &currentBuffer = g_sci->_gfxFrameout->getCurrentBuffer();
+ const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8();
+ initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format);
+ g_sci->_gfxCursor32->unhide();
+ }
+
+ _decoder->close();
+ _status = kAVINotOpen;
+ g_sci->_gfxFrameout->deletePlane(*_plane);
+ _plane = nullptr;
+ _screenItem = nullptr;
+ return kIOSuccess;
+}
+
+AVIPlayer::IOStatus AVIPlayer::cue(const uint16 frameNo) {
+ if (!_decoder->seekToFrame(frameNo)) {
+ return kIOSeekFailed;
+ }
+
+ _status = kAVIPaused;
+ return kIOSuccess;
+}
+
+uint16 AVIPlayer::getDuration() const {
+ if (_status == kAVINotOpen) {
+ return 0;
+ }
+
+ return _decoder->getFrameCount();
+}
+
+void AVIPlayer::renderFrame() const {
+ const Graphics::Surface *surface = _decoder->decodeNextFrame();
+
+ if (surface->format.bytesPerPixel == 1) {
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_bitmap);
+ if (surface->w > bitmap.getWidth() || surface->h > bitmap.getHeight()) {
+ warning("Attempted to draw a video frame larger than the destination bitmap");
+ return;
+ }
+
+ // KQ7 1.51 encodes videos with palette entry 0 as white, which makes
+ // the area around the video turn white too, since it is coded to use
+ // palette entry 0. This happens to work in the original game because
+ // the video is rendered by VfW, not in the engine itself. To fix this,
+ // we just modify the incoming pixel data from the video so if a pixel
+ // is using entry 0, we change it to use entry 255, which is guaranteed
+ // to always be white
+ if (getSciVersion() == SCI_VERSION_2_1_EARLY && g_sci->getGameId() == GID_KQ7) {
+ uint8 *target = bitmap.getPixels();
+ const uint8 *source = (const uint8 *)surface->getPixels();
+ const uint8 *end = (const uint8 *)surface->getPixels() + surface->w * surface->h;
+
+ while (source != end) {
+ uint8 value = *source++;
+ *target++ = value == 0 ? 255 : value;
+ }
+ } else {
+ bitmap.getBuffer().copyRectToSurface(*surface, 0, 0, Common::Rect(surface->w, surface->h));
+ }
+
+ const bool dirtyPalette = _decoder->hasDirtyPalette();
+ if (dirtyPalette) {
+ Palette palette;
+ const byte *rawPalette = _decoder->getPalette();
+ for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) {
+ palette.colors[i].r = *rawPalette++;
+ palette.colors[i].g = *rawPalette++;
+ palette.colors[i].b = *rawPalette++;
+ palette.colors[i].used = true;
+ }
+
+ // Prevent KQ7 1.51 from setting entry 0 to white
+ palette.colors[0].used = false;
+
+ g_sci->_gfxPalette32->submit(palette);
+ }
+
+ g_sci->_gfxFrameout->updateScreenItem(*_screenItem);
+ g_sci->getSciDebugger()->onFrame();
+ g_sci->_gfxFrameout->frameOut(true);
+ } else {
+ assert(surface->format.bytesPerPixel == 4);
+
+ Common::Rect drawRect(_drawRect);
+
+ if (_pixelDouble) {
+ const uint32 *source = (const uint32 *)surface->getPixels();
+ uint32 *target = (uint32 *)_scaleBuffer;
+ // target pitch here is in uint32s, not bytes
+ const uint16 pitch = surface->pitch / 2;
+ for (int y = 0; y < surface->h; ++y) {
+ for (int x = 0; x < surface->w; ++x) {
+ const uint32 value = *source++;
+
+ target[0] = value;
+ target[1] = value;
+ target[pitch] = value;
+ target[pitch + 1] = value;
+ target += 2;
+ }
+ target += pitch;
+ }
+
+ g_system->copyRectToScreen(_scaleBuffer, surface->pitch * 2, _drawRect.left, _drawRect.top, _drawRect.width(), _drawRect.height());
+ } else {
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+
+ mulinc(drawRect, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight));
+
+ g_system->copyRectToScreen(surface->getPixels(), surface->pitch, drawRect.left, drawRect.top, surface->w, surface->h);
+ }
+ }
+}
+
+AVIPlayer::EventFlags AVIPlayer::playUntilEvent(EventFlags flags) {
+ _decoder->start();
+
+ EventFlags stopFlag = kEventFlagNone;
+ while (!g_engine->shouldQuit()) {
+ if (_decoder->endOfVideo()) {
+ stopFlag = kEventFlagEnd;
+ break;
+ }
+
+ g_sci->getEngineState()->speedThrottler(_decoder->getTimeToNextFrame());
+ g_sci->getEngineState()->_throttleTrigger = true;
+ if (_decoder->needsUpdate()) {
+ renderFrame();
+ }
+
+ SciEvent event = _eventMan->getSciEvent(SCI_EVENT_MOUSE_PRESS | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagMouseDown) && event.type == SCI_EVENT_MOUSE_PRESS) {
+ stopFlag = kEventFlagMouseDown;
+ break;
+ }
+
+ event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD | SCI_EVENT_PEEK);
+ if ((flags & kEventFlagEscapeKey) && event.type == SCI_EVENT_KEYBOARD) {
+ bool stop = false;
+ while ((event = _eventMan->getSciEvent(SCI_EVENT_KEYBOARD)),
+ event.type != SCI_EVENT_NONE) {
+ if (event.character == SCI_KEY_ESC) {
+ stop = true;
+ break;
+ }
+ }
+
+ if (stop) {
+ stopFlag = kEventFlagEscapeKey;
+ break;
+ }
+ }
+
+ // TODO: Hot rectangles
+ if ((flags & kEventFlagHotRectangle) /* && event.type == SCI_EVENT_HOT_RECTANGLE */) {
+ warning("Hot rectangles not implemented in VMD player");
+ stopFlag = kEventFlagHotRectangle;
+ break;
+ }
+ }
+
+ return stopFlag;
+}
+
+#pragma mark -
#pragma mark VMDPlayer
VMDPlayer::VMDPlayer(SegManager *segMan, EventManager *eventMan) :
@@ -117,7 +581,7 @@ VMDPlayer::IOStatus VMDPlayer::close() {
if (!_planeIsOwned && _screenItem != nullptr) {
g_sci->_gfxFrameout->deleteScreenItem(*_screenItem);
- g_sci->getEngineState()->_segMan->freeBitmap(_screenItem->_celInfo.bitmap);
+ _segMan->freeBitmap(_screenItem->_celInfo.bitmap);
_screenItem = nullptr;
} else if (_plane != nullptr) {
g_sci->_gfxFrameout->deletePlane(*_plane);
@@ -140,7 +604,7 @@ VMDPlayer::IOStatus VMDPlayer::close() {
}
if (!_showCursor) {
- g_sci->_gfxCursor->kernelShow();
+ g_sci->_gfxCursor32->unhide();
}
_lastYieldedFrameNo = 0;
@@ -149,6 +613,22 @@ VMDPlayer::IOStatus VMDPlayer::close() {
return kIOSuccess;
}
+VMDPlayer::VMDStatus VMDPlayer::getStatus() const {
+ if (!_isOpen) {
+ return kVMDNotOpen;
+ }
+ if (_decoder->isPaused()) {
+ return kVMDPaused;
+ }
+ if (_decoder->isPlaying()) {
+ return kVMDPlaying;
+ }
+ if (_decoder->endOfVideo()) {
+ return kVMDFinished;
+ }
+ return kVMDOpen;
+}
+
VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) {
assert(lastFrameNo >= -1);
@@ -201,7 +681,7 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) {
_isInitialized = true;
if (!_showCursor) {
- g_sci->_gfxCursor->kernelHide();
+ g_sci->_gfxCursor32->hide();
}
Common::Rect vmdRect(_x,
diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h
index 7033f7c647..75b8fb2d21 100644
--- a/engines/sci/graphics/video32.h
+++ b/engines/sci/graphics/video32.h
@@ -23,16 +23,216 @@
#ifndef SCI_GRAPHICS_VIDEO32_H
#define SCI_GRAPHICS_VIDEO32_H
-namespace Video { class AdvancedVMDDecoder; }
+#include "common/rect.h" // for Rect
+#include "common/scummsys.h" // for int16, uint8, uint16, int32
+#include "common/str.h" // for String
+#include "sci/engine/vm_types.h" // for reg_t
+#include "sci/video/robot_decoder.h" // for RobotDecoder
+
+namespace Video {
+class AdvancedVMDDecoder;
+class AVIDecoder;
+}
namespace Sci {
+class EventManager;
class Plane;
class ScreenItem;
class SegManager;
+class SEQDecoder;
+struct Palette;
+#pragma mark SEQPlayer
+
+/**
+ * SEQPlayer is used to play SEQ animations.
+ * Used by DOS versions of GK1 and QFG4CD.
+ */
+class SEQPlayer {
+public:
+ SEQPlayer(SegManager *segMan);
+
+ /**
+ * Plays a SEQ animation with the given
+ * file name, with each frame being displayed
+ * for `numTicks` ticks.
+ */
+ void play(const Common::String &fileName, const int16 numTicks, const int16 x, const int16 y);
+
+private:
+ SegManager *_segMan;
+ SEQDecoder *_decoder;
+
+ /**
+ * The plane where the SEQ will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the SEQ surface.
+ */
+ ScreenItem *_screenItem;
+
+ /**
+ * The bitmap used to render video output.
+ */
+ reg_t _bitmap;
+
+ /**
+ * Renders a single frame of video.
+ */
+ void renderFrame() const;
+};
+
+#pragma mark -
+#pragma mark AVIPlayer
+
+/**
+ * AVIPlayer is used to play AVI videos. Used by
+ * Windows versions of GK1CD, KQ7, and QFG4CD.
+ */
+class AVIPlayer {
+public:
+ enum IOStatus {
+ kIOSuccess = 0,
+ kIOFileNotFound = 2,
+ kIOSeekFailed = 12
+ };
+
+ enum AVIStatus {
+ kAVINotOpen = 0,
+ kAVIOpen = 1,
+ kAVIPlaying = 2,
+ kAVIPaused = 3
+ };
+
+ enum EventFlags {
+ kEventFlagNone = 0,
+ kEventFlagEnd = 1,
+ kEventFlagEscapeKey = 2,
+ kEventFlagMouseDown = 4,
+ kEventFlagHotRectangle = 8
+ };
+
+ AVIPlayer(SegManager *segMan, EventManager *eventMan);
+ ~AVIPlayer();
+
+ /**
+ * Opens a stream to an AVI resource.
+ */
+ IOStatus open(const Common::String &fileName);
+
+ /**
+ * Initializes the AVI rendering parameters for the
+ * current AVI. This must be called after `open`.
+ */
+ IOStatus init1x(const int16 x, const int16 y, const int16 width, const int16 height);
+
+ /**
+ * Initializes the AVI rendering parameters for the
+ * current AVI, in pixel-doubling mode. This must
+ * be called after `open`.
+ */
+ IOStatus init2x(const int16 x, const int16 y);
+
+ /**
+ * Begins playback of the current AVI.
+ */
+ IOStatus play(const int16 from, const int16 to, const int16 showStyle, const bool cue);
+
+ /**
+ * Stops playback and closes the currently open AVI stream.
+ */
+ IOStatus close();
+
+ /**
+ * Seeks the currently open AVI stream to the given frame.
+ */
+ IOStatus cue(const uint16 frameNo);
+
+ /**
+ * Returns the duration of the current video.
+ */
+ uint16 getDuration() const;
+
+ /**
+ * Plays the AVI until an event occurs (e.g. user
+ * presses escape, clicks, etc.).
+ */
+ EventFlags playUntilEvent(const EventFlags flags);
+
+private:
+ typedef Common::HashMap<uint16, AVIStatus> StatusMap;
+
+ SegManager *_segMan;
+ EventManager *_eventMan;
+ Video::AVIDecoder *_decoder;
+
+ /**
+ * Playback status of the player.
+ */
+ AVIStatus _status;
+
+ /**
+ * The plane where the AVI will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * The screen item representing the AVI surface,
+ * in 8bpp mode. In 24bpp mode, video is drawn
+ * directly to the screen.
+ */
+ ScreenItem *_screenItem;
+
+ /**
+ * The bitmap used to render video output in
+ * 8bpp mode.
+ */
+ reg_t _bitmap;
+
+ /**
+ * The rectangle where the video will be drawn,
+ * in game script coordinates.
+ */
+ Common::Rect _drawRect;
+
+ /**
+ * The scale buffer for pixel-doubled videos
+ * drawn in 24bpp mode.
+ */
+ void *_scaleBuffer;
+
+ /**
+ * In SCI2.1, whether or not the video should
+ * be pixel doubled for playback.
+ */
+ bool _pixelDouble;
+
+ /**
+ * Performs common initialisation for both
+ * scaled and unscaled videos.
+ */
+ void init();
+
+ /**
+ * Renders video without event input until the
+ * video is complete.
+ */
+ void renderVideo() const;
+
+ /**
+ * Renders a single frame of video.
+ */
+ void renderFrame() const;
+};
+
+#pragma mark -
#pragma mark VMDPlayer
/**
* VMDPlayer is used to play VMD videos.
+ * Used by Phant1, GK2, PQ:SWAT, Shivers, SQ6,
+ * Torin, and Lighthouse.
*/
class VMDPlayer {
public:
@@ -68,6 +268,15 @@ public:
kEventFlagReverse = 0x80
};
+ enum VMDStatus {
+ kVMDNotOpen = 0,
+ kVMDOpen = 1,
+ kVMDPlaying = 2,
+ kVMDPaused = 3,
+ kVMDStopped = 4,
+ kVMDFinished = 5
+ };
+
VMDPlayer(SegManager *segMan, EventManager *eventMan);
~VMDPlayer();
@@ -95,6 +304,11 @@ public:
*/
IOStatus close();
+ /**
+ * Gets the playback status of the VMD player.
+ */
+ VMDStatus getStatus() const;
+
// NOTE: Was WaitForEvent in SSCI
EventFlags kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval);
@@ -297,15 +511,28 @@ private:
bool _showCursor;
};
+/**
+ * Video32 provides facilities for playing back
+ * video in SCI engine.
+ */
class Video32 {
public:
Video32(SegManager *segMan, EventManager *eventMan) :
- _VMDPlayer(segMan, eventMan) {}
+ _SEQPlayer(segMan),
+ _AVIPlayer(segMan, eventMan),
+ _VMDPlayer(segMan, eventMan),
+ _robotPlayer(segMan) {}
+ SEQPlayer &getSEQPlayer() { return _SEQPlayer; }
+ AVIPlayer &getAVIPlayer() { return _AVIPlayer; }
VMDPlayer &getVMDPlayer() { return _VMDPlayer; }
+ RobotDecoder &getRobotPlayer() { return _robotPlayer; }
private:
+ SEQPlayer _SEQPlayer;
+ AVIPlayer _AVIPlayer;
VMDPlayer _VMDPlayer;
+ RobotDecoder _robotPlayer;
};
} // End of namespace Sci
diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp
index 1939e66179..0c09fcbb30 100644
--- a/engines/sci/graphics/view.cpp
+++ b/engines/sci/graphics/view.cpp
@@ -351,18 +351,6 @@ void GfxView::initData(GuiResourceId resourceId) {
celData += celSize;
}
}
-#ifdef ENABLE_SCI32
- // adjust width/height returned to scripts
- if (_sci2ScaleRes != SCI_VIEW_NATIVERES_NONE) {
- for (loopNo = 0; loopNo < _loopCount; loopNo++)
- for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++)
- _screen->adjustBackUpscaledCoordinates(_loop[loopNo].cel[celNo].scriptWidth, _loop[loopNo].cel[celNo].scriptHeight, _sci2ScaleRes);
- } else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) {
- for (loopNo = 0; loopNo < _loopCount; loopNo++)
- for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++)
- _coordAdjuster->fromDisplayToScript(_loop[loopNo].cel[celNo].scriptHeight, _loop[loopNo].cel[celNo].scriptWidth);
- }
-#endif
break;
default:
diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h
index 96b48c0477..5e422468b5 100644
--- a/engines/sci/graphics/view.h
+++ b/engines/sci/graphics/view.h
@@ -92,7 +92,7 @@ private:
void unditherBitmap(byte *bitmap, int16 width, int16 height, byte clearKey);
ResourceManager *_resMan;
- GfxCoordAdjuster *_coordAdjuster;
+ GfxCoordAdjuster16 *_coordAdjuster;
GfxScreen *_screen;
GfxPalette *_palette;
diff --git a/engines/sci/module.mk b/engines/sci/module.mk
index 18d97ea57e..eb2c6a148b 100644
--- a/engines/sci/module.mk
+++ b/engines/sci/module.mk
@@ -93,6 +93,7 @@ MODULE_OBJS += \
graphics/text32.o \
graphics/transitions32.o \
graphics/video32.o \
+ graphics/cursor32.o \
sound/audio32.o \
sound/decoders/sol.o \
video/robot_decoder.o
diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp
index 48278e35a7..2e69932e49 100644
--- a/engines/sci/resource.cpp
+++ b/engines/sci/resource.cpp
@@ -573,6 +573,9 @@ Resource *ResourceManager::testResource(ResourceId id) {
}
int ResourceManager::addAppropriateSources() {
+#ifdef ENABLE_SCI32
+ _multiDiscAudio = false;
+#endif
if (Common::File::exists("resource.map")) {
// SCI0-SCI2 file naming scheme
ResourceSource *map = addExternalMap("resource.map");
@@ -615,6 +618,10 @@ int ResourceManager::addAppropriateSources() {
if (mapFiles.empty() || files.empty() || mapFiles.size() != files.size())
return 0;
+ if (Common::File::exists("resaud.001")) {
+ _multiDiscAudio = true;
+ }
+
for (Common::ArchiveMemberList::const_iterator mapIterator = mapFiles.begin(); mapIterator != mapFiles.end(); ++mapIterator) {
Common::String mapName = (*mapIterator)->getName();
int mapNumber = atoi(strrchr(mapName.c_str(), '.') + 1);
@@ -659,6 +666,7 @@ int ResourceManager::addAppropriateSourcesForDetection(const Common::FSList &fsl
#ifdef ENABLE_SCI32
ResourceSource *sci21PatchMap = 0;
const Common::FSNode *sci21PatchRes = 0;
+ _multiDiscAudio = false;
#endif
// First, find resource.map
@@ -859,6 +867,13 @@ void ResourceManager::addResourcesFromChunk(uint16 id) {
scanNewSources();
}
+void ResourceManager::findDisc(const int16 discNo) {
+ // Since all resources are expected to be copied from the original discs
+ // into a single game directory, this call just records the number of the CD
+ // that the game has requested
+ _currentDiscNo = discNo;
+}
+
#endif
void ResourceManager::freeResourceSources() {
@@ -878,7 +893,9 @@ void ResourceManager::init() {
_LRU.clear();
_resMap.clear();
_audioMapSCI1 = NULL;
-
+#ifdef ENABLE_SCI32
+ _currentDiscNo = 1;
+#endif
// FIXME: put this in an Init() function, so that we can error out if detection fails completely
_mapVersion = detectMapVersion();
@@ -1477,6 +1494,12 @@ void ResourceManager::readResourcePatchesBase36() {
for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) {
name = (*x)->getName();
+ // The S/T prefixes often conflict with non-patch files and generate
+ // spurious warnings about invalid patches
+ if (name.hasSuffix(".DLL") || name.hasSuffix(".EXE") || name.hasSuffix(".TXT")) {
+ continue;
+ }
+
ResourceId resource36 = convertPatchNameBase36((ResourceType)i, name);
/*
@@ -1738,11 +1761,42 @@ int ResourceManager::readResourceMapSCI1(ResourceSource *map) {
// if we use the first entries in the resource file, half of the
// game will be English and umlauts will also be missing :P
if (resource->_source->getSourceType() == kSourceVolume) {
+ // Maps are read during the scanning process (below), so
+ // need to be treated as unallocated in order for the new
+ // data from this volume to be picked up and used
+ if (resId.getType() == kResourceTypeMap) {
+ resource->_status = kResStatusNoMalloc;
+ }
resource->_source = source;
resource->_fileOffset = fileOffset;
resource->size = 0;
}
}
+
+#ifdef ENABLE_SCI32
+ // Different CDs may have different audio maps on each disc. The
+ // ResourceManager does not know how to deal with this; it expects
+ // each resource ID to be unique across an entire game. To work
+ // around this problem, all audio maps from this disc must be
+ // processed immediately, since they will be replaced by the audio
+ // map from the next disc on the next call to readResourceMapSCI1
+ if (_multiDiscAudio && resId.getType() == kResourceTypeMap) {
+ IntMapResourceSource *audioMap = static_cast<IntMapResourceSource *>(addSource(new IntMapResourceSource("MAP", mapVolumeNr, resId.getNumber())));
+ Common::String volumeName;
+ if (resId.getNumber() == 65535) {
+ volumeName = Common::String::format("RESSFX.%03d", mapVolumeNr);
+ } else {
+ volumeName = Common::String::format("RESAUD.%03d", mapVolumeNr);
+ }
+
+ ResourceSource *audioVolume = addSource(new AudioVolumeResourceSource(this, volumeName, audioMap, mapVolumeNr));
+ if (!audioMap->_scanned) {
+ audioVolume->_scanned = true;
+ audioMap->_scanned = true;
+ audioMap->scanSource(this);
+ }
+ }
+#endif
}
}
diff --git a/engines/sci/resource.h b/engines/sci/resource.h
index f70bf48bd4..70db5909b7 100644
--- a/engines/sci/resource.h
+++ b/engines/sci/resource.h
@@ -296,6 +296,7 @@ protected:
typedef Common::HashMap<ResourceId, Resource *, ResourceIdHash> ResourceMap;
+class IntMapResourceSource;
class ResourceManager {
// FIXME: These 'friend' declarations are meant to be a temporary hack to
// ease transition to the ResourceSource class system.
@@ -397,6 +398,30 @@ public:
* resource manager.
*/
void addResourcesFromChunk(uint16 id);
+
+ /**
+ * Updates the currently active disc number.
+ */
+ void findDisc(const int16 discNo);
+
+ /**
+ * Gets the currently active disc number.
+ */
+ int16 getCurrentDiscNo() const { return _currentDiscNo; }
+
+private:
+ /**
+ * The currently active disc number.
+ */
+ int16 _currentDiscNo;
+
+ /**
+ * If true, the game has multiple audio volumes that contain different
+ * audio files for each disc.
+ */
+ bool _multiDiscAudio;
+
+public:
#endif
bool detectHires();
@@ -520,7 +545,7 @@ protected:
* @param map The map
* @return 0 on success, an SCI_ERROR_* code otherwise
*/
- int readAudioMapSCI11(ResourceSource *map);
+ int readAudioMapSCI11(IntMapResourceSource *map);
/**
* Reads SCI1 audio map files.
diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp
index 5ab443a16d..cbc4a02739 100644
--- a/engines/sci/resource_audio.cpp
+++ b/engines/sci/resource_audio.cpp
@@ -277,7 +277,7 @@ void ResourceManager::removeAudioResource(ResourceId resId) {
// w syncSize (iff seq has bit 7 set)
// w syncAscSize (iff seq has bit 6 set)
-int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
+int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) {
#ifndef ENABLE_SCI32
// SCI32 support is not built in. Check if this is a SCI32 game
// and if it is abort here.
@@ -286,17 +286,19 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
#endif
uint32 offset = 0;
- Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_volumeNumber), false);
+ Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_mapNumber), false);
if (!mapRes) {
- warning("Failed to open %i.MAP", map->_volumeNumber);
+ warning("Failed to open %i.MAP", map->_mapNumber);
return SCI_ERROR_RESMAP_NOT_FOUND;
}
- ResourceSource *src = findVolume(map, 0);
+ ResourceSource *src = findVolume(map, map->_volumeNumber);
- if (!src)
+ if (!src) {
+ warning("Failed to find volume for %i.MAP", map->_mapNumber);
return SCI_ERROR_NO_RESOURCE_FILES_FOUND;
+ }
byte *ptr = mapRes->data;
@@ -309,7 +311,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
break;
}
- if (map->_volumeNumber == 65535) {
+ if (map->_mapNumber == 65535) {
while (ptr < mapRes->data + mapRes->size) {
uint16 n = READ_LE_UINT16(ptr);
ptr += 2;
@@ -327,7 +329,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
addResource(ResourceId(kResourceTypeAudio, n), src, offset);
}
- } else if (map->_volumeNumber == 0 && entrySize == 10 && ptr[3] == 0) {
+ } else if (map->_mapNumber == 0 && entrySize == 10 && ptr[3] == 0) {
// QFG3 demo format
// ptr[3] would be 'seq' in the normal format and cannot possibly be 0
while (ptr < mapRes->data + mapRes->size) {
@@ -344,7 +346,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
addResource(ResourceId(kResourceTypeAudio, n), src, offset, size);
}
- } else if (map->_volumeNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) {
+ } else if (map->_mapNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) {
// LB2 Floppy/Mother Goose SCI1.1 format
Common::SeekableReadStream *stream = getVolumeFile(src);
@@ -400,7 +402,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
// FIXME: The sync36 resource seems to be two bytes too big in KQ6CD
// (bytes taken from the RAVE resource right after it)
if (syncSize > 0)
- addResource(ResourceId(kResourceTypeSync36, map->_volumeNumber, n & 0xffffff3f), src, offset, syncSize);
+ addResource(ResourceId(kResourceTypeSync36, map->_mapNumber, n & 0xffffff3f), src, offset, syncSize);
}
if (n & 0x40) {
@@ -410,12 +412,12 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) {
ptr += 2;
if (kq6HiresSyncSize > 0) {
- addResource(ResourceId(kResourceTypeRave, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize, kq6HiresSyncSize);
+ addResource(ResourceId(kResourceTypeRave, map->_mapNumber, n & 0xffffff3f), src, offset + syncSize, kq6HiresSyncSize);
syncSize += kq6HiresSyncSize;
}
}
- addResource(ResourceId(kResourceTypeAudio36, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize);
+ addResource(ResourceId(kResourceTypeAudio36, map->_mapNumber, n & 0xffffff3f), src, offset + syncSize);
}
}
@@ -937,13 +939,21 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource *
}
bool ResourceManager::addAudioSources() {
+#ifdef ENABLE_SCI32
+ // Multi-disc audio is added during addAppropriateSources for those titles
+ // that require it
+ if (_multiDiscAudio) {
+ return true;
+ }
+#endif
+
Common::List<ResourceId> resources = listResources(kResourceTypeMap);
Common::List<ResourceId>::iterator itr;
for (itr = resources.begin(); itr != resources.end(); ++itr) {
- ResourceSource *src = addSource(new IntMapResourceSource("MAP", itr->getNumber()));
+ ResourceSource *src = addSource(new IntMapResourceSource("MAP", 0, itr->getNumber()));
- if ((itr->getNumber() == 65535) && Common::File::exists("RESOURCE.SFX"))
+ if (itr->getNumber() == 65535 && Common::File::exists("RESOURCE.SFX"))
addSource(new AudioVolumeResourceSource(this, "RESOURCE.SFX", src, 0));
else if (Common::File::exists("RESOURCE.AUD"))
addSource(new AudioVolumeResourceSource(this, "RESOURCE.AUD", src, 0));
@@ -991,7 +1001,7 @@ void ResourceManager::changeAudioDirectory(Common::String path) {
if ((it->getNumber() == 65535))
continue;
- ResourceSource *src = addSource(new IntMapResourceSource(mapName, it->getNumber()));
+ ResourceSource *src = addSource(new IntMapResourceSource(mapName, 0, it->getNumber()));
addSource(new AudioVolumeResourceSource(this, audioResourceName, src, 0));
}
diff --git a/engines/sci/resource_intern.h b/engines/sci/resource_intern.h
index 461d684005..fe4b0a97f4 100644
--- a/engines/sci/resource_intern.h
+++ b/engines/sci/resource_intern.h
@@ -134,8 +134,9 @@ public:
class IntMapResourceSource : public ResourceSource {
public:
- IntMapResourceSource(const Common::String &name, int volNum)
- : ResourceSource(kSourceIntMap, name, volNum) {
+ uint16 _mapNumber;
+ IntMapResourceSource(const Common::String &name, int volNum, int mapNum)
+ : ResourceSource(kSourceIntMap, name, volNum), _mapNumber(mapNum) {
}
virtual void scanSource(ResourceManager *resMan);
diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp
index 4c178b6ed7..86c0cffe15 100644
--- a/engines/sci/sci.cpp
+++ b/engines/sci/sci.cpp
@@ -65,6 +65,7 @@
#ifdef ENABLE_SCI32
#include "sci/graphics/controls32.h"
+#include "sci/graphics/cursor32.h"
#include "sci/graphics/frameout.h"
#include "sci/graphics/palette32.h"
#include "sci/graphics/remap32.h"
@@ -72,17 +73,12 @@
#include "sci/graphics/transitions32.h"
#include "sci/graphics/video32.h"
#include "sci/sound/audio32.h"
-// TODO: Move this to video32
-#include "sci/video/robot_decoder.h"
#endif
namespace Sci {
SciEngine *g_sci = 0;
-
-class GfxDriver;
-
SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gameId)
: Engine(syst), _gameDescription(desc), _gameId(gameId), _rng("sci") {
@@ -96,6 +92,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
#ifdef ENABLE_SCI32
_audio32 = nullptr;
_video32 = nullptr;
+ _gfxCursor32 = nullptr;
#endif
_features = 0;
_resMan = 0;
@@ -130,6 +127,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam
DebugMan.addDebugChannel(kDebugLevelScripts, "Scripts", "Notifies when scripts are unloaded");
DebugMan.addDebugChannel(kDebugLevelScriptPatcher, "ScriptPatcher", "Notifies when scripts are patched");
DebugMan.addDebugChannel(kDebugLevelWorkarounds, "Workarounds", "Notifies when workarounds are triggered");
+ DebugMan.addDebugChannel(kDebugLevelVideo, "Video", "Video (SEQ, VMD, RBT) debugging");
DebugMan.addDebugChannel(kDebugLevelGC, "GC", "Garbage Collector debugging");
DebugMan.addDebugChannel(kDebugLevelResMan, "ResMan", "Resource manager debugging");
DebugMan.addDebugChannel(kDebugLevelOnStartup, "OnStartup", "Enter debugger at start of game");
@@ -171,11 +169,11 @@ SciEngine::~SciEngine() {
delete _gfxControls32;
delete _gfxPaint32;
delete _gfxText32;
- delete _robotDecoder;
// GfxFrameout and GfxPalette32 must be deleted after Video32 since
// destruction of screen items in the Video32 destructor relies on these
// components
delete _video32;
+ delete _gfxCursor32;
delete _gfxPalette32;
delete _gfxTransitions32;
delete _gfxFrameout;
@@ -244,35 +242,31 @@ Common::Error SciEngine::run() {
_scriptPatcher = new ScriptPatcher();
SegManager *segMan = new SegManager(_resMan, _scriptPatcher);
- // Read user option for hires graphics
+ // Read user option for forcing hires graphics
// Only show/selectable for:
// - King's Quest 6 CD
// - King's Quest 6 CD demo
// - Gabriel Knight 1 CD
// - Police Quest 4 CD
// TODO: Check, if Gabriel Knight 1 floppy supports high resolution
- // TODO: Check, if Gabriel Knight 1 on Mac supports high resolution
- switch (getPlatform()) {
- case Common::kPlatformDOS:
- case Common::kPlatformWindows:
- // Only DOS+Windows
- switch (_gameId) {
- case GID_KQ6:
- case GID_GK1:
- case GID_PQ4:
- if (isCD())
- _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
- break;
- default:
- break;
- }
- default:
- break;
- };
+ //
+ // Gabriel Knight 1 on Mac is hi-res only, so it should NOT get this option.
+ // Confirmed by [md5] and originally by clone2727.
+ if (Common::checkGameGUIOption(GAMEOPTION_HIGH_RESOLUTION_GRAPHICS, ConfMan.get("guioptions"))) {
+ // GAMEOPTION_HIGH_RESOLUTION_GRAPHICS is available for the currently detected game,
+ // so read the user option now.
+ // We need to do this, because the option's default is "true", but we don't want "true"
+ // for any game that does not have this option.
+ _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics");
+ }
- // Initialize the game screen
- _gfxScreen = new GfxScreen(_resMan);
- _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering"));
+ if (getSciVersion() < SCI_VERSION_2) {
+ // Initialize the game screen
+ _gfxScreen = new GfxScreen(_resMan);
+ _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering"));
+ } else {
+ _gfxScreen = nullptr;
+ }
_kernel = new Kernel(_resMan, segMan);
_kernel->init();
@@ -709,12 +703,12 @@ void SciEngine::initGraphics() {
#ifdef ENABLE_SCI32
_gfxControls32 = 0;
_gfxText32 = 0;
- _robotDecoder = 0;
_gfxFrameout = 0;
_gfxPaint32 = 0;
_gfxPalette32 = 0;
_gfxRemap32 = 0;
_gfxTransitions32 = 0;
+ _gfxCursor32 = 0;
#endif
if (hasMacIconBar())
@@ -734,24 +728,23 @@ void SciEngine::initGraphics() {
#endif
_gfxCache = new GfxCache(_resMan, _gfxScreen, _gfxPalette16);
- _gfxCursor = new GfxCursor(_resMan, _gfxPalette16, _gfxScreen);
#ifdef ENABLE_SCI32
if (getSciVersion() >= SCI_VERSION_2) {
// SCI32 graphic objects creation
- _gfxCoordAdjuster = new GfxCoordAdjuster32(_gamestate->_segMan);
- _gfxCursor->init(_gfxCoordAdjuster, _eventMan);
- _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster);
+ _gfxCursor32 = new GfxCursor32();
+ _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, nullptr, _gfxCoordAdjuster);
_gfxPaint32 = new GfxPaint32(_gamestate->_segMan);
- _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh);
_gfxTransitions32 = new GfxTransitions32(_gamestate->_segMan);
- _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32, _gfxTransitions32);
+ _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _gfxPalette32, _gfxTransitions32, _gfxCursor32);
+ _gfxCursor32->init(_gfxFrameout->getCurrentBuffer());
_gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache);
_gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32);
_gfxFrameout->run();
} else {
#endif
// SCI0-SCI1.1 graphic objects creation
+ _gfxCursor = new GfxCursor(_resMan, _gfxPalette16, _gfxScreen);
_gfxPorts = new GfxPorts(_gamestate->_segMan, _gfxScreen);
_gfxCoordAdjuster = new GfxCoordAdjuster16(_gfxPorts);
_gfxCursor->init(_gfxCoordAdjuster, _eventMan);
@@ -930,14 +923,19 @@ Common::String SciEngine::getFilePrefix() const {
}
Common::String SciEngine::wrapFilename(const Common::String &name) const {
- return getFilePrefix() + "-" + name;
+ Common::String prefix = getFilePrefix() + "-";
+ if (name.hasPrefix(prefix.c_str()))
+ return name;
+ else
+ return prefix + name;
}
Common::String SciEngine::unwrapFilename(const Common::String &name) const {
Common::String prefix = getFilePrefix() + "-";
if (name.hasPrefix(prefix.c_str()))
return Common::String(name.c_str() + prefix.size());
- return name;
+ else
+ return name;
}
const char *SciEngine::getGameObjectName() {
@@ -1042,17 +1040,19 @@ void SciEngine::syncIngameAudioOptions() {
case GID_SQ6: // SCI2.1
case GID_TORIN: // SCI2.1
case GID_QFG4: // SCI2.1
+ case GID_PQ4: // SCI2
+ case GID_PHANTASMAGORIA: // SCI2.1
+ case GID_MOTHERGOOSEHIRES: // SCI2.1
useGlobal90 = true;
break;
case GID_LSL6: // SCI2.1
// TODO: Uses gameFlags array
break;
+ // Shivers does not use global 90
+ // Police Quest: SWAT does not use global 90
+ //
// TODO: Unknown at the moment:
- // Shivers - seems not to use global 90
- // Police Quest: SWAT - unable to check
- // Police Quest 4 - unable to check
- // Mixed Up Mother Goose - unable to check
- // Phantasmagoria - seems to use global 90, unable to check for subtitles atm
+ // LSL7, Lighthouse, RAMA, Phantasmagoria 2
default:
return;
}
@@ -1086,6 +1086,9 @@ void SciEngine::syncIngameAudioOptions() {
case GID_SQ6: // SCI2.1, SQ6 seems to always use subtitles anyway
case GID_TORIN: // SCI2.1
case GID_QFG4: // SCI2.1
+ case GID_PQ4: // SCI2
+ // Phantasmagoria does not support simultaneous speech + subtitles
+ // Mixed Up Mother Goose Deluxe does not support simultaneous speech + subtitles
#endif // ENABLE_SCI32
_gamestate->variables[VAR_GLOBAL][90] = make_reg(0, 3); // speech + subtitles
break;
diff --git a/engines/sci/sci.h b/engines/sci/sci.h
index a42095259b..b336eb8cce 100644
--- a/engines/sci/sci.h
+++ b/engines/sci/sci.h
@@ -45,6 +45,18 @@ struct ADGameDescription;
*/
namespace Sci {
+// GUI-options, primarily used by detection_tables.h
+#define GAMEOPTION_PREFER_DIGITAL_SFX GUIO_GAMEOPTIONS1
+#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS2
+#define GAMEOPTION_FB01_MIDI GUIO_GAMEOPTIONS3
+#define GAMEOPTION_JONES_CDAUDIO GUIO_GAMEOPTIONS4
+#define GAMEOPTION_KQ6_WINDOWS_CURSORS GUIO_GAMEOPTIONS5
+#define GAMEOPTION_SQ4_SILVER_CURSORS GUIO_GAMEOPTIONS6
+#define GAMEOPTION_EGA_UNDITHER GUIO_GAMEOPTIONS7
+// HIGH_RESOLUTION_GRAPHICS availability is checked for in SciEngine::run()
+#define GAMEOPTION_HIGH_RESOLUTION_GRAPHICS GUIO_GAMEOPTIONS8
+#define GAMEOPTION_ENABLE_BLACK_LINED_VIDEO GUIO_GAMEOPTIONS9
+
struct EngineState;
class Vocabulary;
class ResourceManager;
@@ -63,7 +75,7 @@ class GfxCache;
class GfxCompare;
class GfxControls16;
class GfxControls32;
-class GfxCoordAdjuster;
+class GfxCoordAdjuster16;
class GfxCursor;
class GfxMacIconBar;
class GfxMenu;
@@ -80,12 +92,11 @@ class GfxText32;
class GfxTransitions;
#ifdef ENABLE_SCI32
-// TODO: Move RobotDecoder to Video32
-class RobotDecoder;
class GfxFrameout;
class Audio32;
class Video32;
class GfxTransitions32;
+class GfxCursor32;
#endif
// our engine debug levels
@@ -113,7 +124,8 @@ enum kDebugLevels {
kDebugLevelOnStartup = 1 << 20,
kDebugLevelDebugMode = 1 << 21,
kDebugLevelScriptPatcher = 1 << 22,
- kDebugLevelWorkarounds = 1 << 23
+ kDebugLevelWorkarounds = 1 << 23,
+ kDebugLevelVideo = 1 << 24
};
enum SciGameId {
@@ -357,7 +369,7 @@ public:
GfxCompare *_gfxCompare;
GfxControls16 *_gfxControls16; // Controls for 16-bit gfx
GfxControls32 *_gfxControls32; // Controls for 32-bit gfx
- GfxCoordAdjuster *_gfxCoordAdjuster;
+ GfxCoordAdjuster16 *_gfxCoordAdjuster;
GfxCursor *_gfxCursor;
GfxMenu *_gfxMenu; // Menu for 16-bit gfx
GfxPalette *_gfxPalette16;
@@ -376,9 +388,9 @@ public:
#ifdef ENABLE_SCI32
Audio32 *_audio32;
Video32 *_video32;
- RobotDecoder *_robotDecoder;
GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx
GfxTransitions32 *_gfxTransitions32;
+ GfxCursor32 *_gfxCursor32;
#endif
AudioPlayer *_audio;
diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp
index 288b7c00f5..4af474b918 100644
--- a/engines/sci/sound/audio32.cpp
+++ b/engines/sci/sound/audio32.cpp
@@ -164,7 +164,7 @@ Audio32::~Audio32() {
#pragma mark -
#pragma mark AudioStream implementation
-int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) {
+int Audio32::writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) {
int samplesToRead = numSamples;
// The parent rate converter will request N * 2
@@ -182,7 +182,8 @@ int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream
do {
if (loop && sourceStream->endOfStream()) {
- sourceStream->rewind();
+ Audio::RewindableAudioStream *rewindableStream = dynamic_cast<Audio::RewindableAudioStream *>(sourceStream);
+ rewindableStream->rewind();
}
const int loopSamplesWritten = converter->flow(*sourceStream, targetBuffer, samplesToRead, leftVolume, rightVolume);
@@ -305,7 +306,14 @@ int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) {
}
if (channel.robot) {
- // TODO: Robot audio into output buffer
+ if (channel.stream->endOfStream()) {
+ stop(channelIndex--);
+ } else {
+ const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, kMaxVolume, kMaxVolume, channel.loop);
+ if (channelSamplesWritten > maxSamplesWritten) {
+ maxSamplesWritten = channelSamplesWritten;
+ }
+ }
continue;
}
@@ -443,9 +451,9 @@ void Audio32::freeUnusedChannels() {
Common::StackLock lock(_mutex);
for (int channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) {
const AudioChannel &channel = getChannel(channelIndex);
- if (channel.stream->endOfStream()) {
+ if (!channel.robot && channel.stream->endOfStream()) {
if (channel.loop) {
- channel.stream->rewind();
+ dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->rewind();
} else {
stop(channelIndex--);
}
@@ -466,21 +474,29 @@ void Audio32::freeChannel(const int16 channelIndex) {
Common::StackLock lock(_mutex);
AudioChannel &channel = getChannel(channelIndex);
- // We cannot unlock resources from the audio thread
- // because ResourceManager is not thread-safe; instead,
- // we just record that the resource needs unlocking and
- // unlock it whenever we are on the main thread again
- if (_inAudioThread) {
- _resourcesToUnlock.push_back(channel.resource);
+ // Robots have no corresponding resource to free
+ if (channel.robot) {
+ delete channel.stream;
+ channel.stream = nullptr;
+ channel.robot = false;
} else {
- _resMan->unlockResource(channel.resource);
+ // We cannot unlock resources from the audio thread
+ // because ResourceManager is not thread-safe; instead,
+ // we just record that the resource needs unlocking and
+ // unlock it whenever we are on the main thread again
+ if (_inAudioThread) {
+ _resourcesToUnlock.push_back(channel.resource);
+ } else {
+ _resMan->unlockResource(channel.resource);
+ }
+
+ channel.resource = nullptr;
+ delete channel.stream;
+ channel.stream = nullptr;
+ delete channel.resourceStream;
+ channel.resourceStream = nullptr;
}
- channel.resource = nullptr;
- delete channel.stream;
- channel.stream = nullptr;
- delete channel.resourceStream;
- channel.resourceStream = nullptr;
delete channel.converter;
channel.converter = nullptr;
@@ -527,6 +543,111 @@ void Audio32::setNumOutputChannels(int16 numChannels) {
}
#pragma mark -
+#pragma mark Robot
+
+int16 Audio32::findRobotChannel() const {
+ Common::StackLock lock(_mutex);
+ for (int16 i = 0; i < _numActiveChannels; ++i) {
+ if (_channels[i].robot) {
+ return i;
+ }
+ }
+
+ return kNoExistingChannel;
+}
+
+bool Audio32::playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet) {
+ // Stop immediately
+ if (packet.dataSize == 0) {
+ warning("Stopping robot stream by zero-length packet");
+ return stopRobotAudio();
+ }
+
+ // Flush and then stop
+ if (packet.dataSize == -1) {
+ warning("Stopping robot stream by negative-length packet");
+ return finishRobotAudio();
+ }
+
+ Common::StackLock lock(_mutex);
+ int16 channelIndex = findRobotChannel();
+
+ bool isNewChannel = false;
+ if (channelIndex == kNoExistingChannel) {
+ if (_numActiveChannels == _channels.size()) {
+ return false;
+ }
+
+ channelIndex = _numActiveChannels++;
+ isNewChannel = true;
+ }
+
+ AudioChannel &channel = getChannel(channelIndex);
+
+ if (isNewChannel) {
+ channel.id = ResourceId();
+ channel.resource = nullptr;
+ channel.loop = false;
+ channel.robot = true;
+ channel.fadeStartTick = 0;
+ channel.pausedAtTick = 0;
+ channel.soundNode = NULL_REG;
+ channel.volume = kMaxVolume;
+ // TODO: SCI3 introduces stereo audio
+ channel.pan = -1;
+ channel.converter = Audio::makeRateConverter(RobotAudioStream::kRobotSampleRate, getRate(), false);
+ // The RobotAudioStream buffer size is
+ // ((bytesPerSample * channels * sampleRate * 2000ms) / 1000ms) & ~3
+ // where bytesPerSample = 2, channels = 1, and sampleRate = 22050
+ channel.stream = new RobotAudioStream(88200);
+ _robotAudioPaused = false;
+
+ if (_numActiveChannels == 1) {
+ _startedAtTick = g_sci->getTickCount();
+ }
+ }
+
+ return static_cast<RobotAudioStream *>(channel.stream)->addPacket(packet);
+}
+
+bool Audio32::queryRobotAudio(RobotAudioStream::StreamState &status) const {
+ Common::StackLock lock(_mutex);
+
+ const int16 channelIndex = findRobotChannel();
+ if (channelIndex == kNoExistingChannel) {
+ status.bytesPlaying = 0;
+ return false;
+ }
+
+ status = static_cast<RobotAudioStream *>(getChannel(channelIndex).stream)->getStatus();
+ return true;
+}
+
+bool Audio32::finishRobotAudio() {
+ Common::StackLock lock(_mutex);
+
+ const int16 channelIndex = findRobotChannel();
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ static_cast<RobotAudioStream *>(getChannel(channelIndex).stream)->finish();
+ return true;
+}
+
+bool Audio32::stopRobotAudio() {
+ Common::StackLock lock(_mutex);
+
+ const int16 channelIndex = findRobotChannel();
+ if (channelIndex == kNoExistingChannel) {
+ return false;
+ }
+
+ stop(channelIndex);
+ return true;
+}
+
+#pragma mark -
#pragma mark Playback
uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor) {
@@ -536,14 +657,15 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool
if (channelIndex != kNoExistingChannel) {
AudioChannel &channel = getChannel(channelIndex);
+ Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream);
if (channel.pausedAtTick) {
resume(channelIndex);
- return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000);
+ return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000);
}
warning("Tried to resume channel %s that was not paused", channel.id.toString().c_str());
- return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000);
+ return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000);
}
if (_numActiveChannels == _channels.size()) {
@@ -642,7 +764,7 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool
// use audio streams, and allocate and fill the monitoring buffer
// when reading audio data from the stream.
- channel.duration = /* round up */ 1 + (channel.stream->getLength().msecs() * 60 / 1000);
+ channel.duration = /* round up */ 1 + (dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->getLength().msecs() * 60 / 1000);
const uint32 now = g_sci->getTickCount();
channel.pausedAtTick = autoPlay ? 0 : now;
@@ -687,8 +809,6 @@ bool Audio32::resume(const int16 channelIndex) {
if (channel.robot) {
channel.startedAtTick += now - channel.pausedAtTick;
channel.pausedAtTick = 0;
- // TODO: Robot
- // StartRobot();
return true;
}
}
diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h
index ac3176cc5a..a9905ab6bf 100644
--- a/engines/sci/sound/audio32.h
+++ b/engines/sci/sound/audio32.h
@@ -30,8 +30,10 @@
#include "common/scummsys.h" // for int16, uint8, uint32, uint16
#include "engines/sci/resource.h" // for ResourceId
#include "sci/engine/vm_types.h" // for reg_t, NULL_REG
+#include "sci/video/robot_decoder.h" // for RobotAudioStream
namespace Sci {
+#pragma mark AudioChannel
/**
* An audio channel used by the software SCI mixer.
@@ -53,14 +55,11 @@ struct AudioChannel {
Common::SeekableReadStream *resourceStream;
/**
- * The audio stream loaded into this channel.
- * `SeekableAudioStream` is used here instead of
- * `RewindableAudioStream` because
- * `RewindableAudioStream` does not include the
- * `getLength` function, which is needed to tell the
- * game engine the duration of audio streams.
+ * The audio stream loaded into this channel. Can cast
+ * to `SeekableAudioStream` for normal channels and
+ * `RobotAudioStream` for robot channels.
*/
- Audio::SeekableAudioStream *stream;
+ Audio::AudioStream *stream;
/**
* The converter used to transform and merge the input
@@ -188,7 +187,7 @@ private:
* Mixes audio from the given source stream into the
* target buffer using the given rate converter.
*/
- int writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop);
+ int writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop);
#pragma mark -
#pragma mark Channel management
@@ -395,9 +394,18 @@ private:
#pragma mark -
#pragma mark Robot
public:
+ bool playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet);
+ bool queryRobotAudio(RobotAudioStream::StreamState &outStatus) const;
+ bool finishRobotAudio();
+ bool stopRobotAudio();
private:
/**
+ * Finds a channel that is configured for robot playback.
+ */
+ int16 findRobotChannel() const;
+
+ /**
* When true, channels marked as robot audio will not be
* played.
*/
diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp
index e445403120..ee1ba35406 100644
--- a/engines/sci/sound/decoders/sol.cpp
+++ b/engines/sci/sound/decoders/sol.cpp
@@ -21,6 +21,7 @@
*/
#include "audio/audiostream.h"
+#include "audio/rate.h"
#include "audio/decoders/raw.h"
#include "common/substream.h"
#include "common/util.h"
@@ -52,7 +53,7 @@ static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 };
* Decompresses 16-bit DPCM compressed audio. Each byte read
* outputs one sample into the decompression buffer.
*/
-static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, int16 &sample) {
+static void deDPCM16(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, int16 &sample) {
for (uint32 i = 0; i < numBytes; ++i) {
const uint8 delta = audioStream.readByte();
if (delta & 0x80) {
@@ -65,6 +66,19 @@ static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numByte
}
}
+void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample) {
+ for (uint32 i = 0; i < numBytes; ++i) {
+ const uint8 delta = *in++;
+ if (delta & 0x80) {
+ sample -= tableDPCM16[delta & 0x7f];
+ } else {
+ sample += tableDPCM16[delta];
+ }
+ sample = CLIP<int16>(sample, -32768, 32767);
+ *out++ = TO_LE_16(sample);
+ }
+}
+
/**
* Decompresses one half of an 8-bit DPCM compressed audio
* byte.
@@ -178,7 +192,7 @@ int SOLStream<STEREO, S16BIT>::getRate() const {
template <bool STEREO, bool S16BIT>
bool SOLStream<STEREO, S16BIT>::endOfData() const {
- return _stream->eos() || _stream->pos() >= _dataOffset + _rawDataSize;
+ return _stream->eos() || _stream->pos() >= _rawDataSize;
}
template <bool STEREO, bool S16BIT>
@@ -269,5 +283,4 @@ Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStre
return Audio::makeRawStream(dataStream, sampleRate, rawFlags, disposeAfterUse);
}
-
}
diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp
index a2795d21f9..1757088ea4 100644
--- a/engines/sci/video/robot_decoder.cpp
+++ b/engines/sci/video/robot_decoder.cpp
@@ -20,391 +20,1597 @@
*
*/
-#include "common/archive.h"
-#include "common/stream.h"
-#include "common/substream.h"
-#include "common/system.h"
-#include "common/textconsole.h"
-#include "common/util.h"
-
-#include "graphics/surface.h"
-#include "audio/audiostream.h"
-#include "audio/decoders/raw.h"
-
-#include "sci/resource.h"
-#include "sci/util.h"
-#include "sci/sound/audio.h"
#include "sci/video/robot_decoder.h"
+#include "common/archive.h" // for SearchMan
+#include "common/debug.h" // for debugC
+#include "common/endian.h" // for MKTAG
+#include "common/memstream.h" // for MemoryReadStream
+#include "common/platform.h" // for Platform::kPlatformMacintosh
+#include "common/rational.h" // for operator*, Rational
+#include "common/str.h" // for String
+#include "common/stream.h" // for SeekableReadStream
+#include "common/substream.h" // for SeekableSubReadStreamEndian
+#include "common/textconsole.h" // for error, warning
+#include "common/types.h" // for Flag::NO, Flag::YES
+#include "sci/engine/seg_manager.h" // for SegManager
+#include "sci/graphics/celobj32.h" // for Ratio, ::kLowResX, ::kLowResY
+#include "sci/graphics/text32.h" // for BitmapResource
+#include "sci/sound/audio32.h" // for Audio32
+#include "sci/sci.h" // for kDebugLevels::kDebugLevelVideo
+#include "sci/util.h" // for READ_SCI11ENDIAN_UINT16, READ_SC...
namespace Sci {
-// TODO:
-// - Positioning
-// - Proper handling of frame scaling - scaled frames look squashed
-// (probably because both dimensions should be scaled)
-// - Transparency support
-// - Timing - the arbitrary 100ms delay between each frame is not quite right
-// - Proper handling of sound chunks in some cases, so that the frame size
-// table can be ignored (it's only used to determine the correct sound chunk
-// size at the moment, cause it can be wrong in some cases)
-// - Fix audio "hiccups" - probably data that shouldn't be in the audio frames
-
-
-// Some non technical information on robot files, from an interview with
-// Greg Tomko-Pavia of Sierra On-Line
-// Taken from http://anthonylarme.tripod.com/phantas/phintgtp.html
-//
-// (...) What we needed was a way of playing video, but have it blend into
-// normal room art instead of occupying its own rectangular area. Room art
-// consists of a background pic overlaid with various animating cels
-// (traditional lingo: sprites). The cels each have a priority that determines
-// who is on top and who is behind in the drawing order. Cels are read from
-// *.v56 files (another proprietary format). A Robot is video frames with
-// transparent background including priority and x,y information. Thus, it is
-// like a cel, except it comes from an RBT - not a v56. Because it blends into
-// our graphics engine, it looks just like a part of the room. A RBT can move
-// around the screen and go behind other objects. (...)
-
-enum RobotPalTypes {
- kRobotPalVariable = 0,
- kRobotPalConstant = 1
-};
-
-RobotDecoder::RobotDecoder(bool isBigEndian) {
- _fileStream = 0;
- _pos = Common::Point(0, 0);
- _isBigEndian = isBigEndian;
- _frameTotalSize = 0;
+#pragma mark RobotAudioStream
+
+extern void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample);
+
+RobotAudioStream::RobotAudioStream(const int32 bufferSize) :
+ _loopBuffer((byte *)malloc(bufferSize)),
+ _loopBufferSize(bufferSize),
+ _decompressionBuffer(nullptr),
+ _decompressionBufferSize(0),
+ _decompressionBufferPosition(-1),
+ _waiting(true),
+ _finished(false),
+ _firstPacketPosition(-1) {}
+
+RobotAudioStream::~RobotAudioStream() {
+ free(_loopBuffer);
+ free(_decompressionBuffer);
}
-RobotDecoder::~RobotDecoder() {
- close();
+static void interpolateChannel(int16 *buffer, int32 numSamples, const int8 bufferIndex) {
+ if (numSamples <= 0) {
+ return;
+ }
+
+ if (bufferIndex) {
+ int16 lastSample = *buffer;
+ int sample = lastSample;
+ int16 *target = buffer + 1;
+ const int16 *source = buffer + 2;
+ --numSamples;
+
+ while (numSamples--) {
+ sample = *source + lastSample;
+ lastSample = *source;
+ sample /= 2;
+ *target = sample;
+ source += 2;
+ target += 2;
+ }
+
+ *target = sample;
+ } else {
+ int16 *target = buffer;
+ const int16 *source = buffer + 1;
+ int16 lastSample = *source;
+
+ while (numSamples--) {
+ int sample = *source + lastSample;
+ lastSample = *source;
+ sample /= 2;
+ *target = sample;
+ source += 2;
+ target += 2;
+ }
+ }
}
-bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) {
- close();
+static void copyEveryOtherSample(int16 *out, const int16 *in, int numSamples) {
+ while (numSamples--) {
+ *out = *in++;
+ out += 2;
+ }
+}
+
+bool RobotAudioStream::addPacket(const RobotAudioPacket &packet) {
+ Common::StackLock lock(_mutex);
+
+ if (_finished) {
+ warning("Packet %d sent to finished robot audio stream", packet.position);
+ return false;
+ }
+
+ // `packet.position` is the decompressed (doubled) position of the packet,
+ // so values of `position` will always be divisible either by 2 (even) or by
+ // 4 (odd).
+ const int8 bufferIndex = packet.position % 4 ? 1 : 0;
+
+ // Packet 0 is the first primer, packet 2 is the second primer,
+ // packet 4+ are regular audio data
+ if (packet.position <= 2 && _firstPacketPosition == -1) {
+ _readHead = 0;
+ _readHeadAbs = 0;
+ _maxWriteAbs = _loopBufferSize;
+ _writeHeadAbs = 2;
+ _jointMin[0] = 0;
+ _jointMin[1] = 2;
+ _waiting = true;
+ _finished = false;
+ _firstPacketPosition = packet.position;
+ fillRobotBuffer(packet, bufferIndex);
+ return true;
+ }
- _fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES);
+ const int32 packetEndByte = packet.position + (packet.dataSize * sizeof(int16) * kEOSExpansion);
- readHeaderChunk();
+ // Already read all the way past this packet (or already wrote valid samples
+ // to this channel all the way past this packet), so discard it
+ if (packetEndByte <= MAX(_readHeadAbs, _jointMin[bufferIndex])) {
+ debugC(kDebugLevelVideo, "Rejecting packet %d, read past %d / %d", packet.position, _readHeadAbs, _jointMin[bufferIndex]);
+ return true;
+ }
- // There are several versions of robot files, ranging from 3 to 6.
- // v3: no known examples
- // v4: PQ:SWAT demo
- // v5: SCI2.1 and SCI3 games
- // v6: SCI3 games
- if (_header.version < 4 || _header.version > 6)
- error("Unknown robot version: %d", _header.version);
+ // The loop buffer is full, so tell the caller to send the packet again
+ // later
+ if (_maxWriteAbs <= _jointMin[bufferIndex]) {
+ debugC(kDebugLevelVideo, "Rejecting packet %d, full buffer", packet.position);
+ return false;
+ }
- RobotVideoTrack *videoTrack = new RobotVideoTrack(_header.frameCount);
- addTrack(videoTrack);
+ fillRobotBuffer(packet, bufferIndex);
- if (_header.hasSound)
- addTrack(new RobotAudioTrack());
+ // This packet is the second primer, so allow playback to begin
+ if (_firstPacketPosition != -1 && _firstPacketPosition != packet.position) {
+ debugC(kDebugLevelVideo, "Done waiting. Robot audio begins");
+ _waiting = false;
+ _firstPacketPosition = -1;
+ }
- videoTrack->readPaletteChunk(_fileStream, _header.paletteDataSize);
- readFrameSizesChunk();
- videoTrack->calculateVideoDimensions(_fileStream, _frameTotalSize);
+ // Only part of the packet could be read into the loop buffer before it was
+ // full, so tell the caller to send the packet again later
+ if (packetEndByte > _maxWriteAbs) {
+ debugC(kDebugLevelVideo, "Partial read of packet %d (%d / %d)", packet.position, packetEndByte - _maxWriteAbs, packetEndByte - packet.position);
+ return false;
+ }
+
+ // The entire packet was successfully read into the loop buffer
return true;
}
-bool RobotDecoder::load(GuiResourceId id) {
- // TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) -
- // its drawn at odd coordinates. SV can't play it either (along with some
- // others), so it must be some new functionality added in RAMA's robot
- // videos. Skip it for now.
- if (g_sci->getGameId() == GID_RAMA && id == 1003)
- return false;
+void RobotAudioStream::fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex) {
+ int32 sourceByte = 0;
- // Robots for the options in the RAMA menu
- if (g_sci->getGameId() == GID_RAMA && (id >= 1004 && id <= 1009))
- return false;
+ const int32 decompressedSize = packet.dataSize * sizeof(int16);
+ if (_decompressionBufferPosition != packet.position) {
+ if (decompressedSize != _decompressionBufferSize) {
+ _decompressionBuffer = (byte *)realloc(_decompressionBuffer, decompressedSize);
+ _decompressionBufferSize = decompressedSize;
+ }
- // TODO: The robot video in the Lighthouse demo gets stuck
- if (g_sci->getGameId() == GID_LIGHTHOUSE && id == 16)
- return false;
+ int16 carry = 0;
+ deDPCM16((int16 *)_decompressionBuffer, packet.data, packet.dataSize, carry);
+ _decompressionBufferPosition = packet.position;
+ }
+
+ int32 numBytes = decompressedSize;
+ int32 packetPosition = packet.position;
+ int32 endByte = packet.position + decompressedSize * kEOSExpansion;
+ int32 startByte = MAX(_readHeadAbs + bufferIndex * 2, _jointMin[bufferIndex]);
+ int32 maxWriteByte = _maxWriteAbs + bufferIndex * 2;
+ if (packetPosition < startByte) {
+ sourceByte = (startByte - packetPosition) / kEOSExpansion;
+ numBytes -= sourceByte;
+ packetPosition = startByte;
+ }
+ if (packetPosition > maxWriteByte) {
+ numBytes += (packetPosition - maxWriteByte) / kEOSExpansion;
+ packetPosition = maxWriteByte;
+ }
+ if (endByte > maxWriteByte) {
+ numBytes -= (endByte - maxWriteByte) / kEOSExpansion;
+ endByte = maxWriteByte;
+ }
- Common::String fileName = Common::String::format("%d.rbt", id);
+ const int32 maxJointMin = MAX(_jointMin[0], _jointMin[1]);
+ if (endByte > maxJointMin) {
+ _writeHeadAbs += endByte - maxJointMin;
+ }
+
+ if (packetPosition > _jointMin[bufferIndex]) {
+ int32 packetEndByte = packetPosition % _loopBufferSize;
+ int32 targetBytePosition;
+ int32 numBytesToEnd;
+ if ((packetPosition & ~3) > (_jointMin[1 - bufferIndex] & ~3)) {
+ targetBytePosition = _jointMin[1 - bufferIndex] % _loopBufferSize;
+ if (targetBytePosition >= packetEndByte) {
+ numBytesToEnd = _loopBufferSize - targetBytePosition;
+ memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd);
+ targetBytePosition = (1 - bufferIndex) ? 2 : 0;
+ }
+ numBytesToEnd = packetEndByte - targetBytePosition;
+ if (numBytesToEnd > 0) {
+ memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd);
+ }
+ }
+ targetBytePosition = _jointMin[bufferIndex] % _loopBufferSize;
+ if (targetBytePosition >= packetEndByte) {
+ numBytesToEnd = _loopBufferSize - targetBytePosition;
+ interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0);
+ targetBytePosition = bufferIndex ? 2 : 0;
+ }
+ numBytesToEnd = packetEndByte - targetBytePosition;
+ if (numBytesToEnd > 0) {
+ interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0);
+ }
+ }
+
+ if (numBytes > 0) {
+ int32 targetBytePosition = packetPosition % _loopBufferSize;
+ int32 packetEndByte = endByte % _loopBufferSize;
+ int32 numBytesToEnd = 0;
+ if (targetBytePosition >= packetEndByte) {
+ numBytesToEnd = (_loopBufferSize - (targetBytePosition & ~3)) / kEOSExpansion;
+ copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte), numBytesToEnd / kEOSExpansion);
+ targetBytePosition = bufferIndex ? 2 : 0;
+ }
+ copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte + numBytesToEnd), (packetEndByte - targetBytePosition) / sizeof(int16) / kEOSExpansion);
+ }
+ _jointMin[bufferIndex] = endByte;
+}
+
+void RobotAudioStream::interpolateMissingSamples(int32 numSamples) {
+ int32 numBytes = numSamples * sizeof(int16) * kEOSExpansion;
+ int32 targetPosition = _readHead;
+
+ if (_readHeadAbs > _jointMin[1]) {
+ if (_readHeadAbs > _jointMin[0]) {
+ if (targetPosition + numBytes >= _loopBufferSize) {
+ const int32 numBytesToEdge = (_loopBufferSize - targetPosition);
+ memset(_loopBuffer + targetPosition, 0, numBytesToEdge);
+ numBytes -= numBytesToEdge;
+ targetPosition = 0;
+ }
+ memset(_loopBuffer + targetPosition, 0, numBytes);
+ _jointMin[0] += numBytes;
+ _jointMin[1] += numBytes;
+ } else {
+ if (targetPosition + numBytes >= _loopBufferSize) {
+ const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion;
+ interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 1);
+ numSamples -= numSamplesToEdge;
+ targetPosition = 0;
+ }
+ interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 1);
+ _jointMin[1] += numBytes;
+ }
+ } else if (_readHeadAbs > _jointMin[0]) {
+ if (targetPosition + numBytes >= _loopBufferSize) {
+ const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion;
+ interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 0);
+ numSamples -= numSamplesToEdge;
+ targetPosition = 2;
+ }
+ interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 0);
+ _jointMin[0] += numBytes;
+ }
+}
+
+void RobotAudioStream::finish() {
+ Common::StackLock lock(_mutex);
+ _finished = true;
+}
+
+RobotAudioStream::StreamState RobotAudioStream::getStatus() const {
+ Common::StackLock lock(_mutex);
+ StreamState status;
+ status.bytesPlaying = _readHeadAbs;
+ status.rate = getRate();
+ status.bits = 8 * sizeof(int16);
+ return status;
+}
+
+int RobotAudioStream::readBuffer(Audio::st_sample_t *outBuffer, int numSamples) {
+ Common::StackLock lock(_mutex);
+
+ if (_waiting) {
+ return 0;
+ }
+
+ assert(!((_writeHeadAbs - _readHeadAbs) & 1));
+ const int maxNumSamples = (_writeHeadAbs - _readHeadAbs) / sizeof(Audio::st_sample_t);
+ numSamples = MIN(numSamples, maxNumSamples);
+
+ if (!numSamples) {
+ return 0;
+ }
+
+ interpolateMissingSamples(numSamples);
+
+ Audio::st_sample_t *inBuffer = (Audio::st_sample_t *)(_loopBuffer + _readHead);
+
+ assert(!((_loopBufferSize - _readHead) & 1));
+ const int numSamplesToEnd = (_loopBufferSize - _readHead) / sizeof(Audio::st_sample_t);
+
+ int numSamplesToRead = MIN(numSamples, numSamplesToEnd);
+ Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer);
+
+ if (numSamplesToRead < numSamples) {
+ inBuffer = (Audio::st_sample_t *)_loopBuffer;
+ outBuffer += numSamplesToRead;
+ numSamplesToRead = numSamples - numSamplesToRead;
+ Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer);
+ }
+
+ const int32 numBytes = numSamples * sizeof(Audio::st_sample_t);
+
+ _readHead += numBytes;
+ if (_readHead > _loopBufferSize) {
+ _readHead -= _loopBufferSize;
+ }
+ _readHeadAbs += numBytes;
+ _maxWriteAbs += numBytes;
+ assert(!(_readHead & 1));
+ assert(!(_readHeadAbs & 1));
+
+ return numSamples;
+}
+
+#pragma mark -
+#pragma mark RobotDecoder
+
+RobotDecoder::RobotDecoder(SegManager *segMan) :
+ _delayTime(this),
+ _segMan(segMan),
+ _status(kRobotStatusUninitialized),
+ _audioBuffer(nullptr),
+ _rawPalette((uint8 *)malloc(kRawPaletteSize)) {}
+
+RobotDecoder::~RobotDecoder() {
+ close();
+ free(_rawPalette);
+ free(_audioBuffer);
+}
+
+#pragma mark -
+#pragma mark RobotDecoder - Initialization
+
+void RobotDecoder::initStream(const GuiResourceId robotId) {
+ const Common::String fileName = Common::String::format("%d.rbt", robotId);
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName);
+ _fileOffset = 0;
- if (!stream) {
- warning("Unable to open robot file %s", fileName.c_str());
- return false;
+ if (stream == nullptr) {
+ error("Unable to open robot file %s", fileName.c_str());
+ }
+
+ const uint16 id = stream->readUint16LE();
+ if (id != 0x16) {
+ error("Invalid robot file %s", fileName.c_str());
+ }
+
+ // TODO: Mac version not tested, so this could be totally wrong
+ _stream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), g_sci->getPlatform() == Common::kPlatformMacintosh, DisposeAfterUse::YES);
+ _stream->seek(2, SEEK_SET);
+ if (_stream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) {
+ error("Resource %s is not Robot type!", fileName.c_str());
+ }
+}
+
+void RobotDecoder::initPlayback() {
+ _startFrameNo = 0;
+ _startTime = -1;
+ _startingFrameNo = -1;
+ _cueForceShowFrame = -1;
+ _previousFrameNo = -1;
+ _currentFrameNo = 0;
+ _status = kRobotStatusPaused;
+}
+
+void RobotDecoder::initAudio() {
+ _syncFrame = true;
+
+ _audioRecordInterval = RobotAudioStream::kRobotSampleRate / _frameRate;
+
+ // TODO: Might actually be for all games newer than Lighthouse; check to
+ // see which games have this condition.
+ if (g_sci->getGameId() != GID_LIGHTHOUSE && !(_audioRecordInterval & 1)) {
+ ++_audioRecordInterval;
+ }
+
+ _expectedAudioBlockSize = _audioBlockSize - kAudioBlockHeaderSize;
+ _audioBuffer = (byte *)realloc(_audioBuffer, kRobotZeroCompressSize + _expectedAudioBlockSize);
+
+ if (_primerReservedSize != 0) {
+ const int32 primerHeaderPosition = _stream->pos();
+ _totalPrimerSize = _stream->readSint32();
+ const int16 compressionType = _stream->readSint16();
+ _evenPrimerSize = _stream->readSint32();
+ _oddPrimerSize = _stream->readSint32();
+ _primerPosition = _stream->pos();
+
+ if (compressionType) {
+ error("Unknown audio header compression type %d", compressionType);
+ }
+
+ if (_evenPrimerSize + _oddPrimerSize != _primerReservedSize) {
+ _stream->seek(primerHeaderPosition + _primerReservedSize, SEEK_SET);
+ }
+ } else if (_primerZeroCompressFlag) {
+ _evenPrimerSize = 19922;
+ _oddPrimerSize = 21024;
+ }
+
+ _firstAudioRecordPosition = _evenPrimerSize * 2;
+
+ const int usedEachFrame = (RobotAudioStream::kRobotSampleRate / 2) / _frameRate;
+ _maxSkippablePackets = MAX(0, _audioBlockSize / usedEachFrame - 1);
+}
+
+void RobotDecoder::initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize) {
+ _position = Common::Point(x, y);
+
+ if (scale != 128) {
+ _scaleInfo.x = scale;
+ _scaleInfo.y = scale;
+ _scaleInfo.signal = kScaleSignalDoScaling32;
+ }
+
+ _plane = g_sci->_gfxFrameout->getPlanes().findByObject(plane);
+ if (_plane == nullptr) {
+ error("Invalid plane %04x:%04x passed to RobotDecoder::open", PRINT_REG(plane));
+ }
+
+ _minFrameRate = _frameRate - kMaxFrameRateDrift;
+ _maxFrameRate = _frameRate + kMaxFrameRateDrift;
+
+ if (_xResolution == 0 || _yResolution == 0) {
+ // TODO: Default values were taken from RESOURCE.CFG hires property
+ // if it exists, so need to check games' configuration files for those
+ _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
}
- return loadStream(stream);
+ if (hasPalette) {
+ _stream->read(_rawPalette, paletteSize);
+ } else {
+ _stream->seek(paletteSize, SEEK_CUR);
+ }
+
+ _screenItemList.reserve(kScreenItemListSize);
+ _maxCelArea.reserve(kFixedCelListSize);
+
+ // Fixed cel buffers are for version 5 and newer
+ _fixedCels.reserve(MIN(_maxCelsPerFrame, (int16)kFixedCelListSize));
+ _celDecompressionBuffer.reserve(_maxCelArea[0] + SciBitmap::getBitmapHeaderSize() + kRawPaletteSize);
+ _celDecompressionArea = _maxCelArea[0];
+}
+
+void RobotDecoder::initRecordAndCuePositions() {
+ PositionList recordSizes;
+ _videoSizes.reserve(_numFramesTotal);
+ _recordPositions.reserve(_numFramesTotal);
+ recordSizes.reserve(_numFramesTotal);
+
+ switch(_version) {
+ case 5: // 16-bit sizes and positions
+ for (int i = 0; i < _numFramesTotal; ++i) {
+ _videoSizes.push_back(_stream->readUint16());
+ }
+ for (int i = 0; i < _numFramesTotal; ++i) {
+ recordSizes.push_back(_stream->readUint16());
+ }
+ break;
+ case 6: // 32-bit sizes and positions
+ for (int i = 0; i < _numFramesTotal; ++i) {
+ _videoSizes.push_back(_stream->readSint32());
+ }
+ for (int i = 0; i < _numFramesTotal; ++i) {
+ recordSizes.push_back(_stream->readSint32());
+ }
+ break;
+ default:
+ error("Unknown Robot version %d", _version);
+ }
+
+ for (int i = 0; i < kCueListSize; ++i) {
+ _cueTimes[i] = _stream->readSint32();
+ }
+
+ for (int i = 0; i < kCueListSize; ++i) {
+ _cueValues[i] = _stream->readUint16();
+ }
+
+ Common::copy(_cueTimes, _cueTimes + kCueListSize, _masterCueTimes);
+
+ int bytesRemaining = (_stream->pos() - _fileOffset) % kRobotFrameSize;
+ if (bytesRemaining != 0) {
+ _stream->seek(kRobotFrameSize - bytesRemaining, SEEK_CUR);
+ }
+
+ int position = _stream->pos();
+ _recordPositions.push_back(position);
+ for (int i = 0; i < _numFramesTotal - 1; ++i) {
+ position += recordSizes[i];
+ _recordPositions.push_back(position);
+ }
+}
+
+#pragma mark -
+#pragma mark RobotDecoder - Playback
+
+void RobotDecoder::open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale) {
+ if (_status != kRobotStatusUninitialized) {
+ close();
+ }
+
+ initStream(robotId);
+
+ _version = _stream->readUint16();
+
+ // TODO: Version 4 for PQ:SWAT demo?
+ if (_version < 5 || _version > 6) {
+ error("Unsupported version %d of Robot resource", _version);
+ }
+
+ debugC(kDebugLevelVideo, "Opening version %d robot %d", _version, robotId);
+
+ initPlayback();
+
+ _audioBlockSize = _stream->readUint16();
+ _primerZeroCompressFlag = _stream->readSint16();
+ _stream->seek(2, SEEK_CUR); // unused
+ _numFramesTotal = _stream->readUint16();
+ const uint16 paletteSize = _stream->readUint16();
+ _primerReservedSize = _stream->readUint16();
+ _xResolution = _stream->readSint16();
+ _yResolution = _stream->readSint16();
+ const bool hasPalette = (bool)_stream->readByte();
+ _hasAudio = (bool)_stream->readByte();
+ _stream->seek(2, SEEK_CUR); // unused
+ _frameRate = _normalFrameRate = _stream->readSint16();
+ _isHiRes = (bool)_stream->readSint16();
+ _maxSkippablePackets = _stream->readSint16();
+ _maxCelsPerFrame = _stream->readSint16();
+
+ // used for memory preallocation of fixed cels
+ _maxCelArea.push_back(_stream->readSint32());
+ _maxCelArea.push_back(_stream->readSint32());
+ _maxCelArea.push_back(_stream->readSint32());
+ _maxCelArea.push_back(_stream->readSint32());
+ _stream->seek(8, SEEK_CUR); // reserved
+
+ if (_hasAudio) {
+ initAudio();
+ } else {
+ _stream->seek(_primerReservedSize, SEEK_CUR);
+ }
+
+ _priority = priority;
+ initVideo(x, y, scale, plane, hasPalette, paletteSize);
+ initRecordAndCuePositions();
}
void RobotDecoder::close() {
- VideoDecoder::close();
+ if (_status == kRobotStatusUninitialized) {
+ return;
+ }
+
+ debugC(kDebugLevelVideo, "Closing robot");
- delete _fileStream;
- _fileStream = 0;
+ _status = kRobotStatusUninitialized;
+ _videoSizes.clear();
+ _recordPositions.clear();
+ _celDecompressionBuffer.clear();
+ _doVersion5Scratch.clear();
+ delete _stream;
+ _stream = nullptr;
+
+ for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) {
+ if (_celHandles[i].status == CelHandleInfo::kFrameLifetime) {
+ _segMan->freeBitmap(_celHandles[i].bitmapId);
+ }
+ }
+ _celHandles.clear();
+
+ for (FixedCelsList::size_type i = 0; i < _fixedCels.size(); ++i) {
+ _segMan->freeBitmap(_fixedCels[i]);
+ }
+ _fixedCels.clear();
+
+ if (g_sci->_gfxFrameout->getPlanes().findByObject(_plane->_object) != nullptr) {
+ for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ if (_screenItemList[i] != nullptr) {
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]);
+ }
+ }
+ }
+ _screenItemList.clear();
- delete[] _frameTotalSize;
- _frameTotalSize = 0;
+ if (_hasAudio) {
+ _audioList.reset();
+ }
}
-void RobotDecoder::readNextPacket() {
- // Get our track
- RobotVideoTrack *videoTrack = (RobotVideoTrack *)getTrack(0);
- videoTrack->increaseCurFrame();
- Graphics::Surface *surface = videoTrack->getSurface();
+void RobotDecoder::pause() {
+ if (_status != kRobotStatusPlaying) {
+ return;
+ }
+
+ if (_hasAudio) {
+ _audioList.stopAudioNow();
+ }
+
+ _status = kRobotStatusPaused;
+ _frameRate = _normalFrameRate;
+}
- if (videoTrack->endOfTrack())
+void RobotDecoder::resume() {
+ if (_status != kRobotStatusPaused) {
return;
+ }
+
+ _startingFrameNo = _currentFrameNo;
+ _status = kRobotStatusPlaying;
+ if (_hasAudio) {
+ primeAudio(_currentFrameNo * 60 / _frameRate);
+ _syncFrame = true;
+ }
+
+ setRobotTime(_currentFrameNo);
+ for (int i = 0; i < kCueListSize; ++i) {
+ if (_masterCueTimes[i] != -1 && _masterCueTimes[i] < _currentFrameNo) {
+ _cueTimes[i] = -1;
+ } else {
+ _cueTimes[i] = _masterCueTimes[i];
+ }
+ }
+}
- // Read frame image header (24 bytes)
- _fileStream->skip(3);
- byte frameScale = _fileStream->readByte();
- uint16 frameWidth = _fileStream->readUint16();
- uint16 frameHeight = _fileStream->readUint16();
- _fileStream->skip(4); // unknown, almost always 0
- uint16 frameX = _fileStream->readUint16();
- uint16 frameY = _fileStream->readUint16();
-
- // TODO: In v4 robot files, frameX and frameY have a different meaning.
- // Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up
- // correctly.
- if (_header.version == 4)
- frameX = frameY = 0;
-
- uint16 compressedSize = _fileStream->readUint16();
- uint16 frameFragments = _fileStream->readUint16();
- _fileStream->skip(4); // unknown
- uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100;
-
- // FIXME: A frame's height + position can go off limits... why? With the
- // following, we cut the contents to fit the frame
- uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, surface->h - frameY);
-
- // FIXME: Same goes for the frame's width + position. In this case, we
- // modify the position to fit the contents on screen.
- if (frameWidth + frameX > surface->w)
- frameX = surface->w - frameWidth;
-
- assert(frameWidth + frameX <= surface->w && scaledHeight + frameY <= surface->h);
-
- DecompressorLZS lzs;
- byte *decompressedFrame = new byte[decompressedSize];
- byte *outPtr = decompressedFrame;
-
- if (_header.version == 4) {
- // v4 has just the one fragment, it seems, and ignores the fragment count
- Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedSize);
- lzs.unpack(&fragmentStream, outPtr, compressedSize, decompressedSize);
+void RobotDecoder::showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority) {
+ debugC(kDebugLevelVideo, "Show frame %d (%d %d %d)", frameNo, newX, newY, newPriority);
+
+ if (newX != kUnspecified) {
+ _position.x = newX;
+ }
+
+ if (newY != kUnspecified) {
+ _position.y = newY;
+ }
+
+ if (newPriority != kUnspecified) {
+ _priority = newPriority;
+ }
+
+ _currentFrameNo = frameNo;
+ pause();
+
+ if (frameNo != _previousFrameNo) {
+ seekToFrame(frameNo);
+ doVersion5(false);
} else {
- for (uint16 i = 0; i < frameFragments; ++i) {
- uint32 compressedFragmentSize = _fileStream->readUint32();
- uint32 decompressedFragmentSize = _fileStream->readUint32();
- uint16 compressionType = _fileStream->readUint16();
-
- if (compressionType == 0) {
- Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedFragmentSize);
- lzs.unpack(&fragmentStream, outPtr, compressedFragmentSize, decompressedFragmentSize);
- } else if (compressionType == 2) { // untested
- _fileStream->read(outPtr, compressedFragmentSize);
+ for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ if (_isHiRes) {
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[i].bitmapId);
+
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ if (scriptWidth == kLowResX && scriptHeight == kLowResY) {
+ const Ratio lowResToScreenX(screenWidth, kLowResX);
+ const Ratio lowResToScreenY(screenHeight, kLowResY);
+ const Ratio screenToLowResX(kLowResX, screenWidth);
+ const Ratio screenToLowResY(kLowResY, screenHeight);
+
+ const int16 scaledX = _originalScreenItemX[i] + (_position.x * lowResToScreenX).toInt();
+ const int16 scaledY1 = _originalScreenItemY[i] + (_position.y * lowResToScreenY).toInt();
+ const int16 scaledY2 = scaledY1 + bitmap.getHeight() - 1;
+
+ const int16 lowResX = (scaledX * screenToLowResX).toInt();
+ const int16 lowResY = (scaledY2 * screenToLowResY).toInt();
+
+ bitmap.setDisplace(Common::Point(
+ (scaledX - (lowResX * lowResToScreenX).toInt()) * -1,
+ (lowResY * lowResToScreenY).toInt() - scaledY1
+ ));
+
+ _screenItemX[i] = lowResX;
+ _screenItemY[i] = lowResY;
+ } else {
+ const int16 scaledX = _originalScreenItemX[i] + _position.x;
+ const int16 scaledY = _originalScreenItemY[i] + _position.y + bitmap.getHeight() - 1;
+ bitmap.setDisplace(Common::Point(0, bitmap.getHeight() - 1));
+ _screenItemX[i] = scaledX;
+ _screenItemY[i] = scaledY;
+ }
+ } else {
+ _screenItemX[i] = _originalScreenItemX[i] + _position.x;
+ _screenItemY[i] = _originalScreenItemY[i] + _position.y;
+ }
+
+ if (_screenItemList[i] == nullptr) {
+ CelInfo32 celInfo;
+ celInfo.type = kCelTypeMem;
+ celInfo.bitmap = _celHandles[i].bitmapId;
+ ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo);
+ _screenItemList[i] = screenItem;
+ screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]);
+ if (_priority == -1) {
+ screenItem->_fixedPriority = false;
+ } else {
+ screenItem->_priority = _priority;
+ screenItem->_fixedPriority = true;
+ }
+ g_sci->_gfxFrameout->addScreenItem(*screenItem);
} else {
- error("Unknown frame compression found: %d", compressionType);
+ ScreenItem *screenItem = _screenItemList[i];
+ screenItem->_celInfo.bitmap = _celHandles[i].bitmapId;
+ screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]);
+ if (_priority == -1) {
+ screenItem->_fixedPriority = false;
+ } else {
+ screenItem->_priority = _priority;
+ screenItem->_fixedPriority = true;
+ }
+ g_sci->_gfxFrameout->updateScreenItem(*screenItem);
+ }
+ }
+ }
+
+ _previousFrameNo = frameNo;
+}
+
+int16 RobotDecoder::getCue() const {
+ if (_status == kRobotStatusUninitialized ||
+ _status == kRobotStatusPaused ||
+ _syncFrame) {
+ return 0;
+ }
+
+ if (_status == kRobotStatusEnd) {
+ return -1;
+ }
+
+ const uint16 estimatedNextFrameNo = MIN(calculateNextFrameNo(_delayTime.predictedTicks()), _numFramesTotal);
+
+ for (int i = 0; i < kCueListSize; ++i) {
+ if (_cueTimes[i] != -1 && _cueTimes[i] <= estimatedNextFrameNo) {
+ if (_cueTimes[i] >= _previousFrameNo) {
+ _cueForceShowFrame = _cueTimes[i] + 1;
}
- outPtr += decompressedFragmentSize;
+ _cueTimes[i] = -1;
+ return _cueValues[i];
}
}
- // Copy over the decompressed frame
- byte *inFrame = decompressedFrame;
- byte *outFrame = (byte *)surface->getPixels();
+ return 0;
+}
- // Black out the surface
- memset(outFrame, 0, surface->w * surface->h);
+int16 RobotDecoder::getFrameNo() const {
+ if (_status == kRobotStatusUninitialized) {
+ return 0;
+ }
- // Move to the correct y coordinate
- outFrame += surface->w * frameY;
+ return _currentFrameNo;
+}
+
+RobotDecoder::RobotStatus RobotDecoder::getStatus() const {
+ return _status;
+}
- for (uint16 y = 0; y < scaledHeight; y++) {
- memcpy(outFrame + frameX, inFrame, frameWidth);
- inFrame += frameWidth;
- outFrame += surface->w;
+bool RobotDecoder::seekToFrame(const int frameNo) {
+ return _stream->seek(_recordPositions[frameNo], SEEK_SET);
+}
+
+void RobotDecoder::setRobotTime(const int frameNo) {
+ _startTime = getTickCount();
+ _startFrameNo = frameNo;
+}
+
+#pragma mark -
+#pragma mark RobotDecoder - Timing
+
+RobotDecoder::DelayTime::DelayTime(RobotDecoder *decoder) :
+ _decoder(decoder) {
+ for (int i = 0; i < kDelayListSize; ++i) {
+ _timestamps[i] = i;
+ _delays[i] = 0;
}
- delete[] decompressedFrame;
+ _oldestTimestamp = 0;
+ _newestTimestamp = kDelayListSize - 1;
+ _startTime = 0;
+}
- uint32 audioChunkSize = _frameTotalSize[videoTrack->getCurFrame()] - (24 + compressedSize);
+void RobotDecoder::DelayTime::startTiming() {
+ _startTime = _decoder->getTickCount();
+}
-// TODO: The audio chunk size below is usually correct, but there are some
-// exceptions (e.g. robot 4902 in Phantasmagoria, towards its end)
-#if 0
- // Read frame audio header (14 bytes)
- _fileStream->skip(2); // buffer position
- _fileStream->skip(2); // unknown (usually 1)
- _fileStream->skip(2); /*uint16 audioChunkSize = _fileStream->readUint16() + 8;*/
- _fileStream->skip(2);
-#endif
+void RobotDecoder::DelayTime::endTiming() {
+ const int timeDelta = _decoder->getTickCount() - _startTime;
+ for (uint i = 0; i < kDelayListSize; ++i) {
+ if (_timestamps[i] == _oldestTimestamp) {
+ _timestamps[i] = ++_newestTimestamp;
+ _delays[i] = timeDelta;
+ break;
+ }
+ }
+ ++_newestTimestamp;
+ _startTime = 0;
+ sortList();
+}
- // Queue the next audio frame
- // FIXME: For some reason, there are audio hiccups/gaps
- if (_header.hasSound) {
- RobotAudioTrack *audioTrack = (RobotAudioTrack *)getTrack(1);
- _fileStream->skip(8); // header
- audioChunkSize -= 8;
- audioTrack->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize), audioChunkSize * 2);
+bool RobotDecoder::DelayTime::timingInProgress() const {
+ return _startTime != 0;
+}
+
+int RobotDecoder::DelayTime::predictedTicks() const {
+ return _delays[kDelayListSize / 2];
+}
+
+void RobotDecoder::DelayTime::sortList() {
+ for (uint i = 0; i < kDelayListSize - 1; ++i) {
+ int smallestDelay = _delays[i];
+ uint smallestIndex = i;
+
+ for (uint j = i + 1; j < kDelayListSize - 1; ++j) {
+ if (_delays[j] < smallestDelay) {
+ smallestDelay = _delays[j];
+ smallestIndex = j;
+ }
+ }
+
+ if (smallestIndex != i) {
+ SWAP(_delays[i], _delays[smallestIndex]);
+ SWAP(_timestamps[i], _timestamps[smallestIndex]);
+ }
+ }
+}
+
+uint16 RobotDecoder::calculateNextFrameNo(const uint32 extraTicks) const {
+ return ticksToFrames(getTickCount() + extraTicks - _startTime) + _startFrameNo;
+}
+
+uint32 RobotDecoder::ticksToFrames(const uint32 ticks) const {
+ return (ticks * _frameRate) / 60;
+}
+
+uint32 RobotDecoder::getTickCount() const {
+ return g_sci->getTickCount();
+}
+
+#pragma mark -
+#pragma mark RobotDecoder - Audio
+
+RobotDecoder::AudioList::AudioList() :
+ _blocks(),
+ _blocksSize(0),
+ _oldestBlockIndex(0),
+ _newestBlockIndex(0),
+ _startOffset(0),
+ _status(kRobotAudioReady) {}
+
+void RobotDecoder::AudioList::startAudioNow() {
+ submitDriverMax();
+ g_sci->_audio32->resume(kRobotChannel);
+ _status = kRobotAudioPlaying;
+}
+
+void RobotDecoder::AudioList::stopAudio() {
+ g_sci->_audio32->finishRobotAudio();
+ freeAudioBlocks();
+ _status = kRobotAudioStopping;
+}
+
+void RobotDecoder::AudioList::stopAudioNow() {
+ if (_status == kRobotAudioPlaying || _status == kRobotAudioStopping || _status == kRobotAudioPaused) {
+ g_sci->_audio32->stopRobotAudio();
+ _status = kRobotAudioStopped;
+ }
+
+ freeAudioBlocks();
+}
+
+void RobotDecoder::AudioList::submitDriverMax() {
+ while (_blocksSize != 0) {
+ if (!_blocks[_oldestBlockIndex]->submit(_startOffset)) {
+ return;
+ }
+
+ delete _blocks[_oldestBlockIndex];
+ _blocks[_oldestBlockIndex] = nullptr;
+ ++_oldestBlockIndex;
+ if (_oldestBlockIndex == kAudioListSize) {
+ _oldestBlockIndex = 0;
+ }
+
+ --_blocksSize;
+ }
+}
+
+void RobotDecoder::AudioList::addBlock(const int position, const int size, const byte *data) {
+ assert(data != nullptr);
+ assert(size >= 0);
+ assert(position >= -1);
+
+ if (_blocksSize == kAudioListSize) {
+ delete _blocks[_oldestBlockIndex];
+ _blocks[_oldestBlockIndex] = nullptr;
+ ++_oldestBlockIndex;
+ if (_oldestBlockIndex == kAudioListSize) {
+ _oldestBlockIndex = 0;
+ }
+ --_blocksSize;
+ }
+
+ if (_blocksSize == 0) {
+ _oldestBlockIndex = _newestBlockIndex = 0;
} else {
- _fileStream->skip(audioChunkSize);
- }
-}
-
-void RobotDecoder::readHeaderChunk() {
- // Header (60 bytes)
- _fileStream->skip(6);
- _header.version = _fileStream->readUint16();
- _header.audioChunkSize = _fileStream->readUint16();
- _header.audioSilenceSize = _fileStream->readUint16();
- _fileStream->skip(2);
- _header.frameCount = _fileStream->readUint16();
- _header.paletteDataSize = _fileStream->readUint16();
- _header.unkChunkDataSize = _fileStream->readUint16();
- _fileStream->skip(5);
- _header.hasSound = _fileStream->readByte();
- _fileStream->skip(34);
-
- // Some videos (e.g. robot 1305 in Phantasmagoria and
- // robot 184 in Lighthouse) have an unknown chunk before
- // the palette chunk (probably used for sound preloading).
- // Skip it here.
- if (_header.unkChunkDataSize)
- _fileStream->skip(_header.unkChunkDataSize);
-}
-
-void RobotDecoder::readFrameSizesChunk() {
- // The robot video file contains 2 tables, with one entry for each frame:
- // - A table containing the size of the image in each video frame
- // - A table containing the total size of each video frame.
- // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots,
- // they contain 32-bit integers.
-
- _frameTotalSize = new uint32[_header.frameCount];
-
- // TODO: The table reading code can probably be removed once the
- // audio chunk size is figured out (check the TODO inside processNextFrame())
-#if 0
- // We don't need any of the two tables to play the video, so we ignore
- // both of them.
- uint16 wordSize = _header.version == 6 ? 4 : 2;
- _fileStream->skip(_header.frameCount * wordSize * 2);
-#else
- switch (_header.version) {
- case 4:
- case 5: // sizes are 16-bit integers
- // Skip table with frame image sizes, as we don't need it
- _fileStream->skip(_header.frameCount * 2);
- for (int i = 0; i < _header.frameCount; ++i)
- _frameTotalSize[i] = _fileStream->readUint16();
- break;
- case 6: // sizes are 32-bit integers
- // Skip table with frame image sizes, as we don't need it
- _fileStream->skip(_header.frameCount * 4);
- for (int i = 0; i < _header.frameCount; ++i)
- _frameTotalSize[i] = _fileStream->readUint32();
- break;
- default:
- error("Can't yet handle index table for robot version %d", _header.version);
+ ++_newestBlockIndex;
+ if (_newestBlockIndex == kAudioListSize) {
+ _newestBlockIndex = 0;
+ }
}
-#endif
- // 2 more unknown tables
- _fileStream->skip(1024 + 512);
+ _blocks[_newestBlockIndex] = new AudioBlock(position, size, data);
+ ++_blocksSize;
+}
- // Pad to nearest 2 kilobytes
- uint32 curPos = _fileStream->pos();
- if (curPos & 0x7ff)
- _fileStream->seek((curPos & ~0x7ff) + 2048);
+void RobotDecoder::AudioList::reset() {
+ stopAudioNow();
+ _startOffset = 0;
+ _status = kRobotAudioReady;
}
-RobotDecoder::RobotVideoTrack::RobotVideoTrack(int frameCount) : _frameCount(frameCount) {
- _surface = new Graphics::Surface();
- _curFrame = -1;
- _dirtyPalette = false;
+void RobotDecoder::AudioList::prepareForPrimer() {
+ g_sci->_audio32->pause(kRobotChannel);
+ _status = kRobotAudioPaused;
}
-RobotDecoder::RobotVideoTrack::~RobotVideoTrack() {
- _surface->free();
- delete _surface;
+void RobotDecoder::AudioList::setAudioOffset(const int offset) {
+ _startOffset = offset;
}
-uint16 RobotDecoder::RobotVideoTrack::getWidth() const {
- return _surface->w;
+RobotDecoder::AudioList::AudioBlock::AudioBlock(const int position, const int size, const byte* const data) :
+ _position(position),
+ _size(size) {
+ _data = (byte *)malloc(size);
+ memcpy(_data, data, size);
}
-uint16 RobotDecoder::RobotVideoTrack::getHeight() const {
- return _surface->h;
+RobotDecoder::AudioList::AudioBlock::~AudioBlock() {
+ free(_data);
}
-Graphics::PixelFormat RobotDecoder::RobotVideoTrack::getPixelFormat() const {
- return _surface->format;
+bool RobotDecoder::AudioList::AudioBlock::submit(const int startOffset) {
+ assert(_data != nullptr);
+ RobotAudioStream::RobotAudioPacket packet(_data, _size, (_position - startOffset) * 2);
+ return g_sci->_audio32->playRobotAudio(packet);
}
-void RobotDecoder::RobotVideoTrack::readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize) {
- byte *paletteData = new byte[chunkSize];
- stream->read(paletteData, chunkSize);
+void RobotDecoder::AudioList::freeAudioBlocks() {
+ while (_blocksSize != 0) {
+ delete _blocks[_oldestBlockIndex];
+ _blocks[_oldestBlockIndex] = nullptr;
+ ++_oldestBlockIndex;
+ if (_oldestBlockIndex == kAudioListSize) {
+ _oldestBlockIndex = 0;
+ }
+
+ --_blocksSize;
+ }
+}
- // SCI1.1 palette
- byte palFormat = paletteData[32];
- uint16 palColorStart = paletteData[25];
- uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29);
+bool RobotDecoder::primeAudio(const uint32 startTick) {
+ bool success = true;
+ _audioList.reset();
+
+ if (startTick == 0) {
+ _audioList.prepareForPrimer();
+ byte *evenPrimerBuff = new byte[_evenPrimerSize];
+ byte *oddPrimerBuff = new byte[_oddPrimerSize];
+
+ success = readPrimerData(evenPrimerBuff, oddPrimerBuff);
+ if (success) {
+ if (_evenPrimerSize != 0) {
+ _audioList.addBlock(0, _evenPrimerSize, evenPrimerBuff);
+ }
+ if (_oddPrimerSize != 0) {
+ _audioList.addBlock(1, _oddPrimerSize, oddPrimerBuff);
+ }
+ }
+
+ delete[] evenPrimerBuff;
+ delete[] oddPrimerBuff;
+ } else {
+ assert(_evenPrimerSize * 2 >= _audioRecordInterval || _oddPrimerSize * 2 >= _audioRecordInterval);
+
+ int audioStartFrame = 0;
+ int videoStartFrame = startTick * _frameRate / 60;
+ assert(videoStartFrame < _numFramesTotal);
+
+ int audioStartPosition = (startTick * RobotAudioStream::kRobotSampleRate) / 60;
+ if (audioStartPosition & 1) {
+ audioStartPosition--;
+ }
+ _audioList.setAudioOffset(audioStartPosition);
+ _audioList.prepareForPrimer();
+
+ if (audioStartPosition < _evenPrimerSize * 2 ||
+ audioStartPosition + 1 < _oddPrimerSize * 2) {
+
+ byte *evenPrimerBuffer = new byte[_evenPrimerSize];
+ byte *oddPrimerBuffer = new byte[_oddPrimerSize];
+ success = readPrimerData(evenPrimerBuffer, oddPrimerBuffer);
+ if (success) {
+ int halfAudioStartPosition = audioStartPosition / 2;
+ if (audioStartPosition < _evenPrimerSize * 2) {
+ _audioList.addBlock(audioStartPosition, _evenPrimerSize - halfAudioStartPosition, &evenPrimerBuffer[halfAudioStartPosition]);
+ }
+
+ if (audioStartPosition + 1 < _oddPrimerSize * 2) {
+ _audioList.addBlock(audioStartPosition + 1, _oddPrimerSize - halfAudioStartPosition, &oddPrimerBuffer[halfAudioStartPosition]);
+ }
+ }
+
+ delete[] evenPrimerBuffer;
+ delete[] oddPrimerBuffer;
+ }
- int palOffset = 37;
- memset(_palette, 0, 256 * 3);
+ if (audioStartPosition >= _firstAudioRecordPosition) {
+ int audioRecordSize = _expectedAudioBlockSize;
+ assert(audioRecordSize > 0);
+ assert(_audioRecordInterval > 0);
+ assert(_firstAudioRecordPosition >= 0);
- for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) {
- if (palFormat == kRobotPalVariable)
- palOffset++;
- _palette[colorNo * 3 + 0] = paletteData[palOffset++];
- _palette[colorNo * 3 + 1] = paletteData[palOffset++];
- _palette[colorNo * 3 + 2] = paletteData[palOffset++];
+ audioStartFrame = (audioStartPosition - _firstAudioRecordPosition) / _audioRecordInterval;
+ assert(audioStartFrame < videoStartFrame);
+
+ if (audioStartFrame > 0) {
+ int lastAudioFrame = audioStartFrame - 1;
+ int oddRemainder = lastAudioFrame & 1;
+ int audioRecordStart = (lastAudioFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition;
+ int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition;
+
+ if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) {
+ --audioStartFrame;
+ }
+ }
+
+ assert(!(audioStartPosition & 1));
+ if (audioStartFrame & 1) {
+ ++audioStartPosition;
+ }
+
+ if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition)) {
+ return false;
+ }
+
+ ++audioStartFrame;
+ assert(audioStartFrame < videoStartFrame);
+
+ int oddRemainder = audioStartFrame & 1;
+ int audioRecordStart = (audioStartFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition;
+ int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition;
+
+ if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) {
+ if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition + 1)) {
+ return false;
+ }
+
+ ++audioStartFrame;
+ }
+ }
+
+ int audioPosition, audioSize;
+ for (int i = audioStartFrame; i < videoStartFrame; i++) {
+ if (!readAudioDataFromRecord(i, _audioBuffer, audioPosition, audioSize)) {
+ break;
+ }
+
+ _audioList.addBlock(audioPosition, audioSize, _audioBuffer);
+ }
}
- _dirtyPalette = true;
- delete[] paletteData;
+ return success;
}
-void RobotDecoder::RobotVideoTrack::calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes) {
- // This is an O(n) operation, as each frame has a different size.
- // We need to know the actual frame size to have a constant video size.
- uint32 pos = stream->pos();
+bool RobotDecoder::readPrimerData(byte *outEvenBuffer, byte *outOddBuffer) {
+ if (_primerReservedSize != 0) {
+ if (_totalPrimerSize != 0) {
+ _stream->seek(_primerPosition, SEEK_SET);
+ if (_evenPrimerSize > 0) {
+ _stream->read(outEvenBuffer, _evenPrimerSize);
+ }
+
+ if (_oddPrimerSize > 0) {
+ _stream->read(outOddBuffer, _oddPrimerSize);
+ }
+ }
+ } else if (_primerZeroCompressFlag) {
+ memset(outEvenBuffer, 0, _evenPrimerSize);
+ memset(outOddBuffer, 0, _oddPrimerSize);
+ } else {
+ error("ReadPrimerData - Flags corrupt");
+ }
+
+ return !_stream->err();
+}
+
+bool RobotDecoder::readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize) {
+ _stream->seek(_recordPositions[frameNo] + _videoSizes[frameNo], SEEK_SET);
+ _audioList.submitDriverMax();
+
+ // Compressed absolute position of the audio block in the audio stream
+ const int position = _stream->readSint32();
- uint16 width = 0, height = 0;
+ // Size of the block of audio, excluding the audio block header
+ int size = _stream->readSint32();
- for (int curFrame = 0; curFrame < _frameCount; curFrame++) {
- stream->skip(4);
- uint16 frameWidth = stream->readUint16();
- uint16 frameHeight = stream->readUint16();
- if (frameWidth > width)
- width = frameWidth;
- if (frameHeight > height)
- height = frameHeight;
- stream->skip(frameSizes[curFrame] - 8);
+ assert(size <= _expectedAudioBlockSize);
+
+ if (position == 0) {
+ return false;
}
- stream->seek(pos);
+ if (size != _expectedAudioBlockSize) {
+ memset(outBuffer, 0, kRobotZeroCompressSize);
+ _stream->read(outBuffer + kRobotZeroCompressSize, size);
+ size += kRobotZeroCompressSize;
+ } else {
+ _stream->read(outBuffer, size);
+ }
- _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8());
+ outAudioPosition = position;
+ outAudioSize = size;
+ return !_stream->err();
}
-RobotDecoder::RobotAudioTrack::RobotAudioTrack() {
- _audioStream = Audio::makeQueuingAudioStream(11025, false);
+bool RobotDecoder::readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition) {
+ int audioPosition, audioSize;
+ bool success = readAudioDataFromRecord(startFrame, _audioBuffer, audioPosition, audioSize);
+ if (success) {
+ const int relativeStartOffset = (startPosition - audioPosition) / 2;
+ _audioList.addBlock(startPosition, audioSize - relativeStartOffset, _audioBuffer + relativeStartOffset);
+ }
+
+ return success;
}
-RobotDecoder::RobotAudioTrack::~RobotAudioTrack() {
- delete _audioStream;
+#pragma mark -
+#pragma mark RobotDecoder - Rendering
+
+uint16 RobotDecoder::getFrameSize(Common::Rect &outRect) const {
+ outRect.clip(0, 0);
+ for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) {
+ ScreenItem &screenItem = *_screenItemList[i];
+ outRect.extend(screenItem.getNowSeenRect(*_plane));
+ }
+
+ return _numFramesTotal;
}
-void RobotDecoder::RobotAudioTrack::queueBuffer(byte *buffer, int size) {
- _audioStream->queueBuffer(buffer, size, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN);
+void RobotDecoder::doRobot() {
+ if (_status != kRobotStatusPlaying) {
+ return;
+ }
+
+ if (!_syncFrame) {
+ if (_cueForceShowFrame != -1) {
+ _currentFrameNo = _cueForceShowFrame;
+ _cueForceShowFrame = -1;
+ } else {
+ const int nextFrameNo = calculateNextFrameNo(_delayTime.predictedTicks());
+ if (nextFrameNo < _currentFrameNo) {
+ return;
+ }
+ _currentFrameNo = nextFrameNo;
+ }
+ }
+
+ if (_currentFrameNo >= _numFramesTotal) {
+ const int finalFrameNo = _numFramesTotal - 1;
+ if (_previousFrameNo == finalFrameNo) {
+ _status = kRobotStatusEnd;
+ if (_hasAudio) {
+ _audioList.stopAudio();
+ _frameRate = _normalFrameRate;
+ _hasAudio = false;
+ }
+ return;
+ } else {
+ _currentFrameNo = finalFrameNo;
+ }
+ }
+
+ if (_currentFrameNo == _previousFrameNo) {
+ _audioList.submitDriverMax();
+ return;
+ }
+
+ if (_hasAudio) {
+ for (int candidateFrameNo = _previousFrameNo + _maxSkippablePackets + 1; candidateFrameNo < _currentFrameNo; candidateFrameNo += _maxSkippablePackets + 1) {
+
+ _audioList.submitDriverMax();
+
+ int audioPosition, audioSize;
+ if (readAudioDataFromRecord(candidateFrameNo, _audioBuffer, audioPosition, audioSize)) {
+ _audioList.addBlock(audioPosition, audioSize, _audioBuffer);
+ }
+ }
+ _audioList.submitDriverMax();
+ }
+
+ _delayTime.startTiming();
+ seekToFrame(_currentFrameNo);
+ doVersion5();
+ if (_hasAudio) {
+ _audioList.submitDriverMax();
+ }
+}
+
+void RobotDecoder::frameAlmostVisible() {
+ if (_status == kRobotStatusPlaying && !_syncFrame) {
+ if (_previousFrameNo != _currentFrameNo) {
+ while (calculateNextFrameNo() < _currentFrameNo) {
+ _audioList.submitDriverMax();
+ }
+ }
+ }
+}
+
+void RobotDecoder::frameNowVisible() {
+ if (_status != kRobotStatusPlaying) {
+ return;
+ }
+
+ if (_syncFrame) {
+ _syncFrame = false;
+ if (_hasAudio) {
+ _audioList.startAudioNow();
+ _checkAudioSyncTime = _startTime + kAudioSyncCheckInterval;
+ }
+
+ setRobotTime(_currentFrameNo);
+ }
+
+ if (_delayTime.timingInProgress()) {
+ _delayTime.endTiming();
+ }
+
+ if (_hasAudio) {
+ _audioList.submitDriverMax();
+ }
+
+ if (_previousFrameNo != _currentFrameNo) {
+ _previousFrameNo = _currentFrameNo;
+ }
+
+ if (!_syncFrame && _hasAudio && getTickCount() >= _checkAudioSyncTime) {
+ RobotAudioStream::StreamState status;
+ const bool success = g_sci->_audio32->queryRobotAudio(status);
+ if (!success) {
+ return;
+ }
+
+ const int bytesPerFrame = status.rate / _normalFrameRate * (status.bits == 16 ? 2 : 1);
+ // check again in 1/3rd second
+ _checkAudioSyncTime = getTickCount() + 60 / 3;
+
+ const int currentVideoFrameNo = calculateNextFrameNo() - _startingFrameNo;
+ const int currentAudioFrameNo = status.bytesPlaying / bytesPerFrame;
+ debugC(kDebugLevelVideo, "Video frame %d %s audio frame %d", currentVideoFrameNo, currentVideoFrameNo == currentAudioFrameNo ? "=" : currentVideoFrameNo < currentAudioFrameNo ? "<" : ">", currentAudioFrameNo);
+ if (currentVideoFrameNo < _numFramesTotal &&
+ currentAudioFrameNo < _numFramesTotal) {
+
+ bool shouldResetRobotTime = false;
+
+ if (currentAudioFrameNo < currentVideoFrameNo - 1 && _frameRate != _minFrameRate) {
+ debugC(kDebugLevelVideo, "[v] Reducing frame rate");
+ _frameRate = _minFrameRate;
+ shouldResetRobotTime = true;
+ } else if (currentAudioFrameNo > currentVideoFrameNo + 1 && _frameRate != _maxFrameRate) {
+ debugC(kDebugLevelVideo, "[^] Increasing frame rate");
+ _frameRate = _maxFrameRate;
+ shouldResetRobotTime = true;
+ } else if (_frameRate != _normalFrameRate) {
+ debugC(kDebugLevelVideo, "[=] Setting to normal frame rate");
+ _frameRate = _normalFrameRate;
+ shouldResetRobotTime = true;
+ }
+
+ if (shouldResetRobotTime) {
+ if (currentAudioFrameNo < _currentFrameNo) {
+ setRobotTime(_currentFrameNo);
+ } else {
+ setRobotTime(currentAudioFrameNo);
+ }
+ }
+ }
+ }
+}
+
+void RobotDecoder::expandCel(byte* target, const byte* source, const int16 celWidth, const int16 celHeight) const {
+ assert(source != nullptr && target != nullptr);
+
+ const int sourceHeight = (celHeight * _verticalScaleFactor) / 100;
+ assert(sourceHeight > 0);
+
+ const int16 numerator = celHeight;
+ const int16 denominator = sourceHeight;
+ int remainder = 0;
+ for (int16 y = sourceHeight - 1; y >= 0; --y) {
+ remainder += numerator;
+ int16 linesToDraw = remainder / denominator;
+ remainder %= denominator;
+
+ while (linesToDraw--) {
+ memcpy(target, source, celWidth);
+ target += celWidth;
+ }
+
+ source += celWidth;
+ }
+}
+
+void RobotDecoder::setPriority(const int16 newPriority) {
+ _priority = newPriority;
+}
+
+void RobotDecoder::doVersion5(const bool shouldSubmitAudio) {
+ const RobotScreenItemList::size_type oldScreenItemCount = _screenItemList.size();
+ const int videoSize = _videoSizes[_currentFrameNo];
+ _doVersion5Scratch.resize(videoSize);
+
+ byte *videoFrameData = _doVersion5Scratch.begin();
+
+ if (!_stream->read(videoFrameData, videoSize)) {
+ error("RobotDecoder::doVersion5: Read error");
+ }
+
+ const RobotScreenItemList::size_type screenItemCount = READ_SCI11ENDIAN_UINT16(videoFrameData);
+
+ if (screenItemCount > kScreenItemListSize) {
+ return;
+ }
+
+ if (_hasAudio &&
+ (getSciVersion() < SCI_VERSION_3 || shouldSubmitAudio)) {
+ int audioPosition, audioSize;
+ if (readAudioDataFromRecord(_currentFrameNo, _audioBuffer, audioPosition, audioSize)) {
+ _audioList.addBlock(audioPosition, audioSize, _audioBuffer);
+ }
+ }
+
+ if (screenItemCount > oldScreenItemCount) {
+ _screenItemList.resize(screenItemCount);
+ _screenItemX.resize(screenItemCount);
+ _screenItemY.resize(screenItemCount);
+ _originalScreenItemX.resize(screenItemCount);
+ _originalScreenItemY.resize(screenItemCount);
+ }
+
+ createCels5(videoFrameData + 2, screenItemCount, true);
+ for (RobotScreenItemList::size_type i = 0; i < screenItemCount; ++i) {
+ Common::Point position(_screenItemX[i], _screenItemY[i]);
+
+// TODO: Version 6 robot?
+// int scaleXRemainder;
+ if (_scaleInfo.signal == kScaleSignalDoScaling32) {
+ position.x = (position.x * _scaleInfo.x) / 128;
+// TODO: Version 6 robot?
+// scaleXRemainder = (position.x * _scaleInfo.x) % 128;
+ position.y = (position.y * _scaleInfo.y) / 128;
+ }
+
+ if (_screenItemList[i] == nullptr) {
+ CelInfo32 celInfo;
+ celInfo.bitmap = _celHandles[i].bitmapId;
+ ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo, position, _scaleInfo);
+ _screenItemList[i] = screenItem;
+ // TODO: Version 6 robot?
+ // screenItem->_field_30 = scaleXRemainder;
+
+ if (_priority == -1) {
+ screenItem->_fixedPriority = false;
+ } else {
+ screenItem->_fixedPriority = true;
+ screenItem->_priority = _priority;
+ }
+ g_sci->_gfxFrameout->addScreenItem(*screenItem);
+ } else {
+ ScreenItem *screenItem = _screenItemList[i];
+ screenItem->_celInfo.bitmap = _celHandles[i].bitmapId;
+ screenItem->_position = position;
+ // TODO: Version 6 robot?
+ // screenItem->_field_30 = scaleXRemainder;
+
+ if (_priority == -1) {
+ screenItem->_fixedPriority = false;
+ } else {
+ screenItem->_fixedPriority = true;
+ screenItem->_priority = _priority;
+ }
+ g_sci->_gfxFrameout->updateScreenItem(*screenItem);
+ }
+ }
+
+ for (RobotScreenItemList::size_type i = screenItemCount; i < oldScreenItemCount; ++i) {
+ if (_screenItemList[i] != nullptr) {
+ g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]);
+ _screenItemList[i] = nullptr;
+ }
+ }
+}
+
+void RobotDecoder::createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette) {
+ preallocateCelMemory(rawVideoData, numCels);
+ for (int16 i = 0; i < numCels; ++i) {
+ rawVideoData += createCel5(rawVideoData, i, usePalette);
+ }
+}
+
+uint32 RobotDecoder::createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette) {
+ _verticalScaleFactor = rawVideoData[1];
+ const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2);
+ const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4);
+ const Common::Point celPosition((int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 10),
+ (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 12));
+ const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14);
+ const int16 numDataChunks = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 16);
+
+ rawVideoData += kCelHeaderSize;
+
+ const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth;
+ const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight;
+ const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth;
+ const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight;
+
+ Common::Point displace;
+ if (scriptWidth == kLowResX && scriptHeight == kLowResY) {
+ const Ratio lowResToScreenX(screenWidth, kLowResX);
+ const Ratio lowResToScreenY(screenHeight, kLowResY);
+ const Ratio screenToLowResX(kLowResX, screenWidth);
+ const Ratio screenToLowResY(kLowResY, screenHeight);
+
+ const int16 scaledX = celPosition.x + (_position.x * lowResToScreenX).toInt();
+ const int16 scaledY1 = celPosition.y + (_position.y * lowResToScreenY).toInt();
+ const int16 scaledY2 = scaledY1 + celHeight - 1;
+
+ const int16 lowResX = (scaledX * screenToLowResX).toInt();
+ const int16 lowResY = (scaledY2 * screenToLowResY).toInt();
+
+ displace.x = (scaledX - (lowResX * lowResToScreenX).toInt()) * -1;
+ displace.y = (lowResY * lowResToScreenY).toInt() - scaledY1;
+ _screenItemX[screenItemIndex] = lowResX;
+ _screenItemY[screenItemIndex] = lowResY;
+
+ debugC(kDebugLevelVideo, "Low resolution position c: %d %d l: %d/%d %d/%d d: %d %d s: %d/%d %d/%d x: %d y: %d", celPosition.x, celPosition.y, lowResX, scriptWidth, lowResY, scriptHeight, displace.x, displace.y, scaledX, screenWidth, scaledY2, screenHeight, scaledX - displace.x, scaledY2 - displace.y);
+ } else {
+ const int16 highResX = celPosition.x + _position.x;
+ const int16 highResY = celPosition.y + _position.y + celHeight - 1;
+
+ displace.x = 0;
+ displace.y = celHeight - 1;
+ _screenItemX[screenItemIndex] = highResX;
+ _screenItemY[screenItemIndex] = highResY;
+
+ debugC(kDebugLevelVideo, "High resolution position c: %d %d s: %d %d d: %d %d", celPosition.x, celPosition.y, highResX, highResY, displace.x, displace.y);
+ }
+
+ _originalScreenItemX[screenItemIndex] = celPosition.x;
+ _originalScreenItemY[screenItemIndex] = celPosition.y;
+
+ assert(_celHandles[screenItemIndex].area >= celWidth * celHeight);
+
+ SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[screenItemIndex].bitmapId);
+ assert(bitmap.getWidth() == celWidth && bitmap.getHeight() == celHeight);
+ assert(bitmap.getScaledWidth() == _xResolution && bitmap.getScaledHeight() == _yResolution);
+ assert(bitmap.getHunkPaletteOffset() == (uint32)bitmap.getWidth() * bitmap.getHeight() + SciBitmap::getBitmapHeaderSize());
+ bitmap.setDisplace(displace);
+
+ byte *targetBuffer = nullptr;
+ if (_verticalScaleFactor == 100) {
+ // direct copy to bitmap
+ targetBuffer = bitmap.getPixels();
+ } else {
+ // go through squashed cel decompressor
+ _celDecompressionBuffer.resize(_celDecompressionArea >= celWidth * (celHeight * _verticalScaleFactor / 100));
+ targetBuffer = _celDecompressionBuffer.begin();
+ }
+
+ for (int i = 0; i < numDataChunks; ++i) {
+ uint compressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData);
+ uint decompressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData + 4);
+ uint16 compressionType = READ_SCI11ENDIAN_UINT16(rawVideoData + 8);
+ rawVideoData += 10;
+
+ switch (compressionType) {
+ case kCompressionLZS: {
+ Common::MemoryReadStream videoDataStream(rawVideoData, compressedSize, DisposeAfterUse::NO);
+ _decompressor.unpack(&videoDataStream, targetBuffer, compressedSize, decompressedSize);
+ break;
+ }
+ case kCompressionNone:
+ Common::copy(rawVideoData, rawVideoData + decompressedSize, targetBuffer);
+ break;
+ default:
+ error("Unknown compression type %d!", compressionType);
+ }
+
+ rawVideoData += compressedSize;
+ targetBuffer += decompressedSize;
+ }
+
+ if (_verticalScaleFactor != 100) {
+ expandCel(bitmap.getPixels(), _celDecompressionBuffer.begin(), celWidth, celHeight);
+ }
+
+ if (usePalette) {
+ Common::copy(_rawPalette, _rawPalette + kRawPaletteSize, bitmap.getHunkPalette());
+ }
+
+ return kCelHeaderSize + dataSize;
}
-Audio::AudioStream *RobotDecoder::RobotAudioTrack::getAudioStream() const {
- return _audioStream;
+void RobotDecoder::preallocateCelMemory(const byte *rawVideoData, const int16 numCels) {
+ for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) {
+ CelHandleInfo &celHandle = _celHandles[i];
+
+ if (celHandle.status == CelHandleInfo::kFrameLifetime) {
+ _segMan->freeBitmap(celHandle.bitmapId);
+ celHandle.bitmapId = NULL_REG;
+ celHandle.status = CelHandleInfo::kNoCel;
+ celHandle.area = 0;
+ }
+ }
+ _celHandles.resize(numCels);
+
+ const int numFixedCels = MIN(numCels, (int16)kFixedCelListSize);
+ for (int i = 0; i < numFixedCels; ++i) {
+ CelHandleInfo &celHandle = _celHandles[i];
+
+ // NOTE: There was a check to see if the cel handle was not allocated
+ // here, for some reason, which would mean that nothing was ever
+ // allocated from fixed cels, because the _celHandles array just got
+ // deleted and recreated...
+ if (celHandle.bitmapId == NULL_REG) {
+ break;
+ }
+
+ celHandle.bitmapId = _fixedCels[i];
+ celHandle.status = CelHandleInfo::kRobotLifetime;
+ celHandle.area = _maxCelArea[i];
+ }
+
+ uint maxFrameArea = 0;
+ for (int i = 0; i < numCels; ++i) {
+ const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2);
+ const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4);
+ const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14);
+ const uint area = celWidth * celHeight;
+
+ if (area > maxFrameArea) {
+ maxFrameArea = area;
+ }
+
+ CelHandleInfo &celHandle = _celHandles[i];
+ if (celHandle.status == CelHandleInfo::kRobotLifetime) {
+ if (_maxCelArea[i] < area) {
+ _segMan->freeBitmap(celHandle.bitmapId);
+ _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false);
+ celHandle.area = area;
+ celHandle.status = CelHandleInfo::kFrameLifetime;
+ }
+ } else if (celHandle.status == CelHandleInfo::kNoCel) {
+ _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false);
+ celHandle.area = area;
+ celHandle.status = CelHandleInfo::kFrameLifetime;
+ } else {
+ error("Cel Handle has bad status");
+ }
+
+ rawVideoData += kCelHeaderSize + dataSize;
+ }
+
+ if (maxFrameArea > _celDecompressionBuffer.size()) {
+ _celDecompressionBuffer.resize(maxFrameArea);
+ }
}
} // End of namespace Sci
diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h
index 4faea5008a..9d8c720968 100644
--- a/engines/sci/video/robot_decoder.h
+++ b/engines/sci/video/robot_decoder.h
@@ -20,109 +20,1412 @@
*
*/
-#ifndef SCI_VIDEO_ROBOT_DECODER_H
-#define SCI_VIDEO_ROBOT_DECODER_H
+#ifndef SCI_SOUND_DECODERS_ROBOT_H
+#define SCI_SOUND_DECODERS_ROBOT_H
-#include "common/rational.h"
-#include "common/rect.h"
-#include "video/video_decoder.h"
+#include "audio/audiostream.h" // for AudioStream
+#include "audio/rate.h" // for st_sample_t
+#include "common/array.h" // for Array
+#include "common/mutex.h" // for StackLock, Mutex
+#include "common/rect.h" // for Point, Rect (ptr only)
+#include "common/scummsys.h" // for int16, int32, byte, uint16
+#include "sci/engine/vm_types.h" // for NULL_REG, reg_t
+#include "sci/graphics/helpers.h" // for GuiResourceId
+#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem (ptr o...
-namespace Audio {
-class QueuingAudioStream;
-}
+namespace Common { class SeekableSubReadStreamEndian; }
+namespace Sci {
+class Plane;
+class SegManager;
-namespace Common {
-class SeekableSubReadStreamEndian;
-}
+// There were 3 different Robot video versions, used in the following games:
+// - v4: PQ:SWAT demo
+// - v5: KQ7 DOS, Phantasmagoria, PQ:SWAT, Lighthouse
+// - v6: RAMA
+//
+// Notes on Robot v5/v6 format:
+//
+// Robot is a packetized streaming AV format that encodes multiple bitmaps +
+// positioning data, plus synchronised audio, for rendering in the SCI graphics
+// system.
+//
+// Unlike traditional AV formats, Robot videos almost always require playback
+// within the game engine because certain information (like the resolution of
+// the Robot coordinates and the background for the video) is dependent on data
+// that does not exist within the Robot file itself.
+//
+// The Robot container consists of a file header, an optional primer audio
+// section, an optional colour palette, a frame seek index, a set of cuepoints,
+// and variable-sized packets of compressed video+audio data.
+//
+// Integers in Robot files are coded using native endianness (LSB for x86
+// versions, MSB for 68k/PPC versions).
+//
+// Robot video coding is a relatively simple variable-length compression with no
+// interframe compression. Each cel in a frame is constructed from multiple
+// contiguous data blocks, each of which can be independently compressed with
+// LZS or left uncompressed. An entire cel can also be line decimated, where
+// lines are deleted from the source bitmap at compression time and are
+// reconstructed by decompression using line doubling. Each cel also includes
+// coordinates where it should be placed within the video frame, relative to the
+// top-left corner of the frame.
+//
+// Audio coding is fixed-length, and all audio blocks except for the primer
+// audio are the same size. Audio is encoded with Sierra SOL DPCM16 compression,
+// and is split into two channels ('even' and 'odd'), each at a 11025Hz sample
+// rate. The original signal is restored by interleaving samples from the two
+// channels together. Channel packets are 'even' if they have an ''absolute
+// position of audio'' that is evenly divisible by 2; otherwise, they are 'odd'.
+// Because the channels use DPCM compression, there is an 8-byte runway at the
+// start of every audio block that is never written to the output stream, which
+// is used to move the signal to the correct location by the 9th sample.
+//
+// File header (v5/v6):
+//
+// byte | description
+// 0 | signature 0x16
+// 1 | unused
+// 2-5 | signature 'SOL\0'
+// 6-7 | version (4, 5, and 6 are the only known versions)
+// 8-9 | size of audio blocks
+// 10-11 | primer is compressed flag
+// 12-13 | unused
+// 14-15 | total number of video frames
+// 16-17 | embedded palette size, in bytes
+// 18-19 | primer reserved size
+// 20-21 | coordinate X-resolution (if 0, uses game coordinates)
+// 22-23 | coordinate Y-resolution (if 0, uses game coordinates)
+// 24 | if non-zero, Robot includes a palette
+// 25 | if non-zero, Robot includes audio
+// 26-27 | unused
+// 28-29 | the frame rate, in frames per second
+// 30-31 | coordinate conversion flag; if true, screen item coordinates
+// | from the robot should be used as-is with NO conversion when
+// | explicitly displaying a specific frame
+// 32-33 | the maximum number of packets that can be skipped without causing
+// | audio drop-out
+// 34-35 | the maximum possible number of cels that will be displayed in any
+// | frame of the robot
+// 36-39 | the maximum possible size, in bytes, of the first fixed cel
+// 40-43 | the maximum possible size, in bytes, of the second fixed cel
+// 44-47 | the maximum possible size, in bytes, of the third fixed cel
+// 48-51 | the maximum possible size, in bytes, of the fourth fixed cel
+// 52-59 | unused
+//
+// If the ''file includes audio'' flag is false, seek ''primer reserved size''
+// bytes from the end of the file header to get past a padding zone.
+//
+// If the ''file includes audio'' flag is true, and the ''primer reserved size''
+// is not zero, the data immediately after the file header consists of an audio
+// primer header plus compressed audio data:
+//
+// Audio primer header:
+//
+// byte | description
+// 0-3 | the size, in bytes, of the entire primer audio section
+// 4-5 | the compression format of the primer audio (must be zero)
+// 6-9 | the size, in bytes, of the "even" primer
+// 10-13 | the size, in bytes, of the "odd" primer
+//
+// If the combined sizes of the even and odd primers do not match the ''primer
+// reserved size'', the next header block can be found ''primer reserved size''
+// bytes from the *start* of the audio primer header.
+//
+// Otherwise, if the Robot has audio, and the ''primer reserved size'' is zero,
+// and the ''primer is compressed flag'' is set, the "even" primer size is
+// 19922, the "odd" primer size is 21024, and the "even" and "odd" buffers
+// should be zero-filled.
+//
+// Any other combination of these flags is an error.
+//
+// If the Robot has a palette, the next ''palette size'' bytes should be read
+// as a SCI HunkPalette. Otherwise, seek ''palette size'' bytes from the current
+// position to get to the frame index.
+//
+// The next section of the Robot is the video frame size index. In version 5
+// robots, read ''total number of frames'' 16-bit integers to get the size of
+// the compressed video for each frame. For version 6 robots, use 32-bit
+// integers.
+//
+// The next section of the Robot is the packet size index (combined compressed
+// size of video + audio for each frame). In version 5 Robots, read ''total
+// number of frames'' 16-bit integers. In version 6 robots, use 32-bit integers.
+//
+// The next section of the Robot is the cue times index. Read 256 32-bit
+// integers, which represent the number of ticks from the start of playback that
+// the given cue point falls on.
+//
+// The next section of the Robot is the cue values index. Read 256 16-bit
+// integers, which represent the actual cue values that will be passed back to
+// the game engine when a cue is requested.
+//
+// Finally, to get to the first frame packet, seek from the current position to
+// the start of the next 2048-byte-aligned sector.
+//
+// Frame packet:
+//
+// byte | description
+// 0..n | video data (size is in the ''video frame size index'')
+// n+1.. | optional audio data (size is ''size of audio blocks'')
+//
+// Video data:
+//
+// byte | description
+// 0-2 | number of cels in the frame (max 10)
+// 3..n | cels
+//
+// Cel:
+//
+// 0-17 | cel header
+// 18..n | data chunks
+//
+// Cel header:
+//
+// byte | description
+// 0 | unused
+// 1 | vertical scale factor, in percent decimation (100 = no decimation,
+// | 50 = 50% of lines were removed)
+// 2-3 | cel width
+// 4-5 | cel height
+// 6-9 | unused
+// 10-11 | cel x-position, in Robot coordinates
+// 12-13 | cel y-position, in Robot coordinates
+// 14-15 | cel total data chunk size, in bytes
+// 16-17 | number of data chunks
+//
+// Cel data chunk:
+//
+// 0-9 | cel data chunk header
+// 10..n | cel data
+//
+// Cel data chunk header:
+//
+// byte | description
+// 0-3 | compressed size
+// 4-7 | decompressed size
+// 8-9 | compression type (0 = LZS, 2 = uncompressed)
+//
+// Random frame seeking can be done by calculating the address of the frame
+// packet by adding up the ''packet size index'' entries up to the current
+// frame. This will normally disable audio playback, as audio data in a packet
+// does not correspond to the video in the same packet.
+//
+// Audio data is placed immediately after the end of the video data in a packet,
+// and consists of an audio header plus compressed audio data:
+//
+// Audio data:
+//
+// byte | description
+// 0-7 | audio data header
+// 8-15 | DPCM runway
+// 16..n | compressed audio data
+//
+// Audio data header:
+//
+// byte | description
+// 0-3 | absolute position of audio in the audio stream
+// 4-7 | the size of the audio block, excluding the header
+//
+// When a block of audio is processed, first check to ensure that the
+// decompressed audio block's `position * 2 + length * 4` runs past the end of
+// the last packet of the same evenness/oddness. Discard the audio block
+// entirely if data has already been written past the end of this block for this
+// channel, or if the read head has already read past the end of this audio
+// block.
+//
+// If the block is not discarded, apply DPCM decompression to the entire block,
+// starting from beginning of the DPCM runway, using an initial sample value of
+// 0. Then, copy every sample from the decompressed source outside of the DPCM
+// runway into every *other* sample of the final audio buffer (1 -> 2, 2 -> 4,
+// 3 -> 6, etc.).
+//
+// Finally, for any skipped samples where the opposing (even/odd) channel did
+// not yet write, interpolate the skipped areas by adding together the
+// neighbouring samples from this audio block and dividing by two. (This allows
+// the audio quality to degrade to 11kHz in case it takes too long to decode all
+// the frames in the stream). Interpolated samples must not be written on top of
+// true data from the opposing channel. Audio from later packets must also not
+// be written on top of data in the same channel that was already written by an
+// earlier packet, in particular because the first 8 bytes of the next packet
+// are garbage data used to move the waveform to the correct position (due to
+// the use of DPCM compression).
-namespace Sci {
+#pragma mark -
+#pragma mark RobotAudioStream
+
+/**
+ * A Robot audio stream is a simple loop buffer
+ * that accepts audio blocks from the Robot engine.
+ */
+class RobotAudioStream : public Audio::AudioStream {
+public:
+ enum {
+ /**
+ * The sample rate used for all robot audio.
+ */
+ kRobotSampleRate = 22050,
+
+ /**
+ * Multiplier for the size of a packet that
+ * is being expanded by writing to every other
+ * byte of the target buffer.
+ */
+ kEOSExpansion = 2
+ };
+
+ /**
+ * Playback state information. Used for framerate
+ * calculation.
+ */
+ struct StreamState {
+ /**
+ * The current position of the read head of
+ * the audio stream.
+ */
+ int bytesPlaying;
+
+ /**
+ * The sample rate of the audio stream.
+ * Always 22050.
+ */
+ uint16 rate;
+
+ /**
+ * The bit depth of the audio stream.
+ * Always 16.
+ */
+ uint8 bits;
+ };
+
+ /**
+ * A single packet of compressed audio from a
+ * Robot data stream.
+ */
+ struct RobotAudioPacket {
+ /**
+ * Raw DPCM-compressed audio data.
+ */
+ byte *data;
+
+ /**
+ * The size of the compressed audio data,
+ * in bytes.
+ */
+ int dataSize;
+
+ /**
+ * The uncompressed, file-relative position
+ * of this audio packet.
+ */
+ int position;
+
+ RobotAudioPacket(byte *data_, const int dataSize_, const int position_) :
+ data(data_), dataSize(dataSize_), position(position_) {}
+ };
+
+ RobotAudioStream(const int32 bufferSize);
+ virtual ~RobotAudioStream();
+
+ /**
+ * Adds a new audio packet to the stream.
+ * @returns `true` if the audio packet was fully
+ * consumed, otherwise `false`.
+ */
+ bool addPacket(const RobotAudioPacket &packet);
+
+ /**
+ * Prevents any additional audio packets from
+ * being added to the audio stream.
+ */
+ void finish();
+
+ /**
+ * Returns the current status of the audio
+ * stream.
+ */
+ StreamState getStatus() const;
+
+private:
+ Common::Mutex _mutex;
+
+ /**
+ * Loop buffer for playback. Contains decompressed
+ * 16-bit PCM samples.
+ */
+ byte *_loopBuffer;
+
+ /**
+ * The size of the loop buffer, in bytes.
+ */
+ int32 _loopBufferSize;
+
+ /**
+ * The position of the read head within the loop
+ * buffer, in bytes.
+ */
+ int32 _readHead;
+
+ /**
+ * The lowest file position that can be buffered,
+ * in uncompressed bytes.
+ */
+ int32 _readHeadAbs;
+
+ /**
+ * The highest file position that can be buffered,
+ * in uncompressed bytes.
+ */
+ int32 _maxWriteAbs;
+
+ /**
+ * The highest file position, in uncompressed bytes,
+ * that has been written to the stream.
+ * Different from `_maxWriteAbs`, which is the highest
+ * uncompressed position which *can* be written right
+ * now.
+ */
+ int32 _writeHeadAbs;
+
+ /**
+ * The highest file position, in uncompressed bytes,
+ * that has been written to the even & odd sides of
+ * the stream.
+ *
+ * Index 0 corresponds to the 'even' side; index
+ * 1 correspond to the 'odd' side.
+ */
+ int32 _jointMin[2];
+
+ /**
+ * When `true`, the stream is waiting for all primer
+ * blocks to be received before allowing playback to
+ * begin.
+ */
+ bool _waiting;
+
+ /**
+ * When `true`, the stream will accept no more audio
+ * blocks.
+ */
+ bool _finished;
+
+ /**
+ * The uncompressed position of the first packet of
+ * robot data. Used to decide whether all primer
+ * blocks have been received and the stream should
+ * be started.
+ */
+ int32 _firstPacketPosition;
+
+ /**
+ * Decompression buffer, used to temporarily store
+ * an uncompressed block of audio data.
+ */
+ byte *_decompressionBuffer;
+
+ /**
+ * The size of the decompression buffer, in bytes.
+ */
+ int32 _decompressionBufferSize;
+
+ /**
+ * The position of the packet currently in the
+ * decompression buffer. Used to avoid
+ * re-decompressing audio data that has already
+ * been decompressed during a partial packet read.
+ */
+ int32 _decompressionBufferPosition;
+
+ /**
+ * Calculates the absolute ranges for new fills
+ * into the loop buffer.
+ */
+ void fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex);
+
+ /**
+ * Interpolates `numSamples` samples from the read
+ * head, if no true samples were written for one
+ * (or both) of the joint channels.
+ */
+ void interpolateMissingSamples(const int32 numSamples);
+
+#pragma mark -
+#pragma mark RobotAudioStream - AudioStream implementation
+public:
+ int readBuffer(Audio::st_sample_t *outBuffer, int numSamples) override;
+ virtual bool isStereo() const override { return false; };
+ virtual int getRate() const override { return 22050; };
+ virtual bool endOfData() const override {
+ Common::StackLock lock(_mutex);
+ return _readHeadAbs >= _writeHeadAbs;
+ };
+ virtual bool endOfStream() const override {
+ Common::StackLock lock(_mutex);
+ return _finished && endOfData();
+ }
+};
+
+#pragma mark -
+#pragma mark RobotDecoder
+
+/**
+ * RobotDecoder implements the logic required
+ * for Robot animations.
+ *
+ * @note A paused or finished RobotDecoder was
+ * classified as serializable in SCI3, but the
+ * save/load code would attempt to use uninitialised
+ * values, so it seems that robots were not ever
+ * actually able to be saved.
+ */
+class RobotDecoder {
+public:
+ RobotDecoder(SegManager *segMan);
+ ~RobotDecoder();
+
+private:
+ SegManager *_segMan;
+
+#pragma mark Constants
+public:
+ /**
+ * The playback status of the robot.
+ */
+ enum RobotStatus {
+ kRobotStatusUninitialized = 0,
+ kRobotStatusPlaying = 1,
+ kRobotStatusEnd = 2,
+ kRobotStatusPaused = 3
+ };
+
+ enum {
+ // Special high value used to represent
+ // parameters that should be left unchanged
+ // when calling `showFrame`
+ kUnspecified = 50000
+ };
+
+private:
+ enum {
+ /**
+ * Maximum number of on-screen screen items.
+ */
+ kScreenItemListSize = 10,
+
+ /**
+ * Maximum number of queued audio blocks.
+ */
+ kAudioListSize = 10,
+
+ /**
+ * Maximum number of samples used for frame timing.
+ */
+ kDelayListSize = 10,
+
+ /**
+ * Maximum number of cues.
+ */
+ kCueListSize = 256,
+
+ /**
+ * Maximum number of 'fixed' cels that never
+ * change for the duration of a robot.
+ */
+ kFixedCelListSize = 4,
+
+ /**
+ * The size of a hunk palette in the Robot stream.
+ */
+ kRawPaletteSize = 1200,
+
+ /**
+ * The size of a frame of Robot data. This
+ * value was used to align the first block of
+ * data after the main Robot header to the next
+ * CD sector.
+ */
+ kRobotFrameSize = 2048,
+
+ /**
+ * The size of a block of zero-compressed
+ * audio. Used to fill audio when the size of
+ * an audio packet does not match the expected
+ * packet size.
+ */
+ kRobotZeroCompressSize = 2048,
-class RobotDecoder : public Video::VideoDecoder {
+ /**
+ * The size of the audio block header, in bytes.
+ * The audio block header consists of the
+ * compressed size of the audio in the record,
+ * plus the position of the audio in the
+ * compressed data stream.
+ */
+ kAudioBlockHeaderSize = 8,
+
+ /**
+ * The size of a Robot cel header, in bytes.
+ */
+ kCelHeaderSize = 22,
+
+ /**
+ * The maximum amount that the frame rate is
+ * allowed to drift from the nominal frame rate
+ * in order to correct for AV drift or slow
+ * playback.
+ */
+ kMaxFrameRateDrift = 1
+ };
+
+ /**
+ * The version number for the currently loaded
+ * robot.
+ *
+ * There are several known versions of robot:
+ *
+ * v2: before Nov 1994; no known examples
+ * v3: before Nov 1994; no known examples
+ * v4: Jan 1995; PQ:SWAT demo
+ * v5: Mar 1995; SCI2.1 and SCI3 games
+ * v6: SCI3 games
+ */
+ uint16 _version;
+
+#pragma mark -
+#pragma mark Initialisation
+private:
+ /**
+ * Sets up the read stream for the robot.
+ */
+ void initStream(const GuiResourceId robotId);
+
+ /**
+ * Sets up the initial values for playback control.
+ */
+ void initPlayback();
+
+ /**
+ * Sets up the initial values for audio decoding.
+ */
+ void initAudio();
+
+ /**
+ * Sets up the initial values for video rendering.
+ */
+ void initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize);
+
+ /**
+ * Sets up the robot's data record and cue positions.
+ */
+ void initRecordAndCuePositions();
+
+#pragma mark -
+#pragma mark Playback
public:
- RobotDecoder(bool isBigEndian);
- virtual ~RobotDecoder();
+ /**
+ * Opens a robot file for playback.
+ * Newly opened robots are paused by default.
+ */
+ void open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale);
- bool loadStream(Common::SeekableReadStream *stream);
- bool load(GuiResourceId id);
+ /**
+ * Closes the currently open robot file.
+ */
void close();
- void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); }
- Common::Point getPos() const { return _pos; }
+ /**
+ * Pauses the robot. Once paused, the audio for a robot
+ * is disabled until the end of playback.
+ */
+ void pause();
+
+ /**
+ * Resumes a paused robot.
+ */
+ void resume();
+
+ /**
+ * Moves robot to the specified frame and pauses playback.
+ *
+ * @note Called DisplayFrame in SSCI.
+ */
+ void showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority);
+
+ /**
+ * Retrieves the value associated with the
+ * current cue point.
+ */
+ int16 getCue() const;
+
+ /**
+ * Gets the currently displayed frame.
+ */
+ int16 getFrameNo() const;
+
+ /**
+ * Gets the playback status of the player.
+ */
+ RobotStatus getStatus() const;
+
+private:
+ /**
+ * The read stream containing raw robot data.
+ */
+ Common::SeekableSubReadStreamEndian *_stream;
+
+ /**
+ * The current status of the player.
+ */
+ RobotStatus _status;
+
+ typedef Common::Array<int> PositionList;
+
+ /**
+ * A map of frame numbers to byte offsets within `_stream`.
+ */
+ PositionList _recordPositions;
+
+ /**
+ * The offset of the Robot file within a
+ * resource bundle.
+ */
+ int32 _fileOffset;
+
+ /**
+ * A list of cue times that is updated to
+ * prevent earlier cue values from being
+ * given to the game more than once.
+ */
+ mutable int32 _cueTimes[kCueListSize];
+
+ /**
+ * The original list of cue times from the
+ * raw Robot data.
+ */
+ int32 _masterCueTimes[kCueListSize];
+
+ /**
+ * The list of values to provide to a game
+ * when a cue value is requested.
+ */
+ int32 _cueValues[kCueListSize];
+
+ /**
+ * The current playback frame rate.
+ */
+ int16 _frameRate;
+
+ /**
+ * The nominal playback frame rate.
+ */
+ int16 _normalFrameRate;
+
+ /**
+ * The minimal playback frame rate. Used to
+ * correct for AV sync drift when the video
+ * is more than one frame ahead of the audio.
+ */
+ int16 _minFrameRate;
+
+ /**
+ * The maximum playback frame rate. Used to
+ * correct for AV sync drift when the video
+ * is more than one frame behind the audio.
+ */
+ int16 _maxFrameRate;
+
+ /**
+ * The maximum number of record blocks that
+ * can be skipped without causing audio to
+ * drop out.
+ */
+ int16 _maxSkippablePackets;
+
+ /**
+ * The currently displayed frame number.
+ */
+ int _currentFrameNo;
+
+ /**
+ * The last displayed frame number.
+ */
+ int _previousFrameNo;
+
+ /**
+ * The time, in ticks, when the robot was
+ * last started or resumed.
+ */
+ int32 _startTime;
+
+ /**
+ * The first frame displayed when the
+ * robot was resumed.
+ */
+ int32 _startFrameNo;
+
+ /**
+ * The last frame displayed when the robot
+ * was resumed.
+ */
+ int32 _startingFrameNo;
+
+ /**
+ * Seeks the raw data stream to the record for
+ * the given frame number.
+ */
+ bool seekToFrame(const int frameNo);
-protected:
- void readNextPacket();
+ /**
+ * Sets the start time and frame of the robot
+ * when the robot is started or resumed.
+ */
+ void setRobotTime(const int frameNo);
+#pragma mark -
+#pragma mark Timing
private:
- class RobotVideoTrack : public FixedRateVideoTrack {
+ /**
+ * This class tracks the amount of time it takes for
+ * a frame of robot animation to be rendered. This
+ * information is used by the player to speculatively
+ * skip rendering of future frames to keep the
+ * animation in sync with the robot audio.
+ */
+ class DelayTime {
public:
- RobotVideoTrack(int frameCount);
- ~RobotVideoTrack();
-
- uint16 getWidth() const;
- uint16 getHeight() const;
- Graphics::PixelFormat getPixelFormat() const;
- int getCurFrame() const { return _curFrame; }
- int getFrameCount() const { return _frameCount; }
- const Graphics::Surface *decodeNextFrame() { return _surface; }
- const byte *getPalette() const { _dirtyPalette = false; return _palette; }
- bool hasDirtyPalette() const { return _dirtyPalette; }
-
- void readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize);
- void calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes);
- Graphics::Surface *getSurface() { return _surface; }
- void increaseCurFrame() { _curFrame++; }
-
- protected:
- Common::Rational getFrameRate() const { return Common::Rational(60, 10); }
+ DelayTime(RobotDecoder *decoder);
+
+ /**
+ * Starts performance timing.
+ */
+ void startTiming();
+
+ /**
+ * Ends performance timing.
+ */
+ void endTiming();
+
+ /**
+ * Returns whether or not timing is currently in
+ * progress.
+ */
+ bool timingInProgress() const;
+
+ /**
+ * Returns the median time, in ticks, of the
+ * currently stored timing samples.
+ */
+ int predictedTicks() const;
private:
- int _frameCount;
- int _curFrame;
- byte _palette[256 * 3];
- mutable bool _dirtyPalette;
- Graphics::Surface *_surface;
+ RobotDecoder *_decoder;
+
+ /**
+ * The start time, in ticks, of the current timing
+ * loop. If no loop is in progress, the value is 0.
+ *
+ * @note This is slightly different than SSCI where
+ * the not-timing value was -1.
+ */
+ uint32 _startTime;
+
+ /**
+ * A sorted list containing the timing data for
+ * the last `kDelayListSize` frames, in ticks.
+ */
+ int _delays[kDelayListSize];
+
+ /**
+ * A list of monotonically increasing identifiers
+ * used to identify and replace the oldest sample
+ * in the `_delays` array when finishing the
+ * next timing operation.
+ */
+ uint _timestamps[kDelayListSize];
+
+ /**
+ * The identifier of the oldest timing.
+ */
+ uint _oldestTimestamp;
+
+ /**
+ * The identifier of the newest timing.
+ */
+ uint _newestTimestamp;
+
+ /**
+ * Sorts the list of timings.
+ */
+ void sortList();
};
- class RobotAudioTrack : public AudioTrack {
+ /**
+ * Calculates the next frame number that needs
+ * to be rendered, using the timing data
+ * collected by DelayTime.
+ */
+ uint16 calculateNextFrameNo(const uint32 extraTicks = 0) const;
+
+ /**
+ * Calculates and returns the number of frames
+ * that should be rendered in `ticks` time,
+ * according to the current target frame rate
+ * of the robot.
+ */
+ uint32 ticksToFrames(const uint32 ticks) const;
+
+ /**
+ * Gets the current game time, in ticks.
+ */
+ uint32 getTickCount() const;
+
+ /**
+ * The performance timer for the robot.
+ */
+ DelayTime _delayTime;
+
+#pragma mark -
+#pragma mark Audio
+private:
+ enum {
+ /**
+ * The number of ticks that should elapse
+ * between each AV sync check.
+ */
+ kAudioSyncCheckInterval = 5 * 60 /* 5 seconds */
+ };
+
+ /**
+ * The status of the audio track of a Robot
+ * animation.
+ */
+ enum RobotAudioStatus {
+ kRobotAudioReady = 1,
+ kRobotAudioStopped = 2,
+ kRobotAudioPlaying = 3,
+ kRobotAudioPaused = 4,
+ kRobotAudioStopping = 5
+ };
+
+#pragma mark -
+#pragma mark Audio - AudioList
+private:
+ /**
+ * This class manages packetized audio playback
+ * for robots.
+ */
+ class AudioList {
public:
- RobotAudioTrack();
- ~RobotAudioTrack();
+ AudioList();
+
+ /**
+ * Starts playback of robot audio.
+ */
+ void startAudioNow();
+
+ /**
+ * Stops playback of robot audio, allowing
+ * any queued audio to finish playing back.
+ */
+ void stopAudio();
+
+ /**
+ * Stops playback of robot audio immediately.
+ */
+ void stopAudioNow();
+
+ /**
+ * Submits as many blocks of audio as possible
+ * to the audio engine.
+ */
+ void submitDriverMax();
+
+ /**
+ * Adds a new AudioBlock to the queue.
+ *
+ * @param position The absolute position of the
+ * audio for the block, in compressed bytes.
+ * @param size The size of the buffer.
+ * @param buffer A pointer to compressed audio
+ * data that will be copied into the new
+ * AudioBlock.
+ */
+ void addBlock(const int position, const int size, const byte *buffer);
- Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kMusicSoundType; }
+ /**
+ * Immediately stops any active playback and
+ * purges all audio data in the audio list.
+ */
+ void reset();
- void queueBuffer(byte *buffer, int size);
+ /**
+ * Pauses the robot audio channel in
+ * preparation for the first block of audio
+ * data to be read.
+ */
+ void prepareForPrimer();
- protected:
- Audio::AudioStream *getAudioStream() const;
+ /**
+ * Sets the audio offset which is used to
+ * offset the position of audio packets
+ * sent to the audio stream.
+ */
+ void setAudioOffset(const int offset);
+
+#pragma mark -
+#pragma mark Audio - AudioList - AudioBlock
private:
- Audio::QueuingAudioStream *_audioStream;
+ /**
+ * AudioBlock represents a block of audio
+ * from the Robot's audio track.
+ */
+ class AudioBlock {
+ public:
+ AudioBlock(const int position, const int size, const byte *const data);
+ ~AudioBlock();
+
+ /**
+ * Submits the block of audio to the
+ * audio manager.
+ * @returns true if the block was fully
+ * read, or false if the block was not
+ * read or only partially read.
+ */
+ bool submit(const int startOffset);
+
+ private:
+ /**
+ * The absolute position, in compressed
+ * bytes, of this audio block's audio
+ * data in the audio stream.
+ */
+ int _position;
+
+ /**
+ * The compressed size, in bytes, of
+ * this audio block's audio data.
+ */
+ int _size;
+
+ /**
+ * A buffer containing raw
+ * SOL-compressed audio data.
+ */
+ byte *_data;
+ };
+
+ /**
+ * The list of compressed audio blocks
+ * submitted for playback.
+ */
+ AudioBlock *_blocks[kAudioListSize];
+
+ /**
+ * The number of blocks in `_blocks` that are
+ * ready to be submitted.
+ */
+ uint8 _blocksSize;
+
+ /**
+ * The index of the oldest submitted audio block.
+ */
+ uint8 _oldestBlockIndex;
+
+ /**
+ * The index of the newest submitted audio block.
+ */
+ uint8 _newestBlockIndex;
+
+ /**
+ * The offset used when sending packets to the
+ * audio stream.
+ */
+ int _startOffset;
+
+ /**
+ * The status of robot audio playback.
+ */
+ RobotAudioStatus _status;
+
+ /**
+ * Frees all audio blocks in the `_blocks` list.
+ */
+ void freeAudioBlocks();
};
- struct RobotHeader {
- // 6 bytes, identifier bytes
- uint16 version;
- uint16 audioChunkSize;
- uint16 audioSilenceSize;
- // 2 bytes, unknown
- uint16 frameCount;
- uint16 paletteDataSize;
- uint16 unkChunkDataSize;
- // 5 bytes, unknown
- byte hasSound;
- // 34 bytes, unknown
- } _header;
-
- void readHeaderChunk();
- void readFrameSizesChunk();
-
- Common::Point _pos;
- bool _isBigEndian;
- uint32 *_frameTotalSize;
-
- Common::SeekableSubReadStreamEndian *_fileStream;
-};
+ /**
+ * Whether or not this robot animation has
+ * an audio track.
+ */
+ bool _hasAudio;
+
+ /**
+ * The audio list for the current robot.
+ */
+ AudioList _audioList;
+
+ /**
+ * The size, in bytes, of a block of audio data,
+ * excluding the audio block header.
+ */
+ uint16 _audioBlockSize;
+
+ /**
+ * The expected size of a block of audio data,
+ * in bytes, excluding the audio block header.
+ */
+ int16 _expectedAudioBlockSize;
+
+ /**
+ * The number of compressed audio bytes that are
+ * needed per frame to fill the audio buffer
+ * without causing audio to drop out.
+ */
+ int16 _audioRecordInterval;
+
+ /**
+ * If true, primer audio buffers should be filled
+ * with silence instead of trying to read buffers
+ * from the Robot data.
+ */
+ uint16 _primerZeroCompressFlag;
+
+ /**
+ * The size, in bytes, of the primer audio in the
+ * Robot, including any extra alignment padding.
+ */
+ uint16 _primerReservedSize;
+
+ /**
+ * The combined size, in bytes, of the even and odd
+ * primer channels.
+ */
+ int32 _totalPrimerSize;
+
+ /**
+ * The absolute offset of the primer audio data in
+ * the robot data stream.
+ */
+ int32 _primerPosition;
+
+ /**
+ * The size, in bytes, of the even primer.
+ */
+ int32 _evenPrimerSize;
+
+ /**
+ * The size, in bytes, of the odd primer.
+ */
+ int32 _oddPrimerSize;
+
+ /**
+ * The absolute position in the audio stream of
+ * the first audio packet.
+ */
+ int32 _firstAudioRecordPosition;
-} // End of namespace Sci
+ /**
+ * A temporary buffer used to hold one frame of
+ * raw (DPCM-compressed) audio when reading audio
+ * records from the robot stream.
+ */
+ byte *_audioBuffer;
+ /**
+ * The next tick count when AV sync should be
+ * checked and framerate adjustments made, if
+ * necessary.
+ */
+ uint32 _checkAudioSyncTime;
+
+ /**
+ * Primes the audio buffer with the first frame
+ * of audio data.
+ *
+ * @note `primeAudio` was `InitAudio` in SSCI
+ */
+ bool primeAudio(const uint32 startTick);
+
+ /**
+ * Reads primer data from the robot data stream
+ * and puts it into the given buffers.
+ */
+ bool readPrimerData(byte *outEvenBuffer, byte *outOddBuffer);
+
+ /**
+ * Reads audio data for the given frame number
+ * into the given buffer.
+ *
+ * @param outAudioPosition The position of the
+ * audio, in compressed bytes, in the data stream.
+ * @param outAudioSize The size of the audio data,
+ * in compressed bytes.
+ */
+ bool readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize);
+
+ /**
+ * Submits part of the audio packet of the given
+ * frame to the audio list, starting `startPosition`
+ * bytes into the audio.
+ */
+ bool readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition);
+
+#pragma mark -
+#pragma mark Rendering
+public:
+ /**
+ * Puts the current dimensions of the robot, in game script
+ * coordinates, into the given rect, and returns the total
+ * number of frames in the robot animation.
+ */
+ uint16 getFrameSize(Common::Rect &outRect) const;
+
+ /**
+ * Pumps the robot player for the next frame of video.
+ * This is the main rendering function.
+ */
+ void doRobot();
+
+ /**
+ * Submits any outstanding audio blocks that should
+ * be added to the queue before the robot frame
+ * becomes visible.
+ */
+ void frameAlmostVisible();
+
+ /**
+ * Evaluates frame drift and makes modifications to
+ * the player in order to ensure that future frames
+ * will arrive on time.
+ */
+ void frameNowVisible();
+
+ /**
+ * Scales a vertically compressed cel to its original
+ * uncompressed dimensions.
+ */
+ void expandCel(byte *target, const byte* source, const int16 celWidth, const int16 celHeight) const;
+
+ /**
+ * Sets the visual priority of the robot.
+ * @see Plane::_priority
+ */
+ void setPriority(const int16 newPriority);
+
+private:
+ enum CompressionType {
+ kCompressionLZS = 0,
+ kCompressionNone = 2
+ };
+
+ /**
+ * Describes the state of a Robot video cel.
+ */
+ struct CelHandleInfo {
+ /**
+ * The persistence level of Robot cels.
+ */
+ enum CelHandleLifetime {
+ kNoCel = 0,
+ kFrameLifetime = 1,
+ kRobotLifetime = 2
+ };
+
+ /**
+ * A reg_t pointer to an in-memory
+ * bitmap containing the cel.
+ */
+ reg_t bitmapId;
+
+ /**
+ * The lifetime of the cel, either just
+ * for this frame or for the entire
+ * duration of the robot playback.
+ */
+ CelHandleLifetime status;
+
+ /**
+ * The size, in pixels, of the decompressed
+ * cel.
+ */
+ int area;
+
+ CelHandleInfo() : bitmapId(NULL_REG), status(kNoCel), area(0) {}
+ };
+
+ typedef Common::Array<ScreenItem *> RobotScreenItemList;
+ typedef Common::Array<CelHandleInfo> CelHandleList;
+ typedef Common::Array<int> VideoSizeList;
+ typedef Common::Array<uint> MaxCelAreaList;
+ typedef Common::Array<reg_t> FixedCelsList;
+ typedef Common::Array<Common::Point> CelPositionsList;
+ typedef Common::Array<byte> ScratchMemory;
+
+ /**
+ * Renders a version 5/6 robot frame.
+ */
+ void doVersion5(const bool shouldSubmitAudio = true);
+
+ /**
+ * Creates screen items for a version 5/6 robot.
+ */
+ void createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette);
+
+ /**
+ * Creates a single screen item for a cel in a
+ * version 5/6 robot.
+ *
+ * Returns the size, in bytes, of the raw cel data.
+ */
+ uint32 createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette);
+
+ /**
+ * Preallocates memory for the next `numCels` cels
+ * in the robot data stream.
+ */
+ void preallocateCelMemory(const byte *rawVideoData, const int16 numCels);
+
+ /**
+ * The decompressor for LZS-compressed cels.
+ */
+ DecompressorLZS _decompressor;
+
+ /**
+ * The origin of the robot animation, in screen
+ * coordinates.
+ */
+ Common::Point _position;
+
+ /**
+ * Global scaling applied to the robot.
+ */
+ ScaleInfo _scaleInfo;
+
+ /**
+ * The native resolution of the robot.
+ */
+ int16 _xResolution, _yResolution;
+
+ /**
+ * Whether or not the coordinates read from robot
+ * data are high resolution.
+ */
+ bool _isHiRes;
+
+ /**
+ * The maximum number of cels that will be rendered
+ * on any given frame in this robot. Used for
+ * preallocation of cel memory.
+ */
+ int16 _maxCelsPerFrame;
+
+ /**
+ * The maximum areas, in pixels, for each of
+ * the fixed cels in the robot. Used for
+ * preallocation of cel memory.
+ */
+ MaxCelAreaList _maxCelArea;
+
+ /**
+ * The hunk palette to use when rendering the
+ * current frame, if the `usePalette` flag was set
+ * in the robot header.
+ */
+ uint8 *_rawPalette;
+
+ /**
+ * A list of the raw video data sizes, in bytes,
+ * for each frame of the robot.
+ */
+ VideoSizeList _videoSizes;
+
+ /**
+ * A list of cels that will be present for the
+ * entire duration of the robot animation.
+ */
+ FixedCelsList _fixedCels;
+
+ /**
+ * A list of handles for each cel in the current
+ * frame.
+ */
+ CelHandleList _celHandles;
+
+ /**
+ * Scratch memory used to temporarily store
+ * decompressed cel data for vertically squashed
+ * cels.
+ */
+ ScratchMemory _celDecompressionBuffer;
+
+ /**
+ * The size, in bytes, of the squashed cel
+ * decompression buffer.
+ */
+ int _celDecompressionArea;
+
+ /**
+ * If true, the robot just started playing and
+ * is awaiting output for the first frame.
+ */
+ bool _syncFrame;
+
+ /**
+ * Scratch memory used to store the compressed robot
+ * video data for the current frame.
+ */
+ ScratchMemory _doVersion5Scratch;
+
+ /**
+ * When set to a non-negative value, forces the next
+ * call to doRobot to render the given frame number
+ * instead of whatever frame would have normally been
+ * rendered.
+ */
+ mutable int _cueForceShowFrame;
+
+ /**
+ * The plane where the robot animation will be drawn.
+ */
+ Plane *_plane;
+
+ /**
+ * A list of pointers to ScreenItems used by the robot.
+ */
+ RobotScreenItemList _screenItemList;
+
+ /**
+ * The positions of the various screen items in this
+ * robot, in screen coordinates.
+ */
+ Common::Array<int16> _screenItemX, _screenItemY;
+
+ /**
+ * The raw position values from the cel header for
+ * each screen item currently on-screen.
+ */
+ Common::Array<int16> _originalScreenItemX, _originalScreenItemY;
+
+ /**
+ * The duration of the current robot, in frames.
+ */
+ uint16 _numFramesTotal;
+
+ /**
+ * The screen priority of the video.
+ * @see ScreenItem::_priority
+ */
+ int16 _priority;
+
+ /**
+ * The amount of visual vertical compression applied
+ * to the current cel. A value of 100 means no
+ * compression; a value above 100 indicates how much
+ * the cel needs to be scaled along the y-axis to
+ * return to its original dimensions.
+ */
+ uint8 _verticalScaleFactor;
+};
+} // end of namespace Sci
#endif
diff --git a/engines/scumm/detection.cpp b/engines/scumm/detection.cpp
index 4c9d1221aa..e6740df482 100644
--- a/engines/scumm/detection.cpp
+++ b/engines/scumm/detection.cpp
@@ -974,7 +974,8 @@ bool ScummMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
- (f == kSavesSupportPlayTime);
+ (f == kSavesSupportPlayTime) ||
+ (f == kSimpleSavesNames);
}
bool ScummEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/scumm/he/intern_he.h b/engines/scumm/he/intern_he.h
index c6abac3ecc..8a562ea7b5 100644
--- a/engines/scumm/he/intern_he.h
+++ b/engines/scumm/he/intern_he.h
@@ -56,9 +56,11 @@ public:
Common::Rect _actorClipOverride; // HE specific
int _heTimers[16];
+ uint32 _pauseStartTime;
int getHETimer(int timer);
void setHETimer(int timer);
+ void pauseHETimers(bool pause);
public:
ScummEngine_v60he(OSystem *syst, const DetectorResult &dr);
@@ -94,6 +96,7 @@ protected:
Common::WriteStream *openSaveFileForAppending(const byte *fileName);
void deleteSaveFile(const byte *fileName);
void renameSaveFile(const byte *from, const byte *to);
+ void pauseEngineIntern(bool pause);
Common::SeekableReadStream *openSaveFileForReading(int slot, bool compat, Common::String &fileName);
Common::WriteStream *openSaveFileForWriting(int slot, bool compat, Common::String &fileName);
@@ -288,6 +291,8 @@ public:
virtual byte *getStringAddress(ResId idx);
virtual int setupStringArray(int size);
+ virtual int setupStringArrayFromString(char *cStr);
+ virtual void getStringFromArray(int arrayNumber, char *buffer, int maxLength);
protected:
virtual void setupOpcodes();
diff --git a/engines/scumm/he/logic/moonbase_logic.cpp b/engines/scumm/he/logic/moonbase_logic.cpp
index 1b596fc54c..c504ad4fe8 100644
--- a/engines/scumm/he/logic/moonbase_logic.cpp
+++ b/engines/scumm/he/logic/moonbase_logic.cpp
@@ -24,6 +24,10 @@
#include "scumm/he/logic_he.h"
#include "scumm/he/moonbase/moonbase.h"
#include "scumm/he/moonbase/ai_main.h"
+#ifdef USE_SDL_NET
+#include "scumm/he/moonbase/net_main.h"
+#include "scumm/he/moonbase/net_defines.h"
+#endif
namespace Scumm {
@@ -54,6 +58,45 @@ private:
void op_ai_set_type(int op, int numArgs, int32 *args);
void op_ai_clean_up(int op, int numArgs, int32 *args);
+#ifdef USE_SDL_NET
+ void op_net_remote_start_script(int op, int numArgs, int32 *args);
+ void op_net_remote_send_array(int op, int numArgs, int32 *args);
+ int op_net_remote_start_function(int op, int numArgs, int32 *args);
+ int op_net_do_init_all(int op, int numArgs, int32 *args);
+ int op_net_do_init_provider(int op, int numArgs, int32 *args);
+ int op_net_do_init_session(int op, int numArgs, int32 *args);
+ int op_net_do_init_user(int op, int numArgs, int32 *args);
+ int op_net_query_providers(int op, int numArgs, int32 *args);
+ int op_net_get_provider_name(int op, int numArgs, int32 *args);
+ int op_net_set_provider(int op, int numArgs, int32 *args);
+ int op_net_close_provider(int op, int numArgs, int32 *args);
+ int op_net_start_query_sessions(int op, int numArgs, int32 *args);
+ int op_net_update_query_sessions(int op, int numArgs, int32 *args);
+ int op_net_stop_query_sessions(int op, int numArgs, int32 *args);
+ int op_net_query_sessions(int op, int numArgs, int32 *args);
+ int op_net_get_session_name(int op, int numArgs, int32 *args);
+ int op_net_get_session_player_count(int op, int numArgs, int32 *args);
+ int op_net_destroy_player(int op, int numArgs, int32 *args);
+ int op_net_get_player_long_name(int op, int numArgs, int32 *args);
+ int op_net_get_player_short_name(int op, int numArgs, int32 *args);
+ int op_net_create_session(int op, int numArgs, int32 *args);
+ int op_net_join_session(int op, int numArgs, int32 *args);
+ int op_net_end_session(int op, int numArgs, int32 *args);
+ int op_net_disable_session_player_join(int op, int numArgs, int32 *args);
+ int op_net_enable_session_player_join(int op, int numArgs, int32 *args);
+ int op_net_set_ai_player_count(int op, int numArgs, int32 *args);
+ int op_net_add_user(int op, int numArgs, int32 *args);
+ int op_net_remove_user(int op, int numArgs, int32 *args);
+ int op_net_who_sent_this(int op, int numArgs, int32 *args);
+ int op_net_who_am_i(int op, int numArgs, int32 *args);
+ int op_net_set_provider_by_name(int op, int numArgs, int32 *args);
+ void op_net_set_fake_latency(int op, int numArgs, int32 *args);
+ int op_net_get_host_name(int op, int numArgs, int32 *args);
+ int op_net_get_ip_from_name(int op, int numArgs, int32 *args);
+ int op_net_host_tcpip_game(int op, int numArgs, int32 *args);
+ int op_net_join_tcpip_game(int op, int numArgs, int32 *args);
+#endif
+
private:
ScummEngine_v100he *_vm1;
};
@@ -107,6 +150,7 @@ int LogicHEmoonbase::versionID() {
#define OP_NET_HOST_TCPIP_GAME 1517
#define OP_NET_JOIN_TCPIP_GAME 1518
#define OP_NET_SET_FAKE_LAG 1555
+#define OP_NET_SET_FAKE_LATENCY 1555 /* SET_FAKE_LAG is a valid alias for backwards compatibility */
#define OP_NET_GET_HOST_NAME 1556
#define OP_NET_GET_IP_FROM_NAME 1557
#define OP_NET_GET_SESSION_PLAYER_COUNT 1558
@@ -157,6 +201,86 @@ int32 LogicHEmoonbase::dispatch(int op, int numArgs, int32 *args) {
op_ai_clean_up(op, numArgs, args);
break;
+#ifdef USE_SDL_NET
+ case OP_NET_REMOTE_START_SCRIPT:
+ op_net_remote_start_script(op, numArgs, args);
+ break;
+ case OP_NET_REMOTE_SEND_ARRAY:
+ op_net_remote_send_array(op, numArgs, args);
+ break;
+ case OP_NET_REMOTE_START_FUNCTION:
+ return op_net_remote_start_function(op, numArgs, args);
+ case OP_NET_DO_INIT_ALL:
+ return op_net_do_init_all(op, numArgs, args);
+ case OP_NET_DO_INIT_PROVIDER:
+ return op_net_do_init_provider(op, numArgs, args);
+ case OP_NET_DO_INIT_SESSION:
+ return op_net_do_init_session(op, numArgs, args);
+ case OP_NET_DO_INIT_USER:
+ return op_net_do_init_user(op, numArgs, args);
+ case OP_NET_QUERY_PROVIDERS:
+ return op_net_query_providers(op, numArgs, args);
+ case OP_NET_GET_PROVIDER_NAME:
+ return op_net_get_provider_name(op, numArgs, args);
+ case OP_NET_SET_PROVIDER:
+ return op_net_set_provider(op, numArgs, args);
+ case OP_NET_CLOSE_PROVIDER:
+ return op_net_close_provider(op, numArgs, args);
+ case OP_NET_START_QUERY_SESSIONS:
+ return op_net_start_query_sessions(op, numArgs, args);
+ case OP_NET_UPDATE_QUERY_SESSIONS:
+ return op_net_update_query_sessions(op, numArgs, args);
+ case OP_NET_STOP_QUERY_SESSIONS:
+ return op_net_stop_query_sessions(op, numArgs, args);
+ case OP_NET_QUERY_SESSIONS:
+ return op_net_query_sessions(op, numArgs, args);
+ case OP_NET_GET_SESSION_NAME:
+ return op_net_get_session_name(op, numArgs, args);
+ case OP_NET_GET_SESSION_PLAYER_COUNT:
+ return op_net_get_session_player_count(op, numArgs, args);
+ case OP_NET_DESTROY_PLAYER:
+ return op_net_destroy_player(op, numArgs, args);
+#if 1 // 12/2/99 BPT
+ case OP_NET_GET_PLAYER_LONG_NAME:
+ return op_net_get_player_long_name(op, numArgs, args);
+ case OP_NET_GET_PLAYER_SHORT_NAME:
+ return op_net_get_player_short_name(op, numArgs, args);
+#endif
+ case OP_NET_CREATE_SESSION:
+ return op_net_create_session(op, numArgs, args);
+ case OP_NET_JOIN_SESSION:
+ return op_net_join_session(op, numArgs, args);
+ case OP_NET_END_SESSION:
+ return op_net_end_session(op, numArgs, args);
+ case OP_NET_DISABLE_SESSION_PLAYER_JOIN:
+ return op_net_disable_session_player_join(op, numArgs, args);
+ case OP_NET_ENABLE_SESSION_PLAYER_JOIN:
+ return op_net_enable_session_player_join(op, numArgs, args);
+ case OP_NET_SET_AI_PLAYER_COUNT:
+ return op_net_set_ai_player_count(op, numArgs, args);
+ case OP_NET_ADD_USER:
+ return op_net_add_user(op, numArgs, args);
+ case OP_NET_REMOVE_USER:
+ return op_net_remove_user(op, numArgs, args);
+ case OP_NET_WHO_SENT_THIS:
+ return op_net_who_sent_this(op, numArgs, args);
+ case OP_NET_WHO_AM_I:
+ return op_net_who_am_i(op, numArgs, args);
+ case OP_NET_SET_PROVIDER_BY_NAME:
+ return op_net_set_provider_by_name(op, numArgs, args);
+ case OP_NET_SET_FAKE_LATENCY: // SET_FAKE_LAG is a valid alias for backwards compatibility
+ op_net_set_fake_latency(op, numArgs, args);
+ break;
+ case OP_NET_GET_HOST_NAME:
+ return op_net_get_host_name(op, numArgs, args);
+ case OP_NET_GET_IP_FROM_NAME:
+ return op_net_get_ip_from_name(op, numArgs, args);
+ case OP_NET_HOST_TCPIP_GAME:
+ return op_net_host_tcpip_game(op, numArgs, args);
+ case OP_NET_JOIN_TCPIP_GAME:
+ return op_net_join_tcpip_game(op, numArgs, args);
+#endif
+
default:
LogicHE::dispatch(op, numArgs, args);
}
@@ -248,6 +372,193 @@ void LogicHEmoonbase::op_ai_clean_up(int op, int numArgs, int32 *args) {
_vm1->_moonbase->_ai->cleanUpAI();
}
+#ifdef USE_SDL_NET
+void LogicHEmoonbase::op_net_remote_start_script(int op, int numArgs, int32 *args) {
+ _vm1->_moonbase->_net->remoteStartScript(args[0], args[1], args[2], numArgs - 3, &args[3]);
+}
+
+void LogicHEmoonbase::op_net_remote_send_array(int op, int numArgs, int32 *args) {
+ _vm1->_moonbase->_net->remoteSendArray(args[0], args[1], args[2], args[3]);
+}
+
+int LogicHEmoonbase::op_net_remote_start_function(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->remoteStartScriptFunction(args[0], args[1], args[2], args[3], numArgs - 4, &args[4]);
+}
+
+int LogicHEmoonbase::op_net_do_init_all(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->initAll();
+}
+
+int LogicHEmoonbase::op_net_do_init_provider(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->initProvider();
+}
+
+int LogicHEmoonbase::op_net_do_init_session(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->initSession();
+}
+
+int LogicHEmoonbase::op_net_do_init_user(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->initUser();
+}
+
+int LogicHEmoonbase::op_net_query_providers(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->queryProviders();
+}
+
+int LogicHEmoonbase::op_net_get_provider_name(int op, int numArgs, int32 *args) {
+ char name[MAX_PROVIDER_NAME];
+ _vm1->_moonbase->_net->getProviderName(args[0] - 1, name, sizeof(name));
+ return _vm1->setupStringArrayFromString(name);
+}
+
+int LogicHEmoonbase::op_net_set_provider(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->setProvider(args[0] - 1);
+}
+
+int LogicHEmoonbase::op_net_close_provider(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->closeProvider();
+}
+
+int LogicHEmoonbase::op_net_start_query_sessions(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->startQuerySessions();
+}
+
+int LogicHEmoonbase::op_net_update_query_sessions(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->updateQuerySessions();
+}
+
+int LogicHEmoonbase::op_net_stop_query_sessions(int op, int numArgs, int32 *args) {
+ _vm1->_moonbase->_net->stopQuerySessions();
+ return 1;
+}
+
+int LogicHEmoonbase::op_net_query_sessions(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->querySessions();
+}
+
+int LogicHEmoonbase::op_net_get_session_name(int op, int numArgs, int32 *args) {
+ char name[MAX_PROVIDER_NAME];
+ _vm1->_moonbase->_net->getSessionName(args[0] - 1, name, sizeof(name));
+ return _vm1->setupStringArrayFromString(name);
+}
+
+int LogicHEmoonbase::op_net_get_session_player_count(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->getSessionPlayerCount(args[0] - 1);
+}
+
+int LogicHEmoonbase::op_net_destroy_player(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->destroyPlayer(args[0]);
+}
+
+int LogicHEmoonbase::op_net_get_player_long_name(int op, int numArgs, int32 *args) {
+ return _vm1->setupStringArrayFromString("long name"); // TODO: gdefMultiPlay.playername1
+}
+
+int LogicHEmoonbase::op_net_get_player_short_name(int op, int numArgs, int32 *args) {
+ return _vm1->setupStringArrayFromString("short"); // TODO: gdefMultiPlay.playername2
+}
+
+int LogicHEmoonbase::op_net_create_session(int op, int numArgs, int32 *args) {
+ char name[MAX_SESSION_NAME];
+ _vm1->getStringFromArray(args[0], name, sizeof(name));
+ return _vm1->_moonbase->_net->createSession(name);
+}
+
+int LogicHEmoonbase::op_net_join_session(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->joinSession(args[0] - 1);
+}
+
+int LogicHEmoonbase::op_net_end_session(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->endSession();
+}
+
+int LogicHEmoonbase::op_net_disable_session_player_join(int op, int numArgs, int32 *args) {
+ _vm1->_moonbase->_net->disableSessionJoining();
+ return 1;
+}
+
+int LogicHEmoonbase::op_net_enable_session_player_join(int op, int numArgs, int32 *args) {
+ _vm1->_moonbase->_net->enableSessionJoining();
+ return 1;
+}
+
+int LogicHEmoonbase::op_net_set_ai_player_count(int op, int numArgs, int32 *args) {
+ _vm1->_moonbase->_net->setBotsCount(args[0]);
+ return 1;
+}
+
+int LogicHEmoonbase::op_net_add_user(int op, int numArgs, int32 *args) {
+ char userName[MAX_PLAYER_NAME];
+ _vm1->getStringFromArray(args[0], userName, sizeof(userName));
+ return _vm1->_moonbase->_net->addUser(userName, userName);
+}
+
+int LogicHEmoonbase::op_net_remove_user(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->removeUser();
+}
+
+int LogicHEmoonbase::op_net_who_sent_this(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->whoSentThis();
+}
+
+int LogicHEmoonbase::op_net_who_am_i(int op, int numArgs, int32 *args) {
+ return _vm1->_moonbase->_net->whoAmI();
+}
+
+int LogicHEmoonbase::op_net_set_provider_by_name(int op, int numArgs, int32 *args) {
+ // Parameter 1 is the provider name and
+ // Parameter 2 is the (optional) tcp/ip address
+ return _vm1->_moonbase->_net->setProviderByName(args[0], args[1]);
+}
+
+void LogicHEmoonbase::op_net_set_fake_latency(int op, int numArgs, int32 *args) {
+ _vm1->_moonbase->_net->setFakeLatency(args[0]);
+}
+
+int LogicHEmoonbase::op_net_get_host_name(int op, int numArgs, int32 *args) {
+ char name[MAX_HOSTNAME_SIZE];
+
+ if (_vm1->_moonbase->_net->getHostName(name, MAX_HOSTNAME_SIZE)) {
+ return _vm1->setupStringArrayFromString(name);
+ }
+
+ return 0;
+}
+
+int LogicHEmoonbase::op_net_get_ip_from_name(int op, int numArgs, int32 *args) {
+ char name[MAX_HOSTNAME_SIZE];
+ _vm1->getStringFromArray(args[0], name, sizeof(name));
+
+ char ip[MAX_IP_SIZE];
+
+ if (_vm1->_moonbase->_net->getIPfromName(ip, MAX_IP_SIZE, name)) {
+ return _vm1->setupStringArrayFromString(ip);
+ }
+
+ return 0;
+}
+
+int LogicHEmoonbase::op_net_host_tcpip_game(int op, int numArgs, int32 *args) {
+ char sessionName[MAX_SESSION_NAME];
+ char userName[MAX_PLAYER_NAME];
+
+ _vm1->getStringFromArray(args[0], sessionName, sizeof(sessionName));
+ _vm1->getStringFromArray(args[1], userName, sizeof(userName));
+
+ return _vm1->_moonbase->_net->hostGame(sessionName, userName);
+}
+
+int LogicHEmoonbase::op_net_join_tcpip_game(int op, int numArgs, int32 *args) {
+ char ip[MAX_IP_SIZE];
+ char userName[MAX_PLAYER_NAME];
+
+ _vm1->getStringFromArray(args[0], ip, sizeof(ip));
+ _vm1->getStringFromArray(args[1], userName, sizeof(userName));
+
+ return _vm1->_moonbase->_net->joinGame(ip, userName);
+}
+#endif
+
LogicHE *makeLogicHEmoonbase(ScummEngine_v100he *vm) {
return new LogicHEmoonbase(vm);
}
diff --git a/engines/scumm/he/moonbase/moonbase.cpp b/engines/scumm/he/moonbase/moonbase.cpp
index 15ababd321..941f32db23 100644
--- a/engines/scumm/he/moonbase/moonbase.cpp
+++ b/engines/scumm/he/moonbase/moonbase.cpp
@@ -23,6 +23,9 @@
#include "scumm/he/intern_he.h"
#include "scumm/he/moonbase/moonbase.h"
#include "scumm/he/moonbase/ai_main.h"
+#ifdef USE_SDL_NET
+#include "scumm/he/moonbase/net_main.h"
+#endif
namespace Scumm {
@@ -30,10 +33,16 @@ Moonbase::Moonbase(ScummEngine_v100he *vm) : _vm(vm) {
initFOW();
_ai = new AI(_vm);
+#ifdef USE_SDL_NET
+ _net = new Net(_vm);
+#endif
}
Moonbase::~Moonbase() {
delete _ai;
+#ifdef USE_SDL_NET
+ delete _net;
+#endif
}
int Moonbase::readFromArray(int array, int y, int x) {
diff --git a/engines/scumm/he/moonbase/moonbase.h b/engines/scumm/he/moonbase/moonbase.h
index 71c03cb007..f3399ef192 100644
--- a/engines/scumm/he/moonbase/moonbase.h
+++ b/engines/scumm/he/moonbase/moonbase.h
@@ -30,6 +30,7 @@
namespace Scumm {
class AI;
+class Net;
class Moonbase {
public:
@@ -71,6 +72,9 @@ public:
uint32 _fowSentinelConditionBits;
AI *_ai;
+#ifdef USE_SDL_NET
+ Net *_net;
+#endif
private:
ScummEngine_v100he *_vm;
diff --git a/engines/scumm/he/moonbase/net_defines.h b/engines/scumm/he/moonbase/net_defines.h
new file mode 100644
index 0000000000..130ca1db94
--- /dev/null
+++ b/engines/scumm/he/moonbase/net_defines.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 SCUMM_HE_MOONBASE_NET_DEFINES_H
+#define SCUMM_HE_MOONBASE_NET_DEFINES_H
+
+namespace Scumm {
+
+// pnetwin.h
+
+#define PN_PRIORITY_HIGH 0x00000001
+
+#define PN_SENDTYPE_INDIVIDUAL 1
+#define PN_SENDTYPE_GROUP 2
+#define PN_SENDTYPE_HOST 3
+#define PN_SENDTYPE_ALL 4
+
+#define MAX_GAME_NAME 128 /* Used for the multiplayer networking code */
+#define MAX_PLAYER_NAME 128 /* Used for the multiplayer networking code */
+#define MAX_PROVIDER_NAME 128
+#define MAX_SESSION_NAME 128
+
+#define MAX_GAMES_POLLED 16
+#define MAX_PROVIDERS 16
+
+#define PACKETTYPE_REMOTESTARTSCRIPT 1
+#define PACKETTYPE_REMOTESTARTSCRIPTRETURN 2
+#define PACKETTYPE_REMOTESTARTSCRIPTRESULT 3
+#define PACKETTYPE_REMOTESENDSCUMMARRAY 4
+
+const int SESSION_ERROR = 0;
+const int USER_CREATED_SESSION = 1;
+const int USER_JOINED_SESSION = 2;
+
+const int TCPIP_PROVIDER = -1;
+const int NO_PROVIDER = -2;
+
+const int MAX_PACKET_SIZE = 4096; // bytes
+const int MAX_HOSTNAME_SIZE = 256;
+const int MAX_IP_SIZE = 32;
+const char LOCAL_HOST[] = "127.0.0.1"; //localhost
+
+#define NULL_IP ""; //no IP address (causes enumsessions to search local subnet)
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/he/moonbase/net_main.cpp b/engines/scumm/he/moonbase/net_main.cpp
new file mode 100644
index 0000000000..cdc2eef333
--- /dev/null
+++ b/engines/scumm/he/moonbase/net_main.cpp
@@ -0,0 +1,199 @@
+/* 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 "scumm/he/intern_he.h"
+#include "scumm/he/moonbase/moonbase.h"
+#include "scumm/he/moonbase/net_main.h"
+
+namespace Scumm {
+
+Net::Net(ScummEngine_v100he *vm) : _latencyTime(1), _fakeLatency(false), _vm(vm) {
+ //some defaults for fields
+}
+
+int Net::hostGame(char *sessionName, char *userName) {
+ warning("STUB: op_net_host_tcpip_game(\"%s\", \"%s\")", sessionName, userName); // PN_HostTCPIPGame
+ return 0;
+}
+
+int Net::joinGame(char *IP, char *userName) {
+ warning("STUB: Net::joinGame(\"%s\", \"%s\")", IP, userName); // PN_JoinTCPIPGame
+ return 0;
+}
+
+int Net::addUser(char *shortName, char *longName) {
+ warning("STUB: Net::addUser(\"%s\", \"%s\")", shortName, longName); // PN_AddUser
+ return 0;
+}
+
+int Net::removeUser() {
+ warning("STUB: Net::removeUser()"); // PN_RemoveUser
+ return 0;
+}
+
+int Net::whoSentThis() {
+ warning("STUB: Net::whoSentThis()"); // PN_WhoSentThis
+ return 0;
+}
+
+int Net::whoAmI() {
+ warning("STUB: Net::whoAmI()"); // PN_WhoAmI
+ return 0;
+}
+
+int Net::createSession(char *name) {
+ warning("STUB: Net::createSession(\"%s\")", name); // PN_CreateSession
+ return 0;
+}
+
+int Net::joinSession(int sessionIndex) {
+ warning("STUB: Net::joinSession(%d)", sessionIndex); // PN_JoinSession
+ return 0;
+}
+
+int Net::endSession() {
+ warning("STUB: Net::endSession()"); // PN_EndSession
+ return 0;
+}
+
+void Net::disableSessionJoining() {
+ warning("STUB: Net::disableSessionJoining()"); // PN_DisableSessionPlayerJoin
+}
+
+void Net::enableSessionJoining() {
+ warning("STUB: Net::enableSessionJoining()"); // PN_EnableSessionPlayerJoin
+}
+
+void Net::setBotsCount(int botsCount) {
+ warning("STUB: Net::setBotsCount(%d)", botsCount); // PN_SetAIPlayerCountKludge
+}
+
+int32 Net::setProviderByName(int32 parameter1, int32 parameter2) {
+ warning("STUB: Net::setProviderByName(%d, %d)", parameter1, parameter2); // PN_SetProviderByName
+ return 0;
+}
+
+void Net::setFakeLatency(int time) {
+ _latencyTime = time;
+ debug("NETWORK: Setting Fake Latency to %d ms \n", _latencyTime); // TODO: is it OK to use debug instead of SPUTM_xprintf?
+ _fakeLatency = true;
+}
+
+bool Net::destroyPlayer(int32 playerDPID) {
+ // bool PNETWIN_destroyplayer(DPID idPlayer)
+ warning("STUB: Net::destroyPlayer(%d)", playerDPID);
+ return false;
+}
+
+int32 Net::startQuerySessions() {
+ warning("STUB: Net::startQuerySessions()"); // StartQuerySessions
+ return 0;
+}
+
+int32 Net::updateQuerySessions() {
+ warning("STUB: Net::updateQuerySessions()"); // UpdateQuerySessions
+ return 0;
+}
+
+void Net::stopQuerySessions() {
+ warning("STUB: Net::stopQuerySessions()"); // StopQuerySessions
+}
+
+int Net::querySessions() {
+ warning("STUB: Net::querySessions()"); // PN_QuerySessions
+ return 0;
+}
+
+int Net::queryProviders() {
+ warning("STUB: Net::queryProviders()"); // PN_QueryProviders
+ return 0;
+}
+
+int Net::setProvider(int providerIndex) {
+ warning("STUB: Net::setProvider(%d)", providerIndex); // PN_SetProvider
+ return 0;
+}
+
+int Net::closeProvider() {
+ warning("STUB: Net::closeProvider()"); // PN_CloseProvider
+ return 0;
+}
+
+bool Net::initAll() {
+ warning("STUB: Net::initAll()"); // PN_DoInitAll
+ return false;
+}
+
+bool Net::initProvider() {
+ warning("STUB: Net::initProvider()"); // PN_DoInitProvider
+ return false;
+}
+
+bool Net::initSession() {
+ warning("STUB: Net::initSession()"); // PN_DoInitSession
+ return false;
+}
+
+bool Net::initUser() {
+ warning("STUB: Net::initUser()"); // PN_DoInitUser
+ return false;
+}
+
+void Net::remoteStartScript(int typeOfSend, int sendTypeParam, int priority, int argsCount, int32 *args) {
+ warning("STUB: Net::remoteStartScript(%d, %d, %d, %d, ...)", typeOfSend, sendTypeParam, priority, argsCount); // PN_RemoteStartScriptCommand
+}
+
+void Net::remoteSendArray(int typeOfSend, int sendTypeParam, int priority, int arrayIndex) {
+ warning("STUB: Net::remoteSendArray(%d, %d, %d, %d)", typeOfSend, sendTypeParam, priority, arrayIndex); // PN_RemoteSendArrayCommand
+}
+
+int Net::remoteStartScriptFunction(int typeOfSend, int sendTypeParam, int priority, int defaultReturnValue, int argsCount, int32 *args) {
+ warning("STUB: Net::remoteStartScriptFunction(%d, %d, %d, %d, %d, ...)", typeOfSend, sendTypeParam, priority, defaultReturnValue, argsCount); // PN_RemoteStartScriptFunction
+ return 0;
+}
+
+bool Net::getHostName(char *hostname, int length) {
+ warning("STUB: Net::getHostName(\"%s\", %d)", hostname, length); // PN_GetHostName
+ return false;
+}
+
+bool Net::getIPfromName(char *ip, int ipLength, char *nameBuffer) {
+ warning("STUB: Net::getIPfromName(\"%s\", %d, \"%s\")", ip, ipLength, nameBuffer); // PN_GetIPfromName
+ return false;
+}
+
+void Net::getSessionName(int sessionNumber, char *buffer, int length) {
+ warning("STUB: Net::getSessionName(%d, \"%s\", %d)", sessionNumber, buffer, length); // PN_GetSessionName
+}
+
+int Net::getSessionPlayerCount(int sessionNumber) {
+ warning("STUB: Net::getSessionPlayerCount(%d)", sessionNumber); // case GET_SESSION_PLAYER_COUNT_KLUDGE:
+ //assert(sessionNumber >= 0 && sessionNumber < NUMELEMENTS(gdefMultiPlay.gamedescptr));
+ //return gdefMultiPlay.gamedescptr[sessionNumber].currentplayers;
+ return 0;
+}
+
+void Net::getProviderName(int providerIndex, char *buffer, int length) {
+ warning("STUB: Net::getProviderName(%d, \"%s\", %d)", providerIndex, buffer, length); // PN_GetProviderName
+}
+
+} // End of namespace Scumm
diff --git a/engines/scumm/he/moonbase/net_main.h b/engines/scumm/he/moonbase/net_main.h
new file mode 100644
index 0000000000..8350904fcd
--- /dev/null
+++ b/engines/scumm/he/moonbase/net_main.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 SCUMM_HE_MOONBASE_NET_MAIN_H
+#define SCUMM_HE_MOONBASE_NET_MAIN_H
+
+namespace Scumm {
+
+class ScummEngine_v100he;
+
+//this is a dummy based on ai_main.h Scumm::AI
+
+class Net {
+public:
+ Net(ScummEngine_v100he *vm);
+
+ int hostGame(char *sessionName, char *userName);
+ int joinGame(char *IP, char *userName);
+ int addUser(char *shortName, char *longName);
+ int removeUser();
+ int whoSentThis();
+ int whoAmI();
+ int createSession(char *name);
+ int joinSession(int sessionIndex);
+ int endSession();
+ void disableSessionJoining();
+ void enableSessionJoining();
+ void setBotsCount(int botsCount);
+ int32 setProviderByName(int32 parameter1, int32 parameter2);
+ void setFakeLatency(int time);
+ bool destroyPlayer(int32 playerDPID);
+ int32 startQuerySessions();
+ int32 updateQuerySessions();
+ void stopQuerySessions();
+ int querySessions();
+ int queryProviders();
+ int setProvider(int providerIndex);
+ int closeProvider();
+ bool initAll();
+ bool initProvider();
+ bool initSession();
+ bool initUser();
+ void remoteStartScript(int typeOfSend, int sendTypeParam, int priority, int argsCount, int32 *args);
+ void remoteSendArray(int typeOfSend, int sendTypeParam, int priority, int arrayIndex);
+ int remoteStartScriptFunction(int typeOfSend, int sendTypeParam, int priority, int defaultReturnValue, int argsCount, int32 *args);
+
+private:
+
+public:
+ //getters
+ bool getHostName(char *hostname, int length);
+ bool getIPfromName(char *ip, int ipLength, char *nameBuffer);
+ void getSessionName(int sessionNumber, char *buffer, int length);
+ int getSessionPlayerCount(int sessionNumber);
+ void getProviderName(int providerIndex, char *buffer, int length);
+
+private:
+ //mostly getters
+
+public:
+ //fields
+ int _latencyTime; // ms
+ bool _fakeLatency;
+
+ ScummEngine_v100he *_vm;
+};
+
+} // End of namespace Scumm
+
+#endif
diff --git a/engines/scumm/he/script_v72he.cpp b/engines/scumm/he/script_v72he.cpp
index d32eb766cb..c764de7da1 100644
--- a/engines/scumm/he/script_v72he.cpp
+++ b/engines/scumm/he/script_v72he.cpp
@@ -230,6 +230,23 @@ int ScummEngine_v72he::setupStringArray(int size) {
return readVar(0);
}
+int ScummEngine_v72he::setupStringArrayFromString(char *cStr) {
+ // this is PUI_ScummStringArrayFromCString() found in PUSERMAC.cpp
+ // I can see how its done up there in setupStringArray()
+ // yet I'd note that 'SCUMMVAR_user_reserved' var was used instead of 0
+ // and strlen(), not strlen() + 1 was used
+ // plus, this function actually copies the string, not just 'sets up' the array
+
+ writeVar(0, 0);
+
+ int len = strlen(cStr) + 1;
+ byte *ptr = defineArray(0, kStringArray, 0, 0, 0, len);
+ if (ptr != nullptr)
+ Common::strlcpy((char*)ptr, cStr, len);
+
+ return readVar(0);
+}
+
void ScummEngine_v72he::readArrayFromIndexFile() {
int num;
int a, b, c;
@@ -1484,6 +1501,26 @@ void ScummEngine_v72he::writeFileFromArray(int slot, int32 resID) {
}
}
+void ScummEngine_v72he::getStringFromArray(int arrayNumber, char *buffer, int maxLength) {
+ // I'm not really sure it belongs here and not some other version
+ // this is ARRAY_GetStringFromArray() from ARRAYS.cpp of SPUTM
+
+ // this function makes a C-string out of <arrayNumber> contents
+
+ VAR(0) = arrayNumber; // it was 0 in original code, but I've seen ScummVM Moonbase code which uses VAR_U32_ARRAY_UNK
+
+ int i, ch;
+ for (i = 0; i < maxLength; ++i) {
+ if (!(ch = readArray(0, 0, i))) {
+ break;
+ }
+
+ buffer[i] = ch;
+ }
+
+ buffer[i] = 0;
+}
+
void ScummEngine_v72he::o72_writeFile() {
int32 resID = pop();
int slot = pop();
diff --git a/engines/scumm/module.mk b/engines/scumm/module.mk
index 85ff1aa4cd..fee61ec29f 100644
--- a/engines/scumm/module.mk
+++ b/engines/scumm/module.mk
@@ -150,6 +150,11 @@ MODULE_OBJS += \
he/moonbase/distortion.o \
he/moonbase/moonbase.o \
he/moonbase/moonbase_fow.o
+
+ifdef USE_SDL_NET
+MODULE_OBJS += \
+ he/moonbase/net_main.o
+endif
endif
# This module can be built as a plugin
diff --git a/engines/scumm/object.cpp b/engines/scumm/object.cpp
index da94a34baf..cbc24a8b7e 100644
--- a/engines/scumm/object.cpp
+++ b/engines/scumm/object.cpp
@@ -110,6 +110,16 @@ void ScummEngine::setOwnerOf(int obj, int owner) {
// This causes it to try to remove object 0 from the inventory.
if (_game.id == GID_PASS && obj == 0 && vm.slot[_currentScript].number == 94)
return;
+
+ // WORKAROUND for bug #6802: assert() was triggered in freddi2.
+ // Bug is in room 39. Problem is script 10, in the localvar2==78 case;
+ // this only sets the obj id if var198 is non-zero, but in the asserting
+ // case, it is obj 0. That means two setOwnerOf calls are made with obj 0.
+ // The correct setOwnerOf calls are made afterwards, so just ignoring this
+ // seems to work just fine.
+ if (_game.id == GID_HEGAME && obj == 0 && _currentRoom == 39 && vm.slot[_currentScript].number == 10)
+ return;
+
assert(obj > 0);
if (owner == 0) {
diff --git a/engines/scumm/script_v6.cpp b/engines/scumm/script_v6.cpp
index 6c81f17f2f..62c62c0b4a 100644
--- a/engines/scumm/script_v6.cpp
+++ b/engines/scumm/script_v6.cpp
@@ -707,6 +707,17 @@ void ScummEngine_v6::o6_ifNot() {
void ScummEngine_v6::o6_jump() {
int offset = fetchScriptWordSigned();
+ // WORKAROUND bug #6097: Pressing escape at the lake side entrance of
+ // the cave while Putt Putt is not on solid ground and still talking
+ // will cause the raft to disappear. This is a script bug in the
+ // original game and affects several versions.
+ if (_game.id == GID_PUTTZOO) {
+ if (_game.heversion == 73 && vm.slot[_currentScript].number == 206 && offset == 176 && !isScriptRunning(202))
+ _scummVars[244] = 35;
+ if (_game.features & GF_HE_985 && vm.slot[_currentScript].number == 2054 && offset == 178 && !isScriptRunning(2050))
+ _scummVars[202] = 35;
+ }
+
// WORKAROUND bug #2826144: Talking to the guard at the bigfoot party, after
// he's let you inside, will cause the game to hang, if you end the conversation.
// This is a script bug, due to a missing jump in one segment of the script.
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 68e4887b00..81372d0586 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 Sat Apr 30 14:24:41 2016
+ This file was generated by the md5table tool on Mon Aug 8 18:46:17 2016
DO NOT EDIT MANUALLY!
*/
@@ -188,6 +188,7 @@ static const MD5Table md5table[] = {
{ "3b301b7892f883ce42ab4be6a274fea6", "samnmax", "Floppy", "Floppy", -1, Common::EN_ANY, Common::kPlatformDOS },
{ "3b832f4a90740bf22e9b8ed42ca0128c", "freddi4", "HE 99", "", -1, Common::EN_GRB, Common::kPlatformUnknown },
{ "3c4c471342bd95505a42334367d8f127", "puttmoon", "HE 70", "", 12161, Common::RU_RUS, Common::kPlatformWindows },
+ { "3c90d2a39cafa60b8ebce70a34a59a41", "airport", "", "Demo", 51152, Common::NL_NLD, Common::kPlatformWindows },
{ "3cce1913a3bc586b51a75c3892ff18dd", "indy3", "VGA", "VGA", -1, Common::RU_RUS, Common::kPlatformDOS },
{ "3cf4b6ff78f735b671d8ccc2bc110b15", "maniac", "V2", "V2", -1, Common::ES_ESP, Common::kPlatformAmiga },
{ "3d219e7546039543307b55a91282bf18", "funpack", "", "", -1, Common::EN_ANY, Common::kPlatformDOS },
diff --git a/engines/scumm/scumm.cpp b/engines/scumm/scumm.cpp
index 72c6909f8c..107228453e 100644
--- a/engines/scumm/scumm.cpp
+++ b/engines/scumm/scumm.cpp
@@ -2589,6 +2589,30 @@ void ScummEngine_v60he::setHETimer(int timer) {
_heTimers[timer] = _system->getMillis();
}
+void ScummEngine_v60he::pauseHETimers(bool pause) {
+ // The HE timers rely on system time which of course doesn't pause when
+ // the engine does. By adding the elapsed time we compensate for this.
+ // Fixes bug #6352
+ if (pause) {
+ // Pauses can be layered, we only need the start of the first
+ if (!_pauseStartTime)
+ _pauseStartTime = _system->getMillis();
+ } else {
+ int elapsedTime = _system->getMillis() - _pauseStartTime;
+ for (int i = 0; i < ARRAYSIZE(_heTimers); i++) {
+ if (_heTimers[i] != 0)
+ _heTimers[i] += elapsedTime;
+ }
+ _pauseStartTime = 0;
+ }
+}
+
+void ScummEngine_v60he::pauseEngineIntern(bool pause) {
+ pauseHETimers(pause);
+
+ ScummEngine::pauseEngineIntern(pause);
+}
+
void ScummEngine::pauseGame() {
pauseDialog();
}
diff --git a/engines/sherlock/detection.cpp b/engines/sherlock/detection.cpp
index 5a94b3485f..c6e632f999 100644
--- a/engines/sherlock/detection.cpp
+++ b/engines/sherlock/detection.cpp
@@ -199,7 +199,8 @@ bool SherlockMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
- (f == kSavesSupportThumbnail);
+ (f == kSavesSupportThumbnail) ||
+ (f == kSimpleSavesNames);
}
bool Sherlock::SherlockEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/sky/detection.cpp b/engines/sky/detection.cpp
index 4b91f50a61..d86689e5d7 100644
--- a/engines/sky/detection.cpp
+++ b/engines/sky/detection.cpp
@@ -78,7 +78,7 @@ public:
virtual bool hasFeature(MetaEngineFeature f) const;
virtual GameList getSupportedGames() const;
virtual const ExtraGuiOptions getExtraGuiOptions(const Common::String &target) const;
- virtual GameDescriptor findGame(const char *gameid) const;
+ virtual GameDescriptor findGame(const char *gameid) const;
virtual GameList detectGames(const Common::FSList &fslist) const;
virtual Common::Error createInstance(OSystem *syst, Engine **engine) const;
diff --git a/engines/sword2/sword2.cpp b/engines/sword2/sword2.cpp
index 44371bf6cf..4f3caa437e 100644
--- a/engines/sword2/sword2.cpp
+++ b/engines/sword2/sword2.cpp
@@ -107,7 +107,8 @@ bool Sword2MetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
- (f == kSupportsDeleteSave);
+ (f == kSupportsDeleteSave) ||
+ (f == kSimpleSavesNames);
}
bool Sword2::Sword2Engine::hasFeature(EngineFeature f) const {
diff --git a/engines/testbed/cloud.cpp b/engines/testbed/cloud.cpp
new file mode 100644
index 0000000000..a2d62733be
--- /dev/null
+++ b/engines/testbed/cloud.cpp
@@ -0,0 +1,565 @@
+/* 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/config-manager.h"
+#include "common/stream.h"
+#include "common/util.h"
+#include "testbed/fs.h"
+#include "testbed/cloud.h"
+#include <backends/cloud/cloudmanager.h>
+
+namespace Testbed {
+
+CloudTestSuite::CloudTestSuite() {
+ // Cloud tests depend on CloudMan.
+ // If there is no Storage connected to it, disable this test suite.
+ if (CloudMan.getCurrentStorage() == nullptr) {
+ logPrintf("WARNING! : No Storage connected to CloudMan found. Skipping Cloud tests\n");
+ Testsuite::enable(false);
+ }
+
+ addTest("UserInfo", &CloudTests::testInfo, true);
+ addTest("ListDirectory", &CloudTests::testDirectoryListing, true);
+ addTest("CreateDirectory", &CloudTests::testDirectoryCreating, true);
+ addTest("FileUpload", &CloudTests::testUploading, true);
+ addTest("FileDownload", &CloudTests::testDownloading, true);
+ addTest("FolderDownload", &CloudTests::testFolderDownloading, true);
+ addTest("SyncSaves", &CloudTests::testSavesSync, true);
+}
+
+/*
+void CloudTestSuite::enable(bool flag) {
+ Testsuite::enable(ConfParams.isGameDataFound() ? flag : false);
+}
+*/
+
+///// TESTS GO HERE /////
+
+bool CloudTests::waitForCallback() {
+ const int TIMEOUT = 30;
+
+ Common::Point pt;
+ pt.x = 10; pt.y = 10;
+ Testsuite::writeOnScreen("Waiting for callback...", pt);
+
+ int left = TIMEOUT;
+ while (--left) {
+ if (ConfParams.isCloudTestCallbackCalled()) return true;
+ if (ConfParams.isCloudTestErrorCallbackCalled()) return true;
+ g_system->delayMillis(1000);
+ }
+ return false;
+}
+
+bool CloudTests::waitForCallbackMore() {
+ while (!waitForCallback()) {
+ Common::String info = "It takes more time than expected. Do you want to skip the test or wait more?";
+ if (Testsuite::handleInteractiveInput(info, "Wait", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : info()\n");
+ return false;
+ }
+ }
+ return true;
+}
+
+const char *CloudTests::getRemoteTestPath() {
+ if (CloudMan.getStorageIndex() == Cloud::kStorageDropboxId)
+ return "/testbed";
+ return "testbed";
+}
+
+void CloudTests::infoCallback(Cloud::Storage::StorageInfoResponse response) {
+ ConfParams.setCloudTestCallbackCalled(true);
+ Testsuite::logPrintf("Info! User's ID: %s\n", response.value.uid().c_str());
+ Testsuite::logPrintf("Info! User's email: %s\n", response.value.email().c_str());
+ Testsuite::logPrintf("Info! User's name: %s\n", response.value.name().c_str());
+ Testsuite::logPrintf("Info! User's quota: %lu bytes used / %lu bytes available\n", response.value.used(), response.value.available());
+}
+
+void CloudTests::directoryListedCallback(Cloud::Storage::FileArrayResponse response) {
+ ConfParams.setCloudTestCallbackCalled(true);
+ if (response.value.size() == 0) {
+ Testsuite::logPrintf("Warning! Directory is empty!\n");
+ return;
+ }
+
+ Common::String directory, file;
+ uint32 directories = 0, files = 0;
+ for (uint32 i = 0; i < response.value.size(); ++i) {
+ if (response.value[i].isDirectory()) {
+ if (++directories == 1) directory = response.value[i].path();
+ } else {
+ if (++files == 1) file = response.value[i].name();
+ }
+ }
+
+ if (directories == 0) {
+ Testsuite::logPrintf("Info! %u files listed, first one is '%s'\n", files, file.c_str());
+ } else if (files == 0) {
+ Testsuite::logPrintf("Info! %u directories listed, first one is '%s'\n", directories, directory.c_str());
+ } else {
+ Testsuite::logPrintf("Info! %u directories and %u files listed\n", directories, files);
+ Testsuite::logPrintf("Info! First directory is '%s' and first file is '%s'\n", directory.c_str(), file.c_str());
+ }
+}
+
+void CloudTests::directoryCreatedCallback(Cloud::Storage::BoolResponse response) {
+ ConfParams.setCloudTestCallbackCalled(true);
+ if (response.value) {
+ Testsuite::logPrintf("Info! Directory created!\n");
+ } else {
+ Testsuite::logPrintf("Info! Such directory already exists!\n");
+ }
+}
+
+void CloudTests::fileUploadedCallback(Cloud::Storage::UploadResponse response) {
+ ConfParams.setCloudTestCallbackCalled(true);
+ Testsuite::logPrintf("Info! Uploaded file into '%s'\n", response.value.path().c_str());
+ Testsuite::logPrintf("Info! It's id = '%s' and size = '%u'\n", response.value.id().c_str(), response.value.size());
+}
+
+void CloudTests::fileDownloadedCallback(Cloud::Storage::BoolResponse response) {
+ ConfParams.setCloudTestCallbackCalled(true);
+ if (response.value) {
+ Testsuite::logPrintf("Info! File downloaded!\n");
+ } else {
+ Testsuite::logPrintf("Info! Failed to download the file!\n");
+ }
+}
+
+void CloudTests::directoryDownloadedCallback(Cloud::Storage::FileArrayResponse response) {
+ ConfParams.setCloudTestCallbackCalled(true);
+ if (response.value.size() == 0) {
+ Testsuite::logPrintf("Info! Directory is downloaded successfully!\n");
+ } else {
+ Testsuite::logPrintf("Warning! %u files were not downloaded during folder downloading!\n", response.value.size());
+ }
+}
+
+void CloudTests::savesSyncedCallback(Cloud::Storage::BoolResponse response) {
+ ConfParams.setCloudTestCallbackCalled(true);
+ if (response.value) {
+ Testsuite::logPrintf("Info! Saves are synced successfully!\n");
+ } else {
+ Testsuite::logPrintf("Warning! Saves were not synced!\n");
+ }
+}
+
+void CloudTests::errorCallback(Networking::ErrorResponse response) {
+ ConfParams.setCloudTestErrorCallbackCalled(true);
+ Testsuite::logPrintf("Info! Error Callback was called\n");
+ Testsuite::logPrintf("Info! code = %ld, message = %s\n", response.httpResponseCode, response.response.c_str());
+}
+
+/** This test calls Storage::info(). */
+
+TestExitStatus CloudTests::testInfo() {
+ ConfParams.setCloudTestCallbackCalled(false);
+ ConfParams.setCloudTestErrorCallbackCalled(false);
+
+ if (CloudMan.getCurrentStorage() == nullptr) {
+ Testsuite::logPrintf("Couldn't find connected Storage\n");
+ return kTestFailed;
+ }
+
+ Common::String info = Common::String::format(
+ "Welcome to the Cloud test suite!\n"
+ "We're going to use the %s cloud storage which is connected right now.\n\n"
+ "Testing Cloud Storage API info() method.\n"
+ "In this test we'll try to list user infomation.",
+ CloudMan.getCurrentStorage()->name().c_str()
+ );
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : info()\n");
+ return kTestSkipped;
+ }
+
+ if (CloudMan.info(
+ new Common::GlobalFunctionCallback<Cloud::Storage::StorageInfoResponse>(&infoCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Info was displayed\n");
+ return kTestPassed;
+}
+
+TestExitStatus CloudTests::testDirectoryListing() {
+ ConfParams.setCloudTestCallbackCalled(false);
+ ConfParams.setCloudTestErrorCallbackCalled(false);
+
+ if (CloudMan.getCurrentStorage() == nullptr) {
+ Testsuite::logPrintf("Couldn't find connected Storage\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Cloud Storage API listDirectory() method.\n"
+ "In this test we'll try to list root directory.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : listDirectory()\n");
+ return kTestSkipped;
+ }
+
+ if (CloudMan.listDirectory(
+ "",
+ new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryListedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Directory was listed\n");
+ return kTestPassed;
+}
+
+TestExitStatus CloudTests::testDirectoryCreating() {
+ ConfParams.setCloudTestCallbackCalled(false);
+ ConfParams.setCloudTestErrorCallbackCalled(false);
+
+ if (CloudMan.getCurrentStorage() == nullptr) {
+ Testsuite::logPrintf("Couldn't find connected Storage\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Cloud Storage API createDirectory() method.\n"
+ "In this test we'll try to create a 'testbed' directory.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : createDirectory()\n");
+ return kTestSkipped;
+ }
+
+ Common::String info2 = "We'd list the root directory, create the directory and the list it again.\n"
+ "If all goes smoothly, you'd notice that there are more directories now.";
+ Testsuite::displayMessage(info2);
+
+ // list root directory
+ if (CloudMan.listDirectory(
+ "",
+ new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryListedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ ConfParams.setCloudTestCallbackCalled(false);
+
+ // create 'testbed'
+ if (CloudMan.getCurrentStorage()->createDirectory(
+ getRemoteTestPath(),
+ new Common::GlobalFunctionCallback<Cloud::Storage::BoolResponse>(&directoryCreatedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ ConfParams.setCloudTestCallbackCalled(false);
+
+ // list it again
+ if (CloudMan.listDirectory(
+ "",
+ new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryListedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ if (Testsuite::handleInteractiveInput("Was the CloudMan able to create a 'testbed' directory?", "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! Directory was not created!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Directory was created\n");
+ return kTestPassed;
+}
+
+TestExitStatus CloudTests::testUploading() {
+ ConfParams.setCloudTestCallbackCalled(false);
+ ConfParams.setCloudTestErrorCallbackCalled(false);
+
+ if (CloudMan.getCurrentStorage() == nullptr) {
+ Testsuite::logPrintf("Couldn't find connected Storage\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Cloud Storage API upload() method.\n"
+ "In this test we'll try to upload a 'test1/file.txt' file.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : upload()\n");
+ return kTestSkipped;
+ }
+
+ if (!ConfParams.isGameDataFound()) {
+ Testsuite::logPrintf("Info! Couldn't find the game data, so skipping test : upload()\n");
+ return kTestSkipped;
+ }
+
+ const Common::String &path = ConfMan.get("path");
+ Common::FSDirectory gameRoot(path);
+ Common::FSDirectory *directory = gameRoot.getSubDirectory("test1");
+ Common::FSNode node = directory->getFSNode().getChild("file.txt");
+ delete directory;
+
+ if (CloudMan.getCurrentStorage()->uploadStreamSupported()) {
+ if (CloudMan.getCurrentStorage()->upload(
+ Common::String(getRemoteTestPath()) + "/testfile.txt",
+ node.createReadStream(),
+ new Common::GlobalFunctionCallback<Cloud::Storage::UploadResponse>(&fileUploadedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+ } else {
+ Common::String filepath = node.getPath();
+ if (CloudMan.getCurrentStorage()->upload(
+ Common::String(getRemoteTestPath()) + "/testfile.txt",
+ filepath.c_str(),
+ new Common::GlobalFunctionCallback<Cloud::Storage::UploadResponse>(&fileUploadedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ Common::String info2 = "upload() is finished. Do you want to list '/testbed' directory?";
+
+ if (!Testsuite::handleInteractiveInput(info2, "Yes", "No", kOptionRight)) {
+ ConfParams.setCloudTestCallbackCalled(false);
+
+ if (CloudMan.listDirectory(
+ getRemoteTestPath(),
+ new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryListedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+ }
+
+ if (Testsuite::handleInteractiveInput("Was the CloudMan able to upload into 'testbed/testfile.txt' file?", "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! File was not uploaded!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("File was uploaded\n");
+ return kTestPassed;
+}
+
+TestExitStatus CloudTests::testDownloading() {
+ ConfParams.setCloudTestCallbackCalled(false);
+ ConfParams.setCloudTestErrorCallbackCalled(false);
+
+ if (CloudMan.getCurrentStorage() == nullptr) {
+ Testsuite::logPrintf("Couldn't find connected Storage\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Cloud Storage API download() method.\n"
+ "In this test we'll try to download that 'testbed/testfile.txt' file.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : download()\n");
+ return kTestSkipped;
+ }
+
+ const Common::String &path = ConfMan.get("path");
+ Common::FSDirectory gameRoot(path);
+ Common::FSNode node = gameRoot.getFSNode().getChild("downloaded_file.txt");
+ Common::String filepath = node.getPath();
+ if (CloudMan.getCurrentStorage()->download(
+ Common::String(getRemoteTestPath()) + "/testfile.txt",
+ filepath.c_str(),
+ new Common::GlobalFunctionCallback<Cloud::Storage::BoolResponse>(&fileDownloadedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ if (Testsuite::handleInteractiveInput("Was the CloudMan able to download into 'testbed/downloaded_file.txt' file?", "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! File was not downloaded!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("File was downloaded\n");
+ return kTestPassed;
+}
+
+TestExitStatus CloudTests::testFolderDownloading() {
+ ConfParams.setCloudTestCallbackCalled(false);
+ ConfParams.setCloudTestErrorCallbackCalled(false);
+
+ if (CloudMan.getCurrentStorage() == nullptr) {
+ Testsuite::logPrintf("Couldn't find connected Storage\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Cloud Storage API downloadFolder() method.\n"
+ "In this test we'll try to download remote 'testbed/' directory.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : downloadFolder()\n");
+ return kTestSkipped;
+ }
+
+ const Common::String &path = ConfMan.get("path");
+ Common::FSDirectory gameRoot(path);
+ Common::FSNode node = gameRoot.getFSNode().getChild("downloaded_directory");
+ Common::String filepath = node.getPath();
+ if (CloudMan.downloadFolder(
+ getRemoteTestPath(),
+ filepath.c_str(),
+ new Common::GlobalFunctionCallback<Cloud::Storage::FileArrayResponse>(&directoryDownloadedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ if (Testsuite::handleInteractiveInput("Was the CloudMan able to download into 'testbed/downloaded_directory'?", "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! Directory was not downloaded!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Directory was downloaded\n");
+ return kTestPassed;
+}
+
+TestExitStatus CloudTests::testSavesSync() {
+ ConfParams.setCloudTestCallbackCalled(false);
+ ConfParams.setCloudTestErrorCallbackCalled(false);
+
+ if (CloudMan.getCurrentStorage() == nullptr) {
+ Testsuite::logPrintf("Couldn't find connected Storage\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Cloud Storage API syncSaves() method.\n"
+ "In this test we'll try to sync your saves.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : syncSaves()\n");
+ return kTestSkipped;
+ }
+
+ const Common::String &path = ConfMan.get("path");
+ Common::FSDirectory gameRoot(path);
+ Common::FSNode node = gameRoot.getFSNode().getChild("downloaded_directory");
+ Common::String filepath = node.getPath();
+ if (CloudMan.syncSaves(
+ new Common::GlobalFunctionCallback<Cloud::Storage::BoolResponse>(&savesSyncedCallback),
+ new Common::GlobalFunctionCallback<Networking::ErrorResponse>(&errorCallback)
+ ) == nullptr) {
+ Testsuite::logPrintf("Warning! No Request is returned!\n");
+ }
+
+ if (!waitForCallbackMore()) return kTestSkipped;
+ Testsuite::clearScreen();
+
+ if (ConfParams.isCloudTestErrorCallbackCalled()) {
+ Testsuite::logPrintf("Error callback was called\n");
+ return kTestFailed;
+ }
+
+ if (Testsuite::handleInteractiveInput("Was the CloudMan able to sync saves?", "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! Saves were not synced!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Saves were synced successfully\n");
+ return kTestPassed;
+}
+
+} // End of namespace Testbed
diff --git a/engines/testbed/cloud.h b/engines/testbed/cloud.h
new file mode 100644
index 0000000000..ed27d7da82
--- /dev/null
+++ b/engines/testbed/cloud.h
@@ -0,0 +1,83 @@
+/* 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 TESTBED_CLOUD_H
+#define TESTBED_CLOUD_H
+
+#include "testbed/testsuite.h"
+#include <backends/cloud/storage.h>
+
+// This file can be used as template for header files of other newer testsuites.
+
+namespace Testbed {
+
+namespace CloudTests {
+
+// Helper functions for Cloud tests
+
+bool waitForCallback();
+bool waitForCallbackMore();
+const char *getRemoteTestPath();
+void infoCallback(Cloud::Storage::StorageInfoResponse response);
+void directoryListedCallback(Cloud::Storage::FileArrayResponse response);
+void directoryCreatedCallback(Cloud::Storage::BoolResponse response);
+void fileUploadedCallback(Cloud::Storage::UploadResponse response);
+void fileDownloadedCallback(Cloud::Storage::BoolResponse response);
+void directoryDownloadedCallback(Cloud::Storage::FileArrayResponse response);
+void savesSyncedCallback(Cloud::Storage::BoolResponse response);
+void errorCallback(Networking::ErrorResponse response);
+
+TestExitStatus testInfo();
+TestExitStatus testDirectoryListing();
+TestExitStatus testDirectoryCreating();
+TestExitStatus testUploading();
+TestExitStatus testDownloading();
+TestExitStatus testFolderDownloading();
+TestExitStatus testSavesSync();
+
+} // End of namespace CloudTests
+
+class CloudTestSuite : public Testsuite {
+public:
+ /**
+ * The constructor for the CloudTestSuite
+ * For every test to be executed one must:
+ * 1) Create a function that would invoke the test
+ * 2) Add that test to list by executing addTest()
+ *
+ * @see addTest()
+ */
+ CloudTestSuite();
+ ~CloudTestSuite() {}
+ const char *getName() const {
+ return "Cloud";
+ }
+
+ const char *getDescription() const {
+ return "CloudMan, Storage API tests";
+ }
+
+};
+
+} // End of namespace Testbed
+
+#endif // TESTBED_TEMPLATE_H
diff --git a/engines/testbed/config-params.h b/engines/testbed/config-params.h
index 89aae199b6..57fd099bf4 100644
--- a/engines/testbed/config-params.h
+++ b/engines/testbed/config-params.h
@@ -54,6 +54,10 @@ private:
*/
bool _isInteractive;
bool _isGameDataFound;
+#ifdef USE_LIBCURL
+ bool _isCloudTestCallbackCalled;
+ bool _isCloudTestErrorCallbackCalled;
+#endif
bool _rerunTests;
TestbedConfigManager *_testbedConfMan;
@@ -68,6 +72,14 @@ public:
bool isGameDataFound() { return _isGameDataFound; }
void setGameDataFound(bool status) { _isGameDataFound = status; }
+#ifdef USE_LIBCURL
+ bool isCloudTestCallbackCalled() const { return _isCloudTestCallbackCalled; }
+ void setCloudTestCallbackCalled(bool status) { _isCloudTestCallbackCalled = status; }
+
+ bool isCloudTestErrorCallbackCalled() const { return _isCloudTestErrorCallbackCalled; }
+ void setCloudTestErrorCallbackCalled(bool status) { _isCloudTestErrorCallbackCalled = status; }
+#endif
+
TestbedConfigManager *getTestbedConfigManager() { return _testbedConfMan; }
void setTestbedConfigManager(TestbedConfigManager* confMan) { _testbedConfMan = confMan; }
diff --git a/engines/testbed/misc.cpp b/engines/testbed/misc.cpp
index 5847a8d2e4..20651e76e6 100644
--- a/engines/testbed/misc.cpp
+++ b/engines/testbed/misc.cpp
@@ -22,6 +22,7 @@
#include "testbed/misc.h"
#include "common/timer.h"
+#include <backends/networking/browser/openurl.h>
namespace Testbed {
@@ -160,10 +161,34 @@ TestExitStatus MiscTests::testMutexes() {
return kTestFailed;
}
+TestExitStatus MiscTests::testOpenUrl() {
+ Common::String info = "Testing openUrl() method.\n"
+ "In this test we'll try to open scummvm.org in your default browser.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : openUrl()\n");
+ return kTestSkipped;
+ }
+
+ if (!Networking::Browser::openUrl("http://scummvm.org/")) {
+ Testsuite::logPrintf("Info! openUrl() says it couldn't open the url (probably not supported on this platform)\n");
+ return kTestFailed;
+ }
+
+ if (Testsuite::handleInteractiveInput("Was ScummVM able to open 'http://scummvm.org/' in your default browser?", "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! openUrl() is not working!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("openUrl() is OK\n");
+ return kTestPassed;
+}
+
MiscTestSuite::MiscTestSuite() {
addTest("Datetime", &MiscTests::testDateTime, false);
addTest("Timers", &MiscTests::testTimers, false);
addTest("Mutexes", &MiscTests::testMutexes, false);
+ addTest("openUrl", &MiscTests::testOpenUrl, true);
}
} // End of namespace Testbed
diff --git a/engines/testbed/misc.h b/engines/testbed/misc.h
index 23e303d676..adfc322d9f 100644
--- a/engines/testbed/misc.h
+++ b/engines/testbed/misc.h
@@ -49,6 +49,7 @@ void criticalSection(void *arg);
TestExitStatus testDateTime();
TestExitStatus testTimers();
TestExitStatus testMutexes();
+TestExitStatus testOpenUrl();
// add more here
} // End of namespace MiscTests
@@ -69,7 +70,7 @@ public:
return "Misc";
}
const char *getDescription() const {
- return "Miscellaneous: Timers/Mutexes/Datetime";
+ return "Miscellaneous: Timers/Mutexes/Datetime/openUrl";
}
};
diff --git a/engines/testbed/module.mk b/engines/testbed/module.mk
index ce78a48bc5..99e6157cde 100644
--- a/engines/testbed/module.mk
+++ b/engines/testbed/module.mk
@@ -14,6 +14,16 @@ MODULE_OBJS := \
testbed.o \
testsuite.o
+ifdef USE_LIBCURL
+MODULE_OBJS += \
+ cloud.o
+endif
+
+ifdef USE_SDL_NET
+MODULE_OBJS += \
+ webserver.o
+endif
+
MODULE_DIRS += \
engines/testbed
diff --git a/engines/testbed/testbed.cpp b/engines/testbed/testbed.cpp
index 885429cafd..5943d47c58 100644
--- a/engines/testbed/testbed.cpp
+++ b/engines/testbed/testbed.cpp
@@ -39,6 +39,12 @@
#include "testbed/savegame.h"
#include "testbed/sound.h"
#include "testbed/testbed.h"
+#ifdef USE_LIBCURL
+#include "testbed/cloud.h"
+#endif
+#ifdef USE_SDL_NET
+#include "testbed/webserver.h"
+#endif
namespace Testbed {
@@ -134,6 +140,16 @@ TestbedEngine::TestbedEngine(OSystem *syst)
// Midi
ts = new MidiTestSuite();
_testsuiteList.push_back(ts);
+#ifdef USE_LIBCURL
+ // Cloud
+ ts = new CloudTestSuite();
+ _testsuiteList.push_back(ts);
+#endif
+#ifdef USE_SDL_NET
+ // Webserver
+ ts = new WebserverTestSuite();
+ _testsuiteList.push_back(ts);
+#endif
}
TestbedEngine::~TestbedEngine() {
diff --git a/engines/testbed/webserver.cpp b/engines/testbed/webserver.cpp
new file mode 100644
index 0000000000..feb2911704
--- /dev/null
+++ b/engines/testbed/webserver.cpp
@@ -0,0 +1,237 @@
+/* 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 "testbed/webserver.h"
+#include "backends/networking/sdl_net/localwebserver.h"
+#include "common/config-manager.h"
+#include <backends/networking/browser/openurl.h>
+
+namespace Testbed {
+
+WebserverTestSuite::WebserverTestSuite() {
+ addTest("ResolveIP", &WebserverTests::testIP, true);
+ addTest("IndexPage", &WebserverTests::testIndexPage, true);
+ addTest("FilesPage", &WebserverTests::testFilesPageInvalidParameterValue, true);
+ addTest("CreateDirectory", &WebserverTests::testFilesPageCreateDirectory, true);
+ addTest("UploadFile", &WebserverTests::testFilesPageUploadFile, true);
+ addTest("UploadDirectory", &WebserverTests::testFilesPageUploadDirectory, true);
+ addTest("DownloadFile", &WebserverTests::testFilesPageDownloadFile, true);
+}
+
+///// TESTS GO HERE /////
+
+/** This test calls Storage::info(). */
+
+bool WebserverTests::startServer() {
+ Common::Point pt;
+ pt.x = 10; pt.y = 10;
+ Testsuite::writeOnScreen("Starting webserver...", pt);
+ LocalServer.start();
+ g_system->delayMillis(500);
+ Testsuite::clearScreen();
+ return LocalServer.isRunning();
+}
+
+TestExitStatus WebserverTests::testIP() {
+ if (!startServer()) {
+ Testsuite::logPrintf("Error! Can't start local webserver!\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Welcome to the Webserver test suite!\n"
+ "You would be visiting different server's pages and saying whether they work like they should.\n\n"
+ "Testing Webserver's IP resolving.\n"
+ "In this test we'll try to resolve server's IP.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : IP resolving\n");
+ return kTestSkipped;
+ }
+
+ if (Testsuite::handleInteractiveInput(
+ Common::String::format("Is this your machine's IP?\n%s", LocalServer.getAddress().c_str()),
+ "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! IP was not resolved!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("IP was resolved\n");
+ return kTestPassed;
+}
+
+TestExitStatus WebserverTests::testIndexPage() {
+ if (!startServer()) {
+ Testsuite::logPrintf("Error! Can't start local webserver!\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Webserver's index page.\n"
+ "In this test we'll try to open server's index page.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : index page\n");
+ return kTestSkipped;
+ }
+
+ Networking::Browser::openUrl(LocalServer.getAddress());
+ if (Testsuite::handleInteractiveInput(
+ Common::String::format("The %s page opens well?", LocalServer.getAddress().c_str()),
+ "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! Couldn't open server's index page!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Server's index page is OK\n");
+ return kTestPassed;
+}
+
+TestExitStatus WebserverTests::testFilesPageInvalidParameterValue() {
+ if (!startServer()) {
+ Testsuite::logPrintf("Error! Can't start local webserver!\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Webserver's files page.\n"
+ "In this test we'll try to pass invalid parameter to files page.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : files page (invalid parameter)\n");
+ return kTestSkipped;
+ }
+
+ Networking::Browser::openUrl(LocalServer.getAddress()+"files?path=error");
+ if (Testsuite::handleInteractiveInput(
+ Common::String::format("The %sfiles?path=error page displays error message?", LocalServer.getAddress().c_str()),
+ "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! No error message on invalid parameter in '/files'!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Server's files page detects invalid paramters fine\n");
+ return kTestPassed;
+}
+
+TestExitStatus WebserverTests::testFilesPageCreateDirectory() {
+ if (!startServer()) {
+ Testsuite::logPrintf("Error! Can't start local webserver!\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Webserver's files page Create Directory feature.\n"
+ "In this test you'll try to create directory.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : files page create directory\n");
+ return kTestSkipped;
+ }
+
+ Networking::Browser::openUrl(LocalServer.getAddress() + "files?path=/root/");
+ if (Testsuite::handleInteractiveInput(
+ Common::String::format("You could go to %sfiles page, navigate to some directory with write access and create a directory there?", LocalServer.getAddress().c_str()),
+ "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! Create Directory is not working!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Create Directory is OK\n");
+ return kTestPassed;
+}
+
+TestExitStatus WebserverTests::testFilesPageUploadFile() {
+ if (!startServer()) {
+ Testsuite::logPrintf("Error! Can't start local webserver!\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Webserver's files page Upload Files feature.\n"
+ "In this test you'll try to upload a file.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : files page file upload\n");
+ return kTestSkipped;
+ }
+
+ Networking::Browser::openUrl(LocalServer.getAddress() + "files?path=/root/");
+ if (Testsuite::handleInteractiveInput(
+ Common::String::format("You're able to upload a file in some directory with write access through %sfiles page?", LocalServer.getAddress().c_str()),
+ "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! Upload Files is not working!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Upload Files is OK\n");
+ return kTestPassed;
+}
+
+TestExitStatus WebserverTests::testFilesPageUploadDirectory() {
+ if (!startServer()) {
+ Testsuite::logPrintf("Error! Can't start local webserver!\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Webserver's files page Upload Directory feature.\n"
+ "In this test you'll try to upload a directory (works in Chrome only).";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : files page directory upload\n");
+ return kTestSkipped;
+ }
+
+ Networking::Browser::openUrl(LocalServer.getAddress() + "files?path=/root/");
+ if (Testsuite::handleInteractiveInput(
+ Common::String::format("You're able to upload a directory into some directory with write access through %sfiles page using Chrome?", LocalServer.getAddress().c_str()),
+ "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! Upload Directory is not working!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Upload Directory is OK\n");
+ return kTestPassed;
+}
+
+TestExitStatus WebserverTests::testFilesPageDownloadFile() {
+ if (!startServer()) {
+ Testsuite::logPrintf("Error! Can't start local webserver!\n");
+ return kTestFailed;
+ }
+
+ Common::String info = "Testing Webserver's files downloading feature.\n"
+ "In this test you'll try to download a file.";
+
+ if (Testsuite::handleInteractiveInput(info, "OK", "Skip", kOptionRight)) {
+ Testsuite::logPrintf("Info! Skipping test : files page download\n");
+ return kTestSkipped;
+ }
+
+ Networking::Browser::openUrl(LocalServer.getAddress() + "files?path=/root/");
+ if (Testsuite::handleInteractiveInput(
+ Common::String::format("You're able to download a file through %sfiles page?", LocalServer.getAddress().c_str()),
+ "Yes", "No", kOptionRight)) {
+ Testsuite::logDetailedPrintf("Error! Files downloading is not working!\n");
+ return kTestFailed;
+ }
+
+ Testsuite::logDetailedPrintf("Files downloading is OK\n");
+ return kTestPassed;
+}
+
+} // End of namespace Testbed
diff --git a/engines/testbed/webserver.h b/engines/testbed/webserver.h
new file mode 100644
index 0000000000..3f3083469e
--- /dev/null
+++ b/engines/testbed/webserver.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 TESTBED_WEBSERVER_H
+#define TESTBED_WEBSERVER_H
+
+#include "testbed/testsuite.h"
+
+namespace Testbed {
+
+namespace WebserverTests {
+
+// Helper functions for Webserver tests
+
+bool startServer();
+TestExitStatus testIP();
+TestExitStatus testIndexPage();
+TestExitStatus testFilesPageInvalidParameterValue();
+TestExitStatus testFilesPageCreateDirectory();
+TestExitStatus testFilesPageUploadFile();
+TestExitStatus testFilesPageUploadDirectory();
+TestExitStatus testFilesPageDownloadFile();
+
+} // End of namespace WebserverTests
+
+class WebserverTestSuite : public Testsuite {
+public:
+ /**
+ * The constructor for the WebserverTestSuite
+ * For every test to be executed one must:
+ * 1) Create a function that would invoke the test
+ * 2) Add that test to list by executing addTest()
+ *
+ * @see addTest()
+ */
+ WebserverTestSuite();
+ ~WebserverTestSuite() {}
+ const char *getName() const {
+ return "Webserver";
+ }
+
+ const char *getDescription() const {
+ return "Webserver tests";
+ }
+
+};
+
+} // End of namespace Testbed
+
+#endif // TESTBED_TEMPLATE_H
diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp
index c44f1f4ef3..d6bcfe5ea0 100644
--- a/engines/tinsel/detection.cpp
+++ b/engines/tinsel/detection.cpp
@@ -109,7 +109,8 @@ bool TinselMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
- (f == kSupportsDeleteSave);
+ (f == kSupportsDeleteSave) ||
+ (f == kSimpleSavesNames);
}
bool Tinsel::TinselEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/titanic/carry/arm.cpp b/engines/titanic/carry/arm.cpp
index 880c93d309..a67937ebdf 100644
--- a/engines/titanic/carry/arm.cpp
+++ b/engines/titanic/carry/arm.cpp
@@ -163,7 +163,7 @@ bool CArm::MaitreDHappyMsg(CMaitreDHappyMsg *msg) {
if (!_field158)
playSound("z#47.wav", 100, 0, 0);
if (_string6 == "Key" || _string6 == "AuditoryCentre") {
- CGameObject *child = static_cast<CGameObject *>(getFirstChild());
+ CGameObject *child = dynamic_cast<CGameObject *>(getFirstChild());
if (child) {
child->setVisible(true);
petAddToInventory();
@@ -184,7 +184,7 @@ bool CArm::MaitreDHappyMsg(CMaitreDHappyMsg *msg) {
bool CArm::PETGainedObjectMsg(CPETGainedObjectMsg *msg) {
if (_field158) {
if (_string6 == "Key" || _string6 == "AuditoryCentre") {
- CCarry *child = static_cast<CCarry *>(getFirstChild());
+ CCarry *child = dynamic_cast<CCarry *>(getFirstChild());
if (child) {
_visibleFrame = _field170;
loadFrame(_visibleFrame);
diff --git a/engines/titanic/carry/auditory_centre.cpp b/engines/titanic/carry/auditory_centre.cpp
index d88989a801..0bda975a36 100644
--- a/engines/titanic/carry/auditory_centre.cpp
+++ b/engines/titanic/carry/auditory_centre.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CAuditoryCentre, CBrain)
+ ON_MESSAGE(PuzzleSolvedMsg)
+END_MESSAGE_MAP()
+
void CAuditoryCentre::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBrain::save(file, indent);
@@ -34,4 +38,10 @@ void CAuditoryCentre::load(SimpleFile *file) {
CBrain::load(file);
}
+bool CAuditoryCentre::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ _fieldE0 = 1;
+ setVisible(true);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/auditory_centre.h b/engines/titanic/carry/auditory_centre.h
index 743f8f2498..6f24e86208 100644
--- a/engines/titanic/carry/auditory_centre.h
+++ b/engines/titanic/carry/auditory_centre.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CAuditoryCentre : public CBrain {
+ DECLARE_MESSAGE_MAP;
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/carry/bowl_ear.cpp b/engines/titanic/carry/bowl_ear.cpp
index bb5172e580..852a77899a 100644
--- a/engines/titanic/carry/bowl_ear.cpp
+++ b/engines/titanic/carry/bowl_ear.cpp
@@ -24,6 +24,13 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBowlEar, CEar)
+ ON_MESSAGE(PETGainedObjectMsg)
+ ON_MESSAGE(ReplaceBowlAndNutsMsg)
+ ON_MESSAGE(NutPuzzleMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CBowlEar::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CEar::save(file, indent);
@@ -34,4 +41,28 @@ void CBowlEar::load(SimpleFile *file) {
CEar::load(file);
}
+bool CBowlEar::PETGainedObjectMsg(CPETGainedObjectMsg *msg) {
+ CBowlStateChangeMsg changeMsg(3);
+ changeMsg.execute("ParrotNutBowlActor");
+
+ return CEar::PETGainedObjectMsg(msg);
+}
+
+bool CBowlEar::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
+bool CBowlEar::NutPuzzleMsg(CNutPuzzleMsg *msg) {
+ if (msg->_value == "BowlUnlocked")
+ _fieldE0 = 1;
+
+ return true;
+}
+
+bool CBowlEar::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ setVisible(true);
+ return CEar::MouseDragStartMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/bowl_ear.h b/engines/titanic/carry/bowl_ear.h
index 4f2fbea478..d78092f6d7 100644
--- a/engines/titanic/carry/bowl_ear.h
+++ b/engines/titanic/carry/bowl_ear.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CBowlEar : public CEar {
+ DECLARE_MESSAGE_MAP;
+ bool PETGainedObjectMsg(CPETGainedObjectMsg *msg);
+ bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg);
+ bool NutPuzzleMsg(CNutPuzzleMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/carry/brain.cpp b/engines/titanic/carry/brain.cpp
index 8df0de9961..102d8d9049 100644
--- a/engines/titanic/carry/brain.cpp
+++ b/engines/titanic/carry/brain.cpp
@@ -55,7 +55,7 @@ void CBrain::load(SimpleFile *file) {
}
bool CBrain::UseWithOtherMsg(CUseWithOtherMsg *msg) {
- CBrainSlot *slot = static_cast<CBrainSlot *>(msg->_other);
+ CBrainSlot *slot = dynamic_cast<CBrainSlot *>(msg->_other);
if (slot) {
if (slot->getName() == "CentralCore") {
setVisible(false);
diff --git a/engines/titanic/carry/bridge_piece.cpp b/engines/titanic/carry/bridge_piece.cpp
index fc845feff0..a2cb23add6 100644
--- a/engines/titanic/carry/bridge_piece.cpp
+++ b/engines/titanic/carry/bridge_piece.cpp
@@ -52,17 +52,17 @@ void CBridgePiece::load(SimpleFile *file) {
}
bool CBridgePiece::UseWithOtherMsg(CUseWithOtherMsg *msg) {
- CShipSetting *shipSetting = static_cast<CShipSetting *>(msg->_other);
+ CShipSetting *shipSetting = dynamic_cast<CShipSetting *>(msg->_other);
if (!shipSetting) {
return CCarry::UseWithOtherMsg(msg);
- } else if (shipSetting->_string4 == "NULL") {
+ } else if (shipSetting->_itemName != "NULL") {
petAddToInventory();
return true;
} else {
setVisible(false);
playSound("z#54.wav", 100, 0, 0);
setPosition(shipSetting->_pos1);
- shipSetting->_string4 = getName();
+ shipSetting->_itemName = getName();
petMoveToHiddenRoom();
CAddHeadPieceMsg headpieceMsg(shipSetting->getName() == _string6 ?
diff --git a/engines/titanic/carry/carry.cpp b/engines/titanic/carry/carry.cpp
index 75b3b6f35b..03798e8713 100644
--- a/engines/titanic/carry/carry.cpp
+++ b/engines/titanic/carry/carry.cpp
@@ -127,7 +127,7 @@ bool CCarry::MouseDragEndMsg(CMouseDragEndMsg *msg) {
return true;
}
- CCharacter *npc = static_cast<CCharacter *>(msg->_dropTarget);
+ CCharacter *npc = dynamic_cast<CCharacter *>(msg->_dropTarget);
if (npc) {
CUseWithCharMsg charMsg(npc);
charMsg.execute(this, nullptr, 0);
@@ -157,7 +157,7 @@ bool CCarry::MouseDragEndMsg(CMouseDragEndMsg *msg) {
}
bool CCarry::UseWithCharMsg(CUseWithCharMsg *msg) {
- CSuccUBus *succubus = static_cast<CSuccUBus *>(msg->_character);
+ CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_character);
if (succubus) {
CSubAcceptCCarryMsg carryMsg;
carryMsg._item = this;
diff --git a/engines/titanic/carry/carry.h b/engines/titanic/carry/carry.h
index 72f4024904..06e446a1b5 100644
--- a/engines/titanic/carry/carry.h
+++ b/engines/titanic/carry/carry.h
@@ -44,11 +44,7 @@ class CCarry : public CGameObject {
bool EnterViewMsg(CEnterViewMsg *msg);
bool PassOnDragStartMsg(CPassOnDragStartMsg *msg);
protected:
- CString _string1;
- Point _origPos;
- CString _fullViewName;
int _fieldDC;
- int _fieldE0;
CString _string3;
CString _string4;
Point _tempPos;
@@ -62,6 +58,11 @@ protected:
bool _enterFrameSet;
int _visibleFrame;
public:
+ CString _string1;
+ int _fieldE0;
+ Point _origPos;
+ CString _fullViewName;
+public:
CLASSDEF;
CCarry();
diff --git a/engines/titanic/carry/carry_parrot.cpp b/engines/titanic/carry/carry_parrot.cpp
index cf96204122..b0461ded26 100644
--- a/engines/titanic/carry/carry_parrot.cpp
+++ b/engines/titanic/carry/carry_parrot.cpp
@@ -111,7 +111,7 @@ bool CCarryParrot::MouseDragEndMsg(CMouseDragEndMsg *msg) {
if (compareViewNameTo("ParrotLobby.Node 1.N")) {
if (msg->_mousePos.x >= 75 && msg->_mousePos.x <= 565 &&
- !CParrot::_v2 && !CCage::_v2) {
+ !CParrot::_v2 && !CCage::_open) {
setVisible(false);
_fieldE0 = 0;
CTreeItem *perchedParrot = findUnder(getRoot(), "PerchedParrot");
@@ -133,7 +133,7 @@ bool CCarryParrot::MouseDragEndMsg(CMouseDragEndMsg *msg) {
actMsg.execute("ParrotCage");
}
} else {
- CCharacter *character = static_cast<CCharacter *>(msg->_dropTarget);
+ CCharacter *character = dynamic_cast<CCharacter *>(msg->_dropTarget);
if (character) {
CUseWithCharMsg charMsg(character);
charMsg.execute(this, nullptr, 0);
@@ -167,7 +167,7 @@ bool CCarryParrot::PassOnDragStartMsg(CPassOnDragStartMsg *msg) {
return CCarry::PassOnDragStartMsg(msg);
}
- CTrueTalkNPC *npc = static_cast<CTrueTalkNPC *>(getRoot()->findByName(_string6));
+ CTrueTalkNPC *npc = dynamic_cast<CTrueTalkNPC *>(getRoot()->findByName(_string6));
if (npc)
startTalking(npc, 0x446BF);
@@ -181,7 +181,7 @@ bool CCarryParrot::PassOnDragStartMsg(CPassOnDragStartMsg *msg) {
bool CCarryParrot::PreEnterViewMsg(CPreEnterViewMsg *msg) {
loadSurface();
- CCarryParrot *parrot = static_cast<CCarryParrot *>(getRoot()->findByName("CarryParrot"));
+ CCarryParrot *parrot = dynamic_cast<CCarryParrot *>(getRoot()->findByName("CarryParrot"));
if (parrot)
parrot->_fieldE0 = 0;
@@ -189,7 +189,7 @@ bool CCarryParrot::PreEnterViewMsg(CPreEnterViewMsg *msg) {
}
bool CCarryParrot::UseWithCharMsg(CUseWithCharMsg *msg) {
- CSuccUBus *succubus = static_cast<CSuccUBus *>(msg->_character);
+ CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_character);
if (succubus)
CParrot::_v4 = 3;
@@ -198,7 +198,7 @@ bool CCarryParrot::UseWithCharMsg(CUseWithCharMsg *msg) {
bool CCarryParrot::ActMsg(CActMsg *msg) {
if (msg->_action == "FreeParrot" && (CParrot::_v4 == 4 || CParrot::_v4 == 1)) {
- CTrueTalkNPC *npc = static_cast<CTrueTalkNPC *>(getRoot()->findByName(_string6));
+ CTrueTalkNPC *npc = dynamic_cast<CTrueTalkNPC *>(getRoot()->findByName(_string6));
if (npc)
startTalking(npc, 0x446BF);
@@ -212,7 +212,7 @@ bool CCarryParrot::ActMsg(CActMsg *msg) {
playSound("z#475.wav", 100, 0, 0);
if (!_field140) {
- CCarry *feathers = static_cast<CCarry *>(getRoot()->findByName("Feathers"));
+ CCarry *feathers = dynamic_cast<CCarry *>(getRoot()->findByName("Feathers"));
if (feathers) {
feathers->setVisible(true);
feathers->petAddToInventory();
diff --git a/engines/titanic/carry/central_core.cpp b/engines/titanic/carry/central_core.cpp
index a50c95abbc..e210b34cbe 100644
--- a/engines/titanic/carry/central_core.cpp
+++ b/engines/titanic/carry/central_core.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/carry/central_core.h"
+#include "titanic/npcs/parrot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CCentralCore, CBrain)
+ ON_MESSAGE(UseWithOtherMsg)
+ ON_MESSAGE(DropZoneLostObjectMsg)
+ ON_MESSAGE(DropZoneGotObjectMsg)
+END_MESSAGE_MAP()
+
void CCentralCore::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBrain::save(file, indent);
@@ -34,4 +41,53 @@ void CCentralCore::load(SimpleFile *file) {
CBrain::load(file);
}
+bool CCentralCore::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ CString name = msg->_other->getName();
+ if (name == "HammerDispensorButton") {
+ CPuzzleSolvedMsg solvedMsg;
+ solvedMsg.execute("BigHammer");
+ } else if (name == "SpeechCentre") {
+ CShowTextMsg textMsg("This does not reach.");
+ textMsg.execute("PET");
+ }
+
+ return CBrain::UseWithOtherMsg(msg);
+}
+
+bool CCentralCore::DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg) {
+ CString name = msg->_object->getName();
+ if (name == "PerchCoreHolder") {
+ CParrot::_v2 = 1;
+ if (isEquals("CentralCore"))
+ CParrot::_v5 = 0;
+
+ CActMsg actMsg("LosePerch");
+ actMsg.execute("ParrotLobbyController");
+ } else if (name == "PerchHolder") {
+ CActMsg actMsg("LoseStick");
+ actMsg.execute("ParrotLobbyController");
+ }
+
+ return true;
+}
+
+bool CCentralCore::DropZoneGotObjectMsg(CDropZoneGotObjectMsg *msg) {
+ CString name = msg->_object->getName();
+ if (name == "PerchCoreHolder") {
+ if (isEquals("CentralCore")) {
+ CParrot::_v5 = 1;
+ CActMsg actMsg("CoreReplaced");
+ actMsg.execute("ParrotCage");
+ }
+
+ CActMsg actMsg("GainPerch");
+ actMsg.execute("ParrotLobbyController");
+ } else if (name == "PerchHolder") {
+ CActMsg actMsg("GainStick");
+ actMsg.execute("ParrotLobbyController");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/central_core.h b/engines/titanic/carry/central_core.h
index 9d7bef2c13..cc5d9c2f95 100644
--- a/engines/titanic/carry/central_core.h
+++ b/engines/titanic/carry/central_core.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CCentralCore : public CBrain {
+ DECLARE_MESSAGE_MAP;
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
+ bool DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg);
+ bool DropZoneGotObjectMsg(CDropZoneGotObjectMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/carry/chicken.cpp b/engines/titanic/carry/chicken.cpp
index 65404dc65d..0e8f6b3653 100644
--- a/engines/titanic/carry/chicken.cpp
+++ b/engines/titanic/carry/chicken.cpp
@@ -80,7 +80,7 @@ bool CChicken::UseWithOtherMsg(CUseWithOtherMsg *msg) {
petAddToInventory();
} else {
- CSauceDispensor *dispensor = static_cast<CSauceDispensor *>(msg->_other);
+ CSauceDispensor *dispensor = dynamic_cast<CSauceDispensor *>(msg->_other);
if (!dispensor || _string6 == "None") {
return CCarry::UseWithOtherMsg(msg);
} else {
@@ -94,7 +94,7 @@ bool CChicken::UseWithOtherMsg(CUseWithOtherMsg *msg) {
}
bool CChicken::UseWithCharMsg(CUseWithCharMsg *msg) {
- CSuccUBus *succubus = static_cast<CSuccUBus *>(msg->_character);
+ CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_character);
if (succubus) {
setPosition(Point(330, 300));
CSubAcceptCCarryMsg acceptMsg;
diff --git a/engines/titanic/carry/chicken.h b/engines/titanic/carry/chicken.h
index 65fe30fd81..e64ae458a4 100644
--- a/engines/titanic/carry/chicken.h
+++ b/engines/titanic/carry/chicken.h
@@ -41,7 +41,7 @@ class CChicken : public CCarry {
bool MouseDragEndMsg(CMouseDragEndMsg *msg);
bool PETObjectStateMsg(CPETObjectStateMsg *msg);
bool PETLostObjectMsg(CPETLostObjectMsg *msg);
-private:
+public:
static int _v1;
public:
int _field12C;
diff --git a/engines/titanic/carry/crushed_tv.cpp b/engines/titanic/carry/crushed_tv.cpp
index a265b611a9..486537d28e 100644
--- a/engines/titanic/carry/crushed_tv.cpp
+++ b/engines/titanic/carry/crushed_tv.cpp
@@ -76,5 +76,4 @@ bool CCrushedTV::MouseDragStartMsg(CMouseDragStartMsg *msg) {
return CCarry::MouseDragStartMsg(msg);
}
-
} // End of namespace Titanic
diff --git a/engines/titanic/carry/ear.cpp b/engines/titanic/carry/ear.cpp
index 8d85e247f7..a2234bc6dc 100644
--- a/engines/titanic/carry/ear.cpp
+++ b/engines/titanic/carry/ear.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/carry/ear.h"
+#include "titanic/game/head_slot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEar, CHeadPiece)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(UseWithOtherMsg)
+END_MESSAGE_MAP()
+
CEar::CEar() : CHeadPiece() {
}
@@ -37,4 +43,25 @@ void CEar::load(SimpleFile *file) {
CHeadPiece::load(file);
}
+bool CEar::ActMsg(CActMsg *msg) {
+ if (msg->_action == "MusicSolved")
+ _fieldE0 = true;
+ return true;
+}
+
+bool CEar::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ CHeadSlot *slot = dynamic_cast<CHeadSlot *>(msg->_other);
+ if (slot) {
+ setVisible(false);
+ petMoveToHiddenRoom();
+ setPosition(Point(0, 0));
+
+ CAddHeadPieceMsg addMsg(getName());
+ if (addMsg._value != "NULL")
+ addMsg.execute(addMsg._value == "Ear1" ? "Ear1Slot" : "Ear2Slot");
+ }
+
+ return CCarry::UseWithOtherMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/ear.h b/engines/titanic/carry/ear.h
index edef873d35..a357f46bbf 100644
--- a/engines/titanic/carry/ear.h
+++ b/engines/titanic/carry/ear.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CEar : public CHeadPiece {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
public:
CLASSDEF;
CEar();
diff --git a/engines/titanic/carry/eye.cpp b/engines/titanic/carry/eye.cpp
index 5de1789e54..400df2fdc8 100644
--- a/engines/titanic/carry/eye.cpp
+++ b/engines/titanic/carry/eye.cpp
@@ -21,22 +21,119 @@
*/
#include "titanic/carry/eye.h"
+#include "titanic/game/head_slot.h"
+#include "titanic/pet_control/pet_control.h"
+#include "titanic/game/transport/lift.h"
+#include "titanic/game/television.h"
namespace Titanic {
-CEye::CEye() : CHeadPiece(), _eyeNum(0) {
+BEGIN_MESSAGE_MAP(CEye, CHeadPiece)
+ ON_MESSAGE(UseWithOtherMsg)
+ ON_MESSAGE(UseWithCharMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(PETGainedObjectMsg)
+ ON_MESSAGE(PassOnDragStartMsg)
+END_MESSAGE_MAP()
+
+CEye::CEye() : CHeadPiece(), _eyeFlag(false) {
}
void CEye::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_eyeNum, indent);
+ file->writeNumberLine(_eyeFlag, indent);
CHeadPiece::save(file, indent);
}
void CEye::load(SimpleFile *file) {
file->readNumber();
- _eyeNum = file->readNumber();
+ _eyeFlag = file->readNumber();
CHeadPiece::load(file);
}
+
+bool CEye::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ CHeadSlot *slot = dynamic_cast<CHeadSlot *>(msg->_other);
+ if (slot) {
+ petMoveToHiddenRoom();
+ _flag = true;
+ CAddHeadPieceMsg headMsg(getName());
+
+ if (headMsg._value != "NULL")
+ headMsg.execute(isEquals("Eye1") ? "Eye1Slot" : "Eye2Slot");
+ } else if (msg->_other->isEquals("LiftbotWithoutHead")) {
+ CPetControl *pet = getPetControl();
+ if (!CLift::_v1 && pet->getRoomsElevatorNum() == 4) {
+ _eyeFlag = true;
+ setPosition(_origPos);
+ setVisible(false);
+ CActMsg actMsg1(getName());
+ actMsg1.execute("GetLiftEye");
+
+ CActMsg actMsg2("AddWrongHead");
+ actMsg2.execute("FaultyLiftbot");
+ }
+ } else {
+ return CCarry::UseWithOtherMsg(msg);
+ }
+
+ return true;
+}
+
+bool CEye::UseWithCharMsg(CUseWithCharMsg *msg) {
+ CLift *lift = dynamic_cast<CLift *>(msg->_character);
+ if (lift && lift->getName() == "Well") {
+ CPetControl *pet = getPetControl();
+ if (!CLift::_v1 && pet->getRoomsElevatorNum() == 4) {
+ _eyeFlag = true;
+ setPosition(_origPos);
+ setVisible(false);
+
+ CActMsg actMsg1(getName());
+ actMsg1.execute("GetLiftEye");
+ CActMsg actMsg2("AddWrongHead");
+ actMsg2.execute(msg->_character);
+ }
+
+ return true;
+ } else {
+ return CHeadPiece::UseWithCharMsg(msg);
+ }
+}
+
+bool CEye::ActMsg(CActMsg *msg) {
+ if (msg->_action == "BellbotGetLight") {
+ setVisible(true);
+ petAddToInventory();
+ playSound("z#47.wav");
+
+ CActMsg actMsg("Eye Removed");
+ actMsg.execute("1stClassState");
+ } else {
+ _eyeFlag = false;
+
+ CActMsg actMsg("LoseHead");
+ actMsg.execute("FaultyLiftbot");
+ }
+
+ return true;
+}
+
+bool CEye::PETGainedObjectMsg(CPETGainedObjectMsg *msg) {
+ if (isEquals("Eye1"))
+ CTelevision::_v5 = 0;
+
+ return CHeadPiece::PETGainedObjectMsg(msg);
+}
+
+bool CEye::PassOnDragStartMsg(CPassOnDragStartMsg *msg) {
+ setVisible(true);
+ if (_eyeFlag)
+ CTelevision::_v6 = 0;
+ else if (isEquals("Eye1"))
+ CTelevision::_v5 = 0;
+
+ return CHeadPiece::PassOnDragStartMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/eye.h b/engines/titanic/carry/eye.h
index 066a85609b..886bd39b84 100644
--- a/engines/titanic/carry/eye.h
+++ b/engines/titanic/carry/eye.h
@@ -28,8 +28,14 @@
namespace Titanic {
class CEye : public CHeadPiece {
+ DECLARE_MESSAGE_MAP;
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
+ bool UseWithCharMsg(CUseWithCharMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool PETGainedObjectMsg(CPETGainedObjectMsg *msg);
+ bool PassOnDragStartMsg(CPassOnDragStartMsg *msg);
private:
- int _eyeNum;
+ bool _eyeFlag;
public:
CLASSDEF;
CEye();
diff --git a/engines/titanic/carry/fruit.cpp b/engines/titanic/carry/fruit.cpp
index 832dccf45a..68f3af7229 100644
--- a/engines/titanic/carry/fruit.cpp
+++ b/engines/titanic/carry/fruit.cpp
@@ -21,9 +21,17 @@
*/
#include "titanic/carry/fruit.h"
+#include "titanic/npcs/character.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CFruit, CCarry)
+ ON_MESSAGE(UseWithCharMsg)
+ ON_MESSAGE(LemonFallsFromTreeMsg)
+ ON_MESSAGE(UseWithOtherMsg)
+ ON_MESSAGE(FrameMsg)
+END_MESSAGE_MAP()
+
CFruit::CFruit() : CCarry(), _field12C(0),
_field130(0), _field134(0), _field138(0) {
}
@@ -48,4 +56,43 @@ void CFruit::load(SimpleFile *file) {
CCarry::load(file);
}
+bool CFruit::UseWithCharMsg(CUseWithCharMsg *msg) {
+ if (msg->_character->isEquals("Barbot") && msg->_character->_visible) {
+ CActMsg actMsg("Fruit");
+ actMsg.execute(msg->_character);
+ _fieldE0 = 0;
+ setVisible(false);
+ return true;
+ } else {
+ return CCarry::UseWithCharMsg(msg);
+ }
+}
+
+bool CFruit::LemonFallsFromTreeMsg(CLemonFallsFromTreeMsg *msg) {
+ setVisible(true);
+ dragMove(msg->_pt);
+ _field130 = 1;
+ return true;
+}
+
+bool CFruit::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ petAddToInventory();
+ return true;
+}
+
+bool CFruit::FrameMsg(CFrameMsg *msg) {
+ if (_field130) {
+ if (_bounds.top > 240) {
+ _field130 = 0;
+ _field134 = 1;
+ }
+
+ makeDirty();
+ _bounds.top += 3;
+ makeDirty();
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/fruit.h b/engines/titanic/carry/fruit.h
index 93fe920740..bcbd314de8 100644
--- a/engines/titanic/carry/fruit.h
+++ b/engines/titanic/carry/fruit.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CFruit : public CCarry {
+ DECLARE_MESSAGE_MAP;
+ bool UseWithCharMsg(CUseWithCharMsg *msg);
+ bool LemonFallsFromTreeMsg(CLemonFallsFromTreeMsg *msg);
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
private:
int _field12C;
int _field130;
diff --git a/engines/titanic/carry/glass.cpp b/engines/titanic/carry/glass.cpp
index 051457af03..03050dc60d 100644
--- a/engines/titanic/carry/glass.cpp
+++ b/engines/titanic/carry/glass.cpp
@@ -21,9 +21,21 @@
*/
#include "titanic/carry/glass.h"
+#include "titanic/carry/chicken.h"
+#include "titanic/game/sauce_dispensor.h"
+#include "titanic/npcs/character.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CGlass, CCarry)
+ ON_MESSAGE(UseWithOtherMsg)
+ ON_MESSAGE(UseWithCharMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MouseDragEndMsg)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+END_MESSAGE_MAP()
+
CGlass::CGlass() : CCarry(), _string6("None") {
}
@@ -39,4 +51,108 @@ void CGlass::load(SimpleFile *file) {
CCarry::load(file);
}
+bool CGlass::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ CSauceDispensor *dispensor = dynamic_cast<CSauceDispensor *>(msg->_other);
+ CChicken *chicken = dynamic_cast<CChicken *>(msg->_other);
+
+ if (dispensor && _string6 != "None") {
+ CUse useMsg(this);
+ useMsg.execute(dispensor);
+ } else if (msg->_other->isEquals("Chicken") && _string6 != "None") {
+ if (chicken->_string6 != "None") {
+ if (!chicken->_field12C) {
+ CActMsg actMsg(_string6);
+ actMsg.execute("Chicken");
+ }
+
+ _string6 = "None";
+ loadFrame(0);
+ _visibleFrame = 0;
+ }
+
+ petAddToInventory();
+ } else if (msg->_other->isEquals("Napkin") && _string6 != "None") {
+ petAddToInventory();
+ _string6 = "None";
+ loadFrame(0);
+ _visibleFrame = 0;
+ } else {
+ petAddToInventory();
+ }
+
+ return true;
+}
+
+bool CGlass::UseWithCharMsg(CUseWithCharMsg *msg) {
+ if (msg->_character->isEquals("Barbot") && msg->_character->_visible) {
+ CActMsg actMsg(_string6);
+ setVisible(false);
+
+ if (_string6 != "Bird")
+ setPosition(_origPos);
+
+ actMsg.execute(msg->_character);
+ } else {
+ petAddToInventory();
+ }
+
+ return true;
+}
+
+bool CGlass::ActMsg(CActMsg *msg) {
+ if (msg->_action == "GoToPET") {
+ setVisible(true);
+ petAddToInventory();
+ } else if (msg->_action == "Mustard") {
+ _string6 = "Mustard";
+ loadFrame(1);
+ _visibleFrame = 1;
+ } else if (msg->_action == "Tomato") {
+ _string6 = "Tomato";
+ loadFrame(2);
+ _visibleFrame = 2;
+ } else if (msg->_action == "Bird") {
+ _string6 = "Bird";
+ loadFrame(3);
+ _visibleFrame = 3;
+ } else if (msg->_action == "InTitilator") {
+ _string6 = "None";
+ loadFrame(0);
+ _visibleFrame = 0;
+ }
+
+ return true;
+}
+
+bool CGlass::MouseDragEndMsg(CMouseDragEndMsg *msg) {
+ showMouse();
+ if (msg->_dropTarget) {
+ error("TODO: See what drop target is");
+ CCharacter *npc = dynamic_cast<CCharacter *>(msg->_dropTarget);
+ if (npc) {
+ CUseWithCharMsg useMsg(npc);
+ useMsg.execute(this);
+ } else {
+ CUseWithOtherMsg otherMsg(npc);
+ otherMsg.execute(this);
+ }
+ } else if (compareViewNameTo(_fullViewName) && msg->_mousePos.y < 360) {
+ setPosition(_origPos);
+ } else {
+ petAddToInventory();
+ }
+
+ return true;
+}
+
+bool CGlass::TurnOn(CTurnOn *msg) {
+ setVisible(true);
+ return true;
+}
+
+bool CGlass::TurnOff(CTurnOff *msg) {
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/glass.h b/engines/titanic/carry/glass.h
index 9f4056b1be..608d45cb66 100644
--- a/engines/titanic/carry/glass.h
+++ b/engines/titanic/carry/glass.h
@@ -28,7 +28,14 @@
namespace Titanic {
class CGlass : public CCarry {
-private:
+ DECLARE_MESSAGE_MAP;
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
+ bool UseWithCharMsg(CUseWithCharMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool MouseDragEndMsg(CMouseDragEndMsg *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+public:
CString _string6;
public:
CLASSDEF;
diff --git a/engines/titanic/carry/hammer.cpp b/engines/titanic/carry/hammer.cpp
index d3b912184c..88c766d564 100644
--- a/engines/titanic/carry/hammer.cpp
+++ b/engines/titanic/carry/hammer.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHammer, CCarry)
+ ON_MESSAGE(PuzzleSolvedMsg)
+ ON_MESSAGE(UseWithOtherMsg)
+END_MESSAGE_MAP()
+
CHammer::CHammer() : CCarry() {
}
@@ -37,4 +42,22 @@ void CHammer::load(SimpleFile *file) {
CCarry::load(file);
}
+bool CHammer::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ _fieldE0 = 1;
+ return true;
+}
+
+bool CHammer::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ CString name = msg->_other->getName();
+ if (name == "LongStickDispenser") {
+ CPuzzleSolvedMsg solvedMsg;
+ solvedMsg.execute("LongStickDispenser");
+ } else if (name == "Bomb") {
+ CActMsg actMsg("Hit");
+ actMsg.execute("Bomb");
+ }
+
+ return CCarry::UseWithOtherMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/hammer.h b/engines/titanic/carry/hammer.h
index a455d71434..8cc86f3db1 100644
--- a/engines/titanic/carry/hammer.h
+++ b/engines/titanic/carry/hammer.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CHammer : public CCarry {
+ DECLARE_MESSAGE_MAP;
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
public:
CLASSDEF;
CHammer();
diff --git a/engines/titanic/carry/head_piece.cpp b/engines/titanic/carry/head_piece.cpp
index ae709644a0..34850488a7 100644
--- a/engines/titanic/carry/head_piece.cpp
+++ b/engines/titanic/carry/head_piece.cpp
@@ -24,13 +24,19 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHeadPiece, CCarry)
+ ON_MESSAGE(SenseWorkingMsg)
+ ON_MESSAGE(PETGainedObjectMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
CHeadPiece::CHeadPiece() : CCarry(), _string6("Not Working"),
- _field12C(0), _field13C(0) {
+ _flag(0), _field13C(false) {
}
void CHeadPiece::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_field12C, indent);
+ file->writeNumberLine(_flag, indent);
file->writeQuotedLine(_string6, indent);
file->writeNumberLine(_field13C, indent);
@@ -39,11 +45,49 @@ void CHeadPiece::save(SimpleFile *file, int indent) {
void CHeadPiece::load(SimpleFile *file) {
file->readNumber();
- _field12C = file->readNumber();
+ _flag = file->readNumber();
_string6 = file->readString();
_field13C = file->readNumber();
CCarry::load(file);
}
+bool CHeadPiece::SenseWorkingMsg(CSenseWorkingMsg *msg) {
+ _string6 = msg->_value;
+ return true;
+}
+
+bool CHeadPiece::PETGainedObjectMsg(CPETGainedObjectMsg *msg) {
+ _visibleFrame = 1;
+ if (!_field13C) {
+ stateInc38();
+ _field13C = true;
+ }
+
+ return true;
+}
+
+bool CHeadPiece::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkPoint(msg->_mousePos, false, true)) {
+ return false;
+ } else if (!_fieldE0) {
+ return true;
+ }
+
+ if (_flag) {
+ setVisible(true);
+ moveToView();
+ setPosition(Point(msg->_mousePos.x - _bounds.width() / 2,
+ msg->_mousePos.y - _bounds.height() / 2));
+
+ CTakeHeadPieceMsg takeMsg(getName());
+ if (takeMsg._value != "NULL")
+ takeMsg.execute("TitaniaControl");
+
+ _flag = false;
+ }
+
+ return CCarry::MouseDragStartMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/head_piece.h b/engines/titanic/carry/head_piece.h
index 05ac772853..367f781f0e 100644
--- a/engines/titanic/carry/head_piece.h
+++ b/engines/titanic/carry/head_piece.h
@@ -24,14 +24,19 @@
#define TITANIC_HEAD_PIECE_H
#include "titanic/carry/carry.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CHeadPiece : public CCarry {
-private:
- int _field12C;
+ DECLARE_MESSAGE_MAP;
+ bool SenseWorkingMsg(CSenseWorkingMsg *msg);
+ bool PETGainedObjectMsg(CPETGainedObjectMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+protected:
+ bool _flag;
CString _string6;
- int _field13C;
+ bool _field13C;
public:
CLASSDEF;
CHeadPiece();
diff --git a/engines/titanic/carry/hose.cpp b/engines/titanic/carry/hose.cpp
index 747d58c339..e90119138a 100644
--- a/engines/titanic/carry/hose.cpp
+++ b/engines/titanic/carry/hose.cpp
@@ -21,9 +21,18 @@
*/
#include "titanic/carry/hose.h"
+#include "titanic/npcs/succubus.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHose, CCarry)
+ ON_MESSAGE(DropZoneGotObjectMsg)
+ ON_MESSAGE(PumpingMsg)
+ ON_MESSAGE(UseWithCharMsg)
+ ON_MESSAGE(HoseConnectedMsg)
+ ON_MESSAGE(DropZoneLostObjectMsg)
+END_MESSAGE_MAP()
+
CHoseStatics *CHose::_statics;
void CHose::init() {
@@ -40,18 +49,72 @@ CHose::CHose() : CCarry(),
void CHose::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_statics->_v1, indent);
- file->writeQuotedLine(_statics->_v2, indent);
+ file->writeNumberLine(_statics->_actionVal, indent);
+ file->writeQuotedLine(_statics->_actionTarget, indent);
file->writeQuotedLine(_string6, indent);
CCarry::save(file, indent);
}
void CHose::load(SimpleFile *file) {
file->readNumber();
- _statics->_v1 = file->readNumber();
- _statics->_v2 = file->readString();
+ _statics->_actionVal = file->readNumber();
+ _statics->_actionTarget = file->readString();
_string6 = file->readString();
CCarry::load(file);
}
+bool CHose::DropZoneGotObjectMsg(CDropZoneGotObjectMsg *msg) {
+ _statics->_actionTarget = msg->_object->getName();
+ CPumpingMsg pumpingMsg;
+ pumpingMsg._value = _statics->_actionVal;
+ pumpingMsg.execute(_statics->_actionTarget);
+ CHoseConnectedMsg connectedMsg;
+ connectedMsg._value = 1;
+ connectedMsg.execute(this);
+
+ return true;
+}
+
+bool CHose::PumpingMsg(CPumpingMsg *msg) {
+ _statics->_actionVal = msg->_value;
+ if (!_statics->_actionTarget.empty()) {
+ CPumpingMsg pumpingMsg;
+ pumpingMsg._value = _statics->_actionVal;
+ pumpingMsg.execute(_statics->_actionTarget);
+ }
+
+ return true;
+}
+
+bool CHose::UseWithCharMsg(CUseWithCharMsg *msg) {
+ CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_character);
+ if (!_statics->_actionVal && succubus) {
+ CHoseConnectedMsg connectedMsg(1, this);
+ if (connectedMsg.execute(succubus))
+ return true;
+ }
+
+ return CCarry::UseWithCharMsg(msg);
+}
+
+bool CHose::HoseConnectedMsg(CHoseConnectedMsg *msg) {
+ if (msg->_value) {
+ CHose *hose = dynamic_cast<CHose *>(findChildInstanceOf(CHose::_type));
+ if (hose) {
+ setVisible(true);
+ petAddToInventory();
+ }
+ }
+
+ return true;
+}
+
+bool CHose::DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg) {
+ CPumpingMsg pumpingMsg;
+ pumpingMsg._value = 0;
+ pumpingMsg.execute(msg->_object);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/hose.h b/engines/titanic/carry/hose.h
index ebd45860e8..3c8c1549c1 100644
--- a/engines/titanic/carry/hose.h
+++ b/engines/titanic/carry/hose.h
@@ -28,16 +28,23 @@
namespace Titanic {
struct CHoseStatics {
- int _v1;
- CString _v2;
+ int _actionVal;
+ CString _actionTarget;
+ CHoseStatics() : _actionVal(0) {}
};
class CHose : public CCarry {
+ DECLARE_MESSAGE_MAP;
+ bool DropZoneGotObjectMsg(CDropZoneGotObjectMsg *msg);
+ bool PumpingMsg(CPumpingMsg *msg);
+ bool UseWithCharMsg(CUseWithCharMsg *msg);
+ bool HoseConnectedMsg(CHoseConnectedMsg *msg);
+ bool DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg);
protected:
- static CHoseStatics *_statics;
-
CString _string6;
public:
+ static CHoseStatics *_statics;
+public:
CLASSDEF;
CHose();
static void init();
diff --git a/engines/titanic/carry/key.cpp b/engines/titanic/carry/key.cpp
index 6e947464f1..187ff1b6c3 100644
--- a/engines/titanic/carry/key.cpp
+++ b/engines/titanic/carry/key.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CKey, CCarry)
+ ON_MESSAGE(PuzzleSolvedMsg)
+ ON_MESSAGE(UseWithOtherMsg)
+END_MESSAGE_MAP()
+
CKey::CKey() : CCarry() {
}
@@ -37,4 +42,19 @@ void CKey::load(SimpleFile *file) {
CCarry::load(file);
}
+bool CKey::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ _fieldE0 = 1;
+ setVisible(true);
+ return true;
+}
+
+bool CKey::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ if (msg->_other->getName() == "1stClassPhono") {
+ CActMsg actMsg("Unlock");
+ actMsg.execute(msg->_other);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/key.h b/engines/titanic/carry/key.h
index 8f1600f2b3..9d3957937c 100644
--- a/engines/titanic/carry/key.h
+++ b/engines/titanic/carry/key.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CKey : public CCarry {
+ DECLARE_MESSAGE_MAP;
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
public:
CLASSDEF;
CKey();
diff --git a/engines/titanic/carry/liftbot_head.cpp b/engines/titanic/carry/liftbot_head.cpp
index bcab8e8574..5f516fcf8c 100644
--- a/engines/titanic/carry/liftbot_head.cpp
+++ b/engines/titanic/carry/liftbot_head.cpp
@@ -21,22 +21,83 @@
*/
#include "titanic/carry/liftbot_head.h"
+#include "titanic/game/transport/lift.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
-CLiftbotHead::CLiftbotHead() : CCarry(), _field12C(0) {
+BEGIN_MESSAGE_MAP(CLiftbotHead, CCarry)
+ ON_MESSAGE(UseWithOtherMsg)
+ ON_MESSAGE(UseWithCharMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
+CLiftbotHead::CLiftbotHead() : CCarry(), _flag(false) {
}
void CLiftbotHead::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_field12C, indent);
+ file->writeNumberLine(_flag, indent);
CCarry::save(file, indent);
}
void CLiftbotHead::load(SimpleFile *file) {
file->readNumber();
- _field12C = file->readNumber();
+ _flag = file->readNumber();
CCarry::load(file);
}
+bool CLiftbotHead::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ if (msg->_other->getName() == "LiftbotWithoutHead") {
+ CPetControl *pet = getPetControl();
+ if (CLift::_v1 == 1 && pet->getRoomsElevatorNum() == 4) {
+ _flag = true;
+ CActMsg actMsg("AddRightHead");
+ actMsg.execute("FaultyLiftbot");
+ setVisible(false);
+ }
+
+ return true;
+ } else {
+ return CCarry::UseWithOtherMsg(msg);
+ }
+}
+
+bool CLiftbotHead::UseWithCharMsg(CUseWithCharMsg *msg) {
+ CLift *lift = dynamic_cast<CLift *>(msg->_character);
+ if (lift) {
+ CPetControl *pet = getPetControl();
+ if (lift->isEquals("Well") && !CLift::_v1 && pet->getRoomsElevatorNum() == 4) {
+ _flag = true;
+ CActMsg actMsg("AddRightHead");
+ actMsg.execute(lift);
+ setVisible(false);
+
+ return true;
+ }
+ }
+
+ return CCarry::UseWithCharMsg(msg);
+}
+
+bool CLiftbotHead::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkStartDragging(msg)) {
+ return false;
+ } else if (compareViewNameTo("BottomOfWell.Node 8.N")) {
+ changeView("BottomOfWell.Node 13.N");
+ moveToView();
+
+ CActMsg actMsg("LiftbotHeadTaken");
+ actMsg.execute("BOWLiftbotHeadMonitor");
+
+ return CCarry::MouseDragStartMsg(msg);
+ } else if (_flag) {
+ _flag = false;
+ CActMsg actMsg("LoseHead");
+ actMsg.execute("FaultyLiftbot");
+ }
+
+ return CCarry::MouseDragStartMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/liftbot_head.h b/engines/titanic/carry/liftbot_head.h
index 2fcd6a71f9..44cc51c993 100644
--- a/engines/titanic/carry/liftbot_head.h
+++ b/engines/titanic/carry/liftbot_head.h
@@ -28,8 +28,12 @@
namespace Titanic {
class CLiftbotHead : public CCarry {
+ DECLARE_MESSAGE_MAP;
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
+ bool UseWithCharMsg(CUseWithCharMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
private:
- int _field12C;
+ bool _flag;
public:
CLASSDEF;
CLiftbotHead();
diff --git a/engines/titanic/carry/long_stick.cpp b/engines/titanic/carry/long_stick.cpp
index ab1e42b81f..557b75ab87 100644
--- a/engines/titanic/carry/long_stick.cpp
+++ b/engines/titanic/carry/long_stick.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CLongStick, CCarry)
+ ON_MESSAGE(UseWithOtherMsg)
+ ON_MESSAGE(PuzzleSolvedMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
CLongStick::CLongStick() : CCarry() {
}
@@ -37,4 +43,30 @@ void CLongStick::load(SimpleFile *file) {
CCarry::load(file);
}
+bool CLongStick::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ if (msg->_other->isEquals("SpeechCentre")) {
+ CPuzzleSolvedMsg puzzleMsg;
+ puzzleMsg.execute(msg->_other);
+ } else if (msg->_other->isEquals("LongStickDispensor")) {
+ petDisplayMessage(1, "You already have one.");
+ } else if (msg->_other->isEquals("Bomb")) {
+ CActMsg actMsg("Hit");
+ actMsg.execute("Bomb");
+ } else {
+ return CCarry::UseWithOtherMsg(msg);
+ }
+
+ return true;
+}
+
+bool CLongStick::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ _fieldE0 = 1;
+ return true;
+}
+
+bool CLongStick::LeaveViewMsg(CLeaveViewMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/long_stick.h b/engines/titanic/carry/long_stick.h
index 2ff5b7228e..329ca838f9 100644
--- a/engines/titanic/carry/long_stick.h
+++ b/engines/titanic/carry/long_stick.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CLongStick : public CCarry {
+ DECLARE_MESSAGE_MAP;
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
CLASSDEF;
CLongStick();
diff --git a/engines/titanic/carry/magazine.cpp b/engines/titanic/carry/magazine.cpp
index cdf92fc707..e68c63f8f9 100644
--- a/engines/titanic/carry/magazine.cpp
+++ b/engines/titanic/carry/magazine.cpp
@@ -52,7 +52,7 @@ void CMagazine::load(SimpleFile *file) {
}
bool CMagazine::UseWithCharMsg(CUseWithCharMsg *msg) {
- CDeskbot *deskbot = static_cast<CDeskbot *>(msg->_character);
+ CDeskbot *deskbot = dynamic_cast<CDeskbot *>(msg->_character);
if (deskbot) {
if (deskbot->_deskbotActive) {
setVisible(false);
diff --git a/engines/titanic/carry/maitred_left_arm.cpp b/engines/titanic/carry/maitred_left_arm.cpp
index b31c2a6f6d..0962f232bd 100644
--- a/engines/titanic/carry/maitred_left_arm.cpp
+++ b/engines/titanic/carry/maitred_left_arm.cpp
@@ -21,19 +21,48 @@
*/
#include "titanic/carry/maitred_left_arm.h"
+#include "titanic/npcs/true_talk_npc.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMaitreDLeftArm, CArm)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CMaitreDLeftArm::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_field174, indent);
+ file->writeNumberLine(_flag, indent);
CArm::save(file, indent);
}
void CMaitreDLeftArm::load(SimpleFile *file) {
file->readNumber();
- _field174 = file->readNumber();
+ _flag = file->readNumber();
CArm::load(file);
}
+bool CMaitreDLeftArm::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_flag) {
+ CTrueTalkNPC *maitreD = dynamic_cast<CTrueTalkNPC *>(findRoomObject("MaitreD"));
+ startTalking(maitreD, 126);
+ startTalking(maitreD, 127);
+ }
+
+ return true;
+}
+
+bool CMaitreDLeftArm::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (checkPoint(msg->_mousePos) && !_flag) {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("MD left arm background image");
+ _flag = true;
+
+ CArmPickedUpFromTableMsg takenMsg;
+ takenMsg.execute("Restaurant Table Pan Handler", nullptr, MSGFLAG_SCAN);
+ }
+
+ return CArm::MouseDragStartMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/maitred_left_arm.h b/engines/titanic/carry/maitred_left_arm.h
index 8f5090b073..0e1732df46 100644
--- a/engines/titanic/carry/maitred_left_arm.h
+++ b/engines/titanic/carry/maitred_left_arm.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CMaitreDLeftArm : public CArm {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
private:
- int _field174;
+ bool _flag;
public:
CLASSDEF;
- CMaitreDLeftArm() : CArm(), _field174(0) {}
+ CMaitreDLeftArm() : CArm(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/carry/maitred_right_arm.cpp b/engines/titanic/carry/maitred_right_arm.cpp
index 7030e83c9d..5cec6be9bd 100644
--- a/engines/titanic/carry/maitred_right_arm.cpp
+++ b/engines/titanic/carry/maitred_right_arm.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMaitreDRightArm, CArm)
+ ON_MESSAGE(DropZoneLostObjectMsg)
+END_MESSAGE_MAP()
+
void CMaitreDRightArm::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CArm::save(file, indent);
@@ -34,4 +38,12 @@ void CMaitreDRightArm::load(SimpleFile *file) {
CArm::load(file);
}
+bool CMaitreDRightArm::DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg) {
+ CActMsg actMsg("LoseArm");
+ actMsg.execute("MaitreDBody");
+ actMsg.execute("MaitreD Arm Holder");
+ _fieldE0 = 1;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/maitred_right_arm.h b/engines/titanic/carry/maitred_right_arm.h
index ce07ed7af4..4a53d45f69 100644
--- a/engines/titanic/carry/maitred_right_arm.h
+++ b/engines/titanic/carry/maitred_right_arm.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CMaitreDRightArm : public CArm {
+ DECLARE_MESSAGE_MAP;
+ bool DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/carry/mouth.cpp b/engines/titanic/carry/mouth.cpp
index 8c3791fa9c..e48929a391 100644
--- a/engines/titanic/carry/mouth.cpp
+++ b/engines/titanic/carry/mouth.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/carry/mouth.h"
+#include "titanic/game/head_slot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMouth, CHeadPiece)
+ ON_MESSAGE(UseWithOtherMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(PETGainedObjectMsg)
+END_MESSAGE_MAP()
+
CMouth::CMouth() : CHeadPiece() {
}
@@ -37,4 +44,37 @@ void CMouth::load(SimpleFile *file) {
CHeadPiece::load(file);
}
+bool CMouth::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ CHeadSlot *slot = dynamic_cast<CHeadSlot *>(msg->_other);
+ if (!slot)
+ return CHeadPiece::UseWithOtherMsg(msg);
+
+ _flag = true;
+ setVisible(false);
+ setPosition(Point(0, 0));
+ petMoveToHiddenRoom();
+
+ CAddHeadPieceMsg addMsg(getName());
+ if (addMsg._value != "NULL")
+ addMsg.execute("MouthSlot");
+
+ return true;
+}
+
+bool CMouth::MovieEndMsg(CMovieEndMsg *msg) {
+ return true;
+}
+
+bool CMouth::PETGainedObjectMsg(CPETGainedObjectMsg *msg) {
+ _visibleFrame = 2;
+ loadFrame(2);
+ setVisible(true);
+ if (!_field13C) {
+ stateInc38();
+ _field13C = true;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/mouth.h b/engines/titanic/carry/mouth.h
index e394330494..f5f0f53b45 100644
--- a/engines/titanic/carry/mouth.h
+++ b/engines/titanic/carry/mouth.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CMouth : public CHeadPiece {
+ DECLARE_MESSAGE_MAP;
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool PETGainedObjectMsg(CPETGainedObjectMsg *msg);
public:
CLASSDEF;
CMouth();
diff --git a/engines/titanic/carry/napkin.cpp b/engines/titanic/carry/napkin.cpp
index ace5a389a0..d0ee9acc1a 100644
--- a/engines/titanic/carry/napkin.cpp
+++ b/engines/titanic/carry/napkin.cpp
@@ -43,7 +43,7 @@ void CNapkin::load(SimpleFile *file) {
}
bool CNapkin::UseWithOtherMsg(CUseWithOtherMsg *msg) {
- CChicken *chicken = static_cast<CChicken *>(msg->_other);
+ CChicken *chicken = dynamic_cast<CChicken *>(msg->_other);
if (chicken) {
if (chicken->_string6 == "None" || chicken->_field12C) {
CActMsg actMsg("Clean");
@@ -57,5 +57,4 @@ bool CNapkin::UseWithOtherMsg(CUseWithOtherMsg *msg) {
return CCarry::UseWithOtherMsg(msg);
}
-
} // End of namespace Titanic
diff --git a/engines/titanic/carry/nose.cpp b/engines/titanic/carry/nose.cpp
index 4f3afe24ac..a08d02a88c 100644
--- a/engines/titanic/carry/nose.cpp
+++ b/engines/titanic/carry/nose.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/carry/nose.h"
+#include "titanic/game/head_slot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CNose, CHeadPiece)
+ ON_MESSAGE(ChangeSeasonMsg)
+ ON_MESSAGE(UseWithOtherMsg)
+END_MESSAGE_MAP()
+
CNose::CNose() : CHeadPiece() {
}
@@ -37,4 +43,23 @@ void CNose::load(SimpleFile *file) {
CHeadPiece::load(file);
}
+bool CNose::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ // WORKAROUND: Redundant code in original skipped
+ return true;
+}
+
+bool CNose::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ CHeadSlot *slot = dynamic_cast<CHeadSlot *>(msg->_other);
+ if (!slot)
+ return CCarry::UseWithOtherMsg(msg);
+
+ petMoveToHiddenRoom();
+ _flag = false;
+ CAddHeadPieceMsg addMsg(getName());
+ if (addMsg._value != "NULL")
+ addMsg.execute("NoseSlot");
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/nose.h b/engines/titanic/carry/nose.h
index b688da231a..6e5be30df2 100644
--- a/engines/titanic/carry/nose.h
+++ b/engines/titanic/carry/nose.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CNose : public CHeadPiece {
+ DECLARE_MESSAGE_MAP;
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
public:
CLASSDEF;
CNose();
diff --git a/engines/titanic/carry/perch.cpp b/engines/titanic/carry/perch.cpp
index 281b3fce53..4f0e76bdb0 100644
--- a/engines/titanic/carry/perch.cpp
+++ b/engines/titanic/carry/perch.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPerch, CCentralCore)
+ ON_MESSAGE(UseWithOtherMsg)
+END_MESSAGE_MAP()
+
void CPerch::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CCentralCore::save(file, indent);
@@ -34,4 +38,13 @@ void CPerch::load(SimpleFile *file) {
CCentralCore::load(file);
}
+bool CPerch::UseWithOtherMsg(CUseWithOtherMsg *msg) {
+ if (msg->_other->isEquals("SpeechCentre")) {
+ CShowTextMsg textMsg("This does not reach.");
+ textMsg.execute("PET");
+ }
+
+ return CCentralCore::UseWithOtherMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/perch.h b/engines/titanic/carry/perch.h
index d23868d909..8941c8ea4d 100644
--- a/engines/titanic/carry/perch.h
+++ b/engines/titanic/carry/perch.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CPerch : public CCentralCore {
+ DECLARE_MESSAGE_MAP;
+ bool UseWithOtherMsg(CUseWithOtherMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/carry/phonograph_cylinder.cpp b/engines/titanic/carry/phonograph_cylinder.cpp
index 0684c56611..3dedbc4ac9 100644
--- a/engines/titanic/carry/phonograph_cylinder.cpp
+++ b/engines/titanic/carry/phonograph_cylinder.cpp
@@ -22,6 +22,7 @@
#include "titanic/carry/phonograph_cylinder.h"
#include "titanic/game/phonograph.h"
+#include "titanic/sound/music_room.h"
namespace Titanic {
@@ -101,7 +102,7 @@ void CPhonographCylinder::load(SimpleFile *file) {
}
bool CPhonographCylinder::UseWithOtherMsg(CUseWithOtherMsg *msg) {
- CPhonograph *phonograph = static_cast<CPhonograph *>(msg->_other);
+ CPhonograph *phonograph = dynamic_cast<CPhonograph *>(msg->_other);
if (phonograph) {
CSetVarMsg varMsg("m_RecordStatus", 1);
return true;
@@ -162,10 +163,33 @@ bool CPhonographCylinder::RecordOntoCylinderMsg(CRecordOntoCylinderMsg *msg) {
}
bool CPhonographCylinder::SetMusicControlsMsg(CSetMusicControlsMsg *msg) {
- if (_itemName.left(7) == "STMusic") {
- //todo
- warning("TODO");
- }
+ if (!_itemName.hasPrefix("STMusic"))
+ return true;
+
+ CMusicRoom *musicRoom = getMusicRoom();
+ musicRoom->setMuteControl(BELLS, _bellsMuteControl);
+ musicRoom->setPitchControl(BELLS, _bellsPitchControl);
+ musicRoom->setSpeedControl(BELLS, _bellsSpeedControl);
+ musicRoom->setInversionControl(BELLS, _bellsInversionControl);
+ musicRoom->setDirectionControl(BELLS, _bellsDirectionControl);
+
+ musicRoom->setMuteControl(SNAKE, _snakeMuteControl);
+ musicRoom->setPitchControl(SNAKE, _snakePitchControl);
+ musicRoom->setSpeedControl(SNAKE, _snakeSpeedControl);
+ musicRoom->setInversionControl(SNAKE, _snakeInversionControl);
+ musicRoom->setDirectionControl(SNAKE, _snakeDirectionControl);
+
+ musicRoom->setMuteControl(PIANO, _pianoMuteControl);
+ musicRoom->setPitchControl(PIANO, _pianoPitchControl);
+ musicRoom->setSpeedControl(PIANO, _pianoSpeedControl);
+ musicRoom->setInversionControl(PIANO, _pianoInversionControl);
+ musicRoom->setDirectionControl(PIANO, _pianoDirectionControl);
+
+ musicRoom->setMuteControl(BASS, _bassMuteControl);
+ musicRoom->setPitchControl(BASS, _bassPitchControl);
+ musicRoom->setSpeedControl(BASS, _bassSpeedControl);
+ musicRoom->setInversionControl(BASS, _bassInversionControl);
+ musicRoom->setDirectionControl(BASS, _bassDirectionControl);
return true;
}
diff --git a/engines/titanic/carry/phonograph_ear.cpp b/engines/titanic/carry/phonograph_ear.cpp
index ceb71babd2..95297a77a1 100644
--- a/engines/titanic/carry/phonograph_ear.cpp
+++ b/engines/titanic/carry/phonograph_ear.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPhonographEar, CEar)
+ ON_MESSAGE(CorrectMusicPlayedMsg)
+ ON_MESSAGE(PETGainedObjectMsg)
+ ON_MESSAGE(TimerMsg)
+END_MESSAGE_MAP()
+
void CPhonographEar::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_field140, indent);
@@ -36,4 +42,24 @@ void CPhonographEar::load(SimpleFile *file) {
CEar::load(file);
}
+bool CPhonographEar::CorrectMusicPlayedMsg(CCorrectMusicPlayedMsg *msg) {
+ _fieldE0 = true;
+ return true;
+}
+
+bool CPhonographEar::PETGainedObjectMsg(CPETGainedObjectMsg *msg) {
+ if (_field140) {
+ _field140 = false;
+ addTimer(1000);
+ }
+
+ return CEar::PETGainedObjectMsg(msg);
+}
+
+bool CPhonographEar::TimerMsg(CTimerMsg *msg) {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("Replacement Phonograph Ear");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/phonograph_ear.h b/engines/titanic/carry/phonograph_ear.h
index 582db9f7ef..b5db015f90 100644
--- a/engines/titanic/carry/phonograph_ear.h
+++ b/engines/titanic/carry/phonograph_ear.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CPhonographEar : public CEar {
+ DECLARE_MESSAGE_MAP;
+ bool CorrectMusicPlayedMsg(CCorrectMusicPlayedMsg *msg);
+ bool PETGainedObjectMsg(CPETGainedObjectMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
private:
- int _field140;
+ bool _field140;
public:
CLASSDEF;
- CPhonographEar() : CEar(), _field140(1) {}
+ CPhonographEar() : CEar(), _field140(true) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/carry/photograph.cpp b/engines/titanic/carry/photograph.cpp
index 7f32a0623d..039efd0252 100644
--- a/engines/titanic/carry/photograph.cpp
+++ b/engines/titanic/carry/photograph.cpp
@@ -21,6 +21,7 @@
*/
#include "titanic/carry/photograph.h"
+#include "titanic/core/dont_save_file_item.h"
#include "titanic/core/room_item.h"
namespace Titanic {
@@ -59,8 +60,12 @@ bool CPhotograph::MouseDragEndMsg(CMouseDragEndMsg *msg) {
_v1 = 0;
CGameObject *target = msg->_dropTarget;
- if (target && target->getName() != "NavigationComputer") {
- warning("TODO: CPhotograph::MouseDragEndMsg");
+ if (target && target->isEquals("NavigationComputer")) {
+ moveUnder(getDontSave());
+ makeDirty();
+ playSound("a#46.wav");
+ starFn1(14);
+ showMouse();
return true;
} else {
return CCarry::MouseDragEndMsg(msg);
@@ -78,7 +83,7 @@ bool CPhotograph::MouseDragStartMsg(CMouseDragStartMsg *msg) {
}
bool CPhotograph::PETGainedObjectMsg(CPETGainedObjectMsg *msg) {
- if (getRoom()->getName() == "Home") {
+ if (getRoom()->isEquals("Home")) {
CActMsg actMsg("PlayerPutsPhotoInPET");
actMsg.execute("Doorbot");
}
diff --git a/engines/titanic/carry/plug_in.cpp b/engines/titanic/carry/plug_in.cpp
index c82a4cc422..883458c9b1 100644
--- a/engines/titanic/carry/plug_in.cpp
+++ b/engines/titanic/carry/plug_in.cpp
@@ -47,19 +47,13 @@ bool CPlugIn::UseWithOtherMsg(CUseWithOtherMsg *msg) {
if (otherName == "PET") {
return CCarry::UseWithOtherMsg(msg);
- } else if (otherName == "DatasideTransporter") {
- CString name = getName();
- if (name == "DatasideTransporter") {
- // TODO
- if (name != "SendYourself") {
- // TODO
- }
- } else {
- // TODO
- }
- } else {
+ } else if (isEquals("DatasideTransporter")) {
CShowTextMsg textMsg("This item is incorrectly calibrated.");
textMsg.execute("PET");
+ } else if (isEquals("DatasideTransporter")) {
+ error("TODO: Set msg->_other->fieldC4 = 2");
+ } else if (isEquals("SendYourself")) {
+ error("TODO: Set msg->_other->fieldC8 = 1");
}
return true;
diff --git a/engines/titanic/carry/speech_centre.cpp b/engines/titanic/carry/speech_centre.cpp
index b8076aee76..29ced484a5 100644
--- a/engines/titanic/carry/speech_centre.cpp
+++ b/engines/titanic/carry/speech_centre.cpp
@@ -24,10 +24,17 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSpeechCentre, CBrain)
+ ON_MESSAGE(PuzzleSolvedMsg)
+ ON_MESSAGE(ChangeSeasonMsg)
+ ON_MESSAGE(SpeechFallsFromTreeMsg)
+ ON_MESSAGE(FrameMsg)
+END_MESSAGE_MAP()
+
void CSpeechCentre::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_field13C, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeQuotedLine(_season, indent);
file->writeNumberLine(_field14C, indent);
CBrain::save(file, indent);
@@ -36,10 +43,41 @@ void CSpeechCentre::save(SimpleFile *file, int indent) {
void CSpeechCentre::load(SimpleFile *file) {
file->readNumber();
_field13C = file->readNumber();
- _string1 = file->readString();
+ _season = file->readString();
_field14C = file->readNumber();
CBrain::load(file);
}
+bool CSpeechCentre::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ if (_field13C == 1 && _season == "Autumn")
+ _fieldE0 = true;
+ return true;
+}
+
+bool CSpeechCentre::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ _season = msg->_season;
+ return true;
+}
+
+bool CSpeechCentre::SpeechFallsFromTreeMsg(CSpeechFallsFromTreeMsg *msg) {
+ setVisible(true);
+ dragMove(msg->_pos);
+ _field14C = true;
+ return true;
+}
+
+bool CSpeechCentre::FrameMsg(CFrameMsg *msg) {
+ if (_field14C) {
+ if (_bounds.top > 200)
+ _field14C = false;
+
+ makeDirty();
+ _bounds.top += 3;
+ makeDirty();
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/speech_centre.h b/engines/titanic/carry/speech_centre.h
index 50f47e9c8a..806e22247b 100644
--- a/engines/titanic/carry/speech_centre.h
+++ b/engines/titanic/carry/speech_centre.h
@@ -28,13 +28,18 @@
namespace Titanic {
class CSpeechCentre : public CBrain {
+ DECLARE_MESSAGE_MAP;
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
+ bool SpeechFallsFromTreeMsg(CSpeechFallsFromTreeMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
private:
int _field13C;
- CString _string1;
+ CString _season;
int _field14C;
public:
CLASSDEF;
- CSpeechCentre() : CBrain(), _string1("Summer"),
+ CSpeechCentre() : CBrain(), _season("Summer"),
_field13C(1), _field14C(0) {}
/**
diff --git a/engines/titanic/carry/vision_centre.cpp b/engines/titanic/carry/vision_centre.cpp
index 8c8bab15f8..fd30089fc5 100644
--- a/engines/titanic/carry/vision_centre.cpp
+++ b/engines/titanic/carry/vision_centre.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CVisionCentre, CBrain)
+ ON_MESSAGE(PuzzleSolvedMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CVisionCentre::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBrain::save(file, indent);
@@ -34,4 +40,27 @@ void CVisionCentre::load(SimpleFile *file) {
CBrain::load(file);
}
+bool CVisionCentre::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ _fieldE0 = true;
+ return true;
+}
+
+bool CVisionCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_fieldE0) {
+ return CBrain::MouseButtonDownMsg(msg);
+ } else {
+ petDisplayMessage(1, "It would be nice if you could take that but you can't.");
+ return true;
+ }
+}
+
+bool CVisionCentre::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (_fieldE0) {
+ return CBrain::MouseDragStartMsg(msg);
+ } else {
+ petDisplayMessage(1, "It would be nice if you could take that but you can't.");
+ return true;
+ }
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/carry/vision_centre.h b/engines/titanic/carry/vision_centre.h
index 6cf8e2c653..14055a5f5f 100644
--- a/engines/titanic/carry/vision_centre.h
+++ b/engines/titanic/carry/vision_centre.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CVisionCentre : public CBrain {
+ DECLARE_MESSAGE_MAP;
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/core/background.cpp b/engines/titanic/core/background.cpp
index f180df8867..733dfc1cf3 100644
--- a/engines/titanic/core/background.cpp
+++ b/engines/titanic/core/background.cpp
@@ -30,13 +30,13 @@ BEGIN_MESSAGE_MAP(CBackground, CGameObject)
ON_MESSAGE(VisibleMsg)
END_MESSAGE_MAP()
-CBackground::CBackground() : CGameObject(), _fieldBC(0), _fieldC0(0), _fieldDC(0) {
+CBackground::CBackground() : CGameObject(), _startFrame(0), _endFrame(0), _fieldDC(0) {
}
void CBackground::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
+ file->writeNumberLine(_startFrame, indent);
+ file->writeNumberLine(_endFrame, indent);
file->writeQuotedLine(_string1, indent);
file->writeQuotedLine(_string2, indent);
file->writeNumberLine(_fieldDC, indent);
@@ -46,8 +46,8 @@ void CBackground::save(SimpleFile *file, int indent) {
void CBackground::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
+ _startFrame = file->readNumber();
+ _endFrame = file->readNumber();
_string1 = file->readString();
_string2 = file->readString();
_fieldDC = file->readNumber();
@@ -58,9 +58,9 @@ void CBackground::load(SimpleFile *file) {
bool CBackground::StatusChangeMsg(CStatusChangeMsg *msg) {
setVisible(true);
if (_fieldDC) {
- playMovie(_fieldBC, _fieldC0, 16);
+ playMovie(_startFrame, _endFrame, 16);
} else {
- playMovie(_fieldBC, _fieldC0, 0);
+ playMovie(_startFrame, _endFrame, 0);
}
return true;
}
diff --git a/engines/titanic/core/background.h b/engines/titanic/core/background.h
index 6a2fd21454..b7f160db28 100644
--- a/engines/titanic/core/background.h
+++ b/engines/titanic/core/background.h
@@ -34,8 +34,8 @@ class CBackground : public CGameObject {
bool SetFrameMsg(CSetFrameMsg *msg);
bool VisibleMsg(CVisibleMsg *msg);
protected:
- int _fieldBC;
- int _fieldC0;
+ int _startFrame;
+ int _endFrame;
CString _string1;
CString _string2;
int _fieldDC;
diff --git a/engines/titanic/core/click_responder.cpp b/engines/titanic/core/click_responder.cpp
index f9694557df..9a0e0de7ab 100644
--- a/engines/titanic/core/click_responder.cpp
+++ b/engines/titanic/core/click_responder.cpp
@@ -24,20 +24,33 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CClickResponder, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CClickResponder::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeQuotedLine(_string2, indent);
+ file->writeQuotedLine(_message, indent);
+ file->writeQuotedLine(_soundName, indent);
CGameObject::save(file, indent);
}
void CClickResponder::load(SimpleFile *file) {
file->readNumber();
- _string1 = file->readString();
- _string2 = file->readString();
+ _message = file->readString();
+ _soundName = file->readString();
CGameObject::load(file);
}
+bool CClickResponder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_soundName.empty())
+ playSound(_soundName);
+ if (!_message.empty())
+ petDisplayMessage(_message);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/core/click_responder.h b/engines/titanic/core/click_responder.h
index 78381b9948..40f22d7906 100644
--- a/engines/titanic/core/click_responder.h
+++ b/engines/titanic/core/click_responder.h
@@ -28,8 +28,10 @@
namespace Titanic {
class CClickResponder : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
protected:
- CString _string1, _string2;
+ CString _message, _soundName;
public:
CLASSDEF;
diff --git a/engines/titanic/core/drop_target.cpp b/engines/titanic/core/drop_target.cpp
index 05ea6445c3..13a31c3f8a 100644
--- a/engines/titanic/core/drop_target.cpp
+++ b/engines/titanic/core/drop_target.cpp
@@ -21,30 +21,40 @@
*/
#include "titanic/core/drop_target.h"
+#include "titanic/carry/carry.h"
namespace Titanic {
-CDropTarget::CDropTarget() : CGameObject(), _fieldC4(0),
- _fieldD4(0), _fieldE4(0), _fieldF4(0), _fieldF8(0),
- _fieldFC(0), _field10C(1), _field110(8), _field114(20) {
+BEGIN_MESSAGE_MAP(CDropTarget, CGameObject)
+ ON_MESSAGE(DropObjectMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(VisibleMsg)
+ ON_MESSAGE(DropZoneLostObjectMsg)
+END_MESSAGE_MAP()
+
+CDropTarget::CDropTarget() : CGameObject(), _itemFrame(0),
+ _itemMatchSize(0), _showItem(false), _fieldF4(0), _dropFrame(0),
+ _dragFrame(0), _dragCursorId(CURSOR_ARROW), _dropCursorId(CURSOR_HAND),
+ _clipFlags(20) {
}
void CDropTarget::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writePoint(_pos1, indent);
- file->writeNumberLine(_fieldC4, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeNumberLine(_fieldD4, indent);
- file->writeQuotedLine(_string2, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeQuotedLine(_string3, indent);
+ file->writeNumberLine(_itemFrame, indent);
+ file->writeQuotedLine(_itemMatchName, indent);
+ file->writeNumberLine(_itemMatchSize, indent);
+ file->writeQuotedLine(_soundName, indent);
+ file->writeNumberLine(_showItem, indent);
+ file->writeQuotedLine(_itemName, indent);
file->writeNumberLine(_fieldF4, indent);
- file->writeNumberLine(_fieldF8, indent);
- file->writeNumberLine(_fieldFC, indent);
- file->writeQuotedLine(_string4, indent);
- file->writeNumberLine(_field10C, indent);
- file->writeNumberLine(_field110, indent);
- file->writeNumberLine(_field114, indent);
+ file->writeNumberLine(_dropFrame, indent);
+ file->writeNumberLine(_dragFrame, indent);
+ file->writeQuotedLine(_clipName, indent);
+ file->writeNumberLine(_dragCursorId, indent);
+ file->writeNumberLine(_dropCursorId, indent);
+ file->writeNumberLine(_clipFlags, indent);
CGameObject::save(file, indent);
}
@@ -52,21 +62,129 @@ void CDropTarget::save(SimpleFile *file, int indent) {
void CDropTarget::load(SimpleFile *file) {
file->readNumber();
_pos1 = file->readPoint();
- _fieldC4 = file->readNumber();
- _string1 = file->readString();
- _fieldD4 = file->readNumber();
- _string2 = file->readString();
- _fieldE4 = file->readNumber();
- _string3 = file->readString();
+ _itemFrame = file->readNumber();
+ _itemMatchName = file->readString();
+ _itemMatchSize = file->readNumber();
+ _soundName = file->readString();
+ _showItem = file->readNumber();
+ _itemName = file->readString();
_fieldF4 = file->readNumber();
- _fieldF8 = file->readNumber();
- _fieldFC = file->readNumber();
- _string4 = file->readString();
- _field10C = file->readNumber();
- _field110 = file->readNumber();
- _field114 = file->readNumber();
+ _dropFrame = file->readNumber();
+ _dragFrame = file->readNumber();
+ _clipName = file->readString();
+ _dragCursorId = (CursorId)file->readNumber();
+ _dropCursorId = (CursorId)file->readNumber();
+ _clipFlags = file->readNumber();
CGameObject::load(file);
}
+bool CDropTarget::DropObjectMsg(CDropObjectMsg *msg) {
+ if (!_itemName.empty()) {
+ if (msg->_item->getName() != _itemName) {
+ if (findByName(_itemName, true))
+ return false;
+ }
+ }
+
+ if (!msg->_item->isEquals(_itemMatchName, _itemMatchSize))
+ return false;
+
+ msg->_item->detach();
+ msg->_item->addUnder(this);
+ msg->_item->setPosition(Point(_bounds.left, _bounds.top));
+
+ msg->_item->loadFrame(_itemFrame);
+ if (_showItem)
+ msg->_item->setVisible(false);
+
+ CDropZoneGotObjectMsg gotMsg(this);
+ gotMsg.execute(msg->_item);
+ playSound(_soundName);
+
+ if (_clipName.empty()) {
+ loadFrame(_dropFrame);
+ } else {
+ playClip(_clipName, _clipFlags);
+ }
+
+ _cursorId = _dropCursorId;
+ return true;
+}
+
+bool CDropTarget::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkStartDragging(msg))
+ return false;
+ //msg->_dragItem = msg->_dragItem;
+
+ CGameObject *obj = dynamic_cast<CGameObject *>(findByName(_itemName));
+ if (_itemName.empty() || _fieldF4 || !obj)
+ return false;
+
+ CDropZoneLostObjectMsg lostMsg;
+ lostMsg._object = this;
+ lostMsg.execute(obj);
+
+ loadFrame(_dragFrame);
+ _cursorId = _dragCursorId;
+
+ if (obj->_visible) {
+ msg->execute(obj);
+ } else {
+ msg->_dragItem = obj;
+ CPassOnDragStartMsg passMsg(msg->_mousePos, 1);
+ passMsg.execute(obj);
+ setVisible(true);
+ }
+
+ return true;
+}
+
+bool CDropTarget::EnterViewMsg(CEnterViewMsg *msg) {
+ if (!_itemName.empty()) {
+ CGameObject *obj = dynamic_cast<CGameObject *>(findByName(_itemName));
+ if (!obj) {
+ loadFrame(_dragFrame);
+ _cursorId = _dragCursorId;
+ } else if (_clipName.empty()) {
+ loadFrame(_dropFrame);
+ } else {
+ playClip(_clipName, _clipFlags);
+ }
+
+ _cursorId = _dropCursorId;
+ }
+
+ return true;
+}
+
+bool CDropTarget::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(msg->_visible);
+ _fieldF4 = !msg->_visible;
+ return true;
+}
+
+bool CDropTarget::DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg) {
+ if (!_itemName.empty()) {
+ CGameObject *obj = dynamic_cast<CGameObject *>(findByName(_itemName));
+ if (obj) {
+ if (msg->_object) {
+ obj->detach();
+ obj->addUnder(msg->_object);
+ } else if (dynamic_cast<CCarry *>(obj)) {
+ obj->petAddToInventory();
+ }
+
+ setVisible(true);
+ CDropZoneLostObjectMsg lostMsg(this);
+ lostMsg.execute(obj);
+ }
+
+ loadFrame(_dragFrame);
+ _cursorId = _dragCursorId;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/core/drop_target.h b/engines/titanic/core/drop_target.h
index 4bd0ae448c..e07b640c9f 100644
--- a/engines/titanic/core/drop_target.h
+++ b/engines/titanic/core/drop_target.h
@@ -28,21 +28,27 @@
namespace Titanic {
class CDropTarget : public CGameObject {
-private:
+ DECLARE_MESSAGE_MAP;
+ bool DropObjectMsg(CDropObjectMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool VisibleMsg(CVisibleMsg *msg);
+ bool DropZoneLostObjectMsg(CDropZoneLostObjectMsg *msg);
+protected:
Point _pos1;
- int _fieldC4;
- CString _string1;
- int _fieldD4;
- CString _string2;
- int _fieldE4;
- CString _string3;
+ int _itemFrame;
+ CString _itemMatchName;
+ int _itemMatchSize;
+ CString _soundName;
+ bool _showItem;
+ CString _itemName;
int _fieldF4;
- int _fieldF8;
- int _fieldFC;
- CString _string4;
- int _field10C;
- int _field110;
- int _field114;
+ int _dropFrame;
+ int _dragFrame;
+ CString _clipName;
+ CursorId _dragCursorId;
+ CursorId _dropCursorId;
+ uint _clipFlags;
public:
CLASSDEF;
CDropTarget();
diff --git a/engines/titanic/core/game_object.cpp b/engines/titanic/core/game_object.cpp
index 67b7920f04..7848734792 100644
--- a/engines/titanic/core/game_object.cpp
+++ b/engines/titanic/core/game_object.cpp
@@ -42,7 +42,7 @@ int CGameObject::_soundHandles[4];
void CGameObject::init() {
_credits = nullptr;
- _soundHandles[0] = _soundHandles[1] = 0;
+ _soundHandles[0] = _soundHandles[1] = -1;
_soundHandles[2] = _soundHandles[3] = -1;
}
@@ -438,7 +438,8 @@ bool CGameObject::isSoundActive(int handle) const {
return false;
}
-void CGameObject::playGlobalSound(const CString &resName, int mode, bool initialMute, bool repeated, int handleIndex) {
+void CGameObject::playGlobalSound(const CString &resName, int mode, bool initialMute, bool repeated,
+ int handleIndex, Audio::Mixer::SoundType soundType) {
if (handleIndex < 0 || handleIndex > 3)
return;
CGameManager *gameManager = getGameManager();
@@ -463,19 +464,20 @@ void CGameObject::playGlobalSound(const CString &resName, int mode, bool initial
CProximity prox;
prox._channelVolume = volume;
prox._repeated = repeated;
+ prox._soundType = soundType;
switch (handleIndex) {
case 0:
- prox._channel = 6;
+ prox._channelMode = 6;
break;
case 1:
- prox._channel = 7;
+ prox._channelMode = 7;
break;
case 2:
- prox._channel = 8;
+ prox._channelMode = 8;
break;
case 3:
- prox._channel = 9;
+ prox._channelMode = 9;
break;
default:
break;
@@ -519,7 +521,6 @@ void CGameObject::stopGlobalSound(bool transition, int handleIndex) {
sound.stopSound(_soundHandles[handleIndex]);
_soundHandles[handleIndex] = -1;
}
- warning("CGameObject::soundFn4");
}
void CGameObject::setGlobalSoundVolume(int mode, uint seconds, int handleIndex) {
@@ -657,10 +658,10 @@ void CGameObject::playClip(uint startFrame, uint endFrame) {
gameManager->playClip(clip, room, room);
}
-void CGameObject::playRandomClip(const char **names, uint flags) {
+void CGameObject::playRandomClip(const char *const *names, uint flags) {
// Count size of array
int count = 0;
- for (const char **p = names; *p; ++p)
+ for (const char *const *p = names; *p; ++p)
++count;
// Play clip
@@ -668,6 +669,20 @@ void CGameObject::playRandomClip(const char **names, uint flags) {
playClip(name, flags);
}
+void CGameObject::playCutscene(uint startFrame, uint endFrame) {
+ if (!_surface) {
+ if (!_resource.empty())
+ loadResource(_resource);
+ _resource.clear();
+ }
+
+ if (_surface && _surface->loadIfReady() && _surface->_movie) {
+ disableMouse();
+ _surface->_movie->playCutscene(_bounds, startFrame, endFrame);
+ enableMouse();
+ }
+}
+
void CGameObject::savePosition() {
_savedPos = _bounds;
}
@@ -726,7 +741,7 @@ int CGameObject::playSound(const CString &name, uint volume, int val3, bool repe
}
int CGameObject::playSound(const CString &name, CProximity &prox) {
- if (prox._field28 == 2) {
+ if (prox._positioningMode == POSMODE_VECTOR) {
// If the proximity doesn't have a position defined, default it to
// the position of the view to which the game object belongs
if (prox._posX == 0.0 && prox._posY == 0.0 && prox._posZ == 0.0)
@@ -768,30 +783,34 @@ void CGameObject::stopSound(int handle, uint seconds) {
}
int CGameObject::addTimer(int endVal, uint firstDuration, uint repeatDuration) {
- CTimeEventInfo *timer = new CTimeEventInfo(g_vm->_events->getTicksCount(),
- repeatDuration != 0, firstDuration, repeatDuration, this, endVal, CString());
+ CTimeEventInfo *timer = new CTimeEventInfo(getTicksCount(), repeatDuration != 0,
+ firstDuration, repeatDuration, this, endVal, CString());
getGameManager()->addTimer(timer);
return timer->_id;
}
int CGameObject::addTimer(uint firstDuration, uint repeatDuration) {
- CTimeEventInfo *timer = new CTimeEventInfo(g_vm->_events->getTicksCount(),
- repeatDuration != 0, firstDuration, repeatDuration, this, 0, CString());
+ CTimeEventInfo *timer = new CTimeEventInfo(getTicksCount(), repeatDuration != 0,
+ firstDuration, repeatDuration, this, 0, CString());
getGameManager()->addTimer(timer);
return timer->_id;
}
+void CGameObject::stopTimer(int id) {
+ getGameManager()->stopTimer(id);
+}
+
int CGameObject::startAnimTimer(const CString &action, uint firstDuration, uint repeatDuration) {
- CTimeEventInfo *timer = new CTimeEventInfo(g_vm->_events->getTicksCount(),
- repeatDuration > 0, firstDuration, repeatDuration, this, 0, action);
+ CTimeEventInfo *timer = new CTimeEventInfo(getTicksCount(), repeatDuration > 0,
+ firstDuration, repeatDuration, this, 0, action);
getGameManager()->addTimer(timer);
return timer->_id;
}
-void CGameObject::stopTimer(int id) {
+void CGameObject::stopAnimTimer(int id) {
getGameManager()->stopTimer(id);
}
@@ -852,15 +871,15 @@ CViewItem *CGameObject::parseView(const CString &viewString) {
return nullptr;
// Find the designated node within the room
- CNodeItem *node = static_cast<CNodeItem *>(room->findChildInstanceOf(CNodeItem::_type));
+ CNodeItem *node = dynamic_cast<CNodeItem *>(room->findChildInstanceOf(CNodeItem::_type));
while (node && node->getName() != nodeName)
- node = static_cast<CNodeItem *>(room->findNextInstanceOf(CNodeItem::_type, node));
+ node = dynamic_cast<CNodeItem *>(room->findNextInstanceOf(CNodeItem::_type, node));
if (!node)
return nullptr;
- CViewItem *view = static_cast<CViewItem *>(node->findChildInstanceOf(CViewItem::_type));
+ CViewItem *view = dynamic_cast<CViewItem *>(node->findChildInstanceOf(CViewItem::_type));
while (view && view->getName() != viewName)
- view = static_cast<CViewItem *>(node->findNextInstanceOf(CViewItem::_type, view));
+ view = dynamic_cast<CViewItem *>(node->findNextInstanceOf(CViewItem::_type, view));
if (!view)
return nullptr;
@@ -879,7 +898,12 @@ CString CGameObject::getViewFullName() const {
}
void CGameObject::sleep(uint milli) {
- g_vm->_events->sleep(milli);
+ // Use an empty event target so that standard scene drawing won't happen
+ Events &events = *g_vm->_events;
+ CEventTarget nullTarget;
+ events.addTarget(&nullTarget);
+ events.sleep(milli);
+ events.removeTarget();
}
Point CGameObject::getMousePos() const {
@@ -945,12 +969,12 @@ CGameObject *CGameObject::getNextMail(CGameObject *prior) {
}
CGameObject *CGameObject::findRoomObject(const CString &name) const {
- return static_cast<CGameObject *>(findRoom()->findByName(name));
+ return dynamic_cast<CGameObject *>(findRoom()->findByName(name));
}
CGameObject *CGameObject::findInRoom(const CString &name) {
CRoomItem *room = getRoom();
- return room ? static_cast<CGameObject *>(room->findByName(name)) : nullptr;
+ return room ? dynamic_cast<CGameObject *>(room->findByName(name)) : nullptr;
}
Found CGameObject::find(const CString &name, CGameObject **item, int findAreas) {
@@ -977,7 +1001,7 @@ Found CGameObject::find(const CString &name, CGameObject **item, int findAreas)
}
if (findAreas & FIND_GLOBAL) {
- go = static_cast<CGameObject *>(getRoot()->findByName(name));
+ go = dynamic_cast<CGameObject *>(getRoot()->findByName(name));
if (go) {
*item = go;
return FOUND_GLOBAL;
@@ -998,21 +1022,21 @@ Found CGameObject::find(const CString &name, CGameObject **item, int findAreas)
void CGameObject::moveToView() {
CViewItem *view = getGameManager()->getView();
detach();
- view->addUnder(this);
+ addUnder(view);
}
void CGameObject::moveToView(const CString &name) {
CViewItem *view = parseView(name);
detach();
- view->addUnder(this);
+ addUnder(view);
}
-void CGameObject::stateInc14() {
- getGameManager()->_gameState.inc14();
+void CGameObject::stateChangeSeason() {
+ getGameManager()->_gameState.changeSeason();
}
-int CGameObject::stateGet14() const {
- return getGameManager()->_gameState._field14;
+Season CGameObject::stateGetSeason() const {
+ return getGameManager()->_gameState._seasonNum;
}
void CGameObject::stateSet24() {
@@ -1048,7 +1072,7 @@ void CGameObject::setMovieFrameRate(double rate) {
_surface->setMovieFrameRate(rate);
}
-void CGameObject::setTextBorder(const CString &str, int border, int borderRight) {
+void CGameObject::setText(const CString &str, int border, int borderRight) {
if (!_text)
_text = new CPetText();
_textBorder = border;
@@ -1142,8 +1166,8 @@ void CGameObject::mouseUnlockE4() {
CScreenManager::_screenManagerPtr->_mouseCursor->unlockE4();
}
-void CGameObject::mouseSaveState(int v1, int v2, int v3) {
- CScreenManager::_screenManagerPtr->_mouseCursor->saveState(v1, v2, v3);
+void CGameObject::mouseSetPosition(const Point &pt, double rate) {
+ CScreenManager::_screenManagerPtr->_mouseCursor->setPosition(pt, rate);
}
void CGameObject::lockInputHandler() {
@@ -1172,6 +1196,10 @@ void CGameObject::loadSurface() {
_surface->loadIfReady();
}
+bool CGameObject::changeView(const CString &viewName) {
+ return changeView(viewName, "");
+}
+
bool CGameObject::changeView(const CString &viewName, const CString &clipName) {
CViewItem *newView = parseView(viewName);
CGameManager *gameManager = getGameManager();
@@ -1202,9 +1230,9 @@ void CGameObject::dragMove(const Point &pt) {
setPosition(Point(pt.x - _bounds.width() / 2, pt.y - _bounds.height() / 2));
}
-bool CGameObject::isObjectDragging() const {
+CGameObject *CGameObject::getDraggingObject() const {
CTreeItem *item = getGameManager()->_dragItem;
- return item ? static_cast<CGameObject *>(item) != nullptr : false;
+ return dynamic_cast<CGameObject *>(item);
}
Point CGameObject::getControid() const {
@@ -1232,7 +1260,7 @@ CDontSaveFileItem *CGameObject::getDontSave() const {
}
CPetControl *CGameObject::getPetControl() const {
- return static_cast<CPetControl *>(getDontSaveChild(CPetControl::_type));
+ return dynamic_cast<CPetControl *>(getDontSaveChild(CPetControl::_type));
}
CMailMan *CGameObject::getMailMan() const {
@@ -1271,7 +1299,7 @@ CRoomItem *CGameObject::locateRoom(const CString &name) const {
CGameObject *CGameObject::getHiddenObject(const CString &name) const {
CRoomItem *room = getHiddenRoom();
- return room ? static_cast<CGameObject *>(findUnder(room, name)) : nullptr;
+ return room ? dynamic_cast<CGameObject *>(findUnder(room, name)) : nullptr;
}
CTreeItem *CGameObject::findUnder(CTreeItem *parent, const CString &name) const {
@@ -1363,10 +1391,14 @@ int CGameObject::getClipDuration(const CString &name, int frameRate) const {
return clip ? (clip->_endFrame - clip->_startFrame) * 1000 / frameRate : 0;
}
-uint32 CGameObject::getTickCount() {
+uint32 CGameObject::getTicksCount() {
return g_vm->_events->getTicksCount();
}
+Common::SeekableReadStream *CGameObject::getResource(const CString &name) {
+ return g_vm->_filesManager->getResource(name);
+}
+
bool CGameObject::compareRoomFlags(int mode, uint flags1, uint flags2) {
switch (mode) {
case 1:
@@ -1424,7 +1456,7 @@ void CGameObject::resetMail() {
mailMan->resetValue();
}
-int CGameObject::getNewRandomNumber(int max, int *oldVal) {
+int CGameObject::getRandomNumber(int max, int *oldVal) {
if (oldVal) {
int startingVal = *oldVal;
while (*oldVal == startingVal && max > 0)
@@ -1479,7 +1511,7 @@ CTreeItem *CGameObject::petContainerRemove(CGameObject *obj) {
if (!obj->compareRoomNameTo("CarryParcel"))
return obj;
- CGameObject *item = static_cast<CGameObject *>(pet->getLastChild());
+ CGameObject *item = dynamic_cast<CGameObject *>(pet->getLastChild());
if (item)
item->detach();
@@ -1574,11 +1606,11 @@ void CGameObject::petUnlockInput() {
/*------------------------------------------------------------------------*/
CStarControl *CGameObject::getStarControl() const {
- CStarControl *starControl = static_cast<CStarControl *>(getDontSaveChild(CStarControl::_type));
+ CStarControl *starControl = dynamic_cast<CStarControl *>(getDontSaveChild(CStarControl::_type));
if (!starControl) {
CViewItem *view = getGameManager()->getView();
if (view)
- starControl = static_cast<CStarControl *>(view->findChildInstanceOf(CStarControl::_type));
+ starControl = dynamic_cast<CStarControl *>(view->findChildInstanceOf(CStarControl::_type));
}
return starControl;
@@ -1590,16 +1622,15 @@ void CGameObject::starFn1(int v) {
starControl->fn1(v);
}
-void CGameObject::starFn2() {
+bool CGameObject::starFn2() {
CStarControl *starControl = getStarControl();
- if (starControl)
- starControl->fn4();
+ return starControl ? starControl->fn4() : false;
}
/*------------------------------------------------------------------------*/
void CGameObject::startTalking(const CString &npcName, uint id, CViewItem *view) {
- CTrueTalkNPC *npc = static_cast<CTrueTalkNPC *>(getRoot()->findByName(npcName));
+ CTrueTalkNPC *npc = dynamic_cast<CTrueTalkNPC *>(getRoot()->findByName(npcName));
startTalking(npc, id, view);
}
diff --git a/engines/titanic/core/game_object.h b/engines/titanic/core/game_object.h
index cad05dea00..d72fd94ac4 100644
--- a/engines/titanic/core/game_object.h
+++ b/engines/titanic/core/game_object.h
@@ -23,6 +23,8 @@
#ifndef TITANIC_GAME_OBJECT_H
#define TITANIC_GAME_OBJECT_H
+#include "audio/mixer.h"
+#include "common/stream.h"
#include "titanic/support/mouse_cursor.h"
#include "titanic/support/credit_text.h"
#include "titanic/support/movie_range_info.h"
@@ -32,6 +34,7 @@
#include "titanic/core/named_item.h"
#include "titanic/pet_control/pet_section.h"
#include "titanic/pet_control/pet_text.h"
+#include "titanic/game_state.h"
namespace Titanic {
@@ -52,7 +55,6 @@ class CGameObject : public CNamedItem {
friend class OSMovie;
DECLARE_MESSAGE_MAP;
private:
- static CCreditText *_credits;
static int _soundHandles[4];
private:
/**
@@ -61,16 +63,6 @@ private:
void loadResource(const CString &name);
/**
- * Loads a movie
- */
- void loadMovie(const CString &name, bool pendingFlag = true);
-
- /**
- * Loads an image
- */
- void loadImage(const CString &name, bool pendingFlag = true);
-
- /**
* Process and remove any registered movie range info
*/
void processMoveRangeInfo();
@@ -81,7 +73,8 @@ private:
*/
bool clipRect(const Rect &rect1, Rect &rect2) const;
protected:
- Rect _bounds;
+ static CCreditText *_credits;
+protected:
double _field34;
double _field38;
double _field3C;
@@ -128,6 +121,16 @@ protected:
*/
CViewItem * parseView(const CString &viewString);
+ /**
+ * Loads a movie
+ */
+ void loadMovie(const CString &name, bool pendingFlag = true);
+
+ /**
+ * Loads an image
+ */
+ void loadImage(const CString &name, bool pendingFlag = true);
+
void inc54();
void dec54();
@@ -164,7 +167,10 @@ protected:
void mouseLockE4();
void mouseUnlockE4();
- void mouseSaveState(int v1, int v2, int v3);
+ /**
+ * Sets the mouse to a new position
+ */
+ void mouseSetPosition(const Point &pt, double rate);
/**
* Lock the input handler
@@ -229,8 +235,10 @@ protected:
* @param initialMute If set, sound transitions in from mute over 2 seconds
* @param repeated Flag for repeating sounds
* @param handleIndex Slot 0 to 3 in the shared sound handle list to store the sound's handle
+ * @param soundType Specifies whether the sound is a sound effect or music
*/
- void playGlobalSound(const CString &resName, int mode, bool initialMute, bool repeated, int handleIndex);
+ void playGlobalSound(const CString &resName, int mode, bool initialMute, bool repeated,
+ int handleIndex, Audio::Mixer::SoundType soundType = Audio::Mixer::kMusicSoundType);
/**
* Stops a sound saved in the global sound handle list
@@ -260,14 +268,19 @@ protected:
int addTimer(uint firstDuration, uint repeatDuration = 0);
/**
+ * Stops a timer
+ */
+ void stopTimer(int id);
+
+ /**
* Start an animation timer
*/
int startAnimTimer(const CString &action, uint firstDuration, uint repeatDuration = 0);
/**
- * Stops a timer
+ * Stop an animation timer
*/
- void stopTimer(int id);
+ void stopAnimTimer(int id);
/**
* Causes the game to sleep for the specified time
@@ -336,14 +349,14 @@ protected:
bool changeView(const CString &viewName, const CString &clipName);
/**
- * Get the centre of the game object's bounds
+ * Change the view
*/
- Point getControid() const;
+ bool changeView(const CString &viewName);
/**
* Play an arbitrary clip
*/
- void playClip(const CString &name, uint flags);
+ void playClip(const CString &name, uint flags = 0);
/**
* Play a clip
@@ -351,9 +364,14 @@ protected:
void playClip(uint startFrame, uint endFrame);
/**
+ * Play a cutscene
+ */
+ void playCutscene(uint startFrame, uint endFrame);
+
+ /**
* Play a clip randomly from a passed list of names
*/
- void playRandomClip(const char **names, uint flags);
+ void playRandomClip(const char *const *names, uint flags = 0);
/**
* Return the current view/node/room as a single string
@@ -437,17 +455,12 @@ protected:
/**
* Returns the current system tick count
*/
- uint32 getTickCount();
+ uint32 getTicksCount();
/**
- * Adds an object to the mail list
+ * Gets a resource from the DAT file
*/
- void addMail(int mailId);
-
- /**
- * Sets the mail identifier for an object
- */
- void setMailId(int mailId);
+ Common::SeekableReadStream *getResource(const CString &name);
/**
* Returns true if a mail with a specified Id exists
@@ -460,11 +473,6 @@ protected:
CGameObject *findMail(int id) const;
/**
- * Remove an object from the mail list
- */
- void removeMail(int id, int v);
-
- /**
* Resets the Mail Man value
*/
void resetMail();
@@ -490,9 +498,9 @@ protected:
void setMovieFrameRate(double rate);
/**
- * Set up the text borders for the object
+ * Set up the text and borders for the object
*/
- void setTextBorder(const CString &str, int border = 0, int borderRight = 0);
+ void setText(const CString &str, int border = 0, int borderRight = 0);
/**
* Sets whether the text will use borders
@@ -537,8 +545,9 @@ protected:
/**
* Gets a new random number
*/
- int getNewRandomNumber(int max, int *oldVal = nullptr);
+ int getRandomNumber(int max, int *oldVal = nullptr);
public:
+ Rect _bounds;
bool _isMail;
int _id;
uint _roomFlags;
@@ -622,6 +631,11 @@ public:
void setPosition(const Point &newPos);
/**
+ * Get the centre of the game object's bounds
+ */
+ Point getControid() const;
+
+ /**
* Change the object's status
*/
void playMovie(uint flags);
@@ -692,6 +706,11 @@ public:
int getPriorClass() const;
/**
+ * Sets the mail identifier for an object
+ */
+ void setMailId(int mailId);
+
+ /**
* Returns true if there's an attached surface which has a frame
* ready for display
*/
@@ -718,9 +737,9 @@ public:
void dragMove(const Point &pt);
/**
- * Returns true if an item being dragged is a game object
+ * Returns the currently dragging item (if any) if it's a game object
*/
- bool isObjectDragging() const;
+ CGameObject *getDraggingObject() const;
bool compareRoomFlags(int mode, uint flags1, uint flags2);
@@ -752,6 +771,16 @@ public:
CString getRoomNodeName() const;
/**
+ * Adds an object to the mail list
+ */
+ void addMail(int mailId);
+
+ /**
+ * Remove an object from the mail list
+ */
+ void removeMail(int id, int v);
+
+ /**
* Return the full Id of the current view in a
* room.node.view tuplet form
*/
@@ -874,7 +903,7 @@ public:
CStarControl *getStarControl() const;
void starFn1(int v);
- void starFn2();
+ bool starFn2();
/*--- CTrueTalkManager Methods ---*/
@@ -919,8 +948,17 @@ public:
/*--- CGameState Methods ---*/
void setState1C(bool flag);
- void stateInc14();
- int stateGet14() const;
+
+ /**
+ * Change to the next season
+ */
+ void stateChangeSeason();
+
+ /**
+ * Returns the currently active season
+ */
+ Season stateGetSeason() const;
+
void stateSet24();
int stateGet24() const;
void stateInc38();
diff --git a/engines/titanic/core/game_object_desc_item.cpp b/engines/titanic/core/game_object_desc_item.cpp
index 409334c9d7..a6ee7e3cf5 100644
--- a/engines/titanic/core/game_object_desc_item.cpp
+++ b/engines/titanic/core/game_object_desc_item.cpp
@@ -30,7 +30,7 @@ CGameObjectDescItem::CGameObjectDescItem(): CTreeItem() {
void CGameObjectDescItem::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
_clipList.save(file, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeQuotedLine(_name, indent);
file->writeQuotedLine(_string2, indent);
_list1.save(file, indent);
_list2.save(file, indent);
@@ -45,7 +45,7 @@ void CGameObjectDescItem::load(SimpleFile *file) {
if (val)
_clipList.load(file);
- _string1 = file->readString();
+ _name = file->readString();
_string2 = file->readString();
_list1.load(file);
_list1.load(file);
diff --git a/engines/titanic/core/game_object_desc_item.h b/engines/titanic/core/game_object_desc_item.h
index 7bfecaf5a2..4f485e0d55 100644
--- a/engines/titanic/core/game_object_desc_item.h
+++ b/engines/titanic/core/game_object_desc_item.h
@@ -31,7 +31,7 @@ namespace Titanic {
class CGameObjectDescItem : public CTreeItem {
protected:
- CString _string1;
+ CString _name;
CString _string2;
List<ListItem> _list1;
List<ListItem> _list2;
@@ -49,6 +49,11 @@ public:
* Load the data for the class from file
*/
virtual void load(SimpleFile *file);
+
+ /**
+ * Gets the name of the item, if any
+ */
+ virtual const CString getName() const { return _name; }
};
} // End of namespace Titanic
diff --git a/engines/titanic/core/mail_man.cpp b/engines/titanic/core/mail_man.cpp
index afe13bebad..11e17fc4e5 100644
--- a/engines/titanic/core/mail_man.cpp
+++ b/engines/titanic/core/mail_man.cpp
@@ -37,14 +37,14 @@ void CMailMan::load(SimpleFile *file) {
}
CGameObject *CMailMan::getFirstObject() const {
- return static_cast<CGameObject *>(getFirstChild());
+ return dynamic_cast<CGameObject *>(getFirstChild());
}
CGameObject *CMailMan::getNextObject(CGameObject *prior) const {
if (!prior || prior->getParent() != this)
return nullptr;
- return static_cast<CGameObject *>(prior->getNextSibling());
+ return dynamic_cast<CGameObject *>(prior->getNextSibling());
}
void CMailMan::addMail(CGameObject *obj, int id) {
diff --git a/engines/titanic/core/multi_drop_target.cpp b/engines/titanic/core/multi_drop_target.cpp
index f2998199b1..b95696577f 100644
--- a/engines/titanic/core/multi_drop_target.cpp
+++ b/engines/titanic/core/multi_drop_target.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/core/multi_drop_target.h"
+#include "titanic/support/string_parser.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMultiDropTarget, CDropTarget)
+ ON_MESSAGE(DropObjectMsg)
+END_MESSAGE_MAP()
+
void CMultiDropTarget::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeQuotedLine(_string5, indent);
@@ -40,4 +45,20 @@ void CMultiDropTarget::load(SimpleFile *file) {
CDropTarget::load(file);
}
+bool CMultiDropTarget::DropObjectMsg(CDropObjectMsg *msg) {
+ CStringParser parser1(_string5);
+ CStringParser parser2(_string6);
+ CString seperatorChars = ",";
+
+ while (parser2.parse(_itemMatchName, seperatorChars)) {
+ _dropFrame = parser1.readInt();
+ CDropTarget::DropObjectMsg(msg);
+
+ parser1.skipSeperators(seperatorChars);
+ parser2.skipSeperators(seperatorChars);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/core/multi_drop_target.h b/engines/titanic/core/multi_drop_target.h
index c004b9bece..ab552f96e1 100644
--- a/engines/titanic/core/multi_drop_target.h
+++ b/engines/titanic/core/multi_drop_target.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CMultiDropTarget : public CDropTarget {
+ DECLARE_MESSAGE_MAP;
+ bool DropObjectMsg(CDropObjectMsg *msg);
public:
CString _string5;
CString _string6;
diff --git a/engines/titanic/core/named_item.cpp b/engines/titanic/core/named_item.cpp
index 6eafbf8c8b..9c4c28d04d 100644
--- a/engines/titanic/core/named_item.cpp
+++ b/engines/titanic/core/named_item.cpp
@@ -51,11 +51,11 @@ void CNamedItem::load(SimpleFile *file) {
CTreeItem::load(file);
}
-int CNamedItem::compareTo(const CString &name, int maxLen) const {
+bool CNamedItem::isEquals(const CString &name, int maxLen) const {
if (maxLen) {
- return getName().left(maxLen).compareToIgnoreCase(name);
+ return getName().left(maxLen).compareToIgnoreCase(name) == 0;
} else {
- return getName().compareToIgnoreCase(name);
+ return getName().compareToIgnoreCase(name) == 0;
}
}
diff --git a/engines/titanic/core/named_item.h b/engines/titanic/core/named_item.h
index 809cda1156..9ee3d490ae 100644
--- a/engines/titanic/core/named_item.h
+++ b/engines/titanic/core/named_item.h
@@ -59,9 +59,9 @@ public:
virtual const CString getName() const { return _name; }
/**
- * Compares the name of the item to a passed name
+ * Returns true if the item's name matches a passed name
*/
- virtual int compareTo(const CString &name, int maxLen) const;
+ virtual bool isEquals(const CString &name, int maxLen = 0) const;
/**
* Find a parent node for the item
diff --git a/engines/titanic/core/project_item.cpp b/engines/titanic/core/project_item.cpp
index 76293233b0..df48bad501 100644
--- a/engines/titanic/core/project_item.cpp
+++ b/engines/titanic/core/project_item.cpp
@@ -85,7 +85,7 @@ void CProjectItem::buildFilesList() {
CTreeItem *treeItem = getFirstChild();
while (treeItem) {
if (treeItem->isFileItem()) {
- CString name = static_cast<CFileItem *>(treeItem)->getFilename();
+ CString name = dynamic_cast<CFileItem *>(treeItem)->getFilename();
_files.add()->_name = name;
}
diff --git a/engines/titanic/core/room_item.cpp b/engines/titanic/core/room_item.cpp
index 541a8e1a9e..49b3232ba1 100644
--- a/engines/titanic/core/room_item.cpp
+++ b/engines/titanic/core/room_item.cpp
@@ -36,8 +36,8 @@ void CRoomItem::save(SimpleFile *file, int indent) {
_exitMovieKey.save(file, indent);
file->writeQuotedLine("Room dimensions x 1000", indent);
- file->writeNumberLine(_roomDimensionX * 1000.0, indent + 1);
- file->writeNumberLine(_roomDimensionY * 1000.0, indent + 1);
+ file->writeNumberLine((int)(_roomDimensionX * 1000.0), indent + 1);
+ file->writeNumberLine((int)(_roomDimensionY * 1000.0), indent + 1);
file->writeQuotedLine("Transition Movie", indent);
_transitionMovieKey.save(file, indent);
diff --git a/engines/titanic/core/saveable_object.cpp b/engines/titanic/core/saveable_object.cpp
index 62cee47045..db3249c107 100644
--- a/engines/titanic/core/saveable_object.cpp
+++ b/engines/titanic/core/saveable_object.cpp
@@ -92,7 +92,7 @@
#include "titanic/game/bar_menu.h"
#include "titanic/game/bar_menu_button.h"
#include "titanic/game/belbot_get_light.h"
-#include "titanic/game/bilge_succubus.h"
+#include "titanic/npcs/bilge_succubus.h"
#include "titanic/game/bomb.h"
#include "titanic/game/bottom_of_well_monitor.h"
#include "titanic/game/bowl_unlocker.h"
@@ -103,7 +103,6 @@
#include "titanic/game/broken_pellerator.h"
#include "titanic/game/broken_pellerator_froz.h"
#include "titanic/game/cage.h"
-#include "titanic/game/call_pellerator.h"
#include "titanic/game/captains_wheel.h"
#include "titanic/game/cdrom.h"
#include "titanic/game/cdrom_computer.h"
@@ -166,6 +165,8 @@
#include "titanic/game/music_room_stop_phonograph_button.h"
#include "titanic/game/music_system_lock.h"
#include "titanic/game/nav_helmet.h"
+#include "titanic/game/nav_helmet_on.h"
+#include "titanic/game/nav_helmet_off.h"
#include "titanic/game/navigation_computer.h"
#include "titanic/game/no_nut_bowl.h"
#include "titanic/game/nose_holder.h"
@@ -228,7 +229,7 @@
#include "titanic/game/parrot/parrot_nut_bowl_actor.h"
#include "titanic/game/parrot/parrot_nut_eater.h"
#include "titanic/game/parrot/parrot_perch_holder.h"
-#include "titanic/game/parrot/parrot_succubus.h"
+#include "titanic/npcs/parrot_succubus.h"
#include "titanic/game/parrot/parrot_trigger.h"
#include "titanic/game/parrot/player_meets_parrot.h"
#include "titanic/game/pet/pet.h"
@@ -251,7 +252,7 @@
#include "titanic/game/pickup/pick_up_vis_centre.h"
#include "titanic/game/placeholder/bar_shelf_vis_centre.h"
#include "titanic/game/placeholder/lemon_on_bar.h"
-#include "titanic/game/placeholder/place_holder_item.h"
+#include "titanic/game/placeholder/place_holder.h"
#include "titanic/game/placeholder/tv_on_bar.h"
#include "titanic/game/sgt/armchair.h"
#include "titanic/game/sgt/basin.h"
@@ -285,7 +286,6 @@
#include "titanic/gfx/chev_right_off.h"
#include "titanic/gfx/chev_right_on.h"
#include "titanic/gfx/chev_send_rec_switch.h"
-#include "titanic/gfx/chev_switch.h"
#include "titanic/gfx/edit_control.h"
#include "titanic/gfx/elevator_button.h"
#include "titanic/gfx/get_from_succ.h"
@@ -333,6 +333,7 @@
#include "titanic/messages/pet_messages.h"
#include "titanic/messages/service_elevator_door.h"
+#include "titanic/moves/call_pellerator.h"
#include "titanic/moves/enter_bomb_room.h"
#include "titanic/moves/enter_bridge.h"
#include "titanic/moves/enter_exit_first_class_state.h"
@@ -575,6 +576,8 @@ DEFFN(CMusicRoomPhonograph);
DEFFN(CMusicRoomStopPhonographButton);
DEFFN(CMusicSystemLock);
DEFFN(CNavHelmet);
+DEFFN(CNavHelmetOn);
+DEFFN(CNavHelmetOff);
DEFFN(CNavigationComputer);
DEFFN(CNoNutBowl);
DEFFN(CNoseHolder);
@@ -665,7 +668,7 @@ DEFFN(CPickUpSpeechCentre);
DEFFN(CPickUpVisCentre);
DEFFN(CBarShelfVisCentre);
DEFFN(CLemonOnBar);
-DEFFN(CPlaceHolderItem);
+DEFFN(CPlaceHolder);
DEFFN(CTVOnBar);
DEFFN(CArmchair);
DEFFN(CBasin);
@@ -701,7 +704,6 @@ DEFFN(CChevLeftOn);
DEFFN(CChevRightOff);
DEFFN(CChevRightOn);
DEFFN(CChevSendRecSwitch);
-DEFFN(CChevSwitch);
DEFFN(CEditControl);
DEFFN(CElevatorButton);
DEFFN(CGetFromSucc);
@@ -757,7 +759,7 @@ DEFFN(CAutoSoundEvent);
DEFFN(CBilgeAutoSoundEvent);
DEFFN(CBilgeDispensorEvent);
DEFFN(CBodyInBilgeRoomMsg);
-DEFFN(CBowlStateChange);
+DEFFN(CBowlStateChangeMsg);
DEFFN(CCarryObjectArrivedMsg);
DEFFN(CChangeMusicMsg);
DEFFN(CChangeSeasonMsg);
@@ -1162,6 +1164,8 @@ void CSaveableObject::initClassList() {
ADDFN(CMusicRoomStopPhonographButton, CEjectPhonographButton);
ADDFN(CMusicSystemLock, CDropTarget);
ADDFN(CNavHelmet, CGameObject);
+ ADDFN(CNavHelmetOn, CGameObject);
+ ADDFN(CNavHelmetOff, CGameObject);
ADDFN(CNavigationComputer, CGameObject);
ADDFN(CNoNutBowl, CBackground);
ADDFN(CNoseHolder, CDropTarget);
@@ -1245,10 +1249,10 @@ void CSaveableObject::initClassList() {
ADDFN(CPickUpLemon, CPickUp);
ADDFN(CPickUpSpeechCentre, CPickUp);
ADDFN(CPickUpVisCentre, CPickUp);
- ADDFN(CBarShelfVisCentre, CPlaceHolderItem);
- ADDFN(CLemonOnBar, CPlaceHolderItem);
- ADDFN(CPlaceHolderItem, CGameObject);
- ADDFN(CTVOnBar, CPlaceHolderItem);
+ ADDFN(CBarShelfVisCentre, CPlaceHolder);
+ ADDFN(CLemonOnBar, CPlaceHolder);
+ ADDFN(CPlaceHolder, CGameObject);
+ ADDFN(CTVOnBar, CPlaceHolder);
ADDFN(CArmchair, CSGTStateRoom);
ADDFN(CBasin, CSGTStateRoom);
ADDFN(CBedfoot, CSGTStateRoom);
@@ -1283,7 +1287,6 @@ void CSaveableObject::initClassList() {
ADDFN(CChevRightOff, CToggleSwitch);
ADDFN(CChevRightOn, CToggleSwitch);
ADDFN(CChevSendRecSwitch, CToggleSwitch);
- ADDFN(CChevSwitch, CToggleSwitch);
ADDFN(CEditControl, CGameObject);
ADDFN(CElevatorButton, CSTButton);
ADDFN(CGetFromSucc, CToggleSwitch);
@@ -1344,7 +1347,7 @@ void CSaveableObject::initClassList() {
ADDFN(CBilgeAutoSoundEvent, CAutoSoundEvent);
ADDFN(CBilgeDispensorEvent, CAutoSoundEvent);
ADDFN(CBodyInBilgeRoomMsg, CMessage);
- ADDFN(CBowlStateChange, CMessage);
+ ADDFN(CBowlStateChangeMsg, CMessage);
ADDFN(CCarryObjectArrivedMsg, CMessage);
ADDFN(CChangeMusicMsg, CMessage);
ADDFN(CChangeSeasonMsg, CMessage);
diff --git a/engines/titanic/core/tree_item.cpp b/engines/titanic/core/tree_item.cpp
index 6adbbe39fa..86c34cab8b 100644
--- a/engines/titanic/core/tree_item.cpp
+++ b/engines/titanic/core/tree_item.cpp
@@ -34,7 +34,7 @@
#include "titanic/core/room_item.h"
#include "titanic/pet_control/pet_control.h"
#include "titanic/game_manager.h"
-#include "titanic/game/placeholder/place_holder_item.h"
+#include "titanic/game/placeholder/place_holder.h"
namespace Titanic {
@@ -96,7 +96,7 @@ bool CTreeItem::isLinkItem() const {
}
bool CTreeItem::isPlaceHolderItem() const {
- return isInstanceOf(CPlaceHolderItem::_type);
+ return isInstanceOf(CPlaceHolder::_type);
}
bool CTreeItem::isNamedItem() const {
@@ -252,19 +252,32 @@ void CTreeItem::detach() {
_priorSibling = _nextSibling = _parent = nullptr;
}
-CNamedItem *CTreeItem::findByName(const CString &name, int maxLen) {
+void CTreeItem::attach(CTreeItem *item) {
+ _nextSibling = item;
+ _priorSibling = item->_priorSibling;
+ _parent = item->_parent;
+
+ if (item->_priorSibling)
+ item->_priorSibling->_nextSibling = this;
+
+ item->_priorSibling = this;
+ if (item->_parent && !item->_parent->_firstChild)
+ item->_parent->_firstChild = this;
+}
+
+CNamedItem *CTreeItem::findByName(const CString &name, bool subMatch) {
CString nameLower = name;
nameLower.toLowercase();
for (CTreeItem *treeItem = this; treeItem; treeItem = treeItem->scan(this)) {
- CString nodeName = treeItem->getName();
- nodeName.toLowercase();
+ CString itemName = treeItem->getName();
+ itemName.toLowercase();
- if (maxLen) {
- if (nodeName.left(maxLen).compareTo(nameLower))
+ if (subMatch) {
+ if (itemName.left(name.size()).compareTo(nameLower))
return dynamic_cast<CNamedItem *>(treeItem);
} else {
- if (!nodeName.compareTo(nameLower))
+ if (!itemName.compareTo(nameLower))
return dynamic_cast<CNamedItem *>(treeItem);
}
}
diff --git a/engines/titanic/core/tree_item.h b/engines/titanic/core/tree_item.h
index db4ba30a44..e92f5cda49 100644
--- a/engines/titanic/core/tree_item.h
+++ b/engines/titanic/core/tree_item.h
@@ -125,6 +125,11 @@ public:
virtual const CString getName() const { return CString(); }
/**
+ * Returns true if the item's name matches a passed name
+ */
+ virtual bool isEquals(const CString &name, int maxLen = 0) const { return false; }
+
+ /**
* Compares the name of the item to a passed name
*/
virtual int compareTo(const CString &name, int maxLen = 0) const { return false; }
@@ -242,9 +247,17 @@ public:
void detach();
/**
+ * Attaches a tree item to a new node
+ */
+ void attach(CTreeItem *item);
+
+ /**
* Finds a tree item by name
+ * @param name Name to find
+ * @param subMatch If false, does an exact name match.
+ * If false, matches any item that starts with the given name
*/
- CNamedItem *findByName(const CString &name, int maxLen = 0);
+ CNamedItem *findByName(const CString &name, bool subMatch = false);
};
} // End of namespace Titanic
diff --git a/engines/titanic/core/turn_on_play_sound.cpp b/engines/titanic/core/turn_on_play_sound.cpp
index 2f9dba24a6..ab50b33134 100644
--- a/engines/titanic/core/turn_on_play_sound.cpp
+++ b/engines/titanic/core/turn_on_play_sound.cpp
@@ -24,26 +24,37 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CTurnOnPlaySound, CTurnOnObject)
+ ON_MESSAGE(MouseButtonUpMsg)
+END_MESSAGE_MAP()
+
CTurnOnPlaySound::CTurnOnPlaySound() : CTurnOnObject(),
- _string3("NULL"), _fieldF8(80), _fieldFC(0) {
+ _soundName("NULL"), _soundVolume(80), _soundVal3(0) {
}
void CTurnOnPlaySound::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string3, indent);
- file->writeNumberLine(_fieldF8, indent);
- file->writeNumberLine(_fieldFC, indent);
+ file->writeQuotedLine(_soundName, indent);
+ file->writeNumberLine(_soundVolume, indent);
+ file->writeNumberLine(_soundVal3, indent);
CTurnOnObject::save(file, indent);
}
void CTurnOnPlaySound::load(SimpleFile *file) {
file->readNumber();
- _string3 = file->readString();
- _fieldF8 = file->readNumber();
- _fieldFC = file->readNumber();
+ _soundName = file->readString();
+ _soundVolume = file->readNumber();
+ _soundVal3 = file->readNumber();
CTurnOnObject::load(file);
}
+bool CTurnOnPlaySound::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ if (_soundName != "NULL")
+ playSound(_soundName, _soundVolume, _soundVal3);
+
+ return CTurnOnObject::MouseButtonUpMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/core/turn_on_play_sound.h b/engines/titanic/core/turn_on_play_sound.h
index 1164135071..29a25a5665 100644
--- a/engines/titanic/core/turn_on_play_sound.h
+++ b/engines/titanic/core/turn_on_play_sound.h
@@ -28,10 +28,12 @@
namespace Titanic {
class CTurnOnPlaySound : public CTurnOnObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
private:
- CString _string3;
- int _fieldF8;
- int _fieldFC;
+ CString _soundName;
+ int _soundVolume;
+ int _soundVal3;
public:
CLASSDEF;
CTurnOnPlaySound();
diff --git a/engines/titanic/core/turn_on_turn_off.cpp b/engines/titanic/core/turn_on_turn_off.cpp
index d43ddf7038..6498c226a0 100644
--- a/engines/titanic/core/turn_on_turn_off.cpp
+++ b/engines/titanic/core/turn_on_turn_off.cpp
@@ -24,16 +24,21 @@
namespace Titanic {
-CTurnOnTurnOff::CTurnOnTurnOff() : CBackground(), _fieldE0(0),
- _fieldE4(0), _fieldE8(0), _fieldEC(0), _fieldF0(0) {
+BEGIN_MESSAGE_MAP(CTurnOnTurnOff, CBackground)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+END_MESSAGE_MAP()
+
+CTurnOnTurnOff::CTurnOnTurnOff() : CBackground(), _startFrameOn(0),
+ _endFrameOn(0), _startFrameOff(0), _endFrameOff(0), _fieldF0(false) {
}
void CTurnOnTurnOff::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
- file->writeNumberLine(_fieldEC, indent);
+ file->writeNumberLine(_startFrameOn, indent);
+ file->writeNumberLine(_endFrameOn, indent);
+ file->writeNumberLine(_startFrameOff, indent);
+ file->writeNumberLine(_endFrameOff, indent);
file->writeNumberLine(_fieldF0, indent);
CBackground::save(file, indent);
@@ -41,13 +46,37 @@ void CTurnOnTurnOff::save(SimpleFile *file, int indent) {
void CTurnOnTurnOff::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
- _fieldEC = file->readNumber();
+ _startFrameOn = file->readNumber();
+ _endFrameOn = file->readNumber();
+ _startFrameOff = file->readNumber();
+ _endFrameOff = file->readNumber();
_fieldF0 = file->readNumber();
CBackground::load(file);
}
+bool CTurnOnTurnOff::TurnOn(CTurnOn *msg) {
+ if (!_fieldF0) {
+ if (_fieldDC)
+ playMovie(_startFrameOn, _endFrameOn, MOVIE_GAMESTATE);
+ else
+ playMovie(_startFrameOn, _endFrameOn, MOVIE_NOTIFY_OBJECT);
+ _fieldF0 = true;
+ }
+
+ return true;
+}
+
+bool CTurnOnTurnOff::TurnOff(CTurnOff *msg) {
+ if (!_fieldF0) {
+ if (_fieldDC)
+ playMovie(_startFrameOff, _endFrameOff, MOVIE_GAMESTATE);
+ else
+ playMovie(_startFrameOff, _endFrameOff, MOVIE_NOTIFY_OBJECT);
+ _fieldF0 = true;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/core/turn_on_turn_off.h b/engines/titanic/core/turn_on_turn_off.h
index adca6876ff..c09f0e0d7d 100644
--- a/engines/titanic/core/turn_on_turn_off.h
+++ b/engines/titanic/core/turn_on_turn_off.h
@@ -28,12 +28,15 @@
namespace Titanic {
class CTurnOnTurnOff : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
private:
- int _fieldE0;
- int _fieldE4;
- int _fieldE8;
- int _fieldEC;
- int _fieldF0;
+ int _startFrameOn;
+ int _endFrameOn;
+ int _startFrameOff;
+ int _endFrameOff;
+ bool _fieldF0;
public:
CLASSDEF;
CTurnOnTurnOff();
diff --git a/engines/titanic/core/view_item.cpp b/engines/titanic/core/view_item.cpp
index 03e2753839..af23fca027 100644
--- a/engines/titanic/core/view_item.cpp
+++ b/engines/titanic/core/view_item.cpp
@@ -168,8 +168,8 @@ void CViewItem::enterView(CViewItem *newView) {
CLinkItem *CViewItem::findLink(CViewItem *newView) {
for (CTreeItem *treeItem = getFirstChild(); treeItem;
- treeItem = scan(treeItem)) {
- CLinkItem *link = static_cast<CLinkItem *>(treeItem);
+ treeItem = treeItem->scan(this)) {
+ CLinkItem *link = dynamic_cast<CLinkItem *>(treeItem);
if (link && link->connectsTo(newView))
return link;
}
diff --git a/engines/titanic/debugger.cpp b/engines/titanic/debugger.cpp
index 37fc546851..a9da83f724 100644
--- a/engines/titanic/debugger.cpp
+++ b/engines/titanic/debugger.cpp
@@ -231,7 +231,7 @@ bool Debugger::cmdItem(int argc, const char **argv) {
}
// Get the item
- CCarry *item = static_cast<CCarry *>(
+ CCarry *item = dynamic_cast<CCarry *>(
g_vm->_window->_project->findByName(argv[1]));
assert(item);
diff --git a/engines/titanic/events.cpp b/engines/titanic/events.cpp
index 8a7cd550e8..318ddf5726 100644
--- a/engines/titanic/events.cpp
+++ b/engines/titanic/events.cpp
@@ -84,6 +84,11 @@ void Events::pollEvents() {
void Events::pollEventsAndWait() {
pollEvents();
g_system->delayMillis(10);
+
+ // Regularly update the sound mixer
+ CGameManager *gameManager = g_vm->_window->_gameManager;
+ if (gameManager)
+ gameManager->_sound.updateMixer();
}
bool Events::checkForNextFrameCounter() {
@@ -114,12 +119,9 @@ uint32 Events::getTicksCount() const {
void Events::sleep(uint time) {
uint32 delayEnd = g_system->getMillis() + time;
- CSound &sound = g_vm->_window->_gameManager->_sound;
- while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd) {
+ while (!_vm->shouldQuit() && g_system->getMillis() < delayEnd)
pollEventsAndWait();
- sound.updateMixer();
- }
}
bool Events::waitForPress(uint expiry) {
diff --git a/engines/titanic/game/announce.cpp b/engines/titanic/game/announce.cpp
index df6689d262..74c126476f 100644
--- a/engines/titanic/game/announce.cpp
+++ b/engines/titanic/game/announce.cpp
@@ -24,27 +24,109 @@
namespace Titanic {
-CAnnounce::CAnnounce() : _fieldBC(0), _fieldC0(0), _fieldC4(1), _fieldC8(0) {
+BEGIN_MESSAGE_MAP(CAnnounce, CGameObject)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
+CAnnounce::CAnnounce() : _nameIndex(0), _soundHandle(0), _leaveFlag(1), _enabled(false) {
}
void CAnnounce::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
- file->writeNumberLine(_fieldC8, indent);
+ file->writeNumberLine(_nameIndex, indent);
+ file->writeNumberLine(_soundHandle, indent);
+ file->writeNumberLine(_leaveFlag, indent);
+ file->writeNumberLine(_enabled, indent);
CGameObject::save(file, indent);
}
void CAnnounce::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
- _fieldC8 = file->readNumber();
+ _nameIndex = file->readNumber();
+ _soundHandle = file->readNumber();
+ _leaveFlag = file->readNumber();
+ _enabled = file->readNumber();
CGameObject::load(file);
}
+bool CAnnounce::TimerMsg(CTimerMsg *msg) {
+ if (!_enabled)
+ return false;
+
+ if (msg->_timerCtr == 1) {
+ CString numStr = "0";
+ CString waveNames1[20] = {
+ "z#181.wav", "z#211.wav", "z#203.wav", "z#202.wav", "z#201.wav",
+ "z#200.wav", "z#199.wav", "z#198.wav", "z#197.wav", "z#196.wav",
+ "z#210.wav", "z#209.wav", "z#208.wav", "z#207.wav", "z#206.wav",
+ "z#205.wav", "z#204.wav", "z#145.wav", "", ""
+ };
+ CString waveNames2[37] = {
+ "z#154.wav", "z#153.wav", "z#152.wav", "z#151.wav", "z#150.wav",
+ "z#149.wav", "z#148.wav", "z#169.wav", "z#171.wav", "z#178.wav",
+ "z#176.wav", "z#177.wav", "z#165.wav", "z#170.wav", "z#180.wav",
+ "z#156.wav", "z#172.wav", "z#173.wav", "z#160.wav", "z#158.wav",
+ "z#161.wav", "z#179.wav", "z#163.wav", "z#164.wav", "z#162.wav",
+ "z#159.wav", "z#175.wav", "z#166.wav", "z#174.wav", "z#157.wav",
+ "", "", "", "", "", "", ""
+ };
+
+ int randVal = _nameIndex ? getRandomNumber(2) : 0;
+ switch (randVal) {
+ case 0:
+ case 1:
+ _soundHandle = playSound("z#189.wav");
+ if (_nameIndex < 20) {
+ queueSound(waveNames1[_nameIndex], _soundHandle);
+ ++_nameIndex;
+ } else {
+ queueSound(waveNames1[1 + getRandomNumber(17)], _soundHandle);
+ }
+ break;
+
+ case 2:
+ _soundHandle = playSound("z#189.wav");
+ queueSound(waveNames2[1 + getRandomNumber(35)], _soundHandle);
+ break;
+
+ default:
+ break;
+ }
+
+ addTimer(1, 300000 + getRandomNumber(30000), 0);
+ if (getRandomNumber(3) == 0)
+ addTimer(2, 4000, 0);
+
+ } else if (msg->_timerCtr == 2) {
+ CParrotSpeakMsg speakMsg;
+ speakMsg._target = "Announcements";
+ speakMsg.execute("PerchedParrot");
+ }
+
+ return true;
+}
+
+bool CAnnounce::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ if (_leaveFlag) {
+ addTimer(1, 1000, 0);
+ _leaveFlag = 0;
+ _enabled = true;
+ }
+
+ return true;
+}
+
+bool CAnnounce::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Enable")
+ _enabled = true;
+ else if (msg->_action == "Disable")
+ _enabled = false;
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/announce.h b/engines/titanic/game/announce.h
index f960241c36..9bf060daae 100644
--- a/engines/titanic/game/announce.h
+++ b/engines/titanic/game/announce.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CAnnounce : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool TimerMsg(CTimerMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool ActMsg(CActMsg *msg);
private:
- int _fieldBC;
- int _fieldC0;
- int _fieldC4;
- int _fieldC8;
+ int _nameIndex;
+ int _soundHandle;
+ bool _leaveFlag;
+ bool _enabled;
public:
CLASSDEF;
CAnnounce();
diff --git a/engines/titanic/game/annoy_barbot.cpp b/engines/titanic/game/annoy_barbot.cpp
index d69d9fff3c..8b22f9c13a 100644
--- a/engines/titanic/game/annoy_barbot.cpp
+++ b/engines/titanic/game/annoy_barbot.cpp
@@ -26,6 +26,10 @@ namespace Titanic {
int CAnnoyBarbot::_v1;
+BEGIN_MESSAGE_MAP(CAnnoyBarbot, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CAnnoyBarbot::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_v1, indent);
@@ -38,4 +42,13 @@ void CAnnoyBarbot::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CAnnoyBarbot::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if ((++_v1 % 3) == 1) {
+ CActMsg actMsg("GoRingBell");
+ actMsg.execute("Barbot");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/annoy_barbot.h b/engines/titanic/game/annoy_barbot.h
index 955a82bdf8..0ccfe43794 100644
--- a/engines/titanic/game/annoy_barbot.h
+++ b/engines/titanic/game/annoy_barbot.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CAnnoyBarbot : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
private:
static int _v1;
public:
diff --git a/engines/titanic/game/arboretum_gate.cpp b/engines/titanic/game/arboretum_gate.cpp
index 9caa87c48d..1435e3e204 100644
--- a/engines/titanic/game/arboretum_gate.cpp
+++ b/engines/titanic/game/arboretum_gate.cpp
@@ -25,135 +25,306 @@
namespace Titanic {
BEGIN_MESSAGE_MAP(CArboretumGate, CBackground)
+ ON_MESSAGE(ChangeSeasonMsg)
ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
ON_MESSAGE(LeaveViewMsg)
ON_MESSAGE(TurnOff)
ON_MESSAGE(MouseButtonDownMsg)
ON_MESSAGE(EnterViewMsg)
ON_MESSAGE(TurnOn)
- ON_MESSAGE(MovieEndMsg)
END_MESSAGE_MAP()
int CArboretumGate::_v1;
-int CArboretumGate::_v2;
+int CArboretumGate::_initialFrame;
int CArboretumGate::_v3;
CArboretumGate::CArboretumGate() : CBackground() {
- _string1 = "NULL";
- _string2 = "NULL";
- _fieldE0 = 0;
+ _viewName1 = "NULL";
+ _viewName2 = "NULL";
+ _seasonNum = 0;
_fieldF0 = 0;
- _fieldF4 = 244;
- _fieldF8 = 304;
- _fieldFC = 122;
- _field100 = 182;
- _field104 = 183;
- _field108 = 243;
- _field10C = 665;
- _field110 = 724;
- _field114 = 61;
- _field118 = 121;
- _field11C = 0;
- _field120 = 60;
- _field124 = 485;
- _field128 = 544;
- _field12C = 425;
- _field130 = 484;
- _field134 = 545;
- _field138 = 604;
- _field13C = 605;
- _field140 = 664;
- _field144 = 305;
- _field148 = 364;
- _field14C = 365;
- _field150 = 424;
+ _startFrameSpringOff = 244;
+ _endFrameSpringOff = 304;
+ _startFrameSummerOff = 122;
+ _endFrameSummerOff = 182;
+ _startFrameAutumnOff1 = 183;
+ _endFrameAutumnOff1 = 243;
+ _startFrameAutumnOff2 = 665;
+ _endFrameAutumnOff2 = 724;
+ _startFrameWinterOff1 = 61;
+ _endFrameWinterOff1 = 121;
+ _startFrameWinterOff2 = 0;
+ _endFrameWinterOff2 = 60;
+ _startFrameSpringOn = 485;
+ _endFrameSpringOn = 544;
+ _startFrameSummerOn = 425;
+ _endFrameSummerOn = 484;
+ _startFrameAutumnOn1 = 545;
+ _endFrameAutumnOn1 = 604;
+ _startFrameAutumnOn2 = 605;
+ _endFrameAutumnOn2 = 664;
+ _startFrameWinterOn1 = 305;
+ _endFrameWinterOn1 = 364;
+ _startFrameWinterOn2 = 365;
+ _endFrameWinterOn2 = 424;
}
void CArboretumGate::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
+ file->writeNumberLine(_seasonNum, indent);
file->writeNumberLine(_v1, indent);
- file->writeNumberLine(_v2, indent);
+ file->writeNumberLine(_initialFrame, indent);
file->writeNumberLine(_v3, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeQuotedLine(_viewName1, indent);
file->writeNumberLine(_fieldF0, indent);
- file->writeNumberLine(_fieldF4, indent);
- file->writeNumberLine(_fieldF8, indent);
- file->writeNumberLine(_fieldFC, indent);
- file->writeNumberLine(_field100, indent);
- file->writeNumberLine(_field104, indent);
- file->writeNumberLine(_field108, indent);
- file->writeNumberLine(_field10C, indent);
- file->writeNumberLine(_field110, indent);
- file->writeNumberLine(_field114, indent);
- file->writeNumberLine(_field118, indent);
- file->writeNumberLine(_field11C, indent);
- file->writeNumberLine(_field120, indent);
- file->writeNumberLine(_field124, indent);
- file->writeNumberLine(_field128, indent);
- file->writeNumberLine(_field12C, indent);
- file->writeNumberLine(_field130, indent);
- file->writeNumberLine(_field134, indent);
- file->writeNumberLine(_field138, indent);
- file->writeNumberLine(_field13C, indent);
- file->writeNumberLine(_field140, indent);
- file->writeNumberLine(_field144, indent);
- file->writeNumberLine(_field148, indent);
- file->writeNumberLine(_field14C, indent);
- file->writeNumberLine(_field150, indent);
- file->writeQuotedLine(_string2, indent);
+ file->writeNumberLine(_startFrameSpringOff, indent);
+ file->writeNumberLine(_endFrameSpringOff, indent);
+ file->writeNumberLine(_startFrameSummerOff, indent);
+ file->writeNumberLine(_endFrameSummerOff, indent);
+ file->writeNumberLine(_startFrameAutumnOff1, indent);
+ file->writeNumberLine(_endFrameAutumnOff1, indent);
+ file->writeNumberLine(_startFrameAutumnOff2, indent);
+ file->writeNumberLine(_endFrameAutumnOff2, indent);
+ file->writeNumberLine(_startFrameWinterOff1, indent);
+ file->writeNumberLine(_endFrameWinterOff1, indent);
+ file->writeNumberLine(_startFrameWinterOff2, indent);
+ file->writeNumberLine(_endFrameWinterOff2, indent);
+ file->writeNumberLine(_startFrameSpringOn, indent);
+ file->writeNumberLine(_endFrameSpringOn, indent);
+ file->writeNumberLine(_startFrameSummerOn, indent);
+ file->writeNumberLine(_endFrameSummerOn, indent);
+ file->writeNumberLine(_startFrameAutumnOn1, indent);
+ file->writeNumberLine(_endFrameAutumnOn1, indent);
+ file->writeNumberLine(_startFrameAutumnOn2, indent);
+ file->writeNumberLine(_endFrameAutumnOn2, indent);
+ file->writeNumberLine(_startFrameWinterOn1, indent);
+ file->writeNumberLine(_endFrameWinterOn1, indent);
+ file->writeNumberLine(_startFrameWinterOn2, indent);
+ file->writeNumberLine(_endFrameWinterOn2, indent);
+ file->writeQuotedLine(_viewName2, indent);
CBackground::save(file, indent);
}
void CArboretumGate::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
+ _seasonNum = file->readNumber();
_v1 = file->readNumber();
- _v2 = file->readNumber();
+ _initialFrame = file->readNumber();
_v3 = file->readNumber();
- _string1 = file->readString();
+ _viewName1 = file->readString();
_fieldF0 = file->readNumber();
- _fieldF4 = file->readNumber();
- _fieldF8 = file->readNumber();
- _fieldFC = file->readNumber();
- _field100 = file->readNumber();
- _field104 = file->readNumber();
- _field108 = file->readNumber();
- _field10C = file->readNumber();
- _field110 = file->readNumber();
- _field114 = file->readNumber();
- _field118 = file->readNumber();
- _field11C = file->readNumber();
- _field120 = file->readNumber();
- _field124 = file->readNumber();
- _field128 = file->readNumber();
- _field12C = file->readNumber();
- _field130 = file->readNumber();
- _field134 = file->readNumber();
- _field138 = file->readNumber();
- _field13C = file->readNumber();
- _field140 = file->readNumber();
- _field144 = file->readNumber();
- _field148 = file->readNumber();
- _field14C = file->readNumber();
- _field150 = file->readNumber();
- _string2 = file->readString();
+ _startFrameSpringOff = file->readNumber();
+ _endFrameSpringOff = file->readNumber();
+ _startFrameSummerOff = file->readNumber();
+ _endFrameSummerOff = file->readNumber();
+ _startFrameAutumnOff1 = file->readNumber();
+ _endFrameAutumnOff1 = file->readNumber();
+ _startFrameAutumnOff2 = file->readNumber();
+ _endFrameAutumnOff2 = file->readNumber();
+ _startFrameWinterOff1 = file->readNumber();
+ _endFrameWinterOff1 = file->readNumber();
+ _startFrameWinterOff2 = file->readNumber();
+ _endFrameWinterOff2 = file->readNumber();
+ _startFrameSpringOn = file->readNumber();
+ _endFrameSpringOn = file->readNumber();
+ _startFrameSummerOn = file->readNumber();
+ _endFrameSummerOn = file->readNumber();
+ _startFrameAutumnOn1 = file->readNumber();
+ _endFrameAutumnOn1 = file->readNumber();
+ _startFrameAutumnOn2 = file->readNumber();
+ _endFrameAutumnOn2 = file->readNumber();
+ _startFrameWinterOn1 = file->readNumber();
+ _endFrameWinterOn1 = file->readNumber();
+ _startFrameWinterOn2 = file->readNumber();
+ _endFrameWinterOn2 = file->readNumber();
+ _viewName2 = file->readString();
CBackground::load(file);
}
-bool CArboretumGate::ActMsg(CActMsg *msg) { return false; }
-bool CArboretumGate::LeaveViewMsg(CLeaveViewMsg *msg) { return false; }
-bool CArboretumGate::TurnOff(CTurnOff *msg) { return false; }
-bool CArboretumGate::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { return false; }
+bool CArboretumGate::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ _seasonNum = (_seasonNum + 1) % 4;
+ return true;
+}
-bool CArboretumGate::EnterViewMsg(CEnterViewMsg *msg) {
- warning("CArboretumGate::handleEvent");
+bool CArboretumGate::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PlayerGetsSpeechCentre") {
+ _v1 = 1;
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute("SpCtrOverlay");
+ } else if (msg->_action == "ExitLFrozen") {
+ if (_v3) {
+ _viewName2 = "FrozenArboretum.Node 2.W";
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ } else {
+ changeView("FrozenArboretum.Node 2.W");
+ }
+ } else if (msg->_action == "ExitRFrozen") {
+ if (_v3) {
+ _viewName2 = "FrozenArboretum.Node 2.E";
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ } else {
+ changeView("FrozenArboretum.Node 2.E");
+ }
+ } else if (msg->_action == "ExitLNormal") {
+ if (_v3) {
+ _viewName2 = "Arboretum.Node 2.W";
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ } else {
+ changeView("Arboretum.Node 2.W");
+ }
+ } else if (msg->_action == "ExitRNormal") {
+ if (_v3) {
+ _viewName2 = "Arboretum.Node 2.E";
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ }
+ else {
+ changeView("Arboretum.Node 2.E");
+ }
+ }
+
+ return true;
+}
+
+bool CArboretumGate::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(!_v3);
+
+ if (_viewName1 != "NULL") {
+ changeView(_viewName1);
+ } else if (_viewName2 != "NULL") {
+ changeView(_viewName2);
+ _viewName2 = "NULL";
+ }
+
+ return true;
+}
+
+bool CArboretumGate::LeaveViewMsg(CLeaveViewMsg *msg) {
return false;
}
-bool CArboretumGate::TurnOn(CTurnOn *msg) { return false; }
-bool CArboretumGate::MovieEndMsg(CMovieEndMsg *msg) { return false; }
+bool CArboretumGate::TurnOff(CTurnOff *msg) {
+ if (!_v3) {
+ switch (_seasonNum) {
+ case SEASON_SUMMER:
+ playMovie(_startFrameSummerOff, _endFrameSummerOff, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ break;
+
+ case SEASON_AUTUMN:
+ if (_v1) {
+ playMovie(_startFrameAutumnOff2, _endFrameAutumnOff2, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ } else {
+ playMovie(_startFrameAutumnOff1, _endFrameAutumnOff1, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ }
+ break;
+
+ case SEASON_WINTER:
+ if (_v1) {
+ playMovie(_startFrameWinterOff2, _endFrameWinterOff2, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ } else {
+ playMovie(_startFrameWinterOff1, _endFrameWinterOff1, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ }
+ break;
+
+ case SEASON_SPRING:
+ playMovie(_startFrameSpringOff, _endFrameSpringOff, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ break;
+
+ default:
+ break;
+ }
+
+ _v3 = 1;
+ CArboretumGateMsg gateMsg;
+ gateMsg.execute("Arboretum", nullptr, MSGFLAG_SCAN);
+ }
+
+ return true;
+}
+
+bool CArboretumGate::TurnOn(CTurnOn *msg) {
+ if (_v3) {
+ CArboretumGateMsg gateMsg(0);
+ gateMsg.execute("Arboretum");
+ setVisible(true);
+
+ switch (_seasonNum) {
+ case SEASON_SUMMER:
+ playMovie(_startFrameSummerOn, _endFrameSummerOn, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ break;
+
+ case SEASON_AUTUMN:
+ if (_v1) {
+ playMovie(_startFrameAutumnOn2, _endFrameAutumnOn2, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ } else {
+ playMovie(_startFrameAutumnOn1, _endFrameAutumnOn1, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ }
+ break;
+
+ case SEASON_WINTER:
+ if (_v1) {
+ playMovie(_startFrameWinterOn2, _endFrameWinterOn2, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ } else {
+ playMovie(_startFrameWinterOn1, _endFrameWinterOn1, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ }
+ break;
+
+ case SEASON_SPRING:
+ playMovie(_startFrameSpringOn, _endFrameSpringOn, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ break;
+
+ default:
+ break;
+ }
+
+ _v3 = 0;
+ }
+
+ return true;
+}
+
+bool CArboretumGate::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_v3) {
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CArboretumGate::EnterViewMsg(CEnterViewMsg *msg) {
+ if (!_v3) {
+ switch (_seasonNum) {
+ case SEASON_SUMMER:
+ _initialFrame = _startFrameSummerOff;
+ break;
+
+ case SEASON_AUTUMN:
+ _initialFrame = _v1 ? _startFrameAutumnOff2 : _startFrameAutumnOff1;
+ break;
+
+ case SEASON_WINTER:
+ _initialFrame = _v1 ? _startFrameWinterOff1 : _startFrameWinterOff2;
+ break;
+
+ case SEASON_SPRING:
+ _initialFrame = _startFrameSpringOff;
+ break;
+
+ default:
+ break;
+ }
+
+ loadFrame(_initialFrame);
+ }
+
+ return true;
+}
} // End of namespace Titanic
diff --git a/engines/titanic/game/arboretum_gate.h b/engines/titanic/game/arboretum_gate.h
index 927b2190c7..b1c06cf773 100644
--- a/engines/titanic/game/arboretum_gate.h
+++ b/engines/titanic/game/arboretum_gate.h
@@ -31,48 +31,47 @@ namespace Titanic {
class CArboretumGate : public CBackground {
DECLARE_MESSAGE_MAP;
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
bool LeaveViewMsg(CLeaveViewMsg *msg);
bool TurnOff(CTurnOff *msg);
bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
bool EnterViewMsg(CEnterViewMsg *msg);
bool TurnOn(CTurnOn *msg);
- bool MovieEndMsg(CMovieEndMsg *msg);
private:
static int _v1;
- static int _v2;
+ static int _initialFrame;
static int _v3;
private:
- int _fieldE0;
- CString _string1;
- int _fieldE8;
- int _fieldEC;
+ int _seasonNum;
+ CString _viewName1;
int _fieldF0;
- int _fieldF4;
- int _fieldF8;
- int _fieldFC;
- int _field100;
- int _field104;
- int _field108;
- int _field10C;
- int _field110;
- int _field114;
- int _field118;
- int _field11C;
- int _field120;
- int _field124;
- int _field128;
- int _field12C;
- int _field130;
- int _field134;
- int _field138;
- int _field13C;
- int _field140;
- int _field144;
- int _field148;
- int _field14C;
- int _field150;
- CString _string2;
+ int _startFrameSpringOff;
+ int _endFrameSpringOff;
+ int _startFrameSummerOff;
+ int _endFrameSummerOff;
+ int _startFrameAutumnOff2;
+ int _endFrameAutumnOff2;
+ int _startFrameAutumnOff1;
+ int _endFrameAutumnOff1;
+ int _startFrameWinterOff2;
+ int _endFrameWinterOff2;
+ int _startFrameWinterOff1;
+ int _endFrameWinterOff1;
+ int _startFrameSpringOn;
+ int _endFrameSpringOn;
+ int _startFrameSummerOn;
+ int _endFrameSummerOn;
+ int _startFrameAutumnOn1;
+ int _endFrameAutumnOn1;
+ int _startFrameAutumnOn2;
+ int _endFrameAutumnOn2;
+ int _startFrameWinterOn1;
+ int _endFrameWinterOn1;
+ int _startFrameWinterOn2;
+ int _endFrameWinterOn2;
+ CString _viewName2;
public:
CLASSDEF;
CArboretumGate();
diff --git a/engines/titanic/game/auto_animate.cpp b/engines/titanic/game/auto_animate.cpp
index 172b8c44df..16e6e56747 100644
--- a/engines/titanic/game/auto_animate.cpp
+++ b/engines/titanic/game/auto_animate.cpp
@@ -24,24 +24,44 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CAutoAnimate, CBackground)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(InitializeAnimMsg)
+END_MESSAGE_MAP()
+
void CAutoAnimate::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
+ file->writeNumberLine(_enabled, indent);
file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
+ file->writeNumberLine(_repeat, indent);
CBackground::save(file, indent);
}
void CAutoAnimate::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
+ _enabled = file->readNumber();
_fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
+ _repeat = file->readNumber();
CBackground::load(file);
}
bool CAutoAnimate::EnterViewMsg(CEnterViewMsg *msg) {
- warning("CAutoAnimate::handleEvent");
+ if (_enabled) {
+ uint flags = _repeat ? MOVIE_REPEAT : 0;
+ if (_startFrame != _endFrame)
+ playMovie(_startFrame, _endFrame, flags);
+ else
+ playMovie(flags);
+
+ if (!_fieldE4)
+ _enabled = false;
+ }
+
+ return true;
+}
+
+bool CAutoAnimate::InitializeAnimMsg(CInitializeAnimMsg *msg) {
+ _enabled = true;
return true;
}
diff --git a/engines/titanic/game/auto_animate.h b/engines/titanic/game/auto_animate.h
index 7bca808bfb..735aba922e 100644
--- a/engines/titanic/game/auto_animate.h
+++ b/engines/titanic/game/auto_animate.h
@@ -29,14 +29,16 @@
namespace Titanic {
class CAutoAnimate : public CBackground {
+ DECLARE_MESSAGE_MAP;
bool EnterViewMsg(CEnterViewMsg *msg);
+ bool InitializeAnimMsg(CInitializeAnimMsg *msg);
private:
- int _fieldE0;
+ bool _enabled;
int _fieldE4;
- int _fieldE8;
+ bool _repeat;
public:
CLASSDEF;
- CAutoAnimate() : CBackground(), _fieldE0(1), _fieldE4(1), _fieldE8(0) {}
+ CAutoAnimate() : CBackground(), _enabled(true), _fieldE4(1), _repeat(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/bar_bell.cpp b/engines/titanic/game/bar_bell.cpp
index b33ee1c26c..5f17dffda1 100644
--- a/engines/titanic/game/bar_bell.cpp
+++ b/engines/titanic/game/bar_bell.cpp
@@ -24,15 +24,22 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBarBell, CGameObject)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
CBarBell::CBarBell() : CGameObject(), _fieldBC(0),
- _fieldC0(65), _fieldC4(0), _fieldC8(0), _fieldCC(0) {
+ _volume(65), _soundVal3(0), _fieldC8(0), _fieldCC(0) {
}
void CBarBell::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
+ file->writeNumberLine(_volume, indent);
+ file->writeNumberLine(_soundVal3, indent);
file->writeNumberLine(_fieldC8, indent);
file->writeNumberLine(_fieldCC, indent);
@@ -42,8 +49,8 @@ void CBarBell::save(SimpleFile *file, int indent) {
void CBarBell::load(SimpleFile *file) {
file->readNumber();
_fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
+ _volume = file->readNumber();
+ _soundVal3 = file->readNumber();
_fieldC8 = file->readNumber();
_fieldCC = file->readNumber();
@@ -55,4 +62,70 @@ bool CBarBell::EnterRoomMsg(CEnterRoomMsg *msg) {
return true;
}
+bool CBarBell::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if ((_fieldC8 % 3) == 2) {
+ switch (_fieldBC) {
+ case 0:
+ case 1:
+ case 5:
+ playSound("c#54.wav", _volume, _soundVal3);
+ break;
+
+ case 2:
+ playSound("c#52.wav", _volume, _soundVal3);
+ break;
+
+ case 3:
+ playSound("c#53.wav", _volume, _soundVal3);
+ break;
+
+ case 4:
+ playSound("c#55.wav", _volume, _soundVal3);
+ break;
+
+ default:
+ playSound("c#51.wav", _volume, _soundVal3);
+ break;
+ }
+ } else if (_fieldBC >= 5) {
+ if (_fieldBC == 6) {
+ CActMsg actMsg("BellRing3");
+ actMsg.execute("Barbot");
+ }
+
+ playSound("c#51.wav", _volume, _soundVal3);
+ } else {
+ if (_fieldBC == 3) {
+ CActMsg actMsg("BellRing1");
+ actMsg.execute("Barbot");
+ } else if (_fieldBC == 4) {
+ CActMsg actMsg("BellRing2");
+ actMsg.execute("Barbot");
+ }
+
+ playSound("c#54.wav", _volume, _soundVal3);
+ }
+
+ return true;
+}
+
+bool CBarBell::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ if (!_fieldBC) {
+ CTurnOn onMsg;
+ onMsg.execute("Barbot");
+ }
+
+ ++_fieldBC;
+ return true;
+}
+
+bool CBarBell::ActMsg(CActMsg *msg) {
+ if (msg->_action == "ResetCount") {
+ _fieldBC = 0;
+ ++_fieldC8;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/bar_bell.h b/engines/titanic/game/bar_bell.h
index 5d1d2c54e0..b50fe505ba 100644
--- a/engines/titanic/game/bar_bell.h
+++ b/engines/titanic/game/bar_bell.h
@@ -29,11 +29,15 @@
namespace Titanic {
class CBarBell : public CGameObject {
+ DECLARE_MESSAGE_MAP;
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool ActMsg(CActMsg *msg);
public:
int _fieldBC;
- int _fieldC0;
- int _fieldC4;
+ int _volume;
+ int _soundVal3;
int _fieldC8;
int _fieldCC;
public:
diff --git a/engines/titanic/game/bar_menu.cpp b/engines/titanic/game/bar_menu.cpp
index b24c429c9b..3812a8dab6 100644
--- a/engines/titanic/game/bar_menu.cpp
+++ b/engines/titanic/game/bar_menu.cpp
@@ -24,25 +24,81 @@
namespace Titanic {
-CBarMenu::CBarMenu() : CGameObject(), _fieldBC(0), _fieldC0(0), _fieldC4(6) {
+BEGIN_MESSAGE_MAP(CBarMenu, CGameObject)
+ ON_MESSAGE(PETActivateMsg)
+ ON_MESSAGE(PETDownMsg)
+ ON_MESSAGE(PETUpMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
+CBarMenu::CBarMenu() : CGameObject(), _barFrameNumber(0), _visibleFlag(false), _numFrames(6) {
}
void CBarMenu::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
+ file->writeNumberLine(_barFrameNumber, indent);
+ file->writeNumberLine(_visibleFlag, indent);
+ file->writeNumberLine(_numFrames, indent);
CGameObject::save(file, indent);
}
void CBarMenu::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
+ _barFrameNumber = file->readNumber();
+ _visibleFlag = file->readNumber();
+ _numFrames = file->readNumber();
CGameObject::load(file);
}
+bool CBarMenu::PETActivateMsg(CPETActivateMsg *msg) {
+ if (msg->_name == "Television") {
+ _visibleFlag = !_visibleFlag;
+ setVisible(_visibleFlag);
+ loadFrame(_barFrameNumber);
+ }
+
+ return true;
+}
+
+bool CBarMenu::PETDownMsg(CPETDownMsg *msg) {
+ if (_visibleFlag) {
+ if (--_barFrameNumber < 0)
+ _barFrameNumber = _numFrames - 1;
+
+ loadFrame(_barFrameNumber);
+ }
+
+ return true;
+}
+
+bool CBarMenu::PETUpMsg(CPETUpMsg *msg) {
+ if (_visibleFlag) {
+ _barFrameNumber = (_barFrameNumber + 1) % _numFrames;
+ loadFrame(_barFrameNumber);
+ }
+
+ return true;
+}
+
+bool CBarMenu::EnterViewMsg(CEnterViewMsg *msg) {
+ petSetArea(PET_REMOTE);
+ petHighlightGlyph(2);
+ petSetRemoteTarget();
+ setVisible(_visibleFlag);
+ loadFrame(_barFrameNumber);
+
+ return true;
+}
+
+bool CBarMenu::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petClear();
+ _visibleFlag = false;
+ setVisible(false);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/bar_menu.h b/engines/titanic/game/bar_menu.h
index 84c0219084..f16f7b035d 100644
--- a/engines/titanic/game/bar_menu.h
+++ b/engines/titanic/game/bar_menu.h
@@ -24,14 +24,21 @@
#define TITANIC_BAR_MENU_H
#include "titanic/core/game_object.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CBarMenu : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool PETActivateMsg(CPETActivateMsg *msg);
+ bool PETDownMsg(CPETDownMsg *msg);
+ bool PETUpMsg(CPETUpMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
- int _fieldBC;
- int _fieldC0;
- int _fieldC4;
+ int _barFrameNumber;
+ bool _visibleFlag;
+ int _numFrames;
public:
CLASSDEF;
CBarMenu();
diff --git a/engines/titanic/game/bar_menu_button.cpp b/engines/titanic/game/bar_menu_button.cpp
index f57d72c64a..874584db30 100644
--- a/engines/titanic/game/bar_menu_button.cpp
+++ b/engines/titanic/game/bar_menu_button.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/game/bar_menu_button.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBarMenuButton, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+END_MESSAGE_MAP()
+
void CBarMenuButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value, indent);
@@ -36,4 +42,20 @@ void CBarMenuButton::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CBarMenuButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CBarMenuButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ if (_value) {
+ CPETUpMsg upMsg("", -1);
+ upMsg.execute("BarTelevision");
+ } else {
+ CPETDownMsg downMsg("", -1);
+ downMsg.execute("BarTelevision");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/bar_menu_button.h b/engines/titanic/game/bar_menu_button.h
index c666df413b..300435c209 100644
--- a/engines/titanic/game/bar_menu_button.h
+++ b/engines/titanic/game/bar_menu_button.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CBarMenuButton : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
public:
int _value;
public:
diff --git a/engines/titanic/game/belbot_get_light.cpp b/engines/titanic/game/belbot_get_light.cpp
index 3e678a8a0c..2cc4c3ae19 100644
--- a/engines/titanic/game/belbot_get_light.cpp
+++ b/engines/titanic/game/belbot_get_light.cpp
@@ -24,6 +24,13 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBelbotGetLight, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(MovieFrameMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CBelbotGetLight::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeQuotedLine(_value, indent);
@@ -36,4 +43,36 @@ void CBelbotGetLight::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CBelbotGetLight::ActMsg(CActMsg *msg) {
+ if (msg->_action == "BellbotGetLight") {
+ _value = getFullViewName();
+ lockMouse();
+ changeView("1stClassState.Node 11.N", "");
+ }
+
+ return true;
+}
+
+bool CBelbotGetLight::MovieEndMsg(CMovieEndMsg *msg) {
+ sleep(1000);
+ changeView(_value, "");
+ unlockMouse();
+ return true;
+}
+
+bool CBelbotGetLight::MovieFrameMsg(CMovieFrameMsg *msg) {
+ if (getMovieFrame() == 37) {
+ CActMsg actMsg("BellbotGetLight");
+ actMsg.execute("Eye1");
+ }
+
+ return true;
+}
+
+bool CBelbotGetLight::EnterViewMsg(CEnterViewMsg *msg) {
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ movieEvent(37);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/belbot_get_light.h b/engines/titanic/game/belbot_get_light.h
index a3aa0f737e..1707ad4793 100644
--- a/engines/titanic/game/belbot_get_light.h
+++ b/engines/titanic/game/belbot_get_light.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CBelbotGetLight : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool MovieFrameMsg(CMovieFrameMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
CString _value;
public:
diff --git a/engines/titanic/game/bomb.cpp b/engines/titanic/game/bomb.cpp
index 9a08f26ece..f3f1129e22 100644
--- a/engines/titanic/game/bomb.cpp
+++ b/engines/titanic/game/bomb.cpp
@@ -21,21 +21,67 @@
*/
#include "titanic/game/bomb.h"
-#include "titanic/titanic.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBomb, CBackground)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(TrueTalkGetStateValueMsg)
+ ON_MESSAGE(SetFrameMsg)
+END_MESSAGE_MAP()
+
+static const char *const WAVE_NAMES1[] = {
+ "z#353.wav", "z#339.wav", "z#325.wav", "z#311.wav", "z#297.wav",
+ "z#283.wav", "z#269.wav", "z#255.wav", "z#241.wav"
+};
+
+static const char *const WAVE_NAMES2[] = {
+ "", "z#352.wav", "z#338.wav", "z#324.wav", "z#310.wav", "z#296.wav",
+ "z#281.wav", "z#268.wav", "z#254.wav", "z#240.wav", "", "z#351.wav",
+ "z#337.wav", "z#323.wav", "z#309.wav", "z#295.wav", "z#282.wav",
+ "z#267.wav", "z#253.wav", "z#239.wav"
+};
+
+static const char *const WAVE_NAMES3[100] = {
+ "bombcountdown_c0.wav", "z#355.wav", "z#341.wav", "z#327.wav", "z#313.wav",
+ "z#299.wav", "z#285.wav", "z#271.wav", "z#257.wav", "z#243.wav",
+ "z#354.wav", "z#350.wav", "z#349.wav", "z#348.wav", "z#347.wav",
+ "z#346.wav", "z#345.wav", "z#344.wav", "z#343.wav", "z#342.wav",
+ "z#340.wav", "z#336.wav", "z#335.wav", "z#334.wav", "z#333.wav",
+ "z#332.wav", "z#331.wav", "z#330.wav", "z#329.wav", "z#328.wav",
+ "z#326.wav", "z#322.wav", "z#321.wav", "z#320.wav", "z#319.wav",
+ "z#318.wav", "z#317.wav", "z#316.wav", "z#315.wav", "z#314.wav",
+ "z#312.wav", "z#308.wav", "z#307.wav", "z#306.wav", "z#305.wav",
+ "z#304.wav", "z#303.wav", "z#302.wav", "z#301.wav", "z#300.wav",
+ "z#298.wav", "z#294.wav", "z#293.wav", "z#292.wav", "z#291.wav",
+ "z#290.wav", "z#289.wav", "z#288.wav", "z#287.wav", "z#286.wav",
+ "z#284.wav", "z#280.wav", "z#279.wav", "z#278.wav", "z#277.wav",
+ "z#276.wav", "z#275.wav", "z#274.wav", "z#273.wav", "z#272.wav",
+ "z#270.wav", "z#266.wav", "z#265.wav", "z#264.wav", "z#263.wav",
+ "z#262.wav", "z#261.wav", "z#260.wav", "z#259.wav", "z#258.wav",
+ "z#256.wav", "z#252.wav", "z#251.wav", "z#250.wav", "z#249.wav",
+ "z#248.wav", "z#247.wav", "z#246.wav", "z#245.wav", "z#244.wav",
+ "z#242.wav", "z#238.wav", "z#237.wav", "z#236.wav", "z#235.wav",
+ "z#234.wav", "z#233.wav", "z#232.wav", "z#231.wav", "z#230.wav",
+};
+
CBomb::CBomb() : CBackground() {
_fieldE0 = 0;
_fieldE4 = 0;
_fieldE8 = 17;
_fieldEC = 9;
_fieldF0 = 0;
- _fieldF4 = 999;
- _fieldF8 = 0;
+ _countdown = 999;
+ _soundHandle = 0;
_fieldFC = 0;
_startingTicks = 0;
- _field104 = 60;
+ _volume = 60;
}
void CBomb::save(SimpleFile *file, int indent) {
@@ -45,11 +91,11 @@ void CBomb::save(SimpleFile *file, int indent) {
file->writeNumberLine(_fieldE8, indent);
file->writeNumberLine(_fieldEC, indent);
file->writeNumberLine(_fieldF0, indent);
- file->writeNumberLine(_fieldF4, indent);
- file->writeNumberLine(_fieldF8, indent);
+ file->writeNumberLine(_countdown, indent);
+ file->writeNumberLine(_soundHandle, indent);
file->writeNumberLine(_fieldFC, indent);
file->writeNumberLine(_startingTicks, indent);
- file->writeNumberLine(_field104, indent);
+ file->writeNumberLine(_volume, indent);
CBackground::save(file, indent);
}
@@ -61,20 +107,256 @@ void CBomb::load(SimpleFile *file) {
_fieldE8 = file->readNumber();
_fieldEC = file->readNumber();
_fieldF0 = file->readNumber();
- _fieldF4 = file->readNumber();
- _fieldF8 = file->readNumber();
+ _countdown = file->readNumber();
+ _soundHandle = file->readNumber();
_fieldFC = file->readNumber();
_startingTicks = file->readNumber();
- _field104 = file->readNumber();
+ _volume = file->readNumber();
CBackground::load(file);
}
+bool CBomb::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _fieldE4 += msg->_newStatus;
+
+ if (_fieldE4 == 23) {
+ startAnimTimer("Disarmed", 2000);
+ lockMouse();
+ }
+
+ _fieldF0 %= 1000;
+ if (!(_fieldF0 % 20) && _countdown < 995) {
+ int val = getRandomNumber(5) + 25;
+ if (_fieldF0 < 20 || _fieldF0 > 80)
+ val = 28;
+
+ CString name;
+ switch (val - 25) {
+ case 0:
+ name = "z#372.wav";
+ break;
+ case 1:
+ name = "z#371.wav";
+ break;
+ case 2:
+ name = "z#370.wav";
+ break;
+ case 3:
+ name = "z#369.wav";
+ break;
+ case 4:
+ name = "z#368.wav";
+ break;
+ default:
+ name = "z#366.wav";
+ break;
+ }
+
+ _soundHandle = queueSound(name, _soundHandle, _volume);
+ }
+
+ return true;
+}
+
+bool CBomb::EnterViewMsg(CEnterViewMsg *msg) {
+ _fieldE4 = 2;
+ return true;
+}
+
+bool CBomb::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ playSound("z#62.wav");
+
+ if (_fieldE0) {
+ stopSound(_soundHandle);
+ if (_fieldE4 < 23) {
+ _fieldE8 = MIN(_fieldE8 + 1, 23);
+
+ CString name;
+ switch (_fieldE8) {
+ case 18:
+ name = "z#380.wav";
+ break;
+ case 19:
+ name = "z#379.wav";
+ break;
+ case 20:
+ name = "z#377.wav";
+ break;
+ case 21:
+ name = "z#376.wav";
+ break;
+ case 22:
+ name = "z#375.wav";
+ break;
+ default:
+ name = "z#374.wav";
+ break;
+ }
+
+ _soundHandle = queueSound(name, _soundHandle, _volume);
+ _countdown = 999;
+ }
+ } else {
+ _soundHandle = playSound("z#389.wav", _volume);
+ _fieldE0 = true;
+ CActMsg actMsg("Arm Bomb");
+ actMsg.execute("EndExplodeShip");
+ }
+
+ return true;
+}
+
bool CBomb::EnterRoomMsg(CEnterRoomMsg *msg) {
- _fieldE8 = 12;
+ _fieldE8 = 17;
_fieldEC = 9;
_fieldF0 = 0;
- _startingTicks = g_vm->_events->getTicksCount();
+ _startingTicks = getTicksCount();
+ return true;
+}
+
+bool CBomb::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Hit") {
+ playSound("z#63.wav");
+ stopSound(_soundHandle);
+
+ if (_fieldEC < 17)
+ ++_fieldEC;
+
+ CString name;
+ switch (_fieldEC) {
+ case 10:
+ name = "z#388.wav";
+ break;
+ case 11:
+ name = "z#387.wav";
+ break;
+ case 12:
+ name = "z#386.wav";
+ break;
+ case 13:
+ name = "z#385.wav";
+ break;
+ case 14:
+ name = "z#384.wav";
+ break;
+ case 15:
+ name = "z#383.wav";
+ break;
+ case 16:
+ name = "z#382.wav";
+ break;
+ default:
+ name = "z#381.wav";
+ break;
+ }
+
+ _soundHandle = queueSound(name, _soundHandle, _volume);
+ _countdown = 999;
+ }
+
+ return true;
+}
+
+bool CBomb::TurnOn(CTurnOn *msg) {
+ if (!_fieldE0) {
+ _soundHandle = playSound("z#389.wav", _volume);
+ _fieldE0 = true;
+
+ CActMsg actMsg("Arm Bomb");
+ actMsg.execute("EndExplodeShip");
+ addTimer(0);
+ }
+
+ changeView("Titania.Node 8.W", "");
+ CActMsg actMsg("Titania.Node 8.N");
+ actMsg.execute("BombNav");
+ actMsg.execute("EnterBombRoom");
+
+ return true;
+}
+
+bool CBomb::TimerMsg(CTimerMsg *msg) {
+ if (msg->_action == "Disarmed") {
+ stopSound(_soundHandle);
+ playSound("z#364.wav", _volume);
+
+ CActMsg actMsg1("Disarm Bomb");
+ actMsg1.execute("EndExplodeShip");
+ _fieldE0 = false;
+ CActMsg actMsg2("Titania.Node 5.N");
+ actMsg2.execute("BombNav");
+ actMsg2.execute("EnterBombNav");
+
+ changeView("Titania.Node 8.W", "");
+ changeView("Titania.Node 13.N", "");
+ unlockMouse();
+ }
+
+ if (compareRoomNameTo("Titania")) {
+ if (msg->_actionVal == 1 && getRandomNumber(9) == 0) {
+ if (!_fieldE0)
+ return true;
+
+ CParrotSpeakMsg speakMsg("Bomb", "BombCountdown");
+ speakMsg.execute("PerchedParrot");
+ }
+
+ if (_fieldE0) {
+ if (isSoundActive(_soundHandle)) {
+ if (msg->_actionVal == 0) {
+ addTimer(1, 1000, 0);
+ } else {
+ _soundHandle = 0;
+ int section = _countdown / 100;
+ int index = _countdown % 100;
+
+ if (_countdown >= 100) {
+ CString name1 = index ? WAVE_NAMES2[section] :
+ WAVE_NAMES1[section];
+ playSound(name1, _volume);
+ }
+
+ CString name2 = WAVE_NAMES3[index];
+ if (_countdown == 10) {
+ name2 = "z#229.wav";
+ _countdown = 998;
+ }
+
+ if (_soundHandle > 0) {
+ _soundHandle = queueSound(name2, _soundHandle, _volume);
+ } else {
+ _soundHandle = playSound(name2, _volume);
+ }
+
+ --_countdown;
+ addTimer(0, 1000, 0);
+ }
+ } else {
+ addTimer(0, 100, 0);
+ }
+ }
+ } else {
+ if (_fieldE0) {
+ --_countdown;
+ addTimer(6000);
+
+ if (_countdown < 11)
+ _countdown = getRandomNumber(900) + 50;
+ }
+ }
+
+ return true;
+}
+
+bool CBomb::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) {
+ if (msg->_stateNum == 10)
+ msg->_stateVal = _fieldE0;
+
+ return true;
+}
+
+bool CBomb::SetFrameMsg(CSetFrameMsg *msg) {
+ _volume = msg->_frameNumber;
return true;
}
diff --git a/engines/titanic/game/bomb.h b/engines/titanic/game/bomb.h
index ab4df16db0..f78c42cff0 100644
--- a/engines/titanic/game/bomb.h
+++ b/engines/titanic/game/bomb.h
@@ -29,18 +29,27 @@
namespace Titanic {
class CBomb : public CBackground {
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg);
+ bool SetFrameMsg(CSetFrameMsg *msg);
+ DECLARE_MESSAGE_MAP;
private:
int _fieldE0;
int _fieldE4;
int _fieldE8;
int _fieldEC;
int _fieldF0;
- int _fieldF4;
- int _fieldF8;
+ int _countdown;
+ int _soundHandle;
int _fieldFC;
int _startingTicks;
- int _field104;
+ int _volume;
public:
CLASSDEF;
CBomb();
diff --git a/engines/titanic/game/bottom_of_well_monitor.cpp b/engines/titanic/game/bottom_of_well_monitor.cpp
index beb2a80ce9..38211040d8 100644
--- a/engines/titanic/game/bottom_of_well_monitor.cpp
+++ b/engines/titanic/game/bottom_of_well_monitor.cpp
@@ -24,6 +24,13 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBottomOfWellMonitor, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
int CBottomOfWellMonitor::_v1;
int CBottomOfWellMonitor::_v2;
@@ -31,7 +38,7 @@ void CBottomOfWellMonitor::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_v1, indent);
file->writeNumberLine(_v2, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
@@ -39,8 +46,69 @@ void CBottomOfWellMonitor::load(SimpleFile *file) {
file->readNumber();
_v1 = file->readNumber();
_v2 = file->readNumber();
- _value = file->readNumber();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CBottomOfWellMonitor::ActMsg(CActMsg *msg) {
+ if (msg->_action == "TelevisionTaken") {
+ _v1 = 0;
+ _cursorId = CURSOR_ARROW;
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("CrushedTV2NE");
+ visibleMsg.execute("CrushedTV4SW");
+ _cursorId = CURSOR_ARROW;
+ } else if (msg->_action == "LiftbotHeadTaken") {
+ _v2 = 0;
+ _cursorId = CURSOR_ARROW;
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("LiftbotHead2NE");
+ visibleMsg.execute("LiftbotHead4SW");
+ _cursorId = CURSOR_ARROW;
+ } else if (msg->_action == "LiftbotHeadTaken") {
+ _v2 = 1;
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("CrushedTV2NE");
+ visibleMsg.execute("CrushedTV4SW");
+ _cursorId = CURSOR_MOVE_DOWN1;
+ }
+
+ return true;
+}
+
+bool CBottomOfWellMonitor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (isEquals("BOWTelevisionMonitor")) {
+ if (_v1)
+ changeView("BottomOfWell.Node 7.N", "");
+ } else {
+ if (_v2)
+ changeView("BottomOfWell.Node 8.N", "");
+ }
+
+ return true;
+}
+
+bool CBottomOfWellMonitor::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_flag) {
+ if (isEquals("BOWTelevisionMonitor")) {
+ if (_v1) {
+ changeView("BottomOfWell.Node 7.N", "");
+ _flag = false;
+ }
+ } else {
+ if (_v2) {
+ changeView("BottomOfWell.Node 8.N", "");
+ _flag = false;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CBottomOfWellMonitor::LeaveViewMsg(CLeaveViewMsg *msg) {
+ _flag = true;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/bottom_of_well_monitor.h b/engines/titanic/game/bottom_of_well_monitor.h
index 65424aad70..be9ae2c093 100644
--- a/engines/titanic/game/bottom_of_well_monitor.h
+++ b/engines/titanic/game/bottom_of_well_monitor.h
@@ -28,12 +28,17 @@
namespace Titanic {
class CBottomOfWellMonitor : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
static int _v1, _v2;
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CBottomOfWellMonitor() : _value(1) {}
+ CBottomOfWellMonitor() : _flag(true) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/bowl_unlocker.cpp b/engines/titanic/game/bowl_unlocker.cpp
index c3c501dbd6..c4adac34f2 100644
--- a/engines/titanic/game/bowl_unlocker.cpp
+++ b/engines/titanic/game/bowl_unlocker.cpp
@@ -21,19 +21,58 @@
*/
#include "titanic/game/bowl_unlocker.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBowlUnlocker, CGameObject)
+ ON_MESSAGE(NutPuzzleMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
void CBowlUnlocker::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_bowlUnlocked, indent);
CGameObject::save(file, indent);
}
void CBowlUnlocker::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _bowlUnlocked = file->readNumber();
CGameObject::load(file);
}
+bool CBowlUnlocker::NutPuzzleMsg(CNutPuzzleMsg *msg) {
+ if (msg->_value == "UnlockBowl") {
+ setVisible(true);
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ }
+
+ return true;
+}
+
+bool CBowlUnlocker::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ _bowlUnlocked = true;
+
+ CNutPuzzleMsg puzzleMsg("BowlUnlocked");
+ puzzleMsg.execute(getRoom(), nullptr, MSGFLAG_SCAN);
+
+ playSound("z#47.wav");
+ return true;
+}
+
+bool CBowlUnlocker::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_bowlUnlocked)
+ msg->execute("Ear1");
+ return true;
+}
+
+bool CBowlUnlocker::LeaveViewMsg(CLeaveViewMsg *msg) {
+ _bowlUnlocked = false;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/bowl_unlocker.h b/engines/titanic/game/bowl_unlocker.h
index 2559ac2c52..b940661904 100644
--- a/engines/titanic/game/bowl_unlocker.h
+++ b/engines/titanic/game/bowl_unlocker.h
@@ -28,11 +28,16 @@
namespace Titanic {
class CBowlUnlocker : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool NutPuzzleMsg(CNutPuzzleMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
- int _value;
+ bool _bowlUnlocked;
public:
CLASSDEF;
- CBowlUnlocker() : CGameObject(), _value(0) {}
+ CBowlUnlocker() : CGameObject(), _bowlUnlocked(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/brain_slot.cpp b/engines/titanic/game/brain_slot.cpp
index f1963142ac..1518d9b0b3 100644
--- a/engines/titanic/game/brain_slot.cpp
+++ b/engines/titanic/game/brain_slot.cpp
@@ -21,18 +21,27 @@
*/
#include "titanic/game/brain_slot.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
-int CBrainSlot::_v1;
-int CBrainSlot::_v2;
+BEGIN_MESSAGE_MAP(CBrainSlot, CGameObject)
+ ON_MESSAGE(SetFrameMsg)
+ ON_MESSAGE(AddHeadPieceMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
+int CBrainSlot::_added;
+bool CBrainSlot::_woken;
void CBrainSlot::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value1, indent);
- file->writeQuotedLine(_value2, indent);
- file->writeNumberLine(_v1, indent);
- file->writeNumberLine(_v2, indent);
+ file->writeQuotedLine(_target, indent);
+ file->writeNumberLine(_added, indent);
+ file->writeNumberLine(_woken, indent);
CGameObject::save(file, indent);
}
@@ -40,11 +49,101 @@ void CBrainSlot::save(SimpleFile *file, int indent) {
void CBrainSlot::load(SimpleFile *file) {
file->readNumber();
_value1 = file->readNumber();
- _value2 = file->readString();
- _v1 = file->readNumber();
- _v2 = file->readNumber();
+ _target = file->readString();
+ _added = file->readNumber();
+ _woken = file->readNumber();
CGameObject::load(file);
}
+bool CBrainSlot::SetFrameMsg(CSetFrameMsg *msg) {
+ loadFrame(msg->_frameNumber);
+ _value1 = 1;
+ return true;
+}
+
+bool CBrainSlot::AddHeadPieceMsg(CAddHeadPieceMsg *msg) {
+ _added = 1;
+ _cursorId = CURSOR_HAND;
+ CAddHeadPieceMsg addMsg("NULL");
+
+ if (isEquals("AuditoryCentreSlot")) {
+ if (msg->_value == "AuditoryCentre")
+ addMsg._value = "AuditoryCentre";
+ } else if (isEquals("SpeechCentreSlot")) {
+ if (msg->_value == "SpeechCentre")
+ addMsg._value = "SpeechCentre";
+ } else if (isEquals("OlfactoryCentreSlot")) {
+ if (msg->_value == "OlfactoryCentre")
+ addMsg._value = "OlfactoryCentre";
+ } else if (isEquals("VisionCentreSlot")) {
+ if (msg->_value == "VisionCentre")
+ addMsg._value = "VisionCentre";
+ } else if (isEquals("CentralCoreSlot")) {
+ if (msg->_value == "CentralCore")
+ addMsg._value = "CentralCore";
+ }
+
+ if (addMsg._value != "NULL")
+ addMsg.execute("TitaniaControl");
+
+ if (addMsg._value == "OlfactoryCentre")
+ loadFrame(2);
+ else if (addMsg._value == "AuditoryCentre")
+ loadFrame(1);
+ else if (addMsg._value == "SpeechCentre")
+ loadFrame(3);
+ else if (addMsg._value == "VisionCentre")
+ loadFrame(4);
+ else if (addMsg._value == "CentralCore") {
+ CActMsg actMsg("Insert Central Core");
+ actMsg.execute("CentralCoreSlot");
+ }
+
+ _target = msg->_value;
+ _value1 = 1;
+ return true;
+}
+
+bool CBrainSlot::EnterViewMsg(CEnterViewMsg *msg) {
+ if (getName() == "CentralCoreSlot")
+ loadFrame(21);
+ if (_woken)
+ _cursorId = CURSOR_ARROW;
+
+ return true;
+}
+
+bool CBrainSlot::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Insert Central Core")
+ playMovie(0, 21, 0);
+ else if (msg->_action == "Woken")
+ _woken = true;
+
+ return true;
+}
+
+bool CBrainSlot::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!_value1 || _woken || !checkPoint(msg->_mousePos, false, true))
+ return false;
+
+ _cursorId = CURSOR_ARROW;
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute(_target);
+ CTakeHeadPieceMsg takeMsg(_target);
+ takeMsg.execute("TitaniaControl");
+
+ loadFrame(isEquals("CentralCoreSlot") ? 21 : 0);
+ _value1 = 0;
+
+ CPassOnDragStartMsg passMsg;
+ passMsg._mousePos = msg->_mousePos;
+ passMsg.execute(_target);
+
+ msg->_dragItem = getRoot()->findByName(_target);
+ _added = 0;
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/brain_slot.h b/engines/titanic/game/brain_slot.h
index 94b6d7f227..4d500cc59a 100644
--- a/engines/titanic/game/brain_slot.h
+++ b/engines/titanic/game/brain_slot.h
@@ -28,11 +28,18 @@
namespace Titanic {
class CBrainSlot : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool SetFrameMsg(CSetFrameMsg *msg);
+ bool AddHeadPieceMsg(CAddHeadPieceMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
- static int _v1, _v2;
+ static int _added;
+ static bool _woken;
public:
int _value1;
- CString _value2;
+ CString _target;
public:
CLASSDEF;
CBrainSlot() : CGameObject(), _value1(0) {}
diff --git a/engines/titanic/game/bridge_door.cpp b/engines/titanic/game/bridge_door.cpp
index 57cdbd23ad..bfa30fd650 100644
--- a/engines/titanic/game/bridge_door.cpp
+++ b/engines/titanic/game/bridge_door.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBridgeDoor, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CBridgeDoor::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +40,23 @@ void CBridgeDoor::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CBridgeDoor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ setVisible(true);
+ playMovie(0, 6, 0);
+ changeView("Titania.Node 12.N");
+
+ return true;
+}
+
+bool CBridgeDoor::StatusChangeMsg(CStatusChangeMsg *msg) {
+ setVisible(true);
+ playMovie(7, 0, MOVIE_NOTIFY_OBJECT);
+ return true;
+}
+
+bool CBridgeDoor::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/bridge_door.h b/engines/titanic/game/bridge_door.h
index c1872a29be..010a8b8bc0 100644
--- a/engines/titanic/game/bridge_door.h
+++ b/engines/titanic/game/bridge_door.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CBridgeDoor : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/bridge_view.cpp b/engines/titanic/game/bridge_view.cpp
index 9854969494..466480a64c 100644
--- a/engines/titanic/game/bridge_view.cpp
+++ b/engines/titanic/game/bridge_view.cpp
@@ -24,16 +24,92 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBridgeView, CBackground)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CBridgeView::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
+ file->writeNumberLine(_mode, indent);
CBackground::save(file, indent);
}
void CBridgeView::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
+ _mode = file->readNumber();
CBackground::load(file);
}
+bool CBridgeView::ActMsg(CActMsg *msg) {
+ CTurnOn onMsg;
+ CSetVolumeMsg volumeMsg;
+ volumeMsg._secondsTransition = 1;
+
+ if (msg->_action == "End") {
+ _mode = 4;
+ petLockInput();
+ petHide();
+ setVisible(true);
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ } else if (msg->_action == "Go") {
+ _mode = 1;
+ setVisible(true);
+ volumeMsg._volume = 100;
+ volumeMsg.execute("EngineSounds");
+ onMsg.execute("EngineSounds");
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ } else {
+ volumeMsg._volume = 50;
+ volumeMsg.execute("EngineSounds");
+ onMsg.execute("EngineSounds");
+
+ if (msg->_action == "Cruise") {
+ _mode = 2;
+ setVisible(true);
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ } else if (msg->_action == "GoENd") {
+ _mode = 3;
+ setVisible(true);
+ CChangeMusicMsg musicMsg;
+ musicMsg._flags = 1;
+ musicMsg.execute("BridgeAutoMusicPlayer");
+ playSound("a#42.wav");
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ }
+ }
+
+ return true;
+}
+
+bool CBridgeView::MovieEndMsg(CMovieEndMsg *msg) {
+ CTurnOff offMsg;
+ offMsg.execute("EngineSounds");
+
+ switch (_mode) {
+ case 0:
+ case 1:
+ setVisible(false);
+ dec54();
+ break;
+
+ case 2: {
+ setVisible(false);
+ CActMsg actMsg("End");
+ actMsg.execute("HomeSequence");
+ break;
+ }
+
+ case 3:
+ setVisible(false);
+ changeView("TheEnd.Node 3.N");
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/bridge_view.h b/engines/titanic/game/bridge_view.h
index d7c7c35aa9..45cfa3f4c8 100644
--- a/engines/titanic/game/bridge_view.h
+++ b/engines/titanic/game/bridge_view.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CBridgeView : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
- int _fieldE0;
+ int _mode;
public:
CLASSDEF;
- CBridgeView() : CBackground(), _fieldE0(0) {}
+ CBridgeView() : CBackground(), _mode(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/broken_pell_base.cpp b/engines/titanic/game/broken_pell_base.cpp
index 59e2b9bca1..02c2d873ac 100644
--- a/engines/titanic/game/broken_pell_base.cpp
+++ b/engines/titanic/game/broken_pell_base.cpp
@@ -26,7 +26,7 @@ namespace Titanic {
EMPTY_MESSAGE_MAP(CBrokenPellBase, CBackground);
-int CBrokenPellBase::_v1;
+bool CBrokenPellBase::_v1;
int CBrokenPellBase::_v2;
void CBrokenPellBase::save(SimpleFile *file, int indent) {
diff --git a/engines/titanic/game/broken_pell_base.h b/engines/titanic/game/broken_pell_base.h
index f63cd0112b..4ca7eddd20 100644
--- a/engines/titanic/game/broken_pell_base.h
+++ b/engines/titanic/game/broken_pell_base.h
@@ -29,8 +29,8 @@ namespace Titanic {
class CBrokenPellBase : public CBackground {
DECLARE_MESSAGE_MAP;
-private:
- static int _v1;
+protected:
+ static bool _v1;
static int _v2;
int _fieldE0;
diff --git a/engines/titanic/game/broken_pellerator.cpp b/engines/titanic/game/broken_pellerator.cpp
index d3b204b1e5..8fb7244b7e 100644
--- a/engines/titanic/game/broken_pellerator.cpp
+++ b/engines/titanic/game/broken_pellerator.cpp
@@ -21,9 +21,17 @@
*/
#include "titanic/game/broken_pellerator.h"
+#include "titanic/core/view_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBrokenPellerator, CBrokenPellBase)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CBrokenPellerator::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeQuotedLine(_string2, indent);
@@ -44,4 +52,103 @@ void CBrokenPellerator::load(SimpleFile *file) {
CBrokenPellBase::load(file);
}
+bool CBrokenPellerator::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_v1) {
+ changeView(_v2 ? _string5 : _string4);
+ } else {
+ if (_v2) {
+ playMovie(28, 43, 0);
+ } else {
+ playMovie(0, 14, MOVIE_NOTIFY_OBJECT);
+ }
+
+ _v1 = true;
+ }
+
+ return true;
+}
+
+bool CBrokenPellerator::LeaveViewMsg(CLeaveViewMsg *msg) {
+ CString name = msg->_newView->getNodeViewName();
+ if (name == "Node 3.S" || name == "Node 3.N") {
+ _v1 = false;
+ loadFrame(0);
+ }
+
+ return true;
+}
+
+bool CBrokenPellerator::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PlayerGetsHose") {
+ _v2 = 1;
+ loadFrame(43);
+
+ CStatusChangeMsg statusMsg;
+ statusMsg.execute("PickupHose");
+ } else {
+ _fieldE0 = 0;
+ bool closeFlag = msg->_action == "Close";
+ if (msg->_action == "CloseLeft") {
+ closeFlag = true;
+ _fieldE0 = 1;
+ }
+ if (msg->_action == "CloseRight") {
+ closeFlag = true;
+ _fieldE0 = 2;
+ }
+
+ if (closeFlag) {
+ if (_v1) {
+ _v1 = false;
+ if (_v2)
+ playMovie(43, 57, MOVIE_NOTIFY_OBJECT);
+ else
+ playMovie(14, 28, MOVIE_NOTIFY_OBJECT);
+ } else {
+ switch (_fieldE0) {
+ case 1:
+ changeView(_string2);
+ break;
+ case 2:
+ changeView(_string3);
+ break;
+ default:
+ break;
+ }
+
+ _fieldE0 = 0;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CBrokenPellerator::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == 14) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("PickUpHose");
+ }
+
+ if (msg->_endFrame == 28) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("PickUpHose");
+ }
+
+ switch (_fieldE0) {
+ case 1:
+ changeView(_string2);
+ break;
+ case 2:
+ changeView(_string3);
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/broken_pellerator.h b/engines/titanic/game/broken_pellerator.h
index 6fbde91053..3b8c3ba587 100644
--- a/engines/titanic/game/broken_pellerator.h
+++ b/engines/titanic/game/broken_pellerator.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CBrokenPellerator : public CBrokenPellBase {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
private:
CString _string2;
CString _string3;
diff --git a/engines/titanic/game/broken_pellerator_froz.cpp b/engines/titanic/game/broken_pellerator_froz.cpp
index 4b21ea93d0..690ab76820 100644
--- a/engines/titanic/game/broken_pellerator_froz.cpp
+++ b/engines/titanic/game/broken_pellerator_froz.cpp
@@ -21,9 +21,17 @@
*/
#include "titanic/game/broken_pellerator_froz.h"
+#include "titanic/core/view_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBrokenPelleratorFroz, CBrokenPellBase)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CBrokenPelleratorFroz::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeQuotedLine(_string2, indent);
@@ -44,4 +52,99 @@ void CBrokenPelleratorFroz::load(SimpleFile *file) {
CBrokenPellBase::load(file);
}
+bool CBrokenPelleratorFroz::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_v1) {
+ changeView(_v2 ? _string5 : _string4);
+ } else {
+ _v1 = true;
+ if (_v2) {
+ playMovie(0, 13, 0);
+ } else {
+ playMovie(43, 55, MOVIE_NOTIFY_OBJECT);
+ }
+ }
+
+ return true;
+}
+
+bool CBrokenPelleratorFroz::LeaveViewMsg(CLeaveViewMsg *msg) {
+ CString name = msg->_newView->getNodeViewName();
+
+ if (name == "Node 3.S" || name == "Node 3.E") {
+ _v1 = false;
+ loadFrame(0);
+ }
+
+ return true;
+}
+
+bool CBrokenPelleratorFroz::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PlayerGetsHose") {
+ _v2 = 1;
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("FPickUpHose");
+ } else {
+ _fieldE0 = 0;
+ bool closeFlag = msg->_action == "Close";
+ if (msg->_action == "CloseLeft") {
+ closeFlag = true;
+ _fieldE0 = 1;
+ }
+ if (msg->_action == "CloseRight") {
+ closeFlag = true;
+ _fieldE0 = 2;
+ }
+
+ if (closeFlag) {
+ if (_v1) {
+ _v1 = false;
+ if (_v2)
+ playMovie(29, 42, MOVIE_NOTIFY_OBJECT);
+ else
+ playMovie(72, 84, MOVIE_NOTIFY_OBJECT);
+ } else {
+ switch (_fieldE0) {
+ case 1:
+ changeView(_string2);
+ break;
+ case 2:
+ changeView(_string3);
+ break;
+ default:
+ break;
+ }
+
+ _fieldE0 = 0;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CBrokenPelleratorFroz::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == 55) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("FPickUpHose");
+ }
+
+ if (msg->_endFrame == 84) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("FPickUpHose");
+ }
+
+ if (_fieldE0 == 1) {
+ changeView(_string2);
+ _fieldE0 = 0;
+ } else if (_fieldE0 == 2) {
+ changeView(_string3);
+ _fieldE0 = 0;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/broken_pellerator_froz.h b/engines/titanic/game/broken_pellerator_froz.h
index 1df6d2d0b2..ccdae6ffa8 100644
--- a/engines/titanic/game/broken_pellerator_froz.h
+++ b/engines/titanic/game/broken_pellerator_froz.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CBrokenPelleratorFroz : public CBrokenPellBase {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
private:
CString _string2;
CString _string3;
diff --git a/engines/titanic/game/cage.cpp b/engines/titanic/game/cage.cpp
index 7fbc052278..bbac384cea 100644
--- a/engines/titanic/game/cage.cpp
+++ b/engines/titanic/game/cage.cpp
@@ -21,16 +21,25 @@
*/
#include "titanic/game/cage.h"
+#include "titanic/npcs/parrot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CCage, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(PreEnterViewMsg)
+ ON_MESSAGE(MouseMoveMsg)
+END_MESSAGE_MAP()
+
int CCage::_v1;
-int CCage::_v2;
+bool CCage::_open;
void CCage::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_v1, indent);
- file->writeNumberLine(_v2, indent);
+ file->writeNumberLine(_open, indent);
CBackground::save(file, indent);
}
@@ -38,9 +47,64 @@ void CCage::save(SimpleFile *file, int indent) {
void CCage::load(SimpleFile *file) {
file->readNumber();
_v1 = file->readNumber();
- _v2 = file->readNumber();
+ _open = file->readNumber();
CBackground::load(file);
}
+bool CCage::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (CParrot::_v4 && !CParrot::_v5) {
+ CActMsg actMsg(_open ? "Open" : "Shut");
+ actMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CCage::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Shut") {
+ if (!_open) {
+ playClip("Shut", MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT);
+ disableMouse();
+ }
+ } else if (msg->_action == "Open") {
+ if (_open) {
+ playClip("Open", MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT);
+ disableMouse();
+ }
+ } else if (msg->_action == "CoreReplaced") {
+ CActMsg actMsg("Shut");
+ actMsg.execute(this);
+ } else if (msg->_action == "OpenNow") {
+ loadFrame(0);
+ _open = false;
+ }
+
+ return true;
+}
+
+bool CCage::MovieEndMsg(CMovieEndMsg *msg) {
+ enableMouse();
+ _open = clipExistsByEnd("Shut", msg->_endFrame);
+
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = _open ? 1 : (CParrot::_v4 == 0 ? 1 : 0);
+ statusMsg.execute("PerchCoreHolder");
+
+ return true;
+}
+
+bool CCage::PreEnterViewMsg(CPreEnterViewMsg *msg) {
+ loadSurface();
+ _open = CParrot::_v4 != 0;
+ loadFrame(_open ? 8 : 0);
+
+ return true;
+}
+
+bool CCage::MouseMoveMsg(CMouseMoveMsg *msg) {
+ _cursorId = CParrot::_v4 && !CParrot::_v5 ? CURSOR_ACTIVATE : CURSOR_ARROW;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/cage.h b/engines/titanic/game/cage.h
index bbce978489..48b1b46ab7 100644
--- a/engines/titanic/game/cage.h
+++ b/engines/titanic/game/cage.h
@@ -28,9 +28,15 @@
namespace Titanic {
class CCage : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool PreEnterViewMsg(CPreEnterViewMsg *msg);
+ bool MouseMoveMsg(CMouseMoveMsg *msg);
public:
static int _v1;
- static int _v2;
+ static bool _open;
public:
CLASSDEF;
diff --git a/engines/titanic/game/captains_wheel.cpp b/engines/titanic/game/captains_wheel.cpp
index c84c9194ce..79908b561d 100644
--- a/engines/titanic/game/captains_wheel.cpp
+++ b/engines/titanic/game/captains_wheel.cpp
@@ -24,6 +24,15 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CCaptainsWheel, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
CCaptainsWheel::CCaptainsWheel() : CBackground(),
_fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0),
_fieldF0(0), _fieldF4(0) {
@@ -53,4 +62,148 @@ void CCaptainsWheel::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CCaptainsWheel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_fieldE0) {
+ _fieldE0 = false;
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ playMovie(162, 168, 0);
+ } else {
+ playMovie(0, 8, MOVIE_NOTIFY_OBJECT);
+ }
+
+ return true;
+}
+
+bool CCaptainsWheel::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (_fieldE0) {
+ _fieldE0 = false;
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ playMovie(162, 168, MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
+bool CCaptainsWheel::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Spin") {
+ if (_fieldE0) {
+ CTurnOn onMsg;
+ onMsg.execute("RatchetySound");
+ playMovie(8, 142, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+ } else if (msg->_action == "Honk") {
+ if (_fieldE0) {
+ playMovie(150, 160, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+ } else if (msg->_action == "Go") {
+ if (!_fieldE0) {
+ inc54();
+ _fieldE0 = false;
+ _fieldE4 = 1;
+
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ playMovie(162, 168, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+ } else if (msg->_action == "Cruise") {
+ if (_fieldE0) {
+ inc54();
+ _fieldE0 = false;
+ _fieldE4 = 2;
+
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ playMovie(162, 168, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+ } else if (msg->_action == "SetDestin") {
+ playSound("a#44.wav");
+ CSetVolumeMsg volumeMsg;
+ volumeMsg._volume = 25;
+ volumeMsg.execute("EngineSounds");
+ CTurnOn onMsg;
+ onMsg.execute("EngineSounds");
+ _fieldF0 = 1;
+ } else if (msg->_action == "ClearDestin") {
+ _fieldF0 = 0;
+ }
+
+ return true;
+}
+
+bool CCaptainsWheel::TurnOff(CTurnOff *msg) {
+ CSignalObject signalMsg;
+ signalMsg._numValue = 0;
+
+ static const char *const NAMES[8] = {
+ "WheelSpin", "SeagullHorn", "WheelStopButt", "StopHotSpot",
+ "WheelCruiseButt", "CruiseHotSpot", "WheelGoButt","GoHotSpot"
+ };
+ for (int idx = 0; idx < 8; ++idx)
+ signalMsg.execute(NAMES[idx]);
+
+ return true;
+}
+
+bool CCaptainsWheel::TurnOn(CTurnOn *msg) {
+ CSignalObject signalMsg;
+ signalMsg._numValue = 1;
+ signalMsg.execute("WheelSpin");
+ signalMsg.execute("SeagullHorn");
+
+ if (_fieldE0) {
+ signalMsg.execute("WheelStopButt");
+ signalMsg.execute("StopHotSpot");
+ }
+
+ if (_fieldEC) {
+ signalMsg.execute("WheelCruiseButt");
+ signalMsg.execute("CruiseHotSpot");
+ }
+
+ if (_fieldF0) {
+ signalMsg.execute("WheelGoButt");
+ signalMsg.execute("GoHotSpot");
+ }
+
+ return true;
+}
+
+bool CCaptainsWheel::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == 8) {
+ _fieldE0 = true;
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ }
+
+ if (msg->_endFrame == 142) {
+ CTurnOff offMsg;
+ offMsg.execute("RatchetySound");
+ }
+
+ if (msg->_endFrame == 168) {
+ switch (_fieldE4) {
+ case 1: {
+ CActMsg actMsg(starFn2() ? "GoEnd" : "Go");
+ actMsg.execute("GoSequence");
+ break;
+ }
+
+ case 2: {
+ CActMsg actMsg("Cruise");
+ actMsg.execute("CruiseSequence");
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ _fieldE4 = 0;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/captains_wheel.h b/engines/titanic/game/captains_wheel.h
index 549dcbe685..3aca45c21f 100644
--- a/engines/titanic/game/captains_wheel.h
+++ b/engines/titanic/game/captains_wheel.h
@@ -28,6 +28,13 @@
namespace Titanic {
class CCaptainsWheel : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
int _fieldE0;
int _fieldE4;
diff --git a/engines/titanic/game/cell_point_button.cpp b/engines/titanic/game/cell_point_button.cpp
index 18ece09cb0..207dd73543 100644
--- a/engines/titanic/game/cell_point_button.cpp
+++ b/engines/titanic/game/cell_point_button.cpp
@@ -24,12 +24,17 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CCellPointButton, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
CCellPointButton::CCellPointButton() : CBackground() {
_fieldE0 = 0;
_fieldE4 = 0;
_fieldE8 = 0;
_fieldEC = 0;
- _fieldF0 = 0;
+ _regionNum = 0;
_fieldF4 = 0;
_fieldF8 = 0;
_fieldFC = 0;
@@ -44,7 +49,7 @@ void CCellPointButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(_fieldE4, indent);
file->writeNumberLine(_fieldE8, indent);
file->writeNumberLine(_fieldEC, indent);
- file->writeNumberLine(_fieldF0, indent);
+ file->writeNumberLine(_regionNum, indent);
file->writeNumberLine(_fieldF4, indent);
file->writeNumberLine(_fieldF8, indent);
file->writeNumberLine(_fieldFC, indent);
@@ -52,7 +57,7 @@ void CCellPointButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(_field104, indent);
file->writeNumberLine(_field108, indent);
file->writeQuotedLine(_string3, indent);
- file->writeNumberLine(_field118, indent);
+ file->writeNumberLine(_dialNum, indent);
CBackground::save(file, indent);
}
@@ -63,7 +68,7 @@ void CCellPointButton::load(SimpleFile *file) {
_fieldE4 = file->readNumber();
_fieldE8 = file->readNumber();
_fieldEC = file->readNumber();
- _fieldF0 = file->readNumber();
+ _regionNum = file->readNumber();
_fieldF4 = file->readNumber();
_fieldF8 = file->readNumber();
_fieldFC = file->readNumber();
@@ -71,9 +76,28 @@ void CCellPointButton::load(SimpleFile *file) {
_field104 = file->readNumber();
_field108 = file->readNumber();
_string3 = file->readString();
- _field118 = file->readNumber();
+ _dialNum = file->readNumber();
CBackground::load(file);
}
+bool CCellPointButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (getRandomNumber(2) == 0) {
+ CParrotSpeakMsg speakMsg("Cellpoints", _string3);
+ speakMsg.execute("PerchedParrot");
+ }
+
+ playMovie(0);
+ _regionNum = _regionNum ? 0 : 1;
+ playSound("z#425.wav");
+ talkSetDialRegion(_string3, _dialNum, _regionNum);
+
+ return true;
+}
+
+bool CCellPointButton::EnterViewMsg(CEnterViewMsg *msg) {
+ _regionNum = talkGetDialRegion(_string3, _dialNum);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/cell_point_button.h b/engines/titanic/game/cell_point_button.h
index 6f1fdc3809..33f58cbb83 100644
--- a/engines/titanic/game/cell_point_button.h
+++ b/engines/titanic/game/cell_point_button.h
@@ -28,12 +28,15 @@
namespace Titanic {
class CCellPointButton : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
public:
int _fieldE0;
int _fieldE4;
int _fieldE8;
int _fieldEC;
- int _fieldF0;
+ int _regionNum;
int _fieldF4;
int _fieldF8;
int _fieldFC;
@@ -41,7 +44,7 @@ public:
int _field104;
int _field108;
CString _string3;
- int _field118;
+ int _dialNum;
public:
CLASSDEF;
CCellPointButton();
diff --git a/engines/titanic/game/chev_code.cpp b/engines/titanic/game/chev_code.cpp
index ebc20578b7..0acdf575f4 100644
--- a/engines/titanic/game/chev_code.cpp
+++ b/engines/titanic/game/chev_code.cpp
@@ -24,16 +24,262 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CChevCode, CGameObject)
+ ON_MESSAGE(SetChevLiftBits)
+ ON_MESSAGE(SetChevClassBits)
+ ON_MESSAGE(SetChevFloorBits)
+ ON_MESSAGE(SetChevRoomBits)
+ ON_MESSAGE(GetChevLiftNum)
+ ON_MESSAGE(GetChevClassNum)
+ ON_MESSAGE(GetChevFloorNum)
+ ON_MESSAGE(GetChevRoomNum)
+ ON_MESSAGE(CheckChevCode)
+ ON_MESSAGE(GetChevCodeFromRoomNameMsg)
+END_MESSAGE_MAP()
+
void CChevCode::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_chevCode, indent);
CGameObject::save(file, indent);
}
void CChevCode::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _chevCode = file->readNumber();
CGameObject::load(file);
}
+bool CChevCode::SetChevLiftBits(CSetChevLiftBits *msg) {
+ _chevCode &= ~0xC0000;
+ if (msg->_liftNum > 0 && msg->_liftNum < 5)
+ _chevCode = ((msg->_liftNum - 1) << 18) | _chevCode;
+
+ return true;
+}
+
+bool CChevCode::SetChevClassBits(CSetChevClassBits *msg) {
+ _chevCode &= ~0x30000;
+ if (msg->_classNum > 0 && msg->_classNum < 4)
+ _chevCode = (msg->_classNum << 16) | msg->_classNum;
+
+ return true;
+}
+
+bool CChevCode::SetChevFloorBits(CSetChevFloorBits *msg) {
+ int section = (msg->_floorNum + 4) / 10;
+ int index = (msg->_floorNum + 4) % 10;
+ _chevCode &= ~0xFF00;
+
+ int val = 0;
+ switch (section) {
+ case 0:
+ val = 144;
+ break;
+ case 1:
+ val = 208;
+ break;
+ case 2:
+ val = 224;
+ break;
+ case 3:
+ val = 240;
+ break;
+ default:
+ break;
+ }
+
+ _chevCode |= ((index + val) << 8);
+ return true;
+}
+
+bool CChevCode::SetChevRoomBits(CSetChevRoomBits *msg) {
+ _chevCode &= ~0xff;
+ if (msg->_roomNum > 0 && msg->_roomNum < 128)
+ _chevCode |= msg->_roomNum * 2;
+
+ return true;
+}
+
+bool CChevCode::GetChevLiftNum(CGetChevLiftNum *msg) {
+ msg->_liftNum = ((_chevCode >> 18) & 3) + 1;
+ return true;
+}
+
+bool CChevCode::GetChevClassNum(CGetChevClassNum *msg) {
+ msg->_classNum = (_chevCode >> 16) & 3;
+ return true;
+}
+
+bool CChevCode::GetChevFloorNum(CGetChevFloorNum *msg) {
+ int val1 = (_chevCode >> 8) & 0xF;
+ int val2 = ((_chevCode >> 12) & 0xF) - 9;
+
+ switch (val2) {
+ case 0:
+ val2 = 0;
+ break;
+ case 4:
+ val2 = 1;
+ break;
+ case 5:
+ val2 = 2;
+ break;
+ case 6:
+ val2 = 3;
+ break;
+ default:
+ val2 = 4;
+ break;
+ }
+
+ msg->_floorNum = (val1 >= 10) ? 0 : val1 * 10;
+ return true;
+}
+
+bool CChevCode::GetChevRoomNum(CGetChevRoomNum *msg) {
+ msg->_roomNum = (_chevCode >> 1) & 0x7F;
+ return true;
+}
+
+bool CChevCode::CheckChevCode(CCheckChevCode *msg) {
+ CGetChevClassNum getClassMsg;
+ CGetChevLiftNum getLiftMsg;
+ CGetChevFloorNum getFloorMsg;
+ CGetChevRoomNum getRoomMsg;
+ CString roomName;
+ int classNum = 0;
+ uint bits = 0;
+
+ if (_chevCode & 1) {
+ switch (_chevCode) {
+ case 0x1D0D9:
+ roomName = "ParrLobby";
+ classNum = 4;
+ break;
+ case 0x196D9:
+ roomName = "FCRestrnt";
+ classNum = 4;
+ break;
+ case 0x39FCB:
+ roomName = "Bridge";
+ classNum = 4;
+ break;
+ case 0x2F86D:
+ roomName = "CrtrsCham";
+ classNum = 4;
+ break;
+ case 0x465FB:
+ roomName = "SculpCham";
+ classNum = 4;
+ break;
+ case 0x3D94B:
+ roomName = "BilgeRoom";
+ classNum = 4;
+ break;
+ case 0x59FAD:
+ roomName = "BoWell";
+ classNum = 4;
+ break;
+ case 0x4D6AF:
+ roomName = "Arboretum";
+ classNum = 4;
+ break;
+ case 0x8A397:
+ roomName = "TitRoom";
+ classNum = 4;
+ break;
+ case 0x79C45:
+ roomName = "PromDeck";
+ classNum = 4;
+ break;
+ case 0xB3D97:
+ roomName = "Bar";
+ classNum = 4;
+ break;
+ case 0xCC971:
+ roomName = "EmbLobby";
+ classNum = 4;
+ break;
+ case 0xF34DB:
+ roomName = "MusicRoom";
+ classNum = 4;
+ break;
+ default:
+ roomName = "BadRoom";
+ classNum = 5;
+ break;
+ }
+
+ bits = classNum == 5 ? 0x3D94B : _chevCode;
+ } else {
+ getFloorMsg.execute(this);
+ getRoomMsg.execute(this);
+ getClassMsg.execute(this);
+ getLiftMsg.execute(this);
+ if (getFloorMsg._floorNum > 37 || getRoomMsg._roomNum > 18)
+ classNum = 5;
+
+ if (classNum == 5) {
+ bits = 0x3D94B;
+ } else {
+ switch (getClassMsg._classNum) {
+ case 1:
+ if (getFloorMsg._floorNum >= 2 && getFloorMsg._floorNum <= 18
+ && getRoomMsg._roomNum >= 1 && getRoomMsg._roomNum <= 3
+ && getLiftMsg._liftNum >= 1 && getLiftMsg._liftNum <= 4)
+ classNum = 1;
+ else
+ classNum = 5;
+ break;
+
+ case 2:
+ if (getFloorMsg._floorNum >= 19 && getFloorMsg._floorNum <= 26
+ && getRoomMsg._roomNum >= 1 && getRoomMsg._roomNum <= 5
+ && getLiftMsg._liftNum >= 1 && getLiftMsg._liftNum <= 4)
+ classNum = 2;
+ else
+ classNum = 5;
+ break;
+
+ case 3:
+ if (getFloorMsg._floorNum >= 27 && getFloorMsg._floorNum <= 37
+ && getRoomMsg._roomNum >= 1 && getRoomMsg._roomNum <= 18
+ && (getLiftMsg._liftNum & 1) == 1
+ && getLiftMsg._liftNum >= 1 && getLiftMsg._liftNum <= 4)
+ classNum = 3;
+ else
+ classNum = 5;
+ break;
+ }
+ }
+ }
+
+ msg->_classNum = classNum;
+ msg->_chevCode = bits;
+
+ // WORKAROUND: Skipped code from original that was for debugging purposes only
+ return true;
+}
+
+bool CChevCode::GetChevCodeFromRoomNameMsg(CGetChevCodeFromRoomNameMsg *msg) {
+ static const char *const ROOM_NAMES[13] = {
+ "ParrotLobby", "sculptureChamber", "Bar", "EmbLobby", "MusicRoom",
+ "Titania", "BottomOfWell", "Arboretum", "PromenadeDeck",
+ "FCRestrnt", "CrtrsCham", "BilgeRoom", "Bridge"
+ };
+ static const uint CHEV_CODES[13] = {
+ 0x1D0D9, 0x465FB, 0xB3D97, 0xCC971, 0xF34DB, 0x8A397, 0x59FAD,
+ 0x4D6AF, 0x79C45, 0x196D9, 0x2F86D, 0x3D94B, 0x39FCB
+ };
+
+ for (int idx = 0; idx < 13; ++idx) {
+ if (msg->_roomName == ROOM_NAMES[idx]) {
+ msg->_chevCode = CHEV_CODES[idx];
+ break;
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/chev_code.h b/engines/titanic/game/chev_code.h
index c4552d00a2..4a71b13f9e 100644
--- a/engines/titanic/game/chev_code.h
+++ b/engines/titanic/game/chev_code.h
@@ -28,11 +28,22 @@
namespace Titanic {
class CChevCode : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool SetChevLiftBits(CSetChevLiftBits *msg);
+ bool SetChevClassBits(CSetChevClassBits *msg);
+ bool SetChevFloorBits(CSetChevFloorBits *msg);
+ bool SetChevRoomBits(CSetChevRoomBits *msg);
+ bool GetChevLiftNum(CGetChevLiftNum *msg);
+ bool GetChevClassNum(CGetChevClassNum *msg);
+ bool GetChevFloorNum(CGetChevFloorNum *msg);
+ bool GetChevRoomNum(CGetChevRoomNum *msg);
+ bool CheckChevCode(CCheckChevCode *msg);
+ bool GetChevCodeFromRoomNameMsg(CGetChevCodeFromRoomNameMsg *msg);
public:
- int _value;
+ int _chevCode;
public:
CLASSDEF;
- CChevCode() : CGameObject(), _value(0) {}
+ CChevCode() : CGameObject(), _chevCode(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/chev_panel.cpp b/engines/titanic/game/chev_panel.cpp
index 245968e356..ed730c9d61 100644
--- a/engines/titanic/game/chev_panel.cpp
+++ b/engines/titanic/game/chev_panel.cpp
@@ -21,25 +21,101 @@
*/
#include "titanic/game/chev_panel.h"
+#include "titanic/game/chev_code.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CChevPanel, CGameObject)
+ ON_MESSAGE(MouseDragStartMsg)
+ ON_MESSAGE(MouseDragMoveMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(SetChevPanelBitMsg)
+ ON_MESSAGE(MouseDragEndMsg)
+ ON_MESSAGE(ClearChevPanelBits)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(SetChevPanelButtonsMsg)
+END_MESSAGE_MAP()
+
void CChevPanel::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
+ file->writeNumberLine(_startPos.x, indent);
+ file->writeNumberLine(_startPos.y, indent);
+ file->writeNumberLine(_chevCode, indent);
CGameObject::save(file, indent);
}
void CChevPanel::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
+ _startPos.x = file->readNumber();
+ _startPos.y = file->readNumber();
+ _chevCode = file->readNumber();
CGameObject::load(file);
}
+bool CChevPanel::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (checkStartDragging(msg)) {
+ _startPos = Point(msg->_mousePos.x - _bounds.left,
+ msg->_mousePos.y - _bounds.top);
+ CChildDragStartMsg dragMsg(_startPos);
+ dragMsg.execute(this, nullptr, MSGFLAG_SCAN);
+ }
+
+ return true;
+}
+
+bool CChevPanel::MouseDragMoveMsg(CMouseDragMoveMsg *msg) {
+ CChildDragMoveMsg dragMsg(_startPos);
+ dragMsg.execute(this, nullptr, MSGFLAG_SCAN);
+
+ setPosition(msg->_mousePos - _startPos);
+ return true;
+}
+
+bool CChevPanel::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ CChevCode chevCode;
+ chevCode._chevCode = _chevCode;
+ CCheckChevCode checkCode;
+ checkCode.execute(this);
+ CClearChevPanelBits panelBits;
+ panelBits.execute(this, nullptr, MSGFLAG_SCAN);
+ CSetChevPanelButtonsMsg setMsg;
+ setMsg._chevCode = checkCode._chevCode;
+ setMsg.execute(this);
+
+ return true;
+}
+
+bool CChevPanel::SetChevPanelBitMsg(CSetChevPanelBitMsg *msg) {
+ _chevCode = (_chevCode & ~(1 << msg->_value1)) | (msg->_value2 << msg->_value1);
+ return true;
+}
+
+bool CChevPanel::MouseDragEndMsg(CMouseDragEndMsg *msg) {
+ setPosition(msg->_mousePos - _startPos);
+ return true;
+}
+
+bool CChevPanel::ClearChevPanelBits(CClearChevPanelBits *msg) {
+ CSetChevPanelButtonsMsg setMsg;
+ setMsg._chevCode = 0;
+ setMsg.execute(this);
+
+ return true;
+}
+
+bool CChevPanel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CChevPanel::SetChevPanelButtonsMsg(CSetChevPanelButtonsMsg *msg) {
+ _chevCode = msg->_chevCode;
+ CSetChevButtonImageMsg setMsg;
+ setMsg._value2 = 1;
+ setMsg.execute(this, nullptr, MSGFLAG_SCAN);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/chev_panel.h b/engines/titanic/game/chev_panel.h
index 99b5501ac2..bcfb920221 100644
--- a/engines/titanic/game/chev_panel.h
+++ b/engines/titanic/game/chev_panel.h
@@ -28,13 +28,21 @@
namespace Titanic {
class CChevPanel : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+ bool MouseDragMoveMsg(CMouseDragMoveMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool SetChevPanelBitMsg(CSetChevPanelBitMsg *msg);
+ bool MouseDragEndMsg(CMouseDragEndMsg *msg);
+ bool ClearChevPanelBits(CClearChevPanelBits *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool SetChevPanelButtonsMsg(CSetChevPanelButtonsMsg *msg);
public:
- int _fieldBC;
- int _fieldC0;
- int _fieldC4;
+ Point _startPos;
+ int _chevCode;
public:
CLASSDEF;
- CChevPanel() : _fieldBC(0), _fieldC0(0), _fieldC4(0) {}
+ CChevPanel() : _chevCode(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/chicken_cooler.cpp b/engines/titanic/game/chicken_cooler.cpp
index 29232e10bf..d10405de38 100644
--- a/engines/titanic/game/chicken_cooler.cpp
+++ b/engines/titanic/game/chicken_cooler.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/game/chicken_cooler.h"
+#include "titanic/carry/chicken.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CChickenCooler, CGameObject)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CChickenCooler::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldBC, indent);
@@ -41,7 +47,32 @@ void CChickenCooler::load(SimpleFile *file) {
}
bool CChickenCooler::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CChickenCoolor::handlEvent");
+ if (_fieldC0) {
+ CGameObject *obj = getMailManFirstObject();
+ if (obj) {
+ // WORKAROUND: Redundant loop for chicken in originalhere
+ } else {
+ getNextMail(nullptr);
+ if (CChicken::_v1 > _fieldBC)
+ CChicken::_v1 = _fieldBC;
+ }
+ }
+
+ return true;
+}
+
+bool CChickenCooler::EnterViewMsg(CEnterViewMsg *msg) {
+ if (!_fieldC0) {
+ for (CGameObject *obj = getMailManFirstObject(); obj;
+ obj = getNextMail(obj)) {
+ if (obj->isEquals("Chicken"))
+ return true;
+ }
+
+ if (CChicken::_v1 > _fieldBC)
+ CChicken::_v1 = _fieldBC;
+ }
+
return true;
}
diff --git a/engines/titanic/game/chicken_cooler.h b/engines/titanic/game/chicken_cooler.h
index 724727b905..54dba90686 100644
--- a/engines/titanic/game/chicken_cooler.h
+++ b/engines/titanic/game/chicken_cooler.h
@@ -29,7 +29,9 @@
namespace Titanic {
class CChickenCooler : public CGameObject {
+ DECLARE_MESSAGE_MAP;
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
int _fieldBC;
int _fieldC0;
diff --git a/engines/titanic/game/chicken_dispensor.cpp b/engines/titanic/game/chicken_dispensor.cpp
index a9bf576765..7fb8fefcda 100644
--- a/engines/titanic/game/chicken_dispensor.cpp
+++ b/engines/titanic/game/chicken_dispensor.cpp
@@ -21,9 +21,21 @@
*/
#include "titanic/game/chicken_dispensor.h"
+#include "titanic/core/project_item.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CChickenDispensor, CBackground)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+ ON_MESSAGE(TurnOff)
+END_MESSAGE_MAP()
+
CChickenDispensor::CChickenDispensor() : CBackground(),
_fieldE0(0), _fieldE4(0), _fieldE8(0) {
}
@@ -45,4 +57,133 @@ void CChickenDispensor::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CChickenDispensor::StatusChangeMsg(CStatusChangeMsg *msg) {
+ msg->execute("SGTRestLeverAnimation");
+ int v1 = _fieldE8 ? 0 : _fieldE4;
+ CPetControl *pet = getPetControl();
+ CGameObject *obj;
+
+ for (obj = pet->getFirstObject(); obj; obj = pet->getNextObject(obj)) {
+ if (obj->isEquals("Chicken")) {
+ petDisplayMessage(1, "Chickens are allocated on a one-per-customer basis.");
+ return true;
+ }
+ }
+
+ for (obj = getMailManFirstObject(); obj; obj = getNextMail(obj)) {
+ if (obj->isEquals("Chicken")) {
+ petDisplayMessage(1, "Chickens are allocated on a one-per-customer basis.");
+ return true;
+ }
+ }
+
+ if (v1 == 1 || v1 == 2)
+ _fieldE8 = 1;
+
+ switch (v1) {
+ case 0:
+ petDisplayMessage(1, "Only one piece of chicken per passenger. Thank you.");
+ break;
+ case 1:
+ setVisible(true);
+ if (_fieldE0) {
+ playMovie(0, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("z#400.wav");
+ _fieldE4 = 0;
+ } else {
+ playMovie(12, 16, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _fieldE8 = 1;
+ _fieldE4 = 0;
+ }
+ break;
+
+ case 2:
+ setVisible(true);
+ if (_fieldE0) {
+ playMovie(0, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("z#400.wav");
+ } else {
+ playMovie(12, 16, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _fieldE8 = 1;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CChickenDispensor::MovieEndMsg(CMovieEndMsg *msg) {
+ if (getMovieFrame() == 16) {
+ playSound("b#50.wav", 50);
+ CActMsg actMsg("Dispense Chicken");
+ actMsg.execute("Chicken");
+ } else if (_fieldE8) {
+ _cursorId = CURSOR_ARROW;
+ loadFrame(0);
+ setVisible(false);
+ if (_fieldE4 == 2)
+ _fieldE8 = 0;
+ } else {
+ loadFrame(0);
+ setVisible(false);
+ changeView("SgtLobby.Node 1.N");
+ }
+
+ return true;
+}
+
+bool CChickenDispensor::ActMsg(CActMsg *msg) {
+ if (msg->_action == "EnableObject")
+ _fieldE0 = 0;
+ else if (msg->_action == "DisableObject")
+ _fieldE0 = 1;
+ else if (msg->_action == "IncreaseQuantity")
+ _fieldE4 = 2;
+ else if (msg->_action == "DecreaseQuantity")
+ _fieldE4 = 1;
+
+ return true;
+}
+
+bool CChickenDispensor::LeaveViewMsg(CLeaveViewMsg *msg) {
+ return true;
+}
+
+bool CChickenDispensor::EnterViewMsg(CEnterViewMsg *msg) {
+ playSound("b#51.wav");
+ _fieldE8 = 0;
+ _cursorId = CURSOR_ARROW;
+ return true;
+}
+
+bool CChickenDispensor::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (getMovieFrame() == 16) {
+ setVisible(false);
+ loadFrame(0);
+ _cursorId = CURSOR_ARROW;
+ _fieldE8 = 1;
+
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("Chicken");
+ CPassOnDragStartMsg passMsg(msg->_mousePos, 1);
+ passMsg.execute("Chicken");
+
+ msg->_dragItem = getRoot()->findByName("Chicken");
+ }
+
+ return true;
+}
+
+bool CChickenDispensor::TurnOff(CTurnOff *msg) {
+ if (getMovieFrame() == 16)
+ setVisible(false);
+ playMovie(16, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _fieldE8 = 0;
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/chicken_dispensor.h b/engines/titanic/game/chicken_dispensor.h
index d86b850871..5e3ba47ee8 100644
--- a/engines/titanic/game/chicken_dispensor.h
+++ b/engines/titanic/game/chicken_dispensor.h
@@ -28,6 +28,14 @@
namespace Titanic {
class CChickenDispensor : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+ bool TurnOff(CTurnOff *msg);
public:
int _fieldE0;
int _fieldE4;
diff --git a/engines/titanic/game/close_broken_pel.cpp b/engines/titanic/game/close_broken_pel.cpp
index d27441ac96..c234590849 100644
--- a/engines/titanic/game/close_broken_pel.cpp
+++ b/engines/titanic/game/close_broken_pel.cpp
@@ -24,16 +24,26 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CCloseBrokenPel, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CCloseBrokenPel::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string3, indent);
+ file->writeQuotedLine(_target, indent);
CBackground::save(file, indent);
}
void CCloseBrokenPel::load(SimpleFile *file) {
file->readNumber();
- _string3 = file->readString();
+ _target = file->readString();
CBackground::load(file);
}
+bool CCloseBrokenPel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CActMsg actMsg("Close");
+ actMsg.execute(_target);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/close_broken_pel.h b/engines/titanic/game/close_broken_pel.h
index aacda6c002..4bd66255df 100644
--- a/engines/titanic/game/close_broken_pel.h
+++ b/engines/titanic/game/close_broken_pel.h
@@ -28,8 +28,10 @@
namespace Titanic {
class CCloseBrokenPel : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
- CString _string3;
+ CString _target;
public:
CLASSDEF;
diff --git a/engines/titanic/game/code_wheel.cpp b/engines/titanic/game/code_wheel.cpp
index d8ce48e390..94ee25435a 100644
--- a/engines/titanic/game/code_wheel.cpp
+++ b/engines/titanic/game/code_wheel.cpp
@@ -24,13 +24,20 @@
namespace Titanic {
-CodeWheel::CodeWheel() : CBomb(), _field108(0), _field10C(4), _field110(0) {
+BEGIN_MESSAGE_MAP(CodeWheel, CBomb)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
+CodeWheel::CodeWheel() : CBomb(), _field108(0), _state(4), _field110(0) {
}
void CodeWheel::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_field108, indent);
- file->writeNumberLine(_field10C, indent);
+ file->writeNumberLine(_state, indent);
file->writeNumberLine(_field110, indent);
CBomb::save(file, indent);
@@ -39,10 +46,63 @@ void CodeWheel::save(SimpleFile *file, int indent) {
void CodeWheel::load(SimpleFile *file) {
file->readNumber();
_field108 = file->readNumber();
- _field10C = file->readNumber();
+ _state = file->readNumber();
_field110 = file->readNumber();
CBomb::load(file);
}
+bool CodeWheel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ static const int START_FRAMES[15] = {
+ 0, 5, 10, 15, 19, 24, 28, 33, 38, 42, 47, 52, 57, 61, 66
+ };
+ static const int END_FRAMES[15] = {
+ 5, 10, 15, 19, 24, 28, 33, 38, 42, 47, 52, 57, 61, 66, 70
+ };
+
+ int yp = _bounds.top + _bounds.height() / 2;
+ if (msg->_mousePos.y > yp) {
+ if (_state == _field108)
+ _field110 = true;
+
+ _state = (_state + 1) % 15;
+ playMovie(START_FRAMES[_state], END_FRAMES[_state],
+ MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ } else {
+ if (_state == _field108)
+ _field110 = true;
+
+ playMovie(START_FRAMES[14 - _state] + 68, END_FRAMES[14 - _state] + 68,
+ MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+
+ _state = (_state <= 0) ? 14 : _state - 1;
+ }
+
+ playSound("z#59.wav");
+ return true;
+}
+
+bool CodeWheel::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(24);
+ _state = 4;
+ return true;
+}
+
+bool CodeWheel::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ return true;
+}
+
+bool CodeWheel::MovieEndMsg(CMovieEndMsg *msg) {
+ sleep(200);
+ CStatusChangeMsg changeMsg;
+ changeMsg._newStatus = 0;
+ if (_field110)
+ changeMsg._newStatus = -1;
+ if (_field108 == _state)
+ changeMsg._newStatus = 1;
+ changeMsg.execute("Bomb");
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/code_wheel.h b/engines/titanic/game/code_wheel.h
index 63af97c6fb..e38a45b631 100644
--- a/engines/titanic/game/code_wheel.h
+++ b/engines/titanic/game/code_wheel.h
@@ -28,9 +28,14 @@
namespace Titanic {
class CodeWheel : public CBomb {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
private:
int _field108;
- int _field10C;
+ int _state;
int _field110;
public:
CLASSDEF;
diff --git a/engines/titanic/game/cookie.cpp b/engines/titanic/game/cookie.cpp
index 915bb93b4a..96edca4058 100644
--- a/engines/titanic/game/cookie.cpp
+++ b/engines/titanic/game/cookie.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CCookie, CGameObject)
+ ON_MESSAGE(LeaveNodeMsg)
+ ON_MESSAGE(FreshenCookieMsg)
+END_MESSAGE_MAP()
+
void CCookie::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value1, indent);
@@ -40,4 +45,16 @@ void CCookie::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CCookie::LeaveNodeMsg(CLeaveNodeMsg *msg) {
+ if (_value2)
+ _value1 = 1;
+ return true;
+}
+
+bool CCookie::FreshenCookieMsg(CFreshenCookieMsg *msg) {
+ _value1 = msg->_value2;
+ _value2 = msg->_value1;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/cookie.h b/engines/titanic/game/cookie.h
index 7ae04f1144..2018deeb3e 100644
--- a/engines/titanic/game/cookie.h
+++ b/engines/titanic/game/cookie.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CCookie : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool LeaveNodeMsg(CLeaveNodeMsg *msg);
+ bool FreshenCookieMsg(CFreshenCookieMsg *msg);
public:
int _value1;
int _value2;
diff --git a/engines/titanic/game/credits.cpp b/engines/titanic/game/credits.cpp
index 7078d41a17..d9149f6dd2 100644
--- a/engines/titanic/game/credits.cpp
+++ b/engines/titanic/game/credits.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CCredits, CGameObject)
+ ON_MESSAGE(SignalObject)
+ ON_MESSAGE(TimerMsg)
+END_MESSAGE_MAP()
+
CCredits::CCredits() : CGameObject(), _fieldBC(-1), _fieldC0(1) {
}
@@ -43,4 +48,34 @@ void CCredits::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CCredits::SignalObject(CSignalObject *msg) {
+ petHide();
+ disableMouse();
+ addTimer(50);
+ return true;
+}
+
+bool CCredits::TimerMsg(CTimerMsg *msg) {
+ stopGlobalSound(true, -1);
+ setVisible(true);
+ loadSound("a#16.wav");
+ loadSound("a#24.wav");
+
+ playCutscene(0, 18);
+ playGlobalSound("a#16.wav", -1, false, false, 0);
+ playCutscene(19, 642);
+ playSound("a#24.wav");
+ playCutscene(643, 750);
+
+ COpeningCreditsMsg creditsMsg;
+ creditsMsg.execute("Service Elevator Entity");
+ changeView("EmbLobby.Node 6.S");
+
+ setVisible(false);
+ petShow();
+ enableMouse();
+ stopGlobalSound(true, -1);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/credits.h b/engines/titanic/game/credits.h
index fa9794b6de..23fd25584d 100644
--- a/engines/titanic/game/credits.h
+++ b/engines/titanic/game/credits.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CCredits : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool SignalObject(CSignalObject *msg);
+ bool TimerMsg(CTimerMsg *msg);
public:
int _fieldBC, _fieldC0;
public:
diff --git a/engines/titanic/game/credits_button.cpp b/engines/titanic/game/credits_button.cpp
index 90bb1b5ebe..ee8f7bb329 100644
--- a/engines/titanic/game/credits_button.cpp
+++ b/engines/titanic/game/credits_button.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CCreditsButton, CBackground)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
CCreditsButton::CCreditsButton() : CBackground(), _fieldE0(1) {
}
@@ -39,4 +44,19 @@ void CCreditsButton::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CCreditsButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ return true;
+}
+
+bool CCreditsButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_fieldE0) {
+ playSound("a#20.wav");
+ CSignalObject signalMsg;
+ signalMsg._numValue = 1;
+ signalMsg.execute("CreditsPlayer");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/credits_button.h b/engines/titanic/game/credits_button.h
index 5e0bf96677..4a53083195 100644
--- a/engines/titanic/game/credits_button.h
+++ b/engines/titanic/game/credits_button.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CCreditsButton : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
int _fieldE0;
public:
diff --git a/engines/titanic/game/desk_click_responder.cpp b/engines/titanic/game/desk_click_responder.cpp
index d9b2cb64b4..0650b3a1f5 100644
--- a/engines/titanic/game/desk_click_responder.cpp
+++ b/engines/titanic/game/desk_click_responder.cpp
@@ -24,10 +24,15 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CDeskClickResponder, CClickResponder)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LoadSuccessMsg)
+END_MESSAGE_MAP()
+
void CDeskClickResponder::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldD4, indent);
- file->writeNumberLine(_fieldD8, indent);
+ file->writeNumberLine(_ticks, indent);
CClickResponder::save(file, indent);
}
@@ -35,9 +40,28 @@ void CDeskClickResponder::save(SimpleFile *file, int indent) {
void CDeskClickResponder::load(SimpleFile *file) {
file->readNumber();
_fieldD4 = file->readNumber();
- _fieldD8 = file->readNumber();
+ _ticks = file->readNumber();
CClickResponder::load(file);
}
+bool CDeskClickResponder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ _fieldD4 = (_fieldD4 + 1) % 3;
+ if (_fieldD4)
+ return CClickResponder::MouseButtonDownMsg(msg);
+
+ uint ticks = getTicksCount();
+ if (!_ticks || ticks > (_ticks + 4000)) {
+ playSound("a#22.wav");
+ _ticks = ticks;
+ }
+
+ return true;
+}
+
+bool CDeskClickResponder::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ _ticks = 0;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/desk_click_responder.h b/engines/titanic/game/desk_click_responder.h
index 12825ba9de..13cf7f4b87 100644
--- a/engines/titanic/game/desk_click_responder.h
+++ b/engines/titanic/game/desk_click_responder.h
@@ -28,9 +28,12 @@
namespace Titanic {
class CDeskClickResponder : public CClickResponder {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
protected:
int _fieldD4;
- int _fieldD8;
+ uint _ticks;
public:
CLASSDEF;
diff --git a/engines/titanic/game/doorbot_elevator_handler.cpp b/engines/titanic/game/doorbot_elevator_handler.cpp
index 13fc368137..39978e9ed7 100644
--- a/engines/titanic/game/doorbot_elevator_handler.cpp
+++ b/engines/titanic/game/doorbot_elevator_handler.cpp
@@ -24,24 +24,32 @@
namespace Titanic {
-int CDoorbotElevatorHandler::_v1;
+BEGIN_MESSAGE_MAP(CDoorbotElevatorHandler, CGameObject)
+ ON_MESSAGE(EnterNodeMsg)
+END_MESSAGE_MAP()
void CDoorbotElevatorHandler::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value, indent);
- file->writeNumberLine(_v1, indent);
+ file->writeNumberLine(_called, indent);
CGameObject::save(file, indent);
}
void CDoorbotElevatorHandler::load(SimpleFile *file) {
file->readNumber();
_value = file->readNumber();
- _v1 = file->readNumber();
+ _called = file->readNumber();
CGameObject::load(file);
}
bool CDoorbotElevatorHandler::EnterNodeMsg(CEnterNodeMsg *msg) {
- warning("CDoorbotElevatorHandler::handleEvent");
+ if (!_called) {
+ CDoorbotNeededInElevatorMsg elevatorMsg;
+ elevatorMsg._value = 0;
+ elevatorMsg.execute("Doorbot");
+ _called = true;
+ }
+
return true;
}
diff --git a/engines/titanic/game/doorbot_elevator_handler.h b/engines/titanic/game/doorbot_elevator_handler.h
index 7b39e727e3..f846273f14 100644
--- a/engines/titanic/game/doorbot_elevator_handler.h
+++ b/engines/titanic/game/doorbot_elevator_handler.h
@@ -29,9 +29,10 @@
namespace Titanic {
class CDoorbotElevatorHandler : public CGameObject {
+ DECLARE_MESSAGE_MAP;
bool EnterNodeMsg(CEnterNodeMsg *msg);
private:
- static int _v1;
+ bool _called;
int _value;
public:
CLASSDEF;
diff --git a/engines/titanic/game/doorbot_home_handler.cpp b/engines/titanic/game/doorbot_home_handler.cpp
index b848308845..92898ca626 100644
--- a/engines/titanic/game/doorbot_home_handler.cpp
+++ b/engines/titanic/game/doorbot_home_handler.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CDoorbotHomeHandler, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
CDoorbotHomeHandler::CDoorbotHomeHandler() {
}
@@ -37,4 +41,10 @@ void CDoorbotHomeHandler::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CDoorbotHomeHandler::EnterViewMsg(CEnterViewMsg *msg) {
+ CDoorbotNeededInHomeMsg neededMsg;
+ neededMsg.execute("Doorbot");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/doorbot_home_handler.h b/engines/titanic/game/doorbot_home_handler.h
index 99ba6d37a9..10552f2b87 100644
--- a/engines/titanic/game/doorbot_home_handler.h
+++ b/engines/titanic/game/doorbot_home_handler.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CDoorbotHomeHandler : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
public:
CLASSDEF;
CDoorbotHomeHandler();
diff --git a/engines/titanic/game/ear_sweet_bowl.cpp b/engines/titanic/game/ear_sweet_bowl.cpp
index 0f7069356d..646b95f0b4 100644
--- a/engines/titanic/game/ear_sweet_bowl.cpp
+++ b/engines/titanic/game/ear_sweet_bowl.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/game/ear_sweet_bowl.h"
+#include "titanic/core/room_item.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEarSweetBowl, CSweetBowl)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(ReplaceBowlAndNutsMsg)
+END_MESSAGE_MAP()
+
void CEarSweetBowl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSweetBowl::save(file, indent);
@@ -34,4 +41,30 @@ void CEarSweetBowl::load(SimpleFile *file) {
CSweetBowl::load(file);
}
+bool CEarSweetBowl::MovieEndMsg(CMovieEndMsg *msg) {
+ CIsEarBowlPuzzleDone doneMsg;
+ doneMsg.execute(findRoom());
+
+ if (!doneMsg._value) {
+ CPetControl *pet = getPetControl();
+ if (pet)
+ pet->hasRoomFlags();
+
+ CIsParrotPresentMsg parrotMsg;
+ parrotMsg.execute(findRoom());
+
+ if (parrotMsg._value) {
+ CNutPuzzleMsg nutMsg("Jiggle");
+ nutMsg.execute("NutsParrotPlayer");
+ }
+ }
+
+ return true;
+}
+
+bool CEarSweetBowl::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/ear_sweet_bowl.h b/engines/titanic/game/ear_sweet_bowl.h
index 3f41950e47..1324ed224a 100644
--- a/engines/titanic/game/ear_sweet_bowl.h
+++ b/engines/titanic/game/ear_sweet_bowl.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CEarSweetBowl : public CSweetBowl {
+ DECLARE_MESSAGE_MAP;
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/eject_phonograph_button.cpp b/engines/titanic/game/eject_phonograph_button.cpp
index 4657f04126..b2ff441ef8 100644
--- a/engines/titanic/game/eject_phonograph_button.cpp
+++ b/engines/titanic/game/eject_phonograph_button.cpp
@@ -24,24 +24,57 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEjectPhonographButton, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(CylinderHolderReadyMsg)
+END_MESSAGE_MAP()
+
void CEjectPhonographButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeQuotedLine(_string3, indent);
- file->writeQuotedLine(_string4, indent);
+ file->writeNumberLine(_ejected, indent);
+ file->writeNumberLine(_readyFlag, indent);
+ file->writeQuotedLine(_soundName, indent);
+ file->writeQuotedLine(_readySoundName, indent);
CBackground::save(file, indent);
}
void CEjectPhonographButton::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
- _string3 = file->readString();
- _string4 = file->readString();
+ _ejected = file->readNumber();
+ _readyFlag = file->readNumber();
+ _soundName = file->readString();
+ _readySoundName = file->readString();
CBackground::load(file);
}
+bool CEjectPhonographButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CQueryPhonographState queryMsg;
+ queryMsg.execute(getParent(), nullptr, MSGFLAG_SCAN);
+
+ if (!_ejected && !queryMsg._value) {
+ loadFrame(1);
+ playSound(_soundName);
+ _readyFlag = true;
+
+ CEjectCylinderMsg ejectMsg;
+ ejectMsg.execute(getParent(), nullptr, MSGFLAG_SCAN);
+ _ejected = true;
+ }
+
+ return true;
+}
+
+bool CEjectPhonographButton::CylinderHolderReadyMsg(CCylinderHolderReadyMsg *msg) {
+ if (_readyFlag) {
+ loadFrame(0);
+ playSound(_readySoundName);
+ _readyFlag = 0;
+ }
+
+ _ejected = false;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/eject_phonograph_button.h b/engines/titanic/game/eject_phonograph_button.h
index 5f5da8053e..df8e602468 100644
--- a/engines/titanic/game/eject_phonograph_button.h
+++ b/engines/titanic/game/eject_phonograph_button.h
@@ -28,14 +28,17 @@
namespace Titanic {
class CEjectPhonographButton : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool CylinderHolderReadyMsg(CCylinderHolderReadyMsg *msg);
public:
- int _fieldE0;
- int _fieldE4;
- CString _string3;
- CString _string4;
+ bool _ejected;
+ bool _readyFlag;
+ CString _soundName;
+ CString _readySoundName;
public:
CLASSDEF;
- CEjectPhonographButton() : CBackground(), _fieldE0(0), _fieldE4(0) {}
+ CEjectPhonographButton() : CBackground(), _ejected(false), _readyFlag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/elevator_action_area.cpp b/engines/titanic/game/elevator_action_area.cpp
index 1cbff8d64d..d59c9b9e7a 100644
--- a/engines/titanic/game/elevator_action_area.cpp
+++ b/engines/titanic/game/elevator_action_area.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/game/elevator_action_area.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CElevatorActionArea, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CElevatorActionArea::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value, indent);
@@ -36,4 +41,10 @@ void CElevatorActionArea::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CElevatorActionArea::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CServiceElevatorMsg elevMsg(_value);
+ elevMsg.execute(findRoom()->findByName("Service Elevator Entity"));
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/elevator_action_area.h b/engines/titanic/game/elevator_action_area.h
index 6c756fb95f..75d3a06d29 100644
--- a/engines/titanic/game/elevator_action_area.h
+++ b/engines/titanic/game/elevator_action_area.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CElevatorActionArea : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
int _value;
public:
diff --git a/engines/titanic/game/emma_control.cpp b/engines/titanic/game/emma_control.cpp
index 814cb44d79..e3ba7cc42c 100644
--- a/engines/titanic/game/emma_control.cpp
+++ b/engines/titanic/game/emma_control.cpp
@@ -21,27 +21,46 @@
*/
#include "titanic/game/emma_control.h"
+#include "titanic/core/room_item.h"
+#include "titanic/sound/auto_music_player.h"
namespace Titanic {
-int CEmmaControl::_v1;
+BEGIN_MESSAGE_MAP(CEmmaControl, CBackground)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(StatusChangeMsg)
+END_MESSAGE_MAP()
void CEmmaControl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_v1, indent);
- file->writeQuotedLine(_wavFile1, indent);
- file->writeQuotedLine(_wavFile2, indent);
+ file->writeNumberLine(_flag, indent);
+ file->writeQuotedLine(_hiddenSoundName, indent);
+ file->writeQuotedLine(_visibleSoundName, indent);
CBackground::save(file, indent);
}
void CEmmaControl::load(SimpleFile *file) {
file->readNumber();
- _v1 = file->readNumber();
- _wavFile1 = file->readString();
- _wavFile2 = file->readString();
+ _flag = file->readNumber();
+ _hiddenSoundName = file->readString();
+ _visibleSoundName = file->readString();
CBackground::load(file);
}
+bool CEmmaControl::EnterViewMsg(CEnterViewMsg *msg) {
+ setVisible(_flag);
+ return true;
+}
+
+bool CEmmaControl::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _flag = !_flag;
+ setVisible(_flag);
+ CChangeMusicMsg changeMsg(_flag ? _visibleSoundName : _hiddenSoundName, 0);
+ changeMsg.execute(findRoom(), CAutoMusicPlayer::_type,
+ MSGFLAG_SCAN | MSGFLAG_BREAK_IF_HANDLED | MSGFLAG_CLASS_DEF);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/emma_control.h b/engines/titanic/game/emma_control.h
index 721660f61e..e4032ca1a5 100644
--- a/engines/titanic/game/emma_control.h
+++ b/engines/titanic/game/emma_control.h
@@ -28,13 +28,18 @@
namespace Titanic {
class CEmmaControl : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
private:
- static int _v1;
+ bool _flag;
- CString _wavFile1, _wavFile2;
+ CString _hiddenSoundName;
+ CString _visibleSoundName;
public:
CLASSDEF;
- CEmmaControl() : CBackground(), _wavFile1("b#39.wav"), _wavFile2("b#38.wav") {}
+ CEmmaControl() : CBackground(), _flag(false),
+ _hiddenSoundName("b#39.wav"), _visibleSoundName("b#38.wav") {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/empty_nut_bowl.cpp b/engines/titanic/game/empty_nut_bowl.cpp
index ae9cb35e4d..adee2589f6 100644
--- a/engines/titanic/game/empty_nut_bowl.cpp
+++ b/engines/titanic/game/empty_nut_bowl.cpp
@@ -21,19 +21,58 @@
*/
#include "titanic/game/empty_nut_bowl.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEmptyNutBowl, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(ReplaceBowlAndNutsMsg)
+ ON_MESSAGE(NutPuzzleMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CEmptyNutBowl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CEmptyNutBowl::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CEmptyNutBowl::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_flag) {
+ CNutPuzzleMsg nutMsg("UnlockBowl");
+ nutMsg.execute(getRoom(), nullptr, MSGFLAG_SCAN);
+ _flag = false;
+ }
+
+ return true;
+}
+
+bool CEmptyNutBowl::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) {
+ setVisible(false);
+ _flag = true;
+ return true;
+}
+
+bool CEmptyNutBowl::NutPuzzleMsg(CNutPuzzleMsg *msg) {
+ if (msg->_value == "NutsGone")
+ setVisible(true);
+ return true;
+}
+
+bool CEmptyNutBowl::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!_flag) {
+ msg->execute("Ear1");
+ setVisible(false);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/empty_nut_bowl.h b/engines/titanic/game/empty_nut_bowl.h
index 112e2c6075..d67e75b0aa 100644
--- a/engines/titanic/game/empty_nut_bowl.h
+++ b/engines/titanic/game/empty_nut_bowl.h
@@ -28,11 +28,16 @@
namespace Titanic {
class CEmptyNutBowl : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg);
+ bool NutPuzzleMsg(CNutPuzzleMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CEmptyNutBowl() : CGameObject(), _value(1) {}
+ CEmptyNutBowl() : CGameObject(), _flag(true) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/end_credit_text.cpp b/engines/titanic/game/end_credit_text.cpp
index 6e0c21bbe9..4eee13d3fb 100644
--- a/engines/titanic/game/end_credit_text.cpp
+++ b/engines/titanic/game/end_credit_text.cpp
@@ -24,16 +24,49 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEndCreditText, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(TimerMsg)
+END_MESSAGE_MAP()
+
void CEndCreditText::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CEndCreditText::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CEndCreditText::ActMsg(CActMsg *msg) {
+ playGlobalSound("z#41.wav", -1, false, false, 0);
+ createCredits();
+ _flag = true;
+ return true;
+}
+
+bool CEndCreditText::FrameMsg(CFrameMsg *msg) {
+ if (_flag) {
+ if (_credits) {
+ makeDirty();
+ } else {
+ addTimer(5000);
+ _flag = false;
+ }
+ }
+
+ return true;
+}
+
+bool CEndCreditText::TimerMsg(CTimerMsg *msg) {
+ setGlobalSoundVolume(-4, 2, -1);
+ sleep(1000);
+ quitGame();
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/end_credit_text.h b/engines/titanic/game/end_credit_text.h
index 54c6c7ff73..a0e0078837 100644
--- a/engines/titanic/game/end_credit_text.h
+++ b/engines/titanic/game/end_credit_text.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CEndCreditText : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
private:
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CEndCreditText() : CGameObject(), _value(0) {}
+ CEndCreditText() : CGameObject(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/end_credits.cpp b/engines/titanic/game/end_credits.cpp
index 61640b92ad..f613e5a008 100644
--- a/engines/titanic/game/end_credits.cpp
+++ b/engines/titanic/game/end_credits.cpp
@@ -24,16 +24,41 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEndCredits, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(FrameMsg)
+END_MESSAGE_MAP()
+
void CEndCredits::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CEndCredits::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CEndCredits::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_flag) {
+ deinit();
+ stopGlobalSound(true, -1);
+ _flag = false;
+ } else {
+ loadSound("z#41.wav");
+ playGlobalSound("z#41.wav", -1, false, false, 0);
+ _flag = true;
+ }
+
+ return true;
+}
+
+bool CEndCredits::FrameMsg(CFrameMsg *msg) {
+ if (_flag)
+ makeDirty();
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/end_credits.h b/engines/titanic/game/end_credits.h
index d160bc94e8..257c5b64a7 100644
--- a/engines/titanic/game/end_credits.h
+++ b/engines/titanic/game/end_credits.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CEndCredits : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
public:
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CEndCredits() : CGameObject(), _value(0) {}
+ CEndCredits() : CGameObject(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/end_explode_ship.cpp b/engines/titanic/game/end_explode_ship.cpp
index f7ac36503f..10c80f5863 100644
--- a/engines/titanic/game/end_explode_ship.cpp
+++ b/engines/titanic/game/end_explode_ship.cpp
@@ -24,6 +24,13 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEndExplodeShip, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(MovieFrameMsg)
+END_MESSAGE_MAP()
+
void CEndExplodeShip::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value1, indent);
@@ -40,4 +47,61 @@ void CEndExplodeShip::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CEndExplodeShip::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Arm Bomb") {
+ _value1 = 1;
+ } else if (msg->_action == "Disarm Bomb") {
+ _value1 = 0;
+ } else if (msg->_action == "TakeOff") {
+ loadSound("a#31.wav");
+ loadSound("a#14.wav");
+ playGlobalSound("a#13.wav", -1, true, true, 0);
+ addTimer(1, 10212, 0);
+ }
+
+ return true;
+}
+
+bool CEndExplodeShip::TimerMsg(CTimerMsg *msg) {
+ if (msg->_actionVal == 1) {
+ setVisible(true);
+ playMovie(0, 449, 0);
+ movieEvent(58);
+ playMovie(516, _value1 ? 550 : 551, MOVIE_NOTIFY_OBJECT);
+ }
+
+ if (msg->_actionVal == 3) {
+ setGlobalSoundVolume(-4, 2, -1);
+ CActMsg actMsg(_value1 ? "ExplodeCredits" : "Credits");
+ actMsg.execute("EndGameCredits");
+ }
+
+ if (msg->_action == "Room") {
+ playMovie(550, 583, MOVIE_NOTIFY_OBJECT);
+ movieEvent(551);
+ }
+
+ return true;
+}
+
+bool CEndExplodeShip::MovieEndMsg(CMovieEndMsg *msg) {
+ if (getMovieFrame() == 550) {
+ playSound("z#399.wav");
+ startAnimTimer("Boom", 4200, 0);
+ } else {
+ addTimer(3, 8000, 0);
+ }
+
+ return true;
+}
+
+bool CEndExplodeShip::MovieFrameMsg(CMovieFrameMsg *msg) {
+ if (getMovieFrame() == 58)
+ playSound("a#31.wav", 70);
+ else if (getMovieFrame() == 551)
+ playSound("a#14.wav");
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/end_explode_ship.h b/engines/titanic/game/end_explode_ship.h
index b8159d3ca7..c48f822af8 100644
--- a/engines/titanic/game/end_explode_ship.h
+++ b/engines/titanic/game/end_explode_ship.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CEndExplodeShip : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool MovieFrameMsg(CMovieFrameMsg *msg);
public:
int _value1, _value2;
public:
diff --git a/engines/titanic/game/end_game_credits.cpp b/engines/titanic/game/end_game_credits.cpp
index 2d1aa79b1d..4edcef0a17 100644
--- a/engines/titanic/game/end_game_credits.cpp
+++ b/engines/titanic/game/end_game_credits.cpp
@@ -24,23 +24,64 @@
namespace Titanic {
-CEndGameCredits::CEndGameCredits() : CGameObject(), _fieldBC(0) {
+BEGIN_MESSAGE_MAP(CEndGameCredits, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(TimerMsg)
+END_MESSAGE_MAP()
+
+CEndGameCredits::CEndGameCredits() : CGameObject(), _flag(0),
+ _frameRange(0, 28) {
}
void CEndGameCredits::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writePoint(_pos1, indent);
+ file->writeNumberLine(_flag, indent);
+ file->writePoint(_frameRange, indent);
CGameObject::save(file, indent);
}
void CEndGameCredits::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _pos1 = file->readPoint();
+ _flag = file->readNumber();
+ _frameRange = file->readPoint();
CGameObject::load(file);
}
+bool CEndGameCredits::ActMsg(CActMsg *msg) {
+ if (!_flag) {
+ if (msg->_action == "ExplodeCredits")
+ _frameRange = Point(0, 27);
+ if (msg->_action == "Credits")
+ _frameRange = Point(28, 46);
+
+ changeView("TheEnd.Node 4.N");
+ }
+
+ return true;
+}
+
+bool CEndGameCredits::EnterViewMsg(CEnterViewMsg *msg) {
+ playMovie(_frameRange.x, _frameRange.y, MOVIE_NOTIFY_OBJECT);
+ return true;
+}
+
+bool CEndGameCredits::MovieEndMsg(CMovieEndMsg *msg) {
+ if (getMovieFrame() == 46) {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("CreditsBackdrop");
+ }
+
+ return true;
+}
+
+bool CEndGameCredits::TimerMsg(CTimerMsg *msg) {
+ CActMsg actMsg;
+ actMsg.execute("EndCreditsText");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/end_game_credits.h b/engines/titanic/game/end_game_credits.h
index 5962950737..13a92423f6 100644
--- a/engines/titanic/game/end_game_credits.h
+++ b/engines/titanic/game/end_game_credits.h
@@ -28,9 +28,14 @@
namespace Titanic {
class CEndGameCredits : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
private:
- int _fieldBC;
- Point _pos1;
+ bool _flag;
+ Point _frameRange;
public:
CLASSDEF;
CEndGameCredits();
diff --git a/engines/titanic/game/end_sequence_control.cpp b/engines/titanic/game/end_sequence_control.cpp
index d32b3d1713..033a7752a3 100644
--- a/engines/titanic/game/end_sequence_control.cpp
+++ b/engines/titanic/game/end_sequence_control.cpp
@@ -24,6 +24,13 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEndSequenceControl, CGameObject)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CEndSequenceControl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,8 +41,43 @@ void CEndSequenceControl::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CEndSequenceControl::TimerMsg(CTimerMsg *msg) {
+ switch (msg->_actionVal) {
+ case 1:
+ changeView("TheEnd.Node 2.N");
+ break;
+ case 2: {
+ playSound("ShipFlyingMusic.wav");
+ CActMsg actMsg("TakeOff");
+ actMsg.execute("EndExplodeShip");
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CEndSequenceControl::MovieEndMsg(CMovieEndMsg *msg) {
+ setGlobalSoundVolume(-4, 2, -1);
+ changeView("TheEnd.Node 3.N");
+ addTimer(2, 1000, 0);
+ return true;
+}
+
bool CEndSequenceControl::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("TODO: CEndSequenceControl::handleEvent");
+ petHide();
+ disableMouse();
+ addTimer(1, 1000, 0);
+ playGlobalSound("a#15.wav", -1, true, true, 0);
+ return true;
+}
+
+bool CEndSequenceControl::EnterViewMsg(CEnterViewMsg *msg) {
+ movieSetAudioTiming(true);
+ playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
return true;
}
diff --git a/engines/titanic/game/end_sequence_control.h b/engines/titanic/game/end_sequence_control.h
index 35e9a934e1..223f25186d 100644
--- a/engines/titanic/game/end_sequence_control.h
+++ b/engines/titanic/game/end_sequence_control.h
@@ -29,7 +29,11 @@
namespace Titanic {
class CEndSequenceControl : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool TimerMsg(CTimerMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/fan.cpp b/engines/titanic/game/fan.cpp
index eabaf63568..3fdebbd3ef 100644
--- a/engines/titanic/game/fan.cpp
+++ b/engines/titanic/game/fan.cpp
@@ -24,9 +24,15 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CFan, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CFan::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value1, indent);
+ file->writeNumberLine(_state, indent);
file->writeNumberLine(_value2, indent);
CGameObject::save(file, indent);
@@ -34,10 +40,87 @@ void CFan::save(SimpleFile *file, int indent) {
void CFan::load(SimpleFile *file) {
file->readNumber();
- _value1 = file->readNumber();
+ _state = file->readNumber();
_value2 = file->readNumber();
CGameObject::load(file);
}
+bool CFan::EnterViewMsg(CEnterViewMsg *msg) {
+ switch (_state) {
+ case 0:
+ case 1:
+ loadFrame(0);
+ break;
+ case 2:
+ playMovie(24, 34, MOVIE_REPEAT);
+ break;
+ case 3:
+ playMovie(63, 65, MOVIE_REPEAT);
+ break;
+ }
+
+ return true;
+}
+
+bool CFan::StatusChangeMsg(CStatusChangeMsg *msg) {
+ if (msg->_newStatus >= -1 && msg->_newStatus < 3) {
+ int oldState = _state;
+ _state = msg->_newStatus;
+ switch (_state) {
+ case -1:
+ case 0:
+ if (oldState == 0)
+ loadFrame(0);
+ else if (oldState == 1)
+ playMovie(24, 34, MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT);
+ else if (oldState == 2) {
+ playMovie(66, 79, MOVIE_STOP_PREVIOUS);
+ playMovie(24, 34, MOVIE_NOTIFY_OBJECT);
+ }
+ break;
+
+ case 1:
+ if (oldState == 0)
+ playMovie(24, 34, MOVIE_REPEAT | MOVIE_STOP_PREVIOUS);
+ if (oldState == 2)
+ playMovie(66, 79, MOVIE_NOTIFY_OBJECT | MOVIE_STOP_PREVIOUS);
+ break;
+
+ case 2:
+ if (oldState == 1)
+ playMovie(48, 62, MOVIE_NOTIFY_OBJECT | MOVIE_STOP_PREVIOUS);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ msg->execute("PromDeckFanNoises");
+ return true;
+}
+
+bool CFan::MovieEndMsg(CMovieEndMsg *msg) {
+ switch (_state) {
+ case -1:
+ case 0:
+ loadFrame(0);
+ break;
+
+ case 1:
+ playMovie(24, 34, MOVIE_REPEAT);
+ break;
+
+ case 2:
+ playMovie(63, 65, MOVIE_REPEAT);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/fan.h b/engines/titanic/game/fan.h
index 2c5a2410a8..9cffce8b68 100644
--- a/engines/titanic/game/fan.h
+++ b/engines/titanic/game/fan.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CFan : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
- int _value1, _value2;
+ int _state, _value2;
public:
CLASSDEF;
- CFan() : CGameObject(), _value1(0), _value2(0) {}
+ CFan() : CGameObject(), _state(0), _value2(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/fan_control.cpp b/engines/titanic/game/fan_control.cpp
index a42e4dd5c1..56a1e49dec 100644
--- a/engines/titanic/game/fan_control.cpp
+++ b/engines/titanic/game/fan_control.cpp
@@ -24,14 +24,22 @@
namespace Titanic {
-CFanControl::CFanControl() : CGameObject(), _fieldBC(0),
- _fieldC0(0), _fieldC4(0), _fieldC8(0), _fieldCC(0) {
+BEGIN_MESSAGE_MAP(CFanControl, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(TimerMsg)
+END_MESSAGE_MAP()
+
+CFanControl::CFanControl() : CGameObject(), _state(-1),
+ _enabled(false), _fieldC4(0), _fieldC8(false), _fieldCC(0) {
}
void CFanControl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
+ file->writeNumberLine(_state, indent);
+ file->writeNumberLine(_enabled, indent);
file->writeNumberLine(_fieldC4, indent);
file->writeNumberLine(_fieldC8, indent);
file->writeNumberLine(_fieldCC, indent);
@@ -41,8 +49,8 @@ void CFanControl::save(SimpleFile *file, int indent) {
void CFanControl::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
+ _state = file->readNumber();
+ _enabled = file->readNumber();
_fieldC4 = file->readNumber();
_fieldC8 = file->readNumber();
_fieldCC = file->readNumber();
@@ -50,4 +58,125 @@ void CFanControl::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CFanControl::ActMsg(CActMsg *msg) {
+ if (msg->_action == "EnableObject")
+ _enabled = true;
+ else if (msg->_action == "DisableObject")
+ _enabled = false;
+ else if (msg->_action == "StarlingsDead") {
+ _fieldC4 = 0;
+ dec54();
+ _fieldCC = 0;
+ }
+
+ return true;
+}
+
+bool CFanControl::StatusChangeMsg(CStatusChangeMsg *msg) {
+ if (!_fieldCC) {
+ playSound("z#42.wav");
+ if (_enabled) {
+ switch (msg->_newStatus) {
+ case 1:
+ _fieldC8 = !_fieldC8;
+ if (_fieldC8) {
+ playMovie(6, 8, 0);
+ _state = 0;
+ } else {
+ switch (_state) {
+ case 0:
+ playMovie(4, 6, 0);
+ _state = -1;
+ break;
+ case 1:
+ playMovie(0, 6, 0);
+ break;
+ case 2:
+ playMovie(18, 24, 0);
+ playMovie(0, 6, 0);
+ break;
+ default:
+ break;
+ }
+
+ _state = -1;
+ }
+ break;
+
+ case 2:
+ if (_fieldC8) {
+ _state = (_state + 1) % 4;
+ switch (_state) {
+ case 0:
+ playMovie(18, 24, 0);
+ playMovie(0, 4, 0);
+ break;
+ case 1:
+ playMovie(8, 12, 0);
+ break;
+ case 2:
+ if (_fieldC4) {
+ inc54();
+ _fieldCC = 1;
+ playMovie(12, 18, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ } else {
+ playMovie(12, 18, 0);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = _state;
+ statusMsg.execute("RightFan");
+ } else {
+ petDisplayMessage(1, "Unfortunately this fan controller has blown a fuse.");
+ }
+ }
+
+ return true;
+}
+
+bool CFanControl::EnterViewMsg(CEnterViewMsg *msg) {
+ switch (_state) {
+ case 0:
+ loadFrame(6);
+ break;
+ case 1:
+ loadFrame(4);
+ break;
+ case 2:
+ loadFrame(0);
+ break;
+ case 3:
+ loadFrame(18);
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CFanControl::MovieEndMsg(CMovieEndMsg *msg) {
+ addTimer(2000);
+ return true;
+}
+
+bool CFanControl::TimerMsg(CTimerMsg *msg) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("StarlingPuret");
+ changeView("PromenadeDeck.Node 3.S");
+ changeView("PromenadeDeck.Node 3.E");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/fan_control.h b/engines/titanic/game/fan_control.h
index 4d89adb311..1f7402db12 100644
--- a/engines/titanic/game/fan_control.h
+++ b/engines/titanic/game/fan_control.h
@@ -28,11 +28,17 @@
namespace Titanic {
class CFanControl : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
public:
- int _fieldBC;
- int _fieldC0;
+ int _state;
+ bool _enabled;
int _fieldC4;
- int _fieldC8;
+ bool _fieldC8;
int _fieldCC;
public:
CLASSDEF;
diff --git a/engines/titanic/game/fan_decrease.cpp b/engines/titanic/game/fan_decrease.cpp
index 2049b1ebc9..b0b9cc585f 100644
--- a/engines/titanic/game/fan_decrease.cpp
+++ b/engines/titanic/game/fan_decrease.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CFanDecrease, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CFanDecrease::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +38,11 @@ void CFanDecrease::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CFanDecrease::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 2;
+ statusMsg.execute("FanController");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/fan_decrease.h b/engines/titanic/game/fan_decrease.h
index 765c7d1560..2e90d09a3f 100644
--- a/engines/titanic/game/fan_decrease.h
+++ b/engines/titanic/game/fan_decrease.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CFanDecrease : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/fan_increase.cpp b/engines/titanic/game/fan_increase.cpp
index aa23dd9275..abd2e019d3 100644
--- a/engines/titanic/game/fan_increase.cpp
+++ b/engines/titanic/game/fan_increase.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CFanIncrease, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CFanIncrease::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +38,11 @@ void CFanIncrease::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CFanIncrease::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("FanController");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/fan_increase.h b/engines/titanic/game/fan_increase.h
index 08ec1322cd..7ed74e1847 100644
--- a/engines/titanic/game/fan_increase.h
+++ b/engines/titanic/game/fan_increase.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CFanIncrease : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/fan_noises.cpp b/engines/titanic/game/fan_noises.cpp
index 14177ab64e..c6e6d203dd 100644
--- a/engines/titanic/game/fan_noises.cpp
+++ b/engines/titanic/game/fan_noises.cpp
@@ -21,42 +21,185 @@
*/
#include "titanic/game/fan_noises.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
-CFanNoises::CFanNoises() : CGameObject(), _fieldBC(-1),
- _fieldC0(0), _fieldC4(70), _fieldC8(-1), _fieldCC(0),
- _fieldD0(0), _fieldD4(-1) {
+BEGIN_MESSAGE_MAP(CFanNoises, CGameObject)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(SetVolumeMsg)
+ ON_MESSAGE(LoadSuccessMsg)
+END_MESSAGE_MAP()
+
+CFanNoises::CFanNoises() : CGameObject(), _state(-1),
+ _soundHandle(0), _soundPercent(70), _soundV3(-1), _soundSeconds(0),
+ _stopSeconds(0), _startFlag(true) {
}
void CFanNoises::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
- file->writeNumberLine(_fieldC8, indent);
- file->writeNumberLine(_fieldCC, indent);
- file->writeNumberLine(_fieldD0, indent);
- file->writeNumberLine(_fieldD4, indent);
+ file->writeNumberLine(_state, indent);
+ file->writeNumberLine(_soundHandle, indent);
+ file->writeNumberLine(_soundPercent, indent);
+ file->writeNumberLine(_soundV3, indent);
+ file->writeNumberLine(_soundSeconds, indent);
+ file->writeNumberLine(_stopSeconds, indent);
+ file->writeNumberLine(_startFlag, indent);
CGameObject::save(file, indent);
}
void CFanNoises::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
- _fieldC8 = file->readNumber();
- _fieldCC = file->readNumber();
- _fieldD0 = file->readNumber();
- _fieldD4 = file->readNumber();
+ _state = file->readNumber();
+ _soundHandle = file->readNumber();
+ _soundPercent = file->readNumber();
+ _soundV3 = file->readNumber();
+ _soundSeconds = file->readNumber();
+ _stopSeconds = file->readNumber();
+ _startFlag = file->readNumber();
CGameObject::load(file);
}
bool CFanNoises::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CFanNoises::handleEvent");
+ if (getParent() == msg->_newRoom) {
+ if (_soundHandle != -1) {
+ if (isSoundActive(_soundHandle))
+ stopSound(_soundHandle, _stopSeconds);
+ _soundHandle = -1;
+ _startFlag = false;
+ }
+
+ switch (_state) {
+ case 1:
+ _soundHandle = playSound("b#60.wav", 0, _soundV3, true);
+ setSoundVolume(_soundHandle, _soundPercent, _soundSeconds);
+ _startFlag = true;
+ break;
+ case 2:
+ _soundHandle = playSound("b#58.wav", 0, _soundV3, true);
+ setSoundVolume(_soundHandle, _soundPercent, _soundSeconds);
+ _startFlag = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CFanNoises::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ if (getParent() == msg->_oldRoom && _soundHandle != -1) {
+ if (isSoundActive(_soundHandle))
+ stopSound(_soundHandle, _stopSeconds);
+
+ _soundHandle = -1;
+ _startFlag = false;
+ }
+
+ return true;
+}
+
+bool CFanNoises::StatusChangeMsg(CStatusChangeMsg *msg) {
+ if (msg->_newStatus >= -1 && msg->_newStatus <= 2) {
+ int oldState = _state;
+ _state = msg->_newStatus;
+
+ switch (msg->_newStatus) {
+ case -1:
+ case 0:
+ if (_soundHandle != -1) {
+ if (isSoundActive(_soundHandle))
+ stopSound(_soundHandle, 1);
+ _soundHandle = -1;
+ _startFlag = false;
+ }
+
+ switch (oldState) {
+ case 1:
+ case 2:
+ playSound("b#59.wav", _soundPercent, _soundV3);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case 1:
+ if (_soundHandle != -1) {
+ if (isSoundActive(_soundHandle))
+ stopSound(_soundHandle, 1);
+ _soundHandle = -1;
+ _startFlag = false;
+ }
+
+ switch (oldState) {
+ case 1:
+ case 2:
+ _soundHandle = playSound("b#60.wav", _soundPercent, _soundV3);
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case 2:
+ if (_soundHandle != -1) {
+ if (isSoundActive(_soundHandle))
+ stopSound(_soundHandle, 1);
+ _soundHandle = -1;
+ _startFlag = false;
+ }
+
+ if (oldState == 1) {
+ _soundHandle = playSound("b#58.wav", _soundPercent, _soundV3);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CFanNoises::SetVolumeMsg(CSetVolumeMsg *msg) {
+ _soundPercent = msg->_volume;
+
+ if (_soundHandle != -1 && isSoundActive(_soundHandle))
+ setSoundVolume(_soundHandle, _soundPercent, msg->_secondsTransition);
+
+ return true;
+}
+
+bool CFanNoises::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ if (_startFlag) {
+ _startFlag = false;
+ _soundHandle = -1;
+
+ switch (_state) {
+ case 1:
+ playSound("b#60.wav", 0, _soundV3, true);
+ setSoundVolume(_soundHandle, _soundPercent, _soundSeconds);
+ _startFlag = true;
+ break;
+
+ case 2:
+ playSound("b#58.wav", 0, _soundV3, true);
+ setSoundVolume(_soundHandle, _soundPercent, _soundSeconds);
+ _startFlag = true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
return true;
}
diff --git a/engines/titanic/game/fan_noises.h b/engines/titanic/game/fan_noises.h
index bb2c35989d..56c80c0764 100644
--- a/engines/titanic/game/fan_noises.h
+++ b/engines/titanic/game/fan_noises.h
@@ -29,15 +29,20 @@
namespace Titanic {
class CFanNoises : public CGameObject {
+ DECLARE_MESSAGE_MAP;
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool SetVolumeMsg(CSetVolumeMsg *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
private:
- int _fieldBC;
- int _fieldC0;
- int _fieldC4;
- int _fieldC8;
- int _fieldCC;
- int _fieldD0;
- int _fieldD4;
+ int _state;
+ int _soundHandle;
+ int _soundPercent;
+ int _soundV3;
+ int _soundSeconds;
+ int _stopSeconds;
+ bool _startFlag;
public:
CLASSDEF;
CFanNoises();
diff --git a/engines/titanic/game/floor_indicator.cpp b/engines/titanic/game/floor_indicator.cpp
index 360232c38c..3afb03c59d 100644
--- a/engines/titanic/game/floor_indicator.cpp
+++ b/engines/titanic/game/floor_indicator.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/game/floor_indicator.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CFloorIndicator, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CFloorIndicator::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +39,10 @@ void CFloorIndicator::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CFloorIndicator::EnterViewMsg(CEnterViewMsg *msg) {
+ int floorNum = MAX(1, getPetControl()->getRoomsFloorNum());
+ loadFrame(floorNum - 1);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/floor_indicator.h b/engines/titanic/game/floor_indicator.h
index 066209e52e..38a1757ad0 100644
--- a/engines/titanic/game/floor_indicator.h
+++ b/engines/titanic/game/floor_indicator.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CFloorIndicator : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/games_console.cpp b/engines/titanic/game/games_console.cpp
index b7500f9dd9..40311f70ee 100644
--- a/engines/titanic/game/games_console.cpp
+++ b/engines/titanic/game/games_console.cpp
@@ -24,16 +24,42 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CGamesConsole, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
void CGamesConsole::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
+ file->writeNumberLine(_active, indent);
CBackground::save(file, indent);
}
void CGamesConsole::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
+ _active = file->readNumber();
CBackground::load(file);
}
+bool CGamesConsole::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_active) {
+ playMovie(23, 44, 0);
+ _active = false;
+ } else {
+ playMovie(0, 23, 0);
+ _active = true;
+ }
+
+ return true;
+}
+
+bool CGamesConsole::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (_active) {
+ _active = false;
+ playMovie(23, 44, MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/games_console.h b/engines/titanic/game/games_console.h
index 2b1da70e96..f849fd08cc 100644
--- a/engines/titanic/game/games_console.h
+++ b/engines/titanic/game/games_console.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CGamesConsole : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
- int _fieldE0;
+ bool _active;
public:
CLASSDEF;
- CGamesConsole() : CBackground(), _fieldE0(0) {}
+ CGamesConsole() : CBackground(), _active(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/get_lift_eye2.cpp b/engines/titanic/game/get_lift_eye2.cpp
index 7747f7b0c2..914f306f0e 100644
--- a/engines/titanic/game/get_lift_eye2.cpp
+++ b/engines/titanic/game/get_lift_eye2.cpp
@@ -21,34 +21,80 @@
*/
#include "titanic/game/get_lift_eye2.h"
+#include "titanic/game/transport/lift.h"
+#include "titanic/core/project_item.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
-CString *CGetLiftEye2::_v1;
+BEGIN_MESSAGE_MAP(CGetLiftEye2, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(VisibleMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
+CString *CGetLiftEye2::_destObject;
void CGetLiftEye2::init() {
- _v1 = new CString();
+ _destObject = new CString();
}
void CGetLiftEye2::deinit() {
- delete _v1;
+ delete _destObject;
}
void CGetLiftEye2::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(*_v1, indent);
+ file->writeQuotedLine(*_destObject, indent);
CGameObject::save(file, indent);
}
void CGetLiftEye2::load(SimpleFile *file) {
file->readNumber();
- *_v1 = file->readString();
+ *_destObject = file->readString();
CGameObject::load(file);
}
+bool CGetLiftEye2::ActMsg(CActMsg *msg) {
+ *_destObject = msg->_action;
+ setVisible(true);
+ return true;
+}
+
bool CGetLiftEye2::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CGetLiftEye2::handleEvent");
+ CPetControl *pet = getPetControl();
+ if (pet->getRoomsElevatorNum() == 4 && CLift::_v1 == 1 && !CLift::_v6) {
+ _cursorId = CURSOR_HAND;
+ setVisible(true);
+ } else {
+ _cursorId = CURSOR_ARROW;
+ setVisible(false);
+ }
+
return true;
}
+bool CGetLiftEye2::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(true);
+ _cursorId = CURSOR_HAND;
+ return true;
+}
+
+bool CGetLiftEye2::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (checkPoint(msg->_mousePos, false, true)) {
+ _cursorId = CURSOR_ARROW;
+ setVisible(false);
+ CActMsg actMsg("EyeNotHead");
+ actMsg.execute(*_destObject);
+ CPassOnDragStartMsg dragMsg(msg->_mousePos, 1);
+ dragMsg.execute(*_destObject);
+
+ msg->_dragItem = getRoot()->findByName(*_destObject);
+ return true;
+ } else {
+ return false;
+ }
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/get_lift_eye2.h b/engines/titanic/game/get_lift_eye2.h
index 496784a3c1..c0dd49206e 100644
--- a/engines/titanic/game/get_lift_eye2.h
+++ b/engines/titanic/game/get_lift_eye2.h
@@ -28,9 +28,13 @@
namespace Titanic {
class CGetLiftEye2 : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool VisibleMsg(CVisibleMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
- static CString *_v1;
+ static CString *_destObject;
public:
CLASSDEF;
static void init();
diff --git a/engines/titanic/game/glass_smasher.cpp b/engines/titanic/game/glass_smasher.cpp
index 8c33124a47..2123f2dfd0 100644
--- a/engines/titanic/game/glass_smasher.cpp
+++ b/engines/titanic/game/glass_smasher.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CGlassSmasher, CGameObject)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CGlassSmasher::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +39,18 @@ void CGlassSmasher::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CGlassSmasher::StatusChangeMsg(CStatusChangeMsg *msg) {
+ setVisible(true);
+ playSound("b#40.wav");
+ playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ return true;
+}
+
+bool CGlassSmasher::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute("LongStickDispenser");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/glass_smasher.h b/engines/titanic/game/glass_smasher.h
index 7e38f4e36b..e1eef6f87a 100644
--- a/engines/titanic/game/glass_smasher.h
+++ b/engines/titanic/game/glass_smasher.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CGlassSmasher : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/gondolier/gondolier_base.cpp b/engines/titanic/game/gondolier/gondolier_base.cpp
index 1f7339cf38..f3dc31c9f5 100644
--- a/engines/titanic/game/gondolier/gondolier_base.cpp
+++ b/engines/titanic/game/gondolier/gondolier_base.cpp
@@ -24,12 +24,16 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CGondolierBase, CGameObject)
+ ON_MESSAGE(PuzzleSolvedMsg)
+END_MESSAGE_MAP()
+
int CGondolierBase::_v1;
-int CGondolierBase::_v2;
-int CGondolierBase::_v3;
+bool CGondolierBase::_puzzleSolved;
+int CGondolierBase::_volume1;
int CGondolierBase::_v4;
int CGondolierBase::_v5;
-int CGondolierBase::_v6;
+int CGondolierBase::_volume2;
int CGondolierBase::_v7;
int CGondolierBase::_v8;
int CGondolierBase::_v9;
@@ -38,11 +42,11 @@ int CGondolierBase::_v10;
void CGondolierBase::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_v1, indent);
- file->writeNumberLine(_v2, indent);
- file->writeNumberLine(_v3, indent);
+ file->writeNumberLine(_puzzleSolved, indent);
+ file->writeNumberLine(_volume1, indent);
file->writeNumberLine(_v4, indent);
file->writeNumberLine(_v5, indent);
- file->writeNumberLine(_v6, indent);
+ file->writeNumberLine(_volume2, indent);
file->writeNumberLine(_v7, indent);
file->writeNumberLine(_v8, indent);
file->writeNumberLine(_v9, indent);
@@ -54,11 +58,11 @@ void CGondolierBase::save(SimpleFile *file, int indent) {
void CGondolierBase::load(SimpleFile *file) {
file->readNumber();
_v1 = file->readNumber();
- _v2 = file->readNumber();
- _v3 = file->readNumber();
+ _puzzleSolved = file->readNumber();
+ _volume1 = file->readNumber();
_v4 = file->readNumber();
_v5 = file->readNumber();
- _v6 = file->readNumber();
+ _volume2 = file->readNumber();
_v7 = file->readNumber();
_v8 = file->readNumber();
_v9 = file->readNumber();
@@ -67,4 +71,9 @@ void CGondolierBase::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CGondolierBase::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ _puzzleSolved = true;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/gondolier/gondolier_base.h b/engines/titanic/game/gondolier/gondolier_base.h
index a7ea2d4931..06d77ba85f 100644
--- a/engines/titanic/game/gondolier/gondolier_base.h
+++ b/engines/titanic/game/gondolier/gondolier_base.h
@@ -28,13 +28,15 @@
namespace Titanic {
class CGondolierBase : public CGameObject {
-private:
+ DECLARE_MESSAGE_MAP;
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
+protected:
static int _v1;
- static int _v2;
- static int _v3;
+ static bool _puzzleSolved;
+ static int _volume1;
static int _v4;
static int _v5;
- static int _v6;
+ static int _volume2;
static int _v7;
static int _v8;
static int _v9;
diff --git a/engines/titanic/game/gondolier/gondolier_chest.cpp b/engines/titanic/game/gondolier/gondolier_chest.cpp
index b3e7217502..cf6656732b 100644
--- a/engines/titanic/game/gondolier/gondolier_chest.cpp
+++ b/engines/titanic/game/gondolier/gondolier_chest.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CGondolierChest, CGondolierBase)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CGondolierChest::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGondolierBase::save(file, indent);
@@ -34,4 +40,27 @@ void CGondolierChest::load(SimpleFile *file) {
CGondolierBase::load(file);
}
+bool CGondolierChest::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_v1)
+ playMovie(0, 14, MOVIE_NOTIFY_OBJECT);
+ else if (msg->_mousePos.y < 330)
+ return false;
+ else if (!_v8 && !_v5) {
+ playMovie(14, 29, 0);
+ _v1 = 0;
+ }
+
+ return true;
+}
+
+bool CGondolierChest::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == 14)
+ _v1 = 1;
+ return true;
+}
+
+bool CGondolierChest::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ return false;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/gondolier/gondolier_chest.h b/engines/titanic/game/gondolier/gondolier_chest.h
index d796917371..8f069241a0 100644
--- a/engines/titanic/game/gondolier/gondolier_chest.h
+++ b/engines/titanic/game/gondolier/gondolier_chest.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CGondolierChest : public CGondolierBase {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/gondolier/gondolier_face.cpp b/engines/titanic/game/gondolier/gondolier_face.cpp
index bdab8491ed..d7bcfa3561 100644
--- a/engines/titanic/game/gondolier/gondolier_face.cpp
+++ b/engines/titanic/game/gondolier/gondolier_face.cpp
@@ -24,16 +24,35 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CGondolierFace, CGondolierBase)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(StatusChangeMsg)
+END_MESSAGE_MAP()
+
void CGondolierFace::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
+ file->writeNumberLine(_flag, indent);
CGondolierBase::save(file, indent);
}
void CGondolierFace::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
+ _flag = file->readNumber();
CGondolierBase::load(file);
}
+bool CGondolierFace::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_flag)
+ playMovie(MOVIE_REPEAT);
+ else
+ setVisible(false);
+ return true;
+}
+
+bool CGondolierFace::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _flag = msg->_newStatus != 1;
+ setVisible(_flag);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/gondolier/gondolier_face.h b/engines/titanic/game/gondolier/gondolier_face.h
index 71bdd6d444..b441204d3f 100644
--- a/engines/titanic/game/gondolier/gondolier_face.h
+++ b/engines/titanic/game/gondolier/gondolier_face.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CGondolierFace : public CGondolierBase {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
private:
- int _fieldBC;
+ bool _flag;
public:
CLASSDEF;
- CGondolierFace() : CGondolierBase(), _fieldBC(0) {}
+ CGondolierFace() : CGondolierBase(), _flag(true) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/gondolier/gondolier_mixer.cpp b/engines/titanic/game/gondolier/gondolier_mixer.cpp
index 9b7b72c11b..26deda8bca 100644
--- a/engines/titanic/game/gondolier/gondolier_mixer.cpp
+++ b/engines/titanic/game/gondolier/gondolier_mixer.cpp
@@ -25,20 +25,30 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CGondolierMixer, CGondolierBase)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(SetVolumeMsg)
+ ON_MESSAGE(SignalObject)
+ ON_MESSAGE(LoadSuccessMsg)
+END_MESSAGE_MAP()
+
CGondolierMixer::CGondolierMixer() : CGondolierBase(),
- _string1("c#0.wav"), _string2("c#1.wav"),
- _fieldBC(-1), _fieldC0(-1), _fieldC4(0), _fieldC8(0),
- _fieldE4(0) {
+ _soundName1("c#0.wav"), _soundName2("c#1.wav"),
+ _soundHandle1(-1), _soundHandle2(-1), _fieldC4(0), _fieldC8(0),
+ _fieldE4(false) {
}
void CGondolierMixer::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
+ file->writeNumberLine(_soundHandle1, indent);
+ file->writeNumberLine(_soundHandle2, indent);
file->writeNumberLine(_fieldC4, indent);
file->writeNumberLine(_fieldC8, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeQuotedLine(_string2, indent);
+ file->writeQuotedLine(_soundName1, indent);
+ file->writeQuotedLine(_soundName2, indent);
file->writeNumberLine(_fieldE4, indent);
CGondolierBase::save(file, indent);
@@ -46,12 +56,12 @@ void CGondolierMixer::save(SimpleFile *file, int indent) {
void CGondolierMixer::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
+ _soundHandle1 = file->readNumber();
+ _soundHandle2 = file->readNumber();
_fieldC4 = file->readNumber();
_fieldC8 = file->readNumber();
- _string1 = file->readString();
- _string2 = file->readString();
+ _soundName1 = file->readString();
+ _soundName2 = file->readString();
_fieldE4 = file->readNumber();
CGondolierBase::load(file);
@@ -59,10 +69,126 @@ void CGondolierMixer::load(SimpleFile *file) {
bool CGondolierMixer::EnterRoomMsg(CEnterRoomMsg *msg) {
CRoomItem *parentRoom = dynamic_cast<CRoomItem *>(getParent());
- if (parentRoom == msg->_newRoom)
- msg->execute(parentRoom);
+ if (parentRoom == msg->_newRoom) {
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ }
return true;
}
+bool CGondolierMixer::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ CRoomItem *parentRoom = dynamic_cast<CRoomItem *>(getParent());
+ if (parentRoom == msg->_oldRoom) {
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CGondolierMixer::TurnOn(CTurnOn *msg) {
+ if (!_puzzleSolved) {
+ if (_soundHandle1 == -1) {
+ _soundHandle1 = playSound(_soundName1, _volume1 * _v4 / 10, 0, true);
+ _fieldE4 = true;
+ }
+
+ if (_soundHandle2 == -1) {
+ _soundHandle2 = playSound(_soundName1, _volume2 * _v7 / 10, 0, true);
+ _fieldE4 = true;
+ }
+ }
+
+ return true;
+}
+
+bool CGondolierMixer::TurnOff(CTurnOff *msg) {
+ if (_soundHandle1 != -1) {
+ if (isSoundActive(_soundHandle1))
+ stopSound(_soundHandle1, 2);
+
+ _soundHandle1 = -1;
+ _fieldE4 = false;
+ }
+
+ if (_soundHandle2 != -1) {
+ if (isSoundActive(_soundHandle2))
+ stopSound(_soundHandle2, 2);
+
+ _soundHandle2 = -1;
+ _fieldE4 = false;
+ }
+
+ return true;
+}
+
+bool CGondolierMixer::SetVolumeMsg(CSetVolumeMsg *msg) {
+ if (!_puzzleSolved) {
+ _volume1 = _volume2 = msg->_volume;
+
+ if (_soundHandle1 != -1 && isSoundActive(_soundHandle1))
+ setSoundVolume(_soundHandle1, msg->_volume * _v4 / 10, 2);
+ if (_soundHandle2 != -1 && isSoundActive(_soundHandle2))
+ setSoundVolume(_soundHandle2, msg->_volume * _v7 / 10, 2);
+ }
+
+ return true;
+}
+
+bool CGondolierMixer::SignalObject(CSignalObject *msg) {
+ if (!_puzzleSolved) {
+ if (msg->_strValue == "Fly") {
+ _v4 = CLIP(msg->_numValue, 0, 10);
+
+ if (!_v8) {
+ _v7 = 10 - _v4;
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = _v7;
+ statusMsg.execute("GondolierRightLever");
+ }
+ }
+
+ if (msg->_strValue == "Tos") {
+ _v7 = CLIP(msg->_numValue, 0, 10);
+
+ if (!_v5) {
+ _v4 = 10 - _v7;
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = _v4;
+ statusMsg.execute("GondolierLeftLever");
+ }
+ }
+
+ if (!_v4 && !_v7 && _v5 && _v8) {
+ _puzzleSolved = true;
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("GondolierFace");
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("Mouth");
+
+ playSound("z#47.wav");
+ } else {
+ CSetVolumeMsg volumeMsg(_volume1, 2);
+ volumeMsg.execute(this);
+ }
+ }
+
+ return true;
+}
+
+bool CGondolierMixer::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ if (_fieldE4) {
+ _fieldE4 = 0;
+ _soundHandle1 = _soundHandle2 = -1;
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/gondolier/gondolier_mixer.h b/engines/titanic/game/gondolier/gondolier_mixer.h
index 247e520ba6..167650f5bb 100644
--- a/engines/titanic/game/gondolier/gondolier_mixer.h
+++ b/engines/titanic/game/gondolier/gondolier_mixer.h
@@ -29,15 +29,22 @@
namespace Titanic {
class CGondolierMixer : public CGondolierBase {
+ DECLARE_MESSAGE_MAP;
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool SetVolumeMsg(CSetVolumeMsg *msg);
+ bool SignalObject(CSignalObject *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
private:
- int _fieldBC;
- int _fieldC0;
+ int _soundHandle1;
+ int _soundHandle2;
int _fieldC4;
int _fieldC8;
- CString _string1;
- CString _string2;
- int _fieldE4;
+ CString _soundName1;
+ CString _soundName2;
+ bool _fieldE4;
public:
CLASSDEF;
CGondolierMixer();
diff --git a/engines/titanic/game/gondolier/gondolier_slider.cpp b/engines/titanic/game/gondolier/gondolier_slider.cpp
index eb6b1a9ad8..e7a46eb33d 100644
--- a/engines/titanic/game/gondolier/gondolier_slider.cpp
+++ b/engines/titanic/game/gondolier/gondolier_slider.cpp
@@ -24,11 +24,24 @@
namespace Titanic {
+static const int ARRAY[11] = { 0, 0, 1, 4, 9, 15, 21, 27, 32, 35, 36 };
+
+BEGIN_MESSAGE_MAP(CGondolierSlider, CGondolierBase)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseDragMoveMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MouseDragEndMsg)
+ ON_MESSAGE(IsHookedOnMsg)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(SignalObject)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
CGondolierSlider::CGondolierSlider() : CGondolierBase(),
_fieldBC(0), _fieldC0(0), _fieldC4(0), _fieldC8(0),
- _fieldCC(0), _fieldD0(0), _fieldD4(0), _fieldD8(0),
- _fieldDC(0), _fieldE0(0), _fieldE4(0), _fieldE8(0),
- _fieldEC(0), _string1("NULL"), _fieldFC(0), _field118(0) {
+ _arrayIndex(0), _string1("NULL"), _fieldFC(0), _field118(0) {
}
void CGondolierSlider::save(SimpleFile *file, int indent) {
@@ -37,15 +50,15 @@ void CGondolierSlider::save(SimpleFile *file, int indent) {
file->writeNumberLine(_fieldC0, indent);
file->writeNumberLine(_fieldC4, indent);
file->writeNumberLine(_fieldC8, indent);
- file->writeNumberLine(_fieldCC, indent);
- file->writeNumberLine(_fieldD0, indent);
- file->writeNumberLine(_fieldD4, indent);
- file->writeNumberLine(_fieldD8, indent);
- file->writeNumberLine(_fieldDC, indent);
- file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
- file->writeNumberLine(_fieldCC, indent);
+ file->writeNumberLine(_sliderRect1.left, indent);
+ file->writeNumberLine(_sliderRect1.top, indent);
+ file->writeNumberLine(_sliderRect1.right, indent);
+ file->writeNumberLine(_sliderRect1.bottom, indent);
+ file->writeNumberLine(_sliderRect2.left, indent);
+ file->writeNumberLine(_sliderRect2.top, indent);
+ file->writeNumberLine(_sliderRect2.right, indent);
+ file->writeNumberLine(_sliderRect2.bottom, indent);
+ file->writeNumberLine(_sliderRect1.left, indent);
file->writeQuotedLine(_string1, indent);
file->writeNumberLine(_fieldFC, indent);
file->writeQuotedLine(_string2, indent);
@@ -61,15 +74,15 @@ void CGondolierSlider::load(SimpleFile *file) {
_fieldC0 = file->readNumber();
_fieldC4 = file->readNumber();
_fieldC8 = file->readNumber();
- _fieldCC = file->readNumber();
- _fieldD0 = file->readNumber();
- _fieldD4 = file->readNumber();
- _fieldD8 = file->readNumber();
- _fieldDC = file->readNumber();
- _fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
- _fieldEC = file->readNumber();
+ _sliderRect1.left = file->readNumber();
+ _sliderRect1.top = file->readNumber();
+ _sliderRect1.right = file->readNumber();
+ _sliderRect1.bottom = file->readNumber();
+ _sliderRect2.left = file->readNumber();
+ _sliderRect2.top = file->readNumber();
+ _sliderRect2.right = file->readNumber();
+ _sliderRect2.bottom = file->readNumber();
+ _arrayIndex = file->readNumber();
_string1 = file->readString();
_fieldFC = file->readNumber();
_string2 = file->readString();
@@ -79,4 +92,148 @@ void CGondolierSlider::load(SimpleFile *file) {
CGondolierBase::load(file);
}
+bool CGondolierSlider::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_v1)
+ return false;
+ if (_fieldFC ? _v5 : _v8)
+ return false;
+
+ return _sliderRect1.contains(msg->_mousePos);
+}
+
+bool CGondolierSlider::MouseDragMoveMsg(CMouseDragMoveMsg *msg) {
+ if (!(_fieldFC ? _v5 : _v8)) {
+ int minVal = 0x7FFFFFFF;
+ int foundIndex = -1;
+ int yp = (_sliderRect2.top + _sliderRect2.bottom) / 2
+ + _bounds.top - msg->_mousePos.y;
+
+ for (int idx = 0; idx < 11; ++idx) {
+ int yv = yp + ARRAY[idx];
+ if (yv < 0)
+ yv = -yv;
+ if (yv < minVal) {
+ minVal = yv;
+ foundIndex = idx;
+ }
+ }
+
+ if (foundIndex >= 0) {
+ _arrayIndex = foundIndex;
+ CSignalObject signalMsg;
+ signalMsg.execute(this);
+ }
+ }
+
+ return true;
+}
+
+bool CGondolierSlider::EnterViewMsg(CEnterViewMsg *msg) {
+ CSignalObject signalMsg;
+ signalMsg.execute(this);
+ return true;
+}
+
+bool CGondolierSlider::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!_v1)
+ return false;
+ if (_fieldFC ? _v5 : _v8)
+ return false;
+
+ _field118 = checkStartDragging(msg);
+ return _field118;
+}
+
+bool CGondolierSlider::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _arrayIndex = CLIP(10 - msg->_newStatus, 0, 10);
+ _sliderRect1 = _sliderRect2;
+ _sliderRect1.translate(_bounds.left, _bounds.top);
+ _sliderRect1.translate(0, ARRAY[_arrayIndex]);
+
+ loadFrame(_arrayIndex);
+ return true;
+}
+
+bool CGondolierSlider::MouseDragEndMsg(CMouseDragEndMsg *msg) {
+ _field118 = false;
+ return true;
+}
+
+bool CGondolierSlider::IsHookedOnMsg(CIsHookedOnMsg *msg) {
+ if (_fieldFC ? _v5 : _v8)
+ return false;
+
+ if (!_sliderRect1.intersects(msg->_rect)) {
+ _string2 = CString();
+ msg->_result = false;
+ } else {
+ _string2 = _string1;
+ if (_fieldFC) {
+ _v5 = _v9 = 1;
+ } else {
+ _v8 = _v10 = 1;
+ }
+
+ msg->_result = true;
+ }
+
+ return true;
+}
+
+bool CGondolierSlider::FrameMsg(CFrameMsg *msg) {
+ if (_fieldFC ? _v5 : _v8) {
+ if (_arrayIndex < 10) {
+ ++_arrayIndex;
+ CSignalObject signalMsg;
+ signalMsg.execute(this);
+
+ int yp = 0;
+ if (_arrayIndex > 0)
+ yp = ARRAY[_arrayIndex] - ARRAY[_arrayIndex - 1];
+
+ if (!_string2.empty()) {
+ CTranslateObjectMsg transMsg;
+ transMsg._delta = Point(0, yp);
+ transMsg.execute(_string2);
+ }
+ }
+ } else if (_fieldFC ? _v10 : _v9) {
+ if (!_field118 && !_puzzleSolved && _arrayIndex > 0) {
+ CSignalObject signalMsg;
+ signalMsg.execute(this);
+ }
+ }
+
+ return true;
+}
+
+bool CGondolierSlider::SignalObject(CSignalObject *msg) {
+ _arrayIndex = CLIP(_arrayIndex, 0, 10);
+ _sliderRect1 = _sliderRect2;
+ _sliderRect1.translate(_bounds.left, _bounds.top);
+ _sliderRect1.translate(0, ARRAY[_arrayIndex]);
+ loadFrame(_arrayIndex);
+
+ CSignalObject signalMsg;
+ signalMsg._numValue = 10 - _arrayIndex;
+ signalMsg._strValue = _fieldFC ? "Fly" : "Tos";
+ signalMsg.execute(_string3);
+
+ return true;
+}
+
+bool CGondolierSlider::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Unhook") {
+ if (_fieldFC) {
+ _v5 = _v9 = 0;
+ _v10 = _v8;
+ } else {
+ _v8 = _v10 = 0;
+ _v9 = _v5;
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/gondolier/gondolier_slider.h b/engines/titanic/game/gondolier/gondolier_slider.h
index 0ae14a91a0..d1562f5b2d 100644
--- a/engines/titanic/game/gondolier/gondolier_slider.h
+++ b/engines/titanic/game/gondolier/gondolier_slider.h
@@ -28,25 +28,30 @@
namespace Titanic {
class CGondolierSlider : public CGondolierBase {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseDragMoveMsg(CMouseDragMoveMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MouseDragEndMsg(CMouseDragEndMsg *msg);
+ bool IsHookedOnMsg(CIsHookedOnMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
+ bool SignalObject(CSignalObject *msg);
+ bool ActMsg(CActMsg *msg);
private:
int _fieldBC;
int _fieldC0;
int _fieldC4;
int _fieldC8;
- int _fieldCC;
- int _fieldD0;
- int _fieldD4;
- int _fieldD8;
- int _fieldDC;
- int _fieldE0;
- int _fieldE4;
- int _fieldE8;
- int _fieldEC;
+ Rect _sliderRect1;
+ Rect _sliderRect2;
+ int _arrayIndex;
CString _string1;
int _fieldFC;
CString _string2;
CString _string3;
- int _field118;
+ bool _field118;
public:
CLASSDEF;
CGondolierSlider();
diff --git a/engines/titanic/game/hammer_clip.cpp b/engines/titanic/game/hammer_clip.cpp
index e3f3a09a90..7fb64350af 100644
--- a/engines/titanic/game/hammer_clip.cpp
+++ b/engines/titanic/game/hammer_clip.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/game/hammer_clip.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHammerClip, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CHammerClip::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value, indent);
@@ -36,4 +43,41 @@ void CHammerClip::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CHammerClip::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CHammerClip::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _value = msg->_newStatus == 1;
+ if (_value) {
+ CPuzzleSolvedMsg solvedMsg;
+ solvedMsg.execute("BigHammer");
+ _cursorId = CURSOR_HAND;
+ }
+
+ return true;
+}
+
+bool CHammerClip::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkStartDragging(msg))
+ return false;
+
+ if (_value) {
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute("BigHammer");
+ CPassOnDragStartMsg passMsg(msg->_mousePos, 1);
+ passMsg.execute("BigHammer");
+
+ msg->_dragItem = getRoot()->findByName("BigHammer");
+
+ CActMsg actMsg("HammerTaken");
+ actMsg.execute("HammerDispensor");
+ actMsg.execute("HammerDispensorButton");
+ _cursorId = CURSOR_ARROW;
+ _value = 0;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/hammer_clip.h b/engines/titanic/game/hammer_clip.h
index 7f5c5ab5f8..4af58c22a5 100644
--- a/engines/titanic/game/hammer_clip.h
+++ b/engines/titanic/game/hammer_clip.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CHammerClip : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
int _value;
public:
diff --git a/engines/titanic/game/hammer_dispensor.cpp b/engines/titanic/game/hammer_dispensor.cpp
index 440fe1bc7b..bc6a3d5ad8 100644
--- a/engines/titanic/game/hammer_dispensor.cpp
+++ b/engines/titanic/game/hammer_dispensor.cpp
@@ -24,15 +24,22 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHammerDispensor, CBackground)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
CHammerDispensor::CHammerDispensor() : CBackground(),
- _fieldE0(0), _fieldE4(0), _fieldE8(0) {
+ _fieldE0(false), _fieldE4(true), _state(0) {
}
void CHammerDispensor::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldE0, indent);
file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
+ file->writeNumberLine(_state, indent);
CBackground::save(file, indent);
}
@@ -41,9 +48,57 @@ void CHammerDispensor::load(SimpleFile *file) {
file->readNumber();
_fieldE0 = file->readNumber();
_fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
+ _state = file->readNumber();
CBackground::load(file);
}
+bool CHammerDispensor::ActMsg(CActMsg *msg) {
+ if (msg->_action == "DispenseHammer" && !_fieldE0) {
+ _state = 1;
+ playMovie(15, 31, MOVIE_NOTIFY_OBJECT);
+ _fieldE0 = true;
+ }
+
+ if (msg->_action == "HammerTaken" && _fieldE0)
+ loadFrame(32);
+
+ return true;
+}
+
+bool CHammerDispensor::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_fieldE4) {
+ playMovie(7, 14, 0);
+ _fieldE4 = false;
+ }
+
+ return true;
+}
+
+bool CHammerDispensor::LeaveViewMsg(CLeaveViewMsg *msg) {
+ _fieldE4 = true;
+ _fieldE0 = 0;
+ _state = 2;
+
+ if (_fieldE0)
+ playMovie(32, 50, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ else
+ playMovie(0, 7, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ return true;
+}
+
+bool CHammerDispensor::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_state == 1) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("HammerClip");
+ } else if (_state == 2) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 2;
+ statusMsg.execute("HammerClip");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/hammer_dispensor.h b/engines/titanic/game/hammer_dispensor.h
index e1b30d9045..2383a3349e 100644
--- a/engines/titanic/game/hammer_dispensor.h
+++ b/engines/titanic/game/hammer_dispensor.h
@@ -28,10 +28,15 @@
namespace Titanic {
class CHammerDispensor : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
private:
- int _fieldE0;
- int _fieldE4;
- int _fieldE8;
+ bool _fieldE0;
+ bool _fieldE4;
+ int _state;
public:
CLASSDEF;
CHammerDispensor();
diff --git a/engines/titanic/game/hammer_dispensor_button.cpp b/engines/titanic/game/hammer_dispensor_button.cpp
index 3346498623..fbda501a24 100644
--- a/engines/titanic/game/hammer_dispensor_button.cpp
+++ b/engines/titanic/game/hammer_dispensor_button.cpp
@@ -24,9 +24,18 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHammerDispensorButton, CStartAction)
+ ON_MESSAGE(PuzzleSolvedMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
CHammerDispensorButton::CHammerDispensorButton() : CStartAction(),
- _fieldF8(0), _fieldFC(0), _field100(0), _field104(56),
- _field108(6), _field10C(0), _field110(0) {
+ _fieldF8(0), _fieldFC(0), _field100(0), _btnPos(Point(56, 6)),
+ _field10C(nullptr), _field110(0) {
}
void CHammerDispensorButton::save(SimpleFile *file, int indent) {
@@ -34,8 +43,8 @@ void CHammerDispensorButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(_fieldF8, indent);
file->writeNumberLine(_fieldFC, indent);
file->writeNumberLine(_field100, indent);
- file->writeNumberLine(_field104, indent);
- file->writeNumberLine(_field108, indent);
+ file->writeNumberLine(_btnPos.x, indent);
+ file->writeNumberLine(_btnPos.y, indent);
file->writeNumberLine(_field110, indent);
CStartAction::save(file, indent);
@@ -46,11 +55,91 @@ void CHammerDispensorButton::load(SimpleFile *file) {
_fieldF8 = file->readNumber();
_fieldFC = file->readNumber();
_field100 = file->readNumber();
- _field104 = file->readNumber();
- _field108 = file->readNumber();
+ _btnPos.x = file->readNumber();
+ _btnPos.y = file->readNumber();
_field110 = file->readNumber();
CStartAction::load(file);
}
+bool CHammerDispensorButton::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ _fieldF8 = 1;
+ return true;
+}
+
+bool CHammerDispensorButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ playSound("z#93.wav");
+ petDisplayMessage(1, "In case of emergency hammer requirement, poke with long stick.");
+ return true;
+}
+
+bool CHammerDispensorButton::ActMsg(CActMsg *msg) {
+ if (msg->_action == "HammerTaken")
+ _field110 = true;
+ return true;
+}
+
+bool CHammerDispensorButton::FrameMsg(CFrameMsg *msg) {
+ if (!_fieldF8)
+ return true;
+
+ if (!_field10C) {
+ CGameObject *obj = getDraggingObject();
+ if (obj) {
+ if (obj->isEquals("Perch") && getView() == findView())
+ _field10C = obj;
+ }
+ }
+
+ if (_field10C) {
+ Point pt(_btnPos.x + _bounds.left, _btnPos.y + _bounds.top);
+ bool flag = checkPoint(pt, true);
+
+ switch (_fieldFC) {
+ case 0:
+ if (flag) {
+ playSound("z#93.wav");
+ if (++_field100 == 5) {
+ if (!_field110) {
+ CActMsg actMsg(_msgAction);
+ actMsg.execute(_msgTarget);
+ }
+
+ setVisible(false);
+ _fieldF8 = 0;
+ _field100 = 0;
+ }
+
+ _fieldFC = 1;
+ }
+ break;
+
+ case 1:
+ if (!flag) {
+ _fieldFC = 0;
+ ++_field100;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CHammerDispensorButton::LeaveViewMsg(CLeaveViewMsg *msg) {
+ _field10C = nullptr;
+ _field100 = 0;
+ _fieldFC = 0;
+ return true;
+}
+
+bool CHammerDispensorButton::EnterViewMsg(CEnterViewMsg *msg) {
+ setVisible(true);
+ _fieldF8 = 1;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/hammer_dispensor_button.h b/engines/titanic/game/hammer_dispensor_button.h
index 36732adb2d..f497b9dae1 100644
--- a/engines/titanic/game/hammer_dispensor_button.h
+++ b/engines/titanic/game/hammer_dispensor_button.h
@@ -28,13 +28,19 @@
namespace Titanic {
class CHammerDispensorButton : public CStartAction {
+ DECLARE_MESSAGE_MAP;
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
int _fieldF8;
int _fieldFC;
int _field100;
- int _field104;
- int _field108;
- int _field10C;
+ Point _btnPos;
+ CGameObject *_field10C;
int _field110;
public:
CLASSDEF;
diff --git a/engines/titanic/game/head_slot.cpp b/engines/titanic/game/head_slot.cpp
index 32650b75e4..f7df02d364 100644
--- a/engines/titanic/game/head_slot.cpp
+++ b/engines/titanic/game/head_slot.cpp
@@ -21,14 +21,27 @@
*/
#include "titanic/game/head_slot.h"
+#include "titanic/core/project_item.h"
+#include "titanic/game/brain_slot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHeadSlot, CGameObject)
+ ON_MESSAGE(AddHeadPieceMsg)
+ ON_MESSAGE(SenseWorkingMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(LoadSuccessMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
int CHeadSlot::_v1;
CHeadSlot::CHeadSlot() : CGameObject(), _string1("NotWorking"), _string2("NULL"),
_fieldBC(0), _fieldD8(0), _fieldDC(27), _fieldE0(56),
- _fieldE4(82), _fieldE8(112), _fieldEC(0) {
+ _fieldE4(82), _fieldE8(112), _fieldEC(false) {
}
void CHeadSlot::save(SimpleFile *file, int indent) {
@@ -63,4 +76,109 @@ void CHeadSlot::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CHeadSlot::AddHeadPieceMsg(CAddHeadPieceMsg *msg) {
+ setVisible(true);
+ _fieldBC = 1;
+ _string2 = msg->_value;
+ playMovie(_fieldDC, _fieldE8, 0);
+ _cursorId = CURSOR_HAND;
+ msg->execute("TitaniaControl");
+ return true;
+}
+
+bool CHeadSlot::SenseWorkingMsg(CSenseWorkingMsg *msg) {
+ if (_fieldEC)
+ playMovie(_fieldE4, _fieldE8, 0);
+
+ _string1 = msg->_value;
+ _fieldEC = false;
+ return true;
+}
+
+bool CHeadSlot::EnterViewMsg(CEnterViewMsg *msg) {
+ setVisible(true);
+ if (_v1)
+ _cursorId = CURSOR_ARROW;
+
+ if (_v1 == 1 || _string1 == "Working") {
+ playMovie(_fieldE0, _fieldE4, MOVIE_GAMESTATE);
+ _fieldEC = true;
+ } else if (_fieldBC) {
+ playMovie(_fieldE0, _fieldE8, MOVIE_GAMESTATE);
+ _fieldEC = false;
+ } else {
+ playMovie(0, _fieldDC, MOVIE_GAMESTATE);
+ }
+
+ addTimer(5000 + getRandomNumber(3000));
+ return true;
+}
+
+bool CHeadSlot::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (getName() == "YepItsASlot") {
+ stopMovie();
+
+ if (_fieldBC) {
+ loadFrame(_fieldE0);
+ playMovie(_fieldE0, _fieldE8, MOVIE_GAMESTATE);
+ _fieldEC = false;
+ } else {
+ loadFrame(_fieldDC);
+ playMovie(_fieldDC, _fieldE0, MOVIE_GAMESTATE);
+ }
+
+ _fieldEC = false;
+ }
+
+ return true;
+}
+
+bool CHeadSlot::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ return true;
+}
+
+bool CHeadSlot::TimerMsg(CTimerMsg *msg) {
+ if (compareViewNameTo("Titania.Node 15.S") && CBrainSlot::_added == 5
+ && _fieldBC == 1) {
+ if (_string1 == "Working" && !_fieldEC) {
+ playMovie(_fieldE0, _fieldE4, 0);
+ _fieldEC = true;
+ } else if (_string1 == "Random") {
+ playMovie(_fieldE0, _fieldE8, 0);
+ }
+ }
+
+ if (compareViewNameTo("Titania.Node 15.S")) {
+ _fieldD8 = 7000 + getRandomNumber(5000);
+ addTimer(_fieldD8);
+ }
+
+ return true;
+}
+
+bool CHeadSlot::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Woken")
+ _v1 = 1;
+ return true;
+}
+
+bool CHeadSlot::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (_fieldBC && !_v1 && checkPoint(msg->_mousePos, false, true)) {
+ CPassOnDragStartMsg passMsg;
+ passMsg._mousePos = msg->_mousePos;
+ passMsg.execute(_string2);
+
+ msg->_dragItem = getRoot()->findByName(_string2);
+ _cursorId = CURSOR_ARROW;
+ _fieldBC = 0;
+ _fieldEC = false;
+ _string2 = "NULL";
+ stopMovie();
+ loadFrame(0);
+ playMovie(0, _fieldDC, 0);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/head_slot.h b/engines/titanic/game/head_slot.h
index 0080411033..2767db3b61 100644
--- a/engines/titanic/game/head_slot.h
+++ b/engines/titanic/game/head_slot.h
@@ -28,6 +28,15 @@
namespace Titanic {
class CHeadSlot : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool AddHeadPieceMsg(CAddHeadPieceMsg *msg);
+ bool SenseWorkingMsg(CSenseWorkingMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
static int _v1;
public:
@@ -39,7 +48,7 @@ public:
int _fieldE0;
int _fieldE4;
int _fieldE8;
- int _fieldEC;
+ bool _fieldEC;
public:
CLASSDEF;
CHeadSlot();
diff --git a/engines/titanic/game/head_smash_event.cpp b/engines/titanic/game/head_smash_event.cpp
index 5b79acf7a5..5ec3d299d5 100644
--- a/engines/titanic/game/head_smash_event.cpp
+++ b/engines/titanic/game/head_smash_event.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHeadSmashEvent, CBackground)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CHeadSmashEvent::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBackground::save(file, indent);
@@ -34,4 +39,18 @@ void CHeadSmashEvent::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CHeadSmashEvent::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PlayToEnd") {
+ setVisible(true);
+ playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
+bool CHeadSmashEvent::MovieEndMsg(CMovieEndMsg *msg) {
+ changeView("CreatorsChamber.Node 1.W");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/head_smash_event.h b/engines/titanic/game/head_smash_event.h
index 912cf36bf3..09fd7a54cc 100644
--- a/engines/titanic/game/head_smash_event.h
+++ b/engines/titanic/game/head_smash_event.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CHeadSmashEvent : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/head_smash_lever.cpp b/engines/titanic/game/head_smash_lever.cpp
index 5a2fe1f4c2..dabed26478 100644
--- a/engines/titanic/game/head_smash_lever.cpp
+++ b/engines/titanic/game/head_smash_lever.cpp
@@ -24,25 +24,78 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CHeadSmashLever, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(LoadSuccessMsg)
+END_MESSAGE_MAP()
+
CHeadSmashLever::CHeadSmashLever() : CBackground(),
- _fieldE0(0), _fieldE4(0), _fieldE8(0) {}
+ _enabled(false), _fieldE4(false), _ticks(0) {}
void CHeadSmashLever::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
+ file->writeNumberLine(_enabled, indent);
file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
+ file->writeNumberLine(_ticks, indent);
CBackground::save(file, indent);
}
void CHeadSmashLever::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
+ _enabled = file->readNumber();
_fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
+ _ticks = file->readNumber();
CBackground::load(file);
}
+bool CHeadSmashLever::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_enabled) {
+ playMovie(0, 14, 0);
+ playSound("z#54.wav");
+ int soundHandle = playSound("z#45.wav");
+ queueSound("z#49.wav", soundHandle);
+ _ticks = getTicksCount();
+ _fieldE4 = true;
+ } else {
+ playMovie(0);
+ playSound("z#56.wav");
+ }
+
+ return true;
+}
+
+bool CHeadSmashLever::ActMsg(CActMsg *msg) {
+ if (msg->_action == "EnableObject")
+ _enabled = true;
+ else if (msg->_action == "DisableObject")
+ _enabled = false;
+
+ return true;
+}
+
+bool CHeadSmashLever::FrameMsg(CFrameMsg *msg) {
+ if (_fieldE4 && msg->_ticks > (_ticks + 750)) {
+ CActMsg actMsg1("CreatorsChamber.Node 1.S");
+ actMsg1.execute("MoveToCreators");
+ CActMsg actMsg2("PlayToEnd");
+ actMsg2.execute("SmashingStatue");
+
+ playSound("b#16.wav");
+ _fieldE4 = false;
+ }
+
+ return true;
+}
+
+bool CHeadSmashLever::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ if (_fieldE4)
+ _ticks = getTicksCount();
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/head_smash_lever.h b/engines/titanic/game/head_smash_lever.h
index b8f04d39de..19de07922a 100644
--- a/engines/titanic/game/head_smash_lever.h
+++ b/engines/titanic/game/head_smash_lever.h
@@ -28,10 +28,15 @@
namespace Titanic {
class CHeadSmashLever : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
public:
- int _fieldE0;
- int _fieldE4;
- int _fieldE8;
+ bool _enabled;
+ bool _fieldE4;
+ uint _ticks;
public:
CLASSDEF;
CHeadSmashLever();
diff --git a/engines/titanic/game/idle_summoner.cpp b/engines/titanic/game/idle_summoner.cpp
index 19d760a8db..5ca3209e28 100644
--- a/engines/titanic/game/idle_summoner.cpp
+++ b/engines/titanic/game/idle_summoner.cpp
@@ -24,10 +24,16 @@
namespace Titanic {
-CIdleSummoner::CIdleSummoner() : CGameObject(), _fieldBC(0x57E40),
- _fieldC0(0xEA60), _fieldC4(0x57E40), _fieldC8(0xEA60),
- _fieldCC(0xEA60), _fieldD0(0xEA60), _fieldD4(0xEA60),
- _fieldD8(0xEA60), _fieldDC(0xEA60) {
+BEGIN_MESSAGE_MAP(CIdleSummoner, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(LoadSuccessMsg)
+END_MESSAGE_MAP()
+
+CIdleSummoner::CIdleSummoner() : CGameObject(), _fieldBC(360000),
+ _fieldC0(60000), _fieldC4(360000), _fieldC8(60000),
+ _fieldCC(0), _fieldD0(0), _fieldD4(0), _fieldD8(0), _ticks(0) {
}
void CIdleSummoner::save(SimpleFile *file, int indent) {
@@ -40,7 +46,7 @@ void CIdleSummoner::save(SimpleFile *file, int indent) {
file->writeNumberLine(_fieldD0, indent);
file->writeNumberLine(_fieldD4, indent);
file->writeNumberLine(_fieldD8, indent);
- file->writeNumberLine(_fieldDC, indent);
+ file->writeNumberLine(_ticks, indent);
CGameObject::save(file, indent);
}
@@ -55,9 +61,70 @@ void CIdleSummoner::load(SimpleFile *file) {
_fieldD0 = file->readNumber();
_fieldD4 = file->readNumber();
_fieldD8 = file->readNumber();
- _fieldDC = file->readNumber();
+ _ticks = file->readNumber();
CGameObject::load(file);
}
+bool CIdleSummoner::EnterViewMsg(CEnterViewMsg *msg) {
+ CActMsg actMsg("Enable");
+ actMsg.execute(this);
+ return true;
+}
+
+bool CIdleSummoner::TimerMsg(CTimerMsg *msg) {
+ uint nodesCtr = getNodeChangedCtr();
+ if (msg->_actionVal == 1 && !petDoorOrBellbotPresent()
+ && nodesCtr > 0 && _fieldD8) {
+ if (!compareRoomNameTo("TopOfWell") && !compareRoomNameTo("EmbLobby"))
+ return true;
+
+ int region = talkGetDialRegion("BellBot", 1);
+ uint delay = region == 1 ? 15000 : 12000;
+ uint enterTicks = MIN(getNodeEnterTicks(), _ticks);
+
+ CString name;
+ uint ticks = getTicksCount() - enterTicks;
+ if (ticks > delay) {
+ if (region == 1 || getRandomNumber(1) == 1) {
+ name = "BellBot";
+ } else {
+ name = "DoorBot";
+ }
+ _fieldD8 = nodesCtr;
+
+ if (getRoom()) {
+ CSummonBotQueryMsg queryMsg(name);
+ if (queryMsg.execute(this)) {
+ CSummonBotMsg summonMsg(name, 1);
+ summonMsg.execute(this);
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CIdleSummoner::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Enable") {
+ if (!_fieldD4)
+ _fieldD4 = addTimer(15000, 15000);
+ } else if (msg->_action == "Disable") {
+ if (_fieldD4 > 0) {
+ stopAnimTimer(_fieldD4);
+ _fieldD4 = 0;
+ }
+ } else if (msg->_action == "DoorbotDismissed" || msg->_action == "BellbotDismissed") {
+ _ticks = getTicksCount();
+ }
+
+ return true;
+}
+
+bool CIdleSummoner::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ _ticks = getTicksCount();
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/idle_summoner.h b/engines/titanic/game/idle_summoner.h
index 1d9fcdd176..0066694b68 100644
--- a/engines/titanic/game/idle_summoner.h
+++ b/engines/titanic/game/idle_summoner.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CIdleSummoner : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
public:
int _fieldBC;
int _fieldC0;
@@ -37,7 +42,7 @@ public:
int _fieldD0;
int _fieldD4;
int _fieldD8;
- int _fieldDC;
+ uint _ticks;
public:
CIdleSummoner();
CLASSDEF;
diff --git a/engines/titanic/game/lemon_dispensor.cpp b/engines/titanic/game/lemon_dispensor.cpp
index 8e1674cb2d..31a04cbeca 100644
--- a/engines/titanic/game/lemon_dispensor.cpp
+++ b/engines/titanic/game/lemon_dispensor.cpp
@@ -24,22 +24,36 @@
namespace Titanic {
-int CLemonDispensor::_v1;
+BEGIN_MESSAGE_MAP(CLemonDispensor, CBackground)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(ChangeSeasonMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
+bool CLemonDispensor::_isSummer;
int CLemonDispensor::_v2;
int CLemonDispensor::_v3;
+CGameObject *CLemonDispensor::_draggingObject;
CLemonDispensor::CLemonDispensor() : CBackground(),
- _fieldE0(0), _fieldE4(9), _fieldE8(15), _fieldEC(0) {
+ _fieldE0(0), _origPt(Point(9, 15)), _fieldEC(0) {
+}
+
+void CLemonDispensor::init() {
+ _isSummer = false;
+ _v2 = 0;
+ _v3 = 0;
+ _draggingObject = nullptr;
}
void CLemonDispensor::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_v1, indent);
+ file->writeNumberLine(_isSummer, indent);
file->writeNumberLine(_v2, indent);
file->writeNumberLine(_v3, indent);
file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
+ file->writeNumberLine(_origPt.x, indent);
+ file->writeNumberLine(_origPt.y, indent);
file->writeNumberLine(_fieldEC, indent);
CBackground::save(file, indent);
@@ -47,15 +61,63 @@ void CLemonDispensor::save(SimpleFile *file, int indent) {
void CLemonDispensor::load(SimpleFile *file) {
file->readNumber();
- _v1 = file->readNumber();
+ _isSummer = file->readNumber();
_v2 = file->readNumber();
_v3 = file->readNumber();
_fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
+ _origPt.x = file->readNumber();
+ _origPt.y = file->readNumber();
_fieldEC = file->readNumber();
CBackground::load(file);
}
+bool CLemonDispensor::FrameMsg(CFrameMsg *msg) {
+ if (_v2 || !_isSummer)
+ return true;
+
+ if (!_draggingObject) {
+ CGameObject *obj = getDraggingObject();
+ if (obj && getView() == findView()) {
+ if (obj->isEquals("Perch")) {
+ petDisplayMessage(1, "This stick is too short to reach the branches.");
+ return true;
+ }
+
+ if (obj->isEquals("LongStick"))
+ _draggingObject = obj;
+ }
+ }
+
+ if (_draggingObject) {
+ Point pt(_origPt.x + _draggingObject->_bounds.left,
+ _origPt.y + _draggingObject->_bounds.top);
+ bool flag = checkPoint(pt, true);
+
+ if (_fieldEC == 0) {
+ if (flag && ++_v3 > 10) {
+ CLemonFallsFromTreeMsg lemonMsg(pt);
+ lemonMsg.execute("Lemon");
+ _v2 = 1;
+ }
+ } else if (_fieldEC == 1 && !flag) {
+ _fieldEC = 0;
+ }
+ }
+
+ return true;
+}
+
+bool CLemonDispensor::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ _isSummer = msg->_season == "Summer";
+ return true;
+}
+
+bool CLemonDispensor::LeaveViewMsg(CLeaveViewMsg *msg) {
+ _draggingObject = nullptr;
+ _v3 = 0;
+ _fieldEC = 0;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/lemon_dispensor.h b/engines/titanic/game/lemon_dispensor.h
index d6315ed620..933e0b6af0 100644
--- a/engines/titanic/game/lemon_dispensor.h
+++ b/engines/titanic/game/lemon_dispensor.h
@@ -28,20 +28,29 @@
namespace Titanic {
class CLemonDispensor : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool FrameMsg(CFrameMsg *msg);
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
private:
- static int _v1;
+ static bool _isSummer;
static int _v2;
static int _v3;
+ static CGameObject *_draggingObject;
int _fieldE0;
- int _fieldE4;
- int _fieldE8;
+ Point _origPt;
int _fieldEC;
public:
CLASSDEF;
CLemonDispensor();
/**
+ * Initialize statics
+ */
+ static void init();
+
+ /**
* Save the data for the class to file
*/
virtual void save(SimpleFile *file, int indent);
diff --git a/engines/titanic/game/light.cpp b/engines/titanic/game/light.cpp
index fd3c446875..65e357047e 100644
--- a/engines/titanic/game/light.cpp
+++ b/engines/titanic/game/light.cpp
@@ -21,9 +21,22 @@
*/
#include "titanic/game/light.h"
+#include "titanic/game/television.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CLight, CBackground)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(LightsMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(EnterRoomMsg)
+END_MESSAGE_MAP()
+
CLight::CLight() : CBackground(), _fieldE0(0), _fieldE4(0),
_fieldE8(0), _fieldEC(0), _fieldF0(0), _fieldF4(0),
_fieldF8(0), _fieldFC(0) {
@@ -57,8 +70,82 @@ void CLight::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CLight::TurnOff(CTurnOff *msg) {
+ setVisible(false);
+ return true;
+}
+
+bool CLight::LightsMsg(CLightsMsg *msg) {
+ if ((msg->_flag2 && _fieldE8) || (msg->_flag3 && _fieldEC)
+ || (msg->_flag1 && _fieldE4) || (msg->_flag4 && _fieldF0)) {
+ setVisible(true);
+ } else {
+ setVisible(false);
+ }
+
+ return true;
+}
+
+bool CLight::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ // WORKAROUND: Original code doesn't seem to do anything
+ return true;
+}
+
+bool CLight::TurnOn(CTurnOn *msg) {
+ setVisible(true);
+ return true;
+}
+
+bool CLight::StatusChangeMsg(CStatusChangeMsg *msg) {
+ CPetControl *pet = getPetControl();
+ bool flag = pet ? pet->isRoom59706() : false;
+
+ if (_fieldFC == 1 && flag) {
+ petDisplayMessage(1, "That light appears to be loose.");
+ playSound("z#144.wav", 70);
+ } else {
+ petDisplayMessage(1, "Lumi-Glow(tm) Lights. They glow in the dark!");
+ playSound("z#62.wav", 70);
+ }
+
+ return true;
+}
+
+bool CLight::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CPetControl *pet = getPetControl();
+ bool flag = pet ? pet->isRoom59706() : false;
+
+ if (_fieldFC == 1 && flag) {
+ petDisplayMessage(1, "That light appears to be loose.");
+ playSound("z#144.wav", 70);
+ } else {
+ petDisplayMessage(1, "Lumi-Glow(tm) Lights. They glow in the dark!");
+ playSound("z#62.wav", 70);
+ }
+
+ return true;
+}
+
+bool CLight::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Eye Removed")
+ _fieldFC = 0;
+
+ return true;
+}
+
bool CLight::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CLight::handleEvent");
+ CPetControl *pet = getPetControl();
+ setVisible(true);
+
+ if (isEquals("6WTL")) {
+ CLightsMsg lightsMsg(1, 1, 1, 1);
+ lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN);
+
+ bool flag = pet ? pet->isRoom59706() : false;
+ if (flag)
+ CTelevision::_turnOn = true;
+ }
+
return true;
}
diff --git a/engines/titanic/game/light.h b/engines/titanic/game/light.h
index 79e4bc400e..68223275e5 100644
--- a/engines/titanic/game/light.h
+++ b/engines/titanic/game/light.h
@@ -29,6 +29,14 @@
namespace Titanic {
class CLight : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOff(CTurnOff *msg);
+ bool LightsMsg(CLightsMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool ActMsg(CActMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
private:
int _fieldE0;
diff --git a/engines/titanic/game/light_switch.cpp b/engines/titanic/game/light_switch.cpp
index 3f5c8d2084..188691033a 100644
--- a/engines/titanic/game/light_switch.cpp
+++ b/engines/titanic/game/light_switch.cpp
@@ -21,10 +21,24 @@
*/
#include "titanic/game/light_switch.h"
+#include "titanic/game/light.h"
+#include "titanic/game/television.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
-int CLightSwitch::_v1;
+BEGIN_MESSAGE_MAP(CLightSwitch, CBackground)
+ ON_MESSAGE(PETUpMsg)
+ ON_MESSAGE(PETDownMsg)
+ ON_MESSAGE(PETLeftMsg)
+ ON_MESSAGE(PETRightMsg)
+ ON_MESSAGE(PETActivateMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(EnterRoomMsg)
+END_MESSAGE_MAP()
+
+bool CLightSwitch::_flag;
CLightSwitch::CLightSwitch() : CBackground(),
_fieldE0(0), _fieldE4(0), _fieldE8(0) {
@@ -34,7 +48,7 @@ void CLightSwitch::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldE0, indent);
file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_v1, indent);
+ file->writeNumberLine(_flag, indent);
file->writeNumberLine(_fieldE8, indent);
CBackground::save(file, indent);
@@ -44,14 +58,94 @@ void CLightSwitch::load(SimpleFile *file) {
file->readNumber();
_fieldE0 = file->readNumber();
_fieldE4 = file->readNumber();
- _v1 = file->readNumber();
+ _flag = file->readNumber();
_fieldE8 = file->readNumber();
CBackground::load(file);
}
+bool CLightSwitch::PETUpMsg(CPETUpMsg *msg) {
+ if (msg->_name == "Light") {
+ CLightsMsg lightsMsg(true, true, false, false);
+ lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN);
+
+ if (_fieldE8)
+ CTelevision::_turnOn = true;
+ }
+
+ return true;
+}
+
+bool CLightSwitch::PETDownMsg(CPETDownMsg *msg) {
+ if (msg->_name == "Light") {
+ CLightsMsg lightsMsg(false, false, true, true);
+ lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN);
+
+ if (_fieldE8)
+ CTelevision::_turnOn = true;
+ }
+
+ return true;
+}
+
+bool CLightSwitch::PETLeftMsg(CPETLeftMsg *msg) {
+ if (msg->_name == "Light") {
+ CLightsMsg lightsMsg(false, true, true, false);
+ lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN);
+
+ if (_fieldE8)
+ CTelevision::_turnOn = true;
+ }
+
+ return true;
+}
+
+bool CLightSwitch::PETRightMsg(CPETRightMsg *msg) {
+ if (msg->_name == "Light") {
+ CLightsMsg lightsMsg(true, false, false, true);
+ lightsMsg.execute("1stClassState", CLight::_type, MSGFLAG_SCAN);
+
+ if (_fieldE8)
+ CTelevision::_turnOn = true;
+ }
+
+ return true;
+}
+
+bool CLightSwitch::PETActivateMsg(CPETActivateMsg *msg) {
+ if (msg->_name == "Light") {
+ if (_flag) {
+ CTurnOff offMsg;
+ offMsg.execute("1stClassState", CLight::_type, MSGFLAG_CLASS_DEF | MSGFLAG_SCAN);
+
+ } else {
+ CTurnOn onMsg;
+ onMsg.execute("1stClassState", CLight::_type, MSGFLAG_CLASS_DEF | MSGFLAG_SCAN);
+ _flag = false;
+ if (_fieldE8)
+ CTelevision::_turnOn = false;
+ }
+ }
+
+ return true;
+}
+
+bool CLightSwitch::EnterViewMsg(CEnterViewMsg *msg) {
+ petSetRemoteTarget();
+ return true;
+}
+
+bool CLightSwitch::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petClear();
+ return true;
+}
+
bool CLightSwitch::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CLightSwitch::handleEvent");
+ _flag = true;
+ CPetControl *pet = getPetControl();
+ if (pet)
+ _fieldE8 = pet->isRoom59706();
+
return true;
}
diff --git a/engines/titanic/game/light_switch.h b/engines/titanic/game/light_switch.h
index ce62d7d68c..f8c01dc8b0 100644
--- a/engines/titanic/game/light_switch.h
+++ b/engines/titanic/game/light_switch.h
@@ -25,13 +25,22 @@
#include "titanic/core/background.h"
#include "titanic/messages/messages.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CLightSwitch : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool PETUpMsg(CPETUpMsg *msg);
+ bool PETDownMsg(CPETDownMsg *msg);
+ bool PETLeftMsg(CPETLeftMsg *msg);
+ bool PETRightMsg(CPETRightMsg *msg);
+ bool PETActivateMsg(CPETActivateMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
public:
- static int _v1;
+ static bool _flag;
private:
int _fieldE0;
int _fieldE4;
diff --git a/engines/titanic/game/little_lift_button.cpp b/engines/titanic/game/little_lift_button.cpp
index 5005cb1757..afda4cac1d 100644
--- a/engines/titanic/game/little_lift_button.cpp
+++ b/engines/titanic/game/little_lift_button.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/game/little_lift_button.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CLittleLiftButton, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CLittleLiftButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value, indent);
@@ -36,4 +42,23 @@ void CLittleLiftButton::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CLittleLiftButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ playSound("z#60.wav");
+ return true;
+}
+
+bool CLittleLiftButton::MovieEndMsg(CMovieEndMsg *msg) {
+ changeView("SecClassLittleLift.Node 1.N");
+
+ CRoomItem *room = getRoom();
+ if (room) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = _value;
+ statusMsg.execute(room, nullptr, MSGFLAG_SCAN);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/little_lift_button.h b/engines/titanic/game/little_lift_button.h
index b14651f4b8..2cbf3b97ff 100644
--- a/engines/titanic/game/little_lift_button.h
+++ b/engines/titanic/game/little_lift_button.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CLittleLiftButton : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
private:
int _value;
public:
diff --git a/engines/titanic/game/long_stick_dispenser.cpp b/engines/titanic/game/long_stick_dispenser.cpp
index cb562ec3ca..08a29f2e4b 100644
--- a/engines/titanic/game/long_stick_dispenser.cpp
+++ b/engines/titanic/game/long_stick_dispenser.cpp
@@ -21,9 +21,21 @@
*/
#include "titanic/game/long_stick_dispenser.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CLongStickDispenser, CGameObject)
+ ON_MESSAGE(PuzzleSolvedMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(VisibleMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CLongStickDispenser::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldBC, indent);
@@ -42,10 +54,97 @@ void CLongStickDispenser::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CLongStickDispenser::PuzzleSolvedMsg(CPuzzleSolvedMsg *msg) {
+ if (!_fieldBC && !_fieldC4 && !_fieldC0) {
+ CStatusChangeMsg statusMsg;
+ statusMsg.execute("ShatterGlass");
+ _fieldC0 = 1;
+ loadFrame(19);
+ } else if (_fieldC0) {
+ playSound("z#63.wav");
+ petDisplayMessage(1, "'This glass is totally and utterly unbreakable.");
+ }
+
+ return true;
+}
+
+bool CLongStickDispenser::MovieEndMsg(CMovieEndMsg *msg) {
+ CPuzzleSolvedMsg puzzleMsg;
+ puzzleMsg.execute("LongStick");
+ _fieldC0 = 1;
+ return true;
+}
+
+bool CLongStickDispenser::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(msg->_visible);
+ return true;
+}
+
bool CLongStickDispenser::EnterRoomMsg(CEnterRoomMsg *msg) {
_fieldC0 = 0;
_fieldC4 = 1;
return true;
}
+bool CLongStickDispenser::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_fieldC0) {
+ playSound("z#62.wav");
+
+ switch (_fieldBC) {
+ case 0:
+ petDisplayMessage(1, "For emergency long stick, smash glass.");
+ break;
+ case 1:
+ petDisplayMessage(1, "This dispenser has suddenly been fitted with unbreakable glass "
+ "to prevent unseemly hoarding of sticks.");
+ break;
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CLongStickDispenser::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (_fieldC0 == 1) {
+ if (_fieldC4) {
+ playMovie(19, 38, MOVIE_GAMESTATE);
+ } else {
+ playMovie(0, 18, MOVIE_GAMESTATE);
+ _fieldBC = 1;
+ }
+
+ _fieldC4 = 1;
+ _fieldC0 = 0;
+ }
+
+ return true;
+}
+
+bool CLongStickDispenser::EnterViewMsg(CEnterViewMsg *msg) {
+ setVisible(true);
+ loadFrame(38);
+ _cursorId = CURSOR_HAND;
+ return true;
+}
+
+bool CLongStickDispenser::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkStartDragging(msg)) {
+ return false;
+ } else if (_fieldC0 == 1 && _fieldC4 == 1) {
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute("LongStick");
+ CPassOnDragStartMsg dragMsg(msg->_mousePos, 1);
+ dragMsg.execute("LongStick");
+
+ msg->_dragItem = getRoot()->findByName("LongStick");
+ loadFrame(0);
+ _fieldC4 = 0;
+ _cursorId = CURSOR_ARROW;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/long_stick_dispenser.h b/engines/titanic/game/long_stick_dispenser.h
index 2a1b86fb84..be05ef9c65 100644
--- a/engines/titanic/game/long_stick_dispenser.h
+++ b/engines/titanic/game/long_stick_dispenser.h
@@ -29,7 +29,15 @@
namespace Titanic {
class CLongStickDispenser : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool PuzzleSolvedMsg(CPuzzleSolvedMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool VisibleMsg(CVisibleMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
private:
int _fieldBC;
int _fieldC0;
diff --git a/engines/titanic/game/maitred/maitred_arm_holder.cpp b/engines/titanic/game/maitred/maitred_arm_holder.cpp
index 4d35277a33..75d95640d2 100644
--- a/engines/titanic/game/maitred/maitred_arm_holder.cpp
+++ b/engines/titanic/game/maitred/maitred_arm_holder.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMaitreDArmHolder, CDropTarget)
+ ON_MESSAGE(MaitreDArmHolder)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
void CMaitreDArmHolder::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CDropTarget::save(file, indent);
@@ -34,4 +39,18 @@ void CMaitreDArmHolder::load(SimpleFile *file) {
CDropTarget::load(file);
}
+bool CMaitreDArmHolder::MaitreDArmHolder(CMaitreDArmHolder *msg) {
+ _fieldF4 = 0;
+ return true;
+}
+
+bool CMaitreDArmHolder::ActMsg(CActMsg *msg) {
+ if (msg->_action == "LoseArm") {
+ _bounds = Rect();
+ setVisible(false);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/maitred/maitred_arm_holder.h b/engines/titanic/game/maitred/maitred_arm_holder.h
index 3392d60e43..22f961f236 100644
--- a/engines/titanic/game/maitred/maitred_arm_holder.h
+++ b/engines/titanic/game/maitred/maitred_arm_holder.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CMaitreDArmHolder : public CDropTarget {
+ DECLARE_MESSAGE_MAP;
+ bool MaitreDArmHolder(CMaitreDArmHolder *msg);
+ bool ActMsg(CActMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/maitred/maitred_body.cpp b/engines/titanic/game/maitred/maitred_body.cpp
index 6b495e5a1c..4cb12aac8f 100644
--- a/engines/titanic/game/maitred/maitred_body.cpp
+++ b/engines/titanic/game/maitred/maitred_body.cpp
@@ -20,20 +20,56 @@
*
*/
-#include "titanic/game/maitred/maitred_legs.h"
+#include "titanic/game/maitred/maitred_body.h"
namespace Titanic {
-void CMaitreDLegs::save(SimpleFile *file, int indent) {
+BEGIN_MESSAGE_MAP(CMaitreDBody, CMaitreDProdReceptor)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(AnimateMaitreDMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
+void CMaitreDBody::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldC8, indent);
+ file->writeNumberLine(_armed, indent);
CMaitreDProdReceptor::save(file, indent);
}
-void CMaitreDLegs::load(SimpleFile *file) {
+void CMaitreDBody::load(SimpleFile *file) {
file->readNumber();
- _fieldC8 = file->readNumber();
+ _armed = file->readNumber();
CMaitreDProdReceptor::load(file);
}
+bool CMaitreDBody::EnterViewMsg(CEnterViewMsg *msg) {
+ return true;
+}
+
+bool CMaitreDBody::AnimateMaitreDMsg(CAnimateMaitreDMsg *msg) {
+ static const char *const ARMED_CLIPS[5] = {
+ "Talking 1", "Talking 2", "Talking 3", "Talking 4", nullptr
+ };
+ static const char *const UNARMED_CLIPS[5] = {
+ "Armless Talking 1", "Armless Talking 2", "Armless Talking 3",
+ "Armless Talking 4", nullptr
+ };
+
+ if (!hasActiveMovie()) {
+ playRandomClip(_armed ? ARMED_CLIPS : UNARMED_CLIPS);
+ }
+
+ return true;
+}
+
+bool CMaitreDBody::ActMsg(CActMsg *msg) {
+ if (msg->_action == "LoseArm") {
+ _armed = false;
+ loadFrame(262);
+ playSound("c#75.wav");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/maitred/maitred_body.h b/engines/titanic/game/maitred/maitred_body.h
index 7016c15c71..1798958e84 100644
--- a/engines/titanic/game/maitred/maitred_body.h
+++ b/engines/titanic/game/maitred/maitred_body.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CMaitreDBody : public CMaitreDProdReceptor {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool AnimateMaitreDMsg(CAnimateMaitreDMsg *msg);
+ bool ActMsg(CActMsg *msg);
private:
- int _fieldC8;
+ bool _armed;
public:
CLASSDEF;
- CMaitreDBody() : CMaitreDProdReceptor(), _fieldC8(1) {}
+ CMaitreDBody() : CMaitreDProdReceptor(), _armed(true) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/maitred/maitred_legs.cpp b/engines/titanic/game/maitred/maitred_legs.cpp
index 5071805101..8c0b0db5ea 100644
--- a/engines/titanic/game/maitred/maitred_legs.cpp
+++ b/engines/titanic/game/maitred/maitred_legs.cpp
@@ -20,20 +20,76 @@
*
*/
-#include "titanic/game/maitred/maitred_body.h"
+#include "titanic/game/maitred/maitred_legs.h"
namespace Titanic {
-void CMaitreDBody::save(SimpleFile *file, int indent) {
+BEGIN_MESSAGE_MAP(CMaitreDLegs, CMaitreDProdReceptor)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(AnimateMaitreDMsg)
+END_MESSAGE_MAP()
+
+void CMaitreDLegs::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldC8, indent);
+ file->writeNumberLine(_flag, indent);
CMaitreDProdReceptor::save(file, indent);
}
-void CMaitreDBody::load(SimpleFile *file) {
+void CMaitreDLegs::load(SimpleFile *file) {
file->readNumber();
- _fieldC8 = file->readNumber();
+ _flag = file->readNumber();
CMaitreDProdReceptor::load(file);
}
+bool CMaitreDLegs::EnterViewMsg(CEnterViewMsg *msg) {
+ _flag = true;
+ loadFrame(0);
+ return true;
+}
+
+bool CMaitreDLegs::AnimateMaitreDMsg(CAnimateMaitreDMsg *msg) {
+ static const char *const WIGGLE_CLIPS[4] = {
+ "Hip Wiggle", "Knee Bend", "Wire Wiggle", nullptr
+ };
+ static const char *const FIGHTING_CLIPS[4] = {
+ "Fighting 1", "Fighting 2", "Leg Fidget", nullptr
+ };
+ static const char *const ARCING_SOUNDS[9] = {
+ "MaitreD Arcing 1.wav", "MaitreD Arcing 2.wav",
+ "MaitreD Arcing 3.wav", "MaitreD Arcing 4.wav",
+ "MaitreD Arcing 5.wav", "MaitreD Arcing 6.wav",
+ "MaitreD Arcing 7.wav", "MaitreD Arcing 8.wav",
+ "MaitreD Arcing 9.wav"
+ };
+
+ switch (msg->_value) {
+ case 0:
+ if (_flag) {
+ playRandomClip(FIGHTING_CLIPS);
+
+ if (getRandomNumber(2) != 0)
+ playSound(ARCING_SOUNDS[getRandomNumber(9)],
+ 40 + getRandomNumber(30));
+ } else {
+ playClip("Walk Right");
+ _flag = true;
+ }
+ break;
+
+ case 1:
+ if (_flag) {
+ playClip("Walk Left");
+ _flag = false;
+ } else {
+ playRandomClip(WIGGLE_CLIPS);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/maitred/maitred_legs.h b/engines/titanic/game/maitred/maitred_legs.h
index 24ba01e712..b8a32eef4c 100644
--- a/engines/titanic/game/maitred/maitred_legs.h
+++ b/engines/titanic/game/maitred/maitred_legs.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CMaitreDLegs : public CMaitreDProdReceptor {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool AnimateMaitreDMsg(CAnimateMaitreDMsg *msg);
private:
- int _fieldC8;
+ bool _flag;
public:
CLASSDEF;
- CMaitreDLegs() : CMaitreDProdReceptor(), _fieldC8(1) {}
+ CMaitreDLegs() : CMaitreDProdReceptor(), _flag(true) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/maitred/maitred_prod_receptor.cpp b/engines/titanic/game/maitred/maitred_prod_receptor.cpp
index 4823f143b0..2977d417a2 100644
--- a/engines/titanic/game/maitred/maitred_prod_receptor.cpp
+++ b/engines/titanic/game/maitred/maitred_prod_receptor.cpp
@@ -21,13 +21,21 @@
*/
#include "titanic/game/maitred/maitred_prod_receptor.h"
+#include "titanic/npcs/maitre_d.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMaitreDProdReceptor, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseMoveMsg)
+ ON_MESSAGE(ProdMaitreDMsg)
+ ON_MESSAGE(DisableMaitreDProdReceptor)
+END_MESSAGE_MAP()
+
void CMaitreDProdReceptor::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
+ file->writeNumberLine(_counter, indent);
file->writeNumberLine(_fieldC4, indent);
CGameObject::save(file, indent);
@@ -36,10 +44,79 @@ void CMaitreDProdReceptor::save(SimpleFile *file, int indent) {
void CMaitreDProdReceptor::load(SimpleFile *file) {
file->readNumber();
_fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
+ _counter = file->readNumber();
_fieldC4 = file->readNumber();
CGameObject::load(file);
}
+bool CMaitreDProdReceptor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_fieldBC == 2 && dynamic_cast<CGameObject *>(getParent())->hasActiveMovie()) {
+ return false;
+ } else {
+ CProdMaitreDMsg prodMsg(126);
+ prodMsg.execute(this);
+ return true;
+ }
+}
+
+bool CMaitreDProdReceptor::MouseMoveMsg(CMouseMoveMsg *msg) {
+ if (_fieldBC == 2 && dynamic_cast<CGameObject *>(getParent())->hasActiveMovie())
+ return false;
+ else if (++_counter < 20)
+ return true;
+
+ _counter = 0;
+ CProdMaitreDMsg prodMsg(126);
+
+ if (isEquals("Stick"))
+ prodMsg._value = 121;
+ else if (isEquals("Hammer"))
+ prodMsg._value = 122;
+ else if (isEquals("Lemon"))
+ prodMsg._value = 123;
+ else if (isEquals("Chicken"))
+ prodMsg._value = 124;
+ else if (isEquals("Perch"))
+ prodMsg._value = 125;
+
+ CMaitreD *maitreD = dynamic_cast<CMaitreD *>(findRoomObject("MaitreD"));
+ if (maitreD->_field100 <= 0)
+ prodMsg.execute(this);
+
+ return true;
+}
+
+bool CMaitreDProdReceptor::ProdMaitreDMsg(CProdMaitreDMsg *msg) {
+ if (_fieldC4) {
+ CMaitreD *maitreD = dynamic_cast<CMaitreD *>(findRoomObject("MaitreD"));
+ if (maitreD->_field100 <= 0) {
+ CViewItem *view = findView();
+ startTalking(maitreD, msg->_value, view);
+
+ switch (_fieldBC) {
+ case 1:
+ startTalking(maitreD, 128, view);
+ break;
+ case 2:
+ startTalking(maitreD, 129, view);
+ break;
+ case 3:
+ startTalking(maitreD, 127, view);
+ break;
+ default:
+ startTalking(maitreD, 130, view);
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CMaitreDProdReceptor::DisableMaitreDProdReceptor(CDisableMaitreDProdReceptor *msg) {
+ _fieldC4 = 0;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/maitred/maitred_prod_receptor.h b/engines/titanic/game/maitred/maitred_prod_receptor.h
index f3a547b8ef..0b00ce0014 100644
--- a/engines/titanic/game/maitred/maitred_prod_receptor.h
+++ b/engines/titanic/game/maitred/maitred_prod_receptor.h
@@ -28,14 +28,19 @@
namespace Titanic {
class CMaitreDProdReceptor : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseMoveMsg(CMouseMoveMsg *msg);
+ bool ProdMaitreDMsg(CProdMaitreDMsg *msg);
+ bool DisableMaitreDProdReceptor(CDisableMaitreDProdReceptor *msg);
protected:
int _fieldBC;
- int _fieldC0;
+ int _counter;
int _fieldC4;
public:
CLASSDEF;
CMaitreDProdReceptor() : CGameObject(),
- _fieldBC(0), _fieldC0(0), _fieldC4(1) {}
+ _fieldBC(0), _counter(0), _fieldC4(1) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/missiveomat.cpp b/engines/titanic/game/missiveomat.cpp
index 931b146801..6f47131716 100644
--- a/engines/titanic/game/missiveomat.cpp
+++ b/engines/titanic/game/missiveomat.cpp
@@ -21,35 +21,310 @@
*/
#include "titanic/game/missiveomat.h"
+#include "titanic/core/room_item.h"
+#include "titanic/titanic.h"
namespace Titanic {
-CMissiveOMat::CMissiveOMat() : CGameObject(), _fieldBC(1),
- _fieldC0(0), _fieldC4(0), _fieldE0(-1) {
+BEGIN_MESSAGE_MAP(CMissiveOMat, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(KeyCharMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(MissiveOMatActionMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
+CMissiveOMat::CMissiveOMat() : CGameObject(), _mode(1),
+ _totalMessages(0), _messageNum(0), _personIndex(-1) {
+ // Load data for the messages, their from and to names
+ loadArray(_welcomeMessages, "TEXT/MISSIVEOMAT/WELCOME", 3);
+ loadArray(_messages, "TEXT/MISSIVEOMAT/MESSAGES", 58);
+ loadArray(_from, "TEXT/MISSIVEOMAT/FROM", 58);
+ loadArray(_to, "TEXT/MISSIVEOMAT/TO", 58);
+}
+
+void CMissiveOMat::loadArray(CString *arr, const CString &resName, int count) {
+ Common::SeekableReadStream *s = g_vm->_filesManager->getResource(resName);
+ for (int idx = 0; idx < count; ++idx)
+ arr[idx] = readStringFromStream(s);
+ delete s;
}
void CMissiveOMat::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
+ file->writeNumberLine(_mode, indent);
+ file->writeNumberLine(_totalMessages, indent);
+ file->writeNumberLine(_messageNum, indent);
file->writeQuotedLine(_string1, indent);
file->writeQuotedLine(_string2, indent);
- file->writeNumberLine(_fieldE0, indent);
+ file->writeNumberLine(_personIndex, indent);
CGameObject::save(file, indent);
}
void CMissiveOMat::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
+ _mode = file->readNumber();
+ _totalMessages = file->readNumber();
+ _messageNum = file->readNumber();
_string1 = file->readString();
_string2 = file->readString();
- _fieldE0 = file->readNumber();
+ _personIndex = file->readNumber();
CGameObject::load(file);
}
+bool CMissiveOMat::EnterViewMsg(CEnterViewMsg *msg) {
+ CMissiveOMatActionMsg actionMsg(9);
+ actionMsg.execute(this);
+ return true;
+}
+
+bool CMissiveOMat::KeyCharMsg(CKeyCharMsg *msg) {
+ CTreeItem *loginControl = findRoom()->findByName("MissiveOMat Login Control");
+ CTreeItem *welcome = findRoom()->findByName("MissiveOMat Welcome");
+ CTreeItem *scrollUp = findRoom()->findByName("MissiveOMat ScrollUp Button");
+ CEditControlMsg editMsg;
+
+ switch (_mode) {
+ case 1: {
+ playSound("z#228.wav");
+ editMsg._mode = 6;
+ editMsg._param = msg->_key;
+ editMsg.execute(loginControl);
+
+ if (editMsg._param == 1000) {
+ editMsg._mode = 3;
+ editMsg.execute(loginControl);
+
+ _string1 = editMsg._text;
+ if (!_string1.empty()) {
+ loadFrame(2);
+ _mode = 2;
+
+ editMsg._mode = 1;
+ editMsg.execute(loginControl);
+ editMsg._mode = 10;
+ editMsg._param = 24;
+ editMsg.execute(loginControl);
+ }
+ }
+ break;
+ }
+
+ case 2: {
+ playSound("z#228.wav");
+ editMsg._mode = 6;
+ editMsg._param = msg->_key;
+ editMsg.execute(loginControl);
+
+ _string2 = editMsg._text;
+ if (_string1 == "Droot Scraliontis") {
+ _string1 = "Scraliontis";
+ } else if (_string1 == "Antar Brobostigon") {
+ _string1 = "Brobostigon";
+ } else if (_string1 == "colin") {
+ _string1 = "Leovinus";
+ }
+
+ bool flag = false;
+ if (_string1 == "Leovinus") {
+ if (_string2 == "Other") {
+ flag = true;
+ _personIndex = 0;
+ }
+ } else if (_string1 == "Scraliontis") {
+ if (_string2 == "This") {
+ flag = true;
+ _personIndex = 1;
+ }
+ } else if (_string1 == "Brobostigon") {
+ if (_string2 == "That") {
+ flag = true;
+ _personIndex = 2;
+ }
+ }
+
+ if (flag) {
+ _mode = 4;
+ loadFrame(4);
+ editMsg._mode = 1;
+ editMsg.execute(loginControl);
+
+ getTextCursor()->hide();
+ editMsg._mode = 13;
+ editMsg.execute(loginControl);
+
+ editMsg._mode = 12;
+ editMsg.execute(welcome);
+
+ editMsg._mode = 2;
+ editMsg._text = _welcomeMessages[_personIndex];
+ editMsg.execute(welcome);
+
+ editMsg._mode = 12;
+ editMsg._text = "MissiveOMat OK Button";
+ editMsg.execute(welcome);
+ editMsg.execute(scrollUp);
+ } else {
+ _mode = 3;
+ loadFrame(3);
+ addTimer(1500);
+
+ editMsg._mode = 1;
+ editMsg.execute(loginControl);
+
+ getTextCursor()->hide();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CMissiveOMat::TimerMsg(CTimerMsg *msg) {
+ if (_mode == 3) {
+ CTreeItem *loginControl = findRoom()->findByName("MissiveOMat Login Control");
+ CEditControlMsg editMsg;
+ editMsg._mode = 10;
+ editMsg._param = 8;
+ editMsg.execute(loginControl);
+ }
+
+ return true;
+}
+
+bool CMissiveOMat::MissiveOMatActionMsg(CMissiveOMatActionMsg *msg) {
+ CTreeItem *welcome = findByName("MissiveOMat Welcome");
+
+ switch (msg->_action) {
+ case MESSAGE_SHOW: {
+ CTreeItem *btnOk = findRoom()->findByName("MissiveOMat OK Button");
+ CTreeItem *btnNext = findRoom()->findByName("MissiveOMat Next Button");
+ CTreeItem *btnPrev = findRoom()->findByName("MissiveOMat Prev Button");
+ CTreeItem *btnLogout = findRoom()->findByName("MissiveOMat Logout Button");
+
+ _mode = MESSAGE_5;
+ CVisibleMsg visibleMsg;
+ visibleMsg._visible = false;
+ visibleMsg.execute(btnOk);
+ visibleMsg._visible = true;
+ visibleMsg.execute(btnNext);
+ visibleMsg.execute(btnPrev);
+ visibleMsg.execute(btnLogout);
+
+ _messageNum = 0;
+ _totalMessages = 0;
+ CString *strP = &_messages[_personIndex * 19];
+ for (_totalMessages = 0; !strP->empty(); ++strP, ++_totalMessages)
+ ;
+
+ CMissiveOMatActionMsg actionMsg;
+ actionMsg._action = REDRAW_MESSAGE;
+ actionMsg.execute(this);
+ break;
+ }
+
+ case NEXT_MESSAGE:
+ if (_messageNum < (_totalMessages - 1)) {
+ ++_messageNum;
+ CMissiveOMatActionMsg actionMsg;
+ actionMsg._action = REDRAW_MESSAGE;
+ actionMsg.execute(this);
+ }
+ break;
+
+ case PRIOR_MESSAGE:
+ if (_messageNum > 0) {
+ --_messageNum;
+ CMissiveOMatActionMsg actionMsg;
+ actionMsg._action = REDRAW_MESSAGE;
+ actionMsg.execute(this);
+ }
+ break;
+
+ case MESSAGE_5: {
+ CMissiveOMatActionMsg actionMsg;
+ actionMsg._action = MESSAGE_9;
+ actionMsg.execute(this);
+ break;
+ }
+
+ case MESSAGE_DOWN:
+ if (welcome)
+ scrollTextDown();
+ break;
+
+ case MESSAGE_UP:
+ if (welcome)
+ scrollTextUp();
+ break;
+
+ case REDRAW_MESSAGE:
+ if (welcome) {
+ CString str = CString::format(
+ "Missive %d of %d.\nFrom: %s\nTo: %s\n\n%s\n",
+ _messageNum + 1, _totalMessages, _from[_messageNum].c_str(),
+ _to[_messageNum].c_str(), _messages[_messageNum].c_str());
+
+ setText(str);
+ }
+ break;
+
+ case MESSAGE_9: {
+ loadFrame(1);
+ _mode = MESSAGE_NONE;
+ _personIndex = -1;
+
+ static const char *const WIDGETS[7] = {
+ "MissiveOMat Login Control", "MissiveOMat OK Button",
+ "MissiveOMat Next Button", "MissiveOMat Prev Button",
+ "MissiveOMat Logout Button", "MissiveOMat ScrollDown Button",
+ "MissiveOMat ScrollUp Button"
+ };
+ CEditControlMsg editMsg;
+
+ for (int idx = 0; idx < 7; ++idx) {
+ editMsg._mode = 0;
+ editMsg._param = 12;
+ editMsg.execute(WIDGETS[idx]);
+ editMsg._mode = 1;
+ editMsg.execute(WIDGETS[idx]);
+ editMsg._mode = 13;
+ editMsg.execute(WIDGETS[idx]);
+ }
+
+ editMsg._mode = 12;
+ editMsg.execute("MissiveOMat Login Control");
+ editMsg._mode = 10;
+ editMsg._param = 8;
+ editMsg.execute("MissiveOMat Login Control");
+ editMsg._mode = 8;
+ editMsg.execute("MissiveOMat Login Control");
+
+ _string1.clear();
+ _string2.clear();
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CMissiveOMat::LeaveViewMsg(CLeaveViewMsg *msg) {
+ CEditControlMsg editMsg;
+ editMsg._mode = 9;
+ editMsg.execute("MissiveOMat Login Control");
+ petShowCursor();
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/missiveomat.h b/engines/titanic/game/missiveomat.h
index 7fde8cf25d..9810fcc403 100644
--- a/engines/titanic/game/missiveomat.h
+++ b/engines/titanic/game/missiveomat.h
@@ -27,14 +27,33 @@
namespace Titanic {
+enum MissiveOMatAction {
+ MESSAGE_NONE = 1, MESSAGE_SHOW = 2, NEXT_MESSAGE = 3, PRIOR_MESSAGE = 4,
+ MESSAGE_5 = 5, MESSAGE_DOWN = 6, MESSAGE_UP = 7, REDRAW_MESSAGE = 8,
+ MESSAGE_9 = 9
+};
+
class CMissiveOMat : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool KeyCharMsg(CKeyCharMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool MissiveOMatActionMsg(CMissiveOMatActionMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+private:
+ CString _welcomeMessages[3];
+ CString _messages[58];
+ CString _from[58];
+ CString _to[58];
+private:
+ void loadArray(CString *arr, const CString &resName, int count);
public:
- int _fieldBC;
- int _fieldC0;
- int _fieldC4;
+ int _mode;
+ int _totalMessages;
+ int _messageNum;
CString _string1;
CString _string2;
- int _fieldE0;
+ int _personIndex;
public:
CLASSDEF;
CMissiveOMat();
diff --git a/engines/titanic/game/missiveomat_button.cpp b/engines/titanic/game/missiveomat_button.cpp
index d5ae75dbc2..b7ad7f8f6f 100644
--- a/engines/titanic/game/missiveomat_button.cpp
+++ b/engines/titanic/game/missiveomat_button.cpp
@@ -21,21 +21,47 @@
*/
#include "titanic/game/missiveomat_button.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMissiveOMatButton, CEditControl)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(VisibleMsg)
+ ON_MESSAGE(MouseDoubleClickMsg)
+END_MESSAGE_MAP()
+
void CMissiveOMatButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldFC, indent);
+ file->writeNumberLine(_buttonId, indent);
CEditControl::save(file, indent);
}
void CMissiveOMatButton::load(SimpleFile *file) {
file->readNumber();
- _fieldFC = file->readNumber();
+ _buttonId = file->readNumber();
CEditControl::load(file);
}
+bool CMissiveOMatButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CMissiveOMatActionMsg actionMsg;
+ actionMsg._action = _buttonId;
+ actionMsg.execute(findRoom()->findByName("MissiveOMat"));
+ return true;
+}
+
+bool CMissiveOMatButton::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(msg->_visible);
+ return true;
+}
+
+bool CMissiveOMatButton::MouseDoubleClickMsg(CMouseDoubleClickMsg *msg) {
+ CMissiveOMatActionMsg actionMsg;
+ actionMsg._action = _buttonId;
+ actionMsg.execute(findRoom()->findByName("MissiveOMat"));
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/missiveomat_button.h b/engines/titanic/game/missiveomat_button.h
index d36f5bd958..6dbfd4cd56 100644
--- a/engines/titanic/game/missiveomat_button.h
+++ b/engines/titanic/game/missiveomat_button.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CMissiveOMatButton : public CEditControl {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool VisibleMsg(CVisibleMsg *msg);
+ bool MouseDoubleClickMsg(CMouseDoubleClickMsg *msg);
public:
- int _fieldFC;
+ int _buttonId;
public:
CLASSDEF;
- CMissiveOMatButton() : CEditControl(), _fieldFC(2) {}
+ CMissiveOMatButton() : CEditControl(), _buttonId(2) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/movie_tester.cpp b/engines/titanic/game/movie_tester.cpp
index 1b266d9c7e..bbd66a9bce 100644
--- a/engines/titanic/game/movie_tester.cpp
+++ b/engines/titanic/game/movie_tester.cpp
@@ -24,18 +24,36 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMovieTester, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CMovieTester::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value1, indent);
- file->writeNumberLine(_value2, indent);
+ file->writeNumberLine(_movieNumFrames, indent);
+ file->writeNumberLine(_movieFrameNum, indent);
CGameObject::save(file, indent);
}
void CMovieTester::load(SimpleFile *file) {
file->readNumber();
- _value1 = file->readNumber();
- _value2 = file->readNumber();
+ _movieNumFrames = file->readNumber();
+ _movieFrameNum = file->readNumber();
CGameObject::load(file);
}
+bool CMovieTester::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (msg->_buttons == MB_RIGHT) {
+ if (--_movieFrameNum < 0) {
+ _movieFrameNum = _movieNumFrames - 1;
+ }
+ } else {
+ if (++_movieFrameNum >= _movieNumFrames)
+ _movieFrameNum = 0;
+ }
+
+ loadFrame(_movieFrameNum);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/movie_tester.h b/engines/titanic/game/movie_tester.h
index de2ef2cc5e..17a7d489d8 100644
--- a/engines/titanic/game/movie_tester.h
+++ b/engines/titanic/game/movie_tester.h
@@ -28,11 +28,13 @@
namespace Titanic {
class CMovieTester : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
- int _value1, _value2;
+ int _movieNumFrames, _movieFrameNum;
public:
CLASSDEF;
- CMovieTester() : CGameObject(), _value1(0), _value2(0) {}
+ CMovieTester() : CGameObject(), _movieNumFrames(0), _movieFrameNum(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/music_console_button.cpp b/engines/titanic/game/music_console_button.cpp
index 1bc78ffe23..dc86765476 100644
--- a/engines/titanic/game/music_console_button.cpp
+++ b/engines/titanic/game/music_console_button.cpp
@@ -21,9 +21,18 @@
*/
#include "titanic/game/music_console_button.h"
+#include "titanic/core/room_item.h"
+#include "titanic/sound/music_room_handler.h"
+#include "titanic/titanic.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMusicConsoleButton, CMusicPlayer)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(SetMusicControlsMsg)
+END_MESSAGE_MAP()
+
void CMusicConsoleButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CMusicPlayer::save(file, indent);
@@ -34,4 +43,91 @@ void CMusicConsoleButton::load(SimpleFile *file) {
CMusicPlayer::load(file);
}
+bool CMusicConsoleButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_isActive) {
+ CStopMusicMsg stopMsg(this);
+ stopMsg.execute(this);
+ stopMovie();
+ loadFrame(0);
+ } else {
+ CStartMusicMsg startMsg(this);
+ startMsg.execute(this);
+ playMovie(MOVIE_REPEAT);
+
+ CMusicHasStartedMsg startedMsg;
+ startedMsg.execute("Music Room Phonograph");
+
+ if (CMusicRoom::_musicHandler->checkSound(1)
+ && CMusicRoom::_musicHandler->checkSound(2)
+ && CMusicRoom::_musicHandler->checkSound(3)) {
+ CCorrectMusicPlayedMsg correctMsg;
+ correctMsg.execute(findRoom());
+ }
+ }
+
+ return true;
+}
+
+bool CMusicConsoleButton::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (_isActive) {
+ CStopMusicMsg stopMsg(this);
+ stopMsg.execute(this);
+ stopMovie();
+ loadFrame(0);
+ }
+
+ return true;
+}
+
+bool CMusicConsoleButton::SetMusicControlsMsg(CSetMusicControlsMsg *msg) {
+ CMusicRoom *musicRoom = getMusicRoom();
+ CQueryMusicControlSettingMsg queryMsg;
+
+ queryMsg.execute("Bells Mute Control");
+ musicRoom->setMuteControl(BELLS, queryMsg._value == 1 ? 1 : 0);
+ queryMsg.execute("Bells Pitch Control");
+ musicRoom->setPitchControl(BELLS, queryMsg._value);
+ queryMsg.execute("Bells Speed Control");
+ musicRoom->setSpeedControl(BELLS, queryMsg._value);
+ queryMsg.execute("Bells Inversion Control");
+ musicRoom->setInversionControl(BELLS, queryMsg._value == 0 ? 1 : 0);
+ queryMsg.execute("Bells Direction Control");
+ musicRoom->setDirectionControl(BELLS, queryMsg._value == 0 ? 1 : 0);
+
+ queryMsg.execute("Snake Mute Control");
+ musicRoom->setMuteControl(SNAKE, queryMsg._value == 1 ? 1 : 0);
+ queryMsg.execute("Snake Pitch Control");
+ musicRoom->setPitchControl(SNAKE, queryMsg._value);
+ queryMsg.execute("Snake Speed Control");
+ musicRoom->setSpeedControl(SNAKE, queryMsg._value);
+ queryMsg.execute("Snake Inversion Control");
+ musicRoom->setInversionControl(SNAKE, queryMsg._value == 0 ? 1 : 0);
+ queryMsg.execute("Snake Direction Control");
+ musicRoom->setDirectionControl(SNAKE, queryMsg._value == 0 ? 1 : 0);
+
+ queryMsg.execute("Piano Mute Control");
+ musicRoom->setMuteControl(PIANO, queryMsg._value == 1 ? 1 : 0);
+ queryMsg.execute("Piano Pitch Control");
+ musicRoom->setPitchControl(PIANO, queryMsg._value);
+ queryMsg.execute("Piano Speed Control");
+ musicRoom->setSpeedControl(PIANO, queryMsg._value);
+ queryMsg.execute("Piano Inversion Control");
+ musicRoom->setInversionControl(PIANO, queryMsg._value == 0 ? 1 : 0);
+ queryMsg.execute("Piano Direction Control");
+ musicRoom->setDirectionControl(PIANO, queryMsg._value == 0 ? 1 : 0);
+
+ queryMsg.execute("Bass Mute Control");
+ musicRoom->setMuteControl(BASS, queryMsg._value == 1 ? 1 : 0);
+ queryMsg.execute("Bass Pitch Control");
+ musicRoom->setPitchControl(BASS, queryMsg._value);
+ queryMsg.execute("Bass Speed Control");
+ musicRoom->setSpeedControl(BASS, queryMsg._value);
+ queryMsg.execute("Bass Inversion Control");
+ musicRoom->setInversionControl(BASS, queryMsg._value == 0 ? 1 : 0);
+ queryMsg.execute("Bass Direction Control");
+ musicRoom->setDirectionControl(BASS, queryMsg._value == 0 ? 1 : 0);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/music_console_button.h b/engines/titanic/game/music_console_button.h
index 8e05b698d7..80ce719c96 100644
--- a/engines/titanic/game/music_console_button.h
+++ b/engines/titanic/game/music_console_button.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CMusicConsoleButton : public CMusicPlayer {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool SetMusicControlsMsg(CSetMusicControlsMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/music_room_stop_phonograph_button.cpp b/engines/titanic/game/music_room_stop_phonograph_button.cpp
index 44342fc2d6..dee2c0883e 100644
--- a/engines/titanic/game/music_room_stop_phonograph_button.cpp
+++ b/engines/titanic/game/music_room_stop_phonograph_button.cpp
@@ -24,16 +24,52 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMusicRoomStopPhonographButton, CEjectPhonographButton)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(FrameMsg)
+END_MESSAGE_MAP()
+
void CMusicRoomStopPhonographButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_field100, indent);
+ file->writeNumberLine(_ticks, indent);
CEjectPhonographButton::save(file, indent);
}
void CMusicRoomStopPhonographButton::load(SimpleFile *file) {
file->readNumber();
- _field100 = file->readNumber();
+ _ticks = file->readNumber();
CEjectPhonographButton::load(file);
}
+bool CMusicRoomStopPhonographButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_ejected) {
+ loadFrame(1);
+ playSound(_soundName);
+ _readyFlag = true;
+
+ CPhonographStopMsg stopMsg;
+ stopMsg.execute(getParent(), nullptr, MSGFLAG_SCAN);
+ if (stopMsg._value2) {
+ _ticks = getTicksCount();
+ } else {
+ CEjectCylinderMsg ejectMsg;
+ ejectMsg.execute(getParent(), nullptr, MSGFLAG_SCAN);
+ _ejected = true;
+ }
+ }
+
+ return true;
+}
+
+bool CMusicRoomStopPhonographButton::FrameMsg(CFrameMsg *msg) {
+ if (_readyFlag && _ticks && msg->_ticks >= (_ticks + 100)) {
+ loadFrame(0);
+ playSound(_readySoundName);
+ _ticks = 0;
+ _readyFlag = false;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/music_room_stop_phonograph_button.h b/engines/titanic/game/music_room_stop_phonograph_button.h
index 7260e5aaab..dd9e8b4bc0 100644
--- a/engines/titanic/game/music_room_stop_phonograph_button.h
+++ b/engines/titanic/game/music_room_stop_phonograph_button.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CMusicRoomStopPhonographButton : public CEjectPhonographButton {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
private:
- int _field100;
+ uint _ticks;
public:
CLASSDEF;
- CMusicRoomStopPhonographButton() : CEjectPhonographButton(), _field100(0) {}
+ CMusicRoomStopPhonographButton() : CEjectPhonographButton(), _ticks(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/music_system_lock.cpp b/engines/titanic/game/music_system_lock.cpp
index f1e062b3ee..074864e7c3 100644
--- a/engines/titanic/game/music_system_lock.cpp
+++ b/engines/titanic/game/music_system_lock.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/game/music_system_lock.h"
+#include "titanic/core/room_item.h"
+#include "titanic/carry/carry.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMusicSystemLock, CDropTarget)
+ ON_MESSAGE(DropObjectMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CMusicSystemLock::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value, indent);
@@ -36,4 +43,25 @@ void CMusicSystemLock::load(SimpleFile *file) {
CDropTarget::load(file);
}
+bool CMusicSystemLock::DropObjectMsg(CDropObjectMsg *msg) {
+ CTreeItem *key = msg->_item->findByName("Music System Key");
+ if (key) {
+ setVisible(true);
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ }
+
+ return true;
+}
+
+bool CMusicSystemLock::MovieEndMsg(CMovieEndMsg *msg) {
+ CTreeItem *phonograph = findRoom()->findByName("Restaurant Phonograph");
+ CQueryPhonographState queryMsg;
+ queryMsg.execute(phonograph);
+ CLockPhonographMsg lockMsg(queryMsg._value);
+ lockMsg.execute(phonograph, nullptr, MSGFLAG_SCAN);
+
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/music_system_lock.h b/engines/titanic/game/music_system_lock.h
index ff826f5c77..0947915caa 100644
--- a/engines/titanic/game/music_system_lock.h
+++ b/engines/titanic/game/music_system_lock.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CMusicSystemLock : public CDropTarget {
+ DECLARE_MESSAGE_MAP;
+ bool DropObjectMsg(CDropObjectMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
private:
int _value;
public:
diff --git a/engines/titanic/game/nav_helmet.cpp b/engines/titanic/game/nav_helmet.cpp
index 770eb7375e..08ff073c26 100644
--- a/engines/titanic/game/nav_helmet.cpp
+++ b/engines/titanic/game/nav_helmet.cpp
@@ -21,19 +21,114 @@
*/
#include "titanic/game/nav_helmet.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CNavHelmet, CGameObject)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(PETHelmetOnOffMsg)
+ ON_MESSAGE(PETPhotoOnOffMsg)
+ ON_MESSAGE(PETStarFieldLockMsg)
+ ON_MESSAGE(PETSetStarDestinationMsg)
+END_MESSAGE_MAP()
+
void CNavHelmet::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CNavHelmet::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CNavHelmet::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_flag) {
+ setVisible(false);
+
+ CPetControl *pet = getPetControl();
+ if (pet) {
+ pet->setArea(PET_STARFIELD);
+ petDisplayMessage(1, "Now would be an excellent opportunity to adjust your viewing apparatus.");
+ pet->incAreaLocks();
+ }
+
+ starFn1(0);
+ starFn1(12);
+ }
+
+ return true;
+}
+
+bool CNavHelmet::EnterViewMsg(CEnterViewMsg *msg) {
+ petSetRemoteTarget();
+ return true;
+}
+
+bool CNavHelmet::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petClear();
+ return true;
+}
+
+bool CNavHelmet::PETHelmetOnOffMsg(CPETHelmetOnOffMsg *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (_flag) {
+ _flag = false;
+ setVisible(true);
+ starFn1(1);
+ playMovie(61, 120, MOVIE_NOTIFY_OBJECT);
+ playSound("a#47.wav");
+ playSound("a#48.wav");
+
+ if (pet) {
+ pet->decAreaLocks();
+ pet->setArea(PET_REMOTE);
+ }
+
+ dec54();
+ } else {
+ inc54();
+ _flag = true;
+ setVisible(true);
+ playMovie(0, 60, MOVIE_NOTIFY_OBJECT);
+ playSound("a#48.wav");
+ playSound("a#47.wav");
+ }
+
+ return true;
+}
+
+bool CNavHelmet::PETPhotoOnOffMsg(CPETPhotoOnOffMsg *msg) {
+ if (_flag)
+ starFn1(9);
+
+ return true;
+}
+
+bool CNavHelmet::PETStarFieldLockMsg(CPETStarFieldLockMsg *msg) {
+ if (_flag) {
+ if (msg->_value) {
+ playSound("a#6.wav");
+ starFn1(17);
+ } else {
+ playSound("a#5.wav");
+ starFn1(18);
+ }
+ }
+
+ return true;
+}
+
+bool CNavHelmet::PETSetStarDestinationMsg(CPETSetStarDestinationMsg *msg) {
+ CActMsg actMsg("SetDestin");
+ actMsg.execute("CaptainsWheel");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/nav_helmet.h b/engines/titanic/game/nav_helmet.h
index 74caa52534..c408d05c97 100644
--- a/engines/titanic/game/nav_helmet.h
+++ b/engines/titanic/game/nav_helmet.h
@@ -24,15 +24,24 @@
#define TITANIC_NAV_HELMET_H
#include "titanic/core/game_object.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CNavHelmet : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool PETHelmetOnOffMsg(CPETHelmetOnOffMsg *msg);
+ bool PETPhotoOnOffMsg(CPETPhotoOnOffMsg *msg);
+ bool PETStarFieldLockMsg(CPETStarFieldLockMsg *msg);
+ bool PETSetStarDestinationMsg(CPETSetStarDestinationMsg *msg);
private:
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CNavHelmet() : CGameObject(), _value(0) {}
+ CNavHelmet() : CGameObject(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/parrot/parrot_succubus.cpp b/engines/titanic/game/nav_helmet_off.cpp
index 02a29b748e..289e9e3f55 100644
--- a/engines/titanic/game/parrot/parrot_succubus.cpp
+++ b/engines/titanic/game/nav_helmet_off.cpp
@@ -20,30 +20,30 @@
*
*/
-#include "titanic/game/parrot/parrot_succubus.h"
+#include "titanic/game/nav_helmet_off.h"
+#include "titanic/pet_control/pet_control.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
-CParrotSuccUBus::CParrotSuccUBus() : CSuccUBus(), _field1DC(0),
- _field1EC(0), _field1F0(376), _field1F4(393) {
-}
+BEGIN_MESSAGE_MAP(CNavHelmetOff, CNavHelmet)
+ ON_MESSAGE(MouseButtonUpMsg)
+END_MESSAGE_MAP()
-void CParrotSuccUBus::save(SimpleFile *file, int indent) {
+void CNavHelmetOff::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_field1DC, indent);
- file->writeQuotedLine(_string3, indent);
- file->writeNumberLine(_field1EC, indent);
-
- CSuccUBus::save(file, indent);
+ file->writeQuotedLine(_target, indent);
}
-void CParrotSuccUBus::load(SimpleFile *file) {
+void CNavHelmetOff::load(SimpleFile *file) {
file->readNumber();
- _field1DC = file->readNumber();
- _string3 = file->readString();
- _field1EC = file->readNumber();
+ _target = file->readString();
+}
- CSuccUBus::load(file);
+bool CNavHelmetOff::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ CDoffNavHelmet doffMsg;
+ doffMsg.execute(_target);
+ return true;
}
} // End of namespace Titanic
diff --git a/engines/titanic/game/nav_helmet_off.h b/engines/titanic/game/nav_helmet_off.h
new file mode 100644
index 0000000000..c9529fe8e9
--- /dev/null
+++ b/engines/titanic/game/nav_helmet_off.h
@@ -0,0 +1,53 @@
+/* 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 TITANIC_NAV_HELMET_OFF_H
+#define TITANIC_NAV_HELMET_OFF_H
+
+#include "titanic/game/nav_helmet.h"
+#include "titanic/messages/pet_messages.h"
+
+namespace Titanic {
+
+class CNavHelmetOff : public CNavHelmet {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+private:
+ CString _target;
+public:
+ CLASSDEF;
+ CNavHelmetOff() : CNavHelmet(), _target("NULL") {}
+
+ /**
+ * Save the data for the class to file
+ */
+ virtual void save(SimpleFile *file, int indent);
+
+ /**
+ * Load the data for the class from file
+ */
+ virtual void load(SimpleFile *file);
+};
+
+} // End of namespace Titanic
+
+#endif /* TITANIC_NAV_HELMET_OFF_H */
diff --git a/engines/titanic/game/nav_helmet_on.cpp b/engines/titanic/game/nav_helmet_on.cpp
new file mode 100644
index 0000000000..59ceebc4ad
--- /dev/null
+++ b/engines/titanic/game/nav_helmet_on.cpp
@@ -0,0 +1,49 @@
+/* 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 "titanic/game/nav_helmet_on.h"
+#include "titanic/pet_control/pet_control.h"
+#include "titanic/messages/pet_messages.h"
+
+namespace Titanic {
+
+BEGIN_MESSAGE_MAP(CNavHelmetOn, CNavHelmet)
+ ON_MESSAGE(MouseButtonUpMsg)
+END_MESSAGE_MAP()
+
+void CNavHelmetOn::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(1, indent);
+ file->writeQuotedLine(_target, indent);
+}
+
+void CNavHelmetOn::load(SimpleFile *file) {
+ file->readNumber();
+ _target = file->readString();
+}
+
+bool CNavHelmetOn::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ CDonNavHelmet donMsg;
+ donMsg.execute(_target);
+ return true;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/game/bilge_succubus.h b/engines/titanic/game/nav_helmet_on.h
index 4b2a626dc2..452637c48a 100644
--- a/engines/titanic/game/bilge_succubus.h
+++ b/engines/titanic/game/nav_helmet_on.h
@@ -20,22 +20,22 @@
*
*/
-#ifndef TITANIC_BILGE_SUCCUBUS_H
-#define TITANIC_BILGE_SUCCUBUS_H
+#ifndef TITANIC_NAV_HELMET_ON_H
+#define TITANIC_NAV_HELMET_ON_H
-#include "titanic/npcs/succubus.h"
+#include "titanic/game/nav_helmet.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
-class CBilgeSuccUBus : public CSuccUBus {
-public:
- int _field1DC;
- int _field1E0;
- int _field1E4;
- int _field1E8;
+class CNavHelmetOn : public CNavHelmet {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+private:
+ CString _target;
public:
CLASSDEF;
- CBilgeSuccUBus();
+ CNavHelmetOn() : CNavHelmet(), _target("NULL") {}
/**
* Save the data for the class to file
@@ -50,4 +50,4 @@ public:
} // End of namespace Titanic
-#endif /* TITANIC_BILGE_SUCCUBUS_H */
+#endif /* TITANIC_NAV_HELMET_ON_H */
diff --git a/engines/titanic/game/no_nut_bowl.cpp b/engines/titanic/game/no_nut_bowl.cpp
index 47f9d7901e..8c0a95ac9a 100644
--- a/engines/titanic/game/no_nut_bowl.cpp
+++ b/engines/titanic/game/no_nut_bowl.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CNoNutBowl, CBackground)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(ReplaceBowlAndNutsMsg)
+ ON_MESSAGE(NutPuzzleMsg)
+END_MESSAGE_MAP()
+
void CNoNutBowl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBackground::save(file, indent);
@@ -34,4 +40,19 @@ void CNoNutBowl::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CNoNutBowl::ActMsg(CActMsg *msg) {
+ return true;
+}
+
+bool CNoNutBowl::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
+bool CNoNutBowl::NutPuzzleMsg(CNutPuzzleMsg *msg) {
+ if (msg->_value == "NutsGone")
+ setVisible(true);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/no_nut_bowl.h b/engines/titanic/game/no_nut_bowl.h
index 548b324869..cd8bc65179 100644
--- a/engines/titanic/game/no_nut_bowl.h
+++ b/engines/titanic/game/no_nut_bowl.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CNoNutBowl : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg);
+ bool NutPuzzleMsg(CNutPuzzleMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/nose_holder.cpp b/engines/titanic/game/nose_holder.cpp
index cd9433ee9c..ac6c10dafd 100644
--- a/engines/titanic/game/nose_holder.cpp
+++ b/engines/titanic/game/nose_holder.cpp
@@ -24,7 +24,16 @@
namespace Titanic {
-CNoseHolder::CNoseHolder() : CDropTarget(), _field118(0), _field11C(0) {
+BEGIN_MESSAGE_MAP(CNoseHolder, CDropTarget)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
+CNoseHolder::CNoseHolder() : CDropTarget(), _dragObject(nullptr),
+ _field11C(0) {
}
void CNoseHolder::save(SimpleFile *file, int indent) {
@@ -41,4 +50,72 @@ void CNoseHolder::load(SimpleFile *file) {
CDropTarget::load(file);
}
+bool CNoseHolder::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Sneeze" && !_itemName.empty() && _fieldF4) {
+ CProximity prox;
+ prox._positioningMode = POSMODE_VECTOR;
+ playSound("z#35.wav", prox);
+
+ if (getView() == findView()) {
+ setVisible(true);
+ playMovie(1, 13, MOVIE_NOTIFY_OBJECT);
+ }
+ }
+
+ return true;
+}
+
+bool CNoseHolder::FrameMsg(CFrameMsg *msg) {
+ if (!_dragObject) {
+ CGameObject *dragObj = getDraggingObject();
+ if (!dragObj)
+ return false;
+
+ if (!dragObj->isEquals("Feathers") || getView() != findView())
+ return false;
+
+ _dragObject = dragObj;
+ }
+
+ if (_dragObject) {
+ if (!checkPoint(Point(_dragObject->_bounds.left,
+ _dragObject->_bounds.top))) {
+ _field11C = false;
+ } else if (!_field11C) {
+ CActMsg actMsg("Sneeze");
+ actMsg.execute(this);
+ _field11C = true;
+ }
+ }
+
+ return true;
+}
+
+bool CNoseHolder::LeaveViewMsg(CLeaveViewMsg *msg) {
+ _field11C = false;
+ _dragObject = nullptr;
+ if (_fieldF4) {
+ loadFrame(_dropFrame);
+ setVisible(false);
+ }
+
+ return true;
+}
+
+bool CNoseHolder::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_fieldF4) {
+ loadFrame(_dropFrame);
+ setVisible(false);
+ }
+
+ return true;
+}
+
+bool CNoseHolder::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_fieldF4)
+ setVisible(false);
+
+ return CDropTarget::EnterViewMsg(msg);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/nose_holder.h b/engines/titanic/game/nose_holder.h
index b8cca95869..7b3fbba625 100644
--- a/engines/titanic/game/nose_holder.h
+++ b/engines/titanic/game/nose_holder.h
@@ -28,8 +28,14 @@
namespace Titanic {
class CNoseHolder : public CDropTarget {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
- int _field118;
+ CGameObject *_dragObject;
int _field11C;
public:
CLASSDEF;
diff --git a/engines/titanic/game/null_port_hole.cpp b/engines/titanic/game/null_port_hole.cpp
index e651b1b59f..b1514c7cbf 100644
--- a/engines/titanic/game/null_port_hole.cpp
+++ b/engines/titanic/game/null_port_hole.cpp
@@ -27,22 +27,22 @@ namespace Titanic {
EMPTY_MESSAGE_MAP(CNullPortHole, CClickResponder);
CNullPortHole::CNullPortHole() : CClickResponder() {
- _string1 = "For a better view, why not visit the Promenade Deck?";
- _string2 = "b#48.wav";
+ _message = "For a better view, why not visit the Promenade Deck?";
+ _soundName = "b#48.wav";
}
void CNullPortHole::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string2, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeQuotedLine(_soundName, indent);
+ file->writeQuotedLine(_message, indent);
CClickResponder::save(file, indent);
}
void CNullPortHole::load(SimpleFile *file) {
file->readNumber();
- _string2 = file->readString();
- _string1 = file->readString();
+ _soundName = file->readString();
+ _message = file->readString();
CClickResponder::load(file);
}
diff --git a/engines/titanic/game/nut_replacer.cpp b/engines/titanic/game/nut_replacer.cpp
index 9a73355c91..6b05d1d0e9 100644
--- a/engines/titanic/game/nut_replacer.cpp
+++ b/engines/titanic/game/nut_replacer.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CNutReplacer, CGameObject)
+ ON_MESSAGE(ReplaceBowlAndNutsMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CNutReplacer::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +39,15 @@ void CNutReplacer::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CNutReplacer::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) {
+ setVisible(true);
+ playMovie(MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ return true;
+}
+
+bool CNutReplacer::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/nut_replacer.h b/engines/titanic/game/nut_replacer.h
index ead9713801..e2eed4e247 100644
--- a/engines/titanic/game/nut_replacer.h
+++ b/engines/titanic/game/nut_replacer.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CNutReplacer : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/parrot/parrot_lobby_controller.cpp b/engines/titanic/game/parrot/parrot_lobby_controller.cpp
index f1e054a8dd..907e7519b8 100644
--- a/engines/titanic/game/parrot/parrot_lobby_controller.cpp
+++ b/engines/titanic/game/parrot/parrot_lobby_controller.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/game/parrot/parrot_lobby_controller.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrotLobbyController, CParrotLobbyObject)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
void CParrotLobbyController::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CParrotLobbyObject::save(file, indent);
@@ -34,4 +39,34 @@ void CParrotLobbyController::load(SimpleFile *file) {
CParrotLobbyObject::load(file);
}
+bool CParrotLobbyController::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Refresh")
+ return false;
+ else if (msg->_action == "GainParrot")
+ _haveParrot = true;
+ else if (msg->_action == "LoseParrot")
+ _haveParrot = false;
+ else if (msg->_action == "GainPerch")
+ _havePerch = true;
+ else if (msg->_action == "LosePerch")
+ _havePerch = false;
+ else if (msg->_action == "GainStick")
+ _haveStick = true;
+ else if (msg->_action == "LoseStick")
+ _haveStick = false;
+
+ _flags = 0;
+ if (_haveParrot)
+ _flags = 4;
+ if (_havePerch)
+ _flags |= 2;
+ if (_haveStick)
+ _flags |= 1;
+
+ CActMsg actMsg("Refresh");
+ actMsg.execute(findRoom(), CParrotLobbyObject::_type, MSGFLAG_CLASS_DEF | MSGFLAG_SCAN);
+ actMsg.execute("ParrotLobbyUpdater_TOW");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_lobby_controller.h b/engines/titanic/game/parrot/parrot_lobby_controller.h
index d2fa4a1801..896a4e19d2 100644
--- a/engines/titanic/game/parrot/parrot_lobby_controller.h
+++ b/engines/titanic/game/parrot/parrot_lobby_controller.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CParrotLobbyController : public CParrotLobbyObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/parrot/parrot_lobby_link_updater.cpp b/engines/titanic/game/parrot/parrot_lobby_link_updater.cpp
index 25d5ec724b..47311c31f5 100644
--- a/engines/titanic/game/parrot/parrot_lobby_link_updater.cpp
+++ b/engines/titanic/game/parrot/parrot_lobby_link_updater.cpp
@@ -21,9 +21,46 @@
*/
#include "titanic/game/parrot/parrot_lobby_link_updater.h"
+#include "titanic/titanic.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrotLobbyLinkUpdater, CParrotLobbyObject)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
+/*------------------------------------------------------------------------*/
+
+LinkUpdatorEntry::LinkUpdatorEntry() {
+ Common::fill(&_vals[0], &_vals[8], 0);
+}
+
+void LinkUpdatorEntry::load(Common::SeekableReadStream *s) {
+ _linkStr = readStringFromStream(s);
+ for (int idx = 0; idx < 8; ++idx)
+ _vals[idx] = s->readByte();
+}
+
+/*------------------------------------------------------------------------*/
+
+void LinkUpdatorEntries::load(Common::SeekableReadStream *s, int count) {
+ resize(count);
+ for (int idx = 0; idx < count; ++idx)
+ (*this)[idx].load(s);
+}
+
+/*------------------------------------------------------------------------*/
+
+CParrotLobbyLinkUpdater::CParrotLobbyLinkUpdater() : CParrotLobbyObject(), _fieldBC(1) {
+ Common::SeekableReadStream *s = g_vm->_filesManager->getResource("DATA/PARROT_LOBBY_LINK_UPDATOR");
+ _entries[0].load(s, 7);
+ _entries[1].load(s, 5);
+ _entries[2].load(s, 6);
+ _entries[3].load(s, 9);
+ _entries[4].load(s, 1);
+ delete s;
+}
+
void CParrotLobbyLinkUpdater::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CParrotLobbyObject::save(file, indent);
@@ -34,4 +71,45 @@ void CParrotLobbyLinkUpdater::load(SimpleFile *file) {
CParrotLobbyObject::load(file);
}
+bool CParrotLobbyLinkUpdater::ActMsg(CActMsg *msg) {
+ if (msg->_action != "Refresh")
+ return false;
+
+ CNodeItem *node = findNode();
+ LinkUpdatorEntries *entriesP;
+ if (isEquals("ParrotLobbyUpdater_TOW")) {
+ entriesP = &_entries[4];
+ } else {
+ if (node->_nodeNumber > 3)
+ return true;
+ entriesP = &_entries[node->_nodeNumber];
+ }
+ int count = entriesP->size();
+
+ for (CTreeItem *item = node->getFirstChild(); item; item = item->scan(node)) {
+ CLinkItem *link = dynamic_cast<CLinkItem *>(item);
+ if (!link || count == 0)
+ continue;
+
+ CString linkName = link->getName();
+ char c = linkName.lastChar();
+ if (c >= 'a' && c <= 'd')
+ linkName.deleteLastChar();
+
+ for (uint idx = 0; idx < entriesP->size(); ++idx) {
+ const LinkUpdatorEntry &entry = (*entriesP)[idx];
+ if (entry._linkStr == linkName) {
+ int val = entry._vals[CParrotLobbyObject::_flags];
+ if (val)
+ linkName += (char)(0x60 + val);
+
+ link->_name = linkName;
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_lobby_link_updater.h b/engines/titanic/game/parrot/parrot_lobby_link_updater.h
index 0470a62dee..93db931a53 100644
--- a/engines/titanic/game/parrot/parrot_lobby_link_updater.h
+++ b/engines/titanic/game/parrot/parrot_lobby_link_updater.h
@@ -23,16 +23,34 @@
#ifndef TITANIC_PARROT_LOBBY_LINK_UPDATER_H
#define TITANIC_PARROT_LOBBY_LINK_UPDATER_H
+#include "common/stream.h"
#include "titanic/game/parrot/parrot_lobby_object.h"
namespace Titanic {
+struct LinkUpdatorEntry {
+ CString _linkStr;
+ int _vals[8];
+
+ LinkUpdatorEntry();
+ void load(Common::SeekableReadStream *s);
+};
+
+class LinkUpdatorEntries : public Common::Array<LinkUpdatorEntry> {
+public:
+ void load(Common::SeekableReadStream *s, int count);
+};
+
class CParrotLobbyLinkUpdater : public CParrotLobbyObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+private:
+ LinkUpdatorEntries _entries[5];
public:
int _fieldBC;
public:
CLASSDEF;
- CParrotLobbyLinkUpdater() : CParrotLobbyObject(), _fieldBC(1) {}
+ CParrotLobbyLinkUpdater();
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/parrot/parrot_lobby_object.cpp b/engines/titanic/game/parrot/parrot_lobby_object.cpp
index a78ab2b6d9..06222fd063 100644
--- a/engines/titanic/game/parrot/parrot_lobby_object.cpp
+++ b/engines/titanic/game/parrot/parrot_lobby_object.cpp
@@ -26,34 +26,34 @@ namespace Titanic {
EMPTY_MESSAGE_MAP(CParrotLobbyObject, CGameObject);
-int CParrotLobbyObject::_v1;
-int CParrotLobbyObject::_v2;
-int CParrotLobbyObject::_v3;
-int CParrotLobbyObject::_v4;
+bool CParrotLobbyObject::_haveParrot;
+bool CParrotLobbyObject::_havePerch;
+bool CParrotLobbyObject::_haveStick;
+int CParrotLobbyObject::_flags;
void CParrotLobbyObject::init() {
- _v1 = 1;
- _v2 = 1;
- _v3 = 1;
- _v4 = 7;
+ _haveParrot = true;
+ _havePerch = true;
+ _haveStick = true;
+ _flags = 7;
}
void CParrotLobbyObject::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_v1, indent);
- file->writeNumberLine(_v2, indent);
- file->writeNumberLine(_v3, indent);
- file->writeNumberLine(_v4, indent);
+ file->writeNumberLine(_haveParrot, indent);
+ file->writeNumberLine(_havePerch, indent);
+ file->writeNumberLine(_haveStick, indent);
+ file->writeNumberLine(_flags, indent);
CGameObject::save(file, indent);
}
void CParrotLobbyObject::load(SimpleFile *file) {
file->readNumber();
- _v1 = file->readNumber();
- _v2 = file->readNumber();
- _v3 = file->readNumber();
- _v4 = file->readNumber();
+ _haveParrot = file->readNumber();
+ _havePerch = file->readNumber();
+ _haveStick = file->readNumber();
+ _flags = file->readNumber();
CGameObject::load(file);
}
diff --git a/engines/titanic/game/parrot/parrot_lobby_object.h b/engines/titanic/game/parrot/parrot_lobby_object.h
index 5272303888..a210331399 100644
--- a/engines/titanic/game/parrot/parrot_lobby_object.h
+++ b/engines/titanic/game/parrot/parrot_lobby_object.h
@@ -30,10 +30,10 @@ namespace Titanic {
class CParrotLobbyObject : public CGameObject {
DECLARE_MESSAGE_MAP;
public:
- static int _v1;
- static int _v2;
- static int _v3;
- static int _v4;
+ static bool _haveParrot;
+ static bool _havePerch;
+ static bool _haveStick;
+ static int _flags;
static void init();
public:
diff --git a/engines/titanic/game/parrot/parrot_lobby_view_object.cpp b/engines/titanic/game/parrot/parrot_lobby_view_object.cpp
index ae398036a8..1151325676 100644
--- a/engines/titanic/game/parrot/parrot_lobby_view_object.cpp
+++ b/engines/titanic/game/parrot/parrot_lobby_view_object.cpp
@@ -24,16 +24,28 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrotLobbyViewObject, CParrotLobbyObject)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
void CParrotLobbyViewObject::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
+ file->writeNumberLine(_flag, indent);
CParrotLobbyObject::save(file, indent);
}
void CParrotLobbyViewObject::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
+ _flag = file->readNumber();
CParrotLobbyObject::load(file);
}
+bool CParrotLobbyViewObject::ActMsg(CActMsg *msg) {
+ if (msg->_action != "Refresh")
+ return false;
+
+ setVisible(_flag ? _haveParrot : _haveStick);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_lobby_view_object.h b/engines/titanic/game/parrot/parrot_lobby_view_object.h
index 3179bb962d..484d70908e 100644
--- a/engines/titanic/game/parrot/parrot_lobby_view_object.h
+++ b/engines/titanic/game/parrot/parrot_lobby_view_object.h
@@ -28,11 +28,13 @@
namespace Titanic {
class CParrotLobbyViewObject : public CParrotLobbyObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
public:
- int _fieldBC;
+ bool _flag;
public:
CLASSDEF;
- CParrotLobbyViewObject() : CParrotLobbyObject(), _fieldBC(1) {}
+ CParrotLobbyViewObject() : CParrotLobbyObject(), _flag(true) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/parrot/parrot_loser.cpp b/engines/titanic/game/parrot/parrot_loser.cpp
index 6e23ef8314..dc854ee9bd 100644
--- a/engines/titanic/game/parrot/parrot_loser.cpp
+++ b/engines/titanic/game/parrot/parrot_loser.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrotLoser, CGameObject)
+ ON_MESSAGE(LeaveRoomMsg)
+END_MESSAGE_MAP()
+
void CParrotLoser::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +38,10 @@ void CParrotLoser::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CParrotLoser::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ CActMsg actMsg("FreeParrot");
+ actMsg.execute("CarryParrot");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_loser.h b/engines/titanic/game/parrot/parrot_loser.h
index 819fd6614c..e03bfb0727 100644
--- a/engines/titanic/game/parrot/parrot_loser.h
+++ b/engines/titanic/game/parrot/parrot_loser.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CParrotLoser : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp b/engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp
index c83d66cbdf..9dfc866c0e 100644
--- a/engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp
+++ b/engines/titanic/game/parrot/parrot_nut_bowl_actor.cpp
@@ -21,27 +21,95 @@
*/
#include "titanic/game/parrot/parrot_nut_bowl_actor.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrotNutBowlActor, CGameObject)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(BowlStateChangeMsg)
+ ON_MESSAGE(IsEarBowlPuzzleDone)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(ReplaceBowlAndNutsMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(NutPuzzleMsg)
+END_MESSAGE_MAP()
+
CParrotNutBowlActor::CParrotNutBowlActor() : CGameObject(),
- _value1(0), _value2(0) {
+ _puzzleDone(0), _state(0) {
}
void CParrotNutBowlActor::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value1, indent);
- file->writeNumberLine(_value2, indent);
+ file->writeNumberLine(_puzzleDone, indent);
+ file->writeNumberLine(_state, indent);
CGameObject::save(file, indent);
}
void CParrotNutBowlActor::load(SimpleFile *file) {
file->readNumber();
- _value1 = file->readNumber();
- _value2 = file->readNumber();
+ _puzzleDone = file->readNumber();
+ _state = file->readNumber();
CGameObject::load(file);
}
+bool CParrotNutBowlActor::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ if (!_state) {
+ CActMsg actMsg("Jiggle");
+ actMsg.execute("BowlNutsRustler");
+ }
+
+ return true;
+}
+
+bool CParrotNutBowlActor::BowlStateChangeMsg(CBowlStateChangeMsg *msg) {
+ _state = msg->_state;
+ if (msg->_state == 3) {
+ if (!_puzzleDone) {
+ CReplaceBowlAndNutsMsg replaceMsg;
+ replaceMsg.execute(findRoom(), nullptr, MSGFLAG_SCAN);
+ playSound("z#47.wav");
+ }
+
+ _puzzleDone = true;
+ }
+
+ return true;
+}
+
+bool CParrotNutBowlActor::CParrotNutBowlActor::IsEarBowlPuzzleDone(CIsEarBowlPuzzleDone *msg) {
+ msg->_value = _puzzleDone;
+ return true;
+}
+
+bool CParrotNutBowlActor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CParrotNutBowlActor::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) {
+ if (!_puzzleDone)
+ _state = 0;
+ return true;
+}
+
+bool CParrotNutBowlActor::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (!_puzzleDone && _state) {
+ CReplaceBowlAndNutsMsg replaceMsg;
+ replaceMsg.execute(findRoom(), nullptr, MSGFLAG_SCAN);
+ }
+
+ return true;
+}
+
+bool CParrotNutBowlActor::NutPuzzleMsg(CNutPuzzleMsg *msg) {
+ if (msg->_value == "NutsGone")
+ _state = 1;
+ else if (msg->_value == "BowlUnlocked")
+ _state = 2;
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_nut_bowl_actor.h b/engines/titanic/game/parrot/parrot_nut_bowl_actor.h
index d8395bb65a..b228c0ea9e 100644
--- a/engines/titanic/game/parrot/parrot_nut_bowl_actor.h
+++ b/engines/titanic/game/parrot/parrot_nut_bowl_actor.h
@@ -28,8 +28,17 @@
namespace Titanic {
class CParrotNutBowlActor : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool BowlStateChangeMsg(CBowlStateChangeMsg *msg);
+ bool IsEarBowlPuzzleDone(CIsEarBowlPuzzleDone *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool NutPuzzleMsg(CNutPuzzleMsg *msg);
public:
- int _value1, _value2;
+ bool _puzzleDone;
+ int _state;
public:
CLASSDEF;
CParrotNutBowlActor();
diff --git a/engines/titanic/game/parrot/parrot_nut_eater.cpp b/engines/titanic/game/parrot/parrot_nut_eater.cpp
index 309b379ab8..751da931ac 100644
--- a/engines/titanic/game/parrot/parrot_nut_eater.cpp
+++ b/engines/titanic/game/parrot/parrot_nut_eater.cpp
@@ -21,9 +21,17 @@
*/
#include "titanic/game/parrot/parrot_nut_eater.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrotNutEater, CGameObject)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(ReplaceBowlAndNutsMsg)
+ ON_MESSAGE(NutPuzzleMsg)
+ ON_MESSAGE(MovieFrameMsg)
+END_MESSAGE_MAP()
+
CParrotNutEater::CParrotNutEater() : CGameObject(), _fieldBC(0),
_fieldC0(69), _fieldC4(132), _fieldC8(0), _fieldCC(68) {
}
@@ -42,4 +50,48 @@ void CParrotNutEater::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CParrotNutEater::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ CNutPuzzleMsg nutMsg("NutsGone");
+ nutMsg.execute(getRoom(), nullptr, MSGFLAG_SCAN);
+
+ playSound("z#47.wav");
+ return true;
+}
+
+bool CParrotNutEater::ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
+bool CParrotNutEater::NutPuzzleMsg(CNutPuzzleMsg *msg) {
+ if (msg->_value == "Jiggle") {
+ playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ movieEvent(68);
+ movieEvent(132);
+ playSound("z#215.wav");
+
+ CTrueTalkTriggerActionMsg triggerMsg;
+ triggerMsg._param1 = triggerMsg._param2 = 0;
+ triggerMsg.execute("PerchedParrot");
+ }
+
+ return true;
+}
+
+bool CParrotNutEater::MovieFrameMsg(CMovieFrameMsg *msg) {
+ switch (msg->_frameNumber) {
+ case 68:
+ playSound("z#214.wav");
+ break;
+ case 132:
+ playSound("z#216.wav");
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_nut_eater.h b/engines/titanic/game/parrot/parrot_nut_eater.h
index 5dcb01ca11..e09ad63947 100644
--- a/engines/titanic/game/parrot/parrot_nut_eater.h
+++ b/engines/titanic/game/parrot/parrot_nut_eater.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CParrotNutEater : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool ReplaceBowlAndNutsMsg(CReplaceBowlAndNutsMsg *msg);
+ bool NutPuzzleMsg(CNutPuzzleMsg *msg);
+ bool MovieFrameMsg(CMovieFrameMsg *msg);
public:
int _fieldBC;
int _fieldC0;
diff --git a/engines/titanic/game/parrot/parrot_perch_holder.cpp b/engines/titanic/game/parrot/parrot_perch_holder.cpp
index dd8523990b..d594446219 100644
--- a/engines/titanic/game/parrot/parrot_perch_holder.cpp
+++ b/engines/titanic/game/parrot/parrot_perch_holder.cpp
@@ -21,9 +21,19 @@
*/
#include "titanic/game/parrot/parrot_perch_holder.h"
+#include "titanic/game/cage.h"
+#include "titanic/core/project_item.h"
+#include "titanic/npcs/parrot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrotPerchHolder, CMultiDropTarget)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(DropObjectMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
void CParrotPerchHolder::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CMultiDropTarget::save(file, indent);
@@ -34,4 +44,39 @@ void CParrotPerchHolder::load(SimpleFile *file) {
CMultiDropTarget::load(file);
}
+bool CParrotPerchHolder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (CParrot::_v1) {
+ if (CCage::_open) {
+ petDisplayMessage("You cannot take this because the cage is locked shut.");
+ } else if (!CParrot::_v4) {
+ CTrueTalkTriggerActionMsg triggerMsg(280252, 0, 0);
+ triggerMsg.execute(getRoot(), CParrot::_type,
+ MSGFLAG_CLASS_DEF | MSGFLAG_BREAK_IF_HANDLED | MSGFLAG_SCAN);
+ }
+ }
+
+ return true;
+}
+
+bool CParrotPerchHolder::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _fieldF4 = msg->_newStatus;
+ return true;
+}
+
+bool CParrotPerchHolder::DropObjectMsg(CDropObjectMsg *msg) {
+ if (CCage::_open)
+ return false;
+ else
+ return CMultiDropTarget::DropObjectMsg(msg);
+}
+
+bool CParrotPerchHolder::ActMsg(CActMsg *msg) {
+ if (msg->_action == "FlashCore") {
+ playMovie(2, 2, 0);
+ playMovie(1, 1, 0);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_perch_holder.h b/engines/titanic/game/parrot/parrot_perch_holder.h
index ff618f09dc..c1fe243476 100644
--- a/engines/titanic/game/parrot/parrot_perch_holder.h
+++ b/engines/titanic/game/parrot/parrot_perch_holder.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CParrotPerchHolder : public CMultiDropTarget {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool DropObjectMsg(CDropObjectMsg *msg);
+ bool ActMsg(CActMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/parrot/parrot_trigger.cpp b/engines/titanic/game/parrot/parrot_trigger.cpp
index 36e99ada33..b7287ebb6a 100644
--- a/engines/titanic/game/parrot/parrot_trigger.cpp
+++ b/engines/titanic/game/parrot/parrot_trigger.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/game/parrot/parrot_trigger.h"
+#include "titanic/npcs/parrot.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrotTrigger, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CParrotTrigger::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value, indent);
@@ -36,4 +42,11 @@ void CParrotTrigger::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CParrotTrigger::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CTrueTalkTriggerActionMsg triggerMsg(_value, 0, 0);
+ triggerMsg.execute(getRoot(), CParrot::_type,
+ MSGFLAG_CLASS_DEF | MSGFLAG_BREAK_IF_HANDLED | MSGFLAG_SCAN);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_trigger.h b/engines/titanic/game/parrot/parrot_trigger.h
index 28a1663fa8..6fba77b56d 100644
--- a/engines/titanic/game/parrot/parrot_trigger.h
+++ b/engines/titanic/game/parrot/parrot_trigger.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CParrotTrigger : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
int _value;
public:
diff --git a/engines/titanic/game/parrot/player_meets_parrot.cpp b/engines/titanic/game/parrot/player_meets_parrot.cpp
index 6db9345bc0..cdb14516bf 100644
--- a/engines/titanic/game/parrot/player_meets_parrot.cpp
+++ b/engines/titanic/game/parrot/player_meets_parrot.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPlayerMeetsParrot, CGameObject)
+ ON_MESSAGE(EnterRoomMsg)
+END_MESSAGE_MAP()
+
void CPlayerMeetsParrot::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -35,7 +39,7 @@ void CPlayerMeetsParrot::load(SimpleFile *file) {
}
bool CPlayerMeetsParrot::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CPlayerMeetsParrot::handleEvent");
+ stateSet24();
return true;
}
diff --git a/engines/titanic/game/parrot/player_meets_parrot.h b/engines/titanic/game/parrot/player_meets_parrot.h
index 9cee9ee322..edae18801f 100644
--- a/engines/titanic/game/parrot/player_meets_parrot.h
+++ b/engines/titanic/game/parrot/player_meets_parrot.h
@@ -29,6 +29,7 @@
namespace Titanic {
class CPlayerMeetsParrot : public CGameObject {
+ DECLARE_MESSAGE_MAP;
protected:
bool EnterRoomMsg(CEnterRoomMsg *msg);
public:
diff --git a/engines/titanic/game/pet/pet.cpp b/engines/titanic/game/pet/pet.cpp
index cd4e16d38c..99c9e01eb3 100644
--- a/engines/titanic/game/pet/pet.cpp
+++ b/engines/titanic/game/pet/pet.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/game/pet/pet.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPET, CGameObject)
+ ON_MESSAGE(ShowTextMsg)
+END_MESSAGE_MAP()
+
CPET::CPET() : CGameObject(), _fieldBC(0), _fieldC0(3),
_fieldC4(0), _fieldC8(0), _fieldD8(0), _fieldDC(0) {
}
@@ -54,4 +59,11 @@ void CPET::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CPET::ShowTextMsg(CShowTextMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (pet)
+ pet->petDisplayMessage(1, msg->_value);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pet/pet.h b/engines/titanic/game/pet/pet.h
index cdad649401..de31a423d0 100644
--- a/engines/titanic/game/pet/pet.h
+++ b/engines/titanic/game/pet/pet.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CPET : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ShowTextMsg(CShowTextMsg *msg);
public:
int _fieldBC;
int _fieldC0;
diff --git a/engines/titanic/game/pet/pet_lift.cpp b/engines/titanic/game/pet/pet_lift.cpp
index 39b0d01540..afa9dd04cd 100644
--- a/engines/titanic/game/pet/pet_lift.cpp
+++ b/engines/titanic/game/pet/pet_lift.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/game/pet/pet_lift.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPETLift, CPETTransport)
+ ON_MESSAGE(TransportMsg)
+END_MESSAGE_MAP()
+
void CPETLift::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CPETTransport::save(file, indent);
@@ -34,4 +39,36 @@ void CPETLift::load(SimpleFile *file) {
CPETTransport::load(file);
}
+bool CPETLift::TransportMsg(CTransportMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (msg->_value1 != 1)
+ return false;
+
+ int floorNum = -1;
+ if (msg->_roomName == "TopOfWell") {
+ floorNum = 1;
+ } else if (msg->_roomName == "BottomOfWell") {
+ floorNum = 39;
+ } else if (msg->_roomName == "PlayersRoom" && pet) {
+ int assignedFloor = pet->getAssignedFloorNum();
+ if (assignedFloor < 1 || assignedFloor > 39) {
+ pet->petDisplayMessage("You have not assigned a room to go to.");
+ floorNum = -1;
+ }
+ }
+
+ if (floorNum != -1) {
+ int elevatorNum = pet ? pet->getRoomsElevatorNum() : 0;
+
+ if ((elevatorNum == 2 || elevatorNum == 4) && floorNum > 27) {
+ petDisplayMessage("Sorry, this elevator does not go below floor 27.");
+ } else {
+ CTrueTalkTriggerActionMsg triggerMsg(2, floorNum, 0);
+ triggerMsg.execute("Liftbot");
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pet/pet_lift.h b/engines/titanic/game/pet/pet_lift.h
index 88b4e1c029..ce3aace1a6 100644
--- a/engines/titanic/game/pet/pet_lift.h
+++ b/engines/titanic/game/pet/pet_lift.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CPETLift : public CPETTransport {
+ DECLARE_MESSAGE_MAP;
+ bool TransportMsg(CTransportMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/pet/pet_monitor.cpp b/engines/titanic/game/pet/pet_monitor.cpp
index 6a0d207a55..2716a81fa8 100644
--- a/engines/titanic/game/pet/pet_monitor.cpp
+++ b/engines/titanic/game/pet/pet_monitor.cpp
@@ -21,6 +21,8 @@
*/
#include "titanic/game/pet/pet_monitor.h"
+#include "titanic/core/room_item.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
@@ -39,7 +41,23 @@ void CPETMonitor::load(SimpleFile *file) {
}
bool CPETMonitor::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CPETMonitor::handleEvent");
+ bool flag = true;
+ if (msg->_newRoom && msg->_oldRoom) {
+ CString oldRoomName = msg->_oldRoom->getName();
+ CString newRoomName = msg->_newRoom->getName();
+
+ if (newRoomName == "SgtLobby" && oldRoomName == "SGTState")
+ flag = false;
+ }
+
+ if (flag) {
+ CPetControl *pet = getPetControl();
+ if (pet) {
+ pet->setRoomsRoomNum(0);
+ pet->resetRoomsHighlight();
+ }
+ }
+
return true;
}
diff --git a/engines/titanic/game/pet/pet_pellerator.cpp b/engines/titanic/game/pet/pet_pellerator.cpp
index a29942ca59..59516ebcde 100644
--- a/engines/titanic/game/pet/pet_pellerator.cpp
+++ b/engines/titanic/game/pet/pet_pellerator.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPETPellerator, CPETTransport)
+ ON_MESSAGE(PETActivateMsg)
+END_MESSAGE_MAP()
+
void CPETPellerator::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CPETTransport::save(file, indent);
@@ -34,4 +38,24 @@ void CPETPellerator::load(SimpleFile *file) {
CPETTransport::load(file);
}
+bool CPETPellerator::PETActivateMsg(CPETActivateMsg *msg) {
+ CStatusChangeMsg statusMsg;
+
+ if (msg->_name == "PromenadeDeck")
+ statusMsg._newStatus = 0;
+ else if (msg->_name == "MusicRoom")
+ statusMsg._newStatus = 1;
+ else if (msg->_name == "Bar")
+ statusMsg._newStatus = 2;
+ else if (msg->_name == "TopOfWell")
+ statusMsg._newStatus = 4;
+ else if (msg->_name == "1stClassRestaurant")
+ statusMsg._newStatus = 5;
+ else if (msg->_name == "Arboretum")
+ statusMsg._newStatus = 6;
+
+ statusMsg.execute("PelleratorObject");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pet/pet_pellerator.h b/engines/titanic/game/pet/pet_pellerator.h
index 9b90c9af28..51af6f1bcd 100644
--- a/engines/titanic/game/pet/pet_pellerator.h
+++ b/engines/titanic/game/pet/pet_pellerator.h
@@ -24,10 +24,13 @@
#define TITANIC_PET_PELLERATOR_H
#include "titanic/game/pet/pet_transport.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CPETPellerator : public CPETTransport {
+ DECLARE_MESSAGE_MAP;
+ bool PETActivateMsg(CPETActivateMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/pet/pet_sentinal.cpp b/engines/titanic/game/pet/pet_sentinal.cpp
index 1b647d7c62..ac4cbc8418 100644
--- a/engines/titanic/game/pet/pet_sentinal.cpp
+++ b/engines/titanic/game/pet/pet_sentinal.cpp
@@ -21,17 +21,46 @@
*/
#include "titanic/game/pet/pet_sentinal.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPETSentinal, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
+CPETSentinal::CPETSentinal() : CGameObject(), _elevatorNum(0),
+ _wellEntry(0), _resetHighlight(0) {
+}
+
void CPETSentinal::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
+ file->writeNumberLine(_elevatorNum, indent);
+ file->writeNumberLine(_wellEntry, indent);
+ file->writeNumberLine(_resetHighlight, indent);
CGameObject::save(file, indent);
}
void CPETSentinal::load(SimpleFile *file) {
file->readNumber();
+ _elevatorNum = file->readNumber();
+ _wellEntry = file->readNumber();
+ _resetHighlight = file->readNumber();
CGameObject::load(file);
}
+bool CPETSentinal::EnterViewMsg(CEnterViewMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (pet) {
+ if (_elevatorNum != -1)
+ pet->setRoomsElevatorNum(_elevatorNum);
+ if (_wellEntry)
+ pet->setRoomsWellEntry(_wellEntry);
+ if (_resetHighlight)
+ pet->resetRoomsHighlight();
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pet/pet_sentinal.h b/engines/titanic/game/pet/pet_sentinal.h
index f7f9fef0ba..150fe4a87e 100644
--- a/engines/titanic/game/pet/pet_sentinal.h
+++ b/engines/titanic/game/pet/pet_sentinal.h
@@ -28,8 +28,15 @@
namespace Titanic {
class CPETSentinal : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+private:
+ int _elevatorNum;
+ int _wellEntry;
+ bool _resetHighlight;
public:
CLASSDEF;
+ CPETSentinal();
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/pet/pet_sounds.cpp b/engines/titanic/game/pet/pet_sounds.cpp
index d612c745bb..c7f3cd3bf8 100644
--- a/engines/titanic/game/pet/pet_sounds.cpp
+++ b/engines/titanic/game/pet/pet_sounds.cpp
@@ -24,16 +24,40 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPETSounds, CGameObject)
+ ON_MESSAGE(PETPlaySoundMsg)
+ ON_MESSAGE(LoadSuccessMsg)
+END_MESSAGE_MAP()
+
void CPETSounds::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_ticks, indent);
CGameObject::save(file, indent);
}
void CPETSounds::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _ticks = file->readNumber();
CGameObject::load(file);
}
+bool CPETSounds::PETPlaySoundMsg(CPETPlaySoundMsg *msg) {
+ if (msg->_soundNum == 1) {
+ playSound("z#65.wav");
+ } else if (msg->_soundNum == 2 && stateGet24()) {
+ uint ticks = getTicksCount();
+ if (!_ticks || ticks > (_ticks + 12000)) {
+ playSound("z#36.wav");
+ _ticks = ticks;
+ }
+ }
+
+ return true;
+}
+
+bool CPETSounds::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ _ticks = 0;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pet/pet_sounds.h b/engines/titanic/game/pet/pet_sounds.h
index 1d3acdb5f3..2262fde916 100644
--- a/engines/titanic/game/pet/pet_sounds.h
+++ b/engines/titanic/game/pet/pet_sounds.h
@@ -24,15 +24,19 @@
#define TITANIC_PET_SOUNDS_H
#include "titanic/core/game_object.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CPETSounds : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool PETPlaySoundMsg(CPETPlaySoundMsg *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
public:
- int _value;
+ uint _ticks;
public:
CLASSDEF;
- CPETSounds() : CGameObject(), _value(0) {}
+ CPETSounds() : CGameObject(), _ticks(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/pet/pet_transition.cpp b/engines/titanic/game/pet/pet_transition.cpp
index 33cc36ca11..ec10569236 100644
--- a/engines/titanic/game/pet/pet_transition.cpp
+++ b/engines/titanic/game/pet/pet_transition.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/game/pet/pet_transition.h"
+#include "titanic/pet_control/pet_control.h"
+#include "titanic/core/view_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPETTransition, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CPETTransition::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +40,21 @@ void CPETTransition::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CPETTransition::EnterViewMsg(CEnterViewMsg *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (compareRoomNameTo("1stClassLobby") && pet) {
+ int elevatorNum = pet->getRoomsElevatorNum();
+ CString nodeView = msg->_newView->getNodeViewName();
+
+ if (nodeView == "Node 1.E") {
+ pet->setRoomsElevatorNum((elevatorNum == 1 || elevatorNum == 2) ? 1 : 3);
+ } else if (nodeView == "Node 1.W") {
+ pet->setRoomsElevatorNum((elevatorNum == 1 || elevatorNum == 2) ? 2 : 4);
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pet/pet_transition.h b/engines/titanic/game/pet/pet_transition.h
index 4abf16d509..d0fa20ccc5 100644
--- a/engines/titanic/game/pet/pet_transition.h
+++ b/engines/titanic/game/pet/pet_transition.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CPETTransition : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/pet/pet_transport.cpp b/engines/titanic/game/pet/pet_transport.cpp
index 9661cace2c..a48e70ed01 100644
--- a/engines/titanic/game/pet/pet_transport.cpp
+++ b/engines/titanic/game/pet/pet_transport.cpp
@@ -39,7 +39,7 @@ void CPETTransport::load(SimpleFile *file) {
}
bool CPETTransport::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CPETTransport::handleEvent");
+ petClear();
return true;
}
diff --git a/engines/titanic/game/pet_disabler.cpp b/engines/titanic/game/pet_disabler.cpp
index 2275156503..c4946fe39f 100644
--- a/engines/titanic/game/pet_disabler.cpp
+++ b/engines/titanic/game/pet_disabler.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPetDisabler, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
void CPetDisabler::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeQuotedLine(_value, indent);
@@ -36,4 +41,14 @@ void CPetDisabler::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CPetDisabler::EnterViewMsg(CEnterViewMsg *msg) {
+ petLockInput();
+ return true;
+}
+
+bool CPetDisabler::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petUnlockInput();
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pet_disabler.h b/engines/titanic/game/pet_disabler.h
index 92b4dff0a8..06e99be49e 100644
--- a/engines/titanic/game/pet_disabler.h
+++ b/engines/titanic/game/pet_disabler.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CPetDisabler : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
CString _value;
public:
diff --git a/engines/titanic/game/phonograph.cpp b/engines/titanic/game/phonograph.cpp
index 9740e29273..408cfa3413 100644
--- a/engines/titanic/game/phonograph.cpp
+++ b/engines/titanic/game/phonograph.cpp
@@ -24,9 +24,18 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPhonograph, CMusicPlayer)
+ ON_MESSAGE(PhonographPlayMsg)
+ ON_MESSAGE(PhonographStopMsg)
+ ON_MESSAGE(PhonographRecordMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(MusicHasStartedMsg)
+END_MESSAGE_MAP()
+
CPhonograph::CPhonograph() : CMusicPlayer(),
- _fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0),
- _fieldF0(0), _fieldF4(0) {
+ _fieldE0(false), _fieldE4(0), _fieldE8(0), _fieldEC(0),
+ _fieldF0(0), _fieldF4(0) {
}
void CPhonograph::save(SimpleFile *file, int indent) {
@@ -55,8 +64,113 @@ void CPhonograph::load(SimpleFile *file) {
CMusicPlayer::load(file);
}
+bool CPhonograph::PhonographPlayMsg(CPhonographPlayMsg *msg) {
+ CQueryCylinderHolderMsg holderMsg;
+ holderMsg.execute(this);
+ if (!holderMsg._value2) {
+ _fieldE0 = false;
+ return true;
+ }
+
+ CQueryCylinderMsg cylinderMsg;
+ cylinderMsg.execute(holderMsg._target);
+
+ if (cylinderMsg._name.empty()) {
+ _fieldE0 = false;
+ } else if (cylinderMsg._name.hasPrefix("STMusic")) {
+ CStartMusicMsg startMsg(this);
+ startMsg.execute(this);
+ _fieldE0 = true;
+ msg->_value = 1;
+ } else {
+ stopGlobalSound(0, -1);
+ playGlobalSound(cylinderMsg._name, -2, true, true, 0);
+ _fieldE0 = true;
+ msg->_value = 1;
+ }
+
+ return true;
+}
+
+bool CPhonograph::PhonographStopMsg(CPhonographStopMsg *msg) {
+ CQueryCylinderHolderMsg holderMsg;
+ holderMsg.execute(this);
+ if (!holderMsg._value2)
+ return true;
+
+ _fieldE0 = false;
+ CQueryCylinderMsg cylinderMsg;
+ cylinderMsg.execute(holderMsg._target);
+
+ if (_fieldE0) {
+ if (!cylinderMsg._name.empty()) {
+ if (cylinderMsg._name.hasPrefix("STMusic")) {
+ CStopMusicMsg stopMsg;
+ stopMsg.execute(this);
+ } else {
+ stopGlobalSound(msg->_value1, -1);
+ }
+ msg->_value2 = 1;
+ }
+
+ if (!msg->_value3)
+ _fieldE0 = false;
+ } else if (_fieldE4) {
+ _fieldE4 = false;
+ msg->_value2 = 1;
+ }
+
+ return true;
+}
+
+bool CPhonograph::PhonographRecordMsg(CPhonographRecordMsg *msg) {
+ if (!_fieldE0 && !_fieldE4 && !_fieldE8) {
+ CQueryCylinderHolderMsg holderMsg;
+ holderMsg.execute(this);
+
+ if (holderMsg._value2) {
+ _fieldE4 = true;
+ CErasePhonographCylinderMsg eraseMsg;
+ eraseMsg.execute(holderMsg._target);
+ } else {
+ _fieldE4 = false;
+ }
+ }
+
+ return true;
+}
+
bool CPhonograph::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CPhonograph::handleEvent");
+ if (_fieldE0) {
+ CPhonographPlayMsg playMsg;
+ playMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CPhonograph::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ if (_fieldE0) {
+ CPhonographStopMsg stopMsg;
+ stopMsg._value1 = 1;
+ stopMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CPhonograph::MusicHasStartedMsg(CMusicHasStartedMsg *msg) {
+ if (_fieldE4) {
+ CQueryCylinderHolderMsg holderMsg;
+ holderMsg.execute(this);
+ if (holderMsg._value2) {
+ CRecordOntoCylinderMsg recordMsg;
+ recordMsg.execute(holderMsg._target);
+ } else {
+ _fieldE4 = false;
+ }
+ }
+
return true;
}
diff --git a/engines/titanic/game/phonograph.h b/engines/titanic/game/phonograph.h
index 274d4ba367..b13a5ea910 100644
--- a/engines/titanic/game/phonograph.h
+++ b/engines/titanic/game/phonograph.h
@@ -29,10 +29,16 @@
namespace Titanic {
class CPhonograph : public CMusicPlayer {
+ DECLARE_MESSAGE_MAP;
+ bool PhonographPlayMsg(CPhonographPlayMsg *msg);
+ bool PhonographStopMsg(CPhonographStopMsg *msg);
+ bool PhonographRecordMsg(CPhonographRecordMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool MusicHasStartedMsg(CMusicHasStartedMsg *msg);
protected:
CString _string2;
- int _fieldE0;
+ bool _fieldE0;
int _fieldE4;
int _fieldE8;
int _fieldEC;
diff --git a/engines/titanic/game/phonograph_lid.cpp b/engines/titanic/game/phonograph_lid.cpp
index a0518420f7..3741749fbf 100644
--- a/engines/titanic/game/phonograph_lid.cpp
+++ b/engines/titanic/game/phonograph_lid.cpp
@@ -24,16 +24,63 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPhonographLid, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(LockPhonographMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
void CPhonographLid::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_open, indent);
CGameObject::save(file, indent);
}
void CPhonographLid::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _open = file->readNumber();
CGameObject::load(file);
}
+bool CPhonographLid::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CQueryPhonographState stateMsg;
+ stateMsg.execute(getParent(), nullptr, MSGFLAG_SCAN);
+ if (stateMsg._value) {
+ if (_open) {
+ CGameObject *lock = dynamic_cast<CGameObject *>(findByName("Music System Lock"));
+ if (lock)
+ lock->setVisible(false);
+ playMovie(0, 27, 0);
+ } else {
+ playMovie(27, 55, 0);
+ }
+
+ _open = !_open;
+ } else {
+ petDisplayMessage(0, "This is the restaurant music system. It appears to be locked.");
+ }
+
+ return true;
+}
+
+bool CPhonographLid::MovieEndMsg(CMovieEndMsg *msg) {
+ // WORKAROUND: Redundant code in original not included
+ return true;
+}
+
+bool CPhonographLid::LockPhonographMsg(CLockPhonographMsg *msg) {
+ _cursorId = msg->_value ? CURSOR_INVALID : CURSOR_ARROW;
+ return true;
+}
+
+bool CPhonographLid::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (_open) {
+ playMovie(27, 55, MOVIE_GAMESTATE);
+ _open = false;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/phonograph_lid.h b/engines/titanic/game/phonograph_lid.h
index ab32be268b..4e71d70ec2 100644
--- a/engines/titanic/game/phonograph_lid.h
+++ b/engines/titanic/game/phonograph_lid.h
@@ -28,11 +28,16 @@
namespace Titanic {
class CPhonographLid : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool LockPhonographMsg(CLockPhonographMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
private:
- int _value;
+ bool _open;
public:
CLASSDEF;
- CPhonographLid() : CGameObject(), _value(0) {}
+ CPhonographLid() : CGameObject(), _open(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/pickup/pick_up.cpp b/engines/titanic/game/pickup/pick_up.cpp
index c660a36a32..64d2d1d0d2 100644
--- a/engines/titanic/game/pickup/pick_up.cpp
+++ b/engines/titanic/game/pickup/pick_up.cpp
@@ -24,16 +24,26 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPickUp, CGameObject)
+ ON_MESSAGE(StatusChangeMsg)
+END_MESSAGE_MAP()
+
void CPickUp::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
+ file->writeNumberLine(_enabled, indent);
CGameObject::save(file, indent);
}
void CPickUp::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
+ _enabled = file->readNumber();
CGameObject::load(file);
}
+bool CPickUp::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _enabled = msg->_newStatus == 1;
+ setVisible(_enabled);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pickup/pick_up.h b/engines/titanic/game/pickup/pick_up.h
index f0b6794442..f5ee06fd32 100644
--- a/engines/titanic/game/pickup/pick_up.h
+++ b/engines/titanic/game/pickup/pick_up.h
@@ -28,11 +28,13 @@
namespace Titanic {
class CPickUp : public CGameObject {
-private:
- int _fieldBC;
+ DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+protected:
+ bool _enabled;
public:
CLASSDEF;
- CPickUp() : CGameObject(), _fieldBC(0) {}
+ CPickUp() : CGameObject(), _enabled(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/pickup/pick_up_bar_glass.cpp b/engines/titanic/game/pickup/pick_up_bar_glass.cpp
index 85b883281e..9da17b139e 100644
--- a/engines/titanic/game/pickup/pick_up_bar_glass.cpp
+++ b/engines/titanic/game/pickup/pick_up_bar_glass.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/game/pickup/pick_up_bar_glass.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPickUpBarGlass, CPickUp)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CPickUpBarGlass::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CPickUp::save(file, indent);
@@ -34,4 +41,48 @@ void CPickUpBarGlass::load(SimpleFile *file) {
CPickUp::load(file);
}
+bool CPickUpBarGlass::StatusChangeMsg(CStatusChangeMsg *msg) {
+ switch (msg->_newStatus) {
+ case 0:
+ setVisible(false);
+ _enabled = false;
+ break;
+ case 1:
+ setVisible(true);
+ _enabled = true;
+ break;
+ case 2:
+ setVisible(true);
+ _enabled = false;
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CPickUpBarGlass::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (checkStartDragging(msg) && _enabled) {
+ CTurnOn onMsg;
+ onMsg.execute("BeerGlass");
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("BeerGlass");
+ CPassOnDragStartMsg passMsg(msg->_mousePos, 1, 3);
+ passMsg.execute("BeerGlass");
+
+ msg->_dragItem = getRoot()->findByName("BeerGlass");
+
+ CActMsg actMsg("PlayerTakesGlass");
+ actMsg.execute("Barbot");
+ return true;
+ } else {
+ return false;
+ }
+}
+
+bool CPickUpBarGlass::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pickup/pick_up_bar_glass.h b/engines/titanic/game/pickup/pick_up_bar_glass.h
index b5ef6f5a47..d273d96170 100644
--- a/engines/titanic/game/pickup/pick_up_bar_glass.h
+++ b/engines/titanic/game/pickup/pick_up_bar_glass.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CPickUpBarGlass : public CPickUp {
+ DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/pickup/pick_up_hose.cpp b/engines/titanic/game/pickup/pick_up_hose.cpp
index 7375ddaa63..d07088cefd 100644
--- a/engines/titanic/game/pickup/pick_up_hose.cpp
+++ b/engines/titanic/game/pickup/pick_up_hose.cpp
@@ -21,14 +21,24 @@
*/
#include "titanic/game/pickup/pick_up_hose.h"
+#include "titanic/core/project_item.h"
+#include "titanic/core/room_item.h"
+#include "titanic/core/view_item.h"
namespace Titanic {
-int CPickUpHose::_v1;
+BEGIN_MESSAGE_MAP(CPickUpHose, CPickUp)
+ ON_MESSAGE(MouseDragStartMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+bool CPickUpHose::_v1;
void CPickUpHose::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeQuotedLine(_target, indent);
file->writeNumberLine(_v1, indent);
CPickUp::save(file, indent);
@@ -36,10 +46,61 @@ void CPickUpHose::save(SimpleFile *file, int indent) {
void CPickUpHose::load(SimpleFile *file) {
file->readNumber();
- _string1 = file->readString();
+ _target = file->readString();
_v1 = file->readNumber();
CPickUp::load(file);
}
+bool CPickUpHose::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkStartDragging(msg))
+ return true;
+ if (_v1 || !_enabled)
+ return false;
+
+ CViewItem *view = getView();
+ if (view) {
+ _v1 = true;
+ CRoomItem *room = locateRoom("Arboretum");
+ CTreeItem *hose = room ? room->findByName("Hose") : nullptr;
+
+ if (!hose) {
+ room = locateRoom("FrozenArboretum");
+ if (room)
+ hose = room->findByName("Hose");
+ }
+
+ if (hose) {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute(this);
+ moveUnder(view);
+
+ CPassOnDragStartMsg passMsg(msg->_mousePos, 1);
+ passMsg.execute("Hose");
+
+ msg->_dragItem = getRoot()->findByName("Hose");
+ _cursorId = CURSOR_IGNORE;
+
+ CActMsg actMsg("PlayerGetsHose");
+ actMsg.execute(_target);
+ }
+ }
+
+ return true;
+}
+
+bool CPickUpHose::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _cursorId = msg->_newStatus == 1 ? CURSOR_HAND : CURSOR_IGNORE;
+ return true;
+}
+
+bool CPickUpHose::EnterViewMsg(CEnterViewMsg *msg) {
+ _cursorId = CURSOR_IGNORE;
+ return true;
+}
+
+bool CPickUpHose::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return _enabled;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pickup/pick_up_hose.h b/engines/titanic/game/pickup/pick_up_hose.h
index 80ccedc845..2ad7c2a583 100644
--- a/engines/titanic/game/pickup/pick_up_hose.h
+++ b/engines/titanic/game/pickup/pick_up_hose.h
@@ -28,10 +28,15 @@
namespace Titanic {
class CPickUpHose : public CPickUp {
+ DECLARE_MESSAGE_MAP;
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
private:
- static int _v1;
+ static bool _v1;
- CString _string1;
+ CString _target;
public:
CLASSDEF;
diff --git a/engines/titanic/game/pickup/pick_up_lemon.cpp b/engines/titanic/game/pickup/pick_up_lemon.cpp
index 772114f76c..5109c36304 100644
--- a/engines/titanic/game/pickup/pick_up_lemon.cpp
+++ b/engines/titanic/game/pickup/pick_up_lemon.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/game/pickup/pick_up_lemon.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPickUpLemon, CPickUp)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CPickUpLemon::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CPickUp::save(file, indent);
@@ -34,4 +40,23 @@ void CPickUpLemon::load(SimpleFile *file) {
CPickUp::load(file);
}
+bool CPickUpLemon::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CPickUpLemon::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkStartDragging(msg))
+ return true;
+ if (!_enabled)
+ return false;
+
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("Lemon");
+ CPassOnDragStartMsg passMsg(msg->_mousePos, 1);
+ passMsg.execute("Lemon");
+
+ msg->_dragItem = getRoot()->findByName("Lemon");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pickup/pick_up_lemon.h b/engines/titanic/game/pickup/pick_up_lemon.h
index 0312c71012..c196acdeaf 100644
--- a/engines/titanic/game/pickup/pick_up_lemon.h
+++ b/engines/titanic/game/pickup/pick_up_lemon.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CPickUpLemon : public CPickUp {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/pickup/pick_up_speech_centre.cpp b/engines/titanic/game/pickup/pick_up_speech_centre.cpp
index 0b9a8d2c48..5e99c0a3b7 100644
--- a/engines/titanic/game/pickup/pick_up_speech_centre.cpp
+++ b/engines/titanic/game/pickup/pick_up_speech_centre.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/game/pickup/pick_up_speech_centre.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPickUpSpeechCentre, CPickUp)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CPickUpSpeechCentre::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CPickUp::save(file, indent);
@@ -34,4 +41,33 @@ void CPickUpSpeechCentre::load(SimpleFile *file) {
CPickUp::load(file);
}
+bool CPickUpSpeechCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CPickUpSpeechCentre::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _enabled = msg->_newStatus == 1;
+ return true;
+}
+
+bool CPickUpSpeechCentre::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (checkStartDragging(msg)) {
+ if (_enabled) {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("SpeechCentre");
+ CPassOnDragStartMsg passMsg(msg->_mousePos, 1);
+ passMsg.execute("SpeechCentre");
+
+ msg->_dragItem = getRoot()->findByName("SpeechCentre");
+
+ CActMsg actMsg("PlayerGetsSpeechCentre");
+ actMsg.execute("SeasonalAdjust");
+ } else {
+ petDisplayMessage("You can't pick this up on account of it being stuck to the branch.");
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pickup/pick_up_speech_centre.h b/engines/titanic/game/pickup/pick_up_speech_centre.h
index 29dce04fb3..81ee0b5d77 100644
--- a/engines/titanic/game/pickup/pick_up_speech_centre.h
+++ b/engines/titanic/game/pickup/pick_up_speech_centre.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CPickUpSpeechCentre : public CPickUp {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/pickup/pick_up_vis_centre.cpp b/engines/titanic/game/pickup/pick_up_vis_centre.cpp
index 796e46778c..baf1763d09 100644
--- a/engines/titanic/game/pickup/pick_up_vis_centre.cpp
+++ b/engines/titanic/game/pickup/pick_up_vis_centre.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPickUpVisCentre, CPickUp)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
void CPickUpVisCentre::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CPickUp::save(file, indent);
@@ -34,4 +39,22 @@ void CPickUpVisCentre::load(SimpleFile *file) {
CPickUp::load(file);
}
+
+bool CPickUpVisCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CPickUpVisCentre::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkStartDragging(msg) || !_enabled)
+ return false;
+
+ setVisible(false);
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("VisionCentre");
+ msg->execute("VisionCentre");
+ CActMsg actMsg("PlayerTakesVisCentre");
+ actMsg.execute("Barbot");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/pickup/pick_up_vis_centre.h b/engines/titanic/game/pickup/pick_up_vis_centre.h
index 4f808f73c5..a5f59211d3 100644
--- a/engines/titanic/game/pickup/pick_up_vis_centre.h
+++ b/engines/titanic/game/pickup/pick_up_vis_centre.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CPickUpVisCentre : public CPickUp {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp b/engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp
index a8a33fe1b1..6e5037f237 100644
--- a/engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp
+++ b/engines/titanic/game/placeholder/bar_shelf_vis_centre.cpp
@@ -24,16 +24,44 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBarShelfVisCentre, CPlaceHolder)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CBarShelfVisCentre::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
- CPlaceHolderItem::save(file, indent);
+ file->writeNumberLine(_flag, indent);
+ CPlaceHolder::save(file, indent);
}
void CBarShelfVisCentre::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
- CPlaceHolderItem::load(file);
+ _flag = file->readNumber();
+ CPlaceHolder::load(file);
}
+bool CBarShelfVisCentre::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_flag) {
+ CActMsg actMsg("ClickOnVision");
+ actMsg.execute("Barbot");
+ addTimer(3000);
+ _flag = true;
+ }
+
+ return true;
+}
+
+bool CBarShelfVisCentre::TimerMsg(CTimerMsg *msg) {
+ _flag = false;
+ return true;
+}
+
+bool CBarShelfVisCentre::EnterViewMsg(CEnterViewMsg *msg) {
+ _flag = false;
+ return true;
+}
+
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/placeholder/bar_shelf_vis_centre.h b/engines/titanic/game/placeholder/bar_shelf_vis_centre.h
index a53ef2633f..8ad3dcb8d1 100644
--- a/engines/titanic/game/placeholder/bar_shelf_vis_centre.h
+++ b/engines/titanic/game/placeholder/bar_shelf_vis_centre.h
@@ -23,16 +23,20 @@
#ifndef TITANIC_BAR_SHELF_VIS_CENTRE_H
#define TITANIC_BAR_SHELF_VIS_CENTRE_H
-#include "titanic/game/placeholder/place_holder_item.h"
+#include "titanic/game/placeholder/place_holder.h"
namespace Titanic {
-class CBarShelfVisCentre : public CPlaceHolderItem {
+class CBarShelfVisCentre : public CPlaceHolder {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CBarShelfVisCentre() : CPlaceHolderItem(), _value(0) {}
+ CBarShelfVisCentre() : CPlaceHolder(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/placeholder/lemon_on_bar.cpp b/engines/titanic/game/placeholder/lemon_on_bar.cpp
index 08d686e81a..e9cf6a309a 100644
--- a/engines/titanic/game/placeholder/lemon_on_bar.cpp
+++ b/engines/titanic/game/placeholder/lemon_on_bar.cpp
@@ -24,16 +24,29 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CLemonOnBar, CPlaceHolder)
+ ON_MESSAGE(VisibleMsg)
+END_MESSAGE_MAP()
+
void CLemonOnBar::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writePoint(_pos1, indent);
- CPlaceHolderItem::save(file, indent);
+ file->writePoint(_lemonPos, indent);
+ CPlaceHolder::save(file, indent);
}
void CLemonOnBar::load(SimpleFile *file) {
file->readNumber();
- _pos1 = file->readPoint();
- CPlaceHolderItem::load(file);
+ _lemonPos = file->readPoint();
+ CPlaceHolder::load(file);
+}
+
+bool CLemonOnBar::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(msg->_visible);
+ if (msg->_visible)
+ setPosition(_lemonPos);
+ else
+ setPosition(Point(0, 0));
+ return true;
}
} // End of namespace Titanic
diff --git a/engines/titanic/game/placeholder/lemon_on_bar.h b/engines/titanic/game/placeholder/lemon_on_bar.h
index 92dd54c49b..c6512ced67 100644
--- a/engines/titanic/game/placeholder/lemon_on_bar.h
+++ b/engines/titanic/game/placeholder/lemon_on_bar.h
@@ -23,13 +23,15 @@
#ifndef TITANIC_LEMON_ON_BAR_H
#define TITANIC_LEMON_ON_BAR_H
-#include "titanic/game/placeholder/place_holder_item.h"
+#include "titanic/game/placeholder/place_holder.h"
namespace Titanic {
-class CLemonOnBar : public CPlaceHolderItem {
+class CLemonOnBar : public CPlaceHolder {
+ DECLARE_MESSAGE_MAP;
+ bool VisibleMsg(CVisibleMsg *msg);
private:
- Point _pos1;
+ Point _lemonPos;
public:
CLASSDEF;
diff --git a/engines/titanic/game/call_pellerator.cpp b/engines/titanic/game/placeholder/place_holder.cpp
index 0ea48131b1..ae42cabc29 100644
--- a/engines/titanic/game/call_pellerator.cpp
+++ b/engines/titanic/game/placeholder/place_holder.cpp
@@ -20,18 +20,27 @@
*
*/
-#include "titanic/game/call_pellerator.h"
+#include "titanic/game/placeholder/place_holder.h"
namespace Titanic {
-void CCallPellerator::save(SimpleFile *file, int indent) {
+BEGIN_MESSAGE_MAP(CPlaceHolder, CGameObject)
+ ON_MESSAGE(VisibleMsg)
+END_MESSAGE_MAP()
+
+void CPlaceHolder::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
}
-void CCallPellerator::load(SimpleFile *file) {
+void CPlaceHolder::load(SimpleFile *file) {
file->readNumber();
CGameObject::load(file);
}
+bool CPlaceHolder::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(msg->_visible);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/placeholder/place_holder_item.h b/engines/titanic/game/placeholder/place_holder.h
index de04a64bf7..b1aa041710 100644
--- a/engines/titanic/game/placeholder/place_holder_item.h
+++ b/engines/titanic/game/placeholder/place_holder.h
@@ -27,7 +27,9 @@
namespace Titanic {
-class CPlaceHolderItem : public CGameObject {
+class CPlaceHolder : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool VisibleMsg(CVisibleMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/placeholder/tv_on_bar.cpp b/engines/titanic/game/placeholder/tv_on_bar.cpp
index efbbe50461..710b5a346e 100644
--- a/engines/titanic/game/placeholder/tv_on_bar.cpp
+++ b/engines/titanic/game/placeholder/tv_on_bar.cpp
@@ -24,16 +24,30 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CTVOnBar, CPlaceHolder)
+ ON_MESSAGE(VisibleMsg)
+END_MESSAGE_MAP()
+
void CTVOnBar::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writePoint(_pos1, indent);
- CPlaceHolderItem::save(file, indent);
+ file->writePoint(_tvPos, indent);
+ CPlaceHolder::save(file, indent);
}
void CTVOnBar::load(SimpleFile *file) {
file->readNumber();
- _pos1 = file->readPoint();
- CPlaceHolderItem::load(file);
+ _tvPos = file->readPoint();
+ CPlaceHolder::load(file);
+}
+
+bool CTVOnBar::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(msg->_visible);
+ if (msg->_visible)
+ setPosition(_tvPos);
+ else
+ setPosition(Point(0, 0));
+
+ return true;
}
} // End of namespace Titanic
diff --git a/engines/titanic/game/placeholder/tv_on_bar.h b/engines/titanic/game/placeholder/tv_on_bar.h
index d41d972e73..0157bc8764 100644
--- a/engines/titanic/game/placeholder/tv_on_bar.h
+++ b/engines/titanic/game/placeholder/tv_on_bar.h
@@ -23,13 +23,15 @@
#ifndef TITANIC_TV_ON_BAR_H
#define TITANIC_TV_ON_BAR_H
-#include "titanic/game/placeholder/place_holder_item.h"
+#include "titanic/game/placeholder/place_holder.h"
namespace Titanic {
-class CTVOnBar : public CPlaceHolderItem {
+class CTVOnBar : public CPlaceHolder {
+ DECLARE_MESSAGE_MAP;
+ bool VisibleMsg(CVisibleMsg *msg);
private:
- Point _pos1;
+ Point _tvPos;
public:
CLASSDEF;
diff --git a/engines/titanic/game/play_music_button.cpp b/engines/titanic/game/play_music_button.cpp
index 8066739f10..21fd3c336a 100644
--- a/engines/titanic/game/play_music_button.cpp
+++ b/engines/titanic/game/play_music_button.cpp
@@ -21,23 +21,58 @@
*/
#include "titanic/game/play_music_button.h"
+#include "titanic/sound/music_room.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPlayMusicButton, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(FrameMsg)
+END_MESSAGE_MAP()
+
void CPlayMusicButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
+ file->writeNumberLine(_flag, indent);
+ file->writeNumberLine(_ticks, indent);
CBackground::save(file, indent);
}
void CPlayMusicButton::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
+ _flag = file->readNumber();
+ _ticks = file->readNumber();
CBackground::load(file);
}
+bool CPlayMusicButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CMusicRoom *musicRoom = getMusicRoom();
+ if (_flag) {
+ musicRoom->stopMusic();
+ stopMovie();
+ loadFrame(0);
+ _flag = false;
+ } else {
+ musicRoom->startMusic(100);
+ playMovie(MOVIE_REPEAT);
+ _ticks = getTicksCount();
+ _flag = true;
+ }
+
+ return true;
+}
+
+bool CPlayMusicButton::FrameMsg(CFrameMsg *msg) {
+ if (_flag && !CMusicRoom::_musicHandler->poll()) {
+ CMusicRoom *musicRoom = getMusicRoom();
+ musicRoom->stopMusic();
+ stopMovie();
+ loadFrame(0);
+ _flag = false;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/play_music_button.h b/engines/titanic/game/play_music_button.h
index 4e3474181c..824b372bf9 100644
--- a/engines/titanic/game/play_music_button.h
+++ b/engines/titanic/game/play_music_button.h
@@ -28,12 +28,15 @@
namespace Titanic {
class CPlayMusicButton : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
public:
- int _fieldE0;
- int _fieldE4;
+ bool _flag;
+ uint _ticks;
public:
CLASSDEF;
- CPlayMusicButton() : CBackground(), _fieldE0(0), _fieldE4(0) {}
+ CPlayMusicButton() : CBackground(), _flag(false), _ticks(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/play_on_act.cpp b/engines/titanic/game/play_on_act.cpp
index e1ef1201c6..9c368c335d 100644
--- a/engines/titanic/game/play_on_act.cpp
+++ b/engines/titanic/game/play_on_act.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CPlayOnAct, CBackground)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
void CPlayOnAct::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBackground::save(file, indent);
@@ -34,4 +39,20 @@ void CPlayOnAct::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CPlayOnAct::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PlayMovie") {
+ setVisible(true);
+ playMovie(0);
+ } else if (msg->_action == "PlayToEnd") {
+ setVisible(true);
+ playMovie(MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
+bool CPlayOnAct::LeaveViewMsg(CLeaveViewMsg *msg) {
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/play_on_act.h b/engines/titanic/game/play_on_act.h
index 197e647943..72615f2fc4 100644
--- a/engines/titanic/game/play_on_act.h
+++ b/engines/titanic/game/play_on_act.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CPlayOnAct : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/port_hole.cpp b/engines/titanic/game/port_hole.cpp
index f3c447f443..25807b1b1d 100644
--- a/engines/titanic/game/port_hole.cpp
+++ b/engines/titanic/game/port_hole.cpp
@@ -24,26 +24,72 @@
namespace Titanic {
-CPortHole::CPortHole() : CGameObject(), _fieldBC(0),
- _string1("b#47.wav"), _string2("b#46.wav") {
+BEGIN_MESSAGE_MAP(CPortHole, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
+CPortHole::CPortHole() : CGameObject(), _open(false),
+ _closeSoundName("b#47.wav"), _openSoundName("b#46.wav") {
}
void CPortHole::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeQuotedLine(_string2, indent);
+ file->writeNumberLine(_open, indent);
+ file->writeQuotedLine(_closeSoundName, indent);
+ file->writeQuotedLine(_openSoundName, indent);
CGameObject::save(file, indent);
}
void CPortHole::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _string1 = file->readString();
- _string2 = file->readString();
+ _open = file->readNumber();
+ _closeSoundName = file->readString();
+ _openSoundName = file->readString();
CGameObject::load(file);
}
+bool CPortHole::ActMsg(CActMsg *msg) {
+ if (msg->_action == "TogglePortHole") {
+ if (_open) {
+ playMovie(14, 26, MOVIE_NOTIFY_OBJECT);
+ playSound(_closeSoundName);
+ _open = false;
+ } else {
+ setVisible(true);
+ playMovie(1, 13, 0);
+ playSound(_openSoundName);
+ _open = true;
+ }
+ }
+
+ return true;
+}
+
+bool CPortHole::MovieEndMsg(CMovieEndMsg *msg) {
+ _open = false;
+ setVisible(false);
+ return true;
+}
+
+bool CPortHole::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (_open) {
+ playSound(_closeSoundName);
+ playMovie(14, 26, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _open = false;
+ }
+
+ return true;
+}
+
+bool CPortHole::EnterViewMsg(CEnterViewMsg *msg) {
+ setVisible(false);
+ _open = false;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/port_hole.h b/engines/titanic/game/port_hole.h
index 7bba18d12a..9f1997a517 100644
--- a/engines/titanic/game/port_hole.h
+++ b/engines/titanic/game/port_hole.h
@@ -28,9 +28,14 @@
namespace Titanic {
class CPortHole : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
- int _fieldBC;
- CString _string1, _string2;
+ bool _open;
+ CString _closeSoundName, _openSoundName;
public:
CLASSDEF;
CPortHole();
diff --git a/engines/titanic/game/record_phonograph_button.cpp b/engines/titanic/game/record_phonograph_button.cpp
index f022957dbb..1ffaec4228 100644
--- a/engines/titanic/game/record_phonograph_button.cpp
+++ b/engines/titanic/game/record_phonograph_button.cpp
@@ -24,16 +24,44 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CRecordPhonographButton, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(PhonographStopMsg)
+END_MESSAGE_MAP()
+
void CRecordPhonographButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_active, indent);
CBackground::save(file, indent);
}
void CRecordPhonographButton::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _active = file->readNumber();
CBackground::load(file);
}
+bool CRecordPhonographButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CPhonographRecordMsg recordMsg;
+ recordMsg.execute(getParent());
+
+ if (recordMsg._value) {
+ playSound("z#58.wav");
+ loadFrame(1);
+ _active = true;
+ }
+
+ return true;
+}
+
+bool CRecordPhonographButton::PhonographStopMsg(CPhonographStopMsg *msg) {
+ if (_active) {
+ playSound("z#57.wav");
+ loadFrame(0);
+ _active = false;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/record_phonograph_button.h b/engines/titanic/game/record_phonograph_button.h
index 3383c01e31..985a4c4576 100644
--- a/engines/titanic/game/record_phonograph_button.h
+++ b/engines/titanic/game/record_phonograph_button.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CRecordPhonographButton : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool PhonographStopMsg(CPhonographStopMsg *msg);
public:
- int _value;
+ bool _active;
public:
CLASSDEF;
- CRecordPhonographButton() : CBackground(), _value(0) {}
+ CRecordPhonographButton() : CBackground(), _active(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/replacement_ear.cpp b/engines/titanic/game/replacement_ear.cpp
index 1f9960365d..e8bd384207 100644
--- a/engines/titanic/game/replacement_ear.cpp
+++ b/engines/titanic/game/replacement_ear.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CReplacementEar, CBackground)
+ ON_MESSAGE(VisibleMsg)
+END_MESSAGE_MAP()
+
void CReplacementEar::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBackground::save(file, indent);
@@ -34,4 +38,11 @@ void CReplacementEar::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CReplacementEar::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(true);
+ playMovie(MOVIE_GAMESTATE);
+ playSound("z#64.wav");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/replacement_ear.h b/engines/titanic/game/replacement_ear.h
index 0d282b7fb4..7775a9631a 100644
--- a/engines/titanic/game/replacement_ear.h
+++ b/engines/titanic/game/replacement_ear.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CReplacementEar : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool VisibleMsg(CVisibleMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/reserved_table.cpp b/engines/titanic/game/reserved_table.cpp
index a600190709..734d53af45 100644
--- a/engines/titanic/game/reserved_table.cpp
+++ b/engines/titanic/game/reserved_table.cpp
@@ -21,22 +21,55 @@
*/
#include "titanic/game/reserved_table.h"
+#include "titanic/core/room_item.h"
+#include "titanic/core/view_item.h"
+#include "titanic/npcs/maitre_d.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CReservedTable, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(PlayerTriesRestaurantTableMsg)
+END_MESSAGE_MAP()
+
void CReservedTable::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value1, indent);
- file->writeNumberLine(_value2, indent);
+ file->writeNumberLine(_flag, indent);
+ file->writeNumberLine(_tableId, indent);
CGameObject::save(file, indent);
}
void CReservedTable::load(SimpleFile *file) {
file->readNumber();
- _value1 = file->readNumber();
- _value2 = file->readNumber();
+ _flag = file->readNumber();
+ _tableId = file->readNumber();
CGameObject::load(file);
}
+bool CReservedTable::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_flag) {
+ CPlayerTriesRestaurantTableMsg tryMsg(_tableId, 0);
+ tryMsg.execute(findRoom(), CReservedTable::_type, MSGFLAG_CLASS_DEF | MSGFLAG_SCAN);
+ }
+
+ return true;
+}
+
+bool CReservedTable::PlayerTriesRestaurantTableMsg(CPlayerTriesRestaurantTableMsg *msg) {
+ if (msg->_tableId == _tableId) {
+ if (!msg->_result) {
+ CMaitreD *maitreD = dynamic_cast<CMaitreD *>(findRoomObject("MaitreD"));
+ startTalking(maitreD, 118, maitreD->findView());
+ msg->_result = true;
+ }
+
+ _cursorId = CURSOR_INVALID;
+ _flag = true;
+ return true;
+ } else {
+ return false;
+ }
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/reserved_table.h b/engines/titanic/game/reserved_table.h
index a3532c7d14..bc037ae3d9 100644
--- a/engines/titanic/game/reserved_table.h
+++ b/engines/titanic/game/reserved_table.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CReservedTable : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool PlayerTriesRestaurantTableMsg(CPlayerTriesRestaurantTableMsg *msg);
public:
- int _value1, _value2;
+ bool _flag;
+ int _tableId;
public:
CLASSDEF;
- CReservedTable() : CGameObject(), _value1(0), _value2(0) {}
+ CReservedTable() : CGameObject(), _flag(false), _tableId(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/restaurant_cylinder_holder.cpp b/engines/titanic/game/restaurant_cylinder_holder.cpp
index d70009f151..8726d1a925 100644
--- a/engines/titanic/game/restaurant_cylinder_holder.cpp
+++ b/engines/titanic/game/restaurant_cylinder_holder.cpp
@@ -24,20 +24,29 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CRestaurantCylinderHolder, CDropTarget)
+ ON_MESSAGE(EjectCylinderMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(QueryCylinderHolderMsg)
+ ON_MESSAGE(QueryCylinderNameMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
CRestaurantCylinderHolder::CRestaurantCylinderHolder() : CDropTarget(),
_field118(0), _field11C(0), _field12C(0), _field130(0),
- _string6("z#61.wav"), _field140(1) {
+ _ejectSoundName("z#61.wav"), _defaultCursorId(CURSOR_ARROW) {
}
void CRestaurantCylinderHolder::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_field118, indent);
file->writeNumberLine(_field11C, indent);
- file->writeQuotedLine(_string5, indent);
+ file->writeQuotedLine(_target, indent);
file->writeNumberLine(_field12C, indent);
file->writeNumberLine(_field130, indent);
- file->writeQuotedLine(_string6, indent);
- file->writeNumberLine(_field140, indent);
+ file->writeQuotedLine(_ejectSoundName, indent);
+ file->writeNumberLine(_defaultCursorId, indent);
CDropTarget::save(file, indent);
}
@@ -46,13 +55,98 @@ void CRestaurantCylinderHolder::load(SimpleFile *file) {
file->readNumber();
_field118 = file->readNumber();
_field11C = file->readNumber();
- _string5 = file->readString();
+ _target = file->readString();
_field12C = file->readNumber();
_field130 = file->readNumber();
- _string6 = file->readString();
- _field140 = file->readNumber();
+ _ejectSoundName = file->readString();
+ _defaultCursorId = (CursorId)file->readNumber();
CDropTarget::load(file);
}
+bool CRestaurantCylinderHolder::EjectCylinderMsg(CEjectCylinderMsg *msg) {
+ _field11C = true;
+ bool hasCylinder = findByName("Phonograph Cylinder") != nullptr;
+
+ if (_field118) {
+ playClip(hasCylinder ? "CloseHolder_Full" : "CloseHolder_Empty",
+ MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _fieldF4 = 1;
+ } else {
+ playClip(hasCylinder ? "OpenHolder_Full" : "OpenHolder_Empty",
+ MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+
+ playSound(_ejectSoundName, 50);
+ return true;
+}
+
+bool CRestaurantCylinderHolder::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_field118) {
+ CTreeItem *cylinder = findByName("Phonograph Cylinder", true);
+ if (cylinder) {
+ loadFrame(_dropFrame);
+ _cursorId = _dropCursorId;
+ } else {
+ loadFrame(_dragFrame);
+ _cursorId = _dragCursorId;
+ }
+ } else {
+ loadFrame(_field130);
+ _cursorId = _defaultCursorId;
+ }
+
+ return true;
+}
+
+bool CRestaurantCylinderHolder::MovieEndMsg(CMovieEndMsg *msg) {
+ _field11C = false;
+ if (_field118) {
+ _field118 = false;
+ _cursorId = _defaultCursorId;
+
+ CPhonographReadyToPlayMsg readyMsg;
+ readyMsg.execute(_target);
+ } else {
+ _field118 = true;
+ _fieldF4 = false;
+ _cursorId = findByName("Phonograph Cylinder") ? _dropCursorId : _dragCursorId;
+ }
+
+ CCylinderHolderReadyMsg holderMsg;
+ holderMsg.execute(_target);
+ return true;
+}
+
+bool CRestaurantCylinderHolder::QueryCylinderHolderMsg(CQueryCylinderHolderMsg *msg) {
+ CNamedItem *cylinder = findByName("Phonograph Cylinder", true);
+
+ msg->_value1 = _field118;
+ if (cylinder) {
+ msg->_value2 = 1;
+ msg->_target = cylinder;
+ }
+
+ return true;
+}
+
+bool CRestaurantCylinderHolder::QueryCylinderNameMsg(CQueryCylinderNameMsg *msg) {
+ CNamedItem *cylinder = findByName("Phonograph Cylinder", true);
+
+ if (cylinder) {
+ CQueryCylinderMsg queryMsg;
+ queryMsg.execute(cylinder);
+ msg->_name = queryMsg._name;
+ }
+
+ return true;
+}
+
+bool CRestaurantCylinderHolder::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (_field118)
+ return CDropTarget::MouseDragStartMsg(msg);
+ else
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/restaurant_cylinder_holder.h b/engines/titanic/game/restaurant_cylinder_holder.h
index 3aa979b0a5..cd0b0783bd 100644
--- a/engines/titanic/game/restaurant_cylinder_holder.h
+++ b/engines/titanic/game/restaurant_cylinder_holder.h
@@ -28,14 +28,21 @@
namespace Titanic {
class CRestaurantCylinderHolder : public CDropTarget {
+ DECLARE_MESSAGE_MAP;
+ bool EjectCylinderMsg(CEjectCylinderMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool QueryCylinderHolderMsg(CQueryCylinderHolderMsg *msg);
+ bool QueryCylinderNameMsg(CQueryCylinderNameMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
private:
int _field118;
int _field11C;
- CString _string5;
+ CString _target;
int _field12C;
int _field130;
- CString _string6;
- int _field140;
+ CString _ejectSoundName;
+ CursorId _defaultCursorId;
public:
CLASSDEF;
CRestaurantCylinderHolder();
diff --git a/engines/titanic/game/restaurant_phonograph.cpp b/engines/titanic/game/restaurant_phonograph.cpp
index 83a4ac3e71..881079e020 100644
--- a/engines/titanic/game/restaurant_phonograph.cpp
+++ b/engines/titanic/game/restaurant_phonograph.cpp
@@ -21,9 +21,20 @@
*/
#include "titanic/game/restaurant_phonograph.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CRestaurantPhonograph, CPhonograph)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(PhonographPlayMsg)
+ ON_MESSAGE(PhonographStopMsg)
+ ON_MESSAGE(PhonographReadyToPlayMsg)
+ ON_MESSAGE(EjectCylinderMsg)
+ ON_MESSAGE(QueryPhonographState)
+ ON_MESSAGE(LockPhonographMsg)
+END_MESSAGE_MAP()
+
CRestaurantPhonograph::CRestaurantPhonograph() : CPhonograph(),
_fieldF8(1), _field114(0) {}
@@ -48,4 +59,89 @@ void CRestaurantPhonograph::load(SimpleFile *file) {
CPhonograph::load(file);
}
+bool CRestaurantPhonograph::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_fieldF8 && !_fieldE0) {
+ CQueryCylinderHolderMsg holderMsg;
+ holderMsg.execute(this);
+
+ if (!holderMsg._value1) {
+ CPhonographPlayMsg playMsg;
+ playMsg.execute(this);
+ } else if (holderMsg._value2) {
+ CEjectCylinderMsg ejectMsg;
+ ejectMsg.execute(this);
+
+ _fieldE8 = true;
+ if (_field114) {
+ loadFrame(_fieldEC);
+ playSound(_ejectSoundName);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CRestaurantPhonograph::PhonographPlayMsg(CPhonographPlayMsg *msg) {
+ if (_fieldE0) {
+ if (findView() == getView() && (!_fieldE8 || !_field114)) {
+ loadFrame(_fieldEC);
+ playSound(_ejectSoundName);
+ }
+
+ CQueryCylinderNameMsg nameMsg;
+ nameMsg.execute(this);
+ CRestaurantMusicChanged musicMsg(nameMsg._name);
+ musicMsg.execute(findRoom());
+ } else {
+ loadFrame(_fieldF0);
+ }
+
+ return true;
+}
+
+bool CRestaurantPhonograph::PhonographStopMsg(CPhonographStopMsg *msg) {
+ bool flag = _fieldE0;
+ CPhonograph::PhonographStopMsg(msg);
+
+ if (_fieldE0) {
+ loadFrame(_fieldF0);
+ if (flag)
+ playSound(_string3);
+ } else {
+ loadFrame(_fieldEC);
+ }
+
+ return true;
+}
+
+bool CRestaurantPhonograph::PhonographReadyToPlayMsg(CPhonographReadyToPlayMsg *msg) {
+ if (_fieldE8) {
+ CPhonographPlayMsg playMsg;
+ playMsg.execute(this);
+ _fieldE8 = false;
+ }
+
+ return true;
+}
+
+bool CRestaurantPhonograph::EjectCylinderMsg(CEjectCylinderMsg *msg) {
+ if (_fieldE0) {
+ CPhonographStopMsg stopMsg;
+ stopMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CRestaurantPhonograph::QueryPhonographState(CQueryPhonographState *msg) {
+ msg->_value = _fieldF8;
+ return true;
+}
+
+bool CRestaurantPhonograph::LockPhonographMsg(CLockPhonographMsg *msg) {
+ _fieldF8 = msg->_value;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/restaurant_phonograph.h b/engines/titanic/game/restaurant_phonograph.h
index 710a2edd73..9661df0dfb 100644
--- a/engines/titanic/game/restaurant_phonograph.h
+++ b/engines/titanic/game/restaurant_phonograph.h
@@ -28,9 +28,17 @@
namespace Titanic {
class CRestaurantPhonograph : public CPhonograph {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool PhonographPlayMsg(CPhonographPlayMsg *msg);
+ bool PhonographStopMsg(CPhonographStopMsg *msg);
+ bool PhonographReadyToPlayMsg(CPhonographReadyToPlayMsg *msg);
+ bool EjectCylinderMsg(CEjectCylinderMsg *msg);
+ bool QueryPhonographState(CQueryPhonographState *msg);
+ bool LockPhonographMsg(CLockPhonographMsg *msg);
private:
int _fieldF8;
- CString _string2;
+ CString _ejectSoundName;
CString _string3;
int _field114;
public:
diff --git a/engines/titanic/game/sauce_dispensor.cpp b/engines/titanic/game/sauce_dispensor.cpp
index 8dc818d917..adc0b828a2 100644
--- a/engines/titanic/game/sauce_dispensor.cpp
+++ b/engines/titanic/game/sauce_dispensor.cpp
@@ -21,9 +21,20 @@
*/
#include "titanic/game/sauce_dispensor.h"
+#include "titanic/carry/chicken.h"
+#include "titanic/carry/glass.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSauceDispensor, CBackground)
+ ON_MESSAGE(Use)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(StatusChangeMsg)
+END_MESSAGE_MAP()
+
CSauceDispensor::CSauceDispensor() : CBackground(),
_fieldEC(0), _fieldF0(0), _field104(0), _field108(0) {
}
@@ -54,4 +65,105 @@ void CSauceDispensor::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CSauceDispensor::Use(CUse *msg) {
+ CVisibleMsg visibleMsg(true);
+
+ if (msg->_item->isEquals("Chicken")) {
+ CChicken *chicken = dynamic_cast<CChicken *>(msg->_item);
+ _field104 = true;
+ if (_fieldF0) {
+ playSound("b#15.wav", 50);
+
+ if (chicken->_string6 != "None") {
+ petDisplayMessage(1, "This foodstuff is already sufficiently garnished.");
+ msg->execute("Chicken");
+ } else {
+ setVisible(true);
+ if (chicken->_field12C) {
+ playMovie(_pos1.x, _pos1.y, MOVIE_NOTIFY_OBJECT);
+ } else {
+ CActMsg actMsg(_string3);
+ actMsg.execute("Chicken");
+ playMovie(_pos2.x, _pos2.y, MOVIE_NOTIFY_OBJECT);
+ }
+ }
+
+ if (_fieldF0)
+ return true;
+ }
+
+ CMovieEndMsg endMsg(0, 0);
+ endMsg.execute(this);
+ playSound("z#120.wav");
+
+ petDisplayMessage(1, "Sadly, this dispenser is currently empty.");
+ } else if (msg->_item->isEquals("BeerGlass")) {
+ CGlass *glass = dynamic_cast<CGlass *>(msg->_item);
+ _field108 = true;
+
+ if (_field104 || _fieldF0) {
+ petAddToInventory();
+ } else if (glass->_string6 != "None") {
+ visibleMsg.execute("BeerGlass");
+ } else if (_fieldEC) {
+ glass->setPosition(Point(
+ _bounds.left + (_bounds.width() / 2) - (glass->_bounds.width() / 2),
+ 300));
+ setVisible(true);
+
+ CActMsg actMsg(_string3);
+ actMsg.execute("BeerGlass");
+ }
+ }
+
+ return true;
+}
+
+bool CSauceDispensor::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ _fieldEC = false;
+
+ CActMsg actMsg("GoToPET");
+ if (_field104)
+ actMsg.execute("Chicken");
+ if (_field108)
+ actMsg.execute("BeerGlass");
+
+ _field104 = false;
+ _field108 = false;
+ return true;
+}
+
+bool CSauceDispensor::ActMsg(CActMsg *msg) {
+ if (msg->_action == "StarlingsDead")
+ _fieldF0 = true;
+
+ return true;
+}
+
+bool CSauceDispensor::LeaveViewMsg(CLeaveViewMsg *msg) {
+ setVisible(false);
+ loadFrame(0);
+
+ if (_field108) {
+ CGameObject *glass = findRoomObject("Beerglass");
+ if (glass)
+ glass->petAddToInventory();
+ }
+
+ _field104 = false;
+ _field108 = false;
+ return true;
+}
+
+bool CSauceDispensor::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ petDisplayMessage(1, "Please place food source beneath dispenser for sauce delivery.");
+ return true;
+}
+
+bool CSauceDispensor::StatusChangeMsg(CStatusChangeMsg *msg) {
+ petDisplayMessage(1, "Please place food source beneath dispenser for sauce delivery.");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sauce_dispensor.h b/engines/titanic/game/sauce_dispensor.h
index aa177050d5..f8021f368b 100644
--- a/engines/titanic/game/sauce_dispensor.h
+++ b/engines/titanic/game/sauce_dispensor.h
@@ -28,6 +28,13 @@
namespace Titanic {
class CSauceDispensor : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool Use(CUse *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
public:
CString _string3;
int _fieldEC;
diff --git a/engines/titanic/game/search_point.cpp b/engines/titanic/game/search_point.cpp
index f60a3132b7..bbe923267a 100644
--- a/engines/titanic/game/search_point.cpp
+++ b/engines/titanic/game/search_point.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSearchPoint, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CSearchPoint::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value, indent);
@@ -36,4 +40,21 @@ void CSearchPoint::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CSearchPoint::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_value > 0) {
+ CGameObject *child = dynamic_cast<CGameObject *>(getFirstChild());
+ if (child) {
+ child->petAddToInventory();
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute(child->getName());
+ playSound("z#47.wav");
+ }
+
+ if (--_value == 0)
+ _cursorId = CURSOR_ARROW;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/search_point.h b/engines/titanic/game/search_point.h
index 3c5639b104..421f272804 100644
--- a/engines/titanic/game/search_point.h
+++ b/engines/titanic/game/search_point.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CSearchPoint : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
int _value;
public:
diff --git a/engines/titanic/game/season_background.cpp b/engines/titanic/game/season_background.cpp
index 1c63f3d892..20ad6aca1d 100644
--- a/engines/titanic/game/season_background.cpp
+++ b/engines/titanic/game/season_background.cpp
@@ -24,28 +24,113 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSeasonBackground, CBackground)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(ChangeSeasonMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
CSeasonBackground::CSeasonBackground() : CBackground(),
- _fieldE0(0), _fieldE4(0), _fieldE8(46), _fieldEC(0) {
+ _seasonNum(SEASON_SUMMER), _flag(false), _defaultFrame(46), _unused(0) {
}
void CSeasonBackground::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
- file->writeNumberLine(_fieldEC, indent);
+ file->writeNumberLine(_seasonNum, indent);
+ file->writeNumberLine(_flag, indent);
+ file->writeNumberLine(_defaultFrame, indent);
+ file->writeNumberLine(_unused, indent);
CBackground::save(file, indent);
}
void CSeasonBackground::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
- _fieldEC = file->readNumber();
+ _seasonNum = (Season)file->readNumber();
+ _flag = file->readNumber();
+ _defaultFrame = file->readNumber();
+ _unused = file->readNumber();
CBackground::load(file);
}
+bool CSeasonBackground::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(_defaultFrame);
+ return true;
+}
+
+bool CSeasonBackground::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ _seasonNum = (Season)(((int)_seasonNum + 1) % 4);
+
+ switch (_seasonNum) {
+ case SEASON_SUMMER:
+ playMovie(0, 45, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _defaultFrame = 45;
+ break;
+
+ case SEASON_AUTUMN:
+ if (_flag) {
+ playMovie(232, 278, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _defaultFrame = 278;
+ } else {
+ playMovie(45, 91, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _defaultFrame = 91;
+ }
+ break;
+
+ case SEASON_WINTER:
+ if (_flag) {
+ playMovie(278, 326, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _defaultFrame = 326;
+ } else {
+ CStatusChangeMsg changeMsg;
+ changeMsg._newStatus = 0;
+ changeMsg.execute("PickUpSpeechCentre");
+ playMovie(91, 139, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _defaultFrame = 139;
+ }
+ break;
+
+ case SEASON_SPRING:
+ if (_flag) {
+ playMovie(326, 417, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _defaultFrame = 417;
+ } else {
+ playMovie(139, 228, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _defaultFrame = 228;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CSeasonBackground::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == _defaultFrame) {
+ CTurnOn onMsg;
+ onMsg.execute("SeasonalAdjust");
+ }
+
+ if (msg->_endFrame == 91 && !_flag) {
+ CStatusChangeMsg changeMsg;
+ changeMsg.execute("PickUpSpeechCentre");
+ }
+
+ return true;
+}
+
+bool CSeasonBackground::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PlayerGetsSpeechCentre") {
+ loadFrame(278);
+ _defaultFrame = 278;
+ _flag = true;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/season_background.h b/engines/titanic/game/season_background.h
index f0fd2cdc63..d30fd7aedc 100644
--- a/engines/titanic/game/season_background.h
+++ b/engines/titanic/game/season_background.h
@@ -28,11 +28,16 @@
namespace Titanic {
class CSeasonBackground : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool ActMsg(CActMsg *msg);
public:
- int _fieldE0;
- int _fieldE4;
- int _fieldE8;
- int _fieldEC;
+ Season _seasonNum;
+ bool _flag;
+ int _defaultFrame;
+ int _unused;
public:
CLASSDEF;
CSeasonBackground();
diff --git a/engines/titanic/game/season_barrel.cpp b/engines/titanic/game/season_barrel.cpp
index 9594396885..e08cdf323b 100644
--- a/engines/titanic/game/season_barrel.cpp
+++ b/engines/titanic/game/season_barrel.cpp
@@ -24,19 +24,38 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSeasonBarrel, CBackground)
+ ON_MESSAGE(ChangeSeasonMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CSeasonBarrel::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
+ file->writeNumberLine(_unused, indent);
+ file->writeNumberLine(_startFrame, indent);
CBackground::save(file, indent);
}
void CSeasonBarrel::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
+ _unused = file->readNumber();
+ _startFrame = file->readNumber();
CBackground::load(file);
}
+bool CSeasonBarrel::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ if (_startFrame >= 28)
+ _startFrame = 0;
+
+ playMovie(_startFrame, _startFrame + 7, 0);
+ _startFrame += 7;
+ return true;
+}
+
+bool CSeasonBarrel::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(_startFrame);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/season_barrel.h b/engines/titanic/game/season_barrel.h
index f77864599d..6296b6f7b1 100644
--- a/engines/titanic/game/season_barrel.h
+++ b/engines/titanic/game/season_barrel.h
@@ -28,12 +28,15 @@
namespace Titanic {
class CSeasonBarrel : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
public:
- int _fieldE0;
- int _fieldE4;
+ int _unused;
+ int _startFrame;
public:
CLASSDEF;
- CSeasonBarrel() : CBackground(), _fieldE0(0), _fieldE4(7) {}
+ CSeasonBarrel() : CBackground(), _unused(0), _startFrame(7) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/seasonal_adjustment.cpp b/engines/titanic/game/seasonal_adjustment.cpp
index 33a0ae89c5..1f1cb88afb 100644
--- a/engines/titanic/game/seasonal_adjustment.cpp
+++ b/engines/titanic/game/seasonal_adjustment.cpp
@@ -21,9 +21,20 @@
*/
#include "titanic/game/seasonal_adjustment.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSeasonalAdjustment, CBackground)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
void CSeasonalAdjustment::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldE0, indent);
@@ -40,4 +51,86 @@ void CSeasonalAdjustment::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CSeasonalAdjustment::StatusChangeMsg(CStatusChangeMsg *msg) {
+ CChangeSeasonMsg changeMsg;
+ switch (stateGetSeason()) {
+ case SEASON_SUMMER:
+ changeMsg._season = "Summer";
+ break;
+ case SEASON_AUTUMN:
+ changeMsg._season = "Autumn";
+ break;
+ case SEASON_WINTER:
+ changeMsg._season = "Winter";
+ break;
+ case SEASON_SPRING:
+ changeMsg._season = "Spring";
+ break;
+ default:
+ break;
+ }
+
+ changeMsg.execute(getRoot(), nullptr, MSGFLAG_SCAN);
+ return true;
+}
+
+bool CSeasonalAdjustment::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CSeasonalAdjustment::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ playSound("z#42.wav");
+ if (!_fieldE4) {
+ petDisplayMessage(1, "The Seasonal Adjustment switch is not operational at the present time.");
+ } else if (!_fieldE0) {
+ playMovie(0, 6, MOVIE_NOTIFY_OBJECT);
+ playMovie(6, 18, 0);
+ }
+
+ return true;
+}
+
+bool CSeasonalAdjustment::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == 6) {
+ stateChangeSeason();
+ CStatusChangeMsg changeMsg;
+ changeMsg.execute(this);
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ offMsg.execute("LeftPanExit");
+ offMsg.execute("RightPanExit");
+ }
+
+ return true;
+}
+
+bool CSeasonalAdjustment::TurnOn(CTurnOn *msg) {
+ if (_fieldE0) {
+ _fieldE0 = false;
+ CTurnOn onMsg;
+ onMsg.execute("LeftPanExit");
+ onMsg.execute("RightPanExit");
+ }
+
+ return true;
+}
+
+bool CSeasonalAdjustment::TurnOff(CTurnOff *msg) {
+ _fieldE0 = true;
+ return true;
+}
+
+bool CSeasonalAdjustment::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PlayerGetsSpeechCentre") {
+ msg->execute("SeasonBackground");
+ msg->execute("ArbGate");
+ } else if (msg->_action == "EnableObject") {
+ _fieldE4 = true;
+ } else if (msg->_action == "DisableObject") {
+ _fieldE4 = false;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/seasonal_adjustment.h b/engines/titanic/game/seasonal_adjustment.h
index 39132d614d..4b7ef3d4f6 100644
--- a/engines/titanic/game/seasonal_adjustment.h
+++ b/engines/titanic/game/seasonal_adjustment.h
@@ -28,7 +28,15 @@
namespace Titanic {
class CSeasonalAdjustment : public CBackground {
-public:
+ DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool ActMsg(CActMsg *msg);
+private:
int _fieldE0;
int _fieldE4;
public:
diff --git a/engines/titanic/game/service_elevator_window.cpp b/engines/titanic/game/service_elevator_window.cpp
index 95b2735b37..b0cc53abb4 100644
--- a/engines/titanic/game/service_elevator_window.cpp
+++ b/engines/titanic/game/service_elevator_window.cpp
@@ -21,9 +21,19 @@
*/
#include "titanic/game/service_elevator_window.h"
+#include "titanic/core/room_item.h"
+#include "titanic/npcs/doorbot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CServiceElevatorWindow, CBackground)
+ ON_MESSAGE(ServiceElevatorFloorChangeMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
+static const int FACTORS[4] = { 0, 20, 100, 0 };
+
CServiceElevatorWindow::CServiceElevatorWindow() : CBackground(),
_fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0) {
}
@@ -48,4 +58,57 @@ void CServiceElevatorWindow::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CServiceElevatorWindow::ServiceElevatorFloorChangeMsg(CServiceElevatorFloorChangeMsg *msg) {
+ if (getView() == findView()) {
+ CDoorbot *doorbot = dynamic_cast<CDoorbot *>(findRoom()->findByName("Doorbot"));
+ int val = (_fieldE8 && doorbot) ? 65 : 15;
+ CMovieClip *clip = _movieClips.findByName("Going Up");
+
+ if (!clip)
+ return true;
+
+ int count = _endFrame - _startFrame;
+ setMovieFrameRate(1.0 * count / val);
+
+ int startFrame = clip->_startFrame + count * FACTORS[msg->_value1] / 100;
+ int endFrame = clip->_startFrame + count * FACTORS[msg->_value2] / 100;
+
+ if (_fieldE4) {
+ playMovie(startFrame, endFrame, MOVIE_NOTIFY_OBJECT);
+ } else {
+ playMovie(startFrame, endFrame, 0);
+ if (_fieldEC)
+ playClip("Into Space");
+ }
+ }
+
+ _fieldE0 = msg->_value2;
+ return true;
+}
+
+bool CServiceElevatorWindow::MovieEndMsg(CMovieEndMsg *msg) {
+ CServiceElevatorMsg elevMsg(5);
+ elevMsg.execute(findRoom()->findByName("Service Elevator Entity"));
+ return true;
+}
+
+bool CServiceElevatorWindow::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_fieldEC) {
+ playClip("Fade Up");
+ playMovie(1, 2, 0);
+ } else {
+ CMovieClip *clip = _movieClips.findByName("Going Up");
+
+ if (clip) {
+ int frameNum = clip->_startFrame + (clip->_endFrame - clip->_startFrame)
+ * FACTORS[_fieldE0] / 100;
+ loadFrame(frameNum);
+ } else {
+ loadFrame(0);
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/service_elevator_window.h b/engines/titanic/game/service_elevator_window.h
index 4233b8405a..88e1663aba 100644
--- a/engines/titanic/game/service_elevator_window.h
+++ b/engines/titanic/game/service_elevator_window.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CServiceElevatorWindow : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool ServiceElevatorFloorChangeMsg(CServiceElevatorFloorChangeMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
public:
int _fieldE0;
int _fieldE4;
diff --git a/engines/titanic/game/sgt/armchair.cpp b/engines/titanic/game/sgt/armchair.cpp
index 4c4ef44199..f547c3ef9a 100644
--- a/engines/titanic/game/sgt/armchair.cpp
+++ b/engines/titanic/game/sgt/armchair.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CArmchair, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CArmchair::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +40,48 @@ void CArmchair::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CArmchair::TurnOn(CTurnOn *msg) {
+ if (_statics->_v8 == "Closed" && _statics->_v12 == "Closed") {
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("Deskchair");
+
+ if (_statics->_v9 == "Open") {
+ CActMsg actMsg("Squash");
+ actMsg.execute("Deskchair");
+ _startFrame = 22;
+ _endFrame = 31;
+ } else {
+ _startFrame = 0;
+ _endFrame = 10;
+ }
+
+ playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE);
+ playSound("b#0.wav");
+ _statics->_v8 = "Open";
+ _fieldE0 = 0;
+ }
+
+ return true;
+}
+
+bool CArmchair::TurnOff(CTurnOff *msg) {
+ if (_statics->_v8 == "Open") {
+ _statics->_v8 = "Closed";
+ _startFrame = 11;
+ _endFrame = 21;
+ _fieldE0 = 1;
+ playMovie(11, 21, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ playSound("b#0.wav");
+ }
+
+ return true;
+}
+
+bool CArmchair::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_statics->_v8 == "Closed")
+ loadFrame(0);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/armchair.h b/engines/titanic/game/sgt/armchair.h
index b5505554f0..169b9b4aa0 100644
--- a/engines/titanic/game/sgt/armchair.h
+++ b/engines/titanic/game/sgt/armchair.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CArmchair : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/basin.cpp b/engines/titanic/game/sgt/basin.cpp
index 1eb1d161c9..775c67deca 100644
--- a/engines/titanic/game/sgt/basin.cpp
+++ b/engines/titanic/game/sgt/basin.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBasin, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CBasin::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +40,39 @@ void CBasin::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CBasin::TurnOn(CTurnOn *msg) {
+ if (_statics->_v10 == "Open" && _statics->_v11 == "Closed"
+ && _statics->_v2 == "Closed") {
+ setVisible(true);
+ _statics->_v11 = "Open";
+ _fieldE0 = 0;
+ _startFrame = 0;
+ _endFrame = 6;
+ playMovie(0, 6, MOVIE_GAMESTATE);
+ playSound("b#13.wav");
+ }
+
+ return true;
+}
+
+bool CBasin::TurnOff(CTurnOff *msg) {
+ if (_statics->_v11 == "Open") {
+ _statics->_v11 = "Closed";
+ _fieldE0 = 1;
+ _startFrame = 8;
+ _endFrame = 14;
+ playMovie(8, 14, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("b#13.wav");
+ }
+
+ return true;
+}
+
+bool CBasin::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_statics->_v11 == "Closed")
+ setVisible(false);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/basin.h b/engines/titanic/game/sgt/basin.h
index e4a36eb841..1fcb469824 100644
--- a/engines/titanic/game/sgt/basin.h
+++ b/engines/titanic/game/sgt/basin.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CBasin : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/bedfoot.cpp b/engines/titanic/game/sgt/bedfoot.cpp
index 18ea07aca0..ff7d64569a 100644
--- a/engines/titanic/game/sgt/bedfoot.cpp
+++ b/engines/titanic/game/sgt/bedfoot.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBedfoot, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+END_MESSAGE_MAP()
+
void CBedfoot::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +39,91 @@ void CBedfoot::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CBedfoot::TurnOn(CTurnOn *msg) {
+ if (_statics->_v2 == "Closed" && _statics->_v11 == "Closed") {
+ _fieldE0 = 0;
+ _startFrame = 0;
+ if (_statics->_v10 == "Open") {
+ _endFrame = 13;
+ _statics->_v2 = "Open";
+ playSound("b#7.wav");
+ } else {
+ _endFrame = 17;
+ _statics->_v2 = "NotOnWashstand";
+ playSound("b#4.wav");
+ }
+
+ playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE);
+ } else if (_statics->_v2 == "RestingUnderTV") {
+ _fieldE0 = 0;
+ _startFrame = 8;
+ if (_statics->_v10 == "Open") {
+ _statics->_v2 = "Open";
+ playSound("189_436_bed down 1.wav");
+ } else {
+ _statics->_v2 = "NotOnWashstand";
+ playSound("192_436_bed hits floor.wav");
+ }
+
+ playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE);
+ }
+
+ if (_statics->_v2 == "Open")
+ _statics->_v1 = "Closed";
+ else if (_statics->_v2 == "NotOnWashstand")
+ _statics->_v1 = "ClosedWrong";
+
+ return true;
+}
+
+bool CBedfoot::TurnOff(CTurnOff *msg) {
+ if (_statics->_v1 == "Closed" || _statics->_v1 == "ClosedWrong") {
+ setVisible(true);
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("Bedhead");
+ }
+
+ if (_statics->_v2 == "Open" && _statics->_v1 == "Closed") {
+ _fieldE0 = 0;
+ _startFrame = 20;
+ if (_statics->_v4 == "Closed") {
+ _statics->_v2 = "Closed";
+ _endFrame = 30;
+ } else {
+ _statics->_v2 = "RestingUnderTV";
+ _endFrame = 25;
+ }
+
+ playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE);
+ playSound("b#7.wav");
+
+ } else if (_statics->_v2 == "NotOnWashstand" && _statics->_v1 == "ClosedWrong") {
+ _fieldE0 = 0;
+ _startFrame = 17;
+
+ if (_statics->_v4 == "Closed") {
+ _statics->_v2 = "Closed";
+ _endFrame = 30;
+ } else {
+ _statics->_v2 = "RestingUnderTV";
+ _endFrame = 25;
+ }
+
+ playMovie(_startFrame, _endFrame, MOVIE_GAMESTATE);
+ playSound("b#7.wav");
+
+ } else if (_statics->_v2 == "RestingUTV" && _statics->_v4 == "Closed") {
+ _statics->_v2 = "Closed";
+ _startFrame = 25;
+ _endFrame = 30;
+ playMovie(25, 30, MOVIE_GAMESTATE);
+ playSound("b#7.wav");
+ }
+
+ if (_statics->_v2 == "Closed")
+ _statics->_v1 = "Closed";
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/bedfoot.h b/engines/titanic/game/sgt/bedfoot.h
index df3db42d6d..cc7b82b075 100644
--- a/engines/titanic/game/sgt/bedfoot.h
+++ b/engines/titanic/game/sgt/bedfoot.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CBedfoot : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/bedhead.cpp b/engines/titanic/game/sgt/bedhead.cpp
index fad7272f3a..216d22ee71 100644
--- a/engines/titanic/game/sgt/bedhead.cpp
+++ b/engines/titanic/game/sgt/bedhead.cpp
@@ -21,9 +21,61 @@
*/
#include "titanic/game/sgt/bedhead.h"
+#include "titanic/titanic.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBedhead, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+END_MESSAGE_MAP()
+
+void BedheadEntry::load(Common::SeekableReadStream *s) {
+ _name1 = readStringFromStream(s);
+ _name2 = readStringFromStream(s);
+ _name3 = readStringFromStream(s);
+ _name4 = readStringFromStream(s);
+ _startFrame = s->readUint32LE();
+ _endFrame = s->readUint32LE();
+}
+
+/*------------------------------------------------------------------------*/
+
+void BedheadEntries::load(Common::SeekableReadStream *s, int count) {
+ resize(count);
+ for (int idx = 0; idx < count; ++idx)
+ (*this)[idx].load(s);
+}
+
+/*------------------------------------------------------------------------*/
+
+void TurnOnEntries::load(Common::SeekableReadStream *s) {
+ _closed.load(s, 4);
+ _restingTV.load(s, 2);
+ _restingUV.load(s, 2);
+ _closedWrong.load(s, 2);
+}
+
+/*------------------------------------------------------------------------*/
+
+void TurnOffEntries::load(Common::SeekableReadStream *s) {
+ _open.load(s, 3);
+ _restingUTV.load(s, 1);
+ _restingV.load(s, 1);
+ _restingG.load(s, 3);
+ _openWrong.load(s, 1);
+ _restingDWrong.load(s, 1);
+}
+
+/*------------------------------------------------------------------------*/
+
+CBedhead::CBedhead() : CSGTStateRoom() {
+ Common::SeekableReadStream *s = g_vm->_filesManager->getResource("DATA/BEDHEAD");
+ _on.load(s);
+ _off.load(s);
+ delete s;
+}
+
void CBedhead::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +86,85 @@ void CBedhead::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CBedhead::TurnOn(CTurnOn *msg) {
+ if (_statics->_v2 == "Closed" || _statics->_v2 == "RestingUnderTV")
+ return true;
+
+ const BedheadEntries *data = nullptr;
+ if (_statics->_v1 == "Closed")
+ data = &_on._closed;
+ else if (_statics->_v1 == "RestingTV")
+ data = &_on._restingTV;
+ else if (_statics->_v1 == "RestingUV")
+ data = &_on._restingUV;
+ else if (_statics->_v1 == "ClosedWrong")
+ data = &_on._closedWrong;
+ else
+ return true;
+
+ for (uint idx = 0; idx < data->size(); ++idx) {
+ const BedheadEntry &entry = (*data)[idx];
+ if ((entry._name1 == _statics->_v4 || entry._name1 == "Any")
+ && (entry._name2 == _statics->_v3 || entry._name2 == "Any")
+ && (entry._name3 == _statics->_v5 || entry._name3 == "Any")) {
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("Bedfoot");
+ setVisible(true);
+
+ _statics->_v1 = entry._name4;
+ playMovie(entry._startFrame, entry._endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("b#6.wav");
+ _fieldE0 = false;
+ }
+ }
+
+ if (_statics->_v1 == "Open") {
+ playMovie(71, 78, 0);
+ playSound("196_436 bed inflate 2.wav");
+ }
+
+ return true;
+}
+
+bool CBedhead::TurnOff(CTurnOff *msg) {
+ if (_statics->_v1 == "Open") {
+ playMovie(78, 85, 0);
+ playSound("191_436_bed inflate deflate.wav");
+ }
+
+ BedheadEntries *data = nullptr;
+ if (_statics->_v1 == "Open")
+ data = &_off._open;
+ else if (_statics->_v1 == "RestingUTV")
+ data = &_off._restingUTV;
+ else if (_statics->_v1 == "RestingV")
+ data = &_off._restingV;
+ else if (_statics->_v1 == "RestingG")
+ data = &_off._restingG;
+ else if (_statics->_v1 == "OpenWrong")
+ data = &_off._openWrong;
+ else if (_statics->_v1 == "RestingDWrong")
+ data = &_off._restingDWrong;
+ else
+ return true;
+
+ for (uint idx = 0; idx < data->size(); ++idx) {
+ const BedheadEntry &entry = (*data)[idx];
+ if ((entry._name1 == _statics->_v4 || entry._name1 == "Any")
+ && (entry._name2 == _statics->_v3 || entry._name2 == "Any")
+ && (entry._name3 == _statics->_v5 || entry._name3 == "Any")) {
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("Bedfoot");
+ setVisible(true);
+
+ _statics->_v1 = entry._name4;
+ playMovie(entry._startFrame, entry._endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("193_436_bed fold up 1.wav");
+ _fieldE0 = false;
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/bedhead.h b/engines/titanic/game/sgt/bedhead.h
index f1ba31786c..7784cb8caf 100644
--- a/engines/titanic/game/sgt/bedhead.h
+++ b/engines/titanic/game/sgt/bedhead.h
@@ -23,13 +23,56 @@
#ifndef TITANIC_BEDHEAD_H
#define TITANIC_BEDHEAD_H
+#include "common/array.h"
#include "titanic/game/sgt/sgt_state_room.h"
namespace Titanic {
+struct BedheadEntry {
+ CString _name1;
+ CString _name2;
+ CString _name3;
+ CString _name4;
+ int _startFrame;
+ int _endFrame;
+
+ void load(Common::SeekableReadStream *s);
+};
+class BedheadEntries : public Common::Array<BedheadEntry> {
+public:
+ void load(Common::SeekableReadStream *s, int count);
+};
+
+struct TurnOnEntries {
+ BedheadEntries _closed;
+ BedheadEntries _restingTV;
+ BedheadEntries _restingUV;
+ BedheadEntries _closedWrong;
+
+ void load(Common::SeekableReadStream *s);
+};
+
+struct TurnOffEntries {
+ BedheadEntries _open;
+ BedheadEntries _restingUTV;
+ BedheadEntries _restingV;
+ BedheadEntries _restingG;
+ BedheadEntries _openWrong;
+ BedheadEntries _restingDWrong;
+
+ void load(Common::SeekableReadStream *s);
+};
+
class CBedhead : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+private:
+ TurnOnEntries _on;
+ TurnOffEntries _off;
public:
CLASSDEF;
+ CBedhead();
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/sgt/chest_of_drawers.cpp b/engines/titanic/game/sgt/chest_of_drawers.cpp
index be62e12c8e..d9c72d3021 100644
--- a/engines/titanic/game/sgt/chest_of_drawers.cpp
+++ b/engines/titanic/game/sgt/chest_of_drawers.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CChestOfDrawers, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CChestOfDrawers::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +40,41 @@ void CChestOfDrawers::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CChestOfDrawers::TurnOn(CTurnOn *msg) {
+ if (_statics->_v6 == "Closed" && _statics->_v5 == "Open") {
+ _fieldE0 = false;
+ _statics->_v6 = "Open";
+ _startFrame = 1;
+ _endFrame = 14;
+ playSound("b#11.wav");
+ }
+
+ return true;
+}
+
+bool CChestOfDrawers::TurnOff(CTurnOff *msg) {
+ if (_statics->_v6 == "Open" && _statics->_v5 == "Closed") {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("Drawer");
+ _statics->_v6 = "Closed";
+ _fieldE0 = true;
+
+ _startFrame = 14;
+ _endFrame = 27;
+ playMovie(14, 27, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("b#11.wav");
+ }
+
+ return true;
+}
+
+bool CChestOfDrawers::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_statics->_v6 == "Open") {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("Drawer");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/chest_of_drawers.h b/engines/titanic/game/sgt/chest_of_drawers.h
index 16a1bf8fea..5bf852902b 100644
--- a/engines/titanic/game/sgt/chest_of_drawers.h
+++ b/engines/titanic/game/sgt/chest_of_drawers.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CChestOfDrawers : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/desk.cpp b/engines/titanic/game/sgt/desk.cpp
index 4dd0fdab92..09ff66f134 100644
--- a/engines/titanic/game/sgt/desk.cpp
+++ b/engines/titanic/game/sgt/desk.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CDesk, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CDesk::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +40,44 @@ void CDesk::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CDesk::TurnOn(CTurnOn *msg) {
+ if (_statics->_v5 == "Closed" && _statics->_v1 != "RestingG"
+ && _statics->_v1 != "OpenWrong") {
+ _statics->_v5 = "Open";
+ _fieldE0 = false;
+ _startFrame = 1;
+ _endFrame = 26;
+ playMovie(1, 26, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("b#12.wav");
+ }
+
+ return true;
+}
+
+bool CDesk::TurnOff(CTurnOff *msg) {
+ if (_statics->_v5 == "Open" && _statics->_v6 == "Closed"
+ && _statics->_v1 == "Open") {
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("ChestOfDrawers");
+
+ _statics->_v5 = "Closed";
+ _fieldE0 = true;
+ _startFrame = 26;
+ _endFrame = 51;
+ playMovie(26, 51, MOVIE_GAMESTATE);
+ playSound("b#9.wav");
+ }
+
+ return true;
+}
+
+bool CDesk::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_statics->_v5 == "Open") {
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute("ChestOfDrawers");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/desk.h b/engines/titanic/game/sgt/desk.h
index 77b5fa17af..8b9e1fe841 100644
--- a/engines/titanic/game/sgt/desk.h
+++ b/engines/titanic/game/sgt/desk.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CDesk : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/deskchair.cpp b/engines/titanic/game/sgt/deskchair.cpp
index a4a2badeb0..7f64c2ee34 100644
--- a/engines/titanic/game/sgt/deskchair.cpp
+++ b/engines/titanic/game/sgt/deskchair.cpp
@@ -24,6 +24,13 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CDeskchair, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CDeskchair::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +41,49 @@ void CDeskchair::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CDeskchair::TurnOn(CTurnOn *msg) {
+ if (_statics->_v8 == "Closed" && _statics->_v9 == "Closed") {
+ setVisible(true);
+ _statics->_v9 = "Open";
+ _fieldE0 = false;
+ _startFrame = 0;
+ _endFrame = 16;
+ playMovie(0, 16, MOVIE_GAMESTATE);
+ playSound("b#8.wav");
+ }
+
+ return true;
+}
+
+bool CDeskchair::TurnOff(CTurnOff *msg) {
+ if (_statics->_v9 == "Open") {
+ _statics->_v9 = "Closed";
+ _fieldE0 = true;
+ _startFrame = 16;
+ _endFrame = 32;
+ playMovie(16, 32, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("b#2.wav");
+ }
+
+ return true;
+}
+
+bool CDeskchair::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Smash") {
+ setVisible(false);
+ _statics->_v9 = "Closed";
+ _fieldE0 = true;
+ loadFrame(0);
+ return true;
+ } else {
+ return CSGTStateRoom::ActMsg(msg);
+ }
+}
+
+bool CDeskchair::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_statics->_v9 == "Closed")
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/deskchair.h b/engines/titanic/game/sgt/deskchair.h
index 5181b650d2..6e7bbe4169 100644
--- a/engines/titanic/game/sgt/deskchair.h
+++ b/engines/titanic/game/sgt/deskchair.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CDeskchair : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool ActMsg(CActMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/drawer.cpp b/engines/titanic/game/sgt/drawer.cpp
index 03aa1b5358..b8e93c37a6 100644
--- a/engines/titanic/game/sgt/drawer.cpp
+++ b/engines/titanic/game/sgt/drawer.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CDrawer, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
CDrawer::CDrawer() : CSGTStateRoom(), _fieldF4(0) {
}
@@ -39,4 +45,39 @@ void CDrawer::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CDrawer::TurnOn(CTurnOn *msg) {
+ if (_statics->_v7 == "Closed" && _statics->_v6 == "Open") {
+ _statics->_v7 = "Open";
+ _fieldE0 = false;
+ _startFrame = 50;
+ _endFrame = 75;
+ setVisible(true);
+ _statics->_v7 = "Open";
+ playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("b#10.wav");
+ }
+
+ return true;
+}
+
+bool CDrawer::TurnOff(CTurnOff *msg) {
+ if (_statics->_v7 == "Open") {
+ _statics->_v7 = "Closed";
+ _startFrame = 75;
+ _endFrame = 100;
+ _fieldE0 = true;
+ playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playSound("b#10.wav");
+ }
+
+ return true;
+}
+
+bool CDrawer::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_statics->_v7 == "Closed")
+ setVisible(false);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/drawer.h b/engines/titanic/game/sgt/drawer.h
index c079be389f..e8afe66068 100644
--- a/engines/titanic/game/sgt/drawer.h
+++ b/engines/titanic/game/sgt/drawer.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CDrawer : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
private:
int _fieldF4;
public:
diff --git a/engines/titanic/game/sgt/sgt_doors.cpp b/engines/titanic/game/sgt/sgt_doors.cpp
index 516b0f1351..71eae9800c 100644
--- a/engines/titanic/game/sgt/sgt_doors.cpp
+++ b/engines/titanic/game/sgt/sgt_doors.cpp
@@ -21,13 +21,21 @@
*/
#include "titanic/game/sgt/sgt_doors.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSGTDoors, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+END_MESSAGE_MAP()
+
void CSGTDoors::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_value1, indent);
- file->writeNumberLine(_value2, indent);
+ file->writeNumberLine(_open, indent);
CGameObject::save(file, indent);
}
@@ -35,9 +43,58 @@ void CSGTDoors::save(SimpleFile *file, int indent) {
void CSGTDoors::load(SimpleFile *file) {
file->readNumber();
_value1 = file->readNumber();
- _value2 = file->readNumber();
+ _open = file->readNumber();
CGameObject::load(file);
}
+bool CSGTDoors::EnterViewMsg(CEnterViewMsg *msg) {
+ setVisible(true);
+ _open = true;
+ CPetControl *pet = getPetControl();
+
+ if (pet) {
+ int roomNum = pet->getRoomsRoomNum();
+ static const int START_FRAMES[7] = { 0, 26, 30, 34, 38, 42, 46 };
+ static const int END_FRAMES[7] = { 12, 29, 33, 37, 41, 45, 49 };
+
+ if (pet->getRooms1CC() == 1)
+ playMovie(START_FRAMES[roomNum], END_FRAMES[roomNum],
+ MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ else
+ playMovie(0, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
+bool CSGTDoors::LeaveViewMsg(CLeaveViewMsg *msg) {
+ return true;
+}
+
+bool CSGTDoors::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(!_open);
+ return true;
+}
+
+bool CSGTDoors::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ setVisible(true);
+ _open = false;
+ CPetControl *pet = getPetControl();
+
+ if (pet) {
+ int roomNum = pet->getRoomsRoomNum();
+ static const int START_FRAMES[7] = { 12, 69, 65, 61, 57, 53, 49 };
+ static const int END_FRAMES[7] = { 25, 72, 68, 64, 60, 56, 52 };
+
+ if (pet->getRooms1CC() == 1)
+ playMovie(START_FRAMES[roomNum], END_FRAMES[roomNum],
+ MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ else
+ playMovie(12, 25, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/sgt_doors.h b/engines/titanic/game/sgt/sgt_doors.h
index 4b4f4a3153..b19c5860af 100644
--- a/engines/titanic/game/sgt/sgt_doors.h
+++ b/engines/titanic/game/sgt/sgt_doors.h
@@ -28,11 +28,17 @@
namespace Titanic {
class CSGTDoors : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
public:
- int _value1, _value2;
+ int _value1;
+ bool _open;
public:
CLASSDEF;
- CSGTDoors() : CGameObject(), _value1(0), _value2(0) {}
+ CSGTDoors() : CGameObject(), _value1(0), _open(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/sgt/sgt_nav.cpp b/engines/titanic/game/sgt/sgt_nav.cpp
index f98e486fd0..c004f947d2 100644
--- a/engines/titanic/game/sgt/sgt_nav.cpp
+++ b/engines/titanic/game/sgt/sgt_nav.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(SGTNav, CSGTStateRoom)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseMoveMsg)
+END_MESSAGE_MAP()
+
void SGTNav::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +39,43 @@ void SGTNav::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool SGTNav::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CTurnOn onMsg;
+ CTurnOff offMsg;
+
+ if (_statics->_v6 == "Open" && _statics->_v1 == "Open") {
+ if (_statics->_v3 == "Open")
+ offMsg.execute("Vase");
+ if (_statics->_v4 == "Closed")
+ onMsg.execute("SGTTV");
+ if (_statics->_v7 == "Open")
+ offMsg.execute("Drawer");
+ if (_statics->_v8 == "Open")
+ offMsg.execute("Armchair");
+ if (_statics->_v9 == "Open")
+ offMsg.execute("Deskchair");
+ if (_statics->_v12 == "Open")
+ offMsg.execute("Toilet");
+
+ changeView("SGTState.Node 2.E");
+ } else if (_statics->_v1 == "Open") {
+ petDisplayMessage(1, "This is your stateroom. It is for sleeping. If you desire "
+ "entertainment or relaxation, please visit your local leisure lounge.");
+ } else if (_statics->_v6 == "Closed") {
+ petDisplayMessage(1, "The bed will not currently support your weight."
+ " We are working on this problem but are unlikely to be able to fix it.");
+ }
+
+ return true;
+}
+
+bool SGTNav::MouseMoveMsg(CMouseMoveMsg *msg) {
+ if (_statics->_v6 == "Open" && _statics->_v1 == "Open")
+ _cursorId = CURSOR_MOVE_FORWARD;
+ else
+ _cursorId = CURSOR_ARROW;
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/sgt_nav.h b/engines/titanic/game/sgt/sgt_nav.h
index 40fdc4eff1..78f6417229 100644
--- a/engines/titanic/game/sgt/sgt_nav.h
+++ b/engines/titanic/game/sgt/sgt_nav.h
@@ -28,6 +28,9 @@
namespace Titanic {
class SGTNav : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseMoveMsg(CMouseMoveMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/sgt_navigation.cpp b/engines/titanic/game/sgt/sgt_navigation.cpp
index d0c308457c..031226226f 100644
--- a/engines/titanic/game/sgt/sgt_navigation.cpp
+++ b/engines/titanic/game/sgt/sgt_navigation.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/game/sgt/sgt_navigation.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSGTNavigation, CGameObject)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
CSGTNavigationStatics *CSGTNavigation::_statics;
void CSGTNavigation::init() {
@@ -36,20 +43,88 @@ void CSGTNavigation::deinit() {
void CSGTNavigation::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_statics->_v1, indent);
- file->writeQuotedLine(_statics->_v2, indent);
- file->writeQuotedLine(_statics->_v3, indent);
+ file->writeNumberLine(_statics->_changeViewNum, indent);
+ file->writeQuotedLine(_statics->_destView, indent);
+ file->writeQuotedLine(_statics->_destRoom, indent);
CGameObject::save(file, indent);
}
void CSGTNavigation::load(SimpleFile *file) {
file->readNumber();
- _statics->_v1 = file->readNumber();
- _statics->_v2 = file->readString();
- _statics->_v3 = file->readString();
+ _statics->_changeViewNum = file->readNumber();
+ _statics->_destView = file->readString();
+ _statics->_destRoom = file->readString();
CGameObject::load(file);
}
+bool CSGTNavigation::StatusChangeMsg(CStatusChangeMsg *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (isEquals("SGTLL")) {
+ static const int FRAMES[7] = { 0, 149, 112, 74, 0, 36, 74 };
+ _statics->_changeViewNum = msg->_newStatus;
+ if (pet->getRooms1CC() != _statics->_changeViewNum) {
+ changeView("SGTLittleLift.Node 1.N");
+ }
+
+ int startVal = pet->getRooms1CC();
+ if (startVal > _statics->_changeViewNum)
+ playMovie(FRAMES[startVal], FRAMES[_statics->_changeViewNum], MOVIE_GAMESTATE);
+ else
+ playMovie(FRAMES[startVal + 3], FRAMES[_statics->_changeViewNum + 3], MOVIE_GAMESTATE);
+
+ _cursorId = _statics->_changeViewNum != 1 ? CURSOR_MOVE_FORWARD : CURSOR_INVALID;
+
+ pet->setRooms1CC(_statics->_changeViewNum);
+ pet->resetRoomsHighlight();
+ }
+
+ return true;
+}
+
+bool CSGTNavigation::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (compareRoomNameTo("SgtLobby")) {
+ _statics->_destView = getRoomNodeName();
+ _statics->_destRoom = "SgtLobby";
+ changeView("SGTState.Node 1.S");
+ } else if (compareRoomNameTo("SGTLittleLift")) {
+ if (_statics->_changeViewNum != 1) {
+ _statics->_destRoom = "SGTLittleLift";
+ changeView("SGTState.Node 1.S");
+ }
+ } else if (compareRoomNameTo("SGTState")) {
+ if (_statics->_destRoom == "SgtLobby") {
+ if (compareViewNameTo("SGTState.Node 2.N")) {
+ changeView("SGTState.Node 1.N");
+ _statics->_destView += ".S";
+ } else {
+ _statics->_destView += ".N";
+ }
+
+ changeView(_statics->_destView);
+ } else if (_statics->_destRoom == "SGTLittleLift") {
+ if (compareViewNameTo("SGTState.Node 1.S")) {
+ changeView("SGTLittleLift.Node 1.N");
+ } else {
+ changeView("SGTState.Node 1.N");
+ changeView("SGTLittleLift.Node 1.S");
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CSGTNavigation::EnterViewMsg(CEnterViewMsg *msg) {
+ if (isEquals("SGTLL")) {
+ static const int FRAMES[3] = { 0, 36, 74 };
+ CPetControl *pet = getPetControl();
+ loadFrame(FRAMES[pet->getRooms1CC() - 1]);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/sgt_navigation.h b/engines/titanic/game/sgt/sgt_navigation.h
index 6d24fe6761..69ecd1267a 100644
--- a/engines/titanic/game/sgt/sgt_navigation.h
+++ b/engines/titanic/game/sgt/sgt_navigation.h
@@ -28,13 +28,17 @@
namespace Titanic {
struct CSGTNavigationStatics {
- int _v1;
- CString _v2;
- CString _v3;
+ int _changeViewNum;
+ CString _destView;
+ CString _destRoom;
};
class CSGTNavigation : public CGameObject {
-private:
+ DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+protected:
static CSGTNavigationStatics *_statics;
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/sgt_restaurant_doors.cpp b/engines/titanic/game/sgt/sgt_restaurant_doors.cpp
index 74a71e75b2..5c36ceb0ff 100644
--- a/engines/titanic/game/sgt/sgt_restaurant_doors.cpp
+++ b/engines/titanic/game/sgt/sgt_restaurant_doors.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSGTRestaurantDoors, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CSGTRestaurantDoors::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldBC, indent);
@@ -36,4 +40,10 @@ void CSGTRestaurantDoors::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CSGTRestaurantDoors::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CTurnOff offMsg;
+ offMsg.execute("ChickenDispenser");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/sgt_restaurant_doors.h b/engines/titanic/game/sgt/sgt_restaurant_doors.h
index 2a10d8f059..bcaaa71014 100644
--- a/engines/titanic/game/sgt/sgt_restaurant_doors.h
+++ b/engines/titanic/game/sgt/sgt_restaurant_doors.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CSGTRestaurantDoors : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
private:
int _fieldBC;
public:
diff --git a/engines/titanic/game/sgt/sgt_state_control.cpp b/engines/titanic/game/sgt/sgt_state_control.cpp
index 07c1f5efc0..9617f2f127 100644
--- a/engines/titanic/game/sgt/sgt_state_control.cpp
+++ b/engines/titanic/game/sgt/sgt_state_control.cpp
@@ -24,16 +24,59 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSGTStateControl, CBackground)
+ ON_MESSAGE(PETUpMsg)
+ ON_MESSAGE(PETDownMsg)
+ ON_MESSAGE(PETActivateMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
void CSGTStateControl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
+ file->writeNumberLine(_state, indent);
CBackground::save(file, indent);
}
void CSGTStateControl::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
+ _state = file->readNumber();
CBackground::load(file);
}
+bool CSGTStateControl::PETUpMsg(CPETUpMsg *msg) {
+ // WORKAROUND: Redundant code in original not included
+ return true;
+}
+
+bool CSGTStateControl::PETDownMsg(CPETDownMsg *msg) {
+ // WORKAROUND: Redundant code in original not included
+ return true;
+}
+
+bool CSGTStateControl::PETActivateMsg(CPETActivateMsg *msg) {
+ if (msg->_name == "SGTSelector") {
+ static const char *const TARGETS[] = {
+ "Vase", "Bedfoot", "Toilet", "Drawer", "SGTTV", "Armchair", "BedHead",
+ "WashStand", "Desk", "DeskChair", "Basin", "ChestOfDrawers"
+ };
+ _state = msg->_numValue;
+ CActMsg actMsg;
+ actMsg.execute(TARGETS[_state]);
+ }
+
+ return true;
+}
+
+bool CSGTStateControl::EnterViewMsg(CEnterViewMsg *msg) {
+ _state = 1;
+ petSetRemoteTarget();
+ return true;
+}
+
+bool CSGTStateControl::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petClear();
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/sgt_state_control.h b/engines/titanic/game/sgt/sgt_state_control.h
index 49fd5113cd..6f96359761 100644
--- a/engines/titanic/game/sgt/sgt_state_control.h
+++ b/engines/titanic/game/sgt/sgt_state_control.h
@@ -24,15 +24,22 @@
#define TITANIC_SGT_STATE_CONTROL_H
#include "titanic/core/background.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CSGTStateControl : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool PETUpMsg(CPETUpMsg *msg);
+ bool PETDownMsg(CPETDownMsg *msg);
+ bool PETActivateMsg(CPETActivateMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
private:
- int _fieldE0;
+ int _state;
public:
CLASSDEF;
- CSGTStateControl() : CBackground(), _fieldE0(1) {}
+ CSGTStateControl() : CBackground(), _state(1) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/sgt/sgt_state_room.cpp b/engines/titanic/game/sgt/sgt_state_room.cpp
index 55f08de8b4..c089e401b8 100644
--- a/engines/titanic/game/sgt/sgt_state_room.cpp
+++ b/engines/titanic/game/sgt/sgt_state_room.cpp
@@ -21,17 +21,22 @@
*/
#include "titanic/game/sgt/sgt_state_room.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
BEGIN_MESSAGE_MAP(CSGTStateRoom, CBackground)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(VisibleMsg)
ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(LeaveRoomMsg)
END_MESSAGE_MAP()
CSGTStateRoomStatics *CSGTStateRoom::_statics;
void CSGTStateRoom::init() {
_statics = new CSGTStateRoomStatics();
+ _statics->_v1 = "Closed";
}
void CSGTStateRoom::deinit() {
@@ -94,8 +99,80 @@ void CSGTStateRoom::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CSGTStateRoom::ActMsg(CActMsg *msg) {
+ CPetControl *pet = getPetControl();
+ uint roomFlags = pet->getRoomFlags();
+ uint assignedRoom = pet->getAssignedRoomFlags();
+
+ if (roomFlags != assignedRoom) {
+ petDisplayMessage("This is not your assigned room. Please do not enjoy.");
+ } else if (_fieldE0) {
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ } else {
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CSGTStateRoom::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(msg->_visible);
+ return true;
+}
+
bool CSGTStateRoom::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CSGTStateRoom::handleEvent");
+ CPetControl *pet = getPetControl();
+ uint roomFlags = pet->getRoomFlags();
+ uint assignedRoom = pet->getAssignedRoomFlags();
+
+ if (roomFlags == assignedRoom) {
+ loadFrame(_fieldE8);
+ _fieldE0 = _fieldEC;
+ setVisible(_fieldF0);
+
+ if (isEquals("Desk") && _statics->_v5 == "Closed")
+ loadFrame(1);
+ }
+
+ if (isEquals("Drawer")) {
+ petSetArea(PET_REMOTE);
+ if (roomFlags == assignedRoom && getPassengerClass() == 3
+ && _statics->_v13) {
+ playSound("b#21.wav");
+ _statics->_v13 = 0;
+ }
+
+ _statics->_v7 = "Closed";
+ setVisible(false);
+ _fieldE0 = true;
+ } else if (roomFlags != assignedRoom) {
+ loadFrame(0);
+ if (_fieldE4) {
+ setVisible(true);
+ if (isEquals("Desk"))
+ loadFrame(1);
+ } else {
+ setVisible(false);
+ }
+ }
+
+ return true;
+}
+
+bool CSGTStateRoom::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ CPetControl *pet = getPetControl();
+ uint roomFlags = pet->getRoomFlags();
+ uint assignedRoom = pet->getAssignedRoomFlags();
+
+ if (roomFlags == assignedRoom) {
+ _fieldE8 = getMovieFrame();
+ _fieldEC = _fieldE0;
+ _fieldF0 = _visible;
+ }
+
+ _statics->_v14 = roomFlags;
return true;
}
diff --git a/engines/titanic/game/sgt/sgt_state_room.h b/engines/titanic/game/sgt/sgt_state_room.h
index 375da71326..3975f7b59b 100644
--- a/engines/titanic/game/sgt/sgt_state_room.h
+++ b/engines/titanic/game/sgt/sgt_state_room.h
@@ -47,15 +47,18 @@ struct CSGTStateRoomStatics {
class CSGTStateRoom : public CBackground {
DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool VisibleMsg(CVisibleMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
-private:
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+protected:
static CSGTStateRoomStatics *_statics;
-private:
- int _fieldE0;
+protected:
+ bool _fieldE0;
int _fieldE4;
int _fieldE8;
- int _fieldEC;
- int _fieldF0;
+ bool _fieldEC;
+ bool _fieldF0;
public:
CLASSDEF;
CSGTStateRoom();
diff --git a/engines/titanic/game/sgt/sgt_tv.cpp b/engines/titanic/game/sgt/sgt_tv.cpp
index ae4c59e2f9..08ed381b2b 100644
--- a/engines/titanic/game/sgt/sgt_tv.cpp
+++ b/engines/titanic/game/sgt/sgt_tv.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSGTTV, CSGTStateRoom)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CSGTTV::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +40,35 @@ void CSGTTV::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CSGTTV::TurnOff(CTurnOff *msg) {
+ if (CSGTStateRoom::_statics->_v4 == "Open") {
+ CSGTStateRoom::_statics->_v4 = "Closed";
+ _fieldE0 = true;
+ _startFrame = 6;
+ _endFrame = 12;
+ playMovie(6, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
+bool CSGTTV::TurnOn(CTurnOn *msg) {
+ if (CSGTStateRoom::_statics->_v4 == "Closed" &&
+ CSGTStateRoom::_statics->_v2 == "Closed") {
+ CSGTStateRoom::_statics->_v4 = "Open";
+ setVisible(true);
+ _fieldE0 = false;
+ _startFrame = 1;
+ _endFrame = 6;
+ playMovie(1, 6, MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
+bool CSGTTV::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/sgt_tv.h b/engines/titanic/game/sgt/sgt_tv.h
index 90fed90efe..e5de38e84b 100644
--- a/engines/titanic/game/sgt/sgt_tv.h
+++ b/engines/titanic/game/sgt/sgt_tv.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CSGTTV : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOff(CTurnOff *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp b/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp
index ed37b0a5c7..72cd7f9037 100644
--- a/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp
+++ b/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp
@@ -25,19 +25,19 @@
namespace Titanic {
CSGTUpperDoorsSound::CSGTUpperDoorsSound() {
- _string2 = "b#53.wav";
+ _soundName = "b#53.wav";
}
void CSGTUpperDoorsSound::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string2, indent);
+ file->writeQuotedLine(_soundName, indent);
CClickResponder::save(file, indent);
}
void CSGTUpperDoorsSound::load(SimpleFile *file) {
file->readNumber();
- _string2 = file->readString();
+ _soundName = file->readString();
CClickResponder::load(file);
}
diff --git a/engines/titanic/game/sgt/toilet.cpp b/engines/titanic/game/sgt/toilet.cpp
index 799abd6c76..b8e87b645a 100644
--- a/engines/titanic/game/sgt/toilet.cpp
+++ b/engines/titanic/game/sgt/toilet.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CToilet, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CToilet::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +40,42 @@ void CToilet::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CToilet::TurnOn(CTurnOn *msg) {
+ if (CSGTStateRoom::_statics->_v12 == "Closed"
+ && CSGTStateRoom::_statics->_v10 == "Open"
+ && CSGTStateRoom::_statics->_v8 == "Closed") {
+ setVisible(true);
+ CSGTStateRoom::_statics->_v12 = "Open";
+
+ _fieldE0 = false;
+ _startFrame = 0;
+ _endFrame = 11;
+ playMovie(0, 11, MOVIE_GAMESTATE);
+ playSound("b#1.wav");
+ }
+
+ return true;
+}
+
+bool CToilet::TurnOff(CTurnOff *msg) {
+ if (CSGTStateRoom::_statics->_v12 == "Open") {
+ CSGTStateRoom::_statics->_v12 = "Closed";
+
+ _fieldE0 = true;
+ _startFrame = 11;
+ _endFrame = 18;
+ playMovie(11, 18, MOVIE_GAMESTATE);
+ playSound("b#1.wav");
+ }
+
+ return true;
+}
+
+bool CToilet::MovieEndMsg(CMovieEndMsg *msg) {
+ if (CSGTStateRoom::_statics->_v12 == "Closed")
+ setVisible(false);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/toilet.h b/engines/titanic/game/sgt/toilet.h
index d87531ad7a..a4bc318fb2 100644
--- a/engines/titanic/game/sgt/toilet.h
+++ b/engines/titanic/game/sgt/toilet.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CToilet : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/vase.cpp b/engines/titanic/game/sgt/vase.cpp
index 3e04b5db9e..2d37818340 100644
--- a/engines/titanic/game/sgt/vase.cpp
+++ b/engines/titanic/game/sgt/vase.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CVase, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CVase::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +40,38 @@ void CVase::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CVase::TurnOn(CTurnOn *msg) {
+ if (CSGTStateRoom::_statics->_v3 == "Closed") {
+ CSGTStateRoom::_statics->_v3 = "Open";
+ setVisible(true);
+ _fieldE0 = false;
+ _startFrame = 1;
+ _endFrame = 12;
+ playMovie(1, 12, MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
+bool CVase::TurnOff(CTurnOff *msg) {
+ if (CSGTStateRoom::_statics->_v3 == "Open"
+ && CSGTStateRoom::_statics->_v1 != "RestingV"
+ && CSGTStateRoom::_statics->_v1 != "RestingUV") {
+ CSGTStateRoom::_statics->_v3 = "Closed";
+ _fieldE0 = true;
+ _startFrame = 12;
+ _endFrame = 25;
+ playMovie(12, 25, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+
+ return true;
+}
+
+bool CVase::MovieEndMsg(CMovieEndMsg *msg) {
+ if (CSGTStateRoom::_statics->_v3 == "Closed")
+ setVisible(false);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/vase.h b/engines/titanic/game/sgt/vase.h
index 8aa35acdf5..e07d9efb80 100644
--- a/engines/titanic/game/sgt/vase.h
+++ b/engines/titanic/game/sgt/vase.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CVase : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sgt/washstand.cpp b/engines/titanic/game/sgt/washstand.cpp
index 8127a59a59..afdc74414d 100644
--- a/engines/titanic/game/sgt/washstand.cpp
+++ b/engines/titanic/game/sgt/washstand.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CWashstand, CSGTStateRoom)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CWashstand::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CSGTStateRoom::save(file, indent);
@@ -34,4 +40,36 @@ void CWashstand::load(SimpleFile *file) {
CSGTStateRoom::load(file);
}
+bool CWashstand::TurnOn(CTurnOn *msg) {
+ if (_statics->_v10 == "Closed" && _statics->_v2 == "NotOnWashstand") {
+ setVisible(true);
+ _statics->_v10 = "Open";
+ _fieldE0 = false;
+ _startFrame = 0;
+ _endFrame = 14;
+ playMovie(0, 14, MOVIE_GAMESTATE);
+ playSound("b#14.wav");
+ }
+
+ return true;
+}
+
+bool CWashstand::TurnOff(CTurnOff *msg) {
+ if (_statics->_v10 == "Open" && _statics->_v11 == "Closed"
+ && _statics->_v12 == "Closed" && _statics->_v2 == "Open") {
+ _statics->_v10 = "Closed";
+ _fieldE0 = true;
+ _startFrame = 14;
+ _endFrame = 28;
+ playMovie(14, 28, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ playSound("b#14.wav");
+ }
+
+ return true;
+}
+
+bool CWashstand::MovieEndMsg(CMovieEndMsg *msg) {
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sgt/washstand.h b/engines/titanic/game/sgt/washstand.h
index f140b17f49..1b72cfa1c4 100644
--- a/engines/titanic/game/sgt/washstand.h
+++ b/engines/titanic/game/sgt/washstand.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CWashstand : public CSGTStateRoom {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/ship_setting.cpp b/engines/titanic/game/ship_setting.cpp
index 462f396501..93800e899b 100644
--- a/engines/titanic/game/ship_setting.cpp
+++ b/engines/titanic/game/ship_setting.cpp
@@ -21,35 +21,109 @@
*/
#include "titanic/game/ship_setting.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CShipSetting, CBackground)
+ ON_MESSAGE(AddHeadPieceMsg)
+ ON_MESSAGE(SetFrameMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
CShipSetting::CShipSetting() : CBackground(),
- _string4("NULL"), _string5("NULL") {
+ _itemName("NULL"), _frameTarget("NULL") {
}
void CShipSetting::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string3, indent);
+ file->writeQuotedLine(_target, indent);
file->writePoint(_pos1, indent);
- file->writeQuotedLine(_string4, indent);
- file->writeQuotedLine(_string5, indent);
+ file->writeQuotedLine(_itemName, indent);
+ file->writeQuotedLine(_frameTarget, indent);
CBackground::save(file, indent);
}
void CShipSetting::load(SimpleFile *file) {
file->readNumber();
- _string3 = file->readString();
+ _target = file->readString();
_pos1 = file->readPoint();
- _string4 = file->readString();
- _string5 = file->readString();
+ _itemName = file->readString();
+ _frameTarget = file->readString();
CBackground::load(file);
}
+bool CShipSetting::AddHeadPieceMsg(CAddHeadPieceMsg *msg) {
+ _cursorId = CURSOR_HAND;
+
+ if (msg->_value == "Enable") {
+ CTurnOn onMsg;
+ onMsg.execute(_target);
+
+ if (isEquals("ChickenSetting")) {
+ CActMsg actMsg("DecreaseQuantity");
+ actMsg.execute("ChickenDispenser");
+ }
+ } else {
+ CTurnOff offMsg;
+ offMsg.execute(_target);
+ }
+
+ return true;
+}
+
+bool CShipSetting::SetFrameMsg(CSetFrameMsg *msg) {
+ msg->execute(_frameTarget);
+ return true;
+}
+
bool CShipSetting::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CShipSetting::handleEvent");
+ CSetFrameMsg frameMsg;
+
+ if (_itemName == "ChickenBridge")
+ frameMsg._frameNumber = 1;
+ else if (_itemName == "FanBridge")
+ frameMsg._frameNumber = 2;
+ else if (_itemName == "SeasonBridge")
+ frameMsg._frameNumber = 3;
+ else if (_itemName == "BeamBridge")
+ frameMsg._frameNumber = 4;
+
+ frameMsg.execute(this);
+ return true;
+}
+
+bool CShipSetting::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (!checkStartDragging(msg))
+ return false;
+ if (_itemName == "NULL")
+ return true;
+
+ CTurnOff offMsg;
+ offMsg.execute(_target);
+
+ if (isEquals("ChickenSetting") || _itemName == "ChickenBridge") {
+ CActMsg actMsg("IncreaseQuantity");
+ actMsg.execute("ChickenDispenser");
+ }
+
+ if (_itemName != "NULL") {
+ CPassOnDragStartMsg passMsg(msg->_mousePos, 1);
+ passMsg.execute(_itemName);
+
+ msg->_dragItem = getRoot()->findByName(_itemName);
+
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute(_itemName);
+ }
+
+ CSetFrameMsg frameMsg(0);
+ frameMsg.execute(_frameTarget);
+ _itemName = "NULL";
+ _cursorId = CURSOR_ARROW;
return true;
}
diff --git a/engines/titanic/game/ship_setting.h b/engines/titanic/game/ship_setting.h
index 4fcc10a424..198d97d250 100644
--- a/engines/titanic/game/ship_setting.h
+++ b/engines/titanic/game/ship_setting.h
@@ -29,12 +29,16 @@
namespace Titanic {
class CShipSetting : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool AddHeadPieceMsg(CAddHeadPieceMsg *msg);
+ bool SetFrameMsg(CSetFrameMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
public:
- CString _string3;
+ CString _target;
Point _pos1;
- CString _string4;
- CString _string5;
+ CString _itemName;
+ CString _frameTarget;
public:
CLASSDEF;
CShipSetting();
diff --git a/engines/titanic/game/ship_setting_button.cpp b/engines/titanic/game/ship_setting_button.cpp
index 7dc2cabac0..d485e06668 100644
--- a/engines/titanic/game/ship_setting_button.cpp
+++ b/engines/titanic/game/ship_setting_button.cpp
@@ -24,25 +24,69 @@
namespace Titanic {
-CShipSettingButton::CShipSettingButton() : CGameObject(), _fieldC8(0), _fieldCC(0) {
+BEGIN_MESSAGE_MAP(CShipSettingButton, CGameObject)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
+CShipSettingButton::CShipSettingButton() : CGameObject(), _pressed(false), _enabled(false) {
}
void CShipSettingButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeNumberLine(_fieldC8, indent);
- file->writeNumberLine(_fieldCC, indent);
+ file->writeQuotedLine(_target, indent);
+ file->writeNumberLine(_pressed, indent);
+ file->writeNumberLine(_enabled, indent);
CGameObject::save(file, indent);
}
void CShipSettingButton::load(SimpleFile *file) {
file->readNumber();
- _string1 = file->readString();
- _fieldC8 = file->readNumber();
- _fieldCC = file->readNumber();
+ _target = file->readString();
+ _pressed = file->readNumber();
+ _enabled = file->readNumber();
CGameObject::load(file);
}
+bool CShipSettingButton::TurnOn(CTurnOn *msg) {
+ _pressed = true;
+ return true;
+}
+
+bool CShipSettingButton::TurnOff(CTurnOff *msg) {
+ _pressed = false;
+ return true;
+}
+
+bool CShipSettingButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_pressed) {
+ if (_enabled)
+ playMovie(8, 16, 0);
+ else
+ playMovie(0, 8, 0);
+
+ _enabled = !_enabled;
+ CActMsg actMsg(_enabled ? "EnableObject" : "DisableObject");
+ actMsg.execute(_target);
+ } else {
+ if (_enabled) {
+ playMovie(8, 16, 0);
+ playMovie(0, 8, 0);
+ } else {
+ playMovie(0, 16, 0);
+ }
+ }
+
+ return true;
+}
+
+bool CShipSettingButton::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(_enabled ? 8 : 16);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/ship_setting_button.h b/engines/titanic/game/ship_setting_button.h
index e152e8e2c3..e5457fa532 100644
--- a/engines/titanic/game/ship_setting_button.h
+++ b/engines/titanic/game/ship_setting_button.h
@@ -28,10 +28,15 @@
namespace Titanic {
class CShipSettingButton : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
- CString _string1;
- int _fieldC8;
- int _fieldCC;
+ CString _target;
+ bool _pressed;
+ bool _enabled;
public:
CLASSDEF;
CShipSettingButton();
diff --git a/engines/titanic/game/show_cell_points.cpp b/engines/titanic/game/show_cell_points.cpp
index 7d54401a02..985cb93734 100644
--- a/engines/titanic/game/show_cell_points.cpp
+++ b/engines/titanic/game/show_cell_points.cpp
@@ -21,21 +21,50 @@
*/
#include "titanic/game/show_cell_points.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CShowCellpoints, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
void CShowCellpoints::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_strValue, indent);
- file->writeNumberLine(_numValue, indent);
+ file->writeQuotedLine(_npcName, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CShowCellpoints::load(SimpleFile *file) {
file->readNumber();
- _strValue = file->readString();
- _numValue = file->readNumber();
+ _npcName = file->readString();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CShowCellpoints::EnterViewMsg(CEnterViewMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (pet) {
+ petSetArea(PET_CONVERSATION);
+ pet->setActiveNPC(_npcName);
+ pet->incAreaLocks();
+ _flag = true;
+ }
+
+ return true;
+}
+
+bool CShowCellpoints::LeaveViewMsg(CLeaveViewMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (pet && _flag) {
+ pet->resetDials0();
+ pet->decAreaLocks();
+ _flag = false;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/show_cell_points.h b/engines/titanic/game/show_cell_points.h
index 9de2e06dca..205547d7c2 100644
--- a/engines/titanic/game/show_cell_points.h
+++ b/engines/titanic/game/show_cell_points.h
@@ -28,12 +28,15 @@
namespace Titanic {
class CShowCellpoints : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
- CString _strValue;
- int _numValue;
+ CString _npcName;
+ bool _flag;
public:
CLASSDEF;
- CShowCellpoints() : CGameObject(), _numValue(0) {}
+ CShowCellpoints() : CGameObject(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/speech_dispensor.cpp b/engines/titanic/game/speech_dispensor.cpp
index f9cc019672..20ff3c69e0 100644
--- a/engines/titanic/game/speech_dispensor.cpp
+++ b/engines/titanic/game/speech_dispensor.cpp
@@ -24,15 +24,26 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSpeechDispensor, CBackground)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(ChangeSeasonMsg)
+END_MESSAGE_MAP()
+
+CSpeechDispensor::CSpeechDispensor() : CBackground(), _dragItem(nullptr),
+ _fieldE0(0), _state(0), _fieldEC(0), _fieldF8(0), _seasonNum(SEASON_SUMMER) {
+}
+
void CSpeechDispensor::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
+ file->writeNumberLine(_state, indent);
file->writeNumberLine(_fieldEC, indent);
- file->writeNumberLine(_fieldF0, indent);
- file->writeNumberLine(_fieldF4, indent);
+ file->writeNumberLine(_itemPos.x, indent);
+ file->writeNumberLine(_itemPos.y, indent);
file->writeNumberLine(_fieldF8, indent);
- file->writeNumberLine(_fieldFC, indent);
+ file->writeNumberLine(_seasonNum, indent);
CBackground::save(file, indent);
}
@@ -40,14 +51,93 @@ void CSpeechDispensor::save(SimpleFile *file, int indent) {
void CSpeechDispensor::load(SimpleFile *file) {
file->readNumber();
_fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
+ _state = file->readNumber();
_fieldEC = file->readNumber();
- _fieldF0 = file->readNumber();
- _fieldF4 = file->readNumber();
+ _itemPos.x = file->readNumber();
+ _itemPos.y = file->readNumber();
_fieldF8 = file->readNumber();
- _fieldFC = file->readNumber();
+ _seasonNum = (Season)file->readNumber();
CBackground::load(file);
}
+bool CSpeechDispensor::FrameMsg(CFrameMsg *msg) {
+ if (_fieldEC || _seasonNum == SEASON_SUMMER || _seasonNum == SEASON_SPRING)
+ return true;
+
+ CGameObject *dragObject = getDraggingObject();
+ if (!_dragItem && dragObject && getView() == findView()) {
+ if (dragObject->isEquals("Perch")) {
+ petDisplayMessage(1, "This stick is too short to reach the branches.");
+ return true;
+ }
+
+ if (dragObject->isEquals("LongStick"))
+ _dragItem = dragObject;
+ }
+
+ if (_dragItem) {
+ Point pt(_itemPos.x + _dragItem->_bounds.left,
+ _itemPos.y + _dragItem->_bounds.top);
+ if (!checkPoint(pt, true))
+ return true;
+
+ switch (_state) {
+ case 0:
+ playSound("z#93.wav");
+ if (_seasonNum == SEASON_WINTER) {
+ petDisplayMessage(1, "You cannot get this, it is frozen to the branch.");
+ _fieldE0 = false;
+ _state = 1;
+ } else {
+ if (++_fieldE0 >= 5) {
+ CActMsg actMsg("PlayerGetsSpeechCentre");
+ actMsg.execute("SeasonalAdjust");
+ CSpeechFallsFromTreeMsg fallMsg(pt);
+ fallMsg.execute("SpeechCentre");
+
+ _fieldEC = true;
+ _fieldE0 = false;
+ }
+
+ _state = 1;
+ }
+ break;
+
+ case 2:
+ _state = 0;
+ ++_fieldE0;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CSpeechDispensor::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ if (!_fieldEC) {
+ playSound("z#93.wav");
+ if (_fieldF8) {
+ petDisplayMessage(1, "Sadly, this is out of your reach.");
+ } else {
+ petDisplayMessage(1, "You can't pick this up on account of it being stuck to the branch.");
+ }
+ }
+
+ return true;
+}
+
+bool CSpeechDispensor::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _fieldF8 = msg->_newStatus == 1;
+ return true;
+}
+
+bool CSpeechDispensor::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ _seasonNum = (Season)(((int)_seasonNum + 1) % 4);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/speech_dispensor.h b/engines/titanic/game/speech_dispensor.h
index 3b877e8d99..038cc024cc 100644
--- a/engines/titanic/game/speech_dispensor.h
+++ b/engines/titanic/game/speech_dispensor.h
@@ -28,17 +28,22 @@
namespace Titanic {
class CSpeechDispensor : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool FrameMsg(CFrameMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
private:
int _fieldE0;
- int _fieldE4;
- int _fieldE8;
+ int _state;
+ CGameObject *_dragItem;
int _fieldEC;
- int _fieldF0;
- int _fieldF4;
+ Point _itemPos;
int _fieldF8;
- int _fieldFC;
+ Season _seasonNum;
public:
CLASSDEF;
+ CSpeechDispensor();
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/starling_puret.cpp b/engines/titanic/game/starling_puret.cpp
index 359ad774df..2f1909d963 100644
--- a/engines/titanic/game/starling_puret.cpp
+++ b/engines/titanic/game/starling_puret.cpp
@@ -24,16 +24,56 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CStarlingPuret, CGameObject)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CStarlingPuret::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CStarlingPuret::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CStarlingPuret::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _flag = msg->_newStatus == 1;
+ if (_flag) {
+ CStatusChangeMsg changeMsg;
+ changeMsg._newStatus = 1;
+ changeMsg.execute("StarlingLoop01");
+ }
+
+ return true;
+}
+
+bool CStarlingPuret::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_flag) {
+ CStatusChangeMsg changeMsg;
+ changeMsg._newStatus = 1;
+ changeMsg.execute("PromDeckStarlings");
+
+ playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ CSignalObject signalMsg;
+ signalMsg._numValue = 4;
+ signalMsg.execute("PromDeckStarlings");
+ _flag = false;
+ }
+
+ return true;
+}
+
+bool CStarlingPuret::MovieEndMsg(CMovieEndMsg *msg) {
+ CActMsg actMsg("StarlingsDead");
+ actMsg.execute("FanController");
+ actMsg.execute("BirdSauceDisp");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/starling_puret.h b/engines/titanic/game/starling_puret.h
index fcd3319958..62a6173093 100644
--- a/engines/titanic/game/starling_puret.h
+++ b/engines/titanic/game/starling_puret.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CStarlingPuret : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
private:
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CStarlingPuret() : CGameObject(), _value(0) {}
+ CStarlingPuret() : CGameObject(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/stop_phonograph_button.cpp b/engines/titanic/game/stop_phonograph_button.cpp
index d18f4713ac..75e0ca9337 100644
--- a/engines/titanic/game/stop_phonograph_button.cpp
+++ b/engines/titanic/game/stop_phonograph_button.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CStopPhonographButton, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CStopPhonographButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBackground::save(file, indent);
@@ -34,4 +38,19 @@ void CStopPhonographButton::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CStopPhonographButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CQueryPhonographState queryMsg;
+ queryMsg.execute(getParent());
+
+ if (!queryMsg._value) {
+ playMovie(0, 1, 0);
+ playMovie(1, 0, 0);
+
+ CPhonographStopMsg stopMsg;
+ stopMsg.execute(getParent());
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/stop_phonograph_button.h b/engines/titanic/game/stop_phonograph_button.h
index b469375e20..d416c4f8fe 100644
--- a/engines/titanic/game/stop_phonograph_button.h
+++ b/engines/titanic/game/stop_phonograph_button.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CStopPhonographButton : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/sub_glass.cpp b/engines/titanic/game/sub_glass.cpp
index f1349f06ea..041f49097d 100644
--- a/engines/titanic/game/sub_glass.cpp
+++ b/engines/titanic/game/sub_glass.cpp
@@ -24,17 +24,25 @@
namespace Titanic {
-CSUBGlass::CSUBGlass() : _fieldBC(0), _fieldC0(0), _fieldC4(1), _fieldC8(0) {
+BEGIN_MESSAGE_MAP(CSUBGlass, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(SignalObject)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
+CSUBGlass::CSUBGlass() : _fieldBC(0), _startFrame(0), _endFrame(1), _signalStartFrame(0) {
}
void CSUBGlass::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
- file->writeNumberLine(_fieldC8, indent);
- file->writeNumberLine(_fieldCC, indent);
- file->writeQuotedLine(_string, indent);
+ file->writeNumberLine(_startFrame, indent);
+ file->writeNumberLine(_endFrame, indent);
+ file->writeNumberLine(_signalStartFrame, indent);
+ file->writeNumberLine(_signalEndFrame, indent);
+ file->writeQuotedLine(_target, indent);
CGameObject::save(file, indent);
}
@@ -42,13 +50,58 @@ void CSUBGlass::save(SimpleFile *file, int indent) {
void CSUBGlass::load(SimpleFile *file) {
file->readNumber();
_fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
- _fieldC8 = file->readNumber();
- _fieldCC = file->readNumber();
- _string = file->readString();
+ _startFrame = file->readNumber();
+ _endFrame = file->readNumber();
+ _signalStartFrame = file->readNumber();
+ _signalEndFrame = file->readNumber();
+ _target = file->readString();
CGameObject::load(file);
}
+bool CSUBGlass::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+bool CSUBGlass::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ if (!_fieldBC && _startFrame >= 0) {
+ _fieldBC = true;
+ playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT);
+ playSound("z#30.wav");
+ }
+
+ return true;
+}
+
+bool CSUBGlass::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == _endFrame) {
+ _fieldBC = true;
+ CSignalObject signalMsg(getName(), 1);
+ signalMsg.execute(_target);
+ }
+
+ return true;
+}
+
+bool CSUBGlass::SignalObject(CSignalObject *msg) {
+ if (msg->_numValue == 1) {
+ setVisible(true);
+
+ if (_signalStartFrame >= 0) {
+ playMovie(_signalStartFrame, _signalEndFrame, MOVIE_GAMESTATE);
+ playSound("z#30.wav");
+ _fieldBC = false;
+ }
+ }
+
+ return true;
+}
+
+bool CSUBGlass::LeaveViewMsg(CLeaveViewMsg *msg) {
+ _fieldBC = false;
+ setVisible(true);
+ loadFrame(0);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sub_glass.h b/engines/titanic/game/sub_glass.h
index aab5c8400e..22d16ff4d5 100644
--- a/engines/titanic/game/sub_glass.h
+++ b/engines/titanic/game/sub_glass.h
@@ -28,13 +28,19 @@
namespace Titanic {
class CSUBGlass : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool SignalObject(CSignalObject *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
private:
int _fieldBC;
- int _fieldC0;
- int _fieldC4;
- int _fieldC8;
- int _fieldCC;
- CString _string;
+ int _startFrame;
+ int _endFrame;
+ int _signalStartFrame;
+ int _signalEndFrame;
+ CString _target;
public:
CLASSDEF;
CSUBGlass();
diff --git a/engines/titanic/game/sub_wrapper.cpp b/engines/titanic/game/sub_wrapper.cpp
index dcc489316b..4080487d6d 100644
--- a/engines/titanic/game/sub_wrapper.cpp
+++ b/engines/titanic/game/sub_wrapper.cpp
@@ -24,16 +24,56 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSUBWrapper, CGameObject)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(SignalObject)
+END_MESSAGE_MAP()
+
void CSUBWrapper::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CSUBWrapper::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CSUBWrapper::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_flag) {
+ stopMovie();
+ setVisible(false);
+ _flag = false;
+ }
+
+ return true;
+}
+
+bool CSUBWrapper::SignalObject(CSignalObject *msg) {
+ switch (msg->_numValue) {
+ case 1:
+ if (!_flag) {
+ loadFrame(0);
+ setVisible(true);
+ playMovie(MOVIE_NOTIFY_OBJECT);
+ _flag = true;
+ }
+ break;
+
+ case 2:
+ if (!_flag) {
+ setVisible(true);
+ _flag = true;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sub_wrapper.h b/engines/titanic/game/sub_wrapper.h
index 81f8fdc941..1094a2e677 100644
--- a/engines/titanic/game/sub_wrapper.h
+++ b/engines/titanic/game/sub_wrapper.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CSUBWrapper : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool SignalObject(CSignalObject *msg);
public:
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CSUBWrapper() : CGameObject(), _value(0) {}
+ CSUBWrapper() : CGameObject(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/sweet_bowl.cpp b/engines/titanic/game/sweet_bowl.cpp
index e14f900e77..d0a2525bc4 100644
--- a/engines/titanic/game/sweet_bowl.cpp
+++ b/engines/titanic/game/sweet_bowl.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSweetBowl, CGameObject)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
void CSweetBowl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +40,28 @@ void CSweetBowl::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CSweetBowl::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
+bool CSweetBowl::EnterViewMsg(CEnterViewMsg *msg) {
+ setVisible(false);
+ loadSound("b#43.wav");
+ playSound("b#42.wav");
+ return true;
+}
+
+bool CSweetBowl::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Jiggle") {
+ setVisible(true);
+ playMovie(MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ playSound(getRandomNumber(1) == 1 ? "b#42.wav" : "b#43.wav");
+ }
+
+ petDisplayMessage(isEquals("BowlNutsRustler") ?
+ "A bowl of pistachio nuts." : "Not a bowl of pistachio nuts.");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/sweet_bowl.h b/engines/titanic/game/sweet_bowl.h
index cf3d0023a4..53433c8c08 100644
--- a/engines/titanic/game/sweet_bowl.h
+++ b/engines/titanic/game/sweet_bowl.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CSweetBowl : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool ActMsg(CActMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/television.cpp b/engines/titanic/game/television.cpp
index af6bdb8c03..ba30fbe281 100644
--- a/engines/titanic/game/television.cpp
+++ b/engines/titanic/game/television.cpp
@@ -20,10 +20,12 @@
*
*/
-#include "titanic/titanic.h"
#include "titanic/game/television.h"
-#include "titanic/pet_control/pet_control.h"
#include "titanic/game/get_lift_eye2.h"
+#include "titanic/core/project_item.h"
+#include "titanic/carry/magazine.h"
+#include "titanic/pet_control/pet_control.h"
+#include "titanic/titanic.h"
namespace Titanic {
@@ -121,16 +123,16 @@ bool CTelevision::LeaveViewMsg(CLeaveViewMsg *msg) {
}
bool CTelevision::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
- if (msg->_season.compareTo("Autumn")) {
+ if (msg->_season == "Autumn") {
_v1 = 545;
_v3 = 0;
- } else if (msg->_season.compareTo("Winter")) {
+ } else if (msg->_season == "Winter") {
_v1 = 503;
_v3 = 0;
- } else if (msg->_season.compareTo("Spring")) {
+ } else if (msg->_season == "Spring") {
_v1 = 517;
_v3 = 0;
- } else if (msg->_season.compareTo("Winter")) {
+ } else if (msg->_season == "Summer") {
_v1 = 531;
_v3 = 0;
}
@@ -229,7 +231,7 @@ bool CTelevision::PETActivateMsg(CPETActivateMsg *msg) {
}
bool CTelevision::MovieEndMsg(CMovieEndMsg *msg) {
- if (g_vm->getRandomNumber(6) == 0) {
+ if (getRandomNumber(6) == 0) {
CParrotSpeakMsg parrotMsg("Television", "");
parrotMsg.execute("PerchedParrot");
}
@@ -237,10 +239,15 @@ bool CTelevision::MovieEndMsg(CMovieEndMsg *msg) {
if (_fieldE0 == 3 && compareRoomNameTo("SGTState") && !getPassengerClass()) {
playSound("z#47.wav", 100, 0, 0);
_soundHandle = playSound("b#20.wav", 100, 0, 0);
- CTreeItem *magazine = getRoot()->findByName("Magazine");
+ CMagazine *magazine = dynamic_cast<CMagazine *>(getRoot()->findByName("Magazine"));
if (magazine) {
- warning("TODO: CTelevision::MovieEndMsg");
+ CPetControl *pet = getPetControl();
+ uint roomFlags = pet->getRoomFlags();
+
+ debugC(kDebugScripts, "Assigned room - %d", roomFlags);
+ magazine->addMail(roomFlags);
+ magazine->removeMail(roomFlags, roomFlags);
}
loadFrame(561);
@@ -251,7 +258,7 @@ bool CTelevision::MovieEndMsg(CMovieEndMsg *msg) {
loadFrame(502);
else
warning("There is currently nothing available for your viewing pleasure on this channel.");
- } else if (_fieldE0 == 5 && *CGetLiftEye2::_v1 != "NULL") {
+ } else if (_fieldE0 == 5 && *CGetLiftEye2::_destObject != "NULL") {
loadFrame(393 + _v4);
} else {
warning("There is currently nothing available for your viewing pleasure on this channel.");
@@ -282,7 +289,7 @@ bool CTelevision::LightsMsg(CLightsMsg *msg) {
if (pet)
flag = pet->isRoom59706();
- if (msg->_field8 || !flag)
+ if (msg->_flag2 || !flag)
_turnOn = true;
return true;
diff --git a/engines/titanic/game/television.h b/engines/titanic/game/television.h
index 6e6d9b23c2..2e8d469bde 100644
--- a/engines/titanic/game/television.h
+++ b/engines/titanic/game/television.h
@@ -45,18 +45,18 @@ class CTelevision : public CBackground {
bool TurnOn(CTurnOn *msg);
bool LightsMsg(CLightsMsg *msg);
private:
+ int _fieldE0;
+ int _fieldE4;
+ bool _isOn;
+ int _fieldEC;
+ int _soundHandle;
+public:
static int _v1;
static bool _turnOn;
static int _v3;
static int _v4;
static int _v5;
static int _v6;
-private:
- int _fieldE0;
- int _fieldE4;
- bool _isOn;
- int _fieldEC;
- int _soundHandle;
public:
CLASSDEF;
CTelevision();
diff --git a/engines/titanic/game/third_class_canal.cpp b/engines/titanic/game/third_class_canal.cpp
index 6b0a101f7b..97b559e35d 100644
--- a/engines/titanic/game/third_class_canal.cpp
+++ b/engines/titanic/game/third_class_canal.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CThirdClassCanal, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CThirdClassCanal::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CBackground::save(file, indent);
@@ -34,4 +38,9 @@ void CThirdClassCanal::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CThirdClassCanal::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ petDisplayMessage("This area is off limits to passengers.");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/third_class_canal.h b/engines/titanic/game/third_class_canal.h
index f6fc471444..d0be8c05aa 100644
--- a/engines/titanic/game/third_class_canal.h
+++ b/engines/titanic/game/third_class_canal.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CThirdClassCanal : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/throw_tv_down_well.cpp b/engines/titanic/game/throw_tv_down_well.cpp
index c8a9fc7c9e..9de028cbde 100644
--- a/engines/titanic/game/throw_tv_down_well.cpp
+++ b/engines/titanic/game/throw_tv_down_well.cpp
@@ -24,18 +24,73 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CThrowTVDownWell, CGameObject)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(MovieFrameMsg)
+END_MESSAGE_MAP()
+
void CThrowTVDownWell::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_strValue, indent);
- file->writeNumberLine(_numValue, indent);
+ file->writeQuotedLine(_viewName, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CThrowTVDownWell::load(SimpleFile *file) {
file->readNumber();
- _strValue = file->readString();
- _numValue = file->readNumber();
+ _viewName = file->readString();
+ _flag = file->readNumber();
CGameObject::load(file);
}
+bool CThrowTVDownWell::ActMsg(CActMsg *msg) {
+ if (msg->_action == "ThrowTVDownWell" && !_flag) {
+ CString viewName = getFullViewName();
+ lockMouse();
+ addTimer(1, 8000, 0);
+
+ CActMsg actMsg("ThrownTVDownWell");
+ actMsg.execute("BOWTelevisionMonitor");
+ }
+
+ return true;
+}
+
+bool CThrowTVDownWell::EnterViewMsg(CEnterViewMsg *msg) {
+ playMovie(MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ movieEvent(49);
+ return true;
+}
+
+bool CThrowTVDownWell::MovieEndMsg(CMovieEndMsg *msg) {
+ sleep(2000);
+ changeView("ParrotLobby.Node 11.N");
+ playSound("z#471.wav");
+ addTimer(2, 7000, 0);
+ return true;
+}
+
+bool CThrowTVDownWell::TimerMsg(CTimerMsg *msg) {
+ if (msg->_actionVal == 1) {
+ changeView("ParrotLobby.Node 10.N");
+ } else if (msg->_actionVal == 2) {
+ playSound("z#468.wav", 50);
+ sleep(1500);
+ changeView(_viewName);
+ _viewName = "NULL";
+ unlockMouse();
+ playSound("z#47.wav");
+ }
+
+ return true;
+}
+
+bool CThrowTVDownWell::MovieFrameMsg(CMovieFrameMsg *msg) {
+ playSound("z#470.wav");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/throw_tv_down_well.h b/engines/titanic/game/throw_tv_down_well.h
index b6003aa3ef..c9e8fd57a9 100644
--- a/engines/titanic/game/throw_tv_down_well.h
+++ b/engines/titanic/game/throw_tv_down_well.h
@@ -28,12 +28,18 @@
namespace Titanic {
class CThrowTVDownWell : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool MovieFrameMsg(CMovieFrameMsg *msg);
public:
- CString _strValue;
- int _numValue;
+ CString _viewName;
+ bool _flag;
public:
CLASSDEF;
- CThrowTVDownWell() : CGameObject(), _numValue(0) {}
+ CThrowTVDownWell() : CGameObject(), _flag(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/titania_still_control.cpp b/engines/titanic/game/titania_still_control.cpp
index 67866ecdcb..1ce0a85b4e 100644
--- a/engines/titanic/game/titania_still_control.cpp
+++ b/engines/titanic/game/titania_still_control.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CTitaniaStillControl, CGameObject)
+ ON_MESSAGE(SetFrameMsg)
+ ON_MESSAGE(VisibleMsg)
+END_MESSAGE_MAP()
+
void CTitaniaStillControl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +39,15 @@ void CTitaniaStillControl::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CTitaniaStillControl::SetFrameMsg(CSetFrameMsg *msg) {
+ loadFrame(msg->_frameNumber);
+ setVisible(true);
+ return true;
+}
+
+bool CTitaniaStillControl::VisibleMsg(CVisibleMsg *msg) {
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/titania_still_control.h b/engines/titanic/game/titania_still_control.h
index 66c3045187..b77227a539 100644
--- a/engines/titanic/game/titania_still_control.h
+++ b/engines/titanic/game/titania_still_control.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CTitaniaStillControl : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool SetFrameMsg(CSetFrameMsg *msg);
+ bool VisibleMsg(CVisibleMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/tow_parrot_nav.cpp b/engines/titanic/game/tow_parrot_nav.cpp
index 9361808870..57f1649add 100644
--- a/engines/titanic/game/tow_parrot_nav.cpp
+++ b/engines/titanic/game/tow_parrot_nav.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/game/tow_parrot_nav.h"
+#include "titanic/npcs/parrot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CTOWParrotNav, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CTOWParrotNav::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CGameObject::save(file, indent);
@@ -34,4 +39,16 @@ void CTOWParrotNav::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CTOWParrotNav::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CActMsg actMsg("EnteringFromTOW");
+ actMsg.execute("PerchedParrot");
+
+ CString clipString = "_EXIT,36,1,N,9,3,N";
+ if (CParrot::_v4)
+ clipString += 'a';
+ changeView("ParrotLobby.Node 3.N", clipString);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/tow_parrot_nav.h b/engines/titanic/game/tow_parrot_nav.h
index 17618ff6de..252d9b631d 100644
--- a/engines/titanic/game/tow_parrot_nav.h
+++ b/engines/titanic/game/tow_parrot_nav.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CTOWParrotNav : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/transport/gondolier.cpp b/engines/titanic/game/transport/gondolier.cpp
index f731e45bde..8c28ff9b66 100644
--- a/engines/titanic/game/transport/gondolier.cpp
+++ b/engines/titanic/game/transport/gondolier.cpp
@@ -24,14 +24,31 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CGondolier, CTransport)
+ ON_MESSAGE(StatusChangeMsg)
+END_MESSAGE_MAP()
+
+int CGondolier::_v1;
+int CGondolier::_v2;
+
void CGondolier::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
+ file->writeNumberLine(_v1, indent);
+ file->writeNumberLine(_v2, indent);
CTransport::save(file, indent);
}
void CGondolier::load(SimpleFile *file) {
file->readNumber();
+ _v1 = file->readNumber();
+ _v2 = file->readNumber();
CTransport::load(file);
}
+bool CGondolier::StatusChangeMsg(CStatusChangeMsg *msg) {
+ CShowTextMsg textMsg("Only First Class passengers are allowed to use the Gondoliers.");
+ textMsg.execute("PET");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/transport/gondolier.h b/engines/titanic/game/transport/gondolier.h
index ac1617256f..3b1e6d5a8a 100644
--- a/engines/titanic/game/transport/gondolier.h
+++ b/engines/titanic/game/transport/gondolier.h
@@ -28,6 +28,11 @@
namespace Titanic {
class CGondolier : public CTransport {
+ DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+private:
+ static int _v1;
+ static int _v2;
public:
CLASSDEF;
diff --git a/engines/titanic/game/transport/lift.cpp b/engines/titanic/game/transport/lift.cpp
index 72f832bf76..114e840007 100644
--- a/engines/titanic/game/transport/lift.cpp
+++ b/engines/titanic/game/transport/lift.cpp
@@ -21,28 +21,35 @@
*/
#include "titanic/game/transport/lift.h"
+#include "titanic/pet_control/pet_control.h"
+#include "titanic/titanic.h"
namespace Titanic {
BEGIN_MESSAGE_MAP(CLift, CTransport)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(EnterViewMsg)
ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(ActMsg)
END_MESSAGE_MAP()
int CLift::_v1;
-int CLift::_v2;
-int CLift::_v3;
-int CLift::_v4;
-int CLift::_v5;
+int CLift::_elevator1Floor;
+int CLift::_elevator2Floor;
+int CLift::_elevator3Floor;
+int CLift::_elevator4Floor;
int CLift::_v6;
void CLift::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_v1, indent);
- file->writeNumberLine(_v2, indent);
- file->writeNumberLine(_v3, indent);
- file->writeNumberLine(_v4, indent);
- file->writeNumberLine(_v5, indent);
- file->writeNumberLine(_fieldF8, indent);
+ file->writeNumberLine(_elevator1Floor, indent);
+ file->writeNumberLine(_elevator2Floor, indent);
+ file->writeNumberLine(_elevator3Floor, indent);
+ file->writeNumberLine(_elevator4Floor, indent);
+ file->writeNumberLine(_liftNum, indent);
file->writeNumberLine(_v6, indent);
CTransport::save(file, indent);
@@ -51,18 +58,260 @@ void CLift::save(SimpleFile *file, int indent) {
void CLift::load(SimpleFile *file) {
file->readNumber();
_v1 = file->readNumber();
- _v2 = file->readNumber();
- _v3 = file->readNumber();
- _v4 = file->readNumber();
- _v5 = file->readNumber();
- _fieldF8 = file->readNumber();
+ _elevator1Floor = file->readNumber();
+ _elevator2Floor = file->readNumber();
+ _elevator3Floor = file->readNumber();
+ _elevator4Floor = file->readNumber();
+ _liftNum = file->readNumber();
_v6 = file->readNumber();
CTransport::load(file);
}
+bool CLift::StatusChangeMsg(CStatusChangeMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if ((!_v1 && pet->getRoomsElevatorNum() == 4) ||
+ (!_v6 && pet->getRoomsElevatorNum() == 4))
+ return true;
+
+ int oldFloorNum = msg->_oldStatus;
+ int floorNum = msg->_newStatus;
+ int oldClass = 0, newClass = 0;
+ if (oldFloorNum == 19)
+ oldClass = 2;
+ if (oldFloorNum == 27)
+ oldClass = 3;
+ if (floorNum == 19)
+ newClass = 2;
+ if (floorNum == 27)
+ newClass = 3;
+
+ static const int UP_FRAME_NUMBERS[40] = {
+ 0, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53, 58,
+ 63, 68, 73, 78, 83, 88, 93, 118, 123, 128, 133,
+ 138, 143, 148, 153, 228, 233, 238, 243, 248, 253,
+ 258, 263, 268, 273, 278, 298, 299
+ };
+ static const int DOWN_FRAME_NUMBERS[39] = {
+ 598, 589, 584, 579, 574, 569, 564, 559, 554, 549,
+ 544, 539, 534, 529, 524, 519, 514, 509, 504, 479,
+ 474, 469, 464, 459, 454, 449, 444, 369, 364, 359,
+ 354, 349, 344, 339, 334, 329, 324, 319
+ };
+
+ if (pet)
+ pet->setRoomsFloorNum(floorNum);
+ if (pet->getRoomsElevatorNum() == 2 || pet->getRoomsElevatorNum() == 4) {
+ if (floorNum > 27)
+ floorNum = 27;
+ if (oldFloorNum > 27)
+ oldFloorNum = 27;
+ }
+
+ changeView("Lift.Node 1.N");
+ CTurnOn onMsg;
+ onMsg.execute("LiftHood");
+
+ CString debugStr;
+ if (floorNum > oldFloorNum) {
+ // Animate lift going up
+ _startFrame = UP_FRAME_NUMBERS[oldFloorNum - 1];
+ _endFrame = UP_FRAME_NUMBERS[floorNum - 1];
+
+ if (oldClass == newClass) {
+ debugStr = CString::format("Same (%d-%d)", _startFrame, _endFrame);
+ playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ } else if (oldClass == 1 && newClass == 2) {
+ debugStr = CString::format("1 to 2 (%d-108, 108-%d)", _startFrame, _endFrame);
+ playMovie(_startFrame, 108, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(108, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ } else if (oldClass == 1 && newClass == 3) {
+ debugStr = CString::format("1 to 3 (%d-108, 108-190, 190-%d)", _startFrame, _endFrame);
+ playMovie(_startFrame, 108, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(108, 190, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(190, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ } else {
+ debugStr = CString::format("2 to 3 (%d-190, 190-%d)", _startFrame, _endFrame);
+ playMovie(_startFrame, 190, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(190, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+ }
+
+ if (floorNum < oldFloorNum) {
+ // Animate lift going down
+ _startFrame = DOWN_FRAME_NUMBERS[floorNum - 1];
+ _endFrame = DOWN_FRAME_NUMBERS[oldFloorNum - 1];
+
+ if (oldClass == newClass) {
+ debugStr = CString::format("Same (%d-%d)", _startFrame, _endFrame);
+ playMovie(_startFrame, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ } else if (oldClass == 3 && newClass == 2) {
+ debugStr = CString::format("3 to 2 (%d-407, 407-%d)", _startFrame, _endFrame);
+ playMovie(_startFrame, 407, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(407, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ } else if (oldClass == 3 && newClass == 1) {
+ debugStr = CString::format("3 to 1 (%d-407, 407-489, 489-%d)", _startFrame, _endFrame);
+ playMovie(_startFrame, 407, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(407, 489, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(489, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ } else {
+ debugStr = CString::format("2 to 1 (%d-489, 489-%d)", _startFrame, _endFrame);
+ playMovie(_startFrame, 489, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(489, _endFrame, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+ }
+
+ CShipSettingMsg settingMsg;
+ switch (pet->getRoomsElevatorNum()) {
+ case 1:
+ _elevator1Floor = floorNum;
+ break;
+ case 2:
+ _elevator2Floor = floorNum;
+ _elevator4Floor = oldFloorNum;
+ settingMsg._value = oldFloorNum;
+ settingMsg.execute("SGTStateroomTV");
+ break;
+ case 3:
+ _elevator3Floor = floorNum;
+ break;
+ case 4:
+ _elevator4Floor = floorNum;
+ break;
+ default:
+ break;
+ }
+
+ debugC(1, kDebugScripts, "%s", debugStr.c_str());
+ return true;
+}
+
+bool CLift::MovieEndMsg(CMovieEndMsg *msg) {
+ switch (msg->_endFrame) {
+ case 108:
+ setGlobalSoundVolume(-4, 1, 2);
+ setGlobalSoundVolume(-2, 1, 1);
+ break;
+
+ case 190:
+ setGlobalSoundVolume(-4, 1, 1);
+ setGlobalSoundVolume(-2, 1, 2);
+ break;
+
+ case 407:
+ setGlobalSoundVolume(-4, 1, 0);
+ setGlobalSoundVolume(-2, 1, 1);
+ break;
+
+ case 489:
+ setGlobalSoundVolume(-4, 1, 1);
+ setGlobalSoundVolume(-2, 1, 0);
+ break;
+
+ default: {
+ CActMsg actMsg("LiftArrive");
+ actMsg.execute("Liftbot");
+ sleep(500);
+ playSound("352 gp button 1.wav");
+
+ CTurnOff offMsg;
+ offMsg.execute("LiftHood");
+ changeView("Lift.Node 1.W");
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CLift::EnterViewMsg(CEnterViewMsg *msg) {
+ static const int FRAME_NUMBERS[40] = {
+ 0, 8, 13, 18, 23, 28, 33, 38, 43, 48, 53, 58, 63, 68, 73,
+ 78, 83, 88, 93, 118, 123, 128, 133, 138, 143, 148, 153,
+ 228, 233, 238, 243, 248, 253, 258, 263, 268, 273, 278, 298
+ };
+
+ CPetControl *pet = getPetControl();
+ loadFrame(FRAME_NUMBERS[pet->getRoomsFloorNum() - 1]);
+ return true;
+}
+
bool CLift::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CLift::handleEvent");
+ if (isEquals("Well")) {
+ CPetControl *pet = getPetControl();
+ int floorNum = pet->getRoomsFloorNum();
+ int elevNum = pet->getRoomsElevatorNum();
+ loadSound("z#520.wav");
+ loadSound("z#519.wav");
+ loadSound("z#518.wav");
+
+ if (elevNum == 4 && _v1 == 1 && !_v6) {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("GetLiftEye");
+ }
+
+ if (floorNum < 20) {
+ playGlobalSound("z#520.wav", -2, true, true, 0);
+ playGlobalSound("z#519.wav", -4, false, true, 1);
+ playGlobalSound("z#518.wav", -4, false, true, 2);
+ } else if (floorNum < 28) {
+ playGlobalSound("z#520.wav", -4, false, true, 0);
+ playGlobalSound("z#519.wav", -2, true, true, 1);
+ playGlobalSound("z#518.wav", -4, false, true, 2);
+ } else {
+ playGlobalSound("z#520.wav", -4, false, true, 0);
+ playGlobalSound("z#519.wav", -4, false, true, 1);
+ playGlobalSound("z#518.wav", -2, true, true, 2);
+ }
+ }
+
+ return true;
+}
+
+bool CLift::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ stopGlobalSound(true, -1);
+
+ CPetControl *pet = getPetControl();
+ if (pet->getRoomsElevatorNum() == 4 && _v1 == 1 && !_v6) {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("Eye2");
+ }
+
+ return true;
+}
+
+bool CLift::ActMsg(CActMsg *msg) {
+ if (msg->_action == "LoseHead") {
+ _v1 = 0;
+ _v6 = 0;
+
+ CActMsg actMsg1("Lift.Node 2.N");
+ actMsg1.execute("RPanInLiftW");
+ CActMsg actMsg2("Lift.Node 2.S");
+ actMsg2.execute("LPanInLiftW");
+ } else if (msg->_action == "AddWrongHead") {
+ _v1 = 1;
+ _v6 = 0;
+
+ CActMsg actMsg1("Lift.Node 1.N");
+ actMsg1.execute("RPanInLiftW");
+ CActMsg actMsg2("Lift.Node 1.S");
+ actMsg2.execute("LPanInLiftW");
+ } else if (msg->_action == "AddRightHead") {
+ _v1 = 1;
+ _v6 = 1;
+ petSetRooms1D4(0);
+
+ CActMsg actMsg1("Lift.Node 1.N");
+ actMsg1.execute("RPanInLiftW");
+ CActMsg actMsg2("Lift.Node 1.S");
+ actMsg2.execute("LPanInLiftW");
+ CActMsg actMsg3("ActivateLift");
+ actMsg3.execute("Liftbot");
+ }
+
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("LiftbotWithoutHead");
return true;
}
diff --git a/engines/titanic/game/transport/lift.h b/engines/titanic/game/transport/lift.h
index 4595f0fec2..c45d2b64d0 100644
--- a/engines/titanic/game/transport/lift.h
+++ b/engines/titanic/game/transport/lift.h
@@ -30,19 +30,24 @@ namespace Titanic {
class CLift : public CTransport {
DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
-private:
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool ActMsg(CActMsg *msg);
+public:
static int _v1;
- static int _v2;
- static int _v3;
- static int _v4;
- static int _v5;
+ static int _elevator1Floor;
+ static int _elevator2Floor;
+ static int _elevator3Floor;
+ static int _elevator4Floor;
static int _v6;
- int _fieldF8;
+ int _liftNum;
public:
CLASSDEF;
- CLift() : CTransport(), _fieldF8(1) {}
+ CLift() : CTransport(), _liftNum(1) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/transport/lift_indicator.cpp b/engines/titanic/game/transport/lift_indicator.cpp
index 582de8ad3b..7471affc36 100644
--- a/engines/titanic/game/transport/lift_indicator.cpp
+++ b/engines/titanic/game/transport/lift_indicator.cpp
@@ -21,23 +21,32 @@
*/
#include "titanic/game/transport/lift_indicator.h"
+#include "titanic/game/transport/lift.h"
+#include "titanic/pet_control/pet_control.h"
+#include "titanic/titanic.h"
namespace Titanic {
BEGIN_MESSAGE_MAP(CLiftindicator, CLift)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(PETActivateMsg)
+ ON_MESSAGE(MovieEndMsg)
ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(TimerMsg)
END_MESSAGE_MAP()
CLiftindicator::CLiftindicator() : CLift(),
- _fieldFC(0), _field108(0), _field10C(0) {
+ _fieldFC(0), _start(0), _end(0) {
}
void CLiftindicator::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldFC, indent);
- file->writePoint(_pos2, indent);
- file->writeNumberLine(_field108, indent);
- file->writeNumberLine(_field10C, indent);
+ file->writePoint(_indicatorPos, indent);
+ file->writeNumberLine(_start, indent);
+ file->writeNumberLine(_end, indent);
CLift::save(file, indent);
}
@@ -45,11 +54,184 @@ void CLiftindicator::save(SimpleFile *file, int indent) {
void CLiftindicator::load(SimpleFile *file) {
file->readNumber();
_fieldFC = file->readNumber();
- _pos2 = file->readPoint();
- _field108 = file->readNumber();
- _field10C = file->readNumber();
+ _indicatorPos = file->readPoint();
+ _start = file->readNumber();
+ _end = file->readNumber();
CLift::load(file);
}
+bool CLiftindicator::EnterViewMsg(CEnterViewMsg *msg) {
+ double multiplier = _fieldFC * 0.037037037;
+ CPetControl *pet = getPetControl();
+ int floorNum = pet->getRoomsFloorNum();
+ debugC(kDebugScripts, "Lifts = %d,%d,%d,%d, %d",
+ CLift::_elevator1Floor, CLift::_elevator2Floor,
+ CLift::_elevator3Floor, CLift::_elevator4Floor,
+ floorNum);
+
+ if ((pet->petGetRoomsWellEntry() & 1) == (_fieldFC & 1)) {
+ petSetRemoteTarget();
+ petSetArea(PET_REMOTE);
+
+ CString str = CString::format("You are standing outside Elevator %d",
+ petGetRoomsWellEntry());
+ petDisplayMessage(-1, str);
+
+ debugC(kDebugScripts, "Claiming PET - %d, Multiplier = %f",
+ _liftNum, multiplier);
+ }
+
+ switch (_liftNum) {
+ case 0:
+ loadFrame(pet->getRoomsFloorNum());
+ break;
+
+ case 1:
+ case 3:
+ switch (petGetRoomsWellEntry()) {
+ case 1:
+ case 2:
+ setPosition(Point(_bounds.left, _indicatorPos.y +
+ (int)(multiplier * CLift::_elevator1Floor)));
+ _startFrame = CLift::_elevator1Floor;
+ break;
+
+ case 3:
+ case 4:
+ setPosition(Point(_bounds.left, _indicatorPos.y +
+ (int)(multiplier * CLift::_elevator3Floor)));
+ _startFrame = CLift::_elevator3Floor;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case 2:
+ case 4:
+ switch (petGetRoomsWellEntry()) {
+ case 1:
+ case 2:
+ setPosition(Point(_bounds.left, _indicatorPos.y +
+ (int)(multiplier * CLift::_elevator2Floor)));
+ _startFrame = CLift::_elevator2Floor;
+ break;
+
+ case 3:
+ case 4:
+ setPosition(Point(_bounds.left, _indicatorPos.y +
+ (int)(multiplier * CLift::_elevator4Floor)));
+ _startFrame = CLift::_elevator4Floor;
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CLiftindicator::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petClear();
+ return true;
+}
+
+bool CLiftindicator::PETActivateMsg(CPETActivateMsg *msg) {
+ double multiplier = _fieldFC * 0.037037037;
+ CPetControl *pet = getPetControl();
+
+ if (msg->_name == "Lift") {
+ if (petDoorOrBellbotPresent()) {
+ petDisplayMessage(1, "I'm sorry, you cannot enter this elevator at present "
+ "as a bot is in the way.");
+ } else {
+ _endFrame = pet->getRoomsFloorNum();
+
+ if (petGetRoomsWellEntry() == 4 && !CLift::_v6
+ && pet->getRoomsFloorNum() != CLift::_elevator4Floor) {
+ petDisplayMessage(1, "This elevator is currently in an advanced state of non-functionality.");
+ } else {
+ _start = _indicatorPos.y + (int)(_startFrame * multiplier);
+ _end = _indicatorPos.y + (int)(_endFrame * multiplier);
+ lockMouse();
+ addTimer(100);
+
+ if (petGetRoomsWellEntry() == 2) {
+ CLift::_elevator4Floor = CLift::_elevator2Floor;
+ CShipSettingMsg settingMsg;
+ settingMsg._value = CLift::_elevator4Floor;
+ settingMsg.execute("SGTStateroomTV");
+ }
+
+ switch (petGetRoomsWellEntry()) {
+ case 1:
+ CLift::_elevator1Floor = pet->getRoomsFloorNum();
+ break;
+ case 2:
+ CLift::_elevator2Floor = pet->getRoomsFloorNum();
+ break;
+ case 3:
+ CLift::_elevator3Floor = pet->getRoomsFloorNum();
+ break;
+ case 4:
+ CLift::_elevator4Floor = pet->getRoomsFloorNum();
+ break;
+ default:
+ break;
+ }
+
+ debugC(kDebugScripts, "Lifts = %d,%d,%d,%d %d",
+ CLift::_elevator1Floor, CLift::_elevator2Floor,
+ CLift::_elevator3Floor, CLift::_elevator4Floor,
+ petGetRoomsWellEntry());
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CLiftindicator::MovieEndMsg(CMovieEndMsg *msg) {
+ playSound("357 gp button 1.wav");
+ sleep(100);
+ changeView("Lift.Node 1.N");
+
+ unlockMouse();
+ return true;
+}
+
+bool CLiftindicator::EnterRoomMsg(CEnterRoomMsg *msg) {
+ return true;
+}
+
+bool CLiftindicator::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ return true;
+}
+
+bool CLiftindicator::TimerMsg(CTimerMsg *msg) {
+ debugC(kDebugScripts, "Start %d, End %d", _start, _end);
+
+ if (_start > _end) {
+ setPosition(Point(_bounds.left, _bounds.top - 1));
+ --_start;
+ addTimer(20);
+ } else if (_start < _end) {
+ setPosition(Point(_bounds.left, _bounds.top + 1));
+ ++_start;
+ addTimer(20);
+ } else {
+ CMovieEndMsg endMsg(0, 0);
+ endMsg.execute(this);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/transport/lift_indicator.h b/engines/titanic/game/transport/lift_indicator.h
index 945f627417..5d0bc45d7b 100644
--- a/engines/titanic/game/transport/lift_indicator.h
+++ b/engines/titanic/game/transport/lift_indicator.h
@@ -25,17 +25,24 @@
#include "titanic/game/transport/lift.h"
#include "titanic/messages/messages.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CLiftindicator : public CLift {
DECLARE_MESSAGE_MAP;
- bool EnterRoomMsg(CEnterRoomMsg *msg) { return true; }
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool PETActivateMsg(CPETActivateMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
private:
int _fieldFC;
- Point _pos2;
- int _field108;
- int _field10C;
+ Point _indicatorPos;
+ int _start;
+ int _end;
public:
CLASSDEF;
CLiftindicator();
diff --git a/engines/titanic/game/transport/pellerator.cpp b/engines/titanic/game/transport/pellerator.cpp
index e789c20a3d..5bc2423478 100644
--- a/engines/titanic/game/transport/pellerator.cpp
+++ b/engines/titanic/game/transport/pellerator.cpp
@@ -21,34 +21,344 @@
*/
#include "titanic/game/transport/pellerator.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+static const char *const WAVE_NAMES[10] = {
+ "z#465.wav", "z#456.wav", "z#455.wav", "z#453.wav",
+ "z#452.wav", "NoStandingInFunnyWays", "z#450.wav",
+ "z#449.wav", "z#435.wav", "z#434.wav"
+};
+
BEGIN_MESSAGE_MAP(CPellerator, CTransport)
+ ON_MESSAGE(StatusChangeMsg)
ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(TimerMsg)
END_MESSAGE_MAP()
-int CPellerator::_v1;
-int CPellerator::_v2;
+int CPellerator::_soundHandle;
+int CPellerator::_destination;
+
+CPellerator::CPellerator() : CTransport() {
+}
void CPellerator::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_v1, indent);
- file->writeNumberLine(_v2, indent);
+ file->writeNumberLine(_soundHandle, indent);
+ file->writeNumberLine(_destination, indent);
CTransport::save(file, indent);
}
void CPellerator::load(SimpleFile *file) {
file->readNumber();
- _v1 = file->readNumber();
- _v2 = file->readNumber();
+ _soundHandle = file->readNumber();
+ _destination = file->readNumber();
CTransport::load(file);
}
+bool CPellerator::StatusChangeMsg(CStatusChangeMsg *msg) {
+ setVisible(true);
+ playGlobalSound("z#74.wav", -2, true, true, 0);
+ int classNum = getPassengerClass();
+ int newDest = msg->_newStatus;
+
+ if (msg->_newStatus == _destination) {
+ petDisplayMessage(1, "You are already at your chosen destination.");
+ } else if (classNum == 3 || (msg->_newStatus > 4 && classNum != 1)) {
+ petDisplayMessage(1, "Passengers of your class are not permitted to enter this area.");
+ } else if (newDest > _destination) {
+ CString name = getName();
+ changeView(name == "PelleratorObject2" ?
+ "Pellerator.Node 1.N" : "Pellerator.Node 1.S");
+
+ if (name == "PelleratorObject") {
+ for (; _destination < newDest; ++_destination) {
+ switch (_destination) {
+ case 0:
+ case 1:
+ playMovie(315, 323, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(299, 304, 0);
+ playMovie(305, 313, MOVIE_GAMESTATE);
+ break;
+
+ case 2:
+ playMovie(315, 323, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(299, 304, 0);
+ for (int idx = 0; idx < 5; ++idx)
+ playMovie(253, 263, 0);
+ playMovie(153, 197, 0);
+ for (int idx = 0; idx < 5; ++idx)
+ playMovie(253, 263, 0);
+ playMovie(290, 293, MOVIE_GAMESTATE);
+ break;
+
+ case 4:
+ playMovie(267, 270, 0);
+ for (int idx = 0; idx < 5; ++idx)
+ playMovie(253, 263, 0);
+ playMovie(3, 71, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(253, 263, 0);
+ for (int idx = 0; idx < 7; ++idx)
+ playMovie(336, 341, 0);
+ playMovie(342, 348, MOVIE_GAMESTATE);
+ break;
+
+ case 5:
+ playMovie(315, 323, 0);
+ for (int idx = 0; idx < 7; ++idx)
+ playMovie(299, 304, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(253, 263, 0);
+ playMovie(3, 71, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(299, 304, 0);
+
+ }
+ }
+ } else {
+ for (; _destination < newDest; ++_destination) {
+ switch (_destination) {
+ case 0:
+ case 1:
+ playMovie(315, 323, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(299, 304, 0);
+ playMovie(305, 313, MOVIE_GAMESTATE);
+ break;
+
+ case 2:
+ playMovie(315, 323, 0);
+ for (int idx = 0; idx < 4; ++idx)
+ playMovie(299, 304, 0);
+ for (int idx = 0; idx < 15; ++idx)
+ playMovie(245, 255, 0);
+ playMovie(264, 267, MOVIE_GAMESTATE);
+ ++_destination;
+ break;
+
+ case 4:
+ playMovie(241, 244, 0);
+ for (int idx = 0; idx < 15; ++idx)
+ playMovie(245, 255, 0);
+ for (int idx = 0; idx < 7; ++idx)
+ playMovie(336, 341, 0);
+ playMovie(342, 348, MOVIE_GAMESTATE);
+ break;
+
+ case 5:
+ playMovie(315, 323, 0);
+ for (int idx = 0; idx < 7; ++idx)
+ playMovie(229, 304, 0);
+ for (int idx = 0; idx < 12; ++idx)
+ playMovie(245, 255, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(299, 304, 0);
+ playMovie(305, 313, MOVIE_GAMESTATE);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ playMovie(264, 264, MOVIE_NOTIFY_OBJECT);
+ _destination = newDest;
+ } else if (newDest < _destination) {
+ CString name = getName();
+ changeView(name == "PelleratorObject2" ?
+ "Pellerator.Node 1.N" : "Pellerator.Node 1.S");
+
+ if (name == "PelleratorObject") {
+ for (; _destination > newDest; --_destination) {
+ switch (_destination) {
+ case 0:
+ case 1:
+ playMovie(351, 359, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(336, 341, 0);
+ playMovie(342, 348, MOVIE_GAMESTATE);
+ break;
+
+ case 3:
+ playMovie(241, 244, 0);
+ for (int idx = 0; idx < 5; ++idx)
+ playMovie(245, 255, 0);
+ playMovie(197, 239, 0);
+ for (int idx = 0; idx < 5; ++idx)
+ playMovie(245, 255, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(336, 341, 0);
+ playMovie(342, 348, MOVIE_GAMESTATE);
+ --_destination;
+ break;
+
+ case 4:
+ playMovie(315, 323, 0);
+ for (int idx = 0; idx < 7; ++idx)
+ playMovie(299, 304, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(245, 255, 0);
+ playMovie(78, 149, 0);
+ for (int idx = 0; idx < 5; ++idx)
+ playMovie(245, 255, 0);
+ playMovie(264, 267, MOVIE_GAMESTATE);
+ break;
+
+ case 5:
+ playMovie(351, 359, 0);
+ for (int idx = 0; idx < 7; ++idx)
+ playMovie(336, 341, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(245, 255, 0);
+ playMovie(78, 149, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(336, 341, 0);
+ playMovie(342, 348, MOVIE_GAMESTATE);
+ break;
+
+ default:
+ break;
+ }
+ }
+ } else {
+ for (; _destination > newDest; --_destination) {
+ switch (_destination) {
+ case 0:
+ case 1:
+ playMovie(351, 359, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(336, 341, 0);
+ playMovie(342, 348, MOVIE_GAMESTATE);
+ break;
+
+ case 3:
+ playMovie(267, 270, 0);
+ for (int idx = 0; idx < 15; ++idx)
+ playMovie(253, 263, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(336, 341, 0);
+ playMovie(342, 348, MOVIE_GAMESTATE);
+ --_destination;
+ break;
+
+ case 4:
+ playMovie(315, 323, 0);
+ for (int idx = 0; idx < 7; ++idx)
+ playMovie(299, 304, 0);
+ for (int idx = 0; idx < 15; ++idx)
+ playMovie(253, 263, 0);
+ playMovie(290, 293, MOVIE_GAMESTATE);
+ break;
+
+ case 5:
+ playMovie(351, 359, 0);
+ for (int idx = 0; idx < 7; ++idx)
+ playMovie(336, 341, 0);
+ for (int idx = 0; idx < 13; ++idx)
+ playMovie(253, 263, 0);
+ for (int idx = 0; idx < 3; ++idx)
+ playMovie(336, 341, 0);
+ playMovie(342, 348, MOVIE_GAMESTATE);
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ playMovie(264, 264, MOVIE_NOTIFY_OBJECT);
+ _destination = newDest;
+ }
+
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = _destination;
+ statusMsg.execute("ExitPellerator");
+
+ return true;
+}
+
bool CPellerator::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CPellerator::handleEvent");
+ if (isEquals("PelleratorObject")) {
+ for (int idx = 0; idx < 10; ++idx)
+ loadSound(WAVE_NAMES[idx]);
+ addTimer(10000);
+ }
+
+ CString name = msg->_oldRoom ? msg->_oldRoom->getName() : "";
+ int oldVal = _destination;
+
+ if (name.empty()) {
+ _destination = 4;
+ oldVal = 4;
+ } else if (name == "PromenadeDeck") {
+ _destination = 0;
+ } else if (name == "MusicRoomLobby") {
+ _destination = 1;
+ } else if (name == "Bar") {
+ _destination = 2;
+ } else if (name == "TopOfWell") {
+ _destination = 4;
+ } else if (name == "1stClassRestaurant") {
+ _destination = 5;
+ } else if (name == "Arboretum" || name == "FrozenArboretum") {
+ _destination = 6;
+ }
+
+ if (_destination != oldVal) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = _destination;
+ statusMsg.execute("ExitPellerator");
+ }
+
+ loadFrame(264);
+ return true;
+}
+
+bool CPellerator::MovieEndMsg(CMovieEndMsg *msg) {
+ setVisible(false);
+ stopGlobalSound(true, -1);
+
+ switch (_destination) {
+ case 0:
+ _soundHandle = queueSound("z#429.wav", _soundHandle);
+ break;
+ case 1:
+ _soundHandle = queueSound("z#430.wav", _soundHandle);
+ break;
+ case 2:
+ _soundHandle = queueSound("z#431.wav", _soundHandle);
+ break;
+ case 4:
+ _soundHandle = queueSound("z#428.wav", _soundHandle);
+ break;
+ case 5:
+ _soundHandle = queueSound("z#433.wav", _soundHandle);
+ break;
+ case 6:
+ _soundHandle = queueSound("z#432.wav", _soundHandle);
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CPellerator::TimerMsg(CTimerMsg *msg) {
+ if (compareRoomNameTo("Pellerator")) {
+ _soundHandle = queueSound(WAVE_NAMES[getRandomNumber(9)], _soundHandle);
+ addTimer(20000 + getRandomNumber(10000));
+ }
+
return true;
}
diff --git a/engines/titanic/game/transport/pellerator.h b/engines/titanic/game/transport/pellerator.h
index fa400a49cd..c634f435cc 100644
--- a/engines/titanic/game/transport/pellerator.h
+++ b/engines/titanic/game/transport/pellerator.h
@@ -30,12 +30,16 @@ namespace Titanic {
class CPellerator : public CTransport {
DECLARE_MESSAGE_MAP;
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
-private:
- static int _v1;
- static int _v2;
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+public:
+ static int _soundHandle;
+ static int _destination;
public:
CLASSDEF;
+ CPellerator();
/**
* Save the data for the class to file
diff --git a/engines/titanic/game/transport/service_elevator.cpp b/engines/titanic/game/transport/service_elevator.cpp
index 1ea8d14e36..1980825cb6 100644
--- a/engines/titanic/game/transport/service_elevator.cpp
+++ b/engines/titanic/game/transport/service_elevator.cpp
@@ -21,15 +21,27 @@
*/
#include "titanic/game/transport/service_elevator.h"
+#include "titanic/core/room_item.h"
+#include "titanic/npcs/doorbot.h"
-namespace Titanic {
+namespace Titanic {
-int CServiceElevator::_v1;
+BEGIN_MESSAGE_MAP(CServiceElevator, CTransport)
+ ON_MESSAGE(BodyInBilgeRoomMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(ServiceElevatorMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(ServiceElevatorFloorRequestMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(OpeningCreditsMsg)
+END_MESSAGE_MAP()
+
+bool CServiceElevator::_v1;
int CServiceElevator::_v2;
int CServiceElevator::_v3;
CServiceElevator::CServiceElevator() : CTransport(),
- _fieldF8(0), _fieldFC(0), _field100(0), _field104(0) {
+ _fieldF8(0), _soundHandle1(0), _timerId(0), _soundHandle2(0) {
}
void CServiceElevator::save(SimpleFile *file, int indent) {
@@ -38,9 +50,9 @@ void CServiceElevator::save(SimpleFile *file, int indent) {
file->writeNumberLine(_v2, indent);
file->writeNumberLine(_v3, indent);
file->writeNumberLine(_fieldF8, indent);
- file->writeNumberLine(_fieldFC, indent);
- file->writeNumberLine(_field100, indent);
- file->writeNumberLine(_field104, indent);
+ file->writeNumberLine(_soundHandle1, indent);
+ file->writeNumberLine(_timerId, indent);
+ file->writeNumberLine(_soundHandle2, indent);
CTransport::save(file, indent);
}
@@ -51,11 +63,208 @@ void CServiceElevator::load(SimpleFile *file) {
_v2 = file->readNumber();
_v3 = file->readNumber();
_fieldF8 = file->readNumber();
- _fieldFC = file->readNumber();
- _field100 = file->readNumber();
- _field104 = file->readNumber();
+ _soundHandle1 = file->readNumber();
+ _timerId = file->readNumber();
+ _soundHandle2 = file->readNumber();
CTransport::load(file);
}
+bool CServiceElevator::BodyInBilgeRoomMsg(CBodyInBilgeRoomMsg *msg) {
+ _v2 = true;
+ _string1 = "BilgeRoomWith.Node 2.N";
+ return true;
+}
+
+bool CServiceElevator::EnterViewMsg(CEnterViewMsg *msg) {
+ petShow();
+ return true;
+}
+
+bool CServiceElevator::ServiceElevatorMsg(CServiceElevatorMsg *msg) {
+ switch (msg->_value) {
+ case 1:
+ case 2:
+ case 3: {
+ switch (msg->_value) {
+ case 1:
+ _v3 = 0;
+ break;
+ case 2:
+ _v3 = 1;
+ break;
+ case 3:
+ _v3 = 2;
+ break;
+ }
+
+ CServiceElevatorFloorRequestMsg requestMsg;
+ requestMsg.execute(this);
+ break;
+ }
+
+ case 4:
+ if (!_string1.empty()) {
+ if (_string1 == "DeepSpace") {
+ disableMouse();
+ _soundHandle1 = playSound("z#413.wav", 50);
+ _timerId = addTimer(1, 1000, 500);
+ } else {
+ changeView(_string1);
+ }
+ }
+ break;
+
+ case 5:
+ _fieldF8 = false;
+ _fieldDC = _v3;
+ loadSound("z#423.wav");
+ stopSound(_soundHandle2);
+ _soundHandle2 = playSound("z#423.wav", 80);
+
+ switch (_fieldDC) {
+ case 0:
+ _string1 = "DeepSpace";
+ _string2 = "a#2.wav";
+ queueSound("z#416.wav", _soundHandle2, 50);
+ break;
+
+ case 1:
+ _string1 = _v2 ? "BilgeRoomWith.Node 2.N" : "BilgeRoom.Node 1.N";
+ queueSound("z#421.wav", _soundHandle2, 50);
+ break;
+
+ case 2:
+ _string1 = _v1 ? "MoonEmbLobby.Node 1.NE" : "EmbLobby.Node 1.NE";
+ queueSound("z#411.wav", _soundHandle2, 50);
+ break;
+
+ default:
+ break;
+ }
+
+ enableMouse();
+ if (findRoom()->findByName("Doorbot"))
+ addTimer(3, 3000, 0);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CServiceElevator::TimerMsg(CTimerMsg *msg) {
+ CDoorbot *doorbot = dynamic_cast<CDoorbot *>(findRoom()->findByName("Doorbot"));
+
+ switch (msg->_actionVal) {
+ case 0:
+ case 1:
+ if (!isSoundActive(_soundHandle1)) {
+ stopAnimTimer(_timerId);
+ if (msg->_actionVal == 0) {
+ _fieldF8 = true;
+ CServiceElevatorFloorChangeMsg changeMsg(_fieldDC, _v3);
+ changeMsg.execute(getRoom());
+ _soundHandle2 = playSound("z#424.wav");
+
+ if (doorbot) {
+ CActMsg actMsg("DoorbotPlayerPressedTopButton");
+ actMsg.execute(doorbot);
+ }
+ } else {
+ enableMouse();
+ if (doorbot) {
+ CActMsg actMsg;
+ if (_v3 == 0)
+ actMsg._action = "DoorbotPlayerPressedBottomButton";
+ else if (_v3 == 1)
+ actMsg._action = "DoorbotPlayerPressedMiddleButton";
+
+ actMsg.execute(doorbot);
+ }
+ }
+ }
+ break;
+
+ case 3: {
+ CActMsg actMsg("DoorbotReachedEmbLobby");
+ actMsg.execute(doorbot);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CServiceElevator::ServiceElevatorFloorRequestMsg(CServiceElevatorFloorRequestMsg *msg) {
+ disableMouse();
+ CDoorbot *doorbot = dynamic_cast<CDoorbot *>(findRoom()->findByName("Doorbot"));
+
+ if (doorbot && _v3 == 0) {
+ _soundHandle1 = playSound("z#415.wav", 50);
+ addTimer(1, 1000, 500);
+ } else if (doorbot && _v3 == 1) {
+ _soundHandle1 = playSound("z#417.wav", 50);
+ addTimer(1, 1000, 500);
+ } else if (_fieldDC == _v3) {
+ switch (_v3) {
+ case 0:
+ _soundHandle1 = playSound("z#415.wav", 50);
+ break;
+ case 1:
+ _soundHandle1 = playSound("z#420.wav", 50);
+ break;
+ case 2:
+ _soundHandle1 = playSound("z#410.wav", 50);
+ break;
+ default:
+ break;
+ }
+
+ addTimer(1, 1000, 500);
+ } else {
+ switch (_v3) {
+ case 0:
+ _soundHandle1 = playSound("z#414.wav", 50);
+ break;
+ case 1:
+ _soundHandle1 = playSound(_fieldDC ? "z#419.wav" : "z#418.wav", 50);
+ break;
+ case 2:
+ _soundHandle1 = playSound("z#414.wav", 50);
+ break;
+ default:
+ break;
+ }
+
+ addTimer(0, 1000, 500);
+ }
+
+ return true;
+}
+
+bool CServiceElevator::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ CDoorbot *doorbot = dynamic_cast<CDoorbot *>(findRoom()->findByName("Doorbot"));
+
+ if (doorbot) {
+ CPutBotBackInHisBoxMsg boxMsg(0);
+ boxMsg.execute("Doorbot");
+ doorbot->performAction(false);
+ enableMouse();
+ }
+
+ return true;
+}
+
+bool CServiceElevator::OpeningCreditsMsg(COpeningCreditsMsg *msg) {
+ _v1 = false;
+ _string1 = "EmbLobby.Node 1.NE";
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/transport/service_elevator.h b/engines/titanic/game/transport/service_elevator.h
index b2c135021a..5cf1f6f0d5 100644
--- a/engines/titanic/game/transport/service_elevator.h
+++ b/engines/titanic/game/transport/service_elevator.h
@@ -28,15 +28,23 @@
namespace Titanic {
class CServiceElevator : public CTransport {
+ DECLARE_MESSAGE_MAP;
+ bool BodyInBilgeRoomMsg(CBodyInBilgeRoomMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool ServiceElevatorMsg(CServiceElevatorMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool ServiceElevatorFloorRequestMsg(CServiceElevatorFloorRequestMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool OpeningCreditsMsg(COpeningCreditsMsg *msg);
private:
- static int _v1;
+ static bool _v1;
static int _v2;
static int _v3;
int _fieldF8;
- int _fieldFC;
- int _field100;
- int _field104;
+ int _soundHandle1;
+ int _timerId;
+ int _soundHandle2;
public:
CLASSDEF;
CServiceElevator();
diff --git a/engines/titanic/game/up_lighter.cpp b/engines/titanic/game/up_lighter.cpp
index f03b8f37a0..d133a7e9df 100644
--- a/engines/titanic/game/up_lighter.cpp
+++ b/engines/titanic/game/up_lighter.cpp
@@ -21,9 +21,21 @@
*/
#include "titanic/game/up_lighter.h"
+#include "titanic/core/project_item.h"
+#include "titanic/npcs/parrot.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CUpLighter, CDropTarget)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(PumpingMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(ChangeSeasonMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+END_MESSAGE_MAP()
+
CUpLighter::CUpLighter() : CDropTarget(), _field118(0),
_field11C(0), _field120(0), _field124(0) {
}
@@ -48,8 +60,61 @@ void CUpLighter::load(SimpleFile *file) {
CDropTarget::load(file);
}
+bool CUpLighter::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_field118) {
+ playSound("z#47.wav");
+ _field124 = true;
+
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute("NoseHolder");
+ CDropZoneLostObjectMsg lostMsg(nullptr);
+ lostMsg.execute(this);
+ _clipName.clear();
+ _itemMatchName = "Nothing";
+ _field118 = 0;
+ }
+
+ return true;
+}
+
+bool CUpLighter::PumpingMsg(CPumpingMsg *msg) {
+ _field118 = msg->_value;
+ _clipName = (_field118 && !_field124) ? "WholeSequence" : "HoseToNose";
+ return true;
+}
+
+bool CUpLighter::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CTrueTalkTriggerActionMsg triggerMsg(280245, 0, 0);
+ triggerMsg.execute(getRoot(), CParrot::_type,
+ MSGFLAG_BREAK_IF_HANDLED | MSGFLAG_CLASS_DEF | MSGFLAG_SCAN);
+ return true;
+}
+
bool CUpLighter::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CUpLighter::handleEvent");
+ _field11C = true;
+ addTimer(5000 + getRandomNumber(15000), 0);
+ return true;
+}
+
+bool CUpLighter::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ _field120 = msg->_season == "Spring";
+ if (_field120)
+ addTimer(5000 + getRandomNumber(15000), 0);
+ return true;
+}
+
+bool CUpLighter::TimerMsg(CTimerMsg *msg) {
+ if (_field120 && _field11C & !_field118) {
+ CActMsg actMsg("Sneeze");
+ actMsg.execute(findRoom()->findByName("NoseHolder"));
+ addTimer(1000 + getRandomNumber(19000), 0);
+ }
+
+ return true;
+}
+
+bool CUpLighter::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ _field11C = false;
return true;
}
diff --git a/engines/titanic/game/up_lighter.h b/engines/titanic/game/up_lighter.h
index 2367e41314..e6a53cf7bd 100644
--- a/engines/titanic/game/up_lighter.h
+++ b/engines/titanic/game/up_lighter.h
@@ -29,7 +29,14 @@
namespace Titanic {
class CUpLighter : public CDropTarget {
+ DECLARE_MESSAGE_MAP;
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool PumpingMsg(CPumpingMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
private:
int _field118;
int _field11C;
diff --git a/engines/titanic/game/useless_lever.cpp b/engines/titanic/game/useless_lever.cpp
index e48ad55a71..82d8983710 100644
--- a/engines/titanic/game/useless_lever.cpp
+++ b/engines/titanic/game/useless_lever.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CUselessLever, CToggleButton)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CUselessLever::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CToggleButton::save(file, indent);
@@ -34,4 +39,23 @@ void CUselessLever::load(SimpleFile *file) {
CToggleButton::load(file);
}
+bool CUselessLever::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_fieldE0) {
+ playMovie(15, 30, 0);
+ playSound("z#56.wav");
+ _fieldE0 = false;
+ } else {
+ playMovie(0, 14, 0);
+ playSound("z#56.wav");
+ _fieldE0 = true;
+ }
+
+ return true;
+}
+
+bool CUselessLever::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(_fieldE0 ? 15 : 0);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/useless_lever.h b/engines/titanic/game/useless_lever.h
index 27397de216..33ed94b2ac 100644
--- a/engines/titanic/game/useless_lever.h
+++ b/engines/titanic/game/useless_lever.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CUselessLever : public CToggleButton {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/game/wheel_button.cpp b/engines/titanic/game/wheel_button.cpp
index 19c42a8807..730a5d9005 100644
--- a/engines/titanic/game/wheel_button.cpp
+++ b/engines/titanic/game/wheel_button.cpp
@@ -24,14 +24,20 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CWheelButton, CBackground)
+ ON_MESSAGE(SignalObject)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(LeaveViewMsg)
+END_MESSAGE_MAP()
+
CWheelButton::CWheelButton() : CBackground(),
- _fieldE0(0), _fieldE4(0), _fieldE8(0) {
+ _fieldE0(false), _timerId(0), _fieldE8(0) {
}
void CWheelButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
+ file->writeNumberLine(_timerId, indent);
file->writeNumberLine(_fieldE8, indent);
CBackground::save(file, indent);
@@ -40,10 +46,43 @@ void CWheelButton::save(SimpleFile *file, int indent) {
void CWheelButton::load(SimpleFile *file) {
file->readNumber();
_fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
+ _timerId = file->readNumber();
_fieldE8 = file->readNumber();
CBackground::load(file);
}
+bool CWheelButton::SignalObject(CSignalObject *msg) {
+ bool oldFlag = _fieldE0;
+ _fieldE0 = msg->_numValue != 0;
+
+ if (oldFlag != _fieldE0) {
+ if (_fieldE0) {
+ _timerId = addTimer(500, 500);
+ } else {
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+ setVisible(false);
+ }
+ }
+
+ return true;
+}
+
+bool CWheelButton::TimerMsg(CTimerMsg *msg) {
+ setVisible(!_visible);
+ makeDirty();
+ return true;
+}
+
+bool CWheelButton::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (_timerId) {
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+ setVisible(false);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/wheel_button.h b/engines/titanic/game/wheel_button.h
index cb089a660f..2725e60622 100644
--- a/engines/titanic/game/wheel_button.h
+++ b/engines/titanic/game/wheel_button.h
@@ -28,9 +28,13 @@
namespace Titanic {
class CWheelButton : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool SignalObject(CSignalObject *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
public:
- int _fieldE0;
- int _fieldE4;
+ bool _fieldE0;
+ int _timerId;
int _fieldE8;
public:
CLASSDEF;
diff --git a/engines/titanic/game/wheel_hotspot.cpp b/engines/titanic/game/wheel_hotspot.cpp
index f9af594cd5..544e6f5470 100644
--- a/engines/titanic/game/wheel_hotspot.cpp
+++ b/engines/titanic/game/wheel_hotspot.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CWheelHotSpot, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(SignalObject)
+END_MESSAGE_MAP()
+
void CWheelHotSpot::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldE0, indent);
@@ -40,4 +45,39 @@ void CWheelHotSpot::load(SimpleFile *file) {
CBackground::load(file);
}
+bool CWheelHotSpot::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_fieldE0) {
+ CActMsg actMsg;
+
+ switch (_fieldE4) {
+ case 1:
+ actMsg._action = "Stop";
+ actMsg.execute("CaptainsWheel");
+ break;
+
+ case 2:
+ actMsg._action = "Cruise";
+ actMsg.execute("CaptainsWheel");
+ break;
+
+ case 3:
+ actMsg._action = "Go";
+ actMsg.execute("CaptainsWheel");
+ break;
+
+ default:
+ break;
+ }
+ } else if (_fieldE4 == 3) {
+ petDisplayMessage("Go where?");
+ }
+
+ return true;
+}
+
+bool CWheelHotSpot::SignalObject(CSignalObject *msg) {
+ _fieldE0 = msg->_numValue != 0;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/wheel_hotspot.h b/engines/titanic/game/wheel_hotspot.h
index 364fe9a060..e9071a2fa4 100644
--- a/engines/titanic/game/wheel_hotspot.h
+++ b/engines/titanic/game/wheel_hotspot.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CWheelHotSpot : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool SignalObject(CSignalObject *msg);
public:
int _fieldE0;
int _fieldE4;
diff --git a/engines/titanic/game/wheel_spin.cpp b/engines/titanic/game/wheel_spin.cpp
index daa9918e29..79f83873e1 100644
--- a/engines/titanic/game/wheel_spin.cpp
+++ b/engines/titanic/game/wheel_spin.cpp
@@ -24,16 +24,36 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CWheelSpin, CBackground)
+ ON_MESSAGE(SignalObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
void CWheelSpin::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_active, indent);
CBackground::save(file, indent);
}
void CWheelSpin::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _active = file->readNumber();
CBackground::load(file);
}
+bool CWheelSpin::SignalObject(CSignalObject *msg) {
+ _active = msg->_numValue != 0;
+ setVisible(_active);
+ return true;
+}
+
+bool CWheelSpin::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_active) {
+ CActMsg actMsg("Spin");
+ actMsg.execute("CaptainsWheel");
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/game/wheel_spin.h b/engines/titanic/game/wheel_spin.h
index 509db1a4bf..f7993f0188 100644
--- a/engines/titanic/game/wheel_spin.h
+++ b/engines/titanic/game/wheel_spin.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CWheelSpin : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool SignalObject(CSignalObject *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
- int _value;
+ bool _active;
public:
CLASSDEF;
- CWheelSpin() : CBackground(), _value(0) {}
+ CWheelSpin() : CBackground(), _active(false) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/game_manager.cpp b/engines/titanic/game_manager.cpp
index 9bc9e4d067..7d9dc37a10 100644
--- a/engines/titanic/game_manager.cpp
+++ b/engines/titanic/game_manager.cpp
@@ -153,7 +153,7 @@ void CGameManager::playClip(CMovieClip *clip, CRoomItem *oldRoom, CRoomItem *new
lockInputHandler();
CScreenManager::_screenManagerPtr->_mouseCursor->hide();
- _movie->playClip(tempRect, clip->_startFrame, clip->_endFrame);
+ _movie->playCutscene(tempRect, clip->_startFrame, clip->_endFrame);
CScreenManager::_screenManagerPtr->_mouseCursor->show();
unlockInputHandler();
}
@@ -164,7 +164,7 @@ void CGameManager::update() {
frameMessage(getRoom());
_timers.update(g_vm->_events->getTicksCount());
_trueTalkManager.removeCompleted();
- _trueTalkManager.update2();
+
CScreenManager::_screenManagerPtr->_mouseCursor->update();
CViewItem *view = getView();
diff --git a/engines/titanic/game_state.cpp b/engines/titanic/game_state.cpp
index 5628161558..8814dba831 100644
--- a/engines/titanic/game_state.cpp
+++ b/engines/titanic/game_state.cpp
@@ -47,7 +47,7 @@ bool CGameStateMovieList::clear() {
CGameState::CGameState(CGameManager *gameManager) :
_gameManager(gameManager), _gameLocation(this),
_passengerClass(0), _priorClass(0), _mode(GSMODE_NONE),
- _field14(0), _petActive(false), _field1C(false), _quitGame(false),
+ _seasonNum(SEASON_SUMMER), _petActive(false), _field1C(false), _quitGame(false),
_field24(0), _nodeChangeCtr(0), _nodeEnterTicks(0), _field38(0) {
}
@@ -55,7 +55,7 @@ void CGameState::save(SimpleFile *file) const {
file->writeNumber(_petActive);
file->writeNumber(_passengerClass);
file->writeNumber(_priorClass);
- file->writeNumber(_field14);
+ file->writeNumber(_seasonNum);
file->writeNumber(_field24);
file->writeNumber(_field38);
_gameLocation.save(file);
@@ -66,7 +66,7 @@ void CGameState::load(SimpleFile *file) {
_petActive = file->readNumber() != 0;
_passengerClass = file->readNumber();
_priorClass = file->readNumber();
- _field14 = file->readNumber();
+ _seasonNum = (Season)file->readNumber();
_field24 = file->readNumber();
_field38 = file->readNumber();
_gameLocation.load(file);
diff --git a/engines/titanic/game_state.h b/engines/titanic/game_state.h
index 0bfa0d5b31..70d47b55c1 100644
--- a/engines/titanic/game_state.h
+++ b/engines/titanic/game_state.h
@@ -35,7 +35,14 @@ class CGameManager;
enum GameStateMode {
GSMODE_NONE = 0, GSMODE_INTERACTIVE = 1, GSMODE_CUTSCENE = 2,
- GSMODE_3 = 3, GSMODE_4 = 4, GSMODE_5 = 5, GSMODE_PENDING_LOAD = 6
+ GSMODE_3 = 3, GSMODE_4 = 4, GSMODE_INSERT_CD = 5, GSMODE_PENDING_LOAD = 6
+};
+
+enum Season {
+ SEASON_SUMMER = 0,
+ SEASON_AUTUMN = 1,
+ SEASON_WINTER = 2,
+ SEASON_SPRING = 3
};
PTR_LIST_ITEM(CMovie);
@@ -60,7 +67,7 @@ public:
int _passengerClass;
int _priorClass;
GameStateMode _mode;
- int _field14;
+ Season _seasonNum;
bool _petActive;
bool _field1C;
bool _quitGame;
@@ -127,7 +134,13 @@ public:
*/
void addMovie(CMovie *movie);
- void inc14() { _field14 = (_field14 + 1) & 3; }
+ /**
+ * Change to the next season
+ */
+ void changeSeason() {
+ _seasonNum = (Season)(((int)_seasonNum + 1) & 3);
+ }
+
void set24(int v) { _field24 = v; }
int get24() const { return _field24; }
int getNodeChangedCtr() const { return _nodeChangeCtr; }
diff --git a/engines/titanic/gfx/act_button.cpp b/engines/titanic/gfx/act_button.cpp
index c84f358ca9..75c999b10f 100644
--- a/engines/titanic/gfx/act_button.cpp
+++ b/engines/titanic/gfx/act_button.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CActButton, CSTButton)
+ ON_MESSAGE(MouseButtonUpMsg)
+END_MESSAGE_MAP()
+
CActButton::CActButton() : CSTButton() {
}
@@ -37,4 +41,10 @@ void CActButton::load(SimpleFile *file) {
CSTButton::load(file);
}
+bool CActButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ CActMsg actMsg(_actionName);
+ actMsg.execute(_actionTarget);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/act_button.h b/engines/titanic/gfx/act_button.h
index 26e5595411..910ace1d13 100644
--- a/engines/titanic/gfx/act_button.h
+++ b/engines/titanic/gfx/act_button.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CActButton : public CSTButton {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
public:
CLASSDEF;
CActButton();
diff --git a/engines/titanic/gfx/changes_season_button.cpp b/engines/titanic/gfx/changes_season_button.cpp
index d5242ad890..584a9542f3 100644
--- a/engines/titanic/gfx/changes_season_button.cpp
+++ b/engines/titanic/gfx/changes_season_button.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/gfx/changes_season_button.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CChangesSeasonButton, CSTButton)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
CChangesSeasonButton::CChangesSeasonButton() : CSTButton() {
}
@@ -37,4 +42,10 @@ void CChangesSeasonButton::load(SimpleFile *file) {
CSTButton::load(file);
}
+bool CChangesSeasonButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CChangeSeasonMsg changeMsg(_actionName);
+ changeMsg.execute(getRoot(), nullptr, MSGFLAG_SCAN);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/changes_season_button.h b/engines/titanic/gfx/changes_season_button.h
index 2b58a3199b..4f588187eb 100644
--- a/engines/titanic/gfx/changes_season_button.h
+++ b/engines/titanic/gfx/changes_season_button.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CChangesSeasonButton : public CSTButton {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
CChangesSeasonButton();
diff --git a/engines/titanic/gfx/edit_control.cpp b/engines/titanic/gfx/edit_control.cpp
index 3b611e9bbe..3f3c4d4035 100644
--- a/engines/titanic/gfx/edit_control.cpp
+++ b/engines/titanic/gfx/edit_control.cpp
@@ -24,26 +24,27 @@
namespace Titanic {
-CEditControl::CEditControl() : CGameObject(), _fieldBC(0), _fieldC0(0),
- _fieldC4(0), _fieldC8(0), _fieldCC(0), _fieldD0(0), _fieldD4(2),
- _fieldD8(0), _fieldDC(0), _fieldE0(0), _fieldF0(0), _fieldF4(0)
+BEGIN_MESSAGE_MAP(CEditControl, CGameObject)
+ ON_MESSAGE(EditControlMsg)
+END_MESSAGE_MAP()
-{
+CEditControl::CEditControl() : CGameObject(), _fieldBC(false), _fontNumber(0), _fieldD4(2),
+ _textR(0), _textG(0), _textB(0), _fieldF0(0), _fieldF4(0) {
}
void CEditControl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
- file->writeNumberLine(_fieldC8, indent);
- file->writeNumberLine(_fieldCC, indent);
- file->writeNumberLine(_fieldD0, indent);
+ file->writeNumberLine(_editLeft, indent);
+ file->writeNumberLine(_editBottom, indent);
+ file->writeNumberLine(_editHeight, indent);
+ file->writeNumberLine(_maxTextChars, indent);
+ file->writeNumberLine(_fontNumber, indent);
file->writeNumberLine(_fieldD4, indent);
- file->writeNumberLine(_fieldD8, indent);
- file->writeNumberLine(_fieldDC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeNumberLine(_textR, indent);
+ file->writeNumberLine(_textG, indent);
+ file->writeNumberLine(_textB, indent);
+ file->writeQuotedLine(_text, indent);
file->writeNumberLine(_fieldF0, indent);
file->writeNumberLine(_fieldF4, indent);
@@ -53,20 +54,169 @@ void CEditControl::save(SimpleFile *file, int indent) {
void CEditControl::load(SimpleFile *file) {
file->readNumber();
_fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
- _fieldC8 = file->readNumber();
- _fieldCC = file->readNumber();
- _fieldD0 = file->readNumber();
+ _editLeft = file->readNumber();
+ _editBottom = file->readNumber();
+ _editHeight = file->readNumber();
+ _maxTextChars = file->readNumber();
+ _fontNumber = file->readNumber();
_fieldD4 = file->readNumber();
- _fieldD8 = file->readNumber();
- _fieldDC = file->readNumber();
- _fieldE0 = file->readNumber();
- _string1 = file->readString();
+ _textR = file->readNumber();
+ _textG = file->readNumber();
+ _textB = file->readNumber();
+ _text = file->readString();
_fieldF0 = file->readNumber();
_fieldF4 = file->readNumber();
CGameObject::load(file);
}
+bool CEditControl::EditControlMsg(CEditControlMsg *msg) {
+ switch (msg->_mode) {
+ case 0:
+ if (!_editLeft) {
+ _editHeight = _bounds.height();
+ _editBottom = _bounds.bottom;
+ _editLeft = _bounds.left + _bounds.width() / 2;
+ _maxTextChars = msg->_param;
+ setTextFontNumber(_fontNumber);
+
+ CEditControlMsg ctlMsg;
+ ctlMsg._mode = 10;
+ ctlMsg._param = _fieldD4;
+ ctlMsg.execute(this);
+
+ ctlMsg._mode = 11;
+ ctlMsg._textR = _textR;
+ ctlMsg._textG = _textG;
+ ctlMsg._textB = _textB;
+ ctlMsg.execute(this);
+ }
+ break;
+
+ case 1: {
+ _text = "";
+ CEditControlMsg ctlMsg;
+ ctlMsg._mode = 14;
+ ctlMsg.execute(this);
+ break;
+ }
+
+ case 2: {
+ _text = msg->_text;
+ CEditControlMsg ctlMsg;
+ ctlMsg._mode = 14;
+ ctlMsg.execute(this);
+ break;
+ }
+
+ case 3:
+ msg->_text = _text;
+ break;
+
+ case 4:
+ msg->_param = _text.size();
+ break;
+
+ case 5:
+ _maxTextChars = msg->_param;
+ break;
+
+ case 6:
+ if (msg->_param == 8 && !_text.empty()) {
+ _text = _text.left(_text.size() - 1);
+ CEditControlMsg ctlMsg;
+ ctlMsg._mode = 14;
+ ctlMsg.execute(this);
+ } else if (msg->_param == 13) {
+ msg->_param = 1000;
+ } else if (msg->_param >= 32 && msg->_param < 127
+ && _text.size() < _maxTextChars) {
+ char c = (char)msg->_param;
+ _text += c;
+
+ CEditControlMsg ctlMsg;
+ ctlMsg._mode = 14;
+ ctlMsg.execute(this);
+ }
+ break;
+
+ case 7:
+ setTextFontNumber(msg->_param);
+ break;
+
+ case 8:
+ if (!_fieldBC) {
+ _fieldBC = true;
+ CEditControlMsg ctlMsg;
+ ctlMsg._mode = 14;
+ ctlMsg.execute(this);
+ }
+ break;
+
+ case 9:
+ if (_fieldBC) {
+ _fieldBC = false;
+ getTextCursor()->hide();
+ }
+ break;
+
+ case 10: {
+ setTextHasBorders((msg->_param & 1) != 0);
+ if (msg->_param & 4)
+ _fieldF0 = 1;
+ else if (msg->_param & 8)
+ _fieldF0 = 2;
+ else
+ _fieldF0 = 0;
+
+ _fieldF4 = msg->_param & 0x10;
+ CEditControlMsg ctlMsg;
+ ctlMsg._mode = 14;
+ ctlMsg.execute(this);
+ break;
+ }
+
+ case 11:
+ setTextColor(msg->_textR, msg->_textG, msg->_textB);
+ break;
+
+ case 12:
+ setVisible(true);
+ break;
+
+ case 13:
+ setVisible(false);
+ break;
+
+ case 14: {
+ makeDirty();
+ CString str = _fieldF4 ? CString('*', _text.size()) : _text;
+ setText(str);
+
+ int textWidth = getTextWidth();
+ if (_fieldF0 == 2) {
+ _bounds.left = _editLeft - textWidth / 2;
+ _bounds.setWidth(textWidth + 16);
+ setTextBounds();
+ makeDirty();
+ }
+
+ if (_fieldBC) {
+ CTextCursor *textCursor = getTextCursor();
+ textCursor->show();
+ textCursor->setPos(Point(_bounds.left + textWidth + 1, _bounds.top + 3));
+ textCursor->setSize(Point(2, _editHeight - 6));
+ textCursor->setColor(0xff, 0xff, 0xff);
+ textCursor->clearBounds();
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/edit_control.h b/engines/titanic/gfx/edit_control.h
index 77d03cb225..6c02f7afb9 100644
--- a/engines/titanic/gfx/edit_control.h
+++ b/engines/titanic/gfx/edit_control.h
@@ -28,18 +28,20 @@
namespace Titanic {
class CEditControl : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EditControlMsg(CEditControlMsg *msg);
protected:
- int _fieldBC;
- int _fieldC0;
- int _fieldC4;
- int _fieldC8;
- int _fieldCC;
- int _fieldD0;
+ bool _fieldBC;
+ int _editLeft;
+ int _editBottom;
+ int _editHeight;
+ uint _maxTextChars;
+ int _fontNumber;
int _fieldD4;
- int _fieldD8;
- int _fieldDC;
- int _fieldE0;
- CString _string1;
+ byte _textR;
+ byte _textG;
+ byte _textB;
+ CString _text;
int _fieldF0;
int _fieldF4;
public:
diff --git a/engines/titanic/gfx/move_object_button.cpp b/engines/titanic/gfx/move_object_button.cpp
index bdc90a673c..bcd2b2bd76 100644
--- a/engines/titanic/gfx/move_object_button.cpp
+++ b/engines/titanic/gfx/move_object_button.cpp
@@ -21,9 +21,14 @@
*/
#include "titanic/gfx/move_object_button.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMoveObjectButton, CSTButton)
+ ON_MESSAGE(MouseButtonUpMsg)
+END_MESSAGE_MAP()
+
CMoveObjectButton::CMoveObjectButton() : CSTButton(), _field11C(1) {
}
@@ -43,4 +48,14 @@ void CMoveObjectButton::load(SimpleFile *file) {
CSTButton::load(file);
}
+bool CMoveObjectButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ CGameObject *obj = dynamic_cast<CGameObject *>(getRoot()->findByName(_actionTarget));
+ if (obj) {
+ obj->petAddToInventory();
+ obj->setVisible(_field11C);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/move_object_button.h b/engines/titanic/gfx/move_object_button.h
index eb2fdc4ff2..46c49c36e2 100644
--- a/engines/titanic/gfx/move_object_button.h
+++ b/engines/titanic/gfx/move_object_button.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CMoveObjectButton : public CSTButton {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
private:
Point _pos1;
int _field11C;
diff --git a/engines/titanic/gfx/music_control.cpp b/engines/titanic/gfx/music_control.cpp
index 85a3d777ef..317bec209f 100644
--- a/engines/titanic/gfx/music_control.cpp
+++ b/engines/titanic/gfx/music_control.cpp
@@ -24,15 +24,20 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMusicControl, CBackground)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseDoubleClickMsg)
+END_MESSAGE_MAP()
+
CMusicControl::CMusicControl() : CBackground(),
- _fieldE0(0), _fieldE4(0), _fieldE8(1), _fieldEC(1) {
+ _controlArea(BELLS), _controlVal(0), _controlMax(1), _fieldEC(1) {
}
void CMusicControl::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
+ file->writeNumberLine(_controlArea, indent);
+ file->writeNumberLine(_controlVal, indent);
+ file->writeNumberLine(_controlMax, indent);
file->writeNumberLine(_fieldEC, indent);
CBackground::save(file, indent);
@@ -40,12 +45,24 @@ void CMusicControl::save(SimpleFile *file, int indent) {
void CMusicControl::load(SimpleFile *file) {
file->readNumber();
- _fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
+ _controlArea = (MusicControlArea)file->readNumber();
+ _controlVal = file->readNumber();
+ _controlMax = file->readNumber();
_fieldEC = file->readNumber();
CBackground::load(file);
}
+bool CMusicControl::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CMusicSettingChangedMsg changedMsg;
+ changedMsg.execute(this);
+ return true;
+}
+
+bool CMusicControl::MouseDoubleClickMsg(CMouseDoubleClickMsg *msg) {
+ CMusicSettingChangedMsg changedMsg;
+ changedMsg.execute(this);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_control.h b/engines/titanic/gfx/music_control.h
index 04085f789c..a0e73392f9 100644
--- a/engines/titanic/gfx/music_control.h
+++ b/engines/titanic/gfx/music_control.h
@@ -24,14 +24,18 @@
#define TITANIC_MUSIC_CONTROL_H
#include "titanic/core/background.h"
+#include "titanic/sound/music_room.h"
namespace Titanic {
class CMusicControl : public CBackground {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseDoubleClickMsg(CMouseDoubleClickMsg *msg);
public:
- int _fieldE0;
- int _fieldE4;
- int _fieldE8;
+ MusicControlArea _controlArea;
+ int _controlVal;
+ int _controlMax;
int _fieldEC;
public:
CLASSDEF;
diff --git a/engines/titanic/game/bilge_succubus.cpp b/engines/titanic/gfx/music_slider_pitch.cpp
index ceee3f7740..5f0432e742 100644
--- a/engines/titanic/game/bilge_succubus.cpp
+++ b/engines/titanic/gfx/music_slider_pitch.cpp
@@ -20,32 +20,48 @@
*
*/
-#include "titanic/game/bilge_succubus.h"
+#include "titanic/gfx/music_slider_pitch.h"
namespace Titanic {
-CBilgeSuccUBus::CBilgeSuccUBus() : CSuccUBus(), _field1DC(0),
- _field1E0(0), _field1E4(0), _field1E8(0) {
-}
+BEGIN_MESSAGE_MAP(CMusicSliderPitch, CMusicSlider)
+ ON_MESSAGE(MusicSettingChangedMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(QueryMusicControlSettingMsg)
+END_MESSAGE_MAP()
-void CBilgeSuccUBus::save(SimpleFile *file, int indent) {
+void CMusicSliderPitch::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_field1DC, indent);
- file->writeNumberLine(_field1E0, indent);
- file->writeNumberLine(_field1E4, indent);
- file->writeNumberLine(_field1E8, indent);
-
- CSuccUBus::save(file, indent);
+ CMusicSlider::save(file, indent);
}
-void CBilgeSuccUBus::load(SimpleFile *file) {
+void CMusicSliderPitch::load(SimpleFile *file) {
file->readNumber();
- _field1DC = file->readNumber();
- _field1E0 = file->readNumber();
- _field1E4 = file->readNumber();
- _field1E8 = file->readNumber();
+ CMusicSlider::load(file);
+}
+
+bool CMusicSliderPitch::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) {
+ if (_fieldEC) {
+ if (++_controlVal > _controlMax)
+ _controlVal = 0;
+
+ loadFrame(3 - _controlVal);
+ playSound("z#54.wav", 50);
+ } else {
+ playSound("z#46.wav");
+ }
+
+ return true;
+}
+
+bool CMusicSliderPitch::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(3 - _controlVal);
+ return true;
+}
- CSuccUBus::load(file);
+bool CMusicSliderPitch::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) {
+ msg->_value = _controlVal - 2;
+ return true;
}
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_slider_pitch.h b/engines/titanic/gfx/music_slider_pitch.h
index 10c1d62c3a..c375c6db33 100644
--- a/engines/titanic/gfx/music_slider_pitch.h
+++ b/engines/titanic/gfx/music_slider_pitch.h
@@ -28,24 +28,22 @@
namespace Titanic {
class CMusicSliderPitch : public CMusicSlider {
+ DECLARE_MESSAGE_MAP;
+ bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg);
public:
CLASSDEF;
/**
* Save the data for the class to file
*/
- virtual void save(SimpleFile *file, int indent) {
- file->writeNumberLine(1, indent);
- CMusicSlider::save(file, indent);
- }
+ virtual void save(SimpleFile *file, int indent);
/**
* Load the data for the class from file
*/
- virtual void load(SimpleFile *file) {
- file->readNumber();
- CMusicSlider::load(file);
- }
+ virtual void load(SimpleFile *file);
};
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_slider_speed.cpp b/engines/titanic/gfx/music_slider_speed.cpp
new file mode 100644
index 0000000000..93af5d82b7
--- /dev/null
+++ b/engines/titanic/gfx/music_slider_speed.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 "titanic/gfx/music_slider_speed.h"
+
+namespace Titanic {
+
+BEGIN_MESSAGE_MAP(CMusicSliderSpeed, CMusicSlider)
+ ON_MESSAGE(MusicSettingChangedMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(QueryMusicControlSettingMsg)
+END_MESSAGE_MAP()
+
+void CMusicSliderSpeed::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(1, indent);
+ CMusicSlider::save(file, indent);
+}
+
+void CMusicSliderSpeed::load(SimpleFile *file) {
+ file->readNumber();
+ CMusicSlider::load(file);
+}
+
+bool CMusicSliderSpeed::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) {
+ if (_fieldEC) {
+ if (++_controlVal > _controlMax)
+ _controlVal = 0;
+
+ loadFrame(3 - _controlVal);
+ playSound("z#54.wav", 50);
+ } else {
+ playSound("z#46.wav");
+ }
+
+ return true;
+}
+
+bool CMusicSliderSpeed::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(3 - _controlVal);
+ return true;
+}
+
+bool CMusicSliderSpeed::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) {
+ msg->_value = _controlVal - 1;
+ return true;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_slider_speed.h b/engines/titanic/gfx/music_slider_speed.h
index 9814ca0312..2d54f4487c 100644
--- a/engines/titanic/gfx/music_slider_speed.h
+++ b/engines/titanic/gfx/music_slider_speed.h
@@ -27,26 +27,24 @@
namespace Titanic {
- class CMusicSliderSpeed : public CMusicSlider {
- public:
- CLASSDEF;
-
- /**
- * Save the data for the class to file
- */
- virtual void save(SimpleFile *file, int indent) {
- file->writeNumberLine(1, indent);
- CMusicSlider::save(file, indent);
- }
-
- /**
- * Load the data for the class from file
- */
- virtual void load(SimpleFile *file) {
- file->readNumber();
- CMusicSlider::load(file);
- }
- };
+class CMusicSliderSpeed : public CMusicSlider {
+ DECLARE_MESSAGE_MAP;
+ bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg);
+public:
+ CLASSDEF;
+
+ /**
+ * Save the data for the class to file
+ */
+ virtual void save(SimpleFile *file, int indent);
+
+ /**
+ * Load the data for the class from file
+ */
+ virtual void load(SimpleFile *file);
+};
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_switch_inversion.cpp b/engines/titanic/gfx/music_switch_inversion.cpp
new file mode 100644
index 0000000000..d11df79ab4
--- /dev/null
+++ b/engines/titanic/gfx/music_switch_inversion.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 "titanic/gfx/music_switch_inversion.h"
+
+namespace Titanic {
+
+BEGIN_MESSAGE_MAP(CMusicSwitchInversion, CMusicSwitch)
+ ON_MESSAGE(MusicSettingChangedMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(QueryMusicControlSettingMsg)
+END_MESSAGE_MAP()
+
+void CMusicSwitchInversion::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(1, indent);
+ CMusicSwitch::save(file, indent);
+}
+
+void CMusicSwitchInversion::load(SimpleFile *file) {
+ file->readNumber();
+ CMusicSwitch::load(file);
+}
+
+bool CMusicSwitchInversion::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) {
+ if (_fieldEC) {
+ if (++_controlVal > _controlMax)
+ _controlVal = 0;
+
+ loadFrame(_controlVal);
+ playSound("z#59.wav", 50);
+ } else {
+ playSound("z#46.wav");
+ }
+
+ return true;
+}
+
+bool CMusicSwitchInversion::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(_controlVal);
+ return true;
+}
+
+bool CMusicSwitchInversion::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) {
+ msg->_value = _controlVal;
+ return true;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_switch_inversion.h b/engines/titanic/gfx/music_switch_inversion.h
index 8b3718cf14..869b4745ea 100644
--- a/engines/titanic/gfx/music_switch_inversion.h
+++ b/engines/titanic/gfx/music_switch_inversion.h
@@ -28,24 +28,22 @@
namespace Titanic {
class CMusicSwitchInversion : public CMusicSwitch {
+ DECLARE_MESSAGE_MAP;
+ bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg);
public:
CLASSDEF;
/**
* Save the data for the class to file
*/
- virtual void save(SimpleFile *file, int indent) {
- file->writeNumberLine(1, indent);
- CMusicSwitch::save(file, indent);
- }
+ virtual void save(SimpleFile *file, int indent);
/**
* Load the data for the class from file
*/
- virtual void load(SimpleFile *file) {
- file->readNumber();
- CMusicSwitch::load(file);
- }
+ virtual void load(SimpleFile *file);
};
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_switch_reverse.cpp b/engines/titanic/gfx/music_switch_reverse.cpp
new file mode 100644
index 0000000000..9fe6d51d47
--- /dev/null
+++ b/engines/titanic/gfx/music_switch_reverse.cpp
@@ -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.
+ *
+ */
+
+#include "titanic/gfx/music_switch_reverse.h"
+
+namespace Titanic {
+
+BEGIN_MESSAGE_MAP(CMusicSwitchReverse, CMusicSwitch)
+ ON_MESSAGE(MusicSettingChangedMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(QueryMusicControlSettingMsg)
+END_MESSAGE_MAP()
+
+void CMusicSwitchReverse::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(1, indent);
+ CMusicSwitch::save(file, indent);
+}
+
+void CMusicSwitchReverse::load(SimpleFile *file) {
+ file->readNumber();
+ CMusicSwitch::load(file);
+}
+bool CMusicSwitchReverse::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) {
+ if (_fieldEC) {
+ if (++_controlVal > _controlMax)
+ _controlVal = 0;
+
+ loadFrame(_controlVal);
+ playSound("z#59.wav", 50);
+ } else {
+ playSound("z#46.wav");
+ }
+
+ return true;
+}
+
+bool CMusicSwitchReverse::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(_controlVal);
+ return true;
+}
+
+bool CMusicSwitchReverse::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) {
+ msg->_value = _controlVal;
+ return true;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_switch_reverse.h b/engines/titanic/gfx/music_switch_reverse.h
index 3bfcb53b00..c101f19d25 100644
--- a/engines/titanic/gfx/music_switch_reverse.h
+++ b/engines/titanic/gfx/music_switch_reverse.h
@@ -27,26 +27,24 @@
namespace Titanic {
- class CMusicSwitchReverse : public CMusicSwitch {
- public:
- CLASSDEF;
-
- /**
- * Save the data for the class to file
- */
- virtual void save(SimpleFile *file, int indent) {
- file->writeNumberLine(1, indent);
- CMusicSwitch::save(file, indent);
- }
-
- /**
- * Load the data for the class from file
- */
- virtual void load(SimpleFile *file) {
- file->readNumber();
- CMusicSwitch::load(file);
- }
- };
+class CMusicSwitchReverse : public CMusicSwitch {
+ DECLARE_MESSAGE_MAP;
+ bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg);
+public:
+ CLASSDEF;
+
+ /**
+ * Save the data for the class to file
+ */
+ virtual void save(SimpleFile *file, int indent);
+
+ /**
+ * Load the data for the class from file
+ */
+ virtual void load(SimpleFile *file);
+};
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_voice_mute.cpp b/engines/titanic/gfx/music_voice_mute.cpp
new file mode 100644
index 0000000000..034cb4f6a6
--- /dev/null
+++ b/engines/titanic/gfx/music_voice_mute.cpp
@@ -0,0 +1,59 @@
+/* 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 "titanic/gfx/music_voice_mute.h"
+#include "titanic/sound/music_room.h"
+
+namespace Titanic {
+
+BEGIN_MESSAGE_MAP(CMusicVoiceMute, CMusicControl)
+ ON_MESSAGE(MusicSettingChangedMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(QueryMusicControlSettingMsg)
+END_MESSAGE_MAP()
+
+bool CMusicVoiceMute::MusicSettingChangedMsg(CMusicSettingChangedMsg *msg) {
+ if (++_controlVal > _controlMax)
+ _controlVal = 0;
+
+ CMusicRoom *musicRoom = getMusicRoom();
+ musicRoom->setMuteControl(_controlArea, _controlVal == 1 ? 1 : 0);
+ loadFrame(1 - _controlVal);
+ playSound("z#55.wav", 50);
+
+ return true;
+}
+
+bool CMusicVoiceMute::EnterViewMsg(CEnterViewMsg *msg) {
+ loadFrame(1 - _controlVal);
+ CMusicRoom *musicRoom = getMusicRoom();
+ musicRoom->setMuteControl(_controlArea, _controlVal == 1 ? 1 : 0);
+
+ return true;
+}
+
+bool CMusicVoiceMute::QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg) {
+ msg->_value = _controlVal;
+ return true;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/gfx/music_voice_mute.h b/engines/titanic/gfx/music_voice_mute.h
index ca15806c09..f64b107423 100644
--- a/engines/titanic/gfx/music_voice_mute.h
+++ b/engines/titanic/gfx/music_voice_mute.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CMusicVoiceMute : public CMusicControl {
+ DECLARE_MESSAGE_MAP;
+ bool MusicSettingChangedMsg(CMusicSettingChangedMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool QueryMusicControlSettingMsg(CQueryMusicControlSettingMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/gfx/slider_button.cpp b/engines/titanic/gfx/slider_button.cpp
index 0633158e97..b3dbbed08f 100644
--- a/engines/titanic/gfx/slider_button.cpp
+++ b/engines/titanic/gfx/slider_button.cpp
@@ -24,6 +24,14 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSliderButton, CSTButton)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MouseDragMoveMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
CSliderButton::CSliderButton() : CSTButton(), _field114(0),
_field118(0), _field11C(0) {
}
@@ -48,4 +56,39 @@ void CSliderButton::load(SimpleFile *file) {
CSTButton::load(file);
}
+bool CSliderButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ _pos1 = msg->_mousePos;
+ CStatusChangeMsg changeMsg;
+ changeMsg.execute(this);
+ return true;
+}
+
+bool CSliderButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ _pos1 = msg->_mousePos;
+ return true;
+}
+
+bool CSliderButton::MouseDragMoveMsg(CMouseDragMoveMsg *msg) {
+ _pos1 = msg->_mousePos;
+ if (_field118) {
+ CStatusChangeMsg changeMsg;
+ changeMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CSliderButton::StatusChangeMsg(CStatusChangeMsg *msg) {
+ CStatusChangeMsg changeMsg;
+ changeMsg._oldStatus = _currentStatus;
+ _currentStatus = (_pos1.y - _bounds.top) / _field11C;
+ changeMsg._newStatus = _currentStatus;
+ changeMsg.execute(_actionTarget);
+ return true;
+}
+
+bool CSliderButton::EnterViewMsg(CEnterViewMsg *msg) {
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/slider_button.h b/engines/titanic/gfx/slider_button.h
index 398290bb05..18ebbae3db 100644
--- a/engines/titanic/gfx/slider_button.h
+++ b/engines/titanic/gfx/slider_button.h
@@ -28,6 +28,12 @@
namespace Titanic {
class CSliderButton : public CSTButton {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MouseDragMoveMsg(CMouseDragMoveMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
int _field114;
int _field118;
diff --git a/engines/titanic/gfx/st_button.cpp b/engines/titanic/gfx/st_button.cpp
index 4b93d46595..6fc31f4c64 100644
--- a/engines/titanic/gfx/st_button.cpp
+++ b/engines/titanic/gfx/st_button.cpp
@@ -32,10 +32,10 @@ END_MESSAGE_MAP()
CSTButton::CSTButton() : CBackground() {
_statusInc = 0;
- _statusTarget = "NULL";
+ _actionTarget = "NULL";
_fieldF0 = 0;
_currentStatus = 0;
- _string4 = "NULL";
+ _actionName = "NULL";
_soundName = "NULL";
_buttonFrame = 0;
}
@@ -43,10 +43,10 @@ CSTButton::CSTButton() : CBackground() {
void CSTButton::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_statusInc, indent);
- file->writeQuotedLine(_statusTarget, indent);
+ file->writeQuotedLine(_actionTarget, indent);
file->writeNumberLine(_fieldF0, indent);
file->writeNumberLine(_currentStatus, indent);
- file->writeQuotedLine(_string4, indent);
+ file->writeQuotedLine(_actionName, indent);
file->writeQuotedLine(_soundName, indent);
file->writeNumberLine(_buttonFrame, indent);
@@ -56,10 +56,10 @@ void CSTButton::save(SimpleFile *file, int indent) {
void CSTButton::load(SimpleFile *file) {
file->readNumber();
_statusInc = file->readNumber();
- _statusTarget = file->readString();
+ _actionTarget = file->readString();
_fieldF0 = file->readNumber();
_currentStatus = file->readNumber();
- _string4 = file->readString();
+ _actionName = file->readString();
_soundName = file->readString();
_buttonFrame = file->readNumber() != 0;
@@ -79,7 +79,7 @@ bool CSTButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
CStatusChangeMsg statusMsg(oldStatus, newStatus, false);
_currentStatus = newStatus;
- statusMsg.execute(_statusTarget);
+ statusMsg.execute(_actionTarget);
if (!statusMsg._success) {
_currentStatus -= _statusInc;
diff --git a/engines/titanic/gfx/st_button.h b/engines/titanic/gfx/st_button.h
index 789437691b..444c883f59 100644
--- a/engines/titanic/gfx/st_button.h
+++ b/engines/titanic/gfx/st_button.h
@@ -34,12 +34,12 @@ class CSTButton : public CBackground {
bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
bool EnterViewMsg(CEnterViewMsg *msg);
-private:
+protected:
int _statusInc;
- CString _statusTarget;
+ CString _actionTarget;
int _fieldF0;
int _currentStatus;
- CString _string4;
+ CString _actionName;
CString _soundName;
int _buttonFrame;
public:
diff --git a/engines/titanic/gfx/status_change_button.cpp b/engines/titanic/gfx/status_change_button.cpp
index 6644247ff2..e38f1ee07e 100644
--- a/engines/titanic/gfx/status_change_button.cpp
+++ b/engines/titanic/gfx/status_change_button.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CStatusChangeButton, CSTButton)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
CStatusChangeButton::CStatusChangeButton() : CSTButton() {
}
@@ -37,4 +41,11 @@ void CStatusChangeButton::load(SimpleFile *file) {
CSTButton::load(file);
}
+bool CStatusChangeButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CStatusChangeMsg changeMsg;
+ changeMsg._newStatus = _statusInc;
+ changeMsg.execute(_actionTarget);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/status_change_button.h b/engines/titanic/gfx/status_change_button.h
index 9e410c66f2..9d187493a7 100644
--- a/engines/titanic/gfx/status_change_button.h
+++ b/engines/titanic/gfx/status_change_button.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CStatusChangeButton : public CSTButton {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
CStatusChangeButton();
diff --git a/engines/titanic/gfx/toggle_button.h b/engines/titanic/gfx/toggle_button.h
index 5328072982..4fb7cdfaba 100644
--- a/engines/titanic/gfx/toggle_button.h
+++ b/engines/titanic/gfx/toggle_button.h
@@ -29,7 +29,7 @@ namespace Titanic {
class CToggleButton : public CBackground {
DECLARE_MESSAGE_MAP;
-private:
+protected:
int _fieldE0;
public:
CLASSDEF;
diff --git a/engines/titanic/gfx/toggle_switch.cpp b/engines/titanic/gfx/toggle_switch.cpp
index 20cbb863ee..815f96cb5a 100644
--- a/engines/titanic/gfx/toggle_switch.cpp
+++ b/engines/titanic/gfx/toggle_switch.cpp
@@ -24,12 +24,18 @@
namespace Titanic {
-CToggleSwitch::CToggleSwitch() : CGameObject(), _fieldBC(0) {
+BEGIN_MESSAGE_MAP(CToggleSwitch, CGameObject)
+ ON_MESSAGE(MouseButtonUpMsg)
+ ON_MESSAGE(ChildDragStartMsg)
+ ON_MESSAGE(ChildDragMoveMsg)
+END_MESSAGE_MAP()
+
+CToggleSwitch::CToggleSwitch() : CGameObject(), _pressed(false) {
}
void CToggleSwitch::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
+ file->writeNumberLine(_pressed, indent);
file->writePoint(_pos1, indent);
CGameObject::save(file, indent);
@@ -37,10 +43,30 @@ void CToggleSwitch::save(SimpleFile *file, int indent) {
void CToggleSwitch::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
+ _pressed = file->readNumber();
_pos1 = file->readPoint();
CGameObject::load(file);
}
+bool CToggleSwitch::MouseButtonUpMsg(CMouseButtonUpMsg *msg) {
+ _pressed = !_pressed;
+ if (_pressed)
+ fn10(0, 0, 0);
+ else
+ fn10(0xff, 0xff, 0xff);
+ return true;
+}
+
+bool CToggleSwitch::ChildDragStartMsg(CChildDragStartMsg *msg) {
+ _pos1.x = msg->_mousePos.x - _bounds.left;
+ _pos1.y = msg->_mousePos.y - _bounds.top;
+ return true;
+}
+
+bool CToggleSwitch::ChildDragMoveMsg(CChildDragMoveMsg *msg) {
+ setPosition(Point(msg->_mousePos.x - _pos1.x, msg->_mousePos.y - _pos1.y));
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/gfx/toggle_switch.h b/engines/titanic/gfx/toggle_switch.h
index ae96c75ebd..69b8c50eec 100644
--- a/engines/titanic/gfx/toggle_switch.h
+++ b/engines/titanic/gfx/toggle_switch.h
@@ -28,8 +28,12 @@
namespace Titanic {
class CToggleSwitch : public CGameObject {
-private:
- int _fieldBC;
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonUpMsg(CMouseButtonUpMsg *msg);
+ bool ChildDragStartMsg(CChildDragStartMsg *msg);
+ bool ChildDragMoveMsg(CChildDragMoveMsg *msg);
+protected:
+ bool _pressed;
Point _pos1;
public:
CLASSDEF;
diff --git a/engines/titanic/input_handler.cpp b/engines/titanic/input_handler.cpp
index 7c35a5d855..2c51f3ece7 100644
--- a/engines/titanic/input_handler.cpp
+++ b/engines/titanic/input_handler.cpp
@@ -150,7 +150,7 @@ CGameObject *CInputHandler::dragEnd(const Point &pt, CTreeItem *dragItem) {
// Scan through the view items to find the item being dropped on
CGameObject *target = nullptr;
for (CTreeItem *treeItem = view->scan(view); treeItem; treeItem = treeItem->scan(view)) {
- CGameObject *gameObject = static_cast<CGameObject *>(treeItem);
+ CGameObject *gameObject = dynamic_cast<CGameObject *>(treeItem);
if (gameObject && gameObject != dragItem) {
if (gameObject->checkPoint(pt))
target = gameObject;
diff --git a/engines/titanic/main_game_window.cpp b/engines/titanic/main_game_window.cpp
index 486bc417d7..690acdc25f 100644
--- a/engines/titanic/main_game_window.cpp
+++ b/engines/titanic/main_game_window.cpp
@@ -49,19 +49,24 @@ CMainGameWindow::CMainGameWindow(TitanicEngine *vm): _vm(vm),
CMainGameWindow::~CMainGameWindow() {
}
-bool CMainGameWindow::Create() {
- Image image;
- image.load("TITANIC");
-
- // TODO: Stuff
- return true;
-}
-
void CMainGameWindow::applicationStarting() {
// Set the video mode
CScreenManager *screenManager = CScreenManager::setCurrent();
screenManager->setMode(640, 480, 16, 0, true);
+#if 0
+ // Show the initial copyright & info screen for the game
+ if (gDebugLevel <= 0) {
+ Image image;
+ image.load("Bitmap/TITANIC");
+ _vm->_screen->blitFrom(image, Point(
+ SCREEN_WIDTH / 2 - image.w / 2,
+ SCREEN_HEIGHT / 2 - image.h / 2
+ ));
+ _vm->_events->sleep(5000);
+ }
+#endif
+
// Set up the game project, and get game slot
int saveSlot = getSavegameSlot();
if (saveSlot == -2)
@@ -77,8 +82,6 @@ void CMainGameWindow::applicationStarting() {
_inputAllowed = true;
_gameManager->_gameState.setMode(GSMODE_INTERACTIVE);
- // TODO: Cursor/image
-
// Generate starting messages for entering the view, node, and room.
// Note the old fields are nullptr, since there's no previous view/node/room
CViewItem *view = _gameManager->_gameState._gameLocation.getView();
@@ -157,8 +160,9 @@ void CMainGameWindow::draw() {
scrManager->drawCursors();
break;
- case GSMODE_5:
- g_vm->_filesManager->debug(scrManager);
+ case GSMODE_INSERT_CD:
+ scrManager->drawCursors();
+ _vm->_filesManager->insertCD(scrManager);
break;
case GSMODE_PENDING_LOAD:
@@ -216,7 +220,7 @@ void CMainGameWindow::drawViewContents(CScreenManager *screenManager) {
}
void CMainGameWindow::mouseChanged() {
- if (_gameManager->_gameState._mode != GSMODE_5)
+ if (_gameManager->_gameState._mode != GSMODE_INSERT_CD)
_gameManager->update();
}
diff --git a/engines/titanic/main_game_window.h b/engines/titanic/main_game_window.h
index 82e24e250e..530d5796f4 100644
--- a/engines/titanic/main_game_window.h
+++ b/engines/titanic/main_game_window.h
@@ -104,11 +104,6 @@ public:
virtual void keyUp(Common::KeyState keyState);
/**
- * Creates the window
- */
- bool Create();
-
- /**
* Called when the application starts
*/
void applicationStarting();
@@ -136,7 +131,9 @@ public:
/*
* Return whether a given special key is currently pressed
*/
- bool isSpecialPressed(SpecialButtons btn) const { return _specialButtons; }
+ bool isSpecialPressed(SpecialButtons btn) const {
+ return (_specialButtons & btn) != 0;
+ }
/**
* Returns the bitset of the currently pressed special buttons
diff --git a/engines/titanic/messages/auto_sound_event.cpp b/engines/titanic/messages/auto_sound_event.cpp
index baa11c7d41..bc2cd7d074 100644
--- a/engines/titanic/messages/auto_sound_event.cpp
+++ b/engines/titanic/messages/auto_sound_event.cpp
@@ -24,7 +24,11 @@
namespace Titanic {
-CAutoSoundEvent::CAutoSoundEvent() : CGameObject(), _value1(0), _value2(70) {
+BEGIN_MESSAGE_MAP(CAutoSoundEvent, CGameObject)
+ ON_MESSAGE(FrameMsg)
+END_MESSAGE_MAP()
+
+CAutoSoundEvent::CAutoSoundEvent() : CGameObject(), _value1(0), _value2(0xFFFFFF) {
}
void CAutoSoundEvent::save(SimpleFile *file, int indent) {
@@ -43,4 +47,11 @@ void CAutoSoundEvent::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CAutoSoundEvent::FrameMsg(CFrameMsg *msg) {
+ if (_value1 >= 0)
+ _value1 = (_value1 + 1) & _value2;
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/messages/auto_sound_event.h b/engines/titanic/messages/auto_sound_event.h
index eb1c11c4ff..d88976708e 100644
--- a/engines/titanic/messages/auto_sound_event.h
+++ b/engines/titanic/messages/auto_sound_event.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CAutoSoundEvent : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool FrameMsg(CFrameMsg *msg);
public:
int _value1;
int _value2;
diff --git a/engines/titanic/messages/bilge_dispensor_event.cpp b/engines/titanic/messages/bilge_dispensor_event.cpp
index 043ffe75d3..584da00a6f 100644
--- a/engines/titanic/messages/bilge_dispensor_event.cpp
+++ b/engines/titanic/messages/bilge_dispensor_event.cpp
@@ -24,6 +24,13 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBilgeDispensorEvent, CAutoSoundEvent)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(StatusChangeMsg)
+END_MESSAGE_MAP()
+
void CBilgeDispensorEvent::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
CAutoSoundEvent::save(file, indent);
@@ -39,4 +46,32 @@ bool CBilgeDispensorEvent::EnterRoomMsg(CEnterRoomMsg *msg) {
return true;
}
+bool CBilgeDispensorEvent::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ _value1 = -1;
+ return true;
+}
+
+bool CBilgeDispensorEvent::FrameMsg(CFrameMsg *msg) {
+ if (_value1 >= 0 && (_value1 & 0xffff) == 0x4000) {
+ int volume = 20 + getRandomNumber(30);
+ int val3 = getRandomNumber(20) - 10;
+
+ if (getRandomNumber(2) == 0) {
+ playSound("b#18.wav", volume, val3);
+ }
+ }
+
+ CAutoSoundEvent::FrameMsg(msg);
+ return true;
+}
+
+bool CBilgeDispensorEvent::StatusChangeMsg(CStatusChangeMsg *msg) {
+ if (msg->_newStatus == 1)
+ _value1 = -1;
+ else if (msg->_newStatus == 2)
+ _value1 = 0;
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/messages/bilge_dispensor_event.h b/engines/titanic/messages/bilge_dispensor_event.h
index 96ef92a54e..61d3116db4 100644
--- a/engines/titanic/messages/bilge_dispensor_event.h
+++ b/engines/titanic/messages/bilge_dispensor_event.h
@@ -29,9 +29,14 @@
namespace Titanic {
class CBilgeDispensorEvent : public CAutoSoundEvent {
+ DECLARE_MESSAGE_MAP;
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
public:
CLASSDEF;
+ CBilgeDispensorEvent() : CAutoSoundEvent() {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/messages/door_auto_sound_event.cpp b/engines/titanic/messages/door_auto_sound_event.cpp
index b9cedae6de..7618577e50 100644
--- a/engines/titanic/messages/door_auto_sound_event.cpp
+++ b/engines/titanic/messages/door_auto_sound_event.cpp
@@ -24,6 +24,12 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CDoorAutoSoundEvent, CAutoSoundEvent)
+ ON_MESSAGE(PreEnterNodeMsg)
+ ON_MESSAGE(LeaveNodeMsg)
+ ON_MESSAGE(TimerMsg)
+END_MESSAGE_MAP()
+
void CDoorAutoSoundEvent::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeQuotedLine(_string1, indent);
@@ -44,4 +50,16 @@ void CDoorAutoSoundEvent::load(SimpleFile *file) {
CAutoSoundEvent::load(file);
}
+bool CDoorAutoSoundEvent::PreEnterNodeMsg(CPreEnterNodeMsg *msg) {
+ return true;
+}
+
+bool CDoorAutoSoundEvent::LeaveNodeMsg(CLeaveNodeMsg *msg) {
+ return true;
+}
+
+bool CDoorAutoSoundEvent::TimerMsg(CTimerMsg *msg) {
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/messages/door_auto_sound_event.h b/engines/titanic/messages/door_auto_sound_event.h
index e6ea1b0f98..8b064a7221 100644
--- a/engines/titanic/messages/door_auto_sound_event.h
+++ b/engines/titanic/messages/door_auto_sound_event.h
@@ -28,6 +28,10 @@
namespace Titanic {
class CDoorAutoSoundEvent : public CAutoSoundEvent {
+ DECLARE_MESSAGE_MAP;
+ bool PreEnterNodeMsg(CPreEnterNodeMsg *msg);
+ bool LeaveNodeMsg(CLeaveNodeMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
public:
CString _string1;
CString _string2;
diff --git a/engines/titanic/messages/messages.h b/engines/titanic/messages/messages.h
index c1d962f656..b70bc5e16c 100644
--- a/engines/titanic/messages/messages.h
+++ b/engines/titanic/messages/messages.h
@@ -149,38 +149,21 @@ public:
class CEditControlMsg : public CMessage {
public:
- int _field4;
- int _field8;
- CString _string1;
- int _field18;
- int _field1C;
- int _field20;
+ int _mode;
+ int _param;
+ CString _text;
+ byte _textR;
+ byte _textG;
+ byte _textB;
public:
CLASSDEF;
- CEditControlMsg() : _field4(0), _field8(0), _field18(0),
- _field1C(0), _field20(0) {}
+ CEditControlMsg() : _mode(0), _param(0), _textR(0), _textG(0), _textB(0) {}
static bool isSupportedBy(const CTreeItem *item) {
return CMessage::supports(item, _type);
}
};
-class CLightsMsg : public CMessage {
-public:
- int _field4;
- int _field8;
- int _fieldC;
- int _field10;
-public:
- CLASSDEF;
- CLightsMsg() : CMessage(), _field4(0), _field8(0),
- _fieldC(0), _field10(0) {}
-
- static bool isSupportedBy(const CTreeItem *item) {
- return supports(item, _type);
- }
-};
-
MESSAGE1(CTimeMsg, uint, _ticks, 0);
class CTimerMsg : public CTimeMsg {
@@ -206,15 +189,13 @@ MESSAGE1(CAnimateMaitreDMsg, int, value, 0);
MESSAGE1(CArboretumGateMsg, int, value, 0);
MESSAGE0(CArmPickedUpFromTableMsg);
MESSAGE0(CBodyInBilgeRoomMsg);
-MESSAGE1(CBowlStateChange, int, value, 0);
+MESSAGE1(CBowlStateChangeMsg, int, state, 0);
MESSAGE2(CCarryObjectArrivedMsg, CString, strValue, "", int, numValue, 0);
MESSAGE2(CChangeMusicMsg, CString, filename, "", int, flags, 0);
MESSAGE1(CChangeSeasonMsg, CString, season, "Summer");
MESSAGE0(CCheckAllPossibleCodes);
-MESSAGE2(CCheckChevCode, int, value1, 0, int, value2, 0);
+MESSAGE2(CCheckChevCode, int, classNum, 0, uint, chevCode, 0);
MESSAGE1(CChildDragEndMsg, int, value, 0);
-MESSAGE2(CChildDragMoveMsg, int, value1, 0, int, value2, 0);
-MESSAGE2(CChildDragStartMsg, int, value1, 0, int, value2, 0);
MESSAGE0(CClearChevPanelBits);
MESSAGE0(CCorrectMusicPlayedMsg);
MESSAGE0(CCreateMusicPlayerMsg);
@@ -228,8 +209,8 @@ MESSAGE0(CDonNavHelmet);
MESSAGE1(CDoorbotNeededInElevatorMsg, int, value, 0);
MESSAGE0(CDoorbotNeededInHomeMsg);
MESSAGE1(CDropObjectMsg, CCarry *, item, nullptr);
-MESSAGE1(CDropZoneGotObjectMsg, int, value, 0);
-MESSAGE1(CDropZoneLostObjectMsg, int, value, 0);
+MESSAGE1(CDropZoneGotObjectMsg, CGameObject *, object, nullptr);
+MESSAGE1(CDropZoneLostObjectMsg, CGameObject *, object, nullptr);
MESSAGE1(CEjectCylinderMsg, int, value, 0);
MESSAGE2(CPreEnterNodeMsg, CNodeItem *, oldNode, nullptr, CNodeItem *, newNode, nullptr);
MESSAGE2(CPreEnterRoomMsg, CRoomItem *, oldRoom, nullptr, CRoomItem *, newRoom, nullptr);
@@ -239,17 +220,17 @@ MESSAGE2(CEnterRoomMsg, CRoomItem *, oldRoom, nullptr, CRoomItem *, newRoom, nul
MESSAGE2(CEnterViewMsg, CViewItem *, oldView, nullptr, CViewItem *, newView, nullptr);
MESSAGE0(CErasePhonographCylinderMsg);
MESSAGE1(CFrameMsg, uint, ticks, 0);
-MESSAGE2(CFreshenCookieMsg, int, value1, 0, int, value2, 0);
-MESSAGE1(CGetChevClassBits, int, value, 0);
-MESSAGE1(CGetChevClassNum, int, value, 0);
-MESSAGE2(CGetChevCodeFromRoomNameMsg, CString, strValue, "", int, numValue, 0);
-MESSAGE1(CGetChevFloorBits, int, value, 0);
-MESSAGE1(CGetChevFloorNum, int, value, 0);
-MESSAGE1(CGetChevLiftBits, int, value, 0);
-MESSAGE1(CGetChevLiftNum, int, value, 0);
-MESSAGE1(CGetChevRoomBits, int, value, 0);
-MESSAGE1(CGetChevRoomNum, int, value, 0);
-MESSAGE2(CHoseConnectedMsg, int, value1, 1, int, value2, 0);
+MESSAGE2(CFreshenCookieMsg, int, value1, 0, int, value2, 1);
+MESSAGE1(CGetChevClassBits, int, classBits, 0);
+MESSAGE1(CGetChevClassNum, int, classNum, 0);
+MESSAGE2(CGetChevCodeFromRoomNameMsg, CString, roomName, "", uint, chevCode, 0);
+MESSAGE1(CGetChevFloorBits, int, floorBits, 0);
+MESSAGE1(CGetChevFloorNum, int, floorNum, 0);
+MESSAGE1(CGetChevLiftBits, int, liftBits, 0);
+MESSAGE1(CGetChevLiftNum, int, liftNum, 0);
+MESSAGE1(CGetChevRoomBits, int, roomNum, 0);
+MESSAGE1(CGetChevRoomNum, int, roomNum, 0);
+MESSAGE2(CHoseConnectedMsg, int, value, 1, CGameObject *, object, nullptr);
MESSAGE0(CInitializeAnimMsg);
MESSAGE1(CIsEarBowlPuzzleDone, int, value, 0);
MESSAGE3(CIsHookedOnMsg, Rect, rect, Rect(), bool, result, false, CString, string1, "");
@@ -258,41 +239,42 @@ MESSAGE1(CKeyCharMsg, int, key, 32);
MESSAGE2(CLeaveNodeMsg, CNodeItem *, oldNode, nullptr, CNodeItem *, newNode, nullptr);
MESSAGE2(CLeaveRoomMsg, CRoomItem *, oldRoom, nullptr, CRoomItem *, newRoom, nullptr);
MESSAGE2(CLeaveViewMsg, CViewItem *, oldView, nullptr, CViewItem *, newView, nullptr);
-MESSAGE2(CLemonFallsFromTreeMsg, int, value1, 0, int, value2, 0);
+MESSAGE1(CLemonFallsFromTreeMsg, Point, pt, Point());
+MESSAGE4(CLightsMsg, bool, flag1, false, bool, flag2, false, bool, flag3, false, bool, flag4, false);
MESSAGE1(CLoadSuccessMsg, int, ticks, 0);
MESSAGE1(CLockPhonographMsg, int, value, 0);
MESSAGE0(CMaitreDDefeatedMsg);
MESSAGE0(CMaitreDHappyMsg);
-MESSAGE1(CMissiveOMatActionMsg, int, value, 0);
+MESSAGE1(CMissiveOMatActionMsg, int, action, 0);
MESSAGE0(CMoveToStartPosMsg);
MESSAGE2(CMovieEndMsg, int, startFrame, 0, int, endFrame, 0);
MESSAGE2(CMovieFrameMsg, int, frameNumber, 0, int, value2, 0);
MESSAGE0(CMusicHasStartedMsg);
MESSAGE0(CMusicHasStoppedMsg);
MESSAGE0(CMusicSettingChangedMsg);
-MESSAGE2(CNPCPlayAnimationMsg, const char *const *, names, nullptr, int, value2, 0);
+MESSAGE2(CNPCPlayAnimationMsg, const char *const *, names, nullptr, int, maxDuration, 0);
MESSAGE1(CNPCPlayIdleAnimationMsg, const char *const *, names, 0);
MESSAGE3(CNPCPlayTalkingAnimationMsg, int, value1, 0, int, value2, 0, const char *const *, names, nullptr);
MESSAGE0(CNPCQueueIdleAnimMsg);
MESSAGE1(CNutPuzzleMsg, CString, value, "");
MESSAGE1(COnSummonBotMsg, int, value, 0);
MESSAGE0(COpeningCreditsMsg);
-MESSAGE1(CPanningAwayFromParrotMsg, int, value, 0);
-MESSAGE2(CParrotSpeakMsg, CString, value1, "", CString, value2, "");
+MESSAGE1(CPanningAwayFromParrotMsg, CTreeItem *, target, nullptr);
+MESSAGE2(CParrotSpeakMsg, CString, target, "", CString, action, "");
MESSAGE2(CParrotTriesChickenMsg, int, value1, 0, int, value2, 0);
MESSAGE1(CPhonographPlayMsg, int, value, 0);
MESSAGE0(CPhonographReadyToPlayMsg);
MESSAGE1(CPhonographRecordMsg, int, value, 0);
MESSAGE3(CPhonographStopMsg, int, value1, 0, int, value2, 0, int, value3, 0);
MESSAGE2(CPlayRangeMsg, int, value1, 0, int, value2, 0);
-MESSAGE2(CPlayerTriesRestaurantTableMsg, int, value1, 0, int, value2, 0);
+MESSAGE2(CPlayerTriesRestaurantTableMsg, int, tableId, 0, bool, result, false);
MESSAGE1(CPreSaveMsg, int, value, 0);
MESSAGE1(CProdMaitreDMsg, int, value, 0);
-MESSAGE2(CPumpingMsg, int, value1, 0, int, value2, 0);
+MESSAGE2(CPumpingMsg, int, value, 0, CGameObject *, object, nullptr);
MESSAGE1(CPutBotBackInHisBoxMsg, int, value, 0);
MESSAGE1(CPutParrotBackMsg, int, value, 0);
MESSAGE0(CPuzzleSolvedMsg);
-MESSAGE3(CQueryCylinderHolderMsg, int, value1, 0, int, value2, 0, int, value3, 0);
+MESSAGE3(CQueryCylinderHolderMsg, int, value1, 0, int, value2, 0, CTreeItem *, target, (CTreeItem *)nullptr);
MESSAGE1(CQueryCylinderMsg, CString, name, "");
MESSAGE1(CQueryCylinderNameMsg, CString, name, "");
MESSAGE3(CQueryCylinderTypeMsg, int, value1, 0, int, value2, 0, int, value3, 0);
@@ -308,12 +290,12 @@ MESSAGE2(CServiceElevatorFloorChangeMsg, int, value1, 0, int, value2, 0);
MESSAGE0(CServiceElevatorFloorRequestMsg);
MESSAGE1(CServiceElevatorMsg, int, value, 4);
MESSAGE2(CSetChevButtonImageMsg, int, value1, 0, int, value2, 0);
-MESSAGE1(CSetChevClassBits, int, value, 0);
-MESSAGE1(CSetChevFloorBits, int, value, 0);
-MESSAGE1(CSetChevLiftBits, int, value, 0);
+MESSAGE1(CSetChevClassBits, int, classNum, 0);
+MESSAGE1(CSetChevFloorBits, int, floorNum, 0);
+MESSAGE1(CSetChevLiftBits, int, liftNum, 0);
MESSAGE2(CSetChevPanelBitMsg, int, value1, 0, int, value2, 0);
-MESSAGE1(CSetChevPanelButtonsMsg, int, value, 0);
-MESSAGE1(CSetChevRoomBits, int, value, 0);
+MESSAGE1(CSetChevPanelButtonsMsg, int, chevCode, 0);
+MESSAGE1(CSetChevRoomBits, int, roomNum, 0);
MESSAGE1(CSetFrameMsg, int, frameNumber, 0);
MESSAGE0(CSetMusicControlsMsg);
MESSAGE2(CSetVarMsg, CString, varName, "", int, value, 0);
@@ -321,17 +303,17 @@ MESSAGE2(CSetVolumeMsg, int, volume, 70, int, secondsTransition, 0);
MESSAGE2(CShipSettingMsg, int, value, 0, CString, name, "");
MESSAGE1(CShowTextMsg, CString, value, "NO TEXT INCLUDED!!!");
MESSAGE2(CSignalObject, CString, strValue, "", int, numValue, 0);
-MESSAGE2(CSpeechFallsFromTreeMsg, int, value1, 0, int, value2, 0);
+MESSAGE1(CSpeechFallsFromTreeMsg, Point, pos, Point());
MESSAGE1(CStartMusicMsg, CMusicPlayer *, musicPlayer, (CMusicPlayer *)nullptr);
MESSAGE3(CStatusChangeMsg, int, oldStatus, 0, int, newStatus, 0, bool, success, false);
-MESSAGE1(CStopMusicMsg, int, value, 0);
+MESSAGE1(CStopMusicMsg, CMusicPlayer *, musicPlayer, (CMusicPlayer *)nullptr);
MESSAGE4(CSubAcceptCCarryMsg, CString, string1, "", int, value1, 0, int, value2, 0, CCarry *, item, nullptr);
MESSAGE0(CSubDeliverCCarryMsg);
MESSAGE0(CSubSendCCarryMsg);
MESSAGE0(CSUBTransition);
MESSAGE0(CSubTurnOffMsg);
MESSAGE0(CSubTurnOnMsg);
-MESSAGE2(CSummonBotMsg, CString, strValue, "", int, numValue, 0);
+MESSAGE2(CSummonBotMsg, CString, npcName, "", int, value, 0);
MESSAGE1(CSummonBotQueryMsg, CString, npcName, "");
MESSAGE1(CTakeHeadPieceMsg, CString, value, "NULL");
MESSAGE2(CTextInputMsg, CString, input, "", CString, response, "");
@@ -352,7 +334,7 @@ MESSAGE0(CTrueTalkSelfQueueAnimSetMsg);
MESSAGE3(CTrueTalkTriggerActionMsg, int, action, 0, int, param1, 0, int, param2, 0);
MESSAGE0(CTurnOff);
MESSAGE0(CTurnOn);
-MESSAGE1(CUse, CCarry *, item, nullptr);
+MESSAGE1(CUse, CGameObject *, item, nullptr);
MESSAGE1(CUseWithCharMsg, CCharacter *, character, nullptr);
MESSAGE1(CUseWithOtherMsg, CGameObject *, other, 0);
MESSAGE1(CVirtualKeyCharMsg, Common::KeyState, keyState, Common::KeyState());
diff --git a/engines/titanic/messages/mouse_messages.cpp b/engines/titanic/messages/mouse_messages.cpp
index 8ef7f38fd3..18fa625c1c 100644
--- a/engines/titanic/messages/mouse_messages.cpp
+++ b/engines/titanic/messages/mouse_messages.cpp
@@ -23,7 +23,6 @@
#include "titanic/messages/mouse_messages.h"
#include "titanic/support/mouse_cursor.h"
#include "titanic/support/screen_manager.h"
-#include "titanic/titanic.h"
namespace Titanic {
diff --git a/engines/titanic/messages/mouse_messages.h b/engines/titanic/messages/mouse_messages.h
index d17bd51c78..e7c419bbdc 100644
--- a/engines/titanic/messages/mouse_messages.h
+++ b/engines/titanic/messages/mouse_messages.h
@@ -179,6 +179,32 @@ public:
}
};
+class CChildDragMoveMsg : public CMessage {
+public:
+ Point _mousePos;
+public:
+ CLASSDEF;
+ CChildDragMoveMsg() : CMessage() {}
+ CChildDragMoveMsg(const Point &pt) : CMessage(), _mousePos(pt) {}
+
+ static bool isSupportedBy(const CTreeItem *item) {
+ return supports(item, _type);
+ }
+};
+
+class CChildDragStartMsg : public CMessage {
+public:
+ Point _mousePos;
+public:
+ CLASSDEF;
+ CChildDragStartMsg() : CMessage() {}
+ CChildDragStartMsg(const Point &pt) : CMessage(), _mousePos(pt) {}
+
+ static bool isSupportedBy(const CTreeItem *item) {
+ return supports(item, _type);
+ }
+};
+
} // End of namespace Titanic
#endif /* TITANIC_MOUSE_MESSAGES_H */
diff --git a/engines/titanic/messages/pet_messages.h b/engines/titanic/messages/pet_messages.h
index 48e5bab64c..60981726ed 100644
--- a/engines/titanic/messages/pet_messages.h
+++ b/engines/titanic/messages/pet_messages.h
@@ -35,7 +35,7 @@ MESSAGE0(CPETLostObjectMsg);
MESSAGE0(CPETObjectSelectedMsg);
MESSAGE1(CPETObjectStateMsg, int, value, 0);
MESSAGE0(CPETPhotoOnOffMsg);
-MESSAGE1(CPETPlaySoundMsg, int, value, 0);
+MESSAGE1(CPETPlaySoundMsg, int, soundNum, 0);
MESSAGE0(CPETReceiveMsg);
MESSAGE0(CPETSetStarDestinationMsg);
MESSAGE1(CPETStarFieldLockMsg, int, value, 0);
diff --git a/engines/titanic/messages/service_elevator_door.cpp b/engines/titanic/messages/service_elevator_door.cpp
index 748790e4aa..7011b1ad44 100644
--- a/engines/titanic/messages/service_elevator_door.cpp
+++ b/engines/titanic/messages/service_elevator_door.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CServiceElevatorDoor, CDoorAutoSoundEvent)
+ ON_MESSAGE(PreEnterNodeMsg)
+END_MESSAGE_MAP()
+
CServiceElevatorDoor::CServiceElevatorDoor() : CDoorAutoSoundEvent() {
_string1 = "z#31.wav";
_string2 = "z#32.wav";
@@ -45,4 +49,10 @@ void CServiceElevatorDoor::load(SimpleFile *file) {
CDoorAutoSoundEvent::load(file);
}
+bool CServiceElevatorDoor::PreEnterNodeMsg(CPreEnterNodeMsg *msg) {
+ if (!findRoom()->isEquals("BilgeRoomWith"))
+ CDoorAutoSoundEvent::PreEnterNodeMsg(msg);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/messages/service_elevator_door.h b/engines/titanic/messages/service_elevator_door.h
index cc8da0917d..69ad1e15b9 100644
--- a/engines/titanic/messages/service_elevator_door.h
+++ b/engines/titanic/messages/service_elevator_door.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CServiceElevatorDoor : public CDoorAutoSoundEvent {
+ DECLARE_MESSAGE_MAP;
+ bool PreEnterNodeMsg(CPreEnterNodeMsg *msg);
public:
CLASSDEF;
CServiceElevatorDoor();
diff --git a/engines/titanic/module.mk b/engines/titanic/module.mk
index 5c041174a2..01ad617d6c 100644
--- a/engines/titanic/module.mk
+++ b/engines/titanic/module.mk
@@ -81,7 +81,6 @@ MODULE_OBJS := \
game/arb_background.o \
game/arboretum_gate.o \
game/auto_animate.o \
- game/bilge_succubus.o \
game/bar_menu.o \
game/bar_menu_button.o \
game/bar_bell.o \
@@ -95,7 +94,6 @@ MODULE_OBJS := \
game/broken_pell_base.o \
game/broken_pellerator.o \
game/broken_pellerator_froz.o \
- game/call_pellerator.o \
game/cage.o \
game/captains_wheel.o \
game/cdrom.o \
@@ -159,6 +157,8 @@ MODULE_OBJS := \
game/music_system_lock.o \
game/musical_instrument.o \
game/nav_helmet.o \
+ game/nav_helmet_off.o \
+ game/nav_helmet_on.o \
game/navigation_computer.o \
game/no_nut_bowl.o \
game/nose_holder.o \
@@ -221,7 +221,6 @@ MODULE_OBJS := \
game/parrot/parrot_nut_bowl_actor.o \
game/parrot/parrot_nut_eater.o \
game/parrot/parrot_perch_holder.o \
- game/parrot/parrot_succubus.o \
game/parrot/parrot_trigger.o \
game/parrot/player_meets_parrot.o \
game/pet/pet.o \
@@ -243,7 +242,7 @@ MODULE_OBJS := \
game/pickup/pick_up_speech_centre.o \
game/pickup/pick_up_vis_centre.o \
game/placeholder/bar_shelf_vis_centre.o \
- game/placeholder/place_holder_item.o \
+ game/placeholder/place_holder.o \
game/placeholder/lemon_on_bar.o \
game/placeholder/tv_on_bar.o \
game/transport/gondolier.o \
@@ -278,7 +277,6 @@ MODULE_OBJS := \
gfx/chev_right_off.o \
gfx/chev_right_on.o \
gfx/chev_send_rec_switch.o \
- gfx/chev_switch.o \
gfx/edit_control.o \
gfx/elevator_button.o \
gfx/get_from_succ.o \
@@ -297,7 +295,12 @@ MODULE_OBJS := \
gfx/move_object_button.o \
gfx/music_control.o \
gfx/music_slider.o \
+ gfx/music_slider_pitch.o \
+ gfx/music_slider_speed.o \
gfx/music_switch.o \
+ gfx/music_switch_inversion.o \
+ gfx/music_switch_reverse.o \
+ gfx/music_voice_mute.o \
gfx/send_to_succ.o \
gfx/sgt_selector.o \
gfx/slider_button.o \
@@ -319,6 +322,7 @@ MODULE_OBJS := \
messages/messages.o \
messages/mouse_messages.o \
messages/service_elevator_door.o \
+ moves/call_pellerator.o \
moves/enter_bomb_room.o \
moves/enter_bridge.o \
moves/enter_exit_first_class_state.o \
@@ -343,6 +347,7 @@ MODULE_OBJS := \
moves/trip_down_canal.o \
npcs/barbot.o \
npcs/bellbot.o \
+ npcs/bilge_succubus.o \
npcs/callbot.o \
npcs/character.o \
npcs/deskbot.o \
@@ -351,6 +356,7 @@ MODULE_OBJS := \
npcs/maitre_d.o \
npcs/mobile.o \
npcs/parrot.o \
+ npcs/parrot_succubus.o \
npcs/robot_controller.o \
npcs/starlings.o \
npcs/succubus.o \
@@ -399,8 +405,8 @@ MODULE_OBJS := \
sound/dome_from_top_of_well.o \
sound/enter_view_toggles_other_music.o \
sound/gondolier_song.o \
- sound/music_handler.o \
sound/music_room.o \
+ sound/music_room_handler.o \
sound/music_player.o \
sound/music_wave.o \
sound/node_auto_sound_player.o \
@@ -468,6 +474,7 @@ MODULE_OBJS := \
support/screen_manager.o \
support/simple_file.o \
support/string.o \
+ support/string_parser.o \
support/text_cursor.o \
support/time_event_info.o \
support/video_surface.o \
diff --git a/engines/titanic/moves/call_pellerator.cpp b/engines/titanic/moves/call_pellerator.cpp
new file mode 100644
index 0000000000..0dd8195277
--- /dev/null
+++ b/engines/titanic/moves/call_pellerator.cpp
@@ -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.
+ *
+ */
+
+#include "titanic/moves/call_pellerator.h"
+
+namespace Titanic {
+
+BEGIN_MESSAGE_MAP(CCallPellerator, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(PETActivateMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+void CCallPellerator::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(1, indent);
+ CGameObject::save(file, indent);
+}
+
+void CCallPellerator::load(SimpleFile *file) {
+ file->readNumber();
+ CGameObject::load(file);
+}
+
+bool CCallPellerator::EnterViewMsg(CEnterViewMsg *msg) {
+ petSetArea(PET_REMOTE);
+ petHighlightGlyph(1);
+ CString name = getFullViewName();
+
+ if (name == "TopOfWell.Node 6.S") {
+ petDisplayMessage(2, "You are standing outside the Pellerator.");
+ }
+
+ petSetRemoteTarget();
+ return true;
+}
+
+bool CCallPellerator::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petClear();
+ return true;
+}
+
+bool CCallPellerator::PETActivateMsg(CPETActivateMsg *msg) {
+ CString name = getFullViewName();
+
+ if (msg->_name == "Pellerator") {
+ if (petDoorOrBellbotPresent()) {
+ petDisplayMessage("I'm sorry, you cannot enter this pellerator at present as a bot is in the way.");
+ } else if (name == "Bar.Node 1.S") {
+ changeView("Pellerator.Node 1.S");
+ } else {
+ changeView("Pellerator.Node 1.N");
+ }
+ }
+
+ return true;
+}
+
+bool CCallPellerator::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ return true;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/game/call_pellerator.h b/engines/titanic/moves/call_pellerator.h
index 7da4b40794..3a1ef3823a 100644
--- a/engines/titanic/game/call_pellerator.h
+++ b/engines/titanic/moves/call_pellerator.h
@@ -24,10 +24,16 @@
#define TITANIC_CALL_PELLERATOR_H
#include "titanic/core/game_object.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CCallPellerator : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool PETActivateMsg(CPETActivateMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
diff --git a/engines/titanic/moves/enter_bomb_room.cpp b/engines/titanic/moves/enter_bomb_room.cpp
index 55b838d026..9956c669ee 100644
--- a/engines/titanic/moves/enter_bomb_room.cpp
+++ b/engines/titanic/moves/enter_bomb_room.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEnterBombRoom, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
CEnterBombRoom::CEnterBombRoom() : CMovePlayerTo(), _fieldC8(0) {
}
@@ -37,4 +41,10 @@ void CEnterBombRoom::load(SimpleFile *file) {
CMovePlayerTo::load(file);
}
+bool CEnterBombRoom::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ changeView("Titania.Node 2.SE");
+ changeView(_destination);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/enter_bomb_room.h b/engines/titanic/moves/enter_bomb_room.h
index 7fe8287eae..ccdd51f37b 100644
--- a/engines/titanic/moves/enter_bomb_room.h
+++ b/engines/titanic/moves/enter_bomb_room.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CEnterBombRoom : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
protected:
int _fieldC8;
public:
diff --git a/engines/titanic/moves/enter_bridge.cpp b/engines/titanic/moves/enter_bridge.cpp
index 2600ee699f..fb44fe2e02 100644
--- a/engines/titanic/moves/enter_bridge.cpp
+++ b/engines/titanic/moves/enter_bridge.cpp
@@ -24,20 +24,31 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEnterBridge, CGameObject)
+ ON_MESSAGE(EnterRoomMsg)
+END_MESSAGE_MAP()
+
void CEnterBridge::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value, indent);
+ file->writeNumberLine(_flag, indent);
CGameObject::save(file, indent);
}
void CEnterBridge::load(SimpleFile *file) {
file->readNumber();
- _value = file->readNumber();
+ _flag = file->readNumber();
CGameObject::load(file);
}
bool CEnterBridge::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CEnterBridge::handlEvent");
+ if (_flag) {
+ CActMsg actMsg("Disable");
+ actMsg.execute("ShipAnnouncements");
+
+ setState1C(false);
+ _flag = false;
+ }
+
return true;
}
diff --git a/engines/titanic/moves/enter_bridge.h b/engines/titanic/moves/enter_bridge.h
index a2410a6f1f..837c0e9f7d 100644
--- a/engines/titanic/moves/enter_bridge.h
+++ b/engines/titanic/moves/enter_bridge.h
@@ -29,12 +29,13 @@
namespace Titanic {
class CEnterBridge : public CGameObject {
+ DECLARE_MESSAGE_MAP;
bool EnterRoomMsg(CEnterRoomMsg *msg);
private:
- int _value;
+ bool _flag;
public:
CLASSDEF;
- CEnterBridge() : CGameObject(), _value(1) {}
+ CEnterBridge() : CGameObject(), _flag(true) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/moves/enter_exit_first_class_state.cpp b/engines/titanic/moves/enter_exit_first_class_state.cpp
index 0e2c6c0b6c..34e9984aa7 100644
--- a/engines/titanic/moves/enter_exit_first_class_state.cpp
+++ b/engines/titanic/moves/enter_exit_first_class_state.cpp
@@ -24,26 +24,44 @@
namespace Titanic {
-CString *CEnterExitFirstClassState::_v1;
-
-void CEnterExitFirstClassState::init() {
- _v1 = new CString();
-}
-
-void CEnterExitFirstClassState::deinit() {
- delete _v1;
-}
+BEGIN_MESSAGE_MAP(CEnterExitFirstClassState, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
void CEnterExitFirstClassState::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(*_v1, indent);
+ file->writeQuotedLine(_viewName, indent);
CGameObject::save(file, indent);
}
void CEnterExitFirstClassState::load(SimpleFile *file) {
file->readNumber();
- *_v1 = file->readString();
+ _viewName = file->readString();
CGameObject::load(file);
}
+bool CEnterExitFirstClassState::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ switch (getPassengerClass()) {
+ case 1:
+ if (compareRoomNameTo("1stClassLobby")) {
+ _viewName = getRoomNodeName() + ".E";
+ changeView(_viewName);
+ } else if (compareRoomNameTo("1stClassState")) {
+ changeView(_viewName);
+ }
+ break;
+
+ case 2:
+ petDisplayMessage(1, "This room is reserved for the exclusive use of first class passengeres."
+ " That does not currently include you");
+ break;
+
+ default:
+ petDisplayMessage("No losers.");
+ break;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/enter_exit_first_class_state.h b/engines/titanic/moves/enter_exit_first_class_state.h
index a08de07711..fe63e553de 100644
--- a/engines/titanic/moves/enter_exit_first_class_state.h
+++ b/engines/titanic/moves/enter_exit_first_class_state.h
@@ -28,18 +28,10 @@
namespace Titanic {
class CEnterExitFirstClassState : public CGameObject {
-public:
- static CString *_v1;
-
- /**
- * Initialize static data
- */
- static void init();
-
- /**
- * De-initialize static data
- */
- static void deinit();
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+private:
+ CString _viewName;
public:
CLASSDEF;
diff --git a/engines/titanic/moves/enter_exit_mini_lift.cpp b/engines/titanic/moves/enter_exit_mini_lift.cpp
index eb56bdb3bd..3caa674ab8 100644
--- a/engines/titanic/moves/enter_exit_mini_lift.cpp
+++ b/engines/titanic/moves/enter_exit_mini_lift.cpp
@@ -21,23 +21,52 @@
*/
#include "titanic/moves/enter_exit_mini_lift.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEnterExitMiniLift, CSGTNavigation)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
void CEnterExitMiniLift::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
-
+ file->writeNumberLine(_destRoomNum, indent);
+
CSGTNavigation::save(file, indent);
}
void CEnterExitMiniLift::load(SimpleFile *file) {
file->readNumber();
_fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
+ _destRoomNum = file->readNumber();
CSGTNavigation::load(file);
}
+bool CEnterExitMiniLift::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (compareRoomNameTo("SgtLobby")) {
+ _statics->_destView = getRoomNodeName() + ".S";
+ _statics->_destRoom = "SgtLobby";
+ changeView("SGTLittleLift.Node 1.E");
+
+ CPetControl *pet = getPetControl();
+ if (pet)
+ pet->setRoomsRoomNum(_destRoomNum);
+ } else if (compareRoomNameTo("SGTLittleLift")) {
+ if (_statics->_changeViewNum) {
+ changeView(_statics->_destView);
+ }
+ }
+
+ return true;
+}
+
+bool CEnterExitMiniLift::EnterViewMsg(CEnterViewMsg *msg) {
+ _cursorId = _statics->_changeViewNum ? CURSOR_MOVE_FORWARD : CURSOR_INVALID;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/enter_exit_mini_lift.h b/engines/titanic/moves/enter_exit_mini_lift.h
index 26f3dba8d4..f2671a89b2 100644
--- a/engines/titanic/moves/enter_exit_mini_lift.h
+++ b/engines/titanic/moves/enter_exit_mini_lift.h
@@ -28,12 +28,15 @@
namespace Titanic {
class CEnterExitMiniLift : public CSGTNavigation {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
private:
int _fieldBC;
- int _fieldC0;
+ int _destRoomNum;
public:
CLASSDEF;
- CEnterExitMiniLift() : CSGTNavigation(), _fieldBC(0), _fieldC0(1) {}
+ CEnterExitMiniLift() : CSGTNavigation(), _fieldBC(0), _destRoomNum(1) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp b/engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp
index b571a255c5..c7e16ef84e 100644
--- a/engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp
+++ b/engines/titanic/moves/enter_exit_sec_class_mini_lift.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/moves/enter_exit_sec_class_mini_lift.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEnterExitSecClassMiniLift, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(StatusChangeMsg)
+END_MESSAGE_MAP()
+
CEnterExitSecClassMiniLiftStatics *CEnterExitSecClassMiniLift::_statics;
void CEnterExitSecClassMiniLift::init() {
@@ -36,20 +42,53 @@ void CEnterExitSecClassMiniLift::deinit() {
void CEnterExitSecClassMiniLift::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_statics->_v1, indent);
- file->writeNumberLine(_statics->_v2, indent);
- file->writeNumberLine(_value, indent);
+ file->writeQuotedLine(_statics->_viewName, indent);
+ file->writeNumberLine(_statics->_state, indent);
+ file->writeNumberLine(_roomNum, indent);
CGameObject::save(file, indent);
}
void CEnterExitSecClassMiniLift::load(SimpleFile *file) {
file->readNumber();
- _statics->_v1 = file->readString();
- _statics->_v2 = file->readNumber();
- _value = file->readNumber();
+ _statics->_viewName = file->readString();
+ _statics->_state = file->readNumber();
+ _roomNum = file->readNumber();
CGameObject::load(file);
}
+bool CEnterExitSecClassMiniLift::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (compareRoomNameTo("2ndClassLobby")) {
+ _statics->_viewName = getRoomNodeName() + ".W";
+ changeView("SecClassLittleLift.Node 1.E");
+ _statics->_state = 1;
+
+ CPetControl *pet = getPetControl();
+ if (pet) {
+ pet->setRoomsRoomNum(_roomNum);
+ pet->setRooms1CC(1);
+ }
+ } else if (compareRoomNameTo("SecClassLittleLift")) {
+ if (_statics->_state == 1) {
+ changeView(_statics->_viewName);
+ }
+ }
+
+ return true;
+}
+
+bool CEnterExitSecClassMiniLift::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _statics->_state = msg->_newStatus;
+ if (msg->_newStatus == 3)
+ _statics->_state = 2;
+
+ CPetControl *pet = getPetControl();
+ if (pet)
+ pet->setRooms1CC(_statics->_state);
+
+ _cursorId = _statics->_state == 1 ? CURSOR_MOVE_FORWARD : CURSOR_INVALID;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/enter_exit_sec_class_mini_lift.h b/engines/titanic/moves/enter_exit_sec_class_mini_lift.h
index 10c7edca7d..839d2c04fa 100644
--- a/engines/titanic/moves/enter_exit_sec_class_mini_lift.h
+++ b/engines/titanic/moves/enter_exit_sec_class_mini_lift.h
@@ -28,19 +28,22 @@
namespace Titanic {
struct CEnterExitSecClassMiniLiftStatics {
- CString _v1;
- int _v2;
+ CString _viewName;
+ int _state;
- CEnterExitSecClassMiniLiftStatics() : _v2(1) {}
+ CEnterExitSecClassMiniLiftStatics() : _state(1) {}
};
class CEnterExitSecClassMiniLift : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
private:
static CEnterExitSecClassMiniLiftStatics *_statics;
- int _value;
+ int _roomNum;
public:
CLASSDEF;
- CEnterExitSecClassMiniLift() : CGameObject(), _value(0) {}
+ CEnterExitSecClassMiniLift() : CGameObject(), _roomNum(0) {}
static void init();
static void deinit();
diff --git a/engines/titanic/moves/enter_exit_view.cpp b/engines/titanic/moves/enter_exit_view.cpp
index 825156acce..6778ebb52a 100644
--- a/engines/titanic/moves/enter_exit_view.cpp
+++ b/engines/titanic/moves/enter_exit_view.cpp
@@ -24,30 +24,55 @@
namespace Titanic {
-CEnterExitView::CEnterExitView() : CGameObject(), _fieldBC(0),
- _fieldC0(0), _fieldC4(0), _fieldC8(0), _fieldCC(0) {
+BEGIN_MESSAGE_MAP(CEnterExitView, CGameObject)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
+CEnterExitView::CEnterExitView() : CGameObject(), _leaveEndFrame(0),
+ _leaveStartFrame(0), _enterEndFrame(0), _enterStartFrame(0),
+ _visibleAfterMovie(true) {
}
void CEnterExitView::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
- file->writeNumberLine(_fieldC4, indent);
- file->writeNumberLine(_fieldC8, indent);
- file->writeNumberLine(_fieldCC, indent);
+ file->writeNumberLine(_leaveEndFrame, indent);
+ file->writeNumberLine(_leaveStartFrame, indent);
+ file->writeNumberLine(_enterEndFrame, indent);
+ file->writeNumberLine(_enterStartFrame, indent);
+ file->writeNumberLine(_visibleAfterMovie, indent);
CGameObject::save(file, indent);
}
void CEnterExitView::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
- _fieldC4 = file->readNumber();
- _fieldC8 = file->readNumber();
- _fieldCC = file->readNumber();
+ _leaveEndFrame = file->readNumber();
+ _leaveStartFrame = file->readNumber();
+ _enterEndFrame = file->readNumber();
+ _enterStartFrame = file->readNumber();
+ _visibleAfterMovie = file->readNumber();
CGameObject::load(file);
}
+bool CEnterExitView::EnterViewMsg(CEnterViewMsg *msg) {
+ setVisible(true);
+ playMovie(_enterStartFrame, _enterEndFrame, MOVIE_NOTIFY_OBJECT);
+ return true;
+}
+
+bool CEnterExitView::LeaveViewMsg(CLeaveViewMsg *msg) {
+ setVisible(true);
+ playMovie(_leaveStartFrame, _leaveEndFrame, MOVIE_NOTIFY_OBJECT);
+ return true;
+}
+
+bool CEnterExitView::MovieEndMsg(CMovieEndMsg *msg) {
+ if (!_visibleAfterMovie)
+ setVisible(false);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/enter_exit_view.h b/engines/titanic/moves/enter_exit_view.h
index 4a3f1a967b..67aa5643ff 100644
--- a/engines/titanic/moves/enter_exit_view.h
+++ b/engines/titanic/moves/enter_exit_view.h
@@ -28,12 +28,16 @@
namespace Titanic {
class CEnterExitView : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
- int _fieldBC;
- int _fieldC0;
- int _fieldC4;
- int _fieldC8;
- int _fieldCC;
+ int _leaveEndFrame;
+ int _leaveStartFrame;
+ int _enterEndFrame;
+ int _enterStartFrame;
+ bool _visibleAfterMovie;
public:
CLASSDEF;
CEnterExitView();
diff --git a/engines/titanic/moves/enter_sec_class_state.cpp b/engines/titanic/moves/enter_sec_class_state.cpp
index dced724de7..2a35621003 100644
--- a/engines/titanic/moves/enter_sec_class_state.cpp
+++ b/engines/titanic/moves/enter_sec_class_state.cpp
@@ -21,23 +21,88 @@
*/
#include "titanic/moves/enter_sec_class_state.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CEnterSecClassState, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(MovieEndMsg)
+END_MESSAGE_MAP()
+
void CEnterSecClassState::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_value1, indent);
- file->writeNumberLine(_value2, indent);
+ file->writeNumberLine(_mode, indent);
+ file->writeNumberLine(_soundHandle, indent);
CGameObject::save(file, indent);
}
void CEnterSecClassState::load(SimpleFile *file) {
file->readNumber();
- _value1 = file->readNumber();
- _value2 = file->readNumber();
+ _mode = file->readNumber();
+ _soundHandle = file->readNumber();
CGameObject::load(file);
}
+bool CEnterSecClassState::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (getPassengerClass() > 2) {
+ playSound("b#105.wav");
+ petDisplayMessage(1, "Passengers of your class are not permitted to enter this area.");
+ } else if (!compareRoomNameTo("SecClassLittleLift") || _mode == 2) {
+ CActMsg actMsg(getFullViewName().deleteRight(3) + ".S");
+ actMsg.execute("SecClassRoomLeaver");
+ changeView("secClassState.Node 01.N");
+ }
+
+ return true;
+}
+
+bool CEnterSecClassState::StatusChangeMsg(CStatusChangeMsg *msg) {
+ stopSound(_soundHandle);
+
+ if (msg->_newStatus == _mode || (_mode == 2 && msg->_newStatus == 3)) {
+ if (_mode == 2) {
+ _soundHandle = queueSound("b#36.wav", _soundHandle);
+ } else {
+ _soundHandle = queueSound("b#31.wav", _soundHandle);
+ }
+ if (msg->_newStatus == 3)
+ msg->_newStatus = 2;
+ } else {
+ changeView("SecClassLittleLift.Node 1.N");
+ if (msg->_newStatus == 1) {
+ _soundHandle = queueSound("b#32.wav", _soundHandle);
+ } else if (msg->_newStatus == 2) {
+ _soundHandle = queueSound("b#25.wav", _soundHandle);
+ } else if (msg->_newStatus == 3) {
+ _soundHandle = queueSound("b#33.wav", _soundHandle);
+ msg->_newStatus = 2;
+ }
+ }
+
+ if (msg->_newStatus != 3) {
+ if (msg->_newStatus == 2 && _mode == 1)
+ playMovie(0, 10, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ else if (msg->_newStatus == 1)
+ playMovie(11, 21, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+
+ _cursorId = msg->_newStatus == 2 ? CURSOR_MOVE_FORWARD : CURSOR_INVALID;
+ _mode = msg->_newStatus;
+ return true;
+}
+
+bool CEnterSecClassState::MovieEndMsg(CMovieEndMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (pet) {
+ pet->setRooms1CC(_mode);
+ pet->resetRoomsHighlight();
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/enter_sec_class_state.h b/engines/titanic/moves/enter_sec_class_state.h
index c3e3cabf20..2b1bcaa401 100644
--- a/engines/titanic/moves/enter_sec_class_state.h
+++ b/engines/titanic/moves/enter_sec_class_state.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CEnterSecClassState : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
public:
- int _value1, _value2;
+ int _mode, _soundHandle;
public:
CLASSDEF;
- CEnterSecClassState() : CGameObject(), _value1(0), _value2(0) {}
+ CEnterSecClassState() : CGameObject(), _mode(1), _soundHandle(0) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/moves/exit_arboretum.cpp b/engines/titanic/moves/exit_arboretum.cpp
index d606510c6e..ba162843b5 100644
--- a/engines/titanic/moves/exit_arboretum.cpp
+++ b/engines/titanic/moves/exit_arboretum.cpp
@@ -21,29 +21,85 @@
*/
#include "titanic/moves/exit_arboretum.h"
+#include "titanic/game/seasonal_adjustment.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CExitArboretum, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(ChangeSeasonMsg)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+END_MESSAGE_MAP()
+
CExitArboretum::CExitArboretum() : CMovePlayerTo(),
- _fieldC8(0), _fieldCC(0), _fieldD0(1) {
+ _seasonNum(0), _fieldCC(0), _enabled(true) {
}
void CExitArboretum::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldC8, indent);
+ file->writeNumberLine(_seasonNum, indent);
file->writeNumberLine(_fieldCC, indent);
- file->writeNumberLine(_fieldD0, indent);
+ file->writeNumberLine(_enabled, indent);
CMovePlayerTo::save(file, indent);
}
void CExitArboretum::load(SimpleFile *file) {
file->readNumber();
- _fieldC8 = file->readNumber();
+ _seasonNum = file->readNumber();
_fieldCC = file->readNumber();
- _fieldD0 = file->readNumber();
+ _enabled = file->readNumber();
CMovePlayerTo::load(file);
}
+bool CExitArboretum::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_enabled) {
+ CActMsg actMsg;
+ if (_seasonNum == SEASON_WINTER) {
+ switch (_fieldCC) {
+ case 0:
+ actMsg._action = "ExitLFrozen";
+ break;
+ case 1:
+ actMsg._action = "ExitRFrozen";
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (_fieldCC) {
+ case 0:
+ actMsg._action = "ExitLNormal";
+ break;
+ case 1:
+ actMsg._action = "ExitRNormal";
+ break;
+ default:
+ break;
+ }
+ }
+
+ actMsg.execute("ArbGate");
+ }
+
+ return true;
+}
+
+bool CExitArboretum::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ _seasonNum = (_seasonNum + 1) % 4;
+ return true;
+}
+
+bool CExitArboretum::TurnOn(CTurnOn *msg) {
+ _enabled = false;
+ return true;
+}
+
+bool CExitArboretum::TurnOff(CTurnOff *msg) {
+ _enabled = true;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/exit_arboretum.h b/engines/titanic/moves/exit_arboretum.h
index f6ebf71515..b65eb92b17 100644
--- a/engines/titanic/moves/exit_arboretum.h
+++ b/engines/titanic/moves/exit_arboretum.h
@@ -28,10 +28,15 @@
namespace Titanic {
class CExitArboretum : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
protected:
- int _fieldC8;
+ int _seasonNum;
int _fieldCC;
- int _fieldD0;
+ bool _enabled;
public:
CLASSDEF;
CExitArboretum();
diff --git a/engines/titanic/moves/exit_bridge.cpp b/engines/titanic/moves/exit_bridge.cpp
index b913911341..6b69b88004 100644
--- a/engines/titanic/moves/exit_bridge.cpp
+++ b/engines/titanic/moves/exit_bridge.cpp
@@ -24,21 +24,35 @@
namespace Titanic {
-CExitBridge::CExitBridge() : CMovePlayerTo() {
+BEGIN_MESSAGE_MAP(CExitBridge, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+CExitBridge::CExitBridge() : CMovePlayerTo(), _viewName("Titania.Node 1.S") {
}
void CExitBridge::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeQuotedLine(_viewName, indent);
CMovePlayerTo::save(file, indent);
}
void CExitBridge::load(SimpleFile *file) {
file->readNumber();
- _string1 = file->readString();
+ _viewName = file->readString();
CMovePlayerTo::load(file);
}
+bool CExitBridge::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (getGameManager()) {
+ changeView(_destination);
+ playSound("a#53.wav");
+ changeView(_viewName);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/exit_bridge.h b/engines/titanic/moves/exit_bridge.h
index 4ab29524db..6d8ba01c91 100644
--- a/engines/titanic/moves/exit_bridge.h
+++ b/engines/titanic/moves/exit_bridge.h
@@ -28,8 +28,10 @@
namespace Titanic {
class CExitBridge : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
private:
- CString _string1;
+ CString _viewName;
public:
CLASSDEF;
CExitBridge();
diff --git a/engines/titanic/moves/exit_lift.cpp b/engines/titanic/moves/exit_lift.cpp
index a264be883d..de9a3117af 100644
--- a/engines/titanic/moves/exit_lift.cpp
+++ b/engines/titanic/moves/exit_lift.cpp
@@ -21,19 +21,101 @@
*/
#include "titanic/moves/exit_lift.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CExitLift, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+CExitLift::CExitLift() : CGameObject(), _viewName("NULL") {
+}
+
void CExitLift::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_value, indent);
+ file->writeQuotedLine(_viewName, indent);
CGameObject::save(file, indent);
}
void CExitLift::load(SimpleFile *file) {
file->readNumber();
- _value = file->readString();
+ _viewName = file->readString();
CGameObject::load(file);
}
+bool CExitLift::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CPetControl *pet = getPetControl();
+ int floorNum = pet->getRoomsFloorNum();//ebx
+ int elevNum = pet->getRoomsElevatorNum(); //eax
+
+ if (floorNum == 39) {
+ switch (elevNum) {
+ case 1:
+ _viewName = "BottomOfWell.Node 5.SE";
+ break;
+ case 3:
+ _viewName = "BottomOfWell.Node 1.NW";
+ break;
+ default:
+ break;
+ }
+ } else if (floorNum > 27) {
+ switch (elevNum) {
+ case 1:
+ case 2:
+ _viewName = "SgtLobby.Node 1.N";
+ break;
+ default:
+ break;
+ }
+ } else if (floorNum > 19) {
+ switch (elevNum) {
+ case 0:
+ case 2:
+ _viewName = "2ndClassLobby.Node 8.N";
+ break;
+ case 1:
+ case 3:
+ _viewName = "2ndClassLobby.Node 1.N";
+ break;
+ default:
+ break;
+ }
+ } else if (floorNum > 1) {
+ switch (elevNum) {
+ case 0:
+ case 2:
+ _viewName = "1stClassLobby.Node 1.W";
+ break;
+ case 1:
+ case 3:
+ _viewName = "1stClassLobby.Node 1.E";
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (elevNum) {
+ case 0:
+ _viewName = "TopOfWell.Node 6.E";
+ break;
+ case 1:
+ _viewName = "TopOfWell.Node 6.W";
+ break;
+ case 2:
+ _viewName = "TopOfWell.Node 10.W";
+ break;
+ case 3:
+ _viewName = "TopOfWell.Node 10.E";
+ break;
+ default:
+ break;
+ }
+ }
+
+ changeView(_viewName);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/exit_lift.h b/engines/titanic/moves/exit_lift.h
index 04dabfaf13..93d760c35a 100644
--- a/engines/titanic/moves/exit_lift.h
+++ b/engines/titanic/moves/exit_lift.h
@@ -28,10 +28,13 @@
namespace Titanic {
class CExitLift : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
- CString _value;
+ CString _viewName;
public:
CLASSDEF;
+ CExitLift();
/**
* Save the data for the class to file
diff --git a/engines/titanic/moves/exit_pellerator.cpp b/engines/titanic/moves/exit_pellerator.cpp
index 68a2a8da91..12ca2c1e3c 100644
--- a/engines/titanic/moves/exit_pellerator.cpp
+++ b/engines/titanic/moves/exit_pellerator.cpp
@@ -21,9 +21,16 @@
*/
#include "titanic/moves/exit_pellerator.h"
+#include "titanic/game/transport/pellerator.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CExitPellerator, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(StatusChangeMsg)
+ ON_MESSAGE(ChangeSeasonMsg)
+END_MESSAGE_MAP()
+
CExitPelleratorStatics *CExitPellerator::_statics;
void CExitPellerator::init() {
@@ -38,7 +45,7 @@ void CExitPellerator::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeQuotedLine(_statics->_v1, indent);
file->writeNumberLine(_statics->_v2, indent);
- file->writeNumberLine(_statics->_v3, indent);
+ file->writeNumberLine(_statics->_isWinter, indent);
CGameObject::save(file, indent);
}
@@ -47,9 +54,84 @@ void CExitPellerator::load(SimpleFile *file) {
file->readNumber();
_statics->_v1 = file->readString();
_statics->_v2 = file->readNumber();
- _statics->_v3 = file->readNumber();
+ _statics->_isWinter = file->readNumber();
CGameObject::load(file);
}
+bool CExitPellerator::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CString name = getName();
+
+ if (name == "ExitPellerator") {
+ if (_statics->_v2 != 2) {
+ switch (getRandomNumber(2)) {
+ case 0:
+ CPellerator::_soundHandle = queueSound("z#457.wav", CPellerator::_soundHandle);
+ break;
+ case 1:
+ CPellerator::_soundHandle = queueSound("z#458.wav", CPellerator::_soundHandle);
+ break;
+ default:
+ CPellerator::_soundHandle = queueSound("z#464.wav", CPellerator::_soundHandle);
+ break;
+ }
+ }
+
+ switch (_statics->_v2) {
+ case 0:
+ changeView("PromenadeDeck.Node 1.W");
+ break;
+ case 1:
+ changeView("MusicRoomLobby.Node 1.S");
+ break;
+ case 4:
+ changeView("TopOfWell.Node 6.N");
+ break;
+ case 5:
+ changeView("1stClassRestaurant.Lobby Node.E");
+ break;
+ case 6:
+ changeView(_statics->_isWinter ? "FrozenArboretum.Node 4.S" : "Arboretum.Node 4.W");
+ break;
+ default:
+ petDisplayMessage(2, "Please exit from the other side.");
+ CPellerator::_soundHandle = queueSound("z#438.wav", CPellerator::_soundHandle);
+
+ }
+ } else if (name == "ExitPellerator2") {
+ if (_statics->_v2 == 2) {
+ switch (getRandomNumber(2)) {
+ case 0:
+ CPellerator::_soundHandle = queueSound("z#457.wav", CPellerator::_soundHandle);
+ break;
+ case 1:
+ CPellerator::_soundHandle = queueSound("z#458.wav", CPellerator::_soundHandle);
+ break;
+ default:
+ CPellerator::_soundHandle = queueSound("z#464.wav", CPellerator::_soundHandle);
+ break;
+ }
+ }
+
+ if (_statics->_v2 == 2) {
+ changeView("Bar.Node 1.N");
+ } else {
+ petDisplayMessage(2, "Please exit from the other side.");
+ CPellerator::_soundHandle = queueSound("z#438.wav", CPellerator::_soundHandle);
+ }
+ }
+
+ return true;
+}
+
+bool CExitPellerator::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _statics->_v2 = msg->_newStatus;
+ return true;
+}
+
+bool CExitPellerator::ChangeSeasonMsg(CChangeSeasonMsg *msg) {
+ _statics->_isWinter = msg->_season == "Winter";
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/exit_pellerator.h b/engines/titanic/moves/exit_pellerator.h
index 280d1e9a6c..8819d64355 100644
--- a/engines/titanic/moves/exit_pellerator.h
+++ b/engines/titanic/moves/exit_pellerator.h
@@ -30,10 +30,14 @@ namespace Titanic {
struct CExitPelleratorStatics {
CString _v1;
int _v2;
- int _v3;
+ bool _isWinter;
};
class CExitPellerator : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
+ bool ChangeSeasonMsg(CChangeSeasonMsg *msg);
private:
static CExitPelleratorStatics *_statics;
public:
diff --git a/engines/titanic/moves/exit_state_room.cpp b/engines/titanic/moves/exit_state_room.cpp
index 1c78a69ac2..f0b0534c12 100644
--- a/engines/titanic/moves/exit_state_room.cpp
+++ b/engines/titanic/moves/exit_state_room.cpp
@@ -24,7 +24,11 @@
namespace Titanic {
-CExitStateRoom::CExitStateRoom() : CMovePlayerTo(), _fieldC8(0) {
+BEGIN_MESSAGE_MAP(CExitStateRoom, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+CExitStateRoom::CExitStateRoom() : CMovePlayerTo() {
}
void CExitStateRoom::save(SimpleFile *file, int indent) {
@@ -37,4 +41,9 @@ void CExitStateRoom::load(SimpleFile *file) {
CMovePlayerTo::load(file);
}
+bool CExitStateRoom::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ changeView(_destination);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/exit_state_room.h b/engines/titanic/moves/exit_state_room.h
index c0f9737817..19322ced7f 100644
--- a/engines/titanic/moves/exit_state_room.h
+++ b/engines/titanic/moves/exit_state_room.h
@@ -28,8 +28,8 @@
namespace Titanic {
class CExitStateRoom : public CMovePlayerTo {
-protected:
- int _fieldC8;
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
CExitStateRoom();
diff --git a/engines/titanic/moves/exit_tiania.cpp b/engines/titanic/moves/exit_tiania.cpp
index 6cb2422b1f..fb0f149ba9 100644
--- a/engines/titanic/moves/exit_tiania.cpp
+++ b/engines/titanic/moves/exit_tiania.cpp
@@ -24,16 +24,20 @@
namespace Titanic {
-CExitTiania::CExitTiania() : CMovePlayerTo(), _fieldC8(0),
- _string1("NULL"), _string2("NULL"), _string3("NULL") {
+BEGIN_MESSAGE_MAP(CExitTiania, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+CExitTiania::CExitTiania() : CMovePlayerTo(), _fieldC8(0) {
+ _viewNames[0] = _viewNames[1] = _viewNames[2] = "NULL";
}
void CExitTiania::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldC8, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeQuotedLine(_string2, indent);
- file->writeQuotedLine(_string3, indent);
+ file->writeQuotedLine(_viewNames[0], indent);
+ file->writeQuotedLine(_viewNames[1], indent);
+ file->writeQuotedLine(_viewNames[2], indent);
CMovePlayerTo::save(file, indent);
}
@@ -41,11 +45,29 @@ void CExitTiania::save(SimpleFile *file, int indent) {
void CExitTiania::load(SimpleFile *file) {
file->readNumber();
_fieldC8 = file->readNumber();
- _string1 = file->readString();
- _string2 = file->readString();
- _string3 = file->readString();
+ _viewNames[0] = file->readString();
+ _viewNames[1] = file->readString();
+ _viewNames[2] = file->readString();
CMovePlayerTo::load(file);
}
+bool CExitTiania::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (getPassengerClass() == 4) {
+ petDisplayMessage(1, "For mysterious and unknowable reasons, "
+ "this transport is temporarily out of order.");
+ } else {
+ lockMouse();
+ for (int idx = 0; idx < 3; ++idx)
+ changeView(_viewNames[idx]);
+ changeView("Titania.Node 16.N");
+ changeView("Dome.Node 4.N");
+ changeView("Dome.Node 3.N");
+ changeView("Dome.Node 3.S");
+ unlockMouse();
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/exit_tiania.h b/engines/titanic/moves/exit_tiania.h
index c2b7772ce7..b911e102d1 100644
--- a/engines/titanic/moves/exit_tiania.h
+++ b/engines/titanic/moves/exit_tiania.h
@@ -28,11 +28,11 @@
namespace Titanic {
class CExitTiania : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
private:
int _fieldC8;
- CString _string1;
- CString _string2;
- CString _string3;
+ CString _viewNames[3];
public:
CLASSDEF;
CExitTiania();
diff --git a/engines/titanic/moves/move_player_in_parrot_room.cpp b/engines/titanic/moves/move_player_in_parrot_room.cpp
index df38c63cd4..1ef2e96e92 100644
--- a/engines/titanic/moves/move_player_in_parrot_room.cpp
+++ b/engines/titanic/moves/move_player_in_parrot_room.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMovePlayerInParrotRoom, CMovePlayerTo)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
CMovePlayerInParrotRoom::CMovePlayerInParrotRoom() : CMovePlayerTo() {
}
@@ -37,4 +42,20 @@ void CMovePlayerInParrotRoom::load(SimpleFile *file) {
CMovePlayerTo::load(file);
}
+bool CMovePlayerInParrotRoom::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PanAwayFromParrot") {
+ unlockMouse();
+ changeView(_destination);
+ }
+
+ return true;
+}
+
+bool CMovePlayerInParrotRoom::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ lockMouse();
+ CPanningAwayFromParrotMsg awayMsg(this);
+ awayMsg.execute("PerchedParrot");
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/move_player_in_parrot_room.h b/engines/titanic/moves/move_player_in_parrot_room.h
index de693fe2e2..54dc2eb992 100644
--- a/engines/titanic/moves/move_player_in_parrot_room.h
+++ b/engines/titanic/moves/move_player_in_parrot_room.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CMovePlayerInParrotRoom : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
CMovePlayerInParrotRoom();
diff --git a/engines/titanic/moves/move_player_to.cpp b/engines/titanic/moves/move_player_to.cpp
index 9b6000c4f8..a91215b539 100644
--- a/engines/titanic/moves/move_player_to.cpp
+++ b/engines/titanic/moves/move_player_to.cpp
@@ -21,9 +21,15 @@
*/
#include "titanic/moves/move_player_to.h"
+#include "titanic/game_manager.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMovePlayerTo, CGameObject)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
CMovePlayerTo::CMovePlayerTo() : CGameObject() {
}
@@ -41,4 +47,17 @@ void CMovePlayerTo::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CMovePlayerTo::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CGameManager *gameManager = getGameManager();
+ if (gameManager)
+ changeView(_destination);
+
+ return true;
+}
+
+bool CMovePlayerTo::ActMsg(CActMsg *msg) {
+ _destination = msg->_action;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/move_player_to.h b/engines/titanic/moves/move_player_to.h
index 4bfffcb0b2..822df69422 100644
--- a/engines/titanic/moves/move_player_to.h
+++ b/engines/titanic/moves/move_player_to.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CMovePlayerTo : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool ActMsg(CActMsg *msg);
protected:
CString _destination;
public:
diff --git a/engines/titanic/moves/move_player_to_from.cpp b/engines/titanic/moves/move_player_to_from.cpp
index 1a67dc8505..c57cc2cf51 100644
--- a/engines/titanic/moves/move_player_to_from.cpp
+++ b/engines/titanic/moves/move_player_to_from.cpp
@@ -21,10 +21,16 @@
*/
#include "titanic/moves/move_player_to_from.h"
+#include "titanic/core/view_item.h"
+#include "titanic/core/link_item.h"
namespace Titanic {
-CMovePlayerToFrom::CMovePlayerToFrom() : CGameObject() {
+BEGIN_MESSAGE_MAP(CMovePlayerToFrom, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+CMovePlayerToFrom::CMovePlayerToFrom() : CMovePlayerTo() {
}
void CMovePlayerToFrom::save(SimpleFile *file, int indent) {
@@ -41,4 +47,17 @@ void CMovePlayerToFrom::load(SimpleFile *file) {
CGameObject::load(file);
}
+bool CMovePlayerToFrom::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_string2.empty()) {
+ changeView(_destination);
+ } else {
+ CViewItem *view = parseView(_string2);
+ CViewItem *destView = parseView(_destination);
+ CLinkItem *link = view->findLink(destView);
+ changeView(_destination, link->getName());
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/move_player_to_from.h b/engines/titanic/moves/move_player_to_from.h
index c9eefe532f..fde4e94ab5 100644
--- a/engines/titanic/moves/move_player_to_from.h
+++ b/engines/titanic/moves/move_player_to_from.h
@@ -23,11 +23,13 @@
#ifndef TITANIC_MOVE_PLAYER_TO_FROM_H
#define TITANIC_MOVE_PLAYER_TO_FROM_H
-#include "titanic/core/game_object.h"
+#include "titanic/moves/move_player_to.h"
namespace Titanic {
-class CMovePlayerToFrom : public CGameObject {
+class CMovePlayerToFrom : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
private:
CString _string2;
public:
diff --git a/engines/titanic/moves/multi_move.cpp b/engines/titanic/moves/multi_move.cpp
index fb5570df9b..4ca4fdb8f3 100644
--- a/engines/titanic/moves/multi_move.cpp
+++ b/engines/titanic/moves/multi_move.cpp
@@ -24,29 +24,37 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMultiMove, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
CMultiMove::CMultiMove() : CMovePlayerTo() {
}
void CMultiMove::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeQuotedLine(_string2, indent);
- file->writeQuotedLine(_string3, indent);
- file->writeQuotedLine(_string4, indent);
- file->writeQuotedLine(_string5, indent);
+ for (int idx = 0; idx < 5; ++idx)
+ file->writeQuotedLine(_viewNames[idx], indent);
CMovePlayerTo::save(file, indent);
}
void CMultiMove::load(SimpleFile *file) {
file->readNumber();
- _string1 = file->readString();
- _string2 = file->readString();
- _string3 = file->readString();
- _string5 = file->readString();
- _string4 = file->readString();
+ for (int idx = 0; idx < 5; ++idx)
+ _viewNames[idx] = file->readString();
CMovePlayerTo::load(file);
}
+bool CMultiMove::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ lockMouse();
+
+ for (int idx = 0; idx < 5 && _viewNames[idx] != "NULL"; ++idx)
+ changeView(_viewNames[idx]);
+
+ unlockMouse();
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/multi_move.h b/engines/titanic/moves/multi_move.h
index 977afc2a20..12dd246823 100644
--- a/engines/titanic/moves/multi_move.h
+++ b/engines/titanic/moves/multi_move.h
@@ -28,12 +28,10 @@
namespace Titanic {
class CMultiMove : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
private:
- CString _string1;
- CString _string2;
- CString _string3;
- CString _string4;
- CString _string5;
+ CString _viewNames[5];
public:
CLASSDEF;
CMultiMove();
diff --git a/engines/titanic/moves/pan_from_pel.cpp b/engines/titanic/moves/pan_from_pel.cpp
index fccc643ec5..ca48e888c1 100644
--- a/engines/titanic/moves/pan_from_pel.cpp
+++ b/engines/titanic/moves/pan_from_pel.cpp
@@ -24,23 +24,33 @@
namespace Titanic {
-CPanFromPel::CPanFromPel() : CMovePlayerTo(), _fieldC8(0) {
+BEGIN_MESSAGE_MAP(CPanFromPel, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+CPanFromPel::CPanFromPel() : CMovePlayerTo(), _closeLeft(false) {
}
void CPanFromPel::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldC8, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeNumberLine(_closeLeft, indent);
+ file->writeQuotedLine(_target, indent);
CMovePlayerTo::save(file, indent);
}
void CPanFromPel::load(SimpleFile *file) {
file->readNumber();
- _fieldC8 = file->readNumber();
- _string1 = file->readString();
+ _closeLeft = file->readNumber();
+ _target = file->readString();
CMovePlayerTo::load(file);
}
+bool CPanFromPel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ CActMsg actMsg(_closeLeft ? "CloseLeft" : "CloseRight");
+ actMsg.execute(_target);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/pan_from_pel.h b/engines/titanic/moves/pan_from_pel.h
index c81be9f338..0a01aefea3 100644
--- a/engines/titanic/moves/pan_from_pel.h
+++ b/engines/titanic/moves/pan_from_pel.h
@@ -28,9 +28,11 @@
namespace Titanic {
class CPanFromPel : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
protected:
- int _fieldC8;
- CString _string1;
+ bool _closeLeft;
+ CString _target;
public:
CLASSDEF;
CPanFromPel();
diff --git a/engines/titanic/moves/restaurant_pan_handler.cpp b/engines/titanic/moves/restaurant_pan_handler.cpp
index 92f55b46cc..d93e331254 100644
--- a/engines/titanic/moves/restaurant_pan_handler.cpp
+++ b/engines/titanic/moves/restaurant_pan_handler.cpp
@@ -24,24 +24,40 @@
namespace Titanic {
-int CRestaurantPanHandler::_v1;
+BEGIN_MESSAGE_MAP(CRestaurantPanHandler, CMovePlayerTo)
+ ON_MESSAGE(ArmPickedUpFromTableMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
+bool CRestaurantPanHandler::_armPickedUp;
void CRestaurantPanHandler::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_v1, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeQuotedLine(_string2, indent);
+ file->writeNumberLine(_armPickedUp, indent);
+ file->writeQuotedLine(_armlessDestination, indent);
+ file->writeQuotedLine(_armDestination, indent);
CMovePlayerTo::save(file, indent);
}
void CRestaurantPanHandler::load(SimpleFile *file) {
file->readNumber();
- _v1 = file->readNumber();
- _string1 = file->readString();
- _string2 = file->readString();
+ _armPickedUp = file->readNumber();
+ _armlessDestination = file->readString();
+ _armDestination = file->readString();
CMovePlayerTo::load(file);
}
+bool CRestaurantPanHandler::ArmPickedUpFromTableMsg(CArmPickedUpFromTableMsg *msg) {
+ _armPickedUp = true;
+ return true;
+}
+
+bool CRestaurantPanHandler::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ _destination = _armPickedUp ? _armDestination : _armlessDestination;
+ changeView(_destination);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/restaurant_pan_handler.h b/engines/titanic/moves/restaurant_pan_handler.h
index 4925aa685b..9684fd07f5 100644
--- a/engines/titanic/moves/restaurant_pan_handler.h
+++ b/engines/titanic/moves/restaurant_pan_handler.h
@@ -28,11 +28,14 @@
namespace Titanic {
class CRestaurantPanHandler : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool ArmPickedUpFromTableMsg(CArmPickedUpFromTableMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
protected:
- static int _v1;
-
- CString _string1;
- CString _string2;
+ CString _armDestination;
+ CString _armlessDestination;
+public:
+ static bool _armPickedUp;
public:
CLASSDEF;
diff --git a/engines/titanic/moves/restricted_move.cpp b/engines/titanic/moves/restricted_move.cpp
index 5f18dab8ff..37cb1c46dc 100644
--- a/engines/titanic/moves/restricted_move.cpp
+++ b/engines/titanic/moves/restricted_move.cpp
@@ -24,21 +24,59 @@
namespace Titanic {
-CRestrictedMove::CRestrictedMove() : CMovePlayerTo(), _fieldC8(0) {
+BEGIN_MESSAGE_MAP(CRestrictedMove, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
+CRestrictedMove::CRestrictedMove() : CMovePlayerTo(), _classNum(0) {
}
void CRestrictedMove::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldC8, indent);
+ file->writeNumberLine(_classNum, indent);
CMovePlayerTo::save(file, indent);
}
void CRestrictedMove::load(SimpleFile *file) {
file->readNumber();
- _fieldC8 = file->readNumber();
+ _classNum = file->readNumber();
CMovePlayerTo::load(file);
}
+bool CRestrictedMove::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ int classNum = getPassengerClass();
+ if (classNum <= _classNum) {
+ // Okay to change to the given destination
+ changeView(_destination);
+ } else if (classNum != 4) {
+ petDisplayMessage(1, "Passengers of your class are not permitted to enter this area.");
+ } else if (compareRoomNameTo("EmbLobby")) {
+ playSound("a#17.wav");
+ petDisplayMessage(1, "Please check in at the reception desk.");
+ } else if (compareViewNameTo("Titania.Node 1.S")) {
+ playSound("z#226.wav");
+ changeView(_destination);
+ }
+
+ return true;
+}
+
+bool CRestrictedMove::EnterViewMsg(CEnterViewMsg *msg) {
+ int classNum = getPassengerClass();
+ bool flag = classNum > _classNum;
+
+ if (classNum == 4) {
+ if (compareRoomNameTo("EmbLobby"))
+ flag = false;
+ else if (compareViewNameTo("Titania.Node 1.S"))
+ flag = true;
+ }
+
+ _cursorId = flag ? CURSOR_MOVE_FORWARD : CURSOR_INVALID;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/restricted_move.h b/engines/titanic/moves/restricted_move.h
index bdf093c353..639b024701 100644
--- a/engines/titanic/moves/restricted_move.h
+++ b/engines/titanic/moves/restricted_move.h
@@ -28,8 +28,11 @@
namespace Titanic {
class CRestrictedMove : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
protected:
- int _fieldC8;
+ int _classNum;
public:
CLASSDEF;
CRestrictedMove();
diff --git a/engines/titanic/moves/scraliontis_table.cpp b/engines/titanic/moves/scraliontis_table.cpp
index 77d2f9df60..8d39e2104f 100644
--- a/engines/titanic/moves/scraliontis_table.cpp
+++ b/engines/titanic/moves/scraliontis_table.cpp
@@ -24,15 +24,21 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CScraliontisTable, CRestaurantPanHandler)
+ ON_MESSAGE(MouseMoveMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MaitreDDefeatedMsg)
+END_MESSAGE_MAP()
+
CScraliontisTable::CScraliontisTable() : CRestaurantPanHandler(),
- _fieldE0(0), _fieldE4(0), _fieldE8(0), _fieldEC(0) {
+ _fieldE0(false), _counter(0), _ticks(0), _fieldEC(false) {
}
void CScraliontisTable::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
+ file->writeNumberLine(_counter, indent);
+ file->writeNumberLine(_ticks, indent);
file->writeNumberLine(_fieldEC, indent);
CRestaurantPanHandler::save(file, indent);
@@ -41,11 +47,42 @@ void CScraliontisTable::save(SimpleFile *file, int indent) {
void CScraliontisTable::load(SimpleFile *file) {
file->readNumber();
_fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
+ _counter = file->readNumber();
+ _ticks = file->readNumber();
_fieldEC = file->readNumber();
CRestaurantPanHandler::load(file);
}
+bool CScraliontisTable::MouseMoveMsg(CMouseMoveMsg *msg) {
+ if (!_fieldEC && !_fieldE0) {
+ if (++_counter > 20) {
+ CTriggerNPCEvent triggerMsg;
+ triggerMsg.execute("MaitreD");
+ _fieldE0 = true;
+ }
+ }
+
+ return true;
+}
+
+bool CScraliontisTable::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_fieldEC) {
+ changeView(_destination, _armPickedUp ? _armDestination : _armlessDestination);
+ }
+ else if (!_ticks || (getTicksCount() - _ticks) >= 5000) {
+ CTriggerNPCEvent triggerMsg(119);
+ triggerMsg.execute("MaitreD");
+ _ticks = getTicksCount();
+ }
+
+ return true;
+}
+
+bool CScraliontisTable::MaitreDDefeatedMsg(CMaitreDDefeatedMsg *msg) {
+ _cursorId = CURSOR_MOVE_FORWARD;
+ _fieldEC = true;
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/scraliontis_table.h b/engines/titanic/moves/scraliontis_table.h
index 2ce3745654..b0d8c6b5af 100644
--- a/engines/titanic/moves/scraliontis_table.h
+++ b/engines/titanic/moves/scraliontis_table.h
@@ -28,11 +28,15 @@
namespace Titanic {
class CScraliontisTable : public CRestaurantPanHandler {
+ DECLARE_MESSAGE_MAP;
+ bool MouseMoveMsg(CMouseMoveMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MaitreDDefeatedMsg(CMaitreDDefeatedMsg *msg);
private:
- int _fieldE0;
- int _fieldE4;
- int _fieldE8;
- int _fieldEC;
+ bool _fieldE0;
+ int _counter;
+ uint _ticks;
+ bool _fieldEC;
public:
CLASSDEF;
CScraliontisTable();
diff --git a/engines/titanic/moves/trip_down_canal.cpp b/engines/titanic/moves/trip_down_canal.cpp
index c8051dda03..e9818fa96d 100644
--- a/engines/titanic/moves/trip_down_canal.cpp
+++ b/engines/titanic/moves/trip_down_canal.cpp
@@ -24,6 +24,10 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CTripDownCanal, CMovePlayerTo)
+ ON_MESSAGE(MouseButtonDownMsg)
+END_MESSAGE_MAP()
+
CTripDownCanal::CTripDownCanal() : CMovePlayerTo() {
}
@@ -37,4 +41,14 @@ void CTripDownCanal::load(SimpleFile *file) {
CMovePlayerTo::load(file);
}
+bool CTripDownCanal::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (stateGetSeason() == SEASON_WINTER) {
+ petDisplayMessage("Sadly, the Grand Canal transport system is closed for the winter.");
+ } else if (getGameManager()) {
+ changeView(_destination);
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/moves/trip_down_canal.h b/engines/titanic/moves/trip_down_canal.h
index 736caf4131..f43fb05de1 100644
--- a/engines/titanic/moves/trip_down_canal.h
+++ b/engines/titanic/moves/trip_down_canal.h
@@ -28,6 +28,8 @@
namespace Titanic {
class CTripDownCanal : public CMovePlayerTo {
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
public:
CLASSDEF;
CTripDownCanal();
diff --git a/engines/titanic/npcs/barbot.cpp b/engines/titanic/npcs/barbot.cpp
index 8f1c5e6ab3..2524a835f8 100644
--- a/engines/titanic/npcs/barbot.cpp
+++ b/engines/titanic/npcs/barbot.cpp
@@ -21,11 +21,43 @@
*/
#include "titanic/npcs/barbot.h"
+#include "titanic/titanic.h"
namespace Titanic {
int CBarbot::_v0;
+BEGIN_MESSAGE_MAP(CBarbot, CTrueTalkNPC)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(TrueTalkSelfQueueAnimSetMsg)
+ ON_MESSAGE(TrueTalkQueueUpAnimSetMsg)
+ ON_MESSAGE(TrueTalkGetStateValueMsg)
+ ON_MESSAGE(TrueTalkTriggerActionMsg)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(LoadSuccessMsg)
+ ON_MESSAGE(MovieFrameMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(TimerMsg)
+END_MESSAGE_MAP()
+
+CBarbot::FrameRanges::FrameRanges() : Common::Array<FrameRange>() {
+ resize(60);
+ Common::SeekableReadStream *stream = g_vm->_filesManager->getResource("FRAMES/BARBOT");
+ for (int idx = 0; idx < 60; ++idx) {
+ (*this)[idx]._startFrame = stream->readUint32LE();
+ (*this)[idx]._endFrame = stream->readUint32LE();
+ }
+
+ delete stream;
+}
+
+/*------------------------------------------------------------------------*/
+
CBarbot::CBarbot() : CTrueTalkNPC() {
_field108 = 0;
_field10C = 0;
@@ -41,8 +73,8 @@ CBarbot::CBarbot() : CTrueTalkNPC() {
_field134 = 0;
_field138 = 0;
_field13C = -1;
- _field140 = 30;
- _field144 = -1;
+ _volume = 30;
+ _frameNum = -1;
_field148 = -1;
_field14C = 0;
_field150 = 0;
@@ -50,125 +82,6 @@ CBarbot::CBarbot() : CTrueTalkNPC() {
_field158 = -1;
_field15C = 0;
_field160 = 0;
- _field164 = 558;
- _field168 = 585;
- _field16C = 659;
- _field170 = 692;
- _field174 = 802;
- _field178 = 816;
- _field17C = 1941;
- _field180 = 1977;
- _field184 = 1901;
- _field188 = 1941;
- _field18C = 810;
- _field190 = 816;
- _field194 = 857;
- _field198 = 865;
- _field19C = 842;
- _field1A0 = 857;
- _field1A4 = 821;
- _field1A8 = 842;
- _field1AC = 682;
- _field1B0 = 692;
- _field1B4 = 1977;
- _field1B8 = 2018;
- _field1BC = 2140;
- _field1C0 = 2170;
- _field1C4 = 2101;
- _field1C8 = 2139;
- _field1CC = 2018;
- _field1D0 = 2099;
- _field1D4 = 1902;
- _field1D8 = 2015;
- _field1E0 = 1811;
- _field1E4 = 1901;
- _field1E8 = 1810;
- _field1EC = 1703;
- _field1F0 = 1750;
- _field1F4 = 1681;
- _field1F8 = 1702;
- _field1FC = 1642;
-
- _field200 = 1702;
- _field204 = 1571;
- _field208 = 1641;
- _field20C = 1499;
- _field210 = 1570;
- _field214 = 1403;
- _field218 = 1463;
- _field21C = 1464;
- _field220 = 1499;
- _field224 = 1288;
- _field228 = 1295;
- _field22C = 1266;
- _field230 = 1287;
- _field234 = 1245;
- _field238 = 1265;
- _field23C = 1208;
- _field240 = 1244;
- _field244 = 1171;
- _field248 = 1207;
- _field24C = 1120;
- _field250 = 1170;
- _field254 = 1092;
- _field258 = 1120;
- _field25C = 1092;
- _field260 = 1092;
- _field264 = 1044;
- _field268 = 1091;
- _field26C = 1011;
- _field270 = 1043;
- _field274 = 1001;
- _field278 = 1010;
- _field27C = 985;
- _field280 = 1001;
- _field284 = 927;
- _field288 = 984;
- _field28C = 912;
- _field290 = 926;
- _field294 = 898;
- _field298 = 906;
- _field29C = 802;
- _field2A0 = 896;
- _field2A4 = 865;
- _field2A8 = 896;
- _field2AC = 842;
- _field2B0 = 865;
- _field2B4 = 816;
- _field2B8 = 842;
- _field2BC = 802;
- _field2C0 = 842;
- _field2C4 = 740;
- _field2C8 = 740;
- _field2CC = 740;
- _field2D0 = 692;
- _field2D4 = 610;
- _field2D8 = 558;
- _field2E0 = 610;
- _field2E4 = 500;
- _field2E8 = 558;
- _field2EC = 467;
- _field2F0 = 500;
- _field2F4 = 421;
- _field2F8 = 466;
- _field2FC = 349;
- _field300 = 306;
- _field304 = 306;
- _field308 = 348;
- _field30C = 305;
- _field310 = 306;
- _field314 = 281;
- _field318 = 305;
- _field31C = 202;
- _field320 = 281;
- _field324 = 182;
- _field328 = 202;
- _field32C = 165;
- _field330 = 182;
- _field334 = 96;
- _field338 = 165;
- _field33C = 0;
- _field340 = 95;
}
void CBarbot::save(SimpleFile *file, int indent) {
@@ -189,8 +102,8 @@ void CBarbot::save(SimpleFile *file, int indent) {
file->writeNumberLine(_field134, indent);
file->writeNumberLine(_field138, indent);
file->writeNumberLine(_field13C, indent);
- file->writeNumberLine(_field140, indent);
- file->writeNumberLine(_field144, indent);
+ file->writeNumberLine(_volume, indent);
+ file->writeNumberLine(_frameNum, indent);
file->writeNumberLine(_field148, indent);
file->writeNumberLine(_field14C, indent);
file->writeNumberLine(_field150, indent);
@@ -220,8 +133,8 @@ void CBarbot::load(SimpleFile *file) {
_field134 = file->readNumber();
_field138 = file->readNumber();
_field13C = file->readNumber();
- _field140 = file->readNumber();
- _field144 = file->readNumber();
+ _volume = file->readNumber();
+ _frameNum = file->readNumber();
_field148 = file->readNumber();
_field14C = file->readNumber();
_field150 = file->readNumber();
@@ -233,9 +146,588 @@ void CBarbot::load(SimpleFile *file) {
CTrueTalkNPC::load(file);
}
+bool CBarbot::ActMsg(CActMsg *msg) {
+ if (msg->_action == "Vodka") {
+ if (!_field12C) {
+ playRange(_frames[47], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[46]);
+ playRange(_frames[40]);
+ playRange(_frames[7]);
+ playRange(_frames[13]);
+ playRange(_frames[8]);
+ playRange(_frames[40]);
+ playRange(_frames[7]);
+ playRange(_frames[13]);
+ playRange(_frames[8]);
+ playRange(_frames[7]);
+ playRange(_frames[40]);
+ playRange(_frames[13]);
+ playRange(_frames[40]);
+ playRange(_frames[7]);
+ playRange(_frames[8]);
+ playRange(_frames[13]);
+ playRange(_frames[40], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _frameNum = _frames[40]._endFrame;
+ }
+ } else if (msg->_action == "GiveBackVisCentre") {
+ if (_field134) {
+ playRange(_frames[27]);
+ _frameNum = _frames[27]._endFrame;
+ }
+ } else if (msg->_action == "Bird") {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 2;
+ statusMsg.execute("PickUpGlass");
+ _field158 = 3;
+
+ playRange(_frames[32], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[30], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[30]._endFrame;
+
+ if (!_field114 || !_field118 || !_field12C) {
+ playRange(_frames[42], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[42]._endFrame;
+ }
+
+ CActMsg actMsg("InTitilator");
+ actMsg.execute("BeerGlass");
+ } else if (msg->_action == "None") {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 2;
+ statusMsg.execute("PickUpGlass");
+ _field158 = 0;
+
+ playRange(_frames[55], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[54], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[54]._endFrame;
+ } else if (msg->_action == "Mustard" || msg->_action == "Tomato") {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 2;
+ statusMsg.execute("PickUpGlass");
+ _field158 = 1;
+
+ playRange(_frames[55], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[54], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[54]._endFrame;
+
+ CActMsg actMsg("InTitilator");
+ actMsg.execute("BeerGlass");
+ } else if (msg->_action == "Fruit") {
+ if (!_field114) {
+ CActMsg visibleMsg;
+ visibleMsg.execute("LemonOnBar");
+ startTalking(this, 250576);
+ _field114 = 1;
+
+ playRange(_frames[36], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[36]._endFrame;
+
+ if (!_field11C || !_field118 || _field12C) {
+ playRange(_frames[43], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[43]._endFrame;
+ }
+
+ CRemoveFromGameMsg removeMsg;
+ removeMsg.execute("Lemon");
+ }
+ } else if (msg->_action == "CrushedTV") {
+ if (!_field118) {
+ CVisibleMsg visibleMsg;
+ visibleMsg.execute("TVOnBar");
+ startTalking(this, 250584);
+ _field160 = 1;
+
+ playSound("c#5.wav", _volume);
+ playRange(_frames[35], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[34]);
+ playRange(_frames[33], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[33]._endFrame;
+
+ if (!_field11C || !_field114 || !_field12C) {
+ playRange(_frames[41], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[41]._endFrame;
+ }
+
+ CRemoveFromGameMsg removeMsg;
+ removeMsg.execute("CrushedTV");
+ }
+ } else if (msg->_action == "PlayerTakesGlass") {
+ playRange(_frames[53]);
+ _field124 = 0;
+
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("PickUpGlass");
+ } else if (msg->_action == "PlayerTakesVisCentre") {
+ _field128 = 0;
+ loadFrame(0);
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("PickUpVisCentre");
+ } else if (msg->_action == "BellRing1") {
+ startTalking(this, 251105);
+ } else if (msg->_action == "BellRing2") {
+ startTalking(this, 251107);
+ } else if (msg->_action == "BellRing3") {
+ startTalking(this, 250285);
+ } else if (msg->_action == "GoRingBell") {
+ startTalking(this, 250285);
+ } else if (msg->_action == "ClickOnVision") {
+ startTalking(this, 251858);
+ }
+
+ return true;
+}
+
+bool CBarbot::EnterViewMsg(CEnterViewMsg *msg) {
+ // I think this is a remnant of early debugging code
+ if (getName() != "Barbot")
+ playMovie(MOVIE_REPEAT);
+
+ return true;
+}
+
bool CBarbot::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("TODO: Barbot::CEnterRoomMsg");
+ // I think this is a remnant of early debugging code
+ if (getName() != "Barbot")
+ addTimer(g_vm->getRandomNumber(20000));
+
+ return true;
+}
+
+bool CBarbot::TurnOn(CTurnOn *msg) {
+ if (!_fieldC4) {
+ _field13C = -1;
+ setVisible(true);
+
+ CGameObject *glass = findInRoom("BeerGlass");
+ if (!_field130) {
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("BarShelfVisCentre");
+ }
+
+ if (glass && !_field11C) {
+ playRange(_frames[38], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[58], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[57], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[56], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _frameNum = _frames[56]._endFrame;
+ } else {
+ playRange(_frames[38]);
+ playRange(_frames[23], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[21], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[21]._endFrame;
+
+ switch (g_vm->getRandomNumber(2)) {
+ case 0:
+ playRange(_frames[10], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[10]._endFrame;
+ break;
+ case 1:
+ playRange(_frames[12], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[12]._endFrame;
+ break;
+ default:
+ break;
+ }
+ _field124 = 0;
+ }
+
+ _fieldC4 = 1;
+ ++_v0;
+ petSetArea(PET_CONVERSATION);
+ endTalking(this, true);
+ }
+
+ return true;
+}
+
+bool CBarbot::TurnOff(CTurnOff *msg) {
+ if (_fieldC4) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("PickUpGlass");
+ statusMsg.execute("PickUpVisCentre");
+
+ if (_field124) {
+ playRange(_frames[17], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[17]._endFrame;
+ _field124 = 0;
+ }
+
+ if (_field128) {
+ playRange(_frames[28], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _frameNum = _frames[28]._endFrame;
+ _field128 = 0;
+ _field134 = 1;
+ }
+
+ playRange(_frames[29], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ movieEvent(_frames[29]._startFrame);
+ _frameNum = _frames[29]._endFrame;
+ _fieldC4 = 0;
+ }
+
+ return true;
+}
+
+bool CBarbot::LeaveViewMsg(CLeaveViewMsg *msg) {
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ return true;
+}
+
+bool CBarbot::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == _frameNum) {
+ _frameNum = -1;
+ _field14C = getTicksCount();
+ }
+
+ if (msg->_endFrame == _field148) {
+ _field148 = -1;
+ _field150 = getTicksCount();
+ }
+
+ if (msg->_endFrame == _field13C) {
+ if (_field124)
+ playMovie(_frames[53]._startFrame, _frames[53]._startFrame, 0);
+ else if (_field128)
+ playMovie(_frames[27]._endFrame, _frames[27]._endFrame, 0);
+
+ _field13C = -1;
+ return true;
+ }
+
+ if (msg->_endFrame == _frames[58]._endFrame || msg->_endFrame == _frames[21]._endFrame) {
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute("BarShelfVisCentre");
+ }
+
+ if (msg->_endFrame == _frames[57]._endFrame) {
+ startTalking(this, 250575);
+ playSound("c#10.wav", _volume);
+ return true;
+ }
+
+ if (msg->_endFrame == _frames[55]._endFrame) {
+ playSound("c#10.wav", _volume);
+ return true;
+ }
+
+ if (msg->_endFrame == _frames[56]._endFrame
+ || msg->_endFrame == _frames[54]._endFrame) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("PickUpGlass");
+ CMoveToStartPosMsg moveMsg;
+ moveMsg.execute("BeerGlass");
+ return true;
+ }
+
+ if (msg->_endFrame == _frames[30]._endFrame) {
+ _field124 = 0;
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("PickUpGlass");
+ }
+
+ if (msg->_endFrame == _frames[45]._endFrame) {
+ if (!_field130) {
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("BarShelfVisCentre");
+ }
+
+ return true;
+ }
+
+ if (msg->_endFrame == _frames[44]._endFrame) {
+ _field128 = _field130 = 1;
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("PickUpVisCentre");
+ CPuzzleSolvedMsg solvedMsg;
+ solvedMsg.execute("VisionCentre");
+ }
+
+ if (msg->_endFrame == _frames[46]._endFrame) {
+ if (!_field130 && !_field12C && _field11C && _field114 && _field118)
+ startTalking(this, 250571);
+ return true;
+ }
+
+ if (msg->_endFrame == _frames[43]._endFrame
+ || msg->_endFrame == _frames[42]._endFrame
+ || msg->_endFrame == _frames[41]._endFrame) {
+ if (_field124)
+ playMovie(_frames[53]._startFrame, _frames[53]._startFrame, 0);
+ return true;
+ }
+
+ if (msg->_endFrame == _frames[38]._endFrame || msg->_endFrame == _frames[23]._endFrame) {
+ playSound("c#3.wav", _volume);
+ } else if (msg->_endFrame == _frames[36]._endFrame) {
+ playSound("c#6.wav", _volume);
+ }
+ else if (msg->_endFrame == _frames[35]._endFrame) {
+ playSound("c#8.wav", _volume);
+ }
+ else if (msg->_endFrame == _frames[33]._endFrame) {
+ playSound("c#4.wav", _volume);
+ } else if (msg->_endFrame == _frames[32]._endFrame) {
+ startTalking(this, 145);
+ playSound("c#9.wav", _volume);
+ } else if (msg->_endFrame == _frames[47]._endFrame) {
+ playSound("c#9.wav", _volume);
+ _field12C = _field15C = 1;
+ } else if (msg->_endFrame == _frames[30]._endFrame) {
+ playSound("c#4.wav", 60);
+ } else if (msg->_endFrame == _frames[29]._endFrame) {
+ if (!_fieldC4) {
+ performAction(true, nullptr);
+ setVisible(false);
+ CActMsg actMsg("ResetCount");
+ actMsg.execute("BarBell");
+ }
+ } else if (msg->_endFrame == _frames[27]._endFrame) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("PickUpVisCentre");
+ _field128 = 1;
+ _field134 = 0;
+ startTalking(this, 250586);
+ }
+
return true;
}
+bool CBarbot::TrueTalkSelfQueueAnimSetMsg(CTrueTalkSelfQueueAnimSetMsg *msg) {
+ return true;
+}
+
+bool CBarbot::TrueTalkQueueUpAnimSetMsg(CTrueTalkQueueUpAnimSetMsg *msg) {
+ return true;
+}
+
+bool CBarbot::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) {
+ switch (msg->_stateNum) {
+ case 2:
+ if (!_field130) {
+ if (_field15C) {
+ msg->_stateVal = _field134 | 1;
+ return true;
+ }
+ }
+
+ msg->_stateVal = _field134;
+ break;
+
+ case 3:
+ msg->_stateVal = 0;
+ if (_field114)
+ msg->_stateVal = 1;
+ if (_field11C)
+ msg->_stateVal |= 4;
+ if (_field118)
+ msg->_stateVal |= 8;
+ if (_field12C)
+ msg->_stateVal |= 2;
+ break;
+
+ case 9:
+ msg->_stateVal = _field15C ? 1 : 0;
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CBarbot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) {
+ switch (msg->_action) {
+ case 6:
+ if (_field134) {
+ playRange(_frames[27], MOVIE_NOTIFY_OBJECT);
+ _frameNum = _frames[27]._endFrame;
+ } else if (!_field130 && _field15C) {
+ playRange(_frames[45], MOVIE_NOTIFY_OBJECT);
+ playRange(_frames[44], MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ _frameNum = _frames[44]._endFrame;
+ }
+ break;
+
+ case 7: {
+ CActMsg actMsg("Vodka");
+ actMsg.execute(this);
+ break;
+ }
+
+ case 30:
+ _field11C = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CBarbot::FrameMsg(CFrameMsg *msg) {
+ if (!_fieldC4 || _frameNum != -1 || _field148 != -1
+ || (msg->_ticks - _field14C) <= 5000
+ || (msg->_ticks - _field150) <= 1000)
+ return true;
+
+ if (!_field15C) {
+ if (++_field154 > 2) {
+ playRange(_frames[0]);
+ playRange(_frames[1], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[1]._endFrame;
+ _field154 = 0;
+
+ return true;
+ }
+
+ switch (g_vm->getRandomNumber(5)) {
+ case 0:
+ playRange(_frames[4], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[4]._endFrame;
+ break;
+
+ case 1:
+ playRange(_frames[10], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[10]._endFrame;
+ break;
+
+ case 2:
+ playRange(_frames[7], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[7]._endFrame;
+ break;
+
+ case 3:
+ playRange(_frames[0]);
+ playRange(_frames[1], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[1]._endFrame;
+ break;
+
+ case 4:
+ playRange(_frames[3], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[3]._endFrame;
+ break;
+
+ case 5:
+ if (!_field160 && !_field128) {
+ playRange(_frames[15], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[15]._endFrame;
+ }
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ static const int CASES[23] = {
+ 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7
+ };
+ switch (CASES[g_vm->getRandomNumber(22)]) {
+ case 0:
+ playRange(_frames[13], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[13]._endFrame;
+ break;
+
+ case 1:
+ playRange(_frames[4], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[4]._endFrame;
+ break;
+
+ case 2:
+ playRange(_frames[8], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[8]._endFrame;
+ break;
+
+ case 3:
+ playRange(_frames[7], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[7]._endFrame;
+ break;
+
+ case 4:
+ playRange(_frames[10], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[10]._endFrame;
+ break;
+
+ case 5:
+ playRange(_frames[2], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[2]._endFrame;
+ break;
+
+ case 6:
+ playRange(_frames[6], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[6]._endFrame;
+ break;
+
+ default:
+ playRange(_frames[3], MOVIE_NOTIFY_OBJECT);
+ _field148 = _frames[3]._endFrame;
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CBarbot::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ _field14C = _field150 = getTicksCount();
+ _frameNum = -1;
+ _field148 = -1;
+
+ return true;
+}
+
+bool CBarbot::MovieFrameMsg(CMovieFrameMsg *msg) {
+ if (msg->_frameNumber == _frames[29]._startFrame) {
+ playSound("c#2.wav", _volume);
+
+ } else if (msg->_frameNumber == _frames[55]._startFrame
+ || msg->_frameNumber == _frames[32]._startFrame) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("PickUpGlass");
+
+ if (_field158 == 0) {
+ startTalking(this, 250574);
+ } else if (_field158 > 0 && _field158 <= 3) {
+ startTalking(this, 250580);
+ petSetArea(PET_CONVERSATION);
+ }
+
+ _field158 = -1;
+
+ } else if (msg->_frameNumber == _frames[36]._startFrame) {
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("LemonOnBar");
+
+ } else if (msg->_frameNumber == _frames[35]._startFrame) {
+ CVisibleMsg visibleMsg(false);
+ visibleMsg.execute("TVOnBar");
+ }
+
+ return true;
+}
+
+bool CBarbot::TimerMsg(CTimerMsg *msg) {
+ if (!_fieldC4 && compareRoomNameTo("Bar")) {
+ CParrotSpeakMsg speakMsg("Barbot", "AskForDrink");
+ speakMsg.execute("PerchedParrot");
+ addTimer(10000 + getRandomNumber(20000));
+ }
+
+ return true;
+}
+
+void CBarbot::playRange(const FrameRange &range, uint flags) {
+ playMovie(range._startFrame, range._endFrame, flags);
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/barbot.h b/engines/titanic/npcs/barbot.h
index 7557fdd2c6..2bd4cb2f1e 100644
--- a/engines/titanic/npcs/barbot.h
+++ b/engines/titanic/npcs/barbot.h
@@ -29,10 +29,36 @@
namespace Titanic {
class CBarbot : public CTrueTalkNPC {
+ struct FrameRange {
+ int _startFrame;
+ int _endFrame;
+ FrameRange() : _startFrame(0), _endFrame(0) {}
+ };
+ class FrameRanges : public Common::Array<FrameRange> {
+ public:
+ FrameRanges();
+ };
+
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool TrueTalkSelfQueueAnimSetMsg(CTrueTalkSelfQueueAnimSetMsg *msg);
+ bool TrueTalkQueueUpAnimSetMsg(CTrueTalkQueueUpAnimSetMsg *msg);
+ bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg);
+ bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
+ bool MovieFrameMsg(CMovieFrameMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
private:
static int _v0;
private:
+ FrameRanges _frames;
int _field108;
int _field10C;
int _field110;
@@ -47,8 +73,8 @@ private:
int _field134;
int _field138;
int _field13C;
- int _field140;
- int _field144;
+ int _volume;
+ int _frameNum;
int _field148;
int _field14C;
int _field150;
@@ -56,124 +82,11 @@ private:
int _field158;
int _field15C;
int _field160;
- int _field164;
- int _field168;
- int _field16C;
- int _field170;
- int _field174;
- int _field178;
- int _field17C;
- int _field180;
- int _field184;
- int _field188;
- int _field18C;
- int _field190;
- int _field194;
- int _field198;
- int _field19C;
- int _field1A0;
- int _field1A4;
- int _field1A8;
- int _field1AC;
- int _field1B0;
- int _field1B4;
- int _field1B8;
- int _field1BC;
- int _field1C0;
- int _field1C4;
- int _field1C8;
- int _field1CC;
- int _field1D0;
- int _field1D4;
- int _field1D8;
- int _field1E0;
- int _field1E4;
- int _field1E8;
- int _field1EC;
- int _field1F0;
- int _field1F4;
- int _field1F8;
- int _field1FC;
- int _field200;
- int _field204;
- int _field208;
- int _field20C;
- int _field210;
- int _field214;
- int _field218;
- int _field21C;
- int _field220;
- int _field224;
- int _field228;
- int _field22C;
- int _field230;
- int _field234;
- int _field238;
- int _field23C;
- int _field240;
- int _field244;
- int _field248;
- int _field24C;
- int _field250;
- int _field254;
- int _field258;
- int _field25C;
- int _field260;
- int _field264;
- int _field268;
- int _field26C;
- int _field270;
- int _field274;
- int _field278;
- int _field27C;
- int _field280;
- int _field284;
- int _field288;
- int _field28C;
- int _field290;
- int _field294;
- int _field298;
- int _field29C;
- int _field2A0;
- int _field2A4;
- int _field2A8;
- int _field2AC;
- int _field2B0;
- int _field2B4;
- int _field2B8;
- int _field2BC;
- int _field2C0;
- int _field2C4;
- int _field2C8;
- int _field2CC;
- int _field2D0;
- int _field2D4;
- int _field2D8;
- int _field2E0;
- int _field2E4;
- int _field2E8;
- int _field2EC;
- int _field2F0;
- int _field2F4;
- int _field2F8;
- int _field2FC;
- int _field300;
- int _field304;
- int _field308;
- int _field30C;
- int _field310;
- int _field314;
- int _field318;
- int _field31C;
- int _field320;
- int _field324;
- int _field328;
- int _field32C;
- int _field330;
- int _field334;
- int _field338;
- int _field33C;
- int _field340;
+private:
+ /**
+ * Plays a given range of movie frames
+ */
+ void playRange(const FrameRange &range, uint flags = 0);
public:
CLASSDEF;
CBarbot();
diff --git a/engines/titanic/npcs/bellbot.cpp b/engines/titanic/npcs/bellbot.cpp
index 7ee0128a1e..0170491270 100644
--- a/engines/titanic/npcs/bellbot.cpp
+++ b/engines/titanic/npcs/bellbot.cpp
@@ -21,9 +21,28 @@
*/
#include "titanic/npcs/bellbot.h"
+#include "titanic/carry/carry.h"
+#include "titanic/core/room_item.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CBellBot, CTrueTalkNPC)
+ ON_MESSAGE(OnSummonBotMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(Use)
+ ON_MESSAGE(DismissBotMsg)
+ ON_MESSAGE(TrueTalkTriggerActionMsg)
+ ON_MESSAGE(MovieFrameMsg)
+ ON_MESSAGE(PutBotBackInHisBoxMsg)
+ ON_MESSAGE(NPCPlayIdleAnimationMsg)
+ ON_MESSAGE(NPCPlayTalkingAnimationMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(TrueTalkGetStateValueMsg)
+ ON_MESSAGE(TrueTalkNotifySpeechEndedMsg)
+END_MESSAGE_MAP()
+
CBellBot::CBellBot() : CTrueTalkNPC(), _field108(0) {
}
@@ -41,4 +60,217 @@ void CBellBot::load(SimpleFile *file) {
CTrueTalkNPC::load(file);
}
+bool CBellBot::OnSummonBotMsg(COnSummonBotMsg *msg) {
+ if (msg->_value == 1) {
+ _npcFlags |= NPCFLAG_40000;
+ } else {
+ static const char *const ROOM_WAVES[8][2] = {
+ { "EmbLobby", "z#193.wav" },
+ { "PromenadeDeck", "z#191.wav" },
+ { "Arboretum", "z#195.wav" },
+ { "Frozen Arboretum", "z#195.wav" },
+ { "Bar", "z#194.wav" },
+ { "MusicRoom", "z#192.wav" },
+ { "MusicRoomLobby", "z#192.wav" },
+ { "1stClassRestaurant", "z#190.wav" }
+ };
+
+ int idx;
+ for (idx = 0; idx < 8; ++idx) {
+ if (compareRoomNameTo(ROOM_WAVES[idx][0])) {
+ playSound(ROOM_WAVES[idx][1]);
+
+ }
+ }
+ if (idx == 8)
+ playSound("z#147.wav");
+
+ sleep(2000);
+ _npcFlags &= ~NPCFLAG_40000;
+ }
+
+ playClip("Walk On", MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ movieEvent();
+ _npcFlags |= NPCFLAG_10000;
+
+ return true;
+}
+
+bool CBellBot::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (_npcFlags & NPCFLAG_10000) {
+ performAction(1);
+ _npcFlags &= ~NPCFLAG_4;
+ CDismissBotMsg dismissMsg;
+ dismissMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CBellBot::MovieEndMsg(CMovieEndMsg *msg) {
+ if (!(_npcFlags & NPCFLAG_10000)) {
+ CTrueTalkNPC::MovieEndMsg(msg);
+ } else if (clipExistsByEnd("Walk On", msg->_endFrame)) {
+ setPosition(Point(80, 10));
+ loadFrame(543);
+ _npcFlags |= NPCFLAG_4;
+ if (_npcFlags & NPCFLAG_40000) {
+ startTalking(this, 157);
+ _npcFlags &= ~NPCFLAG_40000;
+ }
+
+ endTalking(this, true);
+ petSetArea(PET_CONVERSATION);
+ } else if (clipExistsByEnd("Walk Off", msg->_endFrame)) {
+ CPutBotBackInHisBoxMsg boxMsg;
+ boxMsg.execute(this);
+
+ if (_npcFlags & NPCFLAG_20000)
+ startAnimTimer("SummonDoorbot", 1500);
+ } else {
+ CTrueTalkNPC::MovieEndMsg(msg);
+ }
+
+ return true;
+}
+
+bool CBellBot::Use(CUse *msg) {
+ dynamic_cast<CCarry *>(msg->_item)->_string1 = "Bellbot";
+ return true;
+}
+
+bool CBellBot::DismissBotMsg(CDismissBotMsg *msg) {
+ if (_npcFlags & NPCFLAG_10000) {
+ playClip("Walk Off", MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ if (_npcFlags & NPCFLAG_4) {
+ _npcFlags &= ~NPCFLAG_4;
+ performAction(true);
+ } else {
+ performAction(false);
+ }
+
+ CActMsg actMsg("BellbotDismissed");
+ actMsg.execute("BotIdleSummons");
+ }
+
+ return true;
+}
+
+bool CBellBot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) {
+ switch (msg->_action) {
+ case 1:
+ case 28: {
+ _npcFlags &= ~NPCFLAG_2;
+ CDismissBotMsg dismissMsg;
+ dismissMsg.execute(this);
+ break;
+ }
+
+ case 5:
+ _npcFlags &= ~NPCFLAG_20000;
+ playClip("Walk Off", MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ movieEvent();
+ break;
+
+ case 17: {
+ CActMsg actMsg("ThrowTVDownWell");
+ actMsg.execute("ThrowTVDownWellControl");
+ break;
+ }
+
+ case 29: {
+ CActMsg actMsg("BellbotGetLight");
+ actMsg.execute("BellbotGetLightCutScene");
+ startTalking(this, 158);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CBellBot::MovieFrameMsg(CMovieFrameMsg *msg) {
+ if (clipExistsByStart("Walk Off", msg->_frameNumber)
+ || clipExistsByStart("Walk On", msg->_frameNumber)) {
+ setPosition(Point(20, 10));
+ }
+
+ return true;
+}
+
+bool CBellBot::PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg) {
+ petMoveToHiddenRoom();
+ _npcFlags &= ~NPCFLAG_4;
+ return true;
+}
+
+bool CBellBot::NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg) {
+ static const char *const NAMES[] = {
+ "Sway Side To Side", "Hit Head", "Hands On Hips", "Sway",
+ "Hand Wave", "Slow Sway", "Lean Backwards",
+ "Sway Side To Side 2", "Bob Up And Down", nullptr
+ };
+
+ msg->_names = NAMES;
+ return true;
+}
+
+bool CBellBot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) {
+ static const char *const NAMES[] = {
+ "Hand On Hip Talking", "Hand On Hip Talking", "Hand On Hip Talking",
+ "Sway Side To Side", "Lean Forward", "Hit Head", "Confidential Talking",
+ "Hands On Hips", "Hands On Hips", "Hands On Hips", "Sway", "Laugh",
+ "Hand Wave", "Slow Sway", "Lean Backwards", "Sway Side To Side 2",
+ "Bob Up And Down", "Elbow In Hand", "Elbow In Hand", "Elbow In Hand",
+ nullptr
+ };
+
+ if (msg->_value2 == 2)
+ playClip("Mother Frame", 0);
+ else
+ msg->_names = NAMES;
+
+ return true;
+}
+
+bool CBellBot::TimerMsg(CTimerMsg *msg) {
+ if (msg->_action == "SummonDoorbot") {
+ CTrueTalkNPC::TimerMsg(msg);
+ } else {
+ CRoomItem *room = getRoom();
+ if (room) {
+ CSummonBotMsg botMsg;
+ botMsg._npcName = "Doorbot";
+ botMsg._value = 2;
+ botMsg.execute(room);
+ }
+
+ _npcFlags &= ~NPCFLAG_20000;
+ }
+
+ return true;
+}
+
+bool CBellBot::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) {
+ CPetControl *pet = getPetControl();
+ bool flag = pet ? pet->isRoom59706() : false;
+
+ if (msg->_stateNum == 7)
+ msg->_stateVal = flag ? 1 : 0;
+
+ return true;
+}
+
+bool CBellBot::TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg) {
+ CTrueTalkNPC::TrueTalkNotifySpeechEndedMsg(msg);
+
+ if (msg->_dialogueId == 20991)
+ petDismissBot("DoorBot");
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/bellbot.h b/engines/titanic/npcs/bellbot.h
index 93c4a2857d..c246901cfe 100644
--- a/engines/titanic/npcs/bellbot.h
+++ b/engines/titanic/npcs/bellbot.h
@@ -28,6 +28,20 @@
namespace Titanic {
class CBellBot : public CTrueTalkNPC {
+ DECLARE_MESSAGE_MAP;
+ bool OnSummonBotMsg(COnSummonBotMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool Use(CUse *msg);
+ bool DismissBotMsg(CDismissBotMsg *msg);
+ bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg);
+ bool MovieFrameMsg(CMovieFrameMsg *msg);
+ bool PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg);
+ bool NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg);
+ bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg);
+ bool TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg);
private:
int _field108;
public:
diff --git a/engines/titanic/npcs/bilge_succubus.cpp b/engines/titanic/npcs/bilge_succubus.cpp
new file mode 100644
index 0000000000..16064bf212
--- /dev/null
+++ b/engines/titanic/npcs/bilge_succubus.cpp
@@ -0,0 +1,467 @@
+/* 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 "titanic/npcs/bilge_succubus.h"
+#include "titanic/carry/chicken.h"
+#include "titanic/core/view_item.h"
+#include "titanic/pet_control/pet_control.h"
+
+namespace Titanic {
+
+BEGIN_MESSAGE_MAP(CBilgeSuccUBus, CSuccUBus)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(PETReceiveMsg)
+ ON_MESSAGE(PETDeliverMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(SubAcceptCCarryMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(TrueTalkGetStateValueMsg)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+END_MESSAGE_MAP()
+
+CBilgeSuccUBus::CBilgeSuccUBus() : CSuccUBus(),
+ _bilgeStartFrame1(-1), _bilgeEndFrame1(-1),
+ _bilgeStartFrame2(-1), _bilgeEndFrame2(-1) {
+}
+
+void CBilgeSuccUBus::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(1, indent);
+ file->writeNumberLine(_bilgeStartFrame1, indent);
+ file->writeNumberLine(_bilgeEndFrame1, indent);
+ file->writeNumberLine(_bilgeStartFrame2, indent);
+ file->writeNumberLine(_bilgeEndFrame2, indent);
+
+ CSuccUBus::save(file, indent);
+}
+
+void CBilgeSuccUBus::load(SimpleFile *file) {
+ file->readNumber();
+ _bilgeStartFrame1 = file->readNumber();
+ _bilgeEndFrame1 = file->readNumber();
+ _bilgeStartFrame2 = file->readNumber();
+ _bilgeEndFrame2 = file->readNumber();
+
+ CSuccUBus::load(file);
+}
+
+bool CBilgeSuccUBus::FrameMsg(CFrameMsg *msg) {
+ return true;
+}
+
+bool CBilgeSuccUBus::PETReceiveMsg(CPETReceiveMsg *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (_v2) {
+ if (_startFrame4 >= 0)
+ playMovie(_startFrame4, _endFrame4, MOVIE_GAMESTATE);
+ if (_startFrame5 >= 0)
+ playMovie(_startFrame5, _endFrame5, MOVIE_GAMESTATE);
+
+ playSound("z#28.wav", 70);
+ } else if (!_enabled) {
+ petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present.");
+ return false;
+ } else if (!pet) {
+ return false;
+ } else {
+ uint roomFlags = pet->getRoomFlags();
+ CGameObject *mailObject = findMailByFlags(
+ _v3 && compareRoomNameTo("Titania") ? 3 : _field140,
+ roomFlags);
+
+ if (mailObject) {
+ _mailP = mailObject;
+ if (_startFrame4 >= 0)
+ playMovie(_startFrame4, _endFrame4, MOVIE_GAMESTATE);
+ } else {
+ petDisplayMessage(2, "There is currently nothing to deliver.");
+ }
+ }
+
+ return true;
+}
+
+bool CBilgeSuccUBus::PETDeliverMsg(CPETDeliverMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (!_enabled || !pet)
+ return true;
+
+ uint petRoomFlags = pet->getRoomFlags();
+ CGameObject *mailObject = findMail(petRoomFlags);
+
+ if (!mailObject) {
+ petDisplayMessage(2, "There is currently nothing in the tray to send.");
+ return true;
+ }
+
+ _field19C = 0;
+ _mailP = mailObject;
+
+ uint roomFlags = _roomFlags;
+ if (!pet->testRooms5(roomFlags) ||
+ getPassengerClass() > pet->getMailDest(roomFlags)) {
+ roomFlags = pet->getSpecialRoomFlags("BilgeRoom");
+ _field19C = 1;
+ }
+
+ _isChicken = mailObject->getName() == "Chicken";
+ _isFeathers = mailObject->getName() == "Feathers";
+ _field158 = 0;
+
+ if (_v2) {
+ if (_isFeathers) {
+ startTalking(this, 230022);
+ _field158 = 1;
+
+ if (_startFrame3 >= 0)
+ playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT);
+
+ if (_bilgeStartFrame1 >= 0) {
+ playMovie(_startFrame12, _endFrame12, MOVIE_GAMESTATE);
+ playMovie(_bilgeStartFrame2, _bilgeEndFrame2, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ playMovie(_bilgeStartFrame1, _bilgeEndFrame1, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ inc54();
+ }
+ } else {
+ startTalking(this, 230012);
+ _field158 = 2;
+ if (_startFrame3 >= 0)
+ playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ if (_startFrame4 >= 0)
+ playMovie(_startFrame4, _endFrame4, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ if (_startFrame5 >= 0)
+ playMovie(_startFrame5, _endFrame5, MOVIE_GAMESTATE);
+ }
+ } else {
+ if (_isFeathers) {
+ startTalking(this, 230022);
+ _field158 = 3;
+
+ if (_startFrame3 >= 0)
+ playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ if (_startFrame4 >= 0)
+ playMovie(_startFrame4, _endFrame4, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ if (_startFrame5 >= 0)
+ playMovie(_startFrame5, _endFrame5, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ } else {
+ removeMail(petRoomFlags, roomFlags);
+ startTalking(this, 230012);
+ if (_startFrame3 >= 0) {
+ _field158 = 4;
+ playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CBilgeSuccUBus::MovieEndMsg(CMovieEndMsg *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (msg->_endFrame == _endFrame12) {
+ if (_startFrame10 >= 0)
+ playSound("z#27.wav");
+ } else if (msg->_endFrame == _endFrame10) {
+ if (_startFrame11 >= 0)
+ playSound("z#30.wav");
+ } else {
+ if (_endFrame9 == _endFrame10 && pet) {
+ if (_v2) {
+ startTalking(this, getRandomNumber(1) ? 230062 : 230063);
+ } else if (!findMail(pet->getRoomFlags())) {
+ switch (getRandomNumber(4)) {
+ case 0:
+ startTalking(this, 230001);
+ break;
+ case 1:
+ startTalking(this, 230002);
+ break;
+ case 2:
+ startTalking(this, 230003);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (msg->_endFrame == _endFrame3) {
+ switch (_field158) {
+ case 1:
+ stopSound(_soundHandle);
+ _soundHandle = playSound("z#3.wav");
+ break;
+ case 2:
+ stopSound(_soundHandle);
+ _soundHandle = playSound("z#12.wav");
+ break;
+ case 3:
+ if (_isChicken) {
+ startTalking(this, 230018);
+ _isChicken = false;
+ } else {
+ startTalking(this, 230013);
+ }
+ break;
+ case 4:
+ startTalking(this, 230017);
+ break;
+ default:
+ break;
+ }
+
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+
+ } else if (msg->_endFrame == _bilgeEndFrame2) {
+ playSound("z#25.wav", 70);
+ playSound("z#24.wav", 70);
+
+ } else if (msg->_endFrame == _endFrame4) {
+ if (_mailP) {
+ _mailP->petAddToInventory();
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute(_mailP);
+
+ _mailP = nullptr;
+ petSetArea(PET_INVENTORY);
+
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+ }
+
+ } else if (msg->_endFrame == _bilgeEndFrame1) {
+ changeView("BilgeRoomWith.Node 1.N", "");
+ _v2 = 0;
+ resetMail();
+
+ if (_mailP) {
+ _mailP->petAddToInventory();
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute(_mailP);
+
+ _mailP = nullptr;
+ petSetArea(PET_INVENTORY);
+ }
+
+ startTalking(this, 150);
+ CBodyInBilgeRoomMsg bodyMsg;
+ bodyMsg.execute("Service Elevator Entity");
+ dec54();
+ _field158 = 0;
+
+ } else {
+ _field158 = 0;
+ }
+ }
+
+ return true;
+}
+
+bool CBilgeSuccUBus::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_enabled) {
+ switch (getRandomNumber(4)) {
+ case 0:
+ case 4: {
+ _enabled = false;
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ break;
+ }
+
+ case 1:
+ startTalking(this, 230055);
+ break;
+
+ case 2:
+ startTalking(this, 230067);
+ break;
+
+ case 3:
+ startTalking(this, 230045);
+ break;
+
+ default:
+ break;
+ }
+ } else {
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ _enabled = true;
+ }
+
+ return true;
+}
+
+bool CBilgeSuccUBus::SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (!msg->_item)
+ return false;
+
+ CCarry *item = dynamic_cast<CCarry *>(msg->_item);
+ if (!_enabled || !pet || !item) {
+ item->petAddToInventory();
+ return true;
+ }
+
+ uint petRoomFlags = pet->getRoomFlags();
+ if (mailExists(petRoomFlags)) {
+ petDisplayMessage(2, "The Succ-U-Bus is a Single Entity Delivery Device.");
+ item->petAddToInventory();
+ return true;
+ }
+
+ petContainerRemove(item);
+ pet->phonographAction("");
+ playSound("z#23.wav");
+
+ CChicken *chicken = dynamic_cast<CChicken *>(item);
+ bool chickenFlag = chicken ? chicken->_string6 == "None" : false;
+
+ if (chickenFlag) {
+ if (_startFrame2 >= 0) {
+ startTalking(this, 70219);
+ playMovie(_startFrame2, _endFrame2, 0);
+ }
+
+ if (_startFrame3 >= 0) {
+ _field158 = 5;
+ playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT);
+ }
+
+ CViewItem *view = parseView(item->_fullViewName);
+ if (view) {
+ item->setVisible(false);
+ setPosition(item->_origPos);
+ item->moveUnder(view);
+
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+ } else {
+ return false;
+ }
+ } else {
+ item->addMail(petRoomFlags);
+ if (_startFrame2 >= 0)
+ playMovie(_startFrame2, _endFrame2, 0);
+
+ petSetArea(PET_REMOTE);
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CBilgeSuccUBus::EnterViewMsg(CEnterViewMsg *msg) {
+ petSetRemoteTarget();
+ _mailP = nullptr;
+
+ if (_startFrame8 >= 0)
+ loadFrame(_startFrame8);
+
+ return true;
+}
+
+bool CBilgeSuccUBus::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petDisplayMessage(2, "");
+ petClear();
+
+ if (_soundHandle != -1) {
+ stopSound(_soundHandle);
+ _soundHandle = -1;
+ }
+
+ if (_enabled) {
+ _enabled = false;
+ if (_startFrame10 >= 0)
+ playSound("z#27.wav");
+ }
+
+ performAction(true);
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+
+ return true;
+}
+
+bool CBilgeSuccUBus::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) {
+ if (msg->_stateNum == 1)
+ msg->_stateVal = _enabled;
+
+ return true;
+}
+
+bool CBilgeSuccUBus::TurnOn(CTurnOn *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (pet) {
+ if (_startFrame9 >= 0) {
+ playMovie(_startFrame9, _endFrame9, MOVIE_NOTIFY_OBJECT);
+ playSound("z#26.wav");
+ }
+
+ if (mailExists(pet->getRoomFlags()) && _startFrame2 >= 0)
+ playMovie(_startFrame2, _endFrame2, 0);
+
+ _enabled = true;
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+
+ endTalking(this, true);
+ petSetArea(PET_REMOTE);
+ petHighlightGlyph(16);
+ }
+
+ return true;
+}
+
+bool CBilgeSuccUBus::TurnOff(CTurnOff *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (pet && mailExists(pet->getRoomFlags()) && _startFrame12 >= 0)
+ playMovie(_startFrame12, _endFrame12, MOVIE_NOTIFY_OBJECT);
+ else if (_endFrame12 >= 0)
+ playMovie(_endFrame12, _endFrame12, MOVIE_NOTIFY_OBJECT);
+
+ if (_soundHandle != -1) {
+ stopSound(_soundHandle);
+ _soundHandle = -1;
+ }
+
+ if (_startFrame10 >= 0)
+ playMovie(_startFrame10, _endFrame10, MOVIE_NOTIFY_OBJECT);
+
+ _enabled = false;
+ performAction(true);
+
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+
+ return true;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/npcs/bilge_succubus.h b/engines/titanic/npcs/bilge_succubus.h
new file mode 100644
index 0000000000..754949a306
--- /dev/null
+++ b/engines/titanic/npcs/bilge_succubus.h
@@ -0,0 +1,65 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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 TITANIC_BILGE_SUCCUBUS_H
+#define TITANIC_BILGE_SUCCUBUS_H
+
+#include "titanic/npcs/succubus.h"
+
+namespace Titanic {
+
+class CBilgeSuccUBus : public CSuccUBus {
+ DECLARE_MESSAGE_MAP;
+ bool FrameMsg(CFrameMsg *msg);
+ bool PETReceiveMsg(CPETReceiveMsg *msg);
+ bool PETDeliverMsg(CPETDeliverMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+public:
+ int _bilgeStartFrame1;
+ int _bilgeEndFrame1;
+ int _bilgeStartFrame2;
+ int _bilgeEndFrame2;
+public:
+ CLASSDEF;
+ CBilgeSuccUBus();
+
+ /**
+ * Save the data for the class to file
+ */
+ virtual void save(SimpleFile *file, int indent);
+
+ /**
+ * Load the data for the class from file
+ */
+ virtual void load(SimpleFile *file);
+};
+
+} // End of namespace Titanic
+
+#endif /* TITANIC_BILGE_SUCCUBUS_H */
diff --git a/engines/titanic/npcs/callbot.cpp b/engines/titanic/npcs/callbot.cpp
index eb0d4b71d5..4af9876b35 100644
--- a/engines/titanic/npcs/callbot.cpp
+++ b/engines/titanic/npcs/callbot.cpp
@@ -21,26 +21,54 @@
*/
#include "titanic/npcs/callbot.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
-CCallBot::CCallBot() : CGameObject(), _fieldC8(0) {
+BEGIN_MESSAGE_MAP(CCallBot, CGameObject)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(EnterViewMsg)
+END_MESSAGE_MAP()
+
+CCallBot::CCallBot() : CGameObject(), _enabled(0) {
}
void CCallBot::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string1, indent);
- file->writeNumberLine(_fieldC8, indent);
+ file->writeQuotedLine(_npcName, indent);
+ file->writeNumberLine(_enabled, indent);
CGameObject::save(file, indent);
}
void CCallBot::load(SimpleFile *file) {
file->readNumber();
- _string1 = file->readString();
- _fieldC8 = file->readNumber();
+ _npcName = file->readString();
+ _enabled = file->readNumber();
CGameObject::load(file);
}
+bool CCallBot::TurnOn(CTurnOn *msg) {
+ _enabled = true;
+ return true;
+}
+
+bool CCallBot::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_enabled) {
+ CRoomItem *room = getRoom();
+
+ if (room) {
+ CSummonBotQueryMsg queryMsg;
+ queryMsg._npcName = _npcName;
+ if (queryMsg.execute(room))
+ petOnSummonBot(_npcName, 0);
+ }
+
+ _enabled = false;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/callbot.h b/engines/titanic/npcs/callbot.h
index 9b89d59d3f..ca0e0c55b2 100644
--- a/engines/titanic/npcs/callbot.h
+++ b/engines/titanic/npcs/callbot.h
@@ -28,9 +28,12 @@
namespace Titanic {
class CCallBot : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool TurnOn(CTurnOn *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
protected:
- CString _string1;
- int _fieldC8;
+ CString _npcName;
+ bool _enabled;
public:
CLASSDEF;
CCallBot();
diff --git a/engines/titanic/npcs/character.cpp b/engines/titanic/npcs/character.cpp
index ed36db16d3..2713a6a1aa 100644
--- a/engines/titanic/npcs/character.cpp
+++ b/engines/titanic/npcs/character.cpp
@@ -30,13 +30,13 @@ BEGIN_MESSAGE_MAP(CCharacter, CGameObject)
ON_MESSAGE(TurnOff)
END_MESSAGE_MAP()
-CCharacter::CCharacter() : CGameObject(), _fieldBC(0), _fieldC0(0), _fieldC4(1) {
+CCharacter::CCharacter() : CGameObject(), _startFrame(0), _endFrame(0), _fieldC4(1) {
}
void CCharacter::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldBC, indent);
- file->writeNumberLine(_fieldC0, indent);
+ file->writeNumberLine(_startFrame, indent);
+ file->writeNumberLine(_endFrame, indent);
file->writeNumberLine(_fieldC4, indent);
file->writeQuotedLine(_charName, indent);
@@ -45,8 +45,8 @@ void CCharacter::save(SimpleFile *file, int indent) {
void CCharacter::load(SimpleFile *file) {
file->readNumber();
- _fieldBC = file->readNumber();
- _fieldC0 = file->readNumber();
+ _startFrame = file->readNumber();
+ _endFrame = file->readNumber();
_fieldC4 = file->readNumber();
_charName = file->readString();
diff --git a/engines/titanic/npcs/character.h b/engines/titanic/npcs/character.h
index 4912740189..e27cf4ec35 100644
--- a/engines/titanic/npcs/character.h
+++ b/engines/titanic/npcs/character.h
@@ -33,8 +33,8 @@ class CCharacter : public CGameObject {
bool TurnOn(CTurnOn *msg);
bool TurnOff(CTurnOff *msg);
protected:
- int _fieldBC;
- int _fieldC0;
+ int _startFrame;
+ int _endFrame;
int _fieldC4;
CString _charName;
public:
diff --git a/engines/titanic/npcs/doorbot.cpp b/engines/titanic/npcs/doorbot.cpp
index 76db55f92c..41ef2b2366 100644
--- a/engines/titanic/npcs/doorbot.cpp
+++ b/engines/titanic/npcs/doorbot.cpp
@@ -21,15 +21,35 @@
*/
#include "titanic/npcs/doorbot.h"
+#include "titanic/core/room_item.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CDoorbot, CTrueTalkNPC)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(OnSummonBotMsg)
+ ON_MESSAGE(TrueTalkTriggerActionMsg)
+ ON_MESSAGE(DoorbotNeededInHomeMsg)
+ ON_MESSAGE(DoorbotNeededInElevatorMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(NPCPlayTalkingAnimationMsg)
+ ON_MESSAGE(NPCPlayIdleAnimationMsg)
+ ON_MESSAGE(PutBotBackInHisBoxMsg)
+ ON_MESSAGE(DismissBotMsg)
+ ON_MESSAGE(MovieFrameMsg)
+ ON_MESSAGE(TrueTalkNotifySpeechEndedMsg)
+ ON_MESSAGE(TextInputMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
int CDoorbot::_v1;
int CDoorbot::_v2;
CDoorbot::CDoorbot() : CTrueTalkNPC() {
_field108 = 0;
- _field10C = 0;
+ _timerId = 0;
_field110 = 0;
_field114 = 0;
}
@@ -40,7 +60,7 @@ void CDoorbot::save(SimpleFile *file, int indent) {
file->writeNumberLine(_v2, indent);
file->writeNumberLine(_field108, indent);
- file->writeNumberLine(_field10C, indent);
+ file->writeNumberLine(_timerId, indent);
file->writeNumberLine(_field110, indent);
file->writeNumberLine(_field114, indent);
@@ -53,11 +73,498 @@ void CDoorbot::load(SimpleFile *file) {
_v2 = file->readNumber();
_field108 = file->readNumber();
- _field10C = file->readNumber();
+ _timerId = file->readNumber();
_field110 = file->readNumber();
_field114 = file->readNumber();
CTrueTalkNPC::load(file);
}
+bool CDoorbot::MovieEndMsg(CMovieEndMsg *msg) {
+ if (_npcFlags & NPCFLAG_8000000) {
+ switch (_field108) {
+ case 3:
+ startTalking(this, 221482);
+ _field108 = 4;
+ break;
+
+ case 6:
+ if (clipExistsByEnd("Cloak On", msg->_endFrame)) {
+ petShow();
+ setState1C(true);
+ changeView("ServiceElevator.Node 1.S");
+ changeView("ServiceElevator.Node 1.N");
+ }
+ break;
+
+ case 7:
+ startTalking(this, 221467);
+ _field108 = 8;
+ break;
+
+ case 9:
+ startTalking(this, 221468);
+ break;
+
+ case 11:
+ changeView("ServiceElevator.Node 1.S");
+ changeView("MoonEmbLobby.Node 1.NE");
+ break;
+
+ default:
+ break;
+ }
+
+ CTrueTalkNPC::MovieEndMsg(msg);
+ } else if (_npcFlags & NPCFLAG_100000) {
+ if (clipExistsByEnd("Cloak Off", msg->_endFrame)) {
+ _npcFlags = (_npcFlags & ~NPCFLAG_8) | NPCFLAG_4;
+ endTalking(this, false);
+ startTalking(this, 221474);
+ _npcFlags &= ~NPCFLAG_8000000;
+ _field108 = 0;
+ } else if (clipExistsByEnd("Cloak On", msg->_endFrame)) {
+ petShow();
+ setState1C(true);
+ changeView("ServiceElevator.Node 1.S");
+ } else {
+ CTrueTalkNPC::MovieEndMsg(msg);
+ }
+ } else if (_npcFlags & NPCFLAG_400000) {
+ if (clipExistsByEnd("Whizz On Left", msg->_endFrame)
+ || clipExistsByEnd("Whizz On Right", msg->_endFrame)) {
+ setPosition(Point((600 - _bounds.width()) / 2 + 18, 42));
+ loadFrame(0);
+ endTalking(this, true);
+ _npcFlags |= NPCFLAG_4;
+ petSetArea(PET_CONVERSATION);
+ } else if (clipExistsByEnd("Whizz Off Left", msg->_endFrame)
+ || clipExistsByEnd("Whizz Off Right", msg->_endFrame)) {
+ CPutBotBackInHisBoxMsg boxMsg;
+ boxMsg.execute(this);
+ if (_npcFlags & NPCFLAG_4000000)
+ startAnimTimer("SummonBellbot", 1500);
+ } else {
+ CTrueTalkNPC::MovieEndMsg(msg);
+ }
+ } else {
+ CTrueTalkNPC::MovieEndMsg(msg);
+ }
+
+ return true;
+}
+
+bool CDoorbot::OnSummonBotMsg(COnSummonBotMsg *msg) {
+ const char *const ROOM_WAVES[8][2] = {
+ { "EmbLobby", "z#186.wav" },
+ { "PromenadeDeck", "z#184.wav" },
+ { "Arboretum", "z#188.wav" },
+ { "Frozen Arboretum", "z#188.wav" },
+ { "Bar", "z#187.wav" },
+ { "MusicRoom", "z#185.wav" },
+ { "MusicRoomLobby", "z#185.wav" },
+ { "1stClassRestaurant", "z#183.wav" },
+ };
+
+ if (msg->_value != -1) {
+ int idx;
+ for (idx = 0; idx < 8; ++idx) {
+ if (compareRoomNameTo(ROOM_WAVES[idx][0])) {
+ playSound(ROOM_WAVES[idx][1]);
+
+ }
+ }
+ if (idx == 8)
+ playSound("z#146.wav");
+
+ sleep(2000);
+ }
+
+ playClip(getRandomNumber(1) ? "Whizz On Left" : "Whizz On Right",
+ MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ movieEvent();
+ _npcFlags |= NPCFLAG_400000;
+
+ return true;
+}
+
+bool CDoorbot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) {
+ switch (msg->_action) {
+ case 3:
+ playClip("Cloak On", MOVIE_NOTIFY_OBJECT);
+ break;
+
+ case 4:
+ _npcFlags = (_npcFlags & ~NPCFLAG_2) | NPCFLAG_4000000;
+ playClip("Whizz Off Left", MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ break;
+
+ case 28: {
+ _npcFlags &= ~(NPCFLAG_2 | NPCFLAG_4);
+ CDismissBotMsg dismissMsg;
+ dismissMsg.execute(this);
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CDoorbot::DoorbotNeededInHomeMsg(CDoorbotNeededInHomeMsg *msg) {
+ moveToView();
+ setPosition(Point(90, 42));
+ _npcFlags = NPCFLAG_100000;
+
+ stopMovie();
+ playClip("Cloak Off", MOVIE_NOTIFY_OBJECT);
+
+ _npcFlags |= NPCFLAG_8;
+ return true;
+}
+
+bool CDoorbot::DoorbotNeededInElevatorMsg(CDoorbotNeededInElevatorMsg *msg) {
+ moveToView("ServiceElevator.Node 1.N");
+ setPosition(Point(100, 42));
+
+ if (_npcFlags & NPCFLAG_8000000) {
+ _field108 = 7;
+ _npcFlags |= NPCFLAG_200000;
+ loadFrame(797);
+ } else {
+ _npcFlags = 0;
+ if (msg->_value)
+ endTalking(this, true);
+ }
+
+ return true;
+}
+
+bool CDoorbot::LeaveViewMsg(CLeaveViewMsg *msg) {
+ if (!(_npcFlags & NPCFLAG_8000000) && (_npcFlags & NPCFLAG_400000)) {
+ performAction(true);
+ _npcFlags &= ~NPCFLAG_4;
+ }
+
+ return true;
+}
+
+bool CDoorbot::TimerMsg(CTimerMsg *msg) {
+ if (msg->_action == "NPCIdleAnim") {
+ return CTrueTalkNPC::TimerMsg(msg);
+ } else if (_npcFlags & NPCFLAG_8000000) {
+ switch (msg->_actionVal) {
+ case 0:
+ startTalking(this, 221475);
+ break;
+
+ case 1:
+ startTalking(this, 221476);
+ break;
+
+ case 2:
+ startTalking(this, 221477);
+ break;
+
+ case 3:
+ playClip("DoubleTake Start", 0);
+ playClip("DoubleTake End", 0);
+ playClip("DoubleTake Start", 0);
+ playClip("DoubleTake End", MOVIE_NOTIFY_OBJECT);
+ _field108 = 3;
+ break;
+
+ case 4:
+ startTalking(this, 221483);
+ lockInputHandler();
+ _field114 = true;
+ break;
+
+ case 5:
+ lockInputHandler();
+ mouseLockE4();
+ _field114 = true;
+ startTalking(this, 221485);
+ break;
+
+ case 6:
+ CMouseButtonDownMsg::generate();
+ mouseSetPosition(Point(200, 430), 2500);
+ _timerId = addTimer(7, 2500, 0);
+ break;
+
+ case 7:
+ CMouseButtonDownMsg::generate();
+ startTalking(this, 221486);
+ mouseUnlockE4();
+ unlockInputHandler();
+ _field114 = false;
+ disableMouse();
+ break;
+
+ default:
+ break;
+ }
+ } else if (msg->_action == "SummonBellbot") {
+ CRoomItem *room = getRoom();
+ if (room) {
+ CSummonBotMsg botMsg;
+ botMsg._npcName = "Bellbot";
+ botMsg.execute(room);
+ }
+
+ _npcFlags &= ~NPCFLAG_4000000;
+ }
+
+ return true;
+}
+
+bool CDoorbot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) {
+ const char *const NAMES1[] = {
+ "Mutter Aside", "Rub Chin", "Drunken Eye Roll", "Drunken Head Move",
+ "Look down and mutter", "Look side to side", "Gesture forward and around",
+ "Arms behind back", "Look down", "Rolling around", "Hold face",
+ "Touch chin", "Cross hands in front", nullptr
+ };
+ const char *const NAMES2[] = {
+ "SE Talking 1", "SE Talking 2", "SE Talking 3", "SE Talking 4"
+ };
+ const char *const NAMES3[] = {
+ "SE Ask For Help", nullptr
+ };
+
+ if (msg->_value2 != 2) {
+ if (_npcFlags & NPCFLAG_200000) {
+ if (_field108 == 8 || _field110) {
+ msg->_names = NAMES2;
+ } else if (_field108 == 9) {
+ msg->_names = NAMES3;
+ _field108 = 10;
+ }
+ } else if (_npcFlags & (NPCFLAG_100000 | NPCFLAG_400000)) {
+ msg->_names = NAMES1;
+ }
+ }
+
+ return true;
+}
+
+bool CDoorbot::NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg) {
+ const char *const NAMES[] = {
+ "Hand swivel", "Prompt Push", "Eye Roll", "Say something", nullptr
+ };
+
+ if (!(_npcFlags & (NPCFLAG_100000 | NPCFLAG_200000))
+ && (_npcFlags & NPCFLAG_400000))
+ msg->_names = NAMES;
+
+ return true;
+}
+
+bool CDoorbot::PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg) {
+ petMoveToHiddenRoom();
+ _npcFlags &= ~(NPCFLAG_4 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_8000000);
+ if (msg->_value)
+ performAction(true);
+
+ return true;
+}
+
+bool CDoorbot::DismissBotMsg(CDismissBotMsg *msg) {
+ if (_npcFlags & NPCFLAG_400000) {
+ playClip(getRandomNumber(1) ? "Whizz Off Left" : "Whizz Off Right",
+ MOVIE_STOP_PREVIOUS | MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ movieEvent();
+
+ if (_npcFlags & NPCFLAG_4) {
+ _npcFlags &= ~NPCFLAG_4;
+ performAction(true);
+ } else {
+ performAction(false);
+ }
+
+ CActMsg actMsg("DoorbotDismissed");
+ actMsg.execute("BotIdleSummons");
+ }
+
+ return true;
+}
+
+bool CDoorbot::MovieFrameMsg(CMovieFrameMsg *msg) {
+ if (clipExistsByStart("Whizz Off Left", msg->_frameNumber)
+ || clipExistsByStart("Whizz On Left", msg->_frameNumber)) {
+ setPosition(Point(20, 42));
+ } else if (clipExistsByStart("Whizz Off Right", msg->_frameNumber)
+ || clipExistsByStart("Whizz On Right", msg->_frameNumber)) {
+ setPosition(Point(620 - _bounds.width(), 42));
+ }
+
+ return true;
+}
+
+bool CDoorbot::TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg) {
+ if (_npcFlags & NPCFLAG_8000000) {
+ switch (msg->_dialogueId) {
+ case 10552:
+ playClip("SE Try Buttons", MOVIE_NOTIFY_OBJECT);
+ _field108 = 9;
+ break;
+
+ case 10553:
+ enableMouse();
+ break;
+
+ case 10557:
+ playClip("SE Move To Right", MOVIE_NOTIFY_OBJECT);
+ _field108 = 11;
+ break;
+
+ case 10559:
+ stopAnimTimer(_timerId);
+ _timerId = addTimer(0, 2500, 0);
+ break;
+
+ case 10560:
+ petShow();
+ petSetArea(PET_CONVERSATION);
+ stopAnimTimer(_timerId);
+ _timerId = addTimer(1, 1000, 0);
+ break;
+
+ case 10561:
+ enableMouse();
+ _field108 = 1;
+ stopAnimTimer(_timerId);
+ _timerId = addTimer(2, 10000, 0);
+ break;
+
+ case 10562:
+ if (_field108 == 1) {
+ stopAnimTimer(_timerId);
+ _timerId = addTimer(2, getRandomNumber(5000), 0);
+ }
+ break;
+
+ case 10563:
+ case 10564:
+ disableMouse();
+ startTalking(this, 221480);
+ break;
+
+ case 10565:
+ startTalking(this, 221481);
+ break;
+
+ case 10566:
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+ if (_field110 == 2) {
+ playClip("Cloak On", MOVIE_NOTIFY_OBJECT);
+ _field108 = 6;
+ } else {
+ _timerId = addTimer(3, 2000, 0);
+ }
+ break;
+
+ case 10567: {
+ CActMsg actMsg("BecomeGettable");
+ actMsg.execute("Photograph");
+ enableMouse();
+ stopAnimTimer(_timerId);
+ _timerId = addTimer(4, 5000, 0);
+ break;
+ }
+
+ case 10568:
+ mouseLockE4();
+ mouseSetPosition(Point(600, 250), 2500);
+ _timerId = addTimer(6, 2500, 0);
+ break;
+
+ case 10569:
+ if (_field110 != 2) {
+ stopAnimTimer(_timerId);
+ _timerId = addTimer(5, 3000, 0);
+ }
+ break;
+
+ case 10570:
+ mouseSetPosition(Point(200, 430), 2500);
+ _timerId = addTimer(7, 3000, 0);
+ break;
+
+ case 10571:
+ playClip("Cloak On", MOVIE_NOTIFY_OBJECT);
+ _field108 = 6;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool CDoorbot::TextInputMsg(CTextInputMsg *msg) {
+ if (!(_npcFlags & NPCFLAG_8000000))
+ return CTrueTalkNPC::TextInputMsg(msg);
+
+ if (_field108 == 1) {
+ stopAnimTimer(_timerId);
+ _field108 = 2;
+ _timerId = 0;
+
+ if (msg->_input == "yes" || msg->_input == "yeah"
+ || msg->_input == "yea" || msg->_input == "yup"
+ || msg->_input == "yep" || msg->_input == "sure"
+ || msg->_input == "alright" || msg->_input == "all right"
+ || msg->_input == "ok") {
+ startTalking(this, 221479);
+ } else {
+ startTalking(this, 221478);
+ }
+ }
+
+ return true;
+}
+
+bool CDoorbot::EnterViewMsg(CEnterViewMsg *msg) {
+ if ((_npcFlags & NPCFLAG_8000000) && _field108 == 7)
+ playClip("SE Move And Turn", MOVIE_NOTIFY_OBJECT);
+
+ return true;
+}
+
+bool CDoorbot::ActMsg(CActMsg *msg) {
+ if (msg->_action == "DoorbotPlayerPressedTopButton") {
+ disableMouse();
+ startTalking(this, 221471);
+ } else if (msg->_action == "DoorbotPlayerPressedMiddleButton") {
+ startTalking(this, 221470);
+ }
+ else if (msg->_action == "DoorbotPlayerPressedBottomButton") {
+ startTalking(this, 221469);
+ } else if (msg->_action == "DoorbotReachedEmbLobby") {
+ startTalking(this, 221472);
+ } else if (msg->_action == "PlayerPicksUpPhoto") {
+ _field110 = 1;
+ if (!_field114 && _field108 == 4) {
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+ _field108 = 5;
+ startTalking(this, 221484);
+ }
+ } else if (msg->_action == "PlayerPutsPhotoInPet") {
+ _field110 = 2;
+ if (!_field114 && _field108 == 5) {
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+ startTalking(this, 221486);
+ disableMouse();
+ }
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/doorbot.h b/engines/titanic/npcs/doorbot.h
index b62026c7d9..9095ebc7e7 100644
--- a/engines/titanic/npcs/doorbot.h
+++ b/engines/titanic/npcs/doorbot.h
@@ -28,12 +28,29 @@
namespace Titanic {
class CDoorbot : public CTrueTalkNPC {
+ DECLARE_MESSAGE_MAP;
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool OnSummonBotMsg(COnSummonBotMsg *msg);
+ bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg);
+ bool DoorbotNeededInHomeMsg(CDoorbotNeededInHomeMsg *msg);
+ bool DoorbotNeededInElevatorMsg(CDoorbotNeededInElevatorMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg);
+ bool NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg);
+ bool PutBotBackInHisBoxMsg(CPutBotBackInHisBoxMsg *msg);
+ bool DismissBotMsg(CDismissBotMsg *msg);
+ bool MovieFrameMsg(CMovieFrameMsg *msg);
+ bool TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg);
+ bool TextInputMsg(CTextInputMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool ActMsg(CActMsg *msg);
private:
static int _v1;
static int _v2;
private:
int _field108;
- int _field10C;
+ int _timerId;
int _field110;
int _field114;
public:
diff --git a/engines/titanic/npcs/liftbot.cpp b/engines/titanic/npcs/liftbot.cpp
index 43daa017c1..272617ee62 100644
--- a/engines/titanic/npcs/liftbot.cpp
+++ b/engines/titanic/npcs/liftbot.cpp
@@ -21,35 +21,166 @@
*/
#include "titanic/npcs/liftbot.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
-int CLiftBot::_v1;
-int CLiftBot::_v2;
+BEGIN_MESSAGE_MAP(CLiftBot, CTrueTalkNPC)
+ ON_MESSAGE(TextInputMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(EnterRoomMsg)
+ ON_MESSAGE(TrueTalkTriggerActionMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(TrueTalkGetStateValueMsg)
+ ON_MESSAGE(NPCPlayTalkingAnimationMsg)
+ ON_MESSAGE(ActMsg)
+END_MESSAGE_MAP()
+
+bool CLiftBot::_flag;
+bool CLiftBot::_enabled;
CLiftBot::CLiftBot() : CTrueTalkNPC(), _field108(1) {
}
void CLiftBot::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_v1, indent);
+ file->writeNumberLine(_flag, indent);
file->writeNumberLine(_field108, indent);
- file->writeNumberLine(_v2, indent);
+ file->writeNumberLine(_enabled, indent);
CTrueTalkNPC::save(file, indent);
}
void CLiftBot::load(SimpleFile *file) {
file->readNumber();
- _v1 = file->readNumber();
+ _flag = file->readNumber();
_field108 = file->readNumber();
- _v2 = file->readNumber();
+ _enabled = file->readNumber();
CTrueTalkNPC::load(file);
}
+bool CLiftBot::TextInputMsg(CTextInputMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (_enabled || pet->getRoomsElevatorNum() != 4) {
+ if (getName() != "LiftBot") {
+ CViewItem *view = findView();
+ processInput(msg, view);
+ }
+ }
+
+ return true;
+}
+
+bool CLiftBot::EnterViewMsg(CEnterViewMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (!_enabled && pet->getRoomsElevatorNum() == 4) {
+ loadFrame(700);
+ } else if (!_flag) {
+ if (getName() != "LiftBot") {
+ CViewItem *view = findView();
+ endTalking(this, true, view);
+ petSetArea(PET_CONVERSATION);
+ _flag = 1;
+ }
+ }
+
+ return true;
+}
+
bool CLiftBot::EnterRoomMsg(CEnterRoomMsg *msg) {
- warning("CLiftBot::handleEvent");
+ _flag = 0;
+ changeView("Lift.Node 1.W", "");
+ return true;
+}
+
+bool CLiftBot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) {
+ if (msg->_action == 2 && msg->_param1 != _field108) {
+ CStatusChangeMsg statusMsg(_field108, msg->_param1, false);
+ statusMsg.execute("Well");
+
+ _field108 = msg->_param1;
+ }
+
+ return true;
+}
+
+bool CLiftBot::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ if (getName() != "LiftBot")
+ performAction(false);
+
+ return true;
+}
+
+bool CLiftBot::TurnOff(CTurnOff *msg) {
+ _enabled = false;
+ return true;
+}
+
+bool CLiftBot::TurnOn(CTurnOn *msg) {
+ _enabled = true;
+ if (!_flag) {
+ if (isEquals("LiftBotTalking")) {
+ endTalking(this, MOVIE_REPEAT, findView());
+ petSetArea(PET_CONVERSATION);
+ _flag = true;
+ }
+ }
+
+ return true;
+}
+
+bool CLiftBot::LeaveViewMsg(CLeaveViewMsg *msg) {
+ return true;
+}
+
+bool CLiftBot::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) {
+ if (msg->_stateNum == 4) {
+ CPetControl *pet = getPetControl();
+ if (pet)
+ msg->_stateVal = pet->getAssignedFloorNum();
+ } else if (msg->_stateNum == 5) {
+ msg->_stateVal = _field108;
+ } else if (msg->_stateNum == 6) {
+ CPetControl *pet = getPetControl();
+ if (pet)
+ msg->_stateVal = pet->getRoomsElevatorNum();
+ } else {
+ msg->_stateVal = _field108;
+ }
+
+ return true;
+}
+
+bool CLiftBot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) {
+ const char *const NAMES[] = {
+ "Groaning", "Groaning 2", "Talking 1", "Talking 2", "Talking 3",
+ "Happy Talking", "Complaining", "Aggressive", "Explaining",
+ "Happy Talking 2", "Happy Talking 3", "Happy Talking 4"
+ "Confidential", nullptr
+ };
+
+ if (msg->_value2 == 2)
+ playClip("At Rest", 0);
+ else
+ msg->_names = NAMES;
+ return true;
+}
+
+bool CLiftBot::ActMsg(CActMsg *msg) {
+ if (msg->_action == "ActivateLift") {
+ _enabled = true;
+ CViewItem *view = findView();
+ endTalking(this, true, view);
+ startTalking(this, 155, view);
+ } else if (msg->_action == "LiftArrive") {
+ CViewItem *view = findView();
+ startTalking(this, 156, view);
+ }
+
return true;
}
diff --git a/engines/titanic/npcs/liftbot.h b/engines/titanic/npcs/liftbot.h
index 7550a8a6f0..ccac53d5c7 100644
--- a/engines/titanic/npcs/liftbot.h
+++ b/engines/titanic/npcs/liftbot.h
@@ -29,10 +29,21 @@
namespace Titanic {
class CLiftBot : public CTrueTalkNPC {
+ DECLARE_MESSAGE_MAP;
+ bool TextInputMsg(CTextInputMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
bool EnterRoomMsg(CEnterRoomMsg *msg);
+ bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg);
+ bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg);
+ bool ActMsg(CActMsg *msg);
private:
- static int _v1;
- static int _v2;
+ static bool _flag;
+ static bool _enabled;
private:
int _field108;
public:
diff --git a/engines/titanic/npcs/maitre_d.cpp b/engines/titanic/npcs/maitre_d.cpp
index 903f3a49c9..09444f5611 100644
--- a/engines/titanic/npcs/maitre_d.cpp
+++ b/engines/titanic/npcs/maitre_d.cpp
@@ -21,14 +21,30 @@
*/
#include "titanic/npcs/maitre_d.h"
+#include "titanic/core/room_item.h"
+#include "titanic/sound/music_room.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CMaitreD, CTrueTalkNPC)
+ ON_MESSAGE(RestaurantMusicChanged)
+ ON_MESSAGE(TrueTalkTriggerActionMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(NPCPlayTalkingAnimationMsg)
+ ON_MESSAGE(TimerMsg)
+ ON_MESSAGE(TrueTalkNotifySpeechStartedMsg)
+ ON_MESSAGE(TrueTalkNotifySpeechEndedMsg)
+ ON_MESSAGE(LoadSuccessMsg)
+ ON_MESSAGE(TextInputMsg)
+ ON_MESSAGE(TriggerNPCEvent)
+END_MESSAGE_MAP()
+
int CMaitreD::_v1;
CMaitreD::CMaitreD() : CTrueTalkNPC(),
_string2("z#40.wav"), _string3("z#40.wav"), _field108(0), _field118(1),
- _field11C(0), _field12C(0), _field130(1), _field134(0), _field138(0) {
+ _field11C(0), _field12C(0), _field130(1), _field134(0), _timerId(0) {
}
void CMaitreD::save(SimpleFile *file, int indent) {
@@ -43,7 +59,7 @@ void CMaitreD::save(SimpleFile *file, int indent) {
file->writeNumberLine(_v1, indent);
file->writeNumberLine(_field134, indent);
- file->writeNumberLine(_field138, indent);
+ file->writeNumberLine(_timerId, indent);
CTrueTalkNPC::save(file, indent);
}
@@ -60,9 +76,144 @@ void CMaitreD::load(SimpleFile *file) {
_v1 = file->readNumber();
_field134 = file->readNumber();
- _field138 = file->readNumber();
+ _timerId = file->readNumber();
CTrueTalkNPC::load(file);
}
+bool CMaitreD::RestaurantMusicChanged(CRestaurantMusicChanged *msg) {
+ if (msg->_value.empty()) {
+ _field118 = 0;
+ } else {
+ _string3 = msg->_value;
+ _field118 = _field11C = 1;
+ }
+
+ return true;
+}
+
+bool CMaitreD::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) {
+ if (msg->_action == 8) {
+ _field12C = 1;
+ stopAnimTimer(_timerId);
+ _timerId = startAnimTimer("MD Fight", 3500, 0);
+ } else if (msg->_action == 9) {
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+ } else if (msg->_action == 10) {
+ _field12C = 0;
+ _v1 = 1;
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+
+ CMaitreDDefeatedMsg defeatedMsg;
+ defeatedMsg.execute(findRoom());
+ }
+
+ return true;
+}
+
+bool CMaitreD::EnterViewMsg(CEnterViewMsg *msg) {
+ endTalking(this, true, findView());
+ _field12C = _field134;
+
+ if (_string3 == "STMusic" && (!_field11C || _string2 == _string3))
+ return true;
+
+ if (_string3.contains("nasty ambient"))
+ startTalking(this, 111, findView());
+ else if (!CMusicRoom::_musicHandler->checkSound(1))
+ startTalking(this, 114, findView());
+ else if (!CMusicRoom::_musicHandler->checkSound(3))
+ startTalking(this, 113, findView());
+ else if (!CMusicRoom::_musicHandler->checkSound(2))
+ startTalking(this, 115, findView());
+ else {
+ startTalking(this, 110, findView());
+ CMaitreDHappyMsg happyMsg;
+ happyMsg.execute("MaitreD Left Arm");
+ happyMsg.execute("MaitreD Right Arm");
+ }
+
+ return true;
+}
+
+bool CMaitreD::LeaveViewMsg(CLeaveViewMsg *msg) {
+ _field134 = _field12C;
+ performAction(true);
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+
+ _field12C = 0;
+ return true;
+}
+
+bool CMaitreD::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) {
+ static const char *const NAMES[] = {
+ "Talking0", "Talking1", "Talking2", "Talking3", "Talking4",
+ "Talking5", "Talking6", "Talking7", nullptr
+ };
+
+ if (msg->_value2 != 2) {
+ msg->_names = NAMES;
+
+ CAnimateMaitreDMsg animMsg;
+ if (_field12C)
+ animMsg._value = 0;
+ animMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CMaitreD::TimerMsg(CTimerMsg *msg) {
+ if (msg->_action == "MD Fight") {
+ if (_field12C && compareViewNameTo("1stClassRestaurant.MaitreD Node.N")) {
+ startTalking(this, 131, findView());
+ }
+ } else {
+ CTrueTalkNPC::TimerMsg(msg);
+ }
+
+ return true;
+}
+
+bool CMaitreD::TrueTalkNotifySpeechStartedMsg(CTrueTalkNotifySpeechStartedMsg *msg) {
+ if (_field12C) {
+ stopAnimTimer(_timerId);
+ _timerId = 0;
+ }
+
+ CTrueTalkNPC::TrueTalkNotifySpeechStartedMsg(msg);
+ return true;
+}
+
+bool CMaitreD::TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg) {
+ if (_field12C) {
+ stopAnimTimer(_timerId);
+ _timerId = startAnimTimer("MD Fight", 3000 + getRandomNumber(3000));
+ }
+
+ CTrueTalkNPC::TrueTalkNotifySpeechEndedMsg(msg);
+ return true;
+}
+
+bool CMaitreD::LoadSuccessMsg(CLoadSuccessMsg *msg) {
+ if (_field12C) {
+ _timerId = startAnimTimer("MD Fight", 3000 + getRandomNumber(3000));
+ }
+
+ return true;
+}
+
+bool CMaitreD::TextInputMsg(CTextInputMsg *msg) {
+ CTrueTalkNPC::processInput(msg, findView());
+ return true;
+}
+
+bool CMaitreD::TriggerNPCEvent(CTriggerNPCEvent *msg) {
+ startTalking(this, msg->_value, findView());
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/maitre_d.h b/engines/titanic/npcs/maitre_d.h
index af73f02a9a..878c32cc0b 100644
--- a/engines/titanic/npcs/maitre_d.h
+++ b/engines/titanic/npcs/maitre_d.h
@@ -28,6 +28,18 @@
namespace Titanic {
class CMaitreD : public CTrueTalkNPC {
+ DECLARE_MESSAGE_MAP;
+ bool RestaurantMusicChanged(CRestaurantMusicChanged *msg);
+ bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
+ bool TrueTalkNotifySpeechStartedMsg(CTrueTalkNotifySpeechStartedMsg *msg);
+ bool TrueTalkNotifySpeechEndedMsg(CTrueTalkNotifySpeechEndedMsg *msg);
+ bool LoadSuccessMsg(CLoadSuccessMsg *msg);
+ bool TextInputMsg(CTextInputMsg *msg);
+ bool TriggerNPCEvent(CTriggerNPCEvent *msg);
private:
static int _v1;
private:
@@ -39,7 +51,7 @@ private:
int _field12C;
int _field130;
int _field134;
- int _field138;
+ int _timerId;
public:
CLASSDEF;
CMaitreD();
diff --git a/engines/titanic/npcs/mobile.h b/engines/titanic/npcs/mobile.h
index 68e74a5afe..2ad939afa6 100644
--- a/engines/titanic/npcs/mobile.h
+++ b/engines/titanic/npcs/mobile.h
@@ -29,7 +29,7 @@ namespace Titanic {
class CMobile : public CCharacter {
DECLARE_MESSAGE_MAP;
-private:
+protected:
Point _pos1;
int _fieldDC;
public:
diff --git a/engines/titanic/npcs/parrot.cpp b/engines/titanic/npcs/parrot.cpp
index 49e4f4066e..6e7aa4ec57 100644
--- a/engines/titanic/npcs/parrot.cpp
+++ b/engines/titanic/npcs/parrot.cpp
@@ -21,9 +21,30 @@
*/
#include "titanic/npcs/parrot.h"
+#include "titanic/core/project_item.h"
+#include "titanic/carry/carry.h"
namespace Titanic {
+BEGIN_MESSAGE_MAP(CParrot, CTrueTalkNPC)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(TrueTalkTriggerActionMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(ParrotSpeakMsg)
+ ON_MESSAGE(NPCPlayTalkingAnimationMsg)
+ ON_MESSAGE(NPCPlayIdleAnimationMsg)
+ ON_MESSAGE(FrameMsg)
+ ON_MESSAGE(MovieFrameMsg)
+ ON_MESSAGE(PutParrotBackMsg)
+ ON_MESSAGE(PreEnterViewMsg)
+ ON_MESSAGE(PanningAwayFromParrotMsg)
+ ON_MESSAGE(LeaveRoomMsg)
+END_MESSAGE_MAP()
+
int CParrot::_v1;
int CParrot::_v2;
int CParrot::_v3;
@@ -40,7 +61,7 @@ CParrot::CParrot() : CTrueTalkNPC() {
_field128 = 58;
_field12C = 0;
_field130 = 0;
- _field134 = 0;
+ _field134 = nullptr;
_field138 = 851;
_field13C = 851;
_field140 = 265;
@@ -140,4 +161,596 @@ void CParrot::load(SimpleFile *file) {
CTrueTalkNPC::load(file);
}
+bool CParrot::ActMsg(CActMsg *msg) {
+ if (msg->_action == "PistaccioEaten") {
+ CActMsg actMsg("NutsEaten");
+ actMsg.execute("Ear2");
+ } else if (msg->_action == "Chicken") {
+ // Nothing to do
+ } else if (msg->_action == "CarryParrotLeftView") {
+ if (!_v2) {
+ _v1 = 0;
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("PerchCoreHolder");
+ }
+ } else if (msg->_action == "StartChickenDrag") {
+ if (!_v4) {
+ stopMovie();
+ startTalking(this, 280275, findView());
+ _field12C = 0;
+ }
+ } else if (msg->_action == "EnteringFromTOW" &&
+ (_v4 == 0 || _v4 == 2)) {
+ if (_v2) {
+ _v2 = 2;
+ } else {
+ setVisible(true);
+ CTreeItem *cageBar = getRoot()->findByName("CageBar");
+ detach();
+ attach(cageBar);
+
+ _v4 = 0;
+ CActMsg actMsg1("OpenNow");
+ actMsg1.execute("ParrotCage");
+ CActMsg actMsg2("GainParrot");
+ actMsg2.execute("ParrotLobbyController");
+ }
+ }
+
+ return true;
+}
+
+bool CParrot::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!(_npcFlags & NPCFLAG_2000000) && _field100 <= 0) {
+ CTrueTalkTriggerActionMsg triggerMsg(280250, 280250, 1);
+ triggerMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CParrot::MovieEndMsg(CMovieEndMsg *msg) {
+ if ((_npcFlags & NPCFLAG_2000000) && clipExistsByEnd("Take Off", msg->_endFrame)) {
+ setVisible(false);
+ moveUnder(findRoom());
+ stopMovie();
+
+ CActMsg actMsg1("LoseParrot");
+ actMsg1.execute("ParrotLobbyController");
+
+ if (_field134) {
+ CActMsg actMsg2("PanAwayFromParrot");
+ actMsg2.execute(_field134);
+ _field134 = nullptr;
+ } else {
+ CActMsg actMsg2("Shut");
+ actMsg2.execute("ParrotCage");
+ }
+
+ _npcFlags &= ~NPCFLAG_2000000;
+ _v4 = 2;
+ } else if (_npcFlags & NPCFLAG_10000) {
+ if (_npcFlags & NPCFLAG_20000) {
+ _npcFlags = (_npcFlags & ~NPCFLAG_20000) | NPCFLAG_40000;
+ if (_npcFlags & NPCFLAG_100000) {
+ playClip("Walk Left Loop", MOVIE_NOTIFY_OBJECT);
+ movieEvent(236);
+ } else {
+ playClip("Walk Right Loop", MOVIE_NOTIFY_OBJECT);
+ }
+ } else if (_npcFlags & NPCFLAG_40000) {
+ int xp = _bounds.left + _bounds.width() / 2;
+
+ if (_npcFlags & NPCFLAG_100000) {
+ if ((xp - _field128) > 32) {
+ setPosition(Point(_bounds.left - 40, _bounds.top));
+ playClip("Walk Left Loop", MOVIE_NOTIFY_OBJECT);
+ movieEvent(236);
+ } else {
+ setPosition(Point(_bounds.left - 10, _bounds.top));
+ playClip("Walk Left Outro", MOVIE_NOTIFY_OBJECT);
+ _npcFlags = (_npcFlags & ~NPCFLAG_40000) | NPCFLAG_80000;
+ }
+ } else {
+ if ((_field128 - xp) > 32) {
+ playClip("Walk Right Loop", MOVIE_NOTIFY_OBJECT);
+ movieEvent(244);
+ } else {
+ playClip("Walk Right Outro", MOVIE_NOTIFY_OBJECT);
+ _npcFlags = (_npcFlags & NPCFLAG_40000) | NPCFLAG_80000;
+ }
+ }
+ } else if (_npcFlags & NPCFLAG_80000) {
+ loadFrame(0);
+ if (_npcFlags & NPCFLAG_100000)
+ setPosition(Point(_bounds.left - 30, _bounds.top));
+ else
+ setPosition(Point(_bounds.left + 14, _bounds.top));
+
+ _npcFlags &= ~(NPCFLAG_10000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000);
+ CTrueTalkNPC::MovieEndMsg(msg);
+ } else {
+ if (_npcFlags & NPCFLAG_1000000) {
+ Point pt = getMousePos();
+ if (pt.x > 70 || pt.y < 90 || pt.y > 280) {
+ stopMovie();
+ loadFrame(0);
+ _npcFlags &= ~NPCFLAG_1000000;
+ }
+
+ if (clipExistsByEnd("Walk Left Loop", msg->_endFrame)) {
+ playClip("Lean Over To Chicken", MOVIE_NOTIFY_OBJECT);
+ setPosition(Point(_bounds.left - 55, _bounds.top));
+ _field130 = (-100 - _bounds.left) / 5;
+ movieEvent(261);
+ movieEvent(262);
+ movieEvent(265);
+ movieEvent(268);
+ movieEvent(271);
+ return true;
+
+ } else if (clipExistsByEnd("Lean Over To Chicken", msg->_endFrame)) {
+ playClip("Eat Chicken", 0);
+ playClip("Eat Chicken 2", MOVIE_NOTIFY_OBJECT);
+ _v1 = 1;
+
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 0;
+ statusMsg.execute("PerchCoreHolder");
+
+ CTrueTalkTriggerActionMsg actionMsg;
+ actionMsg._param1 = 280266;
+ actionMsg._param2 = 1;
+ actionMsg.execute(this);
+
+ CCarry *chicken = dynamic_cast<CCarry *>(findUnder(getRoot(), "Chicken"));
+ if (chicken) {
+ CActMsg actMsg("Eaten");
+ actMsg.execute(chicken);
+ }
+
+ _npcFlags &= ~NPCFLAG_1000000;
+ return true;
+ }
+ }
+
+ if (clipExistsByEnd("Eat Chicken 2", msg->_endFrame)) {
+ CStatusChangeMsg statusMsg;
+ statusMsg._newStatus = 1;
+ statusMsg.execute("PerchCoreHolder");
+
+ if (_v2) {
+ loadMovie("z168.avi", false);
+ playClip("Take Off", MOVIE_NOTIFY_OBJECT);
+ setPosition(Point(20, 10));
+ _npcFlags |= NPCFLAG_2000000;
+ } else {
+ _npcFlags &= ~(NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000);
+ _npcFlags |= NPCFLAG_400000;
+ stopMovie();
+ loadFrame(0);
+ setPosition(Point(-90, _bounds.top));
+ }
+ } else {
+ CTrueTalkNPC::MovieEndMsg(msg);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CParrot::EnterViewMsg(CEnterViewMsg *msg) {
+ static const char *const NAMES[] = {
+ "Talking0", "Talking1", "Talking2", "Talking3", "Talking4",
+ "Talking5", "Talking6", "Talking7", nullptr
+ };
+
+ if (!_v4) {
+ setPosition(Point(_field124, _bounds.top));
+ _field118 = 1;
+ _npcFlags &= ~(NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_400000);
+ loadFrame(0);
+ endTalking(this, true, findView());
+
+ if (_field100 > 0) {
+ playRandomClip(NAMES, MOVIE_NOTIFY_OBJECT);
+ } else {
+ startTalking(this, 280258, findView());
+ }
+
+ petSetArea(PET_CONVERSATION);
+ _field12C = 0;
+ _npcFlags |= NPCFLAG_4;
+ }
+
+ return true;
+}
+
+bool CParrot::TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg) {
+ if (_v4) {
+ CViewItem *view = msg->_param2 ? findView() : nullptr;
+ startTalking(this, msg->_action, view);
+ }
+
+ return true;
+}
+
+bool CParrot::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ if (_field118 && !_v4 && checkPoint(msg->_mousePos, false, true)) {
+ setVisible(false);
+ CRoomItem *room = findRoom();
+
+ moveUnder(room);
+ startTalking(this, 280129);
+ performAction(true);
+
+ CCarry *item = dynamic_cast<CCarry *>(getRoot()->findByName(_string2));
+ if (item) {
+ item->_fieldE0 = 1;
+ CPassOnDragStartMsg passMsg;
+ passMsg._mousePos = msg->_mousePos;
+ passMsg.execute(item);
+ msg->_dragItem = item;
+
+ CActMsg actMsg("LoseParrot");
+ actMsg.execute("ParrotLobbyController");
+ }
+ }
+
+ return true;
+}
+
+bool CParrot::LeaveViewMsg(CLeaveViewMsg *msg) {
+ performAction(true);
+ _npcFlags &= ~NPCFLAG_4;
+
+ return true;
+}
+
+bool CParrot::ParrotSpeakMsg(CParrotSpeakMsg *msg) {
+ const char *const ROOM_NAMES[] = {
+ "SGTState", "SGTLittleLift", "SecClassLittleLift", "SecClassState",
+ "Lift", "ServiceElevator", "Dome", "Home", "MoonEmbLobby", nullptr
+ };
+
+ if (!stateGet24() || _v4 == 3 || compareViewNameTo("Titania.Node 18.N"))
+ return true;
+
+ // Check for rooms not to speak in
+ for (const char *const *s = &ROOM_NAMES[0]; *s; ++s) {
+ if (isEquals(*s))
+ return true;
+ }
+
+ // Don't have the parrot speak too often
+ if ((getTicksCount() - _field120) < 20000 || _field100)
+ return true;
+
+ playSound("z#475.wav", 50);
+
+ if (msg->_target == "Bomb") {
+ startTalking("PerchedParrot", 280236);
+ } else if (msg->_target == "Announcements") {
+ startTalking("PerchedParrot", 280263);
+ } else if (msg->_target == "Television") {
+ startTalking("PerchedParrot", 280264);
+ } else if (msg->_target == "Barbot") {
+ if (msg->_action == "AskForDrink")
+ startTalking("PerchedParrot", 280262);
+ } else if (msg->_target == "SuccUBus") {
+ if (msg->_action == "TurnOn")
+ startTalking("PerchedParrot", 80161);
+ else if (msg->_action == "EnterView")
+ startTalking("PerchedParrot", 80159);
+ } else if (msg->_target == "Cellpoints") {
+ if (getRandomNumber(2) == 0) {
+ switch (getRandomNumber(2)) {
+ case 0:
+ startTalking("PerchedParrot", 80193);
+ break;
+ case 1:
+ startTalking("PerchedParrot", 80197);
+ break;
+ case 2:
+ startTalking("PerchedParrot", 80198);
+ break;
+ default:
+ break;
+ }
+ } else if (msg->_action == "DoorBot") {
+ startTalking("PerchedParrot", 80195);
+ } else if (msg->_action == "DeskBot") {
+ startTalking("PerchedParrot", 80194);
+ } else if (msg->_action == "BarBot") {
+ startTalking("PerchedParrot", 80191);
+ } else if (msg->_action == "BellBot") {
+ startTalking("PerchedParrot", 80192);
+ } else if (msg->_action == "LiftBot") {
+ startTalking("PerchedParrot", 80196);
+ }
+ }
+
+ _field120 = getTicksCount();
+ return true;
+}
+
+bool CParrot::NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg) {
+ const char *const NAMES[] = {
+ "Talking0", "Talking1", "Talking2", "Talking3", "Talking4",
+ "Talking5", "Talking6", "Talking7", nullptr
+ };
+
+ if (!(_npcFlags & (NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_400000))
+ && _visible && !_v4) {
+ if (!compareViewNameTo("ParrotLobby.Node 1.N"))
+ msg->_names = NAMES;
+ }
+
+ return true;
+}
+
+bool CParrot::NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg) {
+ const char *const NAMES[] = {
+ "Idle0", "Idle1", "Peck At Feet", "Peck At Feet Left"
+ "Peck At Feet Right", nullptr
+ };
+
+ if (!(_npcFlags & (NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000 | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_400000))
+ && _visible && !_v4 && !compareViewNameTo("ParrotLobby.Node 1.N")) {
+ CGameObject *dragItem = getDraggingObject();
+ if (!dragItem || dragItem->getName() == "Chicken") {
+ if (!_v5 ||getRandomNumber(3) != 0) {
+ if (getRandomNumber(1)) {
+ startTalking(this, 280267, findView());
+ } else {
+ msg->_names = NAMES;
+ }
+ } else {
+ int id = -1;
+ switch (stateGet38()) {
+ case 0:
+ id = 280107;
+ break;
+ case 1:
+ id = 280106;
+ break;
+ case 2:
+ id = 280115;
+ break;
+ case 3:
+ id = 280114;
+ break;
+ case 4:
+ id = 280113;
+ break;
+ case 5:
+ id = 280112;
+ break;
+ case 6:
+ id = 280111;
+ break;
+ case 7:
+ id = 280110;
+ break;
+ case 8:
+ id = 280109;
+ break;
+ case 9:
+ id = 280108;
+ break;
+ case 10:
+ id = 280105;
+ break;
+ case 11:
+ id = 280000;
+ break;
+ default:
+ break;
+ }
+
+ if (id != -1)
+ startTalking(this, id, findView());
+
+ CActMsg actMsg("FlashCore");
+ actMsg.execute("PerchCoreHolder");
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CParrot::FrameMsg(CFrameMsg *msg) {
+ if (compareViewNameTo("ParrotLobby.Node 1.N"))
+ return false;
+ if (_v4)
+ return true;
+
+ Point pt = getMousePos();
+ CGameObject *dragObject = getDraggingObject();
+ int xp = _bounds.left + _bounds.width() / 2;
+
+ if ((_npcFlags & NPCFLAG_400000) && !hasActiveMovie()) {
+ _field128 = xp - (_field124 + _bounds.width() / 2);
+
+ if (xp < 64) {
+ if (_field134) {
+ CActMsg actMsg("PanAwayFromParrot");
+ actMsg.execute(_field134);
+ }
+
+ _npcFlags &= ~(NPCFLAG_10000 | NPCFLAG_20000 | NPCFLAG_40000
+ | NPCFLAG_80000 | NPCFLAG_100000 | NPCFLAG_200000 | NPCFLAG_400000);
+ return true;
+ }
+ }
+
+ bool chickenFlag = dragObject && dragObject->isEquals("Chicken");
+
+ if (_npcFlags & NPCFLAG_1000000) {
+ if (!chickenFlag || pt.x > 70 || pt.y < 90 || pt.y > 280) {
+ stopMovie();
+ loadFrame(0);
+ setPosition(Point(-90, _bounds.top));
+ }
+ } else {
+ if (!chickenFlag)
+ return false;
+ }
+
+ _field128 = CLIP((int)pt.x, 230, 480);
+ if ((_npcFlags & NPCFLAG_10000) || hasActiveMovie())
+ return true;
+
+ if (_field128 > 64) {
+ _npcFlags |= NPCFLAG_10000 | NPCFLAG_20000;
+
+ if (_field128 >= xp) {
+ setPosition(Point(_bounds.left + 30, _bounds.top));
+ _npcFlags |= NPCFLAG_200000;
+ playClip("Walk Right Intro", MOVIE_NOTIFY_OBJECT);
+ } else {
+ _npcFlags |= NPCFLAG_100000;
+ playClip("Walk Left Intro", MOVIE_NOTIFY_OBJECT);
+ }
+ } else if (chickenFlag && pt.y >= 90 && pt.y <= 280 && !_field12C) {
+ CParrotTriesChickenMsg triesMsg;
+ triesMsg.execute(dragObject);
+
+ CTrueTalkTriggerActionMsg triggerMsg;
+ int id;
+ switch (triesMsg._value2) {
+ case 1:
+ id = 280056 + (triesMsg._value1 ? 234 : 0);
+ break;
+ case 2:
+ id = 280055 + (triesMsg._value1 ? 234 : 0);
+ break;
+ case 3:
+ id = 280054 + (triesMsg._value1 ? 234 : 0);
+ break;
+ default:
+ id = 280053 + (triesMsg._value1 ? 234 : 0);
+ break;
+ }
+
+ if (id < 280266) {
+ if (pt.x < 75) {
+ _npcFlags |= NPCFLAG_1000000;
+ playClip("Walk Left Intro", MOVIE_STOP_PREVIOUS);
+ playClip("Walk Left Loop", MOVIE_NOTIFY_OBJECT);
+ movieEvent(236);
+ chickenFlag = false;
+ } else if ((pt.x - xp) > 15) {
+ _npcFlags |= NPCFLAG_800000;
+ playClip("Peck At Feet Right", MOVIE_NOTIFY_OBJECT);
+ movieEvent(170);
+ } else if ((xp - pt.x) > 15) {
+ _npcFlags |= NPCFLAG_800000;
+ playClip("Peck At Feet Left", MOVIE_NOTIFY_OBJECT);
+ movieEvent(142);
+ } else {
+ _npcFlags |= NPCFLAG_800000;
+ playClip("Peck At Feet", MOVIE_NOTIFY_OBJECT);
+ movieEvent(157);
+ }
+ }
+
+ if (chickenFlag) {
+ triggerMsg._param2 = 1;
+ triggerMsg.execute(this);
+ _field12C = 1;
+ }
+ }
+
+ return true;
+}
+
+bool CParrot::MovieFrameMsg(CMovieFrameMsg *msg) {
+ if (_npcFlags & NPCFLAG_800000) {
+ CCarry *chicken = dynamic_cast<CCarry *>(findUnder(getRoot(), "Chicken"));
+ if (chicken) {
+ CActMsg actMsg("Eaten");
+ actMsg.execute(chicken);
+ }
+
+ _npcFlags &= ~NPCFLAG_800000;
+ }
+
+ switch (msg->_frameNumber) {
+ case 244:
+ setPosition(Point(_bounds.left, _bounds.top + 45));
+ break;
+ case 261:
+ case 262:
+ case 265:
+ case 268:
+ case 271:
+ setPosition(Point(_bounds.left + _field130, _bounds.top));
+ break;
+ default:
+ break;
+ }
+
+ return true;
+}
+
+bool CParrot::PutParrotBackMsg(CPutParrotBackMsg *msg) {
+ const char *const NAMES[] = {
+ "Talking0", "Talking1", "Talking2", "Talking3", "Talking4",
+ "Talking5", "Talking6", "Talking7", nullptr
+ };
+
+ int xp = CLIP(msg->_value, 230, 480);
+ setVisible(true);
+ moveToView();
+ _v4 = 0;
+
+ setPosition(Point(xp - _bounds.width() / 2, _bounds.top));
+ playRandomClip(NAMES, MOVIE_NOTIFY_OBJECT);
+
+ CActMsg actMsg("GainParrot");
+ actMsg.execute("ParrotLobbyController");
+
+ return true;
+}
+
+bool CParrot::PreEnterViewMsg(CPreEnterViewMsg *msg) {
+ if (!_v4) {
+ loadMovie("z167.avi", false);
+ loadFrame(0);
+ }
+
+ return true;
+}
+
+bool CParrot::PanningAwayFromParrotMsg(CPanningAwayFromParrotMsg *msg) {
+ if (_v4) {
+ CActMsg actMsg("PanAwayFromParrot");
+ actMsg.execute(msg->_target);
+ _field134 = 0;
+ } else if (_v2) {
+ _field134 = msg->_target;
+ loadMovie("z168.avi", false);
+ stopMovie();
+ playClip("Take Off", MOVIE_NOTIFY_OBJECT);
+ _npcFlags |= NPCFLAG_2000000;
+ } else {
+ _npcFlags |= NPCFLAG_400000;
+ _field134 = msg->_target;
+ stopMovie();
+ }
+
+ return true;
+}
+
+bool CParrot::LeaveRoomMsg(CLeaveRoomMsg *msg) {
+ if (!_v4)
+ startTalking(this, 280259);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/parrot.h b/engines/titanic/npcs/parrot.h
index a3c8540f0e..93e0643857 100644
--- a/engines/titanic/npcs/parrot.h
+++ b/engines/titanic/npcs/parrot.h
@@ -28,6 +28,23 @@
namespace Titanic {
class CParrot : public CTrueTalkNPC {
+ DECLARE_MESSAGE_MAP;
+ bool ActMsg(CActMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool TrueTalkTriggerActionMsg(CTrueTalkTriggerActionMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool ParrotSpeakMsg(CParrotSpeakMsg *msg);
+ bool NPCPlayTalkingAnimationMsg(CNPCPlayTalkingAnimationMsg *msg);
+ bool NPCPlayIdleAnimationMsg(CNPCPlayIdleAnimationMsg *msg);
+ bool FrameMsg(CFrameMsg *msg);
+ bool MovieFrameMsg(CMovieFrameMsg *msg);
+ bool PutParrotBackMsg(CPutParrotBackMsg *msg);
+ bool PreEnterViewMsg(CPreEnterViewMsg *msg);
+ bool PanningAwayFromParrotMsg(CPanningAwayFromParrotMsg *msg);
+ bool LeaveRoomMsg(CLeaveRoomMsg *msg);
public:
static int _v1;
static int _v2;
@@ -44,7 +61,7 @@ private:
int _field128;
int _field12C;
int _field130;
- int _field134;
+ CTreeItem *_field134;
int _field138;
int _field13C;
int _field140;
diff --git a/engines/titanic/npcs/parrot_succubus.cpp b/engines/titanic/npcs/parrot_succubus.cpp
new file mode 100644
index 0000000000..d285c219b5
--- /dev/null
+++ b/engines/titanic/npcs/parrot_succubus.cpp
@@ -0,0 +1,152 @@
+/* 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 "titanic/npcs/parrot_succubus.h"
+#include "titanic/pet_control/pet_control.h"
+#include "titanic/carry/hose.h"
+
+namespace Titanic {
+
+BEGIN_MESSAGE_MAP(CParrotSuccUBus, CSuccUBus)
+ ON_MESSAGE(HoseConnectedMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(LeaveNodeMsg)
+END_MESSAGE_MAP()
+
+CParrotSuccUBus::CParrotSuccUBus() : CSuccUBus(), _field1DC(0),
+ _field1EC(0), _field1F0(376), _field1F4(393) {
+}
+
+void CParrotSuccUBus::save(SimpleFile *file, int indent) {
+ file->writeNumberLine(1, indent);
+ file->writeNumberLine(_field1DC, indent);
+ file->writeQuotedLine(_string3, indent);
+ file->writeNumberLine(_field1EC, indent);
+
+ CSuccUBus::save(file, indent);
+}
+
+void CParrotSuccUBus::load(SimpleFile *file) {
+ file->readNumber();
+ _field1DC = file->readNumber();
+ _string3 = file->readString();
+ _field1EC = file->readNumber();
+
+ CSuccUBus::load(file);
+}
+
+bool CParrotSuccUBus::HoseConnectedMsg(CHoseConnectedMsg *msg) {
+ CPetControl *pet = getPetControl();
+ if (msg->_value == _field1DC)
+ return true;
+ if (mailExists(pet->getRoomFlags()))
+ return false;
+
+ _field1DC = msg->_value;
+ if (_field1DC) {
+ CGameObject *item = msg->_object;
+ _string3 = item->getName();
+ CHoseConnectedMsg hoseMsg(1, this);
+ hoseMsg.execute(msg->_object);
+ item->petMoveToHiddenRoom();
+
+ CPumpingMsg pumpingMsg(1, this);
+ pumpingMsg.execute(this);
+ _field1DC = 1;
+
+ if (_enabled) {
+ _enabled = false;
+ } else {
+ playMovie(_startFrame9, _endFrame9, 0);
+ playSound("z#26.wav");
+ }
+
+ playMovie(_field1C4, _field1C8, MOVIE_NOTIFY_OBJECT);
+ } else {
+ stopMovie();
+ stopSound(_field1EC);
+ playMovie(_field1F0, _field1F4, MOVIE_NOTIFY_OBJECT);
+
+ CPumpingMsg pumpingMsg(0, this);
+ pumpingMsg.execute(_string3);
+
+ CGameObject *obj = getHiddenObject(_string3);
+ if (obj) {
+ obj->petAddToInventory();
+ obj->setVisible(true);
+ }
+
+ _enabled = true;
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CParrotSuccUBus::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_field1DC) {
+ playMovie(_field1CC, _field1D0, MOVIE_REPEAT);
+ return true;
+ } else {
+ return CSuccUBus::EnterViewMsg(msg);
+ }
+}
+
+bool CParrotSuccUBus::MovieEndMsg(CMovieEndMsg *msg) {
+ if (msg->_endFrame == _field1C8) {
+ playMovie(_field1CC, _field1D0, MOVIE_REPEAT);
+ _field1EC = playSound("z#472.wav");
+ return true;
+ } else {
+ return CSuccUBus::MovieEndMsg(msg);
+ }
+}
+
+bool CParrotSuccUBus::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (_field1DC) {
+ CHoseConnectedMsg hoseMsg;
+ hoseMsg._value = 0;
+ hoseMsg.execute(this);
+ return true;
+ } else {
+ return CSuccUBus::MouseButtonDownMsg(msg);
+ }
+}
+
+bool CParrotSuccUBus::LeaveNodeMsg(CLeaveNodeMsg *msg) {
+ if (_field1DC) {
+ getHiddenObject(_string3);
+ if (CHose::_statics->_actionTarget.empty()) {
+ playSound("z#51.wav");
+ CHoseConnectedMsg hoseMsg;
+ hoseMsg._value = 0;
+ hoseMsg.execute(this);
+ }
+ }
+
+ return true;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/game/parrot/parrot_succubus.h b/engines/titanic/npcs/parrot_succubus.h
index 6f5d9e602a..74a4a032eb 100644
--- a/engines/titanic/game/parrot/parrot_succubus.h
+++ b/engines/titanic/npcs/parrot_succubus.h
@@ -28,6 +28,12 @@
namespace Titanic {
class CParrotSuccUBus : public CSuccUBus {
+ DECLARE_MESSAGE_MAP;
+ bool HoseConnectedMsg(CHoseConnectedMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool LeaveNodeMsg(CLeaveNodeMsg *msg);
public:
int _field1DC;
CString _string3;
diff --git a/engines/titanic/npcs/robot_controller.cpp b/engines/titanic/npcs/robot_controller.cpp
index 98866e4505..34c75e30eb 100644
--- a/engines/titanic/npcs/robot_controller.cpp
+++ b/engines/titanic/npcs/robot_controller.cpp
@@ -24,21 +24,37 @@
namespace Titanic {
-CRobotController::CRobotController() : CGameObject(), _string1("BellBot") {
+BEGIN_MESSAGE_MAP(CRobotController, CGameObject)
+ ON_MESSAGE(SummonBotMsg)
+ ON_MESSAGE(SummonBotQueryMsg)
+END_MESSAGE_MAP()
+
+CRobotController::CRobotController() : CGameObject(), _robotName("BellBot") {
}
void CRobotController::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeQuotedLine(_string1, indent);
+ file->writeQuotedLine(_robotName, indent);
CGameObject::save(file, indent);
}
void CRobotController::load(SimpleFile *file) {
file->readNumber();
- _string1 = file->readString();
+ _robotName = file->readString();
CGameObject::load(file);
}
+bool CRobotController::SummonBotMsg(CSummonBotMsg *msg) {
+ if (!petDismissBot(msg->_npcName))
+ petOnSummonBot(msg->_npcName, msg->_value);
+
+ return true;
+}
+
+bool CRobotController::SummonBotQueryMsg(CSummonBotQueryMsg *msg) {
+ return _robotName == msg->_npcName;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/robot_controller.h b/engines/titanic/npcs/robot_controller.h
index 6cbf57aef2..326c2280dd 100644
--- a/engines/titanic/npcs/robot_controller.h
+++ b/engines/titanic/npcs/robot_controller.h
@@ -28,8 +28,11 @@
namespace Titanic {
class CRobotController : public CGameObject {
+ DECLARE_MESSAGE_MAP;
+ bool SummonBotMsg(CSummonBotMsg *msg);
+ bool SummonBotQueryMsg(CSummonBotQueryMsg *msg);
protected:
- CString _string1;
+ CString _robotName;
public:
CLASSDEF;
CRobotController();
diff --git a/engines/titanic/npcs/starlings.cpp b/engines/titanic/npcs/starlings.cpp
index 333f4c4b7a..7e5907f577 100644
--- a/engines/titanic/npcs/starlings.cpp
+++ b/engines/titanic/npcs/starlings.cpp
@@ -24,23 +24,40 @@
namespace Titanic {
-int CStarlings::_v1;
+BEGIN_MESSAGE_MAP(CStarlings, CCharacter)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(StatusChangeMsg)
+END_MESSAGE_MAP()
-CStarlings::CStarlings() : CCharacter() {
+CStarlings::CStarlings() : CCharacter(), _enabled(false) {
}
void CStarlings::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_v1, indent);
+ file->writeNumberLine(_enabled, indent);
CCharacter::save(file, indent);
}
void CStarlings::load(SimpleFile *file) {
file->readNumber();
- _v1 = file->readNumber();
+ _enabled = file->readNumber();
CCharacter::load(file);
}
+bool CStarlings::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_enabled)
+ setVisible(false);
+ else
+ playMovie(MOVIE_REPEAT);
+ return true;
+}
+
+bool CStarlings::StatusChangeMsg(CStatusChangeMsg *msg) {
+ _enabled = msg->_newStatus == 1;
+ setVisible(!_enabled);
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/starlings.h b/engines/titanic/npcs/starlings.h
index 4d96e5c77f..1998e6490d 100644
--- a/engines/titanic/npcs/starlings.h
+++ b/engines/titanic/npcs/starlings.h
@@ -28,8 +28,11 @@
namespace Titanic {
class CStarlings : public CCharacter {
+ DECLARE_MESSAGE_MAP;
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool StatusChangeMsg(CStatusChangeMsg *msg);
private:
- static int _v1;
+ bool _enabled;
public:
CLASSDEF;
CStarlings();
diff --git a/engines/titanic/npcs/succubus.cpp b/engines/titanic/npcs/succubus.cpp
index f66a59cb84..5588d862c3 100644
--- a/engines/titanic/npcs/succubus.cpp
+++ b/engines/titanic/npcs/succubus.cpp
@@ -21,61 +21,77 @@
*/
#include "titanic/npcs/succubus.h"
+#include "titanic/carry/carry.h"
+#include "titanic/carry/chicken.h"
+#include "titanic/core/view_item.h"
+#include "titanic/pet_control/pet_control.h"
namespace Titanic {
-int CSuccUBus::_v0;
+BEGIN_MESSAGE_MAP(CSuccUBus, CTrueTalkNPC)
+ ON_MESSAGE(MouseButtonDownMsg)
+ ON_MESSAGE(SubAcceptCCarryMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(LeaveViewMsg)
+ ON_MESSAGE(PETDeliverMsg)
+ ON_MESSAGE(PETReceiveMsg)
+ ON_MESSAGE(MovieEndMsg)
+ ON_MESSAGE(TrueTalkGetStateValueMsg)
+ ON_MESSAGE(SignalObject)
+ ON_MESSAGE(TurnOn)
+ ON_MESSAGE(TurnOff)
+ ON_MESSAGE(SUBTransition)
+ ON_MESSAGE(SetChevRoomBits)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(MouseDragStartMsg)
+END_MESSAGE_MAP()
+
+bool CSuccUBus::_enabled;
int CSuccUBus::_v1;
int CSuccUBus::_v2;
int CSuccUBus::_v3;
int CSuccUBus::_v4;
CSuccUBus::CSuccUBus() : CTrueTalkNPC() {
- _field108 = -1;
- _field10C = -1;
- _field110 = -1;
- _field114 = -1;
- _field118 = 0x44;
- _field11C = 0xA8;
- _field120 = 0xA8;
- _field124 = 0xF8;
- _field128 = 0;
- _field12C = 0x0E;
- _field130 = 0x0E;
- _field134 = 27;
- _field138 = 40;
- _field13C = 0x44;
+ _startFrame8 = -1;
+ _endFrame8 = -1;
+ _startFrame11 = -1;
+ _endFrame11 = -1;
+ _startFrame3 = 68;
+ _endFrame3 = 168;
+ _startFrame4 = 168;
+ _endFrame4 = 248;
+ _startFrame9 = 0;
+ _endFrame9 = 0x0E;
+ _startFrame10 = 0x0E;
+ _endFrame10 = 27;
+ _startFrame2 = 40;
+ _endFrame2 = 68;
_field140 = 1;
- _field144 = 0;
- _field148 = 0;
- _field14C = 0;
- _field150 = 0xE0;
- _field154 = 0;
+ _mailP = nullptr;
+ _startFrame5 = 0;
+ _endFrame5 = 0;
+ _startFrame12 = 224;
+ _endFrame12 = 248;
_field158 = 0;
_field15C = 0;
_string2 = "NULL";
- _field16C = 28;
- _field170 = 40;
- _field174 = 82;
- _field178 = 284;
- _field17C = 148;
- _field180 = 339;
+ _startFrame1 = 28;
+ _endFrame1 = 40;
+ _rect1 = Rect(82, 284, 148, 339);
_field184 = 15;
_field188 = 0;
- _field18C = 0;
- _field190 = 0;
- _field194 = 240;
- _field198 = 340;
+ _rect2 = Rect(0, 0, 240, 340);
_field19C = 0;
- _field1A0 = -1;
- _field1A4 = 0;
- _field1A8 = 0;
+ _soundHandle = -1;
+ _isChicken = false;
+ _isFeathers = false;
_field1AC = 0;
_field1B0 = 0;
- _field1B4 = 303;
- _field1B8 = 312;
- _field1BC = 313;
- _field1C0 = 325;
+ _startFrame6 = 303;
+ _endFrame6 = 312;
+ _startFrame7 = 313;
+ _endFrame7 = 325;
_field1C4 = 326;
_field1C8 = 347;
_field1CC = 348;
@@ -87,54 +103,54 @@ CSuccUBus::CSuccUBus() : CTrueTalkNPC() {
void CSuccUBus::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_v0, indent);
- file->writeNumberLine(_field108, indent);
- file->writeNumberLine(_field10C, indent);
- file->writeNumberLine(_field110, indent);
- file->writeNumberLine(_field114, indent);
- file->writeNumberLine(_field118, indent);
- file->writeNumberLine(_field11C, indent);
- file->writeNumberLine(_field120, indent);
- file->writeNumberLine(_field124, indent);
- file->writeNumberLine(_field128, indent);
- file->writeNumberLine(_field12C, indent);
- file->writeNumberLine(_field130, indent);
- file->writeNumberLine(_field134, indent);
- file->writeNumberLine(_field138, indent);
- file->writeNumberLine(_field13C, indent);
+ file->writeNumberLine(_enabled, indent);
+ file->writeNumberLine(_startFrame8, indent);
+ file->writeNumberLine(_endFrame8, indent);
+ file->writeNumberLine(_startFrame11, indent);
+ file->writeNumberLine(_endFrame11, indent);
+ file->writeNumberLine(_startFrame3, indent);
+ file->writeNumberLine(_endFrame3, indent);
+ file->writeNumberLine(_startFrame4, indent);
+ file->writeNumberLine(_endFrame4, indent);
+ file->writeNumberLine(_startFrame9, indent);
+ file->writeNumberLine(_endFrame9, indent);
+ file->writeNumberLine(_startFrame10, indent);
+ file->writeNumberLine(_endFrame10, indent);
+ file->writeNumberLine(_startFrame2, indent);
+ file->writeNumberLine(_endFrame2, indent);
file->writeNumberLine(_field140, indent);
file->writeNumberLine(_v2, indent);
- file->writeNumberLine(_field148, indent);
- file->writeNumberLine(_field14C, indent);
- file->writeNumberLine(_field150, indent);
- file->writeNumberLine(_field154, indent);
+ file->writeNumberLine(_startFrame5, indent);
+ file->writeNumberLine(_endFrame5, indent);
+ file->writeNumberLine(_startFrame12, indent);
+ file->writeNumberLine(_endFrame12, indent);
file->writeNumberLine(_field158, indent);
file->writeNumberLine(_field15C, indent);
file->writeQuotedLine(_string2, indent);
- file->writeNumberLine(_field16C, indent);
- file->writeNumberLine(_field170, indent);
- file->writeNumberLine(_field174, indent);
- file->writeNumberLine(_field178, indent);
- file->writeNumberLine(_field17C, indent);
- file->writeNumberLine(_field180, indent);
+ file->writeNumberLine(_startFrame1, indent);
+ file->writeNumberLine(_endFrame1, indent);
+ file->writeNumberLine(_rect1.left, indent);
+ file->writeNumberLine(_rect1.top, indent);
+ file->writeNumberLine(_rect1.right, indent);
+ file->writeNumberLine(_rect1.bottom, indent);
file->writeNumberLine(_field184, indent);
file->writeNumberLine(_field188, indent);
- file->writeNumberLine(_field18C, indent);
- file->writeNumberLine(_field190, indent);
- file->writeNumberLine(_field194, indent);
- file->writeNumberLine(_field198, indent);
+ file->writeNumberLine(_rect2.left, indent);
+ file->writeNumberLine(_rect2.top, indent);
+ file->writeNumberLine(_rect2.right, indent);
+ file->writeNumberLine(_rect2.bottom, indent);
file->writeNumberLine(_field19C, indent);
- file->writeNumberLine(_field1A0, indent);
- file->writeNumberLine(_field1A4, indent);
- file->writeNumberLine(_field1A8, indent);
+ file->writeNumberLine(_soundHandle, indent);
+ file->writeNumberLine(_isChicken, indent);
+ file->writeNumberLine(_isFeathers, indent);
file->writeNumberLine(_field1AC, indent);
file->writeNumberLine(_field1B0, indent);
- file->writeNumberLine(_field1B4, indent);
- file->writeNumberLine(_field1B8, indent);
- file->writeNumberLine(_field1BC, indent);
- file->writeNumberLine(_field1C0, indent);
+ file->writeNumberLine(_startFrame6, indent);
+ file->writeNumberLine(_endFrame6, indent);
+ file->writeNumberLine(_startFrame7, indent);
+ file->writeNumberLine(_endFrame7, indent);
file->writeNumberLine(_field1C4, indent);
file->writeNumberLine(_field1C8, indent);
file->writeNumberLine(_field1CC, indent);
@@ -151,54 +167,54 @@ void CSuccUBus::save(SimpleFile *file, int indent) {
void CSuccUBus::load(SimpleFile *file) {
file->readNumber();
- _v0 = file->readNumber();
- _field108 = file->readNumber();
- _field10C = file->readNumber();
- _field110 = file->readNumber();
- _field114 = file->readNumber();
- _field118 = file->readNumber();
- _field11C = file->readNumber();
- _field120 = file->readNumber();
- _field124 = file->readNumber();
- _field128 = file->readNumber();
- _field12C = file->readNumber();
- _field130 = file->readNumber();
- _field134 = file->readNumber();
- _field138 = file->readNumber();
- _field13C = file->readNumber();
+ _enabled = file->readNumber();
+ _startFrame8 = file->readNumber();
+ _endFrame8 = file->readNumber();
+ _startFrame11 = file->readNumber();
+ _endFrame11 = file->readNumber();
+ _startFrame3 = file->readNumber();
+ _endFrame3 = file->readNumber();
+ _startFrame4 = file->readNumber();
+ _endFrame4 = file->readNumber();
+ _startFrame9 = file->readNumber();
+ _endFrame9 = file->readNumber();
+ _startFrame10 = file->readNumber();
+ _endFrame10 = file->readNumber();
+ _startFrame2 = file->readNumber();
+ _endFrame2 = file->readNumber();
_field140 = file->readNumber();
_v2 = file->readNumber();
- _field148 = file->readNumber();
- _field14C = file->readNumber();
- _field150 = file->readNumber();
- _field154 = file->readNumber();
+ _startFrame5 = file->readNumber();
+ _endFrame5 = file->readNumber();
+ _startFrame12 = file->readNumber();
+ _endFrame12 = file->readNumber();
_field158 = file->readNumber();
_field15C = file->readNumber();
_string2 = file->readString();
- _field16C = file->readNumber();
- _field170 = file->readNumber();
- _field174 = file->readNumber();
- _field178 = file->readNumber();
- _field17C = file->readNumber();
- _field180 = file->readNumber();
+ _startFrame1 = file->readNumber();
+ _endFrame1 = file->readNumber();
+ _rect1.left = file->readNumber();
+ _rect1.top = file->readNumber();
+ _rect1.right = file->readNumber();
+ _rect1.bottom = file->readNumber();
_field184 = file->readNumber();
_field188 = file->readNumber();
- _field18C = file->readNumber();
- _field190 = file->readNumber();
- _field194 = file->readNumber();
- _field198 = file->readNumber();
+ _rect2.left = file->readNumber();
+ _rect2.top = file->readNumber();
+ _rect2.right = file->readNumber();
+ _rect2.bottom = file->readNumber();
_field19C = file->readNumber();
- _field1A0 = file->readNumber();
- _field1A4 = file->readNumber();
- _field1A8 = file->readNumber();
+ _soundHandle = file->readNumber();
+ _isChicken = file->readNumber();
+ _isFeathers = file->readNumber();
_field1AC = file->readNumber();
_field1B0 = file->readNumber();
- _field1B4 = file->readNumber();
- _field1B8 = file->readNumber();
- _field1BC = file->readNumber();
- _field1C0 = file->readNumber();
+ _startFrame6 = file->readNumber();
+ _endFrame6 = file->readNumber();
+ _startFrame7 = file->readNumber();
+ _endFrame7 = file->readNumber();
_field1C4 = file->readNumber();
_field1C8 = file->readNumber();
_field1CC = file->readNumber();
@@ -212,4 +228,559 @@ void CSuccUBus::load(SimpleFile *file) {
CTrueTalkNPC::load(file);
}
+bool CSuccUBus::MouseButtonDownMsg(CMouseButtonDownMsg *msg) {
+ if (!_field1D8) {
+ Rect tempRect = _rect1;
+ tempRect.translate(_bounds.left, _bounds.top);
+
+ if (!_enabled || (_field188 && tempRect.contains(msg->_mousePos))) {
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ _enabled = true;
+ } else if (getRandomNumber(256) < 130) {
+ _enabled = false;
+ CTurnOff offMsg;
+ offMsg.execute(this);
+ } else {
+ switch (getRandomNumber(2)) {
+ case 0:
+ startTalking(this, 230055, findView());
+ break;
+ case 1:
+ startTalking(this, 230067, findView());
+ break;
+ case 2:
+ startTalking(this, 230045, findView());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CSuccUBus::SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg) {
+ if (!msg->_item)
+ return false;
+
+ CPetControl *pet = getPetControl();
+ CCarry *item = dynamic_cast<CCarry *>(msg->_item);
+ Rect tempRect = _rect2;
+ tempRect.translate(_bounds.left, _bounds.top);
+ uint roomFlags = pet ? pet->getRoomFlags() : 0;
+
+ if (!_enabled || !pet || !item || !tempRect.contains(item->getControid())) {
+ item->petAddToInventory();
+ } else if (mailExists(roomFlags)) {
+ petDisplayMessage("The Succ-U-Bus is a Single Entity Delivery Device.");
+ item->petAddToInventory();
+ } else {
+ petContainerRemove(item);
+ pet->phonographAction("");
+
+ CChicken *chicken = dynamic_cast<CChicken *>(item);
+ bool chickenFlag = chicken ? chicken->_string6 != "None" : false;
+
+ item->setVisible(false);
+ if (_startFrame1 >= 0) {
+ playSound("z#23.wav");
+ playMovie(_startFrame1, _endFrame1, 0);
+ }
+
+ if (!chickenFlag) {
+ _field188 = 1;
+ item->addMail(roomFlags);
+ petSetArea(PET_REMOTE);
+ petHighlightGlyph(16);
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+ } else {
+ if (_startFrame2 >= 0) {
+ startTalking(this, 70219, findView());
+ playMovie(_startFrame2, _endFrame2, 0);
+ }
+
+ if (_startFrame3 >= 0) {
+ playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT);
+ _field158 = 2;
+ }
+
+ CViewItem *view = parseView(chicken->_fullViewName);
+ if (!view)
+ return false;
+
+ item->setPosition(item->_origPos);
+ item->moveUnder(view);
+
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+ }
+ }
+
+ return true;
+}
+
+bool CSuccUBus::EnterViewMsg(CEnterViewMsg *msg) {
+ if (getRandomNumber(4) == 0 && compareRoomNameTo("PromenadeDeck")) {
+ CParrotSpeakMsg speakMsg("SuccUBus", "EnterView");
+ speakMsg.execute("PerchedParrot");
+ }
+
+ petSetRemoteTarget();
+ _mailP = nullptr;
+ if (_startFrame8 >= 0)
+ loadFrame(_startFrame8);
+
+ return true;
+}
+
+bool CSuccUBus::LeaveViewMsg(CLeaveViewMsg *msg) {
+ petDisplayMessage(2, "");
+ if (_startFrame8 >= 0)
+ loadFrame(_startFrame8);
+ else if (!_field15C && _startFrame9 >= 0)
+ loadFrame(_startFrame9);
+
+ petClear();
+ if (_soundHandle != -1) {
+ stopSound(_soundHandle, 1);
+ _soundHandle = -1;
+ }
+
+ if (_enabled) {
+ _enabled = false;
+ if (_startFrame10 >= 0)
+ playSound("z#27.wav", 100);
+
+ if (_field15C)
+ setVisible(false);
+ }
+
+ performAction(true, findView());
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+
+ return true;
+}
+
+bool CSuccUBus::PETDeliverMsg(CPETDeliverMsg *msg) {
+ if (_field1D8)
+ return true;
+
+ if (!_enabled) {
+ petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present.");
+ return true;
+ }
+
+ CPetControl *pet = getPetControl();
+ if (!pet)
+ return true;
+
+ CGameObject *mailObject = findMail(pet->getRoomFlags());
+ if (!mailObject) {
+ switch (getRandomNumber(2)) {
+ case 0:
+ startTalking(this, 70111, findView());
+ break;
+ case 1:
+ startTalking(this, 70112, findView());
+ break;
+ case 2:
+ startTalking(this, 70113, findView());
+ break;
+ default:
+ break;
+ }
+
+ petDisplayMessage(2, "There is currently nothing in the tray to send.");
+ } else {
+ _field19C = 0;
+
+ CRoomFlags roomFlags = _roomFlags;
+ if (!pet->testRooms5(roomFlags) || getPassengerClass() > 0) {
+ roomFlags = pet->getSpecialRoomFlags("BilgeRoom");
+ _field19C = 1;
+ } else {
+ pet->getMailDest(roomFlags);
+ }
+
+ _isFeathers = mailObject->getName() == "Feathers";
+ _isChicken = mailObject->getName() == "Chicken";
+ _field158 = 0;
+ _field188 = 0;
+ _field1D8 = 1;
+ inc54();
+
+ if (_isFeathers) {
+ _field19C = 0;
+ removeMail(roomFlags, roomFlags);
+ pet->phonographAction("");
+
+ if (_startFrame2 >= 0) {
+ playMovie(_startFrame2, _endFrame2, 0);
+ startTalking(this, 230022, findView());
+ }
+
+ _field158 = 1;
+ if (_startFrame3 >= 0)
+ playMovie(_startFrame3, _endFrame3, 0);
+
+ if (_startFrame4 >= 0) {
+ _mailP = mailObject;
+ playMovie(_startFrame4, _endFrame4, MOVIE_NOTIFY_OBJECT);
+ }
+
+ if (_startFrame5 >= 0) {
+ playMovie(_startFrame5, _endFrame5, 0);
+ }
+ } else {
+ removeMail(pet->getRoomFlags(), roomFlags);
+ pet->phonographAction("");
+
+ if (_startFrame2 >= 0) {
+ playMovie(_startFrame2, _endFrame2, 0);
+ startTalking(this, 230012, findView());
+ }
+
+ if (_startFrame3 >= 0)
+ playMovie(_startFrame3, _endFrame3, MOVIE_NOTIFY_OBJECT);
+ }
+ }
+
+ return true;
+}
+
+bool CSuccUBus::PETReceiveMsg(CPETReceiveMsg *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (_field1D8 || !pet)
+ return true;
+ if (!_enabled) {
+ petDisplayMessage(2, "The Succ-U-Bus is in Standby, or \"Off\" mode at present.");
+ return true;
+ }
+
+ uint petRoomFlags = pet->getRoomFlags();
+ if (mailExists(petRoomFlags)) {
+ switch (getRandomNumber(2)) {
+ case 0:
+ startTalking(this, 70080, findView());
+ break;
+ case 1:
+ startTalking(this, 70081, findView());
+ break;
+ case 2:
+ startTalking(this, 70082, findView());
+ break;
+ default:
+ break;
+ }
+ } else {
+ CGameObject *mailObject = findMailByFlags(compareRoomNameTo("Titania")
+ ? 3 : _field140, petRoomFlags);
+ if (!mailObject) {
+ if (getRandomNumber(1) == 0) {
+ startTalking(this, 70104, findView());
+ } else {
+ startTalking(this, 70105, findView());
+ }
+
+ playMovie(_startFrame6, _endFrame6, 0);
+ playMovie(_startFrame7, _endFrame7, 0);
+ petDisplayMessage(2, "There is currently nothing to deliver.");
+ } else {
+ startTalking(this, 230004, findView());
+
+ if (_startFrame4 >= 0) {
+ _field158 = 1;
+ _field1D8 = 1;
+ inc54();
+ playMovie(_startFrame4, _endFrame4, MOVIE_NOTIFY_OBJECT);
+ }
+ }
+ }
+
+ return true;
+}
+
+bool CSuccUBus::MovieEndMsg(CMovieEndMsg *msg) {
+ CPetControl *pet = getPetControl();
+ uint petRoomFlags = pet ? pet->getRoomFlags() : 0;
+
+ if (msg->_endFrame == _endFrame10) {
+ if (_startFrame11 >= 0)
+ playSound("z#30.wav", 100);
+
+ if (_field15C) {
+ _field15C = false;
+ setVisible(false);
+ CSignalObject signalMsg;
+ signalMsg._numValue = 1;
+ signalMsg.execute(_string2);
+ }
+ }
+
+ if (msg->_endFrame == _endFrame9) {
+ bool flag = false;
+
+ if (pet && mailExists(petRoomFlags)) {
+ CGameObject *mailObject = _v3 && compareRoomNameTo("Titania") ?
+ findMailByFlags(3, petRoomFlags) :
+ findMailByFlags(_field140, petRoomFlags);
+
+ if (mailObject) {
+ switch (getRandomNumber(4)) {
+ case 0:
+ startTalking(this, 70094, findView());
+ break;
+ case 1:
+ startTalking(this, 70095, findView());
+ break;
+ case 2:
+ startTalking(this, 70096, findView());
+ break;
+ case 3:
+ startTalking(this, 70098, findView());
+ break;
+ case 4:
+ startTalking(this, 70099, findView());
+ break;
+ default:
+ break;
+ }
+ flag = true;
+ }
+ }
+
+ if (!_field188 && !flag) {
+ stopSound(_soundHandle);
+ _soundHandle = -1;
+
+ switch (getRandomNumber(_v2 ? 7 : 5, &_field1B0)) {
+ case 2:
+ startTalking(this, 230001, findView());
+ break;
+ case 3:
+ startTalking(this, 230002, findView());
+ break;
+ case 4:
+ startTalking(this, 230003, findView());
+ break;
+ case 5:
+ startTalking(this, 230064, findView());
+ break;
+ case 6:
+ startTalking(this, 230062, findView());
+ break;
+ case 7:
+ startTalking(this, 230063, findView());
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (msg->_endFrame == _endFrame3) {
+ if (_field158 == 1) {
+ startTalking(this, 230022, findView());
+ } else if (_field158 == 2) {
+ startTalking(this, 230017, findView());
+ } else if (_field19C) {
+ startTalking(this, 230019, findView());
+ _field19C = 0;
+ } else if (_isChicken) {
+ startTalking(this, 230018, findView());
+ _isChicken = false;
+ } else {
+ startTalking(this, 230013, findView());
+ }
+
+ if (_field1D8) {
+ _field1D8 = 0;
+ dec54();
+ }
+
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+ }
+
+ if (msg->_endFrame == _endFrame4) {
+ if (pet && _mailP) {
+ _mailP->setMailId(petRoomFlags);
+ }
+
+ _field188 = 1;
+ _mailP = 0;
+ if (_field1D8) {
+ _field1D8 = 0;
+ dec54();
+ }
+
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CSuccUBus::TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg) {
+ if (msg->_stateNum == 1)
+ msg->_stateVal = _enabled;
+
+ return true;
+}
+
+bool CSuccUBus::SignalObject(CSignalObject *msg) {
+ if (msg->_numValue == 1) {
+ _string2 = msg->_strValue;
+ _field15C = 1;
+ setVisible(true);
+ CTurnOn onMsg;
+ onMsg.execute(this);
+ }
+
+ return true;
+}
+
+bool CSuccUBus::TurnOn(CTurnOn *msg) {
+ if (getRandomNumber(9) == 0) {
+ CParrotSpeakMsg speakMsg("SuccUBus", "TurnOn");
+ speakMsg.execute("PerchedParrot");
+ }
+
+ CPetControl *pet = getPetControl();
+ if (pet) {
+ if (!_field15C && _startFrame8 >= 0) {
+ playMovie(_startFrame8, _endFrame8, 0);
+ playSound("z#30.wav", 100);
+ }
+
+ if (_startFrame9 >= 0) {
+ playMovie(_startFrame9, _endFrame9, MOVIE_NOTIFY_OBJECT);
+ playSound("z#26.wav", 100);
+ }
+
+ uint petRoomFlags = pet->getRoomFlags();
+ if (mailExists(petRoomFlags) && _endFrame1 >= 0)
+ playMovie(_endFrame1, _endFrame1, 0);
+
+ _enabled = true;
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+
+ endTalking(this, true, findView());
+ petSetArea(PET_REMOTE);
+ petHighlightGlyph(16);
+ }
+
+ return true;
+}
+
+bool CSuccUBus::TurnOff(CTurnOff *msg) {
+ if (_soundHandle != -1) {
+ stopSound(_soundHandle);
+ _soundHandle = -1;
+ }
+
+ if (_startFrame10 >= 0) {
+ playSound("z#27.wav", 100);
+ playMovie(_startFrame10, _endFrame10, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+ }
+
+ if (!_field15C && _startFrame11 >= 0)
+ playMovie(_startFrame11, _endFrame11, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE);
+
+ _enabled = false;
+ performAction(true);
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+
+ return true;
+}
+
+bool CSuccUBus::SUBTransition(CSUBTransition *msg) {
+ CPetControl *pet = getPetControl();
+
+ if (pet) {
+ uint petRoomFlags = pet->getRoomFlags();
+
+ if (_enabled) {
+ CGameObject *mailObject = findMail(petRoomFlags);
+ if (mailObject)
+ pet->phonographAction("Send");
+ else
+ pet->phonographAction("Receive");
+ } else {
+ if (pet->isSuccUBusRoom(petRoomFlags))
+ pet->phonographAction("Record");
+ else
+ pet->phonographAction("");
+ }
+ }
+
+ return true;
+}
+
+bool CSuccUBus::SetChevRoomBits(CSetChevRoomBits *msg) {
+ if (_enabled) {
+ _roomFlags = msg->_roomNum;
+ playSound("z#98.wav", 100);
+ }
+
+ return true;
+}
+
+bool CSuccUBus::ActMsg(CActMsg *msg) {
+ if (msg->_action == "EnableObject")
+ _v3 = 1;
+ else if (msg->_action == "DisableObject")
+ _v3 = 0;
+
+ return true;
+}
+
+bool CSuccUBus::MouseDragStartMsg(CMouseDragStartMsg *msg) {
+ CPetControl *pet = getPetControl();
+ Rect tempRect = _rect1;
+ tempRect.translate(_bounds.left, _bounds.top);
+
+ if (_field1D8 || !_enabled || !_field188 || !tempRect.contains(msg->_mousePos)
+ || !pet)
+ return true;
+
+ uint petRoomFlags = pet->getRoomFlags();
+ CGameObject *mailObject = findMail(petRoomFlags);
+ if (!mailObject)
+ return true;
+
+ petAddToCarryParcel(mailObject);
+ CViewItem *view = getView();
+ if (!view)
+ return true;
+
+ mailObject->moveUnder(view);
+ mailObject->setPosition(Point(msg->_mousePos.x + mailObject->_bounds.width() / 2,
+ msg->_mousePos.y + mailObject->_bounds.height() / 2));
+
+ CVisibleMsg visibleMsg(true);
+ visibleMsg.execute(mailObject);
+ CPassOnDragStartMsg dragMsg;
+ dragMsg._mousePos = msg->_mousePos;
+ dragMsg._value3 = 1;
+ dragMsg.execute(mailObject);
+
+ if (!dragMsg._value4)
+ msg->_dragItem = mailObject;
+
+ loadFrame(_field184);
+ _field188 = 0;
+ CSUBTransition transMsg;
+ transMsg.execute(this);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/succubus.h b/engines/titanic/npcs/succubus.h
index f6f5a6b9e9..7ca8037a0a 100644
--- a/engines/titanic/npcs/succubus.h
+++ b/engines/titanic/npcs/succubus.h
@@ -24,62 +24,76 @@
#define TITANIC_SUCCUBUS_H
#include "titanic/npcs/true_talk_npc.h"
+#include "titanic/messages/pet_messages.h"
namespace Titanic {
class CSuccUBus : public CTrueTalkNPC {
-private:
- static int _v0;
+ DECLARE_MESSAGE_MAP;
+ bool MouseButtonDownMsg(CMouseButtonDownMsg *msg);
+ bool SubAcceptCCarryMsg(CSubAcceptCCarryMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool LeaveViewMsg(CLeaveViewMsg *msg);
+ bool PETDeliverMsg(CPETDeliverMsg *msg);
+ bool PETReceiveMsg(CPETReceiveMsg *msg);
+ bool MovieEndMsg(CMovieEndMsg *msg);
+ bool TrueTalkGetStateValueMsg(CTrueTalkGetStateValueMsg *msg);
+ bool SignalObject(CSignalObject *msg);
+ bool TurnOn(CTurnOn *msg);
+ bool TurnOff(CTurnOff *msg);
+ bool SUBTransition(CSUBTransition *msg);
+ bool SetChevRoomBits(CSetChevRoomBits *msg);
+ bool ActMsg(CActMsg *msg);
+ bool MouseDragStartMsg(CMouseDragStartMsg *msg);
+protected:
+ static bool _enabled;
static int _v1;
static int _v2;
static int _v3;
static int _v4;
-private:
- int _field108;
- int _field10C;
- int _field110;
- int _field114;
- int _field118;
- int _field11C;
- int _field120;
- int _field124;
- int _field128;
- int _field12C;
- int _field130;
- int _field134;
- int _field138;
- int _field13C;
+protected:
+ int _startFrame8;
+ int _endFrame8;
+ int _startFrame11;
+ int _endFrame11;
+ int _startFrame3;
+ int _endFrame3;
+ int _startFrame4;
+ int _endFrame4;
+ int _startFrame9;
+ int _endFrame9;
+ int _startFrame10;
+ int _endFrame10;
+ int _startFrame2;
+ int _endFrame2;
int _field140;
- int _field144;
- int _field148;
- int _field14C;
- int _field150;
- int _field154;
+ CGameObject *_mailP;
+ int _startFrame5;
+ int _endFrame5;
+ int _startFrame12;
+ int _endFrame12;
int _field158;
- int _field15C;
+ bool _field15C;
CString _string2;
- int _field16C;
- int _field170;
- int _field174;
- int _field178;
- int _field17C;
- int _field180;
+ int _startFrame1;
+ int _endFrame1;
+ Rect _rect1;
int _field184;
int _field188;
- int _field18C;
+ Rect _rect2;
int _field190;
int _field194;
int _field198;
int _field19C;
- int _field1A0;
- int _field1A4;
- int _field1A8;
+ int _soundHandle;
+ bool _isChicken;
+ bool _isFeathers;
int _field1AC;
int _field1B0;
- int _field1B4;
- int _field1B8;
- int _field1BC;
- int _field1C0;
+ int _startFrame6;
+ int _endFrame6;
+ int _startFrame7;
+ int _endFrame7;
int _field1C4;
int _field1C8;
int _field1CC;
diff --git a/engines/titanic/npcs/summon_bots.cpp b/engines/titanic/npcs/summon_bots.cpp
index 8796e5ffda..6d71847548 100644
--- a/engines/titanic/npcs/summon_bots.cpp
+++ b/engines/titanic/npcs/summon_bots.cpp
@@ -24,6 +24,11 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CSummonBots, CRobotController)
+ ON_MESSAGE(SummonBotQueryMsg)
+ ON_MESSAGE(SummonBotMsg)
+END_MESSAGE_MAP()
+
CSummonBots::CSummonBots() : CRobotController(), _string2("NULL"),
_fieldC8(0), _fieldCC(0) {
}
@@ -46,4 +51,36 @@ void CSummonBots::load(SimpleFile *file) {
CRobotController::load(file);
}
+bool CSummonBots::SummonBotQueryMsg(CSummonBotQueryMsg *msg) {
+ if (msg->_npcName == "BellBot") {
+ if (_fieldC8 && !petCheckNode(_string2))
+ return true;
+ } else if (msg->_npcName == "DoorBot") {
+ if (_fieldCC && !petCheckNode(_string2))
+ return true;
+ }
+
+ return false;
+}
+
+bool CSummonBots::SummonBotMsg(CSummonBotMsg *msg) {
+ if (msg->_npcName == "BellBot") {
+ if (!_fieldC8)
+ return false;
+
+ if (petDismissBot("BellBot"))
+ petOnSummonBot("Bellbot", msg->_value);
+ } else if (msg->_npcName == "DoorBot") {
+ if (!_fieldCC)
+ return false;
+
+ if (petDismissBot("Doorbot"))
+ petOnSummonBot("Doorbot", msg->_value);
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/summon_bots.h b/engines/titanic/npcs/summon_bots.h
index ee537fee76..1da6e68245 100644
--- a/engines/titanic/npcs/summon_bots.h
+++ b/engines/titanic/npcs/summon_bots.h
@@ -28,6 +28,9 @@
namespace Titanic {
class CSummonBots : public CRobotController {
+ DECLARE_MESSAGE_MAP;
+ bool SummonBotQueryMsg(CSummonBotQueryMsg *msg);
+ bool SummonBotMsg(CSummonBotMsg *msg);
protected:
CString _string2;
int _fieldC8;
diff --git a/engines/titanic/npcs/titania.cpp b/engines/titanic/npcs/titania.cpp
index 34c21d0efe..000595f6b7 100644
--- a/engines/titanic/npcs/titania.cpp
+++ b/engines/titanic/npcs/titania.cpp
@@ -24,54 +24,202 @@
namespace Titanic {
+BEGIN_MESSAGE_MAP(CTitania, CCharacter)
+ ON_MESSAGE(AddHeadPieceMsg)
+ ON_MESSAGE(TakeHeadPieceMsg)
+ ON_MESSAGE(ActMsg)
+ ON_MESSAGE(EnterViewMsg)
+ ON_MESSAGE(TimerMsg)
+END_MESSAGE_MAP()
+
CTitania::CTitania() : CCharacter() {
- _fieldD4 = 0;
- _fieldD8 = 0;
- _fieldE0 = 0;
- _fieldE4 = 0;
- _fieldE8 = 0;
- _fieldEC = 0;
- _fieldF0 = 0;
- _fieldF4 = 0;
- _fieldF8 = 0;
- _fieldFC = 0;
- _field100 = 1;
+ _speechCentre = false;
+ _olfactoryCentre = false;
+ _centralCore = false;
+ _visionCentre = false;
+ _eye1 = false;
+ _eye2 = false;
+ _ear1 = false;
+ _ear2 = false;
+ _nose = false;
+ _mouth = false;
+ _showIntro = true;
}
void CTitania::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
- file->writeNumberLine(_fieldD4, indent);
- file->writeNumberLine(_fieldD8, indent);
- file->writeNumberLine(_fieldDC, indent);
- file->writeNumberLine(_fieldE0, indent);
- file->writeNumberLine(_fieldE4, indent);
- file->writeNumberLine(_fieldE8, indent);
- file->writeNumberLine(_fieldEC, indent);
- file->writeNumberLine(_fieldF0, indent);
- file->writeNumberLine(_fieldF4, indent);
- file->writeNumberLine(_fieldF8, indent);
- file->writeNumberLine(_fieldFC, indent);
- file->writeNumberLine(_field100, indent);
+ file->writeNumberLine(_speechCentre, indent);
+ file->writeNumberLine(_olfactoryCentre, indent);
+ file->writeNumberLine(_auditoryCentre, indent);
+ file->writeNumberLine(_centralCore, indent);
+ file->writeNumberLine(_visionCentre, indent);
+ file->writeNumberLine(_eye1, indent);
+ file->writeNumberLine(_eye2, indent);
+ file->writeNumberLine(_ear1, indent);
+ file->writeNumberLine(_ear2, indent);
+ file->writeNumberLine(_nose, indent);
+ file->writeNumberLine(_mouth, indent);
+ file->writeNumberLine(_showIntro, indent);
CCharacter::save(file, indent);
}
void CTitania::load(SimpleFile *file) {
file->readNumber();
- _fieldD4 = file->readNumber();
- _fieldD8 = file->readNumber();
- _fieldDC = file->readNumber();
- _fieldE0 = file->readNumber();
- _fieldE4 = file->readNumber();
- _fieldE8 = file->readNumber();
- _fieldEC = file->readNumber();
- _fieldF0 = file->readNumber();
- _fieldF4 = file->readNumber();
- _fieldF8 = file->readNumber();
- _fieldFC = file->readNumber();
- _field100 = file->readNumber();
+ _speechCentre = file->readNumber();
+ _olfactoryCentre = file->readNumber();
+ _auditoryCentre = file->readNumber();
+ _centralCore = file->readNumber();
+ _visionCentre = file->readNumber();
+ _eye1 = file->readNumber();
+ _eye2 = file->readNumber();
+ _ear1 = file->readNumber();
+ _ear2 = file->readNumber();
+ _nose = file->readNumber();
+ _mouth = file->readNumber();
+ _showIntro = file->readNumber();
CCharacter::load(file);
}
+bool CTitania::AddHeadPieceMsg(CAddHeadPieceMsg *msg) {
+ if (msg->_value == "VisionCentre") {
+ _visionCentre = true;
+ } else if (msg->_value == "AuditoryCentre") {
+ _auditoryCentre = true;
+ } else if (msg->_value == "OlfactoryCentre") {
+ _olfactoryCentre = true;
+ } else if (msg->_value == "SpeechCentre") {
+ _speechCentre = true;
+ } else if (msg->_value == "CentralCore") {
+ _centralCore = true;
+ } else if (msg->_value == "Eye1") {
+ _eye1 = true;
+ } else if (msg->_value == "Eye2") {
+ _eye2 = true;
+ } else if (msg->_value == "Ear1") {
+ _ear1 = true;
+ } else if (msg->_value == "Ear2") {
+ _ear2 = true;
+ } else if (msg->_value == "Mouth") {
+ _mouth = true;
+ } else if (msg->_value == "Nose") {
+ _nose = true;
+ }
+
+ CActMsg actMsg("CheckHead");
+ actMsg.execute(this);
+ return true;
+}
+
+bool CTitania::TakeHeadPieceMsg(CTakeHeadPieceMsg *msg) {
+ if (msg->_value == "VisionCentre") {
+ _visionCentre = false;
+ } else if (msg->_value == "AuditoryCentre") {
+ _auditoryCentre = false;
+ } else if (msg->_value == "OlfactoryCentre") {
+ _olfactoryCentre = false;
+ } else if (msg->_value == "SpeechCentre") {
+ _speechCentre = false;
+ } else if (msg->_value == "CentralCore") {
+ _centralCore = false;
+ } else if (msg->_value == "Eye1") {
+ _eye1 = false;
+ } else if (msg->_value == "Eye2") {
+ _eye2 = false;
+ } else if (msg->_value == "Ear1") {
+ _ear1 = false;
+ } else if (msg->_value == "Ear2") {
+ _ear2 = false;
+ } else if (msg->_value == "Mouth") {
+ _mouth = false;
+ } else if (msg->_value == "Nose") {
+ _nose = false;
+ }
+
+ CActMsg actMsg("CheckHead");
+ actMsg.execute(this);
+ return true;
+}
+
+bool CTitania::ActMsg(CActMsg *msg) {
+ if (msg->_action == "SleepTitania") {
+ setVisible(true);
+ playCutscene(52, 104);
+ playSound("z#47.wav", 100);
+ changeView("Titania.Node 7.S", "");
+
+ petShow();
+ enableMouse();
+ CSetFrameMsg frameMsg;
+ frameMsg.execute("Bomb");
+
+ } else if (msg->_action == "CheckHead") {
+ CSenseWorkingMsg workingMsg1("Not Working");
+ CSenseWorkingMsg workingMsg2("Not Working");
+ CSenseWorkingMsg workingMsg3("Not Working");
+ CSenseWorkingMsg workingMsg4("Not Working");
+
+ if (_eye1 && _eye2) {
+ workingMsg1._value = _visionCentre ? "Working" : "Random";
+ }
+ if (_ear1 && _ear2) {
+ workingMsg2._value = _auditoryCentre ? "Working" : "Random";
+ }
+ if (_nose) {
+ workingMsg4._value = _olfactoryCentre ? "Working" : "Random";
+ }
+ if (_mouth) {
+ workingMsg3._value = _speechCentre ? "Working" : "Random";
+ }
+
+ if (_centralCore && _eye1 && _eye2 && _ear1 && _ear2 && _nose && _mouth
+ && _speechCentre && _olfactoryCentre && _auditoryCentre) {
+ playSound("z#47.wav");
+
+ CActMsg actMsg("Woken");
+ actMsg.execute("MouthSlot");
+ actMsg.execute("VisionCentreSlot");
+ setPassengerClass(4);
+
+ addTimer(1000);
+ } else {
+ workingMsg1.execute("Eye1Slot");
+ workingMsg1.execute("Eye2Slot");
+ workingMsg2.execute("Ear1Slot");
+ workingMsg2.execute("Ear2Slot");
+ workingMsg3.execute("MouthSlot");
+ workingMsg4.execute("NoseSlot");
+ }
+ }
+
+ return true;
+}
+
+bool CTitania::EnterViewMsg(CEnterViewMsg *msg) {
+ if (_showIntro) {
+ _showIntro = false;
+ disableMouse();
+ petHide();
+
+ CSetFrameMsg frameMsg;
+ frameMsg._frameNumber = 25;
+ frameMsg.execute("Bomb");
+ playCutscene(0, 52);
+
+ setVisible(false);
+ CActMsg actMsg("TitaniaSpeech");
+ actMsg.execute("TitaniaSpeech");
+ }
+
+ return true;
+}
+
+bool CTitania::TimerMsg(CTimerMsg *msg) {
+ changeView("Titania.Node 18.N", "");
+ startTalking("PerchedParrot", 80022);
+
+ return true;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/npcs/titania.h b/engines/titanic/npcs/titania.h
index 4edd626ab6..61f8c86018 100644
--- a/engines/titanic/npcs/titania.h
+++ b/engines/titanic/npcs/titania.h
@@ -28,19 +28,25 @@
namespace Titanic {
class CTitania : public CCharacter {
+ DECLARE_MESSAGE_MAP;
+ bool AddHeadPieceMsg(CAddHeadPieceMsg *msg);
+ bool TakeHeadPieceMsg(CTakeHeadPieceMsg *msg);
+ bool ActMsg(CActMsg *msg);
+ bool EnterViewMsg(CEnterViewMsg *msg);
+ bool TimerMsg(CTimerMsg *msg);
private:
- int _fieldD4;
- int _fieldD8;
- int _fieldDC;
- int _fieldE0;
- int _fieldE4;
- int _fieldE8;
- int _fieldEC;
- int _fieldF0;
- int _fieldF4;
- int _fieldF8;
- int _fieldFC;
- int _field100;
+ bool _speechCentre;
+ bool _olfactoryCentre;
+ bool _auditoryCentre;
+ bool _centralCore;
+ bool _visionCentre;
+ bool _eye1;
+ bool _eye2;
+ bool _ear1;
+ bool _ear2;
+ bool _nose;
+ bool _mouth;
+ bool _showIntro;
public:
CLASSDEF;
CTitania();
diff --git a/engines/titanic/npcs/true_talk_npc.cpp b/engines/titanic/npcs/true_talk_npc.cpp
index 9310f285e5..97913dffea 100644
--- a/engines/titanic/npcs/true_talk_npc.cpp
+++ b/engines/titanic/npcs/true_talk_npc.cpp
@@ -23,7 +23,7 @@
#include "titanic/npcs/true_talk_npc.h"
#include "titanic/core/view_item.h"
#include "titanic/pet_control/pet_control.h"
-#include "titanic/titanic.h"
+#include "titanic/game_manager.h"
namespace Titanic {
@@ -103,7 +103,7 @@ bool CTrueTalkNPC::TrueTalkNotifySpeechStartedMsg(CTrueTalkNotifySpeechStartedMs
stopTimer(_speechTimerId);
_soundId = msg->_soundId;
- _fieldF0 = g_vm->_events->getTicksCount();
+ _fieldF0 = getTicksCount();
if (hasActiveMovie() || (_npcFlags & NPCFLAG_2)) {
_npcFlags &= ~NPCFLAG_2;
@@ -147,7 +147,7 @@ bool CTrueTalkNPC::MovieEndMsg(CMovieEndMsg *msg) {
return false;
}
- int diff = g_vm->_events->getTicksCount() - _fieldF0;
+ int diff = getTicksCount() - _fieldF0;
int ticks = MAX((int)_soundId - diff, 0);
CNPCPlayTalkingAnimationMsg msg1(ticks, ticks > 1000 ? 2 : 1, 0);
msg1.execute(this);
@@ -161,7 +161,7 @@ bool CTrueTalkNPC::MovieEndMsg(CMovieEndMsg *msg) {
}
bool CTrueTalkNPC::NPCQueueIdleAnimMsg(CNPCQueueIdleAnimMsg *msg) {
- int rndVal = g_vm->getRandomNumber(_fieldF8 - 1) - (_fieldF8 / 2);
+ int rndVal = getRandomNumber(_fieldF8 - 1) - (_fieldF8 / 2);
_speechTimerId = startAnimTimer("NPCIdleAnim", _fieldF4 + rndVal, 0);
return true;
@@ -188,7 +188,34 @@ bool CTrueTalkNPC::TimerMsg(CTimerMsg *msg) {
}
bool CTrueTalkNPC::NPCPlayAnimationMsg(CNPCPlayAnimationMsg *msg) {
- warning("CTrueTalkNPC::NPCPlayAnimationMsg");
+// const char *const *nameP = msg->_names;
+ int count;
+ for (count = 0; msg->_names[count]; ++count)
+ ;
+
+ if (msg->_maxDuration) {
+ // Randomly pick a clip that's less than the allowed maximum
+ int tries = 10, index;
+ do {
+ index = getRandomNumber(count - 1);
+ } while (getClipDuration(msg->_names[index]) > msg->_maxDuration && --tries);
+
+ if (tries) {
+ // Sequentially go through the clips to find any below the maximum
+ index = 0;
+ for (int idx = 0; idx < count; ++idx) {
+ if (getClipDuration(msg->_names[idx]) < msg->_maxDuration) {
+ index = idx;
+ break;
+ }
+ }
+ }
+
+ playClip(msg->_names[index], MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
+ } else {
+ playClip(msg->_names[getRandomNumber(count - 1)]);
+ }
+
return true;
}
@@ -198,10 +225,6 @@ void CTrueTalkNPC::processInput(CTextInputMsg *msg, CViewItem *view) {
talkManager->processInput(this, msg, view);
}
-void CTrueTalkNPC::stopAnimTimer(int id) {
- getGameManager()->stopTimer(id);
-}
-
void CTrueTalkNPC::setView(CViewItem *view) {
CTrueTalkManager *talkManager = getGameManager()->getTalkManager();
if (talkManager)
diff --git a/engines/titanic/npcs/true_talk_npc.h b/engines/titanic/npcs/true_talk_npc.h
index 0319f7e059..5254eaf9b7 100644
--- a/engines/titanic/npcs/true_talk_npc.h
+++ b/engines/titanic/npcs/true_talk_npc.h
@@ -31,8 +31,12 @@ namespace Titanic {
enum NpcFlag {
NPCFLAG_SPEAKING = 1, NPCFLAG_2 = 2, NPCFLAG_4 = 4, NPCFLAG_8 = 8,
- NPCFLAG_10000 = 0x10000, NPCFLAG_20000 = 0x20000, NPCFLAG_40000 = 0x40000,
- NPCFLAG_80000 = 0x80000, NPCFLAG_100000 = 0x100000
+ NPCFLAG_10000 = 0x10000, NPCFLAG_20000 = 0x20000,
+ NPCFLAG_40000 = 0x40000, NPCFLAG_80000 = 0x80000,
+ NPCFLAG_100000 = 0x100000, NPCFLAG_200000 = 0x200000,
+ NPCFLAG_400000 = 0x400000, NPCFLAG_800000 = 0x800000,
+ NPCFLAG_1000000 = 0x1000000, NPCFLAG_2000000 = 0x2000000,
+ NPCFLAG_4000000 = 0x4000000, NPCFLAG_8000000 = 0x8000000
};
class CViewItem;
@@ -58,20 +62,11 @@ protected:
int _fieldF4;
int _fieldF8;
int _speechTimerId;
- int _field100;
int _field104;
protected:
void processInput(CTextInputMsg *msg, CViewItem *view);
-
- /**
- * Stop an animation timer
- */
- void stopAnimTimer(int id);
-
- /**
- * Perform an action
- */
- void performAction(bool startTalking, CViewItem *view);
+public:
+ int _field100;
public:
CLASSDEF;
CTrueTalkNPC();
@@ -95,6 +90,11 @@ public:
* Start the talker in the given view
*/
void startTalker(CViewItem *view);
+
+ /**
+ * Perform an action
+ */
+ void performAction(bool startTalking, CViewItem *view = nullptr);
};
} // End of namespace Titanic
diff --git a/engines/titanic/pet_control/pet_control.cpp b/engines/titanic/pet_control/pet_control.cpp
index b32a7907a4..7ab76ddc1d 100644
--- a/engines/titanic/pet_control/pet_control.cpp
+++ b/engines/titanic/pet_control/pet_control.cpp
@@ -152,7 +152,7 @@ void CPetControl::postLoad() {
if (!_activeNPCName.empty() && root)
_activeNPC = root->findByName(_activeNPCName);
if (!_remoteTargetName.empty() && root)
- _remoteTarget = static_cast<CGameObject *>(root->findByName(_remoteTargetName));
+ _remoteTarget = dynamic_cast<CGameObject *>(root->findByName(_remoteTargetName));
setArea(_currentArea);
loaded();
@@ -194,6 +194,10 @@ void CPetControl::setActiveNPC(CTrueTalkNPC *npc) {
}
}
+void CPetControl::setActiveNPC(const CString &name) {
+ _conversations.setActiveNPC(name);
+}
+
void CPetControl::refreshNPC() {
_conversations.setNPC(_activeNPCName);
}
@@ -251,7 +255,7 @@ CRoomItem *CPetControl::getHiddenRoom() {
CGameObject *CPetControl::getHiddenObject(const CString &name) {
CRoomItem *room = getHiddenRoom();
- return room ? static_cast<CGameObject *>(findUnder(room, name)) : nullptr;
+ return room ? dynamic_cast<CGameObject *>(findUnder(room, name)) : nullptr;
}
bool CPetControl::containsPt(const Common::Point &pt) const {
@@ -355,7 +359,8 @@ bool CPetControl::VirtualKeyCharMsg(CVirtualKeyCharMsg *msg) {
}
bool CPetControl::TimerMsg(CTimerMsg *msg) {
- warning("TODO: CPetControl::CTimerMsg");
+ if (_timers[msg->_actionVal]._target)
+ _timers[msg->_actionVal]._target->timerExpired(msg->_actionVal);
return true;
}
@@ -376,21 +381,21 @@ void CPetControl::displayMessage(const CString &msg) const {
}
CGameObject *CPetControl::getFirstObject() const {
- return static_cast<CGameObject *>(getFirstChild());
+ return dynamic_cast<CGameObject *>(getFirstChild());
}
CGameObject *CPetControl::getNextObject(CGameObject *prior) const {
if (!prior || prior->getParent() != this)
return nullptr;
- return static_cast<CGameObject *>(prior->getNextSibling());
+ return dynamic_cast<CGameObject *>(prior->getNextSibling());
}
void CPetControl::addToInventory(CGameObject *item) {
item->detach();
if (item->getName() == "CarryParcel") {
- CCarry *child = static_cast<CCarry *>(getLastChild());
+ CCarry *child = dynamic_cast<CCarry *>(getLastChild());
if (child)
child->detach();
@@ -541,7 +546,7 @@ bool CPetControl::isBotInView(const CString &name) const {
// Iterate to find NPC
for (CTreeItem *child = view->getFirstChild(); child; child = child->scan(view)) {
- CGameObject *gameObject = static_cast<CGameObject *>(child);
+ CGameObject *gameObject = dynamic_cast<CGameObject *>(child);
if (gameObject) {
if (!gameObject->getName().compareToIgnoreCase(name))
return true;
@@ -609,7 +614,7 @@ bool CPetControl::isDoorOrBellbotPresent() const {
for (CTreeItem *treeItem = view->getFirstChild(); treeItem;
treeItem = treeItem->scan(view)) {
CString name = treeItem->getName();
- if (static_cast<CGameObject *>(treeItem) &&
+ if (dynamic_cast<CGameObject *>(treeItem) &&
(name.contains("Doorbot") || name.contains("BellBot")))
return true;
}
@@ -638,7 +643,7 @@ void CPetControl::setTimerPersisent(int id, bool flag) {
CGameObject *CPetControl::findBot(const CString &name, CTreeItem *root) {
for (CTreeItem *item = root; item; item = item->scan(root)) {
if (!item->getName().compareToIgnoreCase(name)) {
- CGameObject *obj = static_cast<CGameObject *>(item);
+ CGameObject *obj = dynamic_cast<CGameObject *>(item);
if (obj)
return obj;
}
@@ -660,6 +665,10 @@ void CPetControl::convResetDials(int flag) {
_conversations.resetDials(_activeNPCName);
}
+void CPetControl::resetDials0() {
+ _conversations.resetDials0();
+}
+
int CPetControl::getMailDest(const CRoomFlags &roomFlags) const {
if (!roomFlags.isSuccUBusRoomFlags())
return roomFlags.getPassengerClassNum();
diff --git a/engines/titanic/pet_control/pet_control.h b/engines/titanic/pet_control/pet_control.h
index a86d110458..439a94e2d3 100644
--- a/engines/titanic/pet_control/pet_control.h
+++ b/engines/titanic/pet_control/pet_control.h
@@ -358,9 +358,7 @@ public:
/**
* Sets the active NPC
*/
- void setActiveNPC(const CString &name) {
- _conversations.setActiveNPC(name);
- }
+ void setActiveNPC(const CString &name);
/**
* Sets the actie NPC
@@ -387,7 +385,7 @@ public:
/**
* Resets the conversation dials back to 0 position
*/
- void resetDials0() { _conversations.resetDials0(); }
+ void resetDials0();
/**
* Resets the dial display in the conversation tab to reflect new values
diff --git a/engines/titanic/pet_control/pet_conversations.h b/engines/titanic/pet_control/pet_conversations.h
index 9e8b093d62..3333bdc523 100644
--- a/engines/titanic/pet_control/pet_conversations.h
+++ b/engines/titanic/pet_control/pet_conversations.h
@@ -100,16 +100,6 @@ private:
void summonBot(const CString &name);
/**
- * Starts the NPC timer
- */
- void startNPCTimer();
-
- /**
- * Stops the NPC timer
- */
- void stopNPCTimer();
-
- /**
* Get the TrueTalk script associated with a given NPC
*/
TTnpcScript *getNPCScript(const CString &name) const;
@@ -260,6 +250,16 @@ public:
* Adds a line to the log
*/
void addLine(const CString &line);
+
+ /**
+ * Starts the NPC timer
+ */
+ void startNPCTimer();
+
+ /**
+ * Stops the NPC timer
+ */
+ void stopNPCTimer();
};
} // End of namespace Titanic
diff --git a/engines/titanic/pet_control/pet_drag_chev.cpp b/engines/titanic/pet_control/pet_drag_chev.cpp
index d437d43799..7816570a23 100644
--- a/engines/titanic/pet_control/pet_drag_chev.cpp
+++ b/engines/titanic/pet_control/pet_drag_chev.cpp
@@ -55,7 +55,7 @@ bool CPetDragChev::MouseDragMoveMsg(CMouseDragMoveMsg *msg) {
bool CPetDragChev::MouseDragEndMsg(CMouseDragEndMsg *msg) {
if (msg->_dropTarget) {
- CSuccUBus *succubus = static_cast<CSuccUBus *>(msg->_dropTarget);
+ CSuccUBus *succubus = dynamic_cast<CSuccUBus *>(msg->_dropTarget);
if (succubus) {
CSetChevRoomBits chevMsg(_id);
diff --git a/engines/titanic/pet_control/pet_inventory_glyphs.cpp b/engines/titanic/pet_control/pet_inventory_glyphs.cpp
index ae306649a2..783a8a9717 100644
--- a/engines/titanic/pet_control/pet_inventory_glyphs.cpp
+++ b/engines/titanic/pet_control/pet_inventory_glyphs.cpp
@@ -165,7 +165,7 @@ void CPetInventoryGlyph::getTooltip(CPetText *text) {
bool CPetInventoryGlyph::doAction(CGlyphAction *action) {
CInventoryGlyphAction *invAction = static_cast<CInventoryGlyphAction *>(action);
- CPetInventoryGlyphs *owner = static_cast<CPetInventoryGlyphs *>(_owner);
+ CPetInventoryGlyphs *owner = dynamic_cast<CPetInventoryGlyphs *>(_owner);
if (!invAction)
return false;
@@ -203,8 +203,8 @@ void CPetInventoryGlyph::setItem(CGameObject *item, int val) {
if (_owner && item) {
int v1 = populateItem(item, val);
- _background = static_cast<CPetInventoryGlyphs *>(_owner)->getBackground(v1);
- _image = static_cast<CPetInventory *>(getPetSection())->getImage(v1);
+ _background = dynamic_cast<CPetInventoryGlyphs *>(_owner)->getBackground(v1);
+ _image = dynamic_cast<CPetInventory *>(getPetSection())->getImage(v1);
}
}
@@ -293,7 +293,7 @@ int CPetInventoryGlyph::subMode(CGameObject *item, int val) {
void CPetInventoryGlyph::startBackgroundMovie() {
if (_owner) {
- CPetInventory *section = static_cast<CPetInventory *>(_owner->getOwner());
+ CPetInventory *section = dynamic_cast<CPetInventory *>(_owner->getOwner());
if (section)
section->playMovie(_background, 1);
}
@@ -301,7 +301,7 @@ void CPetInventoryGlyph::startBackgroundMovie() {
void CPetInventoryGlyph::startForegroundMovie() {
if (_owner) {
- CPetInventory *section = static_cast<CPetInventory *>(_owner->getOwner());
+ CPetInventory *section = dynamic_cast<CPetInventory *>(_owner->getOwner());
if (section)
section->playMovie(_image, 1);
}
@@ -309,7 +309,7 @@ void CPetInventoryGlyph::startForegroundMovie() {
void CPetInventoryGlyph::stopMovie() {
if (_owner) {
- CPetInventory *section = static_cast<CPetInventory *>(_owner->getOwner());
+ CPetInventory *section = dynamic_cast<CPetInventory *>(_owner->getOwner());
if (section)
section->playMovie(nullptr, 1);
}
diff --git a/engines/titanic/pet_control/pet_load_save.h b/engines/titanic/pet_control/pet_load_save.h
index dd1c907ef1..26ddec0ff9 100644
--- a/engines/titanic/pet_control/pet_load_save.h
+++ b/engines/titanic/pet_control/pet_load_save.h
@@ -38,11 +38,6 @@ private:
Rect getSlotBounds(int index);
/**
- * Highlight one of the slots
- */
- void highlightSlot(int index);
-
- /**
* Called when savegame slot highlight changes or the view is reset
*/
void highlightChange();
@@ -67,6 +62,11 @@ protected:
* Reset the slot names list
*/
void resetSlots();
+
+ /**
+ * Highlight one of the slots
+ */
+ void highlightSlot(int index);
public:
/**
* Setup the glyph
diff --git a/engines/titanic/pet_control/pet_remote_glyphs.cpp b/engines/titanic/pet_control/pet_remote_glyphs.cpp
index 6b7c8cb4ae..35a7ab39ac 100644
--- a/engines/titanic/pet_control/pet_remote_glyphs.cpp
+++ b/engines/titanic/pet_control/pet_remote_glyphs.cpp
@@ -29,7 +29,7 @@
namespace Titanic {
CPetRemote *CPetRemoteGlyphs::getOwner() const {
- return static_cast<CPetRemote *>(_owner);
+ return dynamic_cast<CPetRemote *>(_owner);
}
void CPetRemoteGlyphs::generateMessage(RemoteMessage msgNum, const CString &name, int num) {
@@ -44,11 +44,11 @@ void CPetRemoteGlyph::setDefaults(const CString &name, CPetControl *petControl)
}
CPetRemoteGlyphs *CPetRemoteGlyph::getOwner() const {
- return static_cast<CPetRemoteGlyphs *>(_owner);
+ return dynamic_cast<CPetRemoteGlyphs *>(_owner);
}
CPetGfxElement *CPetRemoteGlyph::getElement(uint id) const {
- CPetRemote *remote = static_cast<CPetRemote *>(_owner->getOwner());
+ CPetRemote *remote = dynamic_cast<CPetRemote *>(_owner->getOwner());
return remote->getElement(id);
}
diff --git a/engines/titanic/pet_control/pet_rooms.cpp b/engines/titanic/pet_control/pet_rooms.cpp
index 2415c96966..2ec66b08e2 100644
--- a/engines/titanic/pet_control/pet_rooms.cpp
+++ b/engines/titanic/pet_control/pet_rooms.cpp
@@ -304,7 +304,7 @@ CPetRoomsGlyph *CPetRooms::addRoom(uint roomFlags, bool highlight_) {
// Do a preliminary scan of the glyph list for any glyph that is
// no longer valid, and thus can be removed
for (CPetRoomsGlyphs::iterator i = _glyphs.begin(); i != _glyphs.end(); ++i) {
- CPetRoomsGlyph *glyph = static_cast<CPetRoomsGlyph *>(*i);
+ CPetRoomsGlyph *glyph = dynamic_cast<CPetRoomsGlyph *>(*i);
if (!glyph->isAssigned()) {
_glyphs.erase(i);
break;
@@ -340,7 +340,7 @@ bool CPetRooms::changeLocationClass(int newClassNum) {
bool CPetRooms::hasRoomFlags(uint roomFlags) const {
for (CPetRoomsGlyphs::const_iterator i = _glyphs.begin(); i != _glyphs.end(); ++i) {
- const CPetRoomsGlyph *glyph = static_cast<const CPetRoomsGlyph *>(*i);
+ const CPetRoomsGlyph *glyph = dynamic_cast<const CPetRoomsGlyph *>(*i);
if (glyph->isAssigned() && glyph->getRoomFlags() == roomFlags)
return true;
}
diff --git a/engines/titanic/pet_control/pet_rooms_glyphs.cpp b/engines/titanic/pet_control/pet_rooms_glyphs.cpp
index d9e19b1f67..d7ac634f5d 100644
--- a/engines/titanic/pet_control/pet_rooms_glyphs.cpp
+++ b/engines/titanic/pet_control/pet_rooms_glyphs.cpp
@@ -141,7 +141,7 @@ bool CPetRoomsGlyph::dragGlyph(const Point &topLeft, CMouseDragStartMsg *msg) {
void CPetRoomsGlyph::getTooltip(CPetText *text) {
CRoomFlags roomFlags(_roomFlags);
- CPetRooms *owner = static_cast<CPetRooms *>(getPetSection());
+ CPetRooms *owner = dynamic_cast<CPetRooms *>(getPetSection());
CString msg;
if (isCurrentlyAssigned()) {
@@ -172,7 +172,7 @@ void CPetRoomsGlyph::saveGlyph(SimpleFile *file, int indent) {
}
bool CPetRoomsGlyph::proc33(CPetGlyph *glyph) {
- CPetRoomsGlyph *roomGlyph = static_cast<CPetRoomsGlyph *>(glyph);
+ CPetRoomsGlyph *roomGlyph = dynamic_cast<CPetRoomsGlyph *>(glyph);
return CPetGlyph::proc33(glyph) && _roomFlags == roomGlyph->_roomFlags;
}
@@ -236,7 +236,7 @@ void CPetRoomsGlyphs::saveGlyphs(SimpleFile *file, int indent) {
CPetRoomsGlyph *CPetRoomsGlyphs::findAssignedRoom() const {
for (const_iterator i = begin(); i != end(); ++i) {
- CPetRoomsGlyph *glyph = static_cast<CPetRoomsGlyph *>(*i);
+ CPetRoomsGlyph *glyph = dynamic_cast<CPetRoomsGlyph *>(*i);
if (glyph->isCurrentlyAssigned())
return glyph;
}
@@ -246,7 +246,7 @@ CPetRoomsGlyph *CPetRoomsGlyphs::findAssignedRoom() const {
CPetRoomsGlyph *CPetRoomsGlyphs::findGlyphByFlags(uint flags) const {
for (const_iterator i = begin(); i != end(); ++i) {
- CPetRoomsGlyph *glyph = static_cast<CPetRoomsGlyph *>(*i);
+ CPetRoomsGlyph *glyph = dynamic_cast<CPetRoomsGlyph *>(*i);
if (glyph->getRoomFlags() == flags)
return glyph;
}
diff --git a/engines/titanic/pet_control/pet_save.cpp b/engines/titanic/pet_control/pet_save.cpp
index b5e16736bc..9305759117 100644
--- a/engines/titanic/pet_control/pet_save.cpp
+++ b/engines/titanic/pet_control/pet_save.cpp
@@ -22,6 +22,7 @@
#include "titanic/pet_control/pet_save.h"
#include "titanic/pet_control/pet_control.h"
+#include "titanic/core/project_item.h"
namespace Titanic {
@@ -58,15 +59,28 @@ void CPetSave::getTooltip(CPetText *text) {
}
void CPetSave::highlightSave(int index) {
- warning("TODO: CPetSave::highlightSave");
+ if (index >= 0)
+ _slotNames[index].showCursor(-2);
}
void CPetSave::unhighlightSave(int index) {
- warning("TODO: CPetSave::unhighlightSave");
+ if (index >= 0)
+ _slotNames[index].hideCursor();
}
void CPetSave::execute() {
- warning("TODO: CPetSave::execute");
+ CPetControl *pet = getPetControl();
+ if (_savegameSlotNum >= 0) {
+ highlightSlot(-1);
+ CProjectItem *project = pet ? pet->getRoot() : nullptr;
+
+ if (project) {
+ project->saveGame(_savegameSlotNum, _slotNames[_savegameSlotNum].getText());
+ pet->displayMessage("");
+ }
+ } else if (pet) {
+ pet->displayMessage("You must select a game to save first.");
+ }
}
} // End of namespace Titanic
diff --git a/engines/titanic/room_flags.cpp b/engines/titanic/room_flags.cpp
index 4b3aeb424e..9be8ea3d33 100644
--- a/engines/titanic/room_flags.cpp
+++ b/engines/titanic/room_flags.cpp
@@ -339,12 +339,12 @@ bool CRoomFlags::getBit0() const {
uint CRoomFlags::getSpecialRoomFlags(const CString &roomName) {
for (int idx = 0; idx < SUCCUBUS_ROOMS_SIZE; ++idx) {
- if (!roomName.compareTo(SUCCUBUS_ROOMS[idx]._roomName))
+ if (roomName == SUCCUBUS_ROOMS[idx]._roomName)
return SUCCUBUS_ROOMS[idx]._roomFlags;
}
for (int idx = 0; idx < TRANSPORT_ROOMS_SIZE; ++idx) {
- if (!roomName.compareTo(TRANSPORT_ROOMS[idx]._roomName))
+ if (roomName == TRANSPORT_ROOMS[idx]._roomName)
return TRANSPORT_ROOMS[idx]._roomFlags;
}
@@ -353,7 +353,7 @@ uint CRoomFlags::getSpecialRoomFlags(const CString &roomName) {
uint CRoomFlags::getSuccUBusNum(const CString &roomName) const {
for (int idx = 0; idx < SUCCUBUS_ROOMS_SIZE; ++idx) {
- if (!roomName.compareTo(SUCCUBUS_ROOMS[idx]._roomName))
+ if (roomName == SUCCUBUS_ROOMS[idx]._roomName)
return SUCCUBUS_ROOMS[idx]._succubusNum;
}
diff --git a/engines/titanic/sound/auto_sound_player.cpp b/engines/titanic/sound/auto_sound_player.cpp
index 1fb67858da..8267d65037 100644
--- a/engines/titanic/sound/auto_sound_player.cpp
+++ b/engines/titanic/sound/auto_sound_player.cpp
@@ -75,7 +75,7 @@ bool CAutoSoundPlayer::TurnOn(CTurnOn *msg) {
prox._fieldC = _fieldD0;
prox._repeated = _repeated;
if (_fieldE8)
- prox._field28 = 2;
+ prox._positioningMode = POSMODE_VECTOR;
prox._channelVolume = (_startSeconds == -1) ? _volume : 0;
_soundHandle = playSound(_filename, prox);
diff --git a/engines/titanic/sound/music_player.cpp b/engines/titanic/sound/music_player.cpp
index fb5a596875..a1aaf8ff8b 100644
--- a/engines/titanic/sound/music_player.cpp
+++ b/engines/titanic/sound/music_player.cpp
@@ -39,7 +39,7 @@ void CMusicPlayer::save(SimpleFile *file, int indent) {
file->writeNumberLine(1, indent);
file->writeNumberLine(_isActive, indent);
file->writeQuotedLine(_stopTarget, indent);
- file->writeNumberLine(_fieldCC, indent);
+ file->writeNumberLine(_stopWaves, indent);
file->writeNumberLine(_musicId, indent);
CGameObject::save(file, indent);
@@ -49,7 +49,7 @@ void CMusicPlayer::load(SimpleFile *file) {
file->readNumber();
_isActive = file->readNumber();
_stopTarget = file->readString();
- _fieldCC = file->readNumber();
+ _stopWaves = file->readNumber();
_musicId = file->readNumber();
CGameObject::load(file);
@@ -97,7 +97,7 @@ bool CMusicPlayer::StopMusicMsg(CStopMusicMsg *msg) {
}
bool CMusicPlayer::FrameMsg(CFrameMsg *msg) {
- if (_isActive && !CMusicRoom::_musicHandler->isBusy()) {
+ if (_isActive && !CMusicRoom::_musicHandler->poll()) {
getMusicRoom()->stopMusic();
_isActive = false;
@@ -120,11 +120,11 @@ bool CMusicPlayer::LeaveRoomMsg(CLeaveRoomMsg *msg) {
bool CMusicPlayer::CreateMusicPlayerMsg(CCreateMusicPlayerMsg *msg) {
if (CMusicRoom::_musicHandler) {
- CMusicRoom::_musicHandler->set124(_fieldCC);
+ CMusicRoom::_musicHandler->setStopWaves(_stopWaves);
return true;
}
- CMusicHandler *musicHandler = getMusicRoom()->createMusicHandler();
+ CMusicRoomHandler *musicHandler = getMusicRoom()->createMusicHandler();
CMusicWave *wave;
if (musicHandler) {
@@ -156,7 +156,7 @@ bool CMusicPlayer::CreateMusicPlayerMsg(CCreateMusicPlayerMsg *msg) {
wave->load(5, "z#505.wav", 53);
wave->load(6, "z#501.wav", 58);
- CMusicRoom::_musicHandler->set124(_fieldCC);
+ CMusicRoom::_musicHandler->setStopWaves(_stopWaves);
}
return true;
diff --git a/engines/titanic/sound/music_player.h b/engines/titanic/sound/music_player.h
index 3ed1bffdbd..7b82d4da00 100644
--- a/engines/titanic/sound/music_player.h
+++ b/engines/titanic/sound/music_player.h
@@ -41,12 +41,12 @@ class CMusicPlayer : public CGameObject {
protected:
bool _isActive;
CString _stopTarget;
- int _fieldCC;
+ bool _stopWaves;
int _musicId;
public:
CLASSDEF;
CMusicPlayer() : CGameObject(),
- _isActive(false), _fieldCC(0), _musicId(100) {}
+ _isActive(false), _stopWaves(false), _musicId(100) {}
/**
* Save the data for the class to file
diff --git a/engines/titanic/sound/music_room.cpp b/engines/titanic/sound/music_room.cpp
index 06cf866811..9586f55c58 100644
--- a/engines/titanic/sound/music_room.cpp
+++ b/engines/titanic/sound/music_room.cpp
@@ -24,27 +24,26 @@
#include "titanic/sound/music_room.h"
#include "titanic/sound/sound.h"
#include "titanic/game_manager.h"
-#include "titanic/titanic.h"
namespace Titanic {
-CMusicHandler *CMusicRoom::_musicHandler;
+CMusicRoomHandler *CMusicRoom::_musicHandler;
CMusicRoom::CMusicRoom(CGameManager *gameManager) :
_gameManager(gameManager) {
_sound = &_gameManager->_sound;
- _items.resize(4);
+ _controls.resize(4);
}
CMusicRoom::~CMusicRoom() {
destroyMusicHandler();
}
-CMusicHandler *CMusicRoom::createMusicHandler() {
+CMusicRoomHandler *CMusicRoom::createMusicHandler() {
if (_musicHandler)
destroyMusicHandler();
- _musicHandler = new CMusicHandler(_gameManager->_project, &_sound->_soundManager);
+ _musicHandler = new CMusicRoomHandler(_gameManager->_project, &_sound->_soundManager);
return _musicHandler;
}
@@ -53,12 +52,45 @@ void CMusicRoom::destroyMusicHandler() {
_musicHandler = nullptr;
}
-void CMusicRoom::startMusic(int musicId) {
- // TODO
+void CMusicRoom::startMusic(int volume) {
+ if (_musicHandler) {
+ _musicHandler->setSpeedControl2(BELLS, 0);
+ _musicHandler->setSpeedControl2(SNAKE, 1);
+ _musicHandler->setSpeedControl2(PIANO, -1);
+ _musicHandler->setSpeedControl2(BASS, -2);
+
+ _musicHandler->setPitchControl2(BELLS, 1);
+ _musicHandler->setPitchControl2(SNAKE, 2);
+ _musicHandler->setPitchControl2(PIANO, 0);
+ _musicHandler->setPitchControl2(BELLS, 1);
+
+ _musicHandler->setInversionControl2(BELLS, 1);
+ _musicHandler->setInversionControl2(SNAKE, 0);
+ _musicHandler->setInversionControl2(PIANO, 1);
+ _musicHandler->setInversionControl2(BASS, 0);
+
+ _musicHandler->setDirectionControl2(BELLS, 0);
+ _musicHandler->setDirectionControl2(SNAKE, 0);
+ _musicHandler->setDirectionControl2(PIANO, 1);
+ _musicHandler->setDirectionControl2(BASS, 1);
+
+ for (MusicControlArea idx = BELLS; idx <= BASS;
+ idx = (MusicControlArea)((int)idx + 1)) {
+ Controls &controls = _controls[idx];
+ _musicHandler->setSpeedControl(idx, controls._speedControl);
+ _musicHandler->setPitchControl(idx, controls._pitchControl);
+ _musicHandler->setDirectionControl(idx, controls._directionControl);
+ _musicHandler->setInversionControl(idx, controls._inversionControl);
+ _musicHandler->setMuteControl(idx, controls._muteControl);
+ }
+
+ _musicHandler->createWaveFile(volume);
+ }
}
void CMusicRoom::stopMusic() {
- // TODO
+ if (_musicHandler)
+ _musicHandler->stop();
}
} // End of namespace Titanic
diff --git a/engines/titanic/sound/music_room.h b/engines/titanic/sound/music_room.h
index 15363ef392..4b584a0dd4 100644
--- a/engines/titanic/sound/music_room.h
+++ b/engines/titanic/sound/music_room.h
@@ -24,7 +24,7 @@
#define TITANIC_MUSIC_ROOM_H
#include "common/array.h"
-#include "titanic/sound/music_handler.h"
+#include "titanic/sound/music_room_handler.h"
namespace Titanic {
@@ -32,19 +32,20 @@ class CGameManager;
class CSound;
class CMusicRoom {
- struct Entry {
- uint _val1;
- uint _val2;
- uint _val3;
- uint _val4;
- uint _val5;
+ struct Controls {
+ int _speedControl;
+ int _pitchControl;
+ int _directionControl;
+ int _inversionControl;
+ int _muteControl;
- Entry() : _val1(0), _val2(0), _val3(0), _val4(0), _val5(0) {}
+ Controls() : _speedControl(0), _pitchControl(0), _directionControl(0),
+ _inversionControl(0), _muteControl(0) {}
};
private:
- Common::Array<Entry> _items;
+ Common::Array<Controls> _controls;
public:
- static CMusicHandler *_musicHandler;
+ static CMusicRoomHandler *_musicHandler;
public:
CGameManager *_gameManager;
CSound *_sound;
@@ -55,23 +56,23 @@ public:
/**
* Creates a music handler
*/
- CMusicHandler *createMusicHandler();
+ CMusicRoomHandler *createMusicHandler();
/**
* Destroys and currently active music handler
*/
void destroyMusicHandler();
- void setItem1(int index, int val) { _items[index]._val1 = val; }
- void setItem2(int index, int val) { _items[index]._val2 = val; }
- void setItem3(int index, int val) { _items[index]._val3 = val; }
- void setItem4(int index, int val) { _items[index]._val4 = val; }
- void setItem5(int index, int val) { _items[index]._val5 = val; }
+ void setSpeedControl(MusicControlArea index, int val) { _controls[index]._speedControl = val; }
+ void setPitchControl(MusicControlArea index, int val) { _controls[index]._pitchControl = val; }
+ void setDirectionControl(MusicControlArea index, int val) { _controls[index]._directionControl = val; }
+ void setInversionControl(MusicControlArea index, int val) { _controls[index]._inversionControl = val; }
+ void setMuteControl(MusicControlArea index, int val) { _controls[index]._muteControl = val; }
/**
* Start playing a given music number
*/
- void startMusic(int musicId);
+ void startMusic(int volume = 100);
/**
* Stop playing music
diff --git a/engines/titanic/sound/music_room_handler.cpp b/engines/titanic/sound/music_room_handler.cpp
new file mode 100644
index 0000000000..ca37485eab
--- /dev/null
+++ b/engines/titanic/sound/music_room_handler.cpp
@@ -0,0 +1,138 @@
+/* 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 "titanic/sound/music_room_handler.h"
+#include "titanic/sound/sound_manager.h"
+#include "titanic/core/project_item.h"
+
+namespace Titanic {
+
+CMusicRoomHandler::CMusicRoomHandler(CProjectItem *project, CSoundManager *soundManager) :
+ _project(project), _soundManager(soundManager), _stopWaves(false),
+ _soundHandle(-1), _waveFile(nullptr), _soundVolume(100), _ticks(0),
+ _field108(0) {
+ Common::fill(&_musicWaves[0], &_musicWaves[4], (CMusicWave *)nullptr);
+}
+
+CMusicRoomHandler::~CMusicRoomHandler() {
+ stop();
+ for (int idx = 0; idx < 4; ++idx)
+ delete _musicWaves[idx];
+}
+
+CMusicWave *CMusicRoomHandler::createMusicWave(int waveIndex, int count) {
+ switch (waveIndex) {
+ case 0:
+ _musicWaves[waveIndex] = new CMusicWave(_project, _soundManager, 2);
+ break;
+ case 1:
+ _musicWaves[waveIndex] = new CMusicWave(_project, _soundManager, 3);
+ break;
+ case 2:
+ _musicWaves[waveIndex] = new CMusicWave(_project, _soundManager, 0);
+ break;
+ case 3:
+ _musicWaves[waveIndex] = new CMusicWave(_project, _soundManager, 1);
+ break;
+ default:
+ return nullptr;
+ }
+
+ _musicWaves[waveIndex]->setSize(count);
+ return _musicWaves[waveIndex];
+}
+
+void CMusicRoomHandler::createWaveFile(int musicVolume) {
+ _soundVolume = musicVolume;
+// _waveFile = _soundManager->loadMusic()
+}
+
+bool CMusicRoomHandler::poll() {
+ // TODO
+ return false;
+}
+
+void CMusicRoomHandler::stop() {
+ if (_waveFile) {
+ _soundManager->stopSound(_soundHandle);
+ delete _waveFile;
+ _waveFile = nullptr;
+ _soundHandle = -1;
+ }
+
+ for (int idx = 0; idx < 4; ++idx) {
+ if (_stopWaves && _musicWaves[idx])
+ _musicWaves[idx]->stop();
+ }
+}
+
+bool CMusicRoomHandler::checkSound(int index) const {
+ // TODO
+ return false;
+}
+
+void CMusicRoomHandler::setSpeedControl2(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array2[area]._speedControl = value;
+}
+
+void CMusicRoomHandler::setPitchControl2(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array2[area]._pitchControl = value * 3;
+}
+
+void CMusicRoomHandler::setInversionControl2(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array2[area]._inversionControl = value;
+}
+
+void CMusicRoomHandler::setDirectionControl2(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array2[area]._directionControl = value;
+}
+
+void CMusicRoomHandler::setPitchControl(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array1[area]._pitchControl = value;
+}
+
+void CMusicRoomHandler::setSpeedControl(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array1[area]._speedControl = value;
+}
+
+void CMusicRoomHandler::setDirectionControl(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array1[area]._directionControl = value;
+}
+
+void CMusicRoomHandler::setInversionControl(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array1[area]._inversionControl = value;
+}
+
+void CMusicRoomHandler::setMuteControl(MusicControlArea area, int value) {
+ if (area >= 0 && area <= 3 && value >= -2 && value <= 2)
+ _array1[area]._muteControl = value;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/sound/music_room_handler.h b/engines/titanic/sound/music_room_handler.h
new file mode 100644
index 0000000000..61b332dc7a
--- /dev/null
+++ b/engines/titanic/sound/music_room_handler.h
@@ -0,0 +1,125 @@
+/* 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 TITANIC_MUSIC_ROOM_HANDLER_H
+#define TITANIC_MUSIC_ROOM_HANDLER_H
+
+#include "titanic/sound/music_wave.h"
+#include "titanic/sound/wave_file.h"
+
+namespace Titanic {
+
+class CProjectItem;
+class CSoundManager;
+
+enum MusicControlArea { BELLS = 0, SNAKE = 1, PIANO = 2, BASS = 3 };
+
+class CMusicRoomHandler {
+ struct Controls {
+ int _pitchControl;
+ int _speedControl;
+ int _directionControl;
+ int _inversionControl;
+ int _muteControl;
+ Controls() : _pitchControl(0), _speedControl(0), _directionControl(0),
+ _inversionControl(0), _muteControl(0) {}
+ };
+ struct Array5Entry {
+ int _v1;
+ int _v2;
+ Array5Entry() : _v1(0), _v2(0) {}
+ };
+private:
+ CProjectItem *_project;
+ CSoundManager *_soundManager;
+ CMusicWave *_musicWaves[4];
+ Controls _array1[4];
+ Controls _array2[4];
+ Array5Entry _array5[4];
+ bool _stopWaves;
+ CWaveFile *_waveFile;
+ int _soundHandle;
+ int _soundVolume;
+ uint _ticks;
+ int _field108;
+public:
+ CMusicRoomHandler(CProjectItem *project, CSoundManager *soundManager);
+ ~CMusicRoomHandler();
+
+ /**
+ * Creates a new music wave class instance, and assigns it to a slot
+ * in the music handler
+ * @param waveIndex Slot to save new instance in
+ * @param count Number of files the new instance will contain
+ */
+ CMusicWave *createMusicWave(int waveIndex, int count);
+
+ void createWaveFile(int musicVolume);
+
+ /**
+ * Handles regular polling the music handler
+ */
+ bool poll();
+
+ /**
+ * Flags whether the loaded music waves will be stopped when the
+ * music handler is stopped
+ */
+ void setStopWaves(bool flag) { _stopWaves = flag; }
+
+ /**
+ * Stop playing the music
+ */
+ void stop();
+
+ bool checkSound(int index) const;
+
+ /**
+ * Set a setting
+ */
+ void setSpeedControl2(MusicControlArea area, int value);
+
+ /**
+ * Set a setting
+ */
+ void setPitchControl2(MusicControlArea area, int value);
+
+ /**
+ * Set a setting
+ */
+ void setInversionControl2(MusicControlArea area, int value);
+
+ /**
+ * Set a setting
+ */
+ void setDirectionControl2(MusicControlArea area, int value);
+
+ void setPitchControl(MusicControlArea area, int value);
+ void setSpeedControl(MusicControlArea area, int value);
+ void setDirectionControl(MusicControlArea area, int value);
+ void setInversionControl(MusicControlArea area, int value);
+ void setMuteControl(MusicControlArea area, int value);
+};
+
+} // End of namespace Titanic
+
+#endif /* TITANIC_MUSIC_ROOM_HANDLER_H */
diff --git a/engines/titanic/sound/music_wave.cpp b/engines/titanic/sound/music_wave.cpp
index 348f3bdbd4..6b5b187805 100644
--- a/engines/titanic/sound/music_wave.cpp
+++ b/engines/titanic/sound/music_wave.cpp
@@ -26,11 +26,29 @@
namespace Titanic {
-CMusicWave::CMusicWave(CProjectItem *project, CSoundManager *soundManager, int index) {
+CMusicWave::CMusicWave(CProjectItem *project, CSoundManager *soundManager, int index) :
+ _project(project), _soundManager(soundManager) {
+}
+
+void CMusicWave::setSize(uint count) {
+ assert(_items.empty());
+ _items.resize(count);
}
void CMusicWave::load(int index, const CString &filename, int v3) {
- // TODO
+ assert(!_items[index]._waveFile);
+ _items[index]._waveFile = createWaveFile(filename);
+ _items[index]._value = v3;
+}
+
+CWaveFile *CMusicWave::createWaveFile(const CString &name) {
+ if (name.empty())
+ return nullptr;
+ return _soundManager->loadSound(name);
+}
+
+void CMusicWave::stop() {
+
}
} // End of namespace Titanic
diff --git a/engines/titanic/sound/music_wave.h b/engines/titanic/sound/music_wave.h
index d40b2ce74d..b240f4a856 100644
--- a/engines/titanic/sound/music_wave.h
+++ b/engines/titanic/sound/music_wave.h
@@ -23,19 +23,47 @@
#ifndef TITANIC_MUSIC_WAVE_H
#define TITANIC_MUSIC_WAVE_H
+#include "common/array.h"
#include "titanic/support/string.h"
namespace Titanic {
class CProjectItem;
class CSoundManager;
+class CWaveFile;
class CMusicWave {
+ struct CMusicWaveFile {
+ CWaveFile *_waveFile;
+ int _value;
+ CMusicWaveFile() : _waveFile(nullptr), _value(0) {}
+ };
private:
+ CProjectItem *_project;
+ CSoundManager *_soundManager;
+ Common::Array<CMusicWaveFile> _items;
+private:
+ /**
+ * Loads the specified wave file, and returns a CWaveFile instance for it
+ */
+ CWaveFile *createWaveFile(const CString &name);
public:
CMusicWave(CProjectItem *project, CSoundManager *soundManager, int index);
+ /**
+ * Sets the maximum number of allowed files that be defined
+ */
+ void setSize(uint count);
+
+ /**
+ * Loads a new file into the list of available entries
+ */
void load(int index, const CString &filename, int v3);
+
+ /**
+ * Stops the music
+ */
+ void stop();
};
} // End of namespace Titanic
diff --git a/engines/titanic/sound/proximity.cpp b/engines/titanic/sound/proximity.cpp
index 7502eb3ef8..9e70722520 100644
--- a/engines/titanic/sound/proximity.cpp
+++ b/engines/titanic/sound/proximity.cpp
@@ -27,11 +27,11 @@ namespace Titanic {
CProximity::CProximity() : _field4(0), _channelVolume(100), _fieldC(0),
_priorSoundHandle(-1), _field14(0), _frequencyMultiplier(0.0), _field1C(1.875),
- _repeated(false), _channel(10), _field28(0), _azimuth(0.0),
+ _repeated(false), _channelMode(10), _positioningMode(POSMODE_NONE), _azimuth(0.0),
_range(0.5), _elevation(0), _posX(0.0), _posY(0.0), _posZ(0.0),
_hasVelocity(false), _velocityX(0), _velocityY(0), _velocityZ(0),
- _field54(0), _field58(0), _field5C(0), _field60(0), _endTalkerFn(nullptr),
- _talker(nullptr), _field6C(0) {
+ _field54(0), _field58(0), _field5C(0), _freeSoundFlag(false), _endTalkerFn(nullptr),
+ _talker(nullptr), _field6C(0), _soundType(Audio::Mixer::kPlainSoundType) {
}
} // End of namespace Titanic
diff --git a/engines/titanic/sound/proximity.h b/engines/titanic/sound/proximity.h
index 7c1f8598e8..41c2268c2f 100644
--- a/engines/titanic/sound/proximity.h
+++ b/engines/titanic/sound/proximity.h
@@ -23,10 +23,13 @@
#ifndef TITANIC_PROXIMITY_H
#define TITANIC_PROXIMITY_H
+#include "audio/mixer.h"
#include "common/scummsys.h"
namespace Titanic {
+enum PositioningMode { POSMODE_NONE = 0, POSMODE_POLAR = 1, POSMODE_VECTOR = 2 };
+
class TTtalker;
typedef void (*CEndTalkerFn)(TTtalker *talker);
@@ -41,8 +44,8 @@ public:
double _frequencyMultiplier;
double _field1C;
bool _repeated;
- int _channel;
- int _field28;
+ int _channelMode;
+ PositioningMode _positioningMode;
double _azimuth;
double _range;
double _elevation;
@@ -56,10 +59,11 @@ public:
int _field54;
int _field58;
int _field5C;
- int _field60;
+ bool _freeSoundFlag;
CEndTalkerFn _endTalkerFn;
TTtalker *_talker;
int _field6C;
+ Audio::Mixer::SoundType _soundType;
public:
CProximity();
};
diff --git a/engines/titanic/sound/qmixer.cpp b/engines/titanic/sound/qmixer.cpp
index da707c9527..c095b84e17 100644
--- a/engines/titanic/sound/qmixer.cpp
+++ b/engines/titanic/sound/qmixer.cpp
@@ -20,6 +20,7 @@
*
*/
+#include "common/system.h"
#include "titanic/sound/qmixer.h"
namespace Titanic {
@@ -28,7 +29,10 @@ QMixer::QMixer(Audio::Mixer *mixer) : _mixer(mixer) {
}
bool QMixer::qsWaveMixInitEx(const QMIXCONFIG &config) {
- // Not currently implemented in ScummVM
+ assert(_channels.empty());
+ assert(config.iChannels > 0 && config.iChannels < 256);
+
+ _channels.resize(config.iChannels);
return true;
}
@@ -48,6 +52,7 @@ int QMixer::qsWaveMixEnableChannel(int iChannel, uint flags, bool enabled) {
void QMixer::qsWaveMixCloseSession() {
_mixer->stopAll();
+ _channels.clear();
}
void QMixer::qsWaveMixFreeWave(Audio::SoundHandle &handle) {
@@ -59,11 +64,22 @@ void QMixer::qsWaveMixFlushChannel(int iChannel, uint flags) {
}
void QMixer::qsWaveMixSetPanRate(int iChannel, uint flags, uint rate) {
- // Not currently implemented in ScummVM
+ ChannelEntry &channel = _channels[iChannel];
+ channel._panRate = rate;
+ channel._volumeChangeStart = channel._volumeChangeEnd = 0;
}
void QMixer::qsWaveMixSetVolume(int iChannel, uint flags, uint volume) {
- // Not currently implemented in ScummVM
+ ChannelEntry &channel = _channels[iChannel];
+
+ // QMixer volumes go from 0-32767, but we need to convert to 0-255 for ScummVM
+ assert(volume <= 32767);
+ byte newVolume = (volume >= 32700) ? 255 : volume * 255 / 32767;
+
+ channel._volumeStart = newVolume;
+ channel._volumeEnd = volume * 255 / 100; // Convert from 0-100 (percent) to 0-255
+ channel._volumeChangeStart = g_system->getMillis();
+ channel._volumeChangeEnd = channel._volumeChangeStart + channel._panRate;
}
void QMixer::qsWaveMixSetSourcePosition(int iChannel, uint flags, const QSVECTOR &position) {
@@ -94,18 +110,98 @@ void QMixer::qsWaveMixSetSourceVelocity(int iChannel, uint flags, const QSVECTOR
// Not currently implemented in ScummVM
}
-int QMixer::qsWaveMixPlayEx(int iChannel, uint flags, CWaveFile *mixWave, int loops, const QMIXPLAYPARAMS &params) {
- // Not currently implemented in ScummVM
+int QMixer::qsWaveMixPlayEx(int iChannel, uint flags, CWaveFile *waveFile, int loops, const QMIXPLAYPARAMS &params) {
+ if (iChannel == -1) {
+ // Find a free channel
+ for (iChannel = 0; iChannel < (int)_channels.size(); ++iChannel) {
+ if (_channels[iChannel]._sounds.empty())
+ break;
+ }
+ assert(iChannel != (int)_channels.size());
+ }
+
+ // If the new sound replaces current ones, then clear the channel
+ ChannelEntry &channel = _channels[iChannel];
+ if (flags & QMIX_CLEARQUEUE) {
+ if (!channel._sounds.empty() && channel._sounds.front()._started)
+ _mixer->stopHandle(channel._sounds.front()._soundHandle);
+
+ channel._sounds.clear();
+ }
+
+ // Add the sound to the channel
+ channel._sounds.push_back(SoundEntry(waveFile, params.callback, loops, params.dwUser));
+ qsWaveMixPump();
+
return 0;
}
bool QMixer::qsWaveMixIsChannelDone(int iChannel) const {
- // Not currently implemented in ScummVM
- return true;
+ return _channels[iChannel]._sounds.empty();
}
void QMixer::qsWaveMixPump() {
- // TODO: Handle checking for done sounds, and calling their end functions
-}
-
-} // End of namespace Titanic z
+ // Iterate through each of the channels
+ for (uint iChannel = 0; iChannel < _channels.size(); ++iChannel) {
+ ChannelEntry &channel = _channels[iChannel];
+
+ // If there's a transition in sound volume in progress, handle it
+ if (channel._volumeChangeEnd) {
+ byte oldVolume = channel._volume;
+ uint currentTicks = g_system->getMillis();
+
+ if (currentTicks >= channel._volumeChangeEnd) {
+ // Reached end of transition period
+ channel._volume = channel._volumeEnd;
+ channel._volumeChangeStart = channel._volumeChangeEnd = 0;
+ } else {
+ // Transition in progress, so figure out new volume
+ channel._volume = (int)channel._volumeStart +
+ ((int)channel._volumeEnd - (int)channel._volumeStart) *
+ (int)(currentTicks - channel._volumeChangeStart) / (int)channel._panRate;
+ }
+
+ if (channel._volume != oldVolume && !channel._sounds.empty()
+ && channel._sounds.front()._started) {
+ _mixer->setChannelVolume(channel._sounds.front()._soundHandle, channel._volume);
+ }
+ }
+
+ // If the playing sound on the channel is finished, then call
+ // the callback registered for it, and remove it from the list
+ if (!channel._sounds.empty()) {
+ SoundEntry &sound = channel._sounds.front();
+ if (sound._started && !_mixer->isSoundHandleActive(sound._soundHandle)) {
+ if (sound._loops == -1 || sound._loops-- > 0) {
+ // Need to loop the sound again
+ sound._waveFile->_stream->rewind();
+ _mixer->playStream(sound._waveFile->_soundType,
+ &sound._soundHandle, sound._waveFile->_stream,
+ -1, channel._volume, 0, DisposeAfterUse::NO);
+ } else {
+ // Sound is finished
+ if (sound._callback)
+ // Call the callback to signal end
+ sound._callback(iChannel, sound._waveFile, sound._userData);
+
+ // Remove sound record from channel
+ channel._sounds.erase(channel._sounds.begin());
+ }
+ }
+ }
+
+ // If there's an unstarted sound at the front of a channel's
+ // sound list, then start it playing
+ if (!channel._sounds.empty()) {
+ SoundEntry &sound = channel._sounds.front();
+ if (!sound._started) {
+ _mixer->playStream(sound._waveFile->_soundType,
+ &sound._soundHandle, sound._waveFile->_stream,
+ -1, channel._volume, 0, DisposeAfterUse::NO);
+ sound._started = true;
+ }
+ }
+ }
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/sound/qmixer.h b/engines/titanic/sound/qmixer.h
index fa4604cd2e..6a25484c29 100644
--- a/engines/titanic/sound/qmixer.h
+++ b/engines/titanic/sound/qmixer.h
@@ -152,9 +152,12 @@ struct QMIXPLAYPARAMS {
int lEndLoop;
int lEnd;
const void *lpChannelParams; // initialize with these parameters
+ // Properties introduced by ScummVM
+ Audio::Mixer::SoundType _soundType;
QMIXPLAYPARAMS() : dwSize(36), lpImage(nullptr), hwndNotify(0), callback(nullptr),
- dwUser(nullptr), lStart(0), lStartLoop(0), lEndLoop(0), lEnd(0), lpChannelParams(nullptr) {}
+ dwUser(nullptr), lStart(0), lStartLoop(0), lEndLoop(0), lEnd(0),
+ lpChannelParams(nullptr), _soundType(Audio::Mixer::kPlainSoundType) {}
};
/**
@@ -169,8 +172,38 @@ struct QMIXPLAYPARAMS {
* currently ignored, and all sounds play at full volume.
*/
class QMixer {
+ struct SoundEntry {
+ bool _started;
+ CWaveFile *_waveFile;
+ Audio::SoundHandle _soundHandle;
+ LPQMIXDONECALLBACK _callback;
+ int _loops;
+ void *_userData;
+ SoundEntry() : _started(false), _waveFile(nullptr), _callback(nullptr),
+ _loops(0), _userData(nullptr) {}
+
+ SoundEntry(CWaveFile *waveFile, LPQMIXDONECALLBACK callback, int loops, void *userData) :
+ _started(false), _waveFile(waveFile), _callback(callback), _loops(loops), _userData(userData) {}
+ };
+ struct ChannelEntry {
+ // Currently playing and any following queued sounds for the channel
+ Common::List<SoundEntry> _sounds;
+ // Current channel volume
+ byte _volume;
+ // Current time in milliseconds for paning (volume) changes
+ uint _panRate;
+ // Fields used to transition between volume levels
+ uint _volumeChangeStart;
+ uint _volumeChangeEnd;
+ byte _volumeStart;
+ byte _volumeEnd;
+
+ ChannelEntry() : _volume(0), _panRate(0), _volumeChangeStart(0),
+ _volumeChangeEnd(0), _volumeStart(0), _volumeEnd(0) {}
+ };
private:
Audio::Mixer *_mixer;
+ Common::Array<ChannelEntry> _channels;
public:
QMixer(Audio::Mixer *mixer);
virtual ~QMixer() {}
diff --git a/engines/titanic/sound/sound.cpp b/engines/titanic/sound/sound.cpp
index d14c628a78..6d27d1de49 100644
--- a/engines/titanic/sound/sound.cpp
+++ b/engines/titanic/sound/sound.cpp
@@ -68,8 +68,18 @@ void CSound::setVolume(uint handle, uint volume, uint seconds) {
_soundManager.setVolume(handle, volume, seconds);
}
-void CSound::fn4(CWaveFile *waveFile, int val) {
- // TODO
+void CSound::activateSound(CWaveFile *waveFile, bool freeFlag) {
+ for (CSoundItemList::iterator i = _sounds.begin(); i != _sounds.end(); ++i) {
+ CSoundItem *sound = *i;
+ if (sound->_waveFile == waveFile) {
+ sound->_active = true;
+ sound->_freeFlag = freeFlag;
+
+ if (!freeFlag && waveFile->size() > 51200)
+ sound->_freeFlag = true;
+ break;
+ }
+ }
}
void CSound::stopChannel(int channel) {
@@ -77,14 +87,18 @@ void CSound::stopChannel(int channel) {
}
void CSound::checkSounds() {
- for (CSoundItemList::iterator i = _sounds.begin(); i != _sounds.end(); ++i) {
+ for (CSoundItemList::iterator i = _sounds.begin(); i != _sounds.end(); ) {
CSoundItem *soundItem = *i;
- if (soundItem->_field24 && soundItem->_field28) {
+
+ if (soundItem->_active && soundItem->_freeFlag) {
if (_soundManager.isActive(soundItem->_waveFile)) {
- _sounds.remove(soundItem);
+ i = _sounds.erase(i);
delete soundItem;
+ continue;
}
}
+
+ ++i;
}
}
@@ -92,7 +106,7 @@ void CSound::removeOldest() {
for (CSoundItemList::iterator i = _sounds.reverse_begin();
i != _sounds.end(); --i) {
CSoundItem *soundItem = *i;
- if (soundItem->_field28 && !_soundManager.isActive(soundItem->_waveFile)) {
+ if (soundItem->_active && !_soundManager.isActive(soundItem->_waveFile)) {
_sounds.remove(soundItem);
delete soundItem;
break;
@@ -145,7 +159,10 @@ int CSound::playSound(const CString &name, CProximity &prox) {
return -1;
prox._field6C = waveFile->fn1();
- fn4(waveFile, prox._field60);
+ if (prox._soundType != Audio::Mixer::kPlainSoundType)
+ waveFile->_soundType = prox._soundType;
+
+ activateSound(waveFile, prox._freeSoundFlag);
return _soundManager.playSound(*waveFile, prox);
}
@@ -192,7 +209,7 @@ int CSound::playSpeech(CDialogueFile *dialogueFile, int speechId, CProximity &pr
return -1;
prox._field6C = waveFile->fn1();
- fn4(waveFile, prox._field60);
+ activateSound(waveFile, prox._freeSoundFlag);
return _soundManager.playSound(*waveFile, prox);
}
diff --git a/engines/titanic/sound/sound.h b/engines/titanic/sound/sound.h
index b6c77e76b2..de95f9edf1 100644
--- a/engines/titanic/sound/sound.h
+++ b/engines/titanic/sound/sound.h
@@ -41,15 +41,15 @@ public:
CWaveFile *_waveFile;
File *_dialogueFileHandle;
int _speechId;
- int _field24;
- int _field28;
+ bool _freeFlag;
+ bool _active;
public:
CSoundItem() : ListItem(), _waveFile(nullptr), _dialogueFileHandle(nullptr),
- _speechId(0), _field24(0), _field28(0) {}
+ _speechId(0), _freeFlag(false), _active(false) {}
CSoundItem(const CString &name) : ListItem(), _name(name), _waveFile(nullptr),
- _dialogueFileHandle(nullptr), _speechId(0), _field24(0), _field28(0) {}
+ _dialogueFileHandle(nullptr), _speechId(0), _freeFlag(false), _active(false) {}
CSoundItem(File *dialogueFile, int speechId) : ListItem(), _waveFile(nullptr),
- _dialogueFileHandle(dialogueFile), _speechId(speechId), _field24(0), _field28(0) {}
+ _dialogueFileHandle(dialogueFile), _speechId(speechId), _freeFlag(false), _active(false) {}
};
class CSoundItemList : public List<CSoundItem> {
@@ -123,7 +123,10 @@ public:
*/
void setVolume(uint handle, uint volume, uint seconds);
- void fn4(CWaveFile *waveFile, int val);
+ /**
+ * Flags a sound about to be played as activated
+ */
+ void activateSound(CWaveFile *waveFile, bool freeFlag);
/**
* Stops any sounds attached to a given channel
diff --git a/engines/titanic/sound/sound_manager.cpp b/engines/titanic/sound/sound_manager.cpp
index a8bd0dfbe9..81ec5bc475 100644
--- a/engines/titanic/sound/sound_manager.cpp
+++ b/engines/titanic/sound/sound_manager.cpp
@@ -22,6 +22,7 @@
#include "titanic/sound/sound_manager.h"
#include "titanic/titanic.h"
+
namespace Titanic {
const uint SAMPLING_RATE = 22050;
@@ -35,11 +36,11 @@ CSoundManager::CSoundManager() : _musicPercent(75.0), _speechPercent(75.0),
uint CSoundManager::getModeVolume(int mode) {
switch (mode) {
case -1:
- return _masterPercent;
+ return (uint)_masterPercent;
case -2:
- return _masterPercent * 30 / 100;
+ return (uint)(_masterPercent * 30 / 100);
case -3:
- return _masterPercent * 15 / 100;
+ return (uint)(_masterPercent * 15 / 100);
default:
return 0;
}
@@ -97,7 +98,7 @@ void QSoundManager::Slot::clear() {
_ticks = 0;
_channel = -1;
_handle = 0;
- _val3 = 0;
+ _positioningMode = POSMODE_NONE;
}
/*------------------------------------------------------------------------*/
@@ -142,9 +143,16 @@ CWaveFile *QSoundManager::loadSpeech(CDialogueFile *dialogueFile, int speechId)
return waveFile;
}
-int QSoundManager::proc5() const {
- error("TODO");
- return 0;
+CWaveFile *QSoundManager::loadMusic(const CString &name) {
+ CWaveFile *waveFile = new CWaveFile();
+
+ // Try to load the specified sound
+ if (!waveFile->loadMusic(name)) {
+ delete waveFile;
+ return nullptr;
+ }
+
+ return waveFile;
}
int QSoundManager::playSound(CWaveFile &waveFile, CProximity &prox) {
@@ -163,14 +171,14 @@ int QSoundManager::playSound(CWaveFile &waveFile, CProximity &prox) {
}
}
- if (channel >= 0 || (channel = resetChannel(prox._channel)) != -1) {
+ if (channel >= 0 || (channel = resetChannel(prox._channelMode)) != -1) {
return playWave(&waveFile, channel, flags, prox);
}
return 0;
}
-void QSoundManager::stopSound(uint handle) {
+void QSoundManager::stopSound(int handle) {
resetChannel(10);
for (uint idx = 0; idx < _slots.size(); ++idx) {
@@ -206,7 +214,7 @@ void QSoundManager::stopChannel(int channel) {
}
}
-void QSoundManager::setCanFree(uint handle) {
+void QSoundManager::setCanFree(int handle) {
for (uint idx = 0; idx < _slots.size(); ++idx) {
if (_slots[idx]._handle == handle)
_slots[idx]._isTimed = true;
@@ -260,10 +268,11 @@ int QSoundManager::resetChannel(int iChannel) {
return newChannel;
}
-void QSoundManager::setVolume(uint handle, uint volume, uint seconds) {
+void QSoundManager::setVolume(int handle, uint volume, uint seconds) {
for (uint idx = 0; idx < _slots.size(); ++idx) {
Slot &slot = _slots[idx];
if (slot._handle == handle) {
+ assert(slot._channel >= 0);
_channelsVolume[slot._channel] = volume;
updateVolume(slot._channel, seconds * 1000);
@@ -279,7 +288,7 @@ void QSoundManager::setVolume(uint handle, uint volume, uint seconds) {
}
}
-void QSoundManager::setVectorPosition(uint handle, double x, double y, double z, uint panRate) {
+void QSoundManager::setVectorPosition(int handle, double x, double y, double z, uint panRate) {
for (uint idx = 0; idx < _slots.size(); ++idx) {
Slot &slot = _slots[idx];
if (slot._handle == handle) {
@@ -290,7 +299,7 @@ void QSoundManager::setVectorPosition(uint handle, double x, double y, double z,
}
}
-void QSoundManager::setPolarPosition(uint handle, double range, double azimuth, double elevation, uint panRate) {
+void QSoundManager::setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate) {
for (uint idx = 0; idx < _slots.size(); ++idx) {
Slot &slot = _slots[idx];
if (slot._handle == handle) {
@@ -302,7 +311,7 @@ void QSoundManager::setPolarPosition(uint handle, double range, double azimuth,
}
}
-bool QSoundManager::isActive(uint handle) const {
+bool QSoundManager::isActive(int handle) const {
for (uint idx = 0; idx < _slots.size(); ++idx) {
if (_slots[idx]._handle == handle)
return true;
@@ -316,7 +325,7 @@ bool QSoundManager::isActive(const CWaveFile *waveFile) const {
}
void QSoundManager::waveMixPump() {
-
+ qsWaveMixPump();
}
uint QSoundManager::getLatency() const {
@@ -347,7 +356,7 @@ void QSoundManager::setListenerPosition(double posX, double posY, double posZ,
if (stopSounds) {
// Stop any running sounds
for (uint idx = 0; idx < _slots.size(); ++idx) {
- if (_slots[idx]._val3)
+ if (_slots[idx]._positioningMode != 0)
stopSound(_slots[idx]._handle);
}
}
@@ -368,14 +377,17 @@ int QSoundManager::playWave(CWaveFile *waveFile, int iChannel, uint flags, CProx
if (slotIndex == -1)
return -1;
- switch (prox._field28) {
- case 1:
+ // Set the volume
+ setChannelVolume(iChannel, prox._channelVolume, prox._channelMode);
+
+ switch (prox._positioningMode) {
+ case POSMODE_POLAR:
qsWaveMixSetPolarPosition(iChannel, 8, QSPOLAR(prox._azimuth, prox._range, prox._elevation));
qsWaveMixEnableChannel(iChannel, QMIX_CHANNEL_ELEVATION, true);
qsWaveMixSetDistanceMapping(iChannel, 8, QMIX_DISTANCES(5.0, 3.0, 1.0));
break;
- case 2:
+ case POSMODE_VECTOR:
qsWaveMixSetSourcePosition(iChannel, 8, QSVECTOR(prox._posX, prox._posY, prox._posZ));
qsWaveMixEnableChannel(iChannel, QMIX_CHANNEL_ELEVATION, true);
qsWaveMixSetDistanceMapping(iChannel, 8, QMIX_DISTANCES(5.0, 3.0, 1.0));
@@ -402,12 +414,12 @@ int QSoundManager::playWave(CWaveFile *waveFile, int iChannel, uint flags, CProx
slot._handle = _handleCtr++;
slot._channel = iChannel;
slot._waveFile = waveFile;
- slot._val3 = prox._field28;
+ slot._positioningMode = prox._positioningMode;
return slot._handle;
} else {
_sounds.flushChannel(waveFile, iChannel);
- if (prox._field60)
+ if (prox._freeSoundFlag)
delete waveFile;
return 0;
}
@@ -418,7 +430,7 @@ void QSoundManager::soundFreed(Audio::SoundHandle &handle) {
}
void QSoundManager::updateVolume(int channel, uint panRate) {
- uint volume = _channelsVolume[channel] * 327;
+ double volume = _channelsVolume[channel] * 327;
switch (_channelsMode[channel]) {
case 0:
@@ -443,7 +455,7 @@ void QSoundManager::updateVolume(int channel, uint panRate) {
volume = (_musicPercent * volume) / 100;
qsWaveMixSetPanRate(channel, 0, panRate);
- qsWaveMixSetVolume(channel, 0, volume);
+ qsWaveMixSetVolume(channel, 0, (uint)volume);
}
void QSoundManager::updateVolumes() {
diff --git a/engines/titanic/sound/sound_manager.h b/engines/titanic/sound/sound_manager.h
index 2c9975ede4..d1afdb4ad4 100644
--- a/engines/titanic/sound/sound_manager.h
+++ b/engines/titanic/sound/sound_manager.h
@@ -60,7 +60,15 @@ public:
*/
virtual CWaveFile *loadSpeech(CDialogueFile *dialogueFile, int speechId) { return 0; }
- virtual int proc5() const { return 0; }
+ /**
+ * Loads a music file
+ * @param name Name of music resource
+ * @returns Loaded wave file
+ * @remarks The original created a streaming audio buffer for the wave file,
+ * and passed this to the method. For ScummVM, this has been discarded
+ * in favor of simply passing the filename.
+ */
+ virtual CWaveFile *loadMusic(const CString &name) { return nullptr; }
/**
* Start playing a previously loaded wave file
@@ -70,14 +78,14 @@ public:
/**
* Stop playing the specified sound
*/
- virtual void stopSound(uint handle) = 0;
+ virtual void stopSound(int handle) = 0;
/**
* Stops a designated range of channels
*/
virtual void stopChannel(int channel) = 0;
- virtual void proc9(uint handle) {}
+ virtual void proc9(int handle) {}
/**
* Stops sounds on all playing channels
@@ -90,7 +98,7 @@ public:
* @param volume New volume
* @param seconds Number of seconds to transition to the new volume
*/
- virtual void setVolume(uint handle, uint volume, uint seconds) = 0;
+ virtual void setVolume(int handle, uint volume, uint seconds) = 0;
/**
* Set the position for a sound
@@ -100,7 +108,7 @@ public:
* @param z z position in metres
* @param panRate Rate in milliseconds to transition
*/
- virtual void setVectorPosition(uint handle, double x, double y, double z, uint panRate) {}
+ virtual void setVectorPosition(int handle, double x, double y, double z, uint panRate) {}
/**
* Set the position for a sound
@@ -110,12 +118,12 @@ public:
* @param elevation Elevation value in degrees
* @param panRate Rate in milliseconds to transition
*/
- virtual void setPolarPosition(uint handle, double range, double azimuth, double elevation, uint panRate) {}
+ virtual void setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate) {}
/**
* Returns true if the given sound is currently active
*/
- virtual bool isActive(uint handle) const = 0;
+ virtual bool isActive(int handle) const = 0;
/**
* Returns true if the given sound is currently active
@@ -256,9 +264,10 @@ class QSoundManager : public CSoundManager, public QMixer {
uint _ticks;
int _channel;
int _handle;
- uint _val3;
+ PositioningMode _positioningMode;
- Slot() : _waveFile(0), _isTimed(0), _ticks(0), _channel(-1), _handle(0), _val3(0) {}
+ Slot() : _waveFile(0), _isTimed(0), _ticks(0), _channel(-1),
+ _handle(0), _positioningMode(POSMODE_NONE) {}
void clear();
};
private:
@@ -320,7 +329,15 @@ public:
*/
virtual CWaveFile *loadSpeech(CDialogueFile *dialogueFile, int speechId);
- virtual int proc5() const;
+ /**
+ * Loads a music file
+ * @param name Name of music resource
+ * @returns Loaded wave file
+ * @remarks The original created a streaming audio buffer for the wave file,
+ * and passed this to the method. For ScummVM, this has been discarded
+ * in favor of simply passing the filename.
+ */
+ virtual CWaveFile *loadMusic(const CString &name);
/**
* Start playing a previously loaded sound resource
@@ -330,7 +347,7 @@ public:
/**
* Stop playing the specified sound
*/
- virtual void stopSound(uint handle);
+ virtual void stopSound(int handle);
/**
* Stops a designated range of channels
@@ -340,7 +357,7 @@ public:
/**
* Flags that a sound can be freed if a timeout is set
*/
- virtual void setCanFree(uint handle);
+ virtual void setCanFree(int handle);
/**
* Stops sounds on all playing channels
@@ -353,7 +370,7 @@ public:
* @param volume New volume
* @param seconds Number of seconds to transition to the new volume
*/
- virtual void setVolume(uint handle, uint volume, uint seconds);
+ virtual void setVolume(int handle, uint volume, uint seconds);
/**
* Set the position for a sound
@@ -363,7 +380,7 @@ public:
* @param z z position in metres
* @param panRate Rate in milliseconds to transition
*/
- virtual void setVectorPosition(uint handle, double x, double y, double z, uint panRate);
+ virtual void setVectorPosition(int handle, double x, double y, double z, uint panRate);
/**
* Set the position for a sound
@@ -373,12 +390,12 @@ public:
* @param elevation Elevation value in degrees
* @param panRate Rate in milliseconds to transition
*/
- virtual void setPolarPosition(uint handle, double range, double azimuth, double elevation, uint panRate);
+ virtual void setPolarPosition(int handle, double range, double azimuth, double elevation, uint panRate);
/**
* Returns true if the given sound is currently active
*/
- virtual bool isActive(uint handle) const;
+ virtual bool isActive(int handle) const;
/**
* Returns true if the given sound is currently active
diff --git a/engines/titanic/sound/titania_speech.cpp b/engines/titanic/sound/titania_speech.cpp
index a07cc79334..d0ff423342 100644
--- a/engines/titanic/sound/titania_speech.cpp
+++ b/engines/titanic/sound/titania_speech.cpp
@@ -59,7 +59,7 @@ bool CTitaniaSpeech::ActMsg(CActMsg *msg) {
movieSetAudioTiming(true);
loadSound("a#12.wav");
sleep(1000);
- playMovie(0, 187, MOVIE_GAMESTATE || MOVIE_NOTIFY_OBJECT);
+ playMovie(0, 187, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
movieEvent(0);
break;
@@ -78,7 +78,7 @@ bool CTitaniaSpeech::ActMsg(CActMsg *msg) {
visibleMsg._visible = false;
visibleMsg.execute("TitaniaStillControl");
loadSound("a#10.wav");
- playMovie(585, 706, MOVIE_GAMESTATE || MOVIE_NOTIFY_OBJECT);
+ playMovie(585, 706, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
playSound("a#10.wav");
break;
@@ -86,7 +86,7 @@ bool CTitaniaSpeech::ActMsg(CActMsg *msg) {
visibleMsg._visible = false;
visibleMsg.execute("TitaniaStillControl");
loadSound("a#9.wav");
- playMovie(707, 905, MOVIE_GAMESTATE || MOVIE_NOTIFY_OBJECT);
+ playMovie(707, 905, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
playSound("a#9.wav");
break;
@@ -94,7 +94,7 @@ bool CTitaniaSpeech::ActMsg(CActMsg *msg) {
visibleMsg._visible = false;
visibleMsg.execute("TitaniaStillControl");
loadSound("a#8.wav");
- playMovie(906, 938, MOVIE_GAMESTATE || MOVIE_NOTIFY_OBJECT);
+ playMovie(906, 938, MOVIE_GAMESTATE | MOVIE_NOTIFY_OBJECT);
playSound("a#8.wav");
break;
diff --git a/engines/titanic/sound/wave_file.cpp b/engines/titanic/sound/wave_file.cpp
index 7093856217..8c00637d73 100644
--- a/engines/titanic/sound/wave_file.cpp
+++ b/engines/titanic/sound/wave_file.cpp
@@ -28,11 +28,12 @@
namespace Titanic {
-CWaveFile::CWaveFile() : _owner(nullptr), _stream(nullptr), _soundType(SOUND_SFX) {
+CWaveFile::CWaveFile() : _owner(nullptr), _stream(nullptr),
+ _soundType(Audio::Mixer::kPlainSoundType) {
}
CWaveFile::CWaveFile(QSoundManager *owner) : _owner(owner), _stream(nullptr),
- _soundType(SOUND_SFX) {
+ _soundType(Audio::Mixer::kPlainSoundType) {
}
CWaveFile::~CWaveFile() {
@@ -57,7 +58,8 @@ bool CWaveFile::loadSound(const CString &name) {
Common::SeekableReadStream *stream = file.readStream();
_size = stream->size();
_stream = Audio::makeWAVStream(stream->readStream(_size), DisposeAfterUse::YES);
- _soundType = SOUND_SFX;
+ _soundType = Audio::Mixer::kSFXSoundType;
+
return true;
}
@@ -72,7 +74,23 @@ bool CWaveFile::loadSpeech(CDialogueFile *dialogueFile, int speechIndex) {
_size = res->_size;
_stream = Audio::makeWAVStream(new Common::MemoryReadStream(data, _size, DisposeAfterUse::YES),
DisposeAfterUse::YES);
- _soundType = SOUND_SPEECH;
+ _soundType = Audio::Mixer::kSpeechSoundType;
+
+ return true;
+}
+
+bool CWaveFile::loadMusic(const CString &name) {
+ assert(!_stream);
+
+ StdCWadFile file;
+ if (!file.open(name))
+ return false;
+
+ Common::SeekableReadStream *stream = file.readStream();
+ _size = stream->size();
+ _stream = Audio::makeWAVStream(stream->readStream(_size), DisposeAfterUse::YES);
+ _soundType = Audio::Mixer::kMusicSoundType;
+
return true;
}
diff --git a/engines/titanic/sound/wave_file.h b/engines/titanic/sound/wave_file.h
index 33b2a8dedf..aede0c9328 100644
--- a/engines/titanic/sound/wave_file.h
+++ b/engines/titanic/sound/wave_file.h
@@ -32,19 +32,14 @@ namespace Titanic {
class QSoundManager;
-enum SoundType {
- SOUND_SFX = 0,
- SOUND_SPEECH = 1
-};
-
class CWaveFile {
private:
uint _size;
public:
QSoundManager *_owner;
- Audio::AudioStream *_stream;
+ Audio::SeekableAudioStream *_stream;
Audio::SoundHandle _soundHandle;
- SoundType _soundType;
+ Audio::Mixer::SoundType _soundType;
public:
CWaveFile();
CWaveFile(QSoundManager *owner);
@@ -68,6 +63,11 @@ public:
bool loadSpeech(CDialogueFile *dialogueFile, int speechIndex);
/**
+ * Tries to load the specified music wave file
+ */
+ bool loadMusic(const CString &name);
+
+ /**
* Returns true if the wave file has data loaded
*/
bool isLoaded() const { return _stream != nullptr; }
diff --git a/engines/titanic/star_control/star_control_sub13.cpp b/engines/titanic/star_control/star_control_sub13.cpp
index c721b395c6..cc9e239194 100644
--- a/engines/titanic/star_control/star_control_sub13.cpp
+++ b/engines/titanic/star_control/star_control_sub13.cpp
@@ -21,7 +21,6 @@
*/
#include "titanic/star_control/star_control_sub13.h"
-#include "titanic/titanic.h"
namespace Titanic {
diff --git a/engines/titanic/star_control/surface_fader.cpp b/engines/titanic/star_control/surface_fader.cpp
index 089ad51717..0ee03673a4 100644
--- a/engines/titanic/star_control/surface_fader.cpp
+++ b/engines/titanic/star_control/surface_fader.cpp
@@ -29,9 +29,9 @@ namespace Titanic {
CSurfaceFader::CSurfaceFader() : CSurfaceFaderBase() {
_dataP = new byte[_count];
- for (int idx = 0; idx < _count; ++idx) {
- // TODO: Setup data bytes
- }
+ for (int idx = 0; idx < _count; ++idx)
+ _dataP[idx] = (byte)(pow((double)idx / (double)_count, 1.299999952316284)
+ * (double)_count + 0.5);
}
CSurfaceFader::~CSurfaceFader() {
diff --git a/engines/titanic/support/avi_surface.cpp b/engines/titanic/support/avi_surface.cpp
index c37bd83616..d4ebd5cef1 100644
--- a/engines/titanic/support/avi_surface.cpp
+++ b/engines/titanic/support/avi_surface.cpp
@@ -20,19 +20,20 @@
*
*/
-#include "titanic/support/avi_surface.h"
-#include "titanic/support/screen_manager.h"
-#include "titanic/support/video_surface.h"
#include "common/system.h"
#include "graphics/pixelformat.h"
#include "video/avi_decoder.h"
+#include "titanic/support/avi_surface.h"
+#include "titanic/support/screen_manager.h"
+#include "titanic/support/video_surface.h"
+#include "titanic/titanic.h"
namespace Titanic {
Video::AVIDecoder::AVIVideoTrack &AVIDecoder::getVideoTrack() {
for (TrackListIterator it = getTrackListBegin(); it != getTrackListEnd(); it++)
if ((*it)->getTrackType() == Track::kTrackTypeVideo)
- return *static_cast<AVIVideoTrack *>(*it);
+ return *dynamic_cast<AVIVideoTrack *>(*it);
error("Could not find video track");
}
@@ -338,9 +339,9 @@ bool AVISurface::addEvent(int frameNumber, CGameObject *obj) {
}
void AVISurface::setFrameRate(double rate) {
- _decoders[0]->setRate(Common::Rational(rate));
+ _decoders[0]->setRate(Common::Rational((int)rate));
if (_decoders[1])
- _decoders[1]->setRate(Common::Rational(rate));
+ _decoders[1]->setRate(Common::Rational((int)rate));
}
Graphics::ManagedSurface *AVISurface::getSecondarySurface() {
@@ -358,4 +359,36 @@ Graphics::ManagedSurface *AVISurface::duplicateSecondaryFrame() const {
}
}
+void AVISurface::playCutscene(const Rect &r, uint startFrame, uint endFrame) {
+ bool isDifferent = _movieFrameSurface[0]->w != r.width() ||
+ _movieFrameSurface[0]->h != r.height();
+
+ startAtFrame(startFrame);
+ while (_currentFrame < (int)endFrame && !g_vm->shouldQuit()) {
+ if (isNextFrame()) {
+ renderFrame();
+ _currentFrame = _decoders[0]->getCurFrame();
+
+ if (isDifferent) {
+ // Clear the destination area, and use the transBlitFrom method,
+ // which supports arbitrary scaling, to reduce to the desired size
+ g_vm->_screen->fillRect(r, 0);
+ g_vm->_screen->transBlitFrom(*_movieFrameSurface[0],
+ Common::Rect(0, 0, _movieFrameSurface[0]->w, _movieFrameSurface[0]->h), r);
+ } else {
+ g_vm->_screen->blitFrom(*_movieFrameSurface[0], Common::Point(r.left, r.top));
+ }
+
+ g_vm->_screen->update();
+ g_vm->_events->pollEvents();
+ }
+
+ // Brief wait, and check at the same time for clicks to abort the clip
+ if (g_vm->_events->waitForPress(10))
+ break;
+ }
+
+ stop();
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/support/avi_surface.h b/engines/titanic/support/avi_surface.h
index d21182bca9..54b0155bdd 100644
--- a/engines/titanic/support/avi_surface.h
+++ b/engines/titanic/support/avi_surface.h
@@ -176,6 +176,11 @@ public:
* Returns true if it's time for the next
*/
bool isNextFrame() const;
+
+ /**
+ * Plays an interruptable cutscene
+ */
+ void playCutscene(const Rect &r, uint startFrame, uint endFrame);
};
} // End of namespace Titanic
diff --git a/engines/titanic/support/credit_text.cpp b/engines/titanic/support/credit_text.cpp
index 0e9715aaa6..009c3f4944 100644
--- a/engines/titanic/support/credit_text.cpp
+++ b/engines/titanic/support/credit_text.cpp
@@ -28,7 +28,7 @@ namespace Titanic {
CCreditText::CCreditText() : _screenManagerP(nullptr), _field14(0),
_ticks(0), _fontHeight(1), _objectP(nullptr), _totalHeight(0),
_field40(0), _field44(0), _field48(0), _field4C(0), _field50(0),
- _field54(0), _field58(0), _field5C(0) {
+ _field54(0), _field58(0), _counter(0) {
}
void CCreditText::clear() {
@@ -52,7 +52,7 @@ void CCreditText::load(CGameObject *obj, CScreenManager *screenManager,
_field50 = 0;
_field54 = 0;
_field58 = 0;
- _field5C = 0;
+ _counter = 0;
}
void CCreditText::setup() {
@@ -87,8 +87,11 @@ void CCreditText::setup() {
}
_groups.push_back(group);
+ if (hasDots)
+ handleDots(group);
}
+ _screenManagerP->setFontNumber(oldFontNumber);
_groupIt = _groups.begin();
_lineIt = (*_groupIt)->_lines.begin();
_totalHeight = _objectP->getBounds().height() + _fontHeight * 2;
@@ -147,7 +150,108 @@ void CCreditText::handleDots(CCreditLineGroup *group) {
}
bool CCreditText::draw() {
- return false;
+ if (_groupIt == _groups.end())
+ return false;
+
+ if (++_counter > 200) {
+ _field44 += _field50;
+ _field48 += _field54;
+ _field4C += _field58;
+ _field50 = g_vm->getRandomNumber(63) + 192 - _field44;
+ _field54 = g_vm->getRandomNumber(63) + 192 - _field48;
+ _field58 = g_vm->getRandomNumber(63) + 192 - _field4C;
+ _counter = 0;
+ }
+
+ // Positioning adjustment, changing lines and/or group if necessary
+ int yDiff = (int)(g_vm->_events->getTicksCount() - _ticks) / 22 - _field40;
+ while (yDiff > 0) {
+ if (_totalHeight > 0) {
+ if (yDiff < _totalHeight) {
+ _totalHeight -= yDiff;
+ _field40 += yDiff;
+ yDiff = 0;
+ } else {
+ yDiff -= _totalHeight;
+ _field40 += _totalHeight;
+ _totalHeight = 0;
+ }
+ } else {
+ if (yDiff < _fontHeight)
+ break;
+
+ ++_lineIt;
+ yDiff -= _fontHeight;
+ _field40 += _fontHeight;
+
+ if (_lineIt == (*_groupIt)->_lines.end()) {
+ // Move to next line group
+ ++_groupIt;
+ if (_groupIt == _groups.end())
+ // Reached end of groups
+ return false;
+
+ _lineIt = (*_groupIt)->_lines.begin();
+ _totalHeight = _fontHeight * 3 / 2;
+ }
+ }
+ }
+
+ int oldFontNumber = _screenManagerP->setFontNumber(3);
+ CCreditLineGroups::iterator groupIt = _groupIt;
+ CCreditLines::iterator lineIt = _lineIt;
+
+ Point textPos;
+ for (textPos.y = _rect.top + _totalHeight; textPos.y <= _rect.bottom;
+ textPos.y += _fontHeight) {
+ int textR = _field44 + _field50 * _counter / 200;
+ int textG = _field48 + _field54 * _counter / 200;
+ int textB = _field4C + _field58 * _counter / 200;
+
+ // Single iteration loop to figure out RGB values for the line
+ do {
+ int percent = 0;
+ if (textPos.y < (_rect.top + 2 * _fontHeight)) {
+ percent = (textPos.y - _rect.top) * 100 / (_fontHeight * 2);
+ if (percent < 0)
+ percent = 0;
+ } else {
+ int bottom = _rect.bottom - 2 * _fontHeight;
+ if (textPos.y < bottom)
+ break;
+
+ percent = (_rect.bottom - textPos.y) * 100
+ / (_fontHeight * 2);
+ }
+
+ // Adjust the RGB to the specified percentage intensity
+ textR = textR * percent / 100;
+ textG = textG * percent / 100;
+ textB = textB * percent / 100;
+ } while (0);
+
+ // Write out the line
+ _screenManagerP->setFontColor(textR, textG, textB);
+ textPos.x = _rect.left + (_rect.width() - (*lineIt)->_lineWidth) / 2;
+ _screenManagerP->writeString(SURFACE_BACKBUFFER, textPos,
+ _rect, (*lineIt)->_line, (*lineIt)->_lineWidth);
+
+ // Move to next line
+ ++lineIt;
+ if (lineIt == (*groupIt)->_lines.end()) {
+ ++groupIt;
+ if (groupIt == _groups.end())
+ // Finished all lines
+ break;
+
+ lineIt = (*groupIt)->_lines.begin();
+ textPos.y += _fontHeight * 3 / 2;
+ }
+ }
+
+ _objectP->makeDirty();
+ _screenManagerP->setFontNumber(oldFontNumber);
+ return true;
}
} // End of namespace Titanic
diff --git a/engines/titanic/support/credit_text.h b/engines/titanic/support/credit_text.h
index ec8fc22cda..3e5bfca0c2 100644
--- a/engines/titanic/support/credit_text.h
+++ b/engines/titanic/support/credit_text.h
@@ -68,11 +68,11 @@ public:
int _field14;
CCreditLineGroups _groups;
uint _ticks;
- uint _fontHeight;
+ int _fontHeight;
CGameObject *_objectP;
CCreditLineGroups::iterator _groupIt;
CCreditLines::iterator _lineIt;
- uint _totalHeight;
+ int _totalHeight;
int _field40;
int _field44;
int _field48;
@@ -80,7 +80,7 @@ public:
int _field50;
int _field54;
int _field58;
- int _field5C;
+ int _counter;
public:
CCreditText();
diff --git a/engines/titanic/support/direct_draw.cpp b/engines/titanic/support/direct_draw.cpp
index 5ddb25bec9..8e510861ae 100644
--- a/engines/titanic/support/direct_draw.cpp
+++ b/engines/titanic/support/direct_draw.cpp
@@ -23,14 +23,13 @@
#include "common/debug.h"
#include "engines/util.h"
#include "graphics/pixelformat.h"
-#include "titanic/titanic.h"
#include "titanic/support/direct_draw.h"
+#include "titanic/titanic.h"
namespace Titanic {
-DirectDraw::DirectDraw(TitanicEngine *vm) : _vm(vm),
- _windowed(false), _fieldC(0), _width(0), _height(0),
- _bpp(0), _numBackSurfaces(0), _field24(0) {
+DirectDraw::DirectDraw() : _windowed(false), _width(0), _height(0),
+ _bpp(0), _numBackSurfaces(0) {
}
void DirectDraw::setDisplayMode(int width, int height, int bpp, int refreshRate) {
@@ -55,7 +54,7 @@ DirectDrawSurface *DirectDraw::createSurfaceFromDesc(const DDSurfaceDesc &desc)
/*------------------------------------------------------------------------*/
-DirectDrawManager::DirectDrawManager(TitanicEngine *vm, bool windowed) : _directDraw(vm) {
+DirectDrawManager::DirectDrawManager(TitanicEngine *vm, bool windowed) {
_mainSurface = nullptr;
_backSurfaces[0] = _backSurfaces[1] = nullptr;
_directDraw._windowed = windowed;
@@ -75,18 +74,6 @@ void DirectDrawManager::initVideo(int width, int height, int bpp, int numBackSur
}
}
-void DirectDrawManager::setResolution() {
- // TODO
-}
-
-void DirectDrawManager::proc2() {
-
-}
-
-void DirectDrawManager::proc3() {
-
-}
-
void DirectDrawManager::initFullScreen() {
debugC(ERROR_BASIC, kDebugGraphics, "Creating surfaces");
_directDraw.setDisplayMode(_directDraw._width, _directDraw._height,
diff --git a/engines/titanic/support/direct_draw.h b/engines/titanic/support/direct_draw.h
index 85c344c600..a7e9cc8d93 100644
--- a/engines/titanic/support/direct_draw.h
+++ b/engines/titanic/support/direct_draw.h
@@ -32,18 +32,14 @@ namespace Titanic {
class TitanicEngine;
class DirectDraw {
-private:
- TitanicEngine *_vm;
public:
bool _windowed;
- int _fieldC;
int _width;
int _height;
int _bpp;
int _numBackSurfaces;
- int _field24;
public:
- DirectDraw(TitanicEngine *vm);
+ DirectDraw();
/**
* Sets a new display mode
@@ -78,12 +74,6 @@ public:
*/
void initVideo(int width, int height, int bpp, int numBackSurfaces);
- void setResolution();
-
- void proc2();
-
- void proc3();
-
/**
* Initializes the surfaces in windowed mode
*/
diff --git a/engines/titanic/support/exe_resources.cpp b/engines/titanic/support/exe_resources.cpp
index 522e92f718..2b2c9c7635 100644
--- a/engines/titanic/support/exe_resources.cpp
+++ b/engines/titanic/support/exe_resources.cpp
@@ -21,6 +21,7 @@
*/
#include "titanic/support/exe_resources.h"
+#include "titanic/true_talk/script_handler.h"
#include "titanic/titanic.h"
namespace Titanic {
diff --git a/engines/titanic/support/files_manager.cpp b/engines/titanic/support/files_manager.cpp
index 04928b96d6..3ee17e9769 100644
--- a/engines/titanic/support/files_manager.cpp
+++ b/engines/titanic/support/files_manager.cpp
@@ -21,6 +21,7 @@
*/
#include "common/file.h"
+#include "common/memstream.h"
#include "titanic/support/files_manager.h"
#include "titanic/game_manager.h"
@@ -51,7 +52,7 @@ void CFilesManager::loadResourceIndex() {
for (;;) {
offset = _datFile.readUint32LE();
size = _datFile.readUint32LE();
- if (size == 0)
+ if (offset == 0 && size == 0)
break;
Common::String resName;
@@ -103,8 +104,9 @@ void CFilesManager::loadDrive() {
resetView();
}
-void CFilesManager::debug(CScreenManager *screenManager) {
- warning("TODO: CFilesManager::debug");
+void CFilesManager::insertCD(CScreenManager *screenManager) {
+ // We not support running game directly from the original CDs,
+ // so this method can remain stubbed
}
void CFilesManager::resetView() {
@@ -114,10 +116,6 @@ void CFilesManager::resetView() {
}
}
-void CFilesManager::fn4(const CString &name) {
- warning("TODO: CFilesManager::fn4");
-}
-
void CFilesManager::preload(const CString &name) {
// We don't currently do any preloading of resources
}
@@ -126,7 +124,8 @@ Common::SeekableReadStream *CFilesManager::getResource(const CString &str) {
ResourceEntry resEntry = _resources[str];
_datFile.seek(resEntry._offset);
- return _datFile.readStream(resEntry._size);
+ return (resEntry._size > 0) ? _datFile.readStream(resEntry._size) :
+ new Common::MemoryReadStream(nullptr, 0);
}
} // End of namespace Titanic
diff --git a/engines/titanic/support/files_manager.h b/engines/titanic/support/files_manager.h
index ec0c7fc008..c530b05ece 100644
--- a/engines/titanic/support/files_manager.h
+++ b/engines/titanic/support/files_manager.h
@@ -84,15 +84,16 @@ public:
*/
void loadDrive();
- void debug(CScreenManager *screenManager);
+ /**
+ * Shows a dialog for inserting a new CD
+ */
+ void insertCD(CScreenManager *screenManager);
/**
* Resets the view being displayed
*/
void resetView();
- void fn4(const CString &name);
-
/**
* Preloads and caches a file for access shortly
*/
diff --git a/engines/titanic/support/font.cpp b/engines/titanic/support/font.cpp
index 69c0efe504..e519237c3b 100644
--- a/engines/titanic/support/font.cpp
+++ b/engines/titanic/support/font.cpp
@@ -179,6 +179,67 @@ int STFont::writeString(CVideoSurface *surface, const Rect &rect1, const Rect &d
return endP ? endP - str.c_str() : 0;
}
+void STFont::writeString(CVideoSurface *surface, const Point &destPos, Rect &clipRect,
+ const CString &str, int lineWidth) {
+ if (!_fontHeight || !_dataPtr || str.empty())
+ return;
+ if (!lineWidth)
+ // No line width specified, so get in the width
+ lineWidth = stringWidth(str);
+
+ Rect textRect(0, 0, lineWidth, _fontHeight);
+ Point textPt = destPos;
+
+ // Perform clipping as necessary if the text will fall outside clipping area
+ if (textPt.y > clipRect.bottom)
+ return;
+
+ if ((textPt.y + textRect.height()) > clipRect.bottom)
+ textRect.bottom = textRect.top - textPt.y + clipRect.bottom;
+
+ if (textPt.y < clipRect.top) {
+ if ((textPt.y + textRect.height()) < clipRect.top)
+ return;
+
+ textRect.top += clipRect.top - textPt.y;
+ textPt.y = clipRect.top;
+ }
+
+ // Iterate through each character of the string
+ for (const byte *srcP = (const byte *)str.c_str(); *srcP; ++srcP) {
+ byte c = *srcP;
+ if (c == 0xE9)
+ c = '$';
+
+ // Form a rect of the area of the next character to draw
+ Rect charRect(_chars[c]._offset, textRect.top,
+ _chars[c]._offset + _chars[c]._width, textRect.bottom);
+
+ if (textPt.x < clipRect.left) {
+ // Character is either partially or entirely left off-screen
+ if ((textPt.x + charRect.width()) < clipRect.left) {
+ textPt.x += _chars[c]._width;
+ continue;
+ }
+
+ // Partially clipped on left-hand side
+ charRect.left = clipRect.left - textPt.x;
+ textPt.x = clipRect.left;
+ } else if ((textPt.x + charRect.width()) > clipRect.right) {
+ if (textPt.x > clipRect.right)
+ // Now entirely off right-hand side, so stop drawing
+ break;
+
+ // Partially clipped on right-hand side
+ charRect.right += clipRect.right - textPt.x - charRect.width();
+ }
+
+ // At this point, we know we've got to draw at least part of a character,
+ // and have figured out the area of the character to draw
+ copyRect(surface, textPt, charRect);
+ }
+}
+
WriteCharacterResult STFont::writeChar(CVideoSurface *surface, unsigned char c, const Point &pt,
const Rect &destRect, const Rect *srcRect) {
if (c == 233)
diff --git a/engines/titanic/support/font.h b/engines/titanic/support/font.h
index 591fb4661c..6c4fe8e9c3 100644
--- a/engines/titanic/support/font.h
+++ b/engines/titanic/support/font.h
@@ -99,6 +99,12 @@ public:
int yOffset, const CString &str, CTextCursor *textCursor);
/**
+ * Write a string to the specified surface
+ */
+ void writeString(CVideoSurface *surface, const Point &destPos, Rect &clipRect,
+ const CString &str, int lineWidth = 0);
+
+ /**
* Get the text area a string will fit into
* @param str String
* @param maxWidth Maximum width in pixels
diff --git a/engines/titanic/support/image.cpp b/engines/titanic/support/image.cpp
index 1e936b6940..2da2179af0 100644
--- a/engines/titanic/support/image.cpp
+++ b/engines/titanic/support/image.cpp
@@ -21,8 +21,8 @@
*/
#include "titanic/support/image.h"
-#include "titanic/titanic.h"
#include "image/bmp.h"
+#include "titanic/titanic.h"
namespace Titanic {
diff --git a/engines/titanic/support/mouse_cursor.cpp b/engines/titanic/support/mouse_cursor.cpp
index d6a42823f5..d342e6cccb 100644
--- a/engines/titanic/support/mouse_cursor.cpp
+++ b/engines/titanic/support/mouse_cursor.cpp
@@ -26,9 +26,9 @@
#include "titanic/support/mouse_cursor.h"
#include "titanic/support/movie.h"
#include "titanic/support/screen_manager.h"
-#include "titanic/titanic.h"
#include "titanic/support/video_surface.h"
#include "titanic/core/resource_key.h"
+#include "titanic/titanic.h"
namespace Titanic {
@@ -67,7 +67,6 @@ CMouseCursor::~CMouseCursor() {
void CMouseCursor::loadCursorImages() {
const CResourceKey key("ycursors.avi");
- g_vm->_filesManager->fn4(key.getString());
// Iterate through getting each cursor
for (int idx = 0; idx < NUM_CURSORS; ++idx) {
@@ -128,8 +127,11 @@ void CMouseCursor::unlockE4() {
CScreenManager::_screenManagerPtr->_inputHandler->decLockCount();
}
-void CMouseCursor::saveState(int v1, int v2, int v3) {
- // TODO
+void CMouseCursor::setPosition(const Point &pt, double rate) {
+ assert(rate >= 0.0 && rate <= 1.0);
+
+ // TODO: Figure out use of the rate parameter
+ g_system->warpMouse(pt.x, pt.y);
}
} // End of namespace Titanic
diff --git a/engines/titanic/support/mouse_cursor.h b/engines/titanic/support/mouse_cursor.h
index 7a81ad43fa..74fb1f6113 100644
--- a/engines/titanic/support/mouse_cursor.h
+++ b/engines/titanic/support/mouse_cursor.h
@@ -24,8 +24,8 @@
#define TITANIC_MOUSE_CURSOR_H
#include "common/scummsys.h"
-#include "common/rect.h"
#include "graphics/managed_surface.h"
+#include "titanic/support/rect.h"
namespace Titanic {
@@ -105,7 +105,10 @@ public:
void lockE4();
void unlockE4();
- void saveState(int v1, int v2, int v3);
+ /**
+ * Sets the mouse to a new position
+ */
+ void setPosition(const Point &pt, double rate);
};
diff --git a/engines/titanic/support/movie.cpp b/engines/titanic/support/movie.cpp
index 3bb9fb88cf..e863185f84 100644
--- a/engines/titanic/support/movie.cpp
+++ b/engines/titanic/support/movie.cpp
@@ -122,33 +122,25 @@ void OSMovie::play(uint startFrame, uint endFrame, uint initialFrame, uint flags
movieStarted();
}
-void OSMovie::playClip(const Point &drawPos, uint startFrame, uint endFrame) {
+void OSMovie::playCutscene(const Rect &drawRect, uint startFrame, uint endFrame) {
if (!_movieSurface)
_movieSurface = CScreenManager::_screenManagerPtr->createSurface(600, 340);
bool widthLess = _videoSurface->getWidth() < 600;
bool heightLess = _videoSurface->getHeight() < 340;
- Rect r(drawPos.x, drawPos.y,
- drawPos.x + (widthLess ? CLIP_WIDTH_REDUCED : CLIP_WIDTH),
- drawPos.y + (heightLess ? CLIP_HEIGHT_REDUCED : CLIP_HEIGHT)
+ Rect r(drawRect.left, drawRect.top,
+ drawRect.left + (widthLess ? CLIP_WIDTH_REDUCED : CLIP_WIDTH),
+ drawRect.top + (heightLess ? CLIP_HEIGHT_REDUCED : CLIP_HEIGHT)
);
- uint timePerFrame = (uint)(1000.0 / _aviSurface._frameRate);
+ // Set a new event target whilst the clip plays, so standard scene drawing isn't called
+ CEventTarget eventTarget;
+ g_vm->_events->addTarget(&eventTarget);
- for (; endFrame >= startFrame; ++startFrame) {
- // Set the frame
- _aviSurface.setFrame(startFrame);
+ _aviSurface.setFrame(startFrame);
+ _aviSurface.playCutscene(r, startFrame, endFrame);
- // TODO: See if we need to do anything further here. The original had a bunch
- // of calls and using of the _movieSurface; perhaps to allow scaling down
- // videos to half-size
- if (widthLess || heightLess)
- warning("Not properly reducing clip size: %d %d", r.width(), r.height());
-
- // Wait for the next frame, unless the user interrupts the clip
- if (g_vm->_events->waitForPress(timePerFrame))
- break;
- }
+ g_vm->_events->removeTarget();
}
void OSMovie::stop() {
diff --git a/engines/titanic/support/movie.h b/engines/titanic/support/movie.h
index 2d1c264f03..8c89f9e6dd 100644
--- a/engines/titanic/support/movie.h
+++ b/engines/titanic/support/movie.h
@@ -84,9 +84,10 @@ public:
virtual void play(uint startFrame, uint endFrame, uint initialFrame, uint flags, CGameObject *obj) = 0;
/**
- * Plays a sub-section of a movie
+ * Plays a sub-section of a movie, and doesn't return until either
+ * the playback ends or a key has been pressed
*/
- virtual void playClip(const Point &drawPos, uint startFrame, uint endFrame) = 0;
+ virtual void playCutscene(const Rect &drawRect, uint startFrame, uint endFrame) = 0;
/**
* Stops the movie
@@ -182,9 +183,10 @@ public:
virtual void play(uint startFrame, uint endFrame, uint initialFrame, uint flags, CGameObject *obj);
/**
- * Plays a sub-section of a movie
+ * Plays a sub-section of a movie, and doesn't return until either
+ * the playback ends or a key has been pressed
*/
- virtual void playClip(const Point &drawPos, uint startFrame, uint endFrame);
+ virtual void playCutscene(const Rect &drawRect, uint startFrame, uint endFrame);
/**
* Stops the movie
diff --git a/engines/titanic/support/screen_manager.cpp b/engines/titanic/support/screen_manager.cpp
index d795f78764..bcf43fc8cb 100644
--- a/engines/titanic/support/screen_manager.cpp
+++ b/engines/titanic/support/screen_manager.cpp
@@ -21,8 +21,8 @@
*/
#include "titanic/support/screen_manager.h"
-#include "titanic/titanic.h"
#include "titanic/support/video_surface.h"
+#include "titanic/titanic.h"
namespace Titanic {
@@ -38,7 +38,6 @@ CScreenManager::CScreenManager(TitanicEngine *vm): _vm(vm) {
_textCursor = nullptr;
_inputHandler = nullptr;
_fontNumber = 0;
- // TODO: Further initialization
_screenManagerPtr = this;
}
@@ -240,10 +239,25 @@ int OSScreenManager::writeString(int surfaceNum, const Rect &destRect,
yOffset, str, textCursor);
}
-int OSScreenManager::writeString(int surfaceNum, const Rect &srcRect,
- const Rect &destRect, const CString &str, CTextCursor *textCursor) {
- // TODO
- return 0;
+void OSScreenManager::writeString(int surfaceNum, const Point &destPos,
+ const Rect &clipRect, const CString &str, int lineWidth) {
+ CVideoSurface *surface;
+ Rect bounds;
+
+ if (surfaceNum >= 0 && surfaceNum < (int)_backSurfaces.size()) {
+ surface = _backSurfaces[surfaceNum]._surface;
+ bounds = _backSurfaces[surfaceNum]._bounds;
+ } else if (surfaceNum == -1) {
+ surface = _frontRenderSurface;
+ bounds = Rect(0, 0, surface->getWidth(), surface->getHeight());
+ } else {
+ return;
+ }
+
+ Rect destRect = clipRect;
+ destRect.constrain(bounds);
+
+ _fonts[_fontNumber].writeString(surface, destPos, destRect, str, lineWidth);
}
void OSScreenManager::setFontColor(byte r, byte g, byte b) {
diff --git a/engines/titanic/support/screen_manager.h b/engines/titanic/support/screen_manager.h
index 0736f1393c..cad6901b02 100644
--- a/engines/titanic/support/screen_manager.h
+++ b/engines/titanic/support/screen_manager.h
@@ -140,13 +140,13 @@ public:
/**
* Write a string
* @param surfaceNum Destination surface
- * @param srcRect Drawing area
- * @param destRect Bounds of dest surface
+ * @param destPos Position to start writing text at
+ * @param clipRect Clipping area to constrain text to
* @param str Line or lines to write
- * @param textCursor Optional text cursor pointer
+ * @param maxWidth Maximum allowed line width
*/
- virtual int writeString(int surfaceNum, const Rect &srcRect,
- const Rect &destRect, const CString &str, CTextCursor *textCursor) = 0;
+ virtual void writeString(int surfaceNum, const Point &destPos,
+ const Rect &clipRect, const CString &str, int maxWidth) = 0;
/**
* Set the font color
@@ -322,13 +322,13 @@ public:
/**
* Write a string
* @param surfaceNum Destination surface
- * @param srcRect Drawing area
- * @param destRect Bounds of dest surface
+ * @param destPos Position to start writing text at
+ * @param clipRect Clipping area to constrain text to
* @param str Line or lines to write
- * @param textCursor Optional text cursor pointer
+ * @param lineWidth Width in pixels of the string, if known.
*/
- virtual int writeString(int surfaceNum, const Rect &srcRect,
- const Rect &destRect, const CString &str, CTextCursor *textCursor);
+ virtual void writeString(int surfaceNum, const Point &destPos,
+ const Rect &clipRect, const CString &str, int lineWidth = 0);
/**
* Set the font color
diff --git a/engines/titanic/support/simple_file.h b/engines/titanic/support/simple_file.h
index f5d0bc7c1b..01aaa86925 100644
--- a/engines/titanic/support/simple_file.h
+++ b/engines/titanic/support/simple_file.h
@@ -278,7 +278,7 @@ public:
* Set up a stream for write access
*/
virtual void open(Common::OutSaveFile *stream) {
- SimpleFile::open(Common::wrapCompressedWriteStream(stream));
+ SimpleFile::open(new Common::OutSaveFile(Common::wrapCompressedWriteStream(stream)));
}
};
diff --git a/engines/titanic/support/string.cpp b/engines/titanic/support/string.cpp
index cd39c03861..9961cfce59 100644
--- a/engines/titanic/support/string.cpp
+++ b/engines/titanic/support/string.cpp
@@ -58,6 +58,10 @@ CString CString::mid(uint start) const {
return mid(start, strSize - start);
}
+CString CString::deleteRight(uint count) const {
+ return (count >= size()) ? CString() : left(size() - count);
+}
+
int CString::indexOf(char c) const {
const char *charP = strchr(c_str(), c);
return charP ? charP - c_str() : -1;
@@ -122,4 +126,20 @@ CString CString::format(const char *fmt, ...) {
return output;
}
+bool CString::operator==(const CString &x) const {
+ return compareToIgnoreCase(x) == 0;
+}
+
+bool CString::operator==(const char *x) const {
+ return compareToIgnoreCase(x) == 0;
+}
+
+bool CString::operator!=(const CString &x) const {
+ return compareToIgnoreCase(x) != 0;
+}
+
+bool CString::operator!=(const char *x) const {
+ return compareToIgnoreCase(x) != 0;
+}
+
} // End of namespace Titanic
diff --git a/engines/titanic/support/string.h b/engines/titanic/support/string.h
index 9550ce88a7..71242c01c9 100644
--- a/engines/titanic/support/string.h
+++ b/engines/titanic/support/string.h
@@ -49,6 +49,11 @@ public:
explicit CString(char c) : Common::String(c) {}
explicit CString(int val);
+ bool operator==(const CString &x) const;
+ bool operator==(const char *x) const;
+ bool operator!=(const CString &x) const;
+ bool operator!=(const char *x) const;
+
/**
* Returns the left n characters of the string
*/
@@ -70,6 +75,12 @@ public:
CString mid(uint start) const;
/**
+ * Returns a substring consisting of the entire string
+ * except for a specified number of characters at the end
+ */
+ CString deleteRight(uint count) const;
+
+ /**
* Returns the index of the first occurance of a given character
*/
int indexOf(char c) const;
diff --git a/engines/titanic/support/string_parser.cpp b/engines/titanic/support/string_parser.cpp
new file mode 100644
index 0000000000..496440a973
--- /dev/null
+++ b/engines/titanic/support/string_parser.cpp
@@ -0,0 +1,97 @@
+/* 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 "titanic/support/string_parser.h"
+#include "common/util.h"
+
+namespace Titanic {
+
+void CStringParser::skipSeperators(const CString &seperatorChars) {
+ for (; _index < size(); ++_index) {
+ char c = (*this)[_index];
+ if (seperatorChars.indexOf(c) == -1)
+ break;
+ }
+}
+
+bool CStringParser::parse(CString &resultStr, const CString &seperatorChars, bool allowQuotes) {
+ if (_index >= size())
+ return false;
+
+ resultStr.clear();
+ bool quoteFlag = false;
+ while (_index < size()) {
+ char c = (*this)[_index];
+ if (!quoteFlag && seperatorChars.indexOf(c) >= 0)
+ break;
+
+ if (allowQuotes) {
+ if (quoteFlag) {
+ if (c == '"') {
+ // End of quoted string
+ ++_index;
+ break;
+ }
+ } else {
+ if (c == '"') {
+ // Start of quoted string
+ ++_index;
+ quoteFlag = true;
+ continue;
+ }
+ }
+ }
+
+ resultStr += c;
+ ++_index;
+ }
+
+ return true;
+}
+
+uint CStringParser::readInt() {
+ // Get digits from the string
+ CString numStr;
+ while (Common::isDigit(currentChar()))
+ numStr += getNextChar();
+
+ // Throw a wobbly if there wasn't a number
+ if (numStr.empty())
+ error("ReadInt(): No number to read");
+
+ return atoi(numStr.c_str());
+}
+
+char CStringParser::currentChar() const {
+ return (_index >= size()) ? '\0' : (*this)[_index];
+}
+
+char CStringParser::getNextChar() {
+ return (_index >= size()) ? '\0' : (*this)[_index++];
+}
+
+void CStringParser::skipSpaces() {
+ while (_index < size() && Common::isSpace(currentChar()))
+ ++_index;
+}
+
+} // End of namespace Titanic
diff --git a/engines/titanic/support/string_parser.h b/engines/titanic/support/string_parser.h
new file mode 100644
index 0000000000..f89caacfb5
--- /dev/null
+++ b/engines/titanic/support/string_parser.h
@@ -0,0 +1,76 @@
+/* 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 TITANIC_STRING_PARSER_H
+#define TITANIC_STRING_PARSER_H
+
+#include "titanic/support/string.h"
+
+namespace Titanic {
+
+class CStringParser : public CString {
+private:
+ uint _index;
+private:
+ /**
+ * Gets the character at the current index
+ */
+ char currentChar() const;
+
+ /**
+ * Gets the next character, and increments the parsing index
+ */
+ char getNextChar();
+
+ /**
+ * Skips over any spaces
+ */
+ void skipSpaces();
+public:
+ CStringParser() : CString(), _index(0) {}
+ CStringParser(const CString &str) : CString(str), _index(0) {}
+
+ /**
+ * Skips over any specified seperator characters in our string
+ * at the current index
+ */
+ void skipSeperators(const CString &seperatorChars);
+
+ /**
+ * Parses out a string from a source string at the current index
+ * @param resultStr String to hold the resulting sring
+ * @param seperatorChras List of characters that seperate string values
+ * @param allowQuotes If true, handles double-quoted substrings
+ * @returns True if a string entry was extracted
+ */
+ bool parse(CString &resultStr, const CString &seperatorChars, bool allowQuotes = false);
+
+ /**
+ * Reads an integer from the string
+ */
+ uint readInt();
+
+};
+
+} // End of namespace Titanic
+
+#endif /* TITANIC_STRING_PARSER_H */
diff --git a/engines/titanic/support/video_surface.cpp b/engines/titanic/support/video_surface.cpp
index 594f660937..b5f668793a 100644
--- a/engines/titanic/support/video_surface.cpp
+++ b/engines/titanic/support/video_surface.cpp
@@ -163,7 +163,19 @@ void CVideoSurface::blitRect2(const Rect &srcRect, const Rect &destRect, CVideoS
}
void CVideoSurface::movieBlitRect(const Rect &srcRect, const Rect &destRect, CVideoSurface *src) {
- // TODO
+ if (lock()) {
+ if (src->lock()) {
+ Graphics::ManagedSurface *srcSurface = src->_rawSurface;
+ Graphics::ManagedSurface *destSurface = _rawSurface;
+
+ // TODO: Handle the transparency mode correctly
+ destSurface->blitFrom(*srcSurface, srcRect, Point(srcRect.left, srcRect.top));
+
+ src->unlock();
+ }
+
+ unlock();
+ }
}
uint CVideoSurface::getTransparencyColor() {
diff --git a/engines/titanic/titanic.cpp b/engines/titanic/titanic.cpp
index af894d6997..3a721b6095 100644
--- a/engines/titanic/titanic.cpp
+++ b/engines/titanic/titanic.cpp
@@ -33,6 +33,7 @@
#include "titanic/carry/hose.h"
#include "titanic/core/saveable_object.h"
#include "titanic/game/get_lift_eye2.h"
+#include "titanic/game/lemon_dispensor.h"
#include "titanic/game/television.h"
#include "titanic/game/parrot/parrot_lobby_object.h"
#include "titanic/game/sgt/sgt_navigation.h"
@@ -91,6 +92,7 @@ void TitanicEngine::initialize() {
CGameObject::init();
CGetLiftEye2::init();
CHose::init();
+ CLemonDispensor::init();
CMovie::init();
CParrotLobbyObject::init();
CSGTNavigation::init();
diff --git a/engines/titanic/true_talk/script_handler.cpp b/engines/titanic/true_talk/script_handler.cpp
index 64e789a4b9..f434822870 100644
--- a/engines/titanic/true_talk/script_handler.cpp
+++ b/engines/titanic/true_talk/script_handler.cpp
@@ -60,19 +60,18 @@ ScriptChangedResult CScriptHandler::scriptChanged(TTroomScript *roomScript, TTnp
if (result == SCR_1)
result = npcScript->notifyScript(roomScript, dialogueId);
- if (result != SCR_3 && result != SCR_4)
- return result;
+ if (dialogueId == 3 || dialogueId == 4) {
+ delete _concept1P;
+ delete _concept2P;
+ delete _concept3P;
+ delete _concept4P;
+ _concept1P = nullptr;
+ _concept2P = nullptr;
+ _concept3P = nullptr;
+ _concept4P = nullptr;
+ }
++_inputCtr;
- delete _concept1P;
- delete _concept2P;
- delete _concept3P;
- delete _concept4P;
- _concept1P = nullptr;
- _concept2P = nullptr;
- _concept3P = nullptr;
- _concept4P = nullptr;
-
return result;
}
diff --git a/engines/titanic/true_talk/title_engine.cpp b/engines/titanic/true_talk/title_engine.cpp
index 4dd45ba335..363cc3454c 100644
--- a/engines/titanic/true_talk/title_engine.cpp
+++ b/engines/titanic/true_talk/title_engine.cpp
@@ -66,10 +66,6 @@ int STtitleEngine::setResponse(TTscriptBase *script, TTresponse *response) {
return 0;
}
-void STtitleEngine::dump(int val1, int val2) {
- // TODO
-}
-
SimpleFile *STtitleEngine::open(const CString &name) {
Common::SeekableReadStream *stream = g_vm->_filesManager->getResource(
CString::format("TEXT/%s", name.c_str()));
diff --git a/engines/titanic/true_talk/title_engine.h b/engines/titanic/true_talk/title_engine.h
index afd2d3b92f..a980e52215 100644
--- a/engines/titanic/true_talk/title_engine.h
+++ b/engines/titanic/true_talk/title_engine.h
@@ -57,12 +57,6 @@ public:
* Sets a conversation reponse
*/
virtual int setResponse(TTscriptBase *script, TTresponse *response) { return SS_4; }
-
- virtual int proc4(int unused) const = 0;
- virtual int proc5(int64 unused) const = 0;
- virtual int proc6(int64 unused) const = 0;
- virtual int proc7(int64 unused) const = 0;
- virtual int proc8() const = 0;
/**
* Open a designated file
@@ -94,14 +88,6 @@ public:
*/
virtual int setResponse(TTscriptBase *script, TTresponse *response);
- virtual void dump(int val1, int val2);
-
- virtual int proc4(int unused) const { return 0; }
- virtual int proc5(int64 unused) const { return 0; }
- virtual int proc6(int64 unused) const { return 0; }
- virtual int proc7(int64 unused) const { return 0; }
- virtual int proc8() const { return 0; }
-
/**
* Open a designated file
*/
diff --git a/engines/titanic/true_talk/true_talk_manager.cpp b/engines/titanic/true_talk/true_talk_manager.cpp
index 61ad924d90..085f0bd310 100644
--- a/engines/titanic/true_talk/true_talk_manager.cpp
+++ b/engines/titanic/true_talk/true_talk_manager.cpp
@@ -114,7 +114,7 @@ void CTrueTalkManager::loadStatics(SimpleFile *file) {
_v9 = file->readNumber();
_v10 = file->readNumber() != 0;
- for (int idx = count; count > 10; --idx)
+ for (int idx = count; idx > 10; --idx)
file->readNumber();
int count2 = file->readNumber();
@@ -219,10 +219,6 @@ void CTrueTalkManager::removeCompleted() {
}
}
-void CTrueTalkManager::update2() {
- //warning("CTrueTalkManager::update2");
-}
-
void CTrueTalkManager::start(CTrueTalkNPC *npc, uint id, CViewItem *view) {
TTnpcScript *npcScript = getNpcScript(npc);
TTroomScript *roomScript = getRoomScript();
@@ -494,32 +490,32 @@ void CTrueTalkManager::playSpeech(TTtalker *talker, TTroomScript *roomScript, CV
// Setup proximities
CProximity p1, p2, p3;
if (isParrot) {
- p1._channel = 3;
- p2._channel = 5;
- p3._channel = 4;
+ p1._channelMode = 3;
+ p2._channelMode = 5;
+ p3._channelMode = 4;
} else {
- p1._channel = 0;
- p2._channel = 1;
- p3._channel = 2;
+ p1._channelMode = 0;
+ p2._channelMode = 1;
+ p3._channelMode = 2;
}
if (milli > 0) {
p3._channelVolume = (index * 3) / 2;
- p3._field28 = 1;
+ p3._positioningMode = POSMODE_POLAR;
p3._azimuth = -135.0;
p3._range = 1.0;
p3._elevation = 0;
p2._channelVolume = (index * 3) / 4;
- p2._field28 = 0;
+ p2._positioningMode = POSMODE_NONE;
p2._azimuth = 135.0;
p2._range = 1.0;
p2._elevation = 0;
}
- _gameManager->_sound.stopChannel(p1._channel);
+ _gameManager->_sound.stopChannel(p1._channelMode);
if (view) {
- p1._field28 = 2;
+ p1._positioningMode = POSMODE_VECTOR;
view->getPosition(p1._posX, p1._posY, p1._posZ);
}
@@ -589,9 +585,9 @@ int CTrueTalkManager::getPassengerClass() const {
return gameState ? gameState->_passengerClass : 4;
}
-int CTrueTalkManager::getState14() const {
+Season CTrueTalkManager::getCurrentSeason() const {
CGameState *gameState = getGameState();
- return gameState ? gameState->_field14 : 0;
+ return gameState ? gameState->_seasonNum : SEASON_SUMMER;
}
} // End of namespace Titanic
diff --git a/engines/titanic/true_talk/true_talk_manager.h b/engines/titanic/true_talk/true_talk_manager.h
index 8a8895917a..e891f6112a 100644
--- a/engines/titanic/true_talk/true_talk_manager.h
+++ b/engines/titanic/true_talk/true_talk_manager.h
@@ -31,6 +31,7 @@
#include "titanic/true_talk/tt_quotes_tree.h"
#include "titanic/true_talk/tt_scripts.h"
#include "titanic/true_talk/tt_talker.h"
+#include "titanic/game_state.h"
namespace Titanic {
@@ -200,8 +201,6 @@ public:
*/
CGameManager *getGameManager() const;
- void update2();
-
/**
* Start a TrueTalk conversation
*/
@@ -237,7 +236,7 @@ public:
*/
int getPassengerClass() const;
- int getState14() const;
+ Season getCurrentSeason() const;
};
} // End of namespace Titanic
diff --git a/engines/titanic/true_talk/tt_npc_script.cpp b/engines/titanic/true_talk/tt_npc_script.cpp
index 61c3b0e00c..74e2f4f66b 100644
--- a/engines/titanic/true_talk/tt_npc_script.cpp
+++ b/engines/titanic/true_talk/tt_npc_script.cpp
@@ -332,7 +332,7 @@ int TTnpcScript::handleQuote(const TTroomScript *roomScript, const TTsentence *s
uint TTnpcScript::getRangeValue(uint id) {
TTscriptRange *range = findRange(id);
if (!range)
- return 0;
+ return id;
switch (range->_mode) {
case SF_RANDOM: {
@@ -579,14 +579,14 @@ int TTnpcScript::getValue(int testNum) const {
case 4:
if (g_vm->_trueTalkManager) {
- switch (g_vm->_trueTalkManager->getState14()) {
- case 1:
+ switch (g_vm->_trueTalkManager->getCurrentSeason()) {
+ case SEASON_AUTUMN:
CTrueTalkManager::_v6 = 3;
break;
- case 2:
+ case SEASON_WINTER:
CTrueTalkManager::_v6 = 0;
break;
- case 3:
+ case SEASON_SPRING:
CTrueTalkManager::_v6 = 1;
break;
default:
@@ -634,13 +634,12 @@ uint TTnpcScript::getDialogueId(uint tagId) {
}
}
- uint oldTagId = tagId;
tagId = getRangeValue(tagId);
- if (tagId != oldTagId)
+ if (tagId != origId)
tagId = getRangeValue(tagId);
- oldTagId = getDialsBitset();
- uint newId = updateState(origId, tagId, oldTagId);
+ uint dialBits = getDialsBitset();
+ uint newId = updateState(origId, tagId, dialBits);
if (!newId)
return 0;
@@ -654,7 +653,7 @@ uint TTnpcScript::getDialogueId(uint tagId) {
if (tableP->_id == newId)
break;
}
- uint newVal = tableP->_values[oldTagId];
+ uint newVal = tableP->_values[dialBits];
// First slot dialogue Ids
idx = 0;
diff --git a/engines/titanic/true_talk/tt_parser.cpp b/engines/titanic/true_talk/tt_parser.cpp
index 1d9c199054..95a302d53c 100644
--- a/engines/titanic/true_talk/tt_parser.cpp
+++ b/engines/titanic/true_talk/tt_parser.cpp
@@ -575,7 +575,7 @@ int TTparser::loadRequests(TTword *word) {
if (_sentenceConcept) {
if (_sentenceConcept->get18() == 0 || _sentenceConcept->get18() == 2) {
- TTaction *action = static_cast<TTaction *>(word);
+ TTaction *action = dynamic_cast<TTaction *>(word);
_sentenceConcept->set18(action->getVal());
}
}
@@ -1273,7 +1273,7 @@ int TTparser::considerRequests(TTword *word) {
break;
}
- TTparserNode *nextP = static_cast<TTparserNode *>(nodeP->_nextP);
+ TTparserNode *nextP = dynamic_cast<TTparserNode *>(nodeP->_nextP);
if (flag)
delete nodeP;
nodeP = nextP;
@@ -1375,7 +1375,7 @@ void TTparser::removeConcept(TTconcept *concept) {
void TTparser::removeNode(TTparserNode *node) {
if (!node->_priorP)
// Node is the head of the chain, so reset parser's nodes pointer
- _nodesP = static_cast<TTparserNode *>(node->_nextP);
+ _nodesP = dynamic_cast<TTparserNode *>(node->_nextP);
delete node;
}
@@ -1525,7 +1525,7 @@ int TTparser::fn2(TTword *word) {
case 602:
case 607:
- return checkReferent(static_cast<TTpronoun *>(word));
+ return checkReferent(dynamic_cast<TTpronoun *>(word));
case 608:
return 1;
diff --git a/engines/titanic/true_talk/tt_room_script.cpp b/engines/titanic/true_talk/tt_room_script.cpp
index b8fbca7d39..a49a5a5bd1 100644
--- a/engines/titanic/true_talk/tt_room_script.cpp
+++ b/engines/titanic/true_talk/tt_room_script.cpp
@@ -34,7 +34,7 @@ TTroomScriptBase::TTroomScriptBase(int scriptId,
/*------------------------------------------------------------------------*/
TTroomScript::TTroomScript(int scriptId) :
- TTroomScriptBase(scriptId, "", "", 0, -1, -1, -1, 0, 0) {
+ TTroomScriptBase(scriptId, "", "", 0, -1, -1, -1, 0, 0), _field54(0) {
}
bool TTroomScript::proc8() const {
diff --git a/engines/titanic/true_talk/tt_sentence.cpp b/engines/titanic/true_talk/tt_sentence.cpp
index 9588ee021e..f187710de7 100644
--- a/engines/titanic/true_talk/tt_sentence.cpp
+++ b/engines/titanic/true_talk/tt_sentence.cpp
@@ -83,7 +83,7 @@ void TTsentence::copyFrom(const TTsentence &src) {
if (src._nodesP) {
// Source has processed nodes, so duplicate them
for (TTsentenceNode *node = src._nodesP; node;
- node = static_cast<TTsentenceNode *>(node->_nextP)) {
+ node = dynamic_cast<TTsentenceNode *>(node->_nextP)) {
TTsentenceNode *newNode = new TTsentenceNode(node->_wordP);
if (_nodesP)
_nodesP->addToTail(newNode);
@@ -319,7 +319,7 @@ bool TTsentence::localWord(const char *str) const {
bool result = false;
for (TTsentenceNode *nodeP = _nodesP; nodeP && !result;
- nodeP = static_cast<TTsentenceNode *>(nodeP->_nextP)) {
+ nodeP = dynamic_cast<TTsentenceNode *>(nodeP->_nextP)) {
TTsynonym syn;
if (!nodeP->_wordP)
continue;
diff --git a/engines/titanic/true_talk/tt_string_node.cpp b/engines/titanic/true_talk/tt_string_node.cpp
index 2bb0c5a74b..5a21d73a9b 100644
--- a/engines/titanic/true_talk/tt_string_node.cpp
+++ b/engines/titanic/true_talk/tt_string_node.cpp
@@ -55,7 +55,7 @@ void TTstringNode::initialize(TTstringNode *oldNode) {
}
TTstringNode *TTstringNode::findByName(const TTstring &str, int mode) {
- for (TTstringNode *nodeP = this; nodeP; nodeP = static_cast<TTstringNode *>(nodeP->_nextP)) {
+ for (TTstringNode *nodeP = this; nodeP; nodeP = dynamic_cast<TTstringNode *>(nodeP->_nextP)) {
if (nodeP->_mode == mode || (mode == 3 && nodeP->_mode < 3)) {
if (nodeP->_string == str)
return nodeP;
diff --git a/engines/titanic/true_talk/tt_synonym.cpp b/engines/titanic/true_talk/tt_synonym.cpp
index 0f56c5cb22..b476efefcb 100644
--- a/engines/titanic/true_talk/tt_synonym.cpp
+++ b/engines/titanic/true_talk/tt_synonym.cpp
@@ -60,7 +60,7 @@ TTsynonym *TTsynonym::copyFrom(const TTsynonym *src) {
}
int TTsynonym::save(SimpleFile *file) {
- for (TTstringNode *synP = this; synP; synP = static_cast<TTstringNode *>(synP->_nextP)) {
+ for (TTstringNode *synP = this; synP; synP = dynamic_cast<TTstringNode *>(synP->_nextP)) {
file->writeFormat("%s", " 0 ");
synP->_string.save(file);
file->writeFormat("%c", ' ');
diff --git a/engines/titanic/true_talk/tt_vocab.cpp b/engines/titanic/true_talk/tt_vocab.cpp
index 08d6e9e1a7..1d4d2ebbf2 100644
--- a/engines/titanic/true_talk/tt_vocab.cpp
+++ b/engines/titanic/true_talk/tt_vocab.cpp
@@ -288,7 +288,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const {
if (word) {
if (word->_wordClass == WC_ACTION) {
- static_cast<TTaction *>(word)->setVal(1);
+ dynamic_cast<TTaction *>(word)->setVal(1);
}
} else {
tempStr = str;
@@ -311,7 +311,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const {
if (word) {
if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -331,7 +331,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const {
if (word) {
if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -350,7 +350,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const {
word = getPrimeWord(tempStr);
if (word && word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -373,7 +373,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const {
if (word) {
if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -393,7 +393,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const {
if (word) {
if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -412,7 +412,7 @@ TTword *TTvocab::getSuffixedWord(TTstring &str) const {
word = getPrimeWord(tempStr);
if (word) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
@@ -529,7 +529,7 @@ TTword *TTvocab::getPrefixedWord(TTstring &str) const {
if (!word)
tempStr = str;
else if (word->_wordClass == WC_ADJECTIVE) {
- TTadj *adj = static_cast<TTadj *>(word);
+ TTadj *adj = dynamic_cast<TTadj *>(word);
int val1 = word->proc15();
int val2 = word->proc15();
diff --git a/engines/titanic/true_talk/tt_word.cpp b/engines/titanic/true_talk/tt_word.cpp
index df6ee5c7bc..c8676e4b1e 100644
--- a/engines/titanic/true_talk/tt_word.cpp
+++ b/engines/titanic/true_talk/tt_word.cpp
@@ -173,7 +173,7 @@ bool TTword::findSynByName(const TTstring &str, TTsynonym *dest, int mode) const
if (!_synP)
return false;
- const TTsynonym *synP = static_cast<const TTsynonym *>(_synP->findByName(str, mode));
+ const TTsynonym *synP = dynamic_cast<const TTsynonym *>(_synP->findByName(str, mode));
if (synP) {
dest->copyFrom(synP);
dest->_priorP = nullptr;
diff --git a/engines/toltecs/detection.cpp b/engines/toltecs/detection.cpp
index 7c707895e6..cc27341e10 100644
--- a/engines/toltecs/detection.cpp
+++ b/engines/toltecs/detection.cpp
@@ -234,7 +234,8 @@ bool ToltecsMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
(f == kSavesSupportCreationDate) ||
- (f == kSavesSupportPlayTime);
+ (f == kSavesSupportPlayTime) ||
+ (f == kSimpleSavesNames);
}
bool Toltecs::ToltecsEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/toon/detection.cpp b/engines/toon/detection.cpp
index 5d2e0a9bca..e93d676d87 100644
--- a/engines/toon/detection.cpp
+++ b/engines/toon/detection.cpp
@@ -159,7 +159,8 @@ bool ToonMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
- (f == kSavesSupportCreationDate);
+ (f == kSavesSupportCreationDate) ||
+ (f == kSimpleSavesNames);
}
void ToonMetaEngine::removeSaveState(const char *target, int slot) const {
diff --git a/engines/tsage/detection.cpp b/engines/tsage/detection.cpp
index 584ad87742..e476391f71 100644
--- a/engines/tsage/detection.cpp
+++ b/engines/tsage/detection.cpp
@@ -95,6 +95,7 @@ public:
case kSavesSupportThumbnail:
case kSavesSupportCreationDate:
case kSavesSupportPlayTime:
+ case kSimpleSavesNames:
return true;
default:
return false;
diff --git a/engines/tsage/stP1kAlM b/engines/tsage/stP1kAlM
new file mode 100644
index 0000000000..dfbb3b9786
--- /dev/null
+++ b/engines/tsage/stP1kAlM
Binary files differ
diff --git a/engines/voyeur/detection.cpp b/engines/voyeur/detection.cpp
index 7b9fa6722e..eefe174e94 100644
--- a/engines/voyeur/detection.cpp
+++ b/engines/voyeur/detection.cpp
@@ -92,7 +92,8 @@ bool VoyeurMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsLoadingDuringStartup) ||
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
- (f == kSavesSupportThumbnail);
+ (f == kSavesSupportThumbnail) ||
+ (f == kSimpleSavesNames);
}
bool Voyeur::VoyeurEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/wage/design.cpp b/engines/wage/design.cpp
index 86b325e2b9..fd2a67b81e 100644
--- a/engines/wage/design.cpp
+++ b/engines/wage/design.cpp
@@ -235,9 +235,9 @@ void drawPixel(int x, int y, int color, void *data) {
color : kColorWhite;
}
} else {
- int x1 = x;
+ int x1 = x - p->thickness / 2;
int x2 = x1 + p->thickness;
- int y1 = y;
+ int y1 = y - p->thickness / 2;
int y2 = y1 + p->thickness;
for (y = y1; y < y2; y++)
diff --git a/engines/wage/detection.cpp b/engines/wage/detection.cpp
index a27bfd7fde..778cd3c7a7 100644
--- a/engines/wage/detection.cpp
+++ b/engines/wage/detection.cpp
@@ -78,7 +78,8 @@ bool WageMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
- (f == kSupportsDeleteSave);
+ (f == kSupportsDeleteSave) ||
+ (f == kSimpleSavesNames);
}
bool Wage::WageEngine::hasFeature(EngineFeature f) const {
diff --git a/engines/wage/saveload.cpp b/engines/wage/saveload.cpp
index c3b20bdf2f..da10ad41c1 100644
--- a/engines/wage/saveload.cpp
+++ b/engines/wage/saveload.cpp
@@ -109,7 +109,7 @@ Obj *WageEngine::getObjByOffset(int offset, int objBaseOffset) const {
objIndex = (offset - objBaseOffset) / CHR_SIZE;
}
- if (objIndex >= 0 && objIndex < _world->_orderedObjs.size()) {
+ if (objIndex >= 0 && (uint)objIndex < _world->_orderedObjs.size()) {
return _world->_orderedObjs[objIndex];
}
@@ -133,7 +133,7 @@ Chr *WageEngine::getChrByOffset(int offset, int chrBaseOffset) const {
chrIndex = (offset - chrBaseOffset) / CHR_SIZE;
}
- if (chrIndex >= 0 && chrIndex < _world->_orderedChrs.size()) {
+ if (chrIndex >= 0 && (uint)chrIndex < _world->_orderedChrs.size()) {
return _world->_orderedChrs[chrIndex];
}
@@ -160,7 +160,7 @@ Scene *WageEngine::getSceneByOffset(int offset) const {
sceneIndex = 1 + (offset - SCENES_INDEX) / SCENE_SIZE;
}
- if (sceneIndex >= 0 && sceneIndex < _world->_orderedScenes.size()) {
+ if (sceneIndex >= 0 && (uint)sceneIndex < _world->_orderedScenes.size()) {
if (sceneIndex == 0) return _world->_storageScene;
return _world->_orderedScenes[sceneIndex];
}
@@ -224,10 +224,10 @@ int WageEngine::saveGame(const Common::String &fileName, const Common::String &d
out->writeSint32LE(GET_HEX_OBJ_OFFSET(player->_armor[Chr::MAGIC_ARMOR])); //sprtArmIndex
// TODO:
- out->writeSint16LE(0xffff); // ???? - always FFFF
- out->writeSint16LE(0xffff); // ???? - always FFFF
- out->writeSint16LE(0xffff); // ???? - always FFFF
- out->writeSint16LE(0xffff); // ???? - always FFFF
+ out->writeUint16LE(0xffff); // ???? - always FFFF
+ out->writeUint16LE(0xffff); // ???? - always FFFF
+ out->writeUint16LE(0xffff); // ???? - always FFFF
+ out->writeUint16LE(0xffff); // ???? - always FFFF
// did a character just escape?
out->writeSint32LE(GET_HEX_CHR_OFFSET(_running)); //getRunCharHexOffset() == getHexOffsetForChr(running)
@@ -260,7 +260,7 @@ int WageEngine::saveGame(const Common::String &fileName, const Common::String &d
// write user vars
for (uint32 i = 0; i < 26 * 9; ++i)
out->writeSint16LE(playerContext._userVariables[i]);
-
+
// write updated info for all scenes
Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
for (uint32 i = 0; i < orderedScenes.size(); ++i) {
@@ -520,12 +520,12 @@ int WageEngine::loadGame(int slotId) {
// set all user variables
for (uint32 i = 0; i < 26 * 9; ++i) {
- playerContext._userVariables[i] = data->readSint16LE();
+ playerContext._userVariables[i] = data->readSint16LE();
}
// update all scene stats
Common::Array<Scene *> &orderedScenes = _world->_orderedScenes;
- if (numScenes != orderedScenes.size()) {
+ if ((uint)numScenes != orderedScenes.size()) {
warning("scenes number in file (%d) differs from the one in world (%d)", numScenes, orderedScenes.size());
}
for (uint32 i = 0; i < orderedScenes.size(); ++i) {
@@ -559,7 +559,7 @@ int WageEngine::loadGame(int slotId) {
// update all char locations and stats
Common::Array<Chr *> &orderedChrs = _world->_orderedChrs;
- if (numChars != orderedChrs.size()) {
+ if ((uint)numChars != orderedChrs.size()) {
warning("characters number in file (%d) differs from the one in world (%d)", numChars, orderedChrs.size());
}
for (uint32 i = 0; i < orderedChrs.size(); ++i) {
@@ -613,7 +613,7 @@ int WageEngine::loadGame(int slotId) {
// update all object locations and stats
Common::Array<Obj *> &orderedObjs = _world->_orderedObjs;
- if (numObjs != orderedObjs.size()) {
+ if ((uint)numObjs != orderedObjs.size()) {
warning("objects number in file (%d) differs from the one in world (%d)", numObjs, orderedObjs.size());
}
for (uint32 i = 0; i < orderedObjs.size(); ++i) {
@@ -727,7 +727,7 @@ Common::String WageEngine::getSavegameFilename(int16 slotId) const {
return saveLoadSlot;
}
-Common::Error WageEngine::loadGameState(int slot) {
+Common::Error WageEngine::loadGameState(int slot) {
if (loadGame(slot) == 0)
return Common::kNoError;
else
diff --git a/engines/wintermute/base/base_game.cpp b/engines/wintermute/base/base_game.cpp
index ce4c5fdda5..1af7e2b56d 100644
--- a/engines/wintermute/base/base_game.cpp
+++ b/engines/wintermute/base/base_game.cpp
@@ -71,7 +71,7 @@
#include "common/system.h"
#include "common/file.h"
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
#include "engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h"
#endif
@@ -402,7 +402,7 @@ bool BaseGame::initialize1() {
break;
}
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
_scEngine = new DebuggableScEngine(this);
#else
_scEngine = new ScEngine(this);
diff --git a/engines/wintermute/base/base_game.h b/engines/wintermute/base/base_game.h
index 409cc20ba4..6aacc1feab 100644
--- a/engines/wintermute/base/base_game.h
+++ b/engines/wintermute/base/base_game.h
@@ -36,7 +36,7 @@
#include "engines/wintermute/math/rect32.h"
#include "engines/wintermute/debugger.h"
#include "common/events.h"
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
#include "engines/wintermute/base/scriptables/debuggable/debuggable_script_engine.h"
#endif
@@ -152,7 +152,7 @@ public:
BaseRenderer *_renderer;
BaseSoundMgr *_soundMgr;
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
DebuggableScEngine *_scEngine;
#else
ScEngine *_scEngine;
diff --git a/engines/wintermute/base/base_script_holder.cpp b/engines/wintermute/base/base_script_holder.cpp
index 7427a9b082..fd9dd6a2a5 100644
--- a/engines/wintermute/base/base_script_holder.cpp
+++ b/engines/wintermute/base/base_script_holder.cpp
@@ -466,7 +466,7 @@ void BaseScriptHolder::makeFreezable(bool freezable) {
ScScript *BaseScriptHolder::invokeMethodThread(const char *methodName) {
for (int i = _scripts.size() - 1; i >= 0; i--) {
if (_scripts[i]->canHandleMethod(methodName)) {
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
DebuggableScEngine* debuggableEngine;
debuggableEngine = dynamic_cast<DebuggableScEngine*>(_scripts[i]->_engine);
// TODO: Not pretty
diff --git a/engines/wintermute/base/scriptables/script.cpp b/engines/wintermute/base/scriptables/script.cpp
index 938ec031da..c13310255d 100644
--- a/engines/wintermute/base/scriptables/script.cpp
+++ b/engines/wintermute/base/scriptables/script.cpp
@@ -32,7 +32,7 @@
#include "engines/wintermute/base/scriptables/script_engine.h"
#include "engines/wintermute/base/scriptables/script_stack.h"
#include "common/memstream.h"
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
#include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h"
#endif
namespace Wintermute {
@@ -1320,7 +1320,7 @@ ScScript *ScScript::invokeEventHandler(const Common::String &eventName, bool unb
if (!pos) {
return nullptr;
}
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
// TODO: Not pretty
DebuggableScEngine* debuggableEngine;
debuggableEngine = dynamic_cast<DebuggableScEngine*>(_engine);
diff --git a/engines/wintermute/base/scriptables/script_engine.cpp b/engines/wintermute/base/scriptables/script_engine.cpp
index 26122094f1..8d957c6951 100644
--- a/engines/wintermute/base/scriptables/script_engine.cpp
+++ b/engines/wintermute/base/scriptables/script_engine.cpp
@@ -144,7 +144,7 @@ ScScript *ScEngine::runScript(const char *filename, BaseScriptHolder *owner) {
}
// add new script
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
DebuggableScEngine* debuggableEngine;
debuggableEngine = dynamic_cast<DebuggableScEngine*>(this);
// TODO: Not pretty
diff --git a/engines/wintermute/debugger.h b/engines/wintermute/debugger.h
index e5008bee3b..6b1d2312ba 100644
--- a/engines/wintermute/debugger.h
+++ b/engines/wintermute/debugger.h
@@ -23,11 +23,11 @@
#ifndef WINTERMUTE_DEBUGGER_H
#define WINTERMUTE_DEBUGGER_H
-#define EXTENDED_DEBUGGER_ENABLED true
+#define EXTENDED_DEBUGGER_ENABLED 1
#include "gui/debugger.h"
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
#include "engines/wintermute/base/scriptables/debuggable/debuggable_script.h"
#else
#include "engines/wintermute/base/scriptables/script.h"
@@ -71,7 +71,7 @@ public:
bool Cmd_ShowFps(int argc, const char **argv);
bool Cmd_DumpFile(int argc, const char **argv);
-#if EXTENDED_DEBUGGER_ENABLED == true
+#if EXTENDED_DEBUGGER_ENABLED
/**
* Step - break again on next line
*/
diff --git a/engines/zvision/detection.cpp b/engines/zvision/detection.cpp
index cc967070d9..5e535a9954 100644
--- a/engines/zvision/detection.cpp
+++ b/engines/zvision/detection.cpp
@@ -87,7 +87,8 @@ bool ZVisionMetaEngine::hasFeature(MetaEngineFeature f) const {
(f == kSupportsDeleteSave) ||
(f == kSavesSupportMetaInfo) ||
(f == kSavesSupportThumbnail) ||
- (f == kSavesSupportCreationDate);
+ (f == kSavesSupportCreationDate) ||
+ (f == kSimpleSavesNames);
//(f == kSavesSupportPlayTime);
}
diff --git a/graphics/VectorRenderer.h b/graphics/VectorRenderer.h
index 0352808706..5f7b6e60d3 100644
--- a/graphics/VectorRenderer.h
+++ b/graphics/VectorRenderer.h
@@ -28,6 +28,7 @@
#include "common/str.h"
#include "graphics/surface.h"
+#include "graphics/transparent_surface.h"
#include "gui/ThemeEngine.h"
@@ -79,8 +80,11 @@ struct DrawStep {
uint32 scale; /**< scale of all the coordinates in FIXED POINT with 16 bits mantissa */
+ GUI::ThemeEngine::AutoScaleMode autoscale; /**< scale alphaimage if present */
+
DrawingFunctionCallback drawingCall; /**< Pointer to drawing function */
Graphics::Surface *blitSrc;
+ Graphics::TransparentSurface *blitAlphaSrc;
};
VectorRenderer *createRenderer(int mode);
@@ -281,7 +285,7 @@ public:
*
* @param surface Pointer to a Surface object.
*/
- virtual void setSurface(Surface *surface) {
+ virtual void setSurface(TransparentSurface *surface) {
_activeSurface = surface;
}
@@ -420,7 +424,13 @@ public:
void drawCallback_BITMAP(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) {
uint16 x, y, w, h;
stepGetPositions(step, area, x, y, w, h);
- blitAlphaBitmapClip(step.blitSrc, Common::Rect(x, y, x + w, y + h), clip);
+ blitKeyBitmapClip(step.blitSrc, Common::Rect(x, y, x + w, y + h), clip);
+ }
+
+ void drawCallback_ALPHABITMAP(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) {
+ uint16 x, y, w, h;
+ stepGetPositions(step, area, x, y, w, h);
+ blitAlphaBitmap(step.blitAlphaSrc, Common::Rect(x, y, x + w, y + h), step.autoscale, step.xAlign, step.yAlign); //TODO
}
void drawCallback_CROSS(const Common::Rect &area, const DrawStep &step, const Common::Rect &clip) {
@@ -482,8 +492,14 @@ public:
virtual void blitSubSurface(const Graphics::Surface *source, const Common::Rect &r) = 0;
virtual void blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) = 0;
- virtual void blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) = 0;
- virtual void blitAlphaBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) = 0;
+ virtual void blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r) = 0;
+ virtual void blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) = 0;
+
+ virtual void blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r,
+ GUI::ThemeEngine::AutoScaleMode autoscale = GUI::ThemeEngine::kAutoScaleNone,
+ Graphics::DrawStep::VectorAlignment xAlign = Graphics::DrawStep::kVectorAlignManual,
+ Graphics::DrawStep::VectorAlignment yAlign = Graphics::DrawStep::kVectorAlignManual,
+ int alpha = 255) = 0;
/**
* Draws a string into the screen. Wrapper for the Graphics::Font string drawing
@@ -507,7 +523,7 @@ public:
virtual void applyScreenShading(GUI::ThemeEngine::ShadingStyle) = 0;
protected:
- Surface *_activeSurface; /**< Pointer to the surface currently being drawn */
+ TransparentSurface *_activeSurface; /**< Pointer to the surface currently being drawn */
FillMode _fillMode; /**< Defines in which way (if any) are filled the drawn shapes */
ShadowFillMode _shadowFillMode;
diff --git a/graphics/VectorRendererSpec.cpp b/graphics/VectorRendererSpec.cpp
index fc741f6e77..9aed3301fa 100644
--- a/graphics/VectorRendererSpec.cpp
+++ b/graphics/VectorRendererSpec.cpp
@@ -25,6 +25,8 @@
#include "common/frac.h"
#include "graphics/surface.h"
+#include "graphics/transparent_surface.h"
+#include "graphics/nine_patch.h"
#include "graphics/colormasks.h"
#include "gui/ThemeEngine.h"
@@ -848,8 +850,8 @@ blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const
}
template<typename PixelType>
-void VectorRendererSpec<PixelType>::
-blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) {
+void VectorRendererSpec<PixelType>::
+blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r) {
int16 x = r.left;
int16 y = r.top;
@@ -885,9 +887,43 @@ blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r) {
template<typename PixelType>
void VectorRendererSpec<PixelType>::
-blitAlphaBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) {
+blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r, GUI::ThemeEngine::AutoScaleMode autoscale,
+ Graphics::DrawStep::VectorAlignment xAlign, Graphics::DrawStep::VectorAlignment yAlign, int alpha) {
+ if (autoscale == GUI::ThemeEngine::kAutoScaleStretch) {
+ source->blit(*_activeSurface, r.left, r.top, Graphics::FLIP_NONE,
+ nullptr, TS_ARGB(alpha, 255, 255, 255),
+ r.width(), r.height());
+ } else if (autoscale == GUI::ThemeEngine::kAutoScaleFit) {
+ double ratio = (double)r.width() / source->w;
+ double ratio2 = (double)r.height() / source->h;
+
+ if (ratio2 < ratio)
+ ratio = ratio2;
+
+ int offx = 0, offy = 0;
+ if (xAlign == Graphics::DrawStep::kVectorAlignCenter)
+ offx = (r.width() - (int)(source->w * ratio)) >> 1;
+
+ if (yAlign == Graphics::DrawStep::kVectorAlignCenter)
+ offy = (r.height() - (int)(source->h * ratio)) >> 1;
+
+ source->blit(*_activeSurface, r.left + offx, r.top + offy, Graphics::FLIP_NONE,
+ nullptr, TS_ARGB(alpha, 255, 255, 255),
+ (int)(source->w * ratio), (int)(source->h * ratio));
+
+ } else if (autoscale == GUI::ThemeEngine::kAutoScaleNinePatch) {
+ Graphics::NinePatchBitmap nine(source, false);
+ nine.blit(*_activeSurface, r.left, r.top, r.width(), r.height());
+ } else {
+ source->blit(*_activeSurface, r.left, r.top);
+ }
+}
+
+template<typename PixelType>
+void VectorRendererSpec<PixelType>::
+blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping) {
if (clipping.isEmpty() || clipping.contains(r)) {
- blitAlphaBitmap(source, r);
+ blitKeyBitmap(source, r);
return;
}
diff --git a/graphics/VectorRendererSpec.h b/graphics/VectorRendererSpec.h
index bee6d4c425..84c802f6df 100644
--- a/graphics/VectorRendererSpec.h
+++ b/graphics/VectorRendererSpec.h
@@ -93,8 +93,13 @@ public:
void blitSurface(const Graphics::Surface *source, const Common::Rect &r);
void blitSubSurface(const Graphics::Surface *source, const Common::Rect &r);
void blitSubSurfaceClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping);
- void blitAlphaBitmap(const Graphics::Surface *source, const Common::Rect &r);
- void blitAlphaBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping);
+ void blitKeyBitmap(const Graphics::Surface *source, const Common::Rect &r);
+ void blitKeyBitmapClip(const Graphics::Surface *source, const Common::Rect &r, const Common::Rect &clipping);
+ void blitAlphaBitmap(Graphics::TransparentSurface *source, const Common::Rect &r,
+ GUI::ThemeEngine::AutoScaleMode autoscale = GUI::ThemeEngine::kAutoScaleNone,
+ Graphics::DrawStep::VectorAlignment xAlign = Graphics::DrawStep::kVectorAlignManual,
+ Graphics::DrawStep::VectorAlignment yAlign = Graphics::DrawStep::kVectorAlignManual,
+ int alpha = 255);
void applyScreenShading(GUI::ThemeEngine::ShadingStyle shadingStyle);
diff --git a/graphics/primitives.cpp b/graphics/primitives.cpp
index ac1c58b1d8..8663a61606 100644
--- a/graphics/primitives.cpp
+++ b/graphics/primitives.cpp
@@ -108,15 +108,13 @@ void drawThickLine2(int x1, int y1, int x2, int y2, int thick, int color, void (
int dy = abs(y2 - y1);
if (dx == 0) {
- if (y1 > y2)
- SWAP(y1, y2);
- Common::Rect r(x1, y1, x1 + thick - 1, y2);
+ int xn = x1 - thick / 2;
+ Common::Rect r(xn, MIN(y1, y2), xn + thick - 1, MAX(y1, y2));
drawFilledRect(r, color, plotProc, data);
return;
} else if (dy == 0) {
- if (x1 > x2)
- SWAP(x1, x2);
- Common::Rect r(x1, y1, x2, y1 + thick - 1);
+ int yn = y1 - thick / 2;
+ Common::Rect r(MIN(x1, x2), yn, MAX(x1, x2), yn + thick - 1);
drawFilledRect(r, color, plotProc, data);
return;
}
diff --git a/graphics/transparent_surface.cpp b/graphics/transparent_surface.cpp
index c2903d42c3..81b7f3d647 100644
--- a/graphics/transparent_surface.cpp
+++ b/graphics/transparent_surface.cpp
@@ -346,7 +346,7 @@ Common::Rect TransparentSurface::blit(Graphics::Surface &target, int posX, int p
TransparentSurface srcImage(*this, false);
// TODO: Is the data really in the screen format?
if (format.bytesPerPixel != 4) {
- warning("TransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8);
+ warning("TransparentSurface can only blit 32bpp images, but got %d", format.bytesPerPixel * 8);
return retSize;
}
@@ -979,4 +979,82 @@ TransparentSurface *TransparentSurface::scale(uint16 newWidth, uint16 newHeight)
}
+TransparentSurface *TransparentSurface::convertTo(const PixelFormat &dstFormat, const byte *palette) const {
+ assert(pixels);
+
+ TransparentSurface *surface = new TransparentSurface();
+
+ // If the target format is the same, just copy
+ if (format == dstFormat) {
+ surface->copyFrom(*this);
+ return surface;
+ }
+
+ if (format.bytesPerPixel == 0 || format.bytesPerPixel > 4)
+ error("Surface::convertTo(): Can only convert from 1Bpp, 2Bpp, 3Bpp, and 4Bpp");
+
+ if (dstFormat.bytesPerPixel != 2 && dstFormat.bytesPerPixel != 4)
+ error("Surface::convertTo(): Can only convert to 2Bpp and 4Bpp");
+
+ surface->create(w, h, dstFormat);
+
+ if (format.bytesPerPixel == 1) {
+ // Converting from paletted to high color
+ assert(palette);
+
+ for (int y = 0; y < h; y++) {
+ const byte *srcRow = (const byte *)getBasePtr(0, y);
+ byte *dstRow = (byte *)surface->getBasePtr(0, y);
+
+ for (int x = 0; x < w; x++) {
+ byte index = *srcRow++;
+ byte r = palette[index * 3];
+ byte g = palette[index * 3 + 1];
+ byte b = palette[index * 3 + 2];
+
+ uint32 color = dstFormat.RGBToColor(r, g, b);
+
+ if (dstFormat.bytesPerPixel == 2)
+ *((uint16 *)dstRow) = color;
+ else
+ *((uint32 *)dstRow) = color;
+
+ dstRow += dstFormat.bytesPerPixel;
+ }
+ }
+ } else {
+ // Converting from high color to high color
+ for (int y = 0; y < h; y++) {
+ const byte *srcRow = (const byte *)getBasePtr(0, y);
+ byte *dstRow = (byte *)surface->getBasePtr(0, y);
+
+ for (int x = 0; x < w; x++) {
+ uint32 srcColor;
+ if (format.bytesPerPixel == 2)
+ srcColor = READ_UINT16(srcRow);
+ else if (format.bytesPerPixel == 3)
+ srcColor = READ_UINT24(srcRow);
+ else
+ srcColor = READ_UINT32(srcRow);
+
+ srcRow += format.bytesPerPixel;
+
+ // Convert that color to the new format
+ byte r, g, b, a;
+ format.colorToARGB(srcColor, a, r, g, b);
+ uint32 color = dstFormat.ARGBToColor(a, r, g, b);
+
+ if (dstFormat.bytesPerPixel == 2)
+ *((uint16 *)dstRow) = color;
+ else
+ *((uint32 *)dstRow) = color;
+
+ dstRow += dstFormat.bytesPerPixel;
+ }
+ }
+ }
+
+ return surface;
+}
+
} // End of namespace Graphics
diff --git a/graphics/transparent_surface.h b/graphics/transparent_surface.h
index c0d3d26e52..8654183548 100644
--- a/graphics/transparent_surface.h
+++ b/graphics/transparent_surface.h
@@ -151,6 +151,16 @@ struct TransparentSurface : public Graphics::Surface {
*
*/
TransparentSurface *rotoscale(const TransformStruct &transform) const;
+
+ TransparentSurface *convertTo(const PixelFormat &dstFormat, const byte *palette = 0) const;
+
+ float getRatio() {
+ if (!w)
+ return 0;
+
+ return h / (float)w;
+ }
+
AlphaType getAlphaMode() const;
void setAlphaMode(AlphaType);
private:
diff --git a/gui/ThemeEngine.cpp b/gui/ThemeEngine.cpp
index c850a6a02e..96108bccce 100644
--- a/gui/ThemeEngine.cpp
+++ b/gui/ThemeEngine.cpp
@@ -31,11 +31,13 @@
#include "graphics/cursorman.h"
#include "graphics/fontman.h"
#include "graphics/surface.h"
+#include "graphics/transparent_surface.h"
#include "graphics/VectorRenderer.h"
#include "graphics/fonts/bdf.h"
#include "graphics/fonts/ttf.h"
#include "image/bmp.h"
+#include "image/png.h"
#include "gui/widget.h"
#include "gui/ThemeEngine.h"
@@ -59,6 +61,10 @@ const char *const ThemeEngine::kImageStopSmallButton = "stopbtn_small.bmp";
const char *const ThemeEngine::kImageEditSmallButton = "editbtn_small.bmp";
const char *const ThemeEngine::kImageSwitchModeSmallButton = "switchbtn_small.bmp";
const char *const ThemeEngine::kImageFastReplaySmallButton = "fastreplay_small.bmp";
+const char *const ThemeEngine::kImageDropboxLogo = "dropbox.bmp";
+const char *const ThemeEngine::kImageOneDriveLogo = "onedrive.bmp";
+const char *const ThemeEngine::kImageGoogleDriveLogo = "googledrive.bmp";
+const char *const ThemeEngine::kImageBoxLogo = "box.bmp";
struct TextDrawData {
const Graphics::Font *_fontPtr;
@@ -168,11 +174,23 @@ protected:
bool _alpha;
};
+class ThemeItemABitmap : public ThemeItem {
+public:
+ ThemeItemABitmap(ThemeEngine *engine, const Common::Rect &area, Graphics::TransparentSurface *bitmap, ThemeEngine::AutoScaleMode autoscale, int alpha) :
+ ThemeItem(engine, area), _bitmap(bitmap), _autoscale(autoscale), _alpha(alpha) {}
+
+ void drawSelf(bool draw, bool restore);
+
+protected:
+ Graphics::TransparentSurface *_bitmap;
+ ThemeEngine::AutoScaleMode _autoscale;
+ int _alpha;
+};
+
class ThemeItemBitmapClip : public ThemeItem {
public:
ThemeItemBitmapClip(ThemeEngine *engine, const Common::Rect &area, const Common::Rect &clip, const Graphics::Surface *bitmap, bool alpha) :
ThemeItem(engine, area), _bitmap(bitmap), _alpha(alpha), _clip(clip) {}
-
void drawSelf(bool draw, bool restore);
protected:
@@ -308,7 +326,7 @@ void ThemeItemBitmap::drawSelf(bool draw, bool restore) {
if (draw) {
if (_alpha)
- _engine->renderer()->blitAlphaBitmap(_bitmap, _area);
+ _engine->renderer()->blitKeyBitmap(_bitmap, _area);
else
_engine->renderer()->blitSubSurface(_bitmap, _area);
}
@@ -316,13 +334,23 @@ void ThemeItemBitmap::drawSelf(bool draw, bool restore) {
_engine->addDirtyRect(_area);
}
-void ThemeItemBitmapClip::drawSelf(bool draw, bool restore) {
+void ThemeItemABitmap::drawSelf(bool draw, bool restore) {
if (restore)
_engine->restoreBackground(_area);
+ if (draw)
+ _engine->renderer()->blitAlphaBitmap(_bitmap, _area, _autoscale, Graphics::DrawStep::kVectorAlignManual, Graphics::DrawStep::kVectorAlignManual, _alpha);
+
+ _engine->addDirtyRect(_area);
+}
+
+void ThemeItemBitmapClip::drawSelf(bool draw, bool restore) {
+ if (restore)
+ _engine->restoreBackground(_area);
+
if (draw) {
if (_alpha)
- _engine->renderer()->blitAlphaBitmapClip(_bitmap, _area, _clip);
+ _engine->renderer()->blitKeyBitmapClip(_bitmap, _area, _clip);
else
_engine->renderer()->blitSubSurfaceClip(_bitmap, _area, _clip);
}
@@ -401,6 +429,15 @@ ThemeEngine::~ThemeEngine() {
}
_bitmaps.clear();
+ for (AImagesMap::iterator i = _abitmaps.begin(); i != _abitmaps.end(); ++i) {
+ Graphics::TransparentSurface *surf = i->_value;
+ if (surf) {
+ surf->free();
+ delete surf;
+ }
+ }
+ _abitmaps.clear();
+
delete _parser;
delete _themeEval;
delete[] _cursor;
@@ -525,6 +562,15 @@ void ThemeEngine::refresh() {
}
}
_bitmaps.clear();
+
+ for (AImagesMap::iterator i = _abitmaps.begin(); i != _abitmaps.end(); ++i) {
+ Graphics::TransparentSurface *surf = i->_value;
+ if (surf) {
+ surf->free();
+ delete surf;
+ }
+ }
+ _abitmaps.clear();
}
init();
@@ -706,24 +752,51 @@ bool ThemeEngine::addBitmap(const Common::String &filename) {
if (surf)
return true;
- // If not, try to load the bitmap via the BitmapDecoder class.
- Image::BitmapDecoder bitmapDecoder;
const Graphics::Surface *srcSurface = 0;
- Common::ArchiveMemberList members;
- _themeFiles.listMatchingMembers(members, filename);
- for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
- Common::SeekableReadStream *stream = (*i)->createReadStream();
- if (stream) {
- bitmapDecoder.loadStream(*stream);
- srcSurface = bitmapDecoder.getSurface();
- delete stream;
- if (srcSurface)
- break;
+
+ if (filename.hasSuffix(".png")) {
+ // Maybe it is PNG?
+#ifdef USE_PNG
+ Image::PNGDecoder decoder;
+ Common::ArchiveMemberList members;
+ _themeFiles.listMatchingMembers(members, filename);
+ for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
+ Common::SeekableReadStream *stream = (*i)->createReadStream();
+ if (stream) {
+ if (!decoder.loadStream(*stream))
+ error("Error decoding PNG");
+
+ srcSurface = decoder.getSurface();
+ delete stream;
+ if (srcSurface)
+ break;
+ }
}
- }
- if (srcSurface && srcSurface->format.bytesPerPixel != 1)
- surf = srcSurface->convertTo(_overlayFormat);
+ if (srcSurface && srcSurface->format.bytesPerPixel != 1)
+ surf = srcSurface->convertTo(_overlayFormat);
+#else
+ error("No PNG support compiled in");
+#endif
+ } else {
+ // If not, try to load the bitmap via the BitmapDecoder class.
+ Image::BitmapDecoder bitmapDecoder;
+ Common::ArchiveMemberList members;
+ _themeFiles.listMatchingMembers(members, filename);
+ for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
+ Common::SeekableReadStream *stream = (*i)->createReadStream();
+ if (stream) {
+ bitmapDecoder.loadStream(*stream);
+ srcSurface = bitmapDecoder.getSurface();
+ delete stream;
+ if (srcSurface)
+ break;
+ }
+ }
+
+ if (srcSurface && srcSurface->format.bytesPerPixel != 1)
+ surf = srcSurface->convertTo(_overlayFormat);
+ }
// Store the surface into our hashmap (attention, may store NULL entries!)
_bitmaps[filename] = surf;
@@ -731,6 +804,48 @@ bool ThemeEngine::addBitmap(const Common::String &filename) {
return surf != 0;
}
+bool ThemeEngine::addAlphaBitmap(const Common::String &filename) {
+ // Nothing has to be done if the bitmap already has been loaded.
+ Graphics::TransparentSurface *surf = _abitmaps[filename];
+ if (surf)
+ return true;
+
+ const Graphics::TransparentSurface *srcSurface = 0;
+
+ if (filename.hasSuffix(".png")) {
+ // Maybe it is PNG?
+#ifdef USE_PNG
+ Image::PNGDecoder decoder;
+ Common::ArchiveMemberList members;
+ _themeFiles.listMatchingMembers(members, filename);
+ for (Common::ArchiveMemberList::const_iterator i = members.begin(), end = members.end(); i != end; ++i) {
+ Common::SeekableReadStream *stream = (*i)->createReadStream();
+ if (stream) {
+ if (!decoder.loadStream(*stream))
+ error("Error decoding PNG");
+
+ srcSurface = new Graphics::TransparentSurface(*decoder.getSurface(), true);
+ delete stream;
+ if (srcSurface)
+ break;
+ }
+ }
+
+ if (srcSurface && srcSurface->format.bytesPerPixel != 1)
+ surf = srcSurface->convertTo(_overlayFormat);
+#else
+ error("No PNG support compiled in");
+#endif
+ } else {
+ error("Only PNG is supported as alphabitmap");
+ }
+
+ // Store the surface into our hashmap (attention, may store NULL entries!)
+ _abitmaps[filename] = surf;
+
+ return surf != 0;
+}
+
bool ThemeEngine::addDrawData(const Common::String &data, bool cached) {
DrawData id = parseDrawDataId(data);
@@ -977,7 +1092,10 @@ void ThemeEngine::queueDDTextClip(TextData type, TextColor color, const Common::
area.clip(_screen.w, _screen.h);
Common::Rect textArea = drawableTextArea;
if (textArea.isEmpty()) textArea = clippingArea;
- else textArea.clip(clippingArea);
+ else {
+ textArea.clip(clippingArea);
+ if (textArea.isEmpty()) textArea = Common::Rect(0, 0, 1, 1); // one small pixel should be invisible enough
+ }
ThemeItemTextData *q = new ThemeItemTextData(this, _texts[type], _textColors[color], area, textArea, text, alignH, alignV, ellipsis, restoreBg, deltax);
@@ -1004,6 +1122,21 @@ void ThemeEngine::queueBitmap(const Graphics::Surface *bitmap, const Common::Rec
}
}
+void ThemeEngine::queueABitmap(Graphics::TransparentSurface *bitmap, const Common::Rect &r, AutoScaleMode autoscale, int alpha) {
+
+ Common::Rect area = r;
+ area.clip(_screen.w, _screen.h);
+
+ ThemeItemABitmap *q = new ThemeItemABitmap(this, area, bitmap, autoscale, alpha);
+
+ if (_buffering) {
+ _screenQueue.push_back(q);
+ } else {
+ q->drawSelf(true, false);
+ delete q;
+ }
+}
+
void ThemeEngine::queueBitmapClip(const Graphics::Surface *bitmap, const Common::Rect &r, const Common::Rect &clip, bool alpha) {
Common::Rect area = r;
@@ -1291,6 +1424,8 @@ void ThemeEngine::drawDialogBackground(const Common::Rect &r, DialogBackground b
case kDialogBackgroundDefault:
queueDD(kDDDefaultBackground, r);
break;
+ case kDialogBackgroundNone:
+ break;
}
}
@@ -1318,6 +1453,9 @@ void ThemeEngine::drawDialogBackgroundClip(const Common::Rect &r, const Common::
case kDialogBackgroundDefault:
queueDDClip(kDDDefaultBackground, r, clip);
break;
+ case kDialogBackgroundNone:
+ // no op
+ break;
}
}
@@ -1392,6 +1530,13 @@ void ThemeEngine::drawSurface(const Common::Rect &r, const Graphics::Surface &su
queueBitmap(&surface, r, themeTrans);
}
+void ThemeEngine::drawASurface(const Common::Rect &r, Graphics::TransparentSurface &surface, AutoScaleMode autoscale, int alpha) {
+ if (!ready())
+ return;
+
+ queueABitmap(&surface, r, autoscale, alpha);
+}
+
void ThemeEngine::drawSurfaceClip(const Common::Rect &r, const Common::Rect &clip, const Graphics::Surface &surface, WidgetStateInfo state, int alpha, bool themeTrans) {
if (!ready())
return;
diff --git a/gui/ThemeEngine.h b/gui/ThemeEngine.h
index 3c259b4f9d..7506cee95f 100644
--- a/gui/ThemeEngine.h
+++ b/gui/ThemeEngine.h
@@ -32,6 +32,7 @@
#include "common/rect.h"
#include "graphics/surface.h"
+#include "graphics/transparent_surface.h"
#include "graphics/font.h"
#include "graphics/pixelformat.h"
@@ -140,6 +141,7 @@ enum TextColor {
class ThemeEngine {
protected:
typedef Common::HashMap<Common::String, Graphics::Surface *> ImagesMap;
+ typedef Common::HashMap<Common::String, Graphics::TransparentSurface *> AImagesMap;
friend class GUI::Dialog;
friend class GUI::GuiObject;
@@ -169,7 +171,8 @@ public:
kDialogBackgroundSpecial,
kDialogBackgroundPlain,
kDialogBackgroundTooltip,
- kDialogBackgroundDefault
+ kDialogBackgroundDefault,
+ kDialogBackgroundNone
};
/// State of the widget to be drawn
@@ -223,6 +226,14 @@ public:
kShadingLuminance ///< Converting colors to luminance for unused areas
};
+ /// AlphaBitmap scale mode selector
+ enum AutoScaleMode {
+ kAutoScaleNone = 0, ///< Use image dimensions
+ kAutoScaleStretch = 1, ///< Stretch image to full widget size
+ kAutoScaleFit = 2, ///< Scale image to widget size but keep aspect ratio
+ kAutoScaleNinePatch = 3 ///< 9-patch image
+ };
+
// Special image ids for images used in the GUI
static const char *const kImageLogo; ///< ScummVM logo used in the launcher
static const char *const kImageLogoSmall; ///< ScummVM logo used in the GMM
@@ -239,6 +250,10 @@ public:
static const char *const kImageEditSmallButton; ///< Edit recording metadata in recorder onscreen dialog (for 320xY)
static const char *const kImageSwitchModeSmallButton; ///< Switch mode button in recorder onscreen dialog (for 320xY)
static const char *const kImageFastReplaySmallButton; ///< Fast playback mode button in recorder onscreen dialog (for 320xY)
+ static const char *const kImageDropboxLogo; ///< Dropbox logo used in the StorageWizardDialog
+ static const char *const kImageOneDriveLogo; ///< OneDrive logo used in the StorageWizardDialog
+ static const char *const kImageGoogleDriveLogo; ///< Google Drive logo used in the StorageWizardDialog
+ static const char *const kImageBoxLogo; ///< Box logo used in the StorageWizardDialog
/**
* Graphics mode enumeration.
@@ -353,6 +368,8 @@ public:
void drawSurfaceClip(const Common::Rect &r, const Common::Rect &clippingRect, const Graphics::Surface &surface,
WidgetStateInfo state = kStateEnabled, int alpha = 255, bool themeTrans = false);
+ void drawASurface(const Common::Rect &r, Graphics::TransparentSurface &surface, AutoScaleMode autoscale, int alpha);
+
void drawSlider(const Common::Rect &r, int width,
WidgetStateInfo state = kStateEnabled);
void drawSliderClip(const Common::Rect &r, const Common::Rect &clippingRect, int width,
@@ -483,6 +500,14 @@ public:
bool addBitmap(const Common::String &filename);
/**
+ * Interface for the ThemeParser class: Loads a bitmap with transparency file to use on the GUI.
+ * The filename is also used as its identifier.
+ *
+ * @param filename Name of the bitmap file.
+ */
+ bool addAlphaBitmap(const Common::String &filename);
+
+ /**
* Adds a new TextStep from the ThemeParser. This will be deprecated/removed once the
* new Font API is in place. FIXME: Is that so ???
*/
@@ -526,10 +551,18 @@ public:
return _bitmaps.contains(name) ? _bitmaps[name] : 0;
}
+ Graphics::TransparentSurface *getAlphaBitmap(const Common::String &name) {
+ return _abitmaps.contains(name) ? _abitmaps[name] : 0;
+ }
+
const Graphics::Surface *getImageSurface(const Common::String &name) const {
return _bitmaps.contains(name) ? _bitmaps[name] : 0;
}
+ const Graphics::TransparentSurface *getAImageSurface(const Common::String &name) const {
+ return _abitmaps.contains(name) ? _abitmaps[name] : 0;
+ }
+
/**
* Interface for the Theme Parser: Creates a new cursor by loading the given
* bitmap and sets it as the active cursor.
@@ -616,6 +649,7 @@ protected:
bool elipsis, Graphics::TextAlign alignH = Graphics::kTextAlignLeft, TextAlignVertical alignV = kTextAlignVTop, int deltax = 0, const Common::Rect &drawableTextArea = Common::Rect(0, 0, 0, 0));
void queueBitmap(const Graphics::Surface *bitmap, const Common::Rect &r, bool alpha);
void queueBitmapClip(const Graphics::Surface *bitmap, const Common::Rect &clippingRect, const Common::Rect &r, bool alpha);
+ void queueABitmap(Graphics::TransparentSurface *bitmap, const Common::Rect &r, AutoScaleMode autoscale, int alpha);
/**
* DEBUG: Draws a white square and writes some text next to it.
@@ -656,10 +690,10 @@ protected:
GUI::ThemeEval *_themeEval;
/** Main screen surface. This is blitted straight into the overlay. */
- Graphics::Surface _screen;
+ Graphics::TransparentSurface _screen;
/** Backbuffer surface. Stores previous states of the screen to blit back */
- Graphics::Surface _backBuffer;
+ Graphics::TransparentSurface _backBuffer;
/** Sets whether the current drawing is being buffered (stored for later
processing) or drawn directly to the screen. */
@@ -688,6 +722,7 @@ protected:
TextColorData *_textColors[kTextColorMAX];
ImagesMap _bitmaps;
+ AImagesMap _abitmaps;
Graphics::PixelFormat _overlayFormat;
#ifdef USE_RGB_COLOR
Graphics::PixelFormat _cursorFormat;
diff --git a/gui/ThemeParser.cpp b/gui/ThemeParser.cpp
index bd5b406ca8..bd0d2c4898 100644
--- a/gui/ThemeParser.cpp
+++ b/gui/ThemeParser.cpp
@@ -241,6 +241,18 @@ bool ThemeParser::parserCallback_bitmap(ParserNode *node) {
return true;
}
+bool ThemeParser::parserCallback_alphabitmap(ParserNode *node) {
+ if (resolutionCheck(node->values["resolution"]) == false) {
+ node->ignore = true;
+ return true;
+ }
+
+ if (!_theme->addAlphaBitmap(node->values["filename"]))
+ return parserError("Error loading Bitmap file '" + node->values["filename"] + "'");
+
+ return true;
+}
+
bool ThemeParser::parserCallback_text(ParserNode *node) {
Graphics::TextAlign alignH;
GUI::ThemeEngine::TextAlignVertical alignV;
@@ -323,6 +335,8 @@ static Graphics::DrawingFunctionCallback getDrawingFunctionCallback(const Common
return &Graphics::VectorRenderer::drawCallback_BITMAP;
if (name == "cross")
return &Graphics::VectorRenderer::drawCallback_CROSS;
+ if (name == "alphabitmap")
+ return &Graphics::VectorRenderer::drawCallback_ALPHABITMAP;
return 0;
}
@@ -448,6 +462,58 @@ bool ThemeParser::parseDrawStep(ParserNode *stepNode, Graphics::DrawStep *drawst
return parserError("The given filename hasn't been loaded into the GUI.");
}
+ if (functionName == "alphabitmap") {
+ if (!stepNode->values.contains("file"))
+ return parserError("Need to specify a filename for AlphaBitmap blitting.");
+
+ drawstep->blitAlphaSrc = _theme->getAlphaBitmap(stepNode->values["file"]);
+
+ if (!drawstep->blitAlphaSrc)
+ return parserError("The given filename hasn't been loaded into the GUI.");
+
+ if (stepNode->values.contains("autoscale")) {
+ if (stepNode->values["autoscale"] == "true" || stepNode->values["autoscale"] == "stretch") {
+ drawstep->autoscale = ThemeEngine::kAutoScaleStretch;
+ } else if (stepNode->values["autoscale"] == "fit") {
+ drawstep->autoscale = ThemeEngine::kAutoScaleFit;
+ } else if (stepNode->values["autoscale"] == "9patch") {
+ drawstep->autoscale = ThemeEngine::kAutoScaleNinePatch;
+ } else {
+ drawstep->autoscale = ThemeEngine::kAutoScaleNone;
+ }
+ }
+
+ if (stepNode->values.contains("xpos")) {
+ val = stepNode->values["xpos"];
+
+ if (parseIntegerKey(val, 1, &x))
+ drawstep->x = x;
+ else if (val == "center")
+ drawstep->xAlign = Graphics::DrawStep::kVectorAlignCenter;
+ else if (val == "left")
+ drawstep->xAlign = Graphics::DrawStep::kVectorAlignLeft;
+ else if (val == "right")
+ drawstep->xAlign = Graphics::DrawStep::kVectorAlignRight;
+ else
+ return parserError("Invalid value for X Position");
+ }
+
+ if (stepNode->values.contains("ypos")) {
+ val = stepNode->values["ypos"];
+
+ if (parseIntegerKey(val, 1, &x))
+ drawstep->y = x;
+ else if (val == "center")
+ drawstep->yAlign = Graphics::DrawStep::kVectorAlignCenter;
+ else if (val == "top")
+ drawstep->yAlign = Graphics::DrawStep::kVectorAlignTop;
+ else if (val == "bottom")
+ drawstep->yAlign = Graphics::DrawStep::kVectorAlignBottom;
+ else
+ return parserError("Invalid value for Y Position");
+ }
+ }
+
if (functionName == "roundedsq" || functionName == "circle" || functionName == "tab") {
if (stepNode->values.contains("radius") && stepNode->values["radius"] == "auto") {
drawstep->radius = 0xFF;
diff --git a/gui/ThemeParser.h b/gui/ThemeParser.h
index 360e3da009..155731467f 100644
--- a/gui/ThemeParser.h
+++ b/gui/ThemeParser.h
@@ -80,6 +80,10 @@ protected:
XML_PROP(filename, true)
XML_PROP(resolution, false)
KEY_END()
+ XML_KEY(alphabitmap)
+ XML_PROP(filename, true)
+ XML_PROP(resolution, false)
+ KEY_END()
KEY_END()
XML_KEY(cursor)
@@ -142,6 +146,7 @@ protected:
XML_PROP(padding, false)
XML_PROP(orientation, false)
XML_PROP(file, false)
+ XML_PROP(autoscale, false)
KEY_END()
XML_KEY(text)
@@ -224,6 +229,7 @@ protected:
bool parserCallback_drawdata(ParserNode *node);
bool parserCallback_bitmaps(ParserNode *node) { return true; }
bool parserCallback_bitmap(ParserNode *node);
+ bool parserCallback_alphabitmap(ParserNode *node);
bool parserCallback_cursor(ParserNode *node);
diff --git a/engines/titanic/gfx/chev_switch.h b/gui/animation/AccelerateInterpolator.h
index 0305a6ca83..31494d369f 100644
--- a/engines/titanic/gfx/chev_switch.h
+++ b/gui/animation/AccelerateInterpolator.h
@@ -20,29 +20,26 @@
*
*/
-#ifndef TITANIC_CHEV_SWITCH_H
-#define TITANIC_CHEV_SWITCH_H
+// Based on code by omergilad.
-#include "titanic/gfx/toggle_switch.h"
+#ifndef GUI_ANIMATION_ACCELERATEINTERPOLATOR_H
+#define GUI_ANIMATION_ACCELERATEINTERPOLATOR_H
-namespace Titanic {
+#include "gui/animation/Interpolator.h"
-class CChevSwitch : public CToggleSwitch {
+namespace GUI {
+
+class AccelerateInterpolator: public Interpolator {
public:
- CLASSDEF;
- CChevSwitch();
-
- /**
- * Save the data for the class to file
- */
- virtual void save(SimpleFile *file, int indent);
-
- /**
- * Load the data for the class from file
- */
- virtual void load(SimpleFile *file);
+ AccelerateInterpolator() {}
+ virtual ~AccelerateInterpolator() {}
+
+ virtual float interpolate(float linearValue) {
+ return pow(linearValue, 2);
+ }
+
};
-} // End of namespace Titanic
+} // End of namespace GUI
-#endif /* TITANIC_CHEV_SWITCH_H */
+#endif /* GUI_ANIMATION_ACCELERATEINTERPOLATOR_H */
diff --git a/gui/animation/AlphaAnimation.h b/gui/animation/AlphaAnimation.h
new file mode 100644
index 0000000000..82cf3d4ba9
--- /dev/null
+++ b/gui/animation/AlphaAnimation.h
@@ -0,0 +1,53 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_ANIMATION_H
+#define GUI_ANIMATION_ANIMATION_H
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class AlphaAnimation: public Animation {
+public:
+ AlphaAnimation() {}
+ virtual ~AlphaAnimation() {}
+ float getEndAlpha() const { return _endAlpha; }
+ void setEndAlpha(float endAlpha) { _endAlpha = endAlpha; }
+ float getStartAlpha() const { return _startAlpha; }
+ void setStartAlpha(float startAlpha) { _startAlpha = startAlpha; }
+
+protected:
+ virtual void updateInternal(Drawable* drawable, float interpolation) {
+ // Calculate alpha value based on properties and interpolation
+ drawable->setAlpha(_startAlpha * (1 - interpolation) + _endAlpha * interpolation);
+ }
+
+ float _startAlpha;
+ float _endAlpha;
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_ANIMATION_H */
diff --git a/gui/animation/Animation.cpp b/gui/animation/Animation.cpp
new file mode 100644
index 0000000000..ee4d900b4d
--- /dev/null
+++ b/gui/animation/Animation.cpp
@@ -0,0 +1,98 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+Animation::Animation()
+ : _startTime(0), _duration(0), _finished(false), _finishOnEnd(true) {
+}
+
+Animation::~Animation() {
+}
+
+void Animation::start(long currentTime) {
+ _finished = false;
+ _startTime = currentTime;
+}
+
+void Animation::setDuration(long duration) {
+ _duration = duration;
+}
+
+void Animation::update(Drawable *drawable, long currentTime) {
+ float interpolation;
+
+ if (currentTime < _startTime) {
+ // If the start time is in the future, nothing changes - the interpolated value is 0
+ interpolation = 0;
+ } else if (currentTime > _startTime + _duration) {
+ // If the animation is finished, the interpolated value is 1 and the animation is marked as finished
+ interpolation = 1;
+ finishAnimation();
+ } else {
+ // Calculate the interpolated value
+ interpolation = (currentTime - _startTime) / (float) (_duration);
+ }
+
+ // Activate the interpolator if present
+ if (_interpolator.get() != NULL) {
+ interpolation = _interpolator->interpolate(interpolation);
+ }
+
+ updateInternal(drawable, interpolation);
+}
+
+void Animation::finishAnimation() {
+ if (_finishOnEnd) {
+ _finished = true;
+ }
+}
+
+void Animation::updateInternal(Drawable *drawable, float interpolation) {
+ // Default implementation
+}
+
+bool Animation::isFinished() const {
+ return _finished;
+}
+
+bool Animation::isFinishOnEnd() const {
+ return _finishOnEnd;
+}
+
+void Animation::setFinishOnEnd(bool finishOnEnd) {
+ _finishOnEnd = finishOnEnd;
+}
+
+InterpolatorPtr Animation::getInterpolator() const {
+ return _interpolator;
+}
+
+void Animation::setInterpolator(InterpolatorPtr interpolator) {
+ _interpolator = interpolator;
+}
+
+} // End of namespace GUI
diff --git a/gui/animation/Animation.h b/gui/animation/Animation.h
new file mode 100644
index 0000000000..300720b419
--- /dev/null
+++ b/gui/animation/Animation.h
@@ -0,0 +1,76 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_ANIMATION_H
+#define GUI_ANIMATION_ANIMATION_H
+
+#include "gui/animation/Interpolator.h"
+
+namespace GUI {
+
+class Drawable;
+
+class Animation {
+public:
+ Animation();
+ virtual ~Animation() = 0;
+
+ virtual void update(Drawable *drawable, long currentTime);
+
+ /**
+ * Set start time in millis
+ */
+ virtual void start(long currentTime);
+
+ /**
+ * Set duration in millis
+ */
+ virtual void setDuration(long duration);
+
+ virtual bool isFinished() const;
+
+ bool isFinishOnEnd() const;
+
+ void setFinishOnEnd(bool finishOnEnd);
+
+ InterpolatorPtr getInterpolator() const;
+ void setInterpolator(InterpolatorPtr interpolator);
+
+protected:
+ void finishAnimation();
+
+ virtual void updateInternal(Drawable *drawable, float interpolation);
+
+ long _startTime;
+ long _duration;
+ bool _finished;
+ bool _finishOnEnd;
+ InterpolatorPtr _interpolator;
+};
+
+typedef Common::SharedPtr<Animation> AnimationPtr;
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_ANIMATION_H */
diff --git a/gui/animation/DeccelerateInterpolator.h b/gui/animation/DeccelerateInterpolator.h
new file mode 100644
index 0000000000..e25ff6a4ba
--- /dev/null
+++ b/gui/animation/DeccelerateInterpolator.h
@@ -0,0 +1,41 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_DECCELERATEINTERPOLATOR_H
+#define GUI_ANIMATION_DECCELERATEINTERPOLATOR_H
+
+#include "gui/animation/Interpolator.h"
+
+namespace GUI {
+
+class DeccelerateInterpolator: public Interpolator {
+public:
+ DeccelerateInterpolator() {}
+ virtual ~DeccelerateInterpolator() {}
+ virtual float interpolate(float linearValue) { return sqrt(linearValue); }
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_DECCELERATEINTERPOLATOR_H */
diff --git a/gui/animation/Drawable.h b/gui/animation/Drawable.h
new file mode 100644
index 0000000000..cf604270aa
--- /dev/null
+++ b/gui/animation/Drawable.h
@@ -0,0 +1,109 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_DRAWABLE_H
+#define GUI_ANIMATION_DRAWABLE_H
+
+#include "common/ptr.h"
+#include "graphics/transparent_surface.h"
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class Animation;
+typedef Common::SharedPtr<Animation> AnimationPtr;
+
+class Drawable {
+public:
+ Drawable() :
+ _bitmap(NULL), _positionX(0), _positionY(0), _width(0), _height(0), _alpha(1),
+ _usingSnapshot(false), _shouldCenter(false) {
+ _displayRatio = 1.0;
+ }
+
+ virtual ~Drawable() {
+ if (_usingSnapshot)
+ delete _bitmap;
+ }
+
+ void updateAnimation(long currentTime) {
+ if (_animation.get() != NULL) {
+ _animation->update(this, currentTime);
+ }
+ }
+
+ bool isAnimationFinished() {
+ if (_animation.get() != NULL)
+ return _animation->isFinished();
+
+ return false;
+ }
+
+ float getAlpha() const { return _alpha; }
+ void setAlpha(float alpha) { _alpha = alpha; }
+ AnimationPtr getAnimation() const { return _animation; }
+ void setAnimation(AnimationPtr animation) { _animation = animation; }
+ Graphics::TransparentSurface *getBitmap() const { return _bitmap; }
+ void setBitmap(Graphics::TransparentSurface *bitmap) { _bitmap = bitmap; }
+ float getPositionX() const { return _positionX; }
+ void setPositionX(float positionX) { _positionX = positionX; }
+ float getPositionY() const { return _positionY; }
+ void setPositionY(float positionY) { _positionY = positionY; }
+ virtual float getWidth() const { return _width; }
+ void setWidth(float width) { _width = width; }
+
+ virtual float getHeight() const {
+ if (_height == 0)
+ return getWidth() * _bitmap->getRatio() * _displayRatio;
+
+ return _height;
+ }
+
+ void setHeight(float height) { _height = height; }
+ void setDisplayRatio(float ratio) { _displayRatio = ratio; }
+ inline bool shouldCenter() const { return _shouldCenter; }
+ void setShouldCenter(bool shouldCenter) { _shouldCenter = shouldCenter; }
+
+protected:
+ bool _usingSnapshot;
+
+private:
+ Graphics::TransparentSurface *_bitmap;
+ float _positionX;
+ float _positionY;
+ float _width;
+ float _height;
+ float _alpha;
+ bool _shouldCenter;
+ AnimationPtr _animation;
+
+ float _displayRatio;
+};
+
+typedef Common::SharedPtr<Drawable> DrawablePtr;
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_DRAWABLE_H */
diff --git a/gui/animation/Interpolator.h b/gui/animation/Interpolator.h
new file mode 100644
index 0000000000..72d7acb8d4
--- /dev/null
+++ b/gui/animation/Interpolator.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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_INTERPOLATOR_H
+#define GUI_ANIMATION_INTERPOLATOR_H
+
+#include "common/ptr.h"
+
+namespace GUI {
+
+class Interpolator {
+public:
+ Interpolator() {}
+ virtual ~Interpolator() {}
+
+ virtual float interpolate(float linearValue) = 0;
+};
+
+typedef Common::SharedPtr<Interpolator> InterpolatorPtr;
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_INTERPOLATOR_H */
diff --git a/gui/animation/ParallelAnimation.h b/gui/animation/ParallelAnimation.h
new file mode 100644
index 0000000000..ce1f599fb1
--- /dev/null
+++ b/gui/animation/ParallelAnimation.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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_PARALLELANIMATION_H
+#define GUI_ANIMATION_PARALLELANIMATION_H
+
+#include "gui/animation/Animation.h"
+#include "common/array.h"
+
+namespace GUI {
+
+class ParallelAnimation: public Animation {
+public:
+ ParallelAnimation() {}
+ virtual ~ParallelAnimation() {}
+
+ virtual void addAnimation(AnimationPtr animation) {
+ _animations.push_back(animation);
+ }
+
+ virtual void update(Drawable *drawable, long currentTime) {
+ for (AnimationPtr anim : _animations) {
+ anim->update(drawable, currentTime);
+ if (anim->isFinished()) {
+ finishAnimation();
+ }
+ }
+ }
+
+ virtual void start(long currentTime) {
+ Animation::start(currentTime);
+
+ for (AnimationPtr anim : _animations)
+ anim->start(currentTime);
+ }
+
+ virtual void setDuration(long duration) {
+ Animation::setDuration(duration);
+
+ for (AnimationPtr anim : _animations)
+ anim->setDuration(duration);
+ }
+
+private:
+
+ Common::Array<AnimationPtr> _animations;
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_PARALLELANIMATION_H */
diff --git a/gui/animation/RepeatAnimationWrapper.cpp b/gui/animation/RepeatAnimationWrapper.cpp
new file mode 100644
index 0000000000..a7e1413093
--- /dev/null
+++ b/gui/animation/RepeatAnimationWrapper.cpp
@@ -0,0 +1,52 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#include "gui/animation/RepeatAnimationWrapper.h"
+
+namespace GUI {
+
+void RepeatAnimationWrapper::update(Drawable* drawable, long currentTime) {
+ // Update wrapped animation
+ _animation->update(drawable, currentTime);
+
+ // If the animation is finished, increase the repeat count and restart it if needed
+ if (_animation->isFinished()) {
+ ++_repeatCount;
+ if (_timesToRepeat > 0 && _repeatCount >= _timesToRepeat) {
+ finishAnimation();
+ } else {
+ _animation->start(currentTime);
+ }
+ }
+}
+
+void RepeatAnimationWrapper::start(long currentTime) {
+ Animation::start(currentTime);
+ _repeatCount = 0;
+
+ // Start wrapped animation
+ _animation->start(currentTime);
+}
+
+} // End of namespace GUI
diff --git a/engines/titanic/sound/music_handler.h b/gui/animation/RepeatAnimationWrapper.h
index cab2ef8074..3d766dd1c5 100644
--- a/engines/titanic/sound/music_handler.h
+++ b/gui/animation/RepeatAnimationWrapper.h
@@ -20,37 +20,42 @@
*
*/
-#ifndef TITANIC_MUSIC_HANDLER_H
-#define TITANIC_MUSIC_HANDLER_H
+// Based on code by omergilad.
-#include "titanic/sound/music_wave.h"
+#ifndef GUI_ANIMATION_REPEATANIMATIONWRAPPER_H
+#define GUI_ANIMATION_REPEATANIMATIONWRAPPER_H
-namespace Titanic {
+#include "gui/animation/Animation.h"
-class CProjectItem;
-class CSoundManager;
+namespace GUI {
-class CMusicHandler {
-private:
- CProjectItem *_project;
- CSoundManager *_soundManager;
- int _field124;
+class RepeatAnimationWrapper: public Animation {
public:
- CMusicHandler(CProjectItem *project, CSoundManager *soundManager);
+ /**
+ * Animation - animation to repeat
+ *
+ * timesToRepeat - 0 means infinite
+ */
+ RepeatAnimationWrapper(AnimationPtr animation, uint16 timesToRepeat) :
+ _animation(animation), _timesToRepeat(timesToRepeat) {}
+
+ virtual ~RepeatAnimationWrapper() {}
+
+ virtual void update(Drawable* drawable, long currentTime);
/**
- * Creates a new music wave class instance, and assigns it to a slot
- * in the music handler
- * @param waveIndex Slot to save new instance in
- * @param count Number of files the new instance will contain
+ * Set start time in millis
*/
- CMusicWave *createMusicWave(int waveIndex, int count);
+ virtual void start(long currentTime);
+
+private:
+ uint16 _timesToRepeat;
+ uint16 _repeatCount;
- bool isBusy();
+ AnimationPtr _animation;
- void set124(int val) { _field124 = val; }
};
-} // End of namespace Titanic
+} // End of namespace GUI
-#endif /* TITANIC_MUSIC_HANDLER_H */
+#endif /* GUI_ANIMATION_REPEATANIMATIONWRAPPER_H */
diff --git a/gui/animation/ScaleAnimation.h b/gui/animation/ScaleAnimation.h
new file mode 100644
index 0000000000..80a4ae6305
--- /dev/null
+++ b/gui/animation/ScaleAnimation.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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_SCALEANIMATION_H
+#define GUI_ANIMATION_SCALEANIMATION_H
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class ScaleAnimation: public Animation {
+public:
+ ScaleAnimation() : _endWidth(0), _endWidthFactor(0) {}
+
+ virtual ~ScaleAnimation() {}
+
+ float getEndWidth() const { return _endWidth; }
+ void setEndWidth(float endWidth) { _endWidth = endWidth; }
+ float getEndWidthFactor() const { return _endWidthFactor; }
+ void setEndWidthFactor(float endWidthFactor) { _endWidthFactor = endWidthFactor; }
+ float getStartWidth() const { return _startWidth; }
+ void setStartWidth(float startWidth) { _startWidth = startWidth; }
+
+ void updateInternal(Drawable *drawable, float interpolation) {
+ // If start width was set as 0 -> use the current width as the start dimension
+ if (_startWidth == 0)
+ _startWidth = drawable->getWidth();
+
+ // If end width was set as 0 - multiply the start width by the given factor
+ if (_endWidth == 0)
+ _endWidth = _startWidth * _endWidthFactor;
+
+ // Calculate width based on interpolation
+ float width = _startWidth * (1 - interpolation) + _endWidth * interpolation;
+ drawable->setWidth(width);
+ }
+
+private:
+ virtual void updateInternal(Drawable *drawable, float interpolation);
+ float _startWidth;
+ float _endWidth;
+ float _endWidthFactor;
+};
+
+} // End of namespace GUI
+
+
+#endif /* GUI_ANIMATION_SCALEANIMATION_H */
diff --git a/gui/animation/SequenceAnimationComposite.cpp b/gui/animation/SequenceAnimationComposite.cpp
new file mode 100644
index 0000000000..9ecfeebc8c
--- /dev/null
+++ b/gui/animation/SequenceAnimationComposite.cpp
@@ -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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#include "gui/animation/SequenceAnimationComposite.h"
+
+namespace GUI {
+
+void SequenceAnimationComposite::start(long currentTime) {
+ Animation::start(currentTime);
+
+ // The first animation in the sequence should a start time equal to this sequence
+ if (_sequence.size() >= 1)
+ _sequence[0]->start(currentTime);
+
+ // Set the index to 0
+ _index = 0;
+}
+
+void SequenceAnimationComposite::addAnimation(AnimationPtr animation) {
+ _sequence.push_back(animation);
+}
+
+void SequenceAnimationComposite::update(Drawable *drawable, long currentTime) {
+ uint16 sequenceSize = _sequence.size();
+
+ // Check index bounds
+ if (_index >= sequenceSize)
+ return;
+
+ // Get the current animation in the sequence
+ AnimationPtr anim = _sequence[_index];
+
+ // Update the drawable
+ anim->update(drawable, currentTime);
+
+ // Check if the current animation is finished
+ if (anim->isFinished()) {
+ // Increase the index - move to the next animation
+ ++_index;
+
+ if (_index >= sequenceSize) {
+ // Finished the sequence
+ finishAnimation();
+ } else {
+ // Set the start time for the next animation
+ _sequence[_index]->start(currentTime);
+ }
+ }
+}
+
+} // End of namespace GUI
diff --git a/gui/animation/SequenceAnimationComposite.h b/gui/animation/SequenceAnimationComposite.h
new file mode 100644
index 0000000000..4ec0331751
--- /dev/null
+++ b/gui/animation/SequenceAnimationComposite.h
@@ -0,0 +1,51 @@
+/* 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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_SEQUENCEANIMATION_H
+#define GUI_ANIMATION_SEQUENCEANIMATION_H
+
+#include "gui/animation/Animation.h"
+#include "common/array.h"
+
+namespace GUI {
+
+class SequenceAnimationComposite: public Animation {
+public:
+ SequenceAnimationComposite() {}
+ virtual ~SequenceAnimationComposite() {}
+
+ virtual void addAnimation(AnimationPtr animation);
+
+ virtual void update(Drawable* drawable, long currentTime);
+
+ virtual void start(long currentTime);
+
+private:
+ uint16 _index;
+ Common::Array<AnimationPtr> _sequence;
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_SEQUENCEANIMATION_H */
diff --git a/gui/animation/WaitForConditionAnimation.h b/gui/animation/WaitForConditionAnimation.h
new file mode 100644
index 0000000000..5a67a37493
--- /dev/null
+++ b/gui/animation/WaitForConditionAnimation.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.
+ *
+ */
+
+// Based on code by omergilad.
+
+#ifndef GUI_ANIMATION_WAITFORCONDITIONANIMATION_H
+#define GUI_ANIMATION_WAITFORCONDITIONANIMATION_H
+
+#include "gui/animation/Animation.h"
+
+namespace GUI {
+
+class Condition {
+
+public:
+ virtual ~Condition() {}
+
+ virtual bool evaluate() = 0;
+};
+
+typedef Common::SharedPtr<Condition> ConditionPtr;
+
+/**
+ * Used for delaying the animation sequence until a certain condition has been met
+ */
+class WaitForConditionAnimation: public Animation {
+public:
+ WaitForConditionAnimation() {}
+ virtual ~WaitForConditionAnimation() {}
+
+ virtual void update(Drawable *drawable, long currentTime) {
+ // Check the condition - if it has been met, finish.
+ if (_condition.get() != NULL && _condition->evaluate()) {
+ finishAnimation();
+ }
+ }
+
+ ConditionPtr getCondition() const {
+ return _condition;
+ }
+
+ void setCondition(ConditionPtr condition) {
+ _condition = condition;
+ }
+
+private:
+ ConditionPtr _condition;
+};
+
+} // End of namespace GUI
+
+#endif /* GUI_ANIMATION_WAITFORCONDITIONANIMATION_H */
diff --git a/gui/browser.cpp b/gui/browser.cpp
index 83e240a5bc..19fa791cee 100644
--- a/gui/browser.cpp
+++ b/gui/browser.cpp
@@ -49,7 +49,7 @@ BrowserDialog::BrowserDialog(const char *title, bool dirBrowser)
_isDirBrowser = dirBrowser;
_fileList = NULL;
_currentPath = NULL;
- _showHidden = ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain);
+ _showHidden = false;
// Headline - TODO: should be customizable during creation time
new StaticTextWidget(this, "Browser.Headline", title);
@@ -85,8 +85,10 @@ void BrowserDialog::open() {
if (!_node.isDirectory())
_node = Common::FSNode(".");
- // Alway refresh file list
- updateListing();
+ _showHidden = ConfMan.getBool("gui_browser_show_hidden", Common::ConfigManager::kApplicationDomain);
+ _showHiddenWidget->setState(_showHidden);
+
+ // At this point the file list has already been refreshed by the kHiddenCmd handler
}
void BrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
diff --git a/gui/credits.h b/gui/credits.h
index c2c4c84ec6..720f917182 100644
--- a/gui/credits.h
+++ b/gui/credits.h
@@ -845,7 +845,7 @@ static const char *credits[] = {
"C2""For the original Reinherit (SAGA) code",
"C0""Sander Buskens",
"C2""For his work on the initial reversing of Monkey2",
-"C0""Canadacow",
+"C0""Dean Beeler",
"C2""For the original MT-32 emulator",
"C0""Kevin Carnes",
"C2""For Scumm16, the basis of ScummVM's older gfx codecs",
@@ -863,18 +863,18 @@ static const char *credits[] = {
"C2""For contributing some GUI icons",
"C0""Till Kresslein",
"C2""For design of modern ScummVM GUI",
-"C0""Jezar",
+"C0""Jezar Wakefield",
"C2""For his freeverb filter implementation",
"C0""Jim Leiterman",
"C2""Various info on his FM-TOWNS/Marty SCUMM ports",
-"C0""lloyd",
+"C0""Lloyd Rosen",
"C2""For deep tech details about C64 Zak & MM",
"C0""Sarien Team",
"C2""Original AGI engine code",
"A0""Jimmi Thogersen",
"C0""Jimmi Th\370gersen",
"C2""For ScummRev, and much obscure code/documentation",
-"C0""Tristan",
+"C0""Tristan Matthews",
"C2""For additional work on the original MT-32 emulator",
"C0""James Woodcock",
"C2""Soundtrack enhancements",
diff --git a/gui/debugger.cpp b/gui/debugger.cpp
index 72d05e2973..7595efcfbc 100644
--- a/gui/debugger.cpp
+++ b/gui/debugger.cpp
@@ -584,7 +584,7 @@ bool Debugger::cmdMd5(int argc, const char **argv) {
length = atoi(argv[2]);
paramOffset = 2;
}
-
+
// Assume that spaces are part of a single filename.
Common::String filename = argv[1 + paramOffset];
for (int i = 2 + paramOffset; i < argc; i++) {
@@ -624,7 +624,7 @@ bool Debugger::cmdMd5Mac(int argc, const char **argv) {
length = atoi(argv[2]);
paramOffset = 2;
}
-
+
// Assume that spaces are part of a single filename.
Common::String filename = argv[1 + paramOffset];
for (int i = 2 + paramOffset; i < argc; i++) {
diff --git a/gui/downloaddialog.cpp b/gui/downloaddialog.cpp
new file mode 100644
index 0000000000..ea7ace2f2a
--- /dev/null
+++ b/gui/downloaddialog.cpp
@@ -0,0 +1,272 @@
+/* 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 "gui/downloaddialog.h"
+#include "backends/cloud/cloudmanager.h"
+#include "backends/networking/connection/islimited.h"
+#include "common/config-manager.h"
+#include "common/translation.h"
+#include "engines/metaengine.h"
+#include "gui/browser.h"
+#include "gui/chooser.h"
+#include "gui/editgamedialog.h"
+#include "gui/launcher.h"
+#include "gui/message.h"
+#include "gui/remotebrowser.h"
+#include "gui/widgets/edittext.h"
+#include "gui/widgets/list.h"
+
+namespace GUI {
+
+enum {
+ kDownloadDialogButtonCmd = 'Dldb'
+};
+
+DownloadDialog::DownloadDialog(uint32 storageId, LauncherDialog *launcher) :
+ Dialog("GlobalOptions_Cloud_DownloadDialog"), _launcher(launcher), _close(false) {
+ _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
+
+ _browser = new BrowserDialog(_("Select directory where to download game data"), true);
+ _remoteBrowser = new RemoteBrowserDialog(_("Select directory with game data"));
+
+ _remoteDirectoryLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.RemoteDirectory", _("From: "));
+ _localDirectoryLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.LocalDirectory", _("To: "));
+ uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress());
+ _progressBar = new SliderWidget(this, "GlobalOptions_Cloud_DownloadDialog.ProgressBar");
+ _progressBar->setMinValue(0);
+ _progressBar->setMaxValue(100);
+ _progressBar->setValue(progress);
+ _progressBar->setEnabled(false);
+ _percentLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.PercentText", Common::String::format("%u %%", progress));
+ _downloadSizeLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.DownloadSize", "");
+ _downloadSpeedLabel = new StaticTextWidget(this, "GlobalOptions_Cloud_DownloadDialog.DownloadSpeed", "");
+ if (g_system->getOverlayWidth() > 320)
+ _cancelButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.MainButton", _("Cancel download"), 0, kDownloadDialogButtonCmd);
+ else
+ _cancelButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.MainButton", _c("Cancel download", "lowres"), 0, kDownloadDialogButtonCmd);
+
+ _closeButton = new ButtonWidget(this, "GlobalOptions_Cloud_DownloadDialog.CloseButton", _("Hide"), 0, kCloseCmd);
+ refreshWidgets();
+
+ CloudMan.setDownloadTarget(this);
+}
+
+DownloadDialog::~DownloadDialog() {
+ CloudMan.setDownloadTarget(nullptr);
+}
+
+void DownloadDialog::open() {
+ Dialog::open();
+ CloudMan.setDownloadTarget(this);
+ if (!CloudMan.isDownloading())
+ if (!selectDirectories())
+ close();
+ reflowLayout();
+ draw();
+}
+
+void DownloadDialog::close() {
+ CloudMan.setDownloadTarget(nullptr);
+ Dialog::close();
+}
+
+void DownloadDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kDownloadDialogButtonCmd:
+ {
+ CloudMan.setDownloadTarget(nullptr);
+ CloudMan.cancelDownload();
+ close();
+ break;
+ }
+ case kDownloadProgressCmd:
+ if (!_close) {
+ refreshWidgets();
+ draw();
+ }
+ break;
+ case kDownloadEndedCmd:
+ _close = true;
+ break;
+ default:
+ Dialog::handleCommand(sender, cmd, data);
+ }
+}
+
+bool DownloadDialog::selectDirectories() {
+ if (Networking::Connection::isLimited()) {
+ MessageDialog alert(_("It looks like your connection is limited. "
+ "Do you really want to download files with it?"), _("Yes"), _("No"));
+ if (alert.runModal() != GUI::kMessageOK)
+ return false;
+ }
+
+ //first user should select remote directory to download
+ if (_remoteBrowser->runModal() <= 0)
+ return false;
+
+ Cloud::StorageFile remoteDirectory = _remoteBrowser->getResult();
+
+ //now user should select local directory to download into
+ if (_browser->runModal() <= 0)
+ return false;
+
+ Common::FSNode dir(_browser->getResult());
+ Common::FSList files;
+ if (!dir.getChildren(files, Common::FSNode::kListAll)) {
+ MessageDialog alert(_("ScummVM couldn't open the specified directory!"));
+ alert.runModal();
+ return false;
+ }
+
+ //check that there is no file with the remote directory's name in the local one
+ for (Common::FSList::iterator i = files.begin(); i != files.end(); ++i) {
+ if (i->getName().equalsIgnoreCase(remoteDirectory.name())) {
+ //if there is, ask user whether it's OK
+ if (!i->isDirectory()) {
+ GUI::MessageDialog alert(_("Cannot create a directory to download - the specified directory has a file with the same name."), _("OK"));
+ alert.runModal();
+ return false;
+ }
+ GUI::MessageDialog alert(
+ Common::String::format(_("The \"%s\" already exists in the specified directory.\nDo you really want to download files into that directory?"), remoteDirectory.name().c_str()),
+ _("Yes"),
+ _("No")
+ );
+ if (alert.runModal() != GUI::kMessageOK)
+ return false;
+ break;
+ }
+ }
+
+ //make a local path
+ Common::String localPath = dir.getPath();
+
+ //simple heuristic to determine which path separator to use
+ if (localPath.size() && localPath.lastChar() != '/' && localPath.lastChar() != '\\') {
+ int backslashes = 0;
+ for (uint32 i = 0; i < localPath.size(); ++i)
+ if (localPath[i] == '/')
+ --backslashes;
+ else if (localPath[i] == '\\')
+ ++backslashes;
+
+ if (backslashes > 0)
+ localPath += '\\' + remoteDirectory.name();
+ else
+ localPath += '/' + remoteDirectory.name();
+ } else {
+ localPath += remoteDirectory.name();
+ }
+
+ CloudMan.startDownload(remoteDirectory.path(), localPath);
+ CloudMan.setDownloadTarget(this);
+ _localDirectory = localPath;
+ return true;
+}
+
+void DownloadDialog::handleTickle() {
+ if (_close) {
+ if (_launcher)
+ _launcher->doGameDetection(_localDirectory);
+ close();
+ _close = false;
+ return;
+ }
+
+ uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress());
+ if (_progressBar->getValue() != progress) {
+ refreshWidgets();
+ draw();
+ }
+
+ Dialog::handleTickle();
+}
+
+void DownloadDialog::reflowLayout() {
+ Dialog::reflowLayout();
+ refreshWidgets();
+}
+
+namespace {
+Common::String getHumanReadableBytes(uint64 bytes, Common::String &unitsOut) {
+ Common::String result = Common::String::format("%lu", bytes);
+ unitsOut = "B";
+
+ if (bytes >= 1024) {
+ bytes /= 1024;
+ result = Common::String::format("%lu", bytes);
+ unitsOut = "KB";
+ }
+
+ double floating = bytes;
+
+ if (bytes >= 1024) {
+ bytes /= 1024;
+ floating /= 1024.0;
+ unitsOut = "MB";
+ }
+
+ if (bytes >= 1024) {
+ bytes /= 1024;
+ floating /= 1024.0;
+ unitsOut = "GB";
+ }
+
+ if (bytes >= 1024) { // woah
+ bytes /= 1024;
+ floating /= 1024.0;
+ unitsOut = "TB";
+ }
+
+ // print one digit after floating point
+ result = Common::String::format("%.1lf", floating);
+ return result;
+}
+}
+
+Common::String DownloadDialog::getSizeLabelText() {
+ Common::String downloaded, downloadedUnits, total, totalUnits;
+ downloaded = getHumanReadableBytes(CloudMan.getDownloadBytesNumber(), downloadedUnits);
+ total = getHumanReadableBytes(CloudMan.getDownloadTotalBytesNumber(), totalUnits);
+ return Common::String::format(_("Downloaded %s %s / %s %s"), downloaded.c_str(), _(downloadedUnits.c_str()), total.c_str(), _(totalUnits.c_str()));
+}
+
+Common::String DownloadDialog::getSpeedLabelText() {
+ Common::String speed, speedUnits;
+ speed = getHumanReadableBytes(CloudMan.getDownloadSpeed(), speedUnits);
+ speedUnits += "/s";
+ return Common::String::format("Download speed: %s %s", speed.c_str(), _(speedUnits.c_str()));
+}
+
+void DownloadDialog::refreshWidgets() {
+ _localDirectory = CloudMan.getDownloadLocalDirectory();
+ _remoteDirectoryLabel->setLabel(_("From: ") + CloudMan.getDownloadRemoteDirectory());
+ _localDirectoryLabel->setLabel(_("To: ") + _localDirectory);
+ uint32 progress = (uint32)(100 * CloudMan.getDownloadingProgress());
+ _percentLabel->setLabel(Common::String::format("%u %%", progress));
+ _downloadSizeLabel->setLabel(getSizeLabelText());
+ _downloadSpeedLabel->setLabel(getSpeedLabelText());
+ _progressBar->setValue(progress);
+}
+
+} // End of namespace GUI
diff --git a/gui/downloaddialog.h b/gui/downloaddialog.h
new file mode 100644
index 0000000000..9080717195
--- /dev/null
+++ b/gui/downloaddialog.h
@@ -0,0 +1,81 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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 GUI_DOWNLOADDIALOG_H
+#define GUI_DOWNLOADDIALOG_H
+
+#include "gui/dialog.h"
+#include "common/str.h"
+
+namespace GUI {
+class LauncherDialog;
+
+class CommandSender;
+class EditTextWidget;
+class StaticTextWidget;
+class ButtonWidget;
+class SliderWidget;
+class BrowserDialog;
+class RemoteBrowserDialog;
+
+enum DownloadProgress {
+ kDownloadProgressCmd = 'DLPR',
+ kDownloadEndedCmd = 'DLEN'
+};
+
+class DownloadDialog : public Dialog {
+ LauncherDialog *_launcher;
+ BrowserDialog *_browser;
+ RemoteBrowserDialog *_remoteBrowser;
+
+ StaticTextWidget *_remoteDirectoryLabel;
+ StaticTextWidget *_localDirectoryLabel;
+ StaticTextWidget *_percentLabel;
+ StaticTextWidget *_downloadSizeLabel;
+ StaticTextWidget *_downloadSpeedLabel;
+ SliderWidget *_progressBar;
+ ButtonWidget *_cancelButton;
+ ButtonWidget *_closeButton;
+
+ Common::String _localDirectory;
+ bool _close;
+
+ Common::String getSizeLabelText();
+ Common::String getSpeedLabelText();
+
+ void refreshWidgets();
+ bool selectDirectories();
+
+public:
+ DownloadDialog(uint32 storageId, LauncherDialog *launcher);
+ virtual ~DownloadDialog();
+
+ virtual void open();
+ virtual void close();
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleTickle();
+ virtual void reflowLayout();
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/editgamedialog.cpp b/gui/editgamedialog.cpp
new file mode 100644
index 0000000000..fbe76d228a
--- /dev/null
+++ b/gui/editgamedialog.cpp
@@ -0,0 +1,550 @@
+/* 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 "gui/editgamedialog.h"
+
+#include "common/config-manager.h"
+#include "common/gui_options.h"
+#include "common/translation.h"
+#include "common/system.h"
+
+#include "gui/browser.h"
+#include "gui/message.h"
+#ifdef ENABLE_EVENTRECORDER
+#include "gui/onscreendialog.h"
+#include "gui/recorderdialog.h"
+#include "gui/EventRecorder.h"
+#endif
+#include "gui/widgets/edittext.h"
+#include "gui/widgets/tab.h"
+#include "gui/widgets/popup.h"
+
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#endif
+
+using Common::ConfigManager;
+
+namespace GUI {
+
+enum {
+ kStartCmd = 'STRT',
+ kAboutCmd = 'ABOU',
+ kOptionsCmd = 'OPTN',
+ kAddGameCmd = 'ADDG',
+ kEditGameCmd = 'EDTG',
+ kRemoveGameCmd = 'REMG',
+ kLoadGameCmd = 'LOAD',
+ kQuitCmd = 'QUIT',
+ kSearchCmd = 'SRCH',
+ kListSearchCmd = 'LSSR',
+ kSearchClearCmd = 'SRCL',
+
+ kCmdGlobalGraphicsOverride = 'OGFX',
+ kCmdGlobalAudioOverride = 'OSFX',
+ kCmdGlobalMIDIOverride = 'OMID',
+ kCmdGlobalMT32Override = 'OM32',
+ kCmdGlobalVolumeOverride = 'OVOL',
+
+ kCmdChooseSoundFontCmd = 'chsf',
+
+ kCmdExtraBrowser = 'PEXT',
+ kCmdExtraPathClear = 'PEXC',
+ kCmdGameBrowser = 'PGME',
+ kCmdSaveBrowser = 'PSAV',
+ kCmdSavePathClear = 'PSAC'
+};
+
+/*
+* TODO: Clean up this ugly design: we subclass EditTextWidget to perform
+* input validation. It would be much more elegant to use a decorator pattern,
+* or a validation callback, or something like that.
+*/
+class DomainEditTextWidget : public EditTextWidget {
+public:
+ DomainEditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltip = 0)
+ : EditTextWidget(boss, name, text, tooltip) {}
+
+protected:
+ bool tryInsertChar(byte c, int pos) {
+ if (Common::isAlnum(c) || c == '-' || c == '_') {
+ _editString.insertChar(c, pos);
+ return true;
+ }
+ return false;
+ }
+};
+
+EditGameDialog::EditGameDialog(const String &domain, const String &desc)
+ : OptionsDialog(domain, "GameOptions") {
+ // Retrieve all game specific options.
+ const EnginePlugin *plugin = 0;
+ // To allow for game domains without a gameid.
+ // TODO: Is it intentional that this is still supported?
+ String gameId(ConfMan.get("gameid", domain));
+ if (gameId.empty())
+ gameId = domain;
+ // Retrieve the plugin, since we need to access the engine's MetaEngine
+ // implementation.
+ EngineMan.findGame(gameId, &plugin);
+ if (plugin) {
+ _engineOptions = (*plugin)->getExtraGuiOptions(domain);
+ } else {
+ warning("Plugin for target \"%s\" not found! Game specific settings might be missing", domain.c_str());
+ }
+
+ // GAME: Path to game data (r/o), extra data (r/o), and save data (r/w)
+ String gamePath(ConfMan.get("path", _domain));
+ String extraPath(ConfMan.get("extrapath", _domain));
+ String savePath(ConfMan.get("savepath", _domain));
+
+ // GAME: Determine the description string
+ String description(ConfMan.get("description", domain));
+ if (description.empty() && !desc.empty()) {
+ description = desc;
+ }
+
+ // GUI: Add tab widget
+ TabWidget *tab = new TabWidget(this, "GameOptions.TabWidget");
+
+ //
+ // 1) The game tab
+ //
+ tab->addTab(_("Game"));
+
+ // GUI: Label & edit widget for the game ID
+ if (g_system->getOverlayWidth() > 320)
+ new StaticTextWidget(tab, "GameOptions_Game.Id", _("ID:"), _("Short game identifier used for referring to saved games and running the game from the command line"));
+ else
+ new StaticTextWidget(tab, "GameOptions_Game.Id", _c("ID:", "lowres"), _("Short game identifier used for referring to saved games and running the game from the command line"));
+ _domainWidget = new DomainEditTextWidget(tab, "GameOptions_Game.Domain", _domain, _("Short game identifier used for referring to saved games and running the game from the command line"));
+
+ // GUI: Label & edit widget for the description
+ if (g_system->getOverlayWidth() > 320)
+ new StaticTextWidget(tab, "GameOptions_Game.Name", _("Name:"), _("Full title of the game"));
+ else
+ new StaticTextWidget(tab, "GameOptions_Game.Name", _c("Name:", "lowres"), _("Full title of the game"));
+ _descriptionWidget = new EditTextWidget(tab, "GameOptions_Game.Desc", description, _("Full title of the game"));
+
+ // Language popup
+ _langPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.LangPopupDesc", _("Language:"), _("Language of the game. This will not turn your Spanish game version into English"));
+ _langPopUp = new PopUpWidget(tab, "GameOptions_Game.LangPopup", _("Language of the game. This will not turn your Spanish game version into English"));
+ _langPopUp->appendEntry(_("<default>"), (uint32)Common::UNK_LANG);
+ _langPopUp->appendEntry("", (uint32)Common::UNK_LANG);
+ const Common::LanguageDescription *l = Common::g_languages;
+ for (; l->code; ++l) {
+ if (checkGameGUIOptionLanguage(l->id, _guioptionsString))
+ _langPopUp->appendEntry(l->description, l->id);
+ }
+
+ // Platform popup
+ if (g_system->getOverlayWidth() > 320)
+ _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _("Platform:"), _("Platform the game was originally designed for"));
+ else
+ _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _c("Platform:", "lowres"), _("Platform the game was originally designed for"));
+ _platformPopUp = new PopUpWidget(tab, "GameOptions_Game.PlatformPopup", _("Platform the game was originally designed for"));
+ _platformPopUp->appendEntry(_("<default>"));
+ _platformPopUp->appendEntry("");
+ const Common::PlatformDescription *p = Common::g_platforms;
+ for (; p->code; ++p) {
+ _platformPopUp->appendEntry(p->description, p->id);
+ }
+
+ //
+ // 2) The engine tab (shown only if there are custom engine options)
+ //
+ if (_engineOptions.size() > 0) {
+ tab->addTab(_("Engine"));
+
+ addEngineControls(tab, "GameOptions_Engine.", _engineOptions);
+ }
+
+ //
+ // 3) The graphics tab
+ //
+ _graphicsTabId = tab->addTab(g_system->getOverlayWidth() > 320 ? _("Graphics") : _("GFX"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _("Override global graphic settings"), 0, kCmdGlobalGraphicsOverride);
+ else
+ _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _c("Override global graphic settings", "lowres"), 0, kCmdGlobalGraphicsOverride);
+
+ addGraphicControls(tab, "GameOptions_Graphics.");
+
+ //
+ // 4) The audio tab
+ //
+ tab->addTab(_("Audio"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _("Override global audio settings"), 0, kCmdGlobalAudioOverride);
+ else
+ _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _c("Override global audio settings", "lowres"), 0, kCmdGlobalAudioOverride);
+
+ addAudioControls(tab, "GameOptions_Audio.");
+ addSubtitleControls(tab, "GameOptions_Audio.");
+
+ //
+ // 5) The volume tab
+ //
+ if (g_system->getOverlayWidth() > 320)
+ tab->addTab(_("Volume"));
+ else
+ tab->addTab(_c("Volume", "lowres"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _("Override global volume settings"), 0, kCmdGlobalVolumeOverride);
+ else
+ _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _c("Override global volume settings", "lowres"), 0, kCmdGlobalVolumeOverride);
+
+ addVolumeControls(tab, "GameOptions_Volume.");
+
+ //
+ // 6) The MIDI tab
+ //
+ _globalMIDIOverride = NULL;
+ if (!_guioptions.contains(GUIO_NOMIDI)) {
+ tab->addTab(_("MIDI"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _("Override global MIDI settings"), 0, kCmdGlobalMIDIOverride);
+ else
+ _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _c("Override global MIDI settings", "lowres"), 0, kCmdGlobalMIDIOverride);
+
+ addMIDIControls(tab, "GameOptions_MIDI.");
+ }
+
+ //
+ // 7) The MT-32 tab
+ //
+ _globalMT32Override = NULL;
+ if (!_guioptions.contains(GUIO_NOMIDI)) {
+ tab->addTab(_("MT-32"));
+
+ if (g_system->getOverlayWidth() > 320)
+ _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _("Override global MT-32 settings"), 0, kCmdGlobalMT32Override);
+ else
+ _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _c("Override global MT-32 settings", "lowres"), 0, kCmdGlobalMT32Override);
+
+ addMT32Controls(tab, "GameOptions_MT32.");
+ }
+
+ //
+ // 8) The Paths tab
+ //
+ if (g_system->getOverlayWidth() > 320)
+ tab->addTab(_("Paths"));
+ else
+ tab->addTab(_c("Paths", "lowres"));
+
+ // These buttons have to be extra wide, or the text will be truncated
+ // in the small version of the GUI.
+
+ // GUI: Button + Label for the game path
+ if (g_system->getOverlayWidth() > 320)
+ new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _("Game Path:"), 0, kCmdGameBrowser);
+ else
+ new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _c("Game Path:", "lowres"), 0, kCmdGameBrowser);
+ _gamePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.GamepathText", gamePath);
+
+ // GUI: Button + Label for the additional path
+ if (g_system->getOverlayWidth() > 320)
+ new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _("Extra Path:"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
+ else
+ new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _c("Extra Path:", "lowres"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
+ _extraPathWidget = new StaticTextWidget(tab, "GameOptions_Paths.ExtrapathText", extraPath, _("Specifies path to additional data used by the game"));
+
+ _extraPathClearButton = addClearButton(tab, "GameOptions_Paths.ExtraPathClearButton", kCmdExtraPathClear);
+
+ // GUI: Button + Label for the save path
+ if (g_system->getOverlayWidth() > 320)
+ new ButtonWidget(tab, "GameOptions_Paths.Savepath", _("Save Path:"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
+ else
+ new ButtonWidget(tab, "GameOptions_Paths.Savepath", _c("Save Path:", "lowres"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
+ _savePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.SavepathText", savePath, _("Specifies where your saved games are put"));
+
+ _savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear);
+
+ // Activate the first tab
+ tab->setActiveTab(0);
+ _tabWidget = tab;
+
+ // Add OK & Cancel buttons
+ new ButtonWidget(this, "GameOptions.Cancel", _("Cancel"), 0, kCloseCmd);
+ new ButtonWidget(this, "GameOptions.Ok", _("OK"), 0, kOKCmd);
+}
+
+void EditGameDialog::open() {
+ OptionsDialog::open();
+
+ String extraPath(ConfMan.get("extrapath", _domain));
+ if (extraPath.empty() || !ConfMan.hasKey("extrapath", _domain)) {
+ _extraPathWidget->setLabel(_c("None", "path"));
+ }
+
+ String savePath(ConfMan.get("savepath", _domain));
+ if (savePath.empty() || !ConfMan.hasKey("savepath", _domain)) {
+ _savePathWidget->setLabel(_("Default"));
+ }
+
+ int sel, i;
+ bool e;
+
+ // En-/disable dialog items depending on whether overrides are active or not.
+
+ e = ConfMan.hasKey("gfx_mode", _domain) ||
+ ConfMan.hasKey("render_mode", _domain) ||
+ ConfMan.hasKey("fullscreen", _domain) ||
+ ConfMan.hasKey("aspect_ratio", _domain);
+ _globalGraphicsOverride->setState(e);
+
+ e = ConfMan.hasKey("music_driver", _domain) ||
+ ConfMan.hasKey("output_rate", _domain) ||
+ ConfMan.hasKey("opl_driver", _domain) ||
+ ConfMan.hasKey("subtitles", _domain) ||
+ ConfMan.hasKey("talkspeed", _domain);
+ _globalAudioOverride->setState(e);
+
+ e = ConfMan.hasKey("music_volume", _domain) ||
+ ConfMan.hasKey("sfx_volume", _domain) ||
+ ConfMan.hasKey("speech_volume", _domain);
+ _globalVolumeOverride->setState(e);
+
+ if (!_guioptions.contains(GUIO_NOMIDI)) {
+ e = ConfMan.hasKey("soundfont", _domain) ||
+ ConfMan.hasKey("multi_midi", _domain) ||
+ ConfMan.hasKey("midi_gain", _domain);
+ _globalMIDIOverride->setState(e);
+ }
+
+ if (!_guioptions.contains(GUIO_NOMIDI)) {
+ e = ConfMan.hasKey("native_mt32", _domain) ||
+ ConfMan.hasKey("enable_gs", _domain);
+ _globalMT32Override->setState(e);
+ }
+
+ // TODO: game path
+
+ const Common::Language lang = Common::parseLanguage(ConfMan.get("language", _domain));
+
+ if (ConfMan.hasKey("language", _domain)) {
+ _langPopUp->setSelectedTag(lang);
+ } else {
+ _langPopUp->setSelectedTag((uint32)Common::UNK_LANG);
+ }
+
+ if (_langPopUp->numEntries() <= 3) { // If only one language is avaliable
+ _langPopUpDesc->setEnabled(false);
+ _langPopUp->setEnabled(false);
+ }
+
+ // Set the state of engine-specific checkboxes
+ for (uint j = 0; j < _engineOptions.size(); ++j) {
+ // The default values for engine-specific checkboxes are not set when
+ // ScummVM starts, as this would require us to load and poll all of the
+ // engine plugins on startup. Thus, we set the state of each custom
+ // option checkbox to what is specified by the engine plugin, and
+ // update it only if a value has been set in the configuration of the
+ // currently selected game.
+ bool isChecked = _engineOptions[j].defaultState;
+ if (ConfMan.hasKey(_engineOptions[j].configOption, _domain))
+ isChecked = ConfMan.getBool(_engineOptions[j].configOption, _domain);
+ _engineCheckboxes[j]->setState(isChecked);
+ }
+
+ const Common::PlatformDescription *p = Common::g_platforms;
+ const Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", _domain));
+ sel = 0;
+ for (i = 0; p->code; ++p, ++i) {
+ if (platform == p->id)
+ sel = i + 2;
+ }
+ _platformPopUp->setSelected(sel);
+}
+
+
+void EditGameDialog::close() {
+ if (getResult()) {
+ ConfMan.set("description", _descriptionWidget->getEditString(), _domain);
+
+ Common::Language lang = (Common::Language)_langPopUp->getSelectedTag();
+ if (lang < 0)
+ ConfMan.removeKey("language", _domain);
+ else
+ ConfMan.set("language", Common::getLanguageCode(lang), _domain);
+
+ String gamePath(_gamePathWidget->getLabel());
+ if (!gamePath.empty())
+ ConfMan.set("path", gamePath, _domain);
+
+ String extraPath(_extraPathWidget->getLabel());
+ if (!extraPath.empty() && (extraPath != _c("None", "path")))
+ ConfMan.set("extrapath", extraPath, _domain);
+ else
+ ConfMan.removeKey("extrapath", _domain);
+
+ String savePath(_savePathWidget->getLabel());
+ if (!savePath.empty() && (savePath != _("Default")))
+ ConfMan.set("savepath", savePath, _domain);
+ else
+ ConfMan.removeKey("savepath", _domain);
+
+ Common::Platform platform = (Common::Platform)_platformPopUp->getSelectedTag();
+ if (platform < 0)
+ ConfMan.removeKey("platform", _domain);
+ else
+ ConfMan.set("platform", Common::getPlatformCode(platform), _domain);
+
+ // Set the state of engine-specific checkboxes
+ for (uint i = 0; i < _engineOptions.size(); i++) {
+ ConfMan.setBool(_engineOptions[i].configOption, _engineCheckboxes[i]->getState(), _domain);
+ }
+ }
+ OptionsDialog::close();
+}
+
+void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kCmdGlobalGraphicsOverride:
+ setGraphicSettingsState(data != 0);
+ draw();
+ break;
+ case kCmdGlobalAudioOverride:
+ setAudioSettingsState(data != 0);
+ setSubtitleSettingsState(data != 0);
+ if (_globalVolumeOverride == NULL)
+ setVolumeSettingsState(data != 0);
+ draw();
+ break;
+ case kCmdGlobalMIDIOverride:
+ setMIDISettingsState(data != 0);
+ draw();
+ break;
+ case kCmdGlobalMT32Override:
+ setMT32SettingsState(data != 0);
+ draw();
+ break;
+ case kCmdGlobalVolumeOverride:
+ setVolumeSettingsState(data != 0);
+ draw();
+ break;
+ case kCmdChooseSoundFontCmd:
+ {
+ BrowserDialog browser(_("Select SoundFont"), false);
+
+ if (browser.runModal() > 0) {
+ // User made this choice...
+ Common::FSNode file(browser.getResult());
+ _soundFont->setLabel(file.getPath());
+
+ if (!file.getPath().empty() && (file.getPath() != _c("None", "path")))
+ _soundFontClearButton->setEnabled(true);
+ else
+ _soundFontClearButton->setEnabled(false);
+
+ draw();
+ }
+ break;
+ }
+
+ // Change path for the game
+ case kCmdGameBrowser:
+ {
+ BrowserDialog browser(_("Select directory with game data"), true);
+ if (browser.runModal() > 0) {
+ // User made his choice...
+ Common::FSNode dir(browser.getResult());
+
+ // TODO: Verify the game can be found in the new directory... Best
+ // done with optional specific gameid to pluginmgr detectgames?
+ // FSList files = dir.listDir(FSNode::kListFilesOnly);
+
+ _gamePathWidget->setLabel(dir.getPath());
+ draw();
+ }
+ draw();
+ break;
+ }
+
+ // Change path for extra game data (eg, using sword cutscenes when playing via CD)
+ case kCmdExtraBrowser:
+ {
+ BrowserDialog browser(_("Select additional game directory"), true);
+ if (browser.runModal() > 0) {
+ // User made his choice...
+ Common::FSNode dir(browser.getResult());
+ _extraPathWidget->setLabel(dir.getPath());
+ draw();
+ }
+ draw();
+ break;
+ }
+ // Change path for stored save game (perm and temp) data
+ case kCmdSaveBrowser:
+ {
+ BrowserDialog browser(_("Select directory for saved games"), true);
+ if (browser.runModal() > 0) {
+ // User made his choice...
+ Common::FSNode dir(browser.getResult());
+ _savePathWidget->setLabel(dir.getPath());
+#ifdef USE_LIBCURL
+ MessageDialog warningMessage(_("Saves sync feature doesn't work with non-default directories. If you want your saves to sync, use default directory."));
+ warningMessage.runModal();
+#endif
+ draw();
+ }
+ draw();
+ break;
+ }
+
+ case kCmdExtraPathClear:
+ _extraPathWidget->setLabel(_c("None", "path"));
+ break;
+
+ case kCmdSavePathClear:
+ _savePathWidget->setLabel(_("Default"));
+ break;
+
+ case kOKCmd:
+ {
+ // Write back changes made to config object
+ String newDomain(_domainWidget->getEditString());
+ if (newDomain != _domain) {
+ if (newDomain.empty()
+ || newDomain.hasPrefix("_")
+ || newDomain == ConfigManager::kApplicationDomain
+ || ConfMan.hasGameDomain(newDomain)) {
+ MessageDialog alert(_("This game ID is already taken. Please choose another one."));
+ alert.runModal();
+ return;
+ }
+ ConfMan.renameGameDomain(_domain, newDomain);
+ _domain = newDomain;
+ }
+ }
+ // FALL THROUGH to default case
+ default:
+ OptionsDialog::handleCommand(sender, cmd, data);
+ }
+}
+
+} // End of namespace GUI
diff --git a/gui/editgamedialog.h b/gui/editgamedialog.h
new file mode 100644
index 0000000000..0be6c1650e
--- /dev/null
+++ b/gui/editgamedialog.h
@@ -0,0 +1,97 @@
+/* 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 GUI_EDITGAMEDIALOG_H
+#define GUI_EDITGAMEDIALOG_H
+
+#include "engines/game.h"
+#include "gui/dialog.h"
+#include "gui/options.h"
+
+namespace GUI {
+
+class BrowserDialog;
+class CommandSender;
+class DomainEditTextWidget;
+class ListWidget;
+class ButtonWidget;
+class PicButtonWidget;
+class GraphicsWidget;
+class StaticTextWidget;
+class EditTextWidget;
+class SaveLoadChooser;
+
+Common::String addGameToConf(const GameDescriptor &result);
+
+/*
+* A dialog that allows the user to edit a config game entry.
+* TODO: add widgets for some/all of the following
+* - Maybe scaler/graphics mode. But there are two problems:
+* 1) Different backends can have different scalers with different names,
+* so we first have to add a way to query those... no Ender, I don't
+* think a bitmasked property() value is nice for this, because we would
+* have to add to the bitmask values whenever a backends adds a new scaler).
+* 2) At the time the launcher is running, the GFX backend is already setup.
+* So when a game is run via the launcher, the custom scaler setting for it won't be
+* used. So we'd also have to add an API to change the scaler during runtime
+* (the SDL backend can already do that based on user input, but there is no API
+* to achieve it)
+* If the APIs for 1&2 are in place, we can think about adding this to the Edit&Option dialogs
+*/
+
+class EditGameDialog : public OptionsDialog {
+ typedef Common::String String;
+ typedef Common::Array<Common::String> StringArray;
+public:
+ EditGameDialog(const String &domain, const String &desc);
+
+ void open();
+ void close();
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+
+protected:
+ EditTextWidget *_descriptionWidget;
+ DomainEditTextWidget *_domainWidget;
+
+ StaticTextWidget *_gamePathWidget;
+ StaticTextWidget *_extraPathWidget;
+ StaticTextWidget *_savePathWidget;
+ ButtonWidget *_extraPathClearButton;
+ ButtonWidget *_savePathClearButton;
+
+ StaticTextWidget *_langPopUpDesc;
+ PopUpWidget *_langPopUp;
+ StaticTextWidget *_platformPopUpDesc;
+ PopUpWidget *_platformPopUp;
+
+ CheckboxWidget *_globalGraphicsOverride;
+ CheckboxWidget *_globalAudioOverride;
+ CheckboxWidget *_globalMIDIOverride;
+ CheckboxWidget *_globalMT32Override;
+ CheckboxWidget *_globalVolumeOverride;
+
+ ExtraGuiOptions _engineOptions;
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/gui-manager.cpp b/gui/gui-manager.cpp
index 9acd9434ff..9d68d76c9c 100644
--- a/gui/gui-manager.cpp
+++ b/gui/gui-manager.cpp
@@ -216,7 +216,7 @@ void GuiManager::redraw() {
// Tanoku: Do not apply shading more than once when opening many dialogs
// on top of each other. Screen ends up being too dark and it's a
// performance hog.
- if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 2)
+ if (_redrawStatus == kRedrawOpenDialog && _dialogStack.size() > 3)
shading = ThemeEngine::kShadingNone;
switch (_redrawStatus) {
diff --git a/gui/launcher.cpp b/gui/launcher.cpp
index 9a3300b11f..50f060de65 100644
--- a/gui/launcher.cpp
+++ b/gui/launcher.cpp
@@ -33,6 +33,7 @@
#include "gui/about.h"
#include "gui/browser.h"
#include "gui/chooser.h"
+#include "gui/editgamedialog.h"
#include "gui/launcher.h"
#include "gui/massadd.h"
#include "gui/message.h"
@@ -51,6 +52,9 @@
#include "gui/ThemeEval.h"
#include "graphics/cursorman.h"
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#endif
using Common::ConfigManager;
@@ -84,521 +88,6 @@ enum {
kCmdSavePathClear = 'PSAC'
};
-/*
- * TODO: Clean up this ugly design: we subclass EditTextWidget to perform
- * input validation. It would be much more elegant to use a decorator pattern,
- * or a validation callback, or something like that.
- */
-class DomainEditTextWidget : public EditTextWidget {
-public:
- DomainEditTextWidget(GuiObject *boss, const String &name, const String &text, const char *tooltip = 0)
- : EditTextWidget(boss, name, text, tooltip) {
- }
-
-protected:
- bool tryInsertChar(byte c, int pos) {
- if (Common::isAlnum(c) || c == '-' || c == '_') {
- _editString.insertChar(c, pos);
- return true;
- }
- return false;
- }
-};
-
-/*
- * A dialog that allows the user to edit a config game entry.
- * TODO: add widgets for some/all of the following
- * - Maybe scaler/graphics mode. But there are two problems:
- * 1) Different backends can have different scalers with different names,
- * so we first have to add a way to query those... no Ender, I don't
- * think a bitmasked property() value is nice for this, because we would
- * have to add to the bitmask values whenever a backends adds a new scaler).
- * 2) At the time the launcher is running, the GFX backend is already setup.
- * So when a game is run via the launcher, the custom scaler setting for it won't be
- * used. So we'd also have to add an API to change the scaler during runtime
- * (the SDL backend can already do that based on user input, but there is no API
- * to achieve it)
- * If the APIs for 1&2 are in place, we can think about adding this to the Edit&Option dialogs
- */
-
-class EditGameDialog : public OptionsDialog {
- typedef Common::String String;
- typedef Common::Array<Common::String> StringArray;
-public:
- EditGameDialog(const String &domain, const String &desc);
-
- void open();
- void close();
- virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
-
-protected:
- EditTextWidget *_descriptionWidget;
- DomainEditTextWidget *_domainWidget;
-
- StaticTextWidget *_gamePathWidget;
- StaticTextWidget *_extraPathWidget;
- StaticTextWidget *_savePathWidget;
- ButtonWidget *_extraPathClearButton;
- ButtonWidget *_savePathClearButton;
-
- StaticTextWidget *_langPopUpDesc;
- PopUpWidget *_langPopUp;
- StaticTextWidget *_platformPopUpDesc;
- PopUpWidget *_platformPopUp;
-
- CheckboxWidget *_globalGraphicsOverride;
- CheckboxWidget *_globalAudioOverride;
- CheckboxWidget *_globalMIDIOverride;
- CheckboxWidget *_globalMT32Override;
- CheckboxWidget *_globalVolumeOverride;
-
- ExtraGuiOptions _engineOptions;
-};
-
-EditGameDialog::EditGameDialog(const String &domain, const String &desc)
- : OptionsDialog(domain, "GameOptions") {
- // Retrieve all game specific options.
- const EnginePlugin *plugin = 0;
- // To allow for game domains without a gameid.
- // TODO: Is it intentional that this is still supported?
- String gameId(ConfMan.get("gameid", domain));
- if (gameId.empty())
- gameId = domain;
- // Retrieve the plugin, since we need to access the engine's MetaEngine
- // implementation.
- EngineMan.findGame(gameId, &plugin);
- if (plugin) {
- _engineOptions = (*plugin)->getExtraGuiOptions(domain);
- } else {
- warning("Plugin for target \"%s\" not found! Game specific settings might be missing", domain.c_str());
- }
-
- // GAME: Path to game data (r/o), extra data (r/o), and save data (r/w)
- String gamePath(ConfMan.get("path", _domain));
- String extraPath(ConfMan.get("extrapath", _domain));
- String savePath(ConfMan.get("savepath", _domain));
-
- // GAME: Determine the description string
- String description(ConfMan.get("description", domain));
- if (description.empty() && !desc.empty()) {
- description = desc;
- }
-
- // GUI: Add tab widget
- TabWidget *tab = new TabWidget(this, "GameOptions.TabWidget");
-
- //
- // 1) The game tab
- //
- tab->addTab(_("Game"));
-
- // GUI: Label & edit widget for the game ID
- if (g_system->getOverlayWidth() > 320)
- new StaticTextWidget(tab, "GameOptions_Game.Id", _("ID:"), _("Short game identifier used for referring to saved games and running the game from the command line"));
- else
- new StaticTextWidget(tab, "GameOptions_Game.Id", _c("ID:", "lowres"), _("Short game identifier used for referring to saved games and running the game from the command line"));
- _domainWidget = new DomainEditTextWidget(tab, "GameOptions_Game.Domain", _domain, _("Short game identifier used for referring to saved games and running the game from the command line"));
-
- // GUI: Label & edit widget for the description
- if (g_system->getOverlayWidth() > 320)
- new StaticTextWidget(tab, "GameOptions_Game.Name", _("Name:"), _("Full title of the game"));
- else
- new StaticTextWidget(tab, "GameOptions_Game.Name", _c("Name:", "lowres"), _("Full title of the game"));
- _descriptionWidget = new EditTextWidget(tab, "GameOptions_Game.Desc", description, _("Full title of the game"));
-
- // Language popup
- _langPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.LangPopupDesc", _("Language:"), _("Language of the game. This will not turn your Spanish game version into English"));
- _langPopUp = new PopUpWidget(tab, "GameOptions_Game.LangPopup", _("Language of the game. This will not turn your Spanish game version into English"));
- _langPopUp->appendEntry(_("<default>"), (uint32)Common::UNK_LANG);
- _langPopUp->appendEntry("", (uint32)Common::UNK_LANG);
- const Common::LanguageDescription *l = Common::g_languages;
- for (; l->code; ++l) {
- if (checkGameGUIOptionLanguage(l->id, _guioptionsString))
- _langPopUp->appendEntry(l->description, l->id);
- }
-
- // Platform popup
- if (g_system->getOverlayWidth() > 320)
- _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _("Platform:"), _("Platform the game was originally designed for"));
- else
- _platformPopUpDesc = new StaticTextWidget(tab, "GameOptions_Game.PlatformPopupDesc", _c("Platform:", "lowres"), _("Platform the game was originally designed for"));
- _platformPopUp = new PopUpWidget(tab, "GameOptions_Game.PlatformPopup", _("Platform the game was originally designed for"));
- _platformPopUp->appendEntry(_("<default>"));
- _platformPopUp->appendEntry("");
- const Common::PlatformDescription *p = Common::g_platforms;
- for (; p->code; ++p) {
- _platformPopUp->appendEntry(p->description, p->id);
- }
-
- //
- // 2) The engine tab (shown only if there are custom engine options)
- //
- if (_engineOptions.size() > 0) {
- tab->addTab(_("Engine"));
-
- addEngineControls(tab, "GameOptions_Engine.", _engineOptions);
- }
-
- //
- // 3) The graphics tab
- //
- _graphicsTabId = tab->addTab(g_system->getOverlayWidth() > 320 ? _("Graphics") : _("GFX"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _("Override global graphic settings"), 0, kCmdGlobalGraphicsOverride);
- else
- _globalGraphicsOverride = new CheckboxWidget(tab, "GameOptions_Graphics.EnableTabCheckbox", _c("Override global graphic settings", "lowres"), 0, kCmdGlobalGraphicsOverride);
-
- addGraphicControls(tab, "GameOptions_Graphics.");
-
- //
- // 4) The audio tab
- //
- tab->addTab(_("Audio"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _("Override global audio settings"), 0, kCmdGlobalAudioOverride);
- else
- _globalAudioOverride = new CheckboxWidget(tab, "GameOptions_Audio.EnableTabCheckbox", _c("Override global audio settings", "lowres"), 0, kCmdGlobalAudioOverride);
-
- addAudioControls(tab, "GameOptions_Audio.");
- addSubtitleControls(tab, "GameOptions_Audio.");
-
- //
- // 5) The volume tab
- //
- if (g_system->getOverlayWidth() > 320)
- tab->addTab(_("Volume"));
- else
- tab->addTab(_c("Volume", "lowres"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _("Override global volume settings"), 0, kCmdGlobalVolumeOverride);
- else
- _globalVolumeOverride = new CheckboxWidget(tab, "GameOptions_Volume.EnableTabCheckbox", _c("Override global volume settings", "lowres"), 0, kCmdGlobalVolumeOverride);
-
- addVolumeControls(tab, "GameOptions_Volume.");
-
- //
- // 6) The MIDI tab
- //
- _globalMIDIOverride = NULL;
- if (!_guioptions.contains(GUIO_NOMIDI)) {
- tab->addTab(_("MIDI"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _("Override global MIDI settings"), 0, kCmdGlobalMIDIOverride);
- else
- _globalMIDIOverride = new CheckboxWidget(tab, "GameOptions_MIDI.EnableTabCheckbox", _c("Override global MIDI settings", "lowres"), 0, kCmdGlobalMIDIOverride);
-
- addMIDIControls(tab, "GameOptions_MIDI.");
- }
-
- //
- // 7) The MT-32 tab
- //
- _globalMT32Override = NULL;
- if (!_guioptions.contains(GUIO_NOMIDI)) {
- tab->addTab(_("MT-32"));
-
- if (g_system->getOverlayWidth() > 320)
- _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _("Override global MT-32 settings"), 0, kCmdGlobalMT32Override);
- else
- _globalMT32Override = new CheckboxWidget(tab, "GameOptions_MT32.EnableTabCheckbox", _c("Override global MT-32 settings", "lowres"), 0, kCmdGlobalMT32Override);
-
- addMT32Controls(tab, "GameOptions_MT32.");
- }
-
- //
- // 8) The Paths tab
- //
- if (g_system->getOverlayWidth() > 320)
- tab->addTab(_("Paths"));
- else
- tab->addTab(_c("Paths", "lowres"));
-
- // These buttons have to be extra wide, or the text will be truncated
- // in the small version of the GUI.
-
- // GUI: Button + Label for the game path
- if (g_system->getOverlayWidth() > 320)
- new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _("Game Path:"), 0, kCmdGameBrowser);
- else
- new ButtonWidget(tab, "GameOptions_Paths.Gamepath", _c("Game Path:", "lowres"), 0, kCmdGameBrowser);
- _gamePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.GamepathText", gamePath);
-
- // GUI: Button + Label for the additional path
- if (g_system->getOverlayWidth() > 320)
- new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _("Extra Path:"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
- else
- new ButtonWidget(tab, "GameOptions_Paths.Extrapath", _c("Extra Path:", "lowres"), _("Specifies path to additional data used by the game"), kCmdExtraBrowser);
- _extraPathWidget = new StaticTextWidget(tab, "GameOptions_Paths.ExtrapathText", extraPath, _("Specifies path to additional data used by the game"));
-
- _extraPathClearButton = addClearButton(tab, "GameOptions_Paths.ExtraPathClearButton", kCmdExtraPathClear);
-
- // GUI: Button + Label for the save path
- if (g_system->getOverlayWidth() > 320)
- new ButtonWidget(tab, "GameOptions_Paths.Savepath", _("Save Path:"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
- else
- new ButtonWidget(tab, "GameOptions_Paths.Savepath", _c("Save Path:", "lowres"), _("Specifies where your saved games are put"), kCmdSaveBrowser);
- _savePathWidget = new StaticTextWidget(tab, "GameOptions_Paths.SavepathText", savePath, _("Specifies where your saved games are put"));
-
- _savePathClearButton = addClearButton(tab, "GameOptions_Paths.SavePathClearButton", kCmdSavePathClear);
-
- // Activate the first tab
- tab->setActiveTab(0);
- _tabWidget = tab;
-
- // Add OK & Cancel buttons
- new ButtonWidget(this, "GameOptions.Cancel", _("Cancel"), 0, kCloseCmd);
- new ButtonWidget(this, "GameOptions.Ok", _("OK"), 0, kOKCmd);
-}
-
-void EditGameDialog::open() {
- OptionsDialog::open();
-
- String extraPath(ConfMan.get("extrapath", _domain));
- if (extraPath.empty() || !ConfMan.hasKey("extrapath", _domain)) {
- _extraPathWidget->setLabel(_c("None", "path"));
- }
-
- String savePath(ConfMan.get("savepath", _domain));
- if (savePath.empty() || !ConfMan.hasKey("savepath", _domain)) {
- _savePathWidget->setLabel(_("Default"));
- }
-
- int sel, i;
- bool e;
-
- // En-/disable dialog items depending on whether overrides are active or not.
-
- e = ConfMan.hasKey("gfx_mode", _domain) ||
- ConfMan.hasKey("render_mode", _domain) ||
- ConfMan.hasKey("fullscreen", _domain) ||
- ConfMan.hasKey("aspect_ratio", _domain);
- _globalGraphicsOverride->setState(e);
-
- e = ConfMan.hasKey("music_driver", _domain) ||
- ConfMan.hasKey("output_rate", _domain) ||
- ConfMan.hasKey("opl_driver", _domain) ||
- ConfMan.hasKey("subtitles", _domain) ||
- ConfMan.hasKey("talkspeed", _domain);
- _globalAudioOverride->setState(e);
-
- e = ConfMan.hasKey("music_volume", _domain) ||
- ConfMan.hasKey("sfx_volume", _domain) ||
- ConfMan.hasKey("speech_volume", _domain);
- _globalVolumeOverride->setState(e);
-
- if (!_guioptions.contains(GUIO_NOMIDI)) {
- e = ConfMan.hasKey("soundfont", _domain) ||
- ConfMan.hasKey("multi_midi", _domain) ||
- ConfMan.hasKey("midi_gain", _domain);
- _globalMIDIOverride->setState(e);
- }
-
- if (!_guioptions.contains(GUIO_NOMIDI)) {
- e = ConfMan.hasKey("native_mt32", _domain) ||
- ConfMan.hasKey("enable_gs", _domain);
- _globalMT32Override->setState(e);
- }
-
- // TODO: game path
-
- const Common::Language lang = Common::parseLanguage(ConfMan.get("language", _domain));
-
- if (ConfMan.hasKey("language", _domain)) {
- _langPopUp->setSelectedTag(lang);
- } else {
- _langPopUp->setSelectedTag((uint32)Common::UNK_LANG);
- }
-
- if (_langPopUp->numEntries() <= 3) { // If only one language is avaliable
- _langPopUpDesc->setEnabled(false);
- _langPopUp->setEnabled(false);
- }
-
- // Set the state of engine-specific checkboxes
- for (uint j = 0; j < _engineOptions.size(); ++j) {
- // The default values for engine-specific checkboxes are not set when
- // ScummVM starts, as this would require us to load and poll all of the
- // engine plugins on startup. Thus, we set the state of each custom
- // option checkbox to what is specified by the engine plugin, and
- // update it only if a value has been set in the configuration of the
- // currently selected game.
- bool isChecked = _engineOptions[j].defaultState;
- if (ConfMan.hasKey(_engineOptions[j].configOption, _domain))
- isChecked = ConfMan.getBool(_engineOptions[j].configOption, _domain);
- _engineCheckboxes[j]->setState(isChecked);
- }
-
- const Common::PlatformDescription *p = Common::g_platforms;
- const Common::Platform platform = Common::parsePlatform(ConfMan.get("platform", _domain));
- sel = 0;
- for (i = 0; p->code; ++p, ++i) {
- if (platform == p->id)
- sel = i + 2;
- }
- _platformPopUp->setSelected(sel);
-}
-
-
-void EditGameDialog::close() {
- if (getResult()) {
- ConfMan.set("description", _descriptionWidget->getEditString(), _domain);
-
- Common::Language lang = (Common::Language)_langPopUp->getSelectedTag();
- if (lang < 0)
- ConfMan.removeKey("language", _domain);
- else
- ConfMan.set("language", Common::getLanguageCode(lang), _domain);
-
- String gamePath(_gamePathWidget->getLabel());
- if (!gamePath.empty())
- ConfMan.set("path", gamePath, _domain);
-
- String extraPath(_extraPathWidget->getLabel());
- if (!extraPath.empty() && (extraPath != _c("None", "path")))
- ConfMan.set("extrapath", extraPath, _domain);
- else
- ConfMan.removeKey("extrapath", _domain);
-
- String savePath(_savePathWidget->getLabel());
- if (!savePath.empty() && (savePath != _("Default")))
- ConfMan.set("savepath", savePath, _domain);
- else
- ConfMan.removeKey("savepath", _domain);
-
- Common::Platform platform = (Common::Platform)_platformPopUp->getSelectedTag();
- if (platform < 0)
- ConfMan.removeKey("platform", _domain);
- else
- ConfMan.set("platform", Common::getPlatformCode(platform), _domain);
-
- // Set the state of engine-specific checkboxes
- for (uint i = 0; i < _engineOptions.size(); i++) {
- ConfMan.setBool(_engineOptions[i].configOption, _engineCheckboxes[i]->getState(), _domain);
- }
- }
- OptionsDialog::close();
-}
-
-void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
- switch (cmd) {
- case kCmdGlobalGraphicsOverride:
- setGraphicSettingsState(data != 0);
- draw();
- break;
- case kCmdGlobalAudioOverride:
- setAudioSettingsState(data != 0);
- setSubtitleSettingsState(data != 0);
- if (_globalVolumeOverride == NULL)
- setVolumeSettingsState(data != 0);
- draw();
- break;
- case kCmdGlobalMIDIOverride:
- setMIDISettingsState(data != 0);
- draw();
- break;
- case kCmdGlobalMT32Override:
- setMT32SettingsState(data != 0);
- draw();
- break;
- case kCmdGlobalVolumeOverride:
- setVolumeSettingsState(data != 0);
- draw();
- break;
- case kCmdChooseSoundFontCmd: {
- BrowserDialog browser(_("Select SoundFont"), false);
-
- if (browser.runModal() > 0) {
- // User made this choice...
- Common::FSNode file(browser.getResult());
- _soundFont->setLabel(file.getPath());
-
- if (!file.getPath().empty() && (file.getPath() != _c("None", "path")))
- _soundFontClearButton->setEnabled(true);
- else
- _soundFontClearButton->setEnabled(false);
-
- draw();
- }
- break;
- }
-
- // Change path for the game
- case kCmdGameBrowser: {
- BrowserDialog browser(_("Select directory with game data"), true);
- if (browser.runModal() > 0) {
- // User made his choice...
- Common::FSNode dir(browser.getResult());
-
- // TODO: Verify the game can be found in the new directory... Best
- // done with optional specific gameid to pluginmgr detectgames?
- // FSList files = dir.listDir(FSNode::kListFilesOnly);
-
- _gamePathWidget->setLabel(dir.getPath());
- draw();
- }
- draw();
- break;
- }
-
- // Change path for extra game data (eg, using sword cutscenes when playing via CD)
- case kCmdExtraBrowser: {
- BrowserDialog browser(_("Select additional game directory"), true);
- if (browser.runModal() > 0) {
- // User made his choice...
- Common::FSNode dir(browser.getResult());
- _extraPathWidget->setLabel(dir.getPath());
- draw();
- }
- draw();
- break;
- }
- // Change path for stored save game (perm and temp) data
- case kCmdSaveBrowser: {
- BrowserDialog browser(_("Select directory for saved games"), true);
- if (browser.runModal() > 0) {
- // User made his choice...
- Common::FSNode dir(browser.getResult());
- _savePathWidget->setLabel(dir.getPath());
- draw();
- }
- draw();
- break;
- }
-
- case kCmdExtraPathClear:
- _extraPathWidget->setLabel(_c("None", "path"));
- break;
-
- case kCmdSavePathClear:
- _savePathWidget->setLabel(_("Default"));
- break;
-
- case kOKCmd: {
- // Write back changes made to config object
- String newDomain(_domainWidget->getEditString());
- if (newDomain != _domain) {
- if (newDomain.empty()
- || newDomain.hasPrefix("_")
- || newDomain == ConfigManager::kApplicationDomain
- || ConfMan.hasGameDomain(newDomain)) {
- MessageDialog alert(_("This game ID is already taken. Please choose another one."));
- alert.runModal();
- return;
- }
- ConfMan.renameGameDomain(_domain, newDomain);
- _domain = newDomain;
- }
- }
- // FALL THROUGH to default case
- default:
- OptionsDialog::handleCommand(sender, cmd, data);
- }
-}
-
#pragma mark -
LauncherDialog::LauncherDialog()
@@ -839,65 +328,25 @@ void LauncherDialog::addGame() {
if (_browser->runModal() > 0) {
// User made his choice...
- Common::FSNode dir(_browser->getResult());
- Common::FSList files;
- if (!dir.getChildren(files, Common::FSNode::kListAll)) {
- MessageDialog alert(_("ScummVM couldn't open the specified directory!"));
- alert.runModal();
- return;
- }
-
- // ...so let's determine a list of candidates, games that
- // could be contained in the specified directory.
- GameList candidates(EngineMan.detectGames(files));
-
- int idx;
- if (candidates.empty()) {
- // No game was found in the specified directory
- MessageDialog alert(_("ScummVM could not find any game in the specified directory!"));
- alert.runModal();
- idx = -1;
-
- looping = true;
- } else if (candidates.size() == 1) {
- // Exact match
- idx = 0;
- } else {
- // Display the candidates to the user and let her/him pick one
- StringArray list;
- for (idx = 0; idx < (int)candidates.size(); idx++)
- list.push_back(candidates[idx].description());
-
- ChooserDialog dialog(_("Pick the game:"));
- dialog.setList(list);
- idx = dialog.runModal();
- }
- if (0 <= idx && idx < (int)candidates.size()) {
- GameDescriptor result = candidates[idx];
-
- // TODO: Change the detectors to set "path" !
- result["path"] = dir.getPath();
-
- Common::String domain = addGameToConf(result);
-
- // Display edit dialog for the new entry
- EditGameDialog editDialog(domain, result.description());
- if (editDialog.runModal() > 0) {
- // User pressed OK, so make changes permanent
-
- // Write config to disk
- ConfMan.flushToDisk();
-
- // Update the ListWidget, select the new item, and force a redraw
- updateListing();
- selectTarget(editDialog.getDomain());
- draw();
+#ifdef USE_LIBCURL
+ String selectedDirectory = _browser->getResult().getPath();
+ String bannedDirectory = CloudMan.getDownloadLocalDirectory();
+ if (selectedDirectory.size() && selectedDirectory.lastChar() != '/' && selectedDirectory.lastChar() != '\\')
+ selectedDirectory += '/';
+ if (bannedDirectory.size() && bannedDirectory.lastChar() != '/' && bannedDirectory.lastChar() != '\\') {
+ if (selectedDirectory.size()) {
+ bannedDirectory += selectedDirectory.lastChar();
} else {
- // User aborted, remove the the new domain again
- ConfMan.removeGameDomain(domain);
+ bannedDirectory += '/';
}
-
}
+ if (selectedDirectory.equalsIgnoreCase(bannedDirectory)) {
+ MessageDialog alert(_("This directory cannot be used yet, it is being downloaded into!"));
+ alert.runModal();
+ return;
+ }
+#endif
+ looping = !doGameDetection(_browser->getResult().getPath());
}
} while (looping);
}
@@ -1076,6 +525,81 @@ void LauncherDialog::handleKeyUp(Common::KeyState state) {
updateButtons();
}
+bool LauncherDialog::doGameDetection(const Common::String &path) {
+ // Allow user to add a new game to the list.
+ // 2) try to auto detect which game is in the directory, if we cannot
+ // determine it uniquely present a list of candidates to the user
+ // to pick from
+ // 3) Display the 'Edit' dialog for that item, letting the user specify
+ // an alternate description (to distinguish multiple versions of the
+ // game, e.g. 'Monkey German' and 'Monkey English') and set default
+ // options for that game
+ // 4) If no game is found in the specified directory, return to the
+ // dialog.
+
+ // User made his choice...
+ Common::FSNode dir(path);
+ Common::FSList files;
+ if (!dir.getChildren(files, Common::FSNode::kListAll)) {
+ MessageDialog alert(_("ScummVM couldn't open the specified directory!"));
+ alert.runModal();
+ return true;
+ }
+
+ // ...so let's determine a list of candidates, games that
+ // could be contained in the specified directory.
+ GameList candidates(EngineMan.detectGames(files));
+
+ int idx;
+ if (candidates.empty()) {
+ // No game was found in the specified directory
+ MessageDialog alert(_("ScummVM could not find any game in the specified directory!"));
+ alert.runModal();
+ idx = -1;
+ return false;
+ } else if (candidates.size() == 1) {
+ // Exact match
+ idx = 0;
+ } else {
+ // Display the candidates to the user and let her/him pick one
+ StringArray list;
+ for (idx = 0; idx < (int)candidates.size(); idx++)
+ list.push_back(candidates[idx].description());
+
+ ChooserDialog dialog(_("Pick the game:"));
+ dialog.setList(list);
+ idx = dialog.runModal();
+ }
+ if (0 <= idx && idx < (int)candidates.size()) {
+ GameDescriptor result = candidates[idx];
+
+ // TODO: Change the detectors to set "path" !
+ result["path"] = dir.getPath();
+
+ Common::String domain = addGameToConf(result);
+
+ // Display edit dialog for the new entry
+ EditGameDialog editDialog(domain, result.description());
+ if (editDialog.runModal() > 0) {
+ // User pressed OK, so make changes permanent
+
+ // Write config to disk
+ ConfMan.flushToDisk();
+
+ // Update the ListWidget, select the new item, and force a redraw
+ updateListing();
+ selectTarget(editDialog.getDomain());
+ draw();
+ } else {
+ // User aborted, remove the the new domain again
+ ConfMan.removeGameDomain(domain);
+ }
+
+ }
+
+ return true;
+}
+
void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
int item = _list->getSelected();
@@ -1093,7 +617,7 @@ void LauncherDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
loadGameButtonPressed(item);
break;
case kOptionsCmd: {
- GlobalOptionsDialog options;
+ GlobalOptionsDialog options(this);
options.runModal();
}
break;
diff --git a/gui/launcher.h b/gui/launcher.h
index e9c76a5320..58f1c930ed 100644
--- a/gui/launcher.h
+++ b/gui/launcher.h
@@ -51,7 +51,7 @@ public:
virtual void handleKeyDown(Common::KeyState state);
virtual void handleKeyUp(Common::KeyState state);
-
+ bool doGameDetection(const Common::String &path);
protected:
EditTextWidget *_searchWidget;
ListWidget *_list;
diff --git a/gui/module.mk b/gui/module.mk
index 6cbc63d24d..54818c264e 100644
--- a/gui/module.mk
+++ b/gui/module.mk
@@ -6,6 +6,7 @@ MODULE_OBJS := \
console.o \
debugger.o \
dialog.o \
+ editgamedialog.o \
error.o \
EventRecorder.o \
filebrowser-dialog.o \
@@ -24,6 +25,9 @@ MODULE_OBJS := \
ThemeLayout.o \
ThemeParser.o \
Tooltip.o \
+ animation/Animation.o \
+ animation/RepeatAnimationWrapper.o \
+ animation/SequenceAnimationComposite.o \
widget.o \
widgets/editable.o \
widgets/edittext.o \
@@ -54,6 +58,13 @@ MODULE_OBJS += \
endif
endif
+ifdef USE_LIBCURL
+MODULE_OBJS += \
+ downloaddialog.o \
+ remotebrowser.o \
+ storagewizarddialog.o
+endif
+
ifdef ENABLE_EVENTRECORDER
MODULE_OBJS += \
editrecorddialog.o \
diff --git a/gui/object.h b/gui/object.h
index 219bf77f69..1541c35aa8 100644
--- a/gui/object.h
+++ b/gui/object.h
@@ -76,6 +76,8 @@ public:
virtual void setTextDrawableArea(const Common::Rect &r) { _textDrawableArea = r; }
+ virtual int16 getRelX() const { return _x; }
+ virtual int16 getRelY() const { return _y; }
virtual int16 getAbsX() const { return _x; }
virtual int16 getAbsY() const { return _y; }
virtual int16 getChildX() const { return getAbsX(); }
@@ -91,6 +93,10 @@ public:
virtual void removeWidget(Widget *widget);
+ virtual bool isPointIn(int x, int y) {
+ return (x >= _x && x < (_x + _w) && (y >= _y) && (y < _y + _h));
+ }
+
protected:
virtual void releaseFocus() = 0;
};
diff --git a/gui/options.cpp b/gui/options.cpp
index e410971818..3f2674b016 100644
--- a/gui/options.cpp
+++ b/gui/options.cpp
@@ -42,6 +42,18 @@
#include "audio/musicplugin.h"
#include "audio/mixer.h"
#include "audio/fmopl.h"
+#include "widgets/scrollcontainer.h"
+#include "widgets/edittext.h"
+
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#include "gui/downloaddialog.h"
+#include "gui/storagewizarddialog.h"
+#endif
+
+#ifdef USE_SDL_NET
+#include "backends/networking/sdl_net/localwebserver.h"
+#endif
namespace GUI {
@@ -84,6 +96,19 @@ enum {
};
#endif
+#ifdef USE_CLOUD
+enum {
+ kConfigureStorageCmd = 'cfst',
+ kRefreshStorageCmd = 'rfst',
+ kDownloadStorageCmd = 'dlst',
+ kRunServerCmd = 'rnsv',
+ kCloudTabContainerReflowCmd = 'ctcr',
+ kServerPortClearCmd = 'spcl',
+ kChooseRootDirCmd = 'chrp',
+ kRootPathClearCmd = 'clrp'
+};
+#endif
+
static const char *savePeriodLabels[] = { _s("Never"), _s("every 5 mins"), _s("every 10 mins"), _s("every 15 mins"), _s("every 30 mins"), 0 };
static const int savePeriodValues[] = { 0, 5 * 60, 10 * 60, 15 * 60, 30 * 60, -1 };
static const char *outputRateLabels[] = { _s("<default>"), _s("8 kHz"), _s("11 kHz"), _s("22 kHz"), _s("44 kHz"), _s("48 kHz"), 0 };
@@ -1078,8 +1103,8 @@ void OptionsDialog::reflowLayout() {
#pragma mark -
-GlobalOptionsDialog::GlobalOptionsDialog()
- : OptionsDialog(Common::ConfigManager::kApplicationDomain, "GlobalOptions") {
+GlobalOptionsDialog::GlobalOptionsDialog(LauncherDialog *launcher)
+ : OptionsDialog(Common::ConfigManager::kApplicationDomain, "GlobalOptions"), _launcher(launcher) {
// The tab widget
TabWidget *tab = new TabWidget(this, "GlobalOptions.TabWidget");
@@ -1251,6 +1276,76 @@ GlobalOptionsDialog::GlobalOptionsDialog()
new ButtonWidget(tab, "GlobalOptions_Misc.UpdatesCheckManuallyButton", _("Check now"), 0, kUpdatesCheckCmd);
#endif
+#ifdef USE_CLOUD
+ //
+ // 7) The cloud tab
+ //
+ if (g_system->getOverlayWidth() > 320)
+ tab->addTab(_("Cloud"));
+ else
+ tab->addTab(_c("Cloud", "lowres"));
+
+ ScrollContainerWidget *container = new ScrollContainerWidget(tab, "GlobalOptions_Cloud.Container", kCloudTabContainerReflowCmd);
+ container->setTarget(this);
+
+#ifdef USE_LIBCURL
+ _selectedStorageIndex = CloudMan.getStorageIndex();
+#else
+ _selectedStorageIndex = 0;
+#endif
+
+ _storagePopUpDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StoragePopupDesc", _("Storage:"), _("Active cloud storage"));
+ _storagePopUp = new PopUpWidget(container, "GlobalOptions_Cloud_Container.StoragePopup");
+#ifdef USE_LIBCURL
+ Common::StringArray list = CloudMan.listStorages();
+ for (uint32 i = 0; i < list.size(); ++i)
+ _storagePopUp->appendEntry(list[i], i);
+#else
+ _storagePopUp->appendEntry(_("<none>"), 0);
+#endif
+ _storagePopUp->setSelected(_selectedStorageIndex);
+
+ _storageUsernameDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsernameDesc", _("Username:"), _("Username used by this storage"));
+ _storageUsername = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsernameLabel", "<none>");
+
+ _storageUsedSpaceDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsedSpaceDesc", _("Used space:"), _("Space used by ScummVM's saves on this storage"));
+ _storageUsedSpace = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageUsedSpaceLabel", "0 bytes");
+
+ _storageLastSyncDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageLastSyncDesc", _("Last sync time:"), _("When this storage did saves sync last time"));
+ _storageLastSync = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.StorageLastSyncLabel", "<never>");
+
+ _storageConnectButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.ConnectButton", _("Connect"), _("Open wizard dialog to connect your cloud storage account"), kConfigureStorageCmd);
+ _storageRefreshButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RefreshButton", _("Refresh"), _("Refresh current cloud storage information (username and usage)"), kRefreshStorageCmd);
+ _storageDownloadButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.DownloadButton", _("Downloads"), _("Open downloads manager dialog"), kDownloadStorageCmd);
+
+ _runServerButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RunServerButton", _("Run server"), _("Run local webserver"), kRunServerCmd);
+ _serverInfoLabel = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.ServerInfoLabel", _("Not running"));
+
+ // Root path
+ if (g_system->getOverlayWidth() > 320)
+ _rootPathButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RootPathButton", _("/root/ Path:"), _("Specifies where Files Manager can access to"), kChooseRootDirCmd);
+ else
+ _rootPathButton = new ButtonWidget(container, "GlobalOptions_Cloud_Container.RootPathButton", _c("/root/ Path:", "lowres"), _("Specifies where Files Manager can access to"), kChooseRootDirCmd);
+ _rootPath = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.RootPath", "/foo/bar", _("Specifies where Files Manager can access to"));
+
+ _rootPathClearButton = addClearButton(container, "GlobalOptions_Cloud_Container.RootPathClearButton", kRootPathClearCmd);
+
+#ifdef USE_SDL_NET
+ uint32 port = Networking::LocalWebserver::getPort();
+#else
+ uint32 port = 0; // the following widgets are hidden anyway
+#endif
+ _serverPortDesc = new StaticTextWidget(container, "GlobalOptions_Cloud_Container.ServerPortDesc", _("Server's port:"), _("Which port is used by server\nAuth with server is not available with non-default port"));
+ _serverPort = new EditTextWidget(container, "GlobalOptions_Cloud_Container.ServerPortEditText", Common::String::format("%u", port), 0);
+ _serverPortClearButton = addClearButton(container, "GlobalOptions_Cloud_Container.ServerPortClearButton", kServerPortClearCmd);
+
+ setupCloudTab();
+ _redrawCloudTab = false;
+#ifdef USE_SDL_NET
+ _serverWasRunning = false;
+#endif
+#endif
+
// Activate the first tab
tab->setActiveTab(0);
_tabWidget = tab;
@@ -1327,6 +1422,15 @@ void GlobalOptionsDialog::open() {
if (mode == ThemeEngine::kGfxDisabled)
mode = ThemeEngine::_defaultRendererMode;
_rendererPopUp->setSelectedTag(mode);
+
+#ifdef USE_CLOUD
+ Common::String rootPath(ConfMan.get("rootpath", "cloud"));
+ if (rootPath.empty() || !ConfMan.hasKey("rootpath", "cloud")) {
+ _rootPath->setLabel(_c("None", "path"));
+ } else {
+ _rootPath->setLabel(rootPath);
+ }
+#endif
}
void GlobalOptionsDialog::close() {
@@ -1357,6 +1461,14 @@ void GlobalOptionsDialog::close() {
ConfMan.removeKey("pluginspath", _domain);
#endif
+#ifdef USE_CLOUD
+ Common::String rootPath(_rootPath->getLabel());
+ if (!rootPath.empty() && (rootPath != _c("None", "path")))
+ ConfMan.set("rootpath", rootPath, "cloud");
+ else
+ ConfMan.removeKey("rootpath", "cloud");
+#endif
+
ConfMan.setInt("autosave_period", _autosavePeriodPopUp->getSelectedTag(), _domain);
GUI::ThemeEngine::GraphicsMode selected = (GUI::ThemeEngine::GraphicsMode)_rendererPopUp->getSelectedTag();
@@ -1402,7 +1514,38 @@ void GlobalOptionsDialog::close() {
}
#endif
+#ifdef USE_LIBCURL
+ if (CloudMan.getStorageIndex() != _selectedStorageIndex) {
+ if (!CloudMan.switchStorage(_selectedStorageIndex)) {
+ bool anotherStorageIsWorking = CloudMan.isWorking();
+ Common::String message = _("Failed to change cloud storage!");
+ if (anotherStorageIsWorking) {
+ message += "\n";
+ message += _("Current cloud storage is working at the moment.");
+ }
+ MessageDialog dialog(message);
+ dialog.runModal();
+ }
+ }
+#endif
+#ifdef USE_SDL_NET
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ // save server's port
+ uint32 port = Networking::LocalWebserver::getPort();
+ if (_serverPort) {
+ uint64 contents = _serverPort->getEditString().asUint64();
+ if (contents != 0)
+ port = contents;
+ }
+ ConfMan.setInt("local_server_port", port);
+#endif
+#endif
}
+#ifdef USE_SDL_NET
+ if (LocalServer.isRunning()) {
+ LocalServer.stop();
+ }
+#endif
OptionsDialog::close();
}
@@ -1456,6 +1599,21 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
break;
}
#endif
+#ifdef USE_CLOUD
+ case kChooseRootDirCmd: {
+ BrowserDialog browser(_("Select directory for Files Manager /root/"), true);
+ if (browser.runModal() > 0) {
+ // User made his choice...
+ Common::FSNode dir(browser.getResult());
+ Common::String path = dir.getPath();
+ if (path.empty())
+ path = "/"; // absolute root
+ _rootPath->setLabel(path);
+ draw();
+ }
+ break;
+ }
+#endif
case kThemePathClearCmd:
_themePath->setLabel(_c("None", "path"));
break;
@@ -1465,6 +1623,11 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
case kSavePathClearCmd:
_savePath->setLabel(_("Default"));
break;
+#ifdef USE_CLOUD
+ case kRootPathClearCmd:
+ _rootPath->setLabel(_c("None", "path"));
+ break;
+#endif
case kChooseSoundFontCmd: {
BrowserDialog browser(_("Select SoundFont"), false);
if (browser.runModal() > 0) {
@@ -1481,7 +1644,8 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
}
break;
}
- case kChooseThemeCmd: {
+ case kChooseThemeCmd:
+ {
ThemeBrowser browser;
if (browser.runModal() > 0) {
// User made his choice...
@@ -1513,6 +1677,91 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
}
break;
}
+#ifdef USE_CLOUD
+ case kCloudTabContainerReflowCmd:
+ setupCloudTab();
+ break;
+#endif
+#ifdef USE_LIBCURL
+ case kPopUpItemSelectedCmd:
+ {
+ //update container's scrollbar
+ reflowLayout();
+ break;
+ }
+ case kConfigureStorageCmd:
+ {
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ // save server's port
+ uint32 port = Networking::LocalWebserver::getPort();
+ if (_serverPort) {
+ uint64 contents = _serverPort->getEditString().asUint64();
+ if (contents != 0)
+ port = contents;
+ }
+ ConfMan.setInt("local_server_port", port);
+ ConfMan.flushToDisk();
+#endif
+ StorageWizardDialog dialog(_selectedStorageIndex);
+ dialog.runModal();
+ //update container's scrollbar
+ reflowLayout();
+ break;
+ }
+ case kRefreshStorageCmd:
+ {
+ CloudMan.info(
+ new Common::Callback<GlobalOptionsDialog, Cloud::Storage::StorageInfoResponse>(this, &GlobalOptionsDialog::storageInfoCallback),
+ new Common::Callback<GlobalOptionsDialog, Networking::ErrorResponse>(this, &GlobalOptionsDialog::storageErrorCallback)
+ );
+ Common::String dir = CloudMan.savesDirectoryPath();
+ if (dir.lastChar() == '/')
+ dir.deleteLastChar();
+ CloudMan.listDirectory(
+ dir,
+ new Common::Callback<GlobalOptionsDialog, Cloud::Storage::ListDirectoryResponse>(this, &GlobalOptionsDialog::storageListDirectoryCallback),
+ new Common::Callback<GlobalOptionsDialog, Networking::ErrorResponse>(this, &GlobalOptionsDialog::storageErrorCallback)
+ );
+ break;
+ }
+ case kDownloadStorageCmd:
+ {
+ DownloadDialog dialog(_selectedStorageIndex, _launcher);
+ dialog.runModal();
+ break;
+ }
+#endif
+#ifdef USE_SDL_NET
+ case kRunServerCmd:
+ {
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ // save server's port
+ uint32 port = Networking::LocalWebserver::getPort();
+ if (_serverPort) {
+ uint64 contents = _serverPort->getEditString().asUint64();
+ if (contents != 0)
+ port = contents;
+ }
+ ConfMan.setInt("local_server_port", port);
+ ConfMan.flushToDisk();
+#endif
+
+ if (LocalServer.isRunning())
+ LocalServer.stopOnIdle();
+ else
+ LocalServer.start();
+
+ break;
+ }
+
+ case kServerPortClearCmd: {
+ if (_serverPort) {
+ _serverPort->setEditString(Common::String::format("%u", Networking::LocalWebserver::DEFAULT_SERVER_PORT));
+ }
+ draw();
+ break;
+ }
+#endif
#ifdef GUI_ENABLE_KEYSDIALOG
case kChooseKeyMappingCmd:
_keysDialog->runModal();
@@ -1534,6 +1783,23 @@ void GlobalOptionsDialog::handleCommand(CommandSender *sender, uint32 cmd, uint3
}
}
+void GlobalOptionsDialog::handleTickle() {
+ OptionsDialog::handleTickle();
+#ifdef USE_CLOUD
+#ifdef USE_SDL_NET
+ if (LocalServer.isRunning() != _serverWasRunning) {
+ _serverWasRunning = !_serverWasRunning;
+ _redrawCloudTab = true;
+ }
+#endif
+ if (_redrawCloudTab) {
+ setupCloudTab();
+ draw();
+ _redrawCloudTab = false;
+ }
+#endif
+}
+
void GlobalOptionsDialog::reflowLayout() {
int activeTab = _tabWidget->getActiveTab();
@@ -1567,6 +1833,218 @@ void GlobalOptionsDialog::reflowLayout() {
_tabWidget->setActiveTab(activeTab);
OptionsDialog::reflowLayout();
+#ifdef USE_CLOUD
+ setupCloudTab();
+#endif
}
+#ifdef USE_CLOUD
+void GlobalOptionsDialog::setupCloudTab() {
+ int serverLabelPosition = -1; //no override
+#ifdef USE_LIBCURL
+ _selectedStorageIndex = _storagePopUp->getSelectedTag();
+
+ if (_storagePopUpDesc) _storagePopUpDesc->setVisible(true);
+ if (_storagePopUp) _storagePopUp->setVisible(true);
+
+ bool shown = (_selectedStorageIndex != Cloud::kStorageNoneId);
+ if (_storageUsernameDesc) _storageUsernameDesc->setVisible(shown);
+ if (_storageUsername) {
+ Common::String username = CloudMan.getStorageUsername(_selectedStorageIndex);
+ if (username == "")
+ username = _("<none>");
+ _storageUsername->setLabel(username);
+ _storageUsername->setVisible(shown);
+ }
+ if (_storageUsedSpaceDesc) _storageUsedSpaceDesc->setVisible(shown);
+ if (_storageUsedSpace) {
+ uint64 usedSpace = CloudMan.getStorageUsedSpace(_selectedStorageIndex);
+ _storageUsedSpace->setLabel(Common::String::format(_("%llu bytes"), usedSpace));
+ _storageUsedSpace->setVisible(shown);
+ }
+ if (_storageLastSyncDesc) _storageLastSyncDesc->setVisible(shown);
+ if (_storageLastSync) {
+ Common::String sync = CloudMan.getStorageLastSync(_selectedStorageIndex);
+ if (sync == "") {
+ if (_selectedStorageIndex == CloudMan.getStorageIndex() && CloudMan.isSyncing())
+ sync = _("<right now>");
+ else
+ sync = _("<never>");
+ }
+ _storageLastSync->setLabel(sync);
+ _storageLastSync->setVisible(shown);
+ }
+ if (_storageConnectButton)
+ _storageConnectButton->setVisible(shown);
+ if (_storageRefreshButton)
+ _storageRefreshButton->setVisible(shown && _selectedStorageIndex == CloudMan.getStorageIndex());
+ if (_storageDownloadButton)
+ _storageDownloadButton->setVisible(shown && _selectedStorageIndex == CloudMan.getStorageIndex());
+ if (!shown)
+ serverLabelPosition = (_storageUsernameDesc ? _storageUsernameDesc->getRelY() : 0);
+#else
+ _selectedStorageIndex = 0;
+
+ if (_storagePopUpDesc)
+ _storagePopUpDesc->setVisible(false);
+ if (_storagePopUp)
+ _storagePopUp->setVisible(false);
+ if (_storageUsernameDesc)
+ _storageUsernameDesc->setVisible(false);
+ if (_storageUsernameDesc)
+ _storageUsernameDesc->setVisible(false);
+ if (_storageUsername)
+ _storageUsername->setVisible(false);
+ if (_storageUsedSpaceDesc)
+ _storageUsedSpaceDesc->setVisible(false);
+ if (_storageUsedSpace)
+ _storageUsedSpace->setVisible(false);
+ if (_storageLastSyncDesc)
+ _storageLastSyncDesc->setVisible(false);
+ if (_storageLastSync)
+ _storageLastSync->setVisible(false);
+ if (_storageConnectButton)
+ _storageConnectButton->setVisible(false);
+ if (_storageRefreshButton)
+ _storageRefreshButton->setVisible(false);
+ if (_storageDownloadButton)
+ _storageDownloadButton->setVisible(false);
+
+ serverLabelPosition = (_storagePopUpDesc ? _storagePopUpDesc->getRelY() : 0);
+#endif
+#ifdef USE_SDL_NET
+ //determine original widget's positions
+ int16 x, y;
+ uint16 w, h;
+ int serverButtonY, serverInfoY;
+ int serverRootButtonY, serverRootY, serverRootClearButtonY;
+ int serverPortDescY, serverPortY, serverPortClearButtonY;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RunServerButton", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.RunServerButton's position is undefined");
+ serverButtonY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerInfoLabel", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.ServerInfoLabel's position is undefined");
+ serverInfoY = y;
+
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPathButton", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.RootPathButton's position is undefined");
+ serverRootButtonY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPath", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.RootPath's position is undefined");
+ serverRootY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.RootPathClearButton", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.RootPathClearButton's position is undefined");
+ serverRootClearButtonY = y;
+
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortDesc", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.ServerPortDesc's position is undefined");
+ serverPortDescY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortEditText", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.ServerPortEditText's position is undefined");
+ serverPortY = y;
+ if (!g_gui.xmlEval()->getWidgetData("GlobalOptions_Cloud_Container.ServerPortClearButton", x, y, w, h))
+ warning("GlobalOptions_Cloud_Container.ServerPortClearButton's position is undefined");
+ serverPortClearButtonY = y;
+
+ bool serverIsRunning = LocalServer.isRunning();
+
+ if (serverLabelPosition < 0)
+ serverLabelPosition = serverInfoY;
+ if (_runServerButton) {
+ _runServerButton->setVisible(true);
+ _runServerButton->setPos(_runServerButton->getRelX(), serverLabelPosition + serverButtonY - serverInfoY);
+ _runServerButton->setLabel(_(serverIsRunning ? "Stop server" : "Run server"));
+ _runServerButton->setTooltip(_(serverIsRunning ? "Stop local webserver" : "Run local webserver"));
+ }
+ if (_serverInfoLabel) {
+ _serverInfoLabel->setVisible(true);
+ _serverInfoLabel->setPos(_serverInfoLabel->getRelX(), serverLabelPosition);
+ if (serverIsRunning)
+ _serverInfoLabel->setLabel(LocalServer.getAddress());
+ else
+ _serverInfoLabel->setLabel(_("Not running"));
+ }
+ if (_rootPathButton) {
+ _rootPathButton->setVisible(true);
+ _rootPathButton->setPos(_rootPathButton->getRelX(), serverLabelPosition + serverRootButtonY - serverInfoY);
+ }
+ if (_rootPath) {
+ _rootPath->setVisible(true);
+ _rootPath->setPos(_rootPath->getRelX(), serverLabelPosition + serverRootY - serverInfoY);
+ }
+ if (_rootPathClearButton) {
+ _rootPathClearButton->setVisible(true);
+ _rootPathClearButton->setPos(_rootPathClearButton->getRelX(), serverLabelPosition + serverRootClearButtonY - serverInfoY);
+ }
+#ifdef NETWORKING_LOCALWEBSERVER_ENABLE_PORT_OVERRIDE
+ if (_serverPortDesc) {
+ _serverPortDesc->setVisible(true);
+ _serverPortDesc->setPos(_serverPortDesc->getRelX(), serverLabelPosition + serverPortDescY - serverInfoY);
+ _serverPortDesc->setEnabled(!serverIsRunning);
+ }
+ if (_serverPort) {
+ _serverPort->setVisible(true);
+ _serverPort->setPos(_serverPort->getRelX(), serverLabelPosition + serverPortY - serverInfoY);
+ _serverPort->setEnabled(!serverIsRunning);
+ }
+ if (_serverPortClearButton) {
+ _serverPortClearButton->setVisible(true);
+ _serverPortClearButton->setPos(_serverPortClearButton->getRelX(), serverLabelPosition + serverPortClearButtonY - serverInfoY);
+ _serverPortClearButton->setEnabled(!serverIsRunning);
+ }
+#else
+ if (_serverPortDesc)
+ _serverPortDesc->setVisible(false);
+ if (_serverPort)
+ _serverPort->setVisible(false);
+ if (_serverPortClearButton)
+ _serverPortClearButton->setVisible(false);
+#endif
+#else
+ if (_runServerButton)
+ _runServerButton->setVisible(false);
+ if (_serverInfoLabel)
+ _serverInfoLabel->setVisible(false);
+ if (_rootPathButton)
+ _rootPathButton->setVisible(false);
+ if (_rootPath)
+ _rootPath->setVisible(false);
+ if (_rootPathClearButton)
+ _rootPathClearButton->setVisible(false);
+ if (_serverPortDesc)
+ _serverPortDesc->setVisible(false);
+ if (_serverPort)
+ _serverPort->setVisible(false);
+ if (_serverPortClearButton)
+ _serverPortClearButton->setVisible(false);
+#endif
+}
+#endif
+#ifdef USE_LIBCURL
+void GlobalOptionsDialog::storageInfoCallback(Cloud::Storage::StorageInfoResponse response) {
+ //we could've used response.value.email()
+ //but Storage already notified CloudMan
+ //so we just set the flag to redraw our cloud tab
+ _redrawCloudTab = true;
+}
+
+void GlobalOptionsDialog::storageListDirectoryCallback(Cloud::Storage::ListDirectoryResponse response) {
+ Common::Array<Cloud::StorageFile> &files = response.value;
+ uint64 totalSize = 0;
+ for (uint32 i = 0; i < files.size(); ++i)
+ if (!files[i].isDirectory())
+ totalSize += files[i].size();
+ CloudMan.setStorageUsedSpace(CloudMan.getStorageIndex(), totalSize);
+ _redrawCloudTab = true;
+}
+
+void GlobalOptionsDialog::storageErrorCallback(Networking::ErrorResponse response) {
+ debug(9, "GlobalOptionsDialog: error response (%s, %ld):", (response.failed ? "failed" : "interrupted"), response.httpResponseCode);
+ debug(9, "%s", response.response.c_str());
+
+ if (!response.interrupted)
+ g_system->displayMessageOnOSD(_("Request failed.\nCheck your Internet connection."));
+}
+#endif
+
} // End of namespace GUI
diff --git a/gui/options.h b/gui/options.h
index 294b41794b..03dbdac492 100644
--- a/gui/options.h
+++ b/gui/options.h
@@ -37,9 +37,15 @@
#include "gui/fluidsynth-dialog.h"
#endif
+#ifdef USE_LIBCURL
+#include "backends/cloud/storage.h"
+#endif
+
namespace GUI {
+class LauncherDialog;
class CheckboxWidget;
+class EditTextWidget;
class PopUpWidget;
class SliderWidget;
class StaticTextWidget;
@@ -200,16 +206,18 @@ protected:
class GlobalOptionsDialog : public OptionsDialog {
public:
- GlobalOptionsDialog();
+ GlobalOptionsDialog(LauncherDialog *launcher);
~GlobalOptionsDialog();
void open();
void close();
void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ void handleTickle();
virtual void reflowLayout();
protected:
+ LauncherDialog *_launcher;
#ifdef GUI_ENABLE_KEYSDIALOG
KeysDialog *_keysDialog;
#endif
@@ -241,6 +249,43 @@ protected:
StaticTextWidget *_updatesPopUpDesc;
PopUpWidget *_updatesPopUp;
#endif
+
+#ifdef USE_CLOUD
+ //
+ // Cloud controls
+ //
+ uint32 _selectedStorageIndex;
+ StaticTextWidget *_storagePopUpDesc;
+ PopUpWidget *_storagePopUp;
+ StaticTextWidget *_storageUsernameDesc;
+ StaticTextWidget *_storageUsername;
+ StaticTextWidget *_storageUsedSpaceDesc;
+ StaticTextWidget *_storageUsedSpace;
+ StaticTextWidget *_storageLastSyncDesc;
+ StaticTextWidget *_storageLastSync;
+ ButtonWidget *_storageConnectButton;
+ ButtonWidget *_storageRefreshButton;
+ ButtonWidget *_storageDownloadButton;
+ ButtonWidget *_runServerButton;
+ StaticTextWidget *_serverInfoLabel;
+ ButtonWidget *_rootPathButton;
+ StaticTextWidget *_rootPath;
+ ButtonWidget *_rootPathClearButton;
+ StaticTextWidget *_serverPortDesc;
+ EditTextWidget *_serverPort;
+ ButtonWidget *_serverPortClearButton;
+ bool _redrawCloudTab;
+#ifdef USE_SDL_NET
+ bool _serverWasRunning;
+#endif
+
+ void setupCloudTab();
+#endif
+#ifdef USE_LIBCURL
+ void storageInfoCallback(Cloud::Storage::StorageInfoResponse response);
+ void storageListDirectoryCallback(Cloud::Storage::ListDirectoryResponse response);
+ void storageErrorCallback(Networking::ErrorResponse response);
+#endif
};
} // End of namespace GUI
diff --git a/gui/remotebrowser.cpp b/gui/remotebrowser.cpp
new file mode 100644
index 0000000000..b87fc60f7e
--- /dev/null
+++ b/gui/remotebrowser.cpp
@@ -0,0 +1,232 @@
+/* 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 "gui/remotebrowser.h"
+#include "gui/widgets/list.h"
+
+#include "common/config-manager.h"
+#include "common/system.h"
+#include "common/algorithm.h"
+
+#include "common/translation.h"
+#include <backends/networking/curl/request.h>
+#include <backends/cloud/storage.h>
+#include <backends/cloud/cloudmanager.h>
+#include "message.h"
+
+namespace GUI {
+
+enum {
+ kChooseCmd = 'Chos',
+ kGoUpCmd = 'GoUp'
+};
+
+RemoteBrowserDialog::RemoteBrowserDialog(const char *title):
+ Dialog("Browser"), _navigationLocked(false), _updateList(false), _showError(false),
+ _workingRequest(nullptr), _ignoreCallback(false) {
+ _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
+
+ new StaticTextWidget(this, "Browser.Headline", title);
+ _currentPath = new StaticTextWidget(this, "Browser.Path", "DUMMY");
+
+ _fileList = new ListWidget(this, "Browser.List");
+ _fileList->setNumberingMode(kListNumberingOff);
+ _fileList->setEditable(false);
+
+ if (g_system->getOverlayWidth() > 320)
+ new ButtonWidget(this, "Browser.Up", _("Go up"), _("Go to previous directory level"), kGoUpCmd);
+ else
+ new ButtonWidget(this, "Browser.Up", _c("Go up", "lowres"), _("Go to previous directory level"), kGoUpCmd);
+ new ButtonWidget(this, "Browser.Cancel", _("Cancel"), 0, kCloseCmd);
+ new ButtonWidget(this, "Browser.Choose", _("Choose"), 0, kChooseCmd);
+}
+
+RemoteBrowserDialog::~RemoteBrowserDialog() {
+ if (_workingRequest) {
+ _ignoreCallback = true;
+ _workingRequest->finish();
+ }
+}
+
+void RemoteBrowserDialog::open() {
+ Dialog::open();
+ listDirectory(Cloud::StorageFile());
+}
+
+void RemoteBrowserDialog::close() {
+ Dialog::close();
+ if (_workingRequest) {
+ _ignoreCallback = true;
+ _workingRequest->finish();
+ _ignoreCallback = false;
+ }
+}
+
+void RemoteBrowserDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kChooseCmd: {
+ // If nothing is selected in the list widget, choose the current dir.
+ // Else, choose the dir that is selected.
+ int selection = _fileList->getSelected();
+ if (selection >= 0)
+ _choice = _nodeContent[selection];
+ else
+ _choice = _node;
+ setResult(1);
+ close();
+ break;
+ }
+ case kGoUpCmd:
+ goUp();
+ break;
+ case kListItemActivatedCmd:
+ case kListItemDoubleClickedCmd:
+ if (_nodeContent[data].isDirectory()) {
+ _rememberedNodeContents[_node.path()] = _nodeContent;
+ listDirectory(_nodeContent[data]);
+ }
+ break;
+ case kListSelectionChangedCmd:
+ // We do not allow selecting directories,
+ // thus we will invalidate the selection
+ // when the user selects a directory over here.
+ if (data != (uint32)-1 && !_nodeContent[data].isDirectory())
+ _fileList->setSelected(-1);
+ break;
+ default:
+ Dialog::handleCommand(sender, cmd, data);
+ }
+}
+
+void RemoteBrowserDialog::handleTickle() {
+ if (_updateList) {
+ updateListing();
+ _updateList = false;
+ }
+
+ if (_showError) {
+ _showError = false;
+ MessageDialog alert(_("ScummVM couldn't list the directory!"));
+ alert.runModal();
+ }
+
+ Dialog::handleTickle();
+}
+
+void RemoteBrowserDialog::updateListing() {
+ // Update the path display
+ Common::String path = _node.path();
+ if (path.empty())
+ path = "/"; //root
+ if (_navigationLocked)
+ path = "Loading... " + path;
+ _currentPath->setLabel(path);
+
+ if (!_navigationLocked) {
+ // Populate the ListWidget
+ ListWidget::StringArray list;
+ ListWidget::ColorList colors;
+ for (Common::Array<Cloud::StorageFile>::iterator i = _nodeContent.begin(); i != _nodeContent.end(); ++i) {
+ if (i->isDirectory()) {
+ list.push_back(i->name() + "/");
+ colors.push_back(ThemeEngine::kFontColorNormal);
+ } else {
+ list.push_back(i->name());
+ colors.push_back(ThemeEngine::kFontColorAlternate);
+ }
+ }
+
+ _fileList->setList(list, &colors);
+ _fileList->scrollTo(0);
+ }
+
+ _fileList->setEnabled(!_navigationLocked);
+
+ // Finally, redraw
+ draw();
+}
+
+void RemoteBrowserDialog::goUp() {
+ if (_rememberedNodeContents.contains(_node.path()))
+ _rememberedNodeContents.erase(_node.path());
+
+ Common::String path = _node.path();
+ if (path.size() && (path.lastChar() == '/' || path.lastChar() == '\\'))
+ path.deleteLastChar();
+ if (path.empty()) {
+ _rememberedNodeContents.erase("");
+ } else {
+ for (int i = path.size() - 1; i >= 0; --i)
+ if (i == 0 || path[i] == '/' || path[i] == '\\') {
+ path.erase(i);
+ break;
+ }
+ }
+
+ listDirectory(Cloud::StorageFile(path, 0, 0, true));
+}
+
+void RemoteBrowserDialog::listDirectory(Cloud::StorageFile node) {
+ if (_navigationLocked || _workingRequest)
+ return;
+
+ if (_rememberedNodeContents.contains(node.path())) {
+ _nodeContent = _rememberedNodeContents[node.path()];
+ } else {
+ _navigationLocked = true;
+
+ _workingRequest = CloudMan.listDirectory(
+ node.path(),
+ new Common::Callback<RemoteBrowserDialog, Cloud::Storage::ListDirectoryResponse>(this, &RemoteBrowserDialog::directoryListedCallback),
+ new Common::Callback<RemoteBrowserDialog, Networking::ErrorResponse>(this, &RemoteBrowserDialog::directoryListedErrorCallback),
+ false
+ );
+ }
+
+ _backupNode = _node;
+ _node = node;
+ updateListing();
+}
+
+void RemoteBrowserDialog::directoryListedCallback(Cloud::Storage::ListDirectoryResponse response) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ _navigationLocked = false;
+ _nodeContent = response.value;
+ Common::sort(_nodeContent.begin(), _nodeContent.end(), FileListOrder());
+ _updateList = true;
+}
+
+void RemoteBrowserDialog::directoryListedErrorCallback(Networking::ErrorResponse error) {
+ _workingRequest = nullptr;
+ if (_ignoreCallback)
+ return;
+
+ _navigationLocked = false;
+ _node = _backupNode;
+ _updateList = true;
+ _showError = true;
+}
+
+} // End of namespace GUI
diff --git a/gui/remotebrowser.h b/gui/remotebrowser.h
new file mode 100644
index 0000000000..190d8c6895
--- /dev/null
+++ b/gui/remotebrowser.h
@@ -0,0 +1,84 @@
+/* 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 GUI_REMOTEBROWSER_DIALOG_H
+#define GUI_REMOTEBROWSER_DIALOG_H
+
+#include "gui/dialog.h"
+#include "common/fs.h"
+#include <backends/cloud/storagefile.h>
+#include <backends/networking/curl/request.h>
+#include <backends/cloud/storage.h>
+
+namespace GUI {
+
+class ListWidget;
+class StaticTextWidget;
+class CheckboxWidget;
+class CommandSender;
+
+class RemoteBrowserDialog : public Dialog {
+public:
+ RemoteBrowserDialog(const char *title);
+ virtual ~RemoteBrowserDialog();
+
+ virtual void open();
+ virtual void close();
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleTickle();
+
+ const Cloud::StorageFile &getResult() { return _choice; }
+
+protected:
+ ListWidget *_fileList;
+ StaticTextWidget *_currentPath;
+ Cloud::StorageFile _node, _backupNode;
+ Common::Array<Cloud::StorageFile> _nodeContent;
+ Common::HashMap<Common::String, Common::Array<Cloud::StorageFile> > _rememberedNodeContents;
+ Cloud::StorageFile _choice;
+ bool _navigationLocked;
+ bool _updateList;
+ bool _showError;
+
+ Networking::Request *_workingRequest;
+ bool _ignoreCallback;
+
+ void updateListing();
+ void goUp();
+ void listDirectory(Cloud::StorageFile node);
+ void directoryListedCallback(Cloud::Storage::ListDirectoryResponse response);
+ void directoryListedErrorCallback(Networking::ErrorResponse error);
+
+ struct FileListOrder : public Common::BinaryFunction<Cloud::StorageFile, Cloud::StorageFile, bool> {
+ bool operator()(const Cloud::StorageFile &x, const Cloud::StorageFile &y) const {
+ if (x.isDirectory() != y.isDirectory()) {
+ return x.isDirectory(); //x < y (directory < not directory) or x > y (not directory > directory)
+ }
+
+ return x.name() < y.name();
+ }
+ };
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/saveload-dialog.cpp b/gui/saveload-dialog.cpp
index 3d4adfff2b..ae3612f778 100644
--- a/gui/saveload-dialog.cpp
+++ b/gui/saveload-dialog.cpp
@@ -21,6 +21,13 @@
*/
#include "gui/saveload-dialog.h"
+
+#ifdef USE_LIBCURL
+#include "backends/cloud/cloudmanager.h"
+#include "backends/cloud/savessyncrequest.h"
+#include "backends/networking/curl/connectionmanager.h"
+#endif
+
#include "common/translation.h"
#include "common/config-manager.h"
@@ -30,9 +37,68 @@
#include "gui/widgets/edittext.h"
#include "graphics/scaler.h"
+#include <common/savefile.h>
namespace GUI {
+#ifdef USE_LIBCURL
+
+enum {
+ kCancelSyncCmd = 'PDCS',
+ kBackgroundSyncCmd = 'PDBS'
+};
+
+SaveLoadCloudSyncProgressDialog::SaveLoadCloudSyncProgressDialog(bool canRunInBackground): Dialog("SaveLoadCloudSyncProgress"), _close(false) {
+ _label = new StaticTextWidget(this, "SaveLoadCloudSyncProgress.TitleText", "Downloading saves...");
+ uint32 progress = (uint32)(100 * CloudMan.getSyncDownloadingProgress());
+ _progressBar = new SliderWidget(this, "SaveLoadCloudSyncProgress.ProgressBar");
+ _progressBar->setMinValue(0);
+ _progressBar->setMaxValue(100);
+ _progressBar->setValue(progress);
+ _progressBar->setEnabled(false);
+ _percentLabel = new StaticTextWidget(this, "SaveLoadCloudSyncProgress.PercentText", Common::String::format("%u %%", progress));
+ new ButtonWidget(this, "SaveLoadCloudSyncProgress.Cancel", "Cancel", 0, kCancelSyncCmd, Common::ASCII_ESCAPE); // Cancel dialog
+ ButtonWidget *backgroundButton = new ButtonWidget(this, "SaveLoadCloudSyncProgress.Background", "Run in background", 0, kBackgroundSyncCmd, Common::ASCII_RETURN); // Confirm dialog
+ backgroundButton->setEnabled(canRunInBackground);
+ draw();
+}
+
+SaveLoadCloudSyncProgressDialog::~SaveLoadCloudSyncProgressDialog() {
+ CloudMan.setSyncTarget(nullptr); //not that dialog, at least
+}
+
+void SaveLoadCloudSyncProgressDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch(cmd) {
+ case kSavesSyncProgressCmd:
+ _percentLabel->setLabel(Common::String::format("%u%%", data));
+ _progressBar->setValue(data);
+ _progressBar->draw();
+ break;
+
+ case kCancelSyncCmd:
+ setResult(kCancelSyncCmd);
+ close();
+ break;
+
+ case kSavesSyncEndedCmd:
+ case kBackgroundSyncCmd:
+ _close = true;
+ break;
+ }
+
+ Dialog::handleCommand(sender, cmd, data);
+}
+
+void SaveLoadCloudSyncProgressDialog::handleTickle() {
+ if (_close) {
+ setResult(kBackgroundSyncCmd);
+ close();
+ }
+
+ Dialog::handleTickle();
+}
+#endif
+
#ifndef DISABLE_SAVELOADCHOOSER_GRID
SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine &metaEngine) {
const Common::String &userConfig = ConfMan.get("gui_saveload_chooser", Common::ConfigManager::kApplicationDomain);
@@ -45,9 +111,9 @@ SaveLoadChooserType getRequestedSaveLoadDialog(const MetaEngine &metaEngine) {
g_gui.checkScreenChange();
if (g_gui.getWidth() >= 640 && g_gui.getHeight() >= 400
- && metaEngine.hasFeature(MetaEngine::kSavesSupportMetaInfo)
- && metaEngine.hasFeature(MetaEngine::kSavesSupportThumbnail)
- && userConfig.equalsIgnoreCase("grid")) {
+ && metaEngine.hasFeature(MetaEngine::kSavesSupportMetaInfo)
+ && metaEngine.hasFeature(MetaEngine::kSavesSupportThumbnail)
+ && userConfig.equalsIgnoreCase("grid")) {
// In case we are 640x400 or higher, this dialog is not in save mode,
// the user requested the grid dialog and the engines supports it we
// try to set it up.
@@ -66,7 +132,8 @@ enum {
SaveLoadChooserDialog::SaveLoadChooserDialog(const Common::String &dialogName, const bool saveMode)
: Dialog(dialogName), _metaEngine(0), _delSupport(false), _metaInfoSupport(false),
- _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode)
+ _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode),
+ _dialogWasShown(false)
#ifndef DISABLE_SAVELOADCHOOSER_GRID
, _listButton(0), _gridButton(0)
#endif // !DISABLE_SAVELOADCHOOSER_GRID
@@ -78,7 +145,8 @@ SaveLoadChooserDialog::SaveLoadChooserDialog(const Common::String &dialogName, c
SaveLoadChooserDialog::SaveLoadChooserDialog(int x, int y, int w, int h, const bool saveMode)
: Dialog(x, y, w, h), _metaEngine(0), _delSupport(false), _metaInfoSupport(false),
- _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode)
+ _thumbnailSupport(false), _saveDateSupport(false), _playTimeSupport(false), _saveMode(saveMode),
+ _dialogWasShown(false)
#ifndef DISABLE_SAVELOADCHOOSER_GRID
, _listButton(0), _gridButton(0)
#endif // !DISABLE_SAVELOADCHOOSER_GRID
@@ -88,12 +156,27 @@ SaveLoadChooserDialog::SaveLoadChooserDialog(int x, int y, int w, int h, const b
#endif // !DISABLE_SAVELOADCHOOSER_GRID
}
+SaveLoadChooserDialog::~SaveLoadChooserDialog() {
+#ifdef USE_LIBCURL
+ CloudMan.setSyncTarget(nullptr); //not that dialog, at least
+#endif
+}
+
void SaveLoadChooserDialog::open() {
Dialog::open();
// So that quitting ScummVM will not cause the dialog result to say a
// saved game was selected.
setResult(-1);
+
+ _dialogWasShown = false;
+}
+
+void SaveLoadChooserDialog::close() {
+#ifdef USE_LIBCURL
+ CloudMan.setSyncTarget(nullptr); //not that dialog, at least
+#endif
+ Dialog::close();
}
int SaveLoadChooserDialog::run(const Common::String &target, const MetaEngine *metaEngine) {
@@ -132,9 +215,53 @@ void SaveLoadChooserDialog::handleCommand(CommandSender *sender, uint32 cmd, uin
}
#endif // !DISABLE_SAVELOADCHOOSER_GRID
+#ifdef USE_LIBCURL
+ if (cmd == kSavesSyncProgressCmd || cmd == kSavesSyncEndedCmd) {
+ //this dialog only gets these commands if the progress dialog was shown and user clicked "run in background"
+ return updateSaveList();
+ }
+#endif
+
return Dialog::handleCommand(sender, cmd, data);
}
+#ifdef USE_LIBCURL
+void SaveLoadChooserDialog::runSaveSync(bool hasSavepathOverride) {
+ if (!CloudMan.isSyncing()) {
+ if (hasSavepathOverride) {
+ ConnMan.showCloudDisabledIcon();
+ } else {
+ Cloud::SavesSyncRequest *request = CloudMan.syncSaves();
+ if (request)
+ request->setTarget(this);
+ }
+ }
+}
+#endif
+
+void SaveLoadChooserDialog::handleTickle() {
+#ifdef USE_LIBCURL
+ if (!_dialogWasShown && CloudMan.isSyncing()) {
+ Common::Array<Common::String> files = CloudMan.getSyncingFiles();
+ if (!files.empty()) {
+ {
+ SaveLoadCloudSyncProgressDialog dialog(_metaEngine ? _metaEngine->hasFeature(MetaEngine::kSimpleSavesNames) : false);
+ CloudMan.setSyncTarget(&dialog);
+ int result = dialog.runModal();
+ if (result == kCancelSyncCmd) {
+ CloudMan.cancelSync();
+ }
+ }
+ //dialog changes syncTarget to nullptr after that }
+ CloudMan.setSyncTarget(this);
+ _dialogWasShown = true;
+ updateSaveList();
+ }
+ }
+#endif
+ Dialog::handleTickle();
+}
+
void SaveLoadChooserDialog::reflowLayout() {
#ifndef DISABLE_SAVELOADCHOOSER_GRID
addChooserButtons();
@@ -152,6 +279,46 @@ void SaveLoadChooserDialog::reflowLayout() {
Dialog::reflowLayout();
}
+void SaveLoadChooserDialog::updateSaveList() {
+#ifdef USE_LIBCURL
+ Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing
+ g_system->getSavefileManager()->updateSavefilesList(files);
+#endif
+ listSaves();
+}
+
+void SaveLoadChooserDialog::listSaves() {
+ if (!_metaEngine) return; //very strange
+ _saveList = _metaEngine->listSaves(_target.c_str());
+
+#ifdef USE_LIBCURL
+ //if there is Cloud support, add currently synced files as "locked" saves in the list
+ if (_metaEngine->hasFeature(MetaEngine::kSimpleSavesNames)) {
+ Common::String pattern = _target + ".###";
+ Common::Array<Common::String> files = CloudMan.getSyncingFiles(); //returns empty array if not syncing
+ for (uint32 i = 0; i < files.size(); ++i) {
+ if (!files[i].matchString(pattern, true))
+ continue;
+
+ //make up some slot number
+ int slotNum = 0;
+ for (uint32 j = (files[i].size() > 3 ? files[i].size() - 3 : 0); j < files[i].size(); ++j) { //3 last chars
+ char c = files[i][j];
+ if (c < '0' || c > '9')
+ continue;
+ slotNum = slotNum * 10 + (c - '0');
+ }
+
+ SaveStateDescriptor slot(slotNum, files[i]);
+ slot.setLocked(true);
+ _saveList.push_back(slot);
+ }
+
+ Common::sort(_saveList.begin(), _saveList.end(), SaveStateDescriptorSlotComparator());
+ }
+#endif
+}
+
#ifndef DISABLE_SAVELOADCHOOSER_GRID
void SaveLoadChooserDialog::addChooserButtons() {
if (_listButton) {
@@ -353,6 +520,7 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) {
bool isDeletable = _delSupport;
bool isWriteProtected = false;
bool startEditMode = _list->isEditable();
+ bool isLocked = false;
// We used to support letting the themes specify the fill color with our
// initial theme based GUI. But this support was dropped.
@@ -362,10 +530,11 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) {
_playtime->setLabel(_("No playtime saved"));
if (selItem >= 0 && _metaInfoSupport) {
- SaveStateDescriptor desc = _metaEngine->querySaveMetaInfos(_target.c_str(), _saveList[selItem].getSaveSlot());
+ SaveStateDescriptor desc = (_saveList[selItem].getLocked() ? _saveList[selItem] : _metaEngine->querySaveMetaInfos(_target.c_str(), _saveList[selItem].getSaveSlot()));
isDeletable = desc.getDeletableFlag() && _delSupport;
isWriteProtected = desc.getWriteProtectedFlag();
+ isLocked = desc.getLocked();
// Don't allow the user to change the description of write protected games
if (isWriteProtected)
@@ -398,9 +567,9 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) {
if (_list->isEditable()) {
- // Disable the save button if nothing is selected, or if the selected
- // game is write protected
- _chooseButton->setEnabled(selItem >= 0 && !isWriteProtected);
+ // Disable the save button if slot is locked, nothing is selected,
+ // or if the selected game is write protected
+ _chooseButton->setEnabled(!isLocked && selItem >= 0 && !isWriteProtected);
if (startEditMode) {
_list->startEditMode();
@@ -412,13 +581,13 @@ void SaveLoadChooserSimple::updateSelection(bool redraw) {
}
}
} else {
- // Disable the load button if nothing is selected, or if an empty
- // list item is selected.
- _chooseButton->setEnabled(selItem >= 0 && !_list->getSelectedString().empty());
+ // Disable the load button if slot is locked, nothing is selected,
+ // or if an empty list item is selected.
+ _chooseButton->setEnabled(!isLocked && selItem >= 0 && !_list->getSelectedString().empty());
}
// Delete will always be disabled if the engine doesn't support it.
- _deleteButton->setEnabled(isDeletable && (selItem >= 0) && (!_list->getSelectedString().empty()));
+ _deleteButton->setEnabled(isDeletable && !isLocked && (selItem >= 0) && (!_list->getSelectedString().empty()));
if (redraw) {
_gfxWidget->draw();
@@ -463,7 +632,7 @@ void SaveLoadChooserSimple::close() {
}
void SaveLoadChooserSimple::updateSaveList() {
- _saveList = _metaEngine->listSaves(_target.c_str());
+ SaveLoadChooserDialog::updateSaveList();
int curSlot = 0;
int saveSlot = 0;
@@ -496,7 +665,7 @@ void SaveLoadChooserSimple::updateSaveList() {
description = _("Untitled savestate");
colors.push_back(ThemeEngine::kFontColorAlternate);
} else {
- colors.push_back(ThemeEngine::kFontColorNormal);
+ colors.push_back((x->getLocked() ? ThemeEngine::kFontColorAlternate : ThemeEngine::kFontColorNormal));
}
saveNames.push_back(description);
@@ -524,6 +693,7 @@ void SaveLoadChooserSimple::updateSaveList() {
}
_list->setList(saveNames, &colors);
+ draw();
}
// SaveLoadChooserGrid implementation
@@ -619,10 +789,16 @@ void SaveLoadChooserGrid::handleMouseWheel(int x, int y, int direction) {
}
}
+void SaveLoadChooserGrid::updateSaveList() {
+ SaveLoadChooserDialog::updateSaveList();
+ updateSaves();
+ draw();
+}
+
void SaveLoadChooserGrid::open() {
SaveLoadChooserDialog::open();
- _saveList = _metaEngine->listSaves(_target.c_str());
+ listSaves();
_resultString.clear();
// Load information to restore the last page the user had open.
@@ -863,7 +1039,7 @@ void SaveLoadChooserGrid::updateSaves() {
for (uint i = _curPage * _entriesPerPage, curNum = 0; i < _saveList.size() && curNum < _entriesPerPage; ++i, ++curNum) {
const uint saveSlot = _saveList[i].getSaveSlot();
- SaveStateDescriptor desc = _metaEngine->querySaveMetaInfos(_target.c_str(), saveSlot);
+ SaveStateDescriptor desc = (_saveList[i].getLocked() ? _saveList[i] : _metaEngine->querySaveMetaInfos(_target.c_str(), saveSlot));
SlotButton &curButton = _buttons[curNum];
curButton.setVisible(true);
const Graphics::Surface *thumbnail = desc.getThumbnail();
@@ -908,6 +1084,10 @@ void SaveLoadChooserGrid::updateSaves() {
} else {
curButton.button->setEnabled(true);
}
+
+ //that would make it look "disabled" if slot is locked
+ curButton.button->setEnabled(!desc.getLocked());
+ curButton.description->setEnabled(!desc.getLocked());
}
const uint numPages = (_entriesPerPage != 0 && !_saveList.empty()) ? ((_saveList.size() + _entriesPerPage - 1) / _entriesPerPage) : 1;
diff --git a/gui/saveload-dialog.h b/gui/saveload-dialog.h
index 31f28f6452..fb2833355f 100644
--- a/gui/saveload-dialog.h
+++ b/gui/saveload-dialog.h
@@ -30,6 +30,25 @@
namespace GUI {
+#ifdef USE_LIBCURL
+enum SaveLoadCloudSyncProgress {
+ kSavesSyncProgressCmd = 'SSPR',
+ kSavesSyncEndedCmd = 'SSEN'
+};
+
+class SaveLoadCloudSyncProgressDialog : public Dialog { //protected?
+ StaticTextWidget *_label, *_percentLabel;
+ SliderWidget *_progressBar;
+ bool _close;
+public:
+ SaveLoadCloudSyncProgressDialog(bool canRunInBackground);
+ virtual ~SaveLoadCloudSyncProgressDialog();
+
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleTickle();
+};
+#endif
+
#define kSwitchSaveLoadDialog -2
// TODO: We might want to disable the grid based save/load chooser for more
@@ -53,13 +72,21 @@ class SaveLoadChooserDialog : protected Dialog {
public:
SaveLoadChooserDialog(const Common::String &dialogName, const bool saveMode);
SaveLoadChooserDialog(int x, int y, int w, int h, const bool saveMode);
+ virtual ~SaveLoadChooserDialog();
virtual void open();
+ virtual void close();
virtual void reflowLayout();
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+#ifdef USE_LIBCURL
+ virtual void runSaveSync(bool hasSavepathOverride);
+#endif
+
+ virtual void handleTickle();
+
#ifndef DISABLE_SAVELOADCHOOSER_GRID
virtual SaveLoadChooserType getType() const = 0;
#endif // !DISABLE_SAVELOADCHOOSER_GRID
@@ -70,6 +97,19 @@ public:
protected:
virtual int runIntern() = 0;
+ /** Common function to refresh the list on the screen. */
+ virtual void updateSaveList();
+
+ /**
+ * Common function to get saves list from MetaEngine.
+ *
+ * It also checks whether there are some locked saves
+ * because of saves sync and adds such saves as locked
+ * slots. User sees these slots, but is unable to save
+ * or load from these.
+ */
+ virtual void listSaves();
+
const bool _saveMode;
const MetaEngine *_metaEngine;
bool _delSupport;
@@ -78,6 +118,8 @@ protected:
bool _saveDateSupport;
bool _playTimeSupport;
Common::String _target;
+ bool _dialogWasShown;
+ SaveStateList _saveList;
#ifndef DISABLE_SAVELOADCHOOSER_GRID
ButtonWidget *_listButton;
@@ -106,6 +148,8 @@ public:
virtual void open();
virtual void close();
+protected:
+ virtual void updateSaveList();
private:
virtual int runIntern();
@@ -118,10 +162,8 @@ private:
StaticTextWidget *_time;
StaticTextWidget *_playtime;
- SaveStateList _saveList;
String _resultString;
- void updateSaveList();
void updateSelection(bool redraw);
};
@@ -164,13 +206,13 @@ public:
protected:
virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
virtual void handleMouseWheel(int x, int y, int direction);
+ virtual void updateSaveList();
private:
virtual int runIntern();
uint _columns, _lines;
uint _entriesPerPage;
uint _curPage;
- SaveStateList _saveList;
ButtonWidget *_nextButton;
ButtonWidget *_prevButton;
diff --git a/gui/saveload.cpp b/gui/saveload.cpp
index b94d30289b..3072aa6082 100644
--- a/gui/saveload.cpp
+++ b/gui/saveload.cpp
@@ -87,6 +87,10 @@ int SaveLoadChooser::runModalWithPluginAndTarget(const EnginePlugin *plugin, con
if (!_impl)
return -1;
+#ifdef USE_LIBCURL
+ _impl->runSaveSync(ConfMan.hasKey("savepath", target));
+#endif
+
// Set up the game domain as newly active domain, so
// target specific savepath will be checked
String oldDomain = ConfMan.getActiveDomainName();
diff --git a/gui/storagewizarddialog.cpp b/gui/storagewizarddialog.cpp
new file mode 100644
index 0000000000..ad00365813
--- /dev/null
+++ b/gui/storagewizarddialog.cpp
@@ -0,0 +1,361 @@
+/* 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 "gui/storagewizarddialog.h"
+#include "gui/gui-manager.h"
+#include "gui/message.h"
+#include "gui/widget.h"
+#include "gui/widgets/edittext.h"
+#include "gui/widgets/scrollcontainer.h"
+#include "backends/cloud/cloudmanager.h"
+#ifdef USE_SDL_NET
+#include "backends/networking/sdl_net/localwebserver.h"
+#endif
+#include "backends/networking/browser/openurl.h"
+#include "common/translation.h"
+
+namespace GUI {
+
+enum {
+ kConnectCmd = 'Cnnt',
+ kCodeBoxCmd = 'CdBx',
+ kOpenUrlCmd = 'OpUr',
+ kPasteCodeCmd = 'PsCd',
+ kStorageWizardContainerReflowCmd = 'SWCr'
+};
+
+StorageWizardDialog::StorageWizardDialog(uint32 storageId):
+ Dialog("GlobalOptions_Cloud_ConnectionWizard"), _storageId(storageId), _close(false) {
+#ifdef USE_SDL_NET
+ _stopServerOnClose = false;
+#endif
+ _backgroundType = GUI::ThemeEngine::kDialogBackgroundPlain;
+
+ ScrollContainerWidget *container = new ScrollContainerWidget(this, "GlobalOptions_Cloud_ConnectionWizard.Container", kStorageWizardContainerReflowCmd);
+ container->setTarget(this);
+
+ Common::String headline = Common::String::format(_("%s Storage Connection Wizard"), CloudMan.listStorages()[_storageId].c_str());
+ _headlineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.Headline", headline);
+
+ _navigateLineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.NavigateLine", _s("Navigate to the following URL:"));
+ _urlLineWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.URLLine", getUrl());
+
+ _returnLine1 = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ReturnLine1", _s("Obtain the code from the storage, enter it"));
+ _returnLine2 = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ReturnLine2", _s("in the following field and press 'Connect':"));
+ for (uint32 i = 0; i < CODE_FIELDS; ++i)
+ _codeWidget[i] = new EditTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.CodeBox" + Common::String::format("%d", i+1), "", 0, kCodeBoxCmd);
+ _messageWidget = new StaticTextWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.MessageLine", "");
+
+ // Buttons
+ _cancelWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.CancelButton", _("Cancel"), 0, kCloseCmd);
+ _openUrlWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.OpenUrlButton", _("Open URL"), 0, kOpenUrlCmd);
+ _pasteCodeWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.PasteCodeButton", _("Paste"), _("Pastes clipboard contents into fields"), kPasteCodeCmd);
+ _connectWidget = new ButtonWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.ConnectButton", _("Connect"), 0, kConnectCmd);
+
+ // Initialy the code is empty, so disable the connect button
+ _connectWidget->setEnabled(false);
+
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ // hide fields and even the button if local webserver is on
+ _returnLine1->setLabel(_s("You would be navigated to ScummVM's page"));
+ _returnLine2->setLabel(_s("when you'd allow it to use your storage."));
+ }
+
+ _picture = new GraphicsWidget(container, "GlobalOptions_Cloud_ConnectionWizard_Container.Picture");
+#ifndef DISABLE_FANCY_THEMES
+ if (g_gui.theme()->supportsImages()) {
+ _picture->useThemeTransparency(true);
+ switch (_storageId) {
+ case Cloud::kStorageDropboxId:
+ _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageDropboxLogo));
+ break;
+ case Cloud::kStorageOneDriveId:
+ _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageOneDriveLogo));
+ break;
+ case Cloud::kStorageGoogleDriveId:
+ _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageGoogleDriveLogo));
+ break;
+ case Cloud::kStorageBoxId:
+ _picture->setGfx(g_gui.theme()->getImageSurface(ThemeEngine::kImageBoxLogo));
+ break;
+ }
+ }
+#endif
+
+ containerWidgetsReflow();
+}
+
+void StorageWizardDialog::open() {
+ Dialog::open();
+
+ if (CloudMan.isWorking()) {
+ bool doClose = true;
+
+ MessageDialog alert(_("The other Storage is working. Do you want to interrupt it?"), _("Yes"), _("No"));
+ if (alert.runModal() == GUI::kMessageOK) {
+ if (CloudMan.isDownloading())
+ CloudMan.cancelDownload();
+ if (CloudMan.isSyncing())
+ CloudMan.cancelSync();
+
+ // I believe it still would return `true` here, but just in case
+ if (CloudMan.isWorking()) {
+ MessageDialog alert2(_("Wait until current Storage finishes up and try again."));
+ alert2.runModal();
+ } else {
+ doClose = false;
+ }
+ }
+
+ if (doClose) {
+ close();
+ return;
+ }
+ }
+
+#ifdef USE_SDL_NET
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ _stopServerOnClose = !LocalServer.isRunning();
+ LocalServer.start(true); // using "minimal mode" (no "/files", "/download", etc available)
+ LocalServer.indexPageHandler().setTarget(this);
+ }
+#endif
+}
+
+void StorageWizardDialog::close() {
+#ifdef USE_SDL_NET
+ if (Cloud::CloudManager::couldUseLocalServer()) {
+ if (_stopServerOnClose)
+ LocalServer.stopOnIdle();
+ LocalServer.indexPageHandler().setTarget(nullptr);
+ }
+#endif
+ Dialog::close();
+}
+
+void StorageWizardDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
+ switch (cmd) {
+ case kCodeBoxCmd: {
+ Common::String code, message;
+ uint32 correctFields = 0;
+ for (uint32 i = 0; i < CODE_FIELDS; ++i) {
+ Common::String subcode = _codeWidget[i]->getEditString();
+ if (subcode.size() == 0) {
+ ++correctFields;
+ continue;
+ }
+ bool correct = correctChecksum(subcode);
+ if (correct) {
+ code += subcode;
+ code.deleteLastChar();
+ ++correctFields;
+ } else {
+ if (i == correctFields) { //first incorrect field
+ message += Common::String::format("#%d", i + 1);
+ } else {
+ message += Common::String::format(", #%d", i + 1);
+ }
+ }
+ }
+
+ if (message.size() > 0) {
+ Common::String messageTemplate;
+ if (CODE_FIELDS - correctFields == 1)
+ messageTemplate = _("Field %s has a mistake in it.");
+ else
+ messageTemplate = _("Fields %s have mistakes in them.");
+ message = Common::String::format(messageTemplate.c_str(), message.c_str());
+ }
+
+ bool ok = false;
+ if (correctFields == CODE_FIELDS && code.size() > 0) {
+ //the last 3 chars must be an encoded crc16
+ if (code.size() > 3) {
+ uint32 size = code.size();
+ uint32 gotcrc = decodeHashchar(code[size - 3]) | (decodeHashchar(code[size - 2]) << 6) | (decodeHashchar(code[size - 1]) << 12);
+ code.erase(size - 3);
+ uint32 crc = crc16(code);
+ ok = (crc == gotcrc);
+ }
+ if (ok)
+ message = _("All OK!");
+ else
+ message = _("Invalid code");
+ }
+ _connectWidget->setEnabled(ok);
+ _messageWidget->setLabel(message);
+ break;
+ }
+ case kOpenUrlCmd: {
+ if (!Networking::Browser::openUrl(getUrl())) {
+ MessageDialog alert(_("Failed to open URL!\nYou should navigate there manually then."));
+ alert.runModal();
+ }
+ break;
+ }
+ case kPasteCodeCmd: {
+ if (g_system->hasTextInClipboard()) {
+ Common::String message = g_system->getTextFromClipboard();
+ for (uint32 i = 0; i < CODE_FIELDS; ++i) {
+ if (message.empty()) break;
+ Common::String subcode = "";
+ for (uint32 j = 0; j < message.size(); ++j) {
+ if (message[j] == ' ') {
+ message.erase(0, j+1);
+ break;
+ }
+ subcode += message[j];
+ if (j+1 == message.size()) {
+ message = "";
+ break;
+ }
+ }
+ _codeWidget[i]->setEditString(subcode);
+ }
+ handleCommand(sender, kCodeBoxCmd, data);
+ draw();
+ }
+ break;
+ }
+ case kConnectCmd: {
+ Common::String code;
+ for (uint32 i = 0; i < CODE_FIELDS; ++i) {
+ Common::String subcode = _codeWidget[i]->getEditString();
+ if (subcode.size() == 0)
+ continue;
+ code += subcode;
+ code.deleteLastChar();
+ }
+ if (code.size() > 3) {
+ code.erase(code.size() - 3);
+ CloudMan.connectStorage(_storageId, code);
+ setResult(1);
+ close();
+ }
+ break;
+ }
+#ifdef USE_SDL_NET
+ case kStorageCodePassedCmd:
+ CloudMan.connectStorage(_storageId, LocalServer.indexPageHandler().code());
+ _close = true;
+ break;
+#endif
+ case kStorageWizardContainerReflowCmd:
+ containerWidgetsReflow();
+ break;
+ default:
+ Dialog::handleCommand(sender, cmd, data);
+ }
+}
+
+void StorageWizardDialog::handleTickle() {
+ if (_close) {
+ setResult(1);
+ close();
+ }
+
+ Dialog::handleTickle();
+}
+
+void StorageWizardDialog::containerWidgetsReflow() {
+ // contents
+ if (_headlineWidget) _headlineWidget->setVisible(true);
+ if (_navigateLineWidget) _navigateLineWidget->setVisible(true);
+ if (_urlLineWidget) _urlLineWidget->setVisible(true);
+ if (_returnLine1) _returnLine1->setVisible(true);
+ if (_returnLine2) _returnLine2->setVisible(true);
+
+ bool showFields = (!Cloud::CloudManager::couldUseLocalServer());
+ for (uint32 i = 0; i < CODE_FIELDS; ++i)
+ _codeWidget[i]->setVisible(showFields);
+ _messageWidget->setVisible(showFields);
+
+ // left column / first bottom row
+ if (_picture) {
+ _picture->setVisible(g_system->getOverlayWidth() > 320);
+ }
+ if (_openUrlWidget) _openUrlWidget->setVisible(true);
+ if (_pasteCodeWidget) {
+ bool visible = showFields && g_system->hasFeature(OSystem::kFeatureClipboardSupport);
+ _pasteCodeWidget->setVisible(visible);
+ }
+
+ // bottom row
+ if (_cancelWidget) _cancelWidget->setVisible(true);
+ if (_connectWidget) {
+ _connectWidget->setVisible(showFields);
+ }
+}
+
+Common::String StorageWizardDialog::getUrl() const {
+ Common::String url = "https://www.scummvm.org/c/";
+ switch (_storageId) {
+ case Cloud::kStorageDropboxId:
+ url += "db";
+ break;
+ case Cloud::kStorageOneDriveId:
+ url += "od";
+ break;
+ case Cloud::kStorageGoogleDriveId:
+ url += "gd";
+ break;
+ case Cloud::kStorageBoxId:
+ url += "bx";
+ break;
+ }
+
+ if (Cloud::CloudManager::couldUseLocalServer())
+ url += "s";
+
+ return url;
+}
+
+int StorageWizardDialog::decodeHashchar(char c) {
+ const char HASHCHARS[65] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ?!";
+ for (uint32 i = 0; i < 64; ++i)
+ if (c == HASHCHARS[i])
+ return i;
+ return -1;
+}
+
+bool StorageWizardDialog::correctChecksum(Common::String s) {
+ if (s.size() == 0)
+ return false; //no last char
+ int providedChecksum = decodeHashchar(s.lastChar());
+ int calculatedChecksum = 0x2A; //any initial value would do, but it must equal to the one used on the page where these checksums were generated
+ for (uint32 i = 0; i < s.size()-1; ++i) {
+ calculatedChecksum = calculatedChecksum ^ s[i];
+ }
+ return providedChecksum == (calculatedChecksum % 64);
+}
+
+uint32 StorageWizardDialog::crc16(Common::String s) { //"CRC16_CCITT_FALSE"
+ uint32 crc = 0xFFFF, x;
+ for (uint32 i = 0; i < s.size(); ++i) {
+ x = ((crc >> 8) ^ s[i]) & 0xFF;
+ x ^= x >> 4;
+ crc = ((crc << 8) ^ (x << 12) ^ (x << 5) ^ x) & 0xFFFF;
+ }
+ return crc;
+}
+
+} // End of namespace GUI
diff --git a/gui/storagewizarddialog.h b/gui/storagewizarddialog.h
new file mode 100644
index 0000000000..61bc8ac873
--- /dev/null
+++ b/gui/storagewizarddialog.h
@@ -0,0 +1,107 @@
+/* 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 GUI_STORAGEWIZARDDIALOG_H
+#define GUI_STORAGEWIZARDDIALOG_H
+
+#include "gui/dialog.h"
+#include "common/str.h"
+
+namespace GUI {
+
+class CommandSender;
+class EditTextWidget;
+class StaticTextWidget;
+class ButtonWidget;
+class GraphicsWidget;
+
+#ifdef USE_SDL_NET
+enum StorageWizardDialogCommands {
+ kStorageCodePassedCmd = 'SWDC'
+};
+#endif
+
+class StorageWizardDialog : public Dialog {
+ static const uint32 CODE_FIELDS = 8;
+ uint32 _storageId;
+
+ StaticTextWidget *_headlineWidget;
+ StaticTextWidget *_navigateLineWidget;
+ StaticTextWidget *_urlLineWidget;
+ StaticTextWidget *_returnLine1;
+ StaticTextWidget *_returnLine2;
+ EditTextWidget *_codeWidget[CODE_FIELDS];
+ StaticTextWidget *_messageWidget;
+
+ GraphicsWidget *_picture;
+ ButtonWidget *_openUrlWidget;
+ ButtonWidget *_pasteCodeWidget;
+
+ ButtonWidget *_cancelWidget;
+ ButtonWidget *_connectWidget;
+
+ bool _close;
+#ifdef USE_SDL_NET
+ bool _stopServerOnClose;
+#endif
+
+ /** Hides/shows widgets for Container to work with them correctly. */
+ void containerWidgetsReflow();
+
+ /** Return short scummvm.org URL for user to navigate to. */
+ Common::String getUrl() const;
+
+ /**
+ * Return the value corresponding to the given character.
+ *
+ * There is a value corresponding to each of 64 selected
+ * printable characters (0-9, A-Z, a-z, ? and !).
+ *
+ * When given another character, -1 is returned.
+ */
+ int decodeHashchar(char c);
+
+ /**
+ * Return whether checksum is correct.
+ *
+ * The last character of the string is treated as
+ * the checksum of all the others (decoded with
+ * decodeHashchar()).
+ *
+ * Checksum = (c[0] ^ c[1] ^ ...) % 64
+ */
+ bool correctChecksum(Common::String s);
+
+ /** The "CRC16_CCITT_FALSE" CRC-16 algorithm. */
+ uint32 crc16(Common::String s);
+public:
+ StorageWizardDialog(uint32 storageId);
+
+ virtual void open();
+ virtual void close();
+ virtual void handleCommand(CommandSender *sender, uint32 cmd, uint32 data);
+ virtual void handleTickle();
+};
+
+} // End of namespace GUI
+
+#endif
diff --git a/gui/themes/default.inc b/gui/themes/default.inc
index c0ea733de8..d46a603830 100644
--- a/gui/themes/default.inc
+++ b/gui/themes/default.inc
@@ -1092,6 +1092,192 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"/>"
"</layout>"
"</dialog>"
+"<dialog name='GlobalOptions_Cloud' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='0,0,0,0'>"
+"<widget name='Container'/>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_Container' overlays='GlobalOptions_Cloud.Container'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='StoragePopupDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='StoragePopup' "
+"type='PopUp' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='StorageUsernameDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='StorageUsernameLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='StorageUsedSpaceDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='StorageUsedSpaceLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='StorageLastSyncDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='StorageLastSyncLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='ConnectButton' "
+"type='Button' "
+"/>"
+"<widget name='RefreshButton' "
+"type='Button' "
+"/>"
+"<widget name='DownloadButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='RunServerButton' "
+"type='Button' "
+"/>"
+"<widget name='ServerInfoLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='ServerPortDesc' "
+"type='OptionsLabel' "
+"/>"
+"<widget name='ServerPortEditText' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ServerPortClearButton' "
+"height='Globals.Line.Height' "
+"width='Globals.Line.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_DownloadDialog' overlays='Dialog.GlobalOptions'>"
+"<layout type='vertical' padding='16,16,16,8' spacing='8'>"
+"<widget name='RemoteDirectory' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='LocalDirectory' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ProgressBar' "
+"height='Globals.Button.Height' "
+"/>"
+"<space size='1'/>"
+"<widget name='PercentText' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<widget name='DownloadSize' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='DownloadSpeed' "
+"height='Globals.Line.Height' "
+"/>"
+"<space/>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10'>"
+"<widget name='MainButton' "
+"type='Button' "
+"/>"
+"<space/>"
+"<widget name='CloseButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_ConnectionWizard' overlays='Dialog.GlobalOptions'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='Picture' "
+"type='OptionsLabel' "
+"/>"
+"<layout type='vertical' padding='0,0,0,0' spacing='6'>"
+"<widget name='Headline' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='4' />"
+"<widget name='NavigateLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='URLLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='4' />"
+"<widget name='ReturnLine1' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ReturnLine2' "
+"height='Globals.Line.Height' "
+"/>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='CodeBox1' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox2' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox3' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox4' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='CodeBox5' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox6' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox7' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='CodeBox8' "
+"width='70' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<widget name='MessageLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='6' />"
+"</layout>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='10' center='true'>"
+"<widget name='CancelButton' "
+"type='Button' "
+"/>"
+"<widget name='OpenUrlButton' "
+"type='Button' "
+"/>"
+"<widget name='ConnectButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>"
"<layout type='vertical' padding='8,8,8,8' center='true'>"
"<widget name='Action' "
@@ -1592,6 +1778,37 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"</layout>"
"</layout>"
"</dialog>"
+"<dialog name='SaveLoadCloudSyncProgress' overlays='screen_center' inset='8' shading='dim'>"
+"<layout type='vertical' padding='8,8,8,8' center='true'>"
+"<widget name='TitleText' "
+"width='496' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<space size='1'/>"
+"<widget name='ProgressBar' "
+"width='496' "
+"height='Globals.Button.Height' "
+"/>"
+"<space size='1'/>"
+"<widget name='PercentText' "
+"width='496' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<space size='1'/>"
+"<layout type='horizontal' padding='0,0,0,0' center='true' spacing='10'>"
+"<widget name='Cancel' "
+"width='150' "
+"height='Globals.Button.Height' "
+"/>"
+"<widget name='Background' "
+"width='150' "
+"height='Globals.Button.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='SavenameDialog' overlays='screen_center'>"
"<layout type='vertical' padding='8,8,8,8'>"
"<widget name='DescriptionText' "
@@ -2394,6 +2611,197 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"/>"
"</layout>"
"</dialog>"
+"<dialog name='GlobalOptions_Cloud' overlays='Dialog.GlobalOptions.TabWidget'>"
+"<layout type='vertical' padding='0,0,0,0'>"
+"<widget name='Container'/>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_Container' overlays='GlobalOptions_Cloud.Container'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='StoragePopupDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='StoragePopup' "
+"type='PopUp' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='StorageUsernameDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='StorageUsernameLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='StorageUsedSpaceDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='StorageUsedSpaceLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='StorageLastSyncDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='StorageLastSyncLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='ConnectButton' "
+"type='Button' "
+"/>"
+"<widget name='RefreshButton' "
+"type='Button' "
+"/>"
+"<widget name='DownloadButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='RunServerButton' "
+"type='Button' "
+"/>"
+"<widget name='ServerInfoLabel' "
+"height='Globals.Line.Height' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='ServerPortDesc' "
+"width='80' "
+"height='Globals.Line.Height' "
+"textalign='right' "
+"/>"
+"<widget name='ServerPortEditText' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='ServerPortClearButton' "
+"height='Globals.Line.Height' "
+"width='Globals.Line.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_DownloadDialog' overlays='Dialog.GlobalOptions'>"
+"<layout type='vertical' padding='8,8,8,4' spacing='8'>"
+"<widget name='RemoteDirectory' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='LocalDirectory' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ProgressBar' "
+"height='Globals.Button.Height' "
+"/>"
+"<space size='1'/>"
+"<widget name='PercentText' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<widget name='DownloadSize' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='DownloadSpeed' "
+"height='Globals.Line.Height' "
+"/>"
+"<space/>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6'>"
+"<widget name='MainButton' "
+"type='Button' "
+"/>"
+"<space/>"
+"<widget name='CloseButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
+"<dialog name='GlobalOptions_Cloud_ConnectionWizard' overlays='Dialog.GlobalOptions'>"
+"<layout type='vertical' padding='16,16,16,16' spacing='8'>"
+"<layout type='vertical' padding='0,0,0,0' spacing='4'>"
+"<widget name='Headline' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='2' />"
+"<widget name='NavigateLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='URLLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='2' />"
+"<widget name='ReturnLine1' "
+"height='Globals.Line.Height' "
+"/>"
+"<widget name='ReturnLine2' "
+"height='Globals.Line.Height' "
+"/>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='CodeBox1' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox2' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox3' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox4' "
+"width='60' "
+"height='16' "
+"/>"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='4' center='true'>"
+"<widget name='CodeBox5' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox6' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox7' "
+"width='60' "
+"height='16' "
+"/>"
+"<widget name='CodeBox8' "
+"width='60' "
+"height='16' "
+"/>"
+"</layout>"
+"<widget name='MessageLine' "
+"height='Globals.Line.Height' "
+"/>"
+"<space size='4' />"
+"</layout>"
+"<layout type='horizontal' padding='0,0,0,0' spacing='6' center='true'>"
+"<widget name='CancelButton' "
+"type='Button' "
+"/>"
+"<widget name='OpenUrlButton' "
+"type='Button' "
+"/>"
+"<widget name='ConnectButton' "
+"type='Button' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>"
"<layout type='vertical' padding='8,8,8,8' center='true'>"
"<widget name='Action' "
@@ -2888,6 +3296,37 @@ const char *defaultXML1 = "<?xml version = '1.0'?>"
"</layout>"
"</layout>"
"</dialog>"
+"<dialog name='SaveLoadCloudSyncProgress' overlays='screen_center' inset='8' shading='dim'>"
+"<layout type='vertical' padding='8,8,8,8' center='true'>"
+"<widget name='TitleText' "
+"width='240' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<space size='1'/>"
+"<widget name='ProgressBar' "
+"width='240' "
+"height='Globals.Button.Height' "
+"/>"
+"<space size='1'/>"
+"<widget name='PercentText' "
+"width='240' "
+"height='Globals.Line.Height' "
+"textalign='center' "
+"/>"
+"<space size='1'/>"
+"<layout type='horizontal' padding='0,0,0,0' center='true' spacing='10'>"
+"<widget name='Cancel' "
+"width='100' "
+"height='Globals.Button.Height' "
+"/>"
+"<widget name='Background' "
+"width='100' "
+"height='Globals.Button.Height' "
+"/>"
+"</layout>"
+"</layout>"
+"</dialog>"
"<dialog name='SavenameDialog' overlays='screen_center'>"
"<layout type='vertical' padding='8,8,8,8'>"
"<widget name='DescriptionText' "
diff --git a/gui/themes/scummclassic.zip b/gui/themes/scummclassic.zip
index 561f2a5dd3..e574fe5039 100644
--- a/gui/themes/scummclassic.zip
+++ b/gui/themes/scummclassic.zip
Binary files differ
diff --git a/gui/themes/scummclassic/classic_gfx.stx b/gui/themes/scummclassic/classic_gfx.stx
index 49ecea2e11..1311345621 100644
--- a/gui/themes/scummclassic/classic_gfx.stx
+++ b/gui/themes/scummclassic/classic_gfx.stx
@@ -192,7 +192,7 @@
orientation = 'top'
/>
</drawdata>
-
+
<drawdata id = 'scrollbar_button_idle' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
@@ -226,7 +226,7 @@
orientation = 'top'
/>
</drawdata>
-
+
<drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
@@ -314,7 +314,7 @@
bevel = '2'
fill = 'none'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -325,7 +325,7 @@
padding = '0, 0, 7, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -336,20 +336,20 @@
padding = '0, 0, 7, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_idle' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
fill = 'none'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -360,7 +360,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -371,7 +371,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -394,7 +394,7 @@
padding = '0, 0, 7, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -411,13 +411,13 @@
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_disabled' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
fill = 'none'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -428,7 +428,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -439,7 +439,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -462,7 +462,7 @@
padding = '0, 0, 7, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -479,13 +479,13 @@
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'bevelsq'
bevel = '2'
fill = 'none'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -496,7 +496,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
fg_color = 'green'
fill = 'foreground'
@@ -507,7 +507,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -552,7 +552,7 @@
fill = 'foreground'
fg_color = 'green'
/>
- </drawdata>
+ </drawdata>
<drawdata id = 'button_idle' cache = 'false'>
<text font = 'text_button'
diff --git a/gui/themes/scummclassic/classic_layout.stx b/gui/themes/scummclassic/classic_layout.stx
index 5172326859..c67631ad22 100644
--- a/gui/themes/scummclassic/classic_layout.stx
+++ b/gui/themes/scummclassic/classic_layout.stx
@@ -524,6 +524,221 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StoragePopupDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StoragePopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageUsernameDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageUsernameLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageUsedSpaceDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageUsedSpaceLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageLastSyncDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageLastSyncLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ <widget name = 'RefreshButton'
+ type = 'Button'
+ />
+ <widget name = 'DownloadButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'RunServerButton'
+ type = 'Button'
+ />
+ <widget name = 'ServerInfoLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'RootPathButton'
+ type = 'Button'
+ />
+ <widget name = 'RootPath'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'RootPathClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'ServerPortDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'ServerPortEditText'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ServerPortClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '16, 16, 16, 8' spacing = '8'>
+ <widget name = 'RemoteDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'LocalDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ProgressBar'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <widget name = 'DownloadSize'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'DownloadSpeed'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10'>
+ <widget name = 'MainButton'
+ type = 'Button'
+ />
+ <space/>
+ <widget name = 'CloseButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Picture'
+ width = '109'
+ height = '109'
+ />
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox2'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox3'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox4'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox6'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox7'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox8'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '6' />
+ </layout>
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
@@ -1042,6 +1257,38 @@
</layout>
</dialog>
+ <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
+ <widget name = 'TitleText'
+ width = '496'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <widget name = 'ProgressBar'
+ width = '496'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ width = '496'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'>
+ <widget name = 'Cancel'
+ width = '150'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Background'
+ width = '150'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
diff --git a/gui/themes/scummclassic/classic_layout_lowres.stx b/gui/themes/scummclassic/classic_layout_lowres.stx
index 0013b91ee2..e19256695c 100644
--- a/gui/themes/scummclassic/classic_layout_lowres.stx
+++ b/gui/themes/scummclassic/classic_layout_lowres.stx
@@ -529,6 +529,226 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StoragePopupDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StoragePopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageUsernameDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageUsernameLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageUsedSpaceDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageUsedSpaceLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageLastSyncDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageLastSyncLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ <widget name = 'RefreshButton'
+ type = 'Button'
+ />
+ <widget name = 'DownloadButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'RunServerButton'
+ type = 'Button'
+ />
+ <widget name = 'ServerInfoLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '16' center = 'true'>
+ <widget name = 'RootPathButton'
+ type = 'Button'
+ />
+ <widget name = 'RootPathPath'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'RootPathClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'ServerPortDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'ServerPortEditText'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'ServerPortClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '8, 8, 8, 4' spacing = '8'>
+ <widget name = 'RemoteDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'LocalDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ProgressBar'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <widget name = 'DownloadSize'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'DownloadSpeed'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'MainButton'
+ type = 'Button'
+ />
+ <space/>
+ <widget name = 'CloseButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox2'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox3'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox4'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox6'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox7'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox8'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ <space size = '6' />
+ <widget name = 'Picture' width = '1' height = '1' />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
@@ -1040,6 +1260,38 @@
</layout>
</dialog>
+ <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
+ <widget name = 'TitleText'
+ width = '240'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <widget name = 'ProgressBar'
+ width = '240'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ width = '240'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'>
+ <widget name = 'Cancel'
+ width = '100'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Background'
+ width = '100'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
diff --git a/gui/themes/scummmodern.zip b/gui/themes/scummmodern.zip
index d80c481ffc..78b62d4286 100644
--- a/gui/themes/scummmodern.zip
+++ b/gui/themes/scummmodern.zip
Binary files differ
diff --git a/gui/themes/scummmodern/box.bmp b/gui/themes/scummmodern/box.bmp
new file mode 100644
index 0000000000..21fb650f02
--- /dev/null
+++ b/gui/themes/scummmodern/box.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/dropbox.bmp b/gui/themes/scummmodern/dropbox.bmp
new file mode 100644
index 0000000000..4ed95f0009
--- /dev/null
+++ b/gui/themes/scummmodern/dropbox.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/googledrive.bmp b/gui/themes/scummmodern/googledrive.bmp
new file mode 100644
index 0000000000..30377a5f74
--- /dev/null
+++ b/gui/themes/scummmodern/googledrive.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/onedrive.bmp b/gui/themes/scummmodern/onedrive.bmp
new file mode 100644
index 0000000000..cd26d71d3c
--- /dev/null
+++ b/gui/themes/scummmodern/onedrive.bmp
Binary files differ
diff --git a/gui/themes/scummmodern/scummmodern_gfx.stx b/gui/themes/scummmodern/scummmodern_gfx.stx
index 3a1ec5a5f0..e3d2152e4b 100644
--- a/gui/themes/scummmodern/scummmodern_gfx.stx
+++ b/gui/themes/scummmodern/scummmodern_gfx.stx
@@ -119,6 +119,10 @@
<bitmap filename = 'editbtn_small.bmp'/>
<bitmap filename = 'switchbtn_small.bmp'/>
<bitmap filename = 'fastreplay_small.bmp'/>
+ <bitmap filename = 'dropbox.bmp'/>
+ <bitmap filename = 'onedrive.bmp'/>
+ <bitmap filename = 'googledrive.bmp'/>
+ <bitmap filename = 'box.bmp'/>
</bitmaps>
<fonts>
@@ -181,7 +185,7 @@
<text_color id = 'color_button_hover'
color = 'white'
/>
-
+
<text_color id = 'color_alternative_inverted'
color = 'white'
/>
@@ -327,7 +331,7 @@
orientation = 'top'
/>
</drawdata>
-
+
<drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y>399'>
<drawstep func = 'roundedsq'
radius = '10'
@@ -350,7 +354,7 @@
orientation = 'top'
/>
</drawdata>
-
+
<drawdata id = 'scrollbar_button_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'roundedsq'
radius = '10'
@@ -476,7 +480,7 @@
bg_color = 'xtrabrightred'
shadow = '1'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -487,7 +491,7 @@
padding = '0, 0, 6, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -498,14 +502,14 @@
padding = '0, 0, 6, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_idle' cache = 'false' resolution ='y<400'>
<drawstep func = 'roundedsq'
radius = '5'
@@ -515,7 +519,7 @@
bg_color = 'xtrabrightred'
shadow = '1'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -526,7 +530,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -537,7 +541,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -566,7 +570,7 @@
padding = '0, 0, 6, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -577,14 +581,14 @@
padding = '0, 0, 6, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal_hover'
vertical_align = 'center'
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_disabled' cache = 'false' resolution = 'y<400'>
<drawstep func = 'roundedsq'
radius = '5'
@@ -594,7 +598,7 @@
bg_color = 'xtrabrightred'
shadow = '2'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -605,7 +609,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -616,7 +620,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -645,7 +649,7 @@
padding = '0, 0, 6, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -656,14 +660,14 @@
padding = '0, 0, 6, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal_hover'
vertical_align = 'center'
horizontal_align = 'left'
/>
</drawdata>
-
+
<drawdata id = 'popup_hover' cache = 'false' resolution = 'y<400'>
<drawstep func = 'roundedsq'
radius = '5'
@@ -673,7 +677,7 @@
bg_color = 'xtrabrightred'
shadow = '1'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -684,7 +688,7 @@
padding = '0, 0, 3, 0'
orientation = 'bottom'
/>
-
+
<drawstep func = 'triangle'
bg_color = 'shadowcolor'
fill = 'background'
@@ -695,7 +699,7 @@
padding = '0, 0, 3, 0'
orientation = 'top'
/>
-
+
<text font = 'text_default'
text_color = 'color_normal'
vertical_align = 'center'
@@ -777,7 +781,7 @@
bevel = '1'
bevel_color = 'black'
/>
- </drawdata>
+ </drawdata>
<!-- Idle button -->
<drawdata id = 'button_idle' cache = 'false'>
diff --git a/gui/themes/scummmodern/scummmodern_layout.stx b/gui/themes/scummmodern/scummmodern_layout.stx
index 026fa7bc64..bb182c9dbb 100644
--- a/gui/themes/scummmodern/scummmodern_layout.stx
+++ b/gui/themes/scummmodern/scummmodern_layout.stx
@@ -538,6 +538,221 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StoragePopupDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StoragePopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageUsernameDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageUsernameLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageUsedSpaceDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageUsedSpaceLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'StorageLastSyncDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'StorageLastSyncLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ <widget name = 'RefreshButton'
+ type = 'Button'
+ />
+ <widget name = 'DownloadButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'RunServerButton'
+ type = 'Button'
+ />
+ <widget name = 'ServerInfoLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'RootPathButton'
+ type = 'Button'
+ />
+ <widget name = 'RootPath'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'RootPathClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'ServerPortDesc'
+ type = 'OptionsLabel'
+ />
+ <widget name = 'ServerPortEditText'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ServerPortClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '16, 16, 16, 8' spacing = '8'>
+ <widget name = 'RemoteDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'LocalDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ProgressBar'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <widget name = 'DownloadSize'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'DownloadSpeed'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10'>
+ <widget name = 'MainButton'
+ type = 'Button'
+ />
+ <space/>
+ <widget name = 'CloseButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '0'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Picture'
+ width = '109'
+ height = '109'
+ />
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '4' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox2'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox3'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox4'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox6'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox7'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'CodeBox8'
+ width = '70'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '6' />
+ </layout>
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '10' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
@@ -1056,6 +1271,38 @@
</layout>
</dialog>
+ <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
+ <widget name = 'TitleText'
+ width = '496'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <widget name = 'ProgressBar'
+ width = '496'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ width = '496'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'>
+ <widget name = 'Cancel'
+ width = '150'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Background'
+ width = '150'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
diff --git a/gui/themes/scummmodern/scummmodern_layout_lowres.stx b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
index 169e61a9bb..3416fbeb5e 100644
--- a/gui/themes/scummmodern/scummmodern_layout_lowres.stx
+++ b/gui/themes/scummmodern/scummmodern_layout_lowres.stx
@@ -527,6 +527,226 @@
</layout>
</dialog>
+ <dialog name = 'GlobalOptions_Cloud' overlays = 'Dialog.GlobalOptions.TabWidget'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_Container' overlays = 'GlobalOptions_Cloud.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StoragePopupDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StoragePopup'
+ type = 'PopUp'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageUsernameDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageUsernameLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageUsedSpaceDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageUsedSpaceLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'StorageLastSyncDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'StorageLastSyncLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ <widget name = 'RefreshButton'
+ type = 'Button'
+ />
+ <widget name = 'DownloadButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'RunServerButton'
+ type = 'Button'
+ />
+ <widget name = 'ServerInfoLabel'
+ height = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '16' center = 'true'>
+ <widget name = 'RootPathButton'
+ type = 'Button'
+ />
+ <widget name = 'RootPathPath'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'RootPathClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6' center = 'true'>
+ <widget name = 'ServerPortDesc'
+ width = '80'
+ height = 'Globals.Line.Height'
+ textalign = 'right'
+ />
+ <widget name = 'ServerPortEditText'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'ServerPortClearButton'
+ height = 'Globals.Line.Height'
+ width = 'Globals.Line.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_DownloadDialog' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '8, 8, 8, 4' spacing = '8'>
+ <widget name = 'RemoteDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'LocalDirectory'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ProgressBar'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <widget name = 'DownloadSize'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'DownloadSpeed'
+ height = 'Globals.Line.Height'
+ />
+ <space/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '6'>
+ <widget name = 'MainButton'
+ type = 'Button'
+ />
+ <space/>
+ <widget name = 'CloseButton'
+ type = 'Button'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard' overlays = 'Dialog.GlobalOptions'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0'>
+ <widget name = 'Container'/>
+ </layout>
+ </dialog>
+
+ <dialog name = 'GlobalOptions_Cloud_ConnectionWizard_Container' overlays = 'GlobalOptions_Cloud_ConnectionWizard.Container'>
+ <layout type = 'vertical' padding = '16, 16, 16, 16' spacing = '8'>
+ <layout type = 'vertical' padding = '0, 0, 0, 0' spacing = '4'>
+ <widget name = 'Headline'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'NavigateLine'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'URLLine'
+ height = 'Globals.Line.Height'
+ />
+ <space size = '2' />
+ <widget name = 'ReturnLine1'
+ height = 'Globals.Line.Height'
+ />
+ <widget name = 'ReturnLine2'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox1'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox2'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox3'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox4'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CodeBox5'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox6'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox7'
+ width = '60'
+ height = '16'
+ />
+ <widget name = 'CodeBox8'
+ width = '60'
+ height = '16'
+ />
+ </layout>
+ <widget name = 'MessageLine'
+ height = 'Globals.Line.Height'
+ />
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'OpenUrlButton'
+ type = 'Button'
+ />
+ <widget name = 'PasteCodeButton'
+ type = 'Button'
+ />
+ </layout>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' spacing = '4' center = 'true'>
+ <widget name = 'CancelButton'
+ type = 'Button'
+ />
+ <space />
+ <widget name = 'ConnectButton'
+ type = 'Button'
+ />
+ </layout>
+ <space size = '6' />
+ <widget name = 'Picture' width = '1' height = '1' />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name='KeysDialog' overlays='Dialog.GlobalOptions' shading='dim'>
<layout type='vertical' padding='8,8,8,8' center='true'>
<widget name='Action'
@@ -1038,6 +1258,38 @@
</layout>
</dialog>
+ <dialog name = 'SaveLoadCloudSyncProgress' overlays = 'screen_center' inset = '8' shading = 'dim'>
+ <layout type = 'vertical' padding = '8, 8, 8, 8' center = 'true'>
+ <widget name = 'TitleText'
+ width = '240'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <widget name = 'ProgressBar'
+ width = '240'
+ height = 'Globals.Button.Height'
+ />
+ <space size = '1'/>
+ <widget name = 'PercentText'
+ width = '240'
+ height = 'Globals.Line.Height'
+ textalign = 'center'
+ />
+ <space size = '1'/>
+ <layout type = 'horizontal' padding = '0, 0, 0, 0' center = 'true' spacing = '10'>
+ <widget name = 'Cancel'
+ width = '100'
+ height = 'Globals.Button.Height'
+ />
+ <widget name = 'Background'
+ width = '100'
+ height = 'Globals.Button.Height'
+ />
+ </layout>
+ </layout>
+ </dialog>
+
<dialog name = 'SavenameDialog' overlays = 'screen_center'>
<layout type = 'vertical' padding = '8, 8, 8, 8'>
<widget name = 'DescriptionText'
diff --git a/gui/themes/scummtheme.py b/gui/themes/scummtheme.py
index d5fa4dfca7..365787275b 100755
--- a/gui/themes/scummtheme.py
+++ b/gui/themes/scummtheme.py
@@ -5,7 +5,7 @@ import re
import os
import zipfile
-THEME_FILE_EXTENSIONS = ('.stx', '.bmp', '.fcc', '.ttf')
+THEME_FILE_EXTENSIONS = ('.stx', '.bmp', '.fcc', '.ttf', '.png')
def buildTheme(themeName):
if not os.path.isdir(themeName) or not os.path.isfile(os.path.join(themeName, "THEMERC")):
diff --git a/gui/widget.cpp b/gui/widget.cpp
index f2a29c3100..f6e6d09a8a 100644
--- a/gui/widget.cpp
+++ b/gui/widget.cpp
@@ -398,25 +398,28 @@ void ButtonWidget::setUnpressedState() {
PicButtonWidget::PicButtonWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip, uint32 cmd, uint8 hotkey)
: ButtonWidget(boss, x, y, w, h, "", tooltip, cmd, hotkey),
- _gfx(), _alpha(256), _transparency(false) {
+ _alpha(255), _transparency(false), _showButton(true) {
setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
_type = kButtonWidget;
+ _mode = ThemeEngine::kAutoScaleNone;
}
PicButtonWidget::PicButtonWidget(GuiObject *boss, const Common::String &name, const char *tooltip, uint32 cmd, uint8 hotkey)
: ButtonWidget(boss, name, "", tooltip, cmd, hotkey),
- _gfx(), _alpha(256), _transparency(false) {
+ _alpha(255), _transparency(false), _showButton(true), _isAlpha(false) {
setFlags(WIDGET_ENABLED/* | WIDGET_BORDER*/ | WIDGET_CLEARBG);
_type = kButtonWidget;
+ _mode = ThemeEngine::kAutoScaleNone;
}
PicButtonWidget::~PicButtonWidget() {
- _gfx.free();
+ for (int i = 0; i < kPicButtonStateMax + 1; i++)
+ _gfx[i].free();
}
-void PicButtonWidget::setGfx(const Graphics::Surface *gfx) {
- _gfx.free();
+void PicButtonWidget::setGfx(const Graphics::Surface *gfx, int statenum) {
+ _gfx[statenum].free();
if (!gfx || !gfx->getPixels())
return;
@@ -432,10 +435,27 @@ void PicButtonWidget::setGfx(const Graphics::Surface *gfx) {
return;
}
- _gfx.copyFrom(*gfx);
+ _gfx[statenum].copyFrom(*gfx);
+}
+
+void PicButtonWidget::setAGfx(const Graphics::TransparentSurface *gfx, int statenum, ThemeEngine::AutoScaleMode mode) {
+ _agfx[statenum].free();
+
+ if (!gfx || !gfx->getPixels())
+ return;
+
+ if (gfx->format.bytesPerPixel == 1) {
+ warning("PicButtonWidget::setGfx got paletted surface passed");
+ return;
+ }
+
+ _agfx[statenum].copyFrom(*gfx);
+
+ _isAlpha = true;
+ _mode = mode;
}
-void PicButtonWidget::setGfx(int w, int h, int r, int g, int b) {
+void PicButtonWidget::setGfx(int w, int h, int r, int g, int b, int statenum) {
if (w == -1)
w = _w;
if (h == -1)
@@ -443,26 +463,69 @@ void PicButtonWidget::setGfx(int w, int h, int r, int g, int b) {
const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
- _gfx.free();
- _gfx.create(w, h, requiredFormat);
- _gfx.fillRect(Common::Rect(0, 0, w, h), _gfx.format.RGBToColor(r, g, b));
+ _gfx[statenum].free();
+ _gfx[statenum].create(w, h, requiredFormat);
+ _gfx[statenum].fillRect(Common::Rect(0, 0, w, h), _gfx[statenum].format.RGBToColor(r, g, b));
}
void PicButtonWidget::drawWidget() {
- g_gui.theme()->drawButtonClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), "", _state, getFlags());
+ if (_showButton)
+ g_gui.theme()->drawButtonClip(Common::Rect(_x, _y, _x + _w, _y + _h), getBossClipRect(), "", _state, getFlags());
+
+ if (!_isAlpha) {
+ Graphics::Surface *gfx;
+
+ if (_state == ThemeEngine::kStateHighlight)
+ gfx = &_gfx[kPicButtonHighlight];
+ else if (_state == ThemeEngine::kStateDisabled)
+ gfx = &_gfx[kPicButtonStateDisabled];
+ else if (_state == ThemeEngine::kStatePressed)
+ gfx = &_gfx[kPicButtonStatePressed];
+ else
+ gfx = &_gfx[kPicButtonStateEnabled];
- if (_gfx.getPixels()) {
+ if (!gfx->getPixels())
+ gfx = &_gfx[kPicButtonStateEnabled];
+
+ if (gfx->getPixels()) {
// Check whether the set up surface needs to be converted to the GUI
// color format.
- const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
- if (_gfx.format != requiredFormat) {
- _gfx.convertToInPlace(requiredFormat);
+ const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
+ if (gfx->format != requiredFormat) {
+ gfx->convertToInPlace(requiredFormat);
+ }
+
+ const int x = _x + (_w - gfx->w) / 2;
+ const int y = _y + (_h - gfx->h) / 2;
+
+ g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + gfx->w, y + gfx->h), getBossClipRect(), *gfx, _state, _alpha, _transparency);
}
+ } else {
+ Graphics::TransparentSurface *gfx;
+
+ if (_state == ThemeEngine::kStateHighlight)
+ gfx = &_agfx[kPicButtonHighlight];
+ else if (_state == ThemeEngine::kStateDisabled)
+ gfx = &_agfx[kPicButtonStateDisabled];
+ else if (_state == ThemeEngine::kStatePressed)
+ gfx = &_agfx[kPicButtonStatePressed];
+ else
+ gfx = &_agfx[kPicButtonStateEnabled];
- const int x = _x + (_w - _gfx.w) / 2;
- const int y = _y + (_h - _gfx.h) / 2;
+ if (!gfx->getPixels())
+ gfx = &_agfx[kPicButtonStateEnabled];
- g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + _gfx.w, y + _gfx.h), getBossClipRect(), _gfx, _state, _alpha, _transparency);
+ if (gfx->getPixels()) {
+ if (_mode == GUI::ThemeEngine::kAutoScaleNone) {
+ const int x = _x + (_w - gfx->w) / 2;
+ const int y = _y + (_h - gfx->h) / 2;
+
+ g_gui.theme()->drawASurface(Common::Rect(x, y, x + gfx->w, y + gfx->h), *gfx, _mode, _alpha);
+
+ } else {
+ g_gui.theme()->drawASurface(Common::Rect(_x, _y, _x + _w, _y + _h), *gfx, _mode, _alpha);
+ }
+ }
}
}
@@ -652,13 +715,13 @@ int SliderWidget::posToValue(int pos) {
#pragma mark -
GraphicsWidget::GraphicsWidget(GuiObject *boss, int x, int y, int w, int h, const char *tooltip)
- : Widget(boss, x, y, w, h, tooltip), _gfx(), _alpha(256), _transparency(false) {
+ : Widget(boss, x, y, w, h, tooltip), _gfx(), _alpha(255), _transparency(false) {
setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
_type = kGraphicsWidget;
}
GraphicsWidget::GraphicsWidget(GuiObject *boss, const Common::String &name, const char *tooltip)
- : Widget(boss, name, tooltip), _gfx(), _alpha(256), _transparency(false) {
+ : Widget(boss, name, tooltip), _gfx(), _alpha(255), _transparency(false) {
setFlags(WIDGET_ENABLED | WIDGET_CLEARBG);
_type = kGraphicsWidget;
}
@@ -686,6 +749,26 @@ void GraphicsWidget::setGfx(const Graphics::Surface *gfx) {
_gfx.copyFrom(*gfx);
}
+void GraphicsWidget::setAGfx(const Graphics::TransparentSurface *gfx, ThemeEngine::AutoScaleMode mode) {
+ _agfx.free();
+
+ if (!gfx || !gfx->getPixels())
+ return;
+
+ if (gfx->format.bytesPerPixel == 1) {
+ warning("GraphicsWidget::setGfx got paletted surface passed");
+ return;
+ }
+
+ if ((gfx->w > _w || gfx->h > _h) && mode == ThemeEngine::kAutoScaleNone) {
+ warning("GraphicsWidget has size %dx%d, but a surface with %dx%d is to be set", _w, _h, gfx->w, gfx->h);
+ return;
+ }
+
+ _agfx.copyFrom(*gfx);
+ _mode = mode;
+}
+
void GraphicsWidget::setGfx(int w, int h, int r, int g, int b) {
if (w == -1)
w = _w;
@@ -712,6 +795,23 @@ void GraphicsWidget::drawWidget() {
const int y = _y + (_h - _gfx.h) / 2;
g_gui.theme()->drawSurfaceClip(Common::Rect(x, y, x + _gfx.w, y + _gfx.h), getBossClipRect(), _gfx, _state, _alpha, _transparency);
+ } else if (_agfx.getPixels()) {
+ // Check whether the set up surface needs to be converted to the GUI
+ // color format.
+ const Graphics::PixelFormat &requiredFormat = g_gui.theme()->getPixelFormat();
+ if (_agfx.format != requiredFormat) {
+ _agfx.convertToInPlace(requiredFormat);
+ }
+
+ if (_mode == GUI::ThemeEngine::kAutoScaleNone) {
+ const int x = _x + (_w - _agfx.w) / 2;
+ const int y = _y + (_h - _agfx.h) / 2;
+
+ g_gui.theme()->drawASurface(Common::Rect(x, y, x + _agfx.w, y + _agfx.h), _agfx, _mode, _alpha);
+
+ } else {
+ g_gui.theme()->drawASurface(Common::Rect(_x, _y, _x + _w, _y + _h), _agfx, _mode, _alpha);
+ }
}
}
diff --git a/gui/widget.h b/gui/widget.h
index 0f4b300233..e9343f264c 100644
--- a/gui/widget.h
+++ b/gui/widget.h
@@ -80,6 +80,15 @@ enum {
kPressedButtonTime = 200
};
+enum {
+ kPicButtonStateEnabled = 0,
+ kPicButtonHighlight = 1,
+ kPicButtonStateDisabled = 2,
+ kPicButtonStatePressed = 3,
+
+ kPicButtonStateMax = 3
+};
+
/* Widget */
class Widget : public GuiObject {
friend class Dialog;
@@ -221,18 +230,24 @@ public:
PicButtonWidget(GuiObject *boss, const Common::String &name, const char *tooltip = 0, uint32 cmd = 0, uint8 hotkey = 0);
~PicButtonWidget();
- void setGfx(const Graphics::Surface *gfx);
- void setGfx(int w, int h, int r, int g, int b);
+ void setGfx(const Graphics::Surface *gfx, int statenum = kPicButtonStateEnabled);
+ void setAGfx(const Graphics::TransparentSurface *gfx, int statenum = kPicButtonStateEnabled, ThemeEngine::AutoScaleMode mode = ThemeEngine::kAutoScaleNone);
+ void setGfx(int w, int h, int r, int g, int b, int statenum = kPicButtonStateEnabled);
void useAlpha(int alpha) { _alpha = alpha; }
void useThemeTransparency(bool enable) { _transparency = enable; }
+ void setButtonDisplay(bool enable) {_showButton = enable; }
protected:
void drawWidget();
- Graphics::Surface _gfx;
+ Graphics::Surface _gfx[kPicButtonStateMax + 1];
+ Graphics::TransparentSurface _agfx[kPicButtonStateMax + 1];
int _alpha;
bool _transparency;
+ bool _showButton;
+ bool _isAlpha;
+ ThemeEngine::AutoScaleMode _mode;
};
/* CheckboxWidget */
@@ -351,6 +366,7 @@ public:
void setGfx(const Graphics::Surface *gfx);
void setGfx(int w, int h, int r, int g, int b);
+ void setAGfx(const Graphics::TransparentSurface *gfx, ThemeEngine::AutoScaleMode mode = ThemeEngine::kAutoScaleNone);
void useAlpha(int alpha) { _alpha = alpha; }
void useThemeTransparency(bool enable) { _transparency = enable; }
@@ -359,8 +375,10 @@ protected:
void drawWidget();
Graphics::Surface _gfx;
+ Graphics::TransparentSurface _agfx;
int _alpha;
bool _transparency;
+ ThemeEngine::AutoScaleMode _mode;
};
/* ContainerWidget */
diff --git a/gui/widgets/editable.cpp b/gui/widgets/editable.cpp
index 4f7e584c14..02defe9a56 100644
--- a/gui/widgets/editable.cpp
+++ b/gui/widgets/editable.cpp
@@ -185,6 +185,21 @@ bool EditableWidget::handleKeyDown(Common::KeyState state) {
forcecaret = true;
break;
+ case Common::KEYCODE_v:
+ if (g_system->hasFeature(OSystem::kFeatureClipboardSupport) && state.flags & Common::KBD_CTRL) {
+ if (g_system->hasTextInClipboard()) {
+ String text = g_system->getTextFromClipboard();
+ for (uint32 i = 0; i < text.size(); ++i) {
+ if (tryInsertChar(text[i], _caretPos))
+ ++_caretPos;
+ }
+ dirty = true;
+ }
+ } else {
+ defaultKeyDownHandler(state, dirty, forcecaret, handled);
+ }
+ break;
+
#ifdef MACOSX
// Let ctrl-a / ctrl-e move the caret to the start / end of the line.
//
diff --git a/gui/widgets/scrollcontainer.cpp b/gui/widgets/scrollcontainer.cpp
index 1b38478c11..9a7792730d 100644
--- a/gui/widgets/scrollcontainer.cpp
+++ b/gui/widgets/scrollcontainer.cpp
@@ -28,13 +28,13 @@
namespace GUI {
-ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h)
- : Widget(boss, x, y, w, h) {
+ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h, uint32 reflowCmd)
+ : Widget(boss, x, y, w, h), CommandSender(nullptr), _reflowCmd(reflowCmd) {
init();
}
-ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, const Common::String &name)
- : Widget(boss, name) {
+ScrollContainerWidget::ScrollContainerWidget(GuiObject *boss, const Common::String &name, uint32 reflowCmd)
+ : Widget(boss, name), CommandSender(nullptr), _reflowCmd(reflowCmd) {
init();
}
@@ -52,14 +52,14 @@ void ScrollContainerWidget::init() {
void ScrollContainerWidget::recalc() {
int scrollbarWidth = g_gui.xmlEval()->getVar("Globals.Scrollbar.Width", 0);
_limitH = _h;
-
+
//calculate virtual height
const int spacing = g_gui.xmlEval()->getVar("Global.Font.Height", 16); //on the bottom
int h = 0;
int min = spacing, max = 0;
Widget *ptr = _firstWidget;
while (ptr) {
- if (ptr != _verticalScroll) {
+ if (ptr != _verticalScroll && ptr->isVisible()) {
int y = ptr->getAbsY() - getChildY();
min = MIN(min, y - spacing);
max = MAX(max, y + ptr->getHeight() + spacing);
@@ -68,6 +68,8 @@ void ScrollContainerWidget::recalc() {
}
h = max - min;
+ if (h <= _limitH) _scrolledY = 0;
+
_verticalScroll->_numEntries = h;
_verticalScroll->_currentPos = _scrolledY;
_verticalScroll->_entriesPerPage = _limitH;
@@ -115,7 +117,10 @@ void ScrollContainerWidget::reflowLayout() {
ptr->reflowLayout();
ptr = ptr->next();
}
-
+
+ //hide and move widgets, if needed
+ sendCommand(_reflowCmd, 0);
+
//recalculate height
recalc();
@@ -124,7 +129,7 @@ void ScrollContainerWidget::reflowLayout() {
while (ptr) {
int y = ptr->getAbsY() - getChildY();
int h = ptr->getHeight();
- bool visible = true;
+ bool visible = ptr->isVisible();
if (y + h - _scrolledY < 0) visible = false;
if (y - _scrolledY > _limitH) visible = false;
ptr->setVisible(visible);
@@ -132,6 +137,7 @@ void ScrollContainerWidget::reflowLayout() {
}
_verticalScroll->setVisible(_verticalScroll->_numEntries > _limitH); //show when there is something to scroll
+ _verticalScroll->recalc();
}
void ScrollContainerWidget::drawWidget() {
diff --git a/gui/widgets/scrollcontainer.h b/gui/widgets/scrollcontainer.h
index 692c7e3507..c2d47370ee 100644
--- a/gui/widgets/scrollcontainer.h
+++ b/gui/widgets/scrollcontainer.h
@@ -29,16 +29,17 @@
namespace GUI {
-class ScrollContainerWidget: public Widget {
+class ScrollContainerWidget: public Widget, public CommandSender {
ScrollBarWidget *_verticalScroll;
int16 _scrolledX, _scrolledY;
uint16 _limitH;
+ uint32 _reflowCmd;
void recalc();
public:
- ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h);
- ScrollContainerWidget(GuiObject *boss, const Common::String &name);
+ ScrollContainerWidget(GuiObject *boss, int x, int y, int w, int h, uint32 reflowCmd = 0);
+ ScrollContainerWidget(GuiObject *boss, const Common::String &name, uint32 reflowCmd = 0);
~ScrollContainerWidget();
void init();
diff --git a/image/codecs/msrle.cpp b/image/codecs/msrle.cpp
index 89fe869a9e..bb1125e0af 100644
--- a/image/codecs/msrle.cpp
+++ b/image/codecs/msrle.cpp
@@ -101,7 +101,10 @@ void MSRLEDecoder::decode8(Common::SeekableReadStream &stream) {
// Copy data
if (output + value > output_end) {
- stream.skip(value);
+ if (stream.pos() + value >= stream.size())
+ break;
+ else
+ stream.skip(value);
continue;
}
diff --git a/ports.mk b/ports.mk
index 91bde729fd..286c48837e 100644
--- a/ports.mk
+++ b/ports.mk
@@ -461,7 +461,7 @@ win32dist: $(EXECUTABLE)
cp $(srcdir)/doc/de/LIESMICH $(WIN32PATH)/doc/de/LIESMICH.txt
cp $(srcdir)/doc/se/LasMig $(WIN32PATH)/doc/se/LasMig.txt
cp /usr/local/README-SDL.txt $(WIN32PATH)
- cp /usr/local/bin/SDL.dll $(WIN32PATH)
+ cp /usr/local/bin/SDL2.dll $(WIN32PATH)
cp $(srcdir)/dists/win32/graphics/left.bmp $(WIN32PATH)/graphics
cp $(srcdir)/dists/win32/graphics/scummvm-install.ico $(WIN32PATH)/graphics
cp $(srcdir)/dists/win32/migration.bat $(WIN32PATH)
diff --git a/snapcraft.yaml b/snapcraft.yaml
new file mode 100644
index 0000000000..178a323414
--- /dev/null
+++ b/snapcraft.yaml
@@ -0,0 +1,63 @@
+name: scummvm
+version: "1.9.0git"
+summary: ScummVM
+description: |
+ ScummVM is a program which allows you to run certain classic graphical
+ point-and-click adventure games, provided you already have their data
+ files. The clever part about this: ScummVM just replaces the executables
+ shipped with the game, allowing you to play them on systems for which
+ they were never designed!
+confinement: strict
+
+apps:
+ scummvm:
+ command: scummvm
+ plugs: [x11, home, pulseaudio, unity7, opengl]
+
+parts:
+ scummvm:
+ source: .
+ plugin: autotools
+# Quick test build
+# configflags:
+# - --disable-all-engines
+# - --enable-engine=scumm
+ build-packages:
+ - g++
+ - make
+ - libsdl2-dev
+ - libjpeg62-dev
+ - libmpeg2-4-dev
+ - libogg-dev
+ - libvorbis-dev
+ - libflac-dev
+ - libmad0-dev
+ - libpng12-dev
+ - libtheora-dev
+ - libfaad-dev
+ - libfluidsynth-dev
+ - libfreetype6-dev
+ - zlib1g-dev
+ - libunity-dev
+ stage-packages:
+ - libicu55
+ - libasound2
+ - libc6
+ - libfaad2
+ - libflac8
+ - libfluidsynth1
+ - libgl1-mesa-dri
+ - libgl1-mesa-glx
+ - libjpeg62
+ - libjpeg8
+ - libmad0
+ - libmpeg2-4
+ - libogg0
+ - libpng12-0
+ - libsdl2-2.0-0
+ - libsndio6.1
+ - libstdc++6
+ - libtheora0
+ - libvorbis0a
+ - libvorbisfile3
+ - zlib1g