aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Page2008-08-04 22:34:07 +0000
committerChristopher Page2008-08-04 22:34:07 +0000
commit4198ee962399305a4a158b1f43224c00e2e04a1b (patch)
treeeea375b5cb471509df2e2c5d9123d963a62403c6
parenta51f45407659bba43254b466d20b6af2e8f17ffd (diff)
parent4f5479ee744ac6b419cdf7ec1e96fbf7c83d36ef (diff)
downloadscummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.tar.gz
scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.tar.bz2
scummvm-rg350-4198ee962399305a4a158b1f43224c00e2e04a1b.zip
Merged revisions 33188-33189,33191-33193,33196,33198,33202-33203,33206,33210,33212,33218-33220,33222,33224-33226,33229-33243,33246,33248-33250,33252,33258-33261,33263,33266,33270,33272-33283,33285,33287-33290,33295-33298,33321,33325-33330,33332-33335,33337-33340,33342,33345,33347,33349-33350,33352-33357,33359-33367,33369-33371,33373,33375-33377,33379-33380,33383-33385,33387-33389,33392-33394,33400-33402,33404-33405,33407-33410,33412-33416,33418-33419,33425-33427,33432,33436-33438,33444,33446,33452-33453,33455-33459,33463-33464,33466-33471,33473-33474,33478,33490,33492,33495-33496,33509-33512,33518-33519,33522-33527,33529-33530,33537,33541,33544,33546,33550,33552-33554,33556,33558,33561-33562,33565,33568,33570,33574,33576,33578-33581,33584-33587,33590,33596,33604-33611,33614-33615,33617-33618,33620-33621 via svnmerge from
https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/trunk svn-id: r33624
-rw-r--r--AUTHORS16
-rw-r--r--NEWS17
-rw-r--r--backends/fs/abstract-fs.h8
-rw-r--r--backends/fs/posix/posix-fs.cpp24
-rw-r--r--backends/fs/windows/windows-fs.cpp2
-rw-r--r--backends/platform/PalmOS/Src/be_base.cpp5
-rw-r--r--backends/platform/gp2x/gp2x.cpp2
-rw-r--r--backends/platform/iphone/blit_arm.s156
-rw-r--r--backends/platform/iphone/iphone_keyboard.h6
-rw-r--r--backends/platform/iphone/iphone_keyboard.m16
-rw-r--r--backends/platform/iphone/iphone_main.m18
-rw-r--r--backends/platform/iphone/iphone_video.h5
-rw-r--r--backends/platform/iphone/iphone_video.m99
-rw-r--r--backends/platform/iphone/osys_iphone.cpp98
-rw-r--r--backends/platform/iphone/osys_iphone.h11
-rw-r--r--backends/platform/sdl/sdl.cpp108
-rw-r--r--backends/platform/sdl/sdl.h3
-rw-r--r--backends/platform/symbian/BuildPackageUpload_LocalSettings.pl31
-rw-r--r--backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in2
-rw-r--r--backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in2
-rw-r--r--backends/platform/symbian/src/SymbianActions.cpp4
-rw-r--r--backends/platform/symbian/src/SymbianOS.cpp49
-rw-r--r--backends/platform/symbian/src/SymbianOS.h3
-rw-r--r--backends/platform/symbian/src/main_features.inl39
-rw-r--r--backends/platform/symbian/src/portdefs.h1
-rw-r--r--backends/plugins/win32/win32-provider.cpp13
-rw-r--r--backends/saves/default/default-saves.cpp145
-rw-r--r--backends/saves/default/default-saves.h5
-rw-r--r--base/commandLine.cpp44
-rw-r--r--base/game.h4
-rw-r--r--base/main.cpp38
-rw-r--r--base/plugins.cpp3
-rw-r--r--common/advancedDetector.cpp274
-rw-r--r--common/advancedDetector.h5
-rw-r--r--common/algorithm.h54
-rw-r--r--common/config-file.cpp6
-rw-r--r--common/config-manager.cpp389
-rw-r--r--common/config-manager.h12
-rw-r--r--common/file.cpp197
-rw-r--r--common/file.h69
-rw-r--r--common/fs.cpp41
-rw-r--r--common/fs.h67
-rw-r--r--common/func.h248
-rw-r--r--common/hashmap.h4
-rw-r--r--common/ptr.h22
-rw-r--r--common/rect.h4
-rw-r--r--common/savefile.h17
-rw-r--r--common/str.cpp152
-rw-r--r--common/str.h3
-rw-r--r--common/stream.cpp79
-rw-r--r--common/stream.h70
-rw-r--r--common/system.cpp71
-rw-r--r--common/system.h19
-rw-r--r--common/unarj.cpp2
-rw-r--r--common/unarj.h2
-rwxr-xr-xconfigure20
-rw-r--r--dists/engine-data/kyra.datbin236767 -> 240402 bytes
-rw-r--r--dists/msvc7/kyra.vcproj12
-rw-r--r--dists/msvc7/parallaction.vcproj6
-rw-r--r--dists/msvc7/scummvm.sln6
-rw-r--r--dists/msvc7/scummvm.vcproj6
-rw-r--r--dists/msvc71/kyra.vcproj12
-rw-r--r--dists/msvc71/parallaction.vcproj6
-rw-r--r--dists/msvc71/scummvm.sln8
-rw-r--r--dists/msvc71/scummvm.vcproj6
-rw-r--r--dists/msvc8/kyra.vcproj16
-rw-r--r--dists/msvc8/parallaction.vcproj8
-rw-r--r--dists/msvc8/scummvm.sln6
-rw-r--r--dists/msvc8/scummvm.vcproj6
-rw-r--r--dists/msvc8/tinsel.vcproj486
-rw-r--r--dists/msvc9/kyra.vcproj16
-rw-r--r--dists/msvc9/parallaction.vcproj8
-rw-r--r--dists/msvc9/scummvm.sln6
-rw-r--r--dists/msvc9/scummvm.vcproj6
-rw-r--r--dists/msvc9/tinsel.vcproj487
-rw-r--r--engines/agos/agos.cpp5
-rw-r--r--engines/agos/debug.cpp4
-rw-r--r--engines/agos/saveload.cpp8
-rw-r--r--engines/cine/anim.cpp210
-rw-r--r--engines/cine/anim.h60
-rw-r--r--engines/cine/bg.cpp2
-rw-r--r--engines/cine/bg.h3
-rw-r--r--engines/cine/bg_list.cpp2
-rw-r--r--engines/cine/bg_list.h2
-rw-r--r--engines/cine/cine.h6
-rw-r--r--engines/cine/gfx.cpp66
-rw-r--r--engines/cine/gfx.h12
-rw-r--r--engines/cine/main_loop.cpp31
-rw-r--r--engines/cine/object.h2
-rw-r--r--engines/cine/part.cpp4
-rw-r--r--engines/cine/script.h20
-rw-r--r--engines/cine/script_fw.cpp54
-rw-r--r--engines/cine/script_os.cpp6
-rw-r--r--engines/cine/various.cpp1132
-rw-r--r--engines/cine/various.h2
-rw-r--r--engines/cruise/volume.cpp4
-rw-r--r--engines/drascula/drascula.cpp14
-rw-r--r--engines/drascula/interface.cpp25
-rw-r--r--engines/drascula/sound.cpp29
-rw-r--r--engines/engines.mk5
-rw-r--r--engines/gob/dataio.cpp11
-rw-r--r--engines/gob/dataio.h6
-rw-r--r--engines/gob/gob.cpp9
-rw-r--r--engines/gob/gob.h6
-rw-r--r--engines/gob/inter.cpp4
-rw-r--r--engines/gob/saveload.cpp49
-rw-r--r--engines/gob/saveload.h29
-rw-r--r--engines/gob/saveload_v2.cpp20
-rw-r--r--engines/gob/saveload_v3.cpp34
-rw-r--r--engines/gob/saveload_v4.cpp24
-rw-r--r--engines/kyra/detection.cpp219
-rw-r--r--engines/kyra/gui_lok.cpp7
-rw-r--r--engines/kyra/gui_lok.h2
-rw-r--r--engines/kyra/gui_v2.cpp1
-rw-r--r--engines/kyra/gui_v2.h2
-rw-r--r--engines/kyra/kyra_hof.cpp9
-rw-r--r--engines/kyra/kyra_hof.h24
-rw-r--r--engines/kyra/kyra_lok.cpp6
-rw-r--r--engines/kyra/kyra_v1.cpp12
-rw-r--r--engines/kyra/kyra_v1.h13
-rw-r--r--engines/kyra/lol.cpp806
-rw-r--r--engines/kyra/lol.h155
-rw-r--r--engines/kyra/module.mk2
-rw-r--r--engines/kyra/resource.cpp6
-rw-r--r--engines/kyra/saveload.cpp3
-rw-r--r--engines/kyra/screen.cpp114
-rw-r--r--engines/kyra/screen.h6
-rw-r--r--engines/kyra/screen_lol.cpp69
-rw-r--r--engines/kyra/screen_lol.h53
-rw-r--r--engines/kyra/screen_v2.cpp48
-rw-r--r--engines/kyra/screen_v2.h4
-rw-r--r--engines/kyra/script_hof.cpp2
-rw-r--r--engines/kyra/script_tim.cpp391
-rw-r--r--engines/kyra/script_tim.h63
-rw-r--r--engines/kyra/sequences_hof.cpp226
-rw-r--r--engines/kyra/sound.cpp21
-rw-r--r--engines/kyra/sound.h20
-rw-r--r--engines/kyra/sound_adlib.cpp29
-rw-r--r--engines/kyra/sound_towns.cpp2
-rw-r--r--engines/kyra/staticres.cpp152
-rw-r--r--engines/kyra/wsamovie.h2
-rw-r--r--engines/parallaction/balloons.cpp312
-rw-r--r--engines/parallaction/callables_ns.cpp120
-rw-r--r--engines/parallaction/detection.cpp36
-rw-r--r--engines/parallaction/dialogue.cpp48
-rw-r--r--engines/parallaction/disk.h51
-rw-r--r--engines/parallaction/disk_br.cpp572
-rw-r--r--engines/parallaction/exec.h28
-rw-r--r--engines/parallaction/exec_br.cpp43
-rw-r--r--engines/parallaction/exec_ns.cpp254
-rw-r--r--engines/parallaction/font.cpp140
-rw-r--r--engines/parallaction/gfxbase.cpp67
-rw-r--r--engines/parallaction/graphics.cpp202
-rw-r--r--engines/parallaction/graphics.h19
-rw-r--r--engines/parallaction/gui.cpp92
-rw-r--r--engines/parallaction/gui.h93
-rw-r--r--engines/parallaction/gui_br.cpp337
-rw-r--r--engines/parallaction/gui_ns.cpp941
-rw-r--r--engines/parallaction/input.cpp222
-rw-r--r--engines/parallaction/input.h47
-rw-r--r--engines/parallaction/inventory.cpp142
-rw-r--r--engines/parallaction/inventory.h27
-rw-r--r--engines/parallaction/module.mk1
-rw-r--r--engines/parallaction/objects.cpp10
-rw-r--r--engines/parallaction/objects.h22
-rw-r--r--engines/parallaction/parallaction.cpp135
-rw-r--r--engines/parallaction/parallaction.h83
-rw-r--r--engines/parallaction/parallaction_br.cpp130
-rw-r--r--engines/parallaction/parallaction_ns.cpp81
-rw-r--r--engines/parallaction/parser.cpp194
-rw-r--r--engines/parallaction/parser.h142
-rw-r--r--engines/parallaction/parser_br.cpp104
-rw-r--r--engines/parallaction/parser_ns.cpp23
-rw-r--r--engines/parallaction/walk.cpp490
-rw-r--r--engines/parallaction/walk.h84
-rw-r--r--engines/queen/queen.h4
-rw-r--r--engines/queen/sound.cpp43
-rw-r--r--engines/saga/displayinfo.h6
-rw-r--r--engines/saga/font.cpp13
-rw-r--r--engines/saga/font.h3
-rw-r--r--engines/saga/font_map.cpp131
-rw-r--r--engines/saga/interface.cpp39
-rw-r--r--engines/saga/itedata.cpp14
-rw-r--r--engines/saga/itedata.h2
-rw-r--r--engines/saga/saga.h3
-rw-r--r--engines/saga/sprite.cpp8
-rw-r--r--engines/scumm/charset.cpp24
-rw-r--r--engines/scumm/charset.h14
-rw-r--r--engines/scumm/debugger.cpp2
-rw-r--r--engines/scumm/file.cpp16
-rw-r--r--engines/scumm/file.h9
-rw-r--r--engines/scumm/file_nes.cpp15
-rw-r--r--engines/scumm/file_nes.h3
-rw-r--r--engines/scumm/gfx.cpp2
-rw-r--r--engines/scumm/he/script_v60he.cpp2
-rw-r--r--engines/scumm/he/script_v72he.cpp2
-rw-r--r--engines/scumm/he/script_v80he.cpp2
-rw-r--r--engines/scumm/he/wiz_he.cpp6
-rw-r--r--engines/scumm/imuse_digi/dimuse.cpp2
-rw-r--r--engines/scumm/resource.cpp4
-rw-r--r--engines/scumm/saveload.cpp8
-rw-r--r--engines/scumm/saveload.h8
-rw-r--r--engines/scumm/scumm-md5.h10
-rw-r--r--engines/scumm/scumm.h8
-rw-r--r--engines/scumm/string.cpp2
-rw-r--r--engines/sky/control.cpp2
-rw-r--r--engines/sky/disk.cpp4
-rw-r--r--engines/sky/logic.cpp3
-rw-r--r--engines/sky/sound.cpp15
-rw-r--r--engines/sky/sound.h2
-rw-r--r--engines/sword1/resman.cpp4
-rw-r--r--engines/sword2/resman.cpp5
-rw-r--r--engines/tinsel/actors.cpp897
-rw-r--r--engines/tinsel/actors.h125
-rw-r--r--engines/tinsel/anim.cpp404
-rw-r--r--engines/tinsel/anim.h71
-rw-r--r--engines/tinsel/background.cpp232
-rw-r--r--engines/tinsel/background.h107
-rw-r--r--engines/tinsel/bg.cpp189
-rw-r--r--engines/tinsel/cliprect.cpp313
-rw-r--r--engines/tinsel/cliprect.h76
-rw-r--r--engines/tinsel/config.cpp125
-rw-r--r--engines/tinsel/config.h72
-rw-r--r--engines/tinsel/coroutine.h147
-rw-r--r--engines/tinsel/cursor.cpp647
-rw-r--r--engines/tinsel/cursor.h56
-rw-r--r--engines/tinsel/debugger.cpp162
-rw-r--r--engines/tinsel/debugger.h49
-rw-r--r--engines/tinsel/detection.cpp277
-rw-r--r--engines/tinsel/dw.h119
-rw-r--r--engines/tinsel/effect.cpp134
-rw-r--r--engines/tinsel/events.cpp439
-rw-r--r--engines/tinsel/events.h84
-rw-r--r--engines/tinsel/faders.cpp175
-rw-r--r--engines/tinsel/faders.h55
-rw-r--r--engines/tinsel/film.h50
-rw-r--r--engines/tinsel/font.cpp96
-rw-r--r--engines/tinsel/font.h48
-rw-r--r--engines/tinsel/graphics.cpp440
-rw-r--r--engines/tinsel/graphics.h78
-rw-r--r--engines/tinsel/handle.cpp366
-rw-r--r--engines/tinsel/handle.h53
-rw-r--r--engines/tinsel/heapmem.cpp594
-rw-r--r--engines/tinsel/heapmem.h109
-rw-r--r--engines/tinsel/inventory.cpp4535
-rw-r--r--engines/tinsel/inventory.h142
-rw-r--r--engines/tinsel/mareels.cpp132
-rw-r--r--engines/tinsel/module.mk52
-rw-r--r--engines/tinsel/move.cpp1618
-rw-r--r--engines/tinsel/move.h43
-rw-r--r--engines/tinsel/multiobj.cpp533
-rw-r--r--engines/tinsel/multiobj.h124
-rw-r--r--engines/tinsel/music.cpp551
-rw-r--r--engines/tinsel/music.h118
-rw-r--r--engines/tinsel/object.cpp530
-rw-r--r--engines/tinsel/object.h206
-rw-r--r--engines/tinsel/palette.cpp440
-rw-r--r--engines/tinsel/palette.h144
-rw-r--r--engines/tinsel/pcode.cpp593
-rw-r--r--engines/tinsel/pcode.h155
-rw-r--r--engines/tinsel/pdisplay.cpp652
-rw-r--r--engines/tinsel/pid.h72
-rw-r--r--engines/tinsel/play.cpp507
-rw-r--r--engines/tinsel/polygons.cpp1862
-rw-r--r--engines/tinsel/polygons.h125
-rw-r--r--engines/tinsel/rince.cpp709
-rw-r--r--engines/tinsel/rince.h209
-rw-r--r--engines/tinsel/saveload.cpp475
-rw-r--r--engines/tinsel/savescn.cpp336
-rw-r--r--engines/tinsel/savescn.h103
-rw-r--r--engines/tinsel/scene.cpp306
-rw-r--r--engines/tinsel/scene.h73
-rw-r--r--engines/tinsel/sched.cpp345
-rw-r--r--engines/tinsel/sched.h110
-rw-r--r--engines/tinsel/scn.cpp80
-rw-r--r--engines/tinsel/scn.h68
-rw-r--r--engines/tinsel/scroll.cpp432
-rw-r--r--engines/tinsel/scroll.h77
-rw-r--r--engines/tinsel/serializer.h131
-rw-r--r--engines/tinsel/sound.cpp211
-rw-r--r--engines/tinsel/sound.h80
-rw-r--r--engines/tinsel/strres.cpp209
-rw-r--r--engines/tinsel/strres.h69
-rw-r--r--engines/tinsel/text.cpp279
-rw-r--r--engines/tinsel/text.h101
-rw-r--r--engines/tinsel/timers.cpp192
-rw-r--r--engines/tinsel/timers.h53
-rw-r--r--engines/tinsel/tinlib.cpp2980
-rw-r--r--engines/tinsel/tinlib.h41
-rw-r--r--engines/tinsel/tinsel.cpp999
-rw-r--r--engines/tinsel/tinsel.h143
-rw-r--r--engines/tinsel/token.cpp129
-rw-r--r--engines/tinsel/token.h57
-rw-r--r--graphics/font.cpp4
-rw-r--r--gui/credits.h19
-rw-r--r--gui/launcher.cpp12
-rw-r--r--gui/massadd.cpp11
-rw-r--r--ports.mk2
-rw-r--r--sound/midiparser_xmidi.cpp3
-rw-r--r--sound/softsynth/mt32.cpp28
-rw-r--r--test/common/bufferedreadstream.h27
-rw-r--r--test/common/bufferedseekablereadstream.h65
-rw-r--r--test/common/func.h60
-rw-r--r--test/common/ptr.h3
-rw-r--r--test/common/seekablesubreadstream.h20
-rw-r--r--test/common/str.h116
-rw-r--r--test/common/subreadstream.h15
-rw-r--r--tools/create_kyradat/create_kyradat.cpp23
-rw-r--r--tools/create_kyradat/create_kyradat.h37
-rw-r--r--tools/create_kyradat/hof_cd.h11
-rw-r--r--tools/create_kyradat/hof_demo.h5
-rw-r--r--tools/create_kyradat/lol_demo.h15
-rw-r--r--tools/create_kyradat/misc.h23
-rwxr-xr-xtools/credits.pl18
-rw-r--r--tools/scumm-md5.txt8
315 files changed, 39623 insertions, 3834 deletions
diff --git a/AUTHORS b/AUTHORS
index 2491f481a2..2272c20781 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -6,6 +6,11 @@ ScummVM Team
Max Horn
Eugene Sandulenko
+ Retired Project Leaders
+ -----------------------
+ Vincent Hamm - ScummVM co-founder, Original Cruise/CinE author
+ Ludvig Strigeus - Original ScummVM and SimonVM author
+
Engine Teams
------------
SCUMM:
@@ -94,10 +99,19 @@ ScummVM Team
SAGA:
Torbjorn Andersson
+ Sven Hesse
Filippos Karapetis
Andrew Kurushin
Eugene Sandulenko
+ Tinsel;:
+ Torbjorn Andersson
+ Paul Gilbert
+ Sven Hesse
+ Max Horn
+ Filippos Karapetis
+ Joost Peters
+
Touche:
Gregory Montoir
@@ -176,12 +190,10 @@ ScummVM Team
Ralph Brorsen - Help with GUI implementation
Jamieson Christian - iMUSE, MIDI, all things musical
Ruediger Hanke - Port: MorphOS
- Vincent Hamm - ScummVM co-founder, Original Cruise/CinE author
Felix Jakschitsch - Zak256 reverse engineering
Mutwin Kraus - Original MacOS porter
Peter Moraliyski - Port: GP32
Jeremy Newman - Former webmaster
- Ludvig Strigeus - Original ScummVM and SimonVM author
Lionel Ulmer - Port: X11
Won Star - Former GP32 porter
diff --git a/NEWS b/NEWS
index 93ead62d52..e008ae24a4 100644
--- a/NEWS
+++ b/NEWS
@@ -1,6 +1,10 @@
For a more comprehensive changelog for the latest experimental SVN code, see:
http://scummvm.sourceforge.net/daily/ChangeLog
+0.13.0 (????-??-??)
+ New Games:
+ - Added support for Discworld.
+
0.12.0 (????-??-??)
New Games:
- Added support for The Legend of Kyrandia: Book Two: Hand of Fate
@@ -19,11 +23,19 @@ For a more comprehensive changelog for the latest experimental SVN code, see:
- Plugged numerous memory leaks in all engines (part of GSoC'08 task)
AGOS:
+ - Fixed crashes during certain music in Amiga versions of Elvira 1 and
+ Simon the Sorcerer 1.
- Fixed palette issues in Amiga versions of Simon the Sorcerer 1.
+ Queen:
+ - Speech is played at the correct sample rate. (It used to be pitched a bit
+ too low.)
+
SCUMM:
- Rewrote parts of Digital iMUSE, fixing some bugs.
- Rewrote the internal timer code, fixing some speed issues in e.g. COMI.
+ - Improved support for sound effects in Amiga version of Zak McKracken.
+ - Added support for mixed Adlib/MIDI mode in Monkey Island 1 (Floppy).
0.11.1 (2008-02-29)
SCUMM:
@@ -675,9 +687,10 @@ For a more comprehensive changelog for the latest experimental SVN code, see:
OS X.
- Loading COMI savegames for disk 2 doesn't anymore require disk 1 first.
- Rewritten iMUSE engine, and many Music fixes (exp. Monkey Island 2).
-- Support for music in Humongous games and simon2dos/simon2talkie (XMIDI
+- Support for music in DOS versions of Humongous Entertainment games and
+ Simon the Sorcerer 2 (XMIDI format).
+- Support for music in floppy demo of Simon the Sorcerer 1 (Proprietary
format).
-- Support for music in simon1demo (Proprietary format).
- Complete music support for Simon the Sorcerer 2.
- Improved music and sound support in Zak256.
- Added Aspect Ratio option.
diff --git a/backends/fs/abstract-fs.h b/backends/fs/abstract-fs.h
index 8125ad7d95..97de40a2fc 100644
--- a/backends/fs/abstract-fs.h
+++ b/backends/fs/abstract-fs.h
@@ -64,7 +64,7 @@ protected:
*
* @param name String containing the name of the child to create a new node.
*/
- virtual AbstractFilesystemNode *getChild(const String &name) const = 0;
+ virtual AbstractFilesystemNode *getChild(const Common::String &name) const = 0;
/**
* The parent node of this directory.
@@ -100,7 +100,7 @@ public:
*
* @note By default, this method returns the value of getName().
*/
- virtual String getDisplayName() const { return getName(); }
+ virtual Common::String getDisplayName() const { return getName(); }
/**
* Returns the last component of the path pointed by this FilesystemNode.
@@ -111,12 +111,12 @@ public:
*
* @note This method is very architecture dependent, please check the concrete implementation for more information.
*/
- virtual String getName() const = 0;
+ virtual Common::String getName() const = 0;
/**
* Returns the 'path' of the current node, usable in fopen().
*/
- virtual String getPath() const = 0;
+ virtual Common::String getPath() const = 0;
/**
* Indicates whether this path refers to a directory or not.
diff --git a/backends/fs/posix/posix-fs.cpp b/backends/fs/posix/posix-fs.cpp
index 5cde32c851..10782a9057 100644
--- a/backends/fs/posix/posix-fs.cpp
+++ b/backends/fs/posix/posix-fs.cpp
@@ -42,8 +42,8 @@
*/
class POSIXFilesystemNode : public AbstractFilesystemNode {
protected:
- String _displayName;
- String _path;
+ Common::String _displayName;
+ Common::String _path;
bool _isDirectory;
bool _isValid;
@@ -59,17 +59,17 @@ public:
* @param path String with the path the new node should point to.
* @param verify true if the isValid and isDirectory flags should be verified during the construction.
*/
- POSIXFilesystemNode(const String &path, bool verify);
+ POSIXFilesystemNode(const Common::String &path, bool verify);
virtual bool exists() const { return access(_path.c_str(), F_OK) == 0; }
- virtual String getDisplayName() const { return _displayName; }
- virtual String getName() const { return _displayName; }
- virtual String getPath() const { return _path; }
+ virtual Common::String getDisplayName() const { return _displayName; }
+ virtual Common::String getName() const { return _displayName; }
+ virtual Common::String getPath() const { return _path; }
virtual bool isDirectory() const { return _isDirectory; }
virtual bool isReadable() const { return access(_path.c_str(), R_OK) == 0; }
virtual bool isWritable() const { return access(_path.c_str(), W_OK) == 0; }
- virtual AbstractFilesystemNode *getChild(const String &n) const;
+ virtual AbstractFilesystemNode *getChild(const Common::String &n) const;
virtual bool getChildren(AbstractFSList &list, ListMode mode, bool hidden) const;
virtual AbstractFilesystemNode *getParent() const;
@@ -119,7 +119,7 @@ POSIXFilesystemNode::POSIXFilesystemNode() {
_isDirectory = true;
}
-POSIXFilesystemNode::POSIXFilesystemNode(const String &p, bool verify) {
+POSIXFilesystemNode::POSIXFilesystemNode(const Common::String &p, bool verify) {
assert(p.size() > 0);
// Expand "~/" to the value of the HOME env variable
@@ -142,12 +142,12 @@ POSIXFilesystemNode::POSIXFilesystemNode(const String &p, bool verify) {
}
}
-AbstractFilesystemNode *POSIXFilesystemNode::getChild(const String &n) const {
+AbstractFilesystemNode *POSIXFilesystemNode::getChild(const Common::String &n) const {
// FIXME: Pretty lame implementation! We do no error checking to speak
// of, do not check if this is a special node, etc.
assert(_isDirectory);
- String newPath(_path);
+ Common::String newPath(_path);
if (_path.lastChar() != '/')
newPath += '/';
newPath += n;
@@ -175,7 +175,7 @@ bool POSIXFilesystemNode::getChildren(AbstractFSList &myList, ListMode mode, boo
continue;
}
- String newPath(_path);
+ Common::String newPath(_path);
if (newPath.lastChar() != '/')
newPath += '/';
newPath += dp->d_name;
@@ -236,7 +236,7 @@ AbstractFilesystemNode *POSIXFilesystemNode::getParent() const {
const char *start = _path.c_str();
const char *end = lastPathComponent(_path);
- return new POSIXFilesystemNode(String(start, end - start), true);
+ return new POSIXFilesystemNode(Common::String(start, end - start), true);
}
#endif //#if defined(UNIX)
diff --git a/backends/fs/windows/windows-fs.cpp b/backends/fs/windows/windows-fs.cpp
index cbb93e8cd6..ac2f521e21 100644
--- a/backends/fs/windows/windows-fs.cpp
+++ b/backends/fs/windows/windows-fs.cpp
@@ -245,7 +245,7 @@ WindowsFilesystemNode::WindowsFilesystemNode(const String &p, const bool current
_isDirectory = ((fileAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0);
_isValid = true;
// Add a trailing slash, if necessary.
- if (_path.lastChar() != '\\') {
+ if (_isDirectory && _path.lastChar() != '\\') {
_path += '\\';
}
}
diff --git a/backends/platform/PalmOS/Src/be_base.cpp b/backends/platform/PalmOS/Src/be_base.cpp
index afb3f15bae..32e68bde9f 100644
--- a/backends/platform/PalmOS/Src/be_base.cpp
+++ b/backends/platform/PalmOS/Src/be_base.cpp
@@ -30,6 +30,9 @@
#include "backends/timer/default/default-timer.h"
#include "sound/mixer.h"
+#define DEFAULT_SAVE_PATH "/PALM/Programs/ScummVM/Saved"
+
+
OSystem_PalmBase::OSystem_PalmBase() {
_overlayVisible = false;
@@ -100,7 +103,7 @@ void OSystem_PalmBase::initBackend() {
// Create the savefile manager, if none exists yet (we check for this to
// allow subclasses to provide their own).
if (_saveMgr == 0) {
- _saveMgr = new DefaultSaveFileManager();
+ _saveMgr = new DefaultSaveFileManager(DEFAULT_SAVE_PATH);
}
// Create and hook up the mixer, if none exists yet (we check for this to
diff --git a/backends/platform/gp2x/gp2x.cpp b/backends/platform/gp2x/gp2x.cpp
index c138f6c54d..e5f062ed35 100644
--- a/backends/platform/gp2x/gp2x.cpp
+++ b/backends/platform/gp2x/gp2x.cpp
@@ -219,7 +219,7 @@ void OSystem_GP2X::initBackend() {
// Create the savefile manager, if none exists yet (we check for this to
// allow subclasses to provide their own).
if (_savefile == 0) {
- _savefile = new DefaultSaveFileManager();
+ _savefile = new DefaultSaveFileManager(savePath);
}
// Create and hook up the mixer, if none exists yet (we check for this to
diff --git a/backends/platform/iphone/blit_arm.s b/backends/platform/iphone/blit_arm.s
index ae31fdcce4..417f3741cf 100644
--- a/backends/platform/iphone/blit_arm.s
+++ b/backends/platform/iphone/blit_arm.s
@@ -36,47 +36,47 @@ _blitLandscapeScreenRect16bpp:
@ r3 = h
@ <> = _screenWidth
@ <> = _screenHeight
- MOV r12,r13
- STMFD r13!,{r4-r11,r14}
- LDMFD r12,{r12,r14} @ r12 = _screenWidth
+ mov r12,r13
+ stmfd r13!,{r4-r11,r14}
+ ldmfd r12,{r12,r14} @ r12 = _screenWidth
@ r14 = _screenHeight
- ADD r14,r14,r3 @ r14 = _screenHeight + h
- MVN r11,#0
- MLA r11,r3,r12,r11 @ r11= _screenWidth*h-1
- ADD r12,r12,r12
+ add r14,r14,r3 @ r14 = _screenHeight + h
+ mvn r11,#0
+ mla r11,r3,r12,r11 @ r11= _screenWidth*h-1
+ add r12,r12,r12
xloop:
- SUBS r4,r3,#5 @ r4 = y = h
- BLE thin
+ subs r4,r3,#5 @ r4 = y = h
+ ble thin
yloop:
- LDRH r5, [r1],r12 @ r5 = *src src += _screenWidth
- LDRH r6, [r1],r12 @ r6 = *src src += _screenWidth
- LDRH r7, [r1],r12 @ r7 = *src src += _screenWidth
- LDRH r8, [r1],r12 @ r8 = *src src += _screenWidth
- LDRH r9, [r1],r12 @ r9 = *src src += _screenWidth
- LDRH r10,[r1],r12 @ r10= *src src += _screenWidth
- SUBS r4,r4,#6
- STRH r5, [r0],#2 @ *dst++ = r5
- STRH r6, [r0],#2 @ *dst++ = r6
- STRH r7, [r0],#2 @ *dst++ = r7
- STRH r8, [r0],#2 @ *dst++ = r8
- STRH r9, [r0],#2 @ *dst++ = r9
- STRH r10,[r0],#2 @ *dst++ = r10
- BGT yloop
+ ldrh r5, [r1],r12 @ r5 = *src src += _screenWidth
+ ldrh r6, [r1],r12 @ r6 = *src src += _screenWidth
+ ldrh r7, [r1],r12 @ r7 = *src src += _screenWidth
+ ldrh r8, [r1],r12 @ r8 = *src src += _screenWidth
+ ldrh r9, [r1],r12 @ r9 = *src src += _screenWidth
+ ldrh r10,[r1],r12 @ r10= *src src += _screenWidth
+ subs r4,r4,#6
+ strh r5, [r0],#2 @ *dst++ = r5
+ strh r6, [r0],#2 @ *dst++ = r6
+ strh r7, [r0],#2 @ *dst++ = r7
+ strh r8, [r0],#2 @ *dst++ = r8
+ strh r9, [r0],#2 @ *dst++ = r9
+ strh r10,[r0],#2 @ *dst++ = r10
+ bgt yloop
thin:
- ADDS r4,r4,#5
- BEQ lineend
+ adds r4,r4,#5
+ beq lineend
thin_loop:
- LDRH r5,[r1],r12 @ r5 = *src src += _screenWidth
- SUBS r4,r4,#1
- STRH r5,[r0],#2 @ *dst++ = r5
- BGT thin_loop
+ ldrh r5,[r1],r12 @ r5 = *src src += _screenWidth
+ subs r4,r4,#1
+ strh r5,[r0],#2 @ *dst++ = r5
+ bgt thin_loop
lineend:
- SUB r0,r0,r14,LSL #1 @ dst -= _screenHeight + h
- SUB r1,r1,r11,LSL #1 @ src += 1-_screenWidth*h
- SUBS r2,r2,#1
- BGT xloop
+ sub r0,r0,r14,LSL #1 @ dst -= _screenHeight + h
+ sub r1,r1,r11,LSL #1 @ src += 1-_screenWidth*h
+ subs r2,r2,#1
+ bgt xloop
- LDMFD r13!,{r4-r11,PC}
+ ldmfd r13!,{r4-r11,PC}
_blitLandscapeScreenRect8bpp:
@ r0 = dst
@@ -86,55 +86,55 @@ _blitLandscapeScreenRect8bpp:
@ <> = _palette
@ <> = _screenWidth
@ <> = _screenHeight
- MOV r12,r13
- STMFD r13!,{r4-r11,r14}
- LDMFD r12,{r11,r12,r14} @ r11 = _palette
+ mov r12,r13
+ stmfd r13!,{r4-r11,r14}
+ ldmfd r12,{r11,r12,r14} @ r11 = _palette
@ r12 = _screenWidth
@ r14 = _screenHeight
- ADD r14,r14,r3 @ r14 = _screenHeight + h
- MVN r6,#0
- MLA r6,r3,r12,r6 @ r6 = _screenWidth*h-1
+ add r14,r14,r3 @ r14 = _screenHeight + h
+ mvn r6,#0
+ mla r6,r3,r12,r6 @ r6 = _screenWidth*h-1
xloop8:
- MOV r4,r3 @ r4 = y = h
- SUBS r4,r3,#4 @ r4 = y = h
- BLE thin8
+ mov r4,r3 @ r4 = y = h
+ subs r4,r3,#4 @ r4 = y = h
+ ble thin8
yloop8:
- LDRB r5, [r1],r12 @ r5 = *src src += _screenWidth
- LDRB r7, [r1],r12 @ r7 = *src src += _screenWidth
- LDRB r8, [r1],r12 @ r8 = *src src += _screenWidth
- LDRB r9, [r1],r12 @ r9 = *src src += _screenWidth
- LDRB r10,[r1],r12 @ r10= *src src += _screenWidth
- ADD r5, r5, r5
- ADD r7, r7, r7
- ADD r8, r8, r8
- ADD r9, r9, r9
- ADD r10,r10,r10
- LDRH r5, [r11,r5]
- LDRH r7, [r11,r7]
- LDRH r8, [r11,r8]
- LDRH r9, [r11,r9]
- LDRH r10,[r11,r10]
- SUBS r4,r4,#5
- STRH r5, [r0],#2 @ *dst++ = r5
- STRH r7, [r0],#2 @ *dst++ = r7
- STRH r8, [r0],#2 @ *dst++ = r8
- STRH r9, [r0],#2 @ *dst++ = r9
- STRH r10,[r0],#2 @ *dst++ = r10
- BGT yloop8
+ ldrb r5, [r1],r12 @ r5 = *src src += _screenWidth
+ ldrb r7, [r1],r12 @ r7 = *src src += _screenWidth
+ ldrb r8, [r1],r12 @ r8 = *src src += _screenWidth
+ ldrb r9, [r1],r12 @ r9 = *src src += _screenWidth
+ ldrb r10,[r1],r12 @ r10= *src src += _screenWidth
+ add r5, r5, r5
+ add r7, r7, r7
+ add r8, r8, r8
+ add r9, r9, r9
+ add r10,r10,r10
+ ldrh r5, [r11,r5]
+ ldrh r7, [r11,r7]
+ ldrh r8, [r11,r8]
+ ldrh r9, [r11,r9]
+ ldrh r10,[r11,r10]
+ subs r4,r4,#5
+ strh r5, [r0],#2 @ *dst++ = r5
+ strh r7, [r0],#2 @ *dst++ = r7
+ strh r8, [r0],#2 @ *dst++ = r8
+ strh r9, [r0],#2 @ *dst++ = r9
+ strh r10,[r0],#2 @ *dst++ = r10
+ bgt yloop8
thin8:
- ADDS r4,r4,#4
- BEQ lineend8
+ adds r4,r4,#4
+ beq lineend8
thin_loop8:
- LDRB r5,[r1],r12 @ r5 = *src src += _screenWidth
- ADD r5,r5,r5
- LDRH r5,[r11,r5]
- SUBS r4,r4,#1
- STRH r5,[r0],#2 @ *dst++ = r5
- BGT thin_loop8
+ ldrb r5,[r1],r12 @ r5 = *src src += _screenWidth
+ add r5,r5,r5
+ ldrh r5,[r11,r5]
+ subs r4,r4,#1
+ strh r5,[r0],#2 @ *dst++ = r5
+ bgt thin_loop8
lineend8:
- SUB r0,r0,r14,LSL #1 @ dst -= _screenHeight + h
- SUB r1,r1,r6 @ src += 1-_screenWidth*h
- SUBS r2,r2,#1
- BGT xloop8
+ sub r0,r0,r14,LSL #1 @ dst -= _screenHeight + h
+ sub r1,r1,r6 @ src += 1-_screenWidth*h
+ subs r2,r2,#1
+ bgt xloop8
- LDMFD r13!,{r4-r11,PC}
+ ldmfd r13!,{r4-r11,PC}
diff --git a/backends/platform/iphone/iphone_keyboard.h b/backends/platform/iphone/iphone_keyboard.h
index 17a3836efd..6d381d561d 100644
--- a/backends/platform/iphone/iphone_keyboard.h
+++ b/backends/platform/iphone/iphone_keyboard.h
@@ -26,11 +26,7 @@
#import <UIKit/UIKit.h>
#import <UIKit/UITextView.h>
-@protocol KeyboardInputProtocol
-- (void)handleKeyPress:(unichar)c;
-@end
-
-@interface SoftKeyboard : UIKeyboard<KeyboardInputProtocol> {
+@interface SoftKeyboard : UIView {
id inputDelegate;
UITextView* inputView;
}
diff --git a/backends/platform/iphone/iphone_keyboard.m b/backends/platform/iphone/iphone_keyboard.m
index dc2d417746..bd4948e30a 100644
--- a/backends/platform/iphone/iphone_keyboard.m
+++ b/backends/platform/iphone/iphone_keyboard.m
@@ -25,19 +25,6 @@
#import "iphone_keyboard.h"
-// Override settings of the default keyboard implementation
-@implementation UIKeyboardImpl (DisableFeatures)
-
-- (BOOL)autoCapitalizationPreference {
- return false;
-}
-
-- (BOOL)autoCorrectionPreference {
- return false;
-}
-
-@end
-
@implementation TextInputHandler
- (id)initWithKeyboard:(SoftKeyboard*)keyboard; {
@@ -67,7 +54,8 @@
@implementation SoftKeyboard
- (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
+ //self = [super initWithFrame:frame];
+ self = [super initWithFrame:CGRectMake(0.0f, 0.0f, 0.0f, 0.0f)];
inputDelegate = nil;
inputView = [[TextInputHandler alloc] initWithKeyboard:self];
return self;
diff --git a/backends/platform/iphone/iphone_main.m b/backends/platform/iphone/iphone_main.m
index f7f5667bb5..b01e9f3f34 100644
--- a/backends/platform/iphone/iphone_main.m
+++ b/backends/platform/iphone/iphone_main.m
@@ -46,9 +46,14 @@ int main(int argc, char** argv) {
gArgc = argc;
gArgv = argv;
- [[NSAutoreleasePool alloc] init];
-
- return UIApplicationMain(argc, argv, [iPhoneMain class]);
+ NSAutoreleasePool *autoreleasePool = [
+ [ NSAutoreleasePool alloc ] init
+ ];
+
+ UIApplicationUseLegacyEvents(1);
+ int returnCode = UIApplicationMain(argc, argv, [iPhoneMain class]);
+ [ autoreleasePool release ];
+ return returnCode;
}
@implementation iPhoneMain
@@ -74,7 +79,10 @@ int main(int argc, char** argv) {
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// hide the status bar
[UIHardware _setStatusBarHeight:0.0f];
- [self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0];
+ //[self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0];
+
+ //[self setStatusBarStyle:UIStatusBarStyleBlackTranslucent animated:NO];
+ [self setStatusBarHidden:YES animated:YES];
_window = [[UIWindow alloc] initWithContentRect: [UIHardware fullScreenApplicationContentRect]];
[_window retain];
@@ -96,7 +104,7 @@ int main(int argc, char** argv) {
- (void)applicationResume:(GSEventRef)event {
[self removeApplicationBadge];
[UIHardware _setStatusBarHeight:0.0f];
- [self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0];
+ [self setStatusBarHidden:YES animated:YES];
[_view applicationResume];
}
diff --git a/backends/platform/iphone/iphone_video.h b/backends/platform/iphone/iphone_video.h
index 615b2e5345..6e4b446926 100644
--- a/backends/platform/iphone/iphone_video.h
+++ b/backends/platform/iphone/iphone_video.h
@@ -27,12 +27,11 @@
#define _IPHONE_VIDEO__H
#import <UIKit/UIKit.h>
-#import <UIKit/UIView-Geometry.h>
#import <GraphicsServices/GraphicsServices.h>
#import <Foundation/Foundation.h>
#import <CoreSurface/CoreSurface.h>
-#import <LayerKit/LKLayer.h>
+#import <QuartzCore/QuartzCore.h>
#import "iphone_keyboard.h"
@interface iPhoneView : UIView
@@ -41,7 +40,7 @@
NSMutableArray* _events;
NSLock* _lock;
SoftKeyboard* _keyboardView;
- LKLayer* _screenLayer;
+ CALayer* _screenLayer;
int _fullWidth;
int _fullHeight;
diff --git a/backends/platform/iphone/iphone_video.m b/backends/platform/iphone/iphone_video.m
index 6c6944045e..89f159c1d9 100644
--- a/backends/platform/iphone/iphone_video.m
+++ b/backends/platform/iphone/iphone_video.m
@@ -31,8 +31,8 @@
#import <GraphicsServices/GraphicsServices.h>
#import <Foundation/Foundation.h>
#import <CoreSurface/CoreSurface.h>
-#import <LayerKit/LKLayer.h>
#import <UIKit/UIKeyboardLayoutQWERTY.h>
+#import <QuartzCore/QuartzCore.h>
static iPhoneView *sharedInstance = nil;
static int _width = 0;
@@ -53,8 +53,8 @@ void iPhone_updateScreen() {
}
void iPhone_updateScreenRect(int x1, int y1, int x2, int y2) {
- NSRect rect = NSMakeRect(x1, y1, x2, y2);
- [sharedInstance performSelectorOnMainThread:@selector(updateScreenRect:) withObject: [NSValue valueWithRect:rect] waitUntilDone: NO];
+ //CGRect rect = CGRectMake(x1, y1, x2, y2);
+ //[sharedInstance performSelectorOnMainThread:@selector(updateScreenRect:) withObject: [NSValue valueWithRect:rect] waitUntilDone: NO];
}
void iPhone_lockSurface() {
@@ -146,9 +146,9 @@ bool getLocalMouseCoords(CGPoint *point) {
}
- (void)updateScreenRect:(id)rect {
- NSRect nsRect = [rect rectValue];
- CGRect cgRect = CGRectMake(nsRect.origin.x, nsRect.origin.y, nsRect.size.width, nsRect.size.height);
- [sharedInstance setNeedsDisplayInRect: cgRect];
+ // NSRect nsRect = [rect rectValue];
+ // CGRect cgRect = CGRectMake(nsRect.origin.x, nsRect.origin.y, nsRect.size.width, nsRect.size.height);
+ // [sharedInstance setNeedsDisplayInRect: cgRect];
}
- (void)initSurface {
@@ -178,7 +178,7 @@ bool getLocalMouseCoords(CGPoint *point) {
//printf("Surface created.\n");
CoreSurfaceBufferLock(_screenSurface, 3);
- LKLayer* screenLayer = [[LKLayer layer] retain];
+ CALayer* screenLayer = [[CALayer layer] retain];
if (_keyboardView != nil) {
[_keyboardView removeFromSuperview];
@@ -213,7 +213,7 @@ bool getLocalMouseCoords(CGPoint *point) {
_keyboardView = [[SoftKeyboard alloc] initWithFrame:keyFrame];
[_keyboardView setInputDelegate:self];
}
-
+
[self addSubview:[_keyboardView inputView]];
[self addSubview: _keyboardView];
[[_keyboardView inputView] becomeFirstResponder];
@@ -283,11 +283,13 @@ bool getLocalMouseCoords(CGPoint *point) {
}
- (void)mouseDown:(GSEvent*)event {
- struct CGPoint point = GSEventGetLocationInWindow(event);
-
+ //printf("mouseDown()\n");
+ CGRect rect = GSEventGetLocationInWindow(event);
+ CGPoint point = CGPointMake(rect.origin.x, rect.origin.y);
+
if (!getLocalMouseCoords(&point))
return;
-
+
[self addEvent:
[[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:kInputMouseDown], @"type",
@@ -298,12 +300,18 @@ bool getLocalMouseCoords(CGPoint *point) {
];
}
+- (void)touchesBegan {
+ //printf("touchesBegan()\n");
+}
+
- (void)mouseUp:(GSEvent*)event {
- struct CGPoint point = GSEventGetLocationInWindow(event);
-
+ //printf("mouseUp()\n");
+ CGRect rect = GSEventGetLocationInWindow(event);
+ CGPoint point = CGPointMake(rect.origin.x, rect.origin.y);
+
if (!getLocalMouseCoords(&point))
return;
-
+
[self addEvent:
[[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:kInputMouseUp], @"type",
@@ -316,11 +324,12 @@ bool getLocalMouseCoords(CGPoint *point) {
- (void)mouseDragged:(GSEvent*)event {
//printf("mouseDragged()\n");
- struct CGPoint point = GSEventGetLocationInWindow(event);
-
+ CGRect rect = GSEventGetLocationInWindow(event);
+ CGPoint point = CGPointMake(rect.origin.x, rect.origin.y);
+
if (!getLocalMouseCoords(&point))
return;
-
+
[self addEvent:
[[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithInt:kInputMouseDragged], @"type",
@@ -333,19 +342,21 @@ bool getLocalMouseCoords(CGPoint *point) {
- (void)mouseEntered:(GSEvent*)event {
//printf("mouseEntered()\n");
- // struct CGPoint point = GSEventGetLocationInWindow(event);
- //
- // if (!getLocalMouseCoords(&point))
- // return;
- //
- // [self addEvent:
- // [[NSDictionary alloc] initWithObjectsAndKeys:
- // [NSNumber numberWithInt:kInputMouseSecondToggled], @"type",
- // [NSNumber numberWithFloat:point.x], @"x",
- // [NSNumber numberWithFloat:point.y], @"y",
- // nil
- // ]
- // ];
+ CGRect rect = GSEventGetLocationInWindow(event);
+ CGPoint point = CGPointMake(rect.origin.x, rect.origin.y);
+
+
+ if (!getLocalMouseCoords(&point))
+ return;
+
+ [self addEvent:
+ [[NSDictionary alloc] initWithObjectsAndKeys:
+ [NSNumber numberWithInt:kInputMouseSecondToggled], @"type",
+ [NSNumber numberWithFloat:point.x], @"x",
+ [NSNumber numberWithFloat:point.y], @"y",
+ nil
+ ]
+ ];
}
- (void)mouseExited:(GSEvent*)event {
@@ -361,19 +372,19 @@ bool getLocalMouseCoords(CGPoint *point) {
- (void)mouseMoved:(GSEvent*)event
{
//printf("mouseMoved()\n");
- struct CGPoint point = GSEventGetLocationInWindow(event);
-
- if (!getLocalMouseCoords(&point))
- return;
-
- [self addEvent:
- [[NSDictionary alloc] initWithObjectsAndKeys:
- [NSNumber numberWithInt:kInputMouseSecondToggled], @"type",
- [NSNumber numberWithFloat:point.x], @"x",
- [NSNumber numberWithFloat:point.y], @"y",
- nil
- ]
- ];
+ // struct CGPoint point = GSEventGetLocationInWindow(event);
+ //
+ // if (!getLocalMouseCoords(&point))
+ // return;
+ //
+ // [self addEvent:
+ // [[NSDictionary alloc] initWithObjectsAndKeys:
+ // [NSNumber numberWithInt:kInputMouseSecondToggled], @"type",
+ // [NSNumber numberWithFloat:point.x], @"x",
+ // [NSNumber numberWithFloat:point.y], @"y",
+ // nil
+ // ]
+ // ];
}
- (void)handleKeyPress:(unichar)c {
@@ -391,7 +402,7 @@ bool getLocalMouseCoords(CGPoint *point) {
return TRUE;
}
-- (int)swipe:(UIViewSwipeDirection)num withEvent:(GSEvent*)event {
+- (int)swipe:(int)num withEvent:(GSEvent*)event {
//printf("swipe: %i\n", num);
[self addEvent:
diff --git a/backends/platform/iphone/osys_iphone.cpp b/backends/platform/iphone/osys_iphone.cpp
index 4cb90d35b9..3d5571cf3a 100644
--- a/backends/platform/iphone/osys_iphone.cpp
+++ b/backends/platform/iphone/osys_iphone.cpp
@@ -25,8 +25,6 @@
#if defined(IPHONE_BACKEND)
-#include <CoreGraphics/CGDirectDisplay.h>
-#include <CoreSurface/CoreSurface.h>
#include <unistd.h>
#include <pthread.h>
@@ -41,10 +39,15 @@
#include "backends/saves/default/default-saves.h"
#include "backends/timer/default/default-timer.h"
#include "sound/mixer.h"
+#include "sound/mixer_intern.h"
#include "gui/message.h"
#include "osys_iphone.h"
#include "blit_arm.h"
+#include <sys/time.h>
+
+#include <CoreGraphics/CGDirectDisplay.h>
+#include <CoreSurface/CoreSurface.h>
const OSystem::GraphicsMode OSystem_IPHONE::s_supportedGraphicsModes[] = {
{0, 0, 0}
@@ -85,13 +88,13 @@ int OSystem_IPHONE::timerHandler(int t) {
}
void OSystem_IPHONE::initBackend() {
- _savefile = new DefaultSaveFileManager();
- _mixer = new Audio::Mixer();
+ _savefile = new DefaultSaveFileManager(SCUMMVM_SAVE_PATH);
_timer = new DefaultTimerManager();
gettimeofday(&_startTime, NULL);
- setSoundCallback(Audio::Mixer::mixCallback, _mixer);
+ setupMixer();
+
setTimerCallback(&OSystem_IPHONE::timerHandler, 10);
OSystem::initBackend();
@@ -871,7 +874,7 @@ bool OSystem_IPHONE::pollEvent(Common::Event &event) {
suspendLoop();
break;
- case kInputKeyPressed:
+ case kInputKeyPressed: {
int keyPressed = (int)xUnit;
int ascii = keyPressed;
//printf("key: %i\n", keyPressed);
@@ -932,6 +935,7 @@ bool OSystem_IPHONE::pollEvent(Common::Event &event) {
event.kbd.ascii = _queuedInputEvent.kbd.ascii = ascii;
_needEventRestPeriod = true;
break;
+ }
case kInputSwipe: {
Common::KeyCode keycode = Common::KEYCODE_INVALID;
@@ -1088,10 +1092,21 @@ void OSystem_IPHONE::AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBuf
AudioQueueStop(s_AudioQueue.queue, false);
}
-bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) {
+void OSystem_IPHONE::mixCallback(void *sys, byte *samples, int len) {
+ OSystem_IPHONE *this_ = (OSystem_IPHONE *)sys;
+ assert(this_);
+
+ if (this_->_mixer)
+ this_->_mixer->mixCallback(samples, len);
+}
+
+void OSystem_IPHONE::setupMixer() {
//printf("setSoundCallback()\n");
- s_soundCallback = proc;
- s_soundParam = param;
+ _mixer = new Audio::MixerImpl(this);
+
+ s_soundCallback = mixCallback;
+ s_soundParam = this;
+
s_AudioQueue.dataFormat.mSampleRate = AUDIO_SAMPLE_RATE;
s_AudioQueue.dataFormat.mFormatID = kAudioFormatLinearPCM;
@@ -1105,7 +1120,8 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) {
if (AudioQueueNewOutput(&s_AudioQueue.dataFormat, AQBufferCallback, &s_AudioQueue, 0, kCFRunLoopCommonModes, 0, &s_AudioQueue.queue)) {
printf("Couldn't set the AudioQueue callback!\n");
- return false;
+ _mixer->setReady(false);
+ return;
}
uint32 bufferBytes = s_AudioQueue.frameCount * s_AudioQueue.dataFormat.mBytesPerFrame;
@@ -1113,7 +1129,8 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) {
for (int i = 0; i < AUDIO_BUFFERS; i++) {
if (AudioQueueAllocateBuffer(s_AudioQueue.queue, bufferBytes, &s_AudioQueue.buffers[i])) {
printf("Error allocating AudioQueue buffer!\n");
- return false;
+ _mixer->setReady(false);
+ return;
}
AQBufferCallback(&s_AudioQueue, s_AudioQueue.queue, s_AudioQueue.buffers[i]);
@@ -1122,10 +1139,12 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) {
AudioQueueSetParameter(s_AudioQueue.queue, kAudioQueueParam_Volume, 1.0);
if (AudioQueueStart(s_AudioQueue.queue, NULL)) {
printf("Error starting the AudioQueue!\n");
- return false;
+ _mixer->setReady(false);
+ return;
}
-
- return true;
+
+ _mixer->setOutputRate(AUDIO_SAMPLE_RATE);
+ _mixer->setReady(true);
}
int OSystem_IPHONE::getOutputSampleRate() const {
@@ -1146,6 +1165,11 @@ void OSystem_IPHONE::setTimerCallback(TimerProc callback, int interval) {
void OSystem_IPHONE::quit() {
}
+void OSystem_IPHONE::getTimeAndDate(struct tm &t) const {
+ time_t curTime = time(0);
+ t = *localtime(&curTime);
+}
+
void OSystem_IPHONE::setWindowCaption(const char *caption) {
}
@@ -1169,17 +1193,7 @@ OSystem *OSystem_IPHONE_create() {
}
const char* OSystem_IPHONE::getConfigPath() {
- if (s_is113OrHigher)
- return SCUMMVM_PREFS_PATH;
- else
- return SCUMMVM_OLD_PREFS_PATH;
-}
-
-const char* OSystem_IPHONE::getSavePath() {
- if (s_is113OrHigher)
- return SCUMMVM_SAVE_PATH;
- else
- return SCUMMVM_OLD_SAVE_PATH;
+ return SCUMMVM_PREFS_PATH;
}
void OSystem_IPHONE::migrateApp() {
@@ -1193,7 +1207,7 @@ void OSystem_IPHONE::migrateApp() {
if (!file.exists()) {
system("mkdir " SCUMMVM_ROOT_PATH);
system("mkdir " SCUMMVM_SAVE_PATH);
-
+
// Copy over the prefs file
system("cp " SCUMMVM_OLD_PREFS_PATH " " SCUMMVM_PREFS_PATH);
@@ -1207,24 +1221,24 @@ void OSystem_IPHONE::migrateApp() {
void iphone_main(int argc, char *argv[]) {
- OSystem_IPHONE::migrateApp();
-
- // Redirect stdout and stderr if we're launching from the Springboard.
- if (argc == 2 && strcmp(argv[1], "--launchedFromSB") == 0) {
- FILE *newfp = fopen("/tmp/scummvm.log", "a");
- if (newfp != NULL) {
- fclose(stdout);
- fclose(stderr);
- *stdout = *newfp;
- *stderr = *newfp;
- setbuf(stdout, NULL);
- setbuf(stderr, NULL);
-
- //extern int gDebugLevel;
- //gDebugLevel = 10;
- }
+ //OSystem_IPHONE::migrateApp();
+
+ FILE *newfp = fopen("/var/mobile/.scummvm.log", "a");
+ if (newfp != NULL) {
+ fclose(stdout);
+ fclose(stderr);
+ *stdout = *newfp;
+ *stderr = *newfp;
+ setbuf(stdout, NULL);
+ setbuf(stderr, NULL);
+
+ //extern int gDebugLevel;
+ //gDebugLevel = 10;
}
+ system("mkdir " SCUMMVM_ROOT_PATH);
+ system("mkdir " SCUMMVM_SAVE_PATH);
+
g_system = OSystem_IPHONE_create();
assert(g_system);
diff --git a/backends/platform/iphone/osys_iphone.h b/backends/platform/iphone/osys_iphone.h
index a01cad480a..c058686c8c 100644
--- a/backends/platform/iphone/osys_iphone.h
+++ b/backends/platform/iphone/osys_iphone.h
@@ -29,6 +29,8 @@
#include "iphone_common.h"
#include "common/system.h"
#include "common/events.h"
+#include "sound/mixer_intern.h"
+#include "backends/fs/posix/posix-fs-factory.h"
#include <AudioToolbox/AudioQueue.h>
@@ -62,7 +64,7 @@ protected:
static bool s_is113OrHigher;
Common::SaveFileManager *_savefile;
- Audio::Mixer *_mixer;
+ Audio::MixerImpl *_mixer;
Common::TimerManager *_timer;
Graphics::Surface _framebuffer;
@@ -152,12 +154,16 @@ public:
virtual void unlockMutex(MutexRef mutex);
virtual void deleteMutex(MutexRef mutex);
- virtual bool setSoundCallback(SoundProc proc, void *param);
+ static void mixCallback(void *sys, byte *samples, int len);
+ virtual void setupMixer(void);
virtual int getOutputSampleRate() const;
virtual void setTimerCallback(TimerProc callback, int interval);
virtual void quit();
+ FilesystemFactory *getFilesystemFactory() { return &POSIXFilesystemFactory::instance(); }
+ virtual void getTimeAndDate(struct tm &t) const;
+
virtual void setWindowCaption(const char *caption);
virtual Common::SaveFileManager *getSavefileManager();
@@ -166,7 +172,6 @@ public:
static void migrateApp();
static const char* getConfigPath();
- static const char* getSavePath();
protected:
inline void addDirtyRect(int16 x1, int16 y1, int16 w, int16 h);
diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp
index 76ac91c282..75bac0f536 100644
--- a/backends/platform/sdl/sdl.cpp
+++ b/backends/platform/sdl/sdl.cpp
@@ -26,6 +26,11 @@
#include "backends/platform/sdl/sdl.h"
#include "common/config-manager.h"
#include "common/events.h"
+#include "common/file.h"
+#if defined(WIN32) && defined(ARRAYSIZE)
+// winnt.h defines ARRAYSIZE, but we want our own one... - this is needed before including util.h
+#undef ARRAYSIZE
+#endif
#include "common/util.h"
#include "backends/saves/default/default-saves.h"
@@ -40,6 +45,7 @@
#define SAMPLES_PER_SEC 22050
//#define SAMPLES_PER_SEC 44100
+
/*
* Include header files needed for the getFilesystemFactory() method.
*/
@@ -52,6 +58,18 @@
#endif
+#if defined(UNIX)
+#ifdef MACOSX
+#define DEFAULT_CONFIG_FILE "Library/Preferences/ScummVM Preferences"
+#else
+#define DEFAULT_CONFIG_FILE ".scummvmrc"
+#endif
+#else
+#define DEFAULT_CONFIG_FILE "scummvm.ini"
+#endif
+
+
+
static Uint32 timer_handler(Uint32 interval, void *param) {
((DefaultTimerManager *)param)->handler();
return interval;
@@ -170,6 +188,10 @@ OSystem_SDL::OSystem_SDL()
_joystick(0),
_currentShakePos(0), _newShakePos(0),
_paletteDirtyStart(0), _paletteDirtyEnd(0),
+#ifdef MIXER_DOUBLE_BUFFERING
+ _soundMutex(0), _soundCond(0), _soundThread(0),
+ _soundThreadIsRunning(false), _soundThreadShouldQuit(false),
+#endif
_savefile(0),
_mixer(0),
_timer(0),
@@ -241,6 +263,92 @@ FilesystemFactory *OSystem_SDL::getFilesystemFactory() {
#endif
}
+static Common::String getDefaultConfigFileName() {
+ char configFile[MAXPATHLEN];
+#if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__)
+ OSVERSIONINFO win32OsVersion;
+ ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO));
+ win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
+ GetVersionEx(&win32OsVersion);
+ // Check for non-9X version of Windows.
+ if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) {
+ // Use the Application Data directory of the user profile.
+ if (win32OsVersion.dwMajorVersion >= 5) {
+ if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile)))
+ error("Unable to access application data directory");
+ } else {
+ if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile)))
+ error("Unable to access user profile directory");
+
+ strcat(configFile, "\\Application Data");
+ CreateDirectory(configFile, NULL);
+ }
+
+ strcat(configFile, "\\ScummVM");
+ CreateDirectory(configFile, NULL);
+ strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
+
+ if (fopen(configFile, "r") == NULL) {
+ // Check windows directory
+ char oldConfigFile[MAXPATHLEN];
+ GetWindowsDirectory(oldConfigFile, MAXPATHLEN);
+ strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE);
+ if (fopen(oldConfigFile, "r")) {
+ printf("The default location of the config file (scummvm.ini) in ScummVM has changed,\n");
+ printf("under Windows NT4/2000/XP/Vista. You may want to consider moving your config\n");
+ printf("file from the old default location:\n");
+ printf("%s\n", oldConfigFile);
+ printf("to the new default location:\n");
+ printf("%s\n\n", configFile);
+ strcpy(configFile, oldConfigFile);
+ }
+ }
+ } else {
+ // Check windows directory
+ GetWindowsDirectory(configFile, MAXPATHLEN);
+ strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
+ }
+#elif defined(UNIX)
+ // On UNIX type systems, by default we store the config file inside
+ // to the HOME directory of the user.
+ //
+ // GP2X is Linux based but Home dir can be read only so do not use
+ // it and put the config in the executable dir.
+ //
+ // On the iPhone, the home dir of the user when you launch the app
+ // from the Springboard, is /. Which we don't want.
+ const char *home = getenv("HOME");
+ if (home != NULL && strlen(home) < MAXPATHLEN)
+ snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE);
+ else
+ strcpy(configFile, DEFAULT_CONFIG_FILE);
+#else
+ strcpy(configFile, DEFAULT_CONFIG_FILE);
+#endif
+
+ return configFile;
+}
+
+Common::SeekableReadStream *OSystem_SDL::openConfigFileForReading() {
+ Common::File *confFile = new Common::File();
+ assert(confFile);
+ if (!confFile->open(getDefaultConfigFileName())) {
+ delete confFile;
+ confFile = 0;
+ }
+ return confFile;
+}
+
+Common::WriteStream *OSystem_SDL::openConfigFileForWriting() {
+ Common::DumpFile *confFile = new Common::DumpFile();
+ assert(confFile);
+ if (!confFile->open(getDefaultConfigFileName())) {
+ delete confFile;
+ confFile = 0;
+ }
+ return confFile;
+}
+
void OSystem_SDL::setWindowCaption(const char *caption) {
SDL_WM_SetCaption(caption, caption);
}
diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h
index 4ad588f5f5..1c1381ec5c 100644
--- a/backends/platform/sdl/sdl.h
+++ b/backends/platform/sdl/sdl.h
@@ -210,6 +210,9 @@ public:
virtual Common::SaveFileManager *getSavefileManager();
virtual FilesystemFactory *getFilesystemFactory();
+ virtual Common::SeekableReadStream *openConfigFileForReading();
+ virtual Common::WriteStream *openConfigFileForWriting();
+
protected:
bool _inited;
diff --git a/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl b/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl
index 5d85fc03a2..12e5f8f0c4 100644
--- a/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl
+++ b/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl
@@ -2,12 +2,11 @@
##################################################################################################################
@WorkingEngines = qw(
- scumm agos sky queen gob saga
- kyra lure agi
+ scumm agos sky queen gob saga drascula
+ kyra lure agi touche parallaction
);
@TestingEngines = qw(
- cine cruise touche parallaction
- drascula igor made m4
+ cruise igor made m4 cine
);
@BrokenEngines = qw(
sword1
@@ -29,21 +28,21 @@
);
# these are normally enabled for each variation
- $DefaultFeatures = qw(zlib tremor);
- #$DefaultFeatures = qw(zlib mad tremor);
+ #$DefaultFeatures = qw(zlib,mad);
+ $DefaultFeatures = qw(zlib,mad,tremor);
- # you can use these below for speed & clarity or override with custom settings
- $DefaultTopMacros = "
- MACRO USE_ZLIB // LIB:zlib.lib
- //MACRO USE_MAD // LIB:libmad.lib
- MACRO USE_TREMOR // LIB:libtremor.lib
- ";
+ # you can use these below for speed & clarity or override with custom settings
+ $DefaultTopMacros = "
+ MACRO USE_ZLIB // LIB:zlib.lib
+ MACRO USE_MAD // LIB:libmad.lib
+ MACRO USE_TREMOR // LIB:libtremor.lib
+ ";
- $DefaultBottomMacros = "
- MACRO DISABLE_SWORD1 // LIB:scummvm_sword1.lib
- MACRO DISABLE_SWORD2 // LIB:scummvm_sword2.lib
- ";
+ $DefaultBottomMacros = "
+ MACRO DISABLE_SWORD1 // LIB:scummvm_sword1.lib
+ MACRO DISABLE_SWORD2 // LIB:scummvm_sword2.lib
+ ";
##################################################################################################################
##
diff --git a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in
index 3fea916e43..8daf76138c 100644
--- a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in
+++ b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in
@@ -50,7 +50,7 @@ LANG SC
END
EPOCSTACKSIZE 80000
-EPOCHEAPSIZE 3000000 64000000
+EPOCHEAPSIZE 5000000 64000000
START BITMAP ScummVM.mbm
TARGETPATH \Resource\Apps
diff --git a/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in b/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in
index 0013d061ca..cf3d0c1d7b 100644
--- a/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in
+++ b/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in
@@ -51,7 +51,7 @@ LANG SC
END
EPOCSTACKSIZE 80000
-EPOCHEAPSIZE 3000000 64000000
+EPOCHEAPSIZE 5000000 64000000
START BITMAP ScummVM.mbm
TARGETPATH \Resource\Apps
diff --git a/backends/platform/symbian/src/SymbianActions.cpp b/backends/platform/symbian/src/SymbianActions.cpp
index 8fc35e9f8d..da127eaec6 100644
--- a/backends/platform/symbian/src/SymbianActions.cpp
+++ b/backends/platform/symbian/src/SymbianActions.cpp
@@ -140,6 +140,8 @@ void SymbianActions::initInstanceGame() {
bool is_touche = (gameid == "touche");
bool is_agi = (gameid == "agi");
bool is_parallaction = (gameid == "parallaction");
+ bool is_lure = (gameid == "lure");
+ bool is_feeble = (gameid == "feeble");
Actions::initInstanceGame();
@@ -175,7 +177,7 @@ void SymbianActions::initInstanceGame() {
// Skip text
if (!is_cine && !is_parallaction)
_action_enabled[ACTION_SKIP_TEXT] = true;
- if (is_simon || is_sky || is_sword2 || is_queen || is_sword1 || is_gob || is_saga || is_kyra || is_touche)
+ if (is_simon || is_sky || is_sword2 || is_queen || is_sword1 || is_gob || is_saga || is_kyra || is_touche || is_lure || is_feeble)
_key_action[ACTION_SKIP_TEXT].setKey(Common::KEYCODE_ESCAPE, Common::KEYCODE_ESCAPE); // Escape key
else {
_key_action[ACTION_SKIP_TEXT].setKey(SDLK_PERIOD);
diff --git a/backends/platform/symbian/src/SymbianOS.cpp b/backends/platform/symbian/src/SymbianOS.cpp
index 660b0c69ed..0ce44d1704 100644
--- a/backends/platform/symbian/src/SymbianOS.cpp
+++ b/backends/platform/symbian/src/SymbianOS.cpp
@@ -30,6 +30,7 @@
#include "backends/platform/symbian/src/SymbianActions.h"
#include "common/config-manager.h"
#include "common/events.h"
+#include "common/file.h"
#include "gui/Actions.h"
#include "gui/Key.h"
#include "gui/message.h"
@@ -42,6 +43,10 @@
#define SAMPLES_PER_SEC 16000
#endif
+
+#define DEFAULT_CONFIG_FILE "scummvm.ini"
+
+
#define KInputBufferLength 128
// Symbian libc file functionality in order to provide shared file handles
struct TSymbianFileEntry {
@@ -122,6 +127,34 @@ FilesystemFactory *OSystem_SDL_Symbian::getFilesystemFactory() {
return &SymbianFilesystemFactory::instance();
}
+static Common::String getDefaultConfigFileName() {
+ char configFile[MAXPATHLEN];
+ strcpy(configFile, Symbian::GetExecutablePath());
+ strcat(configFile, DEFAULT_CONFIG_FILE);
+ return configFile;
+}
+
+Common::SeekableReadStream *OSystem_SDL_Symbian::openConfigFileForReading() {
+ Common::File *confFile = new Common::File();
+ assert(confFile);
+ if (!confFile->open(getDefaultConfigFileName())) {
+ delete confFile;
+ confFile = 0;
+ }
+ return confFile;
+}
+
+Common::WriteStream *OSystem_SDL_Symbian::openConfigFileForWriting() {
+ Common::DumpFile *confFile = new Common::DumpFile();
+ assert(confFile);
+ if (!confFile->open(getDefaultConfigFileName())) {
+ delete confFile;
+ confFile = 0;
+ }
+ return confFile;
+}
+
+
OSystem_SDL_Symbian::zoneDesc OSystem_SDL_Symbian::_zones[TOTAL_ZONES] = {
{ 0, 0, 320, 145 },
{ 0, 145, 150, 55 },
@@ -617,9 +650,13 @@ bool symbian_feof(FILE* handle) {
long int symbian_ftell(FILE* handle) {
TInt pos = 0;
+ TSymbianFileEntry* entry = ((TSymbianFileEntry*)(handle));
- ((TSymbianFileEntry*)(handle))->iFileHandle.Seek(ESeekCurrent, pos);
-
+ entry->iFileHandle.Seek(ESeekCurrent, pos);
+ if(entry->iInputPos != KErrNotFound)
+ {
+ pos+=(entry->iInputPos - entry->iInputBufferLen);
+ }
return pos;
}
@@ -627,6 +664,7 @@ int symbian_fseek(FILE* handle, long int offset, int whence) {
TSeek seekMode = ESeekStart;
TInt pos = offset;
+ TSymbianFileEntry* entry = ((TSymbianFileEntry*)(handle));
switch(whence) {
case SEEK_SET:
@@ -634,6 +672,9 @@ int symbian_fseek(FILE* handle, long int offset, int whence) {
break;
case SEEK_CUR:
seekMode = ESeekCurrent;
+ if(entry->iInputPos != KErrNotFound) {
+ pos+=(entry->iInputPos - entry->iInputBufferLen);
+ }
break;
case SEEK_END:
seekMode = ESeekEnd;
@@ -641,9 +682,9 @@ int symbian_fseek(FILE* handle, long int offset, int whence) {
}
- ((TSymbianFileEntry*)(handle))->iInputPos = KErrNotFound;
+ entry->iInputPos = KErrNotFound;
- return ((TSymbianFileEntry*)(handle))->iFileHandle.Seek(seekMode, pos);
+ return entry->iFileHandle.Seek(seekMode, pos);
}
void symbian_clearerr(FILE* /*handle*/) {
diff --git a/backends/platform/symbian/src/SymbianOS.h b/backends/platform/symbian/src/SymbianOS.h
index 71d24f6286..68a6fb492f 100644
--- a/backends/platform/symbian/src/SymbianOS.h
+++ b/backends/platform/symbian/src/SymbianOS.h
@@ -71,6 +71,9 @@ protected:
static void symbianMixCallback(void *s, byte *samples, int len);
virtual FilesystemFactory *getFilesystemFactory();
+
+ virtual Common::SeekableReadStream *openConfigFileForReading();
+ virtual Common::WriteStream *openConfigFileForWriting();
public:
// vibration support
#ifdef USE_VIBRA_SE_PXXX
diff --git a/backends/platform/symbian/src/main_features.inl b/backends/platform/symbian/src/main_features.inl
index f572ddb3dd..30bbbea52c 100644
--- a/backends/platform/symbian/src/main_features.inl
+++ b/backends/platform/symbian/src/main_features.inl
@@ -27,62 +27,61 @@
// we want a list of supported engines visible in the program,
// because we also release special builds with only one engine
-#ifndef DISABLE_SCUMM
+#ifdef ENABLE_SCUMM
"SCUMM "
#endif
-#ifndef DISABLE_AGOS
+#ifdef ENABLE_AGOS
"AGOS "
#endif
-#ifndef DISABLE_SKY
+#ifdef ENABLE_SKY
"Sky "
#endif
-#ifndef DISABLE_QUEEN
+#ifdef ENABLE_QUEEN
"Queen "
#endif
-#ifndef DISABLE_GOB
+#ifdef ENABLE_GOB
"Gob "
#endif
-#ifndef DISABLE_SAGA
+#ifdef ENABLE_SAGA
"Saga "
#endif
-#ifndef DISABLE_KYRA
+#ifdef ENABLE_KYRA
"Kyra "
#endif
-#ifndef DISABLE_SWORD1
+#ifdef ENABLE_SWORD1
"Sword1 "
#endif
-#ifndef DISABLE_SWORD2
+#ifdef ENABLE_SWORD2
"Sword2 "
#endif
-#ifndef DISABLE_CINE
+#ifdef ENABLE_CINE
"Cine "
#endif
-#ifndef DISABLE_LURE
+#ifdef ENABLE_LURE
"Lure "
#endif
-#ifndef DISABLE_AGI
+#ifdef ENABLE_AGI
"AGI "
#endif
-#ifndef DISABLE_TOUCHE
+#ifdef ENABLE_TOUCHE
"Touche "
#endif
-#ifndef DISABLE_DRASCULA
+#ifdef ENABLE_DRASCULA
"Drascula "
#endif
-#ifndef DISABLE_IGOR
+#ifdef ENABLE_IGOR
"Igor "
#endif
-#ifndef DISABLE_PARALLACTION
+#ifdef ENABLE_PARALLACTION
"Parallaction "
#endif
-#ifndef DISABLE_CRUISE
+#ifdef ENABLE_CRUISE
"Cruise "
#endif
-#ifndef DISABLE_MADE
+#ifdef ENABLE_MADE
"MADE "
#endif
-
-#ifndef DISABLE_M4
+#ifdef ENABLE_M4
"M4 "
#endif
diff --git a/backends/platform/symbian/src/portdefs.h b/backends/platform/symbian/src/portdefs.h
index 06a4cf374c..4577824b33 100644
--- a/backends/platform/symbian/src/portdefs.h
+++ b/backends/platform/symbian/src/portdefs.h
@@ -157,5 +157,6 @@ void inline *scumm_bsearch(const void *key, const void *base, size_t nmemb, size
namespace Symbian {
extern void FatalError(const char *msg);
extern char* GetExecutablePath();
+#define DYNAMIC_MODULES 1
}
#endif
diff --git a/backends/plugins/win32/win32-provider.cpp b/backends/plugins/win32/win32-provider.cpp
index 64636d8096..b8fdd3d802 100644
--- a/backends/plugins/win32/win32-provider.cpp
+++ b/backends/plugins/win32/win32-provider.cpp
@@ -50,21 +50,14 @@ protected:
virtual VoidFunc findSymbol(const char *symbol) {
#ifndef _WIN32_WCE
- void *func = (void *)GetProcAddress((HMODULE)_dlHandle, symbol);
+ FARPROC func = GetProcAddress((HMODULE)_dlHandle, symbol);
#else
- void *func = (void *)GetProcAddress((HMODULE)_dlHandle, toUnicode(symbol));
+ FARPROC func = GetProcAddress((HMODULE)_dlHandle, toUnicode(symbol));
#endif
if (!func)
debug("Failed loading symbol '%s' from plugin '%s'", symbol, _filename.c_str());
- // FIXME HACK: This is a HACK to circumvent a clash between the ISO C++
- // standard and POSIX: ISO C++ disallows casting between function pointers
- // and data pointers, but dlsym always returns a void pointer. For details,
- // see e.g. <http://www.trilithium.com/johan/2004/12/problem-with-dlsym/>.
- assert(sizeof(VoidFunc) == sizeof(func));
- VoidFunc tmp;
- memcpy(&tmp, &func, sizeof(VoidFunc));
- return tmp;
+ return (void (*)())func;
}
public:
diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp
index 21bc56e441..dc5e8adca7 100644
--- a/backends/saves/default/default-saves.cpp
+++ b/backends/saves/default/default-saves.cpp
@@ -41,79 +41,42 @@
#include <sys/stat.h>
#endif
-
-class StdioSaveFile : public Common::InSaveFile, public Common::OutSaveFile {
-private:
- FILE *fh;
-public:
- StdioSaveFile(const char *filename, bool saveOrLoad) {
- fh = ::fopen(filename, (saveOrLoad? "wb" : "rb"));
- }
- ~StdioSaveFile() {
- if (fh)
- ::fclose(fh);
- }
-
- bool eos() const { return feof(fh) != 0; }
- bool ioFailed() const { return ferror(fh) != 0; }
- void clearIOFailed() { clearerr(fh); }
-
- bool isOpen() const { return fh != 0; }
-
- uint32 read(void *dataPtr, uint32 dataSize) {
- assert(fh);
- return fread(dataPtr, 1, dataSize, fh);
- }
- uint32 write(const void *dataPtr, uint32 dataSize) {
- assert(fh);
- return fwrite(dataPtr, 1, dataSize, fh);
- }
-
- uint32 pos() const {
- assert(fh);
- return ftell(fh);
- }
- uint32 size() const {
- assert(fh);
- uint32 oldPos = ftell(fh);
- fseek(fh, 0, SEEK_END);
- uint32 length = ftell(fh);
- fseek(fh, oldPos, SEEK_SET);
- return length;
- }
-
- void seek(int32 offs, int whence = SEEK_SET) {
- assert(fh);
- fseek(fh, offs, whence);
- }
-};
-
-static void join_paths(const char *filename, const char *directory,
- char *buf, int bufsize) {
- buf[bufsize-1] = '\0';
- strncpy(buf, directory, bufsize-1);
-
-#ifdef WIN32
- // Fix for Win98 issue related with game directory pointing to root drive ex. "c:\"
- if ((buf[0] != 0) && (buf[1] == ':') && (buf[2] == '\\') && (buf[3] == 0)) {
- buf[2] = 0;
- }
+#ifdef UNIX
+#ifdef MACOSX
+#define DEFAULT_SAVE_PATH "Documents/ScummVM Savegames"
+#else
+#define DEFAULT_SAVE_PATH ".scummvm"
#endif
-
- const int dirLen = strlen(buf);
-
- if (dirLen > 0) {
-#if defined(__MORPHOS__) || defined(__amigaos4__)
- if (buf[dirLen-1] != ':' && buf[dirLen-1] != '/')
+#elif defined(__SYMBIAN32__)
+#define DEFAULT_SAVE_PATH "Savegames"
#endif
-#if !defined(__GP32__)
- strncat(buf, "/", bufsize-1); // prevent double /
-#endif
+DefaultSaveFileManager::DefaultSaveFileManager() {
+ // Register default savepath
+ // TODO: Remove this code here, and instead leave setting the
+ // default savepath to the ports using this class.
+#ifdef DEFAULT_SAVE_PATH
+ Common::String savePath;
+#if defined(UNIX) && !defined(IPHONE)
+ const char *home = getenv("HOME");
+ if (home && *home && strlen(home) < MAXPATHLEN) {
+ savePath = home;
+ savePath += "/" DEFAULT_SAVE_PATH;
+ ConfMan.registerDefault("savepath", savePath);
}
- strncat(buf, filename, bufsize-1);
+#elif defined(__SYMBIAN32__)
+ savePath = Symbian::GetExecutablePath();
+ savePath += DEFAULT_SAVE_PATH "\\";
+ ConfMan.registerDefault("savepath", savePath);
+#endif
+#endif // #ifdef DEFAULT_SAVE_PATH
+}
+
+DefaultSaveFileManager::DefaultSaveFileManager(const Common::String &defaultSavepath) {
+ ConfMan.registerDefault("savepath", defaultSavepath);
}
+
Common::StringList DefaultSaveFileManager::listSavefiles(const char *pattern) {
FilesystemNode savePath(getSavePath());
FSList savefiles;
@@ -129,7 +92,8 @@ Common::StringList DefaultSaveFileManager::listSavefiles(const char *pattern) {
return results;
}
-void DefaultSaveFileManager::checkPath(const Common::String &path) {
+void DefaultSaveFileManager::checkPath(const FilesystemNode &dir) {
+ const Common::String path = dir.getPath();
clearError();
#if defined(UNIX) || defined(__SYMBIAN32__)
@@ -196,23 +160,27 @@ void DefaultSaveFileManager::checkPath(const Common::String &path) {
setError(SFM_DIR_NOTDIR, "The given savepath is not a directory: "+path);
}
}
+#else
+ if (!dir.exists()) {
+ // TODO: We could try to mkdir the directory here; or rather, we could
+ // add a mkdir method to FilesystemNode and invoke that here.
+ setError(SFM_DIR_NOENT, "A component of the path does not exist, or the path is an empty string: "+path);
+ } else if (!dir.isDirectory()) {
+ setError(SFM_DIR_NOTDIR, "The given savepath is not a directory: "+path);
+ }
#endif
}
Common::InSaveFile *DefaultSaveFileManager::openForLoading(const char *filename) {
// Ensure that the savepath is valid. If not, generate an appropriate error.
- char buf[256];
- Common::String savePath = getSavePath();
+ FilesystemNode savePath(getSavePath());
checkPath(savePath);
if (getError() == SFM_NO_ERROR) {
- join_paths(filename, savePath.c_str(), buf, sizeof(buf));
- StdioSaveFile *sf = new StdioSaveFile(buf, false);
+ FilesystemNode file = savePath.getChild(filename);
- if (!sf->isOpen()) {
- delete sf;
- sf = 0;
- }
+ // Open the file for reading
+ Common::SeekableReadStream *sf = file.openForReading();
return wrapInSaveFile(sf);
} else {
@@ -222,18 +190,14 @@ Common::InSaveFile *DefaultSaveFileManager::openForLoading(const char *filename)
Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const char *filename) {
// Ensure that the savepath is valid. If not, generate an appropriate error.
- char buf[256];
- Common::String savePath = getSavePath();
+ FilesystemNode savePath(getSavePath());
checkPath(savePath);
if (getError() == SFM_NO_ERROR) {
- join_paths(filename, savePath.c_str(), buf, sizeof(buf));
- StdioSaveFile *sf = new StdioSaveFile(buf, true);
+ FilesystemNode file = savePath.getChild(filename);
- if (!sf->isOpen()) {
- delete sf;
- sf = 0;
- }
+ // Open the file for saving
+ Common::WriteStream *sf = file.openForWriting();
return wrapOutSaveFile(sf);
} else {
@@ -242,18 +206,19 @@ Common::OutSaveFile *DefaultSaveFileManager::openForSaving(const char *filename)
}
bool DefaultSaveFileManager::removeSavefile(const char *filename) {
- char buf[256];
clearError();
- Common::String filenameStr;
- join_paths(filename, getSavePath().c_str(), buf, sizeof(buf));
- if (remove(buf) != 0) {
+ FilesystemNode savePath(getSavePath());
+ FilesystemNode file = savePath.getChild(filename);
+
+ // TODO: Add new method FilesystemNode::remove()
+ if (remove(file.getPath().c_str()) != 0) {
#ifndef _WIN32_WCE
if (errno == EACCES)
- setError(SFM_DIR_ACCESS, "Search or write permission denied: "+filenameStr);
+ setError(SFM_DIR_ACCESS, "Search or write permission denied: "+file.getName());
if (errno == ENOENT)
- setError(SFM_DIR_NOENT, "A component of the path does not exist, or the path is an empty string: "+filenameStr);
+ setError(SFM_DIR_NOENT, "A component of the path does not exist, or the path is an empty string: "+file.getName());
#endif
return false;
} else {
diff --git a/backends/saves/default/default-saves.h b/backends/saves/default/default-saves.h
index f3e0ec5b35..97845c4623 100644
--- a/backends/saves/default/default-saves.h
+++ b/backends/saves/default/default-saves.h
@@ -34,6 +34,9 @@
*/
class DefaultSaveFileManager : public Common::SaveFileManager {
public:
+ DefaultSaveFileManager();
+ DefaultSaveFileManager(const Common::String &defaultSavepath);
+
virtual Common::StringList listSavefiles(const char *pattern);
virtual Common::InSaveFile *openForLoading(const char *filename);
virtual Common::OutSaveFile *openForSaving(const char *filename);
@@ -51,7 +54,7 @@ protected:
* Checks the given path for read access, existence, etc.
* Sets the internal error and error message accordingly.
*/
- void checkPath(const Common::String &path);
+ void checkPath(const FilesystemNode &dir);
};
#endif
diff --git a/base/commandLine.cpp b/base/commandLine.cpp
index b9fd4ecfb7..84f2013ae9 100644
--- a/base/commandLine.cpp
+++ b/base/commandLine.cpp
@@ -34,22 +34,6 @@
#include "sound/mididrv.h"
-#ifdef IPHONE
-#include "backends/platform/iphone/osys_iphone.h"
-#endif
-
-#ifdef UNIX
-#ifdef MACOSX
-#define DEFAULT_SAVE_PATH "Documents/ScummVM Savegames"
-#else
-#define DEFAULT_SAVE_PATH ".scummvm"
-#endif
-#elif defined(__SYMBIAN32__)
-#define DEFAULT_SAVE_PATH "Savegames"
-#elif defined(PALMOS_MODE)
-#define DEFAULT_SAVE_PATH "/PALM/Programs/ScummVM/Saved"
-#endif
-
#define DETECTOR_TESTING_HACK
namespace Base {
@@ -181,9 +165,6 @@ void registerDefaults() {
// Game specific
ConfMan.registerDefault("path", "");
- ConfMan.registerDefault("savepath", "");
-
-// ConfMan.registerDefault("amiga", false);
ConfMan.registerDefault("platform", Common::kPlatformPC);
ConfMan.registerDefault("language", "en");
ConfMan.registerDefault("subtitles", false);
@@ -216,27 +197,6 @@ void registerDefaults() {
ConfMan.registerDefault("alsa_port", "65:0");
#endif
- // Register default savepath
-#ifdef DEFAULT_SAVE_PATH
- char savePath[MAXPATHLEN];
-#if defined(UNIX) && !defined(IPHONE)
- const char *home = getenv("HOME");
- if (home && *home && strlen(home) < MAXPATHLEN) {
- snprintf(savePath, MAXPATHLEN, "%s/%s", home, DEFAULT_SAVE_PATH);
- ConfMan.registerDefault("savepath", savePath);
- }
-#elif defined(__SYMBIAN32__)
- strcpy(savePath, Symbian::GetExecutablePath());
- strcat(savePath, DEFAULT_SAVE_PATH);
- ConfMan.registerDefault("savepath", savePath);
-#elif defined (IPHONE)
- ConfMan.registerDefault("savepath", OSystem_IPHONE::getSavePath());
-
-#elif defined(PALMOS_MODE)
- ConfMan.registerDefault("savepath", DEFAULT_SAVE_PATH);
-#endif
-#endif // #ifdef DEFAULT_SAVE_PATH
-
ConfMan.registerDefault("record_mode", "none");
ConfMan.registerDefault("record_file_name", "record.bin");
ConfMan.registerDefault("record_temp_file_name", "record.tmp");
@@ -684,9 +644,9 @@ static void runDetectorTest() {
failure++;
} else if (candidates.size() > 1) {
if (gameidDiffers) {
- printf(" FAILURE: Multiple games detected, some/all with wrong gameid\n");
+ printf(" WARNING: Multiple games detected, some/all with wrong gameid\n");
} else {
- printf(" FAILURE: Multiple games detected, but all have the same gameid\n");
+ printf(" WARNING: Multiple games detected, but all have the same gameid\n");
}
failure++;
} else if (gameidDiffers) {
diff --git a/base/game.h b/base/game.h
index b068c300ce..d81f2afb8a 100644
--- a/base/game.h
+++ b/base/game.h
@@ -92,6 +92,10 @@ public:
const Common::String &description() const { return getVal("description"); }
Common::Language language() const { return contains("language") ? Common::parseLanguage(getVal("language")) : Common::UNK_LANG; }
Common::Platform platform() const { return contains("platform") ? Common::parsePlatform(getVal("platform")) : Common::kPlatformUnknown; }
+
+ const Common::String &preferredtarget() const {
+ return contains("preferredtarget") ? getVal("preferredtarget") : getVal("gameid");
+ }
};
/** List of games. */
diff --git a/base/main.cpp b/base/main.cpp
index bb7a17b901..4283b6cacf 100644
--- a/base/main.cpp
+++ b/base/main.cpp
@@ -111,35 +111,8 @@ static const EnginePlugin *detectPlugin() {
// TODO: specify the possible return values here
static int runGame(const EnginePlugin *plugin, OSystem &system, const Common::String &edebuglevels) {
- Common::String gameDataPath(ConfMan.get("path"));
- if (gameDataPath.empty()) {
- } else if (gameDataPath.lastChar() != '/'
-#if defined(__MORPHOS__) || defined(__amigaos4__)
- && gameDataPath.lastChar() != ':'
-#endif
- && gameDataPath.lastChar() != '\\') {
- gameDataPath += '/';
- ConfMan.set("path", gameDataPath, Common::ConfigManager::kTransientDomain);
- }
-
- // We add the game "path" to the file search path via File::addDefaultDirectory(),
- // so that MD5-based detection will be able to properly find files with mixed case
- // filenames.
- // FIXME/TODO: Fingolfin still doesn't like this; if those MD5-based detectors used
- // FSNodes instead of File::open, they wouldn't have to do this.
- Common::String path;
- if (ConfMan.hasKey("path")) {
- path = ConfMan.get("path");
- FilesystemNode dir(path);
- if (!dir.isDirectory()) {
- warning("Game directory does not exist (%s)", path.c_str());
- return 0;
- }
- } else {
- path = ".";
- warning("No path was provided. Assuming the data files are in the current directory");
- }
- Common::File::addDefaultDirectory(path);
+ // Query the game data path, for messages
+ Common::String path = ConfMan.hasKey("path") ? ConfMan.get("path") : ".";
// Create the game engine
Engine *engine = 0;
@@ -182,15 +155,14 @@ static int runGame(const EnginePlugin *plugin, OSystem &system, const Common::St
system.setWindowCaption(caption.c_str());
}
- if (ConfMan.hasKey("path"))
- Common::File::addDefaultDirectory(ConfMan.get("path"));
- else
- Common::File::addDefaultDirectory(".");
+ // Add the game path to the directory search list
+ Common::File::addDefaultDirectory(path);
// Add extrapath (if any) to the directory search list
if (ConfMan.hasKey("extrapath"))
Common::File::addDefaultDirectoryRecursive(ConfMan.get("extrapath"));
+ // If a second extrapath is specified on the app domain level, add that as well.
if (ConfMan.hasKey("extrapath", Common::ConfigManager::kApplicationDomain))
Common::File::addDefaultDirectoryRecursive(ConfMan.get("extrapath", Common::ConfigManager::kApplicationDomain));
diff --git a/base/plugins.cpp b/base/plugins.cpp
index dcd394495f..216c6ef1af 100644
--- a/base/plugins.cpp
+++ b/base/plugins.cpp
@@ -140,6 +140,9 @@ public:
#if PLUGIN_ENABLED_STATIC(SWORD2)
LINK_PLUGIN(SWORD2)
#endif
+ #if PLUGIN_ENABLED_STATIC(TINSEL)
+ LINK_PLUGIN(TINSEL)
+ #endif
#if PLUGIN_ENABLED_STATIC(TOUCHE)
LINK_PLUGIN(TOUCHE)
#endif
diff --git a/common/advancedDetector.cpp b/common/advancedDetector.cpp
index 4387bd199e..522b24163e 100644
--- a/common/advancedDetector.cpp
+++ b/common/advancedDetector.cpp
@@ -34,7 +34,11 @@
namespace Common {
-using namespace AdvancedDetector;
+/**
+ * A list of pointers to ADGameDescription structs (or subclasses thereof).
+ */
+typedef Array<const ADGameDescription*> ADGameDescList;
+
/**
* Detect games in specified directory.
@@ -48,7 +52,7 @@ using namespace AdvancedDetector;
* @param platform restrict results to specified platform only
* @return list of ADGameDescription (or subclass) pointers corresponding to matched games
*/
-static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &params, Language language, Platform platform, const Common::String extra);
+static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &params, Language language, Platform platform, const Common::String extra);
/**
@@ -90,6 +94,8 @@ static void upgradeTargetIfNecessary(const Common::ADParams &params) {
warning("Target upgraded from %s to %s", o->from, o->to);
+ // WORKAROUND: Fix for bug #1719463: "DETECTOR: Launching
+ // undefined target adds launcher entry"
if (ConfMan.hasKey("id_came_from_command_line")) {
warning("Target came from command line. Skipping save");
} else {
@@ -194,7 +200,7 @@ static void updateGameDescriptor(GameDescriptor &desc, const ADGameDescription *
}
GameList AdvancedMetaEngine::detectGames(const FSList &fslist) const {
- ADGameDescList matches = detectGame(&fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, "");
+ ADGameDescList matches = detectGame(fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, "");
GameList detectedGames;
// Use fallback detector if there were no matches by other means
@@ -233,7 +239,21 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c
Common::String gameid = ConfMan.get("gameid");
- ADGameDescList matches = detectGame(0, params, language, platform, extra);
+ Common::String path;
+ if (ConfMan.hasKey("path")) {
+ path = ConfMan.get("path");
+ } else {
+ path = ".";
+ warning("No path was provided. Assuming the data files are in the current directory");
+ }
+ FilesystemNode dir(path);
+ FSList files;
+ if (!dir.isDirectory() || !dir.getChildren(files, FilesystemNode::kListAll)) {
+ warning("Game data path does not exist or is not a directory (%s)", path.c_str());
+ return kNoGameDataFoundError;
+ }
+
+ ADGameDescList matches = detectGame(files, params, language, platform, extra);
if (params.singleid == NULL) {
for (uint i = 0; i < matches.size(); i++) {
@@ -268,10 +288,10 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c
return kNoError;
}
-typedef HashMap<String, bool> StringSet;
-typedef HashMap<String, int32> IntMap;
+typedef HashMap<String, bool, IgnoreCase_Hash, IgnoreCase_EqualTo> StringSet;
+typedef HashMap<String, int32, IgnoreCase_Hash, IgnoreCase_EqualTo> IntMap;
-static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) {
+static void reportUnknown(const StringMap &filesMD5, const IntMap &filesSize) {
// TODO: This message should be cleaned up / made more specific.
// For example, we should specify at least which engine triggered this.
//
@@ -287,96 +307,77 @@ static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) {
printf("\n");
}
-static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &params, Language language, Platform platform, const Common::String extra) {
- StringSet filesList;
+static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams &params);
+static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams &params, Language language, Platform platform, const Common::String extra) {
+ StringMap allFiles;
+
+ StringSet detectFiles;
StringMap filesMD5;
IntMap filesSize;
- IntMap allFiles;
-
- File testFile;
- String tstr;
-
- uint i;
- char md5str[32+1];
-
- bool fileMissing;
const ADGameFileDescription *fileDesc;
const ADGameDescription *g;
const byte *descPtr;
debug(3, "Starting detection");
- // First we compose list of files which we need MD5s for
+ // First we compose an efficient to query set of all files in fslist.
+ // Includes nifty stuff like removing trailing dots and ignoring case.
+ for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) {
+ if (file->isDirectory())
+ continue;
+
+ String tstr = file->getName();
+
+ // Strip any trailing dot
+ if (tstr.lastChar() == '.')
+ tstr.deleteLastChar();
+
+ allFiles[tstr] = file->getPath(); // Record the presence of this file
+ }
+
+ // Compute the set of files for which we need MD5s for. I.e. files which are
+ // included in some ADGameDescription *and* present in fslist.
for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) {
g = (const ADGameDescription *)descPtr;
for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
- tstr = String(fileDesc->fileName);
- tstr.toLowercase();
- filesList[tstr] = true;
+ String tstr = fileDesc->fileName;
+ if (allFiles.contains(tstr))
+ detectFiles[tstr] = true;
}
}
- // TODO/FIXME: Fingolfin says: It's not good that we have two different code paths here,
- // one using a FSList, one using File::open, as that will lead to discrepancies and subtle
- // problems caused by those.
- if (fslist != 0) {
- // Get the information of the existing files
- for (FSList::const_iterator file = fslist->begin(); file != fslist->end(); ++file) {
- if (file->isDirectory()) continue;
- tstr = file->getName();
- tstr.toLowercase();
-
- // Strip any trailing dot
- if (tstr.lastChar() == '.')
- tstr.deleteLastChar();
-
- allFiles[tstr] = true;
-
- debug(3, "+ %s", tstr.c_str());
-
- if (!filesList.contains(tstr)) continue;
+ // Get the information for all detection files, if they exist
+ for (StringSet::const_iterator file = detectFiles.begin(); file != detectFiles.end(); ++file) {
+ String fname = file->_key;
- if (!md5_file_string(*file, md5str, params.md5Bytes))
- continue;
- filesMD5[tstr] = md5str;
+ debug(3, "+ %s", fname.c_str());
- debug(3, "> %s: %s", tstr.c_str(), md5str);
+ char md5str[32+1];
+ if (!md5_file_string(allFiles[fname].c_str(), md5str, params.md5Bytes))
+ continue;
+ filesMD5[fname] = md5str;
- if (testFile.open(file->getPath())) {
- filesSize[tstr] = (int32)testFile.size();
- testFile.close();
- }
- }
- } else {
- // Get the information of the requested files
- for (StringSet::const_iterator file = filesList.begin(); file != filesList.end(); ++file) {
- tstr = file->_key;
+ debug(3, "> %s: %s", fname.c_str(), md5str);
- debug(3, "+ %s", tstr.c_str());
- if (!filesMD5.contains(tstr)) {
- if (testFile.open(tstr) || testFile.open(tstr + ".")) {
- filesSize[tstr] = (int32)testFile.size();
- testFile.close();
-
- if (md5_file_string(file->_key.c_str(), md5str, params.md5Bytes)) {
- filesMD5[tstr] = md5str;
- debug(3, "> %s: %s", tstr.c_str(), md5str);
- }
- }
- }
+ File testFile;
+ if (testFile.open(allFiles[fname])) {
+ filesSize[fname] = (int32)testFile.size();
+ testFile.close();
}
}
+
ADGameDescList matched;
int maxFilesMatched = 0;
// MD5 based matching
+ uint i;
for (i = 0, descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize, ++i) {
g = (const ADGameDescription *)descPtr;
- fileMissing = false;
+ bool fileMissing = false;
// Do not even bother to look at entries which do not have matching
// language and platform (if specified).
@@ -385,32 +386,28 @@ static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &p
continue;
}
- if ((params.flags & kADFlagUseExtraAsHint) && extra != "" && g->extra != extra)
+ if ((params.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra)
continue;
// Try to match all files for this game
for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) {
- tstr = fileDesc->fileName;
- tstr.toLowercase();
+ String tstr = fileDesc->fileName;
if (!filesMD5.contains(tstr)) {
fileMissing = true;
break;
}
- if (fileDesc->md5 != NULL) {
- if (fileDesc->md5 != filesMD5[tstr]) {
- debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str());
- fileMissing = true;
- break;
- }
+
+ if (fileDesc->md5 != NULL && fileDesc->md5 != filesMD5[tstr]) {
+ debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str());
+ fileMissing = true;
+ break;
}
- if (fileDesc->fileSize != -1) {
- if (fileDesc->fileSize != filesSize[tstr]) {
- debug(3, "Size Mismatch. Skipping");
- fileMissing = true;
- break;
- }
+ if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSize[tstr]) {
+ debug(3, "Size Mismatch. Skipping");
+ fileMissing = true;
+ break;
}
debug(3, "Matched file: %s", tstr.c_str());
@@ -448,85 +445,68 @@ static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &p
}
}
- // We've found a match
- if (!matched.empty())
- return matched;
-
- if (!filesMD5.empty())
- reportUnknown(filesMD5, filesSize);
-
- // Filename based fallback
- if (params.fileBasedFallback != 0) {
- const ADFileBasedFallback *ptr = params.fileBasedFallback;
- const char* const* filenames = 0;
-
- // First we create list of files required for detection.
- // The filenames can be different than the MD5 based match ones.
- for (; ptr->desc; ptr++) {
- filenames = ptr->filenames;
- for (; *filenames; filenames++) {
- tstr = String(*filenames);
- tstr.toLowercase();
-
- if (!allFiles.contains(tstr)) {
- if (testFile.open(tstr) || testFile.open(tstr + ".")) {
- allFiles[tstr] = true;
- testFile.close();
- }
- }
- }
- }
-
- // Then we perform the actual filename matching. If there are
- // several matches, only the one with the maximum numbers of
- // files is considered.
- int maxNumMatchedFiles = 0;
- const ADGameDescription *matchedDesc = 0;
-
- ptr = params.fileBasedFallback;
+ // We didn't find a match
+ if (matched.empty()) {
+ if (!filesMD5.empty())
+ reportUnknown(filesMD5, filesSize);
+
+ // Filename based fallback
+ if (params.fileBasedFallback != 0)
+ matched = detectGameFilebased(allFiles, params);
+ }
- for (; ptr->desc; ptr++) {
- const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc;
- int numMatchedFiles = 0;
- fileMissing = false;
+ return matched;
+}
- filenames = ptr->filenames;
- for (; *filenames; filenames++) {
- if (fileMissing) {
- continue;
- }
+/**
+ * Check for each ADFileBasedFallback record whether all files listed
+ * in it are present. If multiple pass this test, we pick the one with
+ * the maximal number of matching files. In case of a tie, the entry
+ * coming first in the list is chosen.
+ */
+static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams &params) {
+ const ADFileBasedFallback *ptr;
+ const char* const* filenames;
- tstr = String(*filenames);
- tstr.toLowercase();
+ int maxNumMatchedFiles = 0;
+ const ADGameDescription *matchedDesc = 0;
- debug(3, "++ %s", *filenames);
- if (!allFiles.contains(tstr)) {
- fileMissing = true;
- continue;
- }
+ for (ptr = params.fileBasedFallback; ptr->desc; ++ptr) {
+ const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc;
+ int numMatchedFiles = 0;
+ bool fileMissing = false;
- numMatchedFiles++;
+ for (filenames = ptr->filenames; *filenames; ++filenames) {
+ debug(3, "++ %s", *filenames);
+ if (!allFiles.contains(*filenames)) {
+ fileMissing = true;
+ break;
}
- if (!fileMissing)
- debug(4, "Matched: %s", agdesc->gameid);
+ numMatchedFiles++;
+ }
- if (!fileMissing && numMatchedFiles > maxNumMatchedFiles) {
+ if (!fileMissing) {
+ debug(4, "Matched: %s", agdesc->gameid);
+
+ if (numMatchedFiles > maxNumMatchedFiles) {
matchedDesc = agdesc;
maxNumMatchedFiles = numMatchedFiles;
-
+
debug(4, "and overriden");
}
}
+ }
- if (matchedDesc) { // We got a match
- matched.push_back(matchedDesc);
- if (params.flags & kADFlagPrintWarningOnFileBasedFallback) {
- printf("Your game version has been detected using filename matching as a\n");
- printf("variant of %s.\n", matchedDesc->gameid);
- printf("If this is an original and unmodified version, please report any\n");
- printf("information previously printed by ScummVM to the team.\n");
- }
+ ADGameDescList matched;
+
+ if (matchedDesc) { // We got a match
+ matched.push_back(matchedDesc);
+ if (params.flags & kADFlagPrintWarningOnFileBasedFallback) {
+ printf("Your game version has been detected using filename matching as a\n");
+ printf("variant of %s.\n", matchedDesc->gameid);
+ printf("If this is an original and unmodified version, please report any\n");
+ printf("information previously printed by ScummVM to the team.\n");
}
}
diff --git a/common/advancedDetector.h b/common/advancedDetector.h
index 48b9e213d7..40f5823d1b 100644
--- a/common/advancedDetector.h
+++ b/common/advancedDetector.h
@@ -69,11 +69,6 @@ struct ADGameDescription {
};
/**
- * A list of pointers to ADGameDescription structs (or subclasses thereof).
- */
-typedef Array<const ADGameDescription*> ADGameDescList;
-
-/**
* End marker for a table of ADGameDescription structs. Use this to
* terminate a list to be passed to the AdvancedDetector API.
*/
diff --git a/common/algorithm.h b/common/algorithm.h
index beae34245f..3b6c63d55c 100644
--- a/common/algorithm.h
+++ b/common/algorithm.h
@@ -29,6 +29,11 @@
namespace Common {
+/**
+ * Copies data from the range [first, last) to [dst, dst + (last - first)).
+ * It requires the range [dst, dst + (last - first)) to be valid.
+ * It also requires dst not to be in the range [first, last).
+ */
template<class In, class Out>
Out copy(In first, In last, Out dst) {
while (first != last)
@@ -36,6 +41,13 @@ Out copy(In first, In last, Out dst) {
return dst;
}
+/**
+ * Copies data from the range [first, last) to [dst - (last - first), dst).
+ * It requires the range [dst - (last - first), dst) to be valid.
+ * It also requires dst not to be in the range [first, last).
+ *
+ * Unlike copy copy_backward copies the data from the end to the beginning.
+ */
template<class In, class Out>
Out copy_backward(In first, In last, Out dst) {
while (first != last)
@@ -43,6 +55,15 @@ Out copy_backward(In first, In last, Out dst) {
return dst;
}
+/**
+ * Copies data from the range [first, last) to [dst, dst + (last - first)).
+ * It requires the range [dst, dst + (last - first)) to be valid.
+ * It also requires dst not to be in the range [first, last).
+ *
+ * Unlike copy or copy_backward it does not copy all data. It only copies
+ * a data element when operator() of the op parameter returns true for the
+ * passed data element.
+ */
template<class In, class Out, class Op>
Out copy_if(In first, In last, Out dst, Op op) {
while (first != last) {
@@ -76,6 +97,9 @@ char *set_to(char *first, char *last, Value val) {
return last;
}
+/**
+ * Sets all elements in the range [first, last) to val.
+ */
template<class In, class Value>
In set_to(In first, In last, Value val) {
while (first != last)
@@ -83,6 +107,10 @@ In set_to(In first, In last, Value val) {
return first;
}
+/**
+ * Finds the first data value in the range [first, last) matching v.
+ * For data comperance it uses operator == of the data elements.
+ */
template<class In, class T>
In find(In first, In last, const T &v) {
while (first != last) {
@@ -93,6 +121,10 @@ In find(In first, In last, const T &v) {
return last;
}
+/**
+ * Finds the first data value in the range [first, last) for which
+ * the specified predicate p returns true.
+ */
template<class In, class Pred>
In find_if(In first, In last, Pred p) {
while (first != last) {
@@ -103,15 +135,22 @@ In find_if(In first, In last, Pred p) {
return last;
}
+/**
+ * Applies the function f on all elements of the range [first, last).
+ * The processing order is from beginning to end.
+ */
template<class In, class Op>
Op for_each(In first, In last, Op f) {
while (first != last) f(*first++);
return f;
}
-// Simple sort function, modeled after std::sort.
-// Use it like this: sort(container.begin(), container.end()).
-// Also work on plain old int arrays etc.
+/**
+ * Simple sort function, modeled after std::sort.
+ * Use it like this: sort(container.begin(), container.end()).
+ * Also works on plain old i.e. int arrays etc. For comperance
+ * operator < is used.
+ */
template<class T>
void sort(T first, T last) {
if (first == last)
@@ -131,8 +170,13 @@ void sort(T first, T last) {
}
}
-// Using this with: Common::Less from common/func.h
-// will give the same results as the function above.
+/**
+ * Simple sort function, modeled after std::sort.
+ * It compares data with the given comparator object comp.
+ *
+ * Note: Using this with: Common::Less from common/func.h
+ * will give the same results as the plain sort function.
+ */
template<class T, class StrictWeakOrdering>
void sort(T first, T last, StrictWeakOrdering comp) {
if (first == last)
diff --git a/common/config-file.cpp b/common/config-file.cpp
index fe827e32dc..9f54c9ddde 100644
--- a/common/config-file.cpp
+++ b/common/config-file.cpp
@@ -58,7 +58,7 @@ void ConfigFile::clear() {
bool ConfigFile::loadFromFile(const String &filename) {
File file;
- if (file.open(filename, File::kFileReadMode))
+ if (file.open(filename))
return loadFromStream(file);
else
return false;
@@ -171,8 +171,8 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) {
}
bool ConfigFile::saveToFile(const String &filename) {
- File file;
- if (file.open(filename, File::kFileWriteMode))
+ DumpFile file;
+ if (file.open(filename))
return saveToStream(file);
else
return false;
diff --git a/common/config-manager.cpp b/common/config-manager.cpp
index 59855cf6c9..a424c4f6c7 100644
--- a/common/config-manager.cpp
+++ b/common/config-manager.cpp
@@ -23,38 +23,13 @@
*
*/
-#if defined(WIN32)
-#include <windows.h>
-// winnt.h defines ARRAYSIZE, but we want our own one...
-#undef ARRAYSIZE
-#endif
-
#include "common/config-manager.h"
#include "common/file.h"
#include "common/util.h"
+#include "common/system.h"
DECLARE_SINGLETON(Common::ConfigManager);
-#ifdef __PLAYSTATION2__
-#include "backends/platform/ps2/systemps2.h"
-#endif
-
-#ifdef IPHONE
-#include "backends/platform/iphone/osys_iphone.h"
-#endif
-
-#if defined(UNIX)
-#ifdef MACOSX
-#define DEFAULT_CONFIG_FILE "Library/Preferences/ScummVM Preferences"
-#else
-#define DEFAULT_CONFIG_FILE ".scummvmrc"
-#endif
-#else
-#define DEFAULT_CONFIG_FILE "scummvm.ini"
-#endif
-
-#define MAXLINELEN 256
-
static bool isValidDomainName(const Common::String &domName) {
const char *p = domName.c_str();
while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
@@ -85,242 +60,180 @@ ConfigManager::ConfigManager()
void ConfigManager::loadDefaultConfigFile() {
- char configFile[MAXPATHLEN];
- // GP2X is Linux based but Home dir can be read only so do not use it and put the config in the executable dir.
- // On the iPhone, the home dir of the user when you launch the app from the Springboard, is /. Which we don't want.
-#if defined(UNIX) && !defined(GP2X) && !defined(IPHONE)
- const char *home = getenv("HOME");
- if (home != NULL && strlen(home) < MAXPATHLEN)
- snprintf(configFile, MAXPATHLEN, "%s/%s", home, DEFAULT_CONFIG_FILE);
- else
- strcpy(configFile, DEFAULT_CONFIG_FILE);
-#else
- #if defined (WIN32) && !defined(_WIN32_WCE) && !defined(__SYMBIAN32__)
- OSVERSIONINFO win32OsVersion;
- ZeroMemory(&win32OsVersion, sizeof(OSVERSIONINFO));
- win32OsVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
- GetVersionEx(&win32OsVersion);
- // Check for non-9X version of Windows.
- if (win32OsVersion.dwPlatformId != VER_PLATFORM_WIN32_WINDOWS) {
- // Use the Application Data directory of the user profile.
- if (win32OsVersion.dwMajorVersion >= 5) {
- if (!GetEnvironmentVariable("APPDATA", configFile, sizeof(configFile)))
- error("Unable to access application data directory");
- } else {
- if (!GetEnvironmentVariable("USERPROFILE", configFile, sizeof(configFile)))
- error("Unable to access user profile directory");
+ // Open the default config file
+ SeekableReadStream *stream = g_system->openConfigFileForReading();
+ _filename.clear(); // clear the filename to indicate that we are using the default config file
- strcat(configFile, "\\Application Data");
- CreateDirectory(configFile, NULL);
- }
+ // ... load it ...
+ assert(stream);
+ loadFromStream(*stream);
+
+ // ... and close it again.
+ delete stream;
- strcat(configFile, "\\ScummVM");
- CreateDirectory(configFile, NULL);
- strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
-
- if (fopen(configFile, "r") == NULL) {
- // Check windows directory
- char oldConfigFile[MAXPATHLEN];
- GetWindowsDirectory(oldConfigFile, MAXPATHLEN);
- strcat(oldConfigFile, "\\" DEFAULT_CONFIG_FILE);
- if (fopen(oldConfigFile, "r")) {
- printf("The default location of the config file (scummvm.ini) in ScummVM has changed,\n");
- printf("under Windows NT4/2000/XP/Vista. You may want to consider moving your config\n");
- printf("file from the old default location:\n");
- printf("%s\n", oldConfigFile);
- printf("to the new default location:\n");
- printf("%s\n\n", configFile);
- strcpy(configFile, oldConfigFile);
- }
- }
- } else {
- // Check windows directory
- GetWindowsDirectory(configFile, MAXPATHLEN);
- strcat(configFile, "\\" DEFAULT_CONFIG_FILE);
- }
-
- #elif defined(PALMOS_MODE)
- strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE);
- #elif defined(IPHONE)
- strcpy(configFile, OSystem_IPHONE::getConfigPath());
- #elif defined(__PLAYSTATION2__)
- ((OSystem_PS2*)g_system)->makeConfigPath(configFile);
- #elif defined(__PSP__)
- strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE);
- #elif defined (__SYMBIAN32__)
- strcpy(configFile, Symbian::GetExecutablePath());
- strcat(configFile, DEFAULT_CONFIG_FILE);
- #else
- strcpy(configFile, DEFAULT_CONFIG_FILE);
- #endif
-#endif
-
- loadConfigFile(configFile);
flushToDisk();
}
void ConfigManager::loadConfigFile(const String &filename) {
- _appDomain.clear();
- _gameDomains.clear();
- _transientDomain.clear();
-
_filename = filename;
- _domainSaveOrder.clear();
- loadFile(_filename);
- printf("Using configuration file: %s\n", _filename.c_str());
-}
-void ConfigManager::loadFile(const String &filename) {
File cfg_file;
-
if (!cfg_file.open(filename)) {
printf("Creating configuration file: %s\n", filename.c_str());
} else {
- String domain;
- String comment;
- int lineno = 0;
-
- // TODO: Detect if a domain occurs multiple times (or likewise, if
- // a key occurs multiple times inside one domain).
-
- while (!cfg_file.eof() && !cfg_file.ioFailed()) {
- lineno++;
-
- // Read a line
- String line;
- while (line.lastChar() != '\n') {
- char buf[MAXLINELEN];
- if (!cfg_file.readLine_NEW(buf, MAXLINELEN))
- break;
- line += buf;
+ printf("Using configuration file: %s\n", _filename.c_str());
+ loadFromStream(cfg_file);
+ }
+}
+
+void ConfigManager::loadFromStream(SeekableReadStream &stream) {
+ String domain;
+ String comment;
+ int lineno = 0;
+
+ _appDomain.clear();
+ _gameDomains.clear();
+ _transientDomain.clear();
+ _domainSaveOrder.clear();
+
+ // TODO: Detect if a domain occurs multiple times (or likewise, if
+ // a key occurs multiple times inside one domain).
+
+ while (!stream.eos() && !stream.ioFailed()) {
+ lineno++;
+
+ // Read a line
+ String line;
+ while (line.lastChar() != '\n') {
+ char buf[256];
+ if (!stream.readLine_NEW(buf, 256))
+ break;
+ line += buf;
+ }
+
+ if (line.size() == 0) {
+ // Do nothing
+ } else if (line[0] == '#') {
+ // Accumulate comments here. Once we encounter either the start
+ // of a new domain, or a key-value-pair, we associate the value
+ // of the 'comment' variable with that entity.
+ comment += line;
+ } else if (line[0] == '[') {
+ // It's a new domain which begins here.
+ const char *p = line.c_str() + 1;
+ // Get the domain name, and check whether it's valid (that
+ // is, verify that it only consists of alphanumerics,
+ // dashes and underscores).
+ while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
+ p++;
+
+ if (*p == '\0')
+ error("Config file buggy: missing ] in line %d", lineno);
+ else if (*p != ']')
+ error("Config file buggy: Invalid character '%c' occured in section name in line %d", *p, lineno);
+
+ domain = String(line.c_str() + 1, p);
+
+ // Store domain comment
+ if (domain == kApplicationDomain) {
+ _appDomain.setDomainComment(comment);
+ } else {
+ _gameDomains[domain].setDomainComment(comment);
+ }
+ comment.clear();
+
+ _domainSaveOrder.push_back(domain);
+ } else {
+ // This line should be a line with a 'key=value' pair, or an empty one.
+
+ // Skip leading whitespaces
+ const char *t = line.c_str();
+ while (isspace(*t))
+ t++;
+
+ // Skip empty lines / lines with only whitespace
+ if (*t == 0)
+ continue;
+
+ // If no domain has been set, this config file is invalid!
+ if (domain.empty()) {
+ error("Config file buggy: Key/value pair found outside a domain in line %d", lineno);
}
- if (line.size() == 0) {
- // Do nothing
- } else if (line[0] == '#') {
- // Accumulate comments here. Once we encounter either the start
- // of a new domain, or a key-value-pair, we associate the value
- // of the 'comment' variable with that entity.
- comment += line;
- } else if (line[0] == '[') {
- // It's a new domain which begins here.
- const char *p = line.c_str() + 1;
- // Get the domain name, and check whether it's valid (that
- // is, verify that it only consists of alphanumerics,
- // dashes and underscores).
- while (*p && (isalnum(*p) || *p == '-' || *p == '_'))
- p++;
-
- switch (*p) {
- case '\0':
- error("Config file buggy: missing ] in line %d", lineno);
- break;
- case ']':
- domain = String(line.c_str() + 1, p - (line.c_str() + 1));
- //domain = String(line.c_str() + 1, p); // TODO: Pending Common::String changes
- break;
- default:
- error("Config file buggy: Invalid character '%c' occured in domain name in line %d", *p, lineno);
- }
-
- // Store domain comment
- if (domain == kApplicationDomain) {
- _appDomain.setDomainComment(comment);
- } else {
- _gameDomains[domain].setDomainComment(comment);
- }
- comment.clear();
-
- _domainSaveOrder.push_back(domain);
+ // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
+ const char *p = strchr(t, '=');
+ if (!p)
+ error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
+
+ // Extract the key/value pair
+ String key(t, p);
+ String value(p + 1);
+
+ // Trim of spaces
+ key.trim();
+ value.trim();
+
+ // Finally, store the key/value pair in the active domain
+ set(key, value, domain);
+
+ // Store comment
+ if (domain == kApplicationDomain) {
+ _appDomain.setKVComment(key, comment);
} else {
- // This line should be a line with a 'key=value' pair, or an empty one.
-
- // Skip leading whitespaces
- const char *t = line.c_str();
- while (isspace(*t))
- t++;
-
- // Skip empty lines / lines with only whitespace
- if (*t == 0)
- continue;
-
- // If no domain has been set, this config file is invalid!
- if (domain.empty()) {
- error("Config file buggy: Key/value pair found outside a domain in line %d", lineno);
- }
-
- // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter.
- const char *p = strchr(t, '=');
- if (!p)
- error("Config file buggy: Junk found in line line %d: '%s'", lineno, t);
-
- // Trim spaces before the '=' to obtain the key
- const char *p2 = p;
- while (p2 > t && isspace(*(p2-1)))
- p2--;
- String key(t, p2 - t);
-
- // Skip spaces after the '='
- t = p + 1;
- while (isspace(*t))
- t++;
-
- // Trim trailing spaces
- p2 = t + strlen(t);
- while (p2 > t && isspace(*(p2-1)))
- p2--;
-
- String value(t, p2 - t);
-
- // Finally, store the key/value pair in the active domain
- set(key, value, domain);
-
- // Store comment
- if (domain == kApplicationDomain) {
- _appDomain.setKVComment(key, comment);
- } else {
- _gameDomains[domain].setKVComment(key, comment);
- }
- comment.clear();
+ _gameDomains[domain].setKVComment(key, comment);
}
+ comment.clear();
}
}
}
void ConfigManager::flushToDisk() {
#ifndef __DC__
- File cfg_file;
+ WriteStream *stream;
-// TODO
-// if (!willwrite)
-// return;
-
- if (!cfg_file.open(_filename, File::kFileWriteMode)) {
- warning("Unable to write configuration file: %s", _filename.c_str());
+ if (_filename.empty()) {
+ // Write to the default config file
+ stream = g_system->openConfigFileForWriting();
+ if (!stream) // If writing to the config file is not possible, do nothing
+ return;
} else {
- // First write the domains in _domainSaveOrder, in that order.
- // Note: It's possible for _domainSaveOrder to list domains which
- // are not present anymore.
- StringList::const_iterator i;
- for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) {
- if (kApplicationDomain == *i) {
- writeDomain(cfg_file, *i, _appDomain);
- } else if (_gameDomains.contains(*i)) {
- writeDomain(cfg_file, *i, _gameDomains[*i]);
- }
+ DumpFile *dump = new DumpFile();
+ assert(dump);
+
+ if (!dump->open(_filename)) {
+ warning("Unable to write configuration file: %s", _filename.c_str());
+ delete dump;
+ return;
}
+
+ stream = dump;
+ }
- DomainMap::const_iterator d;
+ // First write the domains in _domainSaveOrder, in that order.
+ // Note: It's possible for _domainSaveOrder to list domains which
+ // are not present anymore.
+ StringList::const_iterator i;
+ for (i = _domainSaveOrder.begin(); i != _domainSaveOrder.end(); ++i) {
+ if (kApplicationDomain == *i) {
+ writeDomain(*stream, *i, _appDomain);
+ } else if (_gameDomains.contains(*i)) {
+ writeDomain(*stream, *i, _gameDomains[*i]);
+ }
+ }
+ DomainMap::const_iterator d;
- // Now write the domains which haven't been written yet
- if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end())
- writeDomain(cfg_file, kApplicationDomain, _appDomain);
- for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) {
- if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end())
- writeDomain(cfg_file, d->_key, d->_value);
- }
+
+ // Now write the domains which haven't been written yet
+ if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), kApplicationDomain) == _domainSaveOrder.end())
+ writeDomain(*stream, kApplicationDomain, _appDomain);
+ for (d = _gameDomains.begin(); d != _gameDomains.end(); ++d) {
+ if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), d->_key) == _domainSaveOrder.end())
+ writeDomain(*stream, d->_key, d->_value);
}
+
+ delete stream;
+
#endif // !__DC__
}
@@ -328,6 +241,12 @@ void ConfigManager::writeDomain(WriteStream &stream, const String &name, const D
if (domain.empty())
return; // Don't bother writing empty domains.
+ // WORKAROUND: Fix for bug #1972625 "ALL: On-the-fly targets are
+ // written to the config file": Do not save domains that came from
+ // the command line
+ if (domain.contains("id_came_from_command_line"))
+ return;
+
String comment;
// Write domain comment (if any)
@@ -642,6 +561,10 @@ void ConfigManager::addGameDomain(const String &domName) {
// the given name already exists?
_gameDomains[domName];
+
+ // Add it to the _domainSaveOrder, if it's not already in there
+ if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), domName) == _domainSaveOrder.end())
+ _domainSaveOrder.push_back(domName);
}
void ConfigManager::removeGameDomain(const String &domName) {
diff --git a/common/config-manager.h b/common/config-manager.h
index bebb59b539..9e5b88a073 100644
--- a/common/config-manager.h
+++ b/common/config-manager.h
@@ -36,7 +36,7 @@
namespace Common {
class WriteStream;
-
+class SeekableReadStream;
/**
* The (singleton) configuration manager, used to query & set configuration
@@ -144,19 +144,11 @@ public:
bool hasGameDomain(const String &domName) const;
const DomainMap & getGameDomains() const { return _gameDomains; }
-/*
- TODO: Callback/change notification system
- typedef void (*ConfigCallback)(const ConstString &key, void *refCon);
-
- void registerCallback(ConfigCallback cfgc, void *refCon, const ConstString &key = String::emptyString)
- void unregisterCallback(ConfigCallback cfgc, const ConstString &key = String::emptyString)
-*/
-
private:
friend class Singleton<SingletonBaseType>;
ConfigManager();
- void loadFile(const String &filename);
+ void loadFromStream(SeekableReadStream &stream);
void writeDomain(WriteStream &stream, const String &name, const Domain &domain);
Domain _transientDomain;
diff --git a/common/file.cpp b/common/file.cpp
index a1ea1aff77..fb837b9499 100644
--- a/common/file.cpp
+++ b/common/file.cpp
@@ -104,7 +104,7 @@
//#define fgets(str, size, file) DS::std_fgets(str, size, file) // not used
//#define getc(handle) DS::std_getc(handle) // not used
//#define getcwd(dir, dunno) DS::std_getcwd(dir, dunno) // not used
- //#define ferror(handle) DS::std_ferror(handle) // not used
+ #define ferror(handle) DS::std_ferror(handle)
#endif
@@ -273,26 +273,14 @@ File::File()
: _handle(0), _ioFailed(false) {
}
-//#define DEBUG_FILE_REFCOUNT
-
File::~File() {
-#ifdef DEBUG_FILE_REFCOUNT
- warning("File::~File on file '%s'", _name.c_str());
-#endif
close();
}
-bool File::open(const String &filename, AccessMode mode) {
- assert(mode == kFileReadMode || mode == kFileWriteMode);
-
- if (filename.empty()) {
- error("File::open: No filename was specified");
- }
-
- if (_handle) {
- error("File::open: This file object already is opened (%s), won't open '%s'", _name.c_str(), filename.c_str());
- }
+bool File::open(const String &filename) {
+ assert(!filename.empty());
+ assert(!_handle);
_name.clear();
clearIOFailed();
@@ -300,32 +288,29 @@ bool File::open(const String &filename, AccessMode mode) {
String fname(filename);
fname.toLowercase();
- const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb";
- if (mode == kFileWriteMode) {
- _handle = fopenNoCase(filename, "", modeStr);
- } else if (_filesMap && _filesMap->contains(fname)) {
+ if (_filesMap && _filesMap->contains(fname)) {
fname = (*_filesMap)[fname];
debug(3, "Opening hashed: %s", fname.c_str());
- _handle = fopen(fname.c_str(), modeStr);
+ _handle = fopen(fname.c_str(), "rb");
} else if (_filesMap && _filesMap->contains(fname + ".")) {
// WORKAROUND: Bug #1458388: "SIMON1: Game Detection fails"
// sometimes instead of "GAMEPC" we get "GAMEPC." (note trailing dot)
fname = (*_filesMap)[fname + "."];
debug(3, "Opening hashed: %s", fname.c_str());
- _handle = fopen(fname.c_str(), modeStr);
+ _handle = fopen(fname.c_str(), "rb");
} else {
if (_defaultDirectories) {
// Try all default directories
StringIntMap::const_iterator x(_defaultDirectories->begin());
for (; _handle == NULL && x != _defaultDirectories->end(); ++x) {
- _handle = fopenNoCase(filename, x->_key, modeStr);
+ _handle = fopenNoCase(filename, x->_key, "rb");
}
}
// Last resort: try the current directory
if (_handle == NULL)
- _handle = fopenNoCase(filename, "", modeStr);
+ _handle = fopenNoCase(filename, "", "rb");
// Last last (really) resort: try looking inside the application bundle on Mac OS X for the lowercase file.
#if defined(MACOSX) || defined(IPHONE)
@@ -335,7 +320,7 @@ bool File::open(const String &filename, AccessMode mode) {
if (fileUrl) {
UInt8 buf[256];
if (CFURLGetFileSystemRepresentation(fileUrl, false, (UInt8 *)buf, 256)) {
- _handle = fopen((char *)buf, modeStr);
+ _handle = fopen((char *)buf, "rb");
}
CFRelease(fileUrl);
}
@@ -345,26 +330,15 @@ bool File::open(const String &filename, AccessMode mode) {
}
- if (_handle == NULL) {
- if (mode == kFileReadMode)
- debug(2, "File %s not found", filename.c_str());
- else
- debug(2, "File %s not opened", filename.c_str());
- return false;
- }
-
+ if (_handle == NULL)
+ debug(2, "File %s not opened", filename.c_str());
+ else
+ _name = filename;
- _name = filename;
-
-#ifdef DEBUG_FILE_REFCOUNT
- warning("File::open on file '%s'", _name.c_str());
-#endif
-
- return true;
+ return _handle != NULL;
}
-bool File::open(const FilesystemNode &node, AccessMode mode) {
- assert(mode == kFileReadMode || mode == kFileWriteMode);
+bool File::open(const FilesystemNode &node) {
if (!node.exists()) {
warning("File::open: Trying to open a FilesystemNode which does not exist");
@@ -389,25 +363,14 @@ bool File::open(const FilesystemNode &node, AccessMode mode) {
clearIOFailed();
_name.clear();
- const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb";
-
- _handle = fopen(node.getPath().c_str(), modeStr);
+ _handle = fopen(node.getPath().c_str(), "rb");
- if (_handle == NULL) {
- if (mode == kFileReadMode)
- debug(2, "File %s not found", filename.c_str());
- else
- debug(2, "File %s not opened", filename.c_str());
- return false;
- }
-
- _name = filename;
-
-#ifdef DEBUG_FILE_REFCOUNT
- warning("File::open on file '%s'", _name.c_str());
-#endif
+ if (_handle == NULL)
+ debug(2, "File %s not found", filename.c_str());
+ else
+ _name = filename;
- return true;
+ return _handle != NULL;
}
bool File::exists(const String &filename) {
@@ -438,7 +401,7 @@ bool File::exists(const String &filename) {
//Try opening the file inside the local directory as a last resort
File tmp;
- return tmp.open(filename, kFileReadMode);
+ return tmp.open(filename);
}
void File::close() {
@@ -462,28 +425,19 @@ void File::clearIOFailed() {
}
bool File::eof() const {
- if (_handle == NULL) {
- error("File::eof: File is not open!");
- return false;
- }
+ assert(_handle);
return feof((FILE *)_handle) != 0;
}
uint32 File::pos() const {
- if (_handle == NULL) {
- error("File::pos: File is not open!");
- return 0;
- }
+ assert(_handle);
return ftell((FILE *)_handle);
}
uint32 File::size() const {
- if (_handle == NULL) {
- error("File::size: File is not open!");
- return 0;
- }
+ assert(_handle);
uint32 oldPos = ftell((FILE *)_handle);
fseek((FILE *)_handle, 0, SEEK_END);
@@ -494,10 +448,7 @@ uint32 File::size() const {
}
void File::seek(int32 offs, int whence) {
- if (_handle == NULL) {
- error("File::seek: File is not open!");
- return;
- }
+ assert(_handle);
if (fseek((FILE *)_handle, offs, whence) != 0)
clearerr((FILE *)_handle);
@@ -507,10 +458,7 @@ uint32 File::read(void *ptr, uint32 len) {
byte *ptr2 = (byte *)ptr;
uint32 real_len;
- if (_handle == NULL) {
- error("File::read: File is not open!");
- return 0;
- }
+ assert(_handle);
if (len == 0)
return 0;
@@ -523,20 +471,91 @@ uint32 File::read(void *ptr, uint32 len) {
return real_len;
}
-uint32 File::write(const void *ptr, uint32 len) {
- if (_handle == NULL) {
- error("File::write: File is not open!");
- return 0;
- }
+
+DumpFile::DumpFile() : _handle(0) {
+}
+
+DumpFile::~DumpFile() {
+ close();
+}
+
+bool DumpFile::open(const String &filename) {
+ assert(!filename.empty());
+ assert(!_handle);
+
+ String fname(filename);
+ fname.toLowercase();
+
+ _handle = fopenNoCase(filename, "", "wb");
+
+ if (_handle == NULL)
+ debug(2, "Failed to open '%s' for writing", filename.c_str());
+
+ return _handle != NULL;
+}
+
+bool DumpFile::open(const FilesystemNode &node) {
+ assert(!_handle);
+
+ if (node.isDirectory()) {
+ warning("File::open: Trying to open a FilesystemNode which is a directory");
+ return false;
+ } /*else if (!node.isReadable() && mode == kFileReadMode) {
+ warning("File::open: Trying to open an unreadable FilesystemNode object for reading");
+ return false;
+ } else if (!node.isWritable() && mode == kFileWriteMode) {
+ warning("File::open: Trying to open an unwritable FilesystemNode object for writing");
+ return false;
+ }*/
+
+ _handle = fopen(node.getPath().c_str(), "wb");
+
+ if (_handle == NULL)
+ debug(2, "File %s not found", node.getName().c_str());
+
+ return _handle != NULL;
+}
+
+void DumpFile::close() {
+ if (_handle)
+ fclose((FILE *)_handle);
+ _handle = NULL;
+}
+
+bool DumpFile::isOpen() const {
+ return _handle != NULL;
+}
+
+bool DumpFile::ioFailed() const {
+ assert(_handle);
+ return ferror((FILE *)_handle) != 0;
+}
+
+void DumpFile::clearIOFailed() {
+ assert(_handle);
+ clearerr((FILE *)_handle);
+}
+
+bool DumpFile::eof() const {
+ assert(_handle);
+ return feof((FILE *)_handle) != 0;
+}
+
+uint32 DumpFile::write(const void *ptr, uint32 len) {
+ assert(_handle);
if (len == 0)
return 0;
- if ((uint32)fwrite(ptr, 1, len, (FILE *)_handle) != len) {
- _ioFailed = true;
- }
+ return (uint32)fwrite(ptr, 1, len, (FILE *)_handle);
+}
- return len;
+void DumpFile::flush() {
+ assert(_handle);
+ // TODO: Should check the return value of fflush, and if it is non-zero,
+ // check errno and set an error flag.
+ fflush((FILE *)_handle);
}
+
} // End of namespace Common
diff --git a/common/file.h b/common/file.h
index 8a69318128..3adeb6ff36 100644
--- a/common/file.h
+++ b/common/file.h
@@ -27,6 +27,7 @@
#define COMMON_FILE_H
#include "common/scummsys.h"
+#include "common/noncopyable.h"
#include "common/str.h"
#include "common/stream.h"
@@ -34,7 +35,10 @@ class FilesystemNode;
namespace Common {
-class File : public SeekableReadStream, public WriteStream {
+/**
+ * TODO: vital to document this core class properly!!! For both users and implementors
+ */
+class File : public SeekableReadStream, public NonCopyable {
protected:
/** File handle to the actual file; 0 if no file is open. */
void *_handle;
@@ -45,19 +49,7 @@ protected:
/** The name of this file, for debugging. */
String _name;
-private:
- // Disallow copying File objects. There is not strict reason for this,
- // except that so far we never had real need for such a feature, and
- // code that accidentally copied File objects tended to break in strange
- // ways.
- File(const File &f);
- File &operator =(const File &f);
-
public:
- enum AccessMode {
- kFileReadMode = 1,
- kFileWriteMode = 2
- };
static void addDefaultDirectory(const String &directory);
static void addDefaultDirectoryRecursive(const String &directory, int level = 4, const String &prefix = "");
@@ -80,8 +72,8 @@ public:
*/
static bool exists(const String &filename);
- virtual bool open(const String &filename, AccessMode mode = kFileReadMode);
- virtual bool open(const FilesystemNode &node, AccessMode mode = kFileReadMode);
+ virtual bool open(const String &filename);
+ virtual bool open(const FilesystemNode &node);
virtual void close();
@@ -114,9 +106,54 @@ public:
virtual uint32 size() const;
void seek(int32 offs, int whence = SEEK_SET);
uint32 read(void *dataPtr, uint32 dataSize);
- uint32 write(const void *dataPtr, uint32 dataSize);
};
+
+/**
+ * TODO: document this class
+ *
+ * Some design ideas:
+ * - automatically drop all files into dumps/ dir? Might not be desired in all cases
+ */
+class DumpFile : public WriteStream, public NonCopyable {
+protected:
+ /** File handle to the actual file; 0 if no file is open. */
+ void *_handle;
+
+public:
+ DumpFile();
+ virtual ~DumpFile();
+
+ virtual bool open(const String &filename);
+ virtual bool open(const FilesystemNode &node);
+
+ virtual void close();
+
+ /**
+ * Checks if the object opened a file successfully.
+ *
+ * @return: true if any file is opened, false otherwise.
+ */
+ bool isOpen() const;
+
+
+ bool ioFailed() const;
+ void clearIOFailed();
+ bool eos() const { return eof(); }
+
+ /**
+ * Checks for end of file.
+ *
+ * @return: true if the end of file is reached, false otherwise.
+ */
+ virtual bool eof() const;
+
+ virtual uint32 write(const void *dataPtr, uint32 dataSize);
+
+ virtual void flush();
+};
+
+
} // End of namespace Common
#endif
diff --git a/common/fs.cpp b/common/fs.cpp
index 7d803dacd4..3f585c6038 100644
--- a/common/fs.cpp
+++ b/common/fs.cpp
@@ -23,10 +23,13 @@
*/
#include "common/util.h"
+#include "common/file.h"
#include "common/system.h"
#include "backends/fs/abstract-fs.h"
#include "backends/fs/fs-factory.h"
+//namespace Common {
+
FilesystemNode::FilesystemNode() {
}
@@ -170,3 +173,41 @@ bool FilesystemNode::lookupFile(FSList &results, const Common::String &p, bool h
return !results.empty();
}
+
+Common::SeekableReadStream *FilesystemNode::openForReading() {
+ if (_realNode == 0)
+ return 0;
+#if 0
+ return _realNode->openForReading();
+#else
+ // FIXME: Until we support openForReading in AbstractFilesystemNode,
+ // we just use Common::File.
+ Common::File *confFile = new Common::File();
+ assert(confFile);
+ if (!confFile->open(*this)) {
+ delete confFile;
+ confFile = 0;
+ }
+ return confFile;
+#endif
+}
+
+Common::WriteStream *FilesystemNode::openForWriting() {
+ if (_realNode == 0)
+ return 0;
+#if 0
+ return _realNode->openForWriting();
+#else
+ // FIXME: Until we support openForWriting in AbstractFilesystemNode,
+ // we just use Common::DumpFile.
+ Common::DumpFile *confFile = new Common::DumpFile();
+ assert(confFile);
+ if (!confFile->open(*this)) {
+ delete confFile;
+ confFile = 0;
+ }
+ return confFile;
+#endif
+}
+
+//} // End of namespace Common
diff --git a/common/fs.h b/common/fs.h
index ed7355cc00..972e0d86af 100644
--- a/common/fs.h
+++ b/common/fs.h
@@ -29,10 +29,18 @@
#include "common/ptr.h"
#include "common/str.h"
+class AbstractFilesystemNode;
+
+namespace Common {
+ class SeekableReadStream;
+ class WriteStream;
+}
+
//namespace Common {
class FilesystemNode;
-class AbstractFilesystemNode;
+//class SeekableReadStream;
+//class WriteStream;
/**
* List of multiple file system nodes. E.g. the contents of a given directory.
@@ -49,22 +57,6 @@ class FSList : public Common::Array<FilesystemNode> {};
* To this end, we abstract away from paths; implementations can be based on
* paths (and it's left to them whether / or \ or : is the path separator :-);
* but it is also possible to use inodes or vrefs (MacOS 9) or anything else.
- *
- * NOTE: Backends still have to provide a way to extract a path from a FSIntern
- *
- * You may ask now: "isn't this cheating? Why do we go through all this when we use
- * a path in the end anyway?!?".
- * Well, for once as long as we don't provide our own file open/read/write API, we
- * still have to use fopen(). Since all our targets already support fopen(), it should
- * be possible to get a fopen() compatible string for any file system node.
- *
- * Secondly, with this abstraction layer, we still avoid a lot of complications based on
- * differences in FS roots, different path separators, or even systems with no real
- * paths (MacOS 9 doesn't even have the notion of a "current directory").
- * And if we ever want to support devices with no FS in the classical sense (Palm...),
- * we can build upon this.
- *
- * This class acts as a wrapper around the AbstractFilesystemNode class defined in backends/fs.
*/
class FilesystemNode {
private:
@@ -108,9 +100,9 @@ public:
bool operator<(const FilesystemNode& node) const;
/**
- * Indicates whether the object referred by this path exists in the filesystem or not.
+ * Indicates whether the object referred by this node exists in the filesystem or not.
*
- * @return bool true if the path exists, false otherwise.
+ * @return bool true if the node exists, false otherwise.
*/
virtual bool exists() const;
@@ -168,7 +160,7 @@ public:
FilesystemNode getParent() const;
/**
- * Indicates whether the path refers to a directory or not.
+ * Indicates whether the node refers to a directory or not.
*
* @todo Currently we assume that a node that is not a directory
* automatically is a file (ignoring things like symlinks or pipes).
@@ -179,28 +171,28 @@ public:
virtual bool isDirectory() const;
/**
- * Indicates whether the object referred by this path can be read from or not.
+ * Indicates whether the object referred by this node can be read from or not.
*
- * If the path refers to a directory, readability implies being able to read
+ * If the node refers to a directory, readability implies being able to read
* and list the directory entries.
*
- * If the path refers to a file, readability implies being able to read the
+ * If the node refers to a file, readability implies being able to read the
* contents of the file.
*
- * @return bool true if the object can be read, false otherwise.
+ * @return true if the object can be read, false otherwise.
*/
virtual bool isReadable() const;
/**
- * Indicates whether the object referred by this path can be written to or not.
+ * Indicates whether the object referred by this node can be written to or not.
*
- * If the path refers to a directory, writability implies being able to modify
+ * If the node refers to a directory, writability implies being able to modify
* the directory entry (i.e. rename the directory, remove it or write files inside of it).
*
- * If the path refers to a file, writability implies being able to write data
+ * If the node refers to a file, writability implies being able to write data
* to the file.
*
- * @return bool true if the object can be written to, false otherwise.
+ * @return true if the object can be written to, false otherwise.
*/
virtual bool isWritable() const;
@@ -221,6 +213,25 @@ public:
* @return true if matches could be found, false otherwise.
*/
virtual bool lookupFile(FSList &results, const Common::String &pattern, bool hidden, bool exhaustive, int depth = -1) const;
+
+
+ /**
+ * Creates a SeekableReadStream instance corresponding to the file
+ * referred by this node. This assumes that the node actually refers
+ * to a readable file. If this is not the case, 0 is returned.
+ *
+ * @return pointer to the stream object, 0 in case of a failure
+ */
+ virtual Common::SeekableReadStream *openForReading();
+
+ /**
+ * Creates a WriteStream instance corresponding to the file
+ * referred by this node. This assumes that the node actually refers
+ * to a readable file. If this is not the case, 0 is returned.
+ *
+ * @return pointer to the stream object, 0 in case of a failure
+ */
+ virtual Common::WriteStream *openForWriting();
};
//} // End of namespace Common
diff --git a/common/func.h b/common/func.h
index 95df96123a..6aa5b76ed4 100644
--- a/common/func.h
+++ b/common/func.h
@@ -29,12 +29,18 @@
namespace Common {
+/**
+ * Generic unary function.
+ */
template<class Arg, class Result>
struct UnaryFunction {
typedef Arg ArgumenType;
typedef Result ResultType;
};
+/**
+ * Generic binary function.
+ */
template<class Arg1, class Arg2, class Result>
struct BinaryFunction {
typedef Arg1 FirstArgumentType;
@@ -42,16 +48,25 @@ struct BinaryFunction {
typedef Result ResultType;
};
+/**
+ * Predicate to check for equallity of two data elements.
+ */
template<class T>
struct EqualTo : public BinaryFunction<T, T, bool> {
bool operator()(const T &x, const T &y) const { return x == y; }
};
+/**
+ * Predicate to check for x being less than y.
+ */
template<class T>
struct Less : public BinaryFunction<T, T, bool> {
bool operator()(const T &x, const T &y) const { return x < y; }
};
+/**
+ * Predicate to check for x being greater than y.
+ */
template<class T>
struct Greater : public BinaryFunction<T, T, bool> {
bool operator()(const T &x, const T &y) const { return x > y; }
@@ -63,15 +78,19 @@ private:
Op _op;
typename Op::FirstArgumentType _arg1;
public:
- Binder1st(const Op &op, const typename Op::FirstArgumentType &arg1) : _op(op), _arg1(arg1) {}
+ Binder1st(const Op &op, typename Op::FirstArgumentType arg1) : _op(op), _arg1(arg1) {}
typename Op::ResultType operator()(typename Op::SecondArgumentType v) const {
return _op(_arg1, v);
}
};
-template<class Op, class T>
-inline Binder1st<Op> bind1st(const Op &op, const T &t) {
+/**
+ * Transforms a binary function object into an unary function object.
+ * To achieve that the first parameter is bound to the passed value t.
+ */
+template<class Op>
+inline Binder1st<Op> bind1st(const Op &op, typename Op::FirstArgumentType t) {
return Binder1st<Op>(op, t);
}
@@ -81,15 +100,19 @@ private:
Op _op;
typename Op::SecondArgumentType _arg2;
public:
- Binder2nd(const Op &op, const typename Op::SecondArgumentType &arg2) : _op(op), _arg2(arg2) {}
+ Binder2nd(const Op &op, typename Op::SecondArgumentType arg2) : _op(op), _arg2(arg2) {}
typename Op::ResultType operator()(typename Op::FirstArgumentType v) const {
return _op(v, _arg2);
}
};
-template<class Op, class T>
-inline Binder2nd<Op> bind2nd(const Op &op, const T &t) {
+/**
+ * Transforms a binary function object into an unary function object.
+ * To achieve that the first parameter is bound to the passed value t.
+ */
+template<class Op>
+inline Binder2nd<Op> bind2nd(const Op &op, typename Op::SecondArgumentType t) {
return Binder2nd<Op>(op, t);
}
@@ -119,18 +142,24 @@ public:
}
};
+/**
+ * Creates an unary function object from a function pointer.
+ */
template<class Arg, class Result>
inline PointerToUnaryFunc<Arg, Result> ptr_fun(Result (*func)(Arg)) {
return PointerToUnaryFunc<Arg, Result>(func);
}
+/**
+ * Creates an binary function object from a function pointer.
+ */
template<class Arg1, class Arg2, class Result>
inline PointerToBinaryFunc<Arg1, Arg2, Result> ptr_fun(Result (*func)(Arg1, Arg2)) {
return PointerToBinaryFunc<Arg1, Arg2, Result>(func);
}
template<class Result, class T>
-class MemFunc0 : public UnaryFunction<T*, Result> {
+class MemFunc0 : public UnaryFunction<T *, Result> {
private:
Result (T::*_func)();
public:
@@ -143,20 +172,20 @@ public:
};
template<class Result, class T>
-class ConstMemFunc0 : public UnaryFunction<T*, Result> {
+class ConstMemFunc0 : public UnaryFunction<T *, Result> {
private:
Result (T::*_func)() const;
public:
typedef Result (T::*FuncType)() const;
ConstMemFunc0(const FuncType &func) : _func(func) {}
- Result operator()(T *v) const {
+ Result operator()(const T *v) const {
return (v->*_func)();
}
};
template<class Result, class Arg, class T>
-class MemFunc1 : public BinaryFunction<T*, Arg, Result> {
+class MemFunc1 : public BinaryFunction<T *, Arg, Result> {
private:
Result (T::*_func)(Arg);
public:
@@ -169,40 +198,166 @@ public:
};
template<class Result, class Arg, class T>
-class ConstMemFunc1 : public BinaryFunction<T*, Arg, Result> {
+class ConstMemFunc1 : public BinaryFunction<T *, Arg, Result> {
private:
Result (T::*_func)(Arg) const;
public:
typedef Result (T::*FuncType)(Arg) const;
ConstMemFunc1(const FuncType &func) : _func(func) {}
- Result operator()(T *v1, Arg v2) const {
+ Result operator()(const T *v1, Arg v2) const {
return (v1->*_func)(v2);
}
};
+/**
+ * Creates a unary function object from a class member function pointer.
+ * The parameter passed to the function object is the 'this' pointer to
+ * be used for the function call.
+ */
template<class Result, class T>
inline MemFunc0<Result, T> mem_fun(Result (T::*f)()) {
return MemFunc0<Result, T>(f);
}
+/**
+ * Creates a unary function object from a class member function pointer.
+ * The parameter passed to the function object is the 'this' pointer to
+ * be used for the function call.
+ */
template<class Result, class T>
inline ConstMemFunc0<Result, T> mem_fun(Result (T::*f)() const) {
return ConstMemFunc0<Result, T>(f);
}
+/**
+ * Creates a binary function object from a class member function pointer.
+ * The first parameter passed to the function object is the 'this' pointer to
+ * be used for the function call.
+ * The second one is the parameter passed to the member function.
+ */
template<class Result, class Arg, class T>
inline MemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg)) {
return MemFunc1<Result, Arg, T>(f);
}
+/**
+ * Creates a binary function object from a class member function pointer.
+ * The first parameter passed to the function object is the 'this' pointer to
+ * be used for the function call.
+ * The second one is the parameter passed to the member function.
+ */
template<class Result, class Arg, class T>
inline ConstMemFunc1<Result, Arg, T> mem_fun(Result (T::*f)(Arg) const) {
return ConstMemFunc1<Result, Arg, T>(f);
}
+template<class Result, class T>
+class MemFuncRef0 : public UnaryFunction<T &, Result> {
+private:
+ Result (T::*_func)();
+public:
+ typedef Result (T::*FuncType)();
+
+ MemFuncRef0(const FuncType &func) : _func(func) {}
+ Result operator()(T &v) const {
+ return (v.*_func)();
+ }
+};
+
+template<class Result, class T>
+class ConstMemFuncRef0 : public UnaryFunction<T &, Result> {
+private:
+ Result (T::*_func)() const;
+public:
+ typedef Result (T::*FuncType)() const;
+
+ ConstMemFuncRef0(const FuncType &func) : _func(func) {}
+ Result operator()(const T &v) const {
+ return (v.*_func)();
+ }
+};
+
+template<class Result, class Arg, class T>
+class MemFuncRef1 : public BinaryFunction<T &, Arg, Result> {
+private:
+ Result (T::*_func)(Arg);
+public:
+ typedef Result (T::*FuncType)(Arg);
+
+ MemFuncRef1(const FuncType &func) : _func(func) {}
+ Result operator()(T &v1, Arg v2) const {
+ return (v1.*_func)(v2);
+ }
+};
+
+template<class Result, class Arg, class T>
+class ConstMemFuncRef1 : public BinaryFunction<T &, Arg, Result> {
+private:
+ Result (T::*_func)(Arg) const;
+public:
+ typedef Result (T::*FuncType)(Arg) const;
+
+ ConstMemFuncRef1(const FuncType &func) : _func(func) {}
+ Result operator()(const T &v1, Arg v2) const {
+ return (v1.*_func)(v2);
+ }
+};
+
+/**
+ * Creates a unary function object from a class member function pointer.
+ * The parameter passed to the function object is the object instance to
+ * be used for the function call. Note unlike mem_fun, it takes a reference
+ * as parameter. Note unlike mem_fun, it takes a reference
+ * as parameter.
+ */
+template<class Result, class T>
+inline MemFuncRef0<Result, T> mem_fun_ref(Result (T::*f)()) {
+ return MemFuncRef0<Result, T>(f);
+}
+
+/**
+ * Creates a unary function object from a class member function pointer.
+ * The parameter passed to the function object is the object instance to
+ * be used for the function call. Note unlike mem_fun, it takes a reference
+ * as parameter.
+ */
+template<class Result, class T>
+inline ConstMemFuncRef0<Result, T> mem_fun_Ref(Result (T::*f)() const) {
+ return ConstMemFuncRef0<Result, T>(f);
+}
+
+/**
+ * Creates a binary function object from a class member function pointer.
+ * The first parameter passed to the function object is the object instance to
+ * be used for the function call. Note unlike mem_fun, it takes a reference
+ * as parameter.
+ * The second one is the parameter passed to the member function.
+ */
+template<class Result, class Arg, class T>
+inline MemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg)) {
+ return MemFuncRef1<Result, Arg, T>(f);
+}
+
+/**
+ * Creates a binary function object from a class member function pointer.
+ * The first parameter passed to the function object is the object instance to
+ * be used for the function call. Note unlike mem_fun, it takes a reference
+ * as parameter.
+ * The second one is the parameter passed to the member function.
+ */
+template<class Result, class Arg, class T>
+inline ConstMemFuncRef1<Result, Arg, T> mem_fun_ref(Result (T::*f)(Arg) const) {
+ return ConstMemFuncRef1<Result, Arg, T>(f);
+}
+
// functor code
+/**
+ * Generic functor object for function objects without parameters.
+ *
+ * @see Functor1
+ */
template<class Res>
struct Functor0 {
virtual ~Functor0() {}
@@ -211,6 +366,18 @@ struct Functor0 {
virtual Res operator()() const = 0;
};
+/**
+ * Functor object for a class member function without parameter.
+ *
+ * Example creation:
+ *
+ * Foo bar;
+ * Functor0Men<void, Foo> myFunctor(&bar, &Foo::myFunc);
+ *
+ * Example usage:
+ *
+ * myFunctor();
+ */
template<class Res, class T>
class Functor0Mem : public Functor0<Res> {
public:
@@ -218,7 +385,7 @@ public:
Functor0Mem(T *t, const FuncType &func) : _t(t), _func(func) {}
- bool isValid() const { return _func != 0; }
+ bool isValid() const { return _func != 0 && _t != 0; }
Res operator()() const {
return (_t->*_func)();
}
@@ -227,6 +394,38 @@ private:
const FuncType _func;
};
+/**
+ * Generic functor object for unary function objects.
+ *
+ * A typical usage for an unary function object is for executing opcodes
+ * in a script interpreter. To achieve that one can create an Common::Array
+ * object with 'Functor1<Arg, Res> *' as type. Now after the right engine version
+ * has been determined and the opcode table to use is found one could easily
+ * add the opcode implementations like this:
+ *
+ * Common::Array<Functor1<ScriptState, void> *> opcodeTable;
+ * opcodeTable[0] = new Functor1Mem<ScriptState, void, MyEngine_v1>(&myEngine, &MyEngine_v1::o1_foo);
+ * opcodeTable[1] = new Functor1Mem<ScriptState, void, MyEngine_v2>(&myEngine, &MyEngine_v2::o2_foo);
+ * // unimplemented/unused opcode
+ * opcodeTable[2] = 0;
+ * etc.
+ *
+ * This makes it easy to add member functions of different classes as
+ * opcode functions to the function table. Since with the generic
+ * Functor1<ScriptState, void> object the only requirement for an
+ * function to be used is 'ScriptState' as argument and 'void' as return
+ * value.
+ *
+ * Now for calling the opcodes one has simple to do:
+ * if (opcodeTable[opcodeNum] && opcodeTable[opcodeNum]->isValid())
+ * (*opcodeTable[opcodeNum])(scriptState);
+ * else
+ * warning("Unimplemented opcode %d", opcodeNum);
+ *
+ * If you want to see an real world example check the kyra engine.
+ * Files: engines/kyra/script.cpp and .h and engine/kyra/script_*.cpp
+ * are interesting for that matter.
+ */
template<class Arg, class Res>
struct Functor1 : public Common::UnaryFunction<Arg, Res> {
virtual ~Functor1() {}
@@ -235,6 +434,13 @@ struct Functor1 : public Common::UnaryFunction<Arg, Res> {
virtual Res operator()(Arg) const = 0;
};
+/**
+ * Functor object for an unary class member function.
+ * Usage is like with Functor0Mem. The resulting functor object
+ * will take one parameter though.
+ *
+ * @see Functor0Men
+ */
template<class Arg, class Res, class T>
class Functor1Mem : public Functor1<Arg, Res> {
public:
@@ -242,7 +448,7 @@ public:
Functor1Mem(T *t, const FuncType &func) : _t(t), _func(func) {}
- bool isValid() const { return _func != 0; }
+ bool isValid() const { return _func != 0 && _t != 0; }
Res operator()(Arg v1) const {
return (_t->*_func)(v1);
}
@@ -251,6 +457,11 @@ private:
const FuncType _func;
};
+/**
+ * Generic functor object for binary function objects.
+ *
+ * @see Functor1
+ */
template<class Arg1, class Arg2, class Res>
struct Functor2 : public Common::BinaryFunction<Arg1, Arg2, Res> {
virtual ~Functor2() {}
@@ -259,6 +470,13 @@ struct Functor2 : public Common::BinaryFunction<Arg1, Arg2, Res> {
virtual Res operator()(Arg1, Arg2) const = 0;
};
+/**
+ * Functor object for a binary class member function.
+ * Usage is like with Functor0Mem. The resulting functor object
+ * will take two parameter though.
+ *
+ * @see Functor0Men
+ */
template<class Arg1, class Arg2, class Res, class T>
class Functor2Mem : public Functor2<Arg1, Arg2, Res> {
public:
@@ -266,7 +484,7 @@ public:
Functor2Mem(T *t, const FuncType &func) : _t(t), _func(func) {}
- bool isValid() const { return _func != 0; }
+ bool isValid() const { return _func != 0 && _t != 0; }
Res operator()(Arg1 v1, Arg2 v2) const {
return (_t->*_func)(v1, v2);
}
diff --git a/common/hashmap.h b/common/hashmap.h
index 1bae44e98e..69f367de97 100644
--- a/common/hashmap.h
+++ b/common/hashmap.h
@@ -65,8 +65,12 @@
// on every system we support, so we should get rid of this.
// The solution should be to write a simple placement new
// on our own.
+
+// Symbian does not have <new> but the new operator
+#if !defined(__SYMBIAN32__)
#include <new>
#endif
+#endif
namespace Common {
diff --git a/common/ptr.h b/common/ptr.h
index eea3c39882..c6fcaa4f75 100644
--- a/common/ptr.h
+++ b/common/ptr.h
@@ -121,7 +121,7 @@ public:
~SharedPtr() { decRef(); }
- SharedPtr &operator =(const SharedPtr &r) {
+ SharedPtr &operator=(const SharedPtr &r) {
if (r._refCount)
++(*r._refCount);
decRef();
@@ -134,7 +134,7 @@ public:
}
template<class T2>
- SharedPtr &operator =(const SharedPtr<T2> &r) {
+ SharedPtr &operator=(const SharedPtr<T2> &r) {
if (r._refCount)
++(*r._refCount);
decRef();
@@ -146,8 +146,8 @@ public:
return *this;
}
- ValueType &operator *() const { assert(_pointer); return *_pointer; }
- Pointer operator ->() const { assert(_pointer); return _pointer; }
+ ValueType &operator*() const { assert(_pointer); return *_pointer; }
+ Pointer operator->() const { assert(_pointer); return _pointer; }
/**
* Returns the plain pointer value. Be sure you know what you
@@ -171,6 +171,16 @@ public:
bool unique() const { return refCount() == 1; }
/**
+ * Resets the SharedPtr object to a NULL pointer.
+ */
+ void reset() {
+ decRef();
+ _deletion = 0;
+ _refCount = 0;
+ _pointer = 0;
+ }
+
+ /**
* Returns the number of references to the assigned pointer.
* This should just be used for debugging purposes.
*/
@@ -199,12 +209,12 @@ private:
} // end of namespace Common
template<class T1, class T2>
-bool operator ==(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) {
+bool operator==(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) {
return l.get() == r.get();
}
template<class T1, class T2>
-bool operator !=(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) {
+bool operator!=(const Common::SharedPtr<T1> &l, const Common::SharedPtr<T2> &r) {
return l.get() != r.get();
}
diff --git a/common/rect.h b/common/rect.h
index f71124434a..dcf1c8b421 100644
--- a/common/rect.h
+++ b/common/rect.h
@@ -177,6 +177,10 @@ struct Rect {
clip(Rect(0, 0, maxw, maxh));
}
+ bool isEmpty() const {
+ return (left >= right || top >= bottom);
+ }
+
bool isValidRect() const {
return (left <= right && top <= bottom);
}
diff --git a/common/savefile.h b/common/savefile.h
index f30ddfc160..d44f946d48 100644
--- a/common/savefile.h
+++ b/common/savefile.h
@@ -39,27 +39,14 @@ namespace Common {
* That typically means "save games", but also includes things like the
* IQ points in Indy3.
*/
-class InSaveFile : public SeekableReadStream {};
+typedef SeekableReadStream InSaveFile;
/**
* A class which allows game engines to save game state data.
* That typically means "save games", but also includes things like the
* IQ points in Indy3.
*/
-class OutSaveFile : public WriteStream {
-public:
- /**
- * Close this savefile, to be called right before destruction of this
- * savefile. The idea is that this ways, I/O errors that occur
- * during closing/flushing of the file can still be handled by the
- * game engine.
- *
- * By default, this just flushes the stream.
- */
- virtual void finalize() {
- flush();
- }
-};
+typedef WriteStream OutSaveFile;
/**
diff --git a/common/str.cpp b/common/str.cpp
index a2e6e0c66d..5f8d4ffb7e 100644
--- a/common/str.cpp
+++ b/common/str.cpp
@@ -111,6 +111,74 @@ String::~String() {
decRefCount(_extern._refCount);
}
+void String::makeUnique() {
+ ensureCapacity(_len, true);
+}
+
+/**
+ * Ensure that enough storage is available to store at least new_len
+ * characters plus a null byte. In addition, if we currently share
+ * the storage with another string, unshare it, so that we can safely
+ * write to the storage.
+ */
+void String::ensureCapacity(uint32 new_len, bool keep_old) {
+ bool isShared;
+ uint32 curCapacity, newCapacity;
+ char *newStorage;
+ int *oldRefCount = _extern._refCount;
+
+ if (isStorageIntern()) {
+ isShared = false;
+ curCapacity = _builtinCapacity - 1;
+ } else {
+ isShared = (oldRefCount && *oldRefCount > 1);
+ curCapacity = _extern._capacity;
+ }
+
+ // Special case: If there is enough space, and we do not share
+ // the storage, then there is nothing to do.
+ if (!isShared && new_len <= curCapacity)
+ return;
+
+ if (isShared && new_len <= _builtinCapacity - 1) {
+ // We share the storage, but there is enough internal storage: Use that.
+ newStorage = _storage;
+ newCapacity = _builtinCapacity - 1;
+ } else {
+ // We need to allocate storage on the heap!
+
+ // Compute a suitable new capacity limit
+ newCapacity = computeCapacity(new_len);
+
+ // Allocate new storage
+ newStorage = (char *)malloc(newCapacity+1);
+ assert(newStorage);
+ }
+
+ // Copy old data if needed, elsewise reset the new storage.
+ if (keep_old) {
+ assert(_len <= newCapacity);
+ memcpy(newStorage, _str, _len + 1);
+ } else {
+ _len = 0;
+ newStorage[0] = 0;
+ }
+
+ // Release hold on the old storage ...
+ decRefCount(oldRefCount);
+
+ // ... in favor of the new storage
+ _str = newStorage;
+
+ if (!isStorageIntern()) {
+ // Set the ref count & capacity if we use an external storage.
+ // It is important to do this *after* copying any old content,
+ // else we would override data that has not yet been copied!
+ _extern._refCount = 0;
+ _extern._capacity = newCapacity;
+ }
+}
+
void String::incRefCount() const {
assert(!isStorageIntern());
if (_extern._refCount == 0) {
@@ -170,7 +238,8 @@ String &String::operator =(const String &str) {
}
String& String::operator =(char c) {
- ensureCapacity(1, false);
+ decRefCount(_extern._refCount);
+ _str = _storage;
_len = 1;
_str[0] = c;
_str[1] = 0;
@@ -253,10 +322,7 @@ void String::deleteLastChar() {
void String::deleteChar(uint32 p) {
assert(p < _len);
- // Call ensureCapacity to make sure we actually *own* the storage
- // to which _str points to -- we wouldn't want to modify a storage
- // which other string objects are sharing, after all.
- ensureCapacity(_len, true);
+ makeUnique();
while (p++ < _len)
_str[p-1] = _str[p];
_len--;
@@ -273,7 +339,7 @@ void String::clear() {
void String::setChar(char c, uint32 p) {
assert(p <= _len);
- ensureCapacity(_len, true);
+ makeUnique();
_str[p] = c;
}
@@ -288,78 +354,36 @@ void String::insertChar(char c, uint32 p) {
}
void String::toLowercase() {
- ensureCapacity(_len, true);
+ makeUnique();
for (uint32 i = 0; i < _len; ++i)
_str[i] = tolower(_str[i]);
}
void String::toUppercase() {
- ensureCapacity(_len, true);
+ makeUnique();
for (uint32 i = 0; i < _len; ++i)
_str[i] = toupper(_str[i]);
}
-/**
- * Ensure that enough storage is available to store at least new_len
- * characters plus a null byte. In addition, if we currently share
- * the storage with another string, unshare it, so that we can safely
- * write to the storage.
- */
-void String::ensureCapacity(uint32 new_len, bool keep_old) {
- bool isShared;
- uint32 curCapacity, newCapacity;
- char *newStorage;
- int *oldRefCount = _extern._refCount;
-
- if (isStorageIntern()) {
- isShared = false;
- curCapacity = _builtinCapacity - 1;
- } else {
- isShared = (oldRefCount && *oldRefCount > 1);
- curCapacity = _extern._capacity;
- }
-
- // Special case: If there is enough space, and we do not share
- // the storage, then there is nothing to do.
- if (!isShared && new_len <= curCapacity)
+void String::trim() {
+ if (_len == 0)
return;
- if (isShared && new_len <= _builtinCapacity - 1) {
- // We share the storage, but there is enough internal storage: Use that.
- newStorage = _storage;
- newCapacity = _builtinCapacity - 1;
- } else {
- // We need to allocate storage on the heap!
-
- // Compute a suitable new capacity limit
- newCapacity = computeCapacity(new_len);
+ makeUnique();
- // Allocate new storage
- newStorage = (char *)malloc(newCapacity+1);
- assert(newStorage);
- }
-
- // Copy old data if needed, elsewise reset the new storage.
- if (keep_old) {
- assert(_len <= newCapacity);
- memcpy(newStorage, _str, _len + 1);
- } else {
- _len = 0;
- newStorage[0] = 0;
- }
-
- // Release hold on the old storage ...
- decRefCount(oldRefCount);
+ // Trim trailing whitespace
+ while (_len >= 1 && isspace(_str[_len-1]))
+ _len--;
+ _str[_len] = 0;
- // ... in favor of the new storage
- _str = newStorage;
+ // Trim leading whitespace
+ char *t = _str;
+ while (isspace(*t))
+ t++;
- if (!isStorageIntern()) {
- // Set the ref count & capacity if we use an external storage.
- // It is important to do this *after* copying any old content,
- // else we would override data that has not yet been copied!
- _extern._refCount = 0;
- _extern._capacity = newCapacity;
+ if (t != _str) {
+ _len -= t - _str;
+ memmove(_str, t, _len + 1);
}
}
diff --git a/common/str.h b/common/str.h
index ae9cb992b6..3479fee8e4 100644
--- a/common/str.h
+++ b/common/str.h
@@ -177,6 +177,8 @@ public:
void toLowercase();
void toUppercase();
+ void trim();
+
uint hash() const;
public:
@@ -200,6 +202,7 @@ public:
}
protected:
+ void makeUnique();
void ensureCapacity(uint32 new_len, bool keep_old);
void incRefCount() const;
void decRefCount(int *oldRefCount);
diff --git a/common/stream.cpp b/common/stream.cpp
index 61166fd451..e06cc28415 100644
--- a/common/stream.cpp
+++ b/common/stream.cpp
@@ -242,4 +242,83 @@ void SeekableSubReadStream::seek(int32 offset, int whence) {
_parentStream->seek(_pos);
}
+BufferedReadStream::BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream)
+ : _parentStream(parentStream),
+ _disposeParentStream(disposeParentStream),
+ _pos(0),
+ _bufSize(0),
+ _realBufSize(bufSize) {
+
+ assert(parentStream);
+ _buf = new byte[bufSize];
+ assert(_buf);
+}
+
+BufferedReadStream::~BufferedReadStream() {
+ if (_disposeParentStream)
+ delete _parentStream;
+ delete _buf;
+}
+
+uint32 BufferedReadStream::read(void *dataPtr, uint32 dataSize) {
+ uint32 alreadyRead = 0;
+ const uint32 bufBytesLeft = _bufSize - _pos;
+
+ // Check whether the data left in the buffer suffices....
+ if (dataSize > bufBytesLeft) {
+ // Nope, we need to read more data
+
+ // First, flush the buffer, if it is non-empty
+ if (0 < bufBytesLeft) {
+ memcpy(dataPtr, _buf + _pos, bufBytesLeft);
+ _pos = _bufSize;
+ alreadyRead += bufBytesLeft;
+ dataPtr = (byte *)dataPtr + bufBytesLeft;
+ dataSize -= bufBytesLeft;
+ }
+
+ // At this point the buffer is empty. Now if the read request
+ // exceeds the buffer size, just satisfy it directly.
+ if (dataSize > _bufSize)
+ return alreadyRead + _parentStream->read(dataPtr, dataSize);
+
+ // Refill the buffer.
+ // If we didn't read as many bytes as requested, the reason
+ // is EOF or an error. In that case we truncate the buffer
+ // size, as well as the number of bytes we are going to
+ // return to the caller.
+ _bufSize = _parentStream->read(_buf, _realBufSize);
+ _pos = 0;
+ if (dataSize > _bufSize)
+ dataSize = _bufSize;
+ }
+
+ // Satisfy the request from the buffer
+ memcpy(dataPtr, _buf + _pos, dataSize);
+ _pos += dataSize;
+ return alreadyRead + dataSize;
+}
+
+BufferedSeekableReadStream::BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream)
+ : BufferedReadStream(parentStream, bufSize, disposeParentStream),
+ _parentStream(parentStream) {
+}
+
+void BufferedSeekableReadStream::seek(int32 offset, int whence) {
+ // If it is a "local" seek, we may get away with "seeking" around
+ // in the buffer only.
+ // Note: We could try to handle SEEK_END and SEEK_SET, too, but
+ // since they are rarely used, it seems not worth the effort.
+ if (whence == SEEK_CUR && (int)_pos + offset >= 0 && _pos + offset <= _bufSize) {
+ _pos += offset;
+ } else {
+ // Seek was not local enough, so we reset the buffer and
+ // just seeks normally in the parent stream.
+ if (whence == SEEK_CUR)
+ offset -= (_bufSize - _pos);
+ _pos = _bufSize;
+ _parentStream->seek(offset, whence);
+ }
+}
+
} // End of namespace Common
diff --git a/common/stream.h b/common/stream.h
index 4cf5fae114..01a946e685 100644
--- a/common/stream.h
+++ b/common/stream.h
@@ -78,6 +78,22 @@ public:
*/
virtual void flush() {}
+ /**
+ * Finalize and close this stream. To be called right before this
+ * stream instance is deleted. The goal here is to enable calling
+ * code to detect and handle I/O errors which might occur when
+ * closing (and this flushing, if buffered) the stream.
+ *
+ * After this method has been called, no further writes may be
+ * peformed on the stream. Calling ioFailed() is allowed.
+ *
+ * By default, this just flushes the stream.
+ */
+ virtual void finalize() {
+ flush();
+ }
+
+
// The remaining methods all have default implementations; subclasses
// need not (and should not) overload them.
@@ -350,15 +366,17 @@ public:
class SubReadStream : virtual public ReadStream {
protected:
ReadStream *_parentStream;
+ bool _disposeParentStream;
uint32 _pos;
uint32 _end;
- bool _disposeParentStream;
public:
SubReadStream(ReadStream *parentStream, uint32 end, bool disposeParentStream = false)
: _parentStream(parentStream),
+ _disposeParentStream(disposeParentStream),
_pos(0),
- _end(end),
- _disposeParentStream(disposeParentStream) {}
+ _end(end) {
+ assert(parentStream);
+ }
~SubReadStream() {
if (_disposeParentStream) delete _parentStream;
}
@@ -414,6 +432,48 @@ public:
}
};
+/**
+ * Wrapper class which adds buffering to any given ReadStream.
+ * Users can specify how big the buffer should be, and whether the
+ * wrapped stream should be disposed when the wrapper is disposed.
+ */
+class BufferedReadStream : virtual public ReadStream {
+protected:
+ ReadStream *_parentStream;
+ bool _disposeParentStream;
+ byte *_buf;
+ uint32 _pos;
+ uint32 _bufSize;
+ uint32 _realBufSize;
+
+public:
+ BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false);
+ ~BufferedReadStream();
+
+ virtual bool eos() const { return (_pos == _bufSize) && _parentStream->eos(); }
+ virtual bool ioFailed() const { return _parentStream->ioFailed(); }
+ virtual void clearIOFailed() { _parentStream->clearIOFailed(); }
+
+ virtual uint32 read(void *dataPtr, uint32 dataSize);
+};
+
+/**
+ * Wrapper class which adds buffering to any given SeekableReadStream.
+ * @see BufferedReadStream
+ */
+class BufferedSeekableReadStream : public BufferedReadStream, public SeekableReadStream {
+protected:
+ SeekableReadStream *_parentStream;
+public:
+ BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false);
+
+ virtual uint32 pos() const { return _parentStream->pos() - (_bufSize - _pos); }
+ virtual uint32 size() const { return _parentStream->size(); }
+
+ virtual void seek(int32 offset, int whence = SEEK_SET);
+};
+
+
/**
* Simple memory based 'stream', which implements the ReadStream interface for
@@ -514,9 +574,9 @@ public:
uint32 size() const { return _bufSize; }
};
-/**
+/**
* A sort of hybrid between MemoryWriteStream and Array classes. A stream
- * that grows as it's written to.
+ * that grows as it's written to.
*/
class MemoryWriteStreamDynamic : public Common::WriteStream {
private:
diff --git a/common/system.cpp b/common/system.cpp
index 8d528258f4..e3f81a69b6 100644
--- a/common/system.cpp
+++ b/common/system.cpp
@@ -29,6 +29,10 @@
#include "common/config-manager.h"
#include "common/system.h"
#include "common/timer.h"
+#if defined(WIN32) && defined(ARRAYSIZE)
+// winnt.h defines ARRAYSIZE, but we want our own one... - this is needed before including util.h
+#undef ARRAYSIZE
+#endif
#include "common/util.h"
#include "graphics/colormasks.h"
@@ -121,3 +125,70 @@ void OSystem::clearScreen() {
memset(screen->pixels, 0, screen->h * screen->pitch);
unlockScreen();
}
+
+
+/*
+FIXME: The config file loading code below needs to be cleaned up.
+ Port specific variants should be pushed into the respective ports.
+
+ Ideally, the default OSystem::openConfigFileForReading/Writing methods
+ should be removed completely.
+*/
+
+#include "common/file.h"
+
+#ifdef __PLAYSTATION2__
+#include "backends/platform/ps2/systemps2.h"
+#endif
+
+#ifdef IPHONE
+#include "backends/platform/iphone/osys_iphone.h"
+#endif
+
+
+#if defined(UNIX)
+#define DEFAULT_CONFIG_FILE ".scummvmrc"
+#else
+#define DEFAULT_CONFIG_FILE "scummvm.ini"
+#endif
+
+static Common::String getDefaultConfigFileName() {
+ char configFile[MAXPATHLEN];
+#if defined(PALMOS_MODE)
+ strcpy(configFile,"/PALM/Programs/ScummVM/" DEFAULT_CONFIG_FILE);
+#elif defined(IPHONE)
+ strcpy(configFile, OSystem_IPHONE::getConfigPath());
+#elif defined(__PLAYSTATION2__)
+ ((OSystem_PS2*)g_system)->makeConfigPath(configFile);
+#elif defined(__PSP__)
+ strcpy(configFile, "ms0:/" DEFAULT_CONFIG_FILE);
+#else
+ strcpy(configFile, DEFAULT_CONFIG_FILE);
+#endif
+
+ return configFile;
+}
+
+Common::SeekableReadStream *OSystem::openConfigFileForReading() {
+ Common::File *confFile = new Common::File();
+ assert(confFile);
+ if (!confFile->open(getDefaultConfigFileName())) {
+ delete confFile;
+ confFile = 0;
+ }
+ return confFile;
+}
+
+Common::WriteStream *OSystem::openConfigFileForWriting() {
+#ifdef __DC__
+ return 0;
+#else
+ Common::DumpFile *confFile = new Common::DumpFile();
+ assert(confFile);
+ if (!confFile->open(getDefaultConfigFileName())) {
+ delete confFile;
+ confFile = 0;
+ }
+ return confFile;
+#endif
+}
diff --git a/common/system.h b/common/system.h
index b895a5cfba..501d0802fd 100644
--- a/common/system.h
+++ b/common/system.h
@@ -44,6 +44,8 @@ namespace Common {
class EventManager;
class SaveFileManager;
class TimerManager;
+ class SeekableReadStream;
+ class WriteStream;
}
class FilesystemFactory;
@@ -900,10 +902,25 @@ public:
/**
* Returns the FilesystemFactory object, depending on the current architecture.
*
- * @return FilesystemFactory* The specific factory for the current architecture.
+ * @return the FSNode factory for the current architecture
*/
virtual FilesystemFactory *getFilesystemFactory() = 0;
+ /**
+ * Open the default config file for reading, by returning a suitable
+ * ReadStream instance. It is the callers responsiblity to delete
+ * the stream after use.
+ */
+ virtual Common::SeekableReadStream *openConfigFileForReading();
+
+ /**
+ * Open the default config file for writing, by returning a suitable
+ * WriteStream instance. It is the callers responsiblity to delete
+ * the stream after use.
+ *
+ * May return 0 to indicate that writing to config file is not possible.
+ */
+ virtual Common::WriteStream *openConfigFileForWriting();
/**
* Return String which is used for backend-specific addition to theme
diff --git a/common/unarj.cpp b/common/unarj.cpp
index f3ac20c285..da88c11fc9 100644
--- a/common/unarj.cpp
+++ b/common/unarj.cpp
@@ -231,7 +231,7 @@ ArjHeader *ArjFile::readHeader() {
}
-bool ArjFile::open(const Common::String &filename, AccessMode mode) {
+bool ArjFile::open(const Common::String &filename) {
if (_isOpen)
error("Attempt to open another instance of archive");
diff --git a/common/unarj.h b/common/unarj.h
index b015999671..c8965968f6 100644
--- a/common/unarj.h
+++ b/common/unarj.h
@@ -110,7 +110,7 @@ public:
void registerArchive(const String &filename);
- bool open(const Common::String &filename, AccessMode mode = kFileReadMode);
+ bool open(const Common::String &filename);
void close();
uint32 read(void *dataPtr, uint32 dataSize);
diff --git a/configure b/configure
index 8c3b45a0dc..037c0bf786 100755
--- a/configure
+++ b/configure
@@ -100,6 +100,7 @@ add_engine saga "SAGA" yes
add_engine sky "Beneath a Steel Sky" yes
add_engine sword1 "Broken Sword 1" yes
add_engine sword2 "Broken Sword 2" yes
+add_engine tinsel "Tinsel" no
add_engine touche "Touche: The Adventures of the Fifth Musketeer" yes
_endian=unknown
@@ -810,6 +811,10 @@ iphone)
_host_os=iphone
_host_cpu=arm
;;
+neuros)
+ _host_os=linux
+ _host_cpu=arm
+ ;;
*)
if test -z "$_host"; then
guessed_host=`$_srcdir/config.guess`
@@ -1122,6 +1127,19 @@ if test -n "$_host"; then
_backend="gp2x"
_build_hq_scalers="no"
;;
+ neuros)
+ echo "Cross-compiling to $_host, forcing endianness, alignment and type sizes"
+ DEFINES="$DEFINES -DUNIX"
+ _endian=little
+ _need_memalign=yes
+ add_line_to_config_h "#define NEUROS"
+ type_1_byte='char'
+ type_2_byte='short'
+ type_4_byte='int'
+ _backend='null'
+ _build_hq_scalers="no"
+ _mt32emu="no"
+ ;;
ppc-amigaos)
echo "Cross-compiling to $_host, forcing endianness, alignment and type sizes"
_endian=big
@@ -1628,7 +1646,7 @@ for engine in $_engines; do
fi
# Save the settings
- defname="ENABLE_`echo $engine | tr [a-z] [A-Z]`"
+ defname="ENABLE_`echo $engine | tr '[a-z]' '[A-Z]'`"
if test "$isbuilt" = "no" ; then
add_line_to_config_mk "# $defname"
else
diff --git a/dists/engine-data/kyra.dat b/dists/engine-data/kyra.dat
index 7171707e8b..5cb5373f45 100644
--- a/dists/engine-data/kyra.dat
+++ b/dists/engine-data/kyra.dat
Binary files differ
diff --git a/dists/msvc7/kyra.vcproj b/dists/msvc7/kyra.vcproj
index d547288d21..2e0e8669e9 100644
--- a/dists/msvc7/kyra.vcproj
+++ b/dists/msvc7/kyra.vcproj
@@ -193,6 +193,12 @@
RelativePath="..\..\engines\kyra\kyra_v2.h">
</File>
<File
+ RelativePath="..\..\engines\kyra\lol.cpp">
+ </File>
+ <File
+ RelativePath="..\..\engines\kyra\lol.h">
+ </File>
+ <File
RelativePath="..\..\engines\kyra\resource.cpp">
</File>
<File
@@ -244,6 +250,12 @@
RelativePath="..\..\engines\kyra\screen_lok.h">
</File>
<File
+ RelativePath="..\..\engines\kyra\screen_lol.cpp">
+ </File>
+ <File
+ RelativePath="..\..\engines\kyra\screen_lol.h">
+ </File>
+ <File
RelativePath="..\..\engines\kyra\screen_mr.cpp">
</File>
<File
diff --git a/dists/msvc7/parallaction.vcproj b/dists/msvc7/parallaction.vcproj
index 9dfb575c6b..2dac8737c2 100644
--- a/dists/msvc7/parallaction.vcproj
+++ b/dists/msvc7/parallaction.vcproj
@@ -148,6 +148,12 @@
RelativePath="..\..\engines\parallaction\graphics.h">
</File>
<File
+ RelativePath="..\..\engines\parallaction\gui.cpp">
+ </File>
+ <File
+ RelativePath="..\..\engines\parallaction\gui.h">
+ </File>
+ <File
RelativePath="..\..\engines\parallaction\gui_br.cpp">
</File>
<File
diff --git a/dists/msvc7/scummvm.sln b/dists/msvc7/scummvm.sln
index e754435a8f..24880538c6 100644
--- a/dists/msvc7/scummvm.sln
+++ b/dists/msvc7/scummvm.sln
@@ -60,6 +60,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}"
+EndProject
Global
GlobalSection(SolutionConfiguration) = preSolution
Debug = Debug
@@ -146,6 +148,10 @@ Global
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/dists/msvc7/scummvm.vcproj b/dists/msvc7/scummvm.vcproj
index 1d195fd938..884cd58a5a 100644
--- a/dists/msvc7/scummvm.vcproj
+++ b/dists/msvc7/scummvm.vcproj
@@ -21,7 +21,7 @@
AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702"
Optimization="0"
AdditionalIncludeDirectories="../..;../../engines"
- PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL"
MinimalRebuild="TRUE"
ExceptionHandling="TRUE"
BasicRuntimeChecks="3"
@@ -38,7 +38,7 @@
Name="VCCustomBuildTool"/>
<Tool
Name="VCLinkerTool"
- AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib"
+ AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib tinsel_Debug/tinsel.lib"
OutputFile="$(OutDir)/scummvm.exe"
LinkIncremental="2"
IgnoreDefaultLibraryNames="libc.lib;libcmt.lib"
@@ -76,7 +76,7 @@
InlineFunctionExpansion="1"
OmitFramePointers="TRUE"
AdditionalIncludeDirectories="../..;../../engines"
- PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL"
StringPooling="TRUE"
MinimalRebuild="FALSE"
ExceptionHandling="TRUE"
diff --git a/dists/msvc71/kyra.vcproj b/dists/msvc71/kyra.vcproj
index f37b1d85fc..efc37c251e 100644
--- a/dists/msvc71/kyra.vcproj
+++ b/dists/msvc71/kyra.vcproj
@@ -207,6 +207,12 @@
RelativePath="..\..\engines\kyra\kyra_v2.h">
</File>
<File
+ RelativePath="..\..\engines\kyra\lol.cpp">
+ </File>
+ <File
+ RelativePath="..\..\engines\kyra\lol.h">
+ </File>
+ <File
RelativePath="..\..\engines\kyra\resource.cpp">
</File>
<File
@@ -258,6 +264,12 @@
RelativePath="..\..\engines\kyra\screen_lok.h">
</File>
<File
+ RelativePath="..\..\engines\kyra\screen_lol.cpp">
+ </File>
+ <File
+ RelativePath="..\..\engines\kyra\screen_lol.h">
+ </File>
+ <File
RelativePath="..\..\engines\kyra\screen_mr.cpp">
</File>
<File
diff --git a/dists/msvc71/parallaction.vcproj b/dists/msvc71/parallaction.vcproj
index 6030b030a8..c72fb68234 100644
--- a/dists/msvc71/parallaction.vcproj
+++ b/dists/msvc71/parallaction.vcproj
@@ -162,6 +162,12 @@
RelativePath="..\..\engines\parallaction\graphics.h">
</File>
<File
+ RelativePath="..\..\engines\parallaction\gui.cpp">
+ </File>
+ <File
+ RelativePath="..\..\engines\parallaction\gui.h">
+ </File>
+ <File
RelativePath="..\..\engines\parallaction\gui_br.cpp">
</File>
<File
diff --git a/dists/msvc71/scummvm.sln b/dists/msvc71/scummvm.sln
index 71bd6a9219..0845fb04ba 100644
--- a/dists/msvc71/scummvm.sln
+++ b/dists/msvc71/scummvm.sln
@@ -98,6 +98,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29
ProjectSection(ProjectDependencies) = postProject
EndProjectSection
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}"
+ ProjectSection(ProjectDependencies) = postProject
+ EndProjectSection
+EndProject
Global
GlobalSection(SolutionConfiguration) = preSolution
Debug = Debug
@@ -184,6 +188,10 @@ Global
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/dists/msvc71/scummvm.vcproj b/dists/msvc71/scummvm.vcproj
index 5817c91d45..6234c89dd5 100644
--- a/dists/msvc71/scummvm.vcproj
+++ b/dists/msvc71/scummvm.vcproj
@@ -21,7 +21,7 @@
AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702"
Optimization="0"
AdditionalIncludeDirectories="../..;../../engines"
- PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL"
MinimalRebuild="TRUE"
ExceptionHandling="TRUE"
BasicRuntimeChecks="3"
@@ -38,7 +38,7 @@
Name="VCCustomBuildTool"/>
<Tool
Name="VCLinkerTool"
- AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib"
+ AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib tinsel_Debug/tinsel.lib"
OutputFile="$(OutDir)/scummvm.exe"
LinkIncremental="2"
IgnoreDefaultLibraryNames="libc.lib;libcmt.lib"
@@ -82,7 +82,7 @@
InlineFunctionExpansion="1"
OmitFramePointers="TRUE"
AdditionalIncludeDirectories="../..;../../engines"
- PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL"
StringPooling="TRUE"
MinimalRebuild="FALSE"
ExceptionHandling="TRUE"
diff --git a/dists/msvc8/kyra.vcproj b/dists/msvc8/kyra.vcproj
index c99abdc5bc..00f7749f4d 100644
--- a/dists/msvc8/kyra.vcproj
+++ b/dists/msvc8/kyra.vcproj
@@ -289,6 +289,14 @@
>
</File>
<File
+ RelativePath="..\..\engines\kyra\lol.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\kyra\lol.h"
+ >
+ </File>
+ <File
RelativePath="..\..\engines\kyra\resource.cpp"
>
</File>
@@ -357,6 +365,14 @@
>
</File>
<File
+ RelativePath="..\..\engines\kyra\screen_lol.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\kyra\screen_lol.h"
+ >
+ </File>
+ <File
RelativePath="..\..\engines\kyra\screen_mr.cpp"
>
</File>
diff --git a/dists/msvc8/parallaction.vcproj b/dists/msvc8/parallaction.vcproj
index c5721bd30f..e268fe1e6b 100644
--- a/dists/msvc8/parallaction.vcproj
+++ b/dists/msvc8/parallaction.vcproj
@@ -229,6 +229,14 @@
>
</File>
<File
+ RelativePath="..\..\engines\parallaction\gui.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\parallaction\gui.h"
+ >
+ </File>
+ <File
RelativePath="..\..\engines\parallaction\gui_br.cpp"
>
</File>
diff --git a/dists/msvc8/scummvm.sln b/dists/msvc8/scummvm.sln
index d3621cbe45..8138c77a2b 100644
--- a/dists/msvc8/scummvm.sln
+++ b/dists/msvc8/scummvm.sln
@@ -61,6 +61,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -147,6 +149,10 @@ Global
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/dists/msvc8/scummvm.vcproj b/dists/msvc8/scummvm.vcproj
index b144dc0b79..8b10a22eb6 100644
--- a/dists/msvc8/scummvm.vcproj
+++ b/dists/msvc8/scummvm.vcproj
@@ -42,7 +42,7 @@
AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996"
Optimization="0"
AdditionalIncludeDirectories="../..;../../engines"
- PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL"
MinimalRebuild="true"
ExceptionHandling="1"
BasicRuntimeChecks="3"
@@ -68,7 +68,7 @@
/>
<Tool
Name="VCLinkerTool"
- AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib"
+ AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib tinsel_Debug/tinsel.lib"
OutputFile="$(OutDir)/scummvm.exe"
LinkIncremental="2"
IgnoreDefaultLibraryNames="libc.lib;libcmt.lib"
@@ -133,7 +133,7 @@
InlineFunctionExpansion="1"
OmitFramePointers="true"
AdditionalIncludeDirectories="../..;../../engines"
- PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL"
StringPooling="true"
MinimalRebuild="false"
ExceptionHandling="1"
diff --git a/dists/msvc8/tinsel.vcproj b/dists/msvc8/tinsel.vcproj
new file mode 100644
index 0000000000..cb6ba0c2e8
--- /dev/null
+++ b/dists/msvc8/tinsel.vcproj
@@ -0,0 +1,486 @@
+<?xml version="1.0" encoding="windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="8.00"
+ Name="tinsel"
+ ProjectGUID="{22AA7760-2C91-11DD-BD0B-0800200C9A66}"
+ RootNamespace="tinsel"
+ Keyword="Win32Proj"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="tinsel_Debug"
+ IntermediateDirectory="tinsel_Debug"
+ ConfigurationType="4"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996"
+ Optimization="0"
+ AdditionalIncludeDirectories="../..;../../engines"
+ PreprocessorDefinitions="WIN32;_DEBUG;USE_ZLIB;USE_MAD;USE_VORBIS"
+ MinimalRebuild="true"
+ ExceptionHandling="1"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ BufferSecurityCheck="true"
+ EnableFunctionLevelLinking="true"
+ ForceConformanceInForLoopScope="true"
+ UsePrecompiledHeader="0"
+ WarningLevel="4"
+ WarnAsError="false"
+ SuppressStartupBanner="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ OutputFile="$(OutDir)/tinsel.lib"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="tinsel_Release"
+ IntermediateDirectory="tinsel_Release"
+ ConfigurationType="4"
+ CharacterSet="2"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996"
+ Optimization="3"
+ InlineFunctionExpansion="2"
+ OmitFramePointers="true"
+ AdditionalIncludeDirectories="../../;../../engines"
+ PreprocessorDefinitions="WIN32;NDEBUG;USE_ZLIB;USE_MAD;USE_VORBIS"
+ StringPooling="true"
+ ExceptionHandling="1"
+ RuntimeLibrary="0"
+ BufferSecurityCheck="false"
+ EnableFunctionLevelLinking="false"
+ ForceConformanceInForLoopScope="true"
+ UsePrecompiledHeader="0"
+ WarningLevel="4"
+ WarnAsError="true"
+ DebugInformationFormat="0"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ OutputFile="$(OutDir)/tinsel.lib"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <File
+ RelativePath="..\..\engines\tinsel\actors.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\actors.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\anim.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\anim.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\background.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\background.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\bg.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\cliprect.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\cliprect.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\config.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\config.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\coroutine.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\cursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\cursor.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\debugger.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\debugger.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\detection.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\dw.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\effect.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\events.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\events.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\faders.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\faders.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\film.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\font.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\font.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\graphics.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\graphics.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\handle.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\handle.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\heapmem.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\heapmem.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\inventory.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\inventory.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\mareels.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\move.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\move.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\multiobj.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\multiobj.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\music.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\music.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\object.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\object.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\palette.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\palette.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\pcode.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\pcode.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\pdisplay.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\pid.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\play.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\polygons.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\polygons.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\rince.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\rince.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\saveload.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\savescn.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\savescn.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scene.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scene.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\sched.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\sched.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scn.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scn.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scroll.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scroll.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\serializer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\sound.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\sound.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\strres.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\strres.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\text.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\text.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\timers.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\timers.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\tinlib.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\tinlib.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\tinsel.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\tinsel.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\token.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\token.h"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/dists/msvc9/kyra.vcproj b/dists/msvc9/kyra.vcproj
index f56a2733b5..ba1b9eb949 100644
--- a/dists/msvc9/kyra.vcproj
+++ b/dists/msvc9/kyra.vcproj
@@ -290,6 +290,14 @@
>
</File>
<File
+ RelativePath="..\..\engines\kyra\lol.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\kyra\lol.h"
+ >
+ </File>
+ <File
RelativePath="..\..\engines\kyra\resource.cpp"
>
</File>
@@ -358,6 +366,14 @@
>
</File>
<File
+ RelativePath="..\..\engines\kyra\screen_lol.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\kyra\screen_lol.h"
+ >
+ </File>
+ <File
RelativePath="..\..\engines\kyra\screen_mr.cpp"
>
</File>
diff --git a/dists/msvc9/parallaction.vcproj b/dists/msvc9/parallaction.vcproj
index d3117b6b0c..f901976c37 100644
--- a/dists/msvc9/parallaction.vcproj
+++ b/dists/msvc9/parallaction.vcproj
@@ -230,6 +230,14 @@
>
</File>
<File
+ RelativePath="..\..\engines\parallaction\gui.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\parallaction\gui.h"
+ >
+ </File>
+ <File
RelativePath="..\..\engines\parallaction\gui_br.cpp"
>
</File>
diff --git a/dists/msvc9/scummvm.sln b/dists/msvc9/scummvm.sln
index a880f0acb6..72bfaaa8c4 100644
--- a/dists/msvc9/scummvm.sln
+++ b/dists/msvc9/scummvm.sln
@@ -61,6 +61,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -147,6 +149,10 @@ Global
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32
{E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32
+ {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/dists/msvc9/scummvm.vcproj b/dists/msvc9/scummvm.vcproj
index 0c10158fdc..31270652df 100644
--- a/dists/msvc9/scummvm.vcproj
+++ b/dists/msvc9/scummvm.vcproj
@@ -43,7 +43,7 @@
AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996"
Optimization="0"
AdditionalIncludeDirectories="../..;../../engines"
- PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE"
+ PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL"
MinimalRebuild="true"
ExceptionHandling="1"
BasicRuntimeChecks="3"
@@ -69,7 +69,7 @@
/>
<Tool
Name="VCLinkerTool"
- AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib"
+ AdditionalDependencies="winmm.lib sdl.lib zlib.lib libmad.lib vorbisfile_static.lib vorbis_static.lib ogg_static.lib libmpeg2.lib sword1_debug/sword1.lib sword2_debug/sword2.lib lure_debug/lure.lib cine_debug/cine.lib cruise_debug/cruise.lib igor_debug/igor.lib kyra_debug/kyra.lib gob_debug/gob.lib queen_debug/queen.lib saga_debug/saga.lib agi_debug/agi.lib scumm_debug/scumm.lib agos_debug/agos.lib drascula_debug/drascula.lib sky_debug/sky.lib parallaction_debug/parallaction.lib m4_debug/m4.lib made_debug/made.lib tinsel_Debug/tinsel.lib"
OutputFile="$(OutDir)/scummvm.exe"
LinkIncremental="2"
IgnoreDefaultLibraryNames="libc.lib;libcmt.lib"
@@ -136,7 +136,7 @@
InlineFunctionExpansion="1"
OmitFramePointers="true"
AdditionalIncludeDirectories="../..;../../engines"
- PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE"
+ PreprocessorDefinitions="WIN32;NDEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL"
StringPooling="true"
MinimalRebuild="false"
ExceptionHandling="1"
diff --git a/dists/msvc9/tinsel.vcproj b/dists/msvc9/tinsel.vcproj
new file mode 100644
index 0000000000..7623290e80
--- /dev/null
+++ b/dists/msvc9/tinsel.vcproj
@@ -0,0 +1,487 @@
+<?xml version="1.0" encoding="windows-1252"?>
+<VisualStudioProject
+ ProjectType="Visual C++"
+ Version="9.00"
+ Name="tinsel"
+ ProjectGUID="{22AA7760-2C91-11DD-BD0B-0800200C9A66}"
+ RootNamespace="tinsel"
+ Keyword="Win32Proj"
+ TargetFrameworkVersion="131072"
+ >
+ <Platforms>
+ <Platform
+ Name="Win32"
+ />
+ </Platforms>
+ <ToolFiles>
+ </ToolFiles>
+ <Configurations>
+ <Configuration
+ Name="Debug|Win32"
+ OutputDirectory="tinsel_Debug"
+ IntermediateDirectory="tinsel_Debug"
+ ConfigurationType="4"
+ CharacterSet="2"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996"
+ Optimization="0"
+ AdditionalIncludeDirectories="../..;../../engines"
+ PreprocessorDefinitions="WIN32;_DEBUG;USE_ZLIB;USE_MAD;USE_VORBIS"
+ MinimalRebuild="true"
+ ExceptionHandling="1"
+ BasicRuntimeChecks="3"
+ RuntimeLibrary="1"
+ BufferSecurityCheck="true"
+ EnableFunctionLevelLinking="true"
+ ForceConformanceInForLoopScope="true"
+ UsePrecompiledHeader="0"
+ WarningLevel="4"
+ WarnAsError="false"
+ SuppressStartupBanner="false"
+ DebugInformationFormat="3"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ OutputFile="$(OutDir)/tinsel.lib"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ <Configuration
+ Name="Release|Win32"
+ OutputDirectory="tinsel_Release"
+ IntermediateDirectory="tinsel_Release"
+ ConfigurationType="4"
+ CharacterSet="2"
+ WholeProgramOptimization="1"
+ >
+ <Tool
+ Name="VCPreBuildEventTool"
+ />
+ <Tool
+ Name="VCCustomBuildTool"
+ />
+ <Tool
+ Name="VCXMLDataGeneratorTool"
+ />
+ <Tool
+ Name="VCWebServiceProxyGeneratorTool"
+ />
+ <Tool
+ Name="VCMIDLTool"
+ />
+ <Tool
+ Name="VCCLCompilerTool"
+ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996"
+ Optimization="3"
+ InlineFunctionExpansion="2"
+ OmitFramePointers="true"
+ AdditionalIncludeDirectories="../../;../../engines"
+ PreprocessorDefinitions="WIN32;NDEBUG;USE_ZLIB;USE_MAD;USE_VORBIS"
+ StringPooling="true"
+ ExceptionHandling="1"
+ RuntimeLibrary="0"
+ BufferSecurityCheck="false"
+ EnableFunctionLevelLinking="false"
+ ForceConformanceInForLoopScope="true"
+ UsePrecompiledHeader="0"
+ WarningLevel="4"
+ WarnAsError="true"
+ DebugInformationFormat="0"
+ />
+ <Tool
+ Name="VCManagedResourceCompilerTool"
+ />
+ <Tool
+ Name="VCResourceCompilerTool"
+ />
+ <Tool
+ Name="VCPreLinkEventTool"
+ />
+ <Tool
+ Name="VCLibrarianTool"
+ OutputFile="$(OutDir)/tinsel.lib"
+ />
+ <Tool
+ Name="VCALinkTool"
+ />
+ <Tool
+ Name="VCXDCMakeTool"
+ />
+ <Tool
+ Name="VCBscMakeTool"
+ />
+ <Tool
+ Name="VCFxCopTool"
+ />
+ <Tool
+ Name="VCPostBuildEventTool"
+ />
+ </Configuration>
+ </Configurations>
+ <References>
+ </References>
+ <Files>
+ <File
+ RelativePath="..\..\engines\tinsel\actors.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\actors.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\anim.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\anim.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\background.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\background.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\bg.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\cliprect.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\cliprect.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\config.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\config.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\coroutine.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\cursor.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\cursor.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\debugger.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\debugger.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\detection.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\dw.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\effect.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\events.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\events.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\faders.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\faders.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\film.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\font.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\font.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\graphics.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\graphics.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\handle.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\handle.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\heapmem.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\heapmem.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\inventory.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\inventory.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\mareels.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\move.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\move.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\multiobj.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\multiobj.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\music.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\music.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\object.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\object.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\palette.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\palette.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\pcode.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\pcode.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\pdisplay.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\pid.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\play.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\polygons.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\polygons.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\rince.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\rince.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\saveload.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\savescn.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\savescn.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scene.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scene.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\sched.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\sched.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scn.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scn.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scroll.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\scroll.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\serializer.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\sound.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\sound.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\strres.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\strres.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\text.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\text.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\timers.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\timers.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\tinlib.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\tinlib.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\tinsel.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\tinsel.h"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\token.cpp"
+ >
+ </File>
+ <File
+ RelativePath="..\..\engines\tinsel\token.h"
+ >
+ </File>
+ </Files>
+ <Globals>
+ </Globals>
+</VisualStudioProject>
diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 6e04ca284b..7d03156bb6 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -577,6 +577,8 @@ int AGOSEngine::init() {
_midiEnabled = true;
+ } else {
+ _driver = NULL;
}
// allocate buffers
@@ -880,7 +882,8 @@ AGOSEngine::~AGOSEngine() {
delete _gameFile;
_midi.close();
- delete _driver;
+ if (_driver)
+ delete _driver;
AudioCD.destroy();
diff --git a/engines/agos/debug.cpp b/engines/agos/debug.cpp
index 76a0b8e76f..2cf285d56a 100644
--- a/engines/agos/debug.cpp
+++ b/engines/agos/debug.cpp
@@ -393,11 +393,11 @@ static const byte bmp_hdr[] = {
};
void dumpBMP(const char *filename, int w, int h, const byte *bytes, const uint32 *palette) {
- Common::File out;
+ Common::DumpFile out;
byte my_hdr[sizeof(bmp_hdr)];
int i;
- out.open(filename, Common::File::kFileWriteMode);
+ out.open(filename);
if (!out.isOpen())
return;
diff --git a/engines/agos/saveload.cpp b/engines/agos/saveload.cpp
index 34e5f2cfeb..4a5c43e706 100644
--- a/engines/agos/saveload.cpp
+++ b/engines/agos/saveload.cpp
@@ -75,7 +75,7 @@ int AGOSEngine::countSaveGames() {
}
char *AGOSEngine::genSaveName(int slot) {
- static char buf[15];
+ static char buf[20];
if (getGameId() == GID_DIMP) {
sprintf(buf, "dimp.sav");
@@ -111,7 +111,7 @@ void AGOSEngine::quickLoadOrSave() {
}
bool success;
- char buf[50];
+ char buf[60];
char *filename = genSaveName(_saveLoadSlot);
if (_saveLoadType == 2) {
@@ -978,7 +978,7 @@ bool AGOSEngine::loadGame(const char *filename, bool restartMode) {
if (restartMode) {
// Load restart state
Common::File *file = new Common::File();
- file->open(filename, Common::File::kFileReadMode);
+ file->open(filename);
f = file;
} else {
f = _saveFileMan->openForLoading(filename);
@@ -1154,7 +1154,7 @@ bool AGOSEngine_Elvira2::loadGame(const char *filename, bool restartMode) {
if (restartMode) {
// Load restart state
Common::File *file = new Common::File();
- file->open(filename, Common::File::kFileReadMode);
+ file->open(filename);
f = file;
} else {
f = _saveFileMan->openForLoading(filename);
diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp
index 10f4aa55b5..eb820804a6 100644
--- a/engines/cine/anim.cpp
+++ b/engines/cine/anim.cpp
@@ -511,14 +511,15 @@ int emptyAnimSpace(int start = 0) {
/*! \brief Load SPL data into animDataTable
* \param resourceName SPL filename
- * \param idx Target index in animDataTable
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded SPL data (-1 if error)
*/
-void loadSpl(const char *resourceName, int16 idx) {
+int loadSpl(const char *resourceName, int16 idx) {
int16 foundFileIdx = findFileInBundle(resourceName);
int entry;
if (foundFileIdx < 0) {
- return;
+ return -1;
}
byte *dataPtr = readBundleFile(foundFileIdx);
@@ -528,12 +529,15 @@ void loadSpl(const char *resourceName, int16 idx) {
animDataTable[entry].load(dataPtr, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize, 1, foundFileIdx, 0, currentPartName);
free(dataPtr);
+ return entry + 1;
}
/*! \brief Load 1bpp mask
* \param resourceName Mask filename
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded mask
*/
-void loadMsk(const char *resourceName) {
+int loadMsk(const char *resourceName, int16 idx) {
int16 foundFileIdx = findFileInBundle(resourceName);
int entry = 0;
byte *dataPtr = readBundleFile(foundFileIdx);
@@ -544,20 +548,23 @@ void loadMsk(const char *resourceName) {
loadAnimHeader(animHeader, readS);
ptr = dataPtr + 0x16;
+ entry = idx < 0 ? emptyAnimSpace() : idx;
+ assert(entry >= 0);
for (int16 i = 0; i < animHeader.numFrames; i++, entry++) {
- entry = emptyAnimSpace(entry);
- assert(entry >= 0);
animDataTable[entry].load(ptr, ANIM_MASK, animHeader.frameWidth, animHeader.frameHeight, foundFileIdx, i, currentPartName);
ptr += animHeader.frameWidth * animHeader.frameHeight;
}
free(dataPtr);
+ return entry;
}
/*! \brief Load animation
* \param resourceName Animation filename
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded animation
*/
-void loadAni(const char *resourceName) {
+int loadAni(const char *resourceName, int16 idx) {
int16 foundFileIdx = findFileInBundle(resourceName);
int entry = 0;
byte *dataPtr = readBundleFile(foundFileIdx);
@@ -571,10 +578,10 @@ void loadAni(const char *resourceName) {
transparentColor = getAnimTransparentColor(resourceName);
- for (int16 i = 0; i < animHeader.numFrames; i++, entry++) {
- entry = emptyAnimSpace(entry);
- assert(entry >= 0);
+ entry = idx < 0 ? emptyAnimSpace() : idx;
+ assert(entry >= 0);
+ for (int16 i = 0; i < animHeader.numFrames; i++, entry++) {
// special case transparency handling
if (!strcmp(resourceName, "L2202.ANI")) {
transparentColor = i < 2 ? 0 : 7;
@@ -587,6 +594,7 @@ void loadAni(const char *resourceName) {
}
free(dataPtr);
+ return entry;
}
/*! \brief Decode 16 color image with palette
@@ -642,13 +650,14 @@ void convert8BBP2(byte *dest, byte *source, int16 width, int16 height) {
/*! \brief Load image set
* \param resourceName Image set filename
- * \param idx Target index in animDataTable
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded image set
*/
-void loadSet(const char *resourceName, int16 idx) {
+int loadSet(const char *resourceName, int16 idx) {
AnimHeader2Struct header2;
uint16 numSpriteInAnim;
int16 foundFileIdx = findFileInBundle(resourceName);
- int16 entry = idx >= 0 ? idx : 0;
+ int16 entry;
byte *ptr, *startOfDataPtr, *dataPtr, *origDataPtr;
int type;
@@ -661,6 +670,9 @@ void loadSet(const char *resourceName, int16 idx) {
startOfDataPtr = ptr + numSpriteInAnim * 0x10;
+ entry = idx < 0 ? emptyAnimSpace() : idx;
+ assert(entry >= 0);
+
for (int16 i = 0; i < numSpriteInAnim; i++, entry++) {
Common::MemoryReadStream readS(ptr, 0x10);
@@ -674,9 +686,6 @@ void loadSet(const char *resourceName, int16 idx) {
ptr += 0x10;
- entry = idx < 0 ? emptyAnimSpace(entry) : idx + i;
- assert(entry >= 0);
-
dataPtr = startOfDataPtr + header2.field_0;
if (header2.type == 1) {
@@ -693,175 +702,126 @@ void loadSet(const char *resourceName, int16 idx) {
}
free(origDataPtr);
+ return entry;
}
/*! \brief Load SEQ data into animDataTable
* \param resourceName SEQ data filename
- * \param idx Target index in animDataTable
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded SEQ data
*/
-void loadSeq(const char *resourceName, int16 idx) {
+int loadSeq(const char *resourceName, int16 idx) {
int16 foundFileIdx = findFileInBundle(resourceName);
byte *dataPtr = readBundleFile(foundFileIdx);
int entry = idx < 0 ? emptyAnimSpace() : idx;
animDataTable[entry].load(dataPtr+0x16, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize-0x16, 1, foundFileIdx, 0, currentPartName);
free(dataPtr);
+ return entry + 1;
}
-void loadResource(const char *resourceName) {
- /* byte isMask = 0; */
- /* byte isSpl = 0; */
-
+/*! \brief Load a resource into animDataTable
+ * \param resourceName Resource's filename
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded resource (-1 if error)
+ * \todo Implement loading of all resource types
+ */
+int loadResource(const char *resourceName, int16 idx) {
+ int result = -1; // Return an error by default
if (strstr(resourceName, ".SPL")) {
- loadSpl(resourceName, -1);
- return;
+ result = loadSpl(resourceName, idx);
} else if (strstr(resourceName, ".MSK")) {
- loadMsk(resourceName);
- return;
+ result = loadMsk(resourceName, idx);
} else if (strstr(resourceName, ".ANI")) {
- loadAni(resourceName);
- return;
+ result = loadAni(resourceName, idx);
} else if (strstr(resourceName, ".ANM")) {
- loadAni(resourceName);
- return;
+ result = loadAni(resourceName, idx);
} else if (strstr(resourceName, ".SET")) {
- loadSet(resourceName, -1);
- return;
+ result = loadSet(resourceName, idx);
} else if (strstr(resourceName, ".SEQ")) {
- loadSeq(resourceName, -1);
- return;
- } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure
- g_cine->quitGame();
- return;
- }
-
- error("loadResource: Cannot determine type for '%s'", resourceName);
-}
-
-/*! \todo There seems to be some additional resource file that is not loaded
- */
-void loadAbs(const char *resourceName, uint16 idx) {
- /* byte isMask = 0; */
- /* byte isSpl = 0; */
-
- if (strstr(resourceName, ".SET")) {
- loadSet(resourceName, idx);
- return;
+ result = loadSeq(resourceName, idx);
} else if (strstr(resourceName, ".H32")) {
- warning("Ignoring file %s (load at %d)", resourceName, idx);
- return;
- } else if (strstr(resourceName, ".SEQ")) {
- loadSeq(resourceName, idx);
- return;
- } else if (strstr(resourceName, ".SPL")) {
- loadSpl(resourceName, idx);
- return;
+ warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
} else if (strstr(resourceName, ".AMI")) {
- warning("Ignoring file %s (load at %d)", resourceName, idx);
- return;
- } else if (strstr(resourceName, ".ANI")) {
- warning("Ignoring file %s (load at %d)", resourceName, idx);
- return;
+ warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
+ } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure
+ g_cine->quitGame();
+ } else {
+ error("loadResource: Cannot determine type for '%s'", resourceName);
}
- error("loadAbs: Cannot determine type for '%s'", resourceName);
+ return result;
}
/*! \brief Load animDataTable from save
* \param fHandle Savefile open for reading
- * \param broken Broken/correct file format switch
+ * \param saveGameFormat The used savegame format
* \todo Add Operation Stealth savefile support
*
* Unlike the old code, this one actually rebuilds the table one frame
* at a time.
*/
-void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {
- int16 currentAnim, foundFileIdx;
- int8 isMask = 0, isSpl = 0;
- byte *dataPtr, *ptr;
- char *animName, part[256];
- byte transparentColor = 0;
- AnimData *currentPtr;
- AnimHeaderStruct animHeader;
-
+void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) {
+ int16 currentAnim, foundFileIdx, frame;
+ char *animName, part[256], name[10];
uint16 width, height, bpp, var1;
- int16 frame;
- char name[10];
- int type;
strcpy(part, currentPartName);
- for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim++) {
- currentPtr = &animDataTable[currentAnim];
+ // We only support these variations of the savegame format at the moment.
+ assert(saveGameFormat == ANIMSIZE_23 || saveGameFormat == ANIMSIZE_30_PTRS_INTACT);
+ const int entrySize = ((saveGameFormat == ANIMSIZE_23) ? 23 : 30);
+ const int fileStartPos = fHandle.pos();
+ currentAnim = 0;
+ while (currentAnim < NUM_MAX_ANIMDATA) {
+ // Seek to the start of the current animation's entry
+ fHandle.seek(fileStartPos + currentAnim * entrySize);
+ // Read in the current animation entry
width = fHandle.readUint16BE();
var1 = fHandle.readUint16BE();
bpp = fHandle.readUint16BE();
height = fHandle.readUint16BE();
- if (!broken) {
- if (!fHandle.readUint32BE()) {
- fHandle.skip(18);
- continue;
- }
- fHandle.readUint32BE();
+ bool validPtr = false;
+ // Handle variables only present in animation entries of size 30
+ if (entrySize == 30) {
+ validPtr = (fHandle.readUint32BE() != 0); // Read data pointer
+ fHandle.readUint32BE(); // Discard mask pointer
}
foundFileIdx = fHandle.readSint16BE();
frame = fHandle.readSint16BE();
fHandle.read(name, 10);
- if (foundFileIdx < 0 || (broken && !fHandle.readByte())) {
+ // Handle variables only present in animation entries of size 23
+ if (entrySize == 23) {
+ validPtr = (fHandle.readByte() != 0);
+ }
+
+ // Don't try to load invalid entries.
+ if (foundFileIdx < 0 || !validPtr) {
+ currentAnim++; // Jump over the invalid entry
continue;
}
+ // Alright, the animation entry looks to be valid so let's start handling it...
if (strcmp(currentPartName, name)) {
closePart();
loadPart(name);
}
animName = partBuffer[foundFileIdx].partName;
- ptr = dataPtr = readBundleFile(foundFileIdx);
-
- isSpl = (strstr(animName, ".SPL")) ? 1 : 0;
- isMask = (strstr(animName, ".MSK")) ? 1 : 0;
-
- if (isSpl) {
- width = (uint16) partBuffer[foundFileIdx].unpackedSize;
- height = 1;
- frame = 0;
- type = ANIM_RAW;
- } else {
- Common::MemoryReadStream readS(ptr, 0x16);
- loadAnimHeader(animHeader, readS);
- ptr += 0x16;
-
- width = animHeader.frameWidth;
- height = animHeader.frameHeight;
-
- if (isMask) {
- type = ANIM_MASK;
- } else {
- type = ANIM_MASKSPRITE;
-
- loadRelatedPalette(animName);
- transparentColor = getAnimTransparentColor(animName);
-
- // special case transparency handling
- if (!strcmp(animName, "L2202.ANI")) {
- transparentColor = (frame < 2) ? 0 : 7;
- } else if (!strcmp(animName, "L4601.ANI")) {
- transparentColor = (frame < 1) ? 0xE : 0;
- }
- }
- }
-
- ptr += frame * width * height;
- currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor);
- free(dataPtr);
+ loadRelatedPalette(animName); // Is this for Future Wars only?
+ const int16 prevAnim = currentAnim;
+ currentAnim = loadResource(animName, currentAnim);
+ assert(currentAnim > prevAnim); // Make sure we advance forward
}
loadPart(part);
+
+ // Make sure we jump over all the animation entries
+ fHandle.seek(fileStartPos + NUM_MAX_ANIMDATA * entrySize);
}
} // End of namespace Cine
diff --git a/engines/cine/anim.h b/engines/cine/anim.h
index d63033e670..317654064b 100644
--- a/engines/cine/anim.h
+++ b/engines/cine/anim.h
@@ -26,8 +26,63 @@
#ifndef CINE_ANIM_H
#define CINE_ANIM_H
+#include "common/endian.h"
+
namespace Cine {
+/**
+ * Cine engine's save game formats.
+ * Enumeration entries (Excluding the one used as an error)
+ * are sorted according to age (i.e. top one is oldest, last one newest etc).
+ *
+ * ANIMSIZE_UNKNOWN:
+ * - Animation data entry size is unknown (Used as an error).
+ *
+ * ANIMSIZE_23:
+ * - Animation data entry size is 23 bytes.
+ * - Used at least by 0.11.0 and 0.11.1 releases of ScummVM.
+ * - Introduced in revision 21772, stopped using in revision 31444.
+ *
+ * ANIMSIZE_30_PTRS_BROKEN:
+ * - Animation data entry size is 30 bytes.
+ * - Data and mask pointers in the saved structs are always NULL.
+ * - Introduced in revision 31453, stopped using in revision 32073.
+ *
+ * ANIMSIZE_30_PTRS_INTACT:
+ * - Animation data entry size is 30 bytes.
+ * - Data and mask pointers in the saved structs are intact,
+ * so you can test them for equality or inequality with NULL
+ * but don't try using them for anything else, it won't work.
+ * - Introduced in revision 31444, got broken in revision 31453,
+ * got fixed in revision 32073 and used after that.
+ *
+ * TEMP_OS_FORMAT:
+ * - Temporary Operation Stealth savegame format.
+ * - NOT backward compatible and NOT to be supported in the future.
+ * This format should ONLY be used during development and abandoned
+ * later in favor of a better format!
+ */
+enum CineSaveGameFormat {
+ ANIMSIZE_UNKNOWN,
+ ANIMSIZE_23,
+ ANIMSIZE_30_PTRS_BROKEN,
+ ANIMSIZE_30_PTRS_INTACT,
+ TEMP_OS_FORMAT
+};
+
+/** Identifier for the temporary Operation Stealth savegame format. */
+static const uint32 TEMP_OS_FORMAT_ID = MKID_BE('TEMP');
+
+/** The current version number of Operation Stealth's savegame format. */
+static const uint32 CURRENT_OS_SAVE_VER = 0;
+
+/** Chunk header used by the temporary Operation Stealth savegame format. */
+struct ChunkHeader {
+ uint32 id; ///< Identifier (e.g. MKID_BE('TEMP'))
+ uint32 version; ///< Version number
+ uint32 size; ///< Size of the chunk after this header in bytes
+};
+
struct AnimHeaderStruct {
byte field_0;
byte field_1;
@@ -99,9 +154,8 @@ extern AnimData animDataTable[NUM_MAX_ANIMDATA];
void freeAnimDataTable(void);
void freeAnimDataRange(byte startIdx, byte numIdx);
-void loadResource(const char *resourceName);
-void loadAbs(const char *resourceName, uint16 idx);
-void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken);
+int loadResource(const char *resourceName, int16 idx = -1);
+void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat);
void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency);
} // End of namespace Cine
diff --git a/engines/cine/bg.cpp b/engines/cine/bg.cpp
index c5b7fb4e3d..2a4e7f0ab1 100644
--- a/engines/cine/bg.cpp
+++ b/engines/cine/bg.cpp
@@ -35,7 +35,7 @@ namespace Cine {
uint16 bgVar0;
byte *additionalBgTable[9];
-byte currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0;
+int16 currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0;
byte loadCtFW(const char *ctName) {
uint16 header[32];
diff --git a/engines/cine/bg.h b/engines/cine/bg.h
index 5fa8209131..9f97bc467d 100644
--- a/engines/cine/bg.h
+++ b/engines/cine/bg.h
@@ -35,6 +35,9 @@ void addBackground(const char *bgName, uint16 bgIdx);
extern uint16 bgVar0;
+extern int16 currentAdditionalBgIdx;
+extern int16 currentAdditionalBgIdx2;
+
} // End of namespace Cine
#endif
diff --git a/engines/cine/bg_list.cpp b/engines/cine/bg_list.cpp
index b10211282f..fddca078e5 100644
--- a/engines/cine/bg_list.cpp
+++ b/engines/cine/bg_list.cpp
@@ -83,7 +83,7 @@ void resetBgIncrustList(void) {
/*! \brief Restore incrust list from savefile
* \param fHandle Savefile open for reading
*/
-void loadBgIncrustFromSave(Common::InSaveFile &fHandle) {
+void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) {
BGIncrust tmp;
int size = fHandle.readSint16BE();
diff --git a/engines/cine/bg_list.h b/engines/cine/bg_list.h
index 1849d6ec3d..9a402baee8 100644
--- a/engines/cine/bg_list.h
+++ b/engines/cine/bg_list.h
@@ -51,7 +51,7 @@ void addSpriteFilledToBGList(int16 idx);
void createBgIncrustListElement(int16 objIdx, int16 param);
void resetBgIncrustList(void);
-void loadBgIncrustFromSave(Common::InSaveFile &fHandle);
+void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle);
} // End of namespace Cine
diff --git a/engines/cine/cine.h b/engines/cine/cine.h
index 06f2dfd982..eaae555812 100644
--- a/engines/cine/cine.h
+++ b/engines/cine/cine.h
@@ -98,7 +98,13 @@ public:
private:
void initialize(void);
+ void resetEngine();
+ bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat);
+ bool loadTempSaveOS(Common::SeekableReadStream &in);
bool makeLoad(char *saveName);
+ void makeSaveFW(Common::OutSaveFile &out);
+ void makeSaveOS(Common::OutSaveFile &out);
+ void makeSave(char *saveFileName);
void mainLoop(int bootScriptIdx);
void readVolCnf();
diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp
index 1f868ccb75..cbddf0fc59 100644
--- a/engines/cine/gfx.cpp
+++ b/engines/cine/gfx.cpp
@@ -601,20 +601,26 @@ void FWRenderer::setScroll(unsigned int shift) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
+/*! \brief Future Wars has no scrolling backgrounds so scroll value is always zero.
+ */
+uint FWRenderer::getScroll() const {
+ return 0;
+}
+
/*! \brief Placeholder for Operation Stealth implementation
*/
void FWRenderer::removeBg(unsigned int idx) {
error("Future Wars renderer doesn't support multiple backgrounds");
}
-void FWRenderer::saveBg(Common::OutSaveFile &fHandle) {
+void FWRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
fHandle.write(_bgName, 13);
}
/*! \brief Restore active and backup palette from save
* \param fHandle Savefile open for reading
*/
-void FWRenderer::restorePalette(Common::InSaveFile &fHandle) {
+void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle) {
int i;
if (!_palette) {
@@ -655,6 +661,49 @@ void FWRenderer::savePalette(Common::OutSaveFile &fHandle) {
}
}
+/*! \brief Write active and backup palette to save
+ * \param fHandle Savefile open for writing
+ */
+void OSRenderer::savePalette(Common::OutSaveFile &fHandle) {
+ int i;
+
+ assert(_activeHiPal);
+
+ // Write the active 256 color palette.
+ for (i = 0; i < _hiPalSize; i++) {
+ fHandle.writeByte(_activeHiPal[i]);
+ }
+
+ // Write the active 256 color palette a second time.
+ // FIXME: The backup 256 color palette should be saved here instead of the active one.
+ for (i = 0; i < _hiPalSize; i++) {
+ fHandle.writeByte(_activeHiPal[i]);
+ }
+}
+
+/*! \brief Restore active and backup palette from save
+ * \param fHandle Savefile open for reading
+ */
+void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle) {
+ int i;
+
+ if (!_activeHiPal) {
+ _activeHiPal = new byte[_hiPalSize];
+ }
+
+ assert(_activeHiPal);
+
+ for (i = 0; i < _hiPalSize; i++) {
+ _activeHiPal[i] = fHandle.readByte();
+ }
+
+ // Jump over the backup 256 color palette.
+ // FIXME: Load the backup 256 color palette and use it properly.
+ fHandle.seek(_hiPalSize, SEEK_CUR);
+
+ _changePal = 1;
+}
+
/*! \brief Rotate active palette
* \param a First color to rotate
* \param b Last color to rotate
@@ -1247,6 +1296,13 @@ void OSRenderer::setScroll(unsigned int shift) {
_bgShift = shift;
}
+/*! \brief Get background scroll
+ * \return Background scroll in pixels
+ */
+uint OSRenderer::getScroll() const {
+ return _bgShift;
+}
+
/*! \brief Unload background from renderer
* \param idx Background to unload
*/
@@ -1270,6 +1326,12 @@ void OSRenderer::removeBg(unsigned int idx) {
memset(_bgTable[idx].name, 0, sizeof (_bgTable[idx].name));
}
+void OSRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
+ for (int i = 0; i < 8; i++) {
+ fHandle.write(_bgTable[i].name, 13);
+ }
+}
+
/*! \brief Fade to black
* \bug Operation Stealth sometimes seems to fade to black using
* transformPalette resulting in double fadeout
diff --git a/engines/cine/gfx.h b/engines/cine/gfx.h
index c63c79ac82..6a3aa1ef89 100644
--- a/engines/cine/gfx.h
+++ b/engines/cine/gfx.h
@@ -108,13 +108,14 @@ public:
virtual void selectBg(unsigned int idx);
virtual void selectScrollBg(unsigned int idx);
virtual void setScroll(unsigned int shift);
+ virtual uint getScroll() const;
virtual void removeBg(unsigned int idx);
- void saveBg(Common::OutSaveFile &fHandle);
+ virtual void saveBgNames(Common::OutSaveFile &fHandle);
virtual void refreshPalette();
virtual void reloadPalette();
- void restorePalette(Common::InSaveFile &fHandle);
- void savePalette(Common::OutSaveFile &fHandle);
+ virtual void restorePalette(Common::SeekableReadStream &fHandle);
+ virtual void savePalette(Common::OutSaveFile &fHandle);
virtual void rotatePalette(int a, int b, int c);
virtual void transformPalette(int first, int last, int r, int g, int b);
@@ -128,6 +129,7 @@ public:
*/
class OSRenderer : public FWRenderer {
private:
+ // FIXME: Background table's size is probably 8 instead of 9. Check to make sure and correct if necessary.
palBg _bgTable[9]; ///< Table of backgrounds loaded into renderer
byte *_activeHiPal; ///< Active 256 color palette
unsigned int _currentBg; ///< Current background
@@ -163,10 +165,14 @@ public:
void selectBg(unsigned int idx);
void selectScrollBg(unsigned int idx);
void setScroll(unsigned int shift);
+ uint getScroll() const;
void removeBg(unsigned int idx);
+ void saveBgNames(Common::OutSaveFile &fHandle);
void refreshPalette();
void reloadPalette();
+ void restorePalette(Common::SeekableReadStream &fHandle);
+ void savePalette(Common::OutSaveFile &fHandle);
void rotatePalette(int a, int b, int c);
void transformPalette(int first, int last, int r, int g, int b);
diff --git a/engines/cine/main_loop.cpp b/engines/cine/main_loop.cpp
index 547379f02d..deac4fd57f 100644
--- a/engines/cine/main_loop.cpp
+++ b/engines/cine/main_loop.cpp
@@ -175,6 +175,20 @@ int getKeyData() {
return k;
}
+/** Removes elements from seqList that have their member variable var4 set to value -1. */
+void purgeSeqList() {
+ Common::List<SeqListElement>::iterator it = seqList.begin();
+ while (it != seqList.end()) {
+ if (it->var4 == -1) {
+ // Erase the element and jump to the next element
+ it = seqList.erase(it);
+ } else {
+ // Let the element be and jump to the next element
+ it++;
+ }
+ }
+}
+
void CineEngine::mainLoop(int bootScriptIdx) {
bool playerAction;
byte di;
@@ -187,7 +201,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
errorVar = 0;
- addScriptToList0(bootScriptIdx);
+ addScriptToGlobalScripts(bootScriptIdx);
menuVar = 0;
@@ -235,12 +249,17 @@ void CineEngine::mainLoop(int bootScriptIdx) {
}
}
- processSeqList();
- executeList1();
- executeList0();
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ processSeqList();
+ }
+ executeObjectScripts();
+ executeGlobalScripts();
- purgeList1();
- purgeList0();
+ purgeObjectScripts();
+ purgeGlobalScripts();
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ purgeSeqList();
+ }
if (playerCommand == -1) {
setMouseCursor(MOUSE_CURSOR_NORMAL);
diff --git a/engines/cine/object.h b/engines/cine/object.h
index 103b2f50ba..7ad65eb75f 100644
--- a/engines/cine/object.h
+++ b/engines/cine/object.h
@@ -50,7 +50,7 @@ struct overlay {
};
#define NUM_MAX_OBJECT 255
-#define NUM_MAX_VAR 256
+#define NUM_MAX_VAR 255
extern objectStruct objectTable[NUM_MAX_OBJECT];
diff --git a/engines/cine/part.cpp b/engines/cine/part.cpp
index b39f1eff7d..88f2dcef52 100644
--- a/engines/cine/part.cpp
+++ b/engines/cine/part.cpp
@@ -289,8 +289,8 @@ void dumpBundle(const char *fileName) {
debug(0, "%s", partBuffer[i].partName);
- Common::File out;
- if (out.open(Common::String("dumps/") + partBuffer[i].partName, Common::File::kFileWriteMode)) {
+ Common::DumpFile out;
+ if (out.open(Common::String("dumps/") + partBuffer[i].partName)) {
out.write(data, partBuffer[i].unpackedSize);
out.close();
}
diff --git a/engines/cine/script.h b/engines/cine/script.h
index fcd21990fa..19576e4c1a 100644
--- a/engines/cine/script.h
+++ b/engines/cine/script.h
@@ -61,7 +61,7 @@ private:
public:
// Explicit to prevent var=0 instead of var[i]=0 typos.
explicit ScriptVars(unsigned int len = 50);
- ScriptVars(Common::InSaveFile &fHandle, unsigned int len = 50);
+ ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len = 50);
ScriptVars(const ScriptVars &src);
~ScriptVars(void);
@@ -71,8 +71,8 @@ public:
void save(Common::OutSaveFile &fHandle) const;
void save(Common::OutSaveFile &fHandle, unsigned int len) const;
- void load(Common::InSaveFile &fHandle);
- void load(Common::InSaveFile &fHandle, unsigned int len);
+ void load(Common::SeekableReadStream &fHandle);
+ void load(Common::SeekableReadStream &fHandle, unsigned int len);
void reset(void);
};
@@ -198,7 +198,7 @@ protected:
int o1_blitAndFade();
int o1_fadeToBlack();
int o1_transformPaletteRange();
- int o1_setDefaultMenuColor2();
+ int o1_setDefaultMenuBgColor();
int o1_palRotate();
int o1_break();
int o1_endScript();
@@ -213,7 +213,7 @@ protected:
int o1_initializeZoneData();
int o1_setZoneDataEntry();
int o1_getZoneDataEntry();
- int o1_setDefaultMenuColor();
+ int o1_setPlayerCommandPosY();
int o1_allowPlayerInput();
int o1_disallowPlayerInput();
int o1_changeDataDisk();
@@ -371,16 +371,16 @@ void dumpScript(char *dumpName);
#define OP_requestCheckPendingDataLoad 0x42
#define OP_endScript 0x50
-void addScriptToList0(uint16 idx);
+void addScriptToGlobalScripts(uint16 idx);
int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx);
void runObjectScript(int16 entryIdx);
-void executeList1(void);
-void executeList0(void);
+void executeObjectScripts(void);
+void executeGlobalScripts(void);
-void purgeList1(void);
-void purgeList0(void);
+void purgeObjectScripts(void);
+void purgeGlobalScripts(void);
} // End of namespace Cine
diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp
index 54a4976000..e761a0c8e4 100644
--- a/engines/cine/script_fw.cpp
+++ b/engines/cine/script_fw.cpp
@@ -38,7 +38,13 @@
namespace Cine {
-ScriptVars globalVars(NUM_MAX_VAR);
+/**
+ * Global variables.
+ * 255 of these are saved, but there's one more that's used for bypassing the copy protection.
+ * In CineEngine::mainLoop(int bootScriptIdx) there's this code: globalVars[VAR_BYPASS_PROTECTION] = 0;
+ * And as VAR_BYPASS_PROTECTION is 255 that's why we're allocating one more than we otherwise would.
+ */
+ScriptVars globalVars(NUM_MAX_VAR + 1);
uint16 compareVars(int16 a, int16 b);
@@ -135,7 +141,7 @@ const Opcode FWScript::_opcodeTable[] = {
{ &FWScript::o1_transformPaletteRange, "bbwww" },
/* 48 */
{ 0, 0 },
- { &FWScript::o1_setDefaultMenuColor2, "b" },
+ { &FWScript::o1_setDefaultMenuBgColor, "b" },
{ &FWScript::o1_palRotate, "bbb" },
{ 0, 0 },
/* 4C */
@@ -174,7 +180,7 @@ const Opcode FWScript::_opcodeTable[] = {
{ &FWScript::o1_setZoneDataEntry, "bw" },
{ &FWScript::o1_getZoneDataEntry, "bb" },
/* 68 */
- { &FWScript::o1_setDefaultMenuColor, "b" },
+ { &FWScript::o1_setPlayerCommandPosY, "b" },
{ &FWScript::o1_allowPlayerInput, "" },
{ &FWScript::o1_disallowPlayerInput, "" },
{ &FWScript::o1_changeDataDisk, "b" },
@@ -230,7 +236,7 @@ ScriptVars::ScriptVars(unsigned int len) : _size(len), _vars(new int16[len]) {
* \param fHandle Savefile open for reading
* \param len Size of array
*/
-ScriptVars::ScriptVars(Common::InSaveFile &fHandle, unsigned int len)
+ScriptVars::ScriptVars(Common::SeekableReadStream &fHandle, unsigned int len)
: _size(len), _vars(new int16[len]) {
assert(_vars);
@@ -306,7 +312,7 @@ void ScriptVars::save(Common::OutSaveFile &fHandle, unsigned int len) const {
/*! \brief Restore array from savefile
* \param fHandle Savefile open for reading
*/
-void ScriptVars::load(Common::InSaveFile &fHandle) {
+void ScriptVars::load(Common::SeekableReadStream &fHandle) {
load(fHandle, _size);
}
@@ -314,7 +320,7 @@ void ScriptVars::load(Common::InSaveFile &fHandle) {
* \param fHandle Savefile open for reading
* \param len Length of data to be read
*/
-void ScriptVars::load(Common::InSaveFile &fHandle, unsigned int len) {
+void ScriptVars::load(Common::SeekableReadStream &fHandle, unsigned int len) {
debug(6, "assert(%d <= %d)", len, _size);
assert(len <= _size);
for (unsigned int i = 0; i < len; i++) {
@@ -1273,7 +1279,7 @@ int FWScript::o1_startGlobalScript() {
assert(param < NUM_MAX_SCRIPT);
debugC(5, kCineDebugScript, "Line: %d: startScript(%d)", _line, param);
- addScriptToList0(param);
+ addScriptToGlobalScripts(param);
return 0;
}
@@ -1399,10 +1405,11 @@ int FWScript::o1_transformPaletteRange() {
return 0;
}
-int FWScript::o1_setDefaultMenuColor2() {
+/** Set the default background color used for message boxes. */
+int FWScript::o1_setDefaultMenuBgColor() {
byte param = getNextByte();
- debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor2(%d)", _line, param);
+ debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuBgColor(%d)", _line, param);
renderer->_messageBg = param;
return 0;
@@ -1568,10 +1575,11 @@ int FWScript::o1_getZoneDataEntry() {
return 0;
}
-int FWScript::o1_setDefaultMenuColor() {
+/** Set the player command string's vertical position on-screen. */
+int FWScript::o1_setPlayerCommandPosY() {
byte param = getNextByte();
- debugC(5, kCineDebugScript, "Line: %d: setDefaultMenuColor(%d)", _line, param);
+ debugC(5, kCineDebugScript, "Line: %d: setPlayerCommandPosY(%d)", _line, param);
renderer->_cmdY = param;
return 0;
@@ -1746,7 +1754,7 @@ int FWScript::o1_unloadMask5() {
//-----------------------------------------------------------------------
-void addScriptToList0(uint16 idx) {
+void addScriptToGlobalScripts(uint16 idx) {
ScriptPtr tmp(scriptInfo->create(*scriptTable[idx], idx));
assert(tmp);
globalScripts.push_back(tmp);
@@ -1820,7 +1828,7 @@ uint16 compareVars(int16 a, int16 b) {
return flag;
}
-void executeList1(void) {
+void executeObjectScripts(void) {
ScriptList::iterator it = objectScripts.begin();
for (; it != objectScripts.end();) {
if ((*it)->_index < 0 || (*it)->execute() < 0) {
@@ -1831,7 +1839,7 @@ void executeList1(void) {
}
}
-void executeList0(void) {
+void executeGlobalScripts(void) {
ScriptList::iterator it = globalScripts.begin();
for (; it != globalScripts.end();) {
if ((*it)->_index < 0 || (*it)->execute() < 0) {
@@ -1842,12 +1850,16 @@ void executeList0(void) {
}
}
-/*! \todo objectScripts.clear()?
+/*! \todo Remove object scripts with script index of -1 (Not script position, but script index!).
+ * This would seem to be valid for both Future Wars and Operation Stealth.
*/
-void purgeList1(void) {
+void purgeObjectScripts(void) {
}
-void purgeList0(void) {
+/*! \todo Remove global scripts with script index of -1 (Not script position, but script index!).
+ * This would seem to be valid for both Future Wars and Operation Stealth.
+ */
+void purgeGlobalScripts(void) {
}
////////////////////////////////////
@@ -2380,7 +2392,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
param = *(localScriptPtr + position);
position++;
- sprintf(lineBuffer, "setDefaultMenuColor2(%d)\n", param);
+ sprintf(lineBuffer, "setDefaultMenuBgColor(%d)\n", param);
break;
}
@@ -2530,7 +2542,7 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
param = *(localScriptPtr + position);
position++;
- sprintf(lineBuffer, "setDefaultMenuBoxColor(%d)\n", param);
+ sprintf(lineBuffer, "setPlayerCommandPosY(%d)\n", param);
break;
}
@@ -2945,10 +2957,10 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
}
void dumpScript(char *dumpName) {
- Common::File fHandle;
+ Common::DumpFile fHandle;
uint16 i;
- fHandle.open(dumpName, Common::File::kFileWriteMode);
+ fHandle.open(dumpName);
for (i = 0; i < decompileBufferPosition; i++) {
fHandle.writeString(Common::String(decompileBuffer[i]));
diff --git a/engines/cine/script_os.cpp b/engines/cine/script_os.cpp
index 78b6c55564..0289a2a0bc 100644
--- a/engines/cine/script_os.cpp
+++ b/engines/cine/script_os.cpp
@@ -131,7 +131,7 @@ const Opcode OSScript::_opcodeTable[] = {
{ &FWScript::o1_transformPaletteRange, "bbwww" },
/* 48 */
{ 0, 0 },
- { &FWScript::o1_setDefaultMenuColor2, "b" },
+ { &FWScript::o1_setDefaultMenuBgColor, "b" },
{ &FWScript::o1_palRotate, "bbb" },
{ 0, 0 },
/* 4C */
@@ -170,7 +170,7 @@ const Opcode OSScript::_opcodeTable[] = {
{ &FWScript::o1_setZoneDataEntry, "bw" },
{ &FWScript::o1_getZoneDataEntry, "bb" },
/* 68 */
- { &FWScript::o1_setDefaultMenuColor, "b" },
+ { &FWScript::o1_setPlayerCommandPosY, "b" },
{ &FWScript::o1_allowPlayerInput, "" },
{ &FWScript::o1_disallowPlayerInput, "" },
{ &FWScript::o1_changeDataDisk, "b" }, /* Same as opcodes 0x95 and 0xA9. */
@@ -636,7 +636,7 @@ int FWScript::o2_loadAbs() {
const char *param2 = getNextString();
debugC(5, kCineDebugScript, "Line: %d: loadABS(%d,%s)", _line, param1, param2);
- loadAbs(param2, param1);
+ loadResource(param2, param1);
return 0;
}
diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp
index b70201ce99..c490756403 100644
--- a/engines/cine/various.cpp
+++ b/engines/cine/various.cpp
@@ -187,6 +187,15 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) {
frame = ABS((int16)(objectTable[it->objIdx].frame));
part = objectTable[it->objIdx].part;
+ // Additional case for negative frame values in Operation Stealth
+ if (g_cine->getGameType() == Cine::GType_OS && objectTable[it->objIdx].frame < 0) {
+ if ((it->type == 1) && (x >= objX) && (objX + frame >= x) && (y >= objY) && (objY + part >= y)) {
+ return it->objIdx;
+ } else {
+ continue;
+ }
+ }
+
if (it->type == 0) {
threshold = animDataTable[frame]._var1;
} else {
@@ -199,16 +208,19 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) {
xdif = x - objX;
ydif = y - objY;
- if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif < 0) || (ydif >= height) || !animDataTable[frame].data()) {
+ if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif <= 0) || (ydif >= height) || !animDataTable[frame].data()) {
continue;
}
if (g_cine->getGameType() == Cine::GType_OS) {
+ // This test isn't present in Operation Stealth's PC version's disassembly
+ // but removing it makes things crash sometimes (e.g. when selecting a verb
+ // and moving the mouse cursor around the floor in the airport's bathroom).
if (xdif >= width) {
continue;
}
- if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != part) {
+ if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != (part & 0x0F)) {
return it->objIdx;
} else if (it->type == 1 && gfxGetBit(xdif, ydif, animDataTable[frame].data(), animDataTable[frame]._width * 4)) {
return it->objIdx;
@@ -227,6 +239,143 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) {
return -1;
}
+bool writeChunkHeader(Common::OutSaveFile &out, const ChunkHeader &header) {
+ out.writeUint32BE(header.id);
+ out.writeUint32BE(header.version);
+ out.writeUint32BE(header.size);
+ return !out.ioFailed();
+}
+
+bool loadChunkHeader(Common::SeekableReadStream &in, ChunkHeader &header) {
+ header.id = in.readUint32BE();
+ header.version = in.readUint32BE();
+ header.size = in.readUint32BE();
+ return !in.ioFailed();
+}
+
+void saveObjectTable(Common::OutSaveFile &out) {
+ out.writeUint16BE(NUM_MAX_OBJECT); // Entry count
+ out.writeUint16BE(0x20); // Entry size
+
+ for (int i = 0; i < NUM_MAX_OBJECT; i++) {
+ out.writeUint16BE(objectTable[i].x);
+ out.writeUint16BE(objectTable[i].y);
+ out.writeUint16BE(objectTable[i].mask);
+ out.writeUint16BE(objectTable[i].frame);
+ out.writeUint16BE(objectTable[i].costume);
+ out.write(objectTable[i].name, 20);
+ out.writeUint16BE(objectTable[i].part);
+ }
+}
+
+void saveZoneData(Common::OutSaveFile &out) {
+ for (int i = 0; i < 16; i++) {
+ out.writeUint16BE(zoneData[i]);
+ }
+}
+
+void saveCommandVariables(Common::OutSaveFile &out) {
+ for (int i = 0; i < 4; i++) {
+ out.writeUint16BE(commandVar3[i]);
+ }
+}
+
+void saveAnimDataTable(Common::OutSaveFile &out) {
+ out.writeUint16BE(NUM_MAX_ANIMDATA); // Entry count
+ out.writeUint16BE(0x1E); // Entry size
+
+ for (int i = 0; i < NUM_MAX_ANIMDATA; i++) {
+ animDataTable[i].save(out);
+ }
+}
+
+void saveScreenParams(Common::OutSaveFile &out) {
+ // Screen parameters, unhandled
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+ out.writeUint16BE(0);
+}
+
+void saveGlobalScripts(Common::OutSaveFile &out) {
+ ScriptList::const_iterator it;
+ out.writeUint16BE(globalScripts.size());
+ for (it = globalScripts.begin(); it != globalScripts.end(); ++it) {
+ (*it)->save(out);
+ }
+}
+
+void saveObjectScripts(Common::OutSaveFile &out) {
+ ScriptList::const_iterator it;
+ out.writeUint16BE(objectScripts.size());
+ for (it = objectScripts.begin(); it != objectScripts.end(); ++it) {
+ (*it)->save(out);
+ }
+}
+
+void saveOverlayList(Common::OutSaveFile &out) {
+ Common::List<overlay>::const_iterator it;
+
+ out.writeUint16BE(overlayList.size());
+
+ for (it = overlayList.begin(); it != overlayList.end(); ++it) {
+ out.writeUint32BE(0); // next
+ out.writeUint32BE(0); // previous?
+ out.writeUint16BE(it->objIdx);
+ out.writeUint16BE(it->type);
+ out.writeSint16BE(it->x);
+ out.writeSint16BE(it->y);
+ out.writeSint16BE(it->width);
+ out.writeSint16BE(it->color);
+ }
+}
+
+void saveBgIncrustList(Common::OutSaveFile &out) {
+ Common::List<BGIncrust>::const_iterator it;
+ out.writeUint16BE(bgIncrustList.size());
+
+ for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) {
+ out.writeUint32BE(0); // next
+ out.writeUint32BE(0); // previous?
+ out.writeUint16BE(it->objIdx);
+ out.writeUint16BE(it->param);
+ out.writeUint16BE(it->x);
+ out.writeUint16BE(it->y);
+ out.writeUint16BE(it->frame);
+ out.writeUint16BE(it->part);
+ }
+}
+
+void saveZoneQuery(Common::OutSaveFile &out) {
+ for (int i = 0; i < 16; i++) {
+ out.writeUint16BE(zoneQuery[i]);
+ }
+}
+
+void saveSeqList(Common::OutSaveFile &out) {
+ Common::List<SeqListElement>::const_iterator it;
+ out.writeUint16BE(seqList.size());
+
+ for (it = seqList.begin(); it != seqList.end(); ++it) {
+ out.writeSint16BE(it->var4);
+ out.writeUint16BE(it->objIdx);
+ out.writeSint16BE(it->var8);
+ out.writeSint16BE(it->frame);
+ out.writeSint16BE(it->varC);
+ out.writeSint16BE(it->varE);
+ out.writeSint16BE(it->var10);
+ out.writeSint16BE(it->var12);
+ out.writeSint16BE(it->var14);
+ out.writeSint16BE(it->var16);
+ out.writeSint16BE(it->var18);
+ out.writeSint16BE(it->var1A);
+ out.writeSint16BE(it->var1C);
+ out.writeSint16BE(it->var1E);
+ }
+}
+
bool CineEngine::loadSaveDirectory(void) {
Common::InSaveFile *fHandle;
char tmp[80];
@@ -244,21 +393,143 @@ bool CineEngine::loadSaveDirectory(void) {
return true;
}
+/*! \brief Savegame format detector
+ * \param fHandle Savefile to check
+ * \return Savegame format on success, ANIMSIZE_UNKNOWN on failure
+ *
+ * This function seeks through the savefile and tries to determine the
+ * savegame format it uses. There's a miniscule chance that the detection
+ * algorithm could get confused and think that the file uses both the older
+ * and the newer format but that is such a remote possibility that I wouldn't
+ * worry about it at all.
+ *
+ * Also detects the temporary Operation Stealth savegame format now.
+ */
+enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle) {
+ const uint32 prevStreamPos = fHandle.pos();
+
+ // First check for the temporary Operation Stealth savegame format.
+ fHandle.seek(0);
+ ChunkHeader hdr;
+ loadChunkHeader(fHandle, hdr);
+ fHandle.seek(prevStreamPos);
+ if (hdr.id == TEMP_OS_FORMAT_ID) {
+ return TEMP_OS_FORMAT;
+ }
+
+ // Ok, so the savegame isn't using the temporary Operation Stealth savegame format.
+ // Let's check for the plain Future Wars savegame format and its different versions then.
+ // The animDataTable begins at savefile position 0x2315.
+ // Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443)
+ // and 30 bytes in the save format after that (Revision 31444 and onwards).
+ // There are 255 entries in the animDataTable in both of the savefile formats.
+ static const uint animDataTableStart = 0x2315;
+ static const uint animEntriesCount = 255;
+ static const uint oldAnimEntrySize = 23;
+ static const uint newAnimEntrySize = 30;
+ static const uint animEntrySizeChoices[] = {oldAnimEntrySize, newAnimEntrySize};
+ Common::Array<uint> animEntrySizeMatches;
+
+ // Try to walk through the savefile using different animDataTable entry sizes
+ // and make a list of all the successful entry sizes.
+ for (uint i = 0; i < ARRAYSIZE(animEntrySizeChoices); i++) {
+ // 206 = 2 * 50 * 2 + 2 * 3 (Size of global and object script entries)
+ // 20 = 4 * 2 + 2 * 6 (Size of overlay and background incrust entries)
+ static const uint sizeofScreenParams = 2 * 6;
+ static const uint globalScriptEntrySize = 206;
+ static const uint objectScriptEntrySize = 206;
+ static const uint overlayEntrySize = 20;
+ static const uint bgIncrustEntrySize = 20;
+ static const uint chainEntrySizes[] = {
+ globalScriptEntrySize,
+ objectScriptEntrySize,
+ overlayEntrySize,
+ bgIncrustEntrySize
+ };
+
+ uint animEntrySize = animEntrySizeChoices[i];
+ // Jump over the animDataTable entries and the screen parameters
+ uint32 newPos = animDataTableStart + animEntrySize * animEntriesCount + sizeofScreenParams;
+ // Check that there's data left after the point we're going to jump to
+ if (newPos >= fHandle.size()) {
+ continue;
+ }
+ fHandle.seek(newPos);
+
+ // Jump over the remaining items in the savegame file
+ // (i.e. the global scripts, object scripts, overlays and background incrusts).
+ bool chainWalkSuccess = true;
+ for (uint chainIndex = 0; chainIndex < ARRAYSIZE(chainEntrySizes); chainIndex++) {
+ // Read entry count and jump over the entries
+ int entryCount = fHandle.readSint16BE();
+ newPos = fHandle.pos() + chainEntrySizes[chainIndex] * entryCount;
+ // Check that we didn't go past the end of file.
+ // Note that getting exactly to the end of file is acceptable.
+ if (newPos > fHandle.size()) {
+ chainWalkSuccess = false;
+ break;
+ }
+ fHandle.seek(newPos);
+ }
+
+ // If we could walk the chain successfully and
+ // got exactly to the end of file then we've got a match.
+ if (chainWalkSuccess && fHandle.pos() == fHandle.size()) {
+ // We found a match, let's save it
+ animEntrySizeMatches.push_back(animEntrySize);
+ }
+ }
+
+ // Check that we got only one entry size match.
+ // If we didn't, then return an error.
+ enum CineSaveGameFormat result = ANIMSIZE_UNKNOWN;
+ if (animEntrySizeMatches.size() == 1) {
+ const uint animEntrySize = animEntrySizeMatches[0];
+ assert(animEntrySize == oldAnimEntrySize || animEntrySize == newAnimEntrySize);
+ if (animEntrySize == oldAnimEntrySize) {
+ result = ANIMSIZE_23;
+ } else { // animEntrySize == newAnimEntrySize
+ // Check data and mask pointers in all of the animDataTable entries
+ // to see whether we've got the version with the broken data and mask pointers or not.
+ // In the broken format all data and mask pointers were always zero.
+ static const uint relativeDataPos = 2 * 4;
+ bool pointersIntact = false;
+ for (uint i = 0; i < animEntriesCount; i++) {
+ fHandle.seek(animDataTableStart + i * animEntrySize + relativeDataPos);
+ uint32 data = fHandle.readUint32BE();
+ uint32 mask = fHandle.readUint32BE();
+ if ((data != 0) || (mask != 0)) {
+ pointersIntact = true;
+ break;
+ }
+ }
+ result = (pointersIntact ? ANIMSIZE_30_PTRS_INTACT : ANIMSIZE_30_PTRS_BROKEN);
+ }
+ } else if (animEntrySizeMatches.size() > 1) {
+ warning("Savegame format detector got confused by input data. Detecting savegame to be using an unknown format");
+ } else { // animEtrySizeMatches.size() == 0
+ debug(3, "Savegame format detector was unable to detect savegame's format");
+ }
+
+ fHandle.seek(prevStreamPos);
+ return result;
+}
+
/*! \brief Restore script list item from savefile
- * \param fHandle Savefile handlem open for reading
+ * \param fHandle Savefile handle open for reading
* \param isGlobal Restore object or global script?
*/
-void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) {
+void loadScriptFromSave(Common::SeekableReadStream &fHandle, bool isGlobal) {
ScriptVars localVars, labels;
uint16 compare, pos;
int16 idx;
- labels.load(*fHandle);
- localVars.load(*fHandle);
+ labels.load(fHandle);
+ localVars.load(fHandle);
- compare = fHandle->readUint16BE();
- pos = fHandle->readUint16BE();
- idx = fHandle->readUint16BE();
+ compare = fHandle.readUint16BE();
+ pos = fHandle.readUint16BE();
+ idx = fHandle.readUint16BE();
// no way to reinitialize these
if (idx < 0) {
@@ -281,7 +552,7 @@ void loadScriptFromSave(Common::InSaveFile *fHandle, bool isGlobal) {
/*! \brief Restore overlay sprites from savefile
* \param fHandle Savefile open for reading
*/
-void loadOverlayFromSave(Common::InSaveFile &fHandle) {
+void loadOverlayFromSave(Common::SeekableReadStream &fHandle) {
overlay tmp;
fHandle.readUint32BE();
@@ -297,128 +568,10 @@ void loadOverlayFromSave(Common::InSaveFile &fHandle) {
overlayList.push_back(tmp);
}
-/*! \brief Savefile format tester
- * \param fHandle Savefile to check
- *
- * This function seeks through savefile and tries to guess if it's the original
- * savegame format or broken format from ScummVM 0.10/0.11
- * The test is incomplete but this should cover 99.99% of cases.
- * If anyone makes a savefile which could confuse this test, assert will
- * report it
- */
-bool brokenSave(Common::InSaveFile &fHandle) {
- // Backward seeking not supported in compressed savefiles
- // if you really want it, finish it yourself
- return false;
-
- // fixed size part: 14093 bytes (12308 bytes in broken save)
- // animDataTable begins at byte 6431
-
- int filesize = fHandle.size();
- int startpos = fHandle.pos();
- int pos, tmp;
- bool correct = false, broken = false;
-
- // check for correct format
- while (filesize > 14093) {
- pos = 14093;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 206;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 206;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 20;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 20;
-
- if (pos == filesize) correct = true;
- break;
- }
- debug(5, "brokenSave: correct format check %s: size=%d, pos=%d",
- correct ? "passed" : "failed", filesize, pos);
-
- // check for broken format
- while (filesize > 12308) {
- pos = 12308;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 206;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 206;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 20;
- if (pos >= filesize) break;
-
- fHandle.seek(pos);
- tmp = fHandle.readUint16BE();
- pos += 2 + tmp * 20;
-
- if (pos == filesize) broken = true;
- break;
- }
- debug(5, "brokenSave: broken format check %s: size=%d, pos=%d",
- broken ? "passed" : "failed", filesize, pos);
-
- // there's a very small chance that both cases will match
- // if anyone runs into it, you'll have to walk through
- // the animDataTable and try to open part file for each entry
- if (!correct && !broken) {
- error("brokenSave: file format check failed");
- } else if (correct && broken) {
- error("brokenSave: both file formats seem to apply");
- }
-
- fHandle.seek(startpos);
- debug(5, "brokenSave: detected %s file format",
- correct ? "correct" : "broken");
-
- return broken;
-}
-
-/*! \todo Implement Operation Stealth loading, this is obviously Future Wars only
- * \todo Add support for loading the zoneQuery table (Operation Stealth specific)
- */
-bool CineEngine::makeLoad(char *saveName) {
- int16 i;
- int16 size;
- bool broken;
- Common::InSaveFile *fHandle;
- char bgName[13];
-
- fHandle = g_saveFileMan->openForLoading(saveName);
-
- if (!fHandle) {
- drawString(otherMessages[0], 0);
- waitPlayerInput();
- // restoreScreen();
- checkDataDisk(-1);
- return false;
- }
-
+void CineEngine::resetEngine() {
g_sound->stopMusic();
freeAnimDataTable();
overlayList.clear();
- // if (g_cine->getGameType() == Cine::GType_OS) {
- // freeUnkList();
- // }
bgIncrustList.clear();
closePart();
@@ -428,7 +581,9 @@ bool CineEngine::makeLoad(char *saveName) {
scriptTable.clear();
messageTable.clear();
- for (i = 0; i < NUM_MAX_OBJECT; i++) {
+ for (int i = 0; i < NUM_MAX_OBJECT; i++) {
+ objectTable[i].x = 0;
+ objectTable[i].y = 0;
objectTable[i].part = 0;
objectTable[i].name[0] = 0;
objectTable[i].frame = 0;
@@ -462,29 +617,294 @@ bool CineEngine::makeLoad(char *saveName) {
checkForPendingDataLoadSwitch = 0;
- broken = brokenSave(*fHandle);
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ seqList.clear();
+ currentAdditionalBgIdx = 0;
+ currentAdditionalBgIdx2 = 0;
+ // TODO: Add resetting of the following variables
+ // adBgVar1 = 0;
+ // adBgVar0 = 0;
+ // gfxFadeOutCompleted = 0;
+ }
+}
+
+bool loadObjectTable(Common::SeekableReadStream &in) {
+ in.readUint16BE(); // Entry count
+ in.readUint16BE(); // Entry size
+
+ for (int i = 0; i < NUM_MAX_OBJECT; i++) {
+ objectTable[i].x = in.readSint16BE();
+ objectTable[i].y = in.readSint16BE();
+ objectTable[i].mask = in.readUint16BE();
+ objectTable[i].frame = in.readSint16BE();
+ objectTable[i].costume = in.readSint16BE();
+ in.read(objectTable[i].name, 20);
+ objectTable[i].part = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
+
+bool loadZoneData(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 16; i++) {
+ zoneData[i] = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
+
+bool loadCommandVariables(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 4; i++) {
+ commandVar3[i] = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
+
+bool loadScreenParams(Common::SeekableReadStream &in) {
+ // TODO: handle screen params (really required ?)
+ in.readUint16BE();
+ in.readUint16BE();
+ in.readUint16BE();
+ in.readUint16BE();
+ in.readUint16BE();
+ in.readUint16BE();
+ return !in.ioFailed();
+}
+
+bool loadGlobalScripts(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadScriptFromSave(in, true);
+ }
+ return !in.ioFailed();
+}
+
+bool loadObjectScripts(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadScriptFromSave(in, false);
+ }
+ return !in.ioFailed();
+}
+
+bool loadOverlayList(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadOverlayFromSave(in);
+ }
+ return !in.ioFailed();
+}
+
+bool loadSeqList(Common::SeekableReadStream &in) {
+ uint size = in.readUint16BE();
+ SeqListElement tmp;
+ for (uint i = 0; i < size; i++) {
+ tmp.var4 = in.readSint16BE();
+ tmp.objIdx = in.readUint16BE();
+ tmp.var8 = in.readSint16BE();
+ tmp.frame = in.readSint16BE();
+ tmp.varC = in.readSint16BE();
+ tmp.varE = in.readSint16BE();
+ tmp.var10 = in.readSint16BE();
+ tmp.var12 = in.readSint16BE();
+ tmp.var14 = in.readSint16BE();
+ tmp.var16 = in.readSint16BE();
+ tmp.var18 = in.readSint16BE();
+ tmp.var1A = in.readSint16BE();
+ tmp.var1C = in.readSint16BE();
+ tmp.var1E = in.readSint16BE();
+ seqList.push_back(tmp);
+ }
+ return !in.ioFailed();
+}
+
+bool loadZoneQuery(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 16; i++) {
+ zoneQuery[i] = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
+
+bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
+ char musicName[13];
+ char bgNames[8][13];
+
+ // First check the temporary Operation Stealth savegame format header.
+ ChunkHeader hdr;
+ loadChunkHeader(in, hdr);
+ if (hdr.id != TEMP_OS_FORMAT_ID) {
+ warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame");
+ return false;
+ } else if (hdr.version > CURRENT_OS_SAVE_VER) {
+ warning("loadTempSaveOS: Detected newer format version. Not loading savegame");
+ return false;
+ } else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) {
+ warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break");
+ } else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER
+ debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match).");
+ }
+
+ // There shouldn't be any data in the header's chunk currently so it's an error if there is.
+ if (hdr.size > 0) {
+ warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame");
+ return false;
+ }
+
+ // Ok, so we've got a correct header for a temporary Operation Stealth savegame.
+ // Let's start loading the plain savegame data then.
+ currentDisk = in.readUint16BE();
+ in.read(currentPartName, 13);
+ in.read(currentPrcName, 13);
+ in.read(currentRelName, 13);
+ in.read(currentMsgName, 13);
+
+ // Load the 8 background names.
+ for (uint i = 0; i < 8; i++) {
+ in.read(bgNames[i], 13);
+ }
+
+ in.read(currentCtName, 13);
+
+ // Moved the loading of current procedure, relation,
+ // backgrounds and Ct here because if they were at the
+ // end of this function then the global scripts loading
+ // made an array out of bounds access. In the original
+ // game's disassembly these aren't here but at the end.
+ // The difference is probably in how we handle loading
+ // the global scripts and some other things (i.e. the
+ // loading routines aren't exactly the same and subtle
+ // semantic differences result in having to do things
+ // in a different order).
+ {
+ // Not sure if this is needed with Operation Stealth...
+ checkDataDisk(currentDisk);
+
+ if (strlen(currentPrcName)) {
+ loadPrc(currentPrcName);
+ }
+
+ if (strlen(currentRelName)) {
+ loadRel(currentRelName);
+ }
+
+ // Load first background (Uses loadBg)
+ if (strlen(bgNames[0])) {
+ loadBg(bgNames[0]);
+ }
+
+ // Add backgrounds 1-7 (Uses addBackground)
+ for (int i = 1; i < 8; i++) {
+ if (strlen(bgNames[i])) {
+ addBackground(bgNames[i], i);
+ }
+ }
+
+ if (strlen(currentCtName)) {
+ loadCtOS(currentCtName);
+ }
+ }
+
+ loadObjectTable(in);
+ renderer->restorePalette(in);
+ globalVars.load(in, NUM_MAX_VAR);
+ loadZoneData(in);
+ loadCommandVariables(in);
+ in.read(commandBuffer, 0x50);
+ loadZoneQuery(in);
+
+ // TODO: Use the loaded string (Current music name (String, 13 bytes)).
+ in.read(musicName, 13);
+
+ // TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)).
+ in.readUint16BE();
+
+ // TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)).
+ in.readUint16BE();
+
+ renderer->_cmdY = in.readUint16BE();
+ in.readUint16BE(); // Some unknown variable that seems to always be zero
+ allowPlayerInput = in.readUint16BE();
+ playerCommand = in.readUint16BE();
+ commandVar1 = in.readUint16BE();
+ isDrawCommandEnabled = in.readUint16BE();
+ var5 = in.readUint16BE();
+ var4 = in.readUint16BE();
+ var3 = in.readUint16BE();
+ var2 = in.readUint16BE();
+ commandVar2 = in.readUint16BE();
+ renderer->_messageBg = in.readUint16BE();
+
+ // TODO: Use the loaded value (adBgVar1 (Uint16BE)).
+ in.readUint16BE();
+
+ currentAdditionalBgIdx = in.readSint16BE();
+ currentAdditionalBgIdx2 = in.readSint16BE();
+
+ // TODO: Check whether the scroll value really gets used correctly after this.
+ // Note that the backgrounds are loaded only later than this value is set.
+ renderer->setScroll(in.readUint16BE());
+
+ // TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?).
+ in.readUint16BE();
+
+ disableSystemMenu = in.readUint16BE();
+
+ // TODO: adBgVar1 = 1 here
+
+ // Load the animDataTable entries
+ in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth).
+ in.readUint16BE(); // Entry size (36 in the PC version of Operation Stealth).
+ loadResourcesFromSave(in, ANIMSIZE_30_PTRS_INTACT);
+
+ loadScreenParams(in);
+ loadGlobalScripts(in);
+ loadObjectScripts(in);
+ loadSeqList(in);
+ loadOverlayList(in);
+ loadBgIncrustFromSave(in);
+
+ // Left this here instead of moving it earlier in this function with
+ // the other current value loadings (e.g. loading of current procedure,
+ // current backgrounds etc). Mostly emulating the way we've handled
+ // Future Wars savegames and hoping that things work out.
+ if (strlen(currentMsgName)) {
+ loadMsg(currentMsgName);
+ }
+
+ // TODO: Add current music loading and playing here
+ // TODO: Palette handling?
+
+ if (in.pos() == in.size()) {
+ debug(3, "loadTempSaveOS: Loaded the whole savefile.");
+ } else {
+ warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over");
+ }
+
+ return !in.ioFailed();
+}
+
+bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) {
+ char bgName[13];
// At savefile position 0x0000:
- currentDisk = fHandle->readUint16BE();
+ currentDisk = in.readUint16BE();
// At 0x0002:
- fHandle->read(currentPartName, 13);
+ in.read(currentPartName, 13);
// At 0x000F:
- fHandle->read(currentDatName, 13);
+ in.read(currentDatName, 13);
// At 0x001C:
- saveVar2 = fHandle->readSint16BE();
+ saveVar2 = in.readSint16BE();
// At 0x001E:
- fHandle->read(currentPrcName, 13);
+ in.read(currentPrcName, 13);
// At 0x002B:
- fHandle->read(currentRelName, 13);
+ in.read(currentRelName, 13);
// At 0x0038:
- fHandle->read(currentMsgName, 13);
+ in.read(currentMsgName, 13);
// At 0x0045:
- fHandle->read(bgName, 13);
+ in.read(bgName, 13);
// At 0x0052:
- fHandle->read(currentCtName, 13);
+ in.read(currentCtName, 13);
checkDataDisk(currentDisk);
@@ -509,118 +929,69 @@ bool CineEngine::makeLoad(char *saveName) {
}
// At 0x005F:
- fHandle->readUint16BE();
- // At 0x0061:
- fHandle->readUint16BE();
+ loadObjectTable(in);
- // At 0x0063:
- for (i = 0; i < 255; i++) {
- // At 0x0063 + i * 32 + 0:
- objectTable[i].x = fHandle->readSint16BE();
- // At 0x0063 + i * 32 + 2:
- objectTable[i].y = fHandle->readSint16BE();
- // At 0x0063 + i * 32 + 4:
- objectTable[i].mask = fHandle->readUint16BE();
- // At 0x0063 + i * 32 + 6:
- objectTable[i].frame = fHandle->readSint16BE();
- // At 0x0063 + i * 32 + 8:
- objectTable[i].costume = fHandle->readSint16BE();
- // At 0x0063 + i * 32 + 10:
- fHandle->read(objectTable[i].name, 20);
- // At 0x0063 + i * 32 + 30:
- objectTable[i].part = fHandle->readUint16BE();
- }
-
- // At 0x2043 (i.e. 0x0063 + 255 * 32):
- renderer->restorePalette(*fHandle);
+ // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32):
+ renderer->restorePalette(in);
// At 0x2083 (i.e. 0x2043 + 16 * 2 * 2):
- globalVars.load(*fHandle, NUM_MAX_VAR - 1);
+ globalVars.load(in, NUM_MAX_VAR);
// At 0x2281 (i.e. 0x2083 + 255 * 2):
- for (i = 0; i < 16; i++) {
- // At 0x2281 + i * 2:
- zoneData[i] = fHandle->readUint16BE();
- }
+ loadZoneData(in);
// At 0x22A1 (i.e. 0x2281 + 16 * 2):
- for (i = 0; i < 4; i++) {
- // At 0x22A1 + i * 2:
- commandVar3[i] = fHandle->readUint16BE();
- }
+ loadCommandVariables(in);
// At 0x22A9 (i.e. 0x22A1 + 4 * 2):
- fHandle->read(commandBuffer, 0x50);
+ in.read(commandBuffer, 0x50);
renderer->setCommand(commandBuffer);
// At 0x22F9 (i.e. 0x22A9 + 0x50):
- renderer->_cmdY = fHandle->readUint16BE();
+ renderer->_cmdY = in.readUint16BE();
// At 0x22FB:
- bgVar0 = fHandle->readUint16BE();
+ bgVar0 = in.readUint16BE();
// At 0x22FD:
- allowPlayerInput = fHandle->readUint16BE();
+ allowPlayerInput = in.readUint16BE();
// At 0x22FF:
- playerCommand = fHandle->readSint16BE();
+ playerCommand = in.readSint16BE();
// At 0x2301:
- commandVar1 = fHandle->readSint16BE();
+ commandVar1 = in.readSint16BE();
// At 0x2303:
- isDrawCommandEnabled = fHandle->readUint16BE();
+ isDrawCommandEnabled = in.readUint16BE();
// At 0x2305:
- var5 = fHandle->readUint16BE();
+ var5 = in.readUint16BE();
// At 0x2307:
- var4 = fHandle->readUint16BE();
+ var4 = in.readUint16BE();
// At 0x2309:
- var3 = fHandle->readUint16BE();
+ var3 = in.readUint16BE();
// At 0x230B:
- var2 = fHandle->readUint16BE();
+ var2 = in.readUint16BE();
// At 0x230D:
- commandVar2 = fHandle->readSint16BE();
+ commandVar2 = in.readSint16BE();
// At 0x230F:
- renderer->_messageBg = fHandle->readUint16BE();
+ renderer->_messageBg = in.readUint16BE();
// At 0x2311:
- fHandle->readUint16BE();
+ in.readUint16BE();
// At 0x2313:
- fHandle->readUint16BE();
+ in.readUint16BE();
// At 0x2315:
- loadResourcesFromSave(*fHandle, broken);
-
- // TODO: handle screen params (really required ?)
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
-
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadScriptFromSave(fHandle, true);
- }
-
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadScriptFromSave(fHandle, false);
- }
-
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadOverlayFromSave(*fHandle);
- }
-
- loadBgIncrustFromSave(*fHandle);
+ loadResourcesFromSave(in, saveGameFormat);
- delete fHandle;
+ loadScreenParams(in);
+ loadGlobalScripts(in);
+ loadObjectScripts(in);
+ loadOverlayList(in);
+ loadBgIncrustFromSave(in);
if (strlen(currentMsgName)) {
loadMsg(currentMsgName);
}
- setMouseCursor(MOUSE_CURSOR_NORMAL);
-
if (strlen(currentDatName)) {
/* i = saveVar2;
saveVar2 = 0;
@@ -630,137 +1001,139 @@ bool CineEngine::makeLoad(char *saveName) {
}*/
}
- return true;
+ return !in.ioFailed();
}
-/*! \todo Add support for saving the zoneQuery table (Operation Stealth specific)
- */
-void makeSave(char *saveFileName) {
- int16 i;
- Common::OutSaveFile *fHandle;
-
- fHandle = g_saveFileMan->openForSaving(saveFileName);
+bool CineEngine::makeLoad(char *saveName) {
+ Common::SharedPtr<Common::InSaveFile> saveFile(g_saveFileMan->openForLoading(saveName));
- if (!fHandle) {
- drawString(otherMessages[1], 0);
+ if (!saveFile) {
+ drawString(otherMessages[0], 0);
waitPlayerInput();
// restoreScreen();
checkDataDisk(-1);
- return;
- }
-
- fHandle->writeUint16BE(currentDisk);
- fHandle->write(currentPartName, 13);
- fHandle->write(currentDatName, 13);
- fHandle->writeUint16BE(saveVar2);
- fHandle->write(currentPrcName, 13);
- fHandle->write(currentRelName, 13);
- fHandle->write(currentMsgName, 13);
- renderer->saveBg(*fHandle);
- fHandle->write(currentCtName, 13);
-
- fHandle->writeUint16BE(0xFF);
- fHandle->writeUint16BE(0x20);
-
- for (i = 0; i < 255; i++) {
- fHandle->writeUint16BE(objectTable[i].x);
- fHandle->writeUint16BE(objectTable[i].y);
- fHandle->writeUint16BE(objectTable[i].mask);
- fHandle->writeUint16BE(objectTable[i].frame);
- fHandle->writeUint16BE(objectTable[i].costume);
- fHandle->write(objectTable[i].name, 20);
- fHandle->writeUint16BE(objectTable[i].part);
- }
-
- renderer->savePalette(*fHandle);
-
- globalVars.save(*fHandle, NUM_MAX_VAR - 1);
-
- for (i = 0; i < 16; i++) {
- fHandle->writeUint16BE(zoneData[i]);
+ return false;
}
- for (i = 0; i < 4; i++) {
- fHandle->writeUint16BE(commandVar3[i]);
+ setMouseCursor(MOUSE_CURSOR_DISK);
+
+ uint32 saveSize = saveFile->size();
+ // TODO: Evaluate the maximum savegame size for the temporary Operation Stealth savegame format.
+ if (saveSize == 0) { // Savefile's compressed using zlib format can't tell their unpacked size, test for it
+ // Can't get information about the savefile's size so let's try
+ // reading as much as we can from the file up to a predefined upper limit.
+ //
+ // Some estimates for maximum savefile sizes (All with 255 animDataTable entries of 30 bytes each):
+ // With 256 global scripts, object scripts, overlays and background incrusts:
+ // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 256 = ~129kB
+ // With 512 global scripts, object scripts, overlays and background incrusts:
+ // 0x2315 + (255 * 30) + (2 * 6) + (206 + 206 + 20 + 20) * 512 = ~242kB
+ //
+ // I think it extremely unlikely that there would be over 512 global scripts, object scripts,
+ // overlays and background incrusts so 256kB seems like quite a safe upper limit.
+ // NOTE: If the savegame format is changed then this value might have to be re-evaluated!
+ // Hopefully devices with more limited memory can also cope with this memory allocation.
+ saveSize = 256 * 1024;
+ }
+ Common::SharedPtr<Common::MemoryReadStream> in(saveFile->readStream(saveSize));
+
+ // Try to detect the used savegame format
+ enum CineSaveGameFormat saveGameFormat = detectSaveGameFormat(*in);
+
+ // Handle problematic savegame formats
+ bool load = true; // Should we try to load the savegame?
+ bool result = false;
+ if (saveGameFormat == ANIMSIZE_30_PTRS_BROKEN) {
+ // One might be able to load the ANIMSIZE_30_PTRS_BROKEN format but
+ // that's not implemented here because it was never used in a stable
+ // release of ScummVM but only during development (From revision 31453,
+ // which introduced the problem, until revision 32073, which fixed it).
+ // Therefore be bail out if we detect this particular savegame format.
+ warning("Detected a known broken savegame format, not loading savegame");
+ load = false; // Don't load the savegame
+ } else if (saveGameFormat == ANIMSIZE_UNKNOWN) {
+ // If we can't detect the savegame format
+ // then let's try the default format and hope for the best.
+ warning("Couldn't detect the used savegame format, trying default savegame format. Things may break");
+ saveGameFormat = ANIMSIZE_30_PTRS_INTACT;
+ }
+
+ if (load) {
+ // Reset the engine's state
+ resetEngine();
+
+ if (saveGameFormat == TEMP_OS_FORMAT) {
+ // Load the temporary Operation Stealth savegame format
+ result = loadTempSaveOS(*in);
+ } else {
+ // Load the plain Future Wars savegame format
+ result = loadPlainSaveFW(*in, saveGameFormat);
+ }
}
- fHandle->write(commandBuffer, 0x50);
-
- fHandle->writeUint16BE(renderer->_cmdY);
-
- fHandle->writeUint16BE(bgVar0);
- fHandle->writeUint16BE(allowPlayerInput);
- fHandle->writeUint16BE(playerCommand);
- fHandle->writeUint16BE(commandVar1);
- fHandle->writeUint16BE(isDrawCommandEnabled);
- fHandle->writeUint16BE(var5);
- fHandle->writeUint16BE(var4);
- fHandle->writeUint16BE(var3);
- fHandle->writeUint16BE(var2);
- fHandle->writeUint16BE(commandVar2);
-
- fHandle->writeUint16BE(renderer->_messageBg);
-
- fHandle->writeUint16BE(0xFF);
- fHandle->writeUint16BE(0x1E);
+ setMouseCursor(MOUSE_CURSOR_NORMAL);
- for (i = 0; i < NUM_MAX_ANIMDATA; i++) {
- animDataTable[i].save(*fHandle);
- }
+ return result;
+}
- fHandle->writeUint16BE(0); // Screen params, unhandled
- fHandle->writeUint16BE(0);
- fHandle->writeUint16BE(0);
- fHandle->writeUint16BE(0);
- fHandle->writeUint16BE(0);
- fHandle->writeUint16BE(0);
+void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
+ out.writeUint16BE(currentDisk);
+ out.write(currentPartName, 13);
+ out.write(currentDatName, 13);
+ out.writeUint16BE(saveVar2);
+ out.write(currentPrcName, 13);
+ out.write(currentRelName, 13);
+ out.write(currentMsgName, 13);
+ renderer->saveBgNames(out);
+ out.write(currentCtName, 13);
+
+ saveObjectTable(out);
+ renderer->savePalette(out);
+ globalVars.save(out, NUM_MAX_VAR);
+ saveZoneData(out);
+ saveCommandVariables(out);
+ out.write(commandBuffer, 0x50);
+
+ out.writeUint16BE(renderer->_cmdY);
+ out.writeUint16BE(bgVar0);
+ out.writeUint16BE(allowPlayerInput);
+ out.writeUint16BE(playerCommand);
+ out.writeUint16BE(commandVar1);
+ out.writeUint16BE(isDrawCommandEnabled);
+ out.writeUint16BE(var5);
+ out.writeUint16BE(var4);
+ out.writeUint16BE(var3);
+ out.writeUint16BE(var2);
+ out.writeUint16BE(commandVar2);
+ out.writeUint16BE(renderer->_messageBg);
+
+ saveAnimDataTable(out);
+ saveScreenParams(out);
+
+ saveGlobalScripts(out);
+ saveObjectScripts(out);
+ saveOverlayList(out);
+ saveBgIncrustList(out);
+}
- {
- ScriptList::iterator it;
- fHandle->writeUint16BE(globalScripts.size());
- for (it = globalScripts.begin(); it != globalScripts.end(); ++it) {
- (*it)->save(*fHandle);
- }
+void CineEngine::makeSave(char *saveFileName) {
+ Common::SharedPtr<Common::OutSaveFile> fHandle(g_saveFileMan->openForSaving(saveFileName));
- fHandle->writeUint16BE(objectScripts.size());
- for (it = objectScripts.begin(); it != objectScripts.end(); ++it) {
- (*it)->save(*fHandle);
- }
- }
+ setMouseCursor(MOUSE_CURSOR_DISK);
- {
- Common::List<overlay>::iterator it;
-
- fHandle->writeUint16BE(overlayList.size());
-
- for (it = overlayList.begin(); it != overlayList.end(); ++it) {
- fHandle->writeUint32BE(0);
- fHandle->writeUint32BE(0);
- fHandle->writeUint16BE(it->objIdx);
- fHandle->writeUint16BE(it->type);
- fHandle->writeSint16BE(it->x);
- fHandle->writeSint16BE(it->y);
- fHandle->writeSint16BE(it->width);
- fHandle->writeSint16BE(it->color);
+ if (!fHandle) {
+ drawString(otherMessages[1], 0);
+ waitPlayerInput();
+ // restoreScreen();
+ checkDataDisk(-1);
+ } else {
+ if (g_cine->getGameType() == GType_FW) {
+ makeSaveFW(*fHandle);
+ } else {
+ makeSaveOS(*fHandle);
}
}
- Common::List<BGIncrust>::iterator it;
- fHandle->writeUint16BE(bgIncrustList.size());
-
- for (it = bgIncrustList.begin(); it != bgIncrustList.end(); ++it) {
- fHandle->writeUint32BE(0); // next
- fHandle->writeUint32BE(0); // unkPtr
- fHandle->writeUint16BE(it->objIdx);
- fHandle->writeUint16BE(it->param);
- fHandle->writeUint16BE(it->x);
- fHandle->writeUint16BE(it->y);
- fHandle->writeUint16BE(it->frame);
- fHandle->writeUint16BE(it->part);
- }
-
- delete fHandle;
-
setMouseCursor(MOUSE_CURSOR_NORMAL);
}
@@ -901,6 +1274,89 @@ void CineEngine::makeSystemMenu(void) {
}
}
+/**
+ * Save an Operation Stealth type savegame. WIP!
+ *
+ * NOTE: This is going to be very much a work in progress so the Operation Stealth's
+ * savegame formats that are going to be tried are extremely probably not going
+ * to be supported at all after Operation Stealth becomes officially supported.
+ * This means that the savegame format will hopefully change to something nicer
+ * when official support for Operation Stealth begins.
+ */
+void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
+ int i;
+
+ // Make a temporary Operation Stealth savegame format chunk header and save it.
+ ChunkHeader header;
+ header.id = TEMP_OS_FORMAT_ID;
+ header.version = CURRENT_OS_SAVE_VER;
+ header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it.
+ writeChunkHeader(out, header);
+
+ // Start outputting the plain savegame data right after the chunk header.
+ out.writeUint16BE(currentDisk);
+ out.write(currentPartName, 13);
+ out.write(currentPrcName, 13);
+ out.write(currentRelName, 13);
+ out.write(currentMsgName, 13);
+ renderer->saveBgNames(out);
+ out.write(currentCtName, 13);
+
+ saveObjectTable(out);
+ renderer->savePalette(out);
+ globalVars.save(out, NUM_MAX_VAR);
+ saveZoneData(out);
+ saveCommandVariables(out);
+ out.write(commandBuffer, 0x50);
+ saveZoneQuery(out);
+
+ // FIXME: Save a proper name here, saving an empty string currently.
+ // 0x2925: Current music name (String, 13 bytes).
+ for (i = 0; i < 13; i++) {
+ out.writeByte(0);
+ }
+ // FIXME: Save proper value for this variable, currently writing zero
+ // 0x2932: Is music loaded? (Uint16BE, Boolean).
+ out.writeUint16BE(0);
+ // FIXME: Save proper value for this variable, currently writing zero
+ // 0x2934: Is music playing? (Uint16BE, Boolean).
+ out.writeUint16BE(0);
+
+ out.writeUint16BE(renderer->_cmdY);
+ out.writeUint16BE(0); // Some unknown variable that seems to always be zero
+ out.writeUint16BE(allowPlayerInput);
+ out.writeUint16BE(playerCommand);
+ out.writeUint16BE(commandVar1);
+ out.writeUint16BE(isDrawCommandEnabled);
+ out.writeUint16BE(var5);
+ out.writeUint16BE(var4);
+ out.writeUint16BE(var3);
+ out.writeUint16BE(var2);
+ out.writeUint16BE(commandVar2);
+ out.writeUint16BE(renderer->_messageBg);
+
+ // FIXME: Save proper value for this variable, currently writing zero.
+ // An unknown variable at 0x295E: adBgVar1 (Uint16BE).
+ out.writeUint16BE(0);
+ out.writeSint16BE(currentAdditionalBgIdx);
+ out.writeSint16BE(currentAdditionalBgIdx2);
+ // FIXME: Save proper value for this variable, currently writing zero.
+ // 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift.
+ out.writeUint16BE(0);
+ // FIXME: Save proper value for this variable, currently writing zero.
+ // An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0?
+ out.writeUint16BE(0);
+ out.writeUint16BE(disableSystemMenu);
+
+ saveAnimDataTable(out);
+ saveScreenParams(out);
+ saveGlobalScripts(out);
+ saveObjectScripts(out);
+ saveSeqList(out);
+ saveOverlayList(out);
+ saveBgIncrustList(out);
+}
+
void drawMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 offset, int16 color, byte* page) {
gfxDrawLine(x + offset, y + offset, x + width - offset, y + offset, color, page); // top
gfxDrawLine(x + offset, currentY + 4 - offset, x + width - offset, currentY + 4 - offset, color, page); // bottom
@@ -1567,7 +2023,7 @@ void checkForPendingDataLoad(void) {
// fixes a crash when failing copy protection in Amiga or Atari ST
// versions of Future Wars.
if (loadPrcOk) {
- addScriptToList0(1);
+ addScriptToGlobalScripts(1);
} else if (scumm_stricmp(currentPrcName, COPY_PROT_FAIL_PRC_NAME)) {
// We only show an error here for other files than the file that
// is loaded if copy protection fails (i.e. L201.ANI).
@@ -1740,6 +2196,9 @@ uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &ele
const int8 *ptr2;
int16 di;
+ debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d",
+ param1, objIdx, ptr, element.var8, element.var14, param3);
+
// In the original an error string is set and 0 is returned if the following doesn't hold
assert(ptr);
@@ -1845,6 +2304,19 @@ void processSeqListElement(SeqListElement &element) {
int16 var_10;
int16 var_4;
int16 var_2;
+
+ // Initial interpretations for variables addressed through ptr1 (8-bit addressing):
+ // These may be inaccurate!
+ // 0: ?
+ // 1: xRadius
+ // 2: yRadius
+ // 3: ?
+ // 4: xAdd
+ // 5: yAdd
+ // 6: ?
+ // 7: ?
+ // After this come (At least at positions 0, 1 and 3 in 16-bit addressing)
+ // 16-bit big-endian values used for addressing through ptr1.
if (element.var12 < element.var10) {
element.var12++;
diff --git a/engines/cine/various.h b/engines/cine/various.h
index 3befde7eb8..5f24d502be 100644
--- a/engines/cine/various.h
+++ b/engines/cine/various.h
@@ -44,7 +44,7 @@ extern bool inMenu;
struct SeqListElement {
int16 var4;
- uint16 objIdx;
+ uint16 objIdx; ///< Is this really unsigned?
int16 var8;
int16 frame;
int16 varC;
diff --git a/engines/cruise/volume.cpp b/engines/cruise/volume.cpp
index e4a3dde78f..b2ff2631c0 100644
--- a/engines/cruise/volume.cpp
+++ b/engines/cruise/volume.cpp
@@ -456,8 +456,8 @@ int16 readVolCnf(void) {
sprintf(nameBuffer, "%s", buffer[j].name);
if (buffer[j].size == buffer[j].extSize) {
- Common::File fout;
- fout.open(nameBuffer, Common::File::kFileWriteMode);
+ Common::DumpFile fout;
+ fout.open(nameBuffer);
if(fout.isOpen())
fout.write(bufferLocal, buffer[j].size);
} else {
diff --git a/engines/drascula/drascula.cpp b/engines/drascula/drascula.cpp
index 1cbe2ae0e1..7c843892b6 100644
--- a/engines/drascula/drascula.cpp
+++ b/engines/drascula/drascula.cpp
@@ -457,13 +457,13 @@ bool DrasculaEngine::runCurrentChapter() {
playMusic(roomMusic);
}
+ delay(25);
updateEvents();
if (menuScreen == 0 && takeObject == 1)
checkObjects();
if (rightMouseButton == 1 && menuScreen == 1) {
- delay(100);
if (currentChapter == 2)
loadPic(menuBackground, backSurface);
else
@@ -473,7 +473,6 @@ bool DrasculaEngine::runCurrentChapter() {
updateEvents();
}
if (rightMouseButton == 1 && menuScreen == 0) {
- delay(100);
characterMoved = 0;
if (trackProtagonist == 2)
trackProtagonist = 1;
@@ -491,10 +490,8 @@ bool DrasculaEngine::runCurrentChapter() {
}
if (leftMouseButton == 1 && menuBar == 1) {
- delay(100);
selectVerbFromBar();
} else if (leftMouseButton == 1 && takeObject == 0) {
- delay(100);
if (verify1())
return true;
} else if (leftMouseButton == 1 && takeObject == 1) {
@@ -987,9 +984,14 @@ char ***DrasculaEngine::loadTexts(Common::File &in) {
}
void DrasculaEngine::freeTexts(char ***ptr) {
+ if (!ptr)
+ return;
+
for (int lang = 0; lang < _numLangs; lang++) {
- free(ptr[lang][0] - DATAALIGNMENT);
- free(ptr[lang]);
+ if (ptr[lang]) {
+ free(ptr[lang][0] - DATAALIGNMENT);
+ free(ptr[lang]);
+ }
}
free(ptr);
}
diff --git a/engines/drascula/interface.cpp b/engines/drascula/interface.cpp
index 6e86788007..ef1d1cc7a3 100644
--- a/engines/drascula/interface.cpp
+++ b/engines/drascula/interface.cpp
@@ -114,7 +114,8 @@ void DrasculaEngine::clearMenu() {
}
void DrasculaEngine::enterName() {
- Common::KeyCode key;
+ Common::KeyCode key, prevkey = Common::KEYCODE_INVALID;
+ int counter = 0;
int v = 0, h = 0;
char select2[23];
strcpy(select2, " ");
@@ -123,17 +124,25 @@ void DrasculaEngine::enterName() {
copyBackground(115, 14, 115, 14, 176, 9, bgSurface, screenSurface);
print_abc(select2, 117, 15);
updateScreen();
+ _system->delayMillis(100);
+
key = getScan();
- delay(70);
- if (key != 0) {
+
+ // Artifically decrease repeat rate.
+ // Part of bug fix#2017432 DRASCULA: Typing is slow when you save a game
+ // Alternative is to roll our own event loop
+ if (key == prevkey)
+ if (++counter == 3) {
+ counter = 0;
+ prevkey = Common::KEYCODE_INVALID;
+ }
+
+ if (key != 0 && key != prevkey) {
+ prevkey = key;
if (key >= 0 && key <= 0xFF && isalpha(key))
select2[v] = tolower(key);
- else if ((key == Common::KEYCODE_LCTRL) || (key == Common::KEYCODE_RCTRL))
- select2[v] = '\164';
- else if (key >= Common::KEYCODE_0 && key <= Common::KEYCODE_9)
+ else if ((key >= Common::KEYCODE_0 && key <= Common::KEYCODE_9) || key == Common::KEYCODE_SPACE)
select2[v] = key;
- else if (key == Common::KEYCODE_SPACE)
- select2[v] = '\167';
else if (key == Common::KEYCODE_ESCAPE)
break;
else if (key == Common::KEYCODE_RETURN) {
diff --git a/engines/drascula/sound.cpp b/engines/drascula/sound.cpp
index 840d6c7cb5..2eb40e2e30 100644
--- a/engines/drascula/sound.cpp
+++ b/engines/drascula/sound.cpp
@@ -37,23 +37,25 @@ void DrasculaEngine::updateVolume(Audio::Mixer::SoundType soundType, int prevVol
}
void DrasculaEngine::volumeControls() {
- int masterVolume, voiceVolume, musicVolume;
-
copyRect(1, 56, 73, 63, 177, 97, tableSurface, screenSurface);
updateScreen(73, 63, 73, 63, 177, 97, screenSurface);
- masterVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) / 16) * 4);
- voiceVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 16) * 4);
- musicVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 16) * 4);
-
for (;;) {
+ int masterVolume = CLIP((_mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) / 16), 0, 15);
+ int voiceVolume = CLIP((_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 16), 0, 15);
+ int musicVolume = CLIP((_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 16), 0, 15);
+
+ int masterVolumeY = 72 + 61 - masterVolume * 4;
+ int voiceVolumeY = 72 + 61 - voiceVolume * 4;
+ int musicVolumeY = 72 + 61 - musicVolume * 4;
+
updateRoom();
copyRect(1, 56, 73, 63, 177, 97, tableSurface, screenSurface);
- copyBackground(183, 56, 82, masterVolume, 39, 2 + ((_mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) / 16) * 4), tableSurface, screenSurface);
- copyBackground(183, 56, 138, voiceVolume, 39, 2 + ((_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 16) * 4), tableSurface, screenSurface);
- copyBackground(183, 56, 194, musicVolume, 39, 2 + ((_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 16) * 4), tableSurface, screenSurface);
+ copyBackground(183, 56, 82, masterVolumeY, 39, 2 + masterVolume * 4, tableSurface, screenSurface);
+ copyBackground(183, 56, 138, voiceVolumeY, 39, 2 + voiceVolume * 4, tableSurface, screenSurface);
+ copyBackground(183, 56, 194, musicVolumeY, 39, 2 + musicVolume * 4, tableSurface, screenSurface);
setCursorTable();
@@ -68,18 +70,15 @@ void DrasculaEngine::volumeControls() {
if (leftMouseButton == 1) {
delay(100);
if (mouseX > 80 && mouseX < 121) {
- updateVolume(Audio::Mixer::kPlainSoundType, mouseY);
- masterVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kPlainSoundType) / 16) * 4);
+ updateVolume(Audio::Mixer::kPlainSoundType, masterVolumeY);
}
if (mouseX > 136 && mouseX < 178) {
- updateVolume(Audio::Mixer::kSFXSoundType, mouseY);
- voiceVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kSFXSoundType) / 16) * 4);
+ updateVolume(Audio::Mixer::kSFXSoundType, voiceVolumeY);
}
if (mouseX > 192 && mouseX < 233) {
- updateVolume(Audio::Mixer::kMusicSoundType, mouseY);
- musicVolume = 72 + 61 - ((_mixer->getVolumeForSoundType(Audio::Mixer::kMusicSoundType) / 16) * 4);
+ updateVolume(Audio::Mixer::kMusicSoundType, musicVolumeY);
}
}
diff --git a/engines/engines.mk b/engines/engines.mk
index cfb8e69f3e..4dba913173 100644
--- a/engines/engines.mk
+++ b/engines/engines.mk
@@ -97,6 +97,11 @@ DEFINES += -DENABLE_SWORD2=$(ENABLE_SWORD2)
MODULES += engines/sword2
endif
+ifdef ENABLE_TINSEL
+DEFINES += -DENABLE_TINSEL=$(ENABLE_TINSEL)
+MODULES += engines/tinsel
+endif
+
ifdef ENABLE_TOUCHE
DEFINES += -DENABLE_TOUCHE=$(ENABLE_TOUCHE)
MODULES += engines/touche
diff --git a/engines/gob/dataio.cpp b/engines/gob/dataio.cpp
index 8ae11b8755..bcf566d134 100644
--- a/engines/gob/dataio.cpp
+++ b/engines/gob/dataio.cpp
@@ -202,7 +202,7 @@ const Common::File *DataIO::file_getHandle(int16 handle) const {
return &_filesHandles[handle];
}
-int16 DataIO::file_open(const char *path, Common::File::AccessMode mode) {
+int16 DataIO::file_open(const char *path) {
int16 i;
for (i = 0; i < MAX_FILES; i++) {
@@ -212,7 +212,7 @@ int16 DataIO::file_open(const char *path, Common::File::AccessMode mode) {
if (i == MAX_FILES)
return -1;
- file_getHandle(i)->open(path, mode);
+ file_getHandle(i)->open(path);
if (file_getHandle(i)->isOpen())
return i;
@@ -467,17 +467,14 @@ void DataIO::closeData(int16 handle) {
file_getHandle(handle)->close();
}
-int16 DataIO::openData(const char *path, Common::File::AccessMode mode) {
+int16 DataIO::openData(const char *path) {
int16 handle;
- if (mode != Common::File::kFileReadMode)
- return file_open(path, mode);
-
handle = getChunk(path);
if (handle >= 0)
return handle;
- return file_open(path, mode);
+ return file_open(path);
}
DataStream *DataIO::openAsStream(int16 handle, bool dispose) {
diff --git a/engines/gob/dataio.h b/engines/gob/dataio.h
index a990dbeda5..4b4c79d1eb 100644
--- a/engines/gob/dataio.h
+++ b/engines/gob/dataio.h
@@ -79,8 +79,7 @@ public:
void closeDataFile(bool itk = 0);
byte *getUnpackedData(const char *name);
void closeData(int16 handle);
- int16 openData(const char *path,
- Common::File::AccessMode mode = Common::File::kFileReadMode);
+ int16 openData(const char *path);
DataStream *openAsStream(int16 handle, bool dispose = false);
int32 getDataSize(const char *name);
@@ -104,8 +103,7 @@ protected:
class GobEngine *_vm;
- int16 file_open(const char *path,
- Common::File::AccessMode mode = Common::File::kFileReadMode);
+ int16 file_open(const char *path);
Common::File *file_getHandle(int16 handle);
const Common::File *file_getHandle(int16 handle) const;
diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp
index a8cbfa0550..d64ce3c9cc 100644
--- a/engines/gob/gob.cpp
+++ b/engines/gob/gob.cpp
@@ -141,6 +141,15 @@ void GobEngine::validateVideoMode(int16 videoMode) {
error("Video mode 0x%X is not supported!", videoMode);
}
+Endianness GobEngine::getEndianness() const {
+ if ((_vm->getPlatform() == Common::kPlatformAmiga) ||
+ (_vm->getPlatform() == Common::kPlatformMacintosh) ||
+ (_vm->getPlatform() == Common::kPlatformAtariST))
+ return kEndiannessBE;
+
+ return kEndiannessLE;
+}
+
Common::Platform GobEngine::getPlatform() const {
return _platform;
}
diff --git a/engines/gob/gob.h b/engines/gob/gob.h
index 842a1dc59f..485389f990 100644
--- a/engines/gob/gob.h
+++ b/engines/gob/gob.h
@@ -79,6 +79,11 @@ class SaveLoad;
#define VAR(var) READ_VAR_UINT32(var)
+enum Endianness {
+ kEndiannessLE,
+ kEndiannessBE
+};
+
enum GameType {
kGameTypeNone = 0,
kGameTypeGob1,
@@ -227,6 +232,7 @@ public:
void validateLanguage();
void validateVideoMode(int16 videoMode);
+ Endianness getEndianness() const;
Common::Platform getPlatform() const;
GameType getGameType() const;
bool isCD() const;
diff --git a/engines/gob/inter.cpp b/engines/gob/inter.cpp
index c0a1bfc21a..4973bd756d 100644
--- a/engines/gob/inter.cpp
+++ b/engines/gob/inter.cpp
@@ -296,9 +296,7 @@ void Inter::callSub(int16 retFlag) {
}
void Inter::allocateVars(uint32 count) {
- if ((_vm->getPlatform() == Common::kPlatformAmiga) ||
- (_vm->getPlatform() == Common::kPlatformMacintosh) ||
- (_vm->getPlatform() == Common::kPlatformAtariST))
+ if (_vm->getEndianness() == kEndiannessBE)
_variables = new VariablesBE(count * 4);
else
_variables = new VariablesLE(count * 4);
diff --git a/engines/gob/saveload.cpp b/engines/gob/saveload.cpp
index 2788716858..fa9f8ea7a9 100644
--- a/engines/gob/saveload.cpp
+++ b/engines/gob/saveload.cpp
@@ -153,7 +153,7 @@ bool TempSprite::fromBuffer(const byte *buffer, int32 size, bool palette) {
}
-PlainSave::PlainSave() {
+PlainSave::PlainSave(Endianness endianness) : _endianness(endianness) {
}
PlainSave::~PlainSave() {
@@ -230,7 +230,8 @@ bool PlainSave::save(int16 dataVar, int32 size, int32 offset, const char *name,
}
bool retVal;
- retVal = SaveLoad::saveDataEndian(*out, dataVar, size, variables, variableSizes);
+ retVal = SaveLoad::saveDataEndian(*out, dataVar, size,
+ variables, variableSizes, _endianness);
out->finalize();
if (out->ioFailed()) {
@@ -258,13 +259,14 @@ bool PlainSave::load(int16 dataVar, int32 size, int32 offset, const char *name,
return false;
}
- bool retVal = SaveLoad::loadDataEndian(*in, dataVar, size, variables, variableSizes);
+ bool retVal = SaveLoad::loadDataEndian(*in, dataVar, size,
+ variables, variableSizes, _endianness);
delete in;
return retVal;
}
-StagedSave::StagedSave() {
+StagedSave::StagedSave(Endianness endianness) : _endianness(endianness) {
_mode = kModeNone;
_name = 0;
_loaded = false;
@@ -487,7 +489,7 @@ bool StagedSave::write() const {
} else
result = SaveLoad::saveDataEndian(*out, 0, _stages[i].size,
- _stages[i].bufVar, _stages[i].bufVarSizes);
+ _stages[i].bufVar, _stages[i].bufVarSizes, _endianness);
}
if (result) {
@@ -533,7 +535,7 @@ bool StagedSave::read() {
_stages[i].bufVarSizes = new byte[_stages[i].size];
result = SaveLoad::loadDataEndian(*in, 0, _stages[i].size,
- _stages[i].bufVar, _stages[i].bufVarSizes);
+ _stages[i].bufVar, _stages[i].bufVarSizes, _endianness);
}
}
@@ -734,12 +736,14 @@ void SaveLoad::buildIndex(byte *buffer, char *name, int n, int32 size, int32 off
}
}
-bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) {
+bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness) {
+ bool LE = (endianness == kEndiannessLE);
+
while (count-- > 0) {
if (*sizes == 3)
- *((uint32 *) buf) = READ_LE_UINT32(buf);
+ *((uint32 *) buf) = LE ? READ_LE_UINT32(buf) : READ_BE_UINT32(buf);
else if (*sizes == 1)
- *((uint16 *) buf) = READ_LE_UINT16(buf);
+ *((uint16 *) buf) = LE ? READ_LE_UINT16(buf) : READ_BE_UINT16(buf);
else if (*sizes != 0) {
warning("SaveLoad::fromEndian(): Corrupted variables sizes");
return false;
@@ -753,12 +757,19 @@ bool SaveLoad::fromEndian(byte *buf, const byte *sizes, uint32 count) {
return true;
}
-bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count) {
+bool SaveLoad::toEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness) {
while (count-- > 0) {
- if (*sizes == 3)
- WRITE_LE_UINT32(buf, *((uint32 *) buf));
- else if (*sizes == 1)
- WRITE_LE_UINT16(buf, *((uint16 *) buf));
+ if (*sizes == 3) {
+ if (endianness == kEndiannessLE)
+ WRITE_LE_UINT32(buf, *((uint32 *) buf));
+ else
+ WRITE_BE_UINT32(buf, *((uint32 *) buf));
+ } else if (*sizes == 1) {
+ if (endianness == kEndiannessLE)
+ WRITE_LE_UINT16(buf, *((uint16 *) buf));
+ else
+ WRITE_BE_UINT16(buf, *((uint16 *) buf));
+ }
else if (*sizes != 0) {
warning("SaveLoad::toEndian(): Corrupted variables sizes");
return false;
@@ -811,7 +822,8 @@ uint32 SaveLoad::write(Common::WriteStream &out,
}
bool SaveLoad::loadDataEndian(Common::ReadStream &in,
- int16 dataVar, uint32 size, byte *variables, byte *variableSizes) {
+ int16 dataVar, uint32 size,
+ byte *variables, byte *variableSizes, Endianness endianness) {
bool retVal = false;
@@ -821,7 +833,7 @@ bool SaveLoad::loadDataEndian(Common::ReadStream &in,
assert(varBuf && sizeBuf);
if (read(in, varBuf, sizeBuf, size) == size) {
- if (fromEndian(varBuf, sizeBuf, size)) {
+ if (fromEndian(varBuf, sizeBuf, size, endianness)) {
memcpy(variables + dataVar, varBuf, size);
memcpy(variableSizes + dataVar, sizeBuf, size);
retVal = true;
@@ -835,7 +847,8 @@ bool SaveLoad::loadDataEndian(Common::ReadStream &in,
}
bool SaveLoad::saveDataEndian(Common::WriteStream &out,
- int16 dataVar, uint32 size, const byte *variables, const byte *variableSizes) {
+ int16 dataVar, uint32 size,
+ const byte *variables, const byte *variableSizes, Endianness endianness) {
bool retVal = false;
@@ -847,7 +860,7 @@ bool SaveLoad::saveDataEndian(Common::WriteStream &out,
memcpy(varBuf, variables + dataVar, size);
memcpy(sizeBuf, variableSizes + dataVar, size);
- if (toEndian(varBuf, sizeBuf, size))
+ if (toEndian(varBuf, sizeBuf, size, endianness))
if (write(out, varBuf, sizeBuf, size) == size)
retVal = true;
diff --git a/engines/gob/saveload.h b/engines/gob/saveload.h
index 29f7ee2594..52c3a9b260 100644
--- a/engines/gob/saveload.h
+++ b/engines/gob/saveload.h
@@ -65,7 +65,7 @@ private:
class PlainSave {
public:
- PlainSave();
+ PlainSave(Endianness endianness);
~PlainSave();
bool save(int16 dataVar, int32 size, int32 offset, const char *name,
@@ -77,11 +77,14 @@ public:
const byte *variables, const byte *variableSizes) const;
bool load(int16 dataVar, int32 size, int32 offset, const char *name,
byte *variables, byte *variableSizes) const;
+
+private:
+ Endianness _endianness;
};
class StagedSave {
public:
- StagedSave();
+ StagedSave(Endianness endianness);
~StagedSave();
void addStage(int32 size, bool endianed = true);
@@ -114,6 +117,8 @@ private:
kModeLoad
};
+ Endianness _endianness;
+
Common::Array<Stage> _stages;
enum Mode _mode;
char *_name;
@@ -178,17 +183,19 @@ public:
static const char *stripPath(const char *fileName);
- static bool fromEndian(byte *buf, const byte *sizes, uint32 count);
- static bool toEndian(byte *buf, const byte *sizes, uint32 count);
+ static bool fromEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness);
+ static bool toEndian(byte *buf, const byte *sizes, uint32 count, Endianness endianness);
static uint32 read(Common::ReadStream &in,
byte *buf, byte *sizes, uint32 count);
static uint32 write(Common::WriteStream &out,
const byte *buf, const byte *sizes, uint32 count);
static bool loadDataEndian(Common::ReadStream &in,
- int16 dataVar, uint32 size, byte *variables, byte *variableSizes);
+ int16 dataVar, uint32 size,
+ byte *variables, byte *variableSizes, Endianness endianness);
static bool saveDataEndian(Common::WriteStream &out,
- int16 dataVar, uint32 size, const byte *variables, const byte *variableSizes);
+ int16 dataVar, uint32 size,
+ const byte *variables, const byte *variableSizes, Endianness endianness);
protected:
GobEngine *_vm;
@@ -228,8 +235,8 @@ protected:
int32 _varSize;
TempSprite _tmpSprite;
- PlainSave _notes;
- StagedSave _save;
+ PlainSave *_notes;
+ StagedSave *_save;
byte _indexBuffer[600];
bool _hasIndex;
@@ -306,8 +313,8 @@ protected:
TempSprite _screenshot;
TempSprite _tmpSprite;
- PlainSave _notes;
- StagedSave _save;
+ PlainSave *_notes;
+ StagedSave *_save;
byte _propBuffer[1000];
byte _indexBuffer[1200];
@@ -370,7 +377,7 @@ protected:
int32 _varSize;
- StagedSave _save;
+ StagedSave *_save;
byte _propBuffer[1000];
byte _indexBuffer[1200];
diff --git a/engines/gob/saveload_v2.cpp b/engines/gob/saveload_v2.cpp
index a92fe8cf01..fc11950368 100644
--- a/engines/gob/saveload_v2.cpp
+++ b/engines/gob/saveload_v2.cpp
@@ -45,6 +45,9 @@ SaveLoad_v2::SaveFile SaveLoad_v2::_saveFiles[] = {
SaveLoad_v2::SaveLoad_v2(GobEngine *vm, const char *targetName) :
SaveLoad(vm, targetName) {
+ _notes = new PlainSave(_vm->getEndianness());
+ _save = new StagedSave(_vm->getEndianness());
+
_saveFiles[0].destName = new char[strlen(targetName) + 5];
_saveFiles[1].destName = _saveFiles[0].destName;
_saveFiles[2].destName = 0;
@@ -58,6 +61,9 @@ SaveLoad_v2::SaveLoad_v2(GobEngine *vm, const char *targetName) :
}
SaveLoad_v2::~SaveLoad_v2() {
+ delete _notes;
+ delete _save;
+
delete[] _saveFiles[0].destName;
delete[] _saveFiles[3].destName;
}
@@ -227,7 +233,7 @@ bool SaveLoad_v2::loadGame(SaveFile &saveFile,
return false;
}
- if (!_save.load(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->load(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -268,7 +274,7 @@ bool SaveLoad_v2::loadNotes(SaveFile &saveFile,
debugC(2, kDebugSaveLoad, "Loading the notes");
- return _notes.load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
+ return _notes->load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
}
bool SaveLoad_v2::saveGame(SaveFile &saveFile,
@@ -313,10 +319,10 @@ bool SaveLoad_v2::saveGame(SaveFile &saveFile,
byte sizes[40];
memset(sizes, 0, 40);
- if(!_save.save(0, 40, 0, saveFile.destName, _indexBuffer + (slot * 40), sizes))
+ if(!_save->save(0, 40, 0, saveFile.destName, _indexBuffer + (slot * 40), sizes))
return false;
- if (!_save.save(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->save(dataVar, size, 40, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -350,7 +356,7 @@ bool SaveLoad_v2::saveNotes(SaveFile &saveFile,
debugC(2, kDebugSaveLoad, "Saving the notes");
- return _notes.save(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
+ return _notes->save(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
return false;
}
@@ -360,8 +366,8 @@ void SaveLoad_v2::assertInited() {
_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4;
- _save.addStage(40);
- _save.addStage(_varSize);
+ _save->addStage(40);
+ _save->addStage(_varSize);
}
} // End of namespace Gob
diff --git a/engines/gob/saveload_v3.cpp b/engines/gob/saveload_v3.cpp
index 67879db3d1..dab5fd9385 100644
--- a/engines/gob/saveload_v3.cpp
+++ b/engines/gob/saveload_v3.cpp
@@ -48,6 +48,9 @@ SaveLoad_v3::SaveLoad_v3(GobEngine *vm, const char *targetName,
uint32 screenshotSize, int32 indexOffset, int32 screenshotOffset) :
SaveLoad(vm, targetName) {
+ _notes = new PlainSave(_vm->getEndianness());
+ _save = new StagedSave(_vm->getEndianness());
+
_screenshotSize = screenshotSize;
_indexOffset = indexOffset;
_screenshotOffset = screenshotOffset;
@@ -71,6 +74,9 @@ SaveLoad_v3::SaveLoad_v3(GobEngine *vm, const char *targetName,
}
SaveLoad_v3::~SaveLoad_v3() {
+ delete _notes;
+ delete _save;
+
delete[] _saveFiles[0].destName;
delete[] _saveFiles[3].destName;
}
@@ -243,7 +249,7 @@ int32 SaveLoad_v3::getSizeNotes(SaveFile &saveFile) {
int32 SaveLoad_v3::getSizeScreenshot(SaveFile &saveFile) {
if (!_useScreenshots) {
_useScreenshots = true;
- _save.addStage(_screenshotSize, false);
+ _save->addStage(_screenshotSize, false);
}
Common::SaveFileManager *saveMan = g_system->getSavefileManager();
@@ -312,7 +318,7 @@ bool SaveLoad_v3::loadGame(SaveFile &saveFile,
return false;
}
- if (!_save.load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -353,7 +359,7 @@ bool SaveLoad_v3::loadNotes(SaveFile &saveFile,
debugC(2, kDebugSaveLoad, "Loading the notes");
- return _notes.load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
+ return _notes->load(dataVar, size, offset, saveFile.destName, _vm->_inter->_variables);
}
bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile,
@@ -363,7 +369,7 @@ bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile,
if (!_useScreenshots) {
_useScreenshots = true;
- _save.addStage(_screenshotSize, false);
+ _save->addStage(_screenshotSize, false);
}
if (offset == _indexOffset) {
@@ -395,7 +401,7 @@ bool SaveLoad_v3::loadScreenshot(SaveFile &saveFile,
byte *buffer = new byte[_screenshotSize];
- if (!_save.load(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {
+ if (!_save->load(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {
delete[] buffer;
return false;
}
@@ -483,13 +489,13 @@ bool SaveLoad_v3::saveGame(SaveFile &saveFile,
_hasIndex = false;
- if(!_save.save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))
+ if(!_save->save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))
return false;
- if(!_save.save(0, 40, 500, saveFile.destName, _indexBuffer + (saveFile.slot * 40), 0))
+ if(!_save->save(0, 40, 500, saveFile.destName, _indexBuffer + (saveFile.slot * 40), 0))
return false;
- if (!_save.save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -523,7 +529,7 @@ bool SaveLoad_v3::saveNotes(SaveFile &saveFile,
debugC(2, kDebugSaveLoad, "Saving the notes");
- return _notes.save(dataVar, size - 160, offset, saveFile.destName, _vm->_inter->_variables);
+ return _notes->save(dataVar, size - 160, offset, saveFile.destName, _vm->_inter->_variables);
return false;
}
@@ -534,7 +540,7 @@ bool SaveLoad_v3::saveScreenshot(SaveFile &saveFile,
if (!_useScreenshots) {
_useScreenshots = true;
- _save.addStage(_screenshotSize, false);
+ _save->addStage(_screenshotSize, false);
}
if (offset >= _screenshotOffset) {
@@ -571,7 +577,7 @@ bool SaveLoad_v3::saveScreenshot(SaveFile &saveFile,
return false;
}
- if (!_save.save(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {
+ if (!_save->save(0, _screenshotSize, _varSize + 540, saveFile.destName, buffer, 0)) {
delete[] buffer;
return false;
}
@@ -588,9 +594,9 @@ void SaveLoad_v3::assertInited() {
_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4;
- _save.addStage(500);
- _save.addStage(40, false);
- _save.addStage(_varSize);
+ _save->addStage(500);
+ _save->addStage(40, false);
+ _save->addStage(_varSize);
}
void SaveLoad_v3::buildScreenshotIndex(byte *buffer, char *name, int n) {
diff --git a/engines/gob/saveload_v4.cpp b/engines/gob/saveload_v4.cpp
index a6548dd82d..0bd3dc03e6 100644
--- a/engines/gob/saveload_v4.cpp
+++ b/engines/gob/saveload_v4.cpp
@@ -50,6 +50,8 @@ SaveLoad_v4::SaveFile SaveLoad_v4::_saveFiles[] = {
SaveLoad_v4::SaveLoad_v4(GobEngine *vm, const char *targetName) :
SaveLoad(vm, targetName) {
+ _save = new StagedSave(_vm->getEndianness());
+
_firstSizeGame = true;
_saveFiles[0].destName = 0;
@@ -76,6 +78,8 @@ SaveLoad_v4::SaveLoad_v4(GobEngine *vm, const char *targetName) :
}
SaveLoad_v4::~SaveLoad_v4() {
+ delete _save;
+
delete[] _screenProps;
delete[] _saveFiles[1].destName;
}
@@ -297,7 +301,7 @@ bool SaveLoad_v4::loadGame(SaveFile &saveFile,
return false;
}
- if (!_save.load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->load(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -314,7 +318,7 @@ bool SaveLoad_v4::loadGameScreenProps(SaveFile &saveFile,
setCurrentSlot(saveFile.destName, saveFile.sourceName[4] - '0');
- if (!_save.load(0, 256000, _varSize + 540, saveFile.destName,
+ if (!_save->load(0, 256000, _varSize + 540, saveFile.destName,
_screenProps, _screenProps + 256000))
return false;
@@ -393,13 +397,13 @@ bool SaveLoad_v4::saveGame(SaveFile &saveFile,
_hasIndex = false;
- if(!_save.save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))
+ if(!_save->save(0, 500, 0, saveFile.destName, _propBuffer, _propBuffer + 500))
return false;
- if(!_save.save(0, 40, 500, saveFile.destName, _indexBuffer + (slot * 40), 0))
+ if(!_save->save(0, 40, 500, saveFile.destName, _indexBuffer + (slot * 40), 0))
return false;
- if (!_save.save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
+ if (!_save->save(dataVar, size, 540, saveFile.destName, _vm->_inter->_variables))
return false;
}
@@ -417,7 +421,7 @@ bool SaveLoad_v4::saveGameScreenProps(SaveFile &saveFile,
setCurrentSlot(saveFile.destName, saveFile.sourceName[4] - '0');
- if (!_save.save(0, 256000, _varSize + 540, saveFile.destName,
+ if (!_save->save(0, 256000, _varSize + 540, saveFile.destName,
_screenProps, _screenProps + 256000))
return false;
@@ -430,10 +434,10 @@ void SaveLoad_v4::assertInited() {
_varSize = READ_LE_UINT32(_vm->_game->_totFileData + 0x2C) * 4;
- _save.addStage(500);
- _save.addStage(40, false);
- _save.addStage(_varSize);
- _save.addStage(256000);
+ _save->addStage(500);
+ _save->addStage(40, false);
+ _save->addStage(_varSize);
+ _save->addStage(256000);
}
} // End of namespace Gob
diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp
index fce1e93bc2..f8aa44be63 100644
--- a/engines/kyra/detection.cpp
+++ b/engines/kyra/detection.cpp
@@ -26,6 +26,7 @@
#include "kyra/kyra_lok.h"
#include "kyra/kyra_hof.h"
#include "kyra/kyra_mr.h"
+#include "kyra/lol.h"
#include "common/config-manager.h"
#include "common/advancedDetector.h"
@@ -55,6 +56,7 @@ namespace {
#define KYRA2_FLOPPY_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA2)
#define KYRA2_FLOPPY_CMP_FLAGS FLAGS(false, false, false, false, false, true, Kyra::GI_KYRA2)
#define KYRA2_CD_FLAGS FLAGS(false, false, true, false, false, false, Kyra::GI_KYRA2)
+#define KYRA2_CD_FAN_FLAGS(x, y) FLAGS_FAN(x, y, false, false, true, false, false, false, Kyra::GI_KYRA2)
#define KYRA2_CD_DEMO_FLAGS FLAGS(true, false, true, false, false, false, Kyra::GI_KYRA2)
#define KYRA2_DEMO_FLAGS FLAGS(true, false, false, false, false, false, Kyra::GI_KYRA2)
#define KYRA2_TOWNS_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA2)
@@ -64,6 +66,11 @@ namespace {
#define KYRA3_CD_INS_FLAGS FLAGS(false, false, true, false, true, false, Kyra::GI_KYRA3)
#define KYRA3_CD_FAN_FLAGS(x, y) FLAGS_FAN(x, y, false, false, true, false, true, false, Kyra::GI_KYRA3)
+#define LOL_CD_FLAGS FLAGS(false, false, true, false, false, false, Kyra::GI_LOL)
+#define LOL_PC98_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_LOL)
+#define LOL_PC98_SJIS_FLAGS FLAGS(false, false, false, true, false, false, Kyra::GI_LOL)
+#define LOL_DEMO_FLAGS FLAGS(true, false, false, false, false, false, Kyra::GI_KYRA2)
+
const KYRAGameDescription adGameDescs[] = {
{
{
@@ -207,7 +214,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // FM-Towns version
{
"kyra1",
- 0,
+ "CD",
{
{ "EMC.PAK", 0, "a046bb0b422061aab8e4c4689400343a", -1 },
{ "TWMUSIC.PAK", 0, "e53bca3a3e3fb49107d59463ec387a59", -1 },
@@ -215,14 +222,14 @@ const KYRAGameDescription adGameDescs[] = {
},
Common::EN_ANY,
Common::kPlatformFMTowns,
- Common::ADGF_NO_FLAGS
+ Common::ADGF_CD
},
KYRA1_TOWNS_FLAGS
},
{
{
"kyra1",
- 0,
+ "CD",
{
{ "JMC.PAK", 0, "9c5707a2a478e8167e44283246612d2c", -1 },
{ "TWMUSIC.PAK", 0, "e53bca3a3e3fb49107d59463ec387a59", -1 },
@@ -230,7 +237,7 @@ const KYRAGameDescription adGameDescs[] = {
},
Common::JA_JPN,
Common::kPlatformFMTowns,
- Common::ADGF_NO_FLAGS
+ Common::ADGF_CD
},
KYRA1_TOWNS_SJIS_FLAGS
},
@@ -418,6 +425,41 @@ const KYRAGameDescription adGameDescs[] = {
KYRA2_CD_FLAGS
},
+ // Italian fan translation, see fr#2003504 "KYRA: add support for Italian version of Kyrandia 2&3"
+ { // CD version
+ {
+ "kyra2",
+ "CD",
+ AD_ENTRY1("FATE.PAK", "30487f3b8d7790c7857f4769ff2dd125"),
+ Common::IT_ITA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ KYRA2_CD_FAN_FLAGS(Common::IT_ITA, Common::EN_ANY)
+ },
+ {
+ {
+ "kyra2",
+ "CD",
+ AD_ENTRY1("FATE.PAK", "30487f3b8d7790c7857f4769ff2dd125"),
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ KYRA2_CD_FAN_FLAGS(Common::IT_ITA, Common::EN_ANY)
+ },
+ {
+ {
+ "kyra2",
+ "CD",
+ AD_ENTRY1("FATE.PAK", "30487f3b8d7790c7857f4769ff2dd125"),
+ Common::FR_FRA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ KYRA2_CD_FAN_FLAGS(Common::IT_ITA, Common::EN_ANY)
+ },
+
{ // Interactive Demo
{
"kyra2",
@@ -454,11 +496,11 @@ const KYRAGameDescription adGameDescs[] = {
KYRA2_CD_DEMO_FLAGS
},
- { // Non-Interactive Demo
+ { // Non-Interactive Demos
{
"kyra2",
"Demo",
- AD_ENTRY1("GENERAL.PAK", "35825783e5b60755fd520360079f9c15"),
+ AD_ENTRY1("VOC.PAK", "ecb3561b63749158172bf21528cf5f45"),
Common::EN_ANY,
Common::kPlatformPC,
Common::ADGF_DEMO
@@ -469,44 +511,44 @@ const KYRAGameDescription adGameDescs[] = {
{ // FM-Towns
{
"kyra2",
- 0,
+ "CD",
AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"),
Common::EN_ANY,
Common::kPlatformFMTowns,
- Common::ADGF_NO_FLAGS
+ Common::ADGF_CD
},
KYRA2_TOWNS_FLAGS
},
{
{
"kyra2",
- 0,
+ "CD",
AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"),
Common::JA_JPN,
Common::kPlatformFMTowns,
- Common::ADGF_NO_FLAGS
+ Common::ADGF_CD
},
KYRA2_TOWNS_SJIS_FLAGS
},
{ // PC-9821
{
"kyra2",
- 0,
+ "CD",
AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"),
Common::EN_ANY,
Common::kPlatformPC98,
- Common::ADGF_NO_FLAGS
+ Common::ADGF_CD
},
KYRA2_TOWNS_FLAGS
},
{
{
"kyra2",
- 0,
+ "CD",
AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"),
Common::JA_JPN,
Common::kPlatformPC98,
- Common::ADGF_NO_FLAGS
+ Common::ADGF_CD
},
KYRA2_TOWNS_SJIS_FLAGS
},
@@ -700,6 +742,151 @@ const KYRAGameDescription adGameDescs[] = {
},
KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA)
},
+
+ // Lands of Lore CD
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "05a4f588fb81dc9c0ef1f2ec20d89e24", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::FR_FRA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "9e4bab499b7ea9337b91ac29fcba6d13", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "9e4bab499b7ea9337b91ac29fcba6d13", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
+ {
+ {
+ "lol",
+ "CD",
+ {
+ { "GENERAL.PAK", 0, "9e4bab499b7ea9337b91ac29fcba6d13", -1 },
+ { "L01.PAK", 0, "759a0ac26808d77ea968bd392355ba1d", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::FR_FRA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE | Common::ADGF_CD
+ },
+ LOL_CD_FLAGS
+ },
+
+ /*{
+ {
+ "lol",
+ 0,
+ {
+ { "GENERAL.PAK", 0, "3fe6539b9b09084c0984eaf7170464e9", -1 },
+ { "MUS.PAK", 0, "008dc69d8cbcdb6bae30e270fab26e76", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC98,
+ Common::ADGF_NO_FLAGS
+ },
+ LOL_PC98_FLAGS
+ },
+
+ {
+ {
+ "lol",
+ 0,
+ {
+ { "GENERAL.PAK", 0, "3fe6539b9b09084c0984eaf7170464e9", -1 },
+ { "MUS.PAK", 0, "008dc69d8cbcdb6bae30e270fab26e76", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::JA_JPN,
+ Common::kPlatformPC98,
+ Common::ADGF_NO_FLAGS
+ },
+ LOL_PC98_SJIS_FLAGS
+ },*/
+
+ {
+ {
+ "lol",
+ "Demo",
+ {
+ { "GENERAL.PAK", 0, "e94863d86c4597a2d581d05481c152ba", -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ LOL_DEMO_FLAGS
+ },
+
{ AD_TABLE_END_MARKER, FLAGS(0, 0, 0, 0, 0, 0, 0) }
};
@@ -707,6 +894,7 @@ const PlainGameDescriptor gameList[] = {
{ "kyra1", "The Legend of Kyrandia" },
{ "kyra2", "The Legend of Kyrandia: The Hand of Fate" },
{ "kyra3", "The Legend of Kyrandia: Malcolm's Revenge" },
+ { "lol", "Lands of Lore: The Throne of Chaos" },
{ 0, 0 }
};
@@ -779,6 +967,9 @@ bool KyraMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common
case Kyra::GI_KYRA3:
*engine = new Kyra::KyraEngine_MR(syst, flags);
break;
+ case Kyra::GI_LOL:
+ *engine = new Kyra::LoLEngine(syst, flags);
+ break;
default:
res = false;
warning("Kyra engine: unknown gameID");
diff --git a/engines/kyra/gui_lok.cpp b/engines/kyra/gui_lok.cpp
index 092aaedb23..818d2f9b4e 100644
--- a/engines/kyra/gui_lok.cpp
+++ b/engines/kyra/gui_lok.cpp
@@ -526,7 +526,7 @@ int GUI_LoK::resumeGame(Button *button) {
void GUI_LoK::setupSavegames(Menu &menu, int num) {
Common::InSaveFile *in;
- static char savenames[5][31];
+ static char savenames[5][35];
uint8 startSlot;
assert(num <= 5);
@@ -545,7 +545,8 @@ void GUI_LoK::setupSavegames(Menu &menu, int num) {
KyraEngine_v1::SaveHeader header;
for (int i = startSlot; i < num && uint(_savegameOffset + i) < _saveSlots.size(); i++) {
if ((in = _vm->openSaveForReading(_vm->getSavegameFilename(_saveSlots[i + _savegameOffset]), header))) {
- strncpy(savenames[i], header.description.c_str(), 31);
+ strncpy(savenames[i], header.description.c_str(), ARRAYSIZE(savenames[0]));
+ savenames[i][34] = 0;
menu.item[i].itemString = savenames[i];
menu.item[i].enabled = 1;
menu.item[i].saveSlot = _saveSlots[i + _savegameOffset];
@@ -669,7 +670,7 @@ void GUI_LoK::updateSavegameString() {
length = strlen(_savegameName);
if (_keyPressed.ascii > 31 && _keyPressed.ascii < 127) {
- if (length < 31) {
+ if (length < ARRAYSIZE(_savegameName)-1) {
_savegameName[length] = _keyPressed.ascii;
_savegameName[length+1] = 0;
redrawTextfield();
diff --git a/engines/kyra/gui_lok.h b/engines/kyra/gui_lok.h
index 49081c7ae2..16b7ef9183 100644
--- a/engines/kyra/gui_lok.h
+++ b/engines/kyra/gui_lok.h
@@ -162,7 +162,7 @@ private:
bool _menuRestoreScreen;
uint8 _toplevelMenu;
int _savegameOffset;
- char _savegameName[31];
+ char _savegameName[35];
const char *_specialSavegameString;
Common::KeyState _keyPressed;
int8 _mouseWheel;
diff --git a/engines/kyra/gui_v2.cpp b/engines/kyra/gui_v2.cpp
index 11f430040c..a7ae2a6c44 100644
--- a/engines/kyra/gui_v2.cpp
+++ b/engines/kyra/gui_v2.cpp
@@ -456,6 +456,7 @@ void GUI_v2::setupSavegameNames(Menu &menu, int num) {
for (int i = startSlot; i < num && uint(_savegameOffset + i) < _saveSlots.size(); ++i) {
if ((in = _vm->openSaveForReading(_vm->getSavegameFilename(_saveSlots[i + _savegameOffset]), header)) != 0) {
strncpy(getTableString(menu.item[i].itemId), header.description.c_str(), 80);
+ getTableString(menu.item[i].itemId)[79] = 0;
menu.item[i].saveSlot = _saveSlots[i + _savegameOffset];
menu.item[i].enabled = true;
delete in;
diff --git a/engines/kyra/gui_v2.h b/engines/kyra/gui_v2.h
index 161752627b..60b7f0ab86 100644
--- a/engines/kyra/gui_v2.h
+++ b/engines/kyra/gui_v2.h
@@ -188,7 +188,7 @@ protected:
// save menu
bool _noSaveProcess;
int _saveSlot;
- char _saveDescription[0x50];
+ char _saveDescription[0x51];
int saveMenu(Button *caller);
int clickSaveSlot(Button *caller);
diff --git a/engines/kyra/kyra_hof.cpp b/engines/kyra/kyra_hof.cpp
index ac69272ef4..fd14e8c909 100644
--- a/engines/kyra/kyra_hof.cpp
+++ b/engines/kyra/kyra_hof.cpp
@@ -230,7 +230,7 @@ int KyraEngine_HoF::init() {
_gui = new GUI_HoF(this);
assert(_gui);
_gui->initStaticData();
- _tim = new TIMInterpreter(this, _system);
+ _tim = new TIMInterpreter(this, _screen, _system);
assert(_tim);
if (_flags.isDemo && !_flags.isTalkie) {
@@ -251,7 +251,7 @@ int KyraEngine_HoF::init() {
_abortIntroFlag = false;
if (_sequenceStrings) {
- for (int i = 0; i < 33; i++)
+ for (int i = 0; i < MIN(33, _sequenceStringsSize); i++)
_sequenceStringsDuration[i] = (int) strlen(_sequenceStrings[i]) * 8;
}
@@ -278,7 +278,10 @@ int KyraEngine_HoF::go() {
seq_showStarcraftLogo();
if (_flags.isDemo && !_flags.isTalkie) {
- seq_playSequences(kSequenceDemoVirgin, kSequenceDemoFisher);
+ if (_flags.gameID == GI_LOL)
+ seq_playSequences(kSequenceLolDemoScene1, kSequenceLolDemoScene6);
+ else
+ seq_playSequences(kSequenceDemoVirgin, kSequenceDemoFisher);
_menuChoice = 4;
} else {
seq_playSequences(kSequenceVirgin, kSequenceZanfaun);
diff --git a/engines/kyra/kyra_hof.h b/engines/kyra/kyra_hof.h
index 866dd55d16..279e9e35a6 100644
--- a/engines/kyra/kyra_hof.h
+++ b/engines/kyra/kyra_hof.h
@@ -97,6 +97,20 @@ enum kNestedSequencesDemo {
kSequenceDemoDig
};
+enum kSequencesLolDemo {
+ kSequenceLolDemoScene1 = 0,
+ kSequenceLolDemoText1,
+ kSequenceLolDemoScene2,
+ kSequenceLolDemoText2,
+ kSequenceLolDemoScene3,
+ kSequenceLolDemoText3,
+ kSequenceLolDemoScene4,
+ kSequenceLolDemoText4,
+ kSequenceLolDemoScene5,
+ kSequenceLolDemoText5,
+ kSequenceLolDemoScene6
+};
+
class WSAMovie_v2;
class KyraEngine_HoF;
class TextDisplayer_HoF;
@@ -242,6 +256,14 @@ protected:
int seq_demoBail(WSAMovie_v2 *wsaObj, int x, int y, int frm);
int seq_demoDig(WSAMovie_v2 *wsaObj, int x, int y, int frm);
+ int seq_lolDemoScene1(WSAMovie_v2 *wsaObj, int x, int y, int frm);
+ int seq_lolDemoScene2(WSAMovie_v2 *wsaObj, int x, int y, int frm);
+ int seq_lolDemoScene3(WSAMovie_v2 *wsaObj, int x, int y, int frm);
+ int seq_lolDemoScene4(WSAMovie_v2 *wsaObj, int x, int y, int frm);
+ int seq_lolDemoScene5(WSAMovie_v2 *wsaObj, int x, int y, int frm);
+ int seq_lolDemoText5(WSAMovie_v2 *wsaObj, int x, int y, int frm);
+ int seq_lolDemoScene6(WSAMovie_v2 *wsaObj, int x, int y, int frm);
+
void seq_sequenceCommand(int command);
void seq_loadNestedSequence(int wsaNum, int seqNum);
void seq_nestedSequenceFrame(int command, int wsaNum);
@@ -264,7 +286,7 @@ protected:
WSAMovie_v2 * wsa, int firstframe, int lastframe, int wsaXpos, int wsaYpos);
void seq_finaleActorScreen();
void seq_displayScrollText(uint8 *data, const ScreenDim *d, int tempPage1, int tempPage2, int speed, int step, Screen::FontId fid1, Screen::FontId fid2, const uint8 *shapeData = 0, const char *const *specialData = 0);
- void seq_scrollPage();
+ void seq_scrollPage(int bottom, int top);
void seq_showStarcraftLogo();
void seq_init();
diff --git a/engines/kyra/kyra_lok.cpp b/engines/kyra/kyra_lok.cpp
index afd164958c..f668ae8401 100644
--- a/engines/kyra/kyra_lok.cpp
+++ b/engines/kyra/kyra_lok.cpp
@@ -173,7 +173,8 @@ int KyraEngine_LoK::init() {
initStaticResource();
- _sound->setSoundList(&_soundData[kMusicIntro]);
+ if (_soundData)
+ _sound->setSoundList(&_soundData[kMusicIntro]);
_trackMap = _dosTrackMap;
_trackMapSize = _dosTrackMapSize;
@@ -316,7 +317,8 @@ void KyraEngine_LoK::startup() {
debugC(9, kDebugLevelMain, "KyraEngine_LoK::startup()");
static const uint8 colorMap[] = { 0, 0, 0, 0, 12, 12, 12, 0, 0, 0, 0, 0 };
_screen->setTextColorMap(colorMap);
- _sound->setSoundList(&_soundData[kMusicIngame]);
+ if (_soundData)
+ _sound->setSoundList(&_soundData[kMusicIngame]);
_sound->loadSoundFile(0);
// _screen->setFont(Screen::FID_6_FNT);
_screen->setAnimBlockPtr(3750);
diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp
index 6121f6979c..f3014911fc 100644
--- a/engines/kyra/kyra_v1.cpp
+++ b/engines/kyra/kyra_v1.cpp
@@ -112,7 +112,7 @@ int KyraEngine_v1::init() {
_sound = new SoundTownsPC98_v2(this, _mixer);
} else if (_flags.platform == Common::kPlatformPC98) {
if (_flags.gameID == GI_KYRA1)
- _sound = new SoundTowns/*SoundPC98*/(this, _mixer);
+ _sound = new SoundPC98(this, _mixer);
else
_sound = new SoundTownsPC98_v2(this, _mixer);
} else if (midiDriver == MD_ADLIB) {
@@ -150,6 +150,16 @@ int KyraEngine_v1::init() {
_res = new Resource(this);
assert(_res);
_res->reset();
+
+ if (_flags.isDemo) {
+ // HACK: check whether this is the HOF demo or the LOL demo.
+ // The LOL demo needs to be detected and run as KyraEngine_HoF,
+ // but the static resource loader and the sequence player will
+ // need correct IDs.
+ if (_res->exists("scene1.cps"))
+ _flags.gameID = GI_LOL;
+ }
+
_staticres = new StaticResource(this);
assert(_staticres);
if (!_staticres->init())
diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h
index 8dd95c79b9..6d61bd997f 100644
--- a/engines/kyra/kyra_v1.h
+++ b/engines/kyra/kyra_v1.h
@@ -34,8 +34,8 @@
#include "kyra/script.h"
namespace Common {
-class InSaveFile;
-class OutSaveFile;
+class SeekableReadStream;
+class WriteStream;
} // end of namespace Common
class KyraMetaEngine;
@@ -64,7 +64,8 @@ struct GameFlags {
enum {
GI_KYRA1 = 0,
GI_KYRA2 = 1,
- GI_KYRA3 = 2
+ GI_KYRA3 = 2,
+ GI_LOL = 4
};
struct AudioDataStruct {
@@ -293,10 +294,10 @@ protected:
kRSHEIoError = 3
};
- static kReadSaveHeaderError readSaveHeader(Common::InSaveFile *file, SaveHeader &header);
+ static kReadSaveHeaderError readSaveHeader(Common::SeekableReadStream *file, SaveHeader &header);
- Common::InSaveFile *openSaveForReading(const char *filename, SaveHeader &header);
- Common::OutSaveFile *openSaveForWriting(const char *filename, const char *saveName) const;
+ Common::SeekableReadStream *openSaveForReading(const char *filename, SaveHeader &header);
+ Common::WriteStream *openSaveForWriting(const char *filename, const char *saveName) const;
};
} // End of namespace Kyra
diff --git a/engines/kyra/lol.cpp b/engines/kyra/lol.cpp
new file mode 100644
index 0000000000..ef1121baa0
--- /dev/null
+++ b/engines/kyra/lol.cpp
@@ -0,0 +1,806 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "kyra/lol.h"
+#include "kyra/screen_lol.h"
+#include "kyra/resource.h"
+#include "kyra/sound.h"
+
+#include "common/endian.h"
+
+namespace Kyra {
+
+LoLEngine::LoLEngine(OSystem *system, const GameFlags &flags) : KyraEngine_v1(system, flags) {
+ _screen = 0;
+
+ switch (_flags.lang) {
+ case Common::EN_ANY:
+ case Common::EN_USA:
+ case Common::EN_GRB:
+ _lang = 0;
+ break;
+
+ case Common::FR_FRA:
+ _lang = 1;
+ break;
+
+ case Common::DE_DEU:
+ _lang = 2;
+ break;
+
+ default:
+ warning("unsupported language, switching back to English");
+ _lang = 0;
+ break;
+ }
+
+ _chargenWSA = 0;
+}
+
+LoLEngine::~LoLEngine() {
+ setupPrologueData(false);
+
+ delete _screen;
+ delete _tim;
+
+ for (Common::Array<const TIMOpcode*>::iterator i = _timIntroOpcodes.begin(); i != _timIntroOpcodes.end(); ++i)
+ delete *i;
+ _timIntroOpcodes.clear();
+}
+
+Screen *LoLEngine::screen() {
+ return _screen;
+}
+
+int LoLEngine::init() {
+ _screen = new Screen_LoL(this, _system);
+ assert(_screen);
+ _screen->setResolution();
+
+ KyraEngine_v1::init();
+
+ _tim = new TIMInterpreter(this, _screen, _system);
+ assert(_tim);
+
+ _screen->setAnimBlockPtr(10000);
+ _screen->setScreenDim(0);
+
+ return 0;
+}
+
+int LoLEngine::go() {
+ setupPrologueData(true);
+ showIntro();
+ _sound->playTrack(6);
+ /*int character = */chooseCharacter();
+ _sound->playTrack(1);
+ _screen->fadeToBlack();
+ setupPrologueData(false);
+
+ return 0;
+}
+
+#pragma mark - Input
+
+int LoLEngine::checkInput(Button *buttonList, bool mainLoop) {
+ debugC(9, kDebugLevelMain, "LoLEngine::checkInput(%p, %d)", (const void*)buttonList, mainLoop);
+ updateInput();
+
+ int keys = 0;
+ int8 mouseWheel = 0;
+
+ while (_eventList.size()) {
+ Common::Event event = *_eventList.begin();
+ bool breakLoop = false;
+
+ switch (event.type) {
+ case Common::EVENT_KEYDOWN:
+ /*if (event.kbd.keycode >= '1' && event.kbd.keycode <= '9' &&
+ (event.kbd.flags == Common::KBD_CTRL || event.kbd.flags == Common::KBD_ALT) && mainLoop) {
+ const char *saveLoadSlot = getSavegameFilename(9 - (event.kbd.keycode - '0') + 990);
+
+ if (event.kbd.flags == Common::KBD_CTRL) {
+ loadGame(saveLoadSlot);
+ _eventList.clear();
+ breakLoop = true;
+ } else {
+ char savegameName[14];
+ sprintf(savegameName, "Quicksave %d", event.kbd.keycode - '0');
+ saveGame(saveLoadSlot, savegameName);
+ }
+ } else if (event.kbd.flags == Common::KBD_CTRL) {
+ if (event.kbd.keycode == 'd')
+ _debugger->attach();
+ }*/
+ break;
+
+ case Common::EVENT_MOUSEMOVE: {
+ Common::Point pos = getMousePos();
+ _mouseX = pos.x;
+ _mouseY = pos.y;
+ } break;
+
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP: {
+ Common::Point pos = getMousePos();
+ _mouseX = pos.x;
+ _mouseY = pos.y;
+ keys = (event.type == Common::EVENT_LBUTTONDOWN ? 199 : (200 | 0x800));
+ breakLoop = true;
+ } break;
+
+ case Common::EVENT_WHEELUP:
+ mouseWheel = -1;
+ break;
+
+ case Common::EVENT_WHEELDOWN:
+ mouseWheel = 1;
+ break;
+
+ default:
+ break;
+ }
+
+ //if (_debugger->isAttached())
+ // _debugger->onFrame();
+
+ if (breakLoop)
+ break;
+
+ _eventList.erase(_eventList.begin());
+ }
+
+ return /*gui_v2()->processButtonList(buttonList, keys | 0x8000, mouseWheel)*/keys;
+}
+
+void LoLEngine::updateInput() {
+ Common::Event event;
+
+ while (_eventMan->pollEvent(event)) {
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ _quitFlag = true;
+ break;
+
+ case Common::EVENT_KEYDOWN:
+ if (event.kbd.keycode == '.' || event.kbd.keycode == Common::KEYCODE_ESCAPE)
+ _eventList.push_back(Event(event, true));
+ else if (event.kbd.keycode == 'q' && event.kbd.flags == Common::KBD_CTRL)
+ _quitFlag = true;
+ else
+ _eventList.push_back(event);
+ break;
+
+ case Common::EVENT_LBUTTONDOWN:
+ _eventList.push_back(Event(event, true));
+ break;
+
+ case Common::EVENT_MOUSEMOVE:
+ _screen->updateScreen();
+ // fall through
+
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_WHEELUP:
+ case Common::EVENT_WHEELDOWN:
+ _eventList.push_back(event);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+void LoLEngine::removeInputTop() {
+ if (!_eventList.empty())
+ _eventList.erase(_eventList.begin());
+}
+
+bool LoLEngine::skipFlag() const {
+ for (Common::List<Event>::const_iterator i = _eventList.begin(); i != _eventList.end(); ++i) {
+ if (i->causedSkip)
+ return true;
+ }
+ return false;
+}
+
+void LoLEngine::resetSkipFlag(bool removeEvent) {
+ for (Common::List<Event>::iterator i = _eventList.begin(); i != _eventList.end(); ++i) {
+ if (i->causedSkip) {
+ if (removeEvent)
+ _eventList.erase(i);
+ else
+ i->causedSkip = false;
+ return;
+ }
+ }
+}
+
+#pragma mark - Intro
+
+void LoLEngine::setupPrologueData(bool load) {
+ static const char * const fileList[] = {
+ "xxx/general.pak",
+ "xxx/introvoc.pak",
+ "xxx/startup.pak",
+ "xxx/intro1.pak",
+ "xxx/intro2.pak",
+ "xxx/intro3.pak",
+ "xxx/intro4.pak",
+ "xxx/intro5.pak",
+ "xxx/intro6.pak",
+ "xxx/intro7.pak",
+ "xxx/intro8.pak",
+ "xxx/intro9.pak"
+ };
+
+ char filename[32];
+ for (uint i = 0; i < ARRAYSIZE(fileList); ++i) {
+ strcpy(filename, fileList[i]);
+ memcpy(filename, _languageExt[_lang], 3);
+
+ if (load) {
+ if (!_res->loadPakFile(filename))
+ error("Couldn't load file: '%s'", filename);
+ } else {
+ _res->unloadPakFile(filename);
+ }
+ }
+
+ if (load) {
+ _chargenWSA = new WSAMovie_v2(this, _screen);
+ assert(_chargenWSA);
+
+ _charSelection = -1;
+ _charSelectionInfoResult = -1;
+
+ _selectionAnimFrames[0] = _selectionAnimFrames[2] = 0;
+ _selectionAnimFrames[1] = _selectionAnimFrames[3] = 1;
+
+ memset(_selectionAnimTimers, 0, sizeof(_selectionAnimTimers));
+ memset(_screen->getPalette(1), 0, 768);
+ } else {
+ delete _chargenWSA; _chargenWSA = 0;
+ }
+}
+
+void LoLEngine::showIntro() {
+ debugC(9, kDebugLevelMain, "LoLEngine::showIntro()");
+
+ TIM *intro = _tim->load("LOLINTRO.TIM", &_timIntroOpcodes);
+
+ _screen->loadFont(Screen::FID_8_FNT, "NEW8P.FNT");
+ _screen->loadFont(Screen::FID_INTRO_FNT, "INTRO.FNT");
+ _screen->setFont(Screen::FID_8_FNT);
+
+ _tim->resetFinishedFlag();
+ _tim->setLangData("LOLINTRO.DIP");
+
+ _screen->hideMouse();
+
+ uint32 palNextFadeStep = 0;
+ while (!_tim->finished() && !_quitFlag && !skipFlag()) {
+ updateInput();
+ _tim->exec(intro, false);
+ _screen->checkedPageUpdate(8, 4);
+
+ if (_tim->_palDiff) {
+ if (palNextFadeStep < _system->getMillis()) {
+ _tim->_palDelayAcc += _tim->_palDelayInc;
+ palNextFadeStep = _system->getMillis() + ((_tim->_palDelayAcc >> 8) * _tickLength);
+ _tim->_palDelayAcc &= 0xFF;
+
+ if (!_screen->fadePalStep(_screen->getPalette(0), _tim->_palDiff)) {
+ _screen->setScreenPalette(_screen->getPalette(0));
+ _tim->_palDiff = 0;
+ }
+ }
+ }
+
+ _system->delayMillis(10);
+ _screen->updateScreen();
+ }
+ _screen->showMouse();
+ _sound->voiceStop();
+
+ // HACK: Remove all input events
+ _eventList.clear();
+
+ _tim->unload(intro);
+ _tim->clearLangData();
+
+ _screen->fadePalette(_screen->getPalette(1), 30, 0);
+}
+
+int LoLEngine::chooseCharacter() {
+ debugC(9, kDebugLevelMain, "LoLEngine::chooseCharacter()");
+
+ _tim->setLangData("LOLINTRO.DIP");
+
+ _screen->loadFont(Screen::FID_9_FNT, "FONT9P.FNT");
+
+ _screen->loadBitmap("ITEMICN.SHP", 3, 3, 0);
+ _screen->setMouseCursor(0, 0, _screen->getPtrToShape(_screen->getCPagePtr(3), 0));
+
+ while (!_screen->isMouseVisible())
+ _screen->showMouse();
+
+ _screen->loadBitmap("CHAR.CPS", 2, 2, _screen->getPalette(0));
+ _screen->loadBitmap("BACKGRND.CPS", 4, 4, _screen->getPalette(0));
+
+ if (!_chargenWSA->open("CHARGEN.WSA", 1, 0))
+ error("Couldn't load CHARGEN.WSA");
+ _chargenWSA->setX(113);
+ _chargenWSA->setY(0);
+ _chargenWSA->setDrawPage(2);
+ _chargenWSA->displayFrame(0, 0, 0, 0);
+
+ _screen->setFont(Screen::FID_9_FNT);
+ _screen->_curPage = 2;
+
+ for (int i = 0; i < 4; ++i)
+ _screen->fprintStringIntro(_charPreviews[i].name, _charPreviews[i].x + 16, _charPreviews[i].y + 36, 0xC0, 0x00, 0x9C, 0x120);
+
+ for (int i = 0; i < 4; ++i) {
+ _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 48, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[0]);
+ _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 56, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[1]);
+ _screen->fprintStringIntro("%d", _charPreviews[i].x + 21, _charPreviews[i].y + 64, 0x98, 0x00, 0x9C, 0x220, _charPreviews[i].attrib[2]);
+ }
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(51), 36, 173, 0x98, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(53), 36, 181, 0x98, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(55), 36, 189, 0x98, 0x00, 0x9C, 0x20);
+
+ _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 0, Screen::CR_NO_P_CHECK);
+ _screen->_curPage = 0;
+
+ _screen->fadePalette(_screen->getPalette(0), 30, 0);
+
+ bool kingIntro = true;
+ while (!_quitFlag) {
+ if (kingIntro)
+ kingSelectionIntro();
+
+ if (_charSelection < 0)
+ processCharacterSelection();
+
+ if (_quitFlag)
+ break;
+
+ if (_charSelection == 100) {
+ kingIntro = true;
+ _charSelection = -1;
+ continue;
+ }
+
+ _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK);
+ _screen->updateScreen();
+ _screen->showMouse();
+
+ if (selectionCharInfo(_charSelection) == -1) {
+ _charSelection = -1;
+ kingIntro = false;
+ } else {
+ break;
+ }
+ }
+
+ if (_quitFlag)
+ return -1;
+
+ uint32 waitTime = _system->getMillis() + 420 * _tickLength;
+ while (waitTime > _system->getMillis() && !skipFlag() && !_quitFlag) {
+ updateInput();
+ _system->delayMillis(10);
+ }
+
+ // HACK: Remove all input events
+ _eventList.clear();
+
+ _tim->clearLangData();
+
+ return _charSelection;
+}
+
+void LoLEngine::kingSelectionIntro() {
+ debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionIntro()");
+
+ _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK);
+ int y = 38;
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(57), 8, y, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(58), 8, y + 10, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(59), 8, y + 20, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(60), 8, y + 30, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(61), 8, y + 40, 0x32, 0x00, 0x9C, 0x20);
+
+ _sound->voicePlay("KING01");
+
+ _chargenWSA->setX(113);
+ _chargenWSA->setY(0);
+ _chargenWSA->setDrawPage(0);
+
+ int index = 4;
+ while (_sound->voiceIsPlaying("KING01") && _charSelection == -1 && !_quitFlag && !skipFlag()) {
+ index = MAX(index, 4);
+
+ _chargenWSA->displayFrame(_chargenFrameTable[index], 0, 0, 0);
+ _screen->copyRegion(_selectionPosTable[_selectionChar1IdxTable[index]*2+0], _selectionPosTable[_selectionChar1IdxTable[index]*2+1], _charPreviews[0].x, _charPreviews[0].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_selectionChar2IdxTable[index]*2+0], _selectionPosTable[_selectionChar2IdxTable[index]*2+1], _charPreviews[1].x, _charPreviews[1].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_selectionChar3IdxTable[index]*2+0], _selectionPosTable[_selectionChar3IdxTable[index]*2+1], _charPreviews[2].x, _charPreviews[2].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_selectionChar4IdxTable[index]*2+0], _selectionPosTable[_selectionChar4IdxTable[index]*2+1], _charPreviews[3].x, _charPreviews[3].y, 32, 32, 4, 0);
+ _screen->updateScreen();
+
+ uint32 waitEnd = _system->getMillis() + 7 * _tickLength;
+ while (waitEnd > _system->getMillis() && _charSelection == -1 && !_quitFlag && !skipFlag()) {
+ _charSelection = getCharSelection();
+ _system->delayMillis(10);
+ }
+
+ index = (index + 1) % 22;
+ }
+
+ resetSkipFlag();
+
+ _chargenWSA->displayFrame(0x10, 0, 0, 0);
+ _screen->updateScreen();
+ _sound->voiceStop("KING01");
+}
+
+void LoLEngine::kingSelectionReminder() {
+ debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionReminder()");
+
+ _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK);
+ int y = 48;
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(62), 8, y, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(63), 8, y + 10, 0x32, 0x00, 0x9C, 0x20);
+
+ _sound->voicePlay("KING02");
+
+ _chargenWSA->setX(113);
+ _chargenWSA->setY(0);
+ _chargenWSA->setDrawPage(0);
+
+ int index = 0;
+ while (_sound->voiceIsPlaying("KING02") && _charSelection == -1 && !_quitFlag && index < 15) {
+ _chargenWSA->displayFrame(_chargenFrameTable[index+9], 0, 0, 0);
+ _screen->copyRegion(_selectionPosTable[_reminderChar1IdxTable[index]*2+0], _selectionPosTable[_reminderChar1IdxTable[index]*2+1], _charPreviews[0].x, _charPreviews[0].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_reminderChar2IdxTable[index]*2+0], _selectionPosTable[_reminderChar2IdxTable[index]*2+1], _charPreviews[1].x, _charPreviews[1].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_reminderChar3IdxTable[index]*2+0], _selectionPosTable[_reminderChar3IdxTable[index]*2+1], _charPreviews[2].x, _charPreviews[2].y, 32, 32, 4, 0);
+ _screen->copyRegion(_selectionPosTable[_reminderChar4IdxTable[index]*2+0], _selectionPosTable[_reminderChar4IdxTable[index]*2+1], _charPreviews[3].x, _charPreviews[3].y, 32, 32, 4, 0);
+ _screen->updateScreen();
+
+ uint32 waitEnd = _system->getMillis() + 8 * _tickLength;
+ while (waitEnd > _system->getMillis() && !_quitFlag) {
+ _charSelection = getCharSelection();
+ _system->delayMillis(10);
+ }
+
+ index = (index + 1) % 22;
+ }
+
+ _sound->voiceStop("KING02");
+}
+
+void LoLEngine::kingSelectionOutro() {
+ debugC(9, kDebugLevelMain, "LoLEngine::kingSelectionOutro()");
+
+ _sound->voicePlay("KING03");
+
+ _chargenWSA->setX(113);
+ _chargenWSA->setY(0);
+ _chargenWSA->setDrawPage(0);
+
+ int index = 0;
+ while (_sound->voiceIsPlaying("KING03") && !_quitFlag && !skipFlag()) {
+ index = MAX(index, 4);
+
+ _chargenWSA->displayFrame(_chargenFrameTable[index], 0, 0, 0);
+ _screen->updateScreen();
+
+ uint32 waitEnd = _system->getMillis() + 8 * _tickLength;
+ while (waitEnd > _system->getMillis() && !_quitFlag && !skipFlag()) {
+ updateInput();
+ _system->delayMillis(10);
+ }
+
+ index = (index + 1) % 22;
+ }
+
+ resetSkipFlag();
+
+ _chargenWSA->displayFrame(0x10, 0, 0, 0);
+ _screen->updateScreen();
+ _sound->voiceStop("KING03");
+}
+
+void LoLEngine::processCharacterSelection() {
+ debugC(9, kDebugLevelMain, "LoLEngine::processCharacterSelection()");
+
+ _charSelection = -1;
+ while (!_quitFlag && _charSelection == -1) {
+ uint32 nextKingMessage = _system->getMillis() + 900 * _tickLength;
+
+ while (nextKingMessage > _system->getMillis() && _charSelection == -1 && !_quitFlag) {
+ updateSelectionAnims();
+ _charSelection = getCharSelection();
+ _system->delayMillis(10);
+ }
+
+ if (_charSelection == -1)
+ kingSelectionReminder();
+ }
+}
+
+void LoLEngine::updateSelectionAnims() {
+ debugC(9, kDebugLevelMain, "LoLEngine::updateSelectionAnims()");
+
+ for (int i = 0; i < 4; ++i) {
+ if (_system->getMillis() < _selectionAnimTimers[i])
+ continue;
+
+ const int index = _selectionAnimIndexTable[_selectionAnimFrames[i] + i * 2];
+ _screen->copyRegion(_selectionPosTable[index*2+0], _selectionPosTable[index*2+1], _charPreviews[i].x, _charPreviews[i].y, 32, 32, 4, 0);
+
+ int delayTime = 0;
+ if (_selectionAnimFrames[i] == 1)
+ delayTime = _rnd.getRandomNumberRng(0, 31) + 80;
+ else
+ delayTime = _rnd.getRandomNumberRng(0, 3) + 10;
+
+ _selectionAnimTimers[i] = _system->getMillis() + delayTime * _tickLength;
+ _selectionAnimFrames[i] = (_selectionAnimFrames[i] + 1) % 2;
+ }
+
+ _screen->updateScreen();
+}
+
+int LoLEngine::selectionCharInfo(int character) {
+ debugC(9, kDebugLevelMain, "LoLEngine::selectionCharInfo(%d)", character);
+ if (character < 0)
+ return -1;
+
+ char filename[16];
+ char vocFilename[6];
+ strcpy(vocFilename, "000X0");
+
+ switch (character) {
+ case 0:
+ strcpy(filename, "face09.shp");
+ vocFilename[3] = 'A';
+ break;
+
+ case 1:
+ strcpy(filename, "face01.shp");
+ vocFilename[3] = 'M';
+ break;
+
+ case 2:
+ strcpy(filename, "face08.shp");
+ vocFilename[3] = 'K';
+ break;
+
+ case 3:
+ strcpy(filename, "face05.shp");
+ vocFilename[3] = 'C';
+ break;
+
+ default:
+ break;
+ };
+
+ _screen->loadBitmap(filename, 9, 9, 0);
+ _screen->copyRegion(0, 122, 0, 122, 320, 78, 4, 0, Screen::CR_NO_P_CHECK);
+ _screen->copyRegion(_charPreviews[character].x - 3, _charPreviews[character].y - 3, 8, 127, 38, 38, 2, 0);
+
+ static const uint8 charSelectInfoIdx[] = { 0x1D, 0x22, 0x27, 0x2C };
+ const int idx = charSelectInfoIdx[character];
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+0), 50, 127, 0x53, 0x00, 0xCF, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+1), 50, 137, 0x53, 0x00, 0xCF, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+2), 50, 147, 0x53, 0x00, 0xCF, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+3), 50, 157, 0x53, 0x00, 0xCF, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(idx+4), 50, 167, 0x53, 0x00, 0xCF, 0x20);
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(69), 100, 168, 0x32, 0x00, 0xCF, 0x20);
+
+ selectionCharInfoIntro(vocFilename);
+ if (_charSelectionInfoResult == -1) {
+ while (_charSelectionInfoResult == -1) {
+ _charSelectionInfoResult = selectionCharAccept();
+ _system->delayMillis(10);
+ }
+ }
+
+ if (_charSelectionInfoResult != 1) {
+ _charSelectionInfoResult = -1;
+ _screen->copyRegion(0, 122, 0, 122, 320, 78, 2, 0, Screen::CR_NO_P_CHECK);
+ _screen->updateScreen();
+ return -1;
+ }
+
+ _screen->copyRegion(48, 127, 48, 127, 272, 60, 4, 0, Screen::CR_NO_P_CHECK);
+ _screen->hideMouse();
+ _screen->copyRegion(48, 127, 48, 160, 272, 35, 4, 0, Screen::CR_NO_P_CHECK);
+ _screen->copyRegion(0, 0, 0, 0, 112, 120, 4, 0, Screen::CR_NO_P_CHECK);
+
+ _screen->fprintStringIntro(_tim->getCTableEntry(64), 3, 28, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(65), 3, 38, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(66), 3, 48, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(67), 3, 58, 0x32, 0x00, 0x9C, 0x20);
+ _screen->fprintStringIntro(_tim->getCTableEntry(68), 3, 68, 0x32, 0x00, 0x9C, 0x20);
+
+ resetSkipFlag();
+ kingSelectionOutro();
+ return character;
+}
+
+void LoLEngine::selectionCharInfoIntro(char *file) {
+ debugC(9, kDebugLevelMain, "LoLEngine::selectionCharInfoIntro(%p)", (const void *)file);
+ int index = 0;
+ file[4] = '0';
+
+ while (_charSelectionInfoResult == -1 && !_quitFlag) {
+ if (!_sound->voicePlay(file))
+ break;
+
+ int i = 0;
+ while (_sound->voiceIsPlaying(file) && _charSelectionInfoResult == -1 && !_quitFlag) {
+ _screen->drawShape(0, _screen->getPtrToShape(_screen->getCPagePtr(9), _charInfoFrameTable[i]), 11, 130, 0, 0);
+ _screen->updateScreen();
+
+ uint32 nextFrame = _system->getMillis() + 8 * _tickLength;
+ while (nextFrame > _system->getMillis() && _charSelectionInfoResult == -1) {
+ _charSelectionInfoResult = selectionCharAccept();
+ _system->delayMillis(10);
+ }
+
+ i = (i + 1) % 32;
+ }
+
+ _sound->voiceStop(file);
+ file[4] = ++index + '0';
+ }
+
+ _screen->drawShape(0, _screen->getPtrToShape(_screen->getCPagePtr(9), 0), 11, 130, 0, 0);
+ _screen->updateScreen();
+}
+
+int LoLEngine::getCharSelection() {
+ int inputFlag = checkInput() & 0xCF;
+ removeInputTop();
+
+ if (inputFlag == 200) {
+ for (int i = 0; i < 4; ++i) {
+ if (_charPreviews[i].x <= _mouseX && _mouseX <= _charPreviews[i].x + 31 &&
+ _charPreviews[i].y <= _mouseY && _mouseY <= _charPreviews[i].y + 31)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+int LoLEngine::selectionCharAccept() {
+ int inputFlag = checkInput() & 0xCF;
+ removeInputTop();
+
+ if (inputFlag == 200) {
+ if (88 <= _mouseX && _mouseX <= 128 && 180 <= _mouseY && _mouseY <= 194)
+ return 1;
+ if (196 <= _mouseX && _mouseX <= 236 && 180 <= _mouseY && _mouseY <= 194)
+ return 0;
+ }
+
+ return -1;
+}
+
+#pragma mark - Opcodes
+
+typedef Common::Functor2Mem<const TIM *, const uint16 *, int, LoLEngine> TIMOpcodeLoL;
+#define SetTimOpcodeTable(x) timTable = &x;
+#define OpcodeTim(x) timTable->push_back(new TIMOpcodeLoL(this, &LoLEngine::x))
+#define OpcodeTimUnImpl() timTable->push_back(new TIMOpcodeLoL(this, 0))
+
+void LoLEngine::setupOpcodeTable() {
+ Common::Array<const TIMOpcode*> *timTable = 0;
+
+ SetTimOpcodeTable(_timIntroOpcodes);
+
+ // 0x00
+ OpcodeTim(tlol_setupPaletteFade);
+ OpcodeTimUnImpl();
+ OpcodeTim(tlol_loadPalette);
+ OpcodeTim(tlol_setupPaletteFadeEx);
+
+ // 0x04
+ OpcodeTim(tlol_processWsaFrame);
+ OpcodeTim(tlol_displayText);
+ OpcodeTimUnImpl();
+ OpcodeTimUnImpl();
+}
+
+#pragma mark -
+
+int LoLEngine::tlol_setupPaletteFade(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::t2_playSoundEffect(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]);
+ _screen->getFadeParams(_screen->getPalette(0), param[0], _tim->_palDelayInc, _tim->_palDiff);
+ _tim->_palDelayAcc = 0;
+ return 1;
+}
+
+int LoLEngine::tlol_loadPalette(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_loadPalette(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]);
+ const char *palFile = (const char *)(tim->text + READ_LE_UINT16(tim->text + (param[0]<<1)));
+ _res->loadFileToBuf(palFile, _screen->getPalette(0), 768);
+ return 1;
+}
+
+int LoLEngine::tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_setupPaletteFadeEx(%p, %p) (%d)", (const void*)tim, (const void*)param, param[0]);
+ memcpy(_screen->getPalette(0), _screen->getPalette(1), 768);
+
+ _screen->getFadeParams(_screen->getPalette(0), param[0], _tim->_palDelayInc, _tim->_palDiff);
+ _tim->_palDelayAcc = 0;
+ return 1;
+}
+
+int LoLEngine::tlol_processWsaFrame(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_processWsaFrame(%p, %p) (%d, %d, %d, %d, %d)",
+ (const void*)tim, (const void*)param, param[0], param[1], param[2], param[3], param[4]);
+ TIMInterpreter::Animation *anim = (TIMInterpreter::Animation *)tim->wsa[param[0]].anim;
+ const int frame = param[1];
+ const int x2 = param[2];
+ const int y2 = param[3];
+ const int factor = MAX<int>(0, (int16)param[4]);
+
+ const int x1 = anim->x;
+ const int y1 = anim->y;
+
+ int w1 = anim->wsa->width();
+ int h1 = anim->wsa->height();
+ int w2 = (w1 * factor) / 100;
+ int h2 = (h1 * factor) / 100;
+
+ anim->wsa->setDrawPage(2);
+ anim->wsa->setX(x1);
+ anim->wsa->setY(y1);
+ anim->wsa->displayFrame(frame, anim->wsaCopyParams & 0xF0FF, 0, 0);
+ _screen->wsaFrameAnimationStep(x1, y1, x2, y2, w1, h1, w2, h2, 2, 8, 0);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+
+ return 1;
+}
+
+int LoLEngine::tlol_displayText(const TIM *tim, const uint16 *param) {
+ debugC(3, kDebugLevelScriptFuncs, "LoLEngine::tlol_displayText(%p, %p) (%d, %d)", (const void*)tim, (const void*)param, param[0], (int16)param[1]);
+ _tim->displayText(param[0], param[1]);
+ return 1;
+}
+
+} // end of namespace Kyra
+
diff --git a/engines/kyra/lol.h b/engines/kyra/lol.h
new file mode 100644
index 0000000000..ee54f8abbb
--- /dev/null
+++ b/engines/kyra/lol.h
@@ -0,0 +1,155 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef KYRA_LOL_H
+#define KYRA_LOL_H
+
+#include "kyra/kyra_v1.h"
+#include "kyra/script_tim.h"
+
+#include "common/list.h"
+
+namespace Kyra {
+
+class Screen_LoL;
+class WSAMovie_v2;
+struct Button;
+
+class LoLEngine : public KyraEngine_v1 {
+public:
+ LoLEngine(OSystem *system, const GameFlags &flags);
+ ~LoLEngine();
+
+ Screen *screen();
+private:
+ Screen_LoL *_screen;
+ TIMInterpreter *_tim;
+
+ int init();
+ int go();
+
+ // input
+ void updateInput();
+ int checkInput(Button *buttonList = 0, bool mainLoop = false);
+ void removeInputTop();
+
+ int _mouseX, _mouseY;
+
+ struct Event {
+ Common::Event event;
+ bool causedSkip;
+
+ Event() : event(), causedSkip(false) {}
+ Event(Common::Event e) : event(e), causedSkip(false) {}
+ Event(Common::Event e, bool skip) : event(e), causedSkip(skip) {}
+
+ operator Common::Event() const { return event; }
+ };
+ Common::List<Event> _eventList;
+
+ virtual bool skipFlag() const;
+ virtual void resetSkipFlag(bool removeEvent = true);
+
+ // intro
+ void setupPrologueData(bool load);
+
+ void showIntro();
+
+ struct CharacterPrev {
+ const char *name;
+ int x, y;
+ int attrib[3];
+ };
+
+ static const CharacterPrev _charPreviews[];
+
+ WSAMovie_v2 *_chargenWSA;
+ static const uint8 _chargenFrameTable[];
+ int chooseCharacter();
+
+ void kingSelectionIntro();
+ void kingSelectionReminder();
+ void kingSelectionOutro();
+ void processCharacterSelection();
+ void updateSelectionAnims();
+ int selectionCharInfo(int character);
+ void selectionCharInfoIntro(char *file);
+
+ int getCharSelection();
+ int selectionCharAccept();
+
+ int _charSelection;
+ int _charSelectionInfoResult;
+
+ uint32 _selectionAnimTimers[4];
+ uint8 _selectionAnimFrames[4];
+ static const uint8 _selectionAnimIndexTable[];
+
+ static const uint16 _selectionPosTable[];
+
+ static const uint8 _selectionChar1IdxTable[];
+ static const uint8 _selectionChar2IdxTable[];
+ static const uint8 _selectionChar3IdxTable[];
+ static const uint8 _selectionChar4IdxTable[];
+
+ static const uint8 _reminderChar1IdxTable[];
+ static const uint8 _reminderChar2IdxTable[];
+ static const uint8 _reminderChar3IdxTable[];
+ static const uint8 _reminderChar4IdxTable[];
+
+ static const uint8 _charInfoFrameTable[];
+
+ // timer
+ void setupTimers() {}
+
+ // sound
+ void snd_playVoiceFile(int) { /* XXX */ }
+
+ // opcode
+ void setupOpcodeTable();
+
+ Common::Array<const TIMOpcode*> _timIntroOpcodes;
+ int tlol_setupPaletteFade(const TIM *tim, const uint16 *param);
+ int tlol_loadPalette(const TIM *tim, const uint16 *param);
+ int tlol_setupPaletteFadeEx(const TIM *tim, const uint16 *param);
+ int tlol_processWsaFrame(const TIM *tim, const uint16 *param);
+ int tlol_displayText(const TIM *tim, const uint16 *param);
+
+ // translation
+ int _lang;
+
+ static const char * const _languageExt[];
+
+ // unneeded
+ void setWalkspeed(uint8) {}
+ void setHandItem(uint16) {}
+ void removeHandItem() {}
+ bool lineIsPassable(int, int) { return false; }
+};
+
+} // end of namespace Kyra
+
+#endif
+
diff --git a/engines/kyra/module.mk b/engines/kyra/module.mk
index ebb63b4b4e..e059a8ce4b 100644
--- a/engines/kyra/module.mk
+++ b/engines/kyra/module.mk
@@ -21,6 +21,7 @@ MODULE_OBJS := \
kyra_v2.o \
kyra_hof.o \
kyra_mr.o \
+ lol.o \
resource.o \
saveload.o \
saveload_lok.o \
@@ -33,6 +34,7 @@ MODULE_OBJS := \
scene_mr.o \
screen.o \
screen_lok.o \
+ screen_lol.o \
screen_v2.o \
screen_hof.o \
screen_mr.o \
diff --git a/engines/kyra/resource.cpp b/engines/kyra/resource.cpp
index afd7eacfda..5d3c5ff715 100644
--- a/engines/kyra/resource.cpp
+++ b/engines/kyra/resource.cpp
@@ -99,6 +99,8 @@ bool Resource::reset() {
loadFileList("FILEDATA.FDT");
return true;
+ } else if (_vm->game() == GI_LOL) {
+ return true;
}
FSList fslist;
@@ -1120,7 +1122,7 @@ bool FileExpander::process(uint8 *dst, const uint8 *src, uint32 outsize, uint32
void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex2, int cnt) {
const uint8 *tbl1 = _tables[srcIndex];
- const uint8 *tbl2 = _tables[dstIndex];
+ uint8 *tbl2 = _tables[dstIndex];
const uint8 *tbl3 = dstIndex2 == 0xff ? 0 : _tables[dstIndex2];
if (!cnt)
@@ -1185,7 +1187,7 @@ void FileExpander::generateTables(uint8 srcIndex, uint8 dstIndex, uint8 dstIndex
}
}
- memset((void*) tbl2, 0, 512);
+ memset(tbl2, 0, 512);
cnt--;
s = tbl1 + cnt;
diff --git a/engines/kyra/saveload.cpp b/engines/kyra/saveload.cpp
index 147c774a52..0dc7cf2c02 100644
--- a/engines/kyra/saveload.cpp
+++ b/engines/kyra/saveload.cpp
@@ -95,6 +95,9 @@ KyraEngine_v1::kReadSaveHeaderError KyraEngine_v1::readSaveHeader(Common::InSave
if (header.version <= 8) {
char buffer[31];
in->read(buffer, 31);
+ // WORKAROUND: Old savegames could contain a missing termination 0 at the
+ // end so we manually add it.
+ buffer[30] = 0;
header.description = buffer;
} else {
header.description = "";
diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp
index 6e7a88b1a3..0cde066cc0 100644
--- a/engines/kyra/screen.cpp
+++ b/engines/kyra/screen.cpp
@@ -380,61 +380,23 @@ void Screen::fadePalette(const uint8 *palData, int delay, const UpdateFunctor *u
debugC(9, kDebugLevelScreen, "Screen::fadePalette(%p, %d, %p)", (const void *)palData, delay, (const void*)upFunc);
updateScreen();
- uint8 fadePal[768];
- memcpy(fadePal, _screenPalette, 768);
- uint8 diff, maxDiff = 0;
- for (int i = 0; i < 768; ++i) {
- diff = ABS(palData[i] - fadePal[i]);
- if (diff > maxDiff) {
- maxDiff = diff;
- }
- }
-
- int16 delayInc = delay << 8;
- if (maxDiff != 0)
- delayInc /= maxDiff;
-
- delay = delayInc;
- for (diff = 1; diff <= maxDiff; ++diff) {
- if (delayInc >= 512)
- break;
- delayInc += delay;
- }
+ int diff = 0, delayInc = 0;
+ getFadeParams(palData, delay, delayInc, diff);
int delayAcc = 0;
while (!_vm->quit()) {
delayAcc += delayInc;
- bool needRefresh = false;
- for (int i = 0; i < 768; ++i) {
- int c1 = palData[i];
- int c2 = fadePal[i];
- if (c1 != c2) {
- needRefresh = true;
- if (c1 > c2) {
- c2 += diff;
- if (c1 < c2)
- c2 = c1;
- }
-
- if (c1 < c2) {
- c2 -= diff;
- if (c1 > c2)
- c2 = c1;
- }
- fadePal[i] = (uint8)c2;
- }
- }
-
- if (!needRefresh)
- break;
+ int refreshed = fadePalStep(palData, diff);
- setScreenPalette(fadePal);
if (upFunc && upFunc->isValid())
(*upFunc)();
else
_system->updateScreen();
- //_system->delayMillis((delayAcc >> 8) * 1000 / 60);
+
+ if (!refreshed)
+ break;
+
_vm->delay((delayAcc >> 8) * 1000 / 60);
delayAcc &= 0xFF;
}
@@ -448,6 +410,64 @@ void Screen::fadePalette(const uint8 *palData, int delay, const UpdateFunctor *u
}
}
+void Screen::getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff) {
+ debugC(9, kDebugLevelScreen, "Screen::getFadeParams(%p, %d, %p, %p)", (const void *)palette, delay, (const void *)&delayInc, (const void *)&diff);
+ uint8 maxDiff = 0;
+
+ const int colors = (_vm->gameFlags().platform == Common::kPlatformAmiga ? 32 : 256) * 3;
+ for (int i = 0; i < colors; ++i) {
+ diff = ABS(palette[i] - _screenPalette[i]);
+ maxDiff = MAX<uint8>(maxDiff, diff);
+ }
+
+ delayInc = delay << 8;
+ if (maxDiff != 0)
+ delayInc /= maxDiff;
+ delayInc &= 0x7FFF;
+
+ delay = delayInc;
+ for (diff = 1; diff <= maxDiff; ++diff) {
+ if (delayInc >= 512)
+ break;
+ delayInc += delay;
+ }
+}
+
+int Screen::fadePalStep(const uint8 *palette, int diff) {
+ debugC(9, kDebugLevelScreen, "Screen::fadePalStep(%p, %d)", (const void *)palette, diff);
+
+ uint8 fadePal[768];
+ memcpy(fadePal, _screenPalette, 768);
+
+ bool needRefresh = false;
+ const int colors = (_vm->gameFlags().platform == Common::kPlatformAmiga ? 32 : 256) * 3;
+ for (int i = 0; i < colors; ++i) {
+ int c1 = palette[i];
+ int c2 = fadePal[i];
+ if (c1 != c2) {
+ needRefresh = true;
+ if (c1 > c2) {
+ c2 += diff;
+ if (c1 < c2)
+ c2 = c1;
+ }
+
+ if (c1 < c2) {
+ c2 -= diff;
+ if (c1 > c2)
+ c2 = c1;
+ }
+
+ fadePal[i] = (uint8)c2;
+ }
+ }
+
+ if (needRefresh)
+ setScreenPalette(fadePal);
+
+ return needRefresh ? 1 : 0;
+}
+
void Screen::setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue) {
debugC(9, kDebugLevelScreen, "Screen::setPaletteIndex(%u, %u, %u, %u)", index, red, green, blue);
_currentPalette[index * 3 + 0] = red;
@@ -459,7 +479,7 @@ void Screen::setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue) {
void Screen::setScreenPalette(const uint8 *palData) {
debugC(9, kDebugLevelScreen, "Screen::setScreenPalette(%p)", (const void *)palData);
- int colors = (_vm->gameFlags().platform == Common::kPlatformAmiga ? 32 : 256);
+ const int colors = (_vm->gameFlags().platform == Common::kPlatformAmiga ? 32 : 256);
if (palData != _screenPalette)
memcpy(_screenPalette, palData, colors*3);
@@ -2442,7 +2462,7 @@ void Screen::setShapePages(int page1, int page2, int minY, int maxY) {
_maskMaxY = maxY;
}
-void Screen::setMouseCursor(int x, int y, byte *shape) {
+void Screen::setMouseCursor(int x, int y, const byte *shape) {
debugC(9, kDebugLevelScreen, "Screen::setMouseCursor(%d, %d, %p)", x, y, (const void *)shape);
if (!shape)
return;
diff --git a/engines/kyra/screen.h b/engines/kyra/screen.h
index f8c85a2bac..99ba2d7c5f 100644
--- a/engines/kyra/screen.h
+++ b/engines/kyra/screen.h
@@ -89,10 +89,12 @@ public:
enum FontId {
FID_6_FNT = 0,
FID_8_FNT,
+ FID_9_FNT,
FID_CRED6_FNT,
FID_CRED8_FNT,
FID_BOOKFONT_FNT,
FID_GOLDFONT_FNT,
+ FID_INTRO_FNT,
FID_NUM
};
@@ -145,6 +147,8 @@ public:
void fadeToBlack(int delay=0x54, const UpdateFunctor *upFunc = 0);
void fadePalette(const uint8 *palData, int delay, const UpdateFunctor *upFunc = 0);
+ virtual void getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff);
+ int fadePalStep(const uint8 *palette, int diff);
void setPaletteIndex(uint8 index, uint8 red, uint8 green, uint8 blue);
void setScreenPalette(const uint8 *palData);
@@ -189,7 +193,7 @@ public:
void hideMouse();
void showMouse();
bool isMouseVisible() const;
- void setMouseCursor(int x, int y, byte *shape);
+ void setMouseCursor(int x, int y, const byte *shape);
// rect handling
virtual int getRectSize(int w, int h) = 0;
diff --git a/engines/kyra/screen_lol.cpp b/engines/kyra/screen_lol.cpp
new file mode 100644
index 0000000000..c6b47a9ca9
--- /dev/null
+++ b/engines/kyra/screen_lol.cpp
@@ -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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "kyra/screen_lol.h"
+#include "kyra/lol.h"
+
+namespace Kyra {
+
+Screen_LoL::Screen_LoL(LoLEngine *vm, OSystem *system) : Screen_v2(vm, system), _vm(vm) {
+}
+
+void Screen_LoL::setScreenDim(int dim) {
+ debugC(9, kDebugLevelScreen, "Screen_LoL::setScreenDim(%d)", dim);
+ assert(dim < _screenDimTableCount);
+ _curDim = &_screenDimTable[dim];
+}
+
+const ScreenDim *Screen_LoL::getScreenDim(int dim) {
+ debugC(9, kDebugLevelScreen, "Screen_LoL::getScreenDim(%d)", dim);
+ assert(dim < _screenDimTableCount);
+ return &_screenDimTable[dim];
+}
+
+void Screen_LoL::fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint16 flags, ...) {
+ debugC(9, kDebugLevelScreen, "Screen_LoL::fprintStringIntro('%s', %d, %d, %d, %d, %d, %d, ...)", format, x, y, c1, c2, c3, flags);
+ char buffer[400];
+
+ va_list args;
+ va_start(args, flags);
+ vsprintf(buffer, format, args);
+ va_end(args);
+
+ if ((flags & 0x0F00) == 0x100)
+ x -= getTextWidth(buffer) >> 1;
+ if ((flags & 0x0F00) == 0x200)
+ x -= getTextWidth(buffer);
+
+ if ((flags & 0x00F0) == 0x20) {
+ printText(buffer, x-1, y, c3, c2);
+ printText(buffer, x, y+1, c3, c2);
+ }
+
+ printText(buffer, x, y, c1, c2);
+}
+
+} // end of namespace Kyra
+
diff --git a/engines/kyra/screen_lol.h b/engines/kyra/screen_lol.h
new file mode 100644
index 0000000000..38df3ca897
--- /dev/null
+++ b/engines/kyra/screen_lol.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef KYRA_SCREEN_LOL_H
+#define KYRA_SCREEN_LOL_H
+
+#include "kyra/screen_v2.h"
+
+namespace Kyra {
+
+class LoLEngine;
+
+class Screen_LoL : public Screen_v2 {
+public:
+ Screen_LoL(LoLEngine *vm, OSystem *system);
+
+ void setScreenDim(int dim);
+ const ScreenDim *getScreenDim(int dim);
+
+ void fprintStringIntro(const char *format, int x, int y, uint8 c1, uint8 c2, uint8 c3, uint16 flags, ...);
+private:
+ LoLEngine *_vm;
+
+ static const ScreenDim _screenDimTable[];
+ static const int _screenDimTableCount;
+};
+
+} // end of namespace Kyra
+
+#endif
+
diff --git a/engines/kyra/screen_v2.cpp b/engines/kyra/screen_v2.cpp
index e26ef87bad..c6ea6a93e8 100644
--- a/engines/kyra/screen_v2.cpp
+++ b/engines/kyra/screen_v2.cpp
@@ -111,6 +111,30 @@ int Screen_v2::findLeastDifferentColor(const uint8 *paletteEntry, const uint8 *p
return r;
}
+void Screen_v2::getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff) {
+ debugC(9, kDebugLevelScreen, "Screen_v2::getFadeParams(%p, %d, %p, %p)", (const void *)palette, delay, (const void *)&delayInc, (const void *)&diff);
+
+ int maxDiff = 0;
+ diff = 0;
+ for (int i = 0; i < 768; ++i) {
+ diff = ABS(palette[i] - _screenPalette[i]);
+ maxDiff = MAX(maxDiff, diff);
+ }
+
+ delayInc = delay << 8;
+ if (maxDiff != 0) {
+ delayInc /= maxDiff;
+ delayInc = MIN(delayInc, 0x7FFF);
+ }
+
+ delay = delayInc;
+ for (diff = 1; diff <= maxDiff; ++diff) {
+ if (delayInc >= 256)
+ break;
+ delayInc += delay;
+ }
+}
+
void Screen_v2::copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src,
int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2) {
uint8 *dstPtr = getPagePtr(_curPage);
@@ -369,7 +393,7 @@ void Screen_v2::wsaFrameAnimationStep(int x1, int y1, int x2, int y2,
int t = (nb * h1) / h2;
if (t != u) {
u = t;
- const uint8 *s = src + (x1 + t) * 320;
+ const uint8 *s = src + x1 + t * 320;
uint8 *dt = (uint8 *)_wsaFrameAnimBuffer;
t = w2 - w1;
@@ -461,5 +485,27 @@ bool Screen_v2::calcBounds(int w0, int h0, int &x1, int &y1, int &w1, int &h1, i
return (w1 == -1) ? false : true;
}
+void Screen_v2::checkedPageUpdate(int srcPage, int dstPage) {
+ debugC(9, kDebugLevelScreen, "Screen_v2::checkedPageUpdate(%d, %d)", srcPage, dstPage);
+
+ const uint32 *src = (const uint32 *)getPagePtr(srcPage);
+ uint32 *dst = (uint32 *)getPagePtr(dstPage);
+ uint32 *page0 = (uint32 *)getPagePtr(0);
+
+ bool updated = false;
+
+ for (int y = 0; y < 200; ++y) {
+ for (int x = 0; x < 80; ++x, ++src, ++dst, ++page0) {
+ if (*src != *dst) {
+ updated = true;
+ *dst = *page0 = *src;
+ }
+ }
+ }
+
+ if (updated)
+ addDirtyRect(0, 0, 320, 200);
+}
+
} // end of namespace Kyra
diff --git a/engines/kyra/screen_v2.h b/engines/kyra/screen_v2.h
index f624228445..7bbdc4b6c3 100644
--- a/engines/kyra/screen_v2.h
+++ b/engines/kyra/screen_v2.h
@@ -40,10 +40,14 @@ public:
void copyWsaRect(int x, int y, int w, int h, int dimState, int plotFunc, const uint8 *src,
int unk1, const uint8 *unkPtr1, const uint8 *unkPtr2);
+ void checkedPageUpdate(int srcPage, int dstPage);
+
// palette handling
uint8 *generateOverlay(const uint8 *palette, uint8 *buffer, int color, uint16 factor);
void applyOverlay(int x, int y, int w, int h, int pageNum, const uint8 *overlay);
int findLeastDifferentColor(const uint8 *paletteEntry, const uint8 *palette, uint16 numColors);
+
+ virtual void getFadeParams(const uint8 *palette, int delay, int &delayInc, int &diff);
// shape handling
uint8 *getPtrToShape(uint8 *shpFile, int shape);
diff --git a/engines/kyra/script_hof.cpp b/engines/kyra/script_hof.cpp
index 94f160d11f..e3a8bf95bc 100644
--- a/engines/kyra/script_hof.cpp
+++ b/engines/kyra/script_hof.cpp
@@ -1492,7 +1492,7 @@ typedef Common::Functor1Mem<EMCState*, int, KyraEngine_HoF> OpcodeV2;
typedef Common::Functor2Mem<const TIM*, const uint16*, int, KyraEngine_HoF> TIMOpcodeV2;
#define OpcodeTim(x) _timOpcodes.push_back(new TIMOpcodeV2(this, &KyraEngine_HoF::x))
-#define OpcodeTimUnImpl() _timOpcodes.push_back(TIMOpcodeV2(this, 0))
+#define OpcodeTimUnImpl() _timOpcodes.push_back(new TIMOpcodeV2(this, 0))
void KyraEngine_HoF::setupOpcodeTable() {
Common::Array<const Opcode*> *table = 0;
diff --git a/engines/kyra/script_tim.cpp b/engines/kyra/script_tim.cpp
index 4b82232049..9b215b2c03 100644
--- a/engines/kyra/script_tim.cpp
+++ b/engines/kyra/script_tim.cpp
@@ -26,12 +26,14 @@
#include "kyra/script_tim.h"
#include "kyra/script.h"
#include "kyra/resource.h"
+#include "kyra/sound.h"
+#include "kyra/wsamovie.h"
#include "common/endian.h"
namespace Kyra {
-TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, OSystem *system) : _vm(vm), _system(system), _currentTim(0) {
+TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, Screen_v2 *screen, OSystem *system) : _vm(vm), _screen(screen), _system(system), _currentTim(0) {
#define COMMAND(x) { &TIMInterpreter::x, #x }
#define COMMAND_UNIMPL() { 0, 0 }
#define cmd_return(n) cmd_return_##n
@@ -39,32 +41,32 @@ TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, OSystem *system) : _vm(vm), _s
// 0x00
COMMAND(cmd_initFunc0),
COMMAND(cmd_stopCurFunc),
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_initWSA),
+ COMMAND(cmd_uninitWSA),
// 0x04
COMMAND(cmd_initFunc),
COMMAND(cmd_stopFunc),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_wsaDisplayFrame),
COMMAND_UNIMPL(),
// 0x08
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_loadVocFile),
+ COMMAND(cmd_unloadVocFile),
+ COMMAND(cmd_playVocFile),
COMMAND_UNIMPL(),
// 0x0C
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_loadSoundFile),
+ COMMAND(cmd_return(1)),
+ COMMAND(cmd_playMusicTrack),
COMMAND_UNIMPL(),
// 0x10
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_return(1)),
+ COMMAND(cmd_return(1)),
COMMAND_UNIMPL(),
COMMAND_UNIMPL(),
// 0x14
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
- COMMAND_UNIMPL(),
+ COMMAND(cmd_setLoopIp),
+ COMMAND(cmd_continueLoop),
+ COMMAND(cmd_resetLoopIp),
COMMAND(cmd_resetAllRuntimes),
// 0x18
COMMAND(cmd_return(1)),
@@ -80,6 +82,19 @@ TIMInterpreter::TIMInterpreter(KyraEngine_v1 *vm, OSystem *system) : _vm(vm), _s
_commands = commandProcs;
_commandsSize = ARRAYSIZE(commandProcs);
+
+ memset(&_animations, 0, sizeof(_animations));
+ _langData = 0;
+ _textDisplayed = false;
+ _textAreaBuffer = new uint8[320*40];
+ assert(_textAreaBuffer);
+
+ _palDelayInc = _palDiff = _palDelayAcc = 0;
+}
+
+TIMInterpreter::~TIMInterpreter() {
+ delete[] _langData;
+ delete[] _textAreaBuffer;
}
TIM *TIMInterpreter::load(const char *filename, const Common::Array<const TIMOpcode*> *opcodes) {
@@ -139,6 +154,11 @@ void TIMInterpreter::unload(TIM *&tim) const {
tim = 0;
}
+void TIMInterpreter::setLangData(const char *filename) {
+ delete[] _langData;
+ _langData = _vm->resource()->fileData(filename, 0);
+}
+
void TIMInterpreter::exec(TIM *tim, bool loop) {
if (!tim)
return;
@@ -175,6 +195,10 @@ void TIMInterpreter::exec(TIM *tim, bool loop) {
_currentTim->procFunc = _currentFunc;
break;
+ case 22:
+ cur.loopIp = 0;
+ break;
+
default:
break;
}
@@ -201,6 +225,205 @@ void TIMInterpreter::refreshTimersAfterPause(uint32 elapsedTime) {
}
}
+void TIMInterpreter::displayText(uint16 textId, int16 flags) {
+ char *text = getTableEntry(textId);
+
+ if (_textDisplayed) {
+ _screen->copyBlockToPage(0, 0, 160, 320, 40, _textAreaBuffer);
+ _textDisplayed = false;
+ }
+
+ if (!text)
+ return;
+ if (!text[0])
+ return;
+
+ char filename[16];
+ memset(filename, 0, sizeof(filename));
+
+ if (text[0] == '$') {
+ const char *end = strchr(text+1, '$');
+ if (end)
+ memcpy(filename, text+1, end-1-text);
+ }
+
+ if (filename[0])
+ _vm->sound()->voicePlay(filename);
+
+ if (text[0] == '$')
+ text = strchr(text + 1, '$') + 1;
+
+ setupTextPalette((flags < 0) ? 1 : flags, 0);
+
+ if (flags < 0) {
+ static const uint8 colorMap[] = { 0x00, 0xF0, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+
+ _screen->setFont(Screen::FID_8_FNT);
+ _screen->setTextColorMap(colorMap);
+ _screen->_charWidth = -2;
+ }
+
+ _screen->_charOffset = -4;
+ _screen->copyRegionToBuffer(0, 0, 160, 320, 40, _textAreaBuffer);
+ _textDisplayed = true;
+
+ char backupChar = 0;
+ char *str = text;
+ int heightAdd = 0;
+
+ while (str[0]) {
+ char *nextLine = strchr(str, '\r');
+
+ backupChar = 0;
+ if (nextLine) {
+ backupChar = nextLine[0];
+ nextLine[0] = '\0';
+ }
+
+ int width = _screen->getTextWidth(str);
+
+ if (flags >= 0)
+ _screen->printText(str, (320 - width) >> 1, 160 + heightAdd, 0xF0, 0x00);
+ else
+ _screen->printText(str, (320 - width) >> 1, 188, 0xF0, 0x00);
+
+ heightAdd += _screen->getFontHeight();
+ str += strlen(str);
+
+ if (backupChar) {
+ nextLine[0] = backupChar;
+ ++str;
+ }
+ }
+
+ _screen->_charOffset = 0;
+
+ if (flags < 0) {
+ static const uint8 colorMap[] = { 0x00, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0x00, 0x00, 0x00, 0x00 };
+
+ _screen->setFont(Screen::FID_INTRO_FNT);
+ _screen->setTextColorMap(colorMap);
+ _screen->_charWidth = 0;
+ }
+}
+
+void TIMInterpreter::setupTextPalette(uint index, int fadePalette) {
+ static const uint16 palTable[] = {
+ 0x00, 0x00, 0x00,
+ 0x64, 0x64, 0x64,
+ 0x61, 0x51, 0x30,
+ 0x29, 0x48, 0x64,
+ 0x00, 0x4B, 0x3B,
+ 0x64, 0x1E, 0x1E,
+ };
+
+ for (int i = 0; i < 15; ++i) {
+ uint8 *palette = _screen->getPalette(0) + (240 + i) * 3;
+
+ uint8 c1 = (((15 - i) << 2) * palTable[index*3+0]) / 100;
+ uint8 c2 = (((15 - i) << 2) * palTable[index*3+1]) / 100;
+ uint8 c3 = (((15 - i) << 2) * palTable[index*3+2]) / 100;
+
+ palette[0] = c1;
+ palette[1] = c2;
+ palette[2] = c3;
+ }
+
+ if (!fadePalette && !_palDiff) {
+ _screen->setScreenPalette(_screen->getPalette(0));
+ } else {
+ _screen->getFadeParams(_screen->getPalette(0), fadePalette, _palDelayInc, _palDiff);
+ _palDelayAcc = 0;
+ }
+}
+
+TIMInterpreter::Animation *TIMInterpreter::initAnimStruct(int index, const char *filename, int x, int y, int, int offscreenBuffer, uint16 wsaFlags) {
+ Animation *anim = &_animations[index];
+ anim->x = x;
+ anim->y = y;
+ anim->wsaCopyParams = wsaFlags;
+
+ uint16 wsaOpenFlags = ((wsaFlags & 0x10) != 0) ? 2 : 0;
+
+ char file[32];
+ snprintf(file, 32, "%s.WSA", filename);
+
+ if (_vm->resource()->exists(file)) {
+ anim->wsa = new WSAMovie_v2(_vm, _screen);
+ assert(anim->wsa);
+
+ anim->wsa->open(file, wsaOpenFlags, (index == 1) ? _screen->getPalette(0) : 0);
+ }
+
+ if (anim->wsa && anim->wsa->opened()) {
+ if (x == -1)
+ anim->x = x = 0;
+ if (y == -1)
+ anim->y = y = 0;
+
+ if (wsaFlags & 2) {
+ _screen->fadePalette(_screen->getPalette(1), 15, 0);
+ _screen->clearPage(8);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+ }
+
+ if (wsaFlags & 4) {
+ snprintf(file, 32, "%s.CPS", filename);
+
+ if (_vm->resource()->exists(file)) {
+ _screen->loadBitmap(file, 3, 3, _screen->getPalette(0));
+ _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 8, Screen::CR_NO_P_CHECK);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+ }
+
+ anim->wsa->setX(x);
+ anim->wsa->setY(y);
+ anim->wsa->setDrawPage(0);
+ anim->wsa->displayFrame(0, 0, 0, 0);
+ }
+
+ if (wsaFlags & 2)
+ _screen->fadePalette(_screen->getPalette(0), 30, 0);
+ } else {
+ if (wsaFlags & 2) {
+ _screen->fadePalette(_screen->getPalette(1), 15, 0);
+ _screen->clearPage(8);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+ }
+
+ snprintf(file, 32, "%s.CPS", filename);
+
+ if (_vm->resource()->exists(file)) {
+ _screen->loadBitmap(file, 3, 3, _screen->getPalette(0));
+ _screen->copyRegion(0, 0, 0, 0, 320, 200, 2, 8, Screen::CR_NO_P_CHECK);
+ _screen->checkedPageUpdate(8, 4);
+ _screen->updateScreen();
+ }
+
+ if (wsaFlags & 2)
+ _screen->fadePalette(_screen->getPalette(0), 30, 0);
+ }
+
+ return anim;
+}
+
+char *TIMInterpreter::getTableEntry(uint idx) {
+ if (!_langData)
+ return 0;
+ else
+ return (char *)(_langData + READ_LE_UINT16(_langData + (idx<<1)));
+}
+
+const char *TIMInterpreter::getCTableEntry(uint idx) const {
+ if (!_langData)
+ return 0;
+ else
+ return (const char *)(_langData + READ_LE_UINT16(_langData + (idx<<1)));
+}
+
int TIMInterpreter::execCommand(int cmd, const uint16 *param) {
if (cmd < 0 || cmd >= _commandsSize) {
warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename);
@@ -212,11 +435,14 @@ int TIMInterpreter::execCommand(int cmd, const uint16 *param) {
return 0;
}
- debugC(5, kDebugLevelScript, "TIMInterpreter::%s(%p)", _commands[cmd].desc, (const void*)param);
+ debugC(5, kDebugLevelScript, "TIMInterpreter::%s(%p)", _commands[cmd].desc, (const void* )param);
return (this->*_commands[cmd].proc)(param);
}
int TIMInterpreter::cmd_initFunc0(const uint16 *param) {
+ for (int i = 0; i < TIM::kWSASlots; ++i)
+ memset(&_currentTim->wsa[i], 0, sizeof(TIM::WSASlot));
+
_currentTim->func[0].ip = _currentTim->func[0].avtl;
_currentTim->func[0].lastTime = _system->getMillis();
return 1;
@@ -230,6 +456,46 @@ int TIMInterpreter::cmd_stopCurFunc(const uint16 *param) {
return -2;
}
+int TIMInterpreter::cmd_initWSA(const uint16 *param) {
+ const int index = param[0];
+
+ TIM::WSASlot &slot = _currentTim->wsa[index];
+
+ slot.x = int16(param[2]);
+ slot.y = int16(param[3]);
+ slot.offscreen = param[4];
+ slot.wsaFlags = param[5];
+ const char *filename = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (param[1]<<1)));
+
+ slot.anim = initAnimStruct(index, filename, slot.x, slot.y, 10, slot.offscreen, slot.wsaFlags);
+ return 1;
+}
+
+int TIMInterpreter::cmd_uninitWSA(const uint16 *param) {
+ const int index = param[0];
+
+ TIM::WSASlot &slot = _currentTim->wsa[index];
+
+ if (!slot.anim)
+ return 0;
+
+ Animation &anim = _animations[index];
+
+ if (slot.offscreen) {
+ delete anim.wsa;
+ anim.wsa = 0;
+ slot.anim = 0;
+ } else {
+ //XXX
+
+ delete anim.wsa;
+ memset(&anim, 0, sizeof(Animation));
+ memset(&slot, 0, sizeof(TIM::WSASlot));
+ }
+
+ return 1;
+}
+
int TIMInterpreter::cmd_initFunc(const uint16 *param) {
uint16 func = *param;
assert(func < TIM::kCountFuncs);
@@ -247,6 +513,89 @@ int TIMInterpreter::cmd_stopFunc(const uint16 *param) {
return 1;
}
+int TIMInterpreter::cmd_wsaDisplayFrame(const uint16 *param) {
+ Animation &anim = _animations[param[0]];
+ const int frame = param[1];
+
+ anim.wsa->setX(anim.x);
+ anim.wsa->setY(anim.y);
+ anim.wsa->setDrawPage((anim.wsaCopyParams & 0x4000) != 0 ? 2 : 8);
+ anim.wsa->displayFrame(frame, anim.wsaCopyParams & 0xF0FF, 0, 0);
+ return 1;
+}
+
+int TIMInterpreter::cmd_displayText(const uint16 *param) {
+ displayText(param[0], param[1]);
+ return 1;
+}
+
+int TIMInterpreter::cmd_loadVocFile(const uint16 *param) {
+ const int stringId = param[0];
+ const int index = param[1];
+
+ _vocFiles[index] = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (stringId << 1)));
+ for (int i = 0; i < 4; ++i)
+ _vocFiles[index].deleteLastChar();
+ return 1;
+}
+
+int TIMInterpreter::cmd_unloadVocFile(const uint16 *param) {
+ const int index = param[0];
+ _vocFiles[index].clear();
+ return 1;
+}
+
+int TIMInterpreter::cmd_playVocFile(const uint16 *param) {
+ const int index = param[0];
+ const int volume = (param[1] * 255) / 100;
+
+ if (index < ARRAYSIZE(_vocFiles) && !_vocFiles[index].empty())
+ _vm->sound()->voicePlay(_vocFiles[index].c_str()/*, volume*/, true);
+ else
+ _vm->snd_playSoundEffect(index, volume);
+
+ return 1;
+}
+
+int TIMInterpreter::cmd_loadSoundFile(const uint16 *param) {
+ const char *file = (const char *)(_currentTim->text + READ_LE_UINT16(_currentTim->text + (param[0]<<1)));
+ _vm->sound()->loadSoundFile(file);
+ return 1;
+}
+
+int TIMInterpreter::cmd_playMusicTrack(const uint16 *param) {
+ _vm->sound()->playTrack(param[0]);
+ return 1;
+}
+
+int TIMInterpreter::cmd_setLoopIp(const uint16 *param) {
+ _currentTim->func[_currentFunc].loopIp = _currentTim->func[_currentFunc].ip;
+ return 1;
+}
+
+int TIMInterpreter::cmd_continueLoop(const uint16 *param) {
+ TIM::Function &func = _currentTim->func[_currentFunc];
+
+ if (!func.loopIp)
+ return -2;
+
+ func.ip = func.loopIp;
+
+ uint16 factor = param[0];
+ if (factor) {
+ const uint32 random = _vm->_rnd.getRandomNumberRng(0, 0x8000);
+ uint32 waitTime = (random * factor) / 0x8000;
+ func.nextTime += waitTime * _vm->tickLength();
+ }
+
+ return 1;
+}
+
+int TIMInterpreter::cmd_resetLoopIp(const uint16 *param) {
+ _currentTim->func[_currentFunc].loopIp = 0;
+ return 1;
+}
+
int TIMInterpreter::cmd_resetAllRuntimes(const uint16 *param) {
for (int i = 0; i < TIM::kCountFuncs; ++i) {
if (_currentTim->func[i].ip)
@@ -256,17 +605,23 @@ int TIMInterpreter::cmd_resetAllRuntimes(const uint16 *param) {
}
int TIMInterpreter::cmd_execOpcode(const uint16 *param) {
+ const uint16 opcode = *param++;
+
if (!_currentTim->opcodes) {
- warning("Trying to execute TIM opcode without opcode list");
+ warning("Trying to execute TIM opcode %d without opcode list (file '%s')", opcode, _currentTim->filename);
return 0;
}
- uint16 opcode = *param++;
if (opcode > _currentTim->opcodes->size()) {
warning("Calling unimplemented TIM opcode(0x%.02X/%d) from file '%s'", opcode, opcode, _currentTim->filename);
return 0;
}
+ if (!(*_currentTim->opcodes)[opcode]->isValid()) {
+ warning("Calling unimplemented TIM opcode(0x%.02X/%d) from file '%s'", opcode, opcode, _currentTim->filename);
+ return 0;
+ }
+
return (*(*_currentTim->opcodes)[opcode])(_currentTim, param);
}
diff --git a/engines/kyra/script_tim.h b/engines/kyra/script_tim.h
index 39a1d90a44..68ef23fd6c 100644
--- a/engines/kyra/script_tim.h
+++ b/engines/kyra/script_tim.h
@@ -30,9 +30,12 @@
#include "common/array.h"
#include "common/func.h"
+#include "common/str.h"
namespace Kyra {
+class WSAMovie_v2;
+class Screen_v2;
struct TIM;
typedef Common::Functor2<const TIM*, const uint16*, int> TIMOpcode;
@@ -52,9 +55,23 @@ struct TIM {
uint32 lastTime;
uint32 nextTime;
+ const uint16 *loopIp;
+
const uint16 *avtl;
} func[kCountFuncs];
+ enum {
+ kWSASlots = 10
+ };
+
+ struct WSASlot {
+ void *anim;
+
+ int16 x, y;
+ uint16 wsaFlags;
+ uint16 offscreen;
+ } wsa[kWSASlots];
+
uint16 *avtl;
uint8 *text;
@@ -63,10 +80,22 @@ struct TIM {
class TIMInterpreter {
public:
- TIMInterpreter(KyraEngine_v1 *vm, OSystem *system);
+ struct Animation {
+ WSAMovie_v2 *wsa;
+ int16 x, y;
+ uint16 wsaCopyParams;
+ };
+
+ TIMInterpreter(KyraEngine_v1 *vm, Screen_v2 *screen, OSystem *system);
+ ~TIMInterpreter();
TIM *load(const char *filename, const Common::Array<const TIMOpcode*> *opcodes);
void unload(TIM *&tim) const;
+
+ void setLangData(const char *filename);
+ void clearLangData() { delete[] _langData; _langData = 0; }
+
+ const char *getCTableEntry(uint idx) const;
void resetFinishedFlag() { _finished = false; }
bool finished() const { return _finished; }
@@ -74,10 +103,15 @@ public:
void exec(TIM *tim, bool loop);
void stopCurFunc() { if (_currentTim) cmd_stopCurFunc(0); }
- void play(const char *filename);
void refreshTimersAfterPause(uint32 elapsedTime);
+
+ void displayText(uint16 textId, int16 flags);
+ void setupTextPalette(uint index, int fadePalette);
+
+ int _palDelayInc, _palDiff, _palDelayAcc;
private:
KyraEngine_v1 *_vm;
+ Screen_v2 *_screen;
OSystem *_system;
TIM *_currentTim;
@@ -85,6 +119,19 @@ private:
bool _finished;
+ Common::String _vocFiles[120];
+
+ Animation _animations[TIM::kWSASlots];
+
+ Animation *initAnimStruct(int index, const char *filename, int x, int y, int, int offscreenBuffer, uint16 wsaFlags);
+
+ char _audioFilename[32];
+
+ uint8 *_langData;
+ char *getTableEntry(uint idx);
+ bool _textDisplayed;
+ uint8 *_textAreaBuffer;
+
int execCommand(int cmd, const uint16 *param);
typedef int (TIMInterpreter::*CommandProc)(const uint16 *);
@@ -98,8 +145,20 @@ private:
int cmd_initFunc0(const uint16 *param);
int cmd_stopCurFunc(const uint16 *param);
+ int cmd_initWSA(const uint16 *param);
+ int cmd_uninitWSA(const uint16 *param);
int cmd_initFunc(const uint16 *param);
int cmd_stopFunc(const uint16 *param);
+ int cmd_wsaDisplayFrame(const uint16 *param);
+ int cmd_displayText(const uint16 *param);
+ int cmd_loadVocFile(const uint16 *param);
+ int cmd_unloadVocFile(const uint16 *param);
+ int cmd_playVocFile(const uint16 *param);
+ int cmd_loadSoundFile(const uint16 *param);
+ int cmd_playMusicTrack(const uint16 *param);
+ int cmd_setLoopIp(const uint16 *param);
+ int cmd_continueLoop(const uint16 *param);
+ int cmd_resetLoopIp(const uint16 *param);
int cmd_resetAllRuntimes(const uint16 *param);
int cmd_execOpcode(const uint16 *param);
int cmd_initFuncNow(const uint16 *param);
diff --git a/engines/kyra/sequences_hof.cpp b/engines/kyra/sequences_hof.cpp
index 7e0220af8a..b42374f44f 100644
--- a/engines/kyra/sequences_hof.cpp
+++ b/engines/kyra/sequences_hof.cpp
@@ -50,7 +50,7 @@ void KyraEngine_HoF::seq_playSequences(int startSeq, int endSeq) {
_sound->setSoundList(&_soundData[(startSeq > kSequenceZanfaun) ? kMusicFinale : kMusicIntro]);
_sound->loadSoundFile(0);
- _screen->_charWidth = -2;
+ _screen->_charWidth = (_flags.gameID == GI_LOL) ? 0 : -2;
memset(_activeWSA, 0, sizeof(ActiveWSA) * 8);
for (int i = 0; i < 8; ++i)
@@ -300,8 +300,8 @@ void KyraEngine_HoF::seq_playSequences(int startSeq, int endSeq) {
_eventList.clear();
seqNum = kSequenceFirates;
}
- } else if (seqNum == kSequenceDemoFisher && !(_abortIntroFlag || skipFlag())) {
- seqNum = kSequenceDemoVirgin;
+ } else if (seqNum == endSeq && !(_abortIntroFlag || skipFlag())) {
+ seqNum = 0;
}
if (_menuChoice) {
@@ -1722,7 +1722,7 @@ int KyraEngine_HoF::seq_demoFisher(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
_seqScrollTextCounter = 0;
}
- seq_scrollPage();
+ seq_scrollPage(24, 144);
_seqFrameCounter++;
if (_seqFrameCounter < 0x256 || _seqFrameCounter > 0x31c) {
if (_seqFrameCounter < 0x174 || _seqFrameCounter > 0x1d7) {
@@ -1740,7 +1740,7 @@ int KyraEngine_HoF::seq_demoFisher(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
}
} else {
- seq_scrollPage();
+ seq_scrollPage(24, 144);
}
return 0;
}
@@ -1796,6 +1796,182 @@ int KyraEngine_HoF::seq_demoDig(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
return frm;
}
+int KyraEngine_HoF::seq_lolDemoScene1(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
+ uint8 *tmpPal = _screen->getPalette(2);
+
+ if (!(_seqFrameCounter % 100)) {
+ if (_seqFrameCounter == 0) {
+ _sound->haltTrack();
+ _sound->playTrack(6);
+ }
+ memcpy(tmpPal, _screen->getPalette(0), 0x300);
+ for (int i = 3; i < 0x300; i++) {
+ tmpPal[i] = ((int)tmpPal[i] * 120) / 64;
+ if (tmpPal[i] > 0x3f)
+ tmpPal[i] = 0x3f;
+ }
+ seq_playTalkText(_rnd.getRandomBit());
+ _screen->setScreenPalette(tmpPal);
+ _screen->updateScreen();
+ delay(8);
+ } else {
+ _screen->setScreenPalette(_screen->getPalette(0));
+ _screen->updateScreen();
+ if (_seqFrameCounter == 40)
+ seq_playTalkText(3);
+ }
+
+ _seqFrameCounter++;
+ return frm;
+}
+
+int KyraEngine_HoF::seq_lolDemoScene2(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
+ switch (_seqFrameCounter - 17) {
+ case 0:
+ _seqFrameDelay = 8;
+ break;
+ case 3:
+ case 6:
+ case 9:
+ seq_playTalkText(8);
+ break;
+ case 15:
+ seq_playTalkText(9);
+ break;
+ case 18:
+ seq_playTalkText(2);
+ break;
+ default:
+ break;
+ }
+ _seqFrameCounter++;
+ return frm;
+}
+
+int KyraEngine_HoF::seq_lolDemoScene3(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
+ if (_seqFrameCounter == 1)
+ seq_playTalkText(6);
+ else if (frm == 26)
+ seq_playTalkText(7);
+
+ _seqFrameCounter++;
+ return frm;
+}
+
+int KyraEngine_HoF::seq_lolDemoScene4(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
+ switch (_seqFrameCounter) {
+ case 11:
+ case 14:
+ case 17:
+ case 20:
+ seq_playTalkText(8);
+ break;
+ case 22:
+ seq_playTalkText(11);
+ break;
+ case 24:
+ seq_playTalkText(8);
+ break;
+ case 30:
+ seq_playTalkText(15);
+ break;
+ case 34:
+ seq_playTalkText(14);
+ break;
+ case 38:
+ seq_playTalkText(13);
+ break;
+ case 42:
+ seq_playTalkText(12);
+ break;
+ default:
+ break;
+ }
+
+ _seqFrameCounter++;
+ return frm;
+}
+
+int KyraEngine_HoF::seq_lolDemoScene5(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
+ switch (_seqFrameCounter++) {
+ case 0:
+ case 4:
+ case 6:
+ case 8:
+ case 10:
+ case 14:
+ case 16:
+ case 18:
+ case 20:
+ case 22:
+ case 24:
+ case 26:
+ case 28:
+ case 30:
+ seq_playTalkText(15);
+ break;
+ case 32:
+ seq_playTalkText(16);
+ break;
+ case 42:
+ seq_playTalkText(6);
+ break;
+ default:
+ break;
+ }
+ return frm;
+}
+
+int KyraEngine_HoF::seq_lolDemoText5(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
+ if (_seqFrameCounter++ == 100)
+ seq_playTalkText(5);
+ return frm;
+}
+
+int KyraEngine_HoF::seq_lolDemoScene6(WSAMovie_v2 *wsaObj, int x, int y, int frm) {
+ while (_seqScrollTextCounter < 0x122) {
+ _seqEndTime = _system->getMillis() + 6 * _tickLength;
+ if (!_seqFrameCounter) {
+ _screen->loadBitmap("adtext.cps", 4, 4, 0);
+ _screen->loadBitmap("adtext2.cps", 6, 6, 0);
+ _screen->copyPageMemory(6, 0, 4, 64000, 1024);
+ _screen->copyPageMemory(6, 1023, 6, 0, 64000);
+ _seqScrollTextCounter = 0;
+ }
+
+ if (_seqFrameCounter % 175) {
+ _screen->setScreenPalette(_screen->getPalette(0));
+ } else {
+ uint8 *tmpPal = _screen->getPalette(2);
+ memcpy(tmpPal, _screen->getPalette(0), 0x300);
+ for (int i = 3; i < 0x300; i++) {
+ tmpPal[i] = ((int)tmpPal[i] * 120) / 64;
+ if (tmpPal[i] > 0x3f)
+ tmpPal[i] = 0x3f;
+ }
+ seq_playTalkText(_rnd.getRandomBit());
+ _screen->setScreenPalette(tmpPal);
+ _screen->updateScreen();
+ delay(8);
+ }
+
+ if (_seqFrameCounter == 40 || _seqFrameCounter == 80 || _seqFrameCounter == 150 || _seqFrameCounter == 300)
+ seq_playTalkText(3);
+
+ _screen->copyPage(12, 2);
+ seq_scrollPage(70, 130);
+ _screen->copyPage(2, 0);
+ _screen->updateScreen();
+ _seqFrameCounter++;
+ if (_seqFrameCounter < 128 || _seqFrameCounter > 207)
+ _seqScrollTextCounter++;
+ delayUntil(_seqEndTime);
+ }
+ _screen->copyPage(2, 12);
+
+ return 0;
+}
+
uint32 KyraEngine_HoF::seq_activeTextsTimeLeft() {
uint32 res = 0;
@@ -1892,16 +2068,14 @@ void KyraEngine_HoF::seq_sequenceCommand(int command) {
switch (command) {
case 0:
memset(pal, 0, 0x300);
- _screen->fadePalette(pal, 16);
+ _screen->fadePalette(pal, 36);
memcpy (_screen->getPalette(0), pal, 0x300);
memcpy (_screen->getPalette(1), pal, 0x300);
break;
case 1:
memset(pal, 0x3F, 0x300);
- //////////XXX
- //////////Unused anyway (at least by fm-towns intro/outro)
-
+ seq_playTalkText(_rnd.getRandomBit());
_screen->fadePalette(pal, 16);
memcpy (_screen->getPalette(0), pal, 0x300);
memcpy (_screen->getPalette(1), pal, 0x300);
@@ -2575,32 +2749,34 @@ void KyraEngine_HoF::seq_displayScrollText(uint8 *data, const ScreenDim *d, int
delete[] textData;
}
-void KyraEngine_HoF::seq_scrollPage() {
+void KyraEngine_HoF::seq_scrollPage(int bottom, int top) {
int dstY, dstH, srcH;
static const ScreenDim d = { 0x00, 0x00, 0x28, 0x320, 0xFF, 0xFE, 0x00, 0x00 };
- if (_seqScrollTextCounter - 143 < 0) {
- dstY = 144 - _seqScrollTextCounter;
+ if (_seqScrollTextCounter - (top - 1) < 0) {
+ dstY = top - _seqScrollTextCounter;
dstH = _seqScrollTextCounter;
srcH = 0;
} else {
dstY = 0;
- srcH = _seqScrollTextCounter - 144;
- dstH = (400 - srcH <= 144) ? 400 - srcH : 144;
+ srcH = _seqScrollTextCounter - top;
+ dstH = (400 - srcH <= top) ? 400 - srcH : top;
}
if (dstH > 0) {
- for (int i = 0; i < 4; i++) {
- const ItemAnimData_v1 *def = &_demoAnimData[i];
- ActiveItemAnim *a = &_activeItemAnim[i];
-
- _screen->fillRect(12, def->y - 8, 28, def->y + 8, 0, 4);
- _screen->drawShape(4, getShapePtr(def->itemIndex + def->frames[a->currentFrame]), 12, def->y - 8, 0, 0);
- if(_seqFrameCounter % 2 == 0)
- a->currentFrame = ++a->currentFrame % 20;
+ if (_demoAnimData) {
+ for (int i = 0; i < 4; i++) {
+ const ItemAnimData_v1 *def = &_demoAnimData[i];
+ ActiveItemAnim *a = &_activeItemAnim[i];
+
+ _screen->fillRect(12, def->y - 8, 28, def->y + 8, 0, 4);
+ _screen->drawShape(4, getShapePtr(def->itemIndex + def->frames[a->currentFrame]), 12, def->y - 8, 0, 0);
+ if(_seqFrameCounter % 2 == 0)
+ a->currentFrame = ++a->currentFrame % 20;
+ }
}
- _screen->copyRegionEx(4, 0, srcH, 2, 2, dstY + 24, 320, dstH, &d);
+ _screen->copyRegionEx(4, 0, srcH, 2, 2, dstY + bottom, 320, dstH, &d);
}
}
@@ -2656,6 +2832,10 @@ void KyraEngine_HoF::seq_init() {
_res->loadFileList(_sequencePakList, _sequencePakListSize);
int numShp = -1;
+
+ if (_flags.gameID == GI_LOL)
+ return;
+
if (_flags.isDemo && !_flags.isTalkie) {
_demoAnimData = _staticres->loadShapeAnimData_v1(k2SeqplayShapeAnimData, _itemAnimDataSize);
uint8 *shp = _res->fileData("icons.shp", 0);
diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp
index 5068268d99..073639e4ca 100644
--- a/engines/kyra/sound.cpp
+++ b/engines/kyra/sound.cpp
@@ -104,6 +104,11 @@ int32 Sound::voicePlay(const char *file, bool isSfx) {
fileSize = 0;
}
+ if (!audioStream) {
+ warning("Couldn't load sound file '%s'", file);
+ return 0;
+ }
+
_soundChannels[h].file = file;
_mixer->playInputStream(isSfx ? Audio::Mixer::kSFXSoundType : Audio::Mixer::kSpeechSoundType, &_soundChannels[h].channelHandle, audioStream);
@@ -202,8 +207,8 @@ bool SoundMidiPC::init() {
}
void SoundMidiPC::updateVolumeSettings() {
- _musicVolume = ConfMan.getInt("music_volume");
- _sfxVolume = ConfMan.getInt("sfx_volume");
+ _musicVolume = CLIP(ConfMan.getInt("music_volume"), 0, 255);
+ _sfxVolume = CLIP(ConfMan.getInt("sfx_volume"), 0, 255);
updateChannelVolume(_musicVolume);
}
@@ -323,7 +328,17 @@ struct DeleterArray {
void SoundMidiPC::loadSoundFile(uint file) {
Common::StackLock lock(_mutex);
- Common::String filename = fileListEntry(file);
+ internalLoadFile(fileListEntry(file));
+}
+
+void SoundMidiPC::loadSoundFile(Common::String file) {
+ Common::StackLock lock(_mutex);
+
+ internalLoadFile(file);
+}
+
+void SoundMidiPC::internalLoadFile(Common::String file) {
+ Common::String filename = file;
filename += ".";
filename += _useC55 ? "C55" : "XMI";
diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h
index cebfdf491f..e5294eb15d 100644
--- a/engines/kyra/sound.h
+++ b/engines/kyra/sound.h
@@ -120,6 +120,12 @@ public:
virtual void loadSoundFile(uint file) = 0;
/**
+ * Load a sound file for playing music
+ * and sound effects from.
+ */
+ virtual void loadSoundFile(Common::String file) = 0;
+
+ /**
* Plays the specified track.
*
* @param track track number
@@ -215,8 +221,6 @@ protected:
int _musicEnabled;
bool _sfxEnabled;
- int _currentTheme;
-
KyraEngine_v1 *_vm;
Audio::Mixer *_mixer;
@@ -260,6 +264,7 @@ public:
void process();
void loadSoundFile(uint file);
+ void loadSoundFile(Common::String file);
void playTrack(uint8 track);
void haltTrack();
@@ -269,6 +274,8 @@ public:
void beginFadeOut();
private:
+ void internalLoadFile(Common::String file);
+
void play(uint8 track);
void unk1();
@@ -280,7 +287,8 @@ private:
uint8 _trackEntries[500];
uint8 *_soundDataPtr;
int _sfxPlayingSound;
- uint _soundFileLoaded;
+
+ Common::String _soundFileLoaded;
uint8 _sfxPriority;
uint8 _sfxFourthByteOfSong;
@@ -316,6 +324,7 @@ public:
void updateVolumeSettings();
void loadSoundFile(uint file);
+ void loadSoundFile(Common::String file);
void playTrack(uint8 track);
void haltTrack();
@@ -343,6 +352,7 @@ public:
bool isMT32() const { return _nativeMT32; }
private:
+ void internalLoadFile(Common::String file);
void updateChannelVolume(uint8 vol);
static void onTimer(void *data);
@@ -397,6 +407,7 @@ public:
void process();
void loadSoundFile(uint file);
+ void loadSoundFile(Common::String) {}
void playTrack(uint8 track);
void haltTrack();
@@ -454,6 +465,7 @@ public:
void process() {}
void loadSoundFile(uint file) {}
+ void loadSoundFile(Common::String) {}
void playTrack(uint8 track);
void haltTrack();
@@ -480,6 +492,7 @@ public:
void process();
void loadSoundFile(uint file) {}
+ void loadSoundFile(Common::String) {}
void playTrack(uint8 track);
void haltTrack();
@@ -513,6 +526,7 @@ public:
void setSoundList(const AudioDataStruct * list) { _music->setSoundList(list); _sfx->setSoundList(list); }
bool hasSoundFile(uint file) const { return _music->hasSoundFile(file) && _sfx->hasSoundFile(file); }
void loadSoundFile(uint file) { _music->loadSoundFile(file); _sfx->loadSoundFile(file); }
+ void loadSoundFile(Common::String file) { _music->loadSoundFile(file); _sfx->loadSoundFile(file); }
void playTrack(uint8 track) { _music->playTrack(track); }
void haltTrack() { _music->haltTrack(); }
diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp
index 68a2f0be9c..62551d2b09 100644
--- a/engines/kyra/sound_adlib.cpp
+++ b/engines/kyra/sound_adlib.cpp
@@ -235,6 +235,10 @@ private:
// * One for instruments, starting at offset 500.
uint8 *getProgram(int progId) {
+ uint16 offset = READ_LE_UINT16(_soundData + 2 * progId);
+ //TODO: Check in LoL CD Adlib driver
+ if (offset == 0xFFFF)
+ return 0;
return _soundData + READ_LE_UINT16(_soundData + 2 * progId);
}
@@ -1282,6 +1286,9 @@ int AdlibDriver::update_setupProgram(uint8 *&dataptr, Channel &channel, uint8 va
return 0;
uint8 *ptr = getProgram(value);
+ //TODO: Check in LoL CD Adlib driver
+ if (!ptr)
+ return 0;
uint8 chan = *ptr++;
uint8 priority = *ptr++;
@@ -2213,12 +2220,12 @@ const int SoundAdlibPC::_kyra1NumSoundTriggers = ARRAYSIZE(SoundAdlibPC::_kyra1S
SoundAdlibPC::SoundAdlibPC(KyraEngine_v1 *vm, Audio::Mixer *mixer)
: Sound(vm, mixer), _driver(0), _trackEntries(), _soundDataPtr(0) {
memset(_trackEntries, 0, sizeof(_trackEntries));
- _v2 = (_vm->gameFlags().gameID == GI_KYRA2);
+ _v2 = (_vm->gameFlags().gameID == GI_KYRA2) || (_vm->gameFlags().gameID == GI_LOL);
_driver = new AdlibDriver(mixer, _v2);
assert(_driver);
_sfxPlayingSound = -1;
- _soundFileLoaded = (uint)-1;
+ _soundFileLoaded.clear();
if (_v2) {
// TODO: Figure out if Kyra 2 uses sound triggers at all.
@@ -2262,7 +2269,7 @@ void SoundAdlibPC::playTrack(uint8 track) {
// sync for each loop. To avoid that, we declare that all four
// of the song channels have to jump "in sync".
- if (track == 4 && scumm_stricmp(fileListEntry(_soundFileLoaded), "KYRA1B") == 0)
+ if (track == 4 && _soundFileLoaded.equalsIgnoreCase("KYRA1B.ADL"))
_driver->setSyncJumpMask(0x000F);
else
_driver->setSyncJumpMask(0);
@@ -2341,6 +2348,15 @@ void SoundAdlibPC::beginFadeOut() {
}
void SoundAdlibPC::loadSoundFile(uint file) {
+ internalLoadFile(fileListEntry(file));
+}
+
+void SoundAdlibPC::loadSoundFile(Common::String file) {
+ internalLoadFile(file);
+}
+
+void SoundAdlibPC::internalLoadFile(Common::String file) {
+ file += ".ADL";
if (_soundFileLoaded == file)
return;
@@ -2349,12 +2365,9 @@ void SoundAdlibPC::loadSoundFile(uint file) {
uint8 *file_data = 0; uint32 file_size = 0;
- char filename[25];
- sprintf(filename, "%s.ADL", fileListEntry(file));
-
- file_data = _vm->resource()->fileData(filename, &file_size);
+ file_data = _vm->resource()->fileData(file.c_str(), &file_size);
if (!file_data) {
- warning("Couldn't find music file: '%s'", filename);
+ warning("Couldn't find music file: '%s'", file.c_str());
return;
}
diff --git a/engines/kyra/sound_towns.cpp b/engines/kyra/sound_towns.cpp
index 0f2b916c9d..ec1962a58f 100644
--- a/engines/kyra/sound_towns.cpp
+++ b/engines/kyra/sound_towns.cpp
@@ -2363,7 +2363,7 @@ TownsPC98_OpnDriver::TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type) :
_numSSG(type == OD_TOWNS ? 0 : 3), _hasADPCM(type == OD_TYPE86 ? true : false),
_numChan(type == OD_TYPE26 ? 3 : 6), _hasStereo(type == OD_TYPE26 ? false : true) {
setTempo(84);
- _baserate = (double)getRate() / 10368.0;
+ _baserate = (486202500.0 / (double)getRate()) / 10368.0;
}
TownsPC98_OpnDriver::~TownsPC98_OpnDriver() {
diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp
index c05795dacd..7d7f0cd4b8 100644
--- a/engines/kyra/staticres.cpp
+++ b/engines/kyra/staticres.cpp
@@ -23,16 +23,17 @@
*
*/
-
#include "common/endian.h"
#include "common/md5.h"
#include "kyra/kyra_v1.h"
#include "kyra/kyra_lok.h"
+#include "kyra/lol.h"
#include "kyra/kyra_v2.h"
#include "kyra/kyra_hof.h"
#include "kyra/kyra_mr.h"
#include "kyra/screen.h"
#include "kyra/screen_lok.h"
+#include "kyra/screen_lol.h"
#include "kyra/screen_hof.h"
#include "kyra/screen_mr.h"
#include "kyra/resource.h"
@@ -42,7 +43,7 @@
namespace Kyra {
-#define RESFILE_VERSION 28
+#define RESFILE_VERSION 31
bool StaticResource::checkKyraDat() {
Common::File kyraDat;
@@ -278,6 +279,16 @@ bool StaticResource::init() {
{ 0, 0, 0 }
};
+ static const FilenameTable lolStaticRes[] = {
+ // Demo Sequence Player
+ { k2SeqplayPakFiles, kStringList, "S_PAKFILES.TXT" },
+ { k2SeqplayStrings, kLanguageList, "S_STRINGS." },
+ { k2SeqplaySfxFiles, kStringList, "S_SFXFILES.TXT" },
+ { k2SeqplaySeqData, k2SeqData, "S_DATA.SEQ" },
+ { k2SeqplayIntroTracks, kStringList, "S_INTRO.TRA" },
+ { 0, 0, 0 }
+ };
+
if (_vm->game() == GI_KYRA1) {
_builtIn = 0;
_filenameTable = kyra1StaticRes;
@@ -287,8 +298,13 @@ bool StaticResource::init() {
} else if (_vm->game() == GI_KYRA3) {
_builtIn = 0;
_filenameTable = kyra3StaticRes;
+ } else if (_vm->game() == GI_LOL) {
+ if (!_vm->gameFlags().isDemo)
+ return true;
+ _builtIn = 0;
+ _filenameTable = lolStaticRes;
} else {
- error("unknown game ID");
+ error("StaticResource: Unknown game ID");
}
char errorBuffer[100];
@@ -917,6 +933,8 @@ const char *StaticResource::getFilename(const char *name) {
filename += ".K2";
else if (_vm->gameFlags().gameID == GI_KYRA3)
filename += ".K3";
+ else if (_vm->gameFlags().gameID == GI_LOL)
+ filename += ".LOL";
if (_vm->gameFlags().isTalkie && _vm->gameFlags().gameID != GI_KYRA3)
filename += ".CD";
@@ -1034,10 +1052,8 @@ void KyraEngine_LoK::initStaticResource() {
}
// audio data tables
-#if 0
static const char *tIntro98[] = { "intro%d.dat" };
static const char *tIngame98[] = { "kyram%d.dat" };
-#endif
static const AudioDataStruct soundData_PC[] = {
{ _soundFilesIntro, _soundFilesIntroSize, 0, 0 },
@@ -1051,21 +1067,20 @@ void KyraEngine_LoK::initStaticResource() {
{ 0, 0, 0, 0}
};
-#if 0
static const AudioDataStruct soundData_PC98[] = {
{ tIntro98, 1, 0, 0 },
{ tIngame98, 1, 0, 0 },
{ 0, 0, 0, 0}
};
-#endif
if (_flags.platform == Common::kPlatformPC)
_soundData = soundData_PC;
else if (_flags.platform == Common::kPlatformFMTowns)
_soundData = soundData_TOWNS;
else if (_flags.platform == Common::kPlatformPC98)
- _soundData = soundData_TOWNS/*soundData_PC98*/;
-
+ _soundData = soundData_PC98;
+ else
+ _soundData = 0;
}
void KyraEngine_LoK::loadMouseShapes() {
@@ -1263,11 +1278,9 @@ void KyraEngine_HoF::initStaticResource() {
static const char *fmtMusicFileListFinale[] = { "finale%d.twn" };
static const char *fmtMusicFileListIngame[] = { "km%02d.twn" };
-#if 0
static const char *pc98MusicFileListIntro[] = { "intro%d.86" };
static const char *pc98MusicFileListFinale[] = { "finale%d.86" };
static const char *pc98MusicFileListIngame[] = { "km%02d.86" };
-#endif
static const AudioDataStruct soundData_PC[] = {
{ _musicFileListIntro, _musicFileListIntroSize, 0, 0 },
@@ -1281,20 +1294,18 @@ void KyraEngine_HoF::initStaticResource() {
{ fmtMusicFileListFinale, 1, _cdaTrackTableFinale, _cdaTrackTableFinaleSize >> 1 }
};
-#if 0
static const AudioDataStruct soundData_PC98[] = {
{ pc98MusicFileListIntro, 1, 0, 0 },
{ pc98MusicFileListIngame, 1, 0, 0 },
{ pc98MusicFileListFinale, 1, 0, 0 }
};
-#endif
if (_flags.platform == Common::kPlatformPC)
_soundData = soundData_PC;
else if (_flags.platform == Common::kPlatformFMTowns)
_soundData = soundData_TOWNS;
else if (_flags.platform == Common::kPlatformPC98)
- _soundData = soundData_TOWNS/*soundData_PC98*/;
+ _soundData = soundData_PC98;
// setup sequence data
_sequences = _staticres->loadHofSequenceData(k2SeqplaySeqData, tmpSize);
@@ -1333,8 +1344,17 @@ void KyraEngine_HoF::initStaticResource() {
&KyraEngine_HoF::seq_demoDig, 0
};
- _callbackS = (_flags.isDemo && !_flags.isTalkie) ? hofDemoSequenceCallbacks : hofSequenceCallbacks;
- _callbackN = (_flags.isDemo && !_flags.isTalkie) ? hofDemoNestedSequenceCallbacks : hofNestedSequenceCallbacks;
+ static const SeqProc lolDemoSequenceCallbacks[] = {
+ &KyraEngine_HoF::seq_lolDemoScene1, 0, &KyraEngine_HoF::seq_lolDemoScene2, 0,
+ &KyraEngine_HoF::seq_lolDemoScene3, 0, &KyraEngine_HoF::seq_lolDemoScene4, 0,
+ &KyraEngine_HoF::seq_lolDemoScene5, &KyraEngine_HoF::seq_lolDemoText5,
+ &KyraEngine_HoF::seq_lolDemoScene6, 0
+ };
+
+ static const SeqProc lolDemoNestedSequenceCallbacks[] = { 0 };
+
+ _callbackS = _flags.gameID == GI_LOL ? lolDemoSequenceCallbacks : ((_flags.isDemo && !_flags.isTalkie) ? hofDemoSequenceCallbacks : hofSequenceCallbacks);
+ _callbackN = _flags.gameID == GI_LOL ? lolDemoNestedSequenceCallbacks : ((_flags.isDemo && !_flags.isTalkie) ? hofDemoNestedSequenceCallbacks : hofNestedSequenceCallbacks);
}
void KyraEngine_MR::initStaticResource() {
@@ -2236,5 +2256,105 @@ const int8 KyraEngine_MR::_albumWSAY[] = {
-1, -2, 2, 2, -6, -6, -6, 0
};
+// lands of lore static res
+
+const ScreenDim Screen_LoL::_screenDimTable[] = {
+ { 0x00, 0x00, 0x28, 0xC8, 0xC7, 0xCF, 0x00, 0x00 }
+};
+
+const int Screen_LoL::_screenDimTableCount = ARRAYSIZE(Screen_LoL::_screenDimTable);
+
+const char * const LoLEngine::_languageExt[] = {
+ "ENG",
+ "FRE",
+ "GER"
+};
+
+const LoLEngine::CharacterPrev LoLEngine::_charPreviews[] = {
+ { "Ak\'shel", 0x060, 0x7F, { 0x0F, 0x08, 0x05 } },
+ { "Michael", 0x09A, 0x7F, { 0x06, 0x0A, 0x0F } },
+ { "Kieran", 0x0D4, 0x7F, { 0x08, 0x06, 0x08 } },
+ { "Conrad", 0x10F, 0x7F, { 0x0A, 0x0C, 0x0A } }
+};
+
+const uint8 LoLEngine::_chargenFrameTable[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04,
+ 0x05, 0x04, 0x03, 0x02, 0x01,
+ 0x00, 0x00, 0x01, 0x02, 0x03,
+ 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
+ 0x0E, 0x0F, 0x10, 0x11, 0x12
+};
+
+const uint16 LoLEngine::_selectionPosTable[] = {
+ 0x6F, 0x00, 0x8F, 0x00, 0xAF, 0x00, 0xCF, 0x00,
+ 0xEF, 0x00, 0x6F, 0x20, 0x8F, 0x20, 0xAF, 0x20,
+ 0xCF, 0x20, 0xEF, 0x20, 0x6F, 0x40, 0x8F, 0x40,
+ 0xAF, 0x40, 0xCF, 0x40, 0xEF, 0x40, 0x10F, 0x00
+};
+
+const uint8 LoLEngine::_selectionChar1IdxTable[] = {
+ 0, 0, 5, 5, 5, 5, 5, 5,
+ 5, 5, 5, 0, 0, 5, 5, 5,
+ 5, 5, 5, 5, 0, 0, 5, 5,
+ 5, 5, 5
+};
+
+const uint8 LoLEngine::_selectionChar2IdxTable[] = {
+ 1, 1, 6, 6, 1, 1, 6, 6,
+ 6, 6, 6, 6, 6, 1, 1, 6,
+ 6, 6, 1, 1, 6, 6, 6, 6,
+ 6, 6, 6
+};
+
+const uint8 LoLEngine::_selectionChar3IdxTable[] = {
+ 2, 2, 7, 7, 7, 7, 2, 2,
+ 7, 7, 7, 7, 7, 7, 7, 2,
+ 2, 7, 7, 7, 7, 2, 2, 7,
+ 7, 7, 7
+};
+
+const uint8 LoLEngine::_selectionChar4IdxTable[] = {
+ 3, 3, 8, 8, 8, 8, 3, 3,
+ 8, 8, 3, 3, 8, 8, 8, 8,
+ 8, 8, 8, 8, 8, 3, 3, 8,
+ 8, 8, 8
+};
+
+const uint8 LoLEngine::_reminderChar1IdxTable[] = {
+ 4, 4, 4, 5, 5, 5, 5, 5,
+ 5, 5, 5, 5, 5, 5, 5, 5,
+ 5
+};
+
+const uint8 LoLEngine::_reminderChar2IdxTable[] = {
+ 9, 9, 9, 6, 6, 6, 6, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6,
+ 6
+};
+
+const uint8 LoLEngine::_reminderChar3IdxTable[] = {
+ 0xE, 0xE, 0xE, 0x7, 0x7, 0x7, 0x7, 0x7,
+ 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7, 0x7,
+ 0x7
+};
+
+const uint8 LoLEngine::_reminderChar4IdxTable[] = {
+ 0xF, 0xF, 0xF, 0x8, 0x8, 0x8, 0x8, 0x8,
+ 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8, 0x8,
+ 0x8
+};
+
+const uint8 LoLEngine::_selectionAnimIndexTable[] = {
+ 0, 5, 1, 6, 2, 7, 3, 8
+};
+
+const uint8 LoLEngine::_charInfoFrameTable[] = {
+ 0x0, 0x7, 0x8, 0x9, 0xA, 0xB, 0xA, 0x9,
+ 0x8, 0x7, 0x0, 0x0, 0x7, 0x8, 0x9, 0xA,
+ 0xB, 0xA, 0x9, 0x8, 0x7, 0x0, 0x0, 0x7,
+ 0x8, 0x9, 0xA, 0xB, 0xA, 0x9, 0x8, 0x7
+};
+
} // End of namespace Kyra
diff --git a/engines/kyra/wsamovie.h b/engines/kyra/wsamovie.h
index 36cd75b1ab..1db8ee8474 100644
--- a/engines/kyra/wsamovie.h
+++ b/engines/kyra/wsamovie.h
@@ -109,7 +109,7 @@ private:
class WSAMovie_v2 : public WSAMovie_v1 {
public:
- WSAMovie_v2(KyraEngine_v1 *vm, Screen_v2 *scren);
+ WSAMovie_v2(KyraEngine_v1 *vm, Screen_v2 *screen);
int open(const char *filename, int unk1, uint8 *palette);
diff --git a/engines/parallaction/balloons.cpp b/engines/parallaction/balloons.cpp
index fab92dada9..bbd8bc9ca7 100644
--- a/engines/parallaction/balloons.cpp
+++ b/engines/parallaction/balloons.cpp
@@ -23,6 +23,8 @@
*
*/
+#include "common/util.h"
+
#include "parallaction/graphics.h"
#include "parallaction/parallaction.h"
@@ -76,6 +78,7 @@ class BalloonManager_ns : public BalloonManager {
uint _numBalloons;
+ void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height);
void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth);
int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness);
Balloon *getBalloon(uint id);
@@ -149,12 +152,12 @@ int BalloonManager_ns::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 w
int16 w, h;
- _gfx->getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
int id = createBalloon(w+5, h, winding, 1);
Balloon *balloon = &_intBalloons[id];
- _gfx->drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
// TODO: extract some text to make a name for obj
balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
@@ -169,12 +172,12 @@ int BalloonManager_ns::setDialogueBalloon(char *text, uint16 winding, byte textC
int16 w, h;
- _gfx->getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
int id = createBalloon(w+5, h, winding, 1);
Balloon *balloon = &_intBalloons[id];
- _gfx->drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
// TODO: extract some text to make a name for obj
balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
@@ -193,7 +196,7 @@ int BalloonManager_ns::setDialogueBalloon(char *text, uint16 winding, byte textC
void BalloonManager_ns::setBalloonText(uint id, char *text, byte textColor) {
Balloon *balloon = getBalloon(id);
balloon->surface->fillRect(balloon->innerBox, 1);
- _gfx->drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
}
@@ -201,11 +204,11 @@ int BalloonManager_ns::setLocationBalloon(char *text, bool endGame) {
int16 w, h;
- _gfx->getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR_NS);
Balloon *balloon = &_intBalloons[id];
- _gfx->drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH);
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, 0, MAX_BALLOON_WIDTH);
// TODO: extract some text to make a name for obj
balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
@@ -242,8 +245,109 @@ void BalloonManager_ns::freeBalloons() {
_numBalloons = 0;
}
+// TODO: get rid of parseNextToken from here. Use the
+// StringTokenizer instead.
+void BalloonManager_ns::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) {
+
+ uint16 lines = 0;
+ uint16 linewidth = 0;
+
+ uint16 rx = 10;
+ uint16 ry = 4;
+
+ uint16 blankWidth = font->getStringWidth(" ");
+ uint16 tokenWidth = 0;
+
+ char token[MAX_TOKEN_LEN];
+
+ if (wrapwidth == -1)
+ wrapwidth = _vm->_screenWidth;
+
+ while (strlen(text) > 0) {
+
+ text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
+
+ if (!scumm_stricmp(token, "%p")) {
+ lines++;
+ rx = 10;
+ ry = 4 + lines*10; // y
+
+ strcpy(token, "> .......");
+ strncpy(token+2, _password, strlen(_password));
+ tokenWidth = font->getStringWidth(token);
+ } else {
+ tokenWidth = font->getStringWidth(token);
+
+ linewidth += tokenWidth;
+
+ if (linewidth > wrapwidth) {
+ // wrap line
+ lines++;
+ rx = 10; // x
+ ry = 4 + lines*10; // y
+ linewidth = tokenWidth;
+ }
+
+ if (!scumm_stricmp(token, "%s")) {
+ sprintf(token, "%d", _score);
+ }
+
+ }
+
+ _gfx->drawText(font, surf, rx, ry, token, color);
+
+ rx += tokenWidth + blankWidth;
+ linewidth += blankWidth;
+
+ text = Common::ltrim(text);
+ }
+
+}
+// TODO: get rid of parseNextToken from here. Use the
+// StringTokenizer instead.
+void BalloonManager_ns::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) {
+ uint16 lines = 0;
+ uint16 w = 0;
+ *width = 0;
+
+ uint16 blankWidth = font->getStringWidth(" ");
+ uint16 tokenWidth = 0;
+
+ char token[MAX_TOKEN_LEN];
+
+ while (strlen(text) != 0) {
+
+ text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
+ tokenWidth = font->getStringWidth(token);
+
+ w += tokenWidth;
+
+ if (!scumm_stricmp(token, "%p")) {
+ lines++;
+ } else {
+ if (w > maxwidth) {
+ w -= tokenWidth;
+ lines++;
+ if (w > *width)
+ *width = w;
+
+ w = tokenWidth;
+ }
+ }
+
+ w += blankWidth;
+ text = Common::ltrim(text);
+ }
+
+ if (*width < w) *width = w;
+ *width += 10;
+
+ *height = lines * 10 + 20;
+
+ return;
+}
@@ -259,17 +363,36 @@ class BalloonManager_br : public BalloonManager {
uint _numBalloons;
- Frames *_leftBalloon;
- Frames *_rightBalloon;
Disk *_disk;
Gfx *_gfx;
+ Frames *_leftBalloon;
+ Frames *_rightBalloon;
+
void cacheAnims();
+ void getStringExtent(Font *font, const char *text, uint16 maxwidth, int16* width, int16* height);
void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth);
int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness);
Balloon *getBalloon(uint id);
Graphics::Surface *expandBalloon(Frames *data, int frameNum);
+ void textSetupRendering(const Common::String &text, Graphics::Surface *dest, Font *font, byte color);
+ void textEmitCenteredLine();
+ void textAccum(const Common::String &token, uint16 width);
+ void textNewLine();
+
+ Common::String _textLine;
+ Graphics::Surface *_textSurf;
+ Font *_textFont;
+ uint16 _textX, _textY;
+ byte _textColor;
+ uint16 _textLines, _textWidth;
+
+ void extentSetup(Font *font, int16 *width, int16 *height);
+ void extentAction();
+
+ int16 *_extentWidth, *_extentHeight;
+
public:
BalloonManager_br(Disk *disk, Gfx *gfx);
@@ -315,11 +438,11 @@ int BalloonManager_br::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 w
Balloon *balloon = &_intBalloons[id];
if (winding == 0) {
- src = _leftBalloon;
+ src = _rightBalloon;
srcFrame = 0;
} else
if (winding == 1) {
- src = _rightBalloon;
+ src = _leftBalloon;
srcFrame = 0;
}
@@ -328,12 +451,12 @@ int BalloonManager_br::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 w
balloon->surface = expandBalloon(src, srcFrame);
src->getRect(srcFrame, balloon->box);
-// drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
// TODO: extract some text to make a name for obj
balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
- balloon->obj->x = x;
- balloon->obj->y = y;
+ balloon->obj->x = x + balloon->box.left;
+ balloon->obj->y = y + balloon->box.top;
balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
_numBalloons++;
@@ -351,11 +474,11 @@ int BalloonManager_br::setDialogueBalloon(char *text, uint16 winding, byte textC
Balloon *balloon = &_intBalloons[id];
if (winding == 0) {
- src = _leftBalloon;
+ src = _rightBalloon;
srcFrame = id;
} else
if (winding == 1) {
- src = _rightBalloon;
+ src = _leftBalloon;
srcFrame = 0;
}
@@ -364,12 +487,12 @@ int BalloonManager_br::setDialogueBalloon(char *text, uint16 winding, byte textC
balloon->surface = expandBalloon(src, srcFrame);
src->getRect(srcFrame, balloon->box);
-// drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
// TODO: extract some text to make a name for obj
balloon->obj = _gfx->registerBalloon(new SurfaceToFrames(balloon->surface), 0);
- balloon->obj->x = 0;
- balloon->obj->y = 10;
+ balloon->obj->x = balloon->box.left;
+ balloon->obj->y = balloon->box.top;
balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
if (id > 0) {
@@ -434,6 +557,155 @@ void BalloonManager_br::cacheAnims() {
}
}
+
+void BalloonManager_br::extentSetup(Font *font, int16 *width, int16 *height) {
+ _extentWidth = width;
+ _extentHeight = height;
+
+ _textLine.clear();
+ _textLines = 0;
+ _textWidth = 0;
+ _textFont = font;
+}
+
+void BalloonManager_br::extentAction() {
+ if (_textWidth > *_extentWidth) {
+ *_extentWidth = _textWidth;
+ }
+ *_extentHeight = _textLines * _textFont->height();
+}
+
+void BalloonManager_br::textSetupRendering(const Common::String &text, Graphics::Surface *dest, Font *font, byte color) {
+ uint16 maxWidth = 216;
+
+ int16 w, h;
+ getStringExtent(font, text.c_str(), maxWidth, &w, &h);
+
+ w += 10;
+ h += 12;
+
+ _textLine.clear();
+ _textSurf = dest;
+ _textFont = font;
+ _textX = 0;
+ _textY = (_textSurf->h - h) / 2;
+ _textColor = color;
+ _textLines = 0;
+ _textWidth = 0;
+}
+
+void BalloonManager_br::textEmitCenteredLine() {
+ if (_textLine.empty()) {
+ return;
+ }
+ uint16 rx = _textX + (_textSurf->w - _textWidth) / 2;
+ uint16 ry = _textY + _textLines * _textFont->height(); // y
+ _gfx->drawText(_textFont, _textSurf, rx, ry, _textLine.c_str(), _textColor);
+}
+
+void BalloonManager_br::textAccum(const Common::String &token, uint16 width) {
+ if (token.empty()) {
+ return;
+ }
+
+ _textWidth += width;
+ _textLine += token;
+}
+
+void BalloonManager_br::textNewLine() {
+ _textLines++;
+ _textWidth = 0;
+ _textLine.clear();
+}
+
+
+// TODO: really, base this and getStringExtent on some kind of LineTokenizer, instead of
+// repeating the algorithm and changing a couple of lines.
+void BalloonManager_br::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapWidth) {
+ textSetupRendering(text, surf, font, color);
+
+ wrapWidth = 216;
+
+ Common::StringTokenizer tokenizer(text, " ");
+ Common::String token;
+ Common::String blank(" ");
+
+ uint16 blankWidth = font->getStringWidth(" ");
+ uint16 tokenWidth = 0;
+
+ while (!tokenizer.empty()) {
+ token = tokenizer.nextToken();
+
+ if (token == '/') {
+ tokenWidth = 0;
+ textEmitCenteredLine();
+ textNewLine();
+ } else {
+ // todo: expand '%'
+ tokenWidth = font->getStringWidth(token.c_str());
+
+ if (_textWidth == 0) {
+ textAccum(token, tokenWidth);
+ } else {
+ if (_textWidth + blankWidth + tokenWidth <= wrapWidth) {
+ textAccum(blank, blankWidth);
+ textAccum(token, tokenWidth);
+ } else {
+ textEmitCenteredLine();
+ textNewLine();
+ textAccum(token, tokenWidth);
+ }
+ }
+ }
+ }
+
+ textEmitCenteredLine();
+}
+
+
+
+void BalloonManager_br::getStringExtent(Font *font, const char *text, uint16 maxwidth, int16* width, int16* height) {
+ extentSetup(font, width, height);
+
+ Common::StringTokenizer tokenizer(text, " ");
+ Common::String token;
+ Common::String blank(" ");
+
+ uint16 blankWidth = font->getStringWidth(" ");
+ uint16 tokenWidth = 0;
+
+ while (!tokenizer.empty()) {
+ token = tokenizer.nextToken();
+
+ if (token == '/') {
+ tokenWidth = 0;
+ extentAction();
+ textNewLine();
+ } else {
+ // todo: expand '%'
+ tokenWidth = font->getStringWidth(token.c_str());
+
+ if (_textWidth == 0) {
+ textAccum(token, tokenWidth);
+ } else {
+ if (_textWidth + blankWidth + tokenWidth <= maxwidth) {
+ textAccum(blank, blankWidth);
+ textAccum(token, tokenWidth);
+ } else {
+ extentAction();
+ textNewLine();
+ textAccum(token, tokenWidth);
+ }
+ }
+ }
+ }
+
+ extentAction();
+}
+
+
+
+
BalloonManager_br::BalloonManager_br(Disk *disk, Gfx *gfx) : _numBalloons(0), _disk(disk), _gfx(gfx), _leftBalloon(0), _rightBalloon(0) {
}
@@ -453,4 +725,6 @@ void Parallaction::setupBalloonManager() {
}
}
+
+
} // namespace Parallaction
diff --git a/engines/parallaction/callables_ns.cpp b/engines/parallaction/callables_ns.cpp
index ed60a193ce..761e11dc7d 100644
--- a/engines/parallaction/callables_ns.cpp
+++ b/engines/parallaction/callables_ns.cpp
@@ -37,18 +37,6 @@
namespace Parallaction {
-// part completion messages
-static const char *endMsg0[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"};
-static const char *endMsg1[] = {"HAI FINITO QUESTA PARTE", "TU AS COMPLETE' CETTE AVENTURE", "YOU HAVE COMPLETED THIS PART", "DU HAST EIN ABENTEUER ERFOLGREICH"};
-static const char *endMsg2[] = {"ORA COMPLETA IL RESTO ", "AVEC SUCCES.", "NOW GO ON WITH THE REST OF", "ZU ENDE GEFUHRT"};
-static const char *endMsg3[] = {"DELL' AVVENTURA", "CONTINUE AVEC LES AUTRES", "THIS ADVENTURE", "MACH' MIT DEN ANDEREN WEITER"};
-// game completion messages
-static const char *endMsg4[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"};
-static const char *endMsg5[] = {"HAI FINITO LE TRE PARTI", "TU AS COMPLETE' LES TROIS PARTIES", "YOU HAVE COMPLETED THE THREE PARTS", "DU HAST DREI ABENTEURE ERFOLGREICH"};
-static const char *endMsg6[] = {"DELL' AVVENTURA", "DE L'AVENTURE", "OF THIS ADVENTURE", "ZU ENDE GEFUHRT"};
-static const char *endMsg7[] = {"ED ORA IL GRAN FINALE ", "ET MAINTENANT LE GRAND FINAL", "NOW THE GREAT FINAL", "UND YETZT DER GROSSE SCHLUSS!"};
-
-
/*
intro callables data members
*/
@@ -143,18 +131,6 @@ static uint16 _rightHandPositions[684] = {
0x00e0, 0x007b, 0x00e0, 0x0077
};
-struct Credit {
- const char *_role;
- const char *_name;
-} _credits[] = {
- {"Music and Sound Effects", "MARCO CAPRELLI"},
- {"PC Version", "RICCARDO BALLARINO"},
- {"Project Manager", "LOVRANO CANEPA"},
- {"Production", "BRUNO BOZ"},
- {"Special Thanks to", "LUIGI BENEDICENTI - GILDA and DANILO"},
- {"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"}
-};
-
/*
game callables
*/
@@ -304,23 +280,19 @@ void Parallaction_ns::_c_trasformata(void *parm) {
}
void Parallaction_ns::_c_offMouse(void *parm) {
- _input->showCursor(false);
- _engineFlags |= kEngineBlockInput;
- return;
+ _input->setMouseState(MOUSE_DISABLED);
}
void Parallaction_ns::_c_onMouse(void *parm) {
- _engineFlags &= ~kEngineBlockInput;
- _input->showCursor(true);
- return;
+ _input->setMouseState(MOUSE_ENABLED_SHOW);
}
void Parallaction_ns::_c_setMask(void *parm) {
- memset(_gfx->_backgroundInfo.mask.data + 3600, 0, 3600);
- _gfx->_backgroundInfo.layers[1] = 500;
+ memset(_gfx->_backgroundInfo->mask.data + 3600, 0, 3600);
+ _gfx->_backgroundInfo->layers[1] = 500;
return;
}
@@ -340,7 +312,7 @@ void Parallaction_ns::_c_endComment(void *param) {
g_system->delayMillis(20);
}
- _input->waitUntilLeftClick();
+ _input->waitForButtonEvent(kMouseLeftUp);
_balloonMan->freeBalloons();
return;
@@ -376,37 +348,12 @@ void Parallaction_ns::_c_finito(void *parm) {
setPartComplete(_char);
cleanInventory();
- _gfx->setPalette(_gfx->_palette);
-
- uint id[4];
-
- if (allPartsComplete()) {
- id[0] = _gfx->createLabel(_menuFont, endMsg4[_language], 1);
- id[1] = _gfx->createLabel(_menuFont, endMsg5[_language], 1);
- id[2] = _gfx->createLabel(_menuFont, endMsg6[_language], 1);
- id[3] = _gfx->createLabel(_menuFont, endMsg7[_language], 1);
- } else {
- id[0] = _gfx->createLabel(_menuFont, endMsg0[_language], 1);
- id[1] = _gfx->createLabel(_menuFont, endMsg1[_language], 1);
- id[2] = _gfx->createLabel(_menuFont, endMsg2[_language], 1);
- id[3] = _gfx->createLabel(_menuFont, endMsg3[_language], 1);
- }
-
- _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 70);
- _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);
- _gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 130);
- _gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 160);
- _input->waitUntilLeftClick();
+ cleanupGame();
- _gfx->freeLabels();
+ _gfx->setPalette(_gfx->_palette);
- if (allPartsComplete()) {
- scheduleLocationSwitch("estgrotta.drki");
- } else {
- selectStartLocation();
- }
+ startEndPartSequence();
- cleanupGame();
return;
}
@@ -467,52 +414,11 @@ void Parallaction_ns::_c_startIntro(void *parm) {
_soundMan->playMusic();
}
- _engineFlags |= kEngineBlockInput;
-
- return;
+ _input->setMouseState(MOUSE_DISABLED);
}
void Parallaction_ns::_c_endIntro(void *parm) {
-
- debugC(1, kDebugExec, "endIntro()");
-
- uint id[2];
- for (uint16 _si = 0; _si < 6; _si++) {
- id[0] = _gfx->createLabel(_menuFont, _credits[_si]._role, 1);
- id[1] = _gfx->createLabel(_menuFont, _credits[_si]._name, 1);
-
- _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80);
- _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);
-
- _gfx->updateScreen();
-
- _input->waitForButtonEvent(kMouseLeftUp, 5500);
-
- _gfx->freeLabels();
- }
- debugC(1, kDebugExec, "endIntro(): done showing credits");
-
- _soundMan->stopMusic();
-
- if ((getFeatures() & GF_DEMO) == 0) {
-
- id[0] = _gfx->createLabel(_menuFont, "CLICK MOUSE BUTTON TO START", 1);
- _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80);
-
- _input->waitUntilLeftClick();
-
- _gfx->freeLabels();
-
- _engineFlags &= ~kEngineBlockInput;
- selectStartLocation();
-
- cleanupGame();
-
- } else {
- _input->waitUntilLeftClick();
- }
-
- return;
+ startCreditSequence();
}
void Parallaction_ns::_c_moveSheet(void *parm) {
@@ -596,11 +502,11 @@ void Parallaction_ns::_c_shade(void *parm) {
_rightHandAnim->_top
);
- uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo.mask.internalWidth;
+ uint16 _di = r.left/4 + r.top * _gfx->_backgroundInfo->mask.internalWidth;
for (uint16 _si = r.top; _si < r.bottom; _si++) {
- memset(_gfx->_backgroundInfo.mask.data + _di, 0, r.width()/4+1);
- _di += _gfx->_backgroundInfo.mask.internalWidth;
+ memset(_gfx->_backgroundInfo->mask.data + _di, 0, r.width()/4+1);
+ _di += _gfx->_backgroundInfo->mask.internalWidth;
}
return;
diff --git a/engines/parallaction/detection.cpp b/engines/parallaction/detection.cpp
index da94e0c5d7..531bd8ee16 100644
--- a/engines/parallaction/detection.cpp
+++ b/engines/parallaction/detection.cpp
@@ -154,7 +154,23 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::ADGF_NO_FLAGS
},
GType_BRA,
- GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT
+ GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
+ },
+
+ {
+ {
+ "bra",
+ "Demo",
+ {
+ { "russia.fnt", 0, "0dd55251d2886d6783718df2b184bf97", 10649 },
+ { NULL, 0, NULL, 0}
+ },
+ Common::UNK_LANG,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GType_BRA,
+ GF_LANG_EN | GF_DEMO,
},
// TODO: Base the detection of Amiga BRA on actual data file, not executable file.
@@ -171,9 +187,25 @@ static const PARALLACTIONGameDescription gameDescriptions[] = {
Common::ADGF_NO_FLAGS
},
GType_BRA,
- GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT
+ GF_LANG_EN | GF_LANG_FR | GF_LANG_DE | GF_LANG_IT | GF_LANG_MULT,
},
+ // TODO: Base the detection of Amiga BRA demo on actual data file, not executable file.
+ {
+ {
+ "bra",
+ "Demo",
+ {
+ { "bigred", 0, "b62a7b589fb5e9071f021227640893bf", 97004 },
+ { NULL, 0, NULL, 0}
+ },
+ Common::UNK_LANG,
+ Common::kPlatformAmiga,
+ Common::ADGF_DEMO
+ },
+ GType_BRA,
+ GF_LANG_EN | GF_DEMO,
+ },
{ AD_TABLE_END_MARKER, 0, 0 }
};
diff --git a/engines/parallaction/dialogue.cpp b/engines/parallaction/dialogue.cpp
index 96491bf084..4f2343be64 100644
--- a/engines/parallaction/dialogue.cpp
+++ b/engines/parallaction/dialogue.cpp
@@ -33,7 +33,7 @@
namespace Parallaction {
#define MAX_PASSWORD_LENGTH 7
-
+/*
#define QUESTION_BALLOON_X 140
#define QUESTION_BALLOON_Y 10
#define QUESTION_CHARACTER_X 190
@@ -41,6 +41,25 @@ namespace Parallaction {
#define ANSWER_CHARACTER_X 10
#define ANSWER_CHARACTER_Y 80
+*/
+struct BalloonPositions {
+ Common::Point _questionBalloon;
+ Common::Point _questionChar;
+
+ Common::Point _answerChar;
+};
+
+BalloonPositions _balloonPositions_NS = {
+ Common::Point(140, 10),
+ Common::Point(190, 80),
+ Common::Point(10, 80)
+};
+
+BalloonPositions _balloonPositions_BR = {
+ Common::Point(0, 0),
+ Common::Point(380, 80),
+ Common::Point(10, 80)
+};
class DialogueManager {
@@ -78,6 +97,7 @@ class DialogueManager {
bool _isKeyDown;
uint16 _downKey;
+ BalloonPositions _ballonPos;
public:
DialogueManager(Parallaction *vm, ZonePtr z);
@@ -112,6 +132,15 @@ protected:
};
DialogueManager::DialogueManager(Parallaction *vm, ZonePtr z) : _vm(vm), _z(z) {
+ int gtype = vm->getGameType();
+ if (gtype == GType_Nippon) {
+ _ballonPos = _balloonPositions_NS;
+ } else
+ if (gtype == GType_BRA) {
+ _ballonPos = _balloonPositions_BR;
+ } else
+ error("unsupported game in DialogueManager");
+
_dialogue = _z->u.speak->_dialogue;
isNpc = scumm_stricmp(_z->u.speak->_name, "yourself") && _z->u.speak->_name[0] != '\0';
_questioner = isNpc ? _vm->_disk->loadTalk(_z->u.speak->_name) : _vm->_char._talk;
@@ -168,16 +197,16 @@ bool DialogueManager::displayAnswers() {
if (_askPassword) {
resetPassword();
// _vm->_balloonMan->setDialogueBalloon(_q->_answers[0]->_text, 1, 3);
- int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y);
+ int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y);
_vm->_gfx->setItemFrame(id, 0);
} else
if (_numVisAnswers == 1) {
- int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y);
+ int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y);
_vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF);
_vm->_balloonMan->setBalloonText(0, _q->_answers[_visAnswers[0]]->_text, 0);
} else
if (_numVisAnswers > 1) {
- int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y);
+ int id = _vm->_gfx->setItem(_answerer, _ballonPos._answerChar.x, _ballonPos._answerChar.y);
_vm->_gfx->setItemFrame(id, _q->_answers[_visAnswers[0]]->_mood & 0xF);
_oldSelection = -1;
_selection = 0;
@@ -189,8 +218,8 @@ bool DialogueManager::displayAnswers() {
bool DialogueManager::displayQuestion() {
if (!scumm_stricmp(_q->_text, "NULL")) return false;
- _vm->_balloonMan->setSingleBalloon(_q->_text, QUESTION_BALLOON_X, QUESTION_BALLOON_Y, _q->_mood & 0x10, 0);
- int id = _vm->_gfx->setItem(_questioner, QUESTION_CHARACTER_X, QUESTION_CHARACTER_Y);
+ _vm->_balloonMan->setSingleBalloon(_q->_text, _ballonPos._questionBalloon.x, _ballonPos._questionBalloon.y, _q->_mood & 0x10, 0);
+ int id = _vm->_gfx->setItem(_questioner, _ballonPos._questionChar.x, _ballonPos._questionChar.y);
_vm->_gfx->setItemFrame(id, _q->_mood & 0xF);
return true;
@@ -361,9 +390,6 @@ void DialogueManager::run() {
break;
case DIALOGUE_OVER:
- if (_cmdList) {
- _vm->_cmdExec->run(*_cmdList);
- }
break;
default:
@@ -383,6 +409,10 @@ void Parallaction::exitDialogueMode() {
debugC(1, kDebugDialogue, "Parallaction::exitDialogueMode()");
_input->_inputMode = Input::kInputModeGame;
+ if (_dialogueMan->_cmdList) {
+ _vm->_cmdExec->run(*_dialogueMan->_cmdList);
+ }
+
// The current instance of _dialogueMan must be destroyed before the zone commands
// are executed, because they may create another instance of _dialogueMan that
// overwrite the current one. This would cause headaches (and it did, actually).
diff --git a/engines/parallaction/disk.h b/engines/parallaction/disk.h
index 694d4efa6d..341229a649 100644
--- a/engines/parallaction/disk.h
+++ b/engines/parallaction/disk.h
@@ -28,6 +28,8 @@
#define PATH_LEN 200
+#include "common/fs.h"
+
#include "common/file.h"
#include "graphics/surface.h"
@@ -202,15 +204,29 @@ public:
class DosDisk_br : public Disk {
protected:
+ uint16 _language;
+
Parallaction *_vm;
- char _partPath[PATH_LEN];
- char _languageDir[2];
+
+ FilesystemNode _baseDir;
+ FilesystemNode _partDir;
+
+ FilesystemNode _aniDir;
+ FilesystemNode _bkgDir;
+ FilesystemNode _mscDir;
+ FilesystemNode _mskDir;
+ FilesystemNode _pthDir;
+ FilesystemNode _rasDir;
+ FilesystemNode _scrDir;
+ FilesystemNode _sfxDir;
+ FilesystemNode _talDir;
protected:
- void errorFileNotFound(const char *s);
+ void errorFileNotFound(const FilesystemNode &dir, const Common::String &filename);
Font *createFont(const char *name, Common::ReadStream &stream);
Sprites* createSprites(Common::ReadStream &stream);
void loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette);
+ GfxObj* createInventoryObjects(Common::SeekableReadStream &stream);
public:
DosDisk_br(Parallaction *vm);
@@ -234,15 +250,33 @@ public:
Common::ReadStream* loadSound(const char* name);
};
+class DosDemo_br : public DosDisk_br {
+
+public:
+ DosDemo_br(Parallaction *vm);
+ virtual ~DosDemo_br();
+
+ Common::String selectArchive(const Common::String& name);
+
+};
+
class AmigaDisk_br : public DosDisk_br {
protected:
BackgroundInfo _backgroundTemp;
- Sprites* createSprites(const char *name);
+ Sprites* createSprites(Common::ReadStream &stream);
Font *createFont(const char *name, Common::SeekableReadStream &stream);
- void loadMask(BackgroundInfo& info, const char *name);
- void loadBackground(BackgroundInfo& info, const char *name);
+ void loadBackground(BackgroundInfo& info, Common::SeekableReadStream &stream);
+
+ FilesystemNode _baseBkgDir;
+ FilesystemNode _fntDir;
+ FilesystemNode _commonAniDir;
+ FilesystemNode _commonBkgDir;
+ FilesystemNode _commonMscDir;
+ FilesystemNode _commonMskDir;
+ FilesystemNode _commonPthDir;
+ FilesystemNode _commonTalDir;
public:
AmigaDisk_br(Parallaction *vm);
@@ -254,6 +288,11 @@ public:
Frames* loadFrames(const char* name);
void loadSlide(BackgroundInfo& info, const char *filename);
void loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path);
+ GfxObj* loadObjects(const char *name);
+ Common::SeekableReadStream* loadMusic(const char* name);
+ Common::ReadStream* loadSound(const char* name);
+ Common::String selectArchive(const Common::String& name);
+
};
} // namespace Parallaction
diff --git a/engines/parallaction/disk_br.cpp b/engines/parallaction/disk_br.cpp
index ee1e111139..11f67a2d58 100644
--- a/engines/parallaction/disk_br.cpp
+++ b/engines/parallaction/disk_br.cpp
@@ -25,6 +25,7 @@
#include "graphics/iff.h"
+#include "common/config-manager.h"
#include "parallaction/parallaction.h"
@@ -90,49 +91,40 @@ struct Sprites : public Frames {
-void DosDisk_br::errorFileNotFound(const char *s) {
- error("File '%s' not found", s);
+void DosDisk_br::errorFileNotFound(const FilesystemNode &dir, const Common::String &filename) {
+ error("File '%s' not found in directory '%s'", filename.c_str(), dir.getDisplayName().c_str());
}
Common::String DosDisk_br::selectArchive(const Common::String& name) {
debugC(5, kDebugDisk, "DosDisk_br::selectArchive");
- Common::String oldPath(_partPath);
- strcpy(_partPath, name.c_str());
+ Common::String oldPath;
+ if (_partDir.exists()) {
+ oldPath = _partDir.getDisplayName();
+ }
+
+ _partDir = _baseDir.getChild(name);
+
+ _aniDir = _partDir.getChild("ani");
+ _bkgDir = _partDir.getChild("bkg");
+ _mscDir = _partDir.getChild("msc");
+ _mskDir = _partDir.getChild("msk");
+ _pthDir = _partDir.getChild("pth");
+ _rasDir = _partDir.getChild("ras");
+ _scrDir = _partDir.getChild("scripts");
+ _sfxDir = _partDir.getChild("sfx");
+ _talDir = _partDir.getChild("tal");
return oldPath;
}
void DosDisk_br::setLanguage(uint16 language) {
debugC(5, kDebugDisk, "DosDisk_br::setLanguage");
-
- switch (language) {
- case 0:
- strcpy(_languageDir, "it");
- break;
-
- case 1:
- strcpy(_languageDir, "fr");
- break;
-
- case 2:
- strcpy(_languageDir, "en");
- break;
-
- case 3:
- strcpy(_languageDir, "ge");
- break;
-
- default:
- error("unknown language");
-
- }
-
- return;
+ assert(language < 4);
+ _language = language;
}
-DosDisk_br::DosDisk_br(Parallaction* vm) : _vm(vm) {
-
+DosDisk_br::DosDisk_br(Parallaction* vm) : _vm(vm), _baseDir(ConfMan.get("path")) {
}
DosDisk_br::~DosDisk_br() {
@@ -141,45 +133,63 @@ DosDisk_br::~DosDisk_br() {
GfxObj* DosDisk_br::loadTalk(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadTalk(%s)", name);
- Common::File stream;
-
- char path[PATH_LEN];
- sprintf(path, "%s/tal/%s", _partPath, name);
- if (!stream.open(path)) {
- sprintf(path, "%s/tal/%s.tal", _partPath, name);
- if (!stream.open(path))
- errorFileNotFound(path);
+ Common::String path(name);
+ FilesystemNode node = _talDir.getChild(path);
+ if (!node.exists()) {
+ path += ".tal";
+ node = _talDir.getChild(path);
+ if (!node.exists())
+ errorFileNotFound(_talDir, path);
}
- return new GfxObj(0, createSprites(stream), name);
+ Common::File stream;
+ stream.open(node);
+
+ // talk position is set to (0,0), because talks are always displayed at
+ // absolute coordinates, set in the dialogue manager. The original used
+ // to null out coordinates every time they were needed. We do it better!
+ Sprites *spr = createSprites(stream);
+ for (int i = 0; i < spr->getNum(); i++) {
+ spr->_sprites[i].x = 0;
+ spr->_sprites[i].y = 0;
+ }
+ return new GfxObj(0, spr, name);
}
Script* DosDisk_br::loadLocation(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadLocation");
- Common::File *stream = new Common::File;
-
- char path[PATH_LEN];
- sprintf(path, "%s/%s/%s.slf", _partPath, _languageDir, name);
- if (!stream->open(path)) {
- sprintf(path, "%s/%s/%s.loc", _partPath, _languageDir, name);
- if (!stream->open(path))
- errorFileNotFound(path);
+ Common::String langs[4] = { "it", "fr", "en", "ge" };
+ FilesystemNode locDir = _partDir.getChild(langs[_language]);
+
+ Common::String path(name);
+ path += ".slf";
+ FilesystemNode node = locDir.getChild(path);
+ if (!node.exists()) {
+ path = Common::String(name) + ".loc";
+ node = locDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(locDir, path);
+ }
}
+ Common::File *stream = new Common::File;
+ stream->open(node);
return new Script(stream, true);
}
Script* DosDisk_br::loadScript(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadScript");
- Common::File *stream = new Common::File;
-
- char path[PATH_LEN];
- sprintf(path, "%s/scripts/%s.scr", _partPath, name);
- if (!stream->open(path))
- errorFileNotFound(path);
+ Common::String path(name);
+ path += ".scr";
+ FilesystemNode node = _scrDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_scrDir, path);
+ }
+ Common::File *stream = new Common::File;
+ stream->open(node);
return new Script(stream, true);
}
@@ -192,6 +202,7 @@ GfxObj* DosDisk_br::loadHead(const char* name) {
void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surface &surf, byte *palette) {
stream.skip(4);
uint width = stream.readUint32BE();
+ if (width & 1) width++;
uint height = stream.readUint32BE();
stream.skip(20);
@@ -208,12 +219,15 @@ void DosDisk_br::loadBitmap(Common::SeekableReadStream &stream, Graphics::Surfac
Frames* DosDisk_br::loadPointer(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadPointer");
- char path[PATH_LEN];
- sprintf(path, "%s.ras", name);
+ Common::String path(name);
+ path += ".ras";
+ FilesystemNode node = _baseDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_baseDir, path);
+ }
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
+ stream.open(node);
Graphics::Surface *surf = new Graphics::Surface;
loadBitmap(stream, *surf, 0);
@@ -224,20 +238,32 @@ Frames* DosDisk_br::loadPointer(const char *name) {
Font* DosDisk_br::loadFont(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadFont");
- char path[PATH_LEN];
- sprintf(path, "%s.fnt", name);
+ Common::String path(name);
+ path += ".fnt";
+ FilesystemNode node = _baseDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_baseDir, path);
+ }
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
-
+ stream.open(node);
return createFont(name, stream);
}
GfxObj* DosDisk_br::loadObjects(const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadObjects");
- return 0;
+
+ Common::String path(name);
+ FilesystemNode node = _partDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_partDir, path);
+ }
+
+ Common::File stream;
+ stream.open(node);
+
+ return createInventoryObjects(stream);
}
void genSlidePath(char *path, const char* name) {
@@ -247,13 +273,15 @@ void genSlidePath(char *path, const char* name) {
GfxObj* DosDisk_br::loadStatic(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadStatic");
- char path[PATH_LEN];
- sprintf(path, "%s/ras/%s", _partPath, name);
- Common::File stream;
- if (!stream.open(path)) {
- errorFileNotFound(path);
+ Common::String path(name);
+ FilesystemNode node = _rasDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_rasDir, path);
}
+ Common::File stream;
+ stream.open(node);
+
Graphics::Surface *surf = new Graphics::Surface;
loadBitmap(stream, *surf, 0);
return new GfxObj(0, new SurfaceToFrames(surf), name);
@@ -283,17 +311,18 @@ Sprites* DosDisk_br::createSprites(Common::ReadStream &stream) {
Frames* DosDisk_br::loadFrames(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadFrames");
- char path[PATH_LEN];
- sprintf(path, "%s/ani/%s", _partPath, name);
-
- Common::File stream;
- if (!stream.open(path)) {
- sprintf(path, "%s/ani/%s.ani", _partPath, name);
- if (!stream.open(path)) {
- errorFileNotFound(path);
+ Common::String path(name);
+ FilesystemNode node = _aniDir.getChild(path);
+ if (!node.exists()) {
+ path += ".ani";
+ node = _aniDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_aniDir, path);
}
}
+ Common::File stream;
+ stream.open(node);
return createSprites(stream);
}
@@ -305,12 +334,15 @@ Frames* DosDisk_br::loadFrames(const char* name) {
void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
debugC(5, kDebugDisk, "DosDisk_br::loadSlide");
- char path[PATH_LEN];
- genSlidePath(path, name);
+ Common::String path(name);
+ path += ".bmp";
+ FilesystemNode node = _baseDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_baseDir, path);
+ }
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
+ stream.open(node);
byte rgb[768];
@@ -328,13 +360,17 @@ void DosDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char *mask, const char* path) {
debugC(5, kDebugDisk, "DosDisk_br::loadScenery");
- char filename[PATH_LEN];
+ Common::String filepath;
+ FilesystemNode node;
Common::File stream;
if (name) {
- sprintf(filename, "%s/bkg/%s.bkg", _partPath, name);
- if (!stream.open(filename))
- errorFileNotFound(filename);
+ filepath = Common::String(name) + ".bkg";
+ node = _bkgDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_bkgDir, filepath);
+ }
+ stream.open(node);
byte rgb[768];
@@ -350,9 +386,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char
}
if (mask) {
- sprintf(filename, "%s/msk/%s.msk", _partPath, mask);
- if (!stream.open(filename))
- errorFileNotFound(filename);
+ filepath = Common::String(mask) + ".msk";
+ node = _mskDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_mskDir, filepath);
+ }
+ stream.open(node);
// NOTE: info.width and info.height are only valid if the background graphics
// have already been loaded
@@ -363,9 +402,12 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char
}
if (path) {
- sprintf(filename, "%s/pth/%s.pth", _partPath, path);
- if (!stream.open(filename))
- errorFileNotFound(filename);
+ filepath = Common::String(path) + ".pth";
+ node = _pthDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_pthDir, filepath);
+ }
+ stream.open(node);
// NOTE: info.width and info.height are only valid if the background graphics
// have already been loaded
@@ -380,15 +422,16 @@ void DosDisk_br::loadScenery(BackgroundInfo& info, const char *name, const char
Table* DosDisk_br::loadTable(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadTable");
- char path[PATH_LEN];
- sprintf(path, "%s/%s.tab", _partPath, name);
-
- Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
+ Common::String path(name);
+ path += ".tab";
+ FilesystemNode node = _partDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_partDir, path);
+ }
+ Common::File stream;
+ stream.open(node);
Table *t = createTableFromStream(100, stream);
-
stream.close();
return t;
@@ -410,56 +453,68 @@ Common::ReadStream* DosDisk_br::loadSound(const char* name) {
-AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) {
+DosDemo_br::DosDemo_br(Parallaction *vm) : DosDisk_br(vm) {
}
-AmigaDisk_br::~AmigaDisk_br() {
+DosDemo_br::~DosDemo_br() {
}
+Common::String DosDemo_br::selectArchive(const Common::String& name) {
+ debugC(5, kDebugDisk, "DosDemo_br::selectArchive");
-/*
- FIXME: mask values are not computed correctly for level 1 and 2
+ Common::String oldPath;
+ if (_partDir.exists()) {
+ oldPath = _partDir.getDisplayName();
+ }
- NOTE: this routine is only able to build masks for Nippon Safes, since mask widths are hardcoded
- into the main loop.
-*/
-void buildMask2(byte* buf) {
+ _partDir = _baseDir;
- byte mask1[16] = { 0, 0x80, 0x20, 0xA0, 8, 0x88, 0x28, 0xA8, 2, 0x82, 0x22, 0xA2, 0xA, 0x8A, 0x2A, 0xAA };
- byte mask0[16] = { 0, 0x40, 0x10, 0x50, 4, 0x44, 0x14, 0x54, 1, 0x41, 0x11, 0x51, 0x5, 0x45, 0x15, 0x55 };
+ _aniDir = _partDir.getChild("ani");
+ _bkgDir = _partDir.getChild("bkg");
+ _mscDir = _partDir.getChild("msc");
+ _mskDir = _partDir.getChild("msk");
+ _pthDir = _partDir.getChild("pth");
+ _rasDir = _partDir.getChild("ras");
+ _scrDir = _partDir.getChild("scripts");
+ _sfxDir = _partDir.getChild("sfx");
+ _talDir = _partDir.getChild("tal");
- byte plane0[40];
- byte plane1[40];
+ return oldPath;
+}
- for (int32 i = 0; i < _vm->_screenHeight; i++) {
- memcpy(plane0, buf, 40);
- memcpy(plane1, buf+40, 40);
- for (uint32 j = 0; j < 40; j++) {
- *buf++ = mask0[(plane0[j] & 0xF0) >> 4] | mask1[(plane1[j] & 0xF0) >> 4];
- *buf++ = mask0[plane0[j] & 0xF] | mask1[plane1[j] & 0xF];
- }
- }
+
+
+AmigaDisk_br::AmigaDisk_br(Parallaction *vm) : DosDisk_br(vm) {
+ _fntDir = _baseDir.getChild("fonts");
+
+ _baseBkgDir = _baseDir.getChild("backs");
+
+ FilesystemNode commonDir = _baseDir.getChild("common");
+ _commonAniDir = commonDir.getChild("anims");
+ _commonBkgDir = commonDir.getChild("backs");
+ _commonMscDir = commonDir.getChild("msc");
+ _commonMskDir = commonDir.getChild("msk");
+ _commonPthDir = commonDir.getChild("pth");
+ _commonTalDir = commonDir.getChild("talks");
}
-void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) {
- char path[PATH_LEN];
- sprintf(path, "%s", name);
+AmigaDisk_br::~AmigaDisk_br() {
+
+}
- Common::File s;
- if (!s.open(path))
- errorFileNotFound(path);
+void AmigaDisk_br::loadBackground(BackgroundInfo& info, Common::SeekableReadStream &stream) {
byte *pal;
- Graphics::ILBMDecoder decoder(s, info.bg, pal);
+ Graphics::ILBMDecoder decoder(stream, info.bg, pal);
decoder.decode();
uint i;
@@ -483,58 +538,60 @@ void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) {
return;
}
-void AmigaDisk_br::loadMask(BackgroundInfo& info, const char *name) {
- debugC(5, kDebugDisk, "AmigaDisk_br::loadMask(%s)", name);
-
- Common::File s;
-
- if (!s.open(name))
- return;
-
- s.seek(0x30, SEEK_SET);
-
- byte r, g, b;
- for (uint i = 0; i < 4; i++) {
- r = s.readByte();
- g = s.readByte();
- b = s.readByte();
-
- info.layers[i] = (((r << 4) & 0xF00) | (g & 0xF0) | (b >> 4)) & 0xFF;
- }
-
- s.seek(0x126, SEEK_SET); // HACK: skipping IFF/ILBM header should be done by analysis, not magic
- Graphics::PackBitsReadStream stream(s);
-
- info.mask.create(info.width, info.height);
- stream.read(info.mask.data, info.mask.size);
- buildMask2(info.mask.data);
-
- return;
-}
void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const char* mask, const char* path) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadScenery '%s', '%s' '%s'", name, mask, path);
- char filename[PATH_LEN];
+ Common::String filepath;
+ FilesystemNode node;
Common::File stream;
if (name) {
- sprintf(filename, "%s/backs/%s.bkg", _partPath, name);
-
- loadBackground(info, filename);
+ filepath = Common::String(name) + ".bkg";
+ node = _bkgDir.getChild(filepath);
+ if (!node.exists()) {
+ filepath = Common::String(name) + ".bkg";
+ node = _commonBkgDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_bkgDir, filepath);
+ }
+ }
+ stream.open(node);
+ loadBackground(info, stream);
+ stream.close();
}
+#if 0
+ if (mask && _mskDir.exists()) {
+ filepath = Common::String(mask) + ".msk";
+ node = _mskDir.getChild(filepath);
+ if (!node.exists()) {
+ filepath = Common::String(mask) + ".msk";
+ node = _commonMskDir.getChild(filepath);
+ }
- if (mask) {
- sprintf(filename, "%s/msk/%s.msk", _partPath, name);
-
- loadMask(info, filename);
+ if (node.exists()) {
+ stream.open(node);
+ stream.seek(0x30, SEEK_SET);
+ Graphics::PackBitsReadStream unpackedStream(stream);
+ info.mask.create(info.width, info.height);
+ unpackedStream.read(info.mask.data, info.mask.size);
+ // TODO: there is another step to do after decompression...
+ loadMask(info, stream);
+ stream.close();
+ }
}
-
- if (path) {
- sprintf(filename, "%s/pth/%s.pth", _partPath, path);
- if (!stream.open(filename))
- errorFileNotFound(filename);
-
+#endif
+ if (path && _pthDir.exists()) {
+ filepath = Common::String(path) + ".pth";
+ node = _pthDir.getChild(filepath);
+ if (!node.exists()) {
+ filepath = Common::String(path) + ".pth";
+ node = _commonPthDir.getChild(filepath);
+ if (!node.exists()) {
+ errorFileNotFound(_pthDir, filepath);
+ }
+ }
+ stream.open(node);
// NOTE: info.width and info.height are only valid if the background graphics
// have already been loaded
info.path.create(info.width, info.height);
@@ -548,22 +605,28 @@ void AmigaDisk_br::loadScenery(BackgroundInfo& info, const char* name, const cha
void AmigaDisk_br::loadSlide(BackgroundInfo& info, const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadSlide '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "backs/%s.bkg", name);
-
- loadBackground(info, path);
+ Common::String path(name);
+ path += ".bkg";
+ FilesystemNode node = _baseBkgDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_baseBkgDir, path);
+ }
+ Common::File stream;
+ stream.open(node);
+ loadBackground(info, stream);
return;
}
GfxObj* AmigaDisk_br::loadStatic(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadStatic '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "%s/ras/%s", _partPath, name);
- Common::File stream;
- if (!stream.open(path)) {
- errorFileNotFound(path);
+ Common::String path(name);
+ FilesystemNode node = _rasDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_rasDir, path);
}
+ Common::File stream;
+ stream.open(node);
byte *pal = 0;
Graphics::Surface* surf = new Graphics::Surface;
@@ -576,13 +639,7 @@ GfxObj* AmigaDisk_br::loadStatic(const char* name) {
return new GfxObj(0, new SurfaceToFrames(surf));
}
-Sprites* AmigaDisk_br::createSprites(const char *path) {
-
- Common::File stream;
- if (!stream.open(path)) {
- errorFileNotFound(path);
- }
-
+Sprites* AmigaDisk_br::createSprites(Common::ReadStream &stream) {
uint16 num = stream.readUint16BE();
Sprites *sprites = new Sprites(num);
@@ -606,32 +663,163 @@ Sprites* AmigaDisk_br::createSprites(const char *path) {
Frames* AmigaDisk_br::loadFrames(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadFrames '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "%s/anims/%s", _partPath, name);
+ Common::String path(name);
+ FilesystemNode node = _aniDir.getChild(path);
+ if (!node.exists()) {
+ path += ".ani";
+ node = _aniDir.getChild(path);
+ if (!node.exists()) {
+ path = Common::String(name);
+ node = _commonAniDir.getChild(path);
+ if (!node.exists()) {
+ path += ".ani";
+ node = _commonAniDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_aniDir, path);
+ }
+ }
+ }
+ }
- return createSprites(path);
+ Common::File stream;
+ stream.open(node);
+ return createSprites(stream);
}
GfxObj* AmigaDisk_br::loadTalk(const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_br::loadTalk '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "%s/talks/%s.tal", _partPath, name);
+ Common::String path(name);
+ FilesystemNode node = _talDir.getChild(path);
+ if (!node.exists()) {
+ path += ".tal";
+ node = _talDir.getChild(path);
+ if (!node.exists()) {
+ path = Common::String(name);
+ node = _commonTalDir.getChild(path);
+ if (!node.exists()) {
+ path += ".tal";
+ node = _commonTalDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_talDir, path);
+ }
+ }
+ }
+ }
- return new GfxObj(0, createSprites(path));
+ Common::File stream;
+ stream.open(node);
+ return new GfxObj(0, createSprites(stream));
}
Font* AmigaDisk_br::loadFont(const char* name) {
debugC(1, kDebugDisk, "AmigaFullDisk::loadFont '%s'", name);
- char path[PATH_LEN];
- sprintf(path, "%s", name);
+ Common::String path(name);
+ path += ".font";
+ FilesystemNode node = _fntDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_fntDir, path);
+ }
+
+ Common::String fontDir;
+ Common::String fontFile;
+ byte ch;
Common::File stream;
- if (!stream.open(path))
- errorFileNotFound(path);
+ stream.open(node);
+ stream.seek(4, SEEK_SET);
+ while ((ch = stream.readByte()) != 0x2F) fontDir += ch;
+ while ((ch = stream.readByte()) != 0) fontFile += ch;
+ stream.close();
+ node = _fntDir.getChild(fontDir);
+ if (!node.exists()) {
+ errorFileNotFound(_fntDir, fontDir);
+ }
+ node = node.getChild(fontFile);
+ if (!node.exists()) {
+ errorFileNotFound(node, fontFile);
+ }
+
+ stream.open(node);
return createFont(name, stream);
}
+Common::SeekableReadStream* AmigaDisk_br::loadMusic(const char* name) {
+ debugC(5, kDebugDisk, "AmigaDisk_br::loadMusic");
+
+ Common::String path(name);
+ FilesystemNode node = _mscDir.getChild(path);
+ if (!node.exists()) {
+ // TODO (Kirben): error out when music file is not found?
+ return 0;
+ }
+
+ Common::File *stream = new Common::File;
+ stream->open(node);
+ return stream;
+}
+
+
+Common::ReadStream* AmigaDisk_br::loadSound(const char* name) {
+ debugC(5, kDebugDisk, "AmigaDisk_br::loadSound");
+
+ Common::String path(name);
+ FilesystemNode node = _sfxDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_sfxDir, path);
+ }
+
+ Common::File *stream = new Common::File;
+ stream->open(node);
+ return stream;
+}
+
+GfxObj* AmigaDisk_br::loadObjects(const char *name) {
+ debugC(5, kDebugDisk, "AmigaDisk_br::loadObjects");
+
+ Common::String path(name);
+ FilesystemNode node = _partDir.getChild(path);
+ if (!node.exists()) {
+ errorFileNotFound(_partDir, path);
+ }
+
+ Common::File stream;
+ stream.open(node);
+
+ byte *pal = 0;
+ Graphics::Surface* surf = new Graphics::Surface;
+
+ Graphics::ILBMDecoder decoder(stream, *surf, pal);
+ decoder.decode();
+
+ free(pal);
+
+ return new GfxObj(0, new SurfaceToFrames(surf));
+}
+
+Common::String AmigaDisk_br::selectArchive(const Common::String& name) {
+ debugC(5, kDebugDisk, "AmigaDisk_br::selectArchive");
+
+ Common::String oldPath;
+ if (_partDir.exists()) {
+ oldPath = _partDir.getDisplayName();
+ }
+
+ _partDir = _baseDir.getChild(name);
+
+ _aniDir = _partDir.getChild("anims");
+ _bkgDir = _partDir.getChild("backs");
+ _mscDir = _partDir.getChild("msc");
+ _mskDir = _partDir.getChild("msk");
+ _pthDir = _partDir.getChild("pth");
+ _rasDir = _partDir.getChild("ras");
+ _scrDir = _partDir.getChild("scripts");
+ _sfxDir = _partDir.getChild("sfx");
+ _talDir = _partDir.getChild("talks");
+
+ return oldPath;
+}
+
} // namespace Parallaction
diff --git a/engines/parallaction/exec.h b/engines/parallaction/exec.h
index 887d6be526..22e75744f1 100644
--- a/engines/parallaction/exec.h
+++ b/engines/parallaction/exec.h
@@ -47,14 +47,30 @@ protected:
struct ParallactionStruct1 {
CommandPtr cmd;
ZonePtr z;
+ bool suspend;
} _ctxt;
OpcodeSet _opcodes;
+ struct SuspendedContext {
+ bool valid;
+ CommandList::iterator first;
+ CommandList::iterator last;
+ ZonePtr zone;
+ } _suspendedCtxt;
+
+ ZonePtr _execZone;
+ void runList(CommandList::iterator first, CommandList::iterator last);
+ void createSuspendList(CommandList::iterator first, CommandList::iterator last);
+ void cleanSuspendedList();
+
public:
virtual void init() = 0;
virtual void run(CommandList &list, ZonePtr z = nullZonePtr);
+ void runSuspended();
+
CommandExec() {
+ _suspendedCtxt.valid = false;
}
virtual ~CommandExec() {
for (Common::Array<const Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i)
@@ -143,23 +159,23 @@ public:
~CommandExec_br();
};
-
-
-
-
class ProgramExec {
protected:
struct ParallactionStruct2 {
AnimationPtr anim;
ProgramPtr program;
InstructionList::iterator inst;
+ InstructionList::iterator ip;
uint16 modCounter;
bool suspend;
} _ctxt;
+ const char **_instructionNames;
+
OpcodeSet _opcodes;
uint16 _modCounter;
+ void runScript(ProgramPtr script, AnimationPtr a);
public:
virtual void init() = 0;
@@ -183,7 +199,7 @@ protected:
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(null);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(show);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
@@ -208,7 +224,6 @@ class ProgramExec_br : public ProgramExec_ns {
protected:
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
@@ -228,7 +243,6 @@ protected:
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif);
DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
public:
void init();
diff --git a/engines/parallaction/exec_br.cpp b/engines/parallaction/exec_br.cpp
index edb832cffc..0b7400f0f7 100644
--- a/engines/parallaction/exec_br.cpp
+++ b/engines/parallaction/exec_br.cpp
@@ -61,8 +61,6 @@ namespace Parallaction {
#define INST_STOP 30
#define INST_ENDSCRIPT 31
-
-
#define SetOpcodeTable(x) table = &x;
typedef Common::Functor0Mem<void, CommandExec_br> OpcodeV1;
@@ -73,6 +71,8 @@ typedef Common::Functor0Mem<void, ProgramExec_br> OpcodeV2;
#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_br::instOp_##op))
#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_br::instOp_##op()
+extern const char *_instructionNamesRes_br[];
+
void Parallaction_br::setupSubtitles(char *s, char *s2, int y) {
debugC(5, kDebugExec, "setupSubtitles(%s, %s, %i)", s, s2, y);
@@ -122,11 +122,19 @@ DECLARE_COMMAND_OPCODE(location) {
DECLARE_COMMAND_OPCODE(open) {
warning("Parallaction_br::cmdOp_open command not yet implemented");
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsClosed;
+ if (_ctxt.cmd->u._zone->u.door->gfxobj) {
+ _vm->updateDoor(_ctxt.cmd->u._zone);
+ }
}
DECLARE_COMMAND_OPCODE(close) {
warning("Parallaction_br::cmdOp_close not yet implemented");
+ _ctxt.cmd->u._zone->_flags |= kFlagsClosed;
+ if (_ctxt.cmd->u._zone->u.door->gfxobj) {
+ _vm->updateDoor(_ctxt.cmd->u._zone);
+ }
}
@@ -165,12 +173,13 @@ DECLARE_COMMAND_OPCODE(call) {
DECLARE_COMMAND_OPCODE(drop) {
- warning("Parallaction_br::cmdOp_drop not yet implemented");
+ _vm->dropItem(_ctxt.cmd->u._object);
}
DECLARE_COMMAND_OPCODE(move) {
- warning("Parallaction_br::cmdOp_move not yet implemented");
+ _vm->_char.scheduleWalk(_ctxt.cmd->u._move.x, _ctxt.cmd->u._move.y);
+ _ctxt.suspend = true;
}
DECLARE_COMMAND_OPCODE(start) {
@@ -194,17 +203,17 @@ DECLARE_COMMAND_OPCODE(followme) {
DECLARE_COMMAND_OPCODE(onmouse) {
- _vm->_input->showCursor(true);
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
}
DECLARE_COMMAND_OPCODE(offmouse) {
- _vm->_input->showCursor(false);
+ _vm->_input->setMouseState(MOUSE_DISABLED);
}
DECLARE_COMMAND_OPCODE(add) {
- warning("Parallaction_br::cmdOp_add not yet implemented");
+ _vm->addInventoryItem(_ctxt.cmd->u._object);
}
@@ -365,13 +374,6 @@ DECLARE_INSTRUCTION_OPCODE(set) {
}
-DECLARE_INSTRUCTION_OPCODE(loop) {
- InstructionPtr inst = *_ctxt.inst;
-
- _ctxt.program->_loopCounter = inst->_opB.getRValue();
- _ctxt.program->_loopStart = _ctxt.inst;
-}
-
DECLARE_INSTRUCTION_OPCODE(inc) {
InstructionPtr inst = *_ctxt.inst;
@@ -495,16 +497,6 @@ DECLARE_INSTRUCTION_OPCODE(stop) {
warning("Parallaction_br::instOp_stop not yet implemented");
}
-DECLARE_INSTRUCTION_OPCODE(endscript) {
- if ((_ctxt.anim->_flags & kFlagsLooping) == 0) {
- _ctxt.anim->_flags &= ~kFlagsActing;
- _vm->_cmdExec->run(_ctxt.anim->_commands, _ctxt.anim);
- _ctxt.program->_status = kProgramDone;
- }
- _ctxt.program->_ip = _ctxt.program->_instructions.begin();
-
- _ctxt.suspend = true;
-}
void CommandExec_br::init() {
Common::Array<const Opcode*> *table = 0;
@@ -576,7 +568,7 @@ void ProgramExec_br::init() {
INSTRUCTION_OPCODE(set); // f
INSTRUCTION_OPCODE(loop);
INSTRUCTION_OPCODE(endloop);
- INSTRUCTION_OPCODE(null); // show
+ INSTRUCTION_OPCODE(show); // show
INSTRUCTION_OPCODE(inc);
INSTRUCTION_OPCODE(inc); // dec
INSTRUCTION_OPCODE(set);
@@ -602,6 +594,7 @@ void ProgramExec_br::init() {
}
ProgramExec_br::ProgramExec_br(Parallaction_br *vm) : ProgramExec_ns(vm), _vm(vm) {
+ _instructionNames = _instructionNamesRes_br;
}
ProgramExec_br::~ProgramExec_br() {
diff --git a/engines/parallaction/exec_ns.cpp b/engines/parallaction/exec_ns.cpp
index 913e7ae648..2235c4e98e 100644
--- a/engines/parallaction/exec_ns.cpp
+++ b/engines/parallaction/exec_ns.cpp
@@ -61,7 +61,7 @@ typedef Common::Functor0Mem<void, ProgramExec_ns> OpcodeV2;
#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &ProgramExec_ns::instOp_##op))
#define DECLARE_INSTRUCTION_OPCODE(op) void ProgramExec_ns::instOp_##op()
-
+extern const char *_instructionNamesRes_ns[];
DECLARE_INSTRUCTION_OPCODE(on) {
@@ -81,13 +81,13 @@ DECLARE_INSTRUCTION_OPCODE(loop) {
InstructionPtr inst = *_ctxt.inst;
_ctxt.program->_loopCounter = inst->_opB.getRValue();
- _ctxt.program->_loopStart = _ctxt.inst;
+ _ctxt.program->_loopStart = _ctxt.ip;
}
DECLARE_INSTRUCTION_OPCODE(endloop) {
if (--_ctxt.program->_loopCounter > 0) {
- _ctxt.inst = _ctxt.program->_loopStart;
+ _ctxt.ip = _ctxt.program->_loopStart;
}
}
@@ -97,7 +97,7 @@ DECLARE_INSTRUCTION_OPCODE(inc) {
if (inst->_flags & kInstMod) { // mod
int16 _bx = (_si > 0 ? _si : -_si);
- if (_modCounter % _bx != 0) return;
+ if (_ctxt.modCounter % _bx != 0) return;
_si = (_si > 0 ? 1 : -1);
}
@@ -142,8 +142,8 @@ DECLARE_INSTRUCTION_OPCODE(put) {
_vm->_gfx->patchBackground(v18, x, y, mask);
}
-DECLARE_INSTRUCTION_OPCODE(null) {
-
+DECLARE_INSTRUCTION_OPCODE(show) {
+ _ctxt.suspend = true;
}
DECLARE_INSTRUCTION_OPCODE(invalid) {
@@ -156,8 +156,10 @@ DECLARE_INSTRUCTION_OPCODE(call) {
DECLARE_INSTRUCTION_OPCODE(wait) {
- if (_engineFlags & kEngineWalking)
+ if (_engineFlags & kEngineWalking) {
+ _ctxt.ip--;
_ctxt.suspend = true;
+ }
}
@@ -186,8 +188,8 @@ DECLARE_INSTRUCTION_OPCODE(endscript) {
_vm->_cmdExec->run(_ctxt.anim->_commands, _ctxt.anim);
_ctxt.program->_status = kProgramDone;
}
- _ctxt.program->_ip = _ctxt.program->_instructions.begin();
+ _ctxt.ip = _ctxt.program->_instructions.begin();
_ctxt.suspend = true;
}
@@ -332,35 +334,40 @@ void Parallaction_ns::drawAnimations() {
for (AnimationList::iterator it = _location._animations.begin(); it != _location._animations.end(); it++) {
- AnimationPtr v18 = *it;
- GfxObj *obj = v18->gfxobj;
+ AnimationPtr anim = *it;
+ GfxObj *obj = anim->gfxobj;
+
+ // Validation is performed here, so that every animation is affected, instead that only the ones
+ // who *own* a script. In fact, some scripts can change values in other animations.
+ // The right way to do this would be to enforce validation when any variable is modified from
+ // a script.
+ anim->validateScriptVars();
- if ((v18->_flags & kFlagsActive) && ((v18->_flags & kFlagsRemove) == 0)) {
+ if ((anim->_flags & kFlagsActive) && ((anim->_flags & kFlagsRemove) == 0)) {
- int16 frame = CLIP((int)v18->_frame, 0, v18->getFrameNum()-1);
- if (v18->_flags & kFlagsNoMasked)
+ if (anim->_flags & kFlagsNoMasked)
layer = 3;
else
- layer = _gfx->_backgroundInfo.getLayer(v18->_top + v18->height());
+ layer = _gfx->_backgroundInfo->getLayer(anim->_top + anim->height());
if (obj) {
_gfx->showGfxObj(obj, true);
- obj->frame = frame;
- obj->x = v18->_left;
- obj->y = v18->_top;
- obj->z = v18->_z;
+ obj->frame = anim->_frame;
+ obj->x = anim->_left;
+ obj->y = anim->_top;
+ obj->z = anim->_z;
obj->layer = layer;
}
}
- if (((v18->_flags & kFlagsActive) == 0) && (v18->_flags & kFlagsRemove)) {
- v18->_flags &= ~kFlagsRemove;
- v18->_oldPos.x = -1000;
+ if (((anim->_flags & kFlagsActive) == 0) && (anim->_flags & kFlagsRemove)) {
+ anim->_flags &= ~kFlagsRemove;
+ anim->_oldPos.x = -1000;
}
- if ((v18->_flags & kFlagsActive) && (v18->_flags & kFlagsRemove)) {
- v18->_flags &= ~kFlagsActive;
- v18->_flags |= kFlagsRemove;
+ if ((anim->_flags & kFlagsActive) && (anim->_flags & kFlagsRemove)) {
+ anim->_flags &= ~kFlagsActive;
+ anim->_flags |= kFlagsRemove;
if (obj) {
_gfx->showGfxObj(obj, false);
}
@@ -372,14 +379,41 @@ void Parallaction_ns::drawAnimations() {
return;
}
+void ProgramExec::runScript(ProgramPtr script, AnimationPtr a) {
+ debugC(9, kDebugExec, "runScript(Animation = %s)", a->_name);
+
+ _ctxt.ip = script->_ip;
+ _ctxt.anim = a;
+ _ctxt.program = script;
+ _ctxt.suspend = false;
+ _ctxt.modCounter = _modCounter;
+
+ InstructionList::iterator inst;
+ for ( ; (a->_flags & kFlagsActing) ; ) {
+
+ inst = _ctxt.ip;
+ _ctxt.inst = inst;
+ _ctxt.ip++;
+
+ debugC(9, kDebugExec, "inst [%02i] %s\n", (*inst)->_index, _instructionNames[(*inst)->_index - 1]);
+
+ script->_status = kProgramRunning;
+
+ (*_opcodes[(*inst)->_index])();
+
+ if (_ctxt.suspend)
+ break;
+
+ }
+ script->_ip = _ctxt.ip;
+
+}
void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator last) {
if (_engineFlags & kEnginePauseJobs) {
return;
}
- debugC(9, kDebugExec, "runScripts");
-
for (ProgramList::iterator it = first; it != last; it++) {
AnimationPtr a = (*it)->_anim;
@@ -390,31 +424,8 @@ void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator
if ((a->_flags & kFlagsActing) == 0)
continue;
- InstructionList::iterator inst = (*it)->_ip;
- while (((*inst)->_index != INST_SHOW) && (a->_flags & kFlagsActing)) {
-
- (*it)->_status = kProgramRunning;
-
- debugC(9, kDebugExec, "Animation: %s, instruction: %i", a->_name, (*inst)->_index); //_instructionNamesRes[(*inst)->_index - 1]);
-
- _ctxt.inst = inst;
- _ctxt.anim = AnimationPtr(a);
- _ctxt.program = *it;
- _ctxt.suspend = false;
-
- (*_opcodes[(*inst)->_index])();
+ runScript(*it, a);
- inst = _ctxt.inst; // handles endloop correctly
-
- if (_ctxt.suspend)
- goto label1;
-
- inst++;
- }
-
- (*it)->_ip = ++inst;
-
-label1:
if (a->_flags & kFlagsCharacter)
a->_z = a->_top + a->height();
}
@@ -424,24 +435,19 @@ label1:
return;
}
-void CommandExec::run(CommandList& list, ZonePtr z) {
- if (list.size() == 0) {
- debugC(3, kDebugExec, "runCommands: nothing to do");
- return;
- }
-
- debugC(3, kDebugExec, "runCommands starting");
+void CommandExec::runList(CommandList::iterator first, CommandList::iterator last) {
uint32 useFlags = 0;
bool useLocalFlags;
- CommandList::iterator it = list.begin();
- for ( ; it != list.end(); it++) {
+ _ctxt.suspend = false;
+
+ for ( ; first != last; first++) {
if (_vm->quit())
break;
- CommandPtr cmd = *it;
+ CommandPtr cmd = *first;
if (_vm->quit())
break;
@@ -462,16 +468,65 @@ void CommandExec::run(CommandList& list, ZonePtr z) {
if (!onMatch || !offMatch) continue;
- _ctxt.z = z;
+ _ctxt.z = _execZone;
_ctxt.cmd = cmd;
(*_opcodes[cmd->_id])();
+
+ if (_ctxt.suspend) {
+ createSuspendList(++first, last);
+ return;
+ }
+ }
+
+}
+
+void CommandExec::run(CommandList& list, ZonePtr z) {
+ if (list.size() == 0) {
+ debugC(3, kDebugExec, "runCommands: nothing to do");
+ return;
}
+ _execZone = z;
+
+ debugC(3, kDebugExec, "runCommands starting");
+ runList(list.begin(), list.end());
debugC(3, kDebugExec, "runCommands completed");
+}
- return;
+void CommandExec::createSuspendList(CommandList::iterator first, CommandList::iterator last) {
+ if (first == last) {
+ return;
+ }
+
+ debugC(3, kDebugExec, "CommandExec::createSuspendList()");
+
+ _suspendedCtxt.valid = true;
+ _suspendedCtxt.first = first;
+ _suspendedCtxt.last = last;
+ _suspendedCtxt.zone = _execZone;
+}
+void CommandExec::cleanSuspendedList() {
+ debugC(3, kDebugExec, "CommandExec::cleanSuspended()");
+
+ _suspendedCtxt.valid = false;
+ _suspendedCtxt.first = _suspendedCtxt.last;
+ _suspendedCtxt.zone = nullZonePtr;
+}
+
+void CommandExec::runSuspended() {
+ if (_engineFlags & kEngineWalking) {
+ return;
+ }
+
+ if (_suspendedCtxt.valid) {
+ debugC(3, kDebugExec, "CommandExec::runSuspended()");
+
+ _execZone = _suspendedCtxt.zone;
+ runList(_suspendedCtxt.first, _suspendedCtxt.last);
+ cleanSuspendedList();
+ }
}
CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : _vm(vm) {
@@ -486,35 +541,69 @@ CommandExec_ns::~CommandExec_ns() {
// ZONE TYPE: EXAMINE
//
-void Parallaction::displayComment(ExamineData *data) {
+void Parallaction::enterCommentMode(ZonePtr z) {
+ if (!z) {
+ return;
+ }
+
+ _commentZone = z;
+
+ ExamineData *data = _commentZone->u.examine;
+
if (!data->_description) {
return;
}
- int id;
+ // TODO: move this balloons stuff into DialogueManager and BalloonManager
+ if (getGameType() == GType_Nippon) {
+ int id;
+ if (data->_filename) {
+ if (data->_cnv == 0) {
+ data->_cnv = _disk->loadStatic(data->_filename);
+ }
- if (data->_filename) {
- if (data->_cnv == 0) {
- data->_cnv = _disk->loadStatic(data->_filename);
+ _gfx->setHalfbriteMode(true);
+ _balloonMan->setSingleBalloon(data->_description, 0, 90, 0, 0);
+ Common::Rect r;
+ data->_cnv->getRect(0, r);
+ id = _gfx->setItem(data->_cnv, 140, (_screenHeight - r.height())/2);
+ _gfx->setItemFrame(id, 0);
+ id = _gfx->setItem(_char._head, 100, 152);
+ _gfx->setItemFrame(id, 0);
+ } else {
+ _balloonMan->setSingleBalloon(data->_description, 140, 10, 0, 0);
+ id = _gfx->setItem(_char._talk, 190, 80);
+ _gfx->setItemFrame(id, 0);
}
-
- _gfx->setHalfbriteMode(true);
- _balloonMan->setSingleBalloon(data->_description, 0, 90, 0, 0);
- Common::Rect r;
- data->_cnv->getRect(0, r);
- id = _gfx->setItem(data->_cnv, 140, (_screenHeight - r.height())/2);
- _gfx->setItemFrame(id, 0);
- id = _gfx->setItem(_char._head, 100, 152);
- _gfx->setItemFrame(id, 0);
- } else {
- _balloonMan->setSingleBalloon(data->_description, 140, 10, 0, 0);
- id = _gfx->setItem(_char._talk, 190, 80);
+ } else
+ if (getGameType() == GType_BRA) {
+ _balloonMan->setSingleBalloon(data->_description, 0, 0, 1, 0);
+ int id = _gfx->setItem(_char._talk, 10, 80);
_gfx->setItemFrame(id, 0);
}
_input->_inputMode = Input::kInputModeComment;
}
+void Parallaction::exitCommentMode() {
+ _input->_inputMode = Input::kInputModeGame;
+
+ hideDialogueStuff();
+ _gfx->setHalfbriteMode(false);
+
+ _cmdExec->run(_commentZone->_commands, _commentZone);
+ _commentZone = nullZonePtr;
+}
+
+void Parallaction::runCommentFrame() {
+ if (_input->_inputMode != Input::kInputModeComment) {
+ return;
+ }
+
+ if (_input->getLastButtonEvent() == kMouseLeftUp) {
+ exitCommentMode();
+ }
+}
uint16 Parallaction::runZone(ZonePtr z) {
@@ -526,8 +615,8 @@ uint16 Parallaction::runZone(ZonePtr z) {
switch(subtype) {
case kZoneExamine:
- displayComment(z->u.examine);
- break;
+ enterCommentMode(z);
+ return 0;
case kZoneGet:
if (z->_flags & kFlagsFixed) break;
@@ -718,7 +807,7 @@ void ProgramExec_ns::init() {
INSTRUCTION_OPCODE(set); // f
INSTRUCTION_OPCODE(loop);
INSTRUCTION_OPCODE(endloop);
- INSTRUCTION_OPCODE(null);
+ INSTRUCTION_OPCODE(show);
INSTRUCTION_OPCODE(inc);
INSTRUCTION_OPCODE(inc); // dec
INSTRUCTION_OPCODE(set);
@@ -733,6 +822,7 @@ void ProgramExec_ns::init() {
}
ProgramExec_ns::ProgramExec_ns(Parallaction_ns *vm) : _vm(vm) {
+ _instructionNames = _instructionNamesRes_ns;
}
ProgramExec_ns::~ProgramExec_ns() {
diff --git a/engines/parallaction/font.cpp b/engines/parallaction/font.cpp
index 91848b30a4..e84dad34aa 100644
--- a/engines/parallaction/font.cpp
+++ b/engines/parallaction/font.cpp
@@ -35,6 +35,7 @@ extern byte _amigaTopazFont[];
class BraFont : public Font {
+protected:
byte *_cp;
uint _bufPitch;
@@ -45,15 +46,15 @@ class BraFont : public Font {
uint *_offsets;
byte *_data;
-
- static byte _charMap[];
+ const byte *_charMap;
byte mapChar(byte c) {
- return _charMap[c];
+ return (_charMap == 0) ? c : _charMap[c];
}
public:
- BraFont(Common::ReadStream &stream) {
+ BraFont(Common::ReadStream &stream, const byte *charMap = 0) {
+ _charMap = charMap;
_numGlyphs = stream.readByte();
_height = stream.readUint32BE();
@@ -137,7 +138,7 @@ public:
};
-byte BraFont::_charMap[] = {
+const byte _braDosFullCharMap[256] = {
// 0
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
// 1
@@ -172,6 +173,111 @@ byte BraFont::_charMap[] = {
0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
};
+const byte _braDosDemoComicCharMap[] = {
+// 0
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 1
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 2
+ 0x34, 0x49, 0x48, 0x34, 0x34, 0x34, 0x34, 0x47, 0x34, 0x34, 0x34, 0x34, 0x40, 0x34, 0x3F, 0x34,
+// 3
+ 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x46, 0x45, 0x34, 0x34, 0x34, 0x42,
+// 4
+ 0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
+// 5
+ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 6
+ 0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+// 7
+ 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 8
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 9
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// A
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// B
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// C
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// D
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// E
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// F
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
+};
+
+const byte _braDosDemoRussiaCharMap[] = {
+// 0
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 1
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 2
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 3
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 4
+ 0x34, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
+// 5
+ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 6
+ 0x34, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28,
+// 7
+ 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 8
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// 9
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// A
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// B
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// C
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// D
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// E
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34,
+// F
+ 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34, 0x34
+};
+
+class BraInventoryObjects : public BraFont, public Frames {
+
+public:
+ BraInventoryObjects(Common::ReadStream &stream) : BraFont(stream) {
+ }
+
+ // Frames implementation
+ uint16 getNum() {
+ return _numGlyphs;
+ }
+
+ byte* getData(uint16 index) {
+ assert(index < _numGlyphs);
+ return _data + (_height * _widths[index]) * index;;
+ }
+
+ void getRect(uint16 index, Common::Rect &r) {
+ assert(index < _numGlyphs);
+ r.left = 0;
+ r.top = 0;
+ r.setWidth(_widths[index]);
+ r.setHeight(_height);
+ }
+
+ uint getRawSize(uint16 index) {
+ assert(index < _numGlyphs);
+ return _widths[index] * _height;
+ }
+
+ uint getSize(uint16 index) {
+ assert(index < _numGlyphs);
+ return _widths[index] * _height;
+ }
+
+};
class DosFont : public Font {
@@ -537,7 +643,19 @@ Font *AmigaDisk_ns::createFont(const char *name, Common::SeekableReadStream &str
Font *DosDisk_br::createFont(const char *name, Common::ReadStream &stream) {
// printf("DosDisk_br::createFont(%s)\n", name);
- return new BraFont(stream);
+ Font *font;
+
+ if (_vm->getFeatures() & GF_DEMO) {
+ if (!scumm_stricmp(name, "russia")) {
+ font = new BraFont(stream, _braDosDemoRussiaCharMap);
+ } else {
+ font = new BraFont(stream, _braDosDemoComicCharMap);
+ }
+ } else {
+ font = new BraFont(stream, _braDosFullCharMap);
+ }
+
+ return font;
}
Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &stream) {
@@ -545,6 +663,12 @@ Font *AmigaDisk_br::createFont(const char *name, Common::SeekableReadStream &str
return new AmigaFont(stream);
}
+GfxObj* DosDisk_br::createInventoryObjects(Common::SeekableReadStream &stream) {
+ Frames *frames = new BraInventoryObjects(stream);
+ return new GfxObj(0, frames, "inventoryobjects");
+}
+
+
void Parallaction_ns::initFonts() {
if (getPlatform() == Common::kPlatformPC) {
@@ -573,8 +697,8 @@ void Parallaction_br::initFonts() {
// fonts/sonya/18
// fonts/vanya/16
- _menuFont = _disk->loadFont("fonts/natasha/16");
- _dialogueFont = _disk->loadFont("fonts/sonya/18");
+ _menuFont = _disk->loadFont("natasha");
+ _dialogueFont = _disk->loadFont("vanya");
Common::MemoryReadStream stream(_amigaTopazFont, 2600, false);
_labelFont = new AmigaFont(stream);
}
diff --git a/engines/parallaction/gfxbase.cpp b/engines/parallaction/gfxbase.cpp
index e8250ac8fd..1c373dda44 100644
--- a/engines/parallaction/gfxbase.cpp
+++ b/engines/parallaction/gfxbase.cpp
@@ -32,7 +32,7 @@
namespace Parallaction {
-GfxObj::GfxObj(uint objType, Frames *frames, const char* name) : type(objType), _frames(frames), x(0), y(0), z(0), frame(0), layer(3), _flags(kGfxObjNormal), _keep(true) {
+GfxObj::GfxObj(uint objType, Frames *frames, const char* name) : _frames(frames), _keep(true), x(0), y(0), z(0), _flags(kGfxObjNormal), type(objType), frame(0), layer(3) {
if (name) {
_name = strdup(name);
} else {
@@ -209,63 +209,6 @@ void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, cons
font->drawString(dst, surf->w, text);
}
-void Gfx::drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth) {
-
- uint16 lines = 0;
- uint16 linewidth = 0;
-
- uint16 rx = 10;
- uint16 ry = 4;
-
- uint16 blankWidth = font->getStringWidth(" ");
- uint16 tokenWidth = 0;
-
- char token[MAX_TOKEN_LEN];
-
- if (wrapwidth == -1)
- wrapwidth = _vm->_screenWidth;
-
- while (strlen(text) > 0) {
-
- text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
-
- if (!scumm_stricmp(token, "%p")) {
- lines++;
- rx = 10;
- ry = 4 + lines*10; // y
-
- strcpy(token, "> .......");
- strncpy(token+2, _password, strlen(_password));
- tokenWidth = font->getStringWidth(token);
- } else {
- tokenWidth = font->getStringWidth(token);
-
- linewidth += tokenWidth;
-
- if (linewidth > wrapwidth) {
- // wrap line
- lines++;
- rx = 10; // x
- ry = 4 + lines*10; // y
- linewidth = tokenWidth;
- }
-
- if (!scumm_stricmp(token, "%s")) {
- sprintf(token, "%d", _score);
- }
-
- }
-
- drawText(font, surf, rx, ry, token, color);
-
- rx += tokenWidth + blankWidth;
- linewidth += blankWidth;
-
- text = Common::ltrim(text);
- }
-
-}
-
#if 0
void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
@@ -348,8 +291,8 @@ void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16
for (uint16 j = 0; j < q.width(); j++) {
if (*s != transparentColor) {
- if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) {
- byte v = _backgroundInfo.mask.getValue(dp.x + j, dp.y + i);
+ if (_backgroundInfo->mask.data && (z < LAYER_FOREGROUND)) {
+ byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i);
if (z >= v) *d = 5;
} else {
*d = 5;
@@ -365,13 +308,13 @@ void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16
}
} else {
- if (_backgroundInfo.mask.data && (z < LAYER_FOREGROUND)) {
+ if (_backgroundInfo->mask.data && (z < LAYER_FOREGROUND)) {
for (uint16 i = 0; i < q.height(); i++) {
for (uint16 j = 0; j < q.width(); j++) {
if (*s != transparentColor) {
- byte v = _backgroundInfo.mask.getValue(dp.x + j, dp.y + i);
+ byte v = _backgroundInfo->mask.getValue(dp.x + j, dp.y + i);
if (z >= v) *d = *s;
}
diff --git a/engines/parallaction/graphics.cpp b/engines/parallaction/graphics.cpp
index 9b9ea8605a..c19d6ae5e5 100644
--- a/engines/parallaction/graphics.cpp
+++ b/engines/parallaction/graphics.cpp
@@ -153,6 +153,13 @@ void Palette::setEntry(uint index, int red, int green, int blue) {
_data[index*3+2] = blue & 0xFF;
}
+void Palette::getEntry(uint index, int &red, int &green, int &blue) {
+ assert(index < _colors);
+ red = _data[index*3];
+ green = _data[index*3+1];
+ blue = _data[index*3+2];
+}
+
void Palette::makeGrayscale() {
byte v;
for (uint16 i = 0; i < _colors; i++) {
@@ -262,7 +269,7 @@ void Gfx::animatePalette() {
PaletteFxRange *range;
for (uint16 i = 0; i < 4; i++) {
- range = &_backgroundInfo.ranges[i];
+ range = &_backgroundInfo->ranges[i];
if ((range->_flags & 1) == 0) continue; // animated palette
range->_timer += range->_step * 2; // update timer
@@ -307,10 +314,14 @@ void Gfx::setProjectorProgram(int16 *data) {
}
void Gfx::drawInventory() {
-
+/*
if ((_engineFlags & kEngineInventory) == 0) {
return;
}
+*/
+ if (_vm->_input->_inputMode != Input::kInputModeInventory) {
+ return;
+ }
Common::Rect r;
_vm->_inventoryRenderer->getRect(r);
@@ -348,29 +359,37 @@ void Gfx::clearScreen() {
}
void Gfx::beginFrame() {
-
- int32 oldBackgroundMode = _varBackgroundMode;
- _varBackgroundMode = getVar("background_mode");
-
- if (oldBackgroundMode != _varBackgroundMode) {
- switch (_varBackgroundMode) {
- case 1:
- _bitmapMask.free();
- break;
- case 2:
- _bitmapMask.create(_backgroundInfo.width, _backgroundInfo.height, 1);
- byte *data = (byte*)_bitmapMask.pixels;
- for (uint y = 0; y < _bitmapMask.h; y++) {
- for (uint x = 0; x < _bitmapMask.w; x++) {
- *data++ = _backgroundInfo.mask.getValue(x, y);
+ _skipBackground = (_backgroundInfo->bg.pixels == 0); // don't render frame if background is missing
+
+ if (!_skipBackground) {
+ int32 oldBackgroundMode = _varBackgroundMode;
+ _varBackgroundMode = getVar("background_mode");
+ if (oldBackgroundMode != _varBackgroundMode) {
+ switch (_varBackgroundMode) {
+ case 1:
+ _bitmapMask.free();
+ break;
+ case 2:
+ _bitmapMask.create(_backgroundInfo->width, _backgroundInfo->height, 1);
+ byte *data = (byte*)_bitmapMask.pixels;
+ for (uint y = 0; y < _bitmapMask.h; y++) {
+ for (uint x = 0; x < _bitmapMask.w; x++) {
+ *data++ = _backgroundInfo->mask.getValue(x, y);
+ }
}
+ break;
}
- break;
}
}
+ _varDrawPathZones = getVar("draw_path_zones");
+ if (_varDrawPathZones == 1 && _vm->getGameType() != GType_BRA) {
+ setVar("draw_path_zones", 0);
+ _varDrawPathZones = 0;
+ warning("Path zones are supported only in Big Red Adventure");
+ }
- if (_vm->_screenWidth >= _backgroundInfo.width) {
+ if (_skipBackground || (_vm->_screenWidth >= _backgroundInfo->width)) {
_varScrollX = 0;
} else {
_varScrollX = getVar("scroll_x");
@@ -395,24 +414,38 @@ int32 Gfx::getRenderMode(const char *type) {
void Gfx::updateScreen() {
- // background may not cover the whole screen, so adjust bulk update size
- uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo.width);
- uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo.height);
+ if (!_skipBackground) {
+ // background may not cover the whole screen, so adjust bulk update size
+ uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo->width);
+ uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo->height);
- byte *backgroundData = 0;
- uint16 backgroundPitch = 0;
- switch (_varBackgroundMode) {
- case 1:
- backgroundData = (byte*)_backgroundInfo.bg.getBasePtr(_varScrollX, 0);
- backgroundPitch = _backgroundInfo.bg.pitch;
- break;
- case 2:
- backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0);
- backgroundPitch = _bitmapMask.pitch;
- break;
+ byte *backgroundData = 0;
+ uint16 backgroundPitch = 0;
+ switch (_varBackgroundMode) {
+ case 1:
+ backgroundData = (byte*)_backgroundInfo->bg.getBasePtr(_varScrollX, 0);
+ backgroundPitch = _backgroundInfo->bg.pitch;
+ break;
+ case 2:
+ backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0);
+ backgroundPitch = _bitmapMask.pitch;
+ break;
+ }
+ g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo->x, _backgroundInfo->y, w, h);
}
- g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo.x, _backgroundInfo.y, w, h);
+ if (_varDrawPathZones == 1) {
+ Graphics::Surface *surf = g_system->lockScreen();
+ ZoneList::iterator b = _vm->_location._zones.begin();
+ ZoneList::iterator e = _vm->_location._zones.end();
+ for (; b != e; b++) {
+ ZonePtr z = *b;
+ if (z->_type & kZonePath) {
+ surf->frameRect(Common::Rect(z->_left, z->_top, z->_right, z->_bottom), 2);
+ }
+ }
+ g_system->unlockScreen();
+ }
_varRenderMode = _varAnimRenderMode;
@@ -466,17 +499,17 @@ void Gfx::patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask)
Common::Rect r(surf.w, surf.h);
r.moveTo(x, y);
- uint16 z = (mask) ? _backgroundInfo.getLayer(y) : LAYER_FOREGROUND;
- blt(r, (byte*)surf.pixels, &_backgroundInfo.bg, z, 0);
+ uint16 z = (mask) ? _backgroundInfo->getLayer(y) : LAYER_FOREGROUND;
+ blt(r, (byte*)surf.pixels, &_backgroundInfo->bg, z, 0);
}
void Gfx::fillBackground(const Common::Rect& r, byte color) {
- _backgroundInfo.bg.fillRect(r, color);
+ _backgroundInfo->bg.fillRect(r, color);
}
void Gfx::invertBackground(const Common::Rect& r) {
- byte *d = (byte*)_backgroundInfo.bg.getBasePtr(r.left, r.top);
+ byte *d = (byte*)_backgroundInfo->bg.getBasePtr(r.left, r.top);
for (int i = 0; i < r.height(); i++) {
for (int j = 0; j < r.width(); j++) {
@@ -484,7 +517,7 @@ void Gfx::invertBackground(const Common::Rect& r) {
d++;
}
- d += (_backgroundInfo.bg.pitch - r.width());
+ d += (_backgroundInfo->bg.pitch - r.width());
}
}
@@ -674,49 +707,6 @@ void Gfx::drawLabels() {
}
-void Gfx::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) {
-
- uint16 lines = 0;
- uint16 w = 0;
- *width = 0;
-
- uint16 blankWidth = font->getStringWidth(" ");
- uint16 tokenWidth = 0;
-
- char token[MAX_TOKEN_LEN];
-
- while (strlen(text) != 0) {
-
- text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
- tokenWidth = font->getStringWidth(token);
-
- w += tokenWidth;
-
- if (!scumm_stricmp(token, "%p")) {
- lines++;
- } else {
- if (w > maxwidth) {
- w -= tokenWidth;
- lines++;
- if (w > *width)
- *width = w;
-
- w = tokenWidth;
- }
- }
-
- w += blankWidth;
- text = Common::ltrim(text);
- }
-
- if (*width < w) *width = w;
- *width += 10;
-
- *height = lines * 10 + 20;
-
- return;
-}
-
void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) {
@@ -734,7 +724,7 @@ void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surf
}
void Gfx::grabBackground(const Common::Rect& r, Graphics::Surface &dst) {
- copyRect(r, _backgroundInfo.bg, dst);
+ copyRect(r, _backgroundInfo->bg, dst);
}
@@ -754,6 +744,8 @@ Gfx::Gfx(Parallaction* vm) :
_screenX = 0;
_screenY = 0;
+ _backgroundInfo = 0;
+
_halfbrite = false;
_hbCircleRadius = 0;
@@ -769,12 +761,22 @@ Gfx::Gfx(Parallaction* vm) :
registerVar("anim_render_mode", 1);
registerVar("misc_render_mode", 1);
+ registerVar("draw_path_zones", 0);
+
+ if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) {
+ // this loads the backup palette needed by the PC version of BRA (see setBackground()).
+ BackgroundInfo paletteInfo;
+ _disk->loadSlide(paletteInfo, "pointer");
+ _backupPal.clone(paletteInfo.palette);
+ }
+
return;
}
Gfx::~Gfx() {
- freeBackground();
+ delete _backgroundInfo;
+
freeLabels();
delete []_unpackedBitmap;
@@ -829,23 +831,29 @@ void Gfx::freeItems() {
_numItems = 0;
}
-void Gfx::freeBackground() {
- _backgroundInfo.free();
-}
-
-void Gfx::setBackground(uint type, const char* name, const char* mask, const char* path) {
-
- freeBackground();
+void Gfx::setBackground(uint type, BackgroundInfo *info) {
+ delete _backgroundInfo;
+ _backgroundInfo = info;
if (type == kBackgroundLocation) {
- _disk->loadScenery(_backgroundInfo, name, mask, path);
- setPalette(_backgroundInfo.palette);
- _palette.clone(_backgroundInfo.palette);
+ // The PC version of BRA needs the entries 20-31 of the palette to be constant, but
+ // the background resource files are screwed up. The right colors come from an unused
+ // bitmap (pointer.bmp). Nothing is known about the Amiga version so far.
+ if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) {
+ int r, g, b;
+ for (uint i = 16; i < 32; i++) {
+ _backupPal.getEntry(i, r, g, b);
+ _backgroundInfo->palette.setEntry(i, r, g, b);
+ }
+ }
+
+ setPalette(_backgroundInfo->palette);
+ _palette.clone(_backgroundInfo->palette);
} else {
- _disk->loadSlide(_backgroundInfo, name);
- setPalette(_backgroundInfo.palette);
+ for (uint i = 0; i < 6; i++)
+ _backgroundInfo->ranges[i]._flags = 0; // disable palette cycling for slides
+ setPalette(_backgroundInfo->palette);
}
-
}
} // namespace Parallaction
diff --git a/engines/parallaction/graphics.h b/engines/parallaction/graphics.h
index a7242ba6f4..23b4569c6a 100644
--- a/engines/parallaction/graphics.h
+++ b/engines/parallaction/graphics.h
@@ -261,6 +261,7 @@ public:
void makeBlack();
void setEntries(byte* data, uint first, uint num);
+ void getEntry(uint index, int &red, int &green, int &blue);
void setEntry(uint index, int red, int green, int blue);
void makeGrayscale();
void fadeTo(const Palette& target, uint step);
@@ -435,7 +436,7 @@ struct BackgroundInfo {
return LAYER_FOREGROUND;
}
- void free() {
+ ~BackgroundInfo() {
bg.free();
mask.free();
path.free();
@@ -471,6 +472,9 @@ typedef Common::HashMap<Common::String, int32, Common::IgnoreCase_Hash, Common::
class Gfx {
+protected:
+ Parallaction* _vm;
+
public:
Disk *_disk;
VarMap _vars;
@@ -496,7 +500,6 @@ public:
void freeLabels();
// dialogue balloons
- void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height);
GfxObj* registerBalloon(Frames *frames, const char *text);
void destroyBalloons();
@@ -508,8 +511,8 @@ public:
void freeItems();
// background surface
- BackgroundInfo _backgroundInfo;
- void setBackground(uint type, const char* name, const char* mask, const char* path);
+ BackgroundInfo *_backgroundInfo;
+ void setBackground(uint type, BackgroundInfo *info);
void patchBackground(Graphics::Surface &surf, int16 x, int16 y, bool mask = false);
void grabBackground(const Common::Rect& r, Graphics::Surface &dst);
void fillBackground(const Common::Rect& r, byte color);
@@ -550,18 +553,23 @@ public:
byte *_unpackedBitmap;
protected:
- Parallaction* _vm;
bool _halfbrite;
+ bool _skipBackground;
+
Common::Point _hbCirclePos;
int _hbCircleRadius;
+ // BRA specific
+ Palette _backupPal;
+
// frame data stored in programmable variables
int32 _varBackgroundMode; // 1 = normal, 2 = only mask
int32 _varScrollX;
int32 _varAnimRenderMode; // 1 = normal, 2 = flat
int32 _varMiscRenderMode; // 1 = normal, 2 = flat
int32 _varRenderMode;
+ int32 _varDrawPathZones; // 0 = don't draw, 1 = draw
Graphics::Surface _bitmapMask;
int32 getRenderMode(const char *type);
@@ -592,7 +600,6 @@ public:
// low level text and patches
void drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color);
- void drawWrappedText(Font *font, Graphics::Surface* surf, char *text, byte color, int16 wrapwidth);
void drawGfxObject(GfxObj *obj, Graphics::Surface &surf, bool scene);
void blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor);
diff --git a/engines/parallaction/gui.cpp b/engines/parallaction/gui.cpp
new file mode 100644
index 0000000000..2dbe64fcf6
--- /dev/null
+++ b/engines/parallaction/gui.cpp
@@ -0,0 +1,92 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "parallaction/gui.h"
+
+namespace Parallaction {
+
+bool MenuInputHelper::run() {
+ if (_newState == 0) {
+ debugC(3, kDebugExec, "MenuInputHelper has set NULL state");
+ return false;
+ }
+
+ if (_newState != _state) {
+ debugC(3, kDebugExec, "MenuInputHelper changing state to '%s'", _newState->_name.c_str());
+
+ _newState->enter();
+ _state = _newState;
+ }
+
+ _newState = _state->run();
+
+ return true;
+}
+
+MenuInputHelper::~MenuInputHelper() {
+ StateMap::iterator b = _map.begin();
+ for ( ; b != _map.end(); b++) {
+ delete b->_value;
+ }
+ _map.clear();
+}
+
+
+void Parallaction::runGuiFrame() {
+ if (_input->_inputMode != Input::kInputModeMenu) {
+ return;
+ }
+
+ if (!_menuHelper) {
+ error("No menu helper defined!");
+ }
+
+ bool res = _menuHelper->run();
+
+ if (!res) {
+ cleanupGui();
+ _input->_inputMode = Input::kInputModeGame;
+ }
+
+}
+
+void Parallaction::cleanupGui() {
+ delete _menuHelper;
+ _menuHelper = 0;
+}
+
+void Parallaction::setInternLanguage(uint id) {
+ //TODO: assert id!
+
+ _language = id;
+ _disk->setLanguage(id);
+}
+
+uint Parallaction::getInternLanguage() {
+ return _language;
+}
+
+
+} // namespace Parallaction
diff --git a/engines/parallaction/gui.h b/engines/parallaction/gui.h
new file mode 100644
index 0000000000..dc6d1bc71b
--- /dev/null
+++ b/engines/parallaction/gui.h
@@ -0,0 +1,93 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef PARALLACTION_GUI_H
+#define PARALLACTION_GUI_H
+
+#include "common/system.h"
+#include "common/hashmap.h"
+
+#include "parallaction/input.h"
+#include "parallaction/parallaction.h"
+#include "parallaction/sound.h"
+
+
+namespace Parallaction {
+
+class MenuInputState;
+
+class MenuInputHelper {
+ typedef Common::HashMap<Common::String, MenuInputState*> StateMap;
+
+ StateMap _map;
+ MenuInputState *_state;
+ MenuInputState *_newState;
+
+public:
+ MenuInputHelper() : _state(0) {
+ }
+
+ ~MenuInputHelper();
+
+ void setState(const Common::String &name) {
+ // bootstrap routine
+ _newState = getState(name);
+ assert(_newState);
+ }
+
+ void addState(const Common::String &name, MenuInputState *state) {
+ _map.setVal(name, state);
+ }
+
+ MenuInputState *getState(const Common::String &name) {
+ return _map[name];
+ }
+
+ bool run();
+};
+
+class MenuInputState {
+
+protected:
+ MenuInputHelper *_helper;
+
+public:
+ MenuInputState(const Common::String &name, MenuInputHelper *helper) : _helper(helper), _name(name) {
+ debugC(3, kDebugExec, "MenuInputState(%s)", name.c_str());
+ _helper->addState(name, this);
+ }
+
+ Common::String _name;
+
+ virtual ~MenuInputState() { }
+
+ virtual MenuInputState* run() = 0;
+ virtual void enter() = 0;
+};
+
+
+} // namespace Parallaction
+
+#endif
diff --git a/engines/parallaction/gui_br.cpp b/engines/parallaction/gui_br.cpp
index 391459a12f..3315433762 100644
--- a/engines/parallaction/gui_br.cpp
+++ b/engines/parallaction/gui_br.cpp
@@ -25,185 +25,268 @@
#include "common/system.h"
-
+#include "parallaction/gui.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
namespace Parallaction {
-enum MenuOptions {
- kMenuPart0 = 0,
- kMenuPart1 = 1,
- kMenuPart2 = 2,
- kMenuPart3 = 3,
- kMenuPart4 = 4,
- kMenuLoadGame = 5,
- kMenuQuit = 6
-};
-
+class SplashInputState_BR : public MenuInputState {
+protected:
+ Common::String _slideName;
+ uint32 _timeOut;
+ Common::String _nextState;
+ uint32 _startTime;
+ Palette blackPal;
+ Palette pal;
-void Parallaction_br::guiStart() {
+ Parallaction_br *_vm;
+ int _fadeSteps;
- // TODO: load progress value from special save game
- _progress = 3;
+public:
+ SplashInputState_BR(Parallaction_br *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm) {
+ }
- int option = guiShowMenu();
- switch (option) {
- case kMenuQuit:
- _quit = true;
- _vm->quitGame();
- break;
+ virtual MenuInputState* run() {
+ if (_fadeSteps > 0) {
+ pal.fadeTo(blackPal, 1);
+ _vm->_gfx->setPalette(pal);
+ _fadeSteps--;
+ // TODO: properly implement timers to avoid delay calls
+ _vm->_system->delayMillis(20);
+ return this;
+ }
- case kMenuLoadGame:
- warning("loadgame not yet implemented");
- break;
+ if (_fadeSteps == 0) {
+ _vm->freeBackground();
+ return _helper->getState(_nextState);
+ }
- default:
- _part = option;
- _disk->selectArchive(_partNames[_part]);
- startPart();
+ uint32 curTime = _vm->_system->getMillis();
+ if (curTime - _startTime > _timeOut) {
+ _fadeSteps = 64;
+ pal.clone(_vm->_gfx->_backgroundInfo->palette);
+ }
+ return this;
}
-}
-void Parallaction_br::guiSplash(const char *name) {
+ virtual void enter() {
+ _vm->_gfx->clearScreen();
+ _vm->showSlide(_slideName.c_str(), CENTER_LABEL_HORIZONTAL, CENTER_LABEL_VERTICAL);
+ _vm->_input->setMouseState(MOUSE_DISABLED);
- _gfx->clearScreen();
- _gfx->setBackground(kBackgroundSlide, name, 0, 0);
- _gfx->_backgroundInfo.x = (_screenWidth - _gfx->_backgroundInfo.width) >> 1;
- _gfx->_backgroundInfo.y = (_screenHeight - _gfx->_backgroundInfo.height) >> 1;
- _gfx->updateScreen();
- _system->delayMillis(600);
+ _startTime = g_system->getMillis();
+ _fadeSteps = -1;
+ }
+};
- Palette blackPal;
- Palette pal(_gfx->_backgroundInfo.palette);
- for (uint i = 0; i < 64; i++) {
- pal.fadeTo(blackPal, 1);
- _gfx->setPalette(pal);
- _gfx->updateScreen();
- _system->delayMillis(20);
+class SplashInputState0_BR : public SplashInputState_BR {
+
+public:
+ SplashInputState0_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro0", helper) {
+ _slideName = "dyna";
+ _timeOut = 600;
+ _nextState = "intro1";
}
+};
-}
+class SplashInputState1_BR : public SplashInputState_BR {
-#define MENUITEMS_X 250
-#define MENUITEMS_Y 200
+public:
+ SplashInputState1_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro1", helper) {
+ _slideName = "core";
+ _timeOut = 600;
+ _nextState = "mainmenu";
+ }
+};
-#define MENUITEM_WIDTH 190
-#define MENUITEM_HEIGHT 18
+class MainMenuInputState_BR : public MenuInputState {
+ Parallaction_br *_vm;
-Frames* Parallaction_br::guiRenderMenuItem(const char *text) {
- // this builds a surface containing two copies of the text.
- // one is in normal color, the other is inverted.
- // the two 'frames' are used to display selected/unselected menu items
+ #define MENUITEMS_X 250
+ #define MENUITEMS_Y 200
- Graphics::Surface *surf = new Graphics::Surface;
- surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1);
+ #define MENUITEM_WIDTH 190
+ #define MENUITEM_HEIGHT 18
- // build first frame to be displayed when item is not selected
- if (getPlatform() == Common::kPlatformPC) {
- _menuFont->setColor(0);
- } else {
- _menuFont->setColor(7);
- }
- _menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text);
+ Frames* renderMenuItem(const char *text) {
+ // this builds a surface containing two copies of the text.
+ // one is in normal color, the other is inverted.
+ // the two 'frames' are used to display selected/unselected menu items
- // build second frame to be displayed when item is selected
- _menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text);
- byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT);
- for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) {
- *s++ ^= 0xD;
- }
+ Graphics::Surface *surf = new Graphics::Surface;
+ surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1);
- // wrap the surface into the suitable Frames adapter
- return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf);
-}
+ // build first frame to be displayed when item is not selected
+ if (_vm->getPlatform() == Common::kPlatformPC) {
+ _vm->_menuFont->setColor(0);
+ } else {
+ _vm->_menuFont->setColor(7);
+ }
+ _vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text);
+
+ // build second frame to be displayed when item is selected
+ _vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text);
+ byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT);
+ for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) {
+ *s++ ^= 0xD;
+ }
+ // wrap the surface into the suitable Frames adapter
+ return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf);
+ }
-int Parallaction_br::guiShowMenu() {
- // TODO: filter menu entries according to progress in game
+ enum MenuOptions {
+ kMenuPart0 = 0,
+ kMenuPart1 = 1,
+ kMenuPart2 = 2,
+ kMenuPart3 = 3,
+ kMenuPart4 = 4,
+ kMenuLoadGame = 5,
+ kMenuQuit = 6
+ };
#define NUM_MENULINES 7
GfxObj *_lines[NUM_MENULINES];
- const char *menuStrings[NUM_MENULINES] = {
- "SEE INTRO",
- "NEW GAME",
- "SAVED GAME",
- "EXIT TO DOS",
- "PART 2",
- "PART 3",
- "PART 4"
- };
+ static const char *_menuStrings[NUM_MENULINES];
+ static const MenuOptions _options[NUM_MENULINES];
- MenuOptions options[NUM_MENULINES] = {
- kMenuPart0,
- kMenuPart1,
- kMenuLoadGame,
- kMenuQuit,
- kMenuPart2,
- kMenuPart3,
- kMenuPart4
- };
+ int _availItems;
+ int _selection;
- _gfx->clearScreen();
- _gfx->setBackground(kBackgroundSlide, "tbra", 0, 0);
- if (getPlatform() == Common::kPlatformPC) {
- _gfx->_backgroundInfo.x = 20;
- _gfx->_backgroundInfo.y = 50;
+ void cleanup() {
+ _vm->_system->showMouse(false);
+ _vm->hideDialogueStuff();
+
+ for (int i = 0; i < _availItems; i++) {
+ delete _lines[i];
+ }
}
- int availItems = 4 + _progress;
+ void performChoice(int selectedItem) {
+ switch (selectedItem) {
+ case kMenuQuit:
+ _engineFlags |= kEngineQuit;
+ break;
- // TODO: keep track of and destroy menu item frames/surfaces
+ case kMenuLoadGame:
+ warning("loadgame not yet implemented");
+ break;
- int i;
- for (i = 0; i < availItems; i++) {
- _lines[i] = new GfxObj(0, guiRenderMenuItem(menuStrings[i]), "MenuItem");
- uint id = _gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF);
- _gfx->setItemFrame(id, 0);
+ default:
+ _vm->startPart(selectedItem);
+ }
}
- int selectedItem = -1;
-
- setMousePointer(0);
-
- uint32 event;
- Common::Point p;
- while (true) {
+public:
+ MainMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("mainmenu", helper), _vm(vm) {
+ }
- _input->readInput();
+ virtual MenuInputState* run() {
- event = _input->getLastButtonEvent();
- if ((event == kMouseLeftUp) && selectedItem >= 0)
- break;
+ int event = _vm->_input->getLastButtonEvent();
+ if ((event == kMouseLeftUp) && _selection >= 0) {
+ cleanup();
+ performChoice(_options[_selection]);
+ return 0;
+ }
- _input->getCursorPos(p);
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
if ((p.x > MENUITEMS_X) && (p.x < (MENUITEMS_X+MENUITEM_WIDTH)) && (p.y > MENUITEMS_Y)) {
- selectedItem = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT;
+ _selection = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT;
- if (!(selectedItem < availItems))
- selectedItem = -1;
+ if (!(_selection < _availItems))
+ _selection = -1;
} else
- selectedItem = -1;
+ _selection = -1;
- for (i = 0; i < availItems; i++) {
- _gfx->setItemFrame(i, selectedItem == i ? 1 : 0);
+ for (int i = 0; i < _availItems; i++) {
+ _vm->_gfx->setItemFrame(i, _selection == i ? 1 : 0);
}
- _gfx->updateScreen();
- _system->delayMillis(20);
- }
- _system->showMouse(false);
- hideDialogueStuff();
+ return this;
+ }
- for (i = 0; i < availItems; i++) {
- delete _lines[i];
+ virtual void enter() {
+ _vm->_gfx->clearScreen();
+ int x = 0, y = 0;
+ if (_vm->getPlatform() == Common::kPlatformPC) {
+ x = 20;
+ y = 50;
+ }
+ _vm->showSlide("tbra", x, y);
+
+ // TODO: load progress from savefile
+ int progress = 3;
+ _availItems = 4 + progress;
+
+ // TODO: keep track of and destroy menu item frames/surfaces
+ int i;
+ for (i = 0; i < _availItems; i++) {
+ _lines[i] = new GfxObj(0, renderMenuItem(_menuStrings[i]), "MenuItem");
+ uint id = _vm->_gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF);
+ _vm->_gfx->setItemFrame(id, 0);
+ }
+ _selection = -1;
+ _vm->setArrowCursor();
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
}
- return options[selectedItem];
+};
+
+const char *MainMenuInputState_BR::_menuStrings[NUM_MENULINES] = {
+ "SEE INTRO",
+ "NEW GAME",
+ "SAVED GAME",
+ "EXIT TO DOS",
+ "PART 2",
+ "PART 3",
+ "PART 4"
+};
+
+const MainMenuInputState_BR::MenuOptions MainMenuInputState_BR::_options[NUM_MENULINES] = {
+ kMenuPart0,
+ kMenuPart1,
+ kMenuLoadGame,
+ kMenuQuit,
+ kMenuPart2,
+ kMenuPart3,
+ kMenuPart4
+};
+
+
+
+
+
+
+
+void Parallaction_br::startGui() {
+ _menuHelper = new MenuInputHelper;
+ new SplashInputState0_BR(this, _menuHelper);
+ new SplashInputState1_BR(this, _menuHelper);
+ new MainMenuInputState_BR(this, _menuHelper);
+
+ _menuHelper->setState("intro0");
+ _input->_inputMode = Input::kInputModeMenu;
+
+ do {
+ _input->readInput();
+ if (!_menuHelper->run()) break;
+ _gfx->beginFrame();
+ _gfx->updateScreen();
+ } while (true);
+
+ delete _menuHelper;
+ _menuHelper = 0;
+
+ _input->_inputMode = Input::kInputModeGame;
}
+
+
} // namespace Parallaction
diff --git a/engines/parallaction/gui_ns.cpp b/engines/parallaction/gui_ns.cpp
index 9c48586dbc..815c27bd1c 100644
--- a/engines/parallaction/gui_ns.cpp
+++ b/engines/parallaction/gui_ns.cpp
@@ -24,7 +24,9 @@
*/
#include "common/system.h"
+#include "common/hashmap.h"
+#include "parallaction/gui.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/sound.h"
@@ -32,313 +34,567 @@
namespace Parallaction {
-const char *introMsg1[] = {
- "INSERISCI IL CODICE",
- "ENTREZ CODE",
- "ENTER CODE",
- "GIB DEN KODE EIN"
-};
+class SplashInputState_NS : public MenuInputState {
+protected:
+ Common::String _slideName;
+ uint32 _timeOut;
+ Common::String _nextState;
+ uint32 _startTime;
-const char *introMsg2[] = {
- "CODICE ERRATO",
- "CODE ERRONE",
- "WRONG CODE",
- "GIB DEN KODE EIN"
-};
+ Parallaction_ns *_vm;
-const char *introMsg3[] = {
- "PRESS LEFT MOUSE BUTTON",
- "TO SEE INTRO",
- "PRESS RIGHT MOUSE BUTTON",
- "TO START"
+public:
+ SplashInputState_NS(Parallaction_ns *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm) {
+ }
+
+ virtual MenuInputState* run() {
+ uint32 curTime = g_system->getMillis();
+ if (curTime - _startTime > _timeOut) {
+ _vm->freeBackground();
+ return _helper->getState(_nextState);
+ }
+ return this;
+ }
+
+ virtual void enter() {
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+ _vm->showSlide(_slideName.c_str());
+ _startTime = g_system->getMillis();
+ }
};
-const char *newGameMsg[] = {
- "NUOVO GIOCO",
- "NEUF JEU",
- "NEW GAME",
- "NEUES SPIEL"
+class SplashInputState0_NS : public SplashInputState_NS {
+
+public:
+ SplashInputState0_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro0", helper) {
+ _slideName = "intro";
+ _timeOut = 2000;
+ _nextState = "intro1";
+ }
};
-const char *loadGameMsg[] = {
- "GIOCO SALVATO",
- "JEU SAUVE'",
- "SAVED GAME",
- "SPIEL GESPEICHERT"
+class SplashInputState1_NS : public SplashInputState_NS {
+
+public:
+ SplashInputState1_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro1", helper) {
+ _slideName = "minintro";
+ _timeOut = 2000;
+ _nextState = "chooselanguage";
+ }
};
-#define BLOCK_WIDTH 16
-#define BLOCK_HEIGHT 24
+class ChooseLanguageInputState_NS : public MenuInputState {
+ #define BLOCK_WIDTH 16
+ #define BLOCK_HEIGHT 24
-#define BLOCK_X 112
-#define BLOCK_Y 130
+ #define BLOCK_X 112
+ #define BLOCK_Y 130
-#define BLOCK_SELECTION_X (BLOCK_X-1)
-#define BLOCK_SELECTION_Y (BLOCK_Y-1)
+ #define BLOCK_SELECTION_X (BLOCK_X-1)
+ #define BLOCK_SELECTION_Y (BLOCK_Y-1)
-#define BLOCK_X_OFFSET (BLOCK_WIDTH+1)
-#define BLOCK_Y_OFFSET 9
+ #define BLOCK_X_OFFSET (BLOCK_WIDTH+1)
+ #define BLOCK_Y_OFFSET 9
-// destination slots for code blocks
-//
-#define SLOT_X 61
-#define SLOT_Y 64
-#define SLOT_WIDTH (BLOCK_WIDTH+2)
+ // destination slots for code blocks
+ //
+ #define SLOT_X 61
+ #define SLOT_Y 64
+ #define SLOT_WIDTH (BLOCK_WIDTH+2)
-#define PASSWORD_LEN 6
+ int _language;
+ bool _allowChoice;
+ Common::String _nextState;
-#define CHAR_DINO 0
-#define CHAR_DONNA 1
-#define CHAR_DOUGH 2
+ static const Common::Rect _dosLanguageSelectBlocks[4];
+ static const Common::Rect _amigaLanguageSelectBlocks[4];
+ const Common::Rect *_blocks;
-static const uint16 _amigaKeys[][PASSWORD_LEN] = {
- { 5, 3, 6, 2, 2, 7 }, // dino
- { 0, 3, 6, 2, 2, 6 }, // donna
- { 1, 3 ,7, 2, 4, 6 } // dough
-};
+ Parallaction_ns *_vm;
-static const uint16 _pcKeys[][PASSWORD_LEN] = {
- { 5, 3, 6, 1, 4, 7 }, // dino
- { 0, 2, 8, 5, 5, 1 }, // donna
- { 1, 7 ,7, 2, 2, 6 } // dough
-};
+public:
+ ChooseLanguageInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("chooselanguage", helper), _vm(vm) {
+ _allowChoice = false;
+ _nextState = "selectgame";
-static const char *_charStartLocation[] = {
- "test.dino",
- "test.donna",
- "test.dough"
+ if (_vm->getPlatform() == Common::kPlatformAmiga) {
+ if (!(_vm->getFeatures() & GF_LANG_MULT)) {
+ if (_vm->getFeatures() & GF_DEMO) {
+ _language = 1; // Amiga Demo supports English
+ _nextState = "startdemo";
+ return;
+ } else {
+ _language = 0; // The only other non multi-lingual version just supports Italian
+ return;
+ }
+ }
+
+ _blocks = _amigaLanguageSelectBlocks;
+ } else {
+ _blocks = _dosLanguageSelectBlocks;
+ }
+
+ _language = -1;
+ _allowChoice = true;
+ }
+
+ virtual MenuInputState* run() {
+ if (!_allowChoice) {
+ _vm->setInternLanguage(_language);
+ return _helper->getState(_nextState);
+ }
+
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return this;
+ }
+
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
+
+ for (uint16 i = 0; i < 4; i++) {
+ if (_blocks[i].contains(p)) {
+ _vm->setInternLanguage(i);
+ _vm->beep();
+ _vm->_gfx->freeLabels();
+ return _helper->getState(_nextState);
+ }
+ }
+
+ return this;
+ }
+
+ virtual void enter() {
+ if (!_allowChoice) {
+ return;
+ }
+
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
+
+ // user can choose language in this version
+ _vm->showSlide("lingua");
+
+ uint id = _vm->_gfx->createLabel(_vm->_introFont, "SELECT LANGUAGE", 1);
+ _vm->_gfx->showLabel(id, 60, 30);
+
+ _vm->setArrowCursor();
+ }
};
-enum {
- NEW_GAME,
- LOAD_GAME
+const Common::Rect ChooseLanguageInputState_NS::_dosLanguageSelectBlocks[4] = {
+ Common::Rect( 80, 110, 128, 180 ), // Italian
+ Common::Rect( 129, 85, 177, 155 ), // French
+ Common::Rect( 178, 60, 226, 130 ), // English
+ Common::Rect( 227, 35, 275, 105 ) // German
};
-enum {
- START_DEMO,
- START_INTRO,
- GAME_LOADED,
- SELECT_CHARACTER
+const Common::Rect ChooseLanguageInputState_NS::_amigaLanguageSelectBlocks[4] = {
+ Common::Rect( -1, -1, -1, -1 ), // Italian: not supported by Amiga multi-lingual version
+ Common::Rect( 129, 85, 177, 155 ), // French
+ Common::Rect( 178, 60, 226, 130 ), // English
+ Common::Rect( 227, 35, 275, 105 ) // German
};
-void Parallaction_ns::guiStart() {
+class SelectGameInputState_NS : public MenuInputState {
- _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
+ int _choice, _oldChoice;
+ Common::String _nextState[2];
- guiSplash();
+ uint _labels[2];
- _language = guiChooseLanguage();
- _disk->setLanguage(_language);
+ Parallaction_ns *_vm;
- int event;
+ static const char *newGameMsg[4];
+ static const char *loadGameMsg[4];
- if (getFeatures() & GF_DEMO) {
- event = START_DEMO;
- } else {
- if (guiSelectGame() == NEW_GAME) {
- event = guiNewGame();
- } else {
- event = loadGame() ? GAME_LOADED : START_INTRO;
- }
+public:
+ SelectGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectgame", helper), _vm(vm) {
+ _choice = 0;
+ _oldChoice = -1;
+
+ _nextState[0] = "newgame";
+ _nextState[1] = "loadgame";
}
- switch (event) {
- case START_DEMO:
- strcpy(_location._name, "fognedemo.dough");
- break;
- case START_INTRO:
- strcpy(_location._name, "fogne.dough");
- break;
+ virtual MenuInputState *run() {
+ int event = _vm->_input->getLastButtonEvent();
+
+ if (event == kMouseLeftUp) {
+ _vm->_gfx->freeLabels();
+ return _helper->getState(_nextState[_choice]);
+ }
- case GAME_LOADED:
- // nothing to do here
- return;
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
+ _choice = (p.x > 160) ? 1 : 0;
- case SELECT_CHARACTER:
- selectStartLocation();
- break;
+ if (_choice != _oldChoice) {
+ if (_oldChoice != -1)
+ _vm->_gfx->hideLabel(_labels[_oldChoice]);
+ if (_choice != -1)
+ _vm->_gfx->showLabel(_labels[_choice], 60, 30);
+
+ _oldChoice = _choice;
+ }
+
+ return this;
}
- return;
-}
+ virtual void enter() {
+ _vm->showSlide("restore");
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
-void Parallaction_ns::selectStartLocation() {
- _inTestResult = false;
+ _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, newGameMsg[_vm->getInternLanguage()], 1);
+ _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, loadGameMsg[_vm->getInternLanguage()], 1);
+ }
- int character = guiSelectCharacter();
- if (character == -1)
- error("invalid character selected from menu screen");
+};
- scheduleLocationSwitch(_charStartLocation[character]);
-}
+const char *SelectGameInputState_NS::newGameMsg[4] = {
+ "NUOVO GIOCO",
+ "NEUF JEU",
+ "NEW GAME",
+ "NEUES SPIEL"
+};
+const char *SelectGameInputState_NS::loadGameMsg[4] = {
+ "GIOCO SALVATO",
+ "JEU SAUVE'",
+ "SAVED GAME",
+ "SPIEL GESPEICHERT"
+};
-void Parallaction_ns::guiSplash() {
- showSlide("intro");
- _gfx->updateScreen();
- g_system->delayMillis(2000);
- freeBackground();
- showSlide("minintro");
- _gfx->updateScreen();
- g_system->delayMillis(2000);
- freeBackground();
-}
+class LoadGameInputState_NS : public MenuInputState {
+ bool _result;
+ Parallaction_ns *_vm;
-int Parallaction_ns::guiNewGame() {
+public:
+ LoadGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("loadgame", helper), _vm(vm) { }
- const char **v14 = introMsg3;
+ virtual MenuInputState* run() {
+ if (!_result) {
+ _vm->scheduleLocationSwitch("fogne.dough");
+ }
+ return 0;
+ }
- _disk->selectArchive("disk1");
+ virtual void enter() {
+ _result = _vm->loadGame();
+ }
+};
- setBackground("test", NULL, NULL);
- _gfx->updateScreen();
- uint id[4];
- id[0] = _gfx->createLabel(_menuFont, v14[0], 1);
- id[1] = _gfx->createLabel(_menuFont, v14[1], 1);
- id[2] = _gfx->createLabel(_menuFont, v14[2], 1);
- id[3] = _gfx->createLabel(_menuFont, v14[3], 1);
- _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50);
- _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70);
- _gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100);
- _gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120);
+class NewGameInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
- _input->showCursor(false);
+ static const char *introMsg3[4];
- _gfx->updateScreen();
+public:
+ NewGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("newgame", helper), _vm(vm) {
+ }
- _input->waitForButtonEvent(kMouseLeftUp | kMouseRightUp);
- uint32 event = _input->getLastButtonEvent();
+ virtual MenuInputState* run() {
+ int event = _vm->_input->getLastButtonEvent();
- _input->showCursor(true);
+ if (event == kMouseLeftUp || event == kMouseRightUp) {
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
+ _vm->_gfx->freeLabels();
- _gfx->freeLabels();
+ if (event == kMouseLeftUp) {
+ _vm->scheduleLocationSwitch("fogne.dough");
+ return 0;
+ }
- if (event != kMouseRightUp) {
- return START_INTRO;
- }
+ return _helper->getState("selectcharacter");
+ }
- return SELECT_CHARACTER;
-}
+ return this;
+ }
-static const Common::Rect _dosLanguageSelectBlocks[4] = {
- Common::Rect( 80, 110, 128, 180 ), // Italian
- Common::Rect( 129, 85, 177, 155 ), // French
- Common::Rect( 178, 60, 226, 130 ), // English
- Common::Rect( 227, 35, 275, 105 ) // German
+ virtual void enter() {
+ _vm->_disk->selectArchive("disk1");
+ _vm->setBackground("test", NULL, NULL);
+ _vm->_input->setMouseState(MOUSE_ENABLED_HIDE);
+
+ uint id[4];
+ id[0] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[0], 1);
+ id[1] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[1], 1);
+ id[2] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[2], 1);
+ id[3] = _vm->_gfx->createLabel(_vm->_menuFont, introMsg3[3], 1);
+ _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50);
+ _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70);
+ _vm->_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100);
+ _vm->_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120);
+ }
};
-static const Common::Rect _amigaLanguageSelectBlocks[4] = {
- Common::Rect( -1, -1, -1, -1 ), // Italian: not supported by Amiga multi-lingual version
- Common::Rect( 129, 85, 177, 155 ), // French
- Common::Rect( 178, 60, 226, 130 ), // English
- Common::Rect( 227, 35, 275, 105 ) // German
+const char *NewGameInputState_NS::introMsg3[4] = {
+ "PRESS LEFT MOUSE BUTTON",
+ "TO SEE INTRO",
+ "PRESS RIGHT MOUSE BUTTON",
+ "TO START"
};
-uint16 Parallaction_ns::guiChooseLanguage() {
- const Common::Rect *blocks;
+class StartDemoInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
- if (getPlatform() == Common::kPlatformAmiga) {
- if (!(getFeatures() & GF_LANG_MULT)) {
- if (getFeatures() & GF_DEMO) {
- return 1; // Amiga Demo supports English
- } else {
- return 0; // The only other non multi-lingual version just supports Italian
- }
- }
+public:
+ StartDemoInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("startdemo", helper), _vm(vm) {
+ }
- blocks = _amigaLanguageSelectBlocks;
- } else {
- blocks = _dosLanguageSelectBlocks;
+ virtual MenuInputState* run() {
+ _vm->scheduleLocationSwitch("fognedemo.dough");
+ return 0;
}
- // user can choose language in dos version
- showSlide("lingua");
+ virtual void enter() {
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+ }
+};
- uint id = _gfx->createLabel(_introFont, "SELECT LANGUAGE", 1);
- _gfx->showLabel(id, 60, 30);
+class SelectCharacterInputState_NS : public MenuInputState {
- setArrowCursor();
+ #define PASSWORD_LEN 6
- Common::Point p;
+ #define CHAR_DINO 0
+ #define CHAR_DONNA 1
+ #define CHAR_DOUGH 2
- int selection = -1;
- while (selection == -1) {
- _input->waitUntilLeftClick();
- _input->getCursorPos(p);
- for (uint16 i = 0; i < 4; i++) {
- if (blocks[i].contains(p)) {
+ static const Common::Rect codeSelectBlocks[9];
+ static const Common::Rect codeTrueBlocks[9];
+
+ Parallaction_ns *_vm;
+
+ int guiGetSelectedBlock(const Common::Point &p) {
+
+ int selection = -1;
+
+ for (uint16 i = 0; i < 9; i++) {
+ if (codeSelectBlocks[i].contains(p)) {
selection = i;
break;
}
}
+
+ if ((selection != -1) && (_vm->getPlatform() == Common::kPlatformAmiga)) {
+ _vm->_gfx->invertBackground(codeTrueBlocks[selection]);
+ _vm->_gfx->updateScreen();
+ _vm->beep();
+ g_system->delayMillis(100);
+ _vm->_gfx->invertBackground(codeTrueBlocks[selection]);
+ _vm->_gfx->updateScreen();
+ }
+
+ return selection;
}
- beep();
+ byte _points[3];
+ bool _fail;
+ const uint16 (*_keys)[PASSWORD_LEN];
+ Graphics::Surface _block;
+ Graphics::Surface _emptySlots;
- _gfx->freeLabels();
+ uint _labels[2];
+ uint _len;
+ uint32 _startTime;
- return selection;
-}
+ enum {
+ CHOICE,
+ FAIL,
+ SUCCESS,
+ DELAY
+ };
+ uint _state;
+ static const char *introMsg1[4];
+ static const char *introMsg2[4];
-uint16 Parallaction_ns::guiSelectGame() {
-// printf("selectGame()\n");
+ static const uint16 _amigaKeys[3][PASSWORD_LEN];
+ static const uint16 _pcKeys[3][PASSWORD_LEN];
+ static const char *_charStartLocation[3];
- showSlide("restore");
- uint16 _si = 0;
- uint16 _di = 3;
+public:
+ SelectCharacterInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectcharacter", helper), _vm(vm) {
+ _keys = (_vm->getPlatform() == Common::kPlatformAmiga && (_vm->getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys;
+ _block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1);
+ }
- uint id0, id1;
- id0 = _gfx->createLabel(_introFont, loadGameMsg[_language], 1);
- id1 = _gfx->createLabel(_introFont, newGameMsg[_language], 1);
+ ~SelectCharacterInputState_NS() {
+ _block.free();
+ _emptySlots.free();
+ }
- Common::Point p;
+ void cleanup() {
+ _points[0] = _points[1] = _points[2] = 0;
+ _vm->_gfx->hideLabel(_labels[1]);
+ _vm->_gfx->showLabel(_labels[0], 60, 30);
+ _fail = false;
+ _len = 0;
+ }
- _input->readInput();
- uint32 event = _input->getLastButtonEvent();
+ void delay() {
+ if (g_system->getMillis() - _startTime < 2000) {
+ return;
+ }
+ cleanup();
+ _state = CHOICE;
+ }
- while (event != kMouseLeftUp) {
+ void choice() {
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return;
+ }
- _input->readInput();
- _input->getCursorPos(p);
- event = _input->getLastButtonEvent();
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
+ int _si = guiGetSelectedBlock(p);
- _si = (p.x > 160) ? 1 : 0;
+ if (_si != -1) {
+ _vm->_gfx->grabBackground(codeTrueBlocks[_si], _block);
+ _vm->_gfx->patchBackground(_block, _len * SLOT_WIDTH + SLOT_X, SLOT_Y, false);
- if (_si != _di) {
- if (_si != 0) {
- // load a game
- _gfx->hideLabel(id1);
- _gfx->showLabel(id0, 60, 30);
- } else {
- // new game
- _gfx->hideLabel(id0);
- _gfx->showLabel(id1, 60, 30);
+ if (_keys[0][_len] != _si && _keys[1][_len] != _si && _keys[2][_len] != _si) {
+ _fail = true;
}
- _di = _si;
+
+ // build user preference
+ _points[0] += (_keys[0][_len] == _si);
+ _points[1] += (_keys[1][_len] == _si);
+ _points[2] += (_keys[2][_len] == _si);
+
+ _len++;
}
- _gfx->updateScreen();
- g_system->delayMillis(30);
+ if (_len == PASSWORD_LEN) {
+ _state = _fail ? FAIL : SUCCESS;
+ }
}
- _gfx->freeLabels();
+ void fail() {
+ _vm->_gfx->patchBackground(_emptySlots, SLOT_X, SLOT_Y, false);
+ _vm->_gfx->hideLabel(_labels[0]);
+ _vm->_gfx->showLabel(_labels[1], 60, 30);
+ _startTime = g_system->getMillis();
+ _state = DELAY;
+ }
+
+ void success() {
+ _vm->_gfx->freeLabels();
+ _vm->_gfx->setBlackPalette();
+ _emptySlots.free();
+
+ // actually select character
+ int character = -1;
+ if (_points[0] >= _points[1] && _points[0] >= _points[2]) {
+ character = CHAR_DINO;
+ } else
+ if (_points[1] >= _points[0] && _points[1] >= _points[2]) {
+ character = CHAR_DONNA;
+ } else
+ if (_points[2] >= _points[0] && _points[2] >= _points[1]) {
+ character = CHAR_DOUGH;
+ } else {
+ error("If you read this, either your CPU or transivity is broken (we believe the former).");
+ }
+
+ _vm->_inTestResult = false;
+ _vm->cleanupGame();
+ _vm->scheduleLocationSwitch(_charStartLocation[character]);
+ }
+
+ virtual MenuInputState* run() {
+ MenuInputState* nextState = this;
+
+ switch (_state) {
+ case DELAY:
+ delay();
+ break;
+
+ case CHOICE:
+ choice();
+ break;
+
+ case FAIL:
+ fail();
+ break;
+
+ case SUCCESS:
+ success();
+ nextState = 0;
+ break;
+
+ default:
+ error("unknown state in SelectCharacterInputState");
+ }
+
+ return nextState;
+ }
+
+ virtual void enter() {
+ _vm->_soundMan->stopMusic();
+ _vm->_disk->selectArchive((_vm->getFeatures() & GF_DEMO) ? "disk0" : "disk1");
+ _vm->showSlide("password");
+
+ _emptySlots.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1);
+ Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT);
+ _vm->_gfx->grabBackground(rect, _emptySlots);
+
+ _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, introMsg1[_vm->getInternLanguage()], 1);
+ _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, introMsg2[_vm->getInternLanguage()], 1);
+
+ cleanup();
+
+ _vm->setArrowCursor();
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
+ _state = CHOICE;
+ }
+};
+
+const char *SelectCharacterInputState_NS::introMsg1[4] = {
+ "INSERISCI IL CODICE",
+ "ENTREZ CODE",
+ "ENTER CODE",
+ "GIB DEN KODE EIN"
+};
+
+const char *SelectCharacterInputState_NS::introMsg2[4] = {
+ "CODICE ERRATO",
+ "CODE ERRONE",
+ "WRONG CODE",
+ "GIB DEN KODE EIN"
+};
+
+const uint16 SelectCharacterInputState_NS::_amigaKeys[][PASSWORD_LEN] = {
+ { 5, 3, 6, 2, 2, 7 }, // dino
+ { 0, 3, 6, 2, 2, 6 }, // donna
+ { 1, 3 ,7, 2, 4, 6 } // dough
+};
+
+const uint16 SelectCharacterInputState_NS::_pcKeys[][PASSWORD_LEN] = {
+ { 5, 3, 6, 1, 4, 7 }, // dino
+ { 0, 2, 8, 5, 5, 1 }, // donna
+ { 1, 7 ,7, 2, 2, 6 } // dough
+};
+
+const char *SelectCharacterInputState_NS::_charStartLocation[] = {
+ "test.dino",
+ "test.donna",
+ "test.dough"
+};
- return _si ? LOAD_GAME : NEW_GAME;
-}
-static const Common::Rect codeSelectBlocks[9] = {
+const Common::Rect SelectCharacterInputState_NS::codeSelectBlocks[9] = {
Common::Rect( 111, 129, 127, 153 ), // na
Common::Rect( 128, 120, 144, 144 ), // wa
Common::Rect( 145, 111, 161, 135 ), // ra
@@ -350,7 +606,7 @@ static const Common::Rect codeSelectBlocks[9] = {
Common::Rect( 247, 57, 263, 81 ) // ka
};
-static const Common::Rect codeTrueBlocks[9] = {
+const Common::Rect SelectCharacterInputState_NS::codeTrueBlocks[9] = {
Common::Rect( 112, 130, 128, 154 ),
Common::Rect( 129, 121, 145, 145 ),
Common::Rect( 146, 112, 162, 136 ),
@@ -363,146 +619,219 @@ static const Common::Rect codeTrueBlocks[9] = {
};
-int Parallaction_ns::guiGetSelectedBlock(const Common::Point &p) {
+class ShowCreditsInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+ int _current;
+ uint32 _startTime;
- int selection = -1;
+ struct Credit {
+ const char *_role;
+ const char *_name;
+ };
- for (uint16 i = 0; i < 9; i++) {
- if (codeSelectBlocks[i].contains(p)) {
- selection = i;
- break;
- }
+ static const Credit _credits[6];
+
+public:
+ ShowCreditsInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("showcredits", helper), _vm(vm) {
}
- if ((selection != -1) && (getPlatform() == Common::kPlatformAmiga)) {
- _gfx->invertBackground(codeTrueBlocks[selection]);
- _gfx->updateScreen();
- beep();
- g_system->delayMillis(100);
- _gfx->invertBackground(codeTrueBlocks[selection]);
- _gfx->updateScreen();
+ void drawCurrentLabel() {
+ uint id[2];
+ id[0] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._role, 1);
+ id[1] = _vm->_gfx->createLabel(_vm->_menuFont, _credits[_current]._name, 1);
+ _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 80);
+ _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);
}
- return selection;
-}
+ virtual MenuInputState* run() {
+ if (_current == -1) {
+ _startTime = g_system->getMillis();
+ _current = 0;
+ drawCurrentLabel();
+ return this;
+ }
-//
-// character selection and protection
-//
-int Parallaction_ns::guiSelectCharacter() {
- debugC(1, kDebugMenu, "Parallaction_ns::guiselectCharacter()");
+ int event = _vm->_input->getLastButtonEvent();
+ uint32 curTime = g_system->getMillis();
+ if ((event == kMouseLeftUp) || (curTime - _startTime > 5500)) {
+ _current++;
+ _startTime = curTime;
+ _vm->_gfx->freeLabels();
- setArrowCursor();
- _soundMan->stopMusic();
+ if (_current == 6) {
+ return _helper->getState("endintro");
+ }
- _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
+ drawCurrentLabel();
+ }
- showSlide("password");
+ return this;
+ }
+ virtual void enter() {
+ _current = -1;
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+ }
+};
- const uint16 (*keys)[PASSWORD_LEN] = (getPlatform() == Common::kPlatformAmiga && (getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys;
- uint16 _di = 0;
- byte points[3] = { 0, 0, 0 };
+const ShowCreditsInputState_NS::Credit ShowCreditsInputState_NS::_credits[6] = {
+ {"Music and Sound Effects", "MARCO CAPRELLI"},
+ {"PC Version", "RICCARDO BALLARINO"},
+ {"Project Manager", "LOVRANO CANEPA"},
+ {"Production", "BRUNO BOZ"},
+ {"Special Thanks to", "LUIGI BENEDICENTI - GILDA and DANILO"},
+ {"Copyright 1992 Euclidea s.r.l ITALY", "All rights reserved"}
+};
- bool fail;
+class EndIntroInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+ bool _isDemo;
- uint id[2];
- id[0] = _gfx->createLabel(_introFont, introMsg1[_language], 1);
- id[1] = _gfx->createLabel(_introFont, introMsg2[_language], 1);
+public:
+ EndIntroInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endintro", helper), _vm(vm) {
+ _isDemo = (_vm->getFeatures() & GF_DEMO) != 0;
+ }
- Graphics::Surface v14;
- v14.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1);
- Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT);
- _gfx->grabBackground(rect, v14);
+ virtual MenuInputState* run() {
- Graphics::Surface block;
- block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1);
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return this;
+ }
- Common::Point p;
+ if (_isDemo) {
+ _engineFlags |= kEngineQuit;
+ return 0;
+ }
- while (true) {
+ _vm->_gfx->freeLabels();
+ return _helper->getState("selectcharacter");
+ }
- points[0] = 0;
- points[1] = 0;
- points[2] = 0;
- fail = false;
+ virtual void enter() {
+ _vm->_soundMan->stopMusic();
+ _vm->_input->setMouseState(MOUSE_DISABLED);
- _gfx->hideLabel(id[1]);
- _gfx->showLabel(id[0], 60, 30);
+ if (!_isDemo) {
+ int label = _vm->_gfx->createLabel(_vm->_menuFont, "CLICK MOUSE BUTTON TO START", 1);
+ _vm->_gfx->showLabel(label, CENTER_LABEL_HORIZONTAL, 80);
+ }
+ }
+};
- _di = 0;
- while (_di < PASSWORD_LEN) {
- _input->waitUntilLeftClick();
- _input->getCursorPos(p);
+class EndPartInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+ bool _allPartsComplete;
- int _si = guiGetSelectedBlock(p);
+ // part completion messages
+ static const char *endMsg0[4];
+ static const char *endMsg1[4];
+ static const char *endMsg2[4];
+ static const char *endMsg3[4];
+ // game completion messages
+ static const char *endMsg4[4];
+ static const char *endMsg5[4];
+ static const char *endMsg6[4];
+ static const char *endMsg7[4];
- if (_si != -1) {
- _gfx->grabBackground(codeTrueBlocks[_si], block);
- _gfx->patchBackground(block, _di * SLOT_WIDTH + SLOT_X, SLOT_Y, false);
- if (keys[0][_di] == _si) {
- points[0]++;
- } else
- if (keys[1][_di] == _si) {
- points[1]++;
- } else
- if (keys[2][_di] == _si) {
- points[2]++;
- } else {
- fail = true;
- }
+public:
+ EndPartInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endpart", helper), _vm(vm) {
+ }
- // build user preference
- points[0] += (keys[0][_di] == _si);
- points[1] += (keys[1][_di] == _si);
- points[2] += (keys[2][_di] == _si);
+ virtual MenuInputState* run() {
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return this;
+ }
- _di++;
- }
+ _vm->_gfx->freeLabels();
+ if (_allPartsComplete) {
+ _vm->scheduleLocationSwitch("estgrotta.drki");
+ return 0;
}
- if (!fail) {
- break;
+ return _helper->getState("selectcharacter");
+ }
+
+ virtual void enter() {
+ _allPartsComplete = _vm->allPartsComplete();
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+
+ uint id[4];
+ if (_allPartsComplete) {
+ id[0] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg4[_language], 1);
+ id[1] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg5[_language], 1);
+ id[2] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg6[_language], 1);
+ id[3] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg7[_language], 1);
+ } else {
+ id[0] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg0[_language], 1);
+ id[1] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg1[_language], 1);
+ id[2] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg2[_language], 1);
+ id[3] = _vm->_gfx->createLabel(_vm->_menuFont, endMsg3[_language], 1);
}
- _gfx->patchBackground(v14, SLOT_X, SLOT_Y, false);
+ _vm->_gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 70);
+ _vm->_gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 100);
+ _vm->_gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 130);
+ _vm->_gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 160);
+ }
+};
- _gfx->hideLabel(id[0]);
- _gfx->showLabel(id[1], 60, 30);
+// part completion messages
+const char *EndPartInputState_NS::endMsg0[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"};
+const char *EndPartInputState_NS::endMsg1[] = {"HAI FINITO QUESTA PARTE", "TU AS COMPLETE' CETTE AVENTURE", "YOU HAVE COMPLETED THIS PART", "DU HAST EIN ABENTEUER ERFOLGREICH"};
+const char *EndPartInputState_NS::endMsg2[] = {"ORA COMPLETA IL RESTO ", "AVEC SUCCES.", "NOW GO ON WITH THE REST OF", "ZU ENDE GEFUHRT"};
+const char *EndPartInputState_NS::endMsg3[] = {"DELL' AVVENTURA", "CONTINUE AVEC LES AUTRES", "THIS ADVENTURE", "MACH' MIT DEN ANDEREN WEITER"};
+// game completion messages
+const char *EndPartInputState_NS::endMsg4[] = {"COMPLIMENTI!", "BRAVO!", "CONGRATULATIONS!", "PRIMA!"};
+const char *EndPartInputState_NS::endMsg5[] = {"HAI FINITO LE TRE PARTI", "TU AS COMPLETE' LES TROIS PARTIES", "YOU HAVE COMPLETED THE THREE PARTS", "DU HAST DREI ABENTEURE ERFOLGREICH"};
+const char *EndPartInputState_NS::endMsg6[] = {"DELL' AVVENTURA", "DE L'AVENTURE", "OF THIS ADVENTURE", "ZU ENDE GEFUHRT"};
+const char *EndPartInputState_NS::endMsg7[] = {"ED ORA IL GRAN FINALE ", "ET MAINTENANT LE GRAND FINAL", "NOW THE GREAT FINAL", "UND YETZT DER GROSSE SCHLUSS!"};
+
+void Parallaction_ns::startGui() {
+ _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
- _gfx->updateScreen();
+ _menuHelper = new MenuInputHelper;
+ assert(_menuHelper);
- g_system->delayMillis(2000);
- }
+ new SelectGameInputState_NS(this, _menuHelper);
+ new LoadGameInputState_NS(this, _menuHelper);
+ new NewGameInputState_NS(this, _menuHelper);
+ new StartDemoInputState_NS(this, _menuHelper);
+ new SelectCharacterInputState_NS(this, _menuHelper);
+ new ChooseLanguageInputState_NS(this, _menuHelper);
+ new SplashInputState1_NS(this, _menuHelper);
+ new SplashInputState0_NS(this, _menuHelper);
+ _menuHelper->setState("intro0");
- _gfx->freeLabels();
+ _input->_inputMode = Input::kInputModeMenu;
+}
- _gfx->setBlackPalette();
- _gfx->updateScreen();
+void Parallaction_ns::startCreditSequence() {
+ _menuHelper = new MenuInputHelper;
+ assert(_menuHelper);
- v14.free();
+ new ShowCreditsInputState_NS(this, _menuHelper);
+ new EndIntroInputState_NS(this, _menuHelper);
+ new SelectCharacterInputState_NS(this, _menuHelper);
+ _menuHelper->setState("showcredits");
+ _input->_inputMode = Input::kInputModeMenu;
+}
- // actually select character
+void Parallaction_ns::startEndPartSequence() {
+ _menuHelper = new MenuInputHelper;
+ assert(_menuHelper);
- int character = -1;
- if (points[0] >= points[1] && points[0] >= points[2]) {
- character = CHAR_DINO;
- } else
- if (points[1] >= points[0] && points[1] >= points[2]) {
- character = CHAR_DONNA;
- } else
- if (points[2] >= points[0] && points[2] >= points[1]) {
- character = CHAR_DOUGH;
- } else {
- error("If you read this, either your CPU or transivity is broken (we believe the former).");
- }
+ new EndPartInputState_NS(this, _menuHelper);
+ new SelectCharacterInputState_NS(this, _menuHelper);
+ _menuHelper->setState("endpart");
- return character;
+ _input->_inputMode = Input::kInputModeMenu;
}
diff --git a/engines/parallaction/input.cpp b/engines/parallaction/input.cpp
index d625199e2b..19747c007e 100644
--- a/engines/parallaction/input.cpp
+++ b/engines/parallaction/input.cpp
@@ -36,25 +36,23 @@ namespace Parallaction {
// loops which could possibly be merged into this one with some effort in changing
// caller code, i.e. adding condition checks.
//
-uint16 Input::readInput() {
+void Input::readInput() {
Common::Event e;
- uint16 KeyDown = 0;
_mouseButtons = kMouseNone;
- _lastKeyDownAscii = -1;
+ _hasKeyPressEvent = false;
Common::EventManager *eventMan = _vm->_system->getEventManager();
while (eventMan->pollEvent(e)) {
switch (e.type) {
case Common::EVENT_KEYDOWN:
- _lastKeyDownAscii = e.kbd.ascii;
+ _hasKeyPressEvent = true;
+ _keyPressed = e.kbd;
+
if (e.kbd.flags == Common::KBD_CTRL && e.kbd.keycode == 'd')
_vm->_debugger->attach();
- if (_vm->getFeatures() & GF_DEMO) break;
- if (e.kbd.keycode == Common::KEYCODE_l) KeyDown = kEvLoadGame;
- if (e.kbd.keycode == Common::KEYCODE_s) KeyDown = kEvSaveGame;
break;
case Common::EVENT_LBUTTONDOWN:
@@ -83,7 +81,7 @@ uint16 Input::readInput() {
case Common::EVENT_RTL:
case Common::EVENT_QUIT:
_vm->_quit = true;
- return KeyDown;
+ return;
default:
break;
@@ -95,13 +93,13 @@ uint16 Input::readInput() {
if (_vm->_debugger->isAttached())
_vm->_debugger->onFrame();
- return KeyDown;
+ return;
}
bool Input::getLastKeyDown(uint16 &ascii) {
- ascii = _lastKeyDownAscii;
- return (_lastKeyDownAscii != -1);
+ ascii = _keyPressed.ascii;
+ return (_hasKeyPressEvent);
}
// FIXME: see comment for readInput()
@@ -128,64 +126,36 @@ void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) {
}
-// FIXME: see comment for readInput()
-void Input::waitUntilLeftClick() {
-
- do {
- readInput();
- _vm->_gfx->updateScreen();
- _vm->_system->delayMillis(30);
- } while (_mouseButtons != kMouseLeftUp);
-
- return;
-}
-
void Input::updateGameInput() {
- int16 keyDown = readInput();
+ readInput();
- debugC(3, kDebugInput, "translateInput: input flags (%i, %i, %i, %i)",
- !_mouseHidden,
- (_engineFlags & kEngineBlockInput) == 0,
- (_engineFlags & kEngineWalking) == 0,
- (_engineFlags & kEngineChangeLocation) == 0
- );
-
- if ((_mouseHidden) ||
- (_engineFlags & kEngineBlockInput) ||
+ if (!isMouseEnabled() ||
(_engineFlags & kEngineWalking) ||
(_engineFlags & kEngineChangeLocation)) {
+ debugC(3, kDebugInput, "updateGameInput: input flags (mouse: %i, walking: %i, changeloc: %i)",
+ isMouseEnabled(),
+ (_engineFlags & kEngineWalking) == 0,
+ (_engineFlags & kEngineChangeLocation) == 0
+ );
+
return;
}
- if (keyDown == kEvQuitGame) {
- _inputData._event = kEvQuitGame;
- } else
- if (keyDown == kEvSaveGame) {
- _inputData._event = kEvSaveGame;
- } else
- if (keyDown == kEvLoadGame) {
- _inputData._event = kEvLoadGame;
- } else {
+ if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) {
+ if (_keyPressed.keycode == Common::KEYCODE_l) _inputData._event = kEvLoadGame;
+ if (_keyPressed.keycode == Common::KEYCODE_s) _inputData._event = kEvSaveGame;
+ }
+
+ if (_inputData._event == kEvNone) {
_inputData._mousePos = _mousePos;
- _inputData._event = kEvNone;
- if (!translateGameInput()) {
- translateInventoryInput();
- }
+ translateGameInput();
}
}
-void Input::updateCommentInput() {
- waitUntilLeftClick();
-
- _vm->hideDialogueStuff();
- _vm->_gfx->setHalfbriteMode(false);
-
- _inputMode = kInputModeGame;
-}
InputData* Input::updateInput() {
@@ -193,15 +163,18 @@ InputData* Input::updateInput() {
switch (_inputMode) {
case kInputModeComment:
- updateCommentInput();
+ case kInputModeDialogue:
+ case kInputModeMenu:
+ readInput();
break;
case kInputModeGame:
updateGameInput();
break;
- case kInputModeDialogue:
+ case kInputModeInventory:
readInput();
+ updateInventoryInput();
break;
}
@@ -230,38 +203,45 @@ void Input::stopHovering() {
_vm->_gfx->hideFloatingLabel();
}
+void Input::takeAction(ZonePtr z) {
+ stopHovering();
+ _vm->pauseJobs();
+ _vm->runZone(z);
+ _vm->resumeJobs();
+}
+
+void Input::walkTo(const Common::Point &dest) {
+ stopHovering();
+ _vm->setArrowCursor();
+ _vm->_char.scheduleWalk(dest.x, dest.y);
+}
bool Input::translateGameInput() {
- if ((_engineFlags & kEnginePauseJobs) || (_engineFlags & kEngineInventory)) {
+ if (_engineFlags & kEnginePauseJobs) {
return false;
}
- if (_actionAfterWalk) {
+ if (_hasDelayedAction) {
// if walking is over, then take programmed action
- _inputData._event = kEvAction;
- _actionAfterWalk = false;
+ takeAction(_delayedActionZone);
+ _hasDelayedAction = false;
+ _delayedActionZone = nullZonePtr;
return true;
}
if (_mouseButtons == kMouseRightDown) {
// right button down shows inventory
-
- if (_vm->hitZone(kZoneYou, _mousePos.x, _mousePos.y) && (_activeItem._id != 0)) {
- _activeItem._index = (_activeItem._id >> 16) & 0xFFFF;
- _engineFlags |= kEngineDragging;
- }
-
- _inputData._event = kEvOpenInventory;
- _transCurrentHoverItem = -1;
+ enterInventoryMode();
return true;
}
// test if mouse is hovering on an interactive zone for the currently selected inventory item
ZonePtr z = _vm->hitZone(_activeItem._id, _mousePos.x, _mousePos.y);
+ Common::Point dest(_mousePos);
if (((_mouseButtons == kMouseLeftUp) && (_activeItem._id == 0) && ((_engineFlags & kEngineWalking) == 0)) && ((!z) || ((z->_type & 0xFFFF) != kZoneCommand))) {
- _inputData._event = kEvWalk;
+ walkTo(dest);
return true;
}
@@ -275,16 +255,17 @@ bool Input::translateGameInput() {
_inputData._zone = z;
if (z->_flags & kFlagsNoWalk) {
// character doesn't need to walk to take specified action
- _inputData._event = kEvAction;
-
+ takeAction(z);
} else {
// action delayed: if Zone defined a moveto position the character is programmed to move there,
// else it will move to the mouse position
- _inputData._event = kEvWalk;
- _actionAfterWalk = true;
+ _delayedActionZone = z;
+ _hasDelayedAction = true;
if (z->_moveTo.y != 0) {
- _inputData._mousePos = z->_moveTo;
+ dest = z->_moveTo;
}
+
+ walkTo(dest);
}
_vm->beep();
@@ -293,31 +274,40 @@ bool Input::translateGameInput() {
}
return true;
-
}
-bool Input::translateInventoryInput() {
- if ((_engineFlags & kEngineInventory) == 0) {
- return false;
+void Input::enterInventoryMode() {
+ bool hitCharacter = _vm->hitZone(kZoneYou, _mousePos.x, _mousePos.y);
+
+ if (hitCharacter) {
+ if (_activeItem._id != 0) {
+ _activeItem._index = (_activeItem._id >> 16) & 0xFFFF;
+ _engineFlags |= kEngineDragging;
+ } else {
+ _vm->setArrowCursor();
+ }
}
- // in inventory
- int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y);
+ stopHovering();
+ _vm->pauseJobs();
+ _vm->openInventory();
- if (_mouseButtons == kMouseRightUp) {
- // right up hides inventory
+ _transCurrentHoverItem = -1;
- _inputData._event = kEvCloseInventory;
- _inputData._inventoryIndex = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y);
- _vm->highlightInventoryItem(-1); // disable
+ _inputMode = kInputModeInventory;
+}
- if ((_engineFlags & kEngineDragging) == 0) {
- return true;
- }
+void Input::exitInventoryMode() {
+ // right up hides inventory
+
+ int pos = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y);
+ _vm->highlightInventoryItem(-1); // disable
+
+ if ((_engineFlags & kEngineDragging)) {
_engineFlags &= ~kEngineDragging;
- ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(_inputData._inventoryIndex));
+ ZonePtr z = _vm->hitZone(kZoneMerge, _activeItem._index, _vm->getInventoryItemIndex(pos));
if (z) {
_vm->dropItem(z->u.merge->_obj1);
@@ -326,25 +316,61 @@ bool Input::translateInventoryInput() {
_vm->_cmdExec->run(z->_commands);
}
- return true;
}
- if (_si == _transCurrentHoverItem) {
- _inputData._event = kEvNone;
+ _vm->closeInventory();
+ if (pos == -1) {
+ _vm->setArrowCursor();
+ } else {
+ const InventoryItem *item = _vm->getInventoryItem(pos);
+ if (item->_index != 0) {
+ _activeItem._id = item->_id;
+ _vm->setInventoryCursor(item->_index);
+ }
+ }
+ _vm->resumeJobs();
+
+ _inputMode = kInputModeGame;
+}
+
+bool Input::updateInventoryInput() {
+ if (_mouseButtons == kMouseRightUp) {
+ exitInventoryMode();
return true;
}
- _transCurrentHoverItem = _si;
- _inputData._event = kEvHoverInventory;
- _inputData._inventoryIndex = _si;
+ int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y);
+ if (_si != _transCurrentHoverItem) {
+ _transCurrentHoverItem = _si;
+ _vm->highlightInventoryItem(_si); // enable
+ }
+
return true;
}
-void Input::showCursor(bool visible) {
- _mouseHidden = !visible;
- _vm->_system->showMouse(visible);
+void Input::setMouseState(MouseTriState state) {
+ assert(state == MOUSE_ENABLED_SHOW || state == MOUSE_ENABLED_HIDE || state == MOUSE_DISABLED);
+ _mouseState = state;
+
+ switch (_mouseState) {
+ case MOUSE_ENABLED_HIDE:
+ case MOUSE_DISABLED:
+ _vm->_system->showMouse(false);
+ break;
+
+ case MOUSE_ENABLED_SHOW:
+ _vm->_system->showMouse(true);
+ break;
+ }
+}
+
+MouseTriState Input::getMouseState() {
+ return _mouseState;
}
+bool Input::isMouseEnabled() {
+ return (_mouseState == MOUSE_ENABLED_SHOW) || (_mouseState == MOUSE_ENABLED_HIDE);
+}
} // namespace Parallaction
diff --git a/engines/parallaction/input.h b/engines/parallaction/input.h
index f260352dba..c1e912db74 100644
--- a/engines/parallaction/input.h
+++ b/engines/parallaction/input.h
@@ -26,6 +26,8 @@
#ifndef PARALLACTION_INPUT_H
#define PARALLACTION_INPUT_H
+#include "common/keyboard.h"
+
#include "parallaction/objects.h"
#include "parallaction/inventory.h"
@@ -47,51 +49,65 @@ struct InputData {
uint _label;
};
+enum MouseTriState {
+ MOUSE_ENABLED_SHOW,
+ MOUSE_ENABLED_HIDE,
+ MOUSE_DISABLED
+};
+
class Input {
void updateGameInput();
- void updateCommentInput();
// input-only
InputData _inputData;
- bool _actionAfterWalk; // actived when the character needs to move before taking an action
- // these two could/should be merged as they carry on the same duty in two member functions,
- // respectively processInput and translateInput
+
+ bool _hasKeyPressEvent;
+ Common::KeyState _keyPressed;
+
+ bool _hasDelayedAction; // actived when the character needs to move before taking an action
+ ZonePtr _delayedActionZone;
+
int16 _transCurrentHoverItem;
InputData *translateInput();
bool translateGameInput();
- bool translateInventoryInput();
+ bool updateInventoryInput();
+ void takeAction(ZonePtr z);
+ void walkTo(const Common::Point &dest);
Parallaction *_vm;
Common::Point _mousePos;
uint16 _mouseButtons;
- int32 _lastKeyDownAscii;
- bool _mouseHidden;
ZonePtr _hoverZone;
+ void enterInventoryMode();
+ void exitInventoryMode();
+
public:
enum {
kInputModeGame = 0,
kInputModeComment = 1,
- kInputModeDialogue = 2
+ kInputModeDialogue = 2,
+ kInputModeInventory = 3,
+ kInputModeMenu = 4
};
Input(Parallaction *vm) : _vm(vm) {
_transCurrentHoverItem = 0;
- _actionAfterWalk = false; // actived when the character needs to move before taking an action
- _mouseHidden = false;
+ _hasDelayedAction = false; // actived when the character needs to move before taking an action
+ _mouseState = MOUSE_DISABLED;
_activeItem._index = 0;
_activeItem._id = 0;
_mouseButtons = 0;
+ _delayedActionZone = nullZonePtr;
}
virtual ~Input() { }
- void showCursor(bool visible);
void getCursorPos(Common::Point& p) {
p = _mousePos;
}
@@ -99,15 +115,20 @@ public:
int _inputMode;
InventoryItem _activeItem;
- uint16 readInput();
+ void readInput();
InputData* updateInput();
void trackMouse(ZonePtr z);
- void waitUntilLeftClick();
void waitForButtonEvent(uint32 buttonEventMask, int32 timeout = -1);
uint32 getLastButtonEvent() { return _mouseButtons; }
bool getLastKeyDown(uint16 &ascii);
void stopHovering();
+
+ MouseTriState _mouseState;
+
+ void setMouseState(MouseTriState state);
+ MouseTriState getMouseState();
+ bool isMouseEnabled();
};
} // namespace Parallaction
diff --git a/engines/parallaction/inventory.cpp b/engines/parallaction/inventory.cpp
index 58848196d7..7b92974205 100644
--- a/engines/parallaction/inventory.cpp
+++ b/engines/parallaction/inventory.cpp
@@ -30,23 +30,58 @@
namespace Parallaction {
-//
-// inventory is a grid made of (at most) 30 cells, 24x24 pixels each,
-// arranged in 6 lines
-//
-// inventory items are stored in cnv files in a 32x24 grid
-// but only 24x24 pixels are actually copied to graphic memory
-//
+
+/*
+#define INVENTORYITEM_PITCH 32
+#define INVENTORYITEM_WIDTH 24
+#define INVENTORYITEM_HEIGHT 24
#define INVENTORY_MAX_ITEMS 30
-#define INVENTORY_FIRST_ITEM 4 // first four entries are used up by verbs
#define INVENTORY_ITEMS_PER_LINE 5
#define INVENTORY_LINES 6
#define INVENTORY_WIDTH (INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
#define INVENTORY_HEIGHT (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
-
+*/
+
+InventoryItem _verbs_NS[] = {
+ { 1, kZoneDoor },
+ { 3, kZoneExamine },
+ { 2, kZoneGet },
+ { 4, kZoneSpeak },
+ { 0, 0 }
+};
+
+InventoryItem _verbs_BR[] = {
+ { 1, kZoneBox },
+ { 2, kZoneGet },
+ { 3, kZoneExamine },
+ { 4, kZoneSpeak },
+ { 0, 0 }
+};
+
+InventoryProperties _invProps_NS = {
+ 32, // INVENTORYITEM_PITCH
+ 24, // INVENTORYITEM_WIDTH
+ 24, // INVENTORYITEM_HEIGHT
+ 30, // INVENTORY_MAX_ITEMS
+ 5, // INVENTORY_ITEMS_PER_LINE
+ 6, // INVENTORY_LINES
+ 5 * 24, // INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
+ 6 * 24 // INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
+};
+
+InventoryProperties _invProps_BR = {
+ 51, // INVENTORYITEM_PITCH
+ 51, // INVENTORYITEM_WIDTH
+ 51, // INVENTORYITEM_HEIGHT
+ 48, // INVENTORY_MAX_ITEMS
+ 6, // INVENTORY_ITEMS_PER_LINE
+ 8, // INVENTORY_LINES
+ 6 * 51, // INVENTORY_WIDTH =(INVENTORY_ITEMS_PER_LINE*INVENTORYITEM_WIDTH)
+ 8 * 51 // INVENTORY_HEIGHT = (INVENTORY_LINES*INVENTORYITEM_HEIGHT)
+};
int16 Parallaction::getHoverInventoryItem(int16 x, int16 y) {
return _inventoryRenderer->hitTest(Common::Point(x,y));
@@ -91,8 +126,19 @@ int16 Parallaction::getInventoryItemIndex(int16 pos) {
}
void Parallaction::initInventory() {
- _inventory = new Inventory(INVENTORY_MAX_ITEMS);
- _inventoryRenderer = new InventoryRenderer(this);
+ InventoryProperties *props;
+ InventoryItem *verbs;
+
+ if (getGameType() == GType_Nippon) {
+ props = &_invProps_NS;
+ verbs = _verbs_NS;
+ } else {
+ props = &_invProps_BR;
+ verbs = _verbs_BR;
+ }
+
+ _inventory = new Inventory(props, verbs);
+ _inventoryRenderer = new InventoryRenderer(this, props);
_inventoryRenderer->bindInventory(_inventory);
}
@@ -119,8 +165,8 @@ void Parallaction::closeInventory() {
-InventoryRenderer::InventoryRenderer(Parallaction *vm) : _vm(vm) {
- _surf.create(INVENTORY_WIDTH, INVENTORY_HEIGHT, 1);
+InventoryRenderer::InventoryRenderer(Parallaction *vm, InventoryProperties *props) : _vm(vm), _props(props) {
+ _surf.create(_props->_width, _props->_height, 1);
}
InventoryRenderer::~InventoryRenderer() {
@@ -131,15 +177,13 @@ void InventoryRenderer::showInventory() {
if (!_inv)
error("InventoryRenderer not bound to inventory");
- _engineFlags |= kEngineInventory;
-
uint16 lines = getNumLines();
Common::Point p;
_vm->_input->getCursorPos(p);
- _pos.x = CLIP(p.x - (INVENTORY_WIDTH / 2), 0, (int)(_vm->_screenWidth - INVENTORY_WIDTH));
- _pos.y = CLIP(p.y - 2 - (lines * INVENTORYITEM_HEIGHT), 0, (int)(_vm->_screenHeight - lines * INVENTORYITEM_HEIGHT));
+ _pos.x = CLIP((int)(p.x - (_props->_width / 2)), 0, (int)(_vm->_screenWidth - _props->_width));
+ _pos.y = CLIP((int)(p.y - 2 - (lines * _props->_itemHeight)), 0, (int)(_vm->_screenHeight - lines * _props->_itemHeight));
refresh();
}
@@ -147,13 +191,11 @@ void InventoryRenderer::showInventory() {
void InventoryRenderer::hideInventory() {
if (!_inv)
error("InventoryRenderer not bound to inventory");
-
- _engineFlags &= ~kEngineInventory;
}
void InventoryRenderer::getRect(Common::Rect& r) const {
- r.setWidth(INVENTORY_WIDTH);
- r.setHeight(INVENTORYITEM_HEIGHT * getNumLines());
+ r.setWidth(_props->_width);
+ r.setHeight(_props->_itemHeight * getNumLines());
r.moveTo(_pos);
}
@@ -163,35 +205,36 @@ ItemPosition InventoryRenderer::hitTest(const Common::Point &p) const {
if (!r.contains(p))
return -1;
- return ((p.x - _pos.x) / INVENTORYITEM_WIDTH) + (INVENTORY_ITEMS_PER_LINE * ((p.y - _pos.y) / INVENTORYITEM_HEIGHT));
+ return ((p.x - _pos.x) / _props->_itemWidth) + (_props->_itemsPerLine * ((p.y - _pos.y) / _props->_itemHeight));
}
-
void InventoryRenderer::drawItem(ItemPosition pos, ItemName name) {
-
Common::Rect r;
getItemRect(pos, r);
+ byte* d = (byte*)_surf.getBasePtr(r.left, r.top);
+ drawItem(name, d, _surf.pitch);
+}
- // FIXME: this will end up in a general blit function
-
+void InventoryRenderer::drawItem(ItemName name, byte *buffer, uint pitch) {
byte* s = _vm->_char._objs->getData(name);
- byte* d = (byte*)_surf.getBasePtr(r.left, r.top);
- for (uint32 i = 0; i < INVENTORYITEM_HEIGHT; i++) {
- memcpy(d, s, INVENTORYITEM_WIDTH);
+ byte* d = buffer;
+ for (uint i = 0; i < _props->_itemHeight; i++) {
+ memcpy(d, s, _props->_itemWidth);
- d += INVENTORY_WIDTH;
- s += INVENTORYITEM_PITCH;
+ s += _props->_itemPitch;
+ d += pitch;
}
}
+
int16 InventoryRenderer::getNumLines() const {
int16 num = _inv->getNumItems();
- return (num / INVENTORY_ITEMS_PER_LINE) + ((num % INVENTORY_ITEMS_PER_LINE) > 0 ? 1 : 0);
+ return (num / _props->_itemsPerLine) + ((num % _props->_itemsPerLine) > 0 ? 1 : 0);
}
void InventoryRenderer::refresh() {
- for (uint16 i = 0; i < INVENTORY_MAX_ITEMS; i++) {
+ for (uint16 i = 0; i < _props->_maxItems; i++) {
ItemName name = _inv->getItemName(i);
drawItem(i, name);
}
@@ -212,25 +255,24 @@ void InventoryRenderer::highlightItem(ItemPosition pos, byte color) {
void InventoryRenderer::getItemRect(ItemPosition pos, Common::Rect &r) {
- r.setHeight(INVENTORYITEM_HEIGHT);
- r.setWidth(INVENTORYITEM_WIDTH);
+ r.setHeight(_props->_itemHeight);
+ r.setWidth(_props->_itemWidth);
- uint16 line = pos / INVENTORY_ITEMS_PER_LINE;
- uint16 col = pos % INVENTORY_ITEMS_PER_LINE;
+ uint16 line = pos / _props->_itemsPerLine;
+ uint16 col = pos % _props->_itemsPerLine;
- r.moveTo(col * INVENTORYITEM_WIDTH, line * INVENTORYITEM_HEIGHT);
+ r.moveTo(col * _props->_itemWidth, line * _props->_itemHeight);
}
+Inventory::Inventory(InventoryProperties *props, InventoryItem *verbs) : _numItems(0), _props(props) {
+ _items = (InventoryItem*)calloc(_props->_maxItems, sizeof(InventoryItem));
-
-Inventory::Inventory(uint16 maxItems) : _maxItems(maxItems), _numItems(0) {
- _items = (InventoryItem*)calloc(_maxItems, sizeof(InventoryItem));
-
- addItem(1, kZoneDoor);
- addItem(3, kZoneExamine);
- addItem(2, kZoneGet);
- addItem(4, kZoneSpeak);
+ int i = 0;
+ for ( ; verbs[i]._id; i++) {
+ addItem(verbs[i]._id, verbs[i]._index);
+ }
+ _numVerbs = i;
}
@@ -241,7 +283,7 @@ Inventory::~Inventory() {
ItemPosition Inventory::addItem(ItemName name, uint32 value) {
debugC(1, kDebugInventory, "addItem(%i, %i)", name, value);
- if (_numItems == INVENTORY_MAX_ITEMS) {
+ if (_numItems == _props->_maxItems) {
debugC(3, kDebugInventory, "addItem: inventory is full");
return -1;
}
@@ -300,9 +342,9 @@ void Inventory::removeItem(ItemName name) {
void Inventory::clear(bool keepVerbs) {
debugC(1, kDebugInventory, "clearInventory()");
- uint first = (keepVerbs ? INVENTORY_FIRST_ITEM : 0);
+ uint first = (keepVerbs ? _numVerbs : 0);
- for (uint16 slot = first; slot < _maxItems; slot++) {
+ for (uint16 slot = first; slot < _numVerbs; slot++) {
_items[slot]._id = 0;
_items[slot]._index = 0;
}
@@ -312,7 +354,7 @@ void Inventory::clear(bool keepVerbs) {
ItemName Inventory::getItemName(ItemPosition pos) const {
- return (pos >= 0 && pos < INVENTORY_MAX_ITEMS) ? _items[pos]._index : 0;
+ return (pos >= 0 && pos < _props->_maxItems) ? _items[pos]._index : 0;
}
const InventoryItem* Inventory::getItem(ItemPosition pos) const {
diff --git a/engines/parallaction/inventory.h b/engines/parallaction/inventory.h
index 8c32c09219..f041627810 100644
--- a/engines/parallaction/inventory.h
+++ b/engines/parallaction/inventory.h
@@ -38,9 +38,19 @@ struct InventoryItem {
uint16 _index; // index to frame in objs file
};
-#define INVENTORYITEM_PITCH 32
-#define INVENTORYITEM_WIDTH 24
-#define INVENTORYITEM_HEIGHT 24
+struct InventoryProperties {
+ uint _itemPitch;
+ uint _itemWidth;
+ uint _itemHeight;
+
+ int _maxItems;
+
+ int _itemsPerLine;
+ int _maxLines;
+
+ int _width;
+ int _height;
+};
#define MAKE_INVENTORY_ID(x) (((x) & 0xFFFF) << 16)
@@ -50,12 +60,14 @@ typedef uint16 ItemName;
class Inventory {
protected:
+ uint16 _numVerbs;
+
InventoryItem *_items;
- uint16 _maxItems;
uint16 _numItems;
+ InventoryProperties *_props;
public:
- Inventory(uint16 maxItems);
+ Inventory(InventoryProperties *props, InventoryItem *verbs);
virtual ~Inventory();
ItemPosition addItem(ItemName name, uint32 value);
@@ -75,6 +87,8 @@ public:
class InventoryRenderer {
Parallaction *_vm;
+ InventoryProperties *_props;
+
Inventory *_inv;
Common::Point _pos;
@@ -87,7 +101,7 @@ protected:
void refresh();
public:
- InventoryRenderer(Parallaction *vm);
+ InventoryRenderer(Parallaction *vm, InventoryProperties *props);
virtual ~InventoryRenderer();
void bindInventory(Inventory *inv) { _inv = inv; }
@@ -97,6 +111,7 @@ public:
ItemPosition hitTest(const Common::Point &p) const;
void highlightItem(ItemPosition pos, byte color);
+ void drawItem(ItemName name, byte *buffer, uint pitch);
byte* getData() const { return (byte*)_surf.pixels; }
diff --git a/engines/parallaction/module.mk b/engines/parallaction/module.mk
index fb867f5285..9d44422541 100644
--- a/engines/parallaction/module.mk
+++ b/engines/parallaction/module.mk
@@ -14,6 +14,7 @@ MODULE_OBJS := \
font.o \
gfxbase.o \
graphics.o \
+ gui.o \
gui_br.o \
gui_ns.o \
input.o \
diff --git a/engines/parallaction/objects.cpp b/engines/parallaction/objects.cpp
index 66025cf0f7..c387484de7 100644
--- a/engines/parallaction/objects.cpp
+++ b/engines/parallaction/objects.cpp
@@ -60,14 +60,14 @@ Animation::~Animation() {
uint16 Animation::width() const {
if (!gfxobj) return 0;
Common::Rect r;
- gfxobj->getRect(0, r);
+ gfxobj->getRect(_frame, r);
return r.width();
}
uint16 Animation::height() const {
if (!gfxobj) return 0;
Common::Rect r;
- gfxobj->getRect(0, r);
+ gfxobj->getRect(_frame, r);
return r.height();
}
@@ -81,6 +81,12 @@ byte* Animation::getFrameData(uint32 index) const {
return gfxobj->getData(index);
}
+void Animation::validateScriptVars() {
+ // this is used to clip values of _frame, _left and _top
+ // which can be screwed up by buggy scripts.
+
+ _frame = CLIP(_frame, (int16)0, (int16)(getFrameNum() - 1));
+}
#define NUM_LOCALS 10
char _localNames[NUM_LOCALS][10];
diff --git a/engines/parallaction/objects.h b/engines/parallaction/objects.h
index 19835da9d0..7e7a811ba6 100644
--- a/engines/parallaction/objects.h
+++ b/engines/parallaction/objects.h
@@ -54,6 +54,7 @@ typedef Common::SharedPtr<Instruction> InstructionPtr;
typedef Common::List<InstructionPtr> InstructionList;
extern InstructionPtr nullInstructionPtr;
+typedef Common::List<Common::Point> PointList;
enum ZoneTypes {
kZoneExamine = 1, // zone displays comment if activated
@@ -67,7 +68,11 @@ enum ZoneTypes {
kZoneNone = 0x100, // used to prevent parsing on peculiar Animations
kZoneTrap = 0x200, // zone activated when character enters
kZoneYou = 0x400, // marks the character
- kZoneCommand = 0x800
+ kZoneCommand = 0x800,
+
+ // BRA specific
+ kZonePath = 0x1000, // defines nodes for assisting walk calculation routines
+ kZoneBox = 0x2000
};
@@ -89,6 +94,7 @@ enum ZoneFlags {
kFlagsYourself = 0x1000,
kFlagsScaled = 0x2000,
kFlagsSelfuse = 0x4000,
+ kFlagsIsAnimation = 0x1000000, // BRA: used in walk code (trap check), to tell is a Zone is an Animation
kFlagsAnimLinked = 0x2000000
};
@@ -256,6 +262,15 @@ struct MergeData { // size = 12
_obj1 = _obj2 = _obj3 = 0;
}
};
+#define MAX_WALKPOINT_LISTS 20
+struct PathData {
+ int _numLists;
+ PointList _lists[MAX_WALKPOINT_LISTS];
+
+ PathData() {
+ _numLists = 0;
+ }
+};
struct TypeData {
GetData *get;
@@ -264,6 +279,8 @@ struct TypeData {
DoorData *door;
HearData *hear;
MergeData *merge;
+ // BRA specific field
+ PathData *path;
TypeData() {
get = NULL;
@@ -272,6 +289,7 @@ struct TypeData {
door = NULL;
hear = NULL;
merge = NULL;
+ path = NULL;
}
};
@@ -432,6 +450,8 @@ struct Animation : public Zone {
virtual uint16 height() const;
uint16 getFrameNum() const;
byte* getFrameData(uint32 index) const;
+
+ void validateScriptVars();
};
class Table {
diff --git a/engines/parallaction/parallaction.cpp b/engines/parallaction/parallaction.cpp
index 2845fcb94b..6a61087804 100644
--- a/engines/parallaction/parallaction.cpp
+++ b/engines/parallaction/parallaction.cpp
@@ -98,6 +98,10 @@ Parallaction::~Parallaction() {
freeCharacter();
destroyInventory();
+ cleanupGui();
+
+ delete _comboArrow;
+
delete _localFlagNames;
delete _gfx;
delete _soundMan;
@@ -139,6 +143,8 @@ int Parallaction::init() {
_debugger = new Debugger(this);
+ _menuHelper = 0;
+
setupBalloonManager();
syncSoundSettings();
@@ -155,7 +161,7 @@ void Parallaction::clearSet(OpcodeSet &opcodes) {
void Parallaction::updateView() {
- if ((_engineFlags & kEnginePauseJobs) && (_engineFlags & kEngineInventory) == 0) {
+ if ((_engineFlags & kEnginePauseJobs) && (_input->_inputMode != Input::kInputModeInventory)) {
return;
}
@@ -259,10 +265,9 @@ void Parallaction::freeLocation() {
_localFlagNames->clear();
- _location._walkNodes.clear();
+ _location._walkPoints.clear();
_gfx->clearGfxObjects(kGfxObjNormal);
- freeBackground();
_location._programs.clear();
freeZones();
@@ -282,15 +287,17 @@ void Parallaction::freeLocation() {
void Parallaction::freeBackground() {
- _gfx->freeBackground();
_pathBuffer = 0;
}
void Parallaction::setBackground(const char* name, const char* mask, const char* path) {
- _gfx->setBackground(kBackgroundLocation, name, mask, path);
- _pathBuffer = &_gfx->_backgroundInfo.path;
+ BackgroundInfo *info = new BackgroundInfo;
+ _disk->loadScenery(*info, name, mask, path);
+
+ _gfx->setBackground(kBackgroundLocation, info);
+ _pathBuffer = &info->path;
return;
}
@@ -301,46 +308,11 @@ void Parallaction::showLocationComment(const char *text, bool end) {
void Parallaction::processInput(InputData *data) {
+ if (!data) {
+ return;
+ }
switch (data->_event) {
- case kEvAction:
- debugC(2, kDebugInput, "processInput: kEvAction");
- _input->stopHovering();
- pauseJobs();
- runZone(data->_zone);
- resumeJobs();
- break;
-
- case kEvOpenInventory:
- _input->stopHovering();
- if (hitZone(kZoneYou, data->_mousePos.x, data->_mousePos.y) == 0) {
- setArrowCursor();
- }
- pauseJobs();
- openInventory();
- break;
-
- case kEvCloseInventory: // closes inventory and possibly select item
- closeInventory();
- setInventoryCursor(data->_inventoryIndex);
- resumeJobs();
- break;
-
- case kEvHoverInventory:
- highlightInventoryItem(data->_inventoryIndex); // enable
- break;
-
- case kEvWalk:
- debugC(2, kDebugInput, "processInput: kEvWalk");
- _input->stopHovering();
- setArrowCursor();
- _char.scheduleWalk(data->_mousePos.x, data->_mousePos.y);
- break;
-
- case kEvQuitGame:
- _quit = true;
- _vm->quitGame();
- break;
case kEvSaveGame:
_input->stopHovering();
@@ -365,20 +337,19 @@ void Parallaction::runGame() {
if (_vm->quit())
return;
- if (_input->_inputMode == Input::kInputModeDialogue) {
- runDialogueFrame();
- } else {
- if (data->_event != kEvNone) {
- processInput(data);
- }
+ runGuiFrame();
+ runDialogueFrame();
+ runCommentFrame();
- if (_vm->quit())
- return;
+ if (_vm->quit())
+ return;
+ if (_input->_inputMode == Input::kInputModeGame) {
+ processInput(data);
runPendingZones();
- if (_vm->quit())
- return;
+ if (_vm->quit())
+ return;
if (_engineFlags & kEngineChangeLocation) {
changeLocation(_location._name);
@@ -393,7 +364,7 @@ void Parallaction::runGame() {
if (_char._ani->gfxobj) {
_char._ani->gfxobj->z = _char._ani->_z;
}
- walk(_char);
+ _char._walker->walk();
drawAnimations();
}
@@ -429,11 +400,10 @@ void Parallaction::doLocationEnterTransition() {
_programExec->runScripts(_location._programs.begin(), _location._programs.end());
drawAnimations();
-
+ showLocationComment(_location._comment, false);
_gfx->updateScreen();
- showLocationComment(_location._comment, false);
- _input->waitUntilLeftClick();
+ _input->waitForButtonEvent(kMouseLeftUp);
_balloonMan->freeBalloons();
// fades maximum intensity palette towards approximation of main palette
@@ -543,7 +513,7 @@ const char Character::_suffixTras[] = "tras";
const char Character::_empty[] = "\0";
-Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation), _builder(_ani) {
+Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation) {
_talk = NULL;
_head = NULL;
_objs = NULL;
@@ -562,24 +532,61 @@ Character::Character(Parallaction *vm) : _vm(vm), _ani(new Animation), _builder(
_ani->_flags = kFlagsActive | kFlagsNoName;
_ani->_type = kZoneYou;
strncpy(_ani->_name, "yourself", ZONENAME_LENGTH);
+
+ // TODO: move creation into Parallaction. Needs to make Character a pointer first.
+ if (_vm->getGameType() == GType_Nippon) {
+ _builder = new PathBuilder_NS(this);
+ _walker = new PathWalker_NS(this);
+ } else {
+ _builder = new PathBuilder_BR(this);
+ _walker = new PathWalker_BR(this);
+ }
+}
+
+Character::~Character() {
+ delete _builder;
+ _builder = 0;
+
+ delete _walker;
+ _walker = 0;
+
+ free();
}
void Character::getFoot(Common::Point &foot) {
- foot.x = _ani->_left + _ani->width() / 2;
- foot.y = _ani->_top + _ani->height();
+ Common::Rect rect;
+ _ani->gfxobj->getRect(_ani->_frame, rect);
+
+ foot.x = _ani->_left + (rect.left + rect.width() / 2);
+ foot.y = _ani->_top + (rect.top + rect.height());
}
void Character::setFoot(const Common::Point &foot) {
- _ani->_left = foot.x - _ani->width() / 2;
- _ani->_top = foot.y - _ani->height();
+ Common::Rect rect;
+ _ani->gfxobj->getRect(_ani->_frame, rect);
+
+ _ani->_left = foot.x - (rect.left + rect.width() / 2);
+ _ani->_top = foot.y - (rect.top + rect.height());
+}
+
+#if 0
+void dumpPath(const PointList &list, const char* text) {
+ for (PointList::iterator it = list.begin(); it != list.end(); it++)
+ printf("node (%i, %i)\n", it->x, it->y);
+
+ return;
}
+#endif
void Character::scheduleWalk(int16 x, int16 y) {
if ((_ani->_flags & kFlagsRemove) || (_ani->_flags & kFlagsActive) == 0) {
return;
}
- _walkPath = _builder.buildPath(x, y);
+ _builder->buildPath(x, y);
+#if 0
+ dumpPath(_walkPath, _name);
+#endif
_engineFlags |= kEngineWalking;
}
diff --git a/engines/parallaction/parallaction.h b/engines/parallaction/parallaction.h
index e9897b67c3..2a32904d63 100644
--- a/engines/parallaction/parallaction.h
+++ b/engines/parallaction/parallaction.h
@@ -100,10 +100,8 @@ enum {
enum EngineFlags {
kEnginePauseJobs = (1 << 1),
- kEngineInventory = (1 << 2),
kEngineWalking = (1 << 3),
kEngineChangeLocation = (1 << 4),
- kEngineBlockInput = (1 << 5),
kEngineDragging = (1 << 6),
kEngineTransformedDonna = (1 << 7),
@@ -113,12 +111,6 @@ enum EngineFlags {
enum {
kEvNone = 0,
- kEvAction = 3,
- kEvOpenInventory = 4,
- kEvCloseInventory = 5,
- kEvHoverInventory = 6,
- kEvWalk = 7,
- kEvQuitGame = 1000,
kEvSaveGame = 2000,
kEvLoadGame = 4000
};
@@ -163,6 +155,7 @@ class Gfx;
class SoundMan;
class Input;
class DialogueManager;
+class MenuInputHelper;
struct Location {
@@ -183,7 +176,7 @@ struct Location {
char _soundFile[50];
// NS specific
- WalkNodeList _walkNodes;
+ PointList _walkPoints;
char _slideText[2][MAX_TOKEN_LEN];
// BRA specific
@@ -204,10 +197,13 @@ struct Character {
GfxObj *_head;
GfxObj *_talk;
GfxObj *_objs;
- PathBuilder _builder;
- WalkNodeList *_walkPath;
+ PathBuilder *_builder;
+ PathWalker *_walker;
+ PointList _walkPath;
Character(Parallaction *vm);
+ ~Character();
+
void getFoot(Common::Point &foot);
void setFoot(const Common::Point &foot);
void scheduleWalk(int16 x, int16 y);
@@ -271,10 +267,6 @@ public:
virtual void syncSoundSettings();
- void finalizeWalk(Character &character);
- int16 selectWalkFrame(Character &character, const Common::Point& pos, const WalkNodePtr to);
- void clipMove(Common::Point& pos, const Common::Point& to);
-
ZonePtr findZone(const char *name);
ZonePtr hitZone(uint32 type, uint16 x, uint16 y);
uint16 runZone(ZonePtr z);
@@ -339,6 +331,7 @@ public:
Common::RandomSource _rnd;
Debugger *_debugger;
+ Frames *_comboArrow;
protected: // data
@@ -366,8 +359,6 @@ protected: // members
void displayComment(ExamineData *data);
- void checkDoor(const Common::Point &foot);
-
void freeCharacter();
int16 pickupItem(ZonePtr z);
@@ -382,18 +373,18 @@ public:
virtual void callFunction(uint index, void* parm) { }
virtual void setArrowCursor() = 0;
- virtual void setInventoryCursor(int pos) = 0;
+ virtual void setInventoryCursor(ItemName name) = 0;
virtual void parseLocation(const char* name) = 0;
void updateDoor(ZonePtr z);
- virtual void walk(Character &character) = 0;
virtual void drawAnimations() = 0;
void beep();
ZonePtr _zoneTrap;
+ PathBuilder* getPathBuilder(Character *ch);
public:
// const char **_zoneFlagNamesRes;
@@ -429,6 +420,17 @@ public:
void exitDialogueMode();
void runDialogueFrame();
+ MenuInputHelper *_menuHelper;
+ void runGuiFrame();
+ void cleanupGui();
+
+ ZonePtr _commentZone;
+ void enterCommentMode(ZonePtr z);
+ void exitCommentMode();
+ void runCommentFrame();
+
+ void setInternLanguage(uint id);
+ uint getInternLanguage();
};
@@ -487,12 +489,18 @@ public:
typedef void (Parallaction_ns::*Callable)(void*);
virtual void callFunction(uint index, void* parm);
- void setMousePointer(uint32 value);
bool loadGame();
bool saveGame();
void switchBackground(const char* background, const char* mask);
+ void showSlide(const char *name, int x = 0, int y = 0);
+ void setArrowCursor();
+
+ // TODO: this should be private!!!!!!!
+ bool _inTestResult;
+ void cleanupGame();
+ bool allPartsComplete();
private:
LocationParser_ns *_locationParser;
@@ -504,17 +512,14 @@ private:
Common::String genSaveFileName(uint slot, bool oldStyle = false);
Common::InSaveFile *getInSaveFile(uint slot);
Common::OutSaveFile *getOutSaveFile(uint slot);
- bool allPartsComplete();
void setPartComplete(const Character& character);
private:
void changeLocation(char *location);
void changeCharacter(const char *name);
void runPendingZones();
- void cleanupGame();
- void setArrowCursor();
- void setInventoryCursor(int pos);
+ void setInventoryCursor(ItemName name);
void doLoadGame(uint16 slot);
@@ -527,7 +532,6 @@ private:
static byte _resMouseArrow[256];
byte *_mouseArrow;
- Frames *_mouseComposedArrow;
static const Callable _dosCallables[25];
static const Callable _amigaCallables[25];
@@ -547,9 +551,6 @@ private:
ZonePtr _moveSarcExaZones[5];
AnimationPtr _rightHandAnim;
- bool _inTestResult;
-
-
// common callables
void _c_play_boogie(void*);
void _c_startIntro(void*);
@@ -586,7 +587,6 @@ private:
const Callable *_callables;
protected:
- void walk(Character &character);
void drawAnimations();
void parseLocation(const char *filename);
@@ -594,15 +594,9 @@ protected:
void selectStartLocation();
- void guiStart();
- int guiSelectCharacter();
- void guiSplash();
- int guiNewGame();
- uint16 guiChooseLanguage();
- uint16 guiSelectGame();
- int guiGetSelectedBlock(const Common::Point &p);
-
- void showSlide(const char *name);
+ void startGui();
+ void startCreditSequence();
+ void startEndPartSequence();
};
@@ -646,7 +640,8 @@ public:
int32 _counters[32];
uint32 _zoneFlags[NUM_LOCATIONS][NUM_ZONES];
-
+ void startPart(uint part);
+ void setArrowCursor();
private:
LocationParser_br *_locationParser;
ProgramParser_br *_programParser;
@@ -655,17 +650,14 @@ private:
void initFonts();
void freeFonts();
- void setArrowCursor();
- void setInventoryCursor(int pos);
+ void setInventoryCursor(ItemName name);
void changeLocation(char *location);
void runPendingZones();
void initPart();
void freePart();
- void startPart();
- void setMousePointer(int16 index);
void initCursors();
Frames *_dinoCursor;
@@ -676,10 +668,7 @@ private:
static const char *_partNames[];
- void guiStart();
- int guiShowMenu();
- void guiSplash(const char *name);
- Frames* guiRenderMenuItem(const char *text);
+ void startGui();
static const Callable _dosCallables[6];
diff --git a/engines/parallaction/parallaction_br.cpp b/engines/parallaction/parallaction_br.cpp
index 0c8058e4bd..f4329edf74 100644
--- a/engines/parallaction/parallaction_br.cpp
+++ b/engines/parallaction/parallaction_br.cpp
@@ -32,6 +32,27 @@
namespace Parallaction {
+struct MouseComboProperties {
+ int _xOffset;
+ int _yOffset;
+ int _width;
+ int _height;
+};
+/*
+// TODO: improve NS's handling of normal cursor before merging cursor code.
+MouseComboProperties _mouseComboProps_NS = {
+ 7, // combo x offset (the icon from the inventory will be rendered from here)
+ 7, // combo y offset (ditto)
+ 32, // combo (arrow + icon) width
+ 32 // combo (arrow + icon) height
+};
+*/
+MouseComboProperties _mouseComboProps_BR = {
+ 8, // combo x offset (the icon from the inventory will be rendered from here)
+ 8, // combo y offset (ditto)
+ 68, // combo (arrow + icon) width
+ 68 // combo (arrow + icon) height
+};
const char *Parallaction_br::_partNames[] = {
"PART0",
@@ -56,7 +77,11 @@ int Parallaction_br::init() {
if (getGameType() == GType_BRA) {
if (getPlatform() == Common::kPlatformPC) {
- _disk = new DosDisk_br(this);
+ if (getFeatures() & GF_DEMO) {
+ _disk = new DosDemo_br(this);
+ } else {
+ _disk = new DosDisk_br(this);
+ }
_disk->setLanguage(2); // NOTE: language is now hardcoded to English. Original used command-line parameters.
_soundMan = new DummySoundMan(this);
} else {
@@ -99,6 +124,7 @@ Parallaction_br::~Parallaction_br() {
delete _dougCursor;
delete _donnaCursor;
+ delete _mouseArrow;
}
void Parallaction_br::callFunction(uint index, void* parm) {
@@ -109,16 +135,14 @@ void Parallaction_br::callFunction(uint index, void* parm) {
int Parallaction_br::go() {
- guiSplash("dyna");
- guiSplash("core");
+ if (getFeatures() & GF_DEMO) {
+ startPart(1);
+ } else {
+ startGui();
+ }
while (quit() == 0) {
- guiStart();
-
- if (quit())
- return _eventMan->shouldRTL();
-
// initCharacter();
_input->_inputMode = Input::kInputModeGame;
@@ -152,6 +176,12 @@ void Parallaction_br::initCursors() {
_dougCursor = _disk->loadPointer("pointer2");
_donnaCursor = _disk->loadPointer("pointer3");
+ Graphics::Surface *surf = new Graphics::Surface;
+ surf->create(_mouseComboProps_BR._width, _mouseComboProps_BR._height, 1);
+ _comboArrow = new SurfaceToFrames(surf);
+
+ // TODO: choose the pointer depending on the active character
+ // For now, we pick Donna's
_mouseArrow = _donnaCursor;
} else {
// TODO: Where are the Amiga cursors?
@@ -159,19 +189,6 @@ void Parallaction_br::initCursors() {
}
-void Parallaction_br::setMousePointer(int16 index) {
- // FIXME: Where are the Amiga cursors?
- if (getPlatform() == Common::kPlatformAmiga)
- return;
-
- Common::Rect r;
- _mouseArrow->getRect(0, r);
-
- _system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0);
- _system->showMouse(true);
-
-}
-
void Parallaction_br::initPart() {
memset(_counters, 0, ARRAYSIZE(_counters));
@@ -180,7 +197,12 @@ void Parallaction_br::initPart() {
_objectsNames = _disk->loadTable("objects");
_countersNames = _disk->loadTable("counters");
-// _disk->loadObjects("icone.ico");
+ // TODO: maybe handle this into Disk
+ if (getPlatform() == Common::kPlatformPC) {
+ _char._objs = _disk->loadObjects("icone.ico");
+ } else {
+ _char._objs = _disk->loadObjects("icons.ico");
+ }
}
@@ -195,11 +217,17 @@ void Parallaction_br::freePart() {
_countersNames = 0;
}
-void Parallaction_br::startPart() {
+void Parallaction_br::startPart(uint part) {
+ _part = part;
+ _disk->selectArchive(_partNames[_part]);
initPart();
- strcpy(_location._name, partFirstLocation[_part]);
+ if (getFeatures() & GF_DEMO) {
+ strcpy(_location._name, "camalb");
+ } else {
+ strcpy(_location._name, partFirstLocation[_part]);
+ }
parseLocation("common");
changeLocation(_location._name);
@@ -209,6 +237,8 @@ void Parallaction_br::startPart() {
void Parallaction_br::runPendingZones() {
ZonePtr z;
+ _cmdExec->runSuspended();
+
if (_activeZone) {
z = _activeZone; // speak Zone or sound
_activeZone = nullZonePtr;
@@ -236,7 +266,10 @@ void Parallaction_br::changeLocation(char *location) {
// free open location stuff
clearSubtitles();
freeBackground();
- _gfx->clearGfxObjects(kGfxObjNormal | kGfxObjCharacter);
+ _gfx->clearGfxObjects(kGfxObjNormal);
+ _gfx->freeLabels();
+ _subtitle[0] = _subtitle[1] = -1;
+
_location._programs.clear();
_location._animations.remove(_char._ani);
@@ -248,12 +281,17 @@ void Parallaction_br::changeLocation(char *location) {
// free(_location._comment);
// _location._comment = 0;
-// _location._commands.clear();
-// _location._aCommands.clear();
-
+ _location._commands.clear();
+ _location._aCommands.clear();
// load new location
parseLocation(location);
+
+ // kFlagsRemove is cleared because the character defaults to visible on new locations
+ // script command can hide the character, anyway, so that's why the flag is cleared
+ // before _location._commands are executed
+ _char._ani->_flags &= ~kFlagsRemove;
+
_cmdExec->run(_location._commands);
// doLocationEnterTransition();
_cmdExec->run(_location._aCommands);
@@ -305,30 +343,46 @@ void Parallaction_br::loadProgram(AnimationPtr a, const char *filename) {
void Parallaction_br::changeCharacter(const char *name) {
- printf("changeCharacter(%s)\n", name);
-
const char *charName = _char.getName();
- if (!scumm_stricmp(charName, name)) {
- return;
+
+ if (scumm_stricmp(charName, name)) {
+ debugC(1, kDebugExec, "changeCharacter(%s)", name);
+
+ _char.setName(name);
+ _char._ani->gfxobj = _gfx->loadAnim(name);
+ _char._ani->gfxobj->setFlags(kGfxObjCharacter);
+ _char._ani->gfxobj->clearFlags(kGfxObjNormal);
+ _char._talk = _disk->loadTalk(name);
}
- _char.setName(name);
- _char._ani->gfxobj = _gfx->loadAnim(name);
- _char._ani->gfxobj->setFlags(kGfxObjCharacter);
- _char._talk = _disk->loadTalk(name);
+ _char._ani->_flags |= kFlagsActive;
}
void Parallaction_br::setArrowCursor() {
+ // FIXME: Where are the Amiga cursors?
+ if (getPlatform() == Common::kPlatformAmiga)
+ return;
+ Common::Rect r;
+ _mouseArrow->getRect(0, r);
+ _system->setMouseCursor(_mouseArrow->getData(0), r.width(), r.height(), 0, 0, 0);
+ _system->showMouse(true);
+ _input->_activeItem._id = 0;
}
-void Parallaction_br::setInventoryCursor(int pos) {
-
+void Parallaction_br::setInventoryCursor(ItemName name) {
+ assert(name > 0);
+ byte *src = _mouseArrow->getData(0);
+ byte *dst = _comboArrow->getData(0);
+ memcpy(dst, src, _comboArrow->getSize(0));
+ // FIXME: destination offseting is not clear
+ _inventoryRenderer->drawItem(name, dst + _mouseComboProps_BR._yOffset * _mouseComboProps_BR._width + _mouseComboProps_BR._xOffset, _mouseComboProps_BR._width);
+ _system->setMouseCursor(dst, _mouseComboProps_BR._width, _mouseComboProps_BR._height, 0, 0, 0);
}
} // namespace Parallaction
diff --git a/engines/parallaction/parallaction_ns.cpp b/engines/parallaction/parallaction_ns.cpp
index 2f7c3dc572..32c4991a15 100644
--- a/engines/parallaction/parallaction_ns.cpp
+++ b/engines/parallaction/parallaction_ns.cpp
@@ -34,6 +34,7 @@
namespace Parallaction {
+
#define MOUSEARROW_WIDTH 16
#define MOUSEARROW_HEIGHT 16
@@ -165,7 +166,6 @@ Parallaction_ns::~Parallaction_ns() {
delete _locationParser;
delete _programParser;
- delete _mouseComposedArrow;
_location._animations.remove(_char._ani);
@@ -182,7 +182,7 @@ void Parallaction_ns::freeFonts() {
}
void Parallaction_ns::initCursors() {
- _mouseComposedArrow = _disk->loadPointer("pointer");
+ _comboArrow = _disk->loadPointer("pointer");
_mouseArrow = _resMouseArrow;
}
@@ -195,36 +195,16 @@ void Parallaction_ns::setArrowCursor() {
_input->_activeItem._id = 0;
_system->setMouseCursor(_mouseArrow, MOUSEARROW_WIDTH, MOUSEARROW_HEIGHT, 0, 0, 0);
- _system->showMouse(true);
-
}
-void Parallaction_ns::setInventoryCursor(int pos) {
-
- if (pos == -1)
- return;
-
- const InventoryItem *item = getInventoryItem(pos);
- if (item->_index == 0)
- return;
-
- _input->_activeItem._id = item->_id;
+void Parallaction_ns::setInventoryCursor(ItemName name) {
+ assert(name > 0);
- byte *v8 = _mouseComposedArrow->getData(0);
+ byte *v8 = _comboArrow->getData(0);
// FIXME: destination offseting is not clear
- byte* s = _char._objs->getData(item->_index);
- byte* d = v8 + 7 + MOUSECOMBO_WIDTH * 7;
-
- for (uint i = 0; i < INVENTORYITEM_HEIGHT; i++) {
- memcpy(d, s, INVENTORYITEM_WIDTH);
-
- s += INVENTORYITEM_PITCH;
- d += MOUSECOMBO_WIDTH;
- }
-
+ _inventoryRenderer->drawItem(name, v8 + 7 * MOUSECOMBO_WIDTH + 7, MOUSECOMBO_WIDTH);
_system->setMouseCursor(v8, MOUSECOMBO_WIDTH, MOUSECOMBO_HEIGHT, 0, 0, 0);
-
}
@@ -247,24 +227,19 @@ int Parallaction_ns::go() {
_gameToLoad = -1;
}
if (_gameToLoad == -1) {
- guiStart();
- } else {
+ startGui();
+ } else {
_disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
- _language = guiChooseLanguage();
- _disk->setLanguage(_language);
+
+ _menuHelper = new MenuInputHelper;
+ assert(_menuHelper);
+
+ new ChooseLanguageInputState_NS(this, _menuHelper);
doLoadGame(_gameToLoad);
}
+
+ startGui();
-
- if (quit())
- return _eventMan->shouldRTL();
-
- changeLocation(_location._name);
-
- if (quit())
- return _eventMan->shouldRTL();
-
- _input->_inputMode = Input::kInputModeGame;
while (!quit()) {
runGame();
}
@@ -296,8 +271,14 @@ void Parallaction_ns::switchBackground(const char* background, const char* mask)
}
-void Parallaction_ns::showSlide(const char *name) {
- _gfx->setBackground(kBackgroundSlide, name, 0, 0);
+void Parallaction_ns::showSlide(const char *name, int x, int y) {
+ BackgroundInfo *info = new BackgroundInfo;
+ _disk->loadSlide(*info, name);
+
+ info->x = (x == CENTER_LABEL_HORIZONTAL) ? ((_vm->_screenWidth - info->width) >> 1) : x;
+ info->y = (y == CENTER_LABEL_VERTICAL) ? ((_vm->_screenHeight - info->height) >> 1) : y;
+
+ _gfx->setBackground(kBackgroundSlide, info);
}
void Parallaction_ns::runPendingZones() {
@@ -314,6 +295,9 @@ void Parallaction_ns::runPendingZones() {
void Parallaction_ns::changeLocation(char *location) {
debugC(1, kDebugExec, "changeLocation(%s)", location);
+ MouseTriState oldMouseState = _input->getMouseState();
+ _input->setMouseState(MOUSE_DISABLED);
+
_soundMan->playLocationMusic(location);
_input->stopHovering();
@@ -321,9 +305,7 @@ void Parallaction_ns::changeLocation(char *location) {
_zoneTrap = nullZonePtr;
- if (_engineFlags & kEngineBlockInput) {
- setArrowCursor();
- }
+ setArrowCursor();
_gfx->showGfxObj(_char._ani->gfxobj, false);
_location._animations.remove(_char._ani);
@@ -337,7 +319,9 @@ void Parallaction_ns::changeLocation(char *location) {
showSlide(locname.slide());
uint id = _gfx->createLabel(_menuFont, _location._slideText[0], 1);
_gfx->showLabel(id, CENTER_LABEL_HORIZONTAL, 14);
- _input->waitUntilLeftClick();
+ _gfx->updateScreen();
+
+ _input->waitForButtonEvent(kMouseLeftUp);
_gfx->freeLabels();
freeBackground();
}
@@ -381,10 +365,9 @@ void Parallaction_ns::changeLocation(char *location) {
if (_location._hasSound)
_soundMan->playSfx(_location._soundFile, 0, true);
- debugC(1, kDebugExec, "changeLocation() done");
-
- return;
+ _input->setMouseState(oldMouseState);
+ debugC(1, kDebugExec, "changeLocation() done");
}
diff --git a/engines/parallaction/parser.cpp b/engines/parallaction/parser.cpp
index 6de0a7d7f5..97caa9dc6c 100644
--- a/engines/parallaction/parser.cpp
+++ b/engines/parallaction/parser.cpp
@@ -28,7 +28,10 @@
namespace Parallaction {
-char _tokens[20][MAX_TOKEN_LEN];
+#define MAX_TOKENS 50
+
+int _numTokens;
+char _tokens[MAX_TOKENS][MAX_TOKEN_LEN];
Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {}
@@ -65,9 +68,11 @@ char *Script::readLine(char *buf, size_t bufSize) {
void Script::clearTokens() {
- for (uint16 i = 0; i < 20; i++)
+ for (uint16 i = 0; i < MAX_TOKENS; i++)
_tokens[i][0] = '\0';
+ _numTokens = 0;
+
return;
}
@@ -151,12 +156,14 @@ char *parseNextToken(char *s, char *tok, uint16 count, const char *brk, bool ign
uint16 Script::fillTokens(char* line) {
uint16 i = 0;
- while (strlen(line) > 0 && i < 20) {
+ while (strlen(line) > 0 && i < MAX_TOKENS) {
line = parseNextToken(line, _tokens[i], MAX_TOKEN_LEN, " \t\n");
line = Common::ltrim(line);
i++;
}
+ _numTokens = i;
+
return i;
}
@@ -243,4 +250,185 @@ void Parser::parseStatement() {
}
+#define BLOCK_BASE 100
+
+class StatementDef {
+protected:
+ Common::String makeLineFromTokens() {
+ Common::String space(" ");
+ Common::String newLine("\n");
+ Common::String text;
+ for (int i = 0; i < _numTokens; i++)
+ text += (Common::String(_tokens[i]) + space);
+ text.deleteLastChar();
+ text += newLine;
+ return text;
+ }
+
+public:
+ uint _score;
+ const char* _name;
+
+
+ StatementDef(uint score, const char *name) : _score(score), _name(name) { }
+ virtual ~StatementDef() { }
+
+ virtual Common::String makeLine(Script &script) = 0;
+
+};
+
+
+class SimpleStatementDef : public StatementDef {
+
+public:
+ SimpleStatementDef(uint score, const char *name) : StatementDef(score, name) { }
+
+ Common::String makeLine(Script &script) {
+ return makeLineFromTokens();
+ }
+
+};
+
+
+
+class BlockStatementDef : public StatementDef {
+
+ const char* _ending1;
+ const char* _ending2;
+
+public:
+ BlockStatementDef(uint score, const char *name, const char *ending1, const char *ending2 = 0) : StatementDef(score, name), _ending1(ending1),
+ _ending2(ending2) { }
+
+ Common::String makeLine(Script &script) {
+ Common::String text = makeLineFromTokens();
+ bool end;
+ do {
+ script.readLineToken(true);
+ text += makeLineFromTokens();
+ end = !scumm_stricmp(_ending1, _tokens[0]) || (_ending2 && !scumm_stricmp(_ending2, _tokens[0]));
+ } while (!end);
+ return text;
+ }
+
+};
+
+class CommentStatementDef : public StatementDef {
+
+ Common::String parseComment(Script &script) {
+ Common::String result;
+ char buf[401];
+
+ do {
+ script.readLine(buf, 400);
+ buf[strlen(buf)-1] = '\0';
+ if (!scumm_stricmp(buf, "endtext"))
+ break;
+ result += Common::String(buf) + "\n";
+ } while (true);
+ result += "endtext\n";
+ return result;
+ }
+
+public:
+ CommentStatementDef(uint score, const char *name) : StatementDef(score, name) { }
+
+ Common::String makeLine(Script &script) {
+ Common::String text = makeLineFromTokens();
+ text += parseComment(script);
+ return text;
+ }
+
+};
+
+
+
+
+PreProcessor::PreProcessor() {
+ _defs.push_back(new SimpleStatementDef(1, "disk" ));
+ _defs.push_back(new SimpleStatementDef(2, "location" ));
+ _defs.push_back(new SimpleStatementDef(3, "localflags" ));
+ _defs.push_back(new SimpleStatementDef(4, "flags" ));
+ _defs.push_back(new SimpleStatementDef(5, "zeta" ));
+ _defs.push_back(new SimpleStatementDef(6, "music" ));
+ _defs.push_back(new SimpleStatementDef(7, "sound" ));
+ _defs.push_back(new SimpleStatementDef(8, "mask" ));
+ _defs.push_back(new SimpleStatementDef(9, "path" ));
+ _defs.push_back(new SimpleStatementDef(10, "character" ));
+ _defs.push_back(new CommentStatementDef(11, "comment" ));
+ _defs.push_back(new CommentStatementDef(12, "endcomment" ));
+ _defs.push_back(new BlockStatementDef(13, "ifchar", "endif" ));
+ _defs.push_back(new BlockStatementDef(BLOCK_BASE, "zone", "endanimation", "endzone" ));
+ _defs.push_back(new BlockStatementDef(BLOCK_BASE, "animation", "endanimation", "endzone" ));
+ _defs.push_back(new BlockStatementDef(1000, "commands", "endcommands" ));
+ _defs.push_back(new BlockStatementDef(1001, "acommands", "endcommands" ));
+ _defs.push_back(new BlockStatementDef(1002, "escape", "endcommands" ));
+ _defs.push_back(new SimpleStatementDef(2000, "endlocation"));
+}
+
+PreProcessor::~PreProcessor() {
+ DefList::iterator it = _defs.begin();
+ for (; it != _defs.end(); it++) {
+ delete *it;
+ }
+}
+
+StatementDef* PreProcessor::findDef(const char* name) {
+ DefList::iterator it = _defs.begin();
+ for (; it != _defs.end(); it++) {
+ if (!scumm_stricmp((*it)->_name, name)) {
+ return *it;
+ }
+ }
+ return 0;
+}
+
+
+
+uint PreProcessor::getDefScore(StatementDef* def) {
+ if (def->_score == BLOCK_BASE) {
+ _numZones++;
+ return (_numZones + BLOCK_BASE);
+ }
+ return def->_score;
+}
+
+
+void PreProcessor::preprocessScript(Script &script, StatementList &list) {
+ _numZones = 0;
+ Common::String text;
+ do {
+ script.readLineToken(false);
+ if (_numTokens == 0)
+ break;
+
+ StatementDef *def = findDef(_tokens[0]);
+ assert(def);
+
+ text = def->makeLine(script);
+ int score = getDefScore(def);
+ list.push_back(StatementListNode(score, def->_name, text));
+ } while (true);
+ Common::sort(list.begin(), list.end());
+}
+
+
+
+
+void testPreprocessing(Parallaction *vm, const char *filename) {
+ Script *script = vm->_disk->loadLocation(filename);
+ StatementList list;
+ PreProcessor pp;
+ pp.preprocessScript(*script, list);
+ delete script;
+ Common::DumpFile dump;
+ dump.open(filename);
+ StatementList::iterator it = list.begin();
+ for ( ; it != list.end(); it++) {
+ dump.write((*it)._text.c_str(), (*it)._text.size());
+ }
+ dump.close();
+}
+
+
} // namespace Parallaction
diff --git a/engines/parallaction/parser.h b/engines/parallaction/parser.h
index 1541fb89b2..7e7937fb19 100644
--- a/engines/parallaction/parser.h
+++ b/engines/parallaction/parser.h
@@ -35,6 +35,7 @@ namespace Parallaction {
char *parseNextToken(char *s, char *tok, uint16 count, const char *brk, bool ignoreQuotes = false);
#define MAX_TOKEN_LEN 50
+extern int _numTokens;
extern char _tokens[][MAX_TOKEN_LEN];
class Script {
@@ -63,6 +64,7 @@ typedef Common::Functor0<void> Opcode;
typedef Common::Array<const Opcode*> OpcodeSet;
+
class Parser {
public:
@@ -95,6 +97,7 @@ public:
class Parallaction_ns;
class Parallaction_br;
+
class LocationParser_ns {
protected:
@@ -128,6 +131,7 @@ protected:
// BRA specific
int numZones;
+ BackgroundInfo *info;
char *bgName;
char *maskName;
char *pathName;
@@ -171,7 +175,7 @@ protected:
DECLARE_UNQUALIFIED_COMMAND_PARSER(animation);
DECLARE_UNQUALIFIED_COMMAND_PARSER(zone);
DECLARE_UNQUALIFIED_COMMAND_PARSER(location);
- DECLARE_UNQUALIFIED_COMMAND_PARSER(drop);
+ DECLARE_UNQUALIFIED_COMMAND_PARSER(invObject);
DECLARE_UNQUALIFIED_COMMAND_PARSER(call);
DECLARE_UNQUALIFIED_COMMAND_PARSER(simple);
DECLARE_UNQUALIFIED_COMMAND_PARSER(move);
@@ -192,8 +196,8 @@ protected:
Question *parseQuestion();
void parseZone(ZoneList &list, char *name);
- void parseZoneTypeBlock(ZonePtr z);
- void parseWalkNodes(WalkNodeList &list);
+ virtual void parseZoneTypeBlock(ZonePtr z);
+ void parsePointList(PointList &list);
void parseAnimation(AnimationList &list, char *name);
void parseCommands(CommandList&);
void parseCommandFlags();
@@ -239,6 +243,23 @@ public:
};
+/*
+ TODO: adapt the parser to effectively use the
+ statement list provided by preprocessor as its
+ input, instead of relying on the current Script
+ class.
+
+ This would need a major rewrite of the parsing
+ system!
+
+ parseNextToken could then be sealed into the
+ PreProcessor class forever, together with the
+ _tokens[] and _numTokens stuff, now dangling as
+ global objects.
+
+ NS balloons code should be dealt with before,
+ though.
+*/
class LocationParser_br : public LocationParser_ns {
protected:
@@ -284,6 +305,9 @@ protected:
DECLARE_UNQUALIFIED_ANIM_PARSER(moveto);
DECLARE_UNQUALIFIED_ANIM_PARSER(endanimation);
+ virtual void parseZoneTypeBlock(ZonePtr z);
+ void parsePathData(ZonePtr z);
+
public:
LocationParser_br(Parallaction_br *vm) : LocationParser_ns((Parallaction_ns*)vm), _vm(vm) {
}
@@ -307,6 +331,7 @@ protected:
Parser *_parser;
Parallaction_ns *_vm;
+
Script *_script;
ProgramPtr _program;
@@ -397,6 +422,117 @@ public:
};
+
+/*
+ This simple stream is temporarily needed to hook the
+ preprocessor output to the parser. It will go away
+ when the parser is rewritten to fully exploit the
+ statement list provided by the preprocessor.
+*/
+
+class ReadStringStream : public Common::ReadStream {
+
+ char *_text;
+ uint32 _pos;
+ uint32 _size;
+
+public:
+ ReadStringStream(const Common::String &text) {
+ _text = new char[text.size() + 1];
+ strcpy(_text, text.c_str());
+ _size = text.size();
+ _pos = 0;
+ }
+
+ ~ReadStringStream() {
+ delete []_text;
+ }
+
+ uint32 read(void *buffer, uint32 size) {
+ if (_pos + size > _size) {
+ size = _size - _pos;
+ }
+ memcpy(buffer, _text + _pos, size);
+ _pos += size;
+ return size;
+ }
+
+ bool eos() const {
+ return _pos == _size;
+ }
+
+};
+
+
+/*
+ Demented as it may sound, the success of a parsing operation in the
+ original BRA depends on what has been parsed before. The game features
+ an innovative chaos system that involves the parser and the very game
+ engine, in order to inflict the user an unforgettable game experience.
+
+ Ok, now for the serious stuff.
+
+ The PreProcessor implemented here fixes the location scripts before
+ they are fed to the parser. It tries to do so by a preliminary scan
+ of the text file, during which a score is assigned to each statement
+ (more on this later). When the whole file has been analyzed, the
+ statements are sorted according to their score, to create a parsable
+ sequence.
+
+ For parsing, the statements in location scripts can be conveniently
+ divided into 3 groups:
+
+ * location definitions
+ * element definitions
+ * start-up commands
+
+ Since the parsing of element definitions requires location parameters
+ to be set, location definitions should be encountered first in the
+ script. Start-up commands in turn may reference elements, so they can
+ be parsed last. The first goal is to make sure the parser gets these
+ three sets in this order.
+
+ Location definitions must also be presented in a certain sequence,
+ because resource files are not fully self-describing. In short, some
+ critical game data in contained in certain files, that must obviously
+ be read before any other can be analyzed. This is the second goal.
+
+ TODO: some words about actual implementation.
+*/
+
+class StatementDef;
+
+struct StatementListNode {
+ int _score;
+ Common::String _name;
+ Common::String _text;
+
+ StatementListNode(int score, const Common::String &name, const Common::String &text) : _score(score), _name(name), _text(text) { }
+
+ bool operator<(const StatementListNode& node) const {
+ return _score < node._score;
+ }
+};
+typedef Common::List<StatementListNode> StatementList;
+
+
+class PreProcessor {
+ typedef Common::List<StatementDef*> DefList;
+
+ int _numZones;
+ DefList _defs;
+
+ StatementDef* findDef(const char* name);
+ uint getDefScore(StatementDef*);
+
+public:
+ PreProcessor();
+ ~PreProcessor();
+ void preprocessScript(Script &script, StatementList &list);
+};
+
+
+
} // namespace Parallaction
#endif
diff --git a/engines/parallaction/parser_br.cpp b/engines/parallaction/parser_br.cpp
index defc917a72..dee08485fb 100644
--- a/engines/parallaction/parser_br.cpp
+++ b/engines/parallaction/parser_br.cpp
@@ -25,6 +25,7 @@
#include "parallaction/parallaction.h"
+
#include "parallaction/sound.h"
namespace Parallaction {
@@ -104,6 +105,7 @@ namespace Parallaction {
#define INST_ENDIF 30
#define INST_STOP 31
+
const char *_zoneTypeNamesRes_br[] = {
"examine",
"door",
@@ -442,7 +444,7 @@ DECLARE_LOCATION_PARSER(redundant) {
DECLARE_LOCATION_PARSER(character) {
debugC(7, kDebugParser, "LOCATION_PARSER(character) ");
- ctxt.characterName = strdup(_tokens[0]);
+ ctxt.characterName = strdup(_tokens[1]);
}
@@ -464,9 +466,9 @@ DECLARE_LOCATION_PARSER(mask) {
debugC(7, kDebugParser, "LOCATION_PARSER(mask) ");
ctxt.maskName = strdup(_tokens[1]);
- _vm->_gfx->_backgroundInfo.layers[0] = atoi(_tokens[2]);
- _vm->_gfx->_backgroundInfo.layers[1] = atoi(_tokens[3]);
- _vm->_gfx->_backgroundInfo.layers[2] = atoi(_tokens[4]);
+ ctxt.info->layers[0] = atoi(_tokens[2]);
+ ctxt.info->layers[1] = atoi(_tokens[3]);
+ ctxt.info->layers[2] = atoi(_tokens[4]);
}
@@ -750,6 +752,67 @@ DECLARE_ZONE_PARSER(type) {
_parser->popTables();
}
+void LocationParser_br::parsePathData(ZonePtr z) {
+
+ PathData *data = new PathData;
+
+ do {
+
+ if (!scumm_stricmp("zone", _tokens[0])) {
+ int id = atoi(_tokens[1]);
+ parsePointList(data->_lists[id]);
+ data->_numLists++;
+ }
+
+ _script->readLineToken(true);
+ } while (scumm_stricmp("endzone", _tokens[0]));
+
+ z->u.path = data;
+}
+
+void LocationParser_br::parseZoneTypeBlock(ZonePtr z) {
+ debugC(7, kDebugParser, "parseZoneTypeBlock(name: %s, type: %x)", z->_name, z->_type);
+
+ switch (z->_type & 0xFFFF) {
+ case kZoneExamine: // examine Zone alloc
+ parseExamineData(z);
+ break;
+
+ case kZoneDoor: // door Zone alloc
+ parseDoorData(z);
+ break;
+
+ case kZoneGet: // get Zone alloc
+ parseGetData(z);
+ break;
+
+ case kZoneMerge: // merge Zone alloc
+ parseMergeData(z);
+ break;
+
+ case kZoneHear: // hear Zone alloc
+ parseHearData(z);
+ break;
+
+ case kZoneSpeak: // speak Zone alloc
+ parseSpeakData(z);
+ break;
+
+ // BRA specific zone
+ case kZonePath:
+ parsePathData(z);
+ break;
+
+ default:
+ // eats up 'ENDZONE' line for unprocessed zone types
+ _script->readLineToken(true);
+ break;
+ }
+
+ debugC(7, kDebugParser, "parseZoneTypeBlock() done");
+
+ return;
+}
DECLARE_ANIM_PARSER(file) {
debugC(7, kDebugParser, "ANIM_PARSER(file) ");
@@ -983,7 +1046,7 @@ void LocationParser_br::init() {
COMMAND_PARSER(zone); // off
COMMAND_PARSER(call);
COMMAND_PARSER(flags); // toggle
- COMMAND_PARSER(drop);
+ COMMAND_PARSER(invObject); // drop
COMMAND_PARSER(simple); // quit
COMMAND_PARSER(move);
COMMAND_PARSER(zone); // stop
@@ -991,7 +1054,7 @@ void LocationParser_br::init() {
COMMAND_PARSER(string); // followme
COMMAND_PARSER(simple); // onmouse
COMMAND_PARSER(simple); // offmouse
- COMMAND_PARSER(drop); // add
+ COMMAND_PARSER(invObject); // add
COMMAND_PARSER(zone); // leave
COMMAND_PARSER(math); // inc
COMMAND_PARSER(math); // dec
@@ -1107,18 +1170,40 @@ void ProgramParser_br::init() {
INSTRUCTION_PARSER(endscript);
}
+
+/*
+ Ancillary routine to support hooking preprocessor and
+ parser.
+*/
+Common::ReadStream *getStream(StatementList &list) {
+ Common::String text;
+ StatementList::iterator it = list.begin();
+ for ( ; it != list.end(); it++) {
+ text += (*it)._text;
+ }
+ return new ReadStringStream(text);
+}
+
void LocationParser_br::parse(Script *script) {
+ PreProcessor pp;
+ StatementList list;
+ pp.preprocessScript(*script, list);
+ Script *script2 = new Script(getStream(list), true);
+
ctxt.numZones = 0;
ctxt.bgName = 0;
ctxt.maskName = 0;
ctxt.pathName = 0;
ctxt.characterName = 0;
+ ctxt.info = new BackgroundInfo;
+
+ LocationParser_ns::parse(script2);
- LocationParser_ns::parse(script);
+ _vm->_disk->loadScenery(*ctxt.info, ctxt.bgName, ctxt.maskName, ctxt.pathName);
+ _vm->_gfx->setBackground(kBackgroundLocation, ctxt.info);
+ _vm->_pathBuffer = &ctxt.info->path;
- _vm->_gfx->setBackground(kBackgroundLocation, ctxt.bgName, ctxt.maskName, ctxt.pathName);
- _vm->_pathBuffer = &_vm->_gfx->_backgroundInfo.path;
if (ctxt.characterName) {
_vm->changeCharacter(ctxt.characterName);
@@ -1129,6 +1214,7 @@ void LocationParser_br::parse(Script *script) {
free(ctxt.pathName);
free(ctxt.characterName);
+ delete script2;
}
} // namespace Parallaction
diff --git a/engines/parallaction/parser_ns.cpp b/engines/parallaction/parser_ns.cpp
index 2f4d2df776..ad0f714fdc 100644
--- a/engines/parallaction/parser_ns.cpp
+++ b/engines/parallaction/parser_ns.cpp
@@ -299,6 +299,7 @@ void LocationParser_ns::parseAnimation(AnimationList &list, char *name) {
AnimationPtr a(new Animation);
strncpy(a->_name, name, ZONENAME_LENGTH);
+ a->_flags |= kFlagsIsAnimation;
list.push_front(AnimationPtr(a));
@@ -658,7 +659,7 @@ DECLARE_COMMAND_PARSER(location) {
}
-DECLARE_COMMAND_PARSER(drop) {
+DECLARE_COMMAND_PARSER(invObject) {
debugC(7, kDebugParser, "COMMAND_PARSER(drop) ");
createCommand(_parser->_lookup);
@@ -1011,7 +1012,7 @@ DECLARE_LOCATION_PARSER(disk) {
DECLARE_LOCATION_PARSER(nodes) {
debugC(7, kDebugParser, "LOCATION_PARSER(nodes) ");
- parseWalkNodes(_vm->_location._walkNodes);
+ parsePointList(_vm->_location._walkPoints);
}
@@ -1124,26 +1125,20 @@ void LocationParser_ns::parse(Script *script) {
resolveCommandForwards();
}
-void LocationParser_ns::parseWalkNodes(WalkNodeList &list) {
- debugC(5, kDebugParser, "parseWalkNodes()");
+void LocationParser_ns::parsePointList(PointList &list) {
+ debugC(5, kDebugParser, "parsePointList()");
_script->readLineToken(true);
while (scumm_stricmp(_tokens[0], "ENDNODES")) {
if (!scumm_stricmp(_tokens[0], "COORD")) {
-
- WalkNodePtr v4(new WalkNode(
- atoi(_tokens[1]),
- atoi(_tokens[2])
- ));
-
- list.push_front(v4);
+ list.push_front(Common::Point(atoi(_tokens[1]), atoi(_tokens[2])));
}
_script->readLineToken(true);
}
- debugC(5, kDebugParser, "parseWalkNodes() done");
+ debugC(5, kDebugParser, "parsePointList() done");
return;
}
@@ -1203,7 +1198,7 @@ void LocationParser_ns::init() {
COMMAND_PARSER(zone); // off
COMMAND_PARSER(call); // call
COMMAND_PARSER(flags); // toggle
- COMMAND_PARSER(drop); // drop
+ COMMAND_PARSER(invObject); // drop
COMMAND_PARSER(simple); // quit
COMMAND_PARSER(move); // move
COMMAND_PARSER(zone); // stop
@@ -1397,7 +1392,7 @@ void LocationParser_ns::parseZone(ZoneList &list, char *name) {
list.push_front(z);
_parser->pushTables(&_locationZoneParsers, _locationZoneStmt);
-
+
return;
}
diff --git a/engines/parallaction/walk.cpp b/engines/parallaction/walk.cpp
index a717b615e6..bf8f423fd5 100644
--- a/engines/parallaction/walk.cpp
+++ b/engines/parallaction/walk.cpp
@@ -28,26 +28,42 @@
namespace Parallaction {
+
+#define IS_PATH_CLEAR(x,y) _vm->_pathBuffer->getValue((x), (y))
+
inline byte PathBuffer::getValue(uint16 x, uint16 y) {
byte m = data[(x >> 3) + y * internalWidth];
- uint n = (_vm->getPlatform() == Common::kPlatformPC) ? (x & 7) : (7 - (x & 7));
- return ((1 << n) & m) >> n;
+ uint bit = 0;
+ switch (_vm->getGameType()) {
+ case GType_Nippon:
+ bit = (_vm->getPlatform() == Common::kPlatformPC) ? (x & 7) : (7 - (x & 7));
+ break;
+
+ case GType_BRA:
+ // Amiga and PC versions pack the path bits the same way in BRA
+ bit = 7 - (x & 7);
+ break;
+
+ default:
+ error("path mask not yet implemented for this game type");
+ }
+ return ((1 << bit) & m) >> bit;
}
// adjusts position towards nearest walkable point
//
-void PathBuilder::correctPathPoint(Common::Point &to) {
+void PathBuilder_NS::correctPathPoint(Common::Point &to) {
- if (_vm->_pathBuffer->getValue(to.x, to.y)) return;
+ if (IS_PATH_CLEAR(to.x, to.y)) return;
int16 right = to.x;
int16 left = to.x;
do {
right++;
- } while ((_vm->_pathBuffer->getValue(right, to.y) == 0) && (right < _vm->_pathBuffer->w));
+ } while (!IS_PATH_CLEAR(right, to.y) && (right < _vm->_pathBuffer->w));
do {
left--;
- } while ((_vm->_pathBuffer->getValue(left, to.y) == 0) && (left > 0));
+ } while (!IS_PATH_CLEAR(left, to.y) && (left > 0));
right = (right == _vm->_pathBuffer->w) ? 1000 : right - to.x;
left = (left == 0) ? 1000 : to.x - left;
@@ -56,10 +72,10 @@ void PathBuilder::correctPathPoint(Common::Point &to) {
int16 bottom = to.y;
do {
top--;
- } while ((_vm->_pathBuffer->getValue(to.x, top) == 0) && (top > 0));
+ } while (!IS_PATH_CLEAR(to.x, top) && (top > 0));
do {
bottom++;
- } while ((_vm->_pathBuffer->getValue(to.x, bottom) == 0) && (bottom < _vm->_pathBuffer->h));
+ } while (!IS_PATH_CLEAR(to.x, bottom) && (bottom < _vm->_pathBuffer->h));
top = (top == 0) ? 1000 : to.y - top;
bottom = (bottom == _vm->_pathBuffer->h) ? 1000 : bottom - to.y;
@@ -84,7 +100,7 @@ void PathBuilder::correctPathPoint(Common::Point &to) {
}
-uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point& stop) {
+uint32 PathBuilder_NS::buildSubPath(const Common::Point& pos, const Common::Point& stop) {
uint32 v28 = 0;
uint32 v2C = 0;
@@ -97,16 +113,15 @@ uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point&
while (true) {
- WalkNodeList::iterator nearest = _vm->_location._walkNodes.end();
- WalkNodeList::iterator locNode = _vm->_location._walkNodes.begin();
+ PointList::iterator nearest = _vm->_location._walkPoints.end();
+ PointList::iterator locNode = _vm->_location._walkPoints.begin();
// scans location path nodes searching for the nearest Node
// which can't be farther than the target position
// otherwise no _closest_node is selected
- while (locNode != _vm->_location._walkNodes.end()) {
+ while (locNode != _vm->_location._walkPoints.end()) {
- Common::Point v8;
- (*locNode)->getPoint(v8);
+ Common::Point v8 = *locNode;
v2C = v8.sqrDist(stop);
v28 = v8.sqrDist(v20);
@@ -118,80 +133,59 @@ uint32 PathBuilder::buildSubPath(const Common::Point& pos, const Common::Point&
locNode++;
}
- if (nearest == _vm->_location._walkNodes.end()) break;
+ if (nearest == _vm->_location._walkPoints.end()) break;
- (*nearest)->getPoint(v20);
+ v20 = *nearest;
v34 = v30 = v20.sqrDist(stop);
- _subPath.push_back(WalkNodePtr(new WalkNode(**nearest)));
+ _subPath.push_back(*nearest);
}
return v34;
}
-#if 0
-void printNodes(WalkNodeList *list, const char* text) {
- printf("%s\n-------------------\n", text);
- for (WalkNodeList::iterator it = list->begin(); it != list->end(); it++)
- printf("node [%p] (%i, %i)\n", *it, (*it)->_x, (*it)->_y);
- return;
-}
-#endif
//
// x, y: mouse click (foot) coordinates
//
-WalkNodeList *PathBuilder::buildPath(uint16 x, uint16 y) {
+void PathBuilder_NS::buildPath(uint16 x, uint16 y) {
debugC(1, kDebugWalk, "PathBuilder::buildPath to (%i, %i)", x, y);
+ _ch->_walkPath.clear();
+
Common::Point to(x, y);
correctPathPoint(to);
debugC(1, kDebugWalk, "found closest path point at (%i, %i)", to.x, to.y);
- WalkNodePtr v48(new WalkNode(to.x, to.y));
- WalkNodePtr v44 = v48;
+ Common::Point v48(to);
+ Common::Point v44(to);
- uint16 v38 = walkFunc1(to.x, to.y, v44);
+ uint16 v38 = walkFunc1(to, v44);
if (v38 == 1) {
// destination directly reachable
debugC(1, kDebugWalk, "direct move to (%i, %i)", to.x, to.y);
-
- _list = new WalkNodeList;
- _list->push_back(v48);
- return _list;
+ _ch->_walkPath.push_back(v48);
+ return;
}
// path is obstructed: look for alternative
- _list = new WalkNodeList;
- _list->push_back(v48);
-#if 0
- printNodes(_list, "start");
-#endif
-
- Common::Point stop(v48->_x, v48->_y);
+ _ch->_walkPath.push_back(v48);
Common::Point pos;
- _vm->_char.getFoot(pos);
+ _ch->getFoot(pos);
- uint32 v34 = buildSubPath(pos, stop);
+ uint32 v34 = buildSubPath(pos, v48);
if (v38 != 0 && v34 > v38) {
// no alternative path (gap?)
- _list->clear();
- _list->push_back(v44);
- return _list;
+ _ch->_walkPath.clear();
+ _ch->_walkPath.push_back(v44);
+ return;
}
- _list->insert(_list->begin(), _subPath.begin(), _subPath.end());
-#if 0
- printNodes(_list, "first segment");
-#endif
+ _ch->_walkPath.insert(_ch->_walkPath.begin(), _subPath.begin(), _subPath.end());
- (*_list->begin())->getPoint(stop);
- buildSubPath(pos, stop);
- _list->insert(_list->begin(), _subPath.begin(), _subPath.end());
-#if 0
- printNodes(_list, "complete");
-#endif
+ buildSubPath(pos, *_ch->_walkPath.begin());
+ _ch->_walkPath.insert(_ch->_walkPath.begin(), _subPath.begin(), _subPath.end());
- return _list;
+ return;
}
@@ -202,23 +196,23 @@ WalkNodeList *PathBuilder::buildPath(uint16 x, uint16 y) {
// 1 : Point reachable in a straight line
// other values: square distance to target (point not reachable in a straight line)
//
-uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
+uint16 PathBuilder_NS::walkFunc1(const Common::Point &to, Common::Point& node) {
- Common::Point arg(x, y);
+ Common::Point arg(to);
- Common::Point v4(0, 0);
+ Common::Point v4;
Common::Point foot;
- _vm->_char.getFoot(foot);
+ _ch->getFoot(foot);
Common::Point v8(foot);
while (foot != arg) {
- if (foot.x < x && _vm->_pathBuffer->getValue(foot.x + 1, foot.y) != 0) foot.x++;
- if (foot.x > x && _vm->_pathBuffer->getValue(foot.x - 1, foot.y) != 0) foot.x--;
- if (foot.y < y && _vm->_pathBuffer->getValue(foot.x, foot.y + 1) != 0) foot.y++;
- if (foot.y > y && _vm->_pathBuffer->getValue(foot.x, foot.y - 1) != 0) foot.y--;
+ if (foot.x < to.x && IS_PATH_CLEAR(foot.x + 1, foot.y)) foot.x++;
+ if (foot.x > to.x && IS_PATH_CLEAR(foot.x - 1, foot.y)) foot.x--;
+ if (foot.y < to.y && IS_PATH_CLEAR(foot.x, foot.y + 1)) foot.y++;
+ if (foot.y > to.y && IS_PATH_CLEAR(foot.x, foot.y - 1)) foot.y--;
if (foot == v8 && foot != arg) {
@@ -228,10 +222,10 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
while (foot != arg) {
- if (foot.x < x && _vm->_pathBuffer->getValue(foot.x + 1, foot.y) == 0) foot.x++;
- if (foot.x > x && _vm->_pathBuffer->getValue(foot.x - 1, foot.y) == 0) foot.x--;
- if (foot.y < y && _vm->_pathBuffer->getValue(foot.x, foot.y + 1) == 0) foot.y++;
- if (foot.y > y && _vm->_pathBuffer->getValue(foot.x, foot.y - 1) == 0) foot.y--;
+ if (foot.x < to.x && !IS_PATH_CLEAR(foot.x + 1, foot.y)) foot.x++;
+ if (foot.x > to.x && !IS_PATH_CLEAR(foot.x - 1, foot.y)) foot.x--;
+ if (foot.y < to.y && !IS_PATH_CLEAR(foot.x, foot.y + 1)) foot.y++;
+ if (foot.y > to.y && !IS_PATH_CLEAR(foot.x, foot.y - 1)) foot.y--;
if (foot == v8 && foot != arg)
return 0;
@@ -239,10 +233,8 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
v8 = foot;
}
- Node->_x = v4.x;
- Node->_y = v4.y;
-
- return (x - v4.x) * (x - v4.x) + (y - v4.y) * (y - v4.y);
+ node = v4;
+ return v4.sqrDist(to);
}
v8 = foot;
@@ -253,21 +245,21 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
return 1;
}
-void Parallaction::clipMove(Common::Point& pos, const Common::Point& to) {
+void PathWalker_NS::clipMove(Common::Point& pos, const Common::Point& to) {
- if ((pos.x < to.x) && (pos.x < _pathBuffer->w) && (_pathBuffer->getValue(pos.x + 2, pos.y) != 0)) {
+ if ((pos.x < to.x) && (pos.x < _vm->_pathBuffer->w) && IS_PATH_CLEAR(pos.x + 2, pos.y)) {
pos.x = (pos.x + 2 < to.x) ? pos.x + 2 : to.x;
}
- if ((pos.x > to.x) && (pos.x > 0) && (_pathBuffer->getValue(pos.x - 2, pos.y) != 0)) {
+ if ((pos.x > to.x) && (pos.x > 0) && IS_PATH_CLEAR(pos.x - 2, pos.y)) {
pos.x = (pos.x - 2 > to.x) ? pos.x - 2 : to.x;
}
- if ((pos.y < to.y) && (pos.y < _pathBuffer->h) && (_pathBuffer->getValue(pos.x, pos.y + 2) != 0)) {
+ if ((pos.y < to.y) && (pos.y < _vm->_pathBuffer->h) && IS_PATH_CLEAR(pos.x, pos.y + 2)) {
pos.y = (pos.y + 2 <= to.y) ? pos.y + 2 : to.y;
}
- if ((pos.y > to.y) && (pos.y > 0) && (_pathBuffer->getValue(pos.x, pos.y - 2) != 0)) {
+ if ((pos.y > to.y) && (pos.y > 0) && IS_PATH_CLEAR(pos.x, pos.y - 2)) {
pos.y = (pos.y - 2 >= to.y) ? pos.y - 2 : to.y;
}
@@ -275,85 +267,81 @@ void Parallaction::clipMove(Common::Point& pos, const Common::Point& to) {
}
-void Parallaction::checkDoor(const Common::Point &foot) {
+void PathWalker_NS::checkDoor(const Common::Point &foot) {
- ZonePtr z = hitZone(kZoneDoor, foot.x, foot.y);
+ ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y);
if (z) {
if ((z->_flags & kFlagsClosed) == 0) {
- _location._startPosition = z->u.door->_startPos;
- _location._startFrame = z->u.door->_startFrame;
- scheduleLocationSwitch(z->u.door->_location);
- _zoneTrap = nullZonePtr;
+ _vm->_location._startPosition = z->u.door->_startPos;
+ _vm->_location._startFrame = z->u.door->_startFrame;
+ _vm->scheduleLocationSwitch(z->u.door->_location);
+ _vm->_zoneTrap = nullZonePtr;
} else {
- _cmdExec->run(z->_commands, z);
+ _vm->_cmdExec->run(z->_commands, z);
}
}
- z = hitZone(kZoneTrap, foot.x, foot.y);
+ z = _vm->hitZone(kZoneTrap, foot.x, foot.y);
if (z) {
- setLocationFlags(kFlagsEnter);
- _cmdExec->run(z->_commands, z);
- clearLocationFlags(kFlagsEnter);
- _zoneTrap = z;
+ _vm->setLocationFlags(kFlagsEnter);
+ _vm->_cmdExec->run(z->_commands, z);
+ _vm->clearLocationFlags(kFlagsEnter);
+ _vm->_zoneTrap = z;
} else
- if (_zoneTrap) {
- setLocationFlags(kFlagsExit);
- _cmdExec->run(_zoneTrap->_commands, _zoneTrap);
- clearLocationFlags(kFlagsExit);
- _zoneTrap = nullZonePtr;
+ if (_vm->_zoneTrap) {
+ _vm->setLocationFlags(kFlagsExit);
+ _vm->_cmdExec->run(_vm->_zoneTrap->_commands, _vm->_zoneTrap);
+ _vm->clearLocationFlags(kFlagsExit);
+ _vm->_zoneTrap = nullZonePtr;
}
}
-void Parallaction::finalizeWalk(Character &character) {
+void PathWalker_NS::finalizeWalk() {
_engineFlags &= ~kEngineWalking;
Common::Point foot;
- character.getFoot(foot);
+ _ch->getFoot(foot);
checkDoor(foot);
- delete character._walkPath;
- character._walkPath = 0;
+ _ch->_walkPath.clear();
}
-void Parallaction_ns::walk(Character &character) {
+void PathWalker_NS::walk() {
if ((_engineFlags & kEngineWalking) == 0) {
return;
}
Common::Point curPos;
- character.getFoot(curPos);
+ _ch->getFoot(curPos);
// update target, if previous was reached
- WalkNodeList::iterator it = character._walkPath->begin();
- if (it != character._walkPath->end()) {
- if ((*it)->_x == curPos.x && (*it)->_y == curPos.y) {
- debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it)->_x, (*it)->_y);
- it = character._walkPath->erase(it);
+ PointList::iterator it = _ch->_walkPath.begin();
+ if (it != _ch->_walkPath.end()) {
+ if (*it == curPos) {
+ debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it).x, (*it).y);
+ it = _ch->_walkPath.erase(it);
}
}
// advance character towards the target
Common::Point targetPos;
- if (it == character._walkPath->end()) {
+ if (it == _ch->_walkPath.end()) {
debugC(1, kDebugWalk, "walk reached last node");
- finalizeWalk(character);
+ finalizeWalk();
targetPos = curPos;
} else {
- if (*it) {
- // targetPos is saved to help setting character direction
- targetPos.x = (*it)->_x;
- targetPos.y = (*it)->_y;
- }
+ // targetPos is saved to help setting character direction
+ targetPos = *it;
Common::Point newPos(curPos);
clipMove(newPos, targetPos);
- character.setFoot(newPos);
+ _ch->setFoot(newPos);
if (newPos == curPos) {
debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle");
- finalizeWalk(character);
+ finalizeWalk();
targetPos = newPos; // when walking is interrupted, targetPos must be hacked so that a still frame can be selected
}
}
@@ -364,25 +352,283 @@ void Parallaction_ns::walk(Character &character) {
// from curPos to newPos is prone to abrutply change in direction, thus making the
// code select 'too different' frames when walking diagonally against obstacles,
// and yielding an annoying shaking effect in the character.
- character.updateDirection(curPos, targetPos);
+ _ch->updateDirection(curPos, targetPos);
}
-WalkNode::WalkNode() : _x(0), _y(0) {
+
+PathBuilder_NS::PathBuilder_NS(Character *ch) : PathBuilder(ch), _list(0) {
}
-WalkNode::WalkNode(int16 x, int16 y) : _x(x), _y(y) {
+
+bool PathBuilder_BR::directPathExists(const Common::Point &from, const Common::Point &to) {
+
+ Common::Point copy(from);
+ Common::Point p(copy);
+
+ while (p != to) {
+
+ if (p.x < to.x && IS_PATH_CLEAR(p.x + 1, p.y)) p.x++;
+ if (p.x > to.x && IS_PATH_CLEAR(p.x - 1, p.y)) p.x--;
+ if (p.y < to.y && IS_PATH_CLEAR(p.x, p.y + 1)) p.y++;
+ if (p.y > to.y && IS_PATH_CLEAR(p.x, p.y - 1)) p.y--;
+
+ if (p == copy && p != to) {
+ return false;
+ }
+
+ copy = p;
+ }
+
+ return true;
}
-WalkNode::WalkNode(const WalkNode& w) : _x(w._x), _y(w._y) {
+void PathBuilder_BR::buildPath(uint16 x, uint16 y) {
+ Common::Point foot;
+ _ch->getFoot(foot);
+
+ debugC(1, kDebugWalk, "buildPath: from (%i, %i) to (%i, %i)", foot.x, foot.y, x, y);
+ _ch->_walkPath.clear();
+
+ // look for easy path first
+ Common::Point dest(x, y);
+ if (directPathExists(foot, dest)) {
+ _ch->_walkPath.push_back(dest);
+ debugC(3, kDebugWalk, "buildPath: direct path found");
+ return;
+ }
+
+ // look for short circuit cases
+ ZonePtr z0 = _vm->hitZone(kZonePath, x, y);
+ if (!z0) {
+ _ch->_walkPath.push_back(dest);
+ debugC(3, kDebugWalk, "buildPath: corner case 0");
+ return;
+ }
+ ZonePtr z1 = _vm->hitZone(kZonePath, foot.x, foot.y);
+ if (!z1 || z1 == z0) {
+ _ch->_walkPath.push_back(dest);
+ debugC(3, kDebugWalk, "buildPath: corner case 1");
+ return;
+ }
+
+ // build complex path
+ int id = atoi(z0->_name);
+
+ if (z1->u.path->_lists[id].empty()) {
+ _ch->_walkPath.clear();
+ debugC(3, kDebugWalk, "buildPath: no path");
+ return;
+ }
+
+ PointList::iterator b = z1->u.path->_lists[id].begin();
+ PointList::iterator e = z1->u.path->_lists[id].end();
+ for ( ; b != e; b++) {
+ _ch->_walkPath.push_front(*b);
+ }
+ _ch->_walkPath.push_back(dest);
+ debugC(3, kDebugWalk, "buildPath: complex path");
+
+ return;
+}
+
+PathBuilder_BR::PathBuilder_BR(Character *ch) : PathBuilder(ch) {
+}
+
+void PathWalker_BR::finalizeWalk() {
+ _engineFlags &= ~kEngineWalking;
+ _first = true;
+ _fieldC = 1;
+
+ Common::Point foot;
+ _ch->getFoot(foot);
+
+ ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y);
+ if (z && ((z->_flags & kFlagsClosed) == 0)) {
+ _vm->_location._startPosition = z->u.door->_startPos; // foot pos
+ _vm->_location._startFrame = z->u.door->_startFrame;
+
+#if 0
+ // TODO: implement working follower. Must find out a location in which the code is
+ // used and which is stable enough.
+ _followerFootInit.x = -1;
+ if (_follower && z->u.door->startPos2.x != -1) {
+ _followerFootInit.x = z->u.door->startPos2.x; // foot pos
+ _followerFootInit.y = z->u.door->startPos2.y; // foot pos
+ }
+ _followerFootInit.z = -1;
+ if (_follower && z->u.door->startPos2.z != -1) {
+ _followerFootInit.z = z->u.door->startPos2.z; // foot pos
+ }
+#endif
+
+ _vm->scheduleLocationSwitch(z->u.door->_location);
+ _vm->_cmdExec->run(z->_commands, z);
+ }
+
+#if 0
+ // TODO: Input::walkTo must be extended to support destination frame in addition to coordinates
+ // TODO: the frame argument must be passed to PathWalker through PathBuilder, so probably
+ // a merge between the two Path managers is the right solution
+ if (_engineFlags & FINAL_WALK_FRAME) { // this flag is set in readInput()
+ _engineFlags &= ~FINAL_WALK_FRAME;
+ _char.ani->_frame = _moveToF; // from readInput()...
+ } else {
+ _char.ani->_frame = _dirFrame; // from walk()
+ }
+ _char.setFoot(foot);
+#endif
+
+ _ch->_ani->_frame = _dirFrame; // temporary solution
+
+#if 0
+ // TODO: support scrolling ;)
+ if (foot.x > _gfx->hscroll + 600) _gfx->scrollRight(78);
+ if (foot.x < _gfx->hscroll + 40) _gfx->scrollLeft(78);
+ if (foot.y > 350) _gfx->scrollDown(100);
+ if (foot.y < 80) _gfx->scrollUp(100);
+#endif
+
+ return;
}
-void WalkNode::getPoint(Common::Point &p) const {
- p.x = _x;
- p.y = _y;
+
+void PathWalker_BR::walk() {
+ if ((_engineFlags & kEngineWalking) == 0) {
+ return;
+ }
+
+#if 0
+ // TODO: support delays in walking. This requires extending Input::walkIo().
+ if (ch._walkDelay > 0) {
+ ch._walkDelay--;
+ if (ch._walkDelay == 0 && _ch._ani->_scriptName) {
+ // stop script and reset
+ _ch._ani->_flags &= ~kFlagsActing;
+ Script *script = findScript(_ch._ani->_scriptName);
+ script->_nextCommand = script->firstCommand;
+ }
+ return;
+ }
+#endif
+
+ GfxObj *obj = _ch->_ani->gfxobj;
+
+ Common::Rect rect;
+ obj->getRect(_ch->_ani->_frame, rect);
+
+ uint scale;
+ if (rect.bottom > _vm->_location._zeta0) {
+ scale = 100;
+ } else
+ if (rect.bottom < _vm->_location._zeta1) {
+ scale = _vm->_location._zeta2;
+ } else {
+ scale = _vm->_location._zeta2 + ((rect.bottom - _vm->_location._zeta1) * (100 - _vm->_location._zeta2)) / (_vm->_location._zeta0 - _vm->_location._zeta1);
+ }
+ int xStep = (scale * 16) / 100 + 1;
+ int yStep = (scale * 10) / 100 + 1;
+
+ debugC(9, kDebugWalk, "calculated step: (%i, %i)\n", xStep, yStep);
+
+ if (_fieldC == 0) {
+ _ch->_walkPath.erase(_ch->_walkPath.begin());
+
+ if (_ch->_walkPath.empty()) {
+ finalizeWalk();
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, case 0\n");
+ return;
+ } else {
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, moving to next node\n");
+ }
+ }
+
+ _ch->getFoot(_startFoot);
+
+ _fieldC = 0;
+ _step++;
+ _step %= 8;
+
+ int walkFrame = _step;
+ _dirFrame = 0;
+ Common::Point newpos(_startFoot), delta;
+
+ Common::Point p(*_ch->_walkPath.begin());
+
+ if (_startFoot.y < p.y && _startFoot.y < 400 && IS_PATH_CLEAR(_startFoot.x, yStep + _startFoot.y)) {
+ if (yStep + _startFoot.y <= p.y) {
+ _fieldC = 1;
+ delta.y = yStep;
+ newpos.y = yStep + _startFoot.y;
+ } else {
+ delta.y = p.y - _startFoot.y;
+ newpos.y = p.y;
+ }
+ _dirFrame = 9;
+ } else
+ if (_startFoot.y > p.y && _startFoot.y > 0 && IS_PATH_CLEAR(_startFoot.x, _startFoot.y - yStep)) {
+ if (_startFoot.y - yStep >= p.y) {
+ _fieldC = 1;
+ delta.y = yStep;
+ newpos.y = _startFoot.y - yStep;
+ } else {
+ delta.y = _startFoot.y - p.y;
+ newpos.y = p.y;
+ }
+ _dirFrame = 0;
+ }
+
+ if (_startFoot.x < p.x && _startFoot.x < 640 && IS_PATH_CLEAR(_startFoot.x + xStep, _startFoot.y)) {
+ if (_startFoot.x + xStep <= p.x) {
+ _fieldC = 1;
+ delta.x = xStep;
+ newpos.x = xStep + _startFoot.x;
+ } else {
+ delta.x = p.x - _startFoot.x;
+ newpos.x = p.x;
+ }
+ if (delta.y < delta.x) {
+ _dirFrame = 18; // right
+ }
+ } else
+ if (_startFoot.x > p.x && _startFoot.x > 0 && IS_PATH_CLEAR(_startFoot.x - xStep, _startFoot.y)) {
+ if (_startFoot.x - xStep >= p.x) {
+ _fieldC = 1;
+ delta.x = xStep;
+ newpos.x = _startFoot.x - xStep;
+ } else {
+ delta.x = _startFoot.x - p.x;
+ newpos.x = p.x;
+ }
+ if (delta.y < delta.x) {
+ _dirFrame = 27; // left
+ }
+ }
+
+ debugC(9, kDebugWalk, "foot (%i, %i) dest (%i, %i) deltas = %i/%i \n", _startFoot.x, _startFoot.y, p.x, p.y, delta.x, delta.y);
+
+ if (_fieldC) {
+ debugC(9, kDebugWalk, "PathWalker_BR::walk, foot moved from (%i, %i) to (%i, %i)\n", _startFoot.x, _startFoot.y, newpos.x, newpos.y);
+ _ch->_ani->_frame = walkFrame + _dirFrame + 1;
+ _startFoot.x = newpos.x;
+ _startFoot.y = newpos.y;
+ _ch->setFoot(_startFoot);
+ _ch->_ani->_z = newpos.y;
+ }
+
+ if (_fieldC || !_ch->_walkPath.empty()) {
+// checkTrap();
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, case 1\n");
+ return;
+ }
+
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, case 2\n");
+ finalizeWalk();
+ return;
}
-PathBuilder::PathBuilder(AnimationPtr anim) : _anim(anim), _list(0) {
+PathWalker_BR::PathWalker_BR(Character *ch) : PathWalker(ch), _fieldC(1), _first(true) {
+
}
diff --git a/engines/parallaction/walk.h b/engines/parallaction/walk.h
index 788a6e1375..8d21e5ebbd 100644
--- a/engines/parallaction/walk.h
+++ b/engines/parallaction/walk.h
@@ -29,43 +29,89 @@
#include "common/ptr.h"
#include "common/list.h"
+#include "parallaction/objects.h"
+
+
namespace Parallaction {
-struct Animation;
+struct Character;
+
+class PathBuilder {
-struct WalkNode {
- int16 _x;
- int16 _y;
+protected:
+ Character *_ch;
public:
- WalkNode();
- WalkNode(int16 x, int16 y);
- WalkNode(const WalkNode& w);
+ PathBuilder(Character *ch) : _ch(ch) { }
+ virtual ~PathBuilder() { }
- void getPoint(Common::Point &p) const;
+ virtual void buildPath(uint16 x, uint16 y) = 0;
};
-typedef Common::SharedPtr<WalkNode> WalkNodePtr;
-typedef Common::List<WalkNodePtr> WalkNodeList;
+class PathBuilder_NS : public PathBuilder {
-class PathBuilder {
-
- AnimationPtr _anim;
-
- WalkNodeList *_list;
- WalkNodeList _subPath;
+ PointList *_list;
+ PointList _subPath;
void correctPathPoint(Common::Point &to);
uint32 buildSubPath(const Common::Point& pos, const Common::Point& stop);
- uint16 walkFunc1(int16 x, int16 y, WalkNodePtr Node);
+ uint16 walkFunc1(const Common::Point &to, Common::Point& node);
public:
- PathBuilder(AnimationPtr anim);
- WalkNodeList* buildPath(uint16 x, uint16 y);
+ PathBuilder_NS(Character *ch);
+ void buildPath(uint16 x, uint16 y);
+};
+
+class PathBuilder_BR : public PathBuilder {
+
+ bool directPathExists(const Common::Point &from, const Common::Point &to);
+
+public:
+ PathBuilder_BR(Character *ch);
+ void buildPath(uint16 x, uint16 y);
+};
+
+class PathWalker {
+protected:
+ Character *_ch;
+public:
+ PathWalker(Character *ch) : _ch(ch) { }
+ virtual ~PathWalker() { }
+ virtual void walk() = 0;
};
+class PathWalker_NS : public PathWalker {
+
+
+ void finalizeWalk();
+ void clipMove(Common::Point& pos, const Common::Point& to);
+ void checkDoor(const Common::Point &foot);
+
+public:
+ PathWalker_NS(Character *ch) : PathWalker(ch) { }
+ void walk();
+};
+
+
+class PathWalker_BR : public PathWalker {
+
+
+ int _walkDelay;
+ int _fieldC;
+ Common::Point _startFoot;
+ bool _first;
+ int _step;
+
+ int _dirFrame;
+
+ void finalizeWalk();
+
+public:
+ PathWalker_BR(Character *ch);
+ void walk();
+};
}
diff --git a/engines/queen/queen.h b/engines/queen/queen.h
index 5ec18c1230..66931e037d 100644
--- a/engines/queen/queen.h
+++ b/engines/queen/queen.h
@@ -29,7 +29,7 @@
#include "engines/engine.h"
namespace Common {
- class InSaveFile;
+ class SeekableReadStream;
}
#if defined(_WIN32_WCE) && (_WIN32_WCE <= 300)
@@ -112,7 +112,7 @@ public:
void makeGameStateName(int slot, char *buf) const;
int getGameStateSlot(const char *filename) const;
void findGameStateDescriptions(char descriptions[100][32]);
- Common::InSaveFile *readGameStateHeader(int slot, GameStateHeader *gsh);
+ Common::SeekableReadStream *readGameStateHeader(int slot, GameStateHeader *gsh);
enum {
SAVESTATE_CUR_VER = 1,
diff --git a/engines/queen/sound.cpp b/engines/queen/sound.cpp
index 9cdd971857..6513a92018 100644
--- a/engines/queen/sound.cpp
+++ b/engines/queen/sound.cpp
@@ -35,6 +35,7 @@
#include "queen/queen.h"
#include "queen/resource.h"
+#include "sound/audiostream.h"
#include "sound/flac.h"
#include "sound/mididrv.h"
#include "sound/mp3.h"
@@ -45,6 +46,42 @@
namespace Queen {
+// The sounds in the PC versions are all played at 11840 Hz. Unfortunately, we
+// did not know that at the time, so there are plenty of compressed versions
+// which claim that they should be played at 11025 Hz. This "wrapper" class
+// works around that.
+
+class AudioStreamWrapper : public Audio::AudioStream {
+protected:
+ Audio::AudioStream *_stream;
+
+public:
+ AudioStreamWrapper(Audio::AudioStream *stream) {
+ _stream = stream;
+ }
+ ~AudioStreamWrapper() {
+ delete _stream;
+ }
+ int readBuffer(int16 *buffer, const int numSamples) {
+ return _stream->readBuffer(buffer, numSamples);
+ }
+ bool isStereo() const {
+ return _stream->isStereo();
+ }
+ bool endOfData() const {
+ return _stream->endOfData();
+ }
+ bool endOfStream() {
+ return _stream->endOfStream();
+ }
+ int getRate() const {
+ return 11840;
+ }
+ int32 getTotalPlayTime() {
+ return _stream->getTotalPlayTime();
+ }
+};
+
class SilentSound : public PCSound {
public:
SilentSound(Audio::Mixer *mixer, QueenEngine *vm) : PCSound(mixer, vm) {}
@@ -69,7 +106,7 @@ protected:
void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {
Common::MemoryReadStream *tmp = f->readStream(size);
assert(tmp);
- _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeMP3Stream(tmp, true));
+ _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeMP3Stream(tmp, true)));
}
};
#endif
@@ -82,7 +119,7 @@ protected:
void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {
Common::MemoryReadStream *tmp = f->readStream(size);
assert(tmp);
- _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeVorbisStream(tmp, true));
+ _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeVorbisStream(tmp, true)));
}
};
#endif
@@ -95,7 +132,7 @@ protected:
void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {
Common::MemoryReadStream *tmp = f->readStream(size);
assert(tmp);
- _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, Audio::makeFlacStream(tmp, true));
+ _mixer->playInputStream(Audio::Mixer::kSFXSoundType, soundHandle, new AudioStreamWrapper(Audio::makeFlacStream(tmp, true)));
}
};
#endif // #ifdef USE_FLAC
diff --git a/engines/saga/displayinfo.h b/engines/saga/displayinfo.h
index 7fee5fbee1..3775e904ff 100644
--- a/engines/saga/displayinfo.h
+++ b/engines/saga/displayinfo.h
@@ -348,9 +348,9 @@ static PanelButton IHNM_QuitPanelButtons[] = {
};
static PanelButton IHNM_LoadPanelButtons[] = {
- // TODO
- {kPanelButtonLoad, 101,19, 60,16, kTextOK,'o',0, 0,0,0},
- {kPanelButtonLoadText, -1,5, 0,0, kTextLoadSuccessful,'-',0, 0,0,0},
+ {kPanelButtonLoad, 26,80, 80,25, kTextOK,'o',0, 0,0,0},
+ {kPanelButtonLoad, 156,80, 80,25, kTextCancel,'c',0, 0,0,0},
+ {kPanelButtonLoadText, -1,30, 0,0, kTextLoadSavedGame,'-',0, 0,0,0},
};
static PanelButton IHNM_SavePanelButtons[] = {
diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp
index 7789949393..482b3a4c82 100644
--- a/engines/saga/font.cpp
+++ b/engines/saga/font.cpp
@@ -240,6 +240,13 @@ void Font::createOutline(FontData *font) {
}
}
+int Font::translateChar(int charId) {
+ if (charId <= 127)
+ return charId; // normal character
+ else
+ return _charMap[charId - 128]; // extended character
+}
+
// Returns the horizontal length in pixels of the graphical representation
// of at most 'count' characters of the string 'text', taking
// into account any formatting options specified by 'flags'.
@@ -259,7 +266,7 @@ int Font::getStringWidth(FontId fontId, const char *text, size_t count, FontEffe
for (ct = count; *txt && (!count || ct > 0); txt++, ct--) {
ch = *txt & 0xFFU;
// Translate character
- ch = _charMap[ch];
+ ch = translateChar(ch);
assert(ch < FONT_CHARCOUNT);
width += font->normal.fontCharEntry[ch].tracking;
}
@@ -338,11 +345,11 @@ void Font::outFont(const FontStyle &drawFont, Surface *ds, const char *text, siz
// Don't do any special font mapping for the Italian fan
// translation of ITE
if (_vm->getLanguage() != Common::IT_ITA)
- c_code = _charMap[c_code];
+ c_code = translateChar(c_code);
}
} else if (_fontMapping == 1) {
// Force font mapping
- c_code = _charMap[c_code];
+ c_code = translateChar(c_code);
} else {
// In all other cases, ignore font mapping
}
diff --git a/engines/saga/font.h b/engines/saga/font.h
index 6b930ddca0..76c0f06725 100644
--- a/engines/saga/font.h
+++ b/engines/saga/font.h
@@ -158,6 +158,7 @@ class Font {
};
Font::FontId knownFont2FontIdx(KnownFont font);
+ int translateChar(int charId);
int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags);
int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags);
@@ -196,7 +197,7 @@ class Font {
return byteLength;
}
- static const int _charMap[256];
+ static const int _charMap[128];
SagaEngine *_vm;
bool _initialized;
diff --git a/engines/saga/font_map.cpp b/engines/saga/font_map.cpp
index 6246cb71da..6abaeea151 100644
--- a/engines/saga/font_map.cpp
+++ b/engines/saga/font_map.cpp
@@ -32,135 +32,8 @@
namespace Saga {
-const int Font::_charMap[256] = {
- 0, // 0
- 1, // 1
- 2, // 2
- 3, // 3
- 4, // 4
- 5, // 5
- 6, // 6
- 7, // 7
- 8, // 8
- 9, // 9
- 10, // 10
- 11, // 11
- 12, // 12
- 13, // 13
- 14, // 14
- 15, // 15
- 16, // 16
- 17, // 17
- 18, // 18
- 19, // 19
- 20, // 20
- 21, // 21
- 22, // 22
- 23, // 23
- 24, // 24
- 25, // 25
- 26, // 26
- 27, // 27
- 28, // 28
- 29, // 29
- 30, // 30
- 31, // 31
- 32, // 32
- 33, // 33
- 34, // 34
- 35, // 35
- 36, // 36
- 37, // 37
- 38, // 38
- 39, // 39
- 40, // 40
- 41, // 41
- 42, // 42
- 43, // 43
- 44, // 44
- 45, // 45
- 46, // 46
- 47, // 47
- 48, // 48
- 49, // 49
- 50, // 50
- 51, // 51
- 52, // 52
- 53, // 53
- 54, // 54
- 55, // 55
- 56, // 56
- 57, // 57
- 58, // 58
- 59, // 59
- 60, // 60
- 61, // 61
- 62, // 62
- 63, // 63
- 64, // 64
- 65, // 65
- 66, // 66
- 67, // 67
- 68, // 68
- 69, // 69
- 70, // 70
- 71, // 71
- 72, // 72
- 73, // 73
- 74, // 74
- 75, // 75
- 76, // 76
- 77, // 77
- 78, // 78
- 79, // 79
- 80, // 80
- 81, // 81
- 82, // 82
- 83, // 83
- 84, // 84
- 85, // 85
- 86, // 86
- 87, // 87
- 88, // 88
- 89, // 89
- 90, // 90
- 91, // 91
- 92, // 92
- 93, // 93
- 94, // 94
- 95, // 95
- 96, // 96
- 97, // 97
- 98, // 98
- 99, // 99
- 100, // 100
- 101, // 101
- 102, // 102
- 103, // 103
- 104, // 104
- 105, // 105
- 106, // 106
- 107, // 107
- 108, // 108
- 109, // 109
- 110, // 110
- 111, // 111
- 112, // 112
- 113, // 113
- 114, // 114
- 115, // 115
- 116, // 116
- 117, // 117
- 118, // 118
- 119, // 119
- 120, // 120
- 121, // 121
- 122, // 122
- 123, // 123
- 124, // 124
- 125, // 125
- 126, // 126
- 127, // 127
+const int Font::_charMap[128] = {
+ // Characters 0 - 127 are mapped directly to ISO 8859-1
199, // 128 LATIN CAPITAL LETTER C WITH CEDILLA
252, // 129 LATIN SMALL LETTER U WITH DIAERESIS
233, // 130 LATIN SMALL LETTER E WITH ACUTE
diff --git a/engines/saga/interface.cpp b/engines/saga/interface.cpp
index 9d3831062c..4a4573ccef 100644
--- a/engines/saga/interface.cpp
+++ b/engines/saga/interface.cpp
@@ -94,7 +94,7 @@ static int IHNMTextStringIdsLUT[56] = {
8, // Give
10, // Options
11, // Test
- 12, //
+ 12, // Demo
13, // Help
14, // Quit Game
16, // Fast
@@ -905,10 +905,13 @@ void Interface::drawPanelText(Surface *ds, InterfacePanel *panel, PanelButton *p
textFont = kKnownFontMedium;
textShadowKnownColor = kKnownColorVerbTextShadow;
} else {
- if (panelButton->id < 39 || panelButton->id > 50) {
+ if ((panelButton->id < 39 || panelButton->id > 50) && panelButton->id != kTextLoadSavedGame) {
// Read non-hardcoded strings from the LUT string table, loaded from the game
// data files
text = _vm->_script->_mainStrings.getString(IHNMTextStringIdsLUT[panelButton->id]);
+ } else if (panelButton->id == kTextLoadSavedGame) {
+ // a bit of a kludge, but it will do
+ text = _vm->getTextString(52);
} else {
// Hardcoded strings in IHNM are read from the ITE hardcoded strings
text = _vm->getTextString(panelButton->id);
@@ -1142,7 +1145,21 @@ void Interface::setLoad(PanelButton *panelButton) {
_loadPanel.currentButton = NULL;
switch (panelButton->id) {
case kTextOK:
- setMode(kPanelMain);
+ if (_vm->getGameType() == GType_ITE) {
+ setMode(kPanelMain);
+ } else {
+ if (_vm->getSaveFilesCount() > 0) {
+ if (_vm->isSaveListFull() || (_optionSaveFileTitleNumber > 0)) {
+ debug(1, "Loading save game %d", _vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber);
+ setMode(kPanelMain);
+ _vm->load(_vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber));
+ }
+ }
+ }
+ break;
+ case kTextCancel:
+ // IHNM only
+ setMode(kPanelOption);
break;
}
}
@@ -1573,7 +1590,6 @@ void Interface::handleChapterSelectionClick(const Point& mousePoint) {
}
void Interface::setOption(PanelButton *panelButton) {
- char * fileName;
_optionPanel.currentButton = NULL;
switch (panelButton->id) {
case kTextContinuePlaying:
@@ -1594,13 +1610,16 @@ void Interface::setOption(PanelButton *panelButton) {
setMode(kPanelQuit);
break;
case kTextLoad:
- if (_vm->getSaveFilesCount() > 0) {
- if (_vm->isSaveListFull() || (_optionSaveFileTitleNumber > 0)) {
- debug(1, "Loading save game %d", _vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber);
- fileName = _vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber);
- setMode(kPanelMain);
- _vm->load(fileName);
+ if (_vm->getGameType() == GType_ITE) {
+ if (_vm->getSaveFilesCount() > 0) {
+ if (_vm->isSaveListFull() || (_optionSaveFileTitleNumber > 0)) {
+ debug(1, "Loading save game %d", _vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber);
+ setMode(kPanelMain);
+ _vm->load(_vm->calcSaveFileName(_vm->getSaveFile(_optionSaveFileTitleNumber)->slotNumber));
+ }
}
+ } else {
+ setMode(kPanelLoad);
}
break;
case kTextSave:
diff --git a/engines/saga/itedata.cpp b/engines/saga/itedata.cpp
index 43c3d21012..bbd5cbb615 100644
--- a/engines/saga/itedata.cpp
+++ b/engines/saga/itedata.cpp
@@ -339,7 +339,7 @@ FxTable ITE_SfxTable[ITE_SFXCOUNT] = {
{ 73, 64 }
};
-const char *ITEinterfaceTextStrings[][52] = {
+const char *ITEinterfaceTextStrings[][53] = {
{
// Note that the "Load Successful!" string is never used in ScummVM
"Walk to", "Look At", "Pick Up", "Talk to", "Open",
@@ -358,7 +358,8 @@ const char *ITEinterfaceTextStrings[][52] = {
"There's no opening to close.",
"I don't know how to do that.",
"Show Dialog",
- "What is Rif's reply?"
+ "What is Rif's reply?",
+ "Loading a saved game"
},
// German
{
@@ -378,7 +379,8 @@ const char *ITEinterfaceTextStrings[][52] = {
"Hier ist keine \231ffnung zum Schlie$en.",
"Ich wei$ nicht, wie ich das machen soll.",
"Text zeigen",
- "Wie lautet die Antwort?"
+ "Wie lautet die Antwort?",
+ "Spielstand wird geladen"
},
// Italian fan translation
{
@@ -398,7 +400,8 @@ const char *ITEinterfaceTextStrings[][52] = {
"Nessuna apertura da chiudere.",
"Non saprei come farlo.",
"Dialoghi",
- "Come risponderebbe Rif?"
+ "Come risponderebbe Rif?",
+ "Vuoi davvero caricare il gioco?"
},
// Spanish IHNM
{
@@ -420,7 +423,8 @@ const char *ITEinterfaceTextStrings[][52] = {
NULL,
NULL,
NULL,
- NULL
+ NULL,
+ "Cardango una partida guardada"
}
};
diff --git a/engines/saga/itedata.h b/engines/saga/itedata.h
index 00efd070c1..6d0f5a9d70 100644
--- a/engines/saga/itedata.h
+++ b/engines/saga/itedata.h
@@ -88,7 +88,7 @@ struct FxTable {
extern ObjectTableData ITE_ObjectTable[ITE_OBJECTCOUNT];
extern FxTable ITE_SfxTable[ITE_SFXCOUNT];
-extern const char *ITEinterfaceTextStrings[][52];
+extern const char *ITEinterfaceTextStrings[][53];
#define PUZZLE_PIECES 15
diff --git a/engines/saga/saga.h b/engines/saga/saga.h
index 123c11eb7d..0b6b3b1478 100644
--- a/engines/saga/saga.h
+++ b/engines/saga/saga.h
@@ -295,7 +295,8 @@ enum TextStringIds {
kTextVoices,
kTextText,
kTextAudio,
- kTextBoth
+ kTextBoth,
+ kTextLoadSavedGame
};
struct GameResourceDescription {
diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp
index be4f2a423d..d9c7b446ba 100644
--- a/engines/saga/sprite.cpp
+++ b/engines/saga/sprite.cpp
@@ -74,9 +74,11 @@ Sprite::Sprite(SagaEngine *vm) : _vm(vm) {
Sprite::~Sprite(void) {
debug(8, "Shutting down sprite subsystem...");
_mainSprites.freeMem();
- _inventorySprites.freeMem();
- _arrowSprites.freeMem();
- _saveReminderSprites.freeMem();
+ if (_vm->getGameType() == GType_IHNM) {
+ _inventorySprites.freeMem();
+ _arrowSprites.freeMem();
+ _saveReminderSprites.freeMem();
+ }
free(_decodeBuf);
}
diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp
index 8f3175f098..609aca996d 100644
--- a/engines/scumm/charset.cpp
+++ b/engines/scumm/charset.cpp
@@ -49,14 +49,14 @@ void ScummEngine::loadCJKFont() {
Common::File fp;
_useCJKMode = false;
_textSurfaceMultiplier = 1;
- _newLineCharacter = 0xfe;
+ _newLineCharacter = 0;
if (_game.version <= 5 && _game.platform == Common::kPlatformFMTowns && _language == Common::JA_JPN) { // FM-TOWNS v3 / v5 Kanji
int numChar = 256 * 32;
_2byteWidth = 16;
_2byteHeight = 16;
// use FM-TOWNS font rom, since game files don't have kanji font resources
- if (fp.open("fmt_fnt.rom", Common::File::kFileReadMode)) {
+ if (fp.open("fmt_fnt.rom")) {
_useCJKMode = true;
debug(2, "Loading FM-TOWNS Kanji rom");
_2byteFontPtr = new byte[((_2byteWidth + 7) / 8) * _2byteHeight * numChar];
@@ -277,7 +277,7 @@ CharsetRenderer::CharsetRenderer(ScummEngine *vm) {
_disableOffsX = false;
_vm = vm;
- _curId = 0;
+ _curId = -1;
}
CharsetRenderer::~CharsetRenderer() {
@@ -289,7 +289,10 @@ CharsetRendererCommon::CharsetRendererCommon(ScummEngine *vm)
_shadowColor = 0;
}
-void CharsetRendererCommon::setCurID(byte id) {
+void CharsetRendererCommon::setCurID(int32 id) {
+ if (id == -1)
+ return;
+
assertRange(0, id, _vm->_numCharsets - 1, "charset");
_curId = id;
@@ -308,7 +311,10 @@ void CharsetRendererCommon::setCurID(byte id) {
_numChars = READ_LE_UINT16(_fontPtr + 2);
}
-void CharsetRendererV3::setCurID(byte id) {
+void CharsetRendererV3::setCurID(int32 id) {
+ if (id == -1)
+ return;
+
assertRange(0, id, _vm->_numCharsets - 1, "charset");
_curId = id;
@@ -668,7 +674,8 @@ void CharsetRenderer::translateColor() {
void CharsetRenderer::saveLoadWithSerializer(Serializer *ser) {
static const SaveLoadEntry charsetRendererEntries[] = {
- MKLINE(CharsetRenderer, _curId, sleByte, VER(73)),
+ MKLINE_OLD(CharsetRenderer, _curId, sleByte, VER(73), VER(73)),
+ MKLINE(CharsetRenderer, _curId, sleInt32, VER(74)),
MKLINE(CharsetRenderer, _color, sleByte, VER(73)),
MKEND()
};
@@ -988,7 +995,10 @@ CharsetRendererNut::~CharsetRendererNut() {
}
}
-void CharsetRendererNut::setCurID(byte id) {
+void CharsetRendererNut::setCurID(int32 id) {
+ if (id == -1)
+ return;
+
int numFonts = ((_vm->_game.id == GID_CMI) && (_vm->_game.features & GF_DEMO)) ? 4 : 5;
assert(id < numFonts);
_curId = id;
diff --git a/engines/scumm/charset.h b/engines/scumm/charset.h
index b62dbc6006..dbe02fc8fc 100644
--- a/engines/scumm/charset.h
+++ b/engines/scumm/charset.h
@@ -67,7 +67,7 @@ public:
protected:
ScummEngine *_vm;
- byte _curId;
+ int32 _curId;
public:
CharsetRenderer(ScummEngine *vm);
@@ -80,7 +80,7 @@ public:
void addLinebreaks(int a, byte *str, int pos, int maxwidth);
void translateColor();
- virtual void setCurID(byte id) = 0;
+ virtual void setCurID(int32 id) = 0;
int getCurID() { return _curId; }
virtual int getFontHeight() = 0;
@@ -113,7 +113,7 @@ protected:
public:
CharsetRendererCommon(ScummEngine *vm);
- void setCurID(byte id);
+ void setCurID(int32 id);
int getFontHeight();
};
@@ -142,7 +142,7 @@ protected:
public:
CharsetRendererNES(ScummEngine *vm) : CharsetRendererCommon(vm) {}
- void setCurID(byte id) {}
+ void setCurID(int32 id) {}
void printChar(int chr, bool ignoreCharsetMask);
void drawChar(int chr, const Graphics::Surface &s, int x, int y);
@@ -159,7 +159,7 @@ public:
void printChar(int chr, bool ignoreCharsetMask);
void drawChar(int chr, const Graphics::Surface &s, int x, int y);
- void setCurID(byte id);
+ void setCurID(int32 id);
void setColor(byte color);
int getCharWidth(byte chr);
};
@@ -168,7 +168,7 @@ class CharsetRendererV2 : public CharsetRendererV3 {
public:
CharsetRendererV2(ScummEngine *vm, Common::Language language);
- void setCurID(byte id) {}
+ void setCurID(int32 id) {}
int getCharWidth(byte chr) { return 8; }
};
@@ -184,7 +184,7 @@ public:
void printChar(int chr, bool ignoreCharsetMask);
- void setCurID(byte id);
+ void setCurID(int32 id);
int getFontHeight();
int getCharHeight(byte chr);
diff --git a/engines/scumm/debugger.cpp b/engines/scumm/debugger.cpp
index 9f9115e207..23af1f9672 100644
--- a/engines/scumm/debugger.cpp
+++ b/engines/scumm/debugger.cpp
@@ -298,7 +298,7 @@ bool ScummDebugger::Cmd_ImportRes(int argc, const char** argv) {
// FIXME add bounds check
if (!strncmp(argv[1], "scr", 3)) {
- file.open(argv[2], Common::File::kFileReadMode);
+ file.open(argv[2]);
if (file.isOpen() == false) {
DebugPrintf("Could not open file %s\n", argv[2]);
return true;
diff --git a/engines/scumm/file.cpp b/engines/scumm/file.cpp
index bc5fc38225..bf13308a0c 100644
--- a/engines/scumm/file.cpp
+++ b/engines/scumm/file.cpp
@@ -58,8 +58,8 @@ void ScummFile::resetSubfile() {
seek(0, SEEK_SET);
}
-bool ScummFile::open(const Common::String &filename, AccessMode mode) {
- if (File::open(filename, mode)) {
+bool ScummFile::open(const Common::String &filename) {
+ if (File::open(filename)) {
resetSubfile();
return true;
} else {
@@ -187,11 +187,6 @@ uint32 ScummFile::read(void *dataPtr, uint32 dataSize) {
return realLen;
}
-uint32 ScummFile::write(const void *, uint32) {
- error("ScummFile does not support writing!");
- return 0;
-}
-
#pragma mark -
#pragma mark --- ScummDiskImage ---
#pragma mark -
@@ -250,11 +245,6 @@ ScummDiskImage::ScummDiskImage(const char *disk1, const char *disk2, GameSetting
}
}
-uint32 ScummDiskImage::write(const void *, uint32) {
- error("ScummDiskImage does not support writing!");
- return 0;
-}
-
void ScummDiskImage::setEnc(byte enc) {
_stream->setEnc(enc);
}
@@ -300,7 +290,7 @@ bool ScummDiskImage::openDisk(char num) {
return true;
}
-bool ScummDiskImage::open(const Common::String &filename, AccessMode mode) {
+bool ScummDiskImage::open(const Common::String &filename) {
uint16 signature;
// check signature
diff --git a/engines/scumm/file.h b/engines/scumm/file.h
index 7064654f89..a2695cac59 100644
--- a/engines/scumm/file.h
+++ b/engines/scumm/file.h
@@ -36,7 +36,7 @@ class BaseScummFile : public Common::File {
public:
virtual void setEnc(byte value) = 0;
- virtual bool open(const Common::String &filename, AccessMode mode = kFileReadMode) = 0;
+ virtual bool open(const Common::String &filename) = 0;
virtual bool openSubFile(const Common::String &filename) = 0;
virtual bool eof() = 0;
@@ -44,7 +44,6 @@ public:
virtual uint32 size() = 0;
virtual void seek(int32 offs, int whence = SEEK_SET) = 0;
virtual uint32 read(void *dataPtr, uint32 dataSize) = 0;
- virtual uint32 write(const void *dataPtr, uint32 dataSize) = 0;
};
class ScummFile : public BaseScummFile {
@@ -59,7 +58,7 @@ public:
void setSubfileRange(uint32 start, uint32 len);
void resetSubfile();
- bool open(const Common::String &filename, AccessMode mode = kFileReadMode);
+ bool open(const Common::String &filename);
bool openSubFile(const Common::String &filename);
bool eof();
@@ -67,7 +66,6 @@ public:
uint32 size();
void seek(int32 offs, int whence = SEEK_SET);
uint32 read(void *dataPtr, uint32 dataSize);
- uint32 write(const void *dataPtr, uint32 dataSize);
};
class ScummDiskImage : public BaseScummFile {
@@ -104,7 +102,7 @@ public:
ScummDiskImage(const char *disk1, const char *disk2, GameSettings game);
void setEnc(byte value);
- bool open(const Common::String &filename, AccessMode mode = kFileReadMode);
+ bool open(const Common::String &filename);
bool openSubFile(const Common::String &filename);
void close();
@@ -113,7 +111,6 @@ public:
uint32 size() { return _stream->size(); }
void seek(int32 offs, int whence = SEEK_SET) { _stream->seek(offs, whence); }
uint32 read(void *dataPtr, uint32 dataSize) { return _stream->read(dataPtr, dataSize); }
- uint32 write(const void *dataPtr, uint32 dataSize);
};
} // End of namespace Scumm
diff --git a/engines/scumm/file_nes.cpp b/engines/scumm/file_nes.cpp
index 95f5eec4ea..8325436f87 100644
--- a/engines/scumm/file_nes.cpp
+++ b/engines/scumm/file_nes.cpp
@@ -62,11 +62,6 @@ struct ScummNESFile::Resource {
ScummNESFile::ScummNESFile() : _stream(0), _buf(0), _ROMset(kROMsetNum) {
}
-uint32 ScummNESFile::write(const void *, uint32) {
- error("ScummNESFile does not support writing!");
- return 0;
-}
-
void ScummNESFile::setEnc(byte enc) {
_stream->setEnc(enc);
}
@@ -1234,7 +1229,7 @@ bool ScummNESFile::generateIndex() {
return true;
}
-bool ScummNESFile::open(const Common::String &filename, AccessMode mode) {
+bool ScummNESFile::open(const Common::String &filename) {
if (_ROMset == kROMsetNum) {
char md5str[32+1];
@@ -1267,9 +1262,8 @@ bool ScummNESFile::open(const Common::String &filename, AccessMode mode) {
}
}
- if (File::open(filename, mode)) {
- if (_stream)
- delete _stream;
+ if (File::open(filename)) {
+ delete _stream;
_stream = 0;
free(_buf);
@@ -1282,8 +1276,7 @@ bool ScummNESFile::open(const Common::String &filename, AccessMode mode) {
}
void ScummNESFile::close() {
- if (_stream)
- delete _stream;
+ delete _stream;
_stream = 0;
free(_buf);
diff --git a/engines/scumm/file_nes.h b/engines/scumm/file_nes.h
index d601c2c496..4d2d6de275 100644
--- a/engines/scumm/file_nes.h
+++ b/engines/scumm/file_nes.h
@@ -64,7 +64,7 @@ public:
ScummNESFile();
void setEnc(byte value);
- bool open(const Common::String &filename, AccessMode mode = kFileReadMode);
+ bool open(const Common::String &filename);
bool openSubFile(const Common::String &filename);
void close();
@@ -73,7 +73,6 @@ public:
uint32 size() { return _stream->size(); }
void seek(int32 offs, int whence = SEEK_SET) { _stream->seek(offs, whence); }
uint32 read(void *dataPtr, uint32 dataSize) { return _stream->read(dataPtr, dataSize); }
- uint32 write(const void *dataPtr, uint32 dataSize);
};
} // End of namespace Scumm
diff --git a/engines/scumm/gfx.cpp b/engines/scumm/gfx.cpp
index 6c8d24d25a..d09accafb0 100644
--- a/engines/scumm/gfx.cpp
+++ b/engines/scumm/gfx.cpp
@@ -669,7 +669,7 @@ void ScummEngine::drawStripToScreen(VirtScreen *vs, int x, int width, int top, i
x += 16;
while (x + width >= _screenWidth)
width -= 16;
- if (width < 0)
+ if (width <= 0)
return;
}
diff --git a/engines/scumm/he/script_v60he.cpp b/engines/scumm/he/script_v60he.cpp
index 4d5ec668a0..9429f8d086 100644
--- a/engines/scumm/he/script_v60he.cpp
+++ b/engines/scumm/he/script_v60he.cpp
@@ -1012,7 +1012,7 @@ void ScummEngine_v60he::o60_openFile() {
_hInFileTable[slot] = _saveFileMan->openForLoading(filename);
if (_hInFileTable[slot] == 0) {
Common::File *f = new Common::File();
- f->open(filename, Common::File::kFileReadMode);
+ f->open(filename);
if (!f->isOpen())
delete f;
else
diff --git a/engines/scumm/he/script_v72he.cpp b/engines/scumm/he/script_v72he.cpp
index 6f68d56693..484e11cf50 100644
--- a/engines/scumm/he/script_v72he.cpp
+++ b/engines/scumm/he/script_v72he.cpp
@@ -1692,7 +1692,7 @@ void ScummEngine_v72he::o72_openFile() {
_hInFileTable[slot] = _saveFileMan->openForLoading(filename);
if (_hInFileTable[slot] == 0) {
Common::File *f = new Common::File();
- f->open(filename, Common::File::kFileReadMode);
+ f->open(filename);
if (!f->isOpen())
delete f;
else
diff --git a/engines/scumm/he/script_v80he.cpp b/engines/scumm/he/script_v80he.cpp
index 393e1d3a8f..39ec715d94 100644
--- a/engines/scumm/he/script_v80he.cpp
+++ b/engines/scumm/he/script_v80he.cpp
@@ -409,7 +409,7 @@ void ScummEngine_v80he::o80_getFileSize() {
Common::SeekableReadStream *f = _saveFileMan->openForLoading((const char *)filename);
if (!f) {
Common::File *file = new Common::File();
- file->open((const char *)filename, Common::File::kFileReadMode);
+ file->open((const char *)filename);
if (!file->isOpen())
delete f;
else
diff --git a/engines/scumm/he/wiz_he.cpp b/engines/scumm/he/wiz_he.cpp
index df472307eb..f514449bff 100644
--- a/engines/scumm/he/wiz_he.cpp
+++ b/engines/scumm/he/wiz_he.cpp
@@ -1881,7 +1881,7 @@ void Wiz::processWizImage(const WizParameters *params) {
memcpy(filename, params->filename, 260);
_vm->convertFilePath(filename);
- if (f.open((const char *)filename, Common::File::kFileReadMode)) {
+ if (f.open((const char *)filename)) {
uint32 id = f.readUint32BE();
if (id == MKID_BE('AWIZ') || id == MKID_BE('MULT')) {
uint32 size = f.readUint32BE();
@@ -1911,7 +1911,7 @@ void Wiz::processWizImage(const WizParameters *params) {
break;
case 4:
if (params->processFlags & kWPFUseFile) {
- Common::File f;
+ Common::DumpFile f;
switch (params->fileWriteMode) {
case 2:
@@ -1924,7 +1924,7 @@ void Wiz::processWizImage(const WizParameters *params) {
memcpy(filename, params->filename, 260);
_vm->convertFilePath(filename);
- if (!f.open((const char *)filename, Common::File::kFileWriteMode)) {
+ if (!f.open((const char *)filename)) {
debug(0, "Unable to open for write '%s'", filename);
_vm->VAR(119) = -3;
} else {
diff --git a/engines/scumm/imuse_digi/dimuse.cpp b/engines/scumm/imuse_digi/dimuse.cpp
index fa50eca604..d3359fa33e 100644
--- a/engines/scumm/imuse_digi/dimuse.cpp
+++ b/engines/scumm/imuse_digi/dimuse.cpp
@@ -57,8 +57,8 @@ IMuseDigital::IMuseDigital(ScummEngine_v7 *scumm, Audio::Mixer *mixer, int fps)
for (int l = 0; l < MAX_DIGITAL_TRACKS + MAX_DIGITAL_FADETRACKS; l++) {
_track[l] = new Track;
assert(_track[l]);
+ memset(_track[l], 0, sizeof(Track));
_track[l]->trackId = l;
- _track[l]->used = false;
}
_vm->_timer->installTimerProc(timer_handler, 1000000 / _callbackFps, this);
diff --git a/engines/scumm/resource.cpp b/engines/scumm/resource.cpp
index 8f7617885a..9823a9483f 100644
--- a/engines/scumm/resource.cpp
+++ b/engines/scumm/resource.cpp
@@ -1299,7 +1299,7 @@ void ScummEngine::allocateArrays() {
void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int length) {
char buf[256];
- Common::File out;
+ Common::DumpFile out;
uint32 size;
if (length >= 0)
@@ -1313,7 +1313,7 @@ void ScummEngine::dumpResource(const char *tag, int idx, const byte *ptr, int le
sprintf(buf, "dumps/%s%d.dmp", tag, idx);
- out.open(buf, Common::File::kFileWriteMode);
+ out.open(buf);
if (out.isOpen() == false)
return;
out.write(ptr, size);
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp
index 5894e837aa..426c7ee48b 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -479,6 +479,9 @@ Graphics::Surface *ScummEngine::loadThumbnailFromSlot(int slot) {
Common::SeekableReadStream *in;
SaveGameHeader hdr;
+ if (slot < 0)
+ return 0;
+
makeSavegameName(filename, slot, false);
if (!(in = _saveFileMan->openForLoading(filename))) {
return 0;
@@ -507,6 +510,9 @@ bool ScummEngine::loadInfosFromSlot(int slot, InfoStuff *stuff) {
Common::SeekableReadStream *in;
SaveGameHeader hdr;
+ if (slot < 0)
+ return 0;
+
makeSavegameName(filename, slot, false);
if (!(in = _saveFileMan->openForLoading(filename))) {
return false;
@@ -595,7 +601,7 @@ bool ScummEngine::loadInfos(Common::SeekableReadStream *file, InfoStuff *stuff)
return true;
}
-void ScummEngine::saveInfos(Common::OutSaveFile* file) {
+void ScummEngine::saveInfos(Common::WriteStream* file) {
SaveInfoSection section;
section.type = MKID_BE('INFO');
section.version = INFOSECTION_VERSION;
diff --git a/engines/scumm/saveload.h b/engines/scumm/saveload.h
index 0ddb4e5d2a..2d7ee64680 100644
--- a/engines/scumm/saveload.h
+++ b/engines/scumm/saveload.h
@@ -31,7 +31,7 @@
namespace Common {
class SeekableReadStream;
- class OutSaveFile;
+ class WriteStream;
}
namespace Scumm {
@@ -50,7 +50,7 @@ namespace Scumm {
* only saves/loads those which are valid for the version of the savegame
* which is being loaded/saved currently.
*/
-#define CURRENT_VER 73
+#define CURRENT_VER 74
/**
* An auxillary macro, used to specify savegame versions. We use this instead
@@ -125,7 +125,7 @@ struct SaveLoadEntry {
class Serializer {
public:
- Serializer(Common::SeekableReadStream *in, Common::OutSaveFile *out, uint32 savegameVersion)
+ Serializer(Common::SeekableReadStream *in, Common::WriteStream *out, uint32 savegameVersion)
: _loadStream(in), _saveStream(out),
_savegameVersion(savegameVersion)
{ }
@@ -151,7 +151,7 @@ public:
protected:
Common::SeekableReadStream *_loadStream;
- Common::OutSaveFile *_saveStream;
+ Common::WriteStream *_saveStream;
uint32 _savegameVersion;
void saveArrayOf(void *b, int len, int datasize, byte filetype);
diff --git a/engines/scumm/scumm-md5.h b/engines/scumm/scumm-md5.h
index 62d777aa33..ce8f0a4d9a 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 Mon Jun 02 08:37:50 2008
+ This file was generated by the md5table tool on Mon Jul 28 00:13:01 2008
DO NOT EDIT MANUALLY!
*/
@@ -75,7 +75,7 @@ static const MD5Table md5table[] = {
{ "16effd200aa6b8abe9c569c3e578814d", "freddi4", "HE 99", "Demo", -1, Common::NL_NLD, Common::kPlatformWindows },
{ "179879b6e35c1ead0d93aab26db0951b", "fbear", "HE 70", "", 13381, Common::EN_ANY, Common::kPlatformWindows },
{ "17b5d5e6af4ae89d62631641d66d5a05", "indy3", "VGA", "VGA", -1, Common::IT_ITA, Common::kPlatformPC },
- { "17f7296f63c78642724f057fd8e736a7", "maniac", "NES", "extracted", -1, Common::EN_USA, Common::kPlatformNES },
+ { "17f7296f63c78642724f057fd8e736a7", "maniac", "NES", "extracted", -1, Common::EN_GRB, Common::kPlatformNES },
{ "17fa250eb72dae2dad511ba79c0b6b0a", "tentacle", "", "Demo", -1, Common::FR_FRA, Common::kPlatformPC },
{ "182344899c2e2998fca0bebcd82aa81a", "atlantis", "", "CD", 12035, Common::EN_ANY, Common::kPlatformPC },
{ "183d7464902d40d00800e8ee1f04117c", "maniac", "V2", "V2", 1988, Common::DE_DEU, Common::kPlatformPC },
@@ -149,7 +149,7 @@ static const MD5Table md5table[] = {
{ "37ff1b308999c4cca7319edfcc1280a0", "puttputt", "HE 70", "Demo", 8269, Common::EN_ANY, Common::kPlatformWindows },
{ "3824e60cdf639d22f6df92a03dc4b131", "fbear", "HE 61", "", 7732, Common::EN_ANY, Common::kPlatformPC },
{ "387a544b8b10b26912d8413bab63a853", "monkey2", "", "Demo", -1, Common::EN_ANY, Common::kPlatformPC },
- { "3905799e081b80a61d4460b7b733c206", "maniac", "NES", "", 262144, Common::EN_GRB, Common::kPlatformNES },
+ { "3905799e081b80a61d4460b7b733c206", "maniac", "NES", "", 262144, Common::EN_USA, Common::kPlatformNES },
{ "3938ee1aa4433fca9d9308c9891172b1", "zak", "FM-TOWNS", "Demo", -1, Common::EN_ANY, Common::kPlatformFMTowns },
{ "399b217b0c8d65d0398076da486363a9", "indy3", "VGA", "VGA", 6295, Common::DE_DEU, Common::kPlatformPC },
{ "39cb9dec16fa16f38d79acd80effb059", "loom", "EGA", "EGA", -1, Common::FR_FRA, Common::kPlatformAmiga },
@@ -357,7 +357,7 @@ static const MD5Table md5table[] = {
{ "90e2f0af4f779629695c6394a65bb702", "spyfox2", "", "", -1, Common::FR_FRA, Common::kPlatformUnknown },
{ "910e31cffb28226bd68c569668a0d6b4", "monkey", "EGA", "EGA", -1, Common::ES_ESP, Common::kPlatformPC },
{ "91469353f7be1b122fa88d23480a1320", "zak", "V2", "V2", -1, Common::FR_FRA, Common::kPlatformAmiga },
- { "91d5db93187fab54d823f73bd6441cb6", "maniac", "NES", "extracted", -1, Common::EN_GRB, Common::kPlatformNES },
+ { "91d5db93187fab54d823f73bd6441cb6", "maniac", "NES", "extracted", -1, Common::EN_USA, Common::kPlatformNES },
{ "927a764615c7fcdd72f591355e089d8c", "monkey", "No Adlib", "EGA", -1, Common::DE_DEU, Common::kPlatformAtariST },
{ "92b078d9d6d9d751da9c26b8b3075779", "tentacle", "", "Floppy", -1, Common::FR_FRA, Common::kPlatformPC },
{ "92e7727e67f5cd979d8a1070e4eb8cb3", "puttzoo", "HE 98.5", "Updated", -1, Common::EN_ANY, Common::kPlatformUnknown },
@@ -503,7 +503,7 @@ static const MD5Table md5table[] = {
{ "d7b247c26bf1f01f8f7daf142be84de3", "balloon", "HE 99", "Updated", -1, Common::EN_ANY, Common::kPlatformWindows },
{ "d831f7c048574dd9d5d85db2a1468099", "maniac", "C64", "", -1, Common::EN_ANY, Common::kPlatformC64 },
{ "d8323015ecb8b10bf53474f6e6b0ae33", "dig", "", "", 16304, Common::UNK_LANG, Common::kPlatformUnknown },
- { "d8d07efcb88f396bee0b402b10c3b1c9", "maniac", "NES", "", 262144, Common::EN_USA, Common::kPlatformNES },
+ { "d8d07efcb88f396bee0b402b10c3b1c9", "maniac", "NES", "", 262144, Common::EN_GRB, Common::kPlatformNES },
{ "d917f311a448e3cc7239c31bddb00dd2", "samnmax", "", "CD", 9080, Common::EN_ANY, Common::kPlatformUnknown },
{ "d9d0dd93d16ab4dec55cabc2b86bbd17", "samnmax", "", "Demo", 6478, Common::EN_ANY, Common::kPlatformPC },
{ "da09e666fc8f5b78d7b0ac65d1a3b56e", "monkey2", "", "", 11135, Common::EN_ANY, Common::kPlatformFMTowns },
diff --git a/engines/scumm/scumm.h b/engines/scumm/scumm.h
index b1ab9f7386..b839d37b08 100644
--- a/engines/scumm/scumm.h
+++ b/engines/scumm/scumm.h
@@ -46,7 +46,7 @@ namespace GUI {
using GUI::Dialog;
namespace Common {
class SeekableReadStream;
- class OutSaveFile;
+ class WriteStream;
}
namespace Scumm {
@@ -556,7 +556,7 @@ protected:
public:
int _numLocalScripts, _numImages, _numRooms, _numScripts, _numSounds; // Used by HE games
int _numCostumes; // FIXME - should be protected, used by Actor::remapActorPalette
- int _numCharsets; // FIXME - should be protected, used by CharsetRenderer
+ int32 _numCharsets; // FIXME - should be protected, used by CharsetRenderer
BaseCostumeLoader *_costumeLoader;
BaseCostumeRenderer *_costumeRenderer;
@@ -633,8 +633,8 @@ public:
protected:
Graphics::Surface *loadThumbnail(Common::SeekableReadStream *file);
bool loadInfos(Common::SeekableReadStream *file, InfoStuff *stuff);
- void saveThumbnail(Common::OutSaveFile *file);
- void saveInfos(Common::OutSaveFile* file);
+ void saveThumbnail(Common::WriteStream *file);
+ void saveInfos(Common::WriteStream* file);
int32 _engineStartTime;
int32 _pauseStartTime;
diff --git a/engines/scumm/string.cpp b/engines/scumm/string.cpp
index f039e2ca23..700632e4b3 100644
--- a/engines/scumm/string.cpp
+++ b/engines/scumm/string.cpp
@@ -279,7 +279,7 @@ bool ScummEngine::handleNextCharsetCode(Actor *a, int *code) {
}
c = *buffer++;
- if (c == _newLineCharacter) {
+ if (_newLineCharacter != 0 && c == _newLineCharacter) {
c = 13;
break;
}
diff --git a/engines/sky/control.cpp b/engines/sky/control.cpp
index a6ab5429dd..3edc087f57 100644
--- a/engines/sky/control.cpp
+++ b/engines/sky/control.cpp
@@ -986,7 +986,7 @@ void Control::handleKeyPress(Common::KeyState kbd, Common::String &textBuf) {
if (kbd.keycode == Common::KEYCODE_BACKSPACE) { // backspace
if (textBuf.size() > 0)
textBuf.deleteLastChar();
- } else {
+ } else if (kbd.ascii) {
// Cannot enter text wider than the save/load panel
if (_enteredTextWidth >= PAN_LINE_WIDTH - 10)
return;
diff --git a/engines/sky/disk.cpp b/engines/sky/disk.cpp
index a2f7d57cb0..a30276f8be 100644
--- a/engines/sky/disk.cpp
+++ b/engines/sky/disk.cpp
@@ -326,14 +326,14 @@ void Disk::fnFlushBuffers(void) {
void Disk::dumpFile(uint16 fileNr) {
char buf[128];
- Common::File out;
+ Common::DumpFile out;
byte* filePtr;
filePtr = loadFile(fileNr);
sprintf(buf, "dumps/file-%d.dmp", fileNr);
if (!Common::File::exists(buf)) {
- if (out.open(buf, Common::File::kFileWriteMode))
+ if (out.open(buf))
out.write(filePtr, _lastLoadedFileSize);
}
free(filePtr);
diff --git a/engines/sky/logic.cpp b/engines/sky/logic.cpp
index 6cd4ce505a..9f13bf9bee 100644
--- a/engines/sky/logic.cpp
+++ b/engines/sky/logic.cpp
@@ -1774,6 +1774,7 @@ bool Logic::fnChooser(uint32 a, uint32 b, uint32 c) {
uint32 size = ((dataFileHeader *)data)->s_height * ((dataFileHeader *)data)->s_width;
uint32 index = 0;
uint32 width = ((dataFileHeader *)data)->s_width;
+ uint32 height = ((dataFileHeader *)data)->s_height;
data += sizeof(dataFileHeader);
@@ -1794,7 +1795,7 @@ bool Logic::fnChooser(uint32 a, uint32 b, uint32 c) {
textCompact->xcood = TOP_LEFT_X; // set coordinates
textCompact->ycood = ycood;
- ycood += 12;
+ ycood += height;
}
if (p == _scriptVariables + TEXT1)
diff --git a/engines/sky/sound.cpp b/engines/sky/sound.cpp
index 928221a9a5..f15038c0b6 100644
--- a/engines/sky/sound.cpp
+++ b/engines/sky/sound.cpp
@@ -1025,6 +1025,7 @@ Sound::Sound(Audio::Mixer *mixer, Disk *pDisk, uint8 pVolume) {
_mixer = mixer;
_saveSounds[0] = _saveSounds[1] = 0xFFFF;
_mainSfxVolume = pVolume;
+ _isPaused = false;
}
Sound::~Sound(void) {
@@ -1254,14 +1255,20 @@ bool Sound::startSpeech(uint16 textNum) {
void Sound::fnPauseFx(void) {
- _mixer->pauseID(SOUND_CH0, true);
- _mixer->pauseID(SOUND_CH1, true);
+ if (!_isPaused) {
+ _isPaused = true;
+ _mixer->pauseID(SOUND_CH0, true);
+ _mixer->pauseID(SOUND_CH1, true);
+ }
}
void Sound::fnUnPauseFx(void) {
- _mixer->pauseID(SOUND_CH0, false);
- _mixer->pauseID(SOUND_CH1, false);
+ if (_isPaused) {
+ _isPaused = false;
+ _mixer->pauseID(SOUND_CH0, false);
+ _mixer->pauseID(SOUND_CH1, false);
+ }
}
} // End of namespace Sky
diff --git a/engines/sky/sound.h b/engines/sky/sound.h
index 28e2e8c88a..0ad509700e 100644
--- a/engines/sky/sound.h
+++ b/engines/sky/sound.h
@@ -89,6 +89,8 @@ private:
uint8 *_sampleRates, *_sfxInfo;
uint8 _mainSfxVolume;
+ bool _isPaused;
+
static uint16 _speechConvertTable[8];
static SfxQueue _sfxQueue[MAX_QUEUED_FX];
};
diff --git a/engines/sword1/resman.cpp b/engines/sword1/resman.cpp
index d54e290b09..adb84eee83 100644
--- a/engines/sword1/resman.cpp
+++ b/engines/sword1/resman.cpp
@@ -212,8 +212,8 @@ void *ResMan::openFetchRes(uint32 id) {
void ResMan::dumpRes(uint32 id) {
char outn[30];
sprintf(outn, "DUMP%08X.BIN", id);
- Common::File outf;
- if (outf.open(outn, Common::File::kFileWriteMode)) {
+ Common::DumpFile outf;
+ if (outf.open(outn)) {
resOpen(id);
MemHandle *memHandle = resHandle(id);
outf.write(memHandle->data, memHandle->size);
diff --git a/engines/sword2/resman.cpp b/engines/sword2/resman.cpp
index 04205e8d0e..880234aab0 100644
--- a/engines/sword2/resman.cpp
+++ b/engines/sword2/resman.cpp
@@ -234,7 +234,6 @@ bool ResourceManager::init() {
/**
* Returns the address of a resource. Loads if not in memory. Retains a count.
*/
-
byte *ResourceManager::openResource(uint32 res, bool dump) {
assert(res < _totalResFiles);
@@ -287,7 +286,6 @@ byte *ResourceManager::openResource(uint32 res, bool dump) {
if (dump) {
char buf[256];
const char *tag;
- Common::File out;
switch (fetchType(_resList[res].ptr)) {
case ANIMATION_FILE:
@@ -337,7 +335,8 @@ byte *ResourceManager::openResource(uint32 res, bool dump) {
sprintf(buf, "dumps/%s-%d.dmp", tag, res);
if (!Common::File::exists(buf)) {
- if (out.open(buf, Common::File::kFileWriteMode))
+ Common::DumpFile out;
+ if (out.open(buf))
out.write(_resList[res].ptr, len);
}
}
diff --git a/engines/tinsel/actors.cpp b/engines/tinsel/actors.cpp
new file mode 100644
index 0000000000..c2f01added
--- /dev/null
+++ b/engines/tinsel/actors.cpp
@@ -0,0 +1,897 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles things to do with actors, delegates much moving actor stuff.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/events.h"
+#include "tinsel/film.h" // for FREEL
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h" // INV_NOICON
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h" // for POBJECT
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/serializer.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+
+//----------------- LOCAL DEFINES --------------------
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** actor struct - one per actor */
+struct ACTOR_STRUC {
+ int32 masking; //!< type of actor masking
+ SCNHANDLE hActorId; //!< handle actor ID string index
+ SCNHANDLE hActorCode; //!< handle to actor script
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int LeadActorId = 0; // The lead actor
+
+static int NumActors = 0; // The total number of actors in the game
+
+struct ACTORINFO {
+ bool alive; // TRUE == alive
+ bool hidden; // TRUE == hidden
+ bool completed; // TRUE == script played out
+
+ int x, y, z;
+
+ int32 mtype; // DEFAULT(b'ground), MASK, ALWAYS
+ SCNHANDLE actorCode; // The actor's script
+
+ const FREEL *presReel; // the present reel
+ int presRnum; // the present reel number
+ SCNHANDLE presFilm; // the film that reel belongs to
+ OBJECT *presObj; // reference for position information
+ int presX, presY;
+
+ bool tagged; // actor tagged?
+ SCNHANDLE hTag; // handle to tag text
+ int tType; // e.g. TAG_Q1TO3
+
+ bool escOn;
+ int escEv;
+
+ COLORREF tColour; // Text colour
+
+ SCNHANDLE playFilm; // revert to this after talks
+ SCNHANDLE talkFilm; // this be deleted in the future!
+ SCNHANDLE latestFilm; // the last film ordered
+ bool talking;
+
+ int steps;
+
+};
+
+static ACTORINFO *actorInfo = 0;
+
+static COLORREF defaultColour = 0; // Text colour
+
+static bool bActorsOn = false;
+
+static int ti = 0;
+
+/**
+ * Called once at start-up time, and again at restart time.
+ * Registers the total number of actors in the game.
+ * @param num Chunk Id
+ */
+void RegisterActors(int num) {
+ if (actorInfo == NULL) {
+ // Store the total number of actors in the game
+ NumActors = num;
+
+ // Check we can save so many
+ assert(NumActors <= MAX_SAVED_ALIVES);
+
+ // Allocate RAM for actorInfo
+ // FIXME: For now, we always allocate MAX_SAVED_ALIVES blocks,
+ // as this makes the save/load code simpler
+ actorInfo = (ACTORINFO *)calloc(MAX_SAVED_ALIVES, sizeof(ACTORINFO));
+
+ // make sure memory allocated
+ if (actorInfo == NULL) {
+ error("Cannot allocate memory for actors");
+ }
+ } else {
+ // Check the total number of actors is still the same
+ assert(num == NumActors);
+
+ memset(actorInfo, 0, MAX_SAVED_ALIVES * sizeof(ACTORINFO));
+ }
+
+ // All actors start off alive.
+ while (num--)
+ actorInfo[num].alive = true;
+}
+
+void FreeActors() {
+ if (actorInfo) {
+ free(actorInfo);
+ actorInfo = NULL;
+ }
+}
+
+/**
+ * Called from dec_lead(), i.e. normally once at start of master script.
+ * @param leadID Lead Id
+ */
+void setleadid(int leadID) {
+ LeadActorId = leadID;
+ actorInfo[leadID-1].mtype = ACT_MASK;
+}
+
+/**
+ * No comment.
+ */
+int LeadId(void) {
+ return LeadActorId;
+}
+
+struct ATP_INIT {
+ int id; // Actor number
+ USER_EVENT event; // Event
+ BUTEVENT bev; // Causal mouse event
+};
+
+/**
+ * Runs actor's glitter code.
+ */
+static void ActorTinselProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ ATP_INIT *atp = (ATP_INIT *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, atp->bev); // May kill us if single click
+
+ // Run the Glitter code
+ assert(actorInfo[atp->id - 1].actorCode); // no code to run
+
+ _ctx->pic = InitInterpretContext(GS_ACTOR, actorInfo[atp->id - 1].actorCode, atp->event, NOPOLY, atp->id, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // If it gets here, actor's code has run to completion
+ actorInfo[atp->id - 1].completed = true;
+
+ CORO_END_CODE;
+}
+
+
+//---------------------------------------------------------------------------
+
+struct RATP_INIT {
+ INT_CONTEXT *pic;
+ int id; // Actor number
+};
+
+static void ActorRestoredProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ RATP_INIT *r = (RATP_INIT *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pic = RestoreInterpretContext(r->pic);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // If it gets here, actor's code has run to completion
+ actorInfo[r->id - 1].completed = true;
+
+ CORO_END_CODE;
+}
+
+void RestoreActorProcess(int id, INT_CONTEXT *pic) {
+ RATP_INIT r = { pic, id };
+
+ g_scheduler->createProcess(PID_TCODE, ActorRestoredProcess, &r, sizeof(r));
+}
+
+/**
+ * Starts up process to runs actor's glitter code.
+ * @param ano Actor Id
+ * @param event Event structure
+ * @param be ButEvent
+ */
+void actorEvent(int ano, USER_EVENT event, BUTEVENT be) {
+ ATP_INIT atp;
+
+ // Only if there is Glitter code associated with this actor.
+ if (actorInfo[ano - 1].actorCode) {
+ atp.id = ano;
+ atp.event = event;
+ atp.bev = be;
+ g_scheduler->createProcess(PID_TCODE, ActorTinselProcess, &atp, sizeof(atp));
+ }
+}
+
+/**
+ * Called at the start of each scene for each actor with a code block.
+ * @param as Actor structure
+ * @param bRunScript Flag for whether to run actor's script for the scene
+ */
+void StartActor(const ACTOR_STRUC *as, bool bRunScript) {
+ SCNHANDLE hActorId = FROM_LE_32(as->hActorId);
+
+ // Zero-out many things
+ actorInfo[hActorId - 1].hidden = false;
+ actorInfo[hActorId - 1].completed = false;
+ actorInfo[hActorId - 1].x = 0;
+ actorInfo[hActorId - 1].y = 0;
+ actorInfo[hActorId - 1].presReel = NULL;
+ actorInfo[hActorId - 1].presFilm = 0;
+ actorInfo[hActorId - 1].presObj = NULL;
+
+ // Store current scene's parameters for this actor
+ actorInfo[hActorId - 1].mtype = FROM_LE_32(as->masking);
+ actorInfo[hActorId - 1].actorCode = FROM_LE_32(as->hActorCode);
+
+ // Run actor's script for this scene
+ if (bRunScript) {
+ if (bActorsOn)
+ actorInfo[hActorId - 1].alive = true;
+
+ if (actorInfo[hActorId - 1].alive && FROM_LE_32(as->hActorCode))
+ actorEvent(hActorId, STARTUP, BE_NONE);
+ }
+}
+
+/**
+ * Called at the start of each scene. Start each actor with a code block.
+ * @param ah Scene handle
+ * @param numActors Number of actors
+ * @param bRunScript Flag for whether to run actor scene scripts
+ */
+void StartActors(SCNHANDLE ah, int numActors, bool bRunScript) {
+ int i;
+
+ // Only actors with code blocks got (x, y) re-initialised, so...
+ for (i = 0; i < NumActors; i++) {
+ actorInfo[i].x = actorInfo[i].y = 0;
+ actorInfo[i].mtype = 0;
+ }
+
+ const ACTOR_STRUC *as = (const ACTOR_STRUC *)LockMem(ah);
+ for (i = 0; i < numActors; i++, as++) {
+ StartActor(as, bRunScript);
+ }
+}
+
+/**
+ * Called between scenes, zeroises all actors.
+ */
+void DropActors(void) {
+ for (int i = 0; i < NumActors; i++) {
+ actorInfo[i].actorCode = 0; // No script
+ actorInfo[i].presReel = NULL; // No reel running
+ actorInfo[i].presFilm = 0; // ditto
+ actorInfo[i].presObj = NULL; // No object
+ actorInfo[i].x = 0; // No position
+ actorInfo[i].y = 0; // ditto
+
+ actorInfo[i].talkFilm = 0;
+ actorInfo[i].latestFilm = 0;
+ actorInfo[i].playFilm = 0;
+ actorInfo[i].talking = false;
+ }
+}
+
+/**
+ * Kill actors.
+ * @param ano Actor Id
+ */
+void DisableActor(int ano) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].alive = false; // Record as dead
+ actorInfo[ano - 1].x = actorInfo[ano - 1].y = 0;
+
+ // Kill off moving actor properly
+ pActor = GetMover(ano);
+ if (pActor)
+ KillMActor(pActor);
+}
+
+/**
+ * Enable actors.
+ * @param ano Actor Id
+ */
+void EnableActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ // Re-incarnate only if it's dead, or it's script ran to completion
+ if (!actorInfo[ano - 1].alive || actorInfo[ano - 1].completed) {
+ actorInfo[ano - 1].alive = true;
+ actorInfo[ano - 1].hidden = false;
+ actorInfo[ano - 1].completed = false;
+
+ // Re-run actor's script for this scene
+ if (actorInfo[ano-1].actorCode)
+ actorEvent(ano, STARTUP, BE_NONE);
+ }
+}
+
+/**
+ * Returns the aliveness (to coin a word) of the actor.
+ * @param ano Actor Id
+ */
+bool actorAlive(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].alive;
+}
+
+/**
+ * Define an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void Tag_Actor(int ano, SCNHANDLE tagtext, int tp) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano-1].tagged = true;
+ actorInfo[ano-1].hTag = tagtext;
+ actorInfo[ano-1].tType = tp;
+}
+
+/**
+ * Undefine an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void UnTagActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano-1].tagged = false;
+}
+
+/**
+ * Redefine an actor as being tagged.
+ * @param ano Actor Id
+ * @param tagtext Scene handle
+ * @param tp tType
+ */
+void ReTagActor(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (actorInfo[ano-1].hTag)
+ actorInfo[ano-1].tagged = true;
+}
+
+/**
+ * Returns a tagged actor's tag type. e.g. TAG_Q1TO3
+ * @param ano Actor Id
+ */
+int TagType(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano-1].tType;
+}
+
+/**
+ * Returns handle to tagged actor's tag text
+ * @param ano Actor Id
+ */
+SCNHANDLE GetActorTag(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].hTag;
+}
+
+/**
+ * Called from TagProcess, FirstTaggedActor() resets the index, then
+ * NextTagged Actor is repeatedly called until the caller gets fed up
+ * or there are no more tagged actors to look at.
+ */
+void FirstTaggedActor(void) {
+ ti = 0;
+}
+
+/**
+ * Called from TagProcess, FirstTaggedActor() resets the index, then
+ * NextTagged Actor is repeatedly called until the caller gets fed up
+ * or there are no more tagged actors to look at.
+ */
+int NextTaggedActor(void) {
+ PMACTOR pActor;
+ bool hid;
+
+ do {
+ if (actorInfo[ti].tagged) {
+ pActor = GetMover(ti+1);
+ if (pActor)
+ hid = getMActorHideState(pActor);
+ else
+ hid = actorInfo[ti].hidden;
+
+ if (!hid) {
+ return ++ti;
+ }
+ }
+ } while (++ti < NumActors);
+
+ return 0;
+}
+
+/**
+ * Returns the masking type of the actor.
+ * @param ano Actor Id
+ */
+int32 actorMaskType(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].mtype;
+}
+
+/**
+ * Store/Return the currently stored co-ordinates of the actor.
+ * Delegate the task for moving actors.
+ * @param ano Actor Id
+ * @param x X position
+ * @param y Y position
+ */
+void storeActorPos(int ano, int x, int y) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].x = x;
+ actorInfo[ano - 1].y = y;
+}
+
+void storeActorSteps(int ano, int steps) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].steps = steps;
+}
+
+int getActorSteps(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].steps;
+}
+
+void storeActorZpos(int ano, int z) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].z = z;
+}
+
+
+void GetActorPos(int ano, int *x, int *y) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor
+
+ pActor = GetMover(ano);
+
+ if (pActor)
+ GetMActorPosition(pActor, x, y);
+ else {
+ *x = actorInfo[ano - 1].x;
+ *y = actorInfo[ano - 1].y;
+ }
+}
+
+/**
+ * Returns the position of the mid-top of the actor.
+ * Delegate the task for moving actors.
+ * @param ano Actor Id
+ * @param x Output x
+ * @param y Output y
+ */
+void GetActorMidTop(int ano, int *x, int *y) {
+ // Not used in JAPAN version
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // unknown actor
+
+ pActor = GetMover(ano);
+
+ if (pActor)
+ GetMActorMidTopPosition(pActor, x, y);
+ else if (actorInfo[ano - 1].presObj) {
+ *x = (MultiLeftmost(actorInfo[ano - 1].presObj)
+ + MultiRightmost(actorInfo[ano - 1].presObj)) / 2;
+ *y = MultiHighest(actorInfo[ano - 1].presObj);
+ } else
+ GetActorPos(ano, x, y); // The best we can do!
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorLeft(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiLeftmost(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorRight(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiRightmost(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ * @param ano Actor Id
+ */
+int GetActorTop(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiHighest(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Return the appropriate co-ordinate of the actor.
+ */
+int GetActorBottom(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (!actorInfo[ano - 1].presObj)
+ return 0;
+
+ return MultiLowest(actorInfo[ano - 1].presObj);
+}
+
+/**
+ * Set actor hidden status to true.
+ * For a moving actor, actually hide it.
+ * @param ano Actor Id
+ */
+void HideActor(int ano) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ if (pActor)
+ hideMActor(pActor, 0);
+ else
+ actorInfo[ano - 1].hidden = true;
+}
+
+/**
+ * Hide an actor if it's a moving actor.
+ * @param ano Actor Id
+ * @param sf sf
+ */
+bool HideMovingActor(int ano, int sf) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ if (pActor) {
+ hideMActor(pActor, sf);
+ return true;
+ } else {
+ if (actorInfo[ano - 1].presObj != NULL)
+ MultiHideObject(actorInfo[ano - 1].presObj); // Hidee object
+ return false;
+ }
+}
+
+/**
+ * Unhide an actor if it's a moving actor.
+ * @param ano Actor Id
+ */
+void unHideMovingActor(int ano) {
+ PMACTOR pActor;
+
+ assert((ano > 0 && ano <= NumActors) || ano == LEAD_ACTOR); // illegal actor
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ assert(pActor); // not a moving actor
+
+ unhideMActor(pActor);
+}
+
+/**
+ * Called after a moving actor had been replaced by an splay().
+ * Moves the actor to where the splay() left it, and continues the
+ * actor's walk (if any) from the new co-ordinates.
+ */
+void restoreMovement(int ano) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ // Get moving actor involved
+ pActor = GetMover(ano);
+
+ assert(pActor); // not a moving actor
+
+ if (pActor->objx == actorInfo[ano - 1].x && pActor->objy == actorInfo[ano - 1].y)
+ return;
+
+ pActor->objx = actorInfo[ano - 1].x;
+ pActor->objy = actorInfo[ano - 1].y;
+
+ if (pActor->actorObj)
+ SSetActorDest(pActor);
+}
+
+/**
+ * More properly should be called:
+ * 'store_actor_reel_and/or_film_and/or_object()'
+ */
+void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y) {
+ PMACTOR pActor;
+
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ pActor = GetMover(ano);
+
+ // Only store the reel and film for a moving actor if NOT called from MActorProcess()
+ // (MActorProcess() calls with reel=film=NULL, pobj not NULL)
+ if (!pActor
+ || !(reel == NULL && film == 0 && pobj != NULL)) {
+ actorInfo[ano - 1].presReel = reel; // Store reel
+ actorInfo[ano - 1].presRnum = reelnum; // Store reel number
+ actorInfo[ano - 1].presFilm = film; // Store film
+ actorInfo[ano - 1].presX = x;
+ actorInfo[ano - 1].presY = y;
+ }
+
+ // Only store the object for a moving actor if called from MActorProcess()
+ if (!pActor) {
+ actorInfo[ano - 1].presObj = pobj; // Store object
+ } else if (reel == NULL && film == 0 && pobj != NULL) {
+ actorInfo[ano - 1].presObj = pobj; // Store object
+ }
+}
+
+/**
+ * Return the present reel/film of the actor.
+ */
+const FREEL *actorReel(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].presReel; // the present reel
+}
+
+/***************************************************************************/
+
+void setActorPlayFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].playFilm = film;
+}
+
+SCNHANDLE getActorPlayFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].playFilm;
+}
+
+void setActorTalkFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].talkFilm = film;
+}
+
+SCNHANDLE getActorTalkFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].talkFilm;
+}
+
+void setActorTalking(int ano, bool tf) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].talking = tf;;
+}
+
+bool isActorTalking(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].talking;
+}
+
+void setActorLatestFilm(int ano, SCNHANDLE film) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].latestFilm = film;
+ actorInfo[ano - 1].steps = 0;
+}
+
+SCNHANDLE getActorLatestFilm(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].latestFilm;
+}
+
+/***************************************************************************/
+
+void updateActorEsc(int ano, bool escOn, int escEvent) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ actorInfo[ano - 1].escOn = escOn;
+ actorInfo[ano - 1].escEv = escEvent;
+}
+
+bool actorEsc(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].escOn;
+}
+
+int actorEev(int ano) {
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ return actorInfo[ano - 1].escEv;
+}
+
+/**
+ * Guess what these do.
+ */
+int AsetZPos(OBJECT *pObj, int y, int32 z) {
+ int zPos;
+
+ z += z ? -1 : 0;
+
+ zPos = y + (z << 10);
+ MultiSetZPosition(pObj, zPos);
+ return zPos;
+}
+
+/**
+ * Guess what these do.
+ */
+void MAsetZPos(PMACTOR pActor, int y, int32 zFactor) {
+ if (!pActor->aHidden)
+ AsetZPos(pActor->actorObj, y, zFactor);
+}
+
+/**
+ * Stores actor's attributes.
+ * Currently only the speech colours.
+ */
+void storeActorAttr(int ano, int r1, int g1, int b1) {
+ assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number
+
+ if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure
+ if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits
+ if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // }
+
+ if (ano == -1)
+ defaultColour = RGB(r1, g1, b1);
+ else
+ actorInfo[ano - 1].tColour = RGB(r1, g1, b1);
+}
+
+/**
+ * Get the actor's stored speech colour.
+ * @param ano Actor Id
+ */
+COLORREF getActorTcol(int ano) {
+ // Not used in JAPAN version
+ assert(ano > 0 && ano <= NumActors); // illegal actor number
+
+ if (actorInfo[ano - 1].tColour)
+ return actorInfo[ano - 1].tColour;
+ else
+ return defaultColour;
+}
+
+/**
+ * Store relevant information pertaining to currently existing actors.
+ */
+int SaveActors(SAVED_ACTOR *sActorInfo) {
+ int i, j;
+
+ for (i = 0, j = 0; i < NumActors; i++) {
+ if (actorInfo[i].presObj != NULL) {
+ assert(j < MAX_SAVED_ACTORS); // Saving too many actors
+
+// sActorInfo[j].hidden = actorInfo[i].hidden;
+ sActorInfo[j].bAlive = actorInfo[i].alive;
+// sActorInfo[j].x = (short)actorInfo[i].x;
+// sActorInfo[j].y = (short)actorInfo[i].y;
+ sActorInfo[j].z = (short)actorInfo[i].z;
+// sActorInfo[j].presReel = actorInfo[i].presReel;
+ sActorInfo[j].presRnum = (short)actorInfo[i].presRnum;
+ sActorInfo[j].presFilm = actorInfo[i].presFilm;
+ sActorInfo[j].presX = (short)actorInfo[i].presX;
+ sActorInfo[j].presY = (short)actorInfo[i].presY;
+ sActorInfo[j].actorID = (short)(i+1);
+ j++;
+ }
+ }
+
+ return j;
+}
+
+void setactorson(void) {
+ bActorsOn = true;
+}
+
+void ActorsLife(int ano, bool bAlive) {
+ assert((ano > 0 && ano <= NumActors) || ano == -1); // illegal actor number
+
+ actorInfo[ano-1].alive = bAlive;
+}
+
+
+void syncAllActorsAlive(Serializer &s) {
+ for (int i = 0; i < MAX_SAVED_ALIVES; i++) {
+ s.syncAsByte(actorInfo[i].alive);
+ s.syncAsByte(actorInfo[i].tagged);
+ s.syncAsByte(actorInfo[i].tType);
+ s.syncAsUint32LE(actorInfo[i].hTag);
+ }
+}
+
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/actors.h b/engines/tinsel/actors.h
new file mode 100644
index 0000000000..91f54519d5
--- /dev/null
+++ b/engines/tinsel/actors.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Prototypes of actor functions
+ */
+
+#ifndef TINSEL_ACTOR_H // prevent multiple includes
+#define TINSEL_ACTOR_H
+
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/events.h" // for USER_EVENT
+#include "tinsel/palette.h" // for COLORREF
+
+namespace Tinsel {
+
+struct FREEL;
+struct INT_CONTEXT;
+struct MACTOR;
+struct OBJECT;
+
+
+/*----------------------------------------------------------------------*/
+
+void RegisterActors(int num);
+void FreeActors(void);
+void setleadid(int rid);
+int LeadId(void);
+void StartActors(SCNHANDLE ah, int numActors, bool bRunScript);
+void DropActors(void); // No actor reels running
+void DisableActor(int actor);
+void EnableActor(int actor);
+void Tag_Actor(int ano, SCNHANDLE tagtext, int tp);
+void UnTagActor(int ano);
+void ReTagActor(int ano);
+int TagType(int ano);
+bool actorAlive(int ano);
+int32 actorMaskType(int ano);
+void GetActorPos(int ano, int *x, int *y);
+void SetActorPos(int ano, int x, int y);
+void GetActorMidTop(int ano, int *x, int *y);
+int GetActorLeft(int ano);
+int GetActorRight(int ano);
+int GetActorTop(int ano);
+int GetActorBottom(int ano);
+void HideActor(int ano);
+bool HideMovingActor(int id, int sf);
+void unHideMovingActor(int id);
+void restoreMovement(int id);
+void storeActorReel(int ano, const FREEL *reel, SCNHANDLE film, OBJECT *pobj, int reelnum, int x, int y);
+const FREEL *actorReel(int ano);
+SCNHANDLE actorFilm(int ano);
+
+void setActorPlayFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorPlayFilm(int ano);
+void setActorTalkFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorTalkFilm(int ano);
+void setActorTalking(int ano, bool tf);
+bool isActorTalking(int ano);
+void setActorLatestFilm(int ano, SCNHANDLE film);
+SCNHANDLE getActorLatestFilm(int ano);
+
+void updateActorEsc(int ano, bool escOn, int escEv);
+bool actorEsc(int ano);
+int actorEev(int ano);
+void storeActorPos(int ano, int x, int y);
+void storeActorSteps(int ano, int steps);
+int getActorSteps(int ano);
+void storeActorZpos(int ano, int z);
+SCNHANDLE GetActorTag(int ano);
+void FirstTaggedActor(void);
+int NextTaggedActor(void);
+int AsetZPos(OBJECT *pObj, int y, int32 zFactor);
+void MAsetZPos(MACTOR *pActor, int y, int32 zFactor);
+void actorEvent(int ano, USER_EVENT event, BUTEVENT be);
+
+void storeActorAttr(int ano, int r1, int g1, int b1);
+COLORREF getActorTcol(int ano);
+
+void setactorson(void);
+
+void ActorsLife(int id, bool bAlive);
+
+/*----------------------------------------------------------------------*/
+
+struct SAVED_ACTOR {
+ short actorID;
+ short z;
+ bool bAlive;
+ SCNHANDLE presFilm; //!< the film that reel belongs to
+ short presRnum; //!< the present reel number
+ short presX, presY;
+};
+
+int SaveActors(SAVED_ACTOR *sActorInfo);
+
+
+void RestoreActorProcess(int id, INT_CONTEXT *pic);
+
+
+/*----------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_ACTOR_H */
diff --git a/engines/tinsel/anim.cpp b/engines/tinsel/anim.cpp
new file mode 100644
index 0000000000..95d834d88a
--- /dev/null
+++ b/engines/tinsel/anim.cpp
@@ -0,0 +1,404 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains utilities to handle object animation.
+ */
+
+#include "tinsel/anim.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/sched.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+/** Animation script commands */
+enum {
+ ANI_END = 0, //!< end of animation script
+ ANI_JUMP = 1, //!< animation script jump
+ ANI_HFLIP = 2, //!< flip animated object horizontally
+ ANI_VFLIP = 3, //!< flip animated object vertically
+ ANI_HVFLIP = 4, //!< flip animated object in both directions
+ ANI_ADJUSTX = 5, //!< adjust animated object x animation point
+ ANI_ADJUSTY = 6, //!< adjust animated object y animation point
+ ANI_ADJUSTXY = 7, //!< adjust animated object x & y animation points
+ ANI_NOSLEEP = 8, //!< do not sleep for this frame
+ ANI_CALL = 9, //!< call routine
+ ANI_HIDE = 10 //!< hide animated object
+};
+
+/** animation script command possibilities */
+union ANI_SCRIPT {
+ int32 op; //!< treat as an opcode or operand
+ uint32 hFrame; //!< treat as a animation frame handle
+};
+
+/**
+ * Advance to next frame routine.
+ * @param pAnim Animation data structure
+ */
+SCRIPTSTATE DoNextFrame(ANIM *pAnim) {
+ // get a pointer to the script
+ const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript);
+
+ while (1) { // repeat until a real image
+
+ switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) {
+ case ANI_END: // end of animation script
+
+ // move to next opcode
+ pAnim->scriptIndex++;
+
+ // indicate script has finished
+ return ScriptFinished;
+
+ case ANI_JUMP: // do animation jump
+
+ // move to jump address
+ pAnim->scriptIndex++;
+
+ // jump to new frame position
+ pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // go fetch a real image
+ break;
+
+ case ANI_HFLIP: // flip animated object horizontally
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_VFLIP: // flip animated object vertically
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiVerticalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_HVFLIP: // flip animated object in both directions
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ MultiVerticalFlip(pAnim->pObject);
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTX: // adjust animated object x animation point
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTY: // adjust animated object y animation point
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op));
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_ADJUSTXY: // adjust animated object x & y animation points
+ {
+ int x, y;
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+ x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+ y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ MultiAdjustXY(pAnim->pObject, x, y);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+ }
+
+ case ANI_NOSLEEP: // do not sleep for this frame
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // indicate not to sleep
+ return ScriptNoSleep;
+
+ case ANI_CALL: // call routine
+
+ // move to function address
+ pAnim->scriptIndex++;
+
+ // make function call
+
+ // REMOVED BUGGY CODE
+ // pFunc is a function pointer that's part of a union and is assumed to be 32-bits.
+ // There is no known place where a function pointer is stored inside the animation
+ // scripts, something which wouldn't have worked anyway. Having played through the
+ // entire game, there hasn't been any occurence of this case, so just error out here
+ // in case we missed something (highly unlikely though)
+ error("ANI_CALL opcode encountered! Please report this error to the ScummVM team");
+ //(*pAni[pAnim->scriptIndex].pFunc)(pAnim);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // go fetch a real image
+ break;
+
+ case ANI_HIDE: // hide animated object
+
+ MultiHideObject(pAnim->pObject);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // dont skip a sleep
+ return ScriptSleep;
+
+ default: // must be an actual animation frame handle
+
+ // set objects new animation frame
+ pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame);
+
+ // re-shape the object
+ MultiReshape(pAnim->pObject);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ // dont skip a sleep
+ return ScriptSleep;
+ }
+ }
+}
+
+/**
+ * Init a ANIM structure for single stepping through a animation script.
+ * @param pAnim Animation data structure
+ * @param pAniObj Object to animate
+ * @param hNewScript Script of multipart frames
+ * @param aniSpeed Sets speed of animation in frames
+ */
+void InitStepAnimScript(ANIM *pAnim, OBJECT *pAniObj, SCNHANDLE hNewScript, int aniSpeed) {
+ OBJECT *pObj; // multi-object list iterator
+
+ pAnim->aniDelta = 1; // will animate on next call to NextAnimRate
+ pAnim->pObject = pAniObj; // set object to animate
+ pAnim->hScript = hNewScript; // set animation script
+ pAnim->scriptIndex = 0; // start of script
+ pAnim->aniRate = aniSpeed; // set speed of animation
+
+ // reset flip flags for the object - let the script do the flipping
+ for (pObj = pAniObj; pObj != NULL; pObj = pObj->pSlave) {
+ AnimateObjectFlags(pObj, pObj->flags & ~(DMA_FLIPH | DMA_FLIPV),
+ pObj->hImg);
+ }
+}
+
+/**
+ * Execute the next command in a animation script.
+ * @param pAnim Animation data structure
+ */
+SCRIPTSTATE StepAnimScript(ANIM *pAnim) {
+ SCRIPTSTATE state;
+
+ if (--pAnim->aniDelta == 0) {
+ // re-init animation delta counter
+ pAnim->aniDelta = pAnim->aniRate;
+
+ // move to next frame
+ while ((state = DoNextFrame(pAnim)) == ScriptNoSleep)
+ ;
+
+ return state;
+ }
+
+ // indicate calling task should sleep
+ return ScriptSleep;
+}
+
+/**
+ * Skip the specified number of frames.
+ * @param pAnim Animation data structure
+ * @param numFrames Number of frames to skip
+ */
+void SkipFrames(ANIM *pAnim, int numFrames) {
+ // get a pointer to the script
+ const ANI_SCRIPT *pAni = (const ANI_SCRIPT *)LockMem(pAnim->hScript);
+
+ if (numFrames <= 0)
+ // do nothing
+ return;
+
+ while (1) { // repeat until a real image
+
+ switch ((int32)FROM_LE_32(pAni[pAnim->scriptIndex].op)) {
+ case ANI_END: // end of animation script
+ // going off the end is probably a error
+ error("SkipFrames(): formally 'assert(0)!'");
+ break;
+
+ case ANI_JUMP: // do animation jump
+
+ // move to jump address
+ pAnim->scriptIndex++;
+
+ // jump to new frame position
+ pAnim->scriptIndex += (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+ break;
+
+ case ANI_HFLIP: // flip animated object horizontally
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ break;
+
+ case ANI_VFLIP: // flip animated object vertically
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiVerticalFlip(pAnim->pObject);
+ break;
+
+ case ANI_HVFLIP: // flip animated object in both directions
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ MultiHorizontalFlip(pAnim->pObject);
+ MultiVerticalFlip(pAnim->pObject);
+ break;
+
+ case ANI_ADJUSTX: // adjust animated object x animation point
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op), 0);
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_ADJUSTY: // adjust animated object y animation point
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+
+ MultiAdjustXY(pAnim->pObject, 0, (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op));
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_ADJUSTXY: // adjust animated object x & y animation points
+ {
+ int x, y;
+
+ // move to x adjustment operand
+ pAnim->scriptIndex++;
+ x = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ // move to y adjustment operand
+ pAnim->scriptIndex++;
+ y = (int32)FROM_LE_32(pAni[pAnim->scriptIndex].op);
+
+ MultiAdjustXY(pAnim->pObject, x, y);
+
+ // next opcode
+ pAnim->scriptIndex++;
+
+ break;
+ }
+
+ case ANI_NOSLEEP: // do not sleep for this frame
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ case ANI_CALL: // call routine
+
+ // skip function address
+ pAnim->scriptIndex += 2;
+ break;
+
+ case ANI_HIDE: // hide animated object
+
+ // next opcode
+ pAnim->scriptIndex++;
+ break;
+
+ default: // must be an actual animation frame handle
+
+ // one less frame
+ if (numFrames-- > 0) {
+ // next opcode
+ pAnim->scriptIndex++;
+ } else {
+ // set objects new animation frame
+ pAnim->pObject->hShape = FROM_LE_32(pAni[pAnim->scriptIndex].hFrame);
+
+ // re-shape the object
+ MultiReshape(pAnim->pObject);
+
+ // we have skipped to the correct place
+ return;
+ }
+ break;
+ }
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/anim.h b/engines/tinsel/anim.h
new file mode 100644
index 0000000000..5b25292a6b
--- /dev/null
+++ b/engines/tinsel/anim.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Object animation definitions
+ */
+
+#ifndef TINSEL_ANIM_H // prevent multiple includes
+#define TINSEL_ANIM_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+struct OBJECT;
+
+/** animation structure */
+struct ANIM {
+ int aniRate; //!< animation speed
+ int aniDelta; //!< animation speed delta counter
+ OBJECT *pObject; //!< object to animate (assumed to be multi-part)
+ uint32 hScript; //!< animation script handle
+ int scriptIndex; //!< current position in animation script
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Anim Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+/** states for DoNextFrame */
+enum SCRIPTSTATE {ScriptFinished, ScriptNoSleep, ScriptSleep};
+
+SCRIPTSTATE DoNextFrame( // Execute the next animation frame of a animation script
+ ANIM *pAnim); // animation data structure
+
+void InitStepAnimScript( // Init a ANIM struct for single stepping through a animation script
+ ANIM *pAnim, // animation data structure
+ OBJECT *pAniObj, // object to animate
+ SCNHANDLE hNewScript, // handle to script of multipart frames
+ int aniSpeed); // sets speed of animation in frames
+
+SCRIPTSTATE StepAnimScript( // Execute the next command in a animation script
+ ANIM *pAnim); // animation data structure
+
+void SkipFrames( // Skip the specified number of frames
+ ANIM *pAnim, // animation data structure
+ int numFrames); // number of frames to skip
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_ANIM_H
diff --git a/engines/tinsel/background.cpp b/engines/tinsel/background.cpp
new file mode 100644
index 0000000000..91d21b4e0b
--- /dev/null
+++ b/engines/tinsel/background.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Background handling code.
+ */
+
+#include "tinsel/background.h"
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h"
+#include "tinsel/sched.h" // process sheduler defs
+#include "tinsel/object.h"
+#include "tinsel/pid.h" // process identifiers
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+// current background
+BACKGND *pCurBgnd = NULL;
+
+/**
+ * Called to initialise a background.
+ * @param pBgnd Pointer to data struct for current background
+ */
+
+void InitBackground(BACKGND *pBgnd) {
+ int i; // playfield counter
+ PLAYFIELD *pPlayfield; // pointer to current playfield
+
+ // set current background
+ pCurBgnd = pBgnd;
+
+ // init background sky colour
+ SetBgndColour(pBgnd->rgbSkyColour);
+
+ // start of playfield array
+ pPlayfield = pBgnd->fieldArray;
+
+ // for each background playfield
+ for (i = 0; i < pBgnd->numPlayfields; i++, pPlayfield++) {
+ // init playfield pos
+ pPlayfield->fieldX = intToFrac(pBgnd->ptInitWorld.x);
+ pPlayfield->fieldY = intToFrac(pBgnd->ptInitWorld.y);
+
+ // no scrolling
+ pPlayfield->fieldXvel = intToFrac(0);
+ pPlayfield->fieldYvel = intToFrac(0);
+
+ // clear playfield display list
+ pPlayfield->pDispList = NULL;
+
+ // clear playfield moved flag
+ pPlayfield->bMoved = false;
+ }
+}
+
+/**
+ * Sets the xy position of the specified playfield in the current background.
+ * @param which Which playfield
+ * @param newXpos New x position
+ * @param newYpos New y position
+ */
+
+void PlayfieldSetPos(int which, int newXpos, int newYpos) {
+ PLAYFIELD *pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // set new integer position
+ pPlayfield->fieldX = intToFrac(newXpos);
+ pPlayfield->fieldY = intToFrac(newYpos);
+
+ // set moved flag
+ pPlayfield->bMoved = true;
+}
+
+/**
+ * Returns the xy position of the specified playfield in the current background.
+ * @param which Which playfield
+ * @param pXpos Returns current x position
+ * @param pYpos Returns current y position
+ */
+
+void PlayfieldGetPos(int which, int *pXpos, int *pYpos) {
+ PLAYFIELD *pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // get current integer position
+ *pXpos = fracToInt(pPlayfield->fieldX);
+ *pYpos = fracToInt(pPlayfield->fieldY);
+}
+
+/**
+ * Returns the display list for the specified playfield.
+ * @param which Which playfield
+ */
+
+OBJECT *GetPlayfieldList(int which) {
+ PLAYFIELD *pPlayfield; // pointer to relavent playfield
+
+ // make sure there is a background
+ assert(pCurBgnd != NULL);
+
+ // make sure the playfield number is in range
+ assert(which >= 0 && which < pCurBgnd->numPlayfields);
+
+ // get playfield pointer
+ pPlayfield = pCurBgnd->fieldArray + which;
+
+ // return the display list pointer for this playfield
+ return (OBJECT *)&pPlayfield->pDispList;
+}
+
+/**
+ * Draws all the playfield object lists for the current background.
+ * The playfield velocity is added to the playfield position in order
+ * to scroll each playfield before it is drawn.
+ */
+
+void DrawBackgnd(void) {
+ int i; // playfield counter
+ PLAYFIELD *pPlay; // playfield pointer
+ int prevX, prevY; // save interger part of position
+ Common::Point ptWin; // window top left
+
+ if (pCurBgnd == NULL)
+ return; // no current background
+
+ // scroll each background playfield
+ for (i = 0; i < pCurBgnd->numPlayfields; i++) {
+ // get pointer to correct playfield
+ pPlay = pCurBgnd->fieldArray + i;
+
+ // save integer part of position
+ prevX = fracToInt(pPlay->fieldX);
+ prevY = fracToInt(pPlay->fieldY);
+
+ // update scrolling
+ pPlay->fieldX += pPlay->fieldXvel;
+ pPlay->fieldY += pPlay->fieldYvel;
+
+ // convert fixed point window pos to a int
+ ptWin.x = fracToInt(pPlay->fieldX);
+ ptWin.y = fracToInt(pPlay->fieldY);
+
+ // set the moved flag if the playfield has moved
+ if (prevX != ptWin.x || prevY != ptWin.y)
+ pPlay->bMoved = true;
+
+ // sort the display list for this background - just in case somebody has changed object Z positions
+ SortObjectList((OBJECT *)&pPlay->pDispList);
+
+ // generate clipping rects for all objects that have moved etc.
+ FindMovingObjects((OBJECT *)&pPlay->pDispList, &ptWin,
+ &pPlay->rcClip, false, pPlay->bMoved);
+
+ // clear playfield moved flag
+ pPlay->bMoved = false;
+ }
+
+ // merge the clipping rectangles
+ MergeClipRect();
+
+ // redraw all playfields within the clipping rectangles
+ const RectList &clipRects = GetClipRects();
+ for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
+ // clear the clip rectangle on the virtual screen
+ // for each background playfield
+ for (i = 0; i < pCurBgnd->numPlayfields; i++) {
+ Common::Rect rcPlayClip; // clip rect for this playfield
+
+ // get pointer to correct playfield
+ pPlay = pCurBgnd->fieldArray + i;
+
+ // convert fixed point window pos to a int
+ ptWin.x = fracToInt(pPlay->fieldX);
+ ptWin.y = fracToInt(pPlay->fieldY);
+
+ if (IntersectRectangle(rcPlayClip, pPlay->rcClip, *r))
+ // redraw all objects within this clipping rect
+ UpdateClipRect((OBJECT *)&pPlay->pDispList,
+ &ptWin, &rcPlayClip);
+ }
+ }
+
+ // transfer any new palettes to the video DAC
+ PalettesToVideoDAC();
+
+ // update the screen within the clipping rectangles
+ for (RectList::const_iterator r = clipRects.begin(); r != clipRects.end(); ++r) {
+ UpdateScreenRect(*r);
+ }
+
+ // delete all the clipping rectangles
+ ResetClipRect();
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/background.h b/engines/tinsel/background.h
new file mode 100644
index 0000000000..7b8d099446
--- /dev/null
+++ b/engines/tinsel/background.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used for handling backgrounds
+ */
+
+#ifndef TINSEL_BACKGND_H // prevent multiple includes
+#define TINSEL_BACKGND_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/palette.h" // palette definitions
+#include "common/frac.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+
+/** Scrolling padding. Needed because scroll process does not normally run on every frame */
+enum {
+ SCROLLX_PAD = 64,
+ SCROLLY_PAD = 64
+};
+
+/** When module BLK_INFO list is this long, switch from a binary to linear search */
+#define LINEAR_SEARCH 5
+
+/** background playfield structure - a playfield is a container for modules */
+struct PLAYFIELD {
+ OBJECT *pDispList; //!< object display list for this playfield
+ frac_t fieldX; //!< current world x position of playfield
+ frac_t fieldY; //!< current world y position of playfield
+ frac_t fieldXvel; //!< current x velocity of playfield
+ frac_t fieldYvel; //!< current y velocity of playfield
+ Common::Rect rcClip; //!< clip rectangle for this playfield
+ bool bMoved; //!< set when playfield has moved
+};
+
+/** multi-playfield background structure - a backgnd is a container of playfields */
+struct BACKGND {
+ COLORREF rgbSkyColour; //!< background sky colour
+ Common::Point ptInitWorld; //!< initial world position
+ Common::Rect rcScrollLimits; //!< scroll limits
+ int refreshRate; //!< background update process refresh rate
+ frac_t *pXscrollTable; //!< pointer to x direction scroll table for this background
+ frac_t *pYscrollTable; //!< pointer to y direction scroll table for this background
+ int numPlayfields; //!< number of playfields for this background
+ PLAYFIELD *fieldArray; //!< pointer to array of all playfields for this background
+ bool bAutoErase; //!< when set - screen is cleared before anything is plotted (unused)
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Background Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void InitBackground( // called to initialise a background
+ BACKGND *pBgnd); // pointer to data struct for current background
+
+void StopBgndScrolling(void); // Stops all background playfields from scrolling
+
+void PlayfieldSetPos( // Sets the xy position of the specified playfield in the current background
+ int which, // which playfield
+ int newXpos, // new x position
+ int newYpos); // new y position
+
+void PlayfieldGetPos( // Returns the xy position of the specified playfield in the current background
+ int which, // which playfield
+ int *pXpos, // returns current x position
+ int *pYpos); // returns current y position
+
+OBJECT *GetPlayfieldList( // Returns the display list for the specified playfield
+ int which); // which playfield
+
+void KillPlayfieldList( // Kills all the objects on the display list for the specified playfield
+ int which); // which playfield
+
+void DrawBackgnd(void); // Draws all playfields for the current background
+
+void RedrawBackgnd(void); // Completely redraws all the playfield object lists for the current background
+
+SCNHANDLE BackPal(void);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_BACKGND_H
diff --git a/engines/tinsel/bg.cpp b/engines/tinsel/bg.cpp
new file mode 100644
index 0000000000..9c1e5f1540
--- /dev/null
+++ b/engines/tinsel/bg.cpp
@@ -0,0 +1,189 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Plays the background film of a scene.
+ */
+
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/dw.h"
+#include "tinsel/faders.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pcode.h" // CONTROL_STARTOFF
+#include "tinsel/pid.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h" // For control()
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static SCNHANDLE BackPalette = 0; // Background's palette
+static OBJECT *pBG = 0; // The main picture's object.
+static int BGspeed = 0;
+static SCNHANDLE BgroundHandle = 0; // Current scene handle - stored in case of Save_Scene()
+static bool DoFadeIn = false;
+static ANIM thisAnim; // used by BGmainProcess()
+
+/**
+ * BackPal
+ */
+SCNHANDLE BackPal(void) {
+ return BackPalette;
+}
+
+/**
+ * SetDoFadeIn
+*/
+void SetDoFadeIn(bool tf) {
+ DoFadeIn = tf;
+}
+
+/**
+ * Called before scene change.
+ */
+void DropBackground(void) {
+ pBG = NULL; // No background
+ BackPalette = 0; // No background palette
+}
+
+/**
+ * Return the width of the current background.
+ */
+int BackgroundWidth(void) {
+ assert(pBG);
+ return MultiRightmost(pBG) + 1;
+}
+
+/**
+ * Return the height of the current background.
+ */
+int BackgroundHeight(void) {
+ assert(pBG);
+ return MultiLowest(pBG) + 1;
+}
+
+/**
+ * Run main animation that comprises the scene background.
+ */
+static void BGmainProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+
+ // get the stuff copied to process when it was created
+ pfr = (const FREEL *)param;
+
+ if (pBG == NULL) {
+ /*** At start of scene ***/
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj));
+
+ // Initialise and insert the object, and initialise its script.
+ pBG = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pBG);
+ InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed);
+
+ if (DoFadeIn) {
+ FadeInFast(NULL);
+ DoFadeIn = false;
+ }
+
+ while (StepAnimScript(&thisAnim) != ScriptFinished)
+ CORO_SLEEP(1);
+
+ error("Background animation has finished!");
+ } else {
+ // New background during scene
+
+ // Just re-initialise the script.
+ InitStepAnimScript(&thisAnim, pBG, FROM_LE_32(pfr->script), BGspeed);
+ StepAnimScript(&thisAnim);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * setBackPal()
+ */
+void setBackPal(SCNHANDLE hPal) {
+ BackPalette = hPal;
+
+ fettleFontPal(BackPalette);
+ CreateTranslucentPalette(BackPalette);
+}
+
+void ChangePalette(SCNHANDLE hPal) {
+ SwapPalette(FindPalette(BackPalette), hPal);
+
+ setBackPal(hPal);
+}
+
+/**
+ * Given the scene background film, extracts the palette handle for
+ * everything else's use, then starts a display process for each reel
+ * in the film.
+ * @param bfilm Scene background film
+ */
+void startupBackground(SCNHANDLE bfilm) {
+ const FILM *pfilm;
+ IMAGE *pim;
+
+ BgroundHandle = bfilm; // Save handle in case of Save_Scene()
+
+ pim = GetImageFromFilm(bfilm, 0, NULL, NULL, &pfilm);
+ setBackPal(FROM_LE_32(pim->hImgPal));
+
+ // Extract the film speed
+ BGspeed = ONE_SECOND / FROM_LE_32(pfilm->frate);
+
+ if (pBG == NULL)
+ control(CONTROL_STARTOFF); // New feature - start scene with control off
+
+ // Start display process for each reel in the film
+ assert(FROM_LE_32(pfilm->numreels) == 1); // Multi-reeled backgrounds withdrawn
+ g_scheduler->createProcess(PID_REEL, BGmainProcess, &pfilm->reels[0], sizeof(FREEL));
+}
+
+/**
+ * Return the current scene handle.
+ */
+SCNHANDLE GetBgroundHandle(void) {
+ return BgroundHandle;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cliprect.cpp b/engines/tinsel/cliprect.cpp
new file mode 100644
index 0000000000..b67ae7b17f
--- /dev/null
+++ b/engines/tinsel/cliprect.cpp
@@ -0,0 +1,313 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the clipping rectangle code.
+ */
+
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h" // normal object drawing
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+
+namespace Tinsel {
+
+/** list of all clip rectangles */
+static RectList s_rectList;
+
+/**
+ * Resets the clipping rectangle allocator.
+ */
+void ResetClipRect(void) {
+ s_rectList.clear();
+}
+
+/**
+ * Allocate a clipping rectangle from the free list.
+ * @param pClip clip rectangle dimensions to allocate
+ */
+void AddClipRect(const Common::Rect &pClip) {
+ s_rectList.push_back(pClip);
+}
+
+const RectList &GetClipRects() {
+ return s_rectList;
+}
+
+/**
+ * Creates the intersection of two rectangles.
+ * Returns True if there is a intersection.
+ * @param pDest Pointer to destination rectangle that is to receive the intersection
+ * @param pSrc1 Pointer to a source rectangle
+ * @param pSrc2 Pointer to a source rectangle
+ */
+bool IntersectRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ pDest.left = MAX(pSrc1.left, pSrc2.left);
+ pDest.top = MAX(pSrc1.top, pSrc2.top);
+ pDest.right = MIN(pSrc1.right, pSrc2.right);
+ pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom);
+
+ return !pDest.isEmpty();
+}
+
+/**
+ * Creates the union of two rectangles.
+ * Returns True if there is a union.
+ * @param pDest destination rectangle that is to receive the new union
+ * @param pSrc1 a source rectangle
+ * @param pSrc2 a source rectangle
+ */
+bool UnionRectangle(Common::Rect &pDest, const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ pDest.left = MIN(pSrc1.left, pSrc2.left);
+ pDest.top = MIN(pSrc1.top, pSrc2.top);
+ pDest.right = MAX(pSrc1.right, pSrc2.right);
+ pDest.bottom = MAX(pSrc1.bottom, pSrc2.bottom);
+
+ return !pDest.isEmpty();
+}
+
+/**
+ * Check if the two rectangles are next to each other.
+ * @param pSrc1 a source rectangle
+ * @param pSrc2 a source rectangle
+ */
+static bool LooseIntersectRectangle(const Common::Rect &pSrc1, const Common::Rect &pSrc2) {
+ Common::Rect pDest;
+
+ pDest.left = MAX(pSrc1.left, pSrc2.left);
+ pDest.top = MAX(pSrc1.top, pSrc2.top);
+ pDest.right = MIN(pSrc1.right, pSrc2.right);
+ pDest.bottom = MIN(pSrc1.bottom, pSrc2.bottom);
+
+ return pDest.isValidRect();
+}
+
+/**
+ * Adds velocities and creates clipping rectangles for all the
+ * objects that have moved on the specified object list.
+ * @param pObjList Playfield display list to draw
+ * @param pWin Playfield window top left position
+ * @param pClip Playfield clipping rectangle
+ * @param bNoVelocity When reset, objects pos is updated with velocity
+ * @param bScrolled) When set, playfield has scrolled
+ */
+void FindMovingObjects(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip, bool bNoVelocity, bool bScrolled) {
+ OBJECT *pObj; // object list traversal pointer
+
+ for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) {
+ if (!bNoVelocity) {
+ // we want to add velocities to objects position
+
+ if (bScrolled) {
+ // this playfield has scrolled
+
+ // indicate change
+ pObj->flags |= DMA_CHANGED;
+ }
+ }
+
+ if ((pObj->flags & DMA_CHANGED) || // object changed
+ HasPalMoved(pObj->pPal)) { // or palette moved
+ // object has changed in some way
+
+ Common::Rect rcClip; // objects clipped bounding rectangle
+ Common::Rect rcObj; // objects bounding rectangle
+
+ // calc intersection of objects previous bounding rectangle
+ // NOTE: previous position is in screen co-ords
+ if (IntersectRectangle(rcClip, pObj->rcPrev, *pClip)) {
+ // previous position is within clipping rect
+ AddClipRect(rcClip);
+ }
+
+ // calc objects current bounding rectangle
+ if (pObj->flags & DMA_ABS) {
+ // object position is absolute
+ rcObj.left = fracToInt(pObj->xPos);
+ rcObj.top = fracToInt(pObj->yPos);
+ } else {
+ // object position is relative to window
+ rcObj.left = fracToInt(pObj->xPos) - pWin->x;
+ rcObj.top = fracToInt(pObj->yPos) - pWin->y;
+ }
+ rcObj.right = rcObj.left + pObj->width;
+ rcObj.bottom = rcObj.top + pObj->height;
+
+ // calc intersection of object with clipping rect
+ if (IntersectRectangle(rcClip, rcObj, *pClip)) {
+ // current position is within clipping rect
+ AddClipRect(rcClip);
+
+ // update previous position
+ pObj->rcPrev = rcClip;
+ } else {
+ // clear previous position
+ pObj->rcPrev = Common::Rect();
+ }
+
+ // clear changed flag
+ pObj->flags &= ~DMA_CHANGED;
+ }
+ }
+}
+
+/**
+ * Merges any clipping rectangles that overlap to try and reduce
+ * the total number of clip rectangles.
+ */
+void MergeClipRect() {
+ if (s_rectList.size() <= 1)
+ return;
+
+ RectList::iterator rOuter, rInner;
+
+ for (rOuter = s_rectList.begin(); rOuter != s_rectList.end(); ++rOuter) {
+ rInner = rOuter;
+ while (++rInner != s_rectList.end()) {
+
+ if (LooseIntersectRectangle(*rOuter, *rInner)) {
+ // these two rectangles overlap or
+ // are next to each other - merge them
+
+ UnionRectangle(*rOuter, *rOuter, *rInner);
+
+ // remove the inner rect from the list
+ s_rectList.erase(rInner);
+
+ // move back to beginning of list
+ rInner = rOuter;
+ }
+ }
+ }
+}
+
+/**
+ * Redraws all objects within this clipping rectangle.
+ * @param pObjList Object list to draw
+ * @param pWin Window top left position
+ * @param pClip Pointer to clip rectangle
+ */
+void UpdateClipRect(OBJECT *pObjList, Common::Point *pWin, Common::Rect *pClip) {
+ int x, y, right, bottom; // object corners
+ int hclip, vclip; // total size of object clipping
+ DRAWOBJECT currentObj; // filled in to draw the current object in list
+ OBJECT *pObj; // object list iterator
+
+ // Initialise the fields of the drawing object to empty
+ memset(&currentObj, 0, sizeof(DRAWOBJECT));
+
+ for (pObj = pObjList->pNext; pObj != NULL; pObj = pObj->pNext) {
+ if (pObj->flags & DMA_ABS) {
+ // object position is absolute
+ x = fracToInt(pObj->xPos);
+ y = fracToInt(pObj->yPos);
+ } else {
+ // object position is relative to window
+ x = fracToInt(pObj->xPos) - pWin->x;
+ y = fracToInt(pObj->yPos) - pWin->y;
+ }
+
+ // calc object right
+ right = x + pObj->width;
+ if (right < 0)
+ // totally clipped if negative
+ continue;
+
+ // calc object bottom
+ bottom = y + pObj->height;
+ if (bottom < 0)
+ // totally clipped if negative
+ continue;
+
+ // bottom clip = low right y - clip low right y
+ currentObj.botClip = bottom - pClip->bottom;
+ if (currentObj.botClip < 0) {
+ // negative - object is not clipped
+ currentObj.botClip = 0;
+ }
+
+ // right clip = low right x - clip low right x
+ currentObj.rightClip = right - pClip->right;
+ if (currentObj.rightClip < 0) {
+ // negative - object is not clipped
+ currentObj.rightClip = 0;
+ }
+
+ // top clip = clip top left y - top left y
+ currentObj.topClip = pClip->top - y;
+ if (currentObj.topClip < 0) {
+ // negative - object is not clipped
+ currentObj.topClip = 0;
+ } else { // clipped - adjust start position to top of clip rect
+ y = pClip->top;
+ }
+
+ // left clip = clip top left x - top left x
+ currentObj.leftClip = pClip->left - x;
+ if (currentObj.leftClip < 0) {
+ // negative - object is not clipped
+ currentObj.leftClip = 0;
+ }
+ else
+ // NOTE: This else statement is disabled in tinsel v1
+ { // clipped - adjust start position to left of clip rect
+ x = pClip->left;
+ }
+
+ // calc object total horizontal clipping
+ hclip = currentObj.leftClip + currentObj.rightClip;
+
+ // calc object total vertical clipping
+ vclip = currentObj.topClip + currentObj.botClip;
+
+ if (hclip + vclip != 0) {
+ // object is clipped in some way
+
+ if (pObj->width <= hclip)
+ // object totally clipped horizontally - ignore
+ continue;
+
+ if (pObj->height <= vclip)
+ // object totally clipped vertically - ignore
+ continue;
+
+ // set clip bit in objects flags
+ currentObj.flags = pObj->flags | DMA_CLIP;
+ } else { // object is not clipped - copy flags
+ currentObj.flags = pObj->flags;
+ }
+
+ // copy objects properties to local object
+ currentObj.width = pObj->width;
+ currentObj.height = pObj->height;
+ currentObj.xPos = (short)x;
+ currentObj.yPos = (short)y;
+ currentObj.pPal = pObj->pPal;
+ currentObj.constant = pObj->constant;
+ currentObj.hBits = pObj->hBits;
+
+ // draw the object
+ DrawObject(&currentObj);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cliprect.h b/engines/tinsel/cliprect.h
new file mode 100644
index 0000000000..28a66c312c
--- /dev/null
+++ b/engines/tinsel/cliprect.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Clipping rectangle defines
+ */
+
+#ifndef TINSEL_CLIPRECT_H // prevent multiple includes
+#define TINSEL_CLIPRECT_H
+
+#include "common/list.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+typedef Common::List<Common::Rect> RectList;
+
+/*----------------------------------------------------------------------*\
+|* Clip Rect Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ResetClipRect(); // Resets the clipping rectangle allocator
+
+void AddClipRect( // Allocate a clipping rectangle from the free list
+ const Common::Rect &pClip); // clip rectangle dimensions to allocate
+
+const RectList &GetClipRects();
+
+bool IntersectRectangle( // Creates the intersection of two rectangles
+ Common::Rect &pDest, // pointer to destination rectangle that is to receive the intersection
+ const Common::Rect &pSrc1, // pointer to a source rectangle
+ const Common::Rect &pSrc2); // pointer to a source rectangle
+
+bool UnionRectangle( // Creates the union of two rectangles
+ Common::Rect &pDest, // destination rectangle that is to receive the new union
+ const Common::Rect &pSrc1, // a source rectangle
+ const Common::Rect &pSrc2); // a source rectangle
+
+void FindMovingObjects( // Creates clipping rectangles for all the objects that have moved on the specified object list
+ OBJECT *pObjList, // playfield display list to draw
+ Common::Point *pWin, // playfield window top left position
+ Common::Rect *pClip, // playfield clipping rectangle
+ bool bVelocity, // when set, objects pos is updated with velocity
+ bool bScrolled); // when set, playfield has scrolled
+
+void MergeClipRect(); // Merges any clipping rectangles that overlap
+
+void UpdateClipRect( // Redraws all objects within this clipping rectangle
+ OBJECT *pObjList, // object list to draw
+ Common::Point *pWin, // window top left position
+ Common::Rect *pClip); // pointer to clip rectangle
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_CLIPRECT_H
diff --git a/engines/tinsel/config.cpp b/engines/tinsel/config.cpp
new file mode 100644
index 0000000000..4c143f1b8d
--- /dev/null
+++ b/engines/tinsel/config.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains configuration functionality
+ */
+
+//#define USE_3FLAGS 1
+
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+
+#include "common/file.h"
+#include "common/config-manager.h"
+
+#include "sound/mixer.h"
+
+namespace Tinsel {
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+int dclickSpeed = DOUBLE_CLICK_TIME;
+int volMidi = MAXMIDIVOL;
+int volSound = MAXSAMPVOL;
+int volVoice = MAXSAMPVOL;
+int speedText = DEFTEXTSPEED;
+int bSubtitles = false;
+int bSwapButtons = 0;
+LANGUAGE language = TXT_ENGLISH;
+int bAmerica = 0;
+
+
+// Shouldn't really be here, but time is short...
+bool bNoBlocking;
+
+/**
+ * WriteConfig()
+ */
+
+void WriteConfig(void) {
+ ConfMan.setInt("dclick_speed", dclickSpeed);
+ ConfMan.setInt("music_volume", (volMidi * Audio::Mixer::kMaxChannelVolume) / MAXMIDIVOL);
+ ConfMan.setInt("sfx_volume", (volSound * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL);
+ ConfMan.setInt("speech_volume", (volVoice * Audio::Mixer::kMaxChannelVolume) / MAXSAMPVOL);
+ ConfMan.setInt("talkspeed", (speedText * 255) / 100);
+ ConfMan.setBool("subtitles", bSubtitles);
+ //ConfMan.setBool("swap_buttons", bSwapButtons ? 1 : 0);
+ //ConfigData.language = language; // not necessary, as language has been set in the launcher
+ //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB
+}
+
+/*---------------------------------------------------------------------*\
+| ReadConfig() |
+|-----------------------------------------------------------------------|
+|
+\*---------------------------------------------------------------------*/
+void ReadConfig(void) {
+ if (ConfMan.hasKey("dclick_speed"))
+ dclickSpeed = ConfMan.getInt("dclick_speed");
+
+ volMidi = (ConfMan.getInt("music_volume") * MAXMIDIVOL) / Audio::Mixer::kMaxChannelVolume;
+ volSound = (ConfMan.getInt("sfx_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume;
+ volVoice = (ConfMan.getInt("speech_volume") * MAXSAMPVOL) / Audio::Mixer::kMaxChannelVolume;
+
+ if (ConfMan.hasKey("talkspeed"))
+ speedText = (ConfMan.getInt("talkspeed") * 100) / 255;
+ if (ConfMan.hasKey("subtitles"))
+ bSubtitles = ConfMan.getBool("subtitles");
+
+ // FIXME: If JAPAN is set, subtitles are forced OFF in the original engine
+
+ //bSwapButtons = ConfMan.getBool("swap_buttons") == 1 ? true : false;
+ //ConfigData.language = language; // not necessary, as language has been set in the launcher
+ //ConfigData.bAmerica = bAmerica; // EN_USA / EN_GRB
+
+// The flags here control how many country flags are displayed in one of the option dialogs.
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ language = ConfigData.language;
+ #ifdef USE_3FLAGS
+ if (language == TXT_ENGLISH || language == TXT_ITALIAN) {
+ language = TXT_GERMAN;
+ bSubtitles = true;
+ }
+ #endif
+ #ifdef USE_4FLAGS
+ if (language == TXT_ENGLISH) {
+ language = TXT_GERMAN;
+ bSubtitles = true;
+ }
+ #endif
+#else
+ language = TXT_ENGLISH;
+#endif
+}
+
+bool isJapanMode() {
+#ifdef JAPAN
+ return true;
+#else
+ return false;
+#endif
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/config.h b/engines/tinsel/config.h
new file mode 100644
index 0000000000..73cc411cb6
--- /dev/null
+++ b/engines/tinsel/config.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_CONFIG_H
+#define TINSEL_CONFIG_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// None of these defined -> 1 language, in ENGLISH.TXT
+//#define USE_5FLAGS 1 // All 5 flags
+//#define USE_4FLAGS 1 // French, German, Italian, Spanish
+//#define USE_3FLAGS 1 // French, German, Spanish
+
+// The Hebrew version appears to the software as being English
+// but it needs to have subtitles on...
+//#define HEBREW 1
+
+//#define JAPAN 1
+
+
+// double click timer initial value
+#define DOUBLE_CLICK_TIME 6 // 6 @ 18Hz = .33 sec
+
+#define DEFTEXTSPEED 0
+
+
+extern int dclickSpeed;
+extern int volMidi;
+extern int volSound;
+extern int volVoice;
+extern int speedText;
+extern int bSubtitles;
+extern int bSwapButtons;
+extern LANGUAGE language;
+extern int bAmerica;
+
+void WriteConfig(void);
+void ReadConfig(void);
+
+extern bool isJapanMode();
+
+
+// Shouldn't really be here, but time is short...
+extern bool bNoBlocking;
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/coroutine.h b/engines/tinsel/coroutine.h
new file mode 100644
index 0000000000..e0292735bb
--- /dev/null
+++ b/engines/tinsel/coroutine.h
@@ -0,0 +1,147 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_COROUTINE_H
+#define TINSEL_COROUTINE_H
+
+#include "common/scummsys.h"
+
+namespace Tinsel {
+
+/*
+ * The following is loosely based on an article by Simon Tatham:
+ * <http://www.chiark.greenend.org.uk/~sgtatham/coroutines.html>.
+ * However, many improvements and tweaks have been made, in particular
+ * by taking advantage of C++ features not available in C.
+ *
+ * Why is this code here? Well, the Tinsel engine apparently used
+ * setjmp/longjmp based coroutines as a core tool from the start, and
+ * so they are deeply ingrained into the whole code base. When we
+ * started to get Tinsel ready for ScummVM, we had to deal with that.
+ * It soon got clear that we could not simply rewrite the code to work
+ * without some form of coroutines. While possible in principle, it
+ * would have meant a major restructuring of the entire code base, a
+ * rather daunting task. Also, it would have very likely introduced
+ * tons of regressons.
+ *
+ * So instead of getting rid of the coroutines, we chose to implement
+ * them in an alternate way, using Simon Tatham's trick as described
+ * above. While the trick is dirty, the result seems to be clear enough,
+ * we hope; plus, it allowed us to stay relatively close to the
+ * original structure of the code, which made it easier to avoid
+ * regressions, and will be helpful in the future when comparing things
+ * against the original code base.
+ */
+
+
+/**
+ * The core of any coroutine context which captures the 'state' of a coroutine.
+ * Private use only
+ */
+struct CoroBaseContext {
+ int _line;
+ int _sleep;
+ CoroBaseContext *_subctx;
+ CoroBaseContext() : _line(0), _sleep(0), _subctx(0) {}
+ ~CoroBaseContext() { delete _subctx; }
+};
+
+typedef CoroBaseContext *CoroContext;
+
+
+/**
+ * Wrapper class which holds a pointer to a pointer to a CoroBaseContext.
+ * The interesting part is the destructor, which kills the context being held,
+ * but ONLY if the _sleep val of that context is zero. This way, a coroutine
+ * can just 'return' w/o having to worry about freeing the allocated context
+ * (in Simon Tatham's original code, one had to use a special macro to
+ * return from a coroutine).
+ */
+class CoroContextHolder {
+ CoroContext &_ctx;
+public:
+ CoroContextHolder(CoroContext &ctx) : _ctx(ctx) {}
+ ~CoroContextHolder() {
+ if (_ctx && _ctx->_sleep == 0) {
+ delete _ctx;
+ _ctx = 0;
+ }
+ }
+};
+
+
+#define CORO_PARAM CoroContext &coroParam
+
+#define CORO_SUBCTX coroParam->_subctx
+
+
+#define CORO_BEGIN_CONTEXT struct CoroContextTag : CoroBaseContext { int DUMMY
+#define CORO_END_CONTEXT(x) } *x = (CoroContextTag *)coroParam
+
+#define CORO_BEGIN_CODE(x) \
+ if (!x) {coroParam = x = new CoroContextTag();}\
+ assert(coroParam);\
+ assert(coroParam->_sleep >= 0);\
+ coroParam->_sleep = 0;\
+ CoroContextHolder tmpHolder(coroParam);\
+ switch(coroParam->_line) { case 0:;
+
+#define CORO_END_CODE \
+ }
+
+#define CORO_SLEEP(delay) \
+ do {\
+ coroParam->_line = __LINE__;\
+ coroParam->_sleep = delay;\
+ return; case __LINE__:;\
+ } while (0)
+
+/** Stop the currently running coroutine */
+#define CORO_KILL_SELF() do { coroParam->_sleep = -1; return; } while(0)
+
+/** Invoke another coroutine */
+#define CORO_INVOKE_ARGS(subCoro, ARGS) \
+ do {\
+ coroParam->_line = __LINE__;\
+ coroParam->_subctx = 0;\
+ do {\
+ subCoro ARGS;\
+ if (!coroParam->_subctx) break;\
+ coroParam->_sleep = coroParam->_subctx->_sleep;\
+ return; case __LINE__:;\
+ } while(1);\
+ } while (0)
+
+#define CORO_INVOKE_0(subCoroutine) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX))
+#define CORO_INVOKE_1(subCoroutine, a0) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0))
+#define CORO_INVOKE_2(subCoroutine, a0,a1) \
+ CORO_INVOKE_ARGS(subCoroutine,(CORO_SUBCTX,a0,a1))
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_COROUTINE_H
diff --git a/engines/tinsel/cursor.cpp b/engines/tinsel/cursor.cpp
new file mode 100644
index 0000000000..b95662cbfe
--- /dev/null
+++ b/engines/tinsel/cursor.cpp
@@ -0,0 +1,647 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Cursor and cursor trails.
+ */
+
+#include "tinsel/cursor.h"
+
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // For EventsManager class
+#include "tinsel/film.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h" // resetidletime()
+#include "tinsel/tinsel.h" // For engine access
+
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+#define ITERATION_BASE FRAC_ONE
+#define ITER_ACCELLERATION (10L << (FRAC_BITS - 4))
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static OBJECT *McurObj = 0; // Main cursor object
+static OBJECT *AcurObj = 0; // Auxiliary cursor object
+
+static ANIM McurAnim = {0,0,0,0,0}; // Main cursor animation structure
+static ANIM AcurAnim = {0,0,0,0,0}; // Auxiliary cursor animation structure
+
+static bool bHiddenCursor = false; // Set when cursor is hidden
+static bool bTempNoTrailers = false; // Set when cursor trails are hidden
+
+static bool bFrozenCursor = false; // Set when cursor position is frozen
+
+static frac_t IterationSize = 0;
+
+static SCNHANDLE CursorHandle = 0; // Handle to cursor reel data
+
+static int numTrails = 0;
+static int nextTrail = 0;
+
+static bool bWhoa = false; // Set by DropCursor() at the end of a scene
+ // - causes cursor processes to do nothing
+ // Reset when main cursor has re-initialised
+
+static bool restart = false; // When main cursor has been bWhoa-ed, it waits
+ // for this to be set to true.
+
+static short ACoX = 0, ACoY = 0; // Auxillary cursor image's animation offsets
+
+
+
+#define MAX_TRAILERS 10
+
+static struct {
+
+ ANIM trailAnim; // Animation structure
+ OBJECT *trailObj; // This trailer's object
+
+} ntrailData [MAX_TRAILERS];
+
+static int lastCursorX = 0, lastCursorY = 0;
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void MoveCursor(void);
+
+/**
+ * Initialise and insert a cursor trail object, set its Z-pos, and hide
+ * it. Also initialise its animation script.
+ */
+static void InitCurTrailObj(int i, int x, int y) {
+ const FREEL *pfr; // pointer to reel
+ IMAGE *pim; // pointer to image
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+
+ const FILM *pfilm;
+
+ if (!numTrails)
+ return;
+
+ // Get rid of old object
+ if (ntrailData[i].trailObj != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+
+ pim = GetImageFromFilm(CursorHandle, i+1, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // No background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Initialise and insert the object, set its Z-pos, and hide it
+ ntrailData[i].trailObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ MultiSetZPosition(ntrailData[i].trailObj, Z_CURSORTRAIL);
+ MultiSetAniXY(ntrailData[i].trailObj, x, y);
+
+ // Initialise the animation script
+ InitStepAnimScript(&ntrailData[i].trailAnim, ntrailData[i].trailObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ StepAnimScript(&ntrailData[i].trailAnim);
+}
+
+/**
+ * Get the cursor position from the mouse driver.
+ */
+static bool GetDriverPosition(int *x, int *y) {
+ Common::Point ptMouse = _vm->getMousePosition();
+ *x = ptMouse.x;
+ *y = ptMouse.y;
+
+ return(*x >= 0 && *x <= SCREEN_WIDTH-1 &&
+ *y >= 0 && *y <= SCREEN_HEIGHT-1);
+}
+
+/**
+ * Move the cursor relative to current position.
+ */
+void AdjustCursorXY(int deltaX, int deltaY) {
+ int x, y;
+
+ if (deltaX || deltaY) {
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(x + deltaX, y + deltaY));
+ }
+ MoveCursor();
+}
+
+/**
+ * Move the cursor to an absolute position.
+ */
+void SetCursorXY(int newx, int newy) {
+ int x, y;
+ int Loffset, Toffset; // Screen offset
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ newx -= Loffset;
+ newy -= Toffset;
+
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(newx, newy));
+ MoveCursor();
+}
+
+/**
+ * Move the cursor to a screen position.
+ */
+void SetCursorScreenXY(int newx, int newy) {
+ int x, y;
+
+ if (GetDriverPosition(&x, &y))
+ _vm->setMousePosition(Common::Point(newx, newy));
+ MoveCursor();
+}
+
+/**
+ * Called by the world and his brother.
+ * Returns the cursor's animation position in (x,y).
+ * Returns false if there is no cursor object.
+ */
+bool GetCursorXYNoWait(int *x, int *y, bool absolute) {
+ if (McurObj == NULL) {
+ *x = *y = 0;
+ return false;
+ }
+
+ GetAniPosition(McurObj, x, y);
+
+ if (absolute) {
+ int Loffset, Toffset; // Screen offset
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ *x += Loffset;
+ *y += Toffset;
+ }
+
+ return true;
+}
+
+/**
+ * Called by the world and his brother.
+ * Returns the cursor's animation position.
+ * If called while there is no cursor object, the calling process ends
+ * up waiting until there is.
+ */
+void GetCursorXY(int *x, int *y, bool absolute) {
+ //while (McurObj == NULL)
+ // ProcessSleepSelf();
+ assert(McurObj);
+ GetCursorXYNoWait(x, y, absolute);
+}
+
+/**
+ * Re-initialise the main cursor to use the main cursor reel.
+ * Called from TINLIB.C to restore cursor after hiding it.
+ * Called from INVENTRY.C to restore cursor after customising it.
+ */
+void RestoreMainCursor(void) {
+ const FILM *pfilm;
+
+ if (McurObj != NULL) {
+ pfilm = (const FILM *)LockMem(CursorHandle);
+
+ InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfilm->reels->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ StepAnimScript(&McurAnim);
+ }
+ bHiddenCursor = false;
+ bFrozenCursor = false;
+}
+
+/**
+ * Called from INVENTRY.C to customise the main cursor.
+ */
+void SetTempCursor(SCNHANDLE pScript) {
+ if (McurObj != NULL)
+ InitStepAnimScript(&McurAnim, McurObj, pScript, 2);
+}
+
+/**
+ * Hide the cursor.
+ */
+void DwHideCursor(void) {
+ int i;
+
+ bHiddenCursor = true;
+
+ if (McurObj)
+ MultiHideObject(McurObj);
+ if (AcurObj)
+ MultiHideObject(AcurObj);
+
+ for (i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * Unhide the cursor.
+ */
+void UnHideCursor(void) {
+ bHiddenCursor = false;
+}
+
+/**
+ * Freeze the cursor.
+ */
+void FreezeCursor(void) {
+ bFrozenCursor = true;
+}
+
+/**
+ * HideCursorTrails
+ */
+void HideCursorTrails(void) {
+ int i;
+
+ bTempNoTrailers = true;
+
+ for (i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * UnHideCursorTrails
+ */
+void UnHideCursorTrails(void) {
+ bTempNoTrailers = false;
+}
+
+/**
+ * Get pointer to image from a film reel. And the rest.
+ */
+IMAGE *GetImageFromReel(const FREEL *pfr, const MULTI_INIT **ppmi) {
+ const MULTI_INIT *pmi;
+ const FRAME *pFrame;
+
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfr->mobj));
+ if (ppmi)
+ *ppmi = pmi;
+
+ pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame));
+
+ // get pointer to image
+ return (IMAGE *)LockMem(READ_LE_UINT32(pFrame));
+}
+
+/**
+ * Get pointer to image from a film. And the rest.
+ */
+IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr, const MULTI_INIT **ppmi, const FILM **ppfilm) {
+ const FILM *pfilm;
+ const FREEL *pfr;
+
+ pfilm = (const FILM *)LockMem(hFilm);
+ if (ppfilm)
+ *ppfilm = pfilm;
+
+ pfr = &pfilm->reels[reel];
+ if (ppfr)
+ *ppfr = pfr;
+
+ return GetImageFromReel(pfr, ppmi);
+}
+
+/**
+ * Delete auxillary cursor. Restore animation offsets in the image.
+ */
+void DelAuxCursor(void) {
+ if (AcurObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), AcurObj);
+ AcurObj = NULL;
+ }
+}
+
+/**
+ * Set auxillary cursor.
+ * Save animation offsets from the image if required.
+ */
+void SetAuxCursor(SCNHANDLE hFilm) {
+ IMAGE *pim; // Pointer to auxillary cursor's image
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+ const FILM *pfilm;
+ int x, y; // Cursor position
+
+ DelAuxCursor(); // Get rid of previous
+
+ GetCursorXY(&x, &y, false); // Note: also waits for cursor to appear
+
+ pim = GetImageFromFilm(hFilm, 0, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // no background palette
+ pim->hImgPal = TO_LE_32(BackPal()); // Poke in the background palette
+
+ ACoX = (short)(FROM_LE_16(pim->imgWidth)/2 - ((int16) FROM_LE_16(pim->anioffX)));
+ ACoY = (short)(FROM_LE_16(pim->imgHeight)/2 - ((int16) FROM_LE_16(pim->anioffY)));
+
+ // Initialise and insert the auxillary cursor object
+ AcurObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), AcurObj);
+
+ // Initialise the animation and set its position
+ InitStepAnimScript(&AcurAnim, AcurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ MultiSetAniXY(AcurObj, x - ACoX, y - ACoY);
+ MultiSetZPosition(AcurObj, Z_ACURSOR);
+
+ if (bHiddenCursor)
+ MultiHideObject(AcurObj);
+}
+
+/**
+ * MoveCursor
+ */
+static void MoveCursor(void) {
+ int startX, startY;
+ Common::Point ptMouse;
+ frac_t newX, newY;
+ unsigned dir;
+
+ // get cursors start animation position
+ GetCursorXYNoWait(&startX, &startY, false);
+
+ // get mouse drivers current position
+ ptMouse = _vm->getMousePosition();
+
+ // convert to fixed point
+ newX = intToFrac(ptMouse.x);
+ newY = intToFrac(ptMouse.y);
+
+ // modify mouse driver position depending on cursor keys
+ if ((dir = _vm->getKeyDirection()) != 0) {
+ if (dir & MSK_LEFT)
+ newX -= IterationSize;
+
+ if (dir & MSK_RIGHT)
+ newX += IterationSize;
+
+ if (dir & MSK_UP)
+ newY -= IterationSize;
+
+ if (dir & MSK_DOWN)
+ newY += IterationSize;
+
+ IterationSize += ITER_ACCELLERATION;
+
+ // set new mouse driver position
+ _vm->setMousePosition(Common::Point(fracToInt(newX), fracToInt(newY)));
+ } else
+
+ IterationSize = ITERATION_BASE;
+
+ // get new mouse driver position - could have been modified
+ ptMouse = _vm->getMousePosition();
+
+ if (lastCursorX != ptMouse.x || lastCursorY != ptMouse.y) {
+ resetUserEventTime();
+
+ if (!bTempNoTrailers && !bHiddenCursor) {
+ InitCurTrailObj(nextTrail++, lastCursorX, lastCursorY);
+ if (nextTrail == numTrails)
+ nextTrail = 0;
+ }
+ }
+
+ // adjust cursor to new mouse position
+ if (McurObj)
+ MultiSetAniXY(McurObj, ptMouse.x, ptMouse.y);
+ if (AcurObj != NULL)
+ MultiSetAniXY(AcurObj, ptMouse.x - ACoX, ptMouse.y - ACoY);
+
+ if (InventoryActive() && McurObj) {
+ // Notify the inventory
+ Xmovement(ptMouse.x - startX);
+ Ymovement(ptMouse.y - startY);
+ }
+
+ lastCursorX = ptMouse.x;
+ lastCursorY = ptMouse.y;
+}
+
+/**
+ * Initialise cursor object.
+ */
+static void InitCurObj(void) {
+ const FILM *pfilm;
+ const FREEL *pfr;
+ const MULTI_INIT *pmi;
+ IMAGE *pim;
+
+ pim = GetImageFromFilm(CursorHandle, 0, &pfr, &pmi, &pfilm);// Get pointer to image
+ assert(BackPal()); // no background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+//---
+
+ AcurObj = NULL; // No auxillary cursor
+
+ McurObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), McurObj);
+
+ InitStepAnimScript(&McurAnim, McurObj, FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+}
+
+/**
+ * Initialise the cursor position.
+ */
+static void InitCurPos(void) {
+ Common::Point ptMouse = _vm->getMousePosition();
+ lastCursorX = ptMouse.x;
+ lastCursorY = ptMouse.y;
+
+ MultiSetZPosition(McurObj, Z_CURSOR);
+ MoveCursor();
+ MultiHideObject(McurObj);
+
+ IterationSize = ITERATION_BASE;
+}
+
+/**
+ * CursorStoppedCheck
+ */
+static void CursorStoppedCheck(CORO_PARAM) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // If scene is closing down
+ if (bWhoa) {
+ // ...wait for next scene start-up
+ while (!restart)
+ CORO_SLEEP(1);
+
+ // Re-initialise
+ InitCurObj();
+ InitCurPos();
+ InventoryIconCursor(); // May be holding something
+
+ // Re-start the cursor trails
+ restart = false; // set all bits
+ bWhoa = false;
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * The main cursor process.
+ */
+void CursorProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (!CursorHandle || !BackPal())
+ CORO_SLEEP(1);
+
+ InitCurObj();
+ InitCurPos();
+ InventoryIconCursor(); // May be holding something
+
+ bWhoa = false;
+ restart = false;
+
+ while (1) {
+ // allow rescheduling
+ CORO_SLEEP(1);
+
+ // Stop/start between scenes
+ CORO_INVOKE_0(CursorStoppedCheck);
+
+ // Step the animation script(s)
+ StepAnimScript(&McurAnim);
+ if (AcurObj != NULL)
+ StepAnimScript(&AcurAnim);
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ if (StepAnimScript(&ntrailData[i].trailAnim) == ScriptFinished) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+ }
+
+ // Move the cursor as appropriate
+ if (!bFrozenCursor)
+ MoveCursor();
+
+ // If the cursor should be hidden...
+ if (bHiddenCursor) {
+ // ...hide the cursor object(s)
+ MultiHideObject(McurObj);
+ if (AcurObj)
+ MultiHideObject(AcurObj);
+
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL)
+ MultiHideObject(ntrailData[i].trailObj);
+ }
+
+ // Wait 'til cursor is again required.
+ while (bHiddenCursor) {
+ CORO_SLEEP(1);
+
+ // Stop/start between scenes
+ CORO_INVOKE_0(CursorStoppedCheck);
+ }
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Called from dec_cursor() Glitter function.
+ * Register the handle to cursor reel data.
+ */
+void DwInitCursor(SCNHANDLE bfilm) {
+ const FILM *pfilm;
+
+ CursorHandle = bfilm;
+
+ pfilm = (const FILM *)LockMem(CursorHandle);
+ numTrails = FROM_LE_32(pfilm->numreels) - 1;
+
+ assert(numTrails <= MAX_TRAILERS);
+}
+
+/**
+ * DropCursor is called when a scene is closing down.
+ */
+void DropCursor(void) {
+ AcurObj = NULL; // No auxillary cursor
+ McurObj = NULL; // No cursor object (imminently deleted elsewhere)
+ bHiddenCursor = false; // Not hidden in next scene
+ bTempNoTrailers = false; // Trailers not hidden in next scene
+ bWhoa = true; // Suspend cursor processes
+
+ for (int i = 0; i < numTrails; i++) {
+ if (ntrailData[i].trailObj != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), ntrailData[i].trailObj);
+ ntrailData[i].trailObj = NULL;
+ }
+ }
+}
+
+/**
+ * RestartCursor is called when a new scene is starting up.
+ */
+void RestartCursor(void) {
+ restart = true; // Get the main cursor to re-initialise
+}
+
+/**
+ * Called when restarting the game, ensures correct re-start with NULL
+ * pointers etc.
+ */
+void RebootCursor(void) {
+ McurObj = AcurObj = NULL;
+ for (int i = 0; i < MAX_TRAILERS; i++)
+ ntrailData[i].trailObj = NULL;
+
+ bHiddenCursor = bTempNoTrailers = bFrozenCursor = false;
+
+ CursorHandle = 0;
+
+ bWhoa = false;
+ restart = false;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/cursor.h b/engines/tinsel/cursor.h
new file mode 100644
index 0000000000..15349dda26
--- /dev/null
+++ b/engines/tinsel/cursor.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Clipping rectangle defines
+ */
+
+#ifndef TINSEL_CURSOR_H // prevent multiple includes
+#define TINSEL_CURSOR_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+void AdjustCursorXY(int deltaX, int deltaY);
+void SetCursorXY(int x, int y);
+void SetCursorScreenXY(int newx, int newy);
+void GetCursorXY(int *x, int *y, bool absolute);
+bool GetCursorXYNoWait(int *x, int *y, bool absolute);
+
+void RestoreMainCursor(void);
+void SetTempCursor(SCNHANDLE pScript);
+void DwHideCursor(void);
+void UnHideCursor(void);
+void FreezeCursor(void);
+void HideCursorTrails(void);
+void UnHideCursorTrails(void);
+void DelAuxCursor(void);
+void SetAuxCursor(SCNHANDLE hFilm);
+void DwInitCursor(SCNHANDLE bfilm);
+void DropCursor(void);
+void RestartCursor(void);
+void RebootCursor(void);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_CURSOR_H
diff --git a/engines/tinsel/debugger.cpp b/engines/tinsel/debugger.cpp
new file mode 100644
index 0000000000..dc37e6a9a1
--- /dev/null
+++ b/engines/tinsel/debugger.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "tinsel/tinsel.h"
+#include "tinsel/debugger.h"
+#include "tinsel/inventory.h"
+#include "tinsel/pcode.h"
+#include "tinsel/scene.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+#include "tinsel/font.h"
+#include "tinsel/strres.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// In PDISPLAY.CPP
+extern void TogglePathDisplay(void);
+// In tinsel.cpp
+extern void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+// In scene.cpp
+extern SCNHANDLE GetSceneHandle(void);
+
+//----------------- SUPPORT FUNCTIONS ---------------------
+
+//static
+int strToInt(const char *s) {
+ if (!*s)
+ // No string at all
+ return 0;
+ else if (toupper(s[strlen(s) - 1]) != 'H')
+ // Standard decimal string
+ return atoi(s);
+
+ // Hexadecimal string
+ uint tmp;
+ sscanf(s, "%xh", &tmp);
+ return (int)tmp;
+}
+
+//----------------- CONSOLE CLASS ---------------------
+
+Console::Console() : GUI::Debugger() {
+ DCmd_Register("item", WRAP_METHOD(Console, cmd_item));
+ DCmd_Register("scene", WRAP_METHOD(Console, cmd_scene));
+ DCmd_Register("music", WRAP_METHOD(Console, cmd_music));
+ DCmd_Register("sound", WRAP_METHOD(Console, cmd_sound));
+ DCmd_Register("string", WRAP_METHOD(Console, cmd_string));
+}
+
+Console::~Console() {
+}
+
+bool Console::cmd_item(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s item_number\n", argv[0]);
+ DebugPrintf("Sets the currently active 'held' item\n");
+ return true;
+ }
+
+ HoldItem(INV_NOICON);
+ HoldItem(strToInt(argv[1]));
+ return false;
+}
+
+bool Console::cmd_scene(int argc, const char **argv) {
+ if (argc < 1 || argc > 3) {
+ DebugPrintf("%s [scene_number [entry number]]\n", argv[0]);
+ DebugPrintf("If no parameters are given, prints the current scene.\n");
+ DebugPrintf("Otherwise changes to the specified scene number. Entry number defaults to 1 if none provided\n");
+ return true;
+ }
+
+ if (argc == 1) {
+ DebugPrintf("Current scene is %d\n", GetSceneHandle() >> SCNHANDLE_SHIFT);
+ return true;
+ }
+
+ uint32 sceneNumber = (uint32)strToInt(argv[1]) << SCNHANDLE_SHIFT;
+ int entryNumber = (argc >= 3) ? strToInt(argv[2]) : 1;
+
+ SetNewScene(sceneNumber, entryNumber, TRANS_CUT);
+ return false;
+}
+
+bool Console::cmd_music(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s track_number or %s -offset\n", argv[0], argv[0]);
+ DebugPrintf("Plays the MIDI track number provided, or the offset inside midi.dat\n");
+ DebugPrintf("A positive number signifies a track number, whereas a negative signifies an offset\n");
+ return true;
+ }
+
+ int param = strToInt(argv[1]);
+ if (param == 0) {
+ DebugPrintf("Track number/offset can't be 0!\n", argv[0]);
+ } else if (param > 0) {
+ // Track provided
+ PlayMidiSequence(GetTrackOffset(param - 1), false);
+ } else if (param < 0) {
+ // Offset provided
+ param = param * -1;
+ PlayMidiSequence(param, false);
+ }
+ return true;
+}
+
+bool Console::cmd_sound(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s id\n", argv[0]);
+ DebugPrintf("Plays the sound with the given ID\n");
+ return true;
+ }
+
+ int id = strToInt(argv[1]);
+ if (_vm->_sound->sampleExists(id))
+ _vm->_sound->playSample(id, Audio::Mixer::kSpeechSoundType);
+ else
+ DebugPrintf("Sample %d does not exist!\n", id);
+
+ return true;
+}
+
+bool Console::cmd_string(int argc, const char **argv) {
+ if (argc < 2) {
+ DebugPrintf("%s id\n", argv[0]);
+ DebugPrintf("Prints the string with the given ID\n");
+ return true;
+ }
+
+ char tmp[TBUFSZ];
+ int id = strToInt(argv[1]);
+ LoadStringRes(id, tmp, TBUFSZ);
+ DebugPrintf("%s\n", tmp);
+
+ return true;
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/debugger.h b/engines/tinsel/debugger.h
new file mode 100644
index 0000000000..219bc71224
--- /dev/null
+++ b/engines/tinsel/debugger.h
@@ -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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_DEBUGGER_H
+#define TINSEL_DEBUGGER_H
+
+#include "gui/debugger.h"
+
+namespace Tinsel {
+
+class TinselEngine;
+
+class Console: public GUI::Debugger {
+protected:
+ bool cmd_item(int argc, const char **argv);
+ bool cmd_scene(int argc, const char **argv);
+ bool cmd_music(int argc, const char **argv);
+ bool cmd_sound(int argc, const char **argv);
+ bool cmd_string(int argc, const char **argv);
+public:
+ Console();
+ virtual ~Console(void);
+};
+
+} // End of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/detection.cpp b/engines/tinsel/detection.cpp
new file mode 100644
index 0000000000..7da4192456
--- /dev/null
+++ b/engines/tinsel/detection.cpp
@@ -0,0 +1,277 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "base/plugins.h"
+
+#include "common/advancedDetector.h"
+#include "common/file.h"
+
+#include "tinsel/tinsel.h"
+
+
+namespace Tinsel {
+
+struct TinselGameDescription {
+ Common::ADGameDescription desc;
+
+ int gameID;
+ int gameType;
+ uint32 features;
+ uint16 version;
+};
+
+uint32 TinselEngine::getGameID() const {
+ return _gameDescription->gameID;
+}
+
+uint32 TinselEngine::getFeatures() const {
+ return _gameDescription->features;
+}
+
+Common::Language TinselEngine::getLanguage() const {
+ return _gameDescription->desc.language;
+}
+
+Common::Platform TinselEngine::getPlatform() const {
+ return _gameDescription->desc.platform;
+}
+
+uint16 TinselEngine::getVersion() const {
+ return _gameDescription->version;
+}
+
+}
+
+static const PlainGameDescriptor tinselGames[] = {
+ {"tinsel", "Tinsel engine game"},
+ {"dw", "Discworld"},
+ {"dw2", "Discworld 2: Mortality Bytes!"},
+ {0, 0}
+};
+
+
+namespace Tinsel {
+
+static const TinselGameDescription gameDescriptions[] = {
+
+ // The DW1 demo was based on an older revision of the Tinsel engine
+ // than the one used in the released game. We call it Tinsel v0 as
+ // opposed to v1 which was used in the full retail version of DW.
+
+ { // Demo from http://www.adventure-treff.de/specials/dl_demos.php
+ {
+ "dw",
+ "Demo",
+ AD_ENTRY1s("dw.gra", "ce1b57761ba705221bcf70955b827b97", 441192),
+ //AD_ENTRY1s("dw.scn", "ccd72f02183d0e96b6e7d8df9492cda8", 23308),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DEMO
+ },
+ GID_DW1,
+ 0,
+ GF_DEMO,
+ TINSEL_V0,
+ },
+
+ {
+ { // This version has *.gra files
+ "dw",
+ "Floppy",
+ AD_ENTRY1s("dw.gra", "c8808ccd988d603dd35dff42013ae7fd", 781656),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_FLOPPY,
+ TINSEL_V1,
+ },
+
+ { // English CD. This version has *.gra files
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.gra", 0, "c8808ccd988d603dd35dff42013ae7fd", 781656},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD,
+ TINSEL_V1,
+ },
+
+ { // English CD with SCN files
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.scn", 0, "70955425870c7720d6eebed903b2ef41", 776188},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD | GF_SCNFILES,
+ TINSEL_V1,
+ },
+
+#if 0
+ { // English Saturn CD. Not (yet?) supported
+ {
+ "dw",
+ "CD",
+ {
+ {"dw.scn", 0, "6803f293c88758057cc685b9437f7637", 382248},
+ {"english.smp", 0, NULL, -1},
+ {NULL, 0, NULL, 0}
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD,
+ TINSEL_V1,
+ },
+#endif
+
+ { // German CD re-release "Neon Edition"
+ // Note: This release has ENGLISH.TXT (with german content) instead of GERMAN.TXT
+ {
+ "dw",
+ "CD",
+ AD_ENTRY1s("dw.scn", "6182c7986eaec893c62fb6ea13a9f225", 774556),
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_CD | GF_SCNFILES,
+ TINSEL_V1,
+ },
+
+ { AD_TABLE_END_MARKER, 0, 0, 0, 0 }
+};
+
+/**
+ * The fallback game descriptor used by the Tinsel engine's fallbackDetector.
+ * Contents of this struct are to be overwritten by the fallbackDetector.
+ */
+static TinselGameDescription g_fallbackDesc = {
+ {
+ "",
+ "",
+ AD_ENTRY1(0, 0), // This should always be AD_ENTRY1(0, 0) in the fallback descriptor
+ Common::UNK_LANG,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ 0,
+ 0,
+ 0,
+ 0,
+};
+
+} // End of namespace Tinsel
+
+static const Common::ADParams detectionParams = {
+ // Pointer to ADGameDescription or its superset structure
+ (const byte *)Tinsel::gameDescriptions,
+ // Size of that superset structure
+ sizeof(Tinsel::TinselGameDescription),
+ // Number of bytes to compute MD5 sum for
+ 5000,
+ // List of all engine targets
+ tinselGames,
+ // Structure for autoupgrading obsolete targets
+ 0,
+ // Name of single gameid (optional)
+ "tinsel",
+ // List of files for file-based fallback detection (optional)
+ 0,
+ // Flags
+ 0
+};
+
+class TinselMetaEngine : public Common::AdvancedMetaEngine {
+public:
+ TinselMetaEngine() : Common::AdvancedMetaEngine(detectionParams) {}
+
+ virtual const char *getName() const {
+ return "Tinsel Engine";
+ }
+
+ virtual const char *getCopyright() const {
+ return "Tinsel Engine";
+ }
+
+ virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const;
+
+ const Common::ADGameDescription *fallbackDetect(const FSList *fslist) const;
+
+};
+
+bool TinselMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const {
+ const Tinsel::TinselGameDescription *gd = (const Tinsel::TinselGameDescription *)desc;
+ if (gd) {
+ *engine = new Tinsel::TinselEngine(syst, gd);
+ }
+ return gd != 0;
+}
+
+const Common::ADGameDescription *TinselMetaEngine::fallbackDetect(const FSList *fslist) const {
+ // Set the default values for the fallback descriptor's ADGameDescription part.
+ Tinsel::g_fallbackDesc.desc.language = Common::UNK_LANG;
+ Tinsel::g_fallbackDesc.desc.platform = Common::kPlatformPC;
+ Tinsel::g_fallbackDesc.desc.flags = Common::ADGF_NO_FLAGS;
+
+ // Set default values for the fallback descriptor's TinselGameDescription part.
+ Tinsel::g_fallbackDesc.gameID = 0;
+ Tinsel::g_fallbackDesc.features = 0;
+ Tinsel::g_fallbackDesc.version = 0;
+
+ //return (const Common::ADGameDescription *)&Tinsel::g_fallbackDesc;
+ return NULL;
+}
+
+#if PLUGIN_ENABLED_DYNAMIC(TINSEL)
+ REGISTER_PLUGIN_DYNAMIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine);
+#else
+ REGISTER_PLUGIN_STATIC(TINSEL, PLUGIN_TYPE_ENGINE, TinselMetaEngine);
+#endif
diff --git a/engines/tinsel/dw.h b/engines/tinsel/dw.h
new file mode 100644
index 0000000000..d14dd43fa2
--- /dev/null
+++ b/engines/tinsel/dw.h
@@ -0,0 +1,119 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_DW_H
+#define TINSEL_DW_H
+
+#include "common/scummsys.h"
+#include "common/endian.h"
+
+namespace Tinsel {
+
+/** scene handle data type */
+typedef uint32 SCNHANDLE;
+
+/** polygon handle */
+typedef int HPOLYGON;
+
+
+#define EOS_CHAR '\0' // string terminator
+#define LF_CHAR '\x0a' // line feed
+
+// file names
+#define SAMPLE_FILE "english.smp" // all samples
+#define SAMPLE_INDEX "english.idx" // sample index filename
+#define MIDI_FILE "midi.dat" // all MIDI sequences
+#define INDEX_FILENAME "index" // name of index file
+
+#define SCNHANDLE_SHIFT 23 // amount to shift scene handles by
+#define NO_SCNHANDLES 300 // number of memory handles for scenes
+#define MASTER_SCNHANDLE (0 << SCNHANDLE_SHIFT) // master scene memory handle
+
+// the minimum value a integer number can have
+#define MIN_INT (1 << (8*sizeof(int) - 1))
+#define MIN_INT16 (-32767)
+
+// the maximum value a integer number can have
+#define MAX_INT (~MIN_INT)
+
+// TODO: v1->v2 scene files
+#ifdef FILE_SPLIT
+// each scene is split into 2 files
+#define INV_OBJ_SCNHANDLE (2 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects)
+#else
+#define INV_OBJ_SCNHANDLE (1 << SCNHANDLE_SHIFT) // inventory object handle (if there are inventory objects)
+#endif
+
+
+#define FIELD_WORLD 0
+#define FIELD_STATUS 1
+
+
+
+
+// We don't set the Z position for print and talk text
+// i.e. it gets a Z position of 0
+
+#define Z_INV_BRECT 10 // Inventory background rectangle
+#define Z_INV_MFRAME 15 // Inventory window frame
+#define Z_INV_HTEXT 15 // Inventory heading text
+#define Z_INV_ICONS 16 // Icons in inventory
+#define Z_INV_ITEXT 995 // Icon text
+
+#define Z_INV_RFRAME 22 // Re-sizing frame
+
+#define Z_CURSOR 1000 // Cursor
+#define Z_CURSORTRAIL 999 // Cursor trails
+#define Z_ACURSOR 990 // Auxillary cursor
+
+#define Z_TAG_TEXT 995 // In front of auxillary cursor
+
+#define Z_MDGROOVE 20
+#define Z_MDSLIDER 21
+
+#define Z_TOPPLAY 100
+
+#define Z_TOPW_TEXT Z_TAG_TEXT
+
+// Started a collection of assorted maximum numbers here:
+#define MAX_MOVERS 6 // Moving actors using path system
+#define MAX_SAVED_ACTORS 32 // Saved 'Normal' actors
+#define MAX_SAVED_ALIVES 512 // Saves actors'lives
+
+// Legal non-existant entrance number for LoadScene()
+#define NO_ENTRY_NUM (-3458) // Magic unlikely number
+
+
+#define SAMPLETIMEOUT (15*ONE_SECOND)
+
+// Language for the resource strings
+enum LANGUAGE {
+ TXT_ENGLISH, TXT_FRENCH, TXT_GERMAN,
+ TXT_ITALIAN, TXT_SPANISH
+};
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_DW_H
diff --git a/engines/tinsel/effect.cpp b/engines/tinsel/effect.cpp
new file mode 100644
index 0000000000..91645da71b
--- /dev/null
+++ b/engines/tinsel/effect.cpp
@@ -0,0 +1,134 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Handles effect polygons.
+//
+// EffectPolyProcess() monitors triggering of effect code (i.e. a moving
+// actor entering an effect polygon).
+// EffectProcess() runs the appropriate effect code.
+//
+// NOTE: Currently will only run one effect process at a time, i.e.
+// effect polygons will not currently nest. It won't be very difficult
+// to fix this if required.
+
+#include "tinsel/actors.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/pid.h"
+#include "tinsel/pcode.h" // LEAD_ACTOR
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+
+
+namespace Tinsel {
+
+struct EP_INIT {
+ HPOLYGON hEpoly;
+ PMACTOR pActor;
+ int index;
+};
+
+/**
+ * Runs an effect polygon's Glitter code with ENTER event, waits for the
+ * actor to leave that polygon. Then runs the polygon's Glitter code
+ * with LEAVE event.
+ */
+static void EffectProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ EP_INIT *to = (EP_INIT *)param; // get the stuff copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int x, y; // Lead actor position
+
+ // Run effect poly enter script
+ effRunPolyTinselCode(to->hEpoly, ENTER, to->pActor->actorID);
+
+ do {
+ CORO_SLEEP(1);
+ GetMActorPosition(to->pActor, &x, &y);
+ } while (InPolygon(x, y, EFFECT) == to->hEpoly);
+
+ // Run effect poly leave script
+ effRunPolyTinselCode(to->hEpoly, LEAVE, to->pActor->actorID);
+
+ SetMAinEffectPoly(to->index, false);
+
+ CORO_END_CODE;
+}
+
+/**
+ * If the actor was not already in an effect polygon, checks to see if
+ * it has just entered one. If it has, a process is started up to run
+ * the polygon's Glitter code.
+ */
+static void FettleEffectPolys(int x, int y, int index, PMACTOR pActor) {
+ HPOLYGON hPoly;
+ EP_INIT epi;
+
+ // If just entered an effect polygon, the effect should be triggered.
+ if (!IsMAinEffectPoly(index)) {
+ hPoly = InPolygon(x, y, EFFECT);
+ if (hPoly != NOPOLY) {
+ //Just entered effect polygon
+ SetMAinEffectPoly(index, true);
+
+ epi.hEpoly = hPoly;
+ epi.pActor = pActor;
+ epi.index = index;
+ g_scheduler->createProcess(PID_TCODE, EffectProcess, &epi, sizeof(epi));
+ }
+ }
+}
+
+/**
+ * Just calls FettleEffectPolys() every clock tick.
+ */
+void EffectPolyProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ while (1) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ PMACTOR pActor = GetLiveMover(i);
+ if (pActor != NULL) {
+ int x, y;
+ GetMActorPosition(pActor, &x, &y);
+ FettleEffectPolys(x, y, i, pActor);
+ }
+ }
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+ CORO_END_CODE;
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/events.cpp b/engines/tinsel/events.cpp
new file mode 100644
index 0000000000..bf9f428fd4
--- /dev/null
+++ b/engines/tinsel/events.cpp
@@ -0,0 +1,439 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Main purpose is to process user events.
+ * Also provides a couple of utility functions.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/handle.h" // For LockMem()
+#include "tinsel/inventory.h"
+#include "tinsel/move.h" // For walking lead actor
+#include "tinsel/pcode.h" // For Interpret()
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h" // For walking lead actor
+#include "tinsel/sched.h"
+#include "tinsel/scroll.h" // For DontScrollCursor()
+#include "tinsel/timers.h" // DwGetCurrentTime()
+#include "tinsel/tinlib.h" // For control()
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in PDISPLAY.C
+extern int GetTaggedActor(void);
+extern HPOLYGON GetTaggedPoly(void);
+
+
+//----------------- EXTERNAL GLOBAL DATA ---------------------
+
+extern bool bEnableF1;
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int userEvents = 0; // Whenever a button or a key comes in
+static uint32 lastUserEvent = 0; // Time it hapenned
+static int butEvents = 0; // Single or double, left or right. Or escape key.
+static int escEvents = 0; // Escape key
+
+
+static int eCount = 0;
+
+/**
+ * Gets called before each schedule, only 1 user action per schedule
+ * is allowed.
+ */
+void ResetEcount(void) {
+ eCount = 0;
+}
+
+
+void IncUserEvents(void) {
+ userEvents++;
+ lastUserEvent = DwGetCurrentTime();
+}
+
+/**
+ * If this is a single click, wait to check it's not the first half of a
+ * double click.
+ * If this is a double click, the process from the waiting single click
+ * gets killed.
+ */
+void AllowDclick(CORO_PARAM, BUTEVENT be) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ if (be == BE_SLEFT) {
+ GetToken(TOKEN_LEFT_BUT);
+ CORO_SLEEP(dclickSpeed+1);
+ FreeToken(TOKEN_LEFT_BUT);
+
+ // Prevent activation of 2 events on the same tick
+ if (++eCount != 1)
+ CORO_KILL_SELF();
+
+ break;
+
+ } else if (be == BE_DLEFT) {
+ GetToken(TOKEN_LEFT_BUT);
+ FreeToken(TOKEN_LEFT_BUT);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Take control from player, if the player has it.
+ * Return TRUE if control taken, FALSE if not.
+ */
+
+bool GetControl(int param) {
+ if (TestToken(TOKEN_CONTROL)) {
+ control(param);
+ return true;
+ } else
+ return false;
+}
+
+struct TP_INIT {
+ HPOLYGON hPoly; // Polygon
+ USER_EVENT event; // Trigerring event
+ BUTEVENT bev; // To allow for double clicks
+ bool take_control; // Set if control should be taken
+ // while code is running.
+ int actor;
+};
+
+/**
+ * Runs glitter code associated with a polygon.
+ */
+static void PolyTinselProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ bool took_control; // Set if this function takes control
+ CORO_END_CONTEXT(_ctx);
+
+ TP_INIT *to = (TP_INIT *)param; // get the stuff copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, to->bev); // May kill us if single click
+
+ // Control may have gone off during AllowDclick()
+ if (!TestToken(TOKEN_CONTROL)
+ && (to->event == WALKTO || to->event == ACTION || to->event == LOOK))
+ CORO_KILL_SELF();
+
+ // Take control, if requested
+ if (to->take_control)
+ _ctx->took_control = GetControl(CONTROL_OFF);
+ else
+ _ctx->took_control = false;
+
+ // Hide conversation if appropriate
+ if (to->event == CONVERSE)
+ convHide(true);
+
+ // Run the code
+ _ctx->pic = InitInterpretContext(GS_POLYGON, getPolyScript(to->hPoly), to->event, to->hPoly, to->actor, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ // Free control if we took it
+ if (_ctx->took_control)
+ control(CONTROL_ON);
+
+ // Restore conv window if applicable
+ if (to->event == CONVERSE)
+ convHide(false);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Runs glitter code associated with a polygon.
+ */
+void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc) {
+ TP_INIT to = { hPoly, event, be, tc, 0 };
+
+ g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+}
+
+void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor) {
+ TP_INIT to = { hPoly, event, BE_NONE, false, actor };
+
+ g_scheduler->createProcess(PID_TCODE, PolyTinselProcess, &to, sizeof(to));
+}
+
+//-----------------------------------------------------------------------
+
+struct WP_INIT {
+ int x; // } Where to walk to
+ int y; // }
+};
+
+/**
+ * Perform a walk directly initiated by a click.
+ */
+static void WalkProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ PMACTOR pActor;
+ CORO_END_CONTEXT(_ctx);
+
+ WP_INIT *to = (WP_INIT *)param; // get the co-ordinates - copied to process when it was created
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pActor = GetMover(LEAD_ACTOR);
+ if (_ctx->pActor->MActorState == NORM_MACTOR) {
+ assert(_ctx->pActor->hCpath != NOPOLY); // Lead actor is not in a path
+
+ GetToken(TOKEN_LEAD);
+ SetActorDest(_ctx->pActor, to->x, to->y, false, 0);
+ DontScrollCursor();
+
+ while (MAmoving(_ctx->pActor))
+ CORO_SLEEP(1);
+
+ FreeToken(TOKEN_LEAD);
+ }
+
+ CORO_END_CODE;
+}
+
+void walkto(int x, int y) {
+ WP_INIT to = { x, y };
+
+ g_scheduler->createProcess(PID_TCODE, WalkProcess, &to, sizeof(to));
+}
+
+/**
+ * Run appropriate actor or polygon glitter code.
+ * If none, and it's a WALKTO event, do a walk.
+ */
+static void User_Event(USER_EVENT uEvent, BUTEVENT be) {
+ int actor;
+ int aniX, aniY;
+ HPOLYGON hPoly;
+
+ // Prevent activation of 2 events on the same tick
+ if (++eCount != 1)
+ return;
+
+ if ((actor = GetTaggedActor()) != 0)
+ actorEvent(actor, uEvent, be);
+ else if ((hPoly = GetTaggedPoly()) != NOPOLY)
+ RunPolyTinselCode(hPoly, uEvent, be, false);
+ else {
+ GetCursorXY(&aniX, &aniY, true);
+
+ // There could be a poly involved which has no tag.
+ if ((hPoly = InPolygon(aniX, aniY, TAG)) != NOPOLY
+ || (hPoly = InPolygon(aniX, aniY, EXIT)) != NOPOLY) {
+ RunPolyTinselCode(hPoly, uEvent, be, false);
+ } else if (uEvent == WALKTO)
+ walkto(aniX, aniY);
+ }
+}
+
+
+/**
+ * ProcessButEvent
+ */
+void ProcessButEvent(BUTEVENT be) {
+ IncUserEvents();
+
+ if (bSwapButtons) {
+ switch (be) {
+ case BE_SLEFT:
+ be = BE_SRIGHT;
+ break;
+ case BE_DLEFT:
+ be = BE_DRIGHT;
+ break;
+ case BE_SRIGHT:
+ be = BE_SLEFT;
+ break;
+ case BE_DRIGHT:
+ be = BE_DLEFT;
+ break;
+ case BE_LDSTART:
+ be = BE_RDSTART;
+ break;
+ case BE_LDEND:
+ be = BE_RDEND;
+ break;
+ case BE_RDSTART:
+ be = BE_LDSTART;
+ break;
+ case BE_RDEND:
+ be = BE_LDEND;
+ break;
+ default:
+ break;
+ }
+ }
+
+// if (be == BE_SLEFT || be == BE_DLEFT || be == BE_SRIGHT || be == BE_DRIGHT)
+ if (be == BE_SLEFT || be == BE_SRIGHT)
+ butEvents++;
+
+ if (!TestToken(TOKEN_CONTROL) && be != BE_LDEND)
+ return;
+
+ if (InventoryActive()) {
+ ButtonToInventory(be);
+ } else {
+ switch (be) {
+ case BE_SLEFT:
+ User_Event(WALKTO, BE_SLEFT);
+ break;
+
+ case BE_DLEFT:
+ User_Event(ACTION, BE_DLEFT);
+ break;
+
+ case BE_SRIGHT:
+ User_Event(LOOK, BE_SRIGHT);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * ProcessKeyEvent
+ */
+
+void ProcessKeyEvent(KEYEVENT ke) {
+ // This stuff to allow F1 key during startup.
+ if (bEnableF1 && ke == OPTION_KEY)
+ control(CONTROL_ON);
+ else
+ IncUserEvents();
+
+ if (ke == ESC_KEY) {
+ escEvents++;
+ butEvents++; // Yes, I do mean this
+ }
+
+ // FIXME: This comparison is weird - I added (BUTEVENT) cast for now to suppress warning
+ if (!TestToken(TOKEN_CONTROL) && (BUTEVENT)ke != BE_LDEND)
+ return;
+
+ switch (ke) {
+ case QUIT_KEY:
+ PopUpConf(QUIT);
+ break;
+
+ case OPTION_KEY:
+ PopUpConf(OPTION);
+ break;
+
+ case SAVE_KEY:
+ PopUpConf(SAVE);
+ break;
+
+ case LOAD_KEY:
+ PopUpConf(LOAD);
+ break;
+
+ case WALKTO_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_SLEFT);
+ else
+ User_Event(WALKTO, BE_NONE);
+ break;
+
+ case ACTION_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_DLEFT);
+ else
+ User_Event(ACTION, BE_NONE);
+ break;
+
+ case LOOK_KEY:
+ if (InventoryActive())
+ ButtonToInventory(BE_SRIGHT);
+ else
+ User_Event(LOOK, BE_NONE);
+ break;
+
+ case ESC_KEY:
+ case PGUP_KEY:
+ case PGDN_KEY:
+ case HOME_KEY:
+ case END_KEY:
+ if (InventoryActive())
+ KeyToInventory(ke);
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * For ESCapable Glitter sequences
+ */
+
+int GetEscEvents(void) {
+ return escEvents;
+}
+
+/**
+ * For cutting short talk()s etc.
+ */
+
+int GetLeftEvents(void) {
+ return butEvents;
+}
+
+/**
+ * For waitkey() Glitter function
+ */
+
+int getUserEvents(void) {
+ return userEvents;
+}
+
+uint32 getUserEventTime(void) {
+ return DwGetCurrentTime() - lastUserEvent;
+}
+
+void resetUserEventTime(void) {
+ lastUserEvent = DwGetCurrentTime();
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/events.h b/engines/tinsel/events.h
new file mode 100644
index 0000000000..bc49d68717
--- /dev/null
+++ b/engines/tinsel/events.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * User events processing and utility functions
+ */
+
+#ifndef TINSEL_EVENTS_H
+#define TINSEL_EVENTS_H
+
+#include "tinsel/dw.h"
+#include "tinsel/coroutine.h"
+
+namespace Tinsel {
+
+enum BUTEVENT {
+ BE_NONE, BE_SLEFT, BE_DLEFT, BE_SRIGHT, BE_DRIGHT,
+ BE_LDSTART, BE_LDEND, BE_RDSTART, BE_RDEND,
+ BE_UNKNOWN
+};
+
+
+enum KEYEVENT {
+ ESC_KEY, QUIT_KEY, SAVE_KEY, LOAD_KEY, OPTION_KEY,
+ PGUP_KEY, PGDN_KEY, HOME_KEY, END_KEY,
+ WALKTO_KEY, ACTION_KEY, LOOK_KEY,
+ NOEVENT_KEY
+};
+
+
+/**
+ * Reasons for running Glitter code.
+ * Do not re-order these as equivalent CONSTs are defined in the master
+ * scene Glitter source file for testing against the event() library function.
+ */
+enum USER_EVENT {
+ POINTED, WALKTO, ACTION, LOOK,
+ ENTER, LEAVE, STARTUP, CONVERSE,
+ UNPOINT, PUTDOWN,
+ NOEVENT
+};
+
+
+void AllowDclick(CORO_PARAM, BUTEVENT be);
+bool GetControl(int param);
+
+void RunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, BUTEVENT be, bool tc);
+void effRunPolyTinselCode(HPOLYGON hPoly, USER_EVENT event, int actor);
+
+void ProcessButEvent(BUTEVENT be);
+void ProcessKeyEvent(KEYEVENT ke);
+
+
+int GetEscEvents(void);
+int GetLeftEvents(void);
+int getUserEvents(void);
+
+uint32 getUserEventTime(void);
+void resetUserEventTime(void);
+
+void ResetEcount(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_EVENTS_H */
diff --git a/engines/tinsel/faders.cpp b/engines/tinsel/faders.cpp
new file mode 100644
index 0000000000..0018727ccb
--- /dev/null
+++ b/engines/tinsel/faders.cpp
@@ -0,0 +1,175 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Fader and Flasher processes.
+ */
+
+#include "tinsel/pid.h" // list of all process IDs
+#include "tinsel/sched.h" // scheduler defs
+#include "tinsel/faders.h" // fader defs
+#include "tinsel/handle.h"
+#include "tinsel/palette.h" // Palette Manager defs
+
+namespace Tinsel {
+
+/** structure used by the "FadeProcess" process */
+struct FADE {
+ const long *pColourMultTable; // list of fixed point colour multipliers - terminated with negative entry
+ PALQ *pPalQ; // palette queue entry to fade
+};
+
+// fixed point fade multiplier tables
+//const long fadeout[] = {0xf000, 0xd000, 0xb000, 0x9000, 0x7000, 0x5000, 0x3000, 0x1000, 0, -1};
+const long fadeout[] = {0xd000, 0xa000, 0x7000, 0x4000, 0x1000, 0, -1};
+//const long fadein[] = {0, 0x1000, 0x3000, 0x5000, 0x7000, 0x9000, 0xb000, 0xd000, 0x10000L, -1};
+const long fadein[] = {0, 0x1000, 0x4000, 0x7000, 0xa000, 0xd000, 0x10000L, -1};
+
+/**
+ * Scale 'colour' by the fixed point colour multiplier 'colourMult'
+ * @param colour Colour to scale
+ * @param colourMult Fixed point multiplier
+ */
+static COLORREF ScaleColour(COLORREF colour, uint32 colourMult) {
+ // apply multiplier to RGB components
+ uint32 red = ((GetRValue(colour) * colourMult) << 8) >> 24;
+ uint32 green = ((GetGValue(colour) * colourMult) << 8) >> 24;
+ uint32 blue = ((GetBValue(colour) * colourMult) << 8) >> 24;
+
+ // return new colour
+ return RGB(red, green, blue);
+}
+
+/**
+ * Applies the fixed point multiplier 'mult' to all colours in
+ * 'pOrig' to produce 'pNew'. Each colour in the palette will be
+ * multiplied by 'mult'.
+ * @param pNew Pointer to new palette
+ * @param pOrig Pointer to original palette
+ * @param numColours Number of colours in the above palettes
+ * @param mult Fixed point multiplier
+ */
+static void FadePalette(COLORREF *pNew, COLORREF *pOrig, int numColours, uint32 mult) {
+ for (int i = 0; i < numColours; i++, pNew++, pOrig++) {
+ // apply multiplier to RGB components
+ *pNew = ScaleColour(*pOrig, mult);
+ }
+}
+
+/**
+ * Process to fade one palette.
+ * A pointer to a 'FADE' structure must be passed to this process when
+ * it is created.
+ */
+static void FadeProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ COLORREF fadeRGB[MAX_COLOURS]; // local copy of palette
+ const long *pColMult; // pointer to colour multiplier table
+ PALETTE *pPalette; // pointer to palette
+ CORO_END_CONTEXT(_ctx);
+
+ // get the fade data structure - copied to process when it was created
+ FADE *pFade = (FADE *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // get pointer to palette - reduce pointer indirection a bit
+ _ctx->pPalette = (PALETTE *)LockMem(pFade->pPalQ->hPal);
+
+ for (_ctx->pColMult = pFade->pColourMultTable; *_ctx->pColMult >= 0; _ctx->pColMult++) {
+ // go through all multipliers in table - until a negative entry
+
+ // fade palette using next multiplier
+ FadePalette(_ctx->fadeRGB, _ctx->pPalette->palRGB,
+ FROM_LE_32(_ctx->pPalette->numColours), (uint32) *_ctx->pColMult);
+
+ // send new palette to video DAC
+ UpdateDACqueue(pFade->pPalQ->posInDAC, FROM_LE_32(_ctx->pPalette->numColours), _ctx->fadeRGB);
+
+ // allow time for video DAC to be updated
+ CORO_SLEEP(1);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Generic palette fader/unfader. Creates a 'FadeProcess' process
+ * for each palette that is to fade.
+ * @param multTable Fixed point colour multiplier table
+ * @param noFadeTable List of palettes not to fade
+ */
+static void Fader(const long multTable[], SCNHANDLE noFadeTable[]) {
+ PALQ *pPal; // palette manager iterator
+
+ // create a process for each palette in the palette queue
+ for (pPal = GetNextPalette(NULL); pPal != NULL; pPal = GetNextPalette(pPal)) {
+ bool bFade = true;
+ // assume we want to fade this palette
+
+ // is palette in the list of palettes not to fade
+ if (noFadeTable != NULL) {
+ // there is a list of palettes not to fade
+ for (int i = 0; noFadeTable[i] != 0; i++) {
+ if (pPal->hPal == noFadeTable[i]) {
+ // palette is in the list - dont fade it
+ bFade = false;
+
+ // leave loop prematurely
+ break;
+ }
+ }
+ }
+
+ if (bFade) {
+ FADE fade;
+
+ // fill in FADE struct
+ fade.pColourMultTable = multTable;
+ fade.pPalQ = pPal;
+
+ // create a fader process for this palette
+ g_scheduler->createProcess(PID_FADER, FadeProcess, (void *)&fade, sizeof(FADE));
+ }
+ }
+}
+
+/**
+ * Fades a list of palettes down to black.
+ * @param noFadeTable A NULL terminated list of palettes not to fade.
+ */
+void FadeOutFast(SCNHANDLE noFadeTable[]) {
+ // call generic fader
+ Fader(fadeout, noFadeTable);
+}
+
+/**
+ * Fades a list of palettes from black to their current colours.
+ * @param noFadeTable A NULL terminated list of palettes not to fade.
+ */
+void FadeInFast(SCNHANDLE noFadeTable[]) {
+ // call generic fader
+ Fader(fadein, noFadeTable);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/faders.h b/engines/tinsel/faders.h
new file mode 100644
index 0000000000..1e9336fae8
--- /dev/null
+++ b/engines/tinsel/faders.h
@@ -0,0 +1,55 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used by the fader and flasher processes
+ */
+
+#ifndef TINSEL_FADERS_H // prevent multiple includes
+#define TINSEL_FADERS_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+enum {
+ /**
+ * Number of iterations in a fade out.
+ * Must match which FadeOut() is in use.
+ */
+ COUNTOUT_COUNT = 6
+};
+
+/*----------------------------------------------------------------------*\
+|* Fader Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+// usefull palette faders - they all need a list of palettes that
+// should not be faded. This parameter can be
+// NULL - fade all palettes.
+
+void FadeOutFast(SCNHANDLE noFadeTable[]);
+void FadeInFast(SCNHANDLE noFadeTable[]);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_FADERS_H
diff --git a/engines/tinsel/film.h b/engines/tinsel/film.h
new file mode 100644
index 0000000000..c8bf4604bc
--- /dev/null
+++ b/engines/tinsel/film.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_FILM_H // prevent multiple includes
+#define TINSEL_FILM_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+struct FREEL {
+ SCNHANDLE mobj;
+ SCNHANDLE script;
+} PACKED_STRUCT;
+
+struct FILM {
+ int32 frate;
+ int32 numreels;
+ FREEL reels[1];
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/font.cpp b/engines/tinsel/font.cpp
new file mode 100644
index 0000000000..620298867e
--- /dev/null
+++ b/engines/tinsel/font.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.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/object.h"
+#include "tinsel/text.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static char tBuffer[TBUFSZ];
+
+static SCNHANDLE hTagFont = 0, hTalkFont = 0;
+
+
+/**
+ * Return address of tBuffer
+ */
+char *tBufferAddr() {
+ return tBuffer;
+}
+
+/**
+ * Return hTagFont handle.
+ */
+SCNHANDLE hTagFontHandle() {
+ return hTagFont;
+}
+
+/**
+ * Return hTalkFont handle.
+ */
+SCNHANDLE hTalkFontHandle() {
+ return hTalkFont;
+}
+
+/**
+ * Called from dec_tagfont() Glitter function. Store the tag font handle.
+ */
+void TagFontHandle(SCNHANDLE hf) {
+ hTagFont = hf; // Store the font handle
+}
+
+/**
+ * Called from dec_talkfont() Glitter function.
+ * Store the talk font handle.
+ */
+void TalkFontHandle(SCNHANDLE hf) {
+ hTalkFont = hf; // Store the font handle
+}
+
+/**
+ * Poke the background palette into character 0's images.
+ */
+void fettleFontPal(SCNHANDLE fontPal) {
+ const FONT *pFont;
+ IMAGE *pImg;
+
+ assert(fontPal);
+ assert(hTagFont); // Tag font not declared
+ assert(hTalkFont); // Talk font not declared
+
+ pFont = (const FONT *)LockMem(hTagFont);
+ pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0
+ pImg->hImgPal = TO_LE_32(fontPal);
+
+ pFont = (const FONT *)LockMem(hTalkFont);
+ pImg = (IMAGE *)LockMem(FROM_LE_32(pFont->fontInit.hObjImg)); // get image for char 0
+ pImg->hImgPal = TO_LE_32(fontPal);
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/font.h b/engines/tinsel/font.h
new file mode 100644
index 0000000000..b75c36191c
--- /dev/null
+++ b/engines/tinsel/font.h
@@ -0,0 +1,48 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_FONT_H // prevent multiple includes
+#define TINSEL_FONT_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// A temporary buffer for extracting text into is defined in font.c
+// Accessed using tBufferAddr(), this is how big it is:
+#define TBUFSZ 512
+
+
+char *tBufferAddr(void);
+SCNHANDLE hTagFontHandle(void);
+SCNHANDLE hTalkFontHandle(void);
+
+void TagFontHandle(SCNHANDLE hf);
+void TalkFontHandle(SCNHANDLE hf);
+void fettleFontPal(SCNHANDLE fontPal);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_FONT_H
diff --git a/engines/tinsel/graphics.cpp b/engines/tinsel/graphics.cpp
new file mode 100644
index 0000000000..cd0937d944
--- /dev/null
+++ b/engines/tinsel/graphics.cpp
@@ -0,0 +1,440 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Low level graphics interface.
+ */
+
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h" // LockMem()
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+// Defines used in graphic drawing
+#define CHARPTR_OFFSET 16
+#define CHAR_WIDTH 4
+#define CHAR_HEIGHT 4
+
+extern uint8 transPalette[MAX_COLOURS];
+
+//----------------- SUPPORT FUNCTIONS ---------------------
+
+/**
+ * Straight rendering with transparency support
+ */
+
+static void WrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
+ // Set up the offset between destination blocks
+ int rightClip = applyClipping ? pObj->rightClip : 0;
+ Common::Rect boxBounds;
+
+ if (applyClipping) {
+ // Adjust the height down to skip any bottom clipping
+ pObj->height -= pObj->botClip;
+
+ // Make adjustment for the top clipping row
+ srcP += sizeof(uint16) * ((pObj->width + 3) >> 2) * (pObj->topClip >> 2);
+ pObj->height -= pObj->topClip;
+ pObj->topClip %= 4;
+ }
+
+ // Vertical loop
+ while (pObj->height > 0) {
+ // Get the start of the next line output
+ uint8 *tempDest = destP;
+
+ // Get the line width, and figure out which row range within the 4 row high blocks
+ // will be displayed if clipping is to be taken into account
+ int width = pObj->width;
+
+ if (!applyClipping) {
+ // No clipping, so so set box bounding area for drawing full 4x4 pixel blocks
+ boxBounds.top = 0;
+ boxBounds.bottom = 3;
+ boxBounds.left = 0;
+ } else {
+ // Handle any possible clipping at the top of the char block.
+ // We already handled topClip partially at the beginning of this function.
+ // Hence the only non-zero values it can assume at this point are 1,2,3,
+ // and that only during the very first iteration (i.e. when the top char
+ // block is drawn only partially). In particular, we set topClip to zero,
+ // as all following blocks are not to be top clipped.
+ boxBounds.top = pObj->topClip;
+ pObj->topClip = 0;
+
+ boxBounds.bottom = MIN(boxBounds.top + pObj->height - 1, 3);
+
+ // Handle any possible clipping at the start of the line
+ boxBounds.left = pObj->leftClip;
+ if (boxBounds.left >= 4) {
+ srcP += sizeof(uint16) * (boxBounds.left >> 2);
+ width -= boxBounds.left & 0xfffc;
+ boxBounds.left %= 4;
+ }
+
+ width -= boxBounds.left;
+ }
+
+ // Horizontal loop
+ while (width > rightClip) {
+ boxBounds.right = MIN(boxBounds.left + width - rightClip - 1, 3);
+ assert(boxBounds.bottom >= boxBounds.top);
+ assert(boxBounds.right >= boxBounds.left);
+
+ int16 indexVal = READ_LE_UINT16(srcP);
+ srcP += sizeof(uint16);
+
+ if (indexVal >= 0) {
+ // Draw a 4x4 block based on the opcode as in index into the block list
+ const uint8 *p = (uint8 *)pObj->charBase + (indexVal << 4);
+ p += boxBounds.top * sizeof(uint32);
+ for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp, p += sizeof(uint32)) {
+ Common::copy(p + boxBounds.left, p + boxBounds.right + 1, tempDest + (SCREEN_WIDTH * (yp - boxBounds.top)));
+ }
+
+ } else {
+ // Draw a 4x4 block with transparency support
+ indexVal &= 0x7fff;
+
+ // If index is zero, then skip drawing the block completely
+ if (indexVal > 0) {
+ // Use the index along with the object's translation offset
+ const uint8 *p = (uint8 *)pObj->charBase + ((pObj->transOffset + indexVal) << 4);
+
+ // Loop through each pixel - only draw a pixel if it's non-zero
+ p += boxBounds.top * sizeof(uint32);
+ for (int yp = boxBounds.top; yp <= boxBounds.bottom; ++yp) {
+ p += boxBounds.left;
+ for (int xp = boxBounds.left; xp <= boxBounds.right; ++xp, ++p) {
+ if (*p)
+ *(tempDest + SCREEN_WIDTH * (yp - boxBounds.top) + (xp - boxBounds.left)) = *p;
+ }
+ p += 3 - boxBounds.right;
+ }
+ }
+ }
+
+ tempDest += boxBounds.right - boxBounds.left + 1;
+ width -= 3 - boxBounds.left + 1;
+
+ // None of the remaining horizontal blocks should be left clipped
+ boxBounds.left = 0;
+ }
+
+ // If there is any width remaining, there must be a right edge clipping
+ if (width >= 0)
+ srcP += sizeof(uint16) * ((width + 3) >> 2);
+
+ // Move to next line line
+ pObj->height -= boxBounds.bottom - boxBounds.top + 1;
+ destP += (boxBounds.bottom - boxBounds.top + 1) * SCREEN_WIDTH;
+ }
+}
+
+/**
+ * Fill the destination area with a constant colour
+ */
+
+static void WrtConst(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
+ if (applyClipping) {
+ pObj->height -= pObj->topClip + pObj->botClip;
+ pObj->width -= pObj->leftClip + pObj->rightClip;
+
+ if (pObj->width <= 0)
+ return;
+ }
+
+ // Loop through any remaining lines
+ while (pObj->height > 0) {
+ Common::set_to(destP, destP + pObj->width, pObj->constant);
+
+ --pObj->height;
+ destP += SCREEN_WIDTH;
+ }
+}
+
+/**
+ * Translates the destination surface within the object's bounds using the transparency
+ * lookup table from transpal.cpp (the contents of which have been moved into palette.cpp)
+ */
+
+static void WrtTrans(DRAWOBJECT *pObj, uint8 *destP, bool applyClipping) {
+ if (applyClipping) {
+ pObj->height -= pObj->topClip + pObj->botClip;
+ pObj->width -= pObj->leftClip + pObj->rightClip;
+
+ if (pObj->width <= 0)
+ return;
+ }
+
+ // Set up the offset between destination lines
+ int lineOffset = SCREEN_WIDTH - pObj->width;
+
+ // Loop through any remaining lines
+ while (pObj->height > 0) {
+ for (int i = 0; i < pObj->width; ++i, ++destP)
+ *destP = transPalette[*destP];
+
+ --pObj->height;
+ destP += lineOffset;
+ }
+}
+
+
+#if 0
+// This commented out code is the untested original WrtNonZero/ClpWrtNonZero combo method
+// from the v1 source. It may be needed to be included later on to support v1 gfx files
+
+/**
+ * Straight rendering with transparency support
+ * Possibly only used in the Discworld Demo
+ */
+
+static void DemoWrtNonZero(DRAWOBJECT *pObj, uint8 *srcP, uint8 *destP, bool applyClipping) {
+ // FIXME: If this method is used for the demo, it still needs to be made Endian safe
+
+ // Set up the offset between destination lines
+ pObj->lineoffset = SCREEN_WIDTH - pObj->width - (applyClipping ? pObj->leftClip - pObj->rightClip : 0);
+
+ // Top clipped line handling
+ while (applyClipping && (pObj->topClip > 0)) {
+ // Loop through discarding the data for the line
+ int width = pObj->width;
+ while (width > 0) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Dump the data
+ srcP += ((opcodeOrLen + 3) / 4) * 4;
+ width -= opcodeOrLen;
+ } else {
+ // Dump the run-length opcode
+ width -= -opcodeOrLen;
+ }
+ }
+
+ --pObj->height;
+ --pObj->topClip;
+ }
+
+ // Loop for the required number of rows
+ while (pObj->height > 0) {
+
+ int width = pObj->width;
+
+ // Handling for left edge clipping - this basically involves dumping data until we reach
+ // the part of the line to be displayed
+ int clipLeft = pObj->leftClip;
+ while (applyClipping && (clipLeft > 0)) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Copy a specified number of bytes
+ // Make adjustments for past the clipping width
+ int remainder = 4 - (opcodeOrLen % 4);
+ srcP += MIN(clipLeft, opcodeOrLen);
+ opcodeOrLen -= MIN(clipLeft, opcodeOrLen);
+ clipLeft -= MIN(clipLeft, opcodeOrLen);
+ width -= opcodeOrLen;
+
+
+ // Handle any right edge clipping (if run length covers entire width)
+ if (width < pObj->rightClip) {
+ remainder += (pObj->rightClip - width);
+ opcodeOrLen -= (pObj->rightClip - width);
+ }
+
+ if (opcodeOrLen > 0)
+ Common::copy(srcP, srcP + opcodeOrLen, destP);
+
+ } else {
+ // Output a run length number of bytes
+ // Get data for byte value and run length
+ opcodeOrLen = -opcodeOrLen;
+ int runLength = opcodeOrLen & 0xff;
+ uint8 colourVal = (opcodeOrLen >> 8) & 0xff;
+
+ // Make adjustments for past the clipping width
+ runLength -= MIN(clipLeft, runLength);
+ clipLeft -= MIN(clipLeft, runLength);
+ width -= runLength;
+
+ // Handle any right edge clipping (if run length covers entire width)
+ if (width < pObj->rightClip)
+ runLength -= (pObj->rightClip - width);
+
+ if (runLength > 0) {
+ // Displayable part starts partway through the slice
+ if (colourVal != 0)
+ Common::set_to(destP, destP + runLength, colourVal);
+ destP += runLength;
+ }
+ }
+
+ if (width < pObj->rightClip)
+ width = 0;
+ }
+
+ // Handling for the visible part of the line
+ int endWidth = applyClipping ? pObj->rightClip : 0;
+ while (width > endWidth) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Copy the specified number of bytes
+ int remainder = 4 - (opcodeOrLen % 4);
+
+ if (width < endWidth) {
+ // Shorten run length by right clipping
+ remainder += (pObj->rightClip - width);
+ opcodeOrLen -= (pObj->rightClip - width);
+ }
+
+ Common::copy(srcP, srcP + opcodeOrLen, destP);
+ srcP += opcodeOrLen + remainder;
+ destP += opcodeOrLen;
+ width -= opcodeOrLen;
+
+ } else {
+ // Handle a given run length
+ opcodeOrLen = -opcodeOrLen;
+ int runLength = opcodeOrLen & 0xff;
+ uint8 colourVal = (opcodeOrLen >> 8) & 0xff;
+
+ if (width < endWidth)
+ // Shorten run length by right clipping
+ runLength -= (pObj->rightClip - width);
+
+ // Only set pixels if colourVal non-zero (0 signifies transparency)
+ if (colourVal != 0)
+ // Fill out a run length of a specified colour
+ Common::set_to(destP, destP + runLength, colourVal);
+
+ destP += runLength;
+ width -= runLength;
+ }
+ }
+
+ // If right edge clipping is being applied, then width may still be non-zero - in
+ // that case all remaining line data until the end of the line must be ignored
+ while (width > 0) {
+ int32 opcodeOrLen = (int32)READ_LE_UINT32(srcP);
+ srcP += sizeof(uint32);
+
+ if (opcodeOrLen >= 0) {
+ // Dump the data
+ srcP += ((opcodeOrLen + 3) / 4) * 4;
+ width -= opcodeOrLen;
+ } else {
+ // Dump the run-length opcode
+ width -= -opcodeOrLen;
+ }
+ }
+
+ --pObj->height;
+ destP += pObj->lineoffset;
+ }
+}
+#endif
+
+//----------------- MAIN FUNCTIONS ---------------------
+
+/**
+ * Clears both the screen surface buffer and screen to the specified value
+ */
+void ClearScreen() {
+ void *pDest = _vm->screen().getBasePtr(0, 0);
+ memset(pDest, 0, SCREEN_WIDTH * SCREEN_HEIGHT);
+ g_system->clearScreen();
+ g_system->updateScreen();
+}
+
+/**
+ * Updates the screen surface within the following rectangle
+ */
+void UpdateScreenRect(const Common::Rect &pClip) {
+ byte *pDest = (byte *)_vm->screen().getBasePtr(pClip.left, pClip.top);
+ g_system->copyRectToScreen(pDest, _vm->screen().pitch, pClip.left, pClip.top, pClip.width(), pClip.height());
+ g_system->updateScreen();
+}
+
+/**
+ * Draws the specified object onto the screen surface buffer
+ */
+void DrawObject(DRAWOBJECT *pObj) {
+ uint8 *srcPtr = NULL;
+ uint8 *destPtr;
+
+ if ((pObj->width <= 0) || (pObj->height <= 0))
+ // Empty image, so return immediately
+ return;
+
+ // If writing constant data, don't bother locking the data pointer and reading src details
+ if ((pObj->flags & DMA_CONST) == 0) {
+ byte *p = (byte *)LockMem(pObj->hBits & 0xFF800000);
+
+ srcPtr = p + (pObj->hBits & 0x7FFFFF);
+ pObj->charBase = (char *)p + READ_LE_UINT32(p + 0x10);
+ pObj->transOffset = READ_LE_UINT32(p + 0x14);
+ }
+
+ // Get destination starting point
+ destPtr = (byte *)_vm->screen().getBasePtr(pObj->xPos, pObj->yPos);
+
+ // Handle various draw types
+ uint8 typeId = pObj->flags & 0xff;
+ switch (typeId) {
+ case 0x01:
+ case 0x08:
+ case 0x41:
+ case 0x48:
+ WrtNonZero(pObj, srcPtr, destPtr, typeId >= 0x40);
+ break;
+
+ case 0x04:
+ case 0x44:
+ // ClpWrtConst with/without clipping
+ WrtConst(pObj,destPtr, typeId == 0x44);
+ break;
+
+ case 0x84:
+ case 0xC4:
+ // WrtTrans with/without clipping
+ WrtTrans(pObj, destPtr, typeId == 0xC4);
+ break;
+
+ default:
+ // NoOp
+ error("Unknown drawing type %d", typeId);
+ break;
+ }
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/graphics.h b/engines/tinsel/graphics.h
new file mode 100644
index 0000000000..85299d4873
--- /dev/null
+++ b/engines/tinsel/graphics.h
@@ -0,0 +1,78 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Low level graphics interface.
+ */
+
+#ifndef TINSEL_GRAPHICS_H // prevent multiple includes
+#define TINSEL_GRAPHICS_H
+
+#include "tinsel/dw.h"
+
+#include "common/rect.h"
+#include "common/system.h"
+#include "graphics/surface.h"
+
+namespace Tinsel {
+
+struct PALQ;
+
+
+#define SCREEN_WIDTH 320 // PC screen dimensions
+#define SCREEN_HEIGHT 200
+#define SCRN_CENTRE_X ((SCREEN_WIDTH - 1) / 2) // screen centre x
+#define SCRN_CENTRE_Y ((SCREEN_HEIGHT - 1) / 2) // screen centre y
+
+/** draw object structure - only used when drawing objects */
+struct DRAWOBJECT {
+ char *charBase; // character set base address
+ int transOffset; // transparent character offset
+ int flags; // object flags - see above for list
+ PALQ *pPal; // objects palette Q position
+ int constant; // which colour in palette for monochrome objects
+ int width; // width of object
+ int height; // height of object
+ SCNHANDLE hBits; // image bitmap handle
+ int lineoffset; // offset to next line
+ int leftClip; // amount to clip off object left
+ int rightClip; // amount to clip off object right
+ int topClip; // amount to clip off object top
+ int botClip; // amount to clip off object bottom
+ short xPos; // x position of object
+ short yPos; // y position of object
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ClearScreen();
+void DrawObject(DRAWOBJECT *pObj);
+
+// called to update a rectangle on the video screen from a video page
+void UpdateScreenRect(const Common::Rect &pClip);
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/handle.cpp b/engines/tinsel/handle.cpp
new file mode 100644
index 0000000000..11623516ec
--- /dev/null
+++ b/engines/tinsel/handle.cpp
@@ -0,0 +1,366 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager code
+ */
+
+#define BODGE
+
+#include "common/file.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/scn.h" // name of "index" file
+#include "tinsel/handle.h"
+#include "tinsel/heapmem.h" // heap memory manager
+
+
+// these are included only so the relevant structs can be used in convertLEStructToNative()
+#include "tinsel/anim.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/film.h"
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/text.h"
+#include "tinsel/scene.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+bool bLockedScene = 0;
+#endif
+
+
+//----------------- LOCAL DEFINES --------------------
+
+struct MEMHANDLE {
+ char szName[12]; //!< 00 - file name of graphics file
+ int32 filesize; //!< 12 - file size and flags
+ MEM_NODE *pNode; //!< 16 - memory node for the graphics
+};
+
+
+/** memory allocation flags - stored in the top bits of the filesize field */
+enum {
+ fPreload = 0x01000000L, //!< preload memory
+ fDiscard = 0x02000000L, //!< discard memory
+ fSound = 0x04000000L, //!< sound data
+ fGraphic = 0x08000000L, //!< graphic data
+ fCompressed = 0x10000000L, //!< compressed data
+ fLoaded = 0x20000000L //!< set when file data has been loaded
+};
+#define FSIZE_MASK 0x00FFFFFFL //!< mask to isolate the filesize
+#define MALLOC_MASK 0xFF000000L //!< mask to isolate the memory allocation flags
+#define OFFSETMASK 0x007fffffL //!< get offset of address
+//#define HANDLEMASK 0xFF800000L //!< get handle of address
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+// handle table gets loaded from index file at runtime
+static MEMHANDLE *handleTable = 0;
+
+// number of handles in the handle table
+static uint numHandles = 0;
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void LoadFile(MEMHANDLE *pH, bool bWarn); // load a memory block as a file
+
+
+/**
+ * Loads the graphics handle table index file and preloads all the
+ * permanent graphics etc.
+ */
+void SetupHandleTable(void) {
+ enum { RECORD_SIZE = 20 };
+
+ int len;
+ uint i;
+ MEMHANDLE *pH;
+ Common::File f;
+
+ if (f.open(INDEX_FILENAME)) {
+ // get size of index file
+ len = f.size();
+
+ if (len > 0) {
+ if ((len % RECORD_SIZE) != 0) {
+ // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+
+ // calc number of handles
+ numHandles = len / RECORD_SIZE;
+
+ // allocate memory for the index file
+ handleTable = (MEMHANDLE *)calloc(numHandles, sizeof(struct MEMHANDLE));
+
+ // make sure memory allocated
+ assert(handleTable);
+
+ // load data
+ for (i = 0; i < numHandles; i++) {
+ f.read(handleTable[i].szName, 12);
+ handleTable[i].filesize = f.readUint32LE();
+ // The pointer should always be NULL. We don't
+ // need to read that from the file.
+ handleTable[i].pNode = NULL;
+ f.seek(4, SEEK_CUR);
+ }
+
+ if (f.ioFailed()) {
+ // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+
+ // close the file
+ f.close();
+ } else { // index file is corrupt
+ error("File %s is corrupt", INDEX_FILENAME);
+ }
+ } else { // cannot find the index file
+ error("Cannot find file %s", INDEX_FILENAME);
+ }
+
+ // allocate memory nodes and load all permanent graphics
+ for (i = 0, pH = handleTable; i < numHandles; i++, pH++) {
+ if (pH->filesize & fPreload) {
+ // allocate a fixed memory node for permanent files
+ pH->pNode = MemoryAlloc(DWM_FIXED, pH->filesize & FSIZE_MASK);
+
+ // make sure memory allocated
+ assert(pH->pNode);
+
+ // load the data
+ LoadFile(pH, true);
+ }
+#ifdef BODGE
+ else if ((pH->filesize & FSIZE_MASK) == 8) {
+ pH->pNode = NULL;
+ }
+#endif
+ else {
+ // allocate a discarded memory node for other files
+ pH->pNode = MemoryAlloc(
+ DWM_MOVEABLE | DWM_DISCARDABLE | DWM_NOALLOC,
+ pH->filesize & FSIZE_MASK);
+
+ // make sure memory allocated
+ assert(pH->pNode);
+ }
+ }
+}
+
+void FreeHandleTable(void) {
+ if (handleTable) {
+ free(handleTable);
+ handleTable = NULL;
+ }
+}
+
+/**
+ * Loads a memory block as a file.
+ * @param pH Memory block pointer
+ * @param bWarn If set, treat warnings as errors
+ */
+void LoadFile(MEMHANDLE *pH, bool bWarn) {
+ Common::File f;
+ char szFilename[sizeof(pH->szName) + 1];
+
+ if (pH->filesize & fCompressed) {
+ error("Compression handling has been removed!");
+ }
+
+ // extract and zero terminate the filename
+ strncpy(szFilename, pH->szName, sizeof(pH->szName));
+ szFilename[sizeof(pH->szName)] = 0;
+
+ if (f.open(szFilename)) {
+ // read the data
+ int bytes;
+ uint8 *addr;
+
+ if (pH->filesize & fPreload)
+ // preload - no need to lock the memory
+ addr = (uint8 *)pH->pNode;
+ else {
+ // discardable - lock the memory
+ addr = (uint8 *)MemoryLock(pH->pNode);
+ }
+#ifdef DEBUG
+ if (addr == NULL) {
+ if (pH->filesize & fPreload)
+ // preload - no need to lock the memory
+ addr = (uint8 *)pH->pNode;
+ else {
+ // discardable - lock the memory
+ addr = (uint8 *)MemoryLock(pH->pNode);
+ }
+ }
+#endif
+
+ // make sure address is valid
+ assert(addr);
+
+ bytes = f.read(addr, pH->filesize & FSIZE_MASK);
+
+ // close the file
+ f.close();
+
+ if ((pH->filesize & fPreload) == 0) {
+ // discardable - unlock the memory
+ MemoryUnlock(pH->pNode);
+ }
+
+ // set the loaded flag
+ pH->filesize |= fLoaded;
+
+ if (bytes == (pH->filesize & FSIZE_MASK)) {
+ return;
+ }
+
+ if (bWarn)
+ // file is corrupt
+ error("File %s is corrupt", szFilename);
+ }
+
+ if (bWarn)
+ // cannot find file
+ error("Cannot find file %s", szFilename);
+}
+
+/**
+ * Returns the address of a image, given its memory handle.
+ * @param offset Handle and offset to data
+ */
+uint8 *LockMem(SCNHANDLE offset) {
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ if (pH->filesize & fPreload) {
+ // permanent files are already loaded
+ return (uint8 *)pH->pNode + (offset & OFFSETMASK);
+ } else {
+ if (pH->pNode->pBaseAddr && (pH->filesize & fLoaded))
+ // already allocated and loaded
+ return pH->pNode->pBaseAddr + (offset & OFFSETMASK);
+
+ if (pH->pNode->pBaseAddr == NULL)
+ // must have been discarded - reallocate the memory
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK,
+ DWM_MOVEABLE | DWM_DISCARDABLE);
+
+ if (pH->pNode->pBaseAddr == NULL)
+ error("Out of memory");
+
+ LoadFile(pH, true);
+
+ // make sure address is valid
+ assert(pH->pNode->pBaseAddr);
+
+ return pH->pNode->pBaseAddr + (offset & OFFSETMASK);
+ }
+}
+
+/**
+ * Called to make the current scene non-discardable.
+ * @param offset Handle and offset to data
+ */
+void LockScene(SCNHANDLE offset) {
+
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+#ifdef DEBUG
+ assert(!bLockedScene); // Trying to lock more than one scene
+#endif
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ // compact the heap to avoid fragmentation while scene is non-discardable
+ HeapCompact(MAX_INT, false);
+
+ if ((pH->filesize & fPreload) == 0) {
+ // change the flags for the node
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE);
+#ifdef DEBUG
+ bLockedScene = true;
+#endif
+ }
+}
+
+/**
+ * Called to make the current scene discardable again.
+ * @param offset Handle and offset to data
+ */
+void UnlockScene(SCNHANDLE offset) {
+
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ if ((pH->filesize & fPreload) == 0) {
+ // change the flags for the node
+ MemoryReAlloc(pH->pNode, pH->filesize & FSIZE_MASK, DWM_MOVEABLE | DWM_DISCARDABLE);
+#ifdef DEBUG
+ bLockedScene = false;
+#endif
+ }
+}
+
+/*----------------------------------------------------------------------*/
+
+#ifdef BODGE
+
+/**
+ * Validates that a specified handle pointer is valid
+ * @param offset Handle and offset to data
+ */
+bool ValidHandle(SCNHANDLE offset) {
+ uint32 handle = offset >> SCNHANDLE_SHIFT; // calc memory handle to use
+ MEMHANDLE *pH; // points to table entry
+
+ // range check the memory handle
+ assert(handle < numHandles);
+
+ pH = handleTable + handle;
+
+ return (pH->filesize & FSIZE_MASK) != 8;
+}
+#endif
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/handle.h b/engines/tinsel/handle.h
new file mode 100644
index 0000000000..2cb1638d9d
--- /dev/null
+++ b/engines/tinsel/handle.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Graphics Memory Manager data structures
+ * TODO: This should really be named dos_hand.h, or the dos_hand.cpp should be renamed
+ */
+
+#ifndef TINSEL_HANDLE_H // prevent multiple includes
+#define TINSEL_HANDLE_H
+
+#include "tinsel/dw.h" // new data types
+
+namespace Tinsel {
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void SetupHandleTable(void); // Loads the graphics handle table index file and preloads all the permanent graphics etc.
+void FreeHandleTable(void);
+
+uint8 *LockMem( // returns the addr of a image, given its memory handle
+ SCNHANDLE offset); // handle and offset to data
+
+void LockScene( // Called to make the current scene non-discardable
+ SCNHANDLE offset); // handle and offset to data
+
+void UnlockScene( // Called to make the current scene discardable again
+ SCNHANDLE offset); // handle and offset to data
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_HANDLE_H
diff --git a/engines/tinsel/heapmem.cpp b/engines/tinsel/heapmem.cpp
new file mode 100644
index 0000000000..aff085d003
--- /dev/null
+++ b/engines/tinsel/heapmem.cpp
@@ -0,0 +1,594 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager code.
+ */
+
+#include "tinsel/heapmem.h"
+#include "tinsel/timers.h" // For DwGetCurrentTime
+
+namespace Tinsel {
+
+// minimum memory required for MS-DOS version of game
+#define MIN_MEM 2506752L
+
+// list of all memory nodes
+MEM_NODE mnodeList[NUM_MNODES];
+
+// pointer to the linked list of free mnodes
+static MEM_NODE *pFreeMemNodes;
+
+#ifdef DEBUG
+// diagnostic mnode counters
+static int numNodes;
+static int maxNodes;
+#endif
+
+// the mnode heap sentinel
+static MEM_NODE heapSentinel;
+
+//
+static MEM_NODE *AllocMemNode(void);
+
+
+/**
+ * Initialises the memory manager.
+ */
+void MemoryInit(void) {
+ MEM_NODE *pNode;
+
+#ifdef DEBUG
+ // clear number of nodes in use
+ numNodes = 0;
+#endif
+
+ // place first node on free list
+ pFreeMemNodes = mnodeList;
+
+ // link all other objects after first
+ for (int i = 1; i < NUM_MNODES; i++) {
+ mnodeList[i - 1].pNext = mnodeList + i;
+ }
+
+ // null the last mnode
+ mnodeList[NUM_MNODES - 1].pNext = NULL;
+
+ // allocatea big chunk of memory
+ const uint32 size = 2*MIN_MEM+655360L;
+ uint8 *mem = (uint8 *)malloc(size);
+ assert(mem);
+
+ // allocate a mnode for this memory
+ pNode = AllocMemNode();
+
+ // make sure mnode was allocated
+ assert(pNode);
+
+ // convert segment to memory address
+ pNode->pBaseAddr = mem;
+
+ // set size of the memory heap
+ pNode->size = size;
+
+ // clear the memory
+ memset(pNode->pBaseAddr, 0, size);
+
+ // set cyclic links to the sentinel
+ heapSentinel.pPrev = pNode;
+ heapSentinel.pNext = pNode;
+ pNode->pPrev = &heapSentinel;
+ pNode->pNext = &heapSentinel;
+
+ // flag sentinel as locked
+ heapSentinel.flags = DWM_LOCKED | DWM_SENTINEL;
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of mnodes used at once.
+ */
+
+void MemoryStats(void) {
+ printf("%i mnodes of %i used.\n", maxNodes, NUM_MNODES);
+}
+#endif
+
+/**
+ * Allocate a mnode from the free list.
+ */
+static MEM_NODE *AllocMemNode(void) {
+ // get the first free mnode
+ MEM_NODE *pMemNode = pFreeMemNodes;
+
+ // make sure a mnode is available
+ assert(pMemNode); // Out of memory nodes
+
+ // the next free mnode
+ pFreeMemNodes = pMemNode->pNext;
+
+ // wipe out the mnode
+ memset(pMemNode, 0, sizeof(MEM_NODE));
+
+#ifdef DEBUG
+ // one more mnode in use
+ if (++numNodes > maxNodes)
+ maxNodes = numNodes;
+#endif
+
+ // return new mnode
+ return pMemNode;
+}
+
+/**
+ * Return a mnode back to the free list.
+ * @param pMemNode Node of the memory object
+ */
+void FreeMemNode(MEM_NODE *pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+#ifdef DEBUG
+ // one less mnode in use
+ --numNodes;
+ assert(numNodes >= 0);
+#endif
+
+ // place free list in mnode next
+ pMemNode->pNext = pFreeMemNodes;
+
+ // add mnode to top of free list
+ pFreeMemNodes = pMemNode;
+}
+
+
+/**
+ * Tries to make space for the specified number of bytes on the specified heap.
+ * @param size Number of bytes to free up
+ * @param bDiscard When set - will discard blocks to fullfill the request
+ */
+bool HeapCompact(long size, bool bDiscard) {
+ MEM_NODE *pHeap = &heapSentinel;
+ MEM_NODE *pPrev, *pCur, *pOldest;
+ long largest; // size of largest free block
+ uint32 oldest; // time of the oldest discardable block
+
+ while (true) {
+ bool bChanged;
+
+ do {
+ bChanged = false;
+ for (pPrev = pHeap->pNext, pCur = pPrev->pNext;
+ pCur != pHeap; pPrev = pCur, pCur = pCur->pNext) {
+ if (pPrev->flags == 0 && pCur->flags == 0) {
+ // set the changed flag
+ bChanged = true;
+
+ // both blocks are free - merge them
+ pPrev->size += pCur->size;
+
+ // unlink the current mnode
+ pPrev->pNext = pCur->pNext;
+ pCur->pNext->pPrev = pPrev;
+
+ // free the current mnode
+ FreeMemNode(pCur);
+
+ // leave the loop
+ break;
+ } else if ((pPrev->flags & (DWM_MOVEABLE | DWM_LOCKED | DWM_DISCARDED)) == DWM_MOVEABLE
+ && pCur->flags == 0) {
+ // a free block after a moveable block - swap them
+
+ // set the changed flag
+ bChanged = true;
+
+ // move the unlocked blocks data up (can overlap)
+ memmove(pPrev->pBaseAddr + pCur->size,
+ pPrev->pBaseAddr, pPrev->size);
+
+ // swap the order in the linked list
+ pPrev->pPrev->pNext = pCur;
+ pCur->pNext->pPrev = pPrev;
+
+ pCur->pPrev = pPrev->pPrev;
+ pPrev->pPrev = pCur;
+
+ pPrev->pNext = pCur->pNext;
+ pCur->pNext = pPrev;
+
+ pCur->pBaseAddr = pPrev->pBaseAddr;
+ pPrev->pBaseAddr += pCur->size;
+
+ // leave the loop
+ break;
+ }
+ }
+ } while (bChanged);
+
+ // find the largest free block
+ for (largest = 0, pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
+ if (pCur->flags == 0 && pCur->size > largest)
+ largest = pCur->size;
+ }
+
+ if (largest >= size)
+ // we have freed enough memory
+ return true;
+
+ if (!bDiscard)
+ // we cannot free enough without discarding blocks
+ return false;
+
+ // find the oldest discardable block
+ oldest = DwGetCurrentTime();
+ pOldest = NULL;
+ for (pCur = pHeap->pNext; pCur != pHeap; pCur = pCur->pNext) {
+ if ((pCur->flags & (DWM_DISCARDABLE | DWM_DISCARDED | DWM_LOCKED))
+ == DWM_DISCARDABLE) {
+ // found a non-discarded discardable block
+ if (pCur->lruTime < oldest) {
+ oldest = pCur->lruTime;
+ pOldest = pCur;
+ }
+ }
+ }
+
+ if (pOldest)
+ // discard the oldest block
+ MemoryDiscard(pOldest);
+ else
+ // cannot discard any blocks
+ return false;
+ }
+}
+
+/**
+ * Allocates the specified number of bytes from the heap.
+ * @param flags Allocation attributes
+ * @param size Number of bytes to allocate
+ */
+MEM_NODE *MemoryAlloc(int flags, long size) {
+ MEM_NODE *pHeap = &heapSentinel;
+ MEM_NODE *pNode;
+ bool bCompacted = true; // set when heap has been compacted
+
+ // compact the heap if we are allocating fixed memory
+ if (flags & DWM_FIXED)
+ HeapCompact(MAX_INT, false);
+
+ while ((flags & DWM_NOALLOC) == 0 && bCompacted) {
+ // search the heap for a free block
+
+ for (pNode = pHeap->pNext; pNode != pHeap; pNode = pNode->pNext) {
+ if (pNode->flags == 0 && pNode->size >= size) {
+ // a free block of the required size
+ pNode->flags = flags;
+
+ // update the LRU time
+ pNode->lruTime = DwGetCurrentTime() + 1;
+
+ if (pNode->size == size) {
+ // an exact fit
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ if (flags & DWM_FIXED)
+ // lock the memory
+ return (MEM_NODE *)MemoryLock(pNode);
+ else
+ // just return the node
+ return pNode;
+ } else {
+ // allocate a node for the remainder of the free block
+ MEM_NODE *pTemp = AllocMemNode();
+
+ // calc size of the free block
+ long freeSize = pNode->size - size;
+
+ // set size of free block
+ pTemp->size = freeSize;
+
+ // set size of node
+ pNode->size = size;
+
+ if (flags & DWM_FIXED) {
+ // place the free node after pNode
+ pTemp->pBaseAddr = pNode->pBaseAddr + size;
+ pTemp->pNext = pNode->pNext;
+ pTemp->pPrev = pNode;
+ pNode->pNext->pPrev = pTemp;
+ pNode->pNext = pTemp;
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ return (MEM_NODE *)MemoryLock(pNode);
+ } else {
+ // place the free node before pNode
+ pTemp->pBaseAddr = pNode->pBaseAddr;
+ pNode->pBaseAddr += freeSize;
+ pTemp->pNext = pNode;
+ pTemp->pPrev = pNode->pPrev;
+ pNode->pPrev->pNext = pTemp;
+ pNode->pPrev = pTemp;
+
+ // check for zeroing the block
+ if (flags & DWM_ZEROINIT)
+ memset(pNode->pBaseAddr, 0, size);
+
+ return pNode;
+ }
+ }
+ }
+ }
+ // compact the heap if we get to here
+ bCompacted = HeapCompact(size, (flags & DWM_NOCOMPACT) ? false : true);
+ }
+
+ // not allocated a block if we get to here
+ if (flags & DWM_DISCARDABLE) {
+ // chain a discarded node onto the end of the heap
+ pNode = AllocMemNode();
+ pNode->flags = flags | DWM_DISCARDED;
+
+ // set mnode at the end of the list
+ pNode->pPrev = pHeap->pPrev;
+ pNode->pNext = pHeap;
+
+ // fix links to this mnode
+ pHeap->pPrev->pNext = pNode;
+ pHeap->pPrev = pNode;
+
+ // return the discarded node
+ return pNode;
+ }
+
+ // could not allocate a block
+ return NULL;
+}
+
+/**
+ * Discards the specified memory object.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryDiscard(MEM_NODE *pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // object must be discardable
+ assert(pMemNode->flags & DWM_DISCARDABLE);
+
+ // object cannot be locked
+ assert((pMemNode->flags & DWM_LOCKED) == 0);
+
+ if ((pMemNode->flags & DWM_DISCARDED) == 0) {
+ // allocate a free node to replace this node
+ MEM_NODE *pTemp = AllocMemNode();
+
+ // copy node data
+ memcpy(pTemp, pMemNode, sizeof(MEM_NODE));
+
+ // flag as a free block
+ pTemp->flags = 0;
+
+ // link in the free node
+ pTemp->pPrev->pNext = pTemp;
+ pTemp->pNext->pPrev = pTemp;
+
+ // discard the node
+ pMemNode->flags |= DWM_DISCARDED;
+ pMemNode->pBaseAddr = NULL;
+ pMemNode->size = 0;
+
+ // and place it at the end of the heap
+ while ((pTemp->flags & DWM_SENTINEL) != DWM_SENTINEL)
+ pTemp = pTemp->pNext;
+
+ // pTemp now points to the heap sentinel
+ // set mnode at the end of the list
+ pMemNode->pPrev = pTemp->pPrev;
+ pMemNode->pNext = pTemp;
+
+ // fix links to this mnode
+ pTemp->pPrev->pNext = pMemNode;
+ pTemp->pPrev = pMemNode;
+ }
+}
+
+/**
+ * Frees the specified memory object and invalidates its node.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryFree(MEM_NODE *pMemNode) {
+ MEM_NODE *pPrev, *pNext;
+
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // get pointer to the next mnode
+ pNext = pMemNode->pNext;
+
+ // get pointer to the previous mnode
+ pPrev = pMemNode->pPrev;
+
+ if (pPrev->flags == 0) {
+ // there is a previous free mnode
+ pPrev->size += pMemNode->size;
+
+ // unlink this mnode
+ pPrev->pNext = pNext; // previous to next
+ pNext->pPrev = pPrev; // next to previous
+
+ // free this mnode
+ FreeMemNode(pMemNode);
+
+ pMemNode = pPrev;
+ }
+ if (pNext->flags == 0) {
+ // the next mnode is free
+ pMemNode->size += pNext->size;
+
+ // flag as a free block
+ pMemNode->flags = 0;
+
+ // unlink the next mnode
+ pMemNode->pNext = pNext->pNext;
+ pNext->pNext->pPrev = pMemNode;
+
+ // free the next mnode
+ FreeMemNode(pNext);
+ }
+}
+
+/**
+ * Locks a memory object and returns a pointer to the first byte
+ * of the objects memory block.
+ * @param pMemNode Node of the memory object
+ */
+void *MemoryLock(MEM_NODE *pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // make sure memory object is not already locked
+ assert((pMemNode->flags & DWM_LOCKED) == 0);
+
+ // check for a discarded or null memory object
+ if ((pMemNode->flags & DWM_DISCARDED) || pMemNode->size == 0)
+ return NULL;
+
+ // set the lock flag
+ pMemNode->flags |= DWM_LOCKED;
+
+ // return memory objects base address
+ return pMemNode->pBaseAddr;
+}
+
+/**
+ * Changes the size or attributes of a specified memory object.
+ * @param pMemNode Node of the memory object
+ * @param size New size of block
+ * @param flags How to reallocate the object
+ */
+MEM_NODE *MemoryReAlloc(MEM_NODE *pMemNode, long size, int flags) {
+ MEM_NODE *pNew;
+
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // validate the flags
+ // cannot be fixed and moveable
+ assert((flags & (DWM_FIXED | DWM_MOVEABLE)) != (DWM_FIXED | DWM_MOVEABLE));
+
+ // cannot be fixed and discardable
+ assert((flags & (DWM_FIXED | DWM_DISCARDABLE)) != (DWM_FIXED | DWM_DISCARDABLE));
+
+ // must be fixed or moveable
+ assert(flags & (DWM_FIXED | DWM_MOVEABLE));
+
+ // align the size to machine boundary requirements
+ size = (size + sizeof(int) - 1) & ~(sizeof(int) - 1);
+
+ // validate the size
+ assert(size);
+
+ // make sure we want the node on the same heap
+ assert((flags & (DWM_SOUND | DWM_GRAPHIC)) == (pMemNode->flags & (DWM_SOUND | DWM_GRAPHIC)));
+
+ if (size == pMemNode->size) {
+ // must be just a change in flags
+
+ // update the nodes flags
+ pMemNode->flags = flags;
+ } else {
+ // unlink the mnode from the current heap
+ pMemNode->pNext->pPrev = pMemNode->pPrev;
+ pMemNode->pPrev->pNext = pMemNode->pNext;
+
+ // allocate a new node
+ pNew = MemoryAlloc((flags & ~DWM_FIXED) | DWM_MOVEABLE, size);
+
+ // make sure memory allocated
+ assert(pNew != NULL);
+
+ // update the nodes flags
+ pNew->flags = flags;
+
+ // copy the node to the current node
+ memcpy(pMemNode, pNew, sizeof(MEM_NODE));
+
+ // relink the mnode into the list
+ pMemNode->pPrev->pNext = pMemNode;
+ pMemNode->pNext->pPrev = pMemNode;
+
+ // free the new node
+ FreeMemNode(pNew);
+ }
+
+ if (flags & DWM_FIXED)
+ // lock the memory
+ return (MEM_NODE *)MemoryLock(pMemNode);
+ else
+ // just return the node
+ return pMemNode;
+}
+
+/**
+ * Unlocks a memory object.
+ * @param pMemNode Node of the memory object
+ */
+void MemoryUnlock(MEM_NODE *pMemNode) {
+ // validate mnode pointer
+ assert(pMemNode >= mnodeList && pMemNode <= mnodeList + NUM_MNODES - 1);
+
+ // make sure memory object is already locked
+ assert(pMemNode->flags & DWM_LOCKED);
+
+ // clear the lock flag
+ pMemNode->flags &= ~DWM_LOCKED;
+
+ // update the LRU time
+ pMemNode->lruTime = DwGetCurrentTime();
+}
+
+/**
+ * Retrieves the mnode associated with the specified pointer to a memory object.
+ * @param pMem Address of memory object
+ */
+MEM_NODE *MemoryHandle(void *pMem) {
+ MEM_NODE *pNode;
+ // search the DOS heap
+ for (pNode = heapSentinel.pNext; pNode != &heapSentinel; pNode = pNode->pNext) {
+ if (pNode->pBaseAddr == pMem)
+ // found it
+ return pNode;
+ }
+
+ // not found if we get to here
+ return NULL;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/heapmem.h b/engines/tinsel/heapmem.h
new file mode 100644
index 0000000000..7fb85985a9
--- /dev/null
+++ b/engines/tinsel/heapmem.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the handle based Memory Manager defines
+ */
+
+#ifndef TINSEL_HEAPMEM_H
+#define TINSEL_HEAPMEM_H
+
+#include "tinsel/dw.h" // new data types
+
+namespace Tinsel {
+
+#define NUM_MNODES 192 // the number of memory management nodes (was 128, then 192)
+
+struct MEM_NODE {
+ MEM_NODE *pNext; // link to the next node in the list
+ MEM_NODE *pPrev; // link to the previous node in the list
+ uint8 *pBaseAddr; // base address of the memory object
+ long size; // size of the memory object
+ uint32 lruTime; // time when memory object was last accessed
+ int flags; // allocation attributes
+};
+
+// allocation flags for the MemoryAlloc function
+#define DWM_FIXED 0x0001 // allocates fixed memory
+#define DWM_MOVEABLE 0x0002 // allocates movable memory
+#define DWM_DISCARDABLE 0x0004 // allocates discardable memory
+#define DWM_NOALLOC 0x0008 // when used with discardable memory - allocates a discarded block
+#define DWM_NOCOMPACT 0x0010 // does not discard memory to satisfy the allocation request
+#define DWM_ZEROINIT 0x0020 // initialises memory contents to zero
+#define DWM_SOUND 0x0040 // allocate from the sound pool
+#define DWM_GRAPHIC 0x0080 // allocate from the graphics pool
+
+// return value from the MemoryFlags function
+#define DWM_DISCARDED 0x0100 // the objects memory block has been discarded
+
+// internal allocation flags
+#define DWM_LOCKED 0x0200 // the objects memory block is locked
+#define DWM_SENTINEL 0x0400 // the objects memory block is a sentinel
+
+
+/*----------------------------------------------------------------------*\
+|* Memory Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void MemoryInit(void); // initialises the memory manager
+
+#ifdef DEBUG
+void MemoryStats(void); // Shows the maximum number of mnodes used at once
+#endif
+
+MEM_NODE *MemoryAlloc( // allocates the specified number of bytes from the heap
+ int flags, // allocation attributes
+ long size); // number of bytes to allocate
+
+void MemoryDiscard( // discards the specified memory object
+ MEM_NODE *pMemNode); // node of the memory object
+
+int MemoryFlags( // returns information about the specified memory object
+ MEM_NODE *pMemNode); // node of the memory object
+
+void MemoryFree( // frees the specified memory object and invalidates its node
+ MEM_NODE *pMemNode); // node of the memory object
+
+MEM_NODE *MemoryHandle( // Retrieves the mnode associated with the specified pointer to a memory object
+ void *pMem); // address of memory object
+
+void *MemoryLock( // locks a memory object and returns a pointer to the first byte of the objects memory block
+ MEM_NODE *pMemNode); // node of the memory object
+
+MEM_NODE *MemoryReAlloc( // changes the size or attributes of a specified memory object
+ MEM_NODE *pMemNode, // node of the memory object
+ long size, // new size of block
+ int flags); // how to reallocate the object
+
+long MemorySize( // returns the size, in bytes, of the specified memory object
+ MEM_NODE *pMemNode); // node of the memory object
+
+void MemoryUnlock( // unlocks a memory object
+ MEM_NODE *pMemNode); // node of the memory object
+
+bool HeapCompact( // Allocates the specified number of bytes from the specified heap
+ long size, // number of bytes to free up
+ bool bDiscard); // when set - will discard blocks to fullfill the request
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/inventory.cpp b/engines/tinsel/inventory.cpp
new file mode 100644
index 0000000000..2a0f3695c0
--- /dev/null
+++ b/engines/tinsel/inventory.cpp
@@ -0,0 +1,4535 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles the inventory and conversation windows.
+ *
+ * And the save/load game windows. Some of this will be platform
+ * specific - I'll try to separate this ASAP.
+ *
+ * And there's still a bit of tidying and commenting to do yet.
+ */
+
+//#define USE_3FLAGS 1
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/music.h"
+#include "tinsel/polygons.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/serializer.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinsel.h" // For engine access
+#include "tinsel/token.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+// In DOS_DW.C
+extern bool bRestart; // restart flag - set to restart the game
+
+#ifdef MAC_OPTIONS
+// In MAC_SOUND.C
+extern int volMaster;
+#endif
+
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// Tag functions in PDISPLAY.C
+extern void EnableTags(void);
+extern void DisableTags(void);
+
+
+//----------------- LOCAL DEFINES --------------------
+
+//#define ALL_CURSORS
+
+#define INV_PICKUP BE_SLEFT // Local names
+#define INV_LOOK BE_SRIGHT // for button events
+#define INV_ACTION BE_DLEFT //
+
+
+// For SlideSlider() and similar
+enum SSFN {
+ S_START, S_SLIDE, S_END, S_TIMEUP, S_TIMEDN
+};
+
+/** attribute values - may become bit field if further attributes are added */
+enum {
+ IO_ONLYINV1 = 0x01,
+ IO_ONLYINV2 = 0x02,
+ IO_DROPCODE = 0x04
+};
+
+//-----------------------
+// Moveable window translucent rectangle position limits
+enum {
+ MAXLEFT = 315, //
+ MINRIGHT = 3, // These values keep 2 pixcells
+ MINTOP = -13, // of header on the screen.
+ MAXTOP = 195 //
+};
+
+//-----------------------
+// Indices into winPartsf's reels
+#define IX_SLIDE 0 // Slider
+#define IX_V26 1
+#define IX_V52 2
+#define IX_V78 3
+#define IX_V104 4
+#define IX_V130 5
+#define IX_H26 6
+#define IX_H52 7
+#define IX_H78 8
+#define IX_H104 9
+#define IX_H130 10
+#define IX_H156 11
+#define IX_H182 12
+#define IX_H208 13
+#define IX_H234 14
+#define IX_TL 15 // Top left corner
+#define IX_TR 16 // Top right corner
+#define IX_BL 17 // Bottom left corner
+#define IX_BR 18 // Bottom right corner
+#define IX_H25 19
+#define IX_V11 20
+#define IX_RTL 21 // Re-sizing top left corner
+#define IX_RTR 22 // Re-sizing top right corner
+#define IX_RBR 23 // Re-sizing bottom right corner
+#define IX_CURLR 24 // }
+#define IX_CURUD 25 // }
+#define IX_CURDU 26 // } Custom cursors
+#define IX_CURDD 27 // }
+#define IX_CURUP 28 // }
+#define IX_CURDOWN 29 // }
+#define IX_MDGROOVE 30 // 'Mixing desk' slider background
+#define IX_MDSLIDER 34 // 'Mixing desk' slider
+
+#define IX_BLANK1 35 //
+#define IX_BLANK2 36 //
+#define IX_BLANK3 37 //
+#define IX_CIRCLE1 38 //
+#define IX_CIRCLE2 39 //
+#define IX_CROSS1 40 //
+#define IX_CROSS2 41 //
+#define IX_CROSS3 42 //
+#define IX_QUIT1 43 //
+#define IX_QUIT2 44 //
+#define IX_QUIT3 45 //
+#define IX_TICK1 46 //
+#define IX_TICK2 47 //
+#define IX_TICK3 48 //
+#define IX_NTR 49 // New top right corner
+#define HOPEDFORREELS 50
+
+#define NORMGRAPH 0
+#define DOWNGRAPH 1
+#define HIGRAPH 2
+//-----------------------
+#define FIX_UK 0
+#define FIX_FR 1
+#define FIX_GR 2
+#define FIX_IT 3
+#define FIX_SP 4
+#define FIX_USA 5
+#define HOPEDFORFREELS 6 // Expected flag reels
+//-----------------------
+
+#define MAX_ININV 150 // Max in an inventory
+#define MAX_CONVBASIC 10 // Max permanent conversation icons
+
+#define MAXHICONS 10 // Max dimensions of
+#define MAXVICONS 6 // an inventory window
+
+#define ITEM_WIDTH 25 // Dimensions of an icon
+#define ITEM_HEIGHT 25 //
+
+// Number of objects that makes up an empty window
+#define MAX_WCOMP 21 // 4 corners + (3+3) sides + (2+2) extra sides
+ // + Bground + title + slider
+ // + more Needed for save game window
+
+#define MAX_ICONS MAXHICONS*MAXVICONS
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+//----- Permanent data (compiled in) -----
+
+// Save game name editing cursor
+
+#define CURSOR_CHAR '_'
+char sCursor[2] = { CURSOR_CHAR, 0 };
+static const int hFillers[MAXHICONS] = {
+ IX_H26, // 2 icons wide
+ IX_H52, // 3
+ IX_H78, // 4
+ IX_H104, // 5
+ IX_H130, // 6
+ IX_H156, // 7
+ IX_H182, // 8
+ IX_H208, // 9
+ IX_H234 // 10 icons wide
+};
+static const int vFillers[MAXVICONS] = {
+ IX_V26, // 2 icons high
+ IX_V52, // 3
+ IX_V78, // 4
+ IX_V104, // 5
+ IX_V130 // 6 icons high
+};
+
+
+//----- Permanent data (set once) -----
+
+static SCNHANDLE winPartsf = 0; // Window members and cursors' graphic data
+static SCNHANDLE flagFilm = 0; // Window members and cursors' graphic data
+static SCNHANDLE configStrings[20];
+
+static INV_OBJECT *pio = 0; // Inventory objects' data
+static int numObjects = 0; // Number of inventory objects
+
+
+//----- Permanent data (updated, valid while inventory closed) -----
+
+static enum {NO_INV, IDLE_INV, ACTIVE_INV, BOGUS_INV} InventoryState;
+
+static int HeldItem = INV_NOICON; // Current held item
+
+struct INV_DEF {
+
+ int MinHicons; // }
+ int MinVicons; // } Dimension limits
+ int MaxHicons; // }
+ int MaxVicons; // }
+
+ int NoofHicons; // }
+ int NoofVicons; // } Current dimentsions
+
+ int ItemOrder[MAX_ININV]; // Contained items
+ int NoofItems; // Current number of held items
+
+ int FirstDisp; // Index to first item currently displayed
+
+ int inventoryX; // } Display position
+ int inventoryY; // }
+ int otherX; // } Display position
+ int otherY; // }
+
+ int MaxInvObj; // Max. allowed contents
+
+ SCNHANDLE hInvTitle; // Window heading
+
+ bool resizable; // Re-sizable window?
+ bool moveable; // Moveable window?
+
+ int sNoofHicons; // }
+ int sNoofVicons; // } Current dimensions
+
+ bool bMax; // Maximised last time open?
+
+};
+
+static INV_DEF InvD[NUM_INV]; // Conversation + 2 inventories + ...
+
+
+// Permanent contents of conversation inventory
+static int Inv0Order[MAX_CONVBASIC]; // Basic items i.e. permanent contents
+static int Num0Order = 0; // - copy to conv. inventory at pop-up time
+
+
+
+//----- Data pertinant to current active inventory -----
+
+static int ino = 0; // Which inventory is currently active
+
+static bool InventoryHidden = false;
+static bool InventoryMaximised = false;
+
+static enum { ID_NONE, ID_MOVE, ID_SLIDE,
+ ID_BOTTOM, ID_TOP, ID_LEFT, ID_RIGHT,
+ ID_TLEFT, ID_TRIGHT, ID_BLEFT, ID_BRIGHT,
+ ID_CSLIDE, ID_MDCONT } InvDragging;
+
+static int SuppH = 0; // 'Linear' element of
+static int SuppV = 0; // dimensions during re-sizing
+
+static int Ychange = 0; //
+static int Ycompensate = 0; // All to do with re-sizing.
+static int Xchange = 0; //
+static int Xcompensate = 0; //
+
+static bool ItemsChanged = 0; // When set, causes items to be re-drawn
+
+static bool bOpenConf = 0;
+
+static int TL = 0, TR = 0, BL = 0, BR = 0; // Used during window construction
+static int TLwidth = 0, TLheight = 0; //
+static int TRwidth = 0; //
+static int BLheight = 0; //
+
+
+
+static OBJECT *objArray[MAX_WCOMP]; // Current display objects (window)
+static OBJECT *iconArray[MAX_ICONS]; // Current display objects (icons)
+static ANIM iconAnims[MAX_ICONS];
+static OBJECT *DobjArray[MAX_WCOMP]; // Current display objects (re-sizing window)
+
+static OBJECT *RectObject = 0, *SlideObject = 0; // Current display objects, for reference
+ // objects are in objArray.
+
+static int slideY = 0; // For positioning the slider
+static int slideYmax = 0, slideYmin = 0; //
+
+// Also to do with the slider
+static struct { int n; int y; } slideStuff[MAX_ININV+1];
+
+#define MAXSLIDES 4
+struct MDSLIDES {
+ int num;
+ OBJECT *obj;
+ int min, max;
+};
+static MDSLIDES mdSlides[MAXSLIDES];
+static int numMdSlides = 0;
+
+static int GlitterIndex = 0;
+
+static HPOLYGON thisConvPoly = 0; // Conversation code is in a polygon code block
+static int thisConvIcon = 0; // Passed to polygon code via convIcon()
+static int pointedIcon = INV_NOICON; // used by InvLabels - icon pointed to on last call
+static volatile int PointedWaitCount = 0; // used by InvTinselProcess - fix the 'repeated pressing bug'
+static int sX = 0; // used by SlideMSlider() - current x-coordinate
+static int lX = 0; // used by SlideMSlider() - last x-coordinate
+
+//----- Data pertinant to configure (incl. load/save game) -----
+
+#define COL_MAINBOX TBLUE1 // Base blue colour
+#define COL_BOX TBLUE1
+#define COL_HILIGHT TBLUE4
+
+#ifdef JAPAN
+#define BOX_HEIGHT 17
+#define EDIT_BOX1_WIDTH 149
+#else
+#define BOX_HEIGHT 13
+#define EDIT_BOX1_WIDTH 145
+#endif
+#define EDIT_BOX2_WIDTH 166
+
+// RGROUP Radio button group - 1 is selectable at a time. Action on double click
+// ARSBUT Action if a radio button is selected
+// AABUT Action always
+// AATBUT Action always, text box
+// AAGBUT Action always, graphic button
+// SLIDER Not a button at all
+enum BTYPE {
+ RGROUP, ARSBUT, AABUT, AATBUT, ARSGBUT, AAGBUT, SLIDER,
+ TOGGLE, DCTEST, FLIP, FRGROUP, NOTHING
+};
+
+enum BFUNC {
+ NOFUNC, SAVEGAME, LOADGAME, IQUITGAME, CLOSEWIN,
+ OPENLOAD, OPENSAVE, OPENREST,
+ OPENSOUND, OPENCONT,
+#ifndef JAPAN
+ OPENSUBT,
+#endif
+ OPENQUIT,
+ INITGAME, MIDIVOL,
+ CLANG, RLANG
+#ifdef MAC_OPTIONS
+ , MASTERVOL, SAMPVOL
+#endif
+};
+
+struct CONFBOX {
+ BTYPE boxType;
+ BFUNC boxFunc;
+ char *boxText;
+ int ixText;
+ int xpos;
+ int ypos;
+ int w; // Doubles as max value for SLIDERs
+ int h; // Doubles as iteration size for SLIDERs
+ int *ival;
+ int bi; // Base index for AAGBUTs
+};
+
+
+#define NO_HEADING (-1)
+#define USE_POINTER (-1)
+#define SIX_LOAD_OPTION 0
+#define SIX_SAVE_OPTION 1
+#define SIX_RESTART_OPTION 2
+#define SIX_SOUND_OPTION 3
+#define SIX_CONTROL_OPTION 4
+#ifndef JAPAN
+#define SIX_SUBTITLES_OPTION 5
+#endif
+#define SIX_QUIT_OPTION 6
+#define SIX_RESUME_OPTION 7
+#define SIX_LOAD_HEADING 8
+#define SIX_SAVE_HEADING 9
+#define SIX_RESTART_HEADING 10
+#define SIX_MVOL_SLIDER 11
+#define SIX_SVOL_SLIDER 12
+#define SIX_VVOL_SLIDER 13
+#define SIX_DCLICK_SLIDER 14
+#define SIX_DCLICK_TEST 15
+#define SIX_SWAP_TOGGLE 16
+#define SIX_TSPEED_SLIDER 17
+#define SIX_STITLE_TOGGLE 18
+#define SIX_QUIT_HEADING 19
+
+
+/*-------------------------------------------------------------*\
+| This is the main menu (that comes up when you hit F1 on a PC) |
+\*-------------------------------------------------------------*/
+
+#ifdef JAPAN
+#define FBY 11 // y-offset of first button
+#define FBX 13 // x-offset of first button
+#else
+#define FBY 20 // y-offset of first button
+#define FBX 15 // x-offset of first button
+#endif
+
+CONFBOX optionBox[] = {
+
+ { AATBUT, OPENLOAD, NULL, SIX_LOAD_OPTION, FBX, FBY, EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENSAVE, NULL, SIX_SAVE_OPTION, FBX, FBY + (BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENREST, NULL, SIX_RESTART_OPTION, FBX, FBY + 2*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENSOUND, NULL, SIX_SOUND_OPTION, FBX, FBY + 3*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENCONT, NULL, SIX_CONTROL_OPTION, FBX, FBY + 4*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifdef JAPAN
+// TODO: If in JAPAN mode, simply disable the subtitles button?
+ { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }
+#else
+ { AATBUT, OPENSUBT, NULL, SIX_SUBTITLES_OPTION,FBX, FBY + 5*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, OPENQUIT, NULL, SIX_QUIT_OPTION, FBX, FBY + 6*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { AATBUT, CLOSEWIN, NULL, SIX_RESUME_OPTION, FBX, FBY + 7*(BOX_HEIGHT + 2), EDIT_BOX1_WIDTH, BOX_HEIGHT, NULL, 0 }
+#endif
+
+};
+
+/*-------------------------------------------------------------*\
+| These are the load and save game menus. |
+\*-------------------------------------------------------------*/
+
+#ifdef JAPAN
+#define NUM_SL_RGROUP 7 // number of visible slots
+#define SY 32 // y-position of first slot
+#else
+#define NUM_SL_RGROUP 9 // number of visible slots
+#define SY 31 // y-position of first slot
+#endif
+
+CONFBOX loadBox[NUM_SL_RGROUP+2] = {
+
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifndef JAPAN
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, LOADGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#endif
+ { ARSGBUT, LOADGAME, NULL, USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 }
+
+};
+
+CONFBOX saveBox[NUM_SL_RGROUP+2] = {
+
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY, EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + (BOX_HEIGHT + 2), EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 2*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 3*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 4*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 5*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 6*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#ifndef JAPAN
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 7*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+ { RGROUP, SAVEGAME, NULL, USE_POINTER, 28, SY + 8*(BOX_HEIGHT + 2),EDIT_BOX2_WIDTH, BOX_HEIGHT, NULL, 0 },
+#endif
+ { ARSGBUT, SAVEGAME, NULL,USE_POINTER, 230, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 230, 44+47, 23, 19, NULL, IX_CROSS1 }
+
+};
+
+
+/*-------------------------------------------------------------*\
+| This is the restart confirmation 'menu'. |
+\*-------------------------------------------------------------*/
+
+CONFBOX restartBox[] = {
+
+#ifdef JAPAN
+ { AAGBUT, INITGAME, NULL, USE_POINTER, 96, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 56, 44, 23, 19, NULL, IX_CROSS1 }
+#else
+ { AAGBUT, INITGAME, NULL, USE_POINTER, 70, 28, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 }
+#endif
+
+};
+
+
+/*-------------------------------------------------------------*\
+| This is the sound control 'menu'. |
+\*-------------------------------------------------------------*/
+
+#ifdef MAC_OPTIONS
+ CONFBOX soundBox[] = {
+ { SLIDER, MASTERVOL, NULL, SIX_MVOL_SLIDER, 142, 20, 100, 2, &volMaster, 0 },
+ { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 20+40, 100, 2, &volMidi, 0 },
+ { SLIDER, SAMPVOL, NULL, SIX_SVOL_SLIDER, 142, 20+2*40, 100, 2, &volSound, 0 },
+ { SLIDER, SAMPVOL, NULL, SIX_VVOL_SLIDER, 142, 20+3*40, 100, 2, &volVoice, 0 }
+ };
+#else
+CONFBOX soundBox[] = {
+ { SLIDER, MIDIVOL, NULL, SIX_MVOL_SLIDER, 142, 25, MAXMIDIVOL, 2, &volMidi, 0 },
+ { SLIDER, NOFUNC, NULL, SIX_SVOL_SLIDER, 142, 25+40, MAXSAMPVOL, 2, &volSound, 0 },
+ { SLIDER, NOFUNC, NULL, SIX_VVOL_SLIDER, 142, 25+2*40, MAXSAMPVOL, 2, &volVoice, 0 }
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the (mouse) control 'menu'. |
+\*-------------------------------------------------------------*/
+
+int bFlipped; // looks like this is just so the code has something to alter!
+
+
+#ifdef MAC_OPTIONS
+CONFBOX controlBox[] = {
+
+ { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 },
+ { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 }
+
+};
+#else
+CONFBOX controlBox[] = {
+
+ { SLIDER, NOFUNC, NULL, SIX_DCLICK_SLIDER, 142, 25, 3*DOUBLE_CLICK_TIME, 1, &dclickSpeed, 0 },
+ { FLIP, NOFUNC, NULL, SIX_DCLICK_TEST, 142, 25+30, 23, 19, &bFlipped, IX_CIRCLE1 },
+#ifdef JAPAN
+ { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 205, 25+70, 23, 19, &bSwapButtons, 0 }
+#else
+ { TOGGLE, NOFUNC, NULL, SIX_SWAP_TOGGLE, 155, 25+70, 23, 19, &bSwapButtons, 0 }
+#endif
+
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the subtitles 'menu'. |
+\*-------------------------------------------------------------*/
+
+#ifndef JAPAN
+CONFBOX subtitlesBox[] = {
+
+#ifdef USE_5FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 100, 56, 32, NULL, FIX_UK },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 100, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 100, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 50, 137, 56, 32, NULL, FIX_IT },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 120, 137, 56, 32, NULL, FIX_SP },
+#endif
+#ifdef USE_4FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 20, 100, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 108, 100, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 64, 137, 56, 32, NULL, FIX_IT },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 152, 137, 56, 32, NULL, FIX_SP },
+#endif
+#ifdef USE_3FLAGS
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 15, 118, 56, 32, NULL, FIX_FR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 85, 118, 56, 32, NULL, FIX_GR },
+ { FRGROUP, NOFUNC, NULL, USE_POINTER, 155, 118, 56, 32, NULL, FIX_SP },
+#endif
+
+ { SLIDER, NOFUNC, NULL, SIX_TSPEED_SLIDER, 142, 20, 100, 2, &speedText, 0 },
+ { TOGGLE, NOFUNC, NULL, SIX_STITLE_TOGGLE, 142, 20+40, 23, 19, &bSubtitles, 0 },
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ { ARSGBUT, CLANG, NULL, USE_POINTER, 230, 110, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, RLANG, NULL, USE_POINTER, 230, 140, 23, 19, NULL, IX_CROSS1 }
+#endif
+
+};
+#endif
+
+
+/*-------------------------------------------------------------*\
+| This is the quit confirmation 'menu'. |
+\*-------------------------------------------------------------*/
+
+CONFBOX quitBox[] = {
+#ifdef JAPAN
+ { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 44, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 44, 23, 19, NULL, IX_CROSS1 }
+#else
+ { AAGBUT, IQUITGAME, NULL, USE_POINTER,70, 28, 23, 19, NULL, IX_TICK1 },
+ { AAGBUT, CLOSEWIN, NULL, USE_POINTER, 30, 28, 23, 19, NULL, IX_CROSS1 }
+#endif
+};
+
+
+CONFBOX topwinBox[] = {
+ { NOTHING, NOFUNC, NULL, USE_POINTER, 0, 0, 0, 0, NULL, 0 }
+};
+
+
+
+struct CONFINIT {
+ int h;
+ int v;
+ int x;
+ int y;
+ bool bExtraWin;
+ CONFBOX *Box;
+ int NumBoxes;
+ int ixHeading;
+};
+
+CONFINIT ciOption = { 6, 5, 72, 23, false, optionBox, ARRAYSIZE(optionBox), NO_HEADING };
+
+CONFINIT ciLoad = { 10, 6, 20, 16, true, loadBox, ARRAYSIZE(loadBox), SIX_LOAD_HEADING };
+CONFINIT ciSave = { 10, 6, 20, 16, true, saveBox, ARRAYSIZE(saveBox), SIX_SAVE_HEADING };
+#ifdef JAPAN
+CONFINIT ciRestart = { 6, 2, 72, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING };
+#else
+CONFINIT ciRestart = { 4, 2, 98, 53, false, restartBox, ARRAYSIZE(restartBox), SIX_RESTART_HEADING };
+#endif
+CONFINIT ciSound = { 10, 5, 20, 16, false, soundBox, ARRAYSIZE(soundBox), NO_HEADING };
+#ifdef MAC_OPTIONS
+ CONFINIT ciControl = { 10, 3, 20, 40, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING };
+#else
+ CONFINIT ciControl = { 10, 5, 20, 16, false, controlBox, ARRAYSIZE(controlBox), NO_HEADING };
+#endif
+#ifndef JAPAN
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+CONFINIT ciSubtitles = { 10, 6, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING };
+#else
+CONFINIT ciSubtitles = { 10, 3, 20, 16, false, subtitlesBox, ARRAYSIZE(subtitlesBox), NO_HEADING };
+#endif
+#endif
+CONFINIT ciQuit = { 4, 2, 98, 53, false, quitBox, ARRAYSIZE(quitBox), SIX_QUIT_HEADING };
+
+CONFINIT ciTopWin = { 6, 5, 72, 23, false, topwinBox, 0, NO_HEADING };
+
+#define NOBOX (-1)
+
+// Conf window globals
+static struct {
+ CONFBOX *Box;
+ int NumBoxes;
+ bool bExtraWin;
+ int ixHeading;
+ bool editableRgroup;
+
+ int selBox;
+ int pointBox; // Box pointed to on last call
+ int saveModifier;
+ int fileBase;
+ int numSaved;
+} cd = {
+ NULL, 0, false, 0, false,
+ NOBOX, NOBOX, 0, 0, 0
+};
+
+// For editing save game names
+char sedit[SG_DESC_LEN+2];
+
+#define HL1 0 // Hilight that moves with the cursor
+#define HL2 1 // Hilight on selected RGROUP box
+#define HL3 2 // Text on selected RGROUP box
+#define NUMHL 3
+
+
+// Data for button press/toggle effects
+static struct {
+ bool bButAnim;
+ CONFBOX *box;
+ bool press; // true = button press; false = button toggle
+} g_buttonEffect = { false, 0, false };
+
+
+//----- LOCAL FORWARD REFERENCES -----
+
+enum {
+ IB_NONE = -1, //
+ IB_UP = -2, // negative numbers returned
+ IB_DOWN = -3, // by WhichInvBox()
+ IB_SLIDE = -4, //
+ IB_SLIDE_UP = -5, //
+ IB_SLIDE_DOWN = -6 //
+};
+
+enum {
+ HI_BIT = ((uint)MIN_INT >> 1), // The next to top bit
+ IS_LEFT = HI_BIT,
+ IS_SLIDER = (IS_LEFT >> 1),
+ IS_RIGHT = (IS_SLIDER >> 1),
+ IS_MASK = (IS_LEFT | IS_SLIDER | IS_RIGHT)
+};
+
+static int WhichInvBox(int curX, int curY, bool bSlides);
+static void SlideMSlider(int x, SSFN fn);
+static OBJECT *AddObject(const FREEL *pfreel, int num);
+static void AddBoxes(bool posnSlide);
+
+static void ConfActionSpecial(int i);
+
+
+/*-------------------------------------------------------------------------*/
+/*** Magic numbers ***/
+
+#define M_SW 5 // Side width
+#define M_TH 5 // Top height
+#ifdef JAPAN
+#define M_TOFF 6 // Title text Y offset from top
+#define M_TBB 20 // Title box bottom Y offset
+#else
+#define M_TOFF 4 // Title text Y offset from top
+#define M_TBB 14 // Title box bottom Y offset
+#endif
+#define M_SBL 26 // Scroll bar left X offset
+#define M_SH 5 // Slider height (*)
+#define M_SW 5 // Slider width (*)
+#define M_SXOFF 9 // Slider X offset from right-hand side
+#ifdef JAPAN
+#define M_IUT 22 // Y offset of top of up arrow
+#define M_IUB 30 // Y offset of bottom of up arrow
+#else
+#define M_IUT 16 // Y offset of top of up arrow
+#define M_IUB 24 // Y offset of bottom of up arrow
+#endif
+#define M_IDT 10 // Y offset (from bottom) of top of down arrow
+#define M_IDB 3 // Y offset (from bottom) of bottom of down arrow
+#define M_IAL 12 // X offset (from right) of left of scroll arrows
+#define M_IAR 3 // X offset (from right) of right of scroll arrows
+
+#define START_ICONX (M_SW+1) // } Relative offset of first icon
+#define START_ICONY (M_TBB+M_TH+1) // } within the inventory window
+
+/*-------------------------------------------------------------------------*/
+
+
+
+#ifndef JAPAN
+bool LanguageChange(void) {
+ LANGUAGE nLang;
+
+#ifdef USE_3FLAGS
+ // VERY quick dodgy bodge
+ if (cd.selBox == 0)
+ nLang = TXT_FRENCH;
+ else if (cd.selBox == 1)
+ nLang = TXT_GERMAN;
+ else
+ nLang = TXT_SPANISH;
+ if (nLang != language) {
+#elif defined(USE_4FLAGS)
+ nLang = (LANGUAGE)(cd.selBox + 1);
+ if (nLang != language) {
+#else
+ if (cd.selBox != language) {
+ nLang = (LANGUAGE)cd.selBox;
+#endif
+ KillInventory();
+ ChangeLanguage(nLang);
+ language = nLang;
+ return true;
+ }
+ else
+ return false;
+}
+#endif
+
+/**************************************************************************/
+/******************** Some miscellaneous functions ************************/
+/**************************************************************************/
+
+/*---------------------------------------------------------------------*\
+| DumpIconArray()/DumpDobjArray()/DumpObjArray() |
+|-----------------------------------------------------------------------|
+| Delete all the objects in iconArray[]/DobjArray[]/objArray[] |
+\*---------------------------------------------------------------------*/
+static void DumpIconArray(void){
+ for (int i = 0; i < MAX_ICONS; i++) {
+ if (iconArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[i]);
+ iconArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Delete all the objects in DobjArray[]
+ */
+
+static void DumpDobjArray(void) {
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (DobjArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), DobjArray[i]);
+ DobjArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Delete all the objects in objArray[]
+ */
+
+static void DumpObjArray(void) {
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (objArray[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), objArray[i]);
+ objArray[i] = NULL;
+ }
+ }
+}
+
+/**
+ * Convert item ID number to pointer to item's compiled data
+ * i.e. Image data and Glitter code.
+ */
+INV_OBJECT *findInvObject(int num) {
+ INV_OBJECT *retval = pio;
+
+ for (int i = 0; i < numObjects; i++, retval++) {
+ if (retval->id == num)
+ return retval;
+ }
+
+ error("Trying to manipulate undefined inventory icon");
+}
+
+/**
+ * Returns position of an item in one of the inventories.
+ * The actual position is not important for the uses that this is put to.
+ */
+
+int InventoryPos(int num) {
+ int i;
+
+ for (i = 0; i < InvD[INV_1].NoofItems; i++) // First inventory
+ if (InvD[INV_1].ItemOrder[i] == num)
+ return i;
+
+ for (i = 0; i < InvD[INV_2].NoofItems; i++) // Second inventory
+ if (InvD[INV_2].ItemOrder[i] == num)
+ return i;
+
+ if (HeldItem == num)
+ return INV_HELDNOTIN; // Held, but not in either inventory
+
+ return INV_NOICON; // Not held, not in either inventory
+}
+
+bool IsInInventory(int object, int invnum) {
+ assert(invnum == INV_1 || invnum == INV_2);
+
+ for (int i = 0; i < InvD[invnum].NoofItems; i++) // First inventory
+ if (InvD[invnum].ItemOrder[i] == object)
+ return true;
+
+ return false;
+}
+
+/**
+ * Returns which item is held (INV_NOICON (-1) if none)
+ */
+
+int WhichItemHeld(void) {
+ return HeldItem;
+}
+
+/**
+ * Called from the cursor module when it re-initialises (at the start of
+ * a new scene). For if we are holding something at scene-change time.
+ */
+
+void InventoryIconCursor(void) {
+ INV_OBJECT *invObj;
+
+ if (HeldItem != INV_NOICON) {
+ invObj = findInvObject(HeldItem);
+ SetAuxCursor(invObj->hFilm);
+ }
+}
+
+/**
+ * Returns TRUE if the inventory is active.
+ */
+
+bool InventoryActive(void) {
+ return (InventoryState == ACTIVE_INV);
+}
+
+int WhichInventoryOpen(void) {
+ if (InventoryState != ACTIVE_INV)
+ return 0;
+ else
+ return ino;
+}
+
+
+/**************************************************************************/
+/************** Running inventory item's Glitter code *********************/
+/**************************************************************************/
+
+struct ITP_INIT {
+ INV_OBJECT *pinvo;
+ USER_EVENT event;
+ BUTEVENT bev;
+};
+
+/**
+ * Run inventory item's Glitter code
+ */
+static void InvTinselProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ int ThisPointedWait; // Fix the 'repeated pressing bug'
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ ITP_INIT *to = (ITP_INIT *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ CORO_INVOKE_1(AllowDclick, to->bev);
+
+ _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, to->event, NOPOLY, 0, to->pinvo);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ if (to->event == POINTED) {
+ _ctx->ThisPointedWait = ++PointedWaitCount;
+ while (1) {
+ CORO_SLEEP(1);
+ int x, y;
+ GetCursorXY(&x, &y, false);
+ if (InvItemId(x, y) != to->pinvo->id)
+ break;
+
+ // Fix the 'repeated pressing bug'
+ if (_ctx->ThisPointedWait != PointedWaitCount)
+ CORO_KILL_SELF();
+ }
+
+ _ctx->pic = InitInterpretContext(GS_INVENTORY, to->pinvo->hScript, UNPOINT, NOPOLY, 0, to->pinvo);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Run inventory item's Glitter code
+ */
+void RunInvTinselCode(INV_OBJECT *pinvo, USER_EVENT event, BUTEVENT be, int index) {
+ ITP_INIT to = { pinvo, event, be };
+
+ if (InventoryHidden)
+ return;
+
+ GlitterIndex = index;
+ g_scheduler->createProcess(PID_TCODE, InvTinselProcess, &to, sizeof(to));
+}
+
+/**************************************************************************/
+/****************** Load/Save game specific functions *********************/
+/**************************************************************************/
+
+/**
+ * Set first load/save file entry displayed.
+ * Point Box[] text pointers to appropriate file descriptions.
+ */
+
+void firstFile(int first) {
+ int i, j;
+
+ i = getList();
+
+ cd.numSaved = i;
+
+ if (first < 0)
+ first = 0;
+ else if (first > MAX_SFILES-NUM_SL_RGROUP)
+ first = MAX_SFILES-NUM_SL_RGROUP;
+
+ if (first == 0 && i < MAX_SFILES && cd.Box == saveBox) {
+ // Blank first entry for new save
+ cd.Box[0].boxText = NULL;
+ cd.saveModifier = j = 1;
+ } else {
+ cd.saveModifier = j = 0;
+ }
+
+ for (i = first; j < NUM_SL_RGROUP; j++, i++) {
+ cd.Box[j].boxText = ListEntry(i, LE_DESC);
+ }
+
+ cd.fileBase = first;
+}
+
+/**
+ * Save the game using filename from selected slot & current description.
+ */
+
+void InvSaveGame(void) {
+ if (cd.selBox != NOBOX) {
+#ifndef JAPAN
+ sedit[strlen(sedit)-1] = 0; // Don't include the cursor!
+#endif
+ SaveGame(ListEntry(cd.selBox-cd.saveModifier+cd.fileBase, LE_NAME), sedit);
+ }
+}
+
+/**
+ * Load the selected saved game.
+ */
+void InvLoadGame(void) {
+ int rGame;
+
+ if (cd.selBox != NOBOX && (cd.selBox+cd.fileBase < cd.numSaved)) {
+ rGame = cd.selBox;
+ cd.selBox = NOBOX;
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+ if (iconArray[HL2] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ iconArray[HL2] = NULL;
+ }
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ RestoreGame(rGame+cd.fileBase);
+ }
+}
+
+/**
+ * Edit the string in sedit[]
+ * Returns TRUE if the string was altered.
+ */
+#ifndef JAPAN
+bool UpdateString(const Common::KeyState &kbd) {
+ int cpos;
+
+ if (!cd.editableRgroup)
+ return false;
+
+ cpos = strlen(sedit)-1;
+
+ if (kbd.keycode == Common::KEYCODE_BACKSPACE) {
+ if (!cpos)
+ return false;
+ sedit[cpos] = 0;
+ cpos--;
+ sedit[cpos] = CURSOR_CHAR;
+ return true;
+// } else if (isalnum(c) || c == ',' || c == '.' || c == '\'' || (c == ' ' && cpos != 0)) {
+ } else if (IsCharImage(hTagFontHandle(), kbd.ascii) || (kbd.ascii == ' ' && cpos != 0)) {
+ if (cpos == SG_DESC_LEN)
+ return false;
+ sedit[cpos] = kbd.ascii;
+ cpos++;
+ sedit[cpos] = CURSOR_CHAR;
+ sedit[cpos+1] = 0;
+ return true;
+ }
+ return false;
+}
+#endif
+
+/**
+ * Keystrokes get sent here when load/save screen is up.
+ */
+bool InvKeyIn(const Common::KeyState &kbd) {
+ if (kbd.keycode == Common::KEYCODE_PAGEUP ||
+ kbd.keycode == Common::KEYCODE_PAGEDOWN ||
+ kbd.keycode == Common::KEYCODE_HOME ||
+ kbd.keycode == Common::KEYCODE_END)
+ return true; // Key needs processing
+
+ if (kbd.keycode == 0 && kbd.ascii == 0) {
+ ;
+ } else if (kbd.keycode == Common::KEYCODE_RETURN) {
+ return true; // Key needs processing
+ } else if (kbd.keycode == Common::KEYCODE_ESCAPE) {
+ return true; // Key needs processing
+ } else {
+#ifndef JAPAN
+ if (UpdateString(kbd)) {
+ /*
+ * Delete display of text currently being edited,
+ * and replace it with freshly edited text.
+ */
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2,
+ InvD[ino].inventoryY + cd.Box[cd.selBox].ypos,
+ hTagFontHandle(), 0);
+ if (MultiRightmost(iconArray[HL3]) > 213) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ UpdateString(Common::KeyState(Common::KEYCODE_BACKSPACE));
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[cd.selBox].xpos + 2,
+ InvD[ino].inventoryY + cd.Box[cd.selBox].ypos,
+ hTagFontHandle(), 0);
+ }
+ MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2);
+ }
+#endif
+ }
+ return false;
+}
+
+/*---------------------------------------------------------------------*\
+| Select() |
+|-----------------------------------------------------------------------|
+| Highlights selected box. |
+| If it's editable (save game), copy existing description and add a |
+| cursor. |
+\*---------------------------------------------------------------------*/
+void Select(int i, bool force) {
+#ifdef JAPAN
+ time_t secs_now;
+ struct tm *time_now;
+#endif
+
+ i &= ~IS_MASK;
+
+ if (cd.selBox == i && !force)
+ return;
+
+ cd.selBox = i;
+
+ // Clear previous selected highlight and text
+ if (iconArray[HL2] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ iconArray[HL2] = NULL;
+ }
+ if (iconArray[HL3] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL3]);
+ iconArray[HL3] = NULL;
+ }
+
+ // New highlight box
+ switch (cd.Box[i].boxType) {
+ case RGROUP:
+ iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w, cd.Box[i].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ MultiSetAniXY(iconArray[HL2],
+ InvD[ino].inventoryX + cd.Box[i].xpos,
+ InvD[ino].inventoryY + cd.Box[i].ypos);
+
+ // Z-position of box, and add edit text if appropriate
+ if (cd.editableRgroup) {
+ MultiSetZPosition(iconArray[HL2], Z_INV_ITEXT+1);
+
+ assert(cd.Box[i].ixText == USE_POINTER);
+#ifdef JAPAN
+ // Current date and time
+ time(&secs_now);
+ time_now = localtime(&secs_now);
+ strftime(sedit, SG_DESC_LEN, "%D %H:%M", time_now);
+#else
+ // Current description with cursor appended
+ if (cd.Box[i].boxText != NULL) {
+ strcpy(sedit, cd.Box[i].boxText);
+ strcat(sedit, sCursor);
+ } else {
+ strcpy(sedit, sCursor);
+ }
+#endif
+ iconArray[HL3] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), sedit, 0,
+ InvD[ino].inventoryX + cd.Box[i].xpos + 2,
+#ifdef JAPAN
+ InvD[ino].inventoryY + cd.Box[i].ypos + 2,
+#else
+ InvD[ino].inventoryY + cd.Box[i].ypos,
+#endif
+ hTagFontHandle(), 0);
+ MultiSetZPosition(iconArray[HL3], Z_INV_ITEXT + 2);
+ } else {
+ MultiSetZPosition(iconArray[HL2], Z_INV_ICONS + 1);
+ }
+
+ _vm->divertKeyInput(InvKeyIn);
+
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ iconArray[HL2] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[i].w+6, cd.Box[i].h+6);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL2]);
+ MultiSetAniXY(iconArray[HL2],
+ InvD[ino].inventoryX + cd.Box[i].xpos - 2,
+ InvD[ino].inventoryY + cd.Box[i].ypos - 2);
+ MultiSetZPosition(iconArray[HL2], Z_INV_BRECT+1);
+
+ break;
+#endif
+ default:
+ break;
+ }
+}
+
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * If the item is not already held, hold it.
+ */
+
+void HoldItem(int item) {
+ INV_OBJECT *invObj;
+
+ if (HeldItem != item) {
+ if (item == INV_NOICON && HeldItem != INV_NOICON)
+ DelAuxCursor(); // no longer aux cursor
+
+ if (item != INV_NOICON) {
+ invObj = findInvObject(item);
+ SetAuxCursor(invObj->hFilm); // and is aux. cursor
+ }
+
+ HeldItem = item; // Item held
+ }
+
+ // Redraw contents - held item not displayed as a content.
+ ItemsChanged = true;
+}
+
+/**
+ * Stop holding an item.
+ */
+
+void DropItem(int item) {
+ if (HeldItem == item) {
+ HeldItem = INV_NOICON; // Item not held
+ DelAuxCursor(); // no longer aux cursor
+ }
+
+ // Redraw contents - held item was not displayed as a content.
+ ItemsChanged = true;
+}
+
+/**
+ * Stick the item into an inventory list (ItemOrder[]), and hold the
+ * item if requested.
+ */
+
+void AddToInventory(int invno, int icon, bool hold) {
+ int i;
+ bool bOpen;
+#ifdef DEBUG
+ INV_OBJECT *invObj;
+#endif
+
+ assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_OPEN)); // Trying to add to illegal inventory
+
+ if (invno == INV_OPEN) {
+ assert(InventoryState == ACTIVE_INV && (ino == INV_1 || ino == INV_2)); // addopeninv() with inventry not open
+ invno = ino;
+ bOpen = true;
+
+ // Make sure it doesn't get in both!
+ RemFromInventory(ino == INV_1 ? INV_2 : INV_1, icon);
+ } else
+ bOpen = false;
+
+#ifdef DEBUG
+ invObj = findInvObject(icon);
+ if ((invObj->attribute & IO_ONLYINV1 && invno != INV_1)
+ || (invObj->attribute & IO_ONLYINV2 && invno != INV_2))
+ error("Trying to add resticted object to wrong inventory");
+#endif
+
+ if (invno == INV_1)
+ RemFromInventory(INV_2, icon);
+ else if (invno == INV_2)
+ RemFromInventory(INV_1, icon);
+
+ // See if it's already there
+ for (i = 0; i < InvD[invno].NoofItems; i++) {
+ if (InvD[invno].ItemOrder[i] == icon)
+ break;
+ }
+
+ // Add it if it isn't already there
+ if (i == InvD[invno].NoofItems) {
+ if (!bOpen) {
+ if (invno == INV_CONV) {
+ // For conversation, insert before last icon
+ // which will always be the goodbye icon
+ InvD[invno].ItemOrder[InvD[invno].NoofItems] = InvD[invno].ItemOrder[InvD[invno].NoofItems-1];
+ InvD[invno].ItemOrder[InvD[invno].NoofItems-1] = icon;
+ InvD[invno].NoofItems++;
+ } else {
+ InvD[invno].ItemOrder[InvD[invno].NoofItems++] = icon;
+ }
+ ItemsChanged = true;
+ } else {
+ // It could be that the index is beyond what you'd expect
+ // as delinv may well have been called
+ if (GlitterIndex < InvD[invno].NoofItems) {
+ memmove(&InvD[invno].ItemOrder[GlitterIndex + 1],
+ &InvD[invno].ItemOrder[GlitterIndex],
+ (InvD[invno].NoofItems-GlitterIndex)*sizeof(int));
+ InvD[invno].ItemOrder[GlitterIndex] = icon;
+ } else {
+ InvD[invno].ItemOrder[InvD[invno].NoofItems] = icon;
+ }
+ InvD[invno].NoofItems++;
+ }
+ }
+
+ // Hold it if requested
+ if (hold)
+ HoldItem(icon);
+}
+
+/**
+ * Take the item from the inventory list (ItemOrder[]).
+ * Return FALSE if item wasn't present, true if it was.
+ */
+
+bool RemFromInventory(int invno, int icon) {
+ int i;
+
+ assert(invno == INV_1 || invno == INV_2 || invno == INV_CONV); // Trying to delete from illegal inventory
+
+ // See if it's there
+ for (i = 0; i < InvD[invno].NoofItems; i++) {
+ if (InvD[invno].ItemOrder[i] == icon)
+ break;
+ }
+
+ if (i == InvD[invno].NoofItems)
+ return false; // Item wasn't there
+ else {
+ memmove(&InvD[invno].ItemOrder[i], &InvD[invno].ItemOrder[i+1], (InvD[invno].NoofItems-i)*sizeof(int));
+ InvD[invno].NoofItems--;
+ ItemsChanged = true;
+ return true; // Item removed
+ }
+}
+
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/*---------------------------------------------------------------------*\
+| InvArea() |
+|-----------------------------------------------------------------------|
+| Work out which area of the inventory window the cursor is in. |
+|-----------------------------------------------------------------------|
+| This used to be worked out with appropriately defined magic numbers. |
+| Then the graphic changed and I got it right again. Then the graphic |
+| changed and I got fed up of faffing about. It's probably easier just |
+| to rework all this. |
+\*---------------------------------------------------------------------*/
+enum { I_NOTIN, I_MOVE, I_BODY,
+ I_TLEFT, I_TRIGHT, I_BLEFT, I_BRIGHT,
+ I_TOP, I_BOTTOM, I_LEFT, I_RIGHT,
+ I_UP, I_SLIDE_UP, I_SLIDE, I_SLIDE_DOWN, I_DOWN,
+ I_ENDCHANGE
+};
+
+#define EXTRA 1 // This was introduced when we decided to increase
+ // the active area of the borders for re-sizing.
+
+/*---------------------------------*/
+#define LeftX InvD[ino].inventoryX
+#define TopY InvD[ino].inventoryY
+/*---------------------------------*/
+
+int InvArea(int x, int y) {
+ int RightX = MultiRightmost(RectObject) + 1;
+ int BottomY = MultiLowest(RectObject) + 1;
+
+// Outside the whole rectangle?
+ if (x <= LeftX - EXTRA || x > RightX + EXTRA
+ || y <= TopY - EXTRA || y > BottomY + EXTRA)
+ return I_NOTIN;
+
+// The bottom line
+ if (y > BottomY - 2 - EXTRA) { // Below top of bottom line?
+ if (x <= LeftX + 2 + EXTRA)
+ return I_BLEFT; // Bottom left corner
+ else if (x > RightX - 2 - EXTRA)
+ return I_BRIGHT; // Bottom right corner
+ else
+ return I_BOTTOM; // Just plain bottom
+ }
+
+// The top line
+ if (y <= TopY + 2 + EXTRA) { // Above bottom of top line?
+ if (x <= LeftX + 2 + EXTRA)
+ return I_TLEFT; // Top left corner
+ else if (x > RightX - 2 - EXTRA)
+ return I_TRIGHT; // Top right corner
+ else
+ return I_TOP; // Just plain top
+ }
+
+// Sides
+ if (x <= LeftX + 2 + EXTRA) // Left of right of left side?
+ return I_LEFT;
+ else if (x > RightX - 2 - EXTRA) // Right of left of right side?
+ return I_RIGHT;
+
+// From here down still needs fixing up properly
+/*
+* In the move area?
+*/
+ if (ino != INV_CONF
+ && x >= LeftX + M_SW - 2 && x <= RightX - M_SW + 3 &&
+ y >= TopY + M_TH - 2 && y < TopY + M_TBB + 2)
+ return I_MOVE;
+
+/*
+* Scroll bits
+*/
+ if (ino == INV_CONF && cd.bExtraWin) {
+ } else {
+ if (x > RightX - M_IAL + 3 && x <= RightX - M_IAR + 1) {
+ if (y > TopY + M_IUT + 1 && y < TopY + M_IUB - 1)
+ return I_UP;
+ if (y > BottomY - M_IDT + 4 && y <= BottomY - M_IDB + 1)
+ return I_DOWN;
+
+ if (y >= TopY + slideYmin && y < TopY + slideYmax + M_SH) {
+ if (y < TopY + slideY)
+ return I_SLIDE_UP;
+ if (y < TopY + slideY + M_SH)
+ return I_SLIDE;
+ else
+ return I_SLIDE_DOWN;
+ }
+ }
+ }
+
+ return I_BODY;
+}
+
+/**
+ * Returns the id of the icon displayed under the given position.
+ * Also return co-ordinates of items tag display position, if requested.
+ */
+
+int InvItem(int *x, int *y, bool update) {
+ int itop, ileft;
+ int row, col;
+ int item;
+ int IconsX;
+
+ itop = InvD[ino].inventoryY + START_ICONY;
+
+ IconsX = InvD[ino].inventoryX + START_ICONX;
+
+ for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) {
+ ileft = IconsX;
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++, item++) {
+ if (*x >= ileft && *x < ileft + ITEM_WIDTH &&
+ *y >= itop && *y < itop + ITEM_HEIGHT) {
+ if (update) {
+ *x = ileft + ITEM_WIDTH/2;
+ *y = itop /*+ ITEM_HEIGHT/4*/;
+ }
+ return item;
+ }
+
+ ileft += ITEM_WIDTH + 1;
+ }
+ itop += ITEM_HEIGHT + 1;
+ }
+ return INV_NOICON;
+}
+
+/**
+ * Returns the id of the icon displayed under the given position.
+ */
+
+int InvItemId(int x, int y) {
+ int itop, ileft;
+ int row, col;
+ int item;
+
+ if (InventoryHidden || InventoryState == IDLE_INV)
+ return INV_NOICON;
+
+ itop = InvD[ino].inventoryY + START_ICONY;
+
+ int IconsX = InvD[ino].inventoryX + START_ICONX;
+
+ for (item = InvD[ino].FirstDisp, row = 0; row < InvD[ino].NoofVicons; row++) {
+ ileft = IconsX;
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++, item++) {
+ if (x >= ileft && x < ileft + ITEM_WIDTH &&
+ y >= itop && y < itop + ITEM_HEIGHT) {
+ return InvD[ino].ItemOrder[item];
+ }
+
+ ileft += ITEM_WIDTH + 1;
+ }
+ itop += ITEM_HEIGHT + 1;
+ }
+ return INV_NOICON;
+}
+
+/*---------------------------------------------------------------------*\
+| WhichInvBox() |
+|-----------------------------------------------------------------------|
+| Finds which box the cursor is in. |
+\*---------------------------------------------------------------------*/
+#define MD_YSLIDTOP 7
+#define MD_YSLIDBOT 18
+#define MD_YBUTTOP 9
+#define MD_YBUTBOT 16
+#define MD_XLBUTL 1
+#define MD_XLBUTR 10
+#define MD_XRBUTL 105
+#define MD_XRBUTR 114
+
+static int WhichInvBox(int curX, int curY, bool bSlides) {
+ if (bSlides) {
+ for (int i = 0; i < numMdSlides; i++) {
+ if (curY > MultiHighest(mdSlides[i].obj) && curY < MultiLowest(mdSlides[i].obj)
+ && curX > MultiLeftmost(mdSlides[i].obj) && curX < MultiRightmost(mdSlides[i].obj))
+ return mdSlides[i].num | IS_SLIDER;
+ }
+ }
+
+ curX -= InvD[ino].inventoryX;
+ curY -= InvD[ino].inventoryY;
+
+ for (int i = 0; i < cd.NumBoxes; i++) {
+ switch (cd.Box[i].boxType) {
+ case SLIDER:
+ if (bSlides) {
+ if (curY >= cd.Box[i].ypos+MD_YBUTTOP && curY < cd.Box[i].ypos+MD_YBUTBOT) {
+ if (curX >= cd.Box[i].xpos+MD_XLBUTL && curX < cd.Box[i].xpos+MD_XLBUTR)
+ return i | IS_LEFT;
+ if (curX >= cd.Box[i].xpos+MD_XRBUTL && curX < cd.Box[i].xpos+MD_XRBUTR)
+ return i | IS_RIGHT;
+ }
+ }
+ break;
+
+ case AAGBUT:
+ case ARSGBUT:
+ case TOGGLE:
+ case FLIP:
+ if (curY > cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h
+ && curX > cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w)
+ return i;
+ break;
+
+ default:
+ // 'Normal' box
+ if (curY >= cd.Box[i].ypos && curY < cd.Box[i].ypos + cd.Box[i].h
+ && curX >= cd.Box[i].xpos && curX < cd.Box[i].xpos + cd.Box[i].w)
+ return i;
+ break;
+ }
+ }
+
+ if (cd.bExtraWin) {
+ if (curX > 20 + 181 && curX < 20 + 181 + 8 &&
+ curY > 24 + 2 && curY < 24 + 139 + 5) {
+
+ if (curY < 24 + 2 + 5) {
+ return IB_UP;
+ } else if (curY > 24 + 139) {
+ return IB_DOWN;
+ } else if (curY+InvD[ino].inventoryY >= slideY && curY+InvD[ino].inventoryY < slideY + 5) {
+ return IB_SLIDE;
+ } else if (curY+InvD[ino].inventoryY < slideY) {
+ return IB_SLIDE_UP;
+ } else if (curY+InvD[ino].inventoryY >= slideY + 5) {
+ return IB_SLIDE_DOWN;
+ }
+ }
+ }
+
+ return IB_NONE;
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * InBoxes
+ */
+void InvBoxes(bool InBody, int curX, int curY) {
+ int index; // Box pointed to on this call
+ const FILM *pfilm;
+
+ // Find out which icon is currently pointed to
+ if (!InBody)
+ index = -1;
+ else {
+ index = WhichInvBox(curX, curY, false);
+ }
+
+ // If no icon pointed to, or points to (logical position of)
+ // currently held icon, then no icon is pointed to!
+ if (index < 0) {
+ // unhigh-light box (if one was)
+ cd.pointBox = NOBOX;
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ } else if (index != cd.pointBox) {
+ cd.pointBox = index;
+ // A new box is pointed to - high-light it
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+ if ((cd.Box[cd.pointBox].boxType == ARSBUT && cd.selBox != NOBOX) ||
+///* I don't agree */ cd.Box[cd.pointBox].boxType == RGROUP ||
+ cd.Box[cd.pointBox].boxType == AATBUT ||
+ cd.Box[cd.pointBox].boxType == AABUT) {
+ iconArray[HL1] = RectangleObject(BackPal(), COL_HILIGHT, cd.Box[cd.pointBox].w, cd.Box[cd.pointBox].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ MultiSetAniXY(iconArray[HL1],
+ InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos,
+ InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+ }
+ else if (cd.Box[cd.pointBox].boxType == AAGBUT ||
+ cd.Box[cd.pointBox].boxType == ARSGBUT ||
+ cd.Box[cd.pointBox].boxType == TOGGLE) {
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ iconArray[HL1] = AddObject(&pfilm->reels[cd.Box[cd.pointBox].bi+HIGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1],
+ InvD[ino].inventoryX + cd.Box[cd.pointBox].xpos,
+ InvD[ino].inventoryY + cd.Box[cd.pointBox].ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+ }
+ }
+}
+
+static void ButtonPress(CORO_PARAM, CONFBOX *box) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FILM *pfilm;
+
+ assert(box->boxType == AAGBUT || box->boxType == ARSGBUT);
+
+ // Replace highlight image with normal image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ pfilm = (const FILM *)LockMem(winPartsf);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Replace normal image with depresses image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold depressed image for 2 frames
+ CORO_SLEEP(2);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Replace depressed image with normal image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ CORO_SLEEP(1);
+
+ CORO_END_CODE;
+}
+
+static void ButtonToggle(CORO_PARAM, CONFBOX *box) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const FILM *pfilm;
+
+ assert(box->boxType == TOGGLE);
+
+ // Remove hilight image
+ if (iconArray[HL1] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+ }
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // Add depressed image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold depressed image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // Toggle state
+ (*box->ival) = *(box->ival) ^ 1; // XOR with true
+ box->bi = *(box->ival) ? IX_TICK1 : IX_CROSS1;
+ AddBoxes(false);
+ // Keep highlight (e.g. flag)
+ if (cd.selBox != NOBOX)
+ Select(cd.selBox, true);
+
+ // New state, depressed image
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+DOWNGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ // Hold new depressed image for 1 frame
+ CORO_SLEEP(1);
+ if (iconArray[HL1] == NULL)
+ return;
+
+ // New state, normal
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = NULL;
+
+ // Hold normal image for 1 frame
+ CORO_SLEEP(1);
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // New state, highlighted
+ pfilm = (const FILM *)LockMem(winPartsf);
+ if (iconArray[HL1] != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), iconArray[HL1]);
+ iconArray[HL1] = AddObject(&pfilm->reels[box->bi+HIGRAPH], -1);
+ MultiSetAniXY(iconArray[HL1], InvD[ino].inventoryX + box->xpos, InvD[ino].inventoryY + box->ypos);
+ MultiSetZPosition(iconArray[HL1], Z_INV_ICONS+1);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Monitors for POINTED event for inventory icons.
+ */
+
+void InvLabels(bool InBody, int aniX, int aniY) {
+ int index; // Icon pointed to on this call
+ INV_OBJECT *invObj;
+
+ // Find out which icon is currently pointed to
+ if (!InBody)
+ index = INV_NOICON;
+ else {
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (index >= InvD[ino].NoofItems)
+ index = INV_NOICON;
+ else
+ index = InvD[ino].ItemOrder[index];
+ }
+ }
+
+ // If no icon pointed to, or points to (logical position of)
+ // currently held icon, then no icon is pointed to!
+ if (index == INV_NOICON || index == HeldItem) {
+ pointedIcon = INV_NOICON;
+ } else if (index != pointedIcon) {
+ // A new icon is pointed to - run its script with POINTED event
+ invObj = findInvObject(index);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, POINTED, BE_NONE, index);
+ pointedIcon = index;
+ }
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * All to do with the slider.
+ * I can't remember how it works - or, indeed, what it does.
+ * It seems to set up slideStuff[], an array of possible first-displayed
+ * icons set against the matching y-positions of the slider.
+ */
+
+void AdjustTop(void) {
+ int tMissing, bMissing, nMissing;
+ int nslideY;
+ int rowsWanted;
+ int slideRange;
+ int n, i;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ rowsWanted = (InvD[ino].NoofItems - InvD[ino].FirstDisp + InvD[ino].NoofHicons-1) / InvD[ino].NoofHicons;
+
+ while (rowsWanted < InvD[ino].NoofVicons) {
+ if (InvD[ino].FirstDisp) {
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ rowsWanted++;
+ } else
+ break;
+ }
+ tMissing = InvD[ino].FirstDisp ? (InvD[ino].FirstDisp + InvD[ino].NoofHicons-1)/InvD[ino].NoofHicons : 0;
+ bMissing = (rowsWanted > InvD[ino].NoofVicons) ? rowsWanted - InvD[ino].NoofVicons : 0;
+
+ nMissing = tMissing + bMissing;
+ slideRange = slideYmax - slideYmin;
+
+ if (!tMissing)
+ nslideY = slideYmin;
+ else if (!bMissing)
+ nslideY = slideYmax;
+ else {
+ nslideY = tMissing*slideRange/nMissing;
+ nslideY += slideYmin;
+ }
+
+ if (nMissing) {
+ n = InvD[ino].FirstDisp - tMissing*InvD[ino].NoofHicons;
+ for (i = 0; i <= nMissing; i++, n += InvD[ino].NoofHicons) {
+ slideStuff[i].n = n;
+ slideStuff[i].y = (i*slideRange/nMissing) + slideYmin;
+ }
+ if (slideStuff[0].n < 0)
+ slideStuff[0].n = 0;
+ assert(i < MAX_ININV + 1);
+ slideStuff[i].n = -1;
+ } else {
+ slideStuff[0].n = 0;
+ slideStuff[0].y = slideYmin;
+ slideStuff[1].n = -1;
+ }
+
+ if (nslideY != slideY) {
+ MultiMoveRelXY(SlideObject, 0, nslideY - slideY);
+ slideY = nslideY;
+ }
+}
+
+/**
+ * Insert an inventory icon object onto the display list.
+ */
+
+OBJECT *AddInvObject(int num, const FREEL **pfreel, const FILM **pfilm) {
+ INV_OBJECT *invObj; // Icon data
+ const MULTI_INIT *pmi; // Its INIT structure - from the reel
+ IMAGE *pim; // ... you get the picture
+ OBJECT *pPlayObj; // The object we insert
+
+ invObj = findInvObject(num);
+
+ // Get pointer to image
+ pim = GetImageFromFilm(invObj->hFilm, 0, pfreel, &pmi, pfilm);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Set up the multi-object
+ pPlayObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj);
+
+ return pPlayObj;
+}
+
+/**
+ * Create display objects for the displayed icons in an inventory window.
+ */
+
+void FillInInventory(void) {
+ int Index; // Index into ItemOrder[]
+ int n = 0; // index into iconArray[]
+ int xpos, ypos;
+ int row, col;
+ const FREEL *pfr;
+ const FILM *pfilm;
+
+ DumpIconArray();
+
+ if (InvDragging != ID_SLIDE)
+ AdjustTop(); // Set up slideStuff[]
+
+ Index = InvD[ino].FirstDisp; // Start from first displayed object
+ n = 0;
+ ypos = START_ICONY; // Y-offset of first display row
+
+ for (row = 0; row < InvD[ino].NoofVicons; row++, ypos += ITEM_HEIGHT + 1) {
+ xpos = START_ICONX; // X-offset of first display column
+
+ for (col = 0; col < InvD[ino].NoofHicons; col++) {
+ if (Index >= InvD[ino].NoofItems)
+ break;
+ else if (InvD[ino].ItemOrder[Index] != HeldItem) {
+ // Create a display object and position it
+ iconArray[n] = AddInvObject(InvD[ino].ItemOrder[Index], &pfr, &pfilm);
+ MultiSetAniXY(iconArray[n], InvD[ino].inventoryX + xpos , InvD[ino].inventoryY + ypos);
+ MultiSetZPosition(iconArray[n], Z_INV_ICONS);
+
+ InitStepAnimScript(&iconAnims[n], iconArray[n], FROM_LE_32(pfr->script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+
+ n++;
+ }
+ Index++;
+ xpos += ITEM_WIDTH + 1; // X-offset of next display column
+ }
+ }
+}
+
+/**
+ * Set up a rectangle as the background to the inventory window.
+ * Additionally, sticks the window title up.
+ */
+
+enum {FROM_HANDLE, FROM_STRING};
+
+void AddBackground(OBJECT **rect, OBJECT **title, int extraH, int extraV, int textFrom) {
+ // Why not 2 ????
+ int width = TLwidth + extraH + TRwidth - 3;
+ int height = TLheight + extraV + BLheight - 3;
+
+ // Create a rectangle object
+ RectObject = *rect = TranslucentObject(width, height);
+
+ // add it to display list and position it
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), *rect);
+ MultiSetAniXY(*rect, InvD[ino].inventoryX + 1, InvD[ino].inventoryY + 1);
+ MultiSetZPosition(*rect, Z_INV_BRECT);
+
+ // Create text object using title string
+ if (textFrom == FROM_HANDLE) {
+ LoadStringRes(InvD[ino].hInvTitle, tBufferAddr(), TBUFSZ);
+ *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*title); // Inventory title string produced NULL text
+ MultiSetZPosition(*title, Z_INV_HTEXT);
+ } else if (textFrom == FROM_STRING && cd.ixHeading != NO_HEADING) {
+ LoadStringRes(configStrings[cd.ixHeading], tBufferAddr(), TBUFSZ);
+ *title = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ InvD[ino].inventoryX + width/2, InvD[ino].inventoryY + M_TOFF,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*title); // Inventory title string produced NULL text
+ MultiSetZPosition(*title, Z_INV_HTEXT);
+ }
+}
+
+/**
+ * Insert a part of the inventory window frame onto the display list.
+ */
+
+static OBJECT *AddObject(const FREEL *pfreel, int num) {
+ const MULTI_INIT *pmi; // Get the MULTI_INIT structure
+ IMAGE *pim;
+ OBJECT *pPlayObj;
+
+ // Get pointer to image
+ pim = GetImageFromReel(pfreel, &pmi);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ // Horrible bodge involving global variables to save
+ // width and/or height of some window frame components
+ if (num == TL) {
+ TLwidth = FROM_LE_16(pim->imgWidth);
+ TLheight = FROM_LE_16(pim->imgHeight);
+ } else if (num == TR) {
+ TRwidth = FROM_LE_16(pim->imgWidth);
+ } else if (num == BL) {
+ BLheight = FROM_LE_16(pim->imgHeight);
+ }
+
+ // Set up and insert the multi-object
+ pPlayObj = MultiInitObject(pmi);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), pPlayObj);
+
+ return pPlayObj;
+}
+
+/**
+ * Display the scroll bar slider.
+ */
+
+void AddSlider(OBJECT **slide, const FILM *pfilm) {
+ SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1);
+ MultiSetAniXY(*slide, MultiRightmost(RectObject)-M_SXOFF+2, InvD[ino].inventoryY + slideY);
+ MultiSetZPosition(*slide, Z_INV_MFRAME);
+}
+
+enum {
+ SLIDE_RANGE = 81,
+ SLIDE_MINX = 8,
+ SLIDE_MAXX = 8+SLIDE_RANGE,
+
+ MDTEXT_YOFF = 6,
+ MDTEXT_XOFF = -4
+};
+
+/**
+ * Display a box with some text in it.
+ */
+
+void AddBox(int *pi, int i) {
+ int x = InvD[ino].inventoryX + cd.Box[i].xpos;
+ int y = InvD[ino].inventoryY + cd.Box[i].ypos;
+ int *pival = cd.Box[i].ival;
+ int xdisp;
+ const FILM *pfilm;
+
+ switch (cd.Box[i].boxType) {
+ default:
+ // Give us a box
+ iconArray[*pi] = RectangleObject(BackPal(), COL_BOX, cd.Box[i].w, cd.Box[i].h);
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), iconArray[*pi]);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ if (cd.Box[i].ixText == USE_POINTER) {
+ if (cd.Box[i].boxText != NULL) {
+ if (cd.Box[i].boxType == RGROUP) {
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0,
+#ifdef JAPAN
+ x+2, y+2, hTagFontHandle(), 0);
+#else
+ x+2, y, hTagFontHandle(), 0);
+#endif
+ } else {
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), cd.Box[i].boxText, 0,
+#ifdef JAPAN
+// Note: it never seems to go here!
+ x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE);
+#else
+ x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE);
+#endif
+ }
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ }
+ } else {
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ assert(cd.Box[i].boxType != RGROUP); // You'll need to add some code!
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+#ifdef JAPAN
+ x + cd.Box[i].w/2, y+2, hTagFontHandle(), TXT_CENTRE);
+#else
+ x + cd.Box[i].w/2, y, hTagFontHandle(), TXT_CENTRE);
+#endif
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ }
+ break;
+
+ case AAGBUT:
+ case ARSGBUT:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ assert(flagFilm != 0); // Language flags not declared!
+
+ pfilm = (const FILM *)LockMem(flagFilm);
+
+ if (bAmerica && cd.Box[i].bi == FIX_UK)
+ cd.Box[i].bi = FIX_USA;
+
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+2);
+ *pi += 1;
+
+ break;
+#endif
+ case FLIP:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ if (*(cd.Box[i].ival))
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi], -1);
+ else
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+1], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+
+ case TOGGLE:
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ cd.Box[i].bi = *(cd.Box[i].ival) ? IX_TICK1 : IX_CROSS1;
+ iconArray[*pi] = AddObject(&pfilm->reels[cd.Box[i].bi+NORMGRAPH], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_INV_BRECT+1);
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+
+ case SLIDER:
+ pfilm = (const FILM *)LockMem(winPartsf);
+ xdisp = SLIDE_RANGE*(*pival)/cd.Box[i].w;
+
+ iconArray[*pi] = AddObject(&pfilm->reels[IX_MDGROOVE], -1);
+ MultiSetAniXY(iconArray[*pi], x, y);
+ MultiSetZPosition(iconArray[*pi], Z_MDGROOVE);
+ *pi += 1;
+ iconArray[*pi] = AddObject(&pfilm->reels[IX_MDSLIDER], -1);
+ MultiSetAniXY(iconArray[*pi], x+SLIDE_MINX+xdisp, y);
+ MultiSetZPosition(iconArray[*pi], Z_MDSLIDER);
+ assert(numMdSlides < MAXSLIDES);
+ mdSlides[numMdSlides].num = i;
+ mdSlides[numMdSlides].min = x+SLIDE_MINX;
+ mdSlides[numMdSlides].max = x+SLIDE_MAXX;
+ mdSlides[numMdSlides++].obj = iconArray[*pi];
+ *pi += 1;
+
+ // Stick in the text
+ assert(cd.Box[i].ixText != USE_POINTER);
+ LoadStringRes(configStrings[cd.Box[i].ixText], tBufferAddr(), TBUFSZ);
+ iconArray[*pi] = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(), 0,
+ x+MDTEXT_XOFF, y+MDTEXT_YOFF, hTagFontHandle(), TXT_RIGHT);
+ MultiSetZPosition(iconArray[*pi], Z_INV_ITEXT);
+ *pi += 1;
+ break;
+ }
+}
+
+/**
+ * Display some boxes.
+ */
+static void AddBoxes(bool posnSlide) {
+ int oCount = NUMHL; // Object count - allow for HL1, HL2 etc.
+
+ DumpIconArray();
+ numMdSlides = 0;
+
+ for (int i = 0; i < cd.NumBoxes; i++) {
+ AddBox(&oCount, i);
+ }
+
+ if (cd.bExtraWin) {
+ if (posnSlide)
+ slideY = slideYmin + (cd.fileBase*(slideYmax-slideYmin))/(MAX_SFILES-NUM_SL_RGROUP);
+ MultiSetAniXY(SlideObject, InvD[ino].inventoryX + 24 + 179, slideY);
+ }
+
+ assert(oCount < MAX_ICONS); // added too many icons
+}
+
+/**
+ * Display the scroll bar slider.
+ */
+
+void AddEWSlider(OBJECT **slide, const FILM *pfilm) {
+ SlideObject = *slide = AddObject(&pfilm->reels[IX_SLIDE], -1);
+ MultiSetAniXY(*slide, InvD[ino].inventoryX + 24 + 127, slideY);
+ MultiSetZPosition(*slide, Z_INV_MFRAME);
+}
+
+/**
+ * AddExtraWindow
+ */
+
+int AddExtraWindow(int x, int y, OBJECT **retObj) {
+ int n = 0;
+ const FILM *pfilm;
+
+ // Get the frame's data
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ x += 20;
+ y += 24;
+
+// Draw the four corners
+ retObj[n] = AddObject(&pfilm->reels[IX_RTL], -1); // Top left
+ MultiSetAniXY(retObj[n], x, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_NTR], -1); // Top right
+ MultiSetAniXY(retObj[n], x + 152, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_BL], -1); // Bottom left
+ MultiSetAniXY(retObj[n], x, y + 124);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_BR], -1); // Bottom right
+ MultiSetAniXY(retObj[n], x + 152, y + 124);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+
+// Draw the edges
+ retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Top
+ MultiSetAniXY(retObj[n], x + 6, y);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_H156], -1); // Bottom
+ MultiSetAniXY(retObj[n], x + 6, y + 143);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Left
+ MultiSetAniXY(retObj[n], x, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 1
+ MultiSetAniXY(retObj[n], x + 179, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[IX_V104], -1); // Right 2
+ MultiSetAniXY(retObj[n], x + 188, y + 20);
+ MultiSetZPosition(retObj[n], Z_INV_MFRAME);
+ n++;
+
+ slideY = slideYmin = y + 9;
+ slideYmax = y + 134;
+ AddEWSlider(&retObj[n++], pfilm);
+
+ return n;
+}
+
+
+enum InventoryType { EMPTY, FULL, CONF };
+
+/**
+ * Construct an inventory window - either a standard one, with
+ * background, slider and icons, or a re-sizing window.
+ */
+void ConstructInventory(InventoryType filling) {
+ int eH, eV; // Extra width and height
+ int n = 0; // Index into object array
+ int zpos; // Z-position of frame
+ int invX = InvD[ino].inventoryX;
+ int invY = InvD[ino].inventoryY;
+ OBJECT **retObj;
+ const FILM *pfilm;
+
+ extern bool RePosition(void); // Forward reference
+ // Select the object array to use
+ if (filling == FULL || filling == CONF) {
+ retObj = objArray; // Standard window
+ zpos = Z_INV_MFRAME;
+ } else {
+ retObj = DobjArray; // Re-sizing window
+ zpos = Z_INV_RFRAME;
+ }
+
+ // Dispose of anything it may be replacing
+ for (int i = 0; i < MAX_WCOMP; i++) {
+ if (retObj[i] != NULL) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), retObj[i]);
+ retObj[i] = NULL;
+ }
+ }
+
+ // Get the frame's data
+ pfilm = (const FILM *)LockMem(winPartsf);
+
+ // Standard window is of granular dimensions
+ if (filling == FULL) {
+ // Round-up/down to nearest number of icons
+ if (SuppH > ITEM_WIDTH / 2)
+ InvD[ino].NoofHicons++;
+ if (SuppV > ITEM_HEIGHT / 2)
+ InvD[ino].NoofVicons++;
+ SuppH = SuppV = 0;
+ }
+
+ // Extra width and height
+ eH = (InvD[ino].NoofHicons - 1) * (ITEM_WIDTH+1) + SuppH;
+ eV = (InvD[ino].NoofVicons - 1) * (ITEM_HEIGHT+1) + SuppV;
+
+ // Which window frame corners to use
+ if (filling == FULL && ino != INV_CONV) {
+ TL = IX_TL;
+ TR = IX_TR;
+ BL = IX_BL;
+ BR = IX_BR;
+ } else {
+ TL = IX_RTL;
+ TR = IX_RTR;
+ BL = IX_BL;
+ BR = IX_RBR;
+ }
+
+// Draw the four corners
+ retObj[n] = AddObject(&pfilm->reels[TL], TL);
+ MultiSetAniXY(retObj[n], invX, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[TR], TR);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[BL], BL);
+ MultiSetAniXY(retObj[n], invX, invY + TLheight + eV);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ retObj[n] = AddObject(&pfilm->reels[BR], BR);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH, invY + TLheight + eV);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+// Draw extra Top and bottom parts
+ if (InvD[ino].NoofHicons > 1) {
+ // Top side
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Bottom of header box
+ if (filling == FULL) {
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Extra bits for conversation - hopefully temporary
+ if (ino == INV_CONV) {
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth - 2, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ retObj[n] = AddObject(&pfilm->reels[IX_H52], -1);
+ MultiSetAniXY(retObj[n], invX + eH - 10, invY + M_TBB + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ }
+
+ // Bottom side
+ retObj[n] = AddObject(&pfilm->reels[hFillers[InvD[ino].NoofHicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth, invY + TLheight + eV + BLheight - M_TH + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ if (SuppH) {
+ int offx = TLwidth + eH - 26;
+ if (offx < TLwidth) // Not too far!
+ offx = TLwidth;
+
+ // Top side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + offx, invY);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Bottom side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_H26], -1);
+ MultiSetAniXY(retObj[n], invX + offx, invY + TLheight + eV + BLheight - M_TH + 1);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+// Draw extra side parts
+ if (InvD[ino].NoofVicons > 1) {
+ // Left side
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Left side of scroll bar
+ if (filling == FULL && ino != INV_CONV) {
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + M_SBL + 1, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+ // Right side
+ retObj[n] = AddObject(&pfilm->reels[vFillers[InvD[ino].NoofVicons-2]], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + TLheight);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+ if (SuppV) {
+ int offy = TLheight + eV - 26;
+ if (offy < 5)
+ offy = 5;
+
+ // Left side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_V26], -1);
+ MultiSetAniXY(retObj[n], invX, invY + offy);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+
+ // Right side extra
+ retObj[n] = AddObject(&pfilm->reels[IX_V26], -1);
+ MultiSetAniXY(retObj[n], invX + TLwidth + eH + TRwidth - M_SW + 1, invY + offy);
+ MultiSetZPosition(retObj[n], zpos);
+ n++;
+ }
+
+ OBJECT **rect, **title;
+
+// Draw background, slider and icons
+ if (filling == FULL) {
+ rect = &retObj[n++];
+ title = &retObj[n++];
+
+ AddBackground(rect, title, eH, eV, FROM_HANDLE);
+
+ if (ino == INV_CONV)
+ SlideObject = NULL;
+ else if (InvD[ino].NoofItems > InvD[ino].NoofHicons*InvD[ino].NoofVicons) {
+ slideYmin = TLheight - 2;
+ slideYmax = TLheight + eV + 10;
+ AddSlider(&retObj[n++], pfilm);
+ }
+
+ FillInInventory();
+ }
+ else if (filling == CONF) {
+ rect = &retObj[n++];
+ title = &retObj[n++];
+
+ AddBackground(rect, title, eH, eV, FROM_STRING);
+ if (cd.bExtraWin)
+ n += AddExtraWindow(invX, invY, &retObj[n]);
+ AddBoxes(true);
+ }
+
+ assert(n < MAX_WCOMP); // added more parts than we can handle!
+
+ // Reposition returns TRUE if needs to move
+ if (InvD[ino].moveable && filling == FULL && RePosition()) {
+ ConstructInventory(FULL);
+ }
+}
+
+
+/**
+ * Call this when drawing a 'FULL', movable inventory. Checks that the
+ * position of the Translucent object is within limits. If it isn't,
+ * adjusts the x/y position of the current inventory and returns TRUE.
+ */
+bool RePosition(void) {
+ int p;
+ bool bMoveitMoveit = false;
+
+ assert(RectObject); // no recangle object!
+
+ // Test for off-screen horizontally
+ p = MultiLeftmost(RectObject);
+ if (p > MAXLEFT) {
+ // Too far to the right
+ InvD[ino].inventoryX += MAXLEFT - p;
+ bMoveitMoveit = true; // I like to....
+ } else {
+ // Too far to the left?
+ p = MultiRightmost(RectObject);
+ if (p < MINRIGHT) {
+ InvD[ino].inventoryX += MINRIGHT - p;
+ bMoveitMoveit = true; // I like to....
+ }
+ }
+
+ // Test for off-screen vertically
+ p = MultiHighest(RectObject);
+ if (p < MINTOP) {
+ // Too high
+ InvD[ino].inventoryY += MINTOP - p;
+ bMoveitMoveit = true; // I like to....
+ } else if (p > MAXTOP) {
+ // Too low
+ InvD[ino].inventoryY += MAXTOP - p;
+ bMoveitMoveit = true; // I like to....
+ }
+
+ return bMoveitMoveit;
+}
+
+/**************************************************************************/
+/***/
+/**************************************************************************/
+
+/**
+ * Get the cursor's reel, poke in the background palette,
+ * and customise the cursor.
+ */
+void AlterCursor(int num) {
+ const FREEL *pfreel;
+ IMAGE *pim;
+
+ // Get pointer to image
+ pim = GetImageFromFilm(winPartsf, num, &pfreel);
+
+ // Poke in the background palette
+ pim->hImgPal = TO_LE_32(BackPal());
+
+ SetTempCursor(FROM_LE_32(pfreel->script));
+}
+
+enum InvCursorFN {IC_AREA, IC_DROP};
+
+/**
+ * InvCursor
+ */
+void InvCursor(InvCursorFN fn, int CurX, int CurY) {
+ static enum { IC_NORMAL, IC_DR, IC_UR, IC_TB, IC_LR,
+ IC_INV, IC_UP, IC_DN } ICursor = IC_NORMAL; // FIXME: local static var
+
+ int area; // The part of the window the cursor is over
+ bool restoreMain = false;
+
+ // If currently dragging, don't be messing about with the cursor shape
+ if (InvDragging != ID_NONE)
+ return;
+
+ switch (fn) {
+ case IC_DROP:
+ ICursor = IC_NORMAL;
+ InvCursor(IC_AREA, CurX, CurY);
+ break;
+
+ case IC_AREA:
+ area = InvArea(CurX, CurY);
+
+ // Check for POINTED events
+ if (ino == INV_CONF)
+ InvBoxes(area == I_BODY, CurX, CurY);
+ else
+ InvLabels(area == I_BODY, CurX, CurY);
+
+ // No cursor trails while within inventory window
+ if (area == I_NOTIN)
+ UnHideCursorTrails();
+ else
+ HideCursorTrails();
+
+ switch (area) {
+ case I_NOTIN:
+ restoreMain = true;
+ break;
+
+ case I_TLEFT:
+ case I_BRIGHT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_DR) {
+ AlterCursor(IX_CURDD);
+ ICursor = IC_DR;
+ }
+ break;
+
+ case I_TRIGHT:
+ case I_BLEFT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_UR) {
+ AlterCursor(IX_CURDU);
+ ICursor = IC_UR;
+ }
+ break;
+
+ case I_TOP:
+ case I_BOTTOM:
+ if (!InvD[ino].resizable) {
+ restoreMain = true;
+ break;
+ }
+ if (ICursor != IC_TB) {
+ AlterCursor(IX_CURUD);
+ ICursor = IC_TB;
+ }
+ break;
+
+ case I_LEFT:
+ case I_RIGHT:
+ if (!InvD[ino].resizable)
+ restoreMain = true;
+ else if (ICursor != IC_LR) {
+ AlterCursor(IX_CURLR);
+ ICursor = IC_LR;
+ }
+ break;
+
+ case I_UP:
+ case I_SLIDE_UP:
+ case I_DOWN:
+ case I_SLIDE_DOWN:
+ case I_SLIDE:
+ case I_MOVE:
+ case I_BODY:
+ restoreMain = true;
+ break;
+ }
+ break;
+ }
+
+ if (restoreMain && ICursor != IC_NORMAL) {
+ RestoreMainCursor();
+ ICursor = IC_NORMAL;
+ }
+}
+
+
+
+
+/*-------------------------------------------------------------------------*/
+
+
+/**************************************************************************/
+/******************** Conversation specific functions *********************/
+/**************************************************************************/
+
+
+void ConvAction(int index) {
+ assert(ino == INV_CONV); // not conv. window!
+
+ switch (index) {
+ case INV_NOICON:
+ return;
+
+ case INV_CLOSEICON:
+ thisConvIcon = -1; // Postamble
+ break;
+
+ case INV_OPENICON:
+ thisConvIcon = -2; // Preamble
+ break;
+
+ default:
+ thisConvIcon = InvD[ino].ItemOrder[index];
+ break;
+ }
+
+ RunPolyTinselCode(thisConvPoly, CONVERSE, BE_NONE, true);
+}
+/*-------------------------------------------------------------------------*/
+
+void AddIconToPermanentDefaultList(int icon) {
+ int i;
+
+ // See if it's already there
+ for (i = 0; i < Num0Order; i++) {
+ if (Inv0Order[i] == icon)
+ break;
+ }
+
+ // Add it if it isn't already there
+ if (i == Num0Order) {
+ Inv0Order[Num0Order++] = icon;
+ }
+}
+
+/*-------------------------------------------------------------------------*/
+
+void convPos(int fn) {
+ if (fn == CONV_DEF)
+ InvD[INV_CONV].inventoryY = 8;
+ else if (fn == CONV_BOTTOM)
+ InvD[INV_CONV].inventoryY = 150;
+}
+
+void ConvPoly(HPOLYGON hPoly) {
+ thisConvPoly = hPoly;
+}
+
+int convIcon(void) {
+ return thisConvIcon;
+}
+
+void CloseDownConv(void) {
+ if (InventoryState == ACTIVE_INV && ino == INV_CONV) {
+ KillInventory();
+ }
+}
+
+void convHide(bool hide) {
+ int aniX, aniY;
+ int i;
+
+ if (InventoryState == ACTIVE_INV && ino == INV_CONV) {
+ if (hide) {
+ for (i = 0; objArray[i] && i < MAX_WCOMP; i++) {
+ MultiAdjustXY(objArray[i], 2*SCREEN_WIDTH, 0);
+ }
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++) {
+ MultiAdjustXY(iconArray[i], 2*SCREEN_WIDTH, 0);
+ }
+ InventoryHidden = true;
+
+ InvLabels(false, 0, 0);
+ } else {
+ InventoryHidden = false;
+
+ for (i = 0; objArray[i] && i < MAX_WCOMP; i++) {
+ MultiAdjustXY(objArray[i], -2*SCREEN_WIDTH, 0);
+ }
+
+ // Don't flash if items changed. If they have, will be redrawn anyway.
+ if (!ItemsChanged) {
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++) {
+ MultiAdjustXY(iconArray[i], -2*SCREEN_WIDTH, 0);
+ }
+ }
+
+ GetCursorXY(&aniX, &aniY, false);
+ InvLabels(true, aniX, aniY);
+ }
+ }
+}
+
+bool convHid(void) {
+ return InventoryHidden;
+}
+
+
+/**************************************************************************/
+/******************* Open and closing functions ***************************/
+/**************************************************************************/
+
+/**
+ * Start up an inventory window.
+ */
+
+void PopUpInventory(int invno) {
+ assert((invno == INV_1 || invno == INV_2 || invno == INV_CONV || invno == INV_CONF)); // Trying to open illegal inventory
+
+ if (InventoryState == IDLE_INV) {
+ bOpenConf = false; // Better safe than sorry...
+
+ DisableTags(); // Tags disabled during inventory
+
+ if (invno == INV_CONV) { // Conversation window?
+ // Start conversation with permanent contents
+ memset(InvD[INV_CONV].ItemOrder, 0, MAX_ININV*sizeof(int));
+ memcpy(InvD[INV_CONV].ItemOrder, Inv0Order, Num0Order*sizeof(int));
+ InvD[INV_CONV].NoofItems = Num0Order;
+ thisConvIcon = 0;
+ } else if (invno == INV_CONF) { // Configuration window?
+ cd.selBox = NOBOX;
+ cd.pointBox = NOBOX;
+ }
+
+ ino = invno; // The open inventory
+
+ ItemsChanged = false; // Nothing changed
+ InvDragging = ID_NONE; // Not dragging
+ InventoryState = ACTIVE_INV; // Inventory actiive
+ InventoryHidden = false; // Not hidden
+ InventoryMaximised = InvD[ino].bMax;
+ if (invno != INV_CONF) // Configuration window?
+ ConstructInventory(FULL); // Draw it up
+ else {
+ ConstructInventory(CONF); // Draw it up
+ }
+ }
+}
+
+void SetConfGlobals(CONFINIT *ci) {
+ InvD[INV_CONF].MinHicons = InvD[INV_CONF].MaxHicons = InvD[INV_CONF].NoofHicons = ci->h;
+ InvD[INV_CONF].MaxVicons = InvD[INV_CONF].MinVicons = InvD[INV_CONF].NoofVicons = ci->v;
+ InvD[INV_CONF].inventoryX = ci->x;
+ InvD[INV_CONF].inventoryY = ci->y;
+ cd.bExtraWin = ci->bExtraWin;
+ cd.Box = ci->Box;
+ cd.NumBoxes = ci->NumBoxes;
+ cd.ixHeading = ci->ixHeading;
+}
+
+/**
+ * PopupConf
+ */
+
+void PopUpConf(CONFTYPE type) {
+ int curX, curY;
+
+ if (InventoryState != IDLE_INV)
+ return;
+
+ InvD[INV_CONF].resizable = false;
+ InvD[INV_CONF].moveable = false;
+
+ switch (type) {
+ case SAVE:
+ case LOAD:
+ if (type == SAVE) {
+ SetCursorScreenXY(262, 91);
+ SetConfGlobals(&ciSave);
+ cd.editableRgroup = true;
+ } else {
+ SetConfGlobals(&ciLoad);
+ cd.editableRgroup = false;
+ }
+ firstFile(0);
+ break;
+
+ case QUIT:
+#ifdef JAPAN
+ SetCursorScreenXY(180, 106);
+#else
+ SetCursorScreenXY(180, 90);
+#endif
+ SetConfGlobals(&ciQuit);
+ break;
+
+ case RESTART:
+#ifdef JAPAN
+ SetCursorScreenXY(180, 106);
+#else
+ SetCursorScreenXY(180, 90);
+#endif
+ SetConfGlobals(&ciRestart);
+ break;
+
+ case OPTION:
+ SetConfGlobals(&ciOption);
+ break;
+
+ case CONTROLS:
+ SetConfGlobals(&ciControl);
+ break;
+
+ case SOUND:
+ SetConfGlobals(&ciSound);
+ break;
+
+#ifndef JAPAN
+ case SUBT:
+ SetConfGlobals(&ciSubtitles);
+ break;
+#endif
+
+ case TOPWIN:
+ SetConfGlobals(&ciTopWin);
+ ino = INV_CONF;
+ ConstructInventory(CONF); // Draw it up
+ InventoryState = BOGUS_INV;
+ return;
+
+ default:
+ return;
+ }
+
+ if (HeldItem != INV_NOICON)
+ DelAuxCursor(); // no longer aux cursor
+
+ PopUpInventory(INV_CONF);
+
+ if (type == SAVE || type == LOAD)
+ Select(0, false);
+#ifndef JAPAN
+#if !defined(USE_3FLAGS) || !defined(USE_4FLAGS) || !defined(USE_5FLAGS)
+ else if (type == SUBT) {
+#ifdef USE_3FLAGS
+ // VERY quick dirty bodges
+ if (language == TXT_FRENCH)
+ Select(0, false);
+ else if (language == TXT_GERMAN)
+ Select(1, false);
+ else
+ Select(2, false);
+#elif defined(USE_4FLAGS)
+ Select(language-1, false);
+#else
+ Select(language, false);
+#endif
+ }
+#endif
+#endif // JAPAN
+
+ GetCursorXY(&curX, &curY, false);
+ InvCursor(IC_AREA, curX, curY);
+}
+
+/**
+ * Close down an inventory window.
+ */
+
+void KillInventory(void) {
+ if (objArray[0] != NULL) {
+ DumpObjArray();
+ DumpDobjArray();
+ DumpIconArray();
+ }
+
+ if (InventoryState == ACTIVE_INV) {
+ EnableTags();
+
+ InvD[ino].bMax = InventoryMaximised;
+
+ UnHideCursorTrails();
+ _vm->divertKeyInput(NULL);
+ }
+
+ InventoryState = IDLE_INV;
+
+ if (bOpenConf) {
+ bOpenConf = false;
+ PopUpConf(OPTION);
+ } else if (ino == INV_CONF)
+ InventoryIconCursor();
+}
+
+void CloseInventory(void) {
+ // If not active, ignore this
+ if (InventoryState != ACTIVE_INV)
+ return;
+
+ // If hidden, a conversation action is still underway - ignore this
+ if (InventoryHidden)
+ return;
+
+ // If conversation, this is a closeing event
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+
+ KillInventory();
+
+ RestoreMainCursor();
+}
+
+
+
+/**************************************************************************/
+/************************ The inventory process ***************************/
+/**************************************************************************/
+
+/**
+ * Redraws the icons if appropriate. Also handle button press/toggle effects
+ */
+void InventoryProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ CORO_SLEEP(1); // allow scheduling
+
+ if (objArray[0] != NULL) {
+ if (ItemsChanged && ino != INV_CONF && !InventoryHidden) {
+ FillInInventory();
+
+ // Needed when clicking on scroll bar.
+ int curX, curY;
+ GetCursorXY(&curX, &curY, false);
+ InvCursor(IC_AREA, curX, curY);
+
+ ItemsChanged = false;
+ }
+ if (ino != INV_CONF) {
+ for (int i = 0; i < MAX_ICONS; i++) {
+ if (iconArray[i] != NULL)
+ StepAnimScript(&iconAnims[i]);
+ }
+ }
+ if (InvDragging == ID_MDCONT) {
+ // Mixing desk control
+ int sval, index, *pival;
+
+ index = cd.selBox & ~IS_MASK;
+ pival = cd.Box[index].ival;
+ sval = *pival;
+
+ if (cd.selBox & IS_LEFT) {
+ *pival -= cd.Box[index].h;
+ if (*pival < 0)
+ *pival = 0;
+ } else if (cd.selBox & IS_RIGHT) {
+ *pival += cd.Box[index].h;
+ if (*pival > cd.Box[index].w)
+ *pival = cd.Box[index].w;
+ }
+
+ if (sval != *pival) {
+ SlideMSlider(0, (cd.selBox & IS_RIGHT) ? S_TIMEUP : S_TIMEDN);
+ }
+ }
+ }
+
+ if (g_buttonEffect.bButAnim) {
+ assert(g_buttonEffect.box);
+ if (g_buttonEffect.press) {
+ if (g_buttonEffect.box->boxType == AAGBUT || g_buttonEffect.box->boxType == ARSGBUT)
+ CORO_INVOKE_1(ButtonPress, g_buttonEffect.box);
+ switch (g_buttonEffect.box->boxFunc) {
+ case SAVEGAME:
+ KillInventory();
+ InvSaveGame();
+ break;
+ case LOADGAME:
+ KillInventory();
+ InvLoadGame();
+ break;
+ case IQUITGAME:
+ _vm->quitFlag = true;
+ break;
+ case CLOSEWIN:
+ KillInventory();
+ break;
+ case OPENLOAD:
+ KillInventory();
+ PopUpConf(LOAD);
+ break;
+ case OPENSAVE:
+ KillInventory();
+ PopUpConf(SAVE);
+ break;
+ case OPENREST:
+ KillInventory();
+ PopUpConf(RESTART);
+ break;
+ case OPENSOUND:
+ KillInventory();
+ PopUpConf(SOUND);
+ break;
+ case OPENCONT:
+ KillInventory();
+ PopUpConf(CONTROLS);
+ break;
+ #ifndef JAPAN
+ case OPENSUBT:
+ KillInventory();
+ PopUpConf(SUBT);
+ break;
+ #endif
+ case OPENQUIT:
+ KillInventory();
+ PopUpConf(QUIT);
+ break;
+ case INITGAME:
+ KillInventory();
+ bRestart = true;
+ break;
+ #if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case CLANG:
+ if (!LanguageChange())
+ KillInventory();
+ break;
+ case RLANG:
+ KillInventory();
+ break;
+ #endif
+ default:
+ break;
+ }
+ } else
+ CORO_INVOKE_1(ButtonToggle, g_buttonEffect.box);
+
+ g_buttonEffect.bButAnim = false;
+ }
+
+ }
+ CORO_END_CODE;
+}
+
+/**************************************************************************/
+/*************** Drag stuff - Resizing and moving window ******************/
+/**************************************************************************/
+
+/**
+ * Appears to find the nearest entry in slideStuff[] to the supplied
+ * y-coordinate.
+ */
+int NearestSlideY(int fity) {
+ int nearDist = 1000;
+ int thisDist;
+ int nearI = 0; // Index of nearest fit
+ int i = 0;
+
+ do {
+ thisDist = ABS(slideStuff[i].y - fity);
+ if (thisDist < nearDist) {
+ nearDist = thisDist;
+ nearI = i;
+ }
+ } while (slideStuff[++i].n != -1);
+ return nearI;
+}
+
+/**
+ * Gets called at the start and end of a drag on the slider, and upon
+ * y-movement during such a drag.
+ */
+void SlideSlider(int y, SSFN fn) {
+ static int newY = 0, lasti = 0; // FIXME: local static var
+ int gotoY, ati;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ newY = slideY;
+ lasti = NearestSlideY(slideY);
+ break;
+
+ case S_SLIDE: // Y-movement during drag
+ newY = newY + y; // New y-position
+
+ if (newY < slideYmin)
+ gotoY = slideYmin; // Above top limit
+ else if (newY > slideYmax)
+ gotoY = slideYmax; // Below bottom limit
+ else
+ gotoY = newY; // Hunky-Dory
+
+ // Move slider to new position
+ MultiMoveRelXY(SlideObject, 0, gotoY - slideY);
+ slideY = gotoY;
+
+ // Re-draw icons if necessary
+ ati = NearestSlideY(slideY);
+ if (ati != lasti) {
+ InvD[ino].FirstDisp = slideStuff[ati].n;
+ assert(InvD[ino].FirstDisp >= 0); // negative first displayed
+ ItemsChanged = true;
+ lasti = ati;
+ }
+ break;
+
+ case S_END: // End of a drag on the slider
+ // Draw icons from new start icon
+ ati = NearestSlideY(slideY);
+ InvD[ino].FirstDisp = slideStuff[ati].n;
+ ItemsChanged = true;
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Gets called at the start and end of a drag on the slider, and upon
+ * y-movement during such a drag.
+ */
+
+void SlideCSlider(int y, SSFN fn) {
+ static int newY = 0; // FIXME: local static var
+ int gotoY;
+ int fc;
+
+ // Only do this if there's a slider
+ if (!SlideObject)
+ return;
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ newY = slideY;
+ break;
+
+ case S_SLIDE: // Y-movement during drag
+ newY = newY + y; // New y-position
+
+ if (newY < slideYmin)
+ gotoY = slideYmin; // Above top limit
+ else if (newY > slideYmax)
+ gotoY = slideYmax; // Below bottom limit
+ else
+ gotoY = newY; // Hunky-Dory
+
+ slideY = gotoY;
+
+ fc = cd.fileBase;
+ firstFile((slideY-slideYmin)*(MAX_SFILES-NUM_SL_RGROUP)/(slideYmax-slideYmin));
+ if (fc != cd.fileBase) {
+ AddBoxes(false);
+ fc -= cd.fileBase;
+ cd.selBox += fc;
+ if (cd.selBox < 0)
+ cd.selBox = 0;
+ else if (cd.selBox >= NUM_SL_RGROUP)
+ cd.selBox = NUM_SL_RGROUP-1;
+ Select(cd.selBox, true);
+ }
+ break;
+
+ case S_END: // End of a drag on the slider
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * Gets called at the start and end of a drag on a mixing desk slider,
+ * and upon x-movement during such a drag.
+ */
+
+static void SlideMSlider(int x, SSFN fn) {
+ static int newX = 0; // FIXME: local static var
+ int gotoX;
+ int index, i;
+
+ if (fn == S_END || fn == S_TIMEUP || fn == S_TIMEDN)
+ ;
+ else if (!(cd.selBox & IS_SLIDER))
+ return;
+
+ // Work out the indices
+ index = cd.selBox & ~IS_MASK;
+ for (i = 0; i < numMdSlides; i++)
+ if (mdSlides[i].num == index)
+ break;
+ assert(i < numMdSlides);
+
+ switch (fn) {
+ case S_START: // Start of a drag on the slider
+ // can use index as a throw-away value
+ GetAniPosition(mdSlides[i].obj, &newX, &index);
+ lX = sX = newX;
+ break;
+
+ case S_SLIDE: // X-movement during drag
+ if (x == 0)
+ return;
+
+ newX = newX + x; // New x-position
+
+ if (newX < mdSlides[i].min)
+ gotoX = mdSlides[i].min; // Below bottom limit
+ else if (newX > mdSlides[i].max)
+ gotoX = mdSlides[i].max; // Above top limit
+ else
+ gotoX = newX; // Hunky-Dory
+
+ // Move slider to new position
+ MultiMoveRelXY(mdSlides[i].obj, gotoX - sX, 0);
+ sX = gotoX;
+
+ if (lX != sX) {
+ *cd.Box[index].ival = (sX - mdSlides[i].min)*cd.Box[index].w/SLIDE_RANGE;
+ if (cd.Box[index].boxFunc == MIDIVOL)
+ SetMidiVolume(*cd.Box[index].ival);
+#ifdef MAC_OPTIONS
+ if (cd.Box[index].boxFunc == MASTERVOL)
+ SetSystemVolume(*cd.Box[index].ival);
+
+ if (cd.Box[index].boxFunc == SAMPVOL)
+ SetSampleVolume(*cd.Box[index].ival);
+#endif
+ lX = sX;
+ }
+ break;
+
+ case S_TIMEUP:
+ case S_TIMEDN:
+ gotoX = SLIDE_RANGE*(*cd.Box[index].ival)/cd.Box[index].w;
+ MultiSetAniX(mdSlides[i].obj, mdSlides[i].min+gotoX);
+
+ if (cd.Box[index].boxFunc == MIDIVOL)
+ SetMidiVolume(*cd.Box[index].ival);
+#ifdef MAC_OPTIONS
+ if (cd.Box[index].boxFunc == MASTERVOL)
+ SetSystemVolume(*cd.Box[index].ival);
+
+ if (cd.Box[index].boxFunc == SAMPVOL)
+ SetSampleVolume(*cd.Box[index].ival);
+#endif
+ break;
+
+ case S_END: // End of a drag on the slider
+ AddBoxes(false); // Might change position slightly
+#ifndef JAPAN
+ if (ino == INV_CONF && cd.Box == subtitlesBox)
+ Select(language, false);
+#endif
+ break;
+ }
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingTaller(void) {
+ if (SuppV) {
+ Ychange += SuppV;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY += SuppV;
+ SuppV = 0;
+ }
+ while (Ychange > (ITEM_HEIGHT+1) && InvD[ino].NoofVicons < InvD[ino].MaxVicons) {
+ Ychange -= (ITEM_HEIGHT+1);
+ InvD[ino].NoofVicons++;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY -= (ITEM_HEIGHT+1);
+ }
+ if (InvD[ino].NoofVicons < InvD[ino].MaxVicons) {
+ SuppV = Ychange;
+ Ychange = 0;
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY -= SuppV;
+ }
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingShorter(void) {
+ int StartNvi = InvD[ino].NoofVicons;
+ int StartUv = SuppV;
+
+ if (SuppV) {
+ Ychange += (SuppV - (ITEM_HEIGHT+1));
+ InvD[ino].NoofVicons++;
+ SuppV = 0;
+ }
+ while (Ychange < -(ITEM_HEIGHT+1) && InvD[ino].NoofVicons > InvD[ino].MinVicons) {
+ Ychange += (ITEM_HEIGHT+1);
+ InvD[ino].NoofVicons--;
+ }
+ if (InvD[ino].NoofVicons > InvD[ino].MinVicons && Ychange) {
+ SuppV = (ITEM_HEIGHT+1) + Ychange;
+ InvD[ino].NoofVicons--;
+ Ychange = 0;
+ }
+ if (Ycompensate == 'T')
+ InvD[ino].inventoryY += (ITEM_HEIGHT+1)*(StartNvi - InvD[ino].NoofVicons) - (SuppV - StartUv);
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingWider(void) {
+ int StartNhi = InvD[ino].NoofHicons;
+ int StartUh = SuppH;
+
+ if (SuppH) {
+ Xchange += SuppH;
+ SuppH = 0;
+ }
+ while (Xchange > (ITEM_WIDTH+1) && InvD[ino].NoofHicons < InvD[ino].MaxHicons) {
+ Xchange -= (ITEM_WIDTH+1);
+ InvD[ino].NoofHicons++;
+ }
+ if (InvD[ino].NoofHicons < InvD[ino].MaxHicons) {
+ SuppH = Xchange;
+ Xchange = 0;
+ }
+ if (Xcompensate == 'L')
+ InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh);
+}
+
+/**
+ * Called from ChangeingSize() during re-sizing.
+ */
+
+void GettingNarrower(void) {
+ int StartNhi = InvD[ino].NoofHicons;
+ int StartUh = SuppH;
+
+ if (SuppH) {
+ Xchange += (SuppH - (ITEM_WIDTH+1));
+ InvD[ino].NoofHicons++;
+ SuppH = 0;
+ }
+ while (Xchange < -(ITEM_WIDTH+1) && InvD[ino].NoofHicons > InvD[ino].MinHicons) {
+ Xchange += (ITEM_WIDTH+1);
+ InvD[ino].NoofHicons--;
+ }
+ if (InvD[ino].NoofHicons > InvD[ino].MinHicons && Xchange) {
+ SuppH = (ITEM_WIDTH+1) + Xchange;
+ InvD[ino].NoofHicons--;
+ Xchange = 0;
+ }
+ if (Xcompensate == 'L')
+ InvD[ino].inventoryX += (ITEM_WIDTH+1)*(StartNhi - InvD[ino].NoofHicons) - (SuppH - StartUh);
+}
+
+
+/**
+ * Called from Xmovement()/Ymovement() during re-sizing.
+ */
+
+void ChangeingSize(void) {
+ /* Make it taller or shorter if necessary. */
+ if (Ychange > 0)
+ GettingTaller();
+ else if (Ychange < 0)
+ GettingShorter();
+
+ /* Make it wider or narrower if necessary. */
+ if (Xchange > 0)
+ GettingWider();
+ else if (Xchange < 0)
+ GettingNarrower();
+
+ ConstructInventory(EMPTY);
+}
+
+/**
+ * Called from cursor module when cursor moves while inventory is up.
+ */
+
+void Xmovement(int x) {
+ int aniX, aniY;
+ int i;
+
+ if (x && objArray[0] != NULL) {
+ switch (InvDragging) {
+ case ID_MOVE:
+ GetAniPosition(objArray[0], &InvD[ino].inventoryX, &aniY);
+ InvD[ino].inventoryX +=x;
+ MultiSetAniX(objArray[0], InvD[ino].inventoryX);
+ for (i = 1; objArray[i] && i < MAX_WCOMP; i++)
+ MultiMoveRelXY(objArray[i], x, 0);
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++)
+ MultiMoveRelXY(iconArray[i], x, 0);
+ break;
+
+ case ID_LEFT:
+ case ID_TLEFT:
+ case ID_BLEFT:
+ Xchange -= x;
+ ChangeingSize();
+ break;
+
+ case ID_RIGHT:
+ case ID_TRIGHT:
+ case ID_BRIGHT:
+ Xchange += x;
+ ChangeingSize();
+ break;
+
+ case ID_NONE:
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_AREA, aniX, aniY);
+ break;
+
+ case ID_MDCONT:
+ SlideMSlider(x, S_SLIDE);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * Called from cursor module when cursor moves while inventory is up.
+ */
+
+void Ymovement(int y) {
+ int aniX, aniY;
+ int i;
+
+ if (y && objArray[0] != NULL) {
+ switch (InvDragging) {
+ case ID_MOVE:
+ GetAniPosition(objArray[0], &aniX, &InvD[ino].inventoryY);
+ InvD[ino].inventoryY +=y;
+ MultiSetAniY(objArray[0], InvD[ino].inventoryY);
+ for (i = 1; objArray[i] && i < MAX_WCOMP; i++)
+ MultiMoveRelXY(objArray[i], 0, y);
+ for (i = 0; iconArray[i] && i < MAX_ICONS; i++)
+ MultiMoveRelXY(iconArray[i], 0, y);
+ break;
+
+ case ID_SLIDE:
+ SlideSlider(y, S_SLIDE);
+ break;
+
+ case ID_CSLIDE:
+ SlideCSlider(y, S_SLIDE);
+ break;
+
+ case ID_BOTTOM:
+ case ID_BLEFT:
+ case ID_BRIGHT:
+ Ychange += y;
+ ChangeingSize();
+ break;
+
+ case ID_TOP:
+ case ID_TLEFT:
+ case ID_TRIGHT:
+ Ychange -= y;
+ ChangeingSize();
+ break;
+
+ case ID_NONE:
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_AREA, aniX, aniY);
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+/**
+ * Called when a drag is commencing.
+ */
+
+void InvDragStart(void) {
+ int curX, curY; // cursor's animation position
+
+ GetCursorXY(&curX, &curY, false);
+
+/*
+* Do something different for Save/Restore screens
+*/
+ if (ino == INV_CONF) {
+ int whichbox;
+
+ whichbox = WhichInvBox(curX, curY, true);
+
+ if (whichbox == IB_SLIDE) {
+ InvDragging = ID_CSLIDE;
+ SlideCSlider(0, S_START);
+ } else if (whichbox > 0 && (whichbox & IS_MASK)) {
+ InvDragging = ID_MDCONT; // Mixing desk control
+ cd.selBox = whichbox;
+ SlideMSlider(0, S_START);
+ }
+ return;
+ }
+
+/*
+* Normal operation
+*/
+ switch (InvArea(curX, curY)) {
+ case I_MOVE:
+ if (InvD[ino].moveable) {
+ InvDragging = ID_MOVE;
+ }
+ break;
+
+ case I_SLIDE:
+ InvDragging = ID_SLIDE;
+ SlideSlider(0, S_START);
+ break;
+
+ case I_BOTTOM:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ InvDragging = ID_BOTTOM;
+ Ycompensate = 'B';
+ }
+ break;
+
+ case I_TOP:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ InvDragging = ID_TOP;
+ Ycompensate = 'T';
+ }
+ break;
+
+ case I_LEFT:
+ if (InvD[ino].resizable) {
+ Xchange = 0;
+ InvDragging = ID_LEFT;
+ Xcompensate = 'L';
+ }
+ break;
+
+ case I_RIGHT:
+ if (InvD[ino].resizable) {
+ Xchange = 0;
+ InvDragging = ID_RIGHT;
+ Xcompensate = 'R';
+ }
+ break;
+
+ case I_TLEFT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'T';
+ Xchange = 0;
+ Xcompensate = 'L';
+ InvDragging = ID_TLEFT;
+ }
+ break;
+
+ case I_TRIGHT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'T';
+ Xchange = 0;
+ Xcompensate = 'R';
+ InvDragging = ID_TRIGHT;
+ }
+ break;
+
+ case I_BLEFT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'B';
+ Xchange = 0;
+ Xcompensate = 'L';
+ InvDragging = ID_BLEFT;
+ }
+ break;
+
+ case I_BRIGHT:
+ if (InvD[ino].resizable) {
+ Ychange = 0;
+ Ycompensate = 'B';
+ Xchange = 0;
+ Xcompensate = 'R';
+ InvDragging = ID_BRIGHT;
+ }
+ break;
+ }
+}
+
+/**
+ * Called when a drag is over.
+ */
+
+void InvDragEnd(void) {
+ int curX, curY; // cursor's animation position
+
+ GetCursorXY(&curX, &curY, false);
+
+ if (InvDragging != ID_NONE) {
+ if (InvDragging == ID_SLIDE) {
+ SlideSlider(0, S_END);
+ } else if (InvDragging == ID_CSLIDE) {
+ ; // No action
+ } else if (InvDragging == ID_MDCONT) {
+ SlideMSlider(0, S_END);
+ } else if (InvDragging == ID_MOVE) {
+ ; // No action
+ } else {
+ // Were re-sizing. Redraw the whole thing.
+ DumpDobjArray();
+ DumpObjArray();
+ ConstructInventory(FULL);
+
+ // If this was the maximised, it no longer is!
+ if (InventoryMaximised) {
+ InventoryMaximised = false;
+ InvD[ino].otherX = InvD[ino].inventoryX;
+ InvD[ino].otherY = InvD[ino].inventoryY;
+ }
+ }
+ InvDragging = ID_NONE;
+ }
+
+ // Cursor could well now be inappropriate
+ InvCursor(IC_AREA, curX, curY);
+
+ Xchange = Ychange = 0; // Probably no need, but does no harm!
+}
+
+
+/**************************************************************************/
+/************** Incoming events - further processing **********************/
+/**************************************************************************/
+
+/**
+ * ConfAction
+ */
+void ConfAction(int i, bool dbl) {
+
+ if (i >= 0) {
+ switch (cd.Box[i].boxType) {
+ case FLIP:
+ if (dbl) {
+ *(cd.Box[i].ival) ^= 1; // XOR with true
+ AddBoxes(false);
+ }
+ break;
+
+ case TOGGLE:
+ if (!g_buttonEffect.bButAnim) {
+ g_buttonEffect.bButAnim = true;
+ g_buttonEffect.box = &cd.Box[i];
+ g_buttonEffect.press = false;
+ }
+ break;
+
+ case RGROUP:
+ if (dbl) {
+ // Already highlighted
+ switch (cd.Box[i].boxFunc) {
+ case SAVEGAME:
+ KillInventory();
+ InvSaveGame();
+ break;
+ case LOADGAME:
+ KillInventory();
+ InvLoadGame();
+ break;
+ default:
+ break;
+ }
+ } else {
+ Select(i, false);
+ }
+ break;
+
+#if defined(USE_3FLAGS) || defined(USE_4FLAGS) || defined(USE_5FLAGS)
+ case FRGROUP:
+ if (dbl) {
+ Select(i, false);
+ LanguageChange();
+ } else {
+ Select(i, false);
+ }
+ break;
+#endif
+
+ case AAGBUT:
+ case ARSGBUT:
+ case ARSBUT:
+ case AABUT:
+ case AATBUT:
+ if (g_buttonEffect.bButAnim)
+ break;
+
+ g_buttonEffect.bButAnim = true;
+ g_buttonEffect.box = &cd.Box[i];
+ g_buttonEffect.press = true;
+ break;
+ default:
+ break;
+ }
+ } else {
+ ConfActionSpecial(i);
+ }
+}
+
+static void ConfActionSpecial(int i) {
+ switch (i) {
+ case IB_NONE:
+ break;
+ case IB_UP: // Scroll up
+ if (cd.fileBase > 0) {
+ firstFile(cd.fileBase-1);
+ AddBoxes(true);
+ if (cd.selBox < NUM_SL_RGROUP-1)
+ cd.selBox += 1;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_DOWN: // Scroll down
+ if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) {
+ firstFile(cd.fileBase+1);
+ AddBoxes(true);
+ if (cd.selBox)
+ cd.selBox -= 1;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_SLIDE_UP:
+ if (cd.fileBase > 0) {
+ firstFile(cd.fileBase-(NUM_SL_RGROUP-1));
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ }
+ break;
+ case IB_SLIDE_DOWN: // Scroll down
+ if (cd.fileBase < MAX_SFILES-NUM_SL_RGROUP) {
+ firstFile(cd.fileBase+(NUM_SL_RGROUP-1));
+ AddBoxes(true);
+ cd.selBox = NUM_SL_RGROUP-1;
+ Select(cd.selBox, true);
+ }
+ break;
+ }
+}
+// SLIDE_UP and SLIDE_DOWN on d click??????
+
+void InvPutDown(int index) {
+ int aniX, aniY;
+ // index is the drop position
+ int hiIndex; // Current position of held item (if in)
+
+ // Find where the held item is positioned in this inventory (if it is)
+ for (hiIndex = 0; hiIndex < InvD[ino].NoofItems; hiIndex++)
+ if (InvD[ino].ItemOrder[hiIndex] == HeldItem)
+ break;
+
+ // If drop position would leave a gap, move it up
+ if (index >= InvD[ino].NoofItems) {
+ if (hiIndex == InvD[ino].NoofItems) // Not in, add it
+ index = InvD[ino].NoofItems;
+ else
+ index = InvD[ino].NoofItems - 1;
+ }
+
+ if (hiIndex == InvD[ino].NoofItems) { // Not in, add it
+ if (InvD[ino].NoofItems < InvD[ino].MaxInvObj) {
+ InvD[ino].NoofItems++;
+
+ // Don't leave it in the other inventory!
+ if (InventoryPos(HeldItem) != INV_HELDNOTIN)
+ RemFromInventory(ino == INV_1 ? INV_2 : INV_1, HeldItem);
+ } else {
+ // No room at the inn!
+ return;
+ }
+ }
+
+ // Position it in the inventory
+ if (index < hiIndex) {
+ memmove(&InvD[ino].ItemOrder[index + 1], &InvD[ino].ItemOrder[index], (hiIndex-index)*sizeof(int));
+ InvD[ino].ItemOrder[index] = HeldItem;
+ } else if (index > hiIndex) {
+ memmove(&InvD[ino].ItemOrder[hiIndex], &InvD[ino].ItemOrder[hiIndex+1], (index-hiIndex)*sizeof(int));
+ InvD[ino].ItemOrder[index] = HeldItem;
+ } else {
+ InvD[ino].ItemOrder[index] = HeldItem;
+ }
+
+ HeldItem = INV_NOICON;
+ ItemsChanged = true;
+ DelAuxCursor();
+ RestoreMainCursor();
+ GetCursorXY(&aniX, &aniY, false);
+ InvCursor(IC_DROP, aniX, aniY);
+}
+
+void InvPdProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ GetToken(TOKEN_LEFT_BUT);
+ CORO_SLEEP(dclickSpeed+1);
+ FreeToken(TOKEN_LEFT_BUT);
+
+ // get the stuff copied to process when it was created
+ int *pindex = (int *)param;
+
+ InvPutDown(*pindex);
+
+ CORO_END_CODE;
+}
+
+void InvPickup(int index) {
+ INV_OBJECT *invObj;
+
+ if (index != INV_NOICON) {
+ if (HeldItem == INV_NOICON && InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ // Pick-up
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, WALKTO, INV_PICKUP, index);
+ } else if (HeldItem != INV_NOICON) { // Put icon down
+ // Put-down
+ invObj = findInvObject(HeldItem);
+
+ if (invObj->attribute & IO_DROPCODE && invObj->hScript)
+ RunInvTinselCode(invObj, PUTDOWN, INV_PICKUP, index);
+
+ else if (!(invObj->attribute & IO_ONLYINV1 && ino !=INV_1)
+ && !(invObj->attribute & IO_ONLYINV2 && ino !=INV_2))
+ g_scheduler->createProcess(PID_TCODE, InvPdProcess, &index, sizeof(index));
+ }
+ }
+}
+
+/**
+ * Pick up/put down icon
+ */
+void InvSLClick(void) {
+ int i;
+ int aniX, aniY; // Cursor's animation position
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_NOTIN:
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+ KillInventory();
+ break;
+
+ case I_SLIDE_UP:
+ if (InvD[ino].NoofVicons == 1)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+
+ case I_UP:
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+
+ case I_SLIDE_DOWN:
+ if (InvD[ino].NoofVicons == 1)
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++) {
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ }
+ ItemsChanged = true;
+ break;
+
+ case I_DOWN:
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) {
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ ItemsChanged = true;
+ }
+ break;
+
+ case I_BODY:
+ if (ino == INV_CONF) {
+ if (!InventoryHidden)
+ ConfAction(WhichInvBox(aniX, aniY, false), false);
+ } else {
+ i = InvItem(&aniX, &aniY, false);
+
+ // Special bodge for David, to
+ // cater for drop in dead space between icons
+ if (i == INV_NOICON && HeldItem != INV_NOICON && (ino == INV_1 || ino == INV_2)) {
+ aniX += 1; // 1 to the right
+ i = InvItem(&aniX, &aniY, false);
+ if (i == INV_NOICON) {
+ aniX -= 1; // 1 down
+ aniY += 1;
+ i = InvItem(&aniX, &aniY, false);
+ if (i == INV_NOICON) {
+ aniX += 1; // 1 down-right
+ i = InvItem(&aniX, &aniY, false);
+ }
+ }
+ }
+
+ if (ino == INV_CONV) {
+ ConvAction(i);
+ } else
+ InvPickup(i);
+ }
+ break;
+ }
+}
+
+void InvAction(void) {
+ int index;
+ INV_OBJECT *invObj;
+ int aniX, aniY;
+ int i;
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_BODY:
+ if (ino == INV_CONF) {
+ if (!InventoryHidden)
+ ConfAction(WhichInvBox(aniX, aniY, false), true);
+ } else if (ino == INV_CONV) {
+ index = InvItem(&aniX, &aniY, false);
+ ConvAction(index);
+ } else {
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, ACTION, INV_ACTION, index);
+ }
+ }
+ }
+ break;
+
+ case I_MOVE: // Maximise/unmaximise inventory
+ if (!InvD[ino].resizable)
+ break;
+
+ if (!InventoryMaximised) {
+ InvD[ino].sNoofHicons = InvD[ino].NoofHicons;
+ InvD[ino].sNoofVicons = InvD[ino].NoofVicons;
+ InvD[ino].NoofHicons = InvD[ino].MaxHicons;
+ InvD[ino].NoofVicons = InvD[ino].MaxVicons;
+ InventoryMaximised = true;
+
+ i = InvD[ino].inventoryX;
+ InvD[ino].inventoryX = InvD[ino].otherX;
+ InvD[ino].otherX = i;
+ i = InvD[ino].inventoryY;
+ InvD[ino].inventoryY = InvD[ino].otherY;
+ InvD[ino].otherY = i;
+ } else {
+ InvD[ino].NoofHicons = InvD[ino].sNoofHicons;
+ InvD[ino].NoofVicons = InvD[ino].sNoofVicons;
+ InventoryMaximised = false;
+
+ i = InvD[ino].inventoryX;
+ InvD[ino].inventoryX = InvD[ino].otherX;
+ InvD[ino].otherX = i;
+ i = InvD[ino].inventoryY;
+ InvD[ino].inventoryY = InvD[ino].otherY;
+ InvD[ino].otherY = i;
+ }
+
+ // Delete current, and re-draw
+ DumpDobjArray();
+ DumpObjArray();
+ ConstructInventory(FULL);
+ break;
+
+ case I_UP:
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ break;
+ case I_DOWN:
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems) {
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ ItemsChanged = true;
+ }
+ break;
+ }
+
+}
+
+
+void InvLook(void) {
+ int index;
+ INV_OBJECT *invObj;
+ int aniX, aniY;
+
+ GetCursorXY(&aniX, &aniY, false);
+
+ switch (InvArea(aniX, aniY)) {
+ case I_BODY:
+ index = InvItem(&aniX, &aniY, false);
+ if (index != INV_NOICON) {
+ if (InvD[ino].ItemOrder[index] && InvD[ino].ItemOrder[index] != HeldItem) {
+ invObj = findInvObject(InvD[ino].ItemOrder[index]);
+ if (invObj->hScript)
+ RunInvTinselCode(invObj, LOOK, INV_LOOK, index);
+ }
+ }
+ break;
+
+ case I_NOTIN:
+ if (ino == INV_CONV)
+ ConvAction(INV_CLOSEICON);
+ KillInventory();
+ break;
+ }
+}
+
+
+/**************************************************************************/
+/********************* Incoming events ************************************/
+/**************************************************************************/
+
+
+void ButtonToInventory(BUTEVENT be) {
+ if (InventoryHidden)
+ return;
+
+ switch (be) {
+ case INV_PICKUP: // BE_SLEFT
+ InvSLClick();
+ break;
+
+ case INV_LOOK: // BE_SRIGHT
+ if (IsConfWindow())
+ InvSLClick();
+ else
+ InvLook();
+ break;
+
+ case INV_ACTION: // BE_DLEFT
+ if (InvDragging != ID_MDCONT)
+ InvDragEnd();
+ InvAction();
+ break;
+
+ case BE_LDSTART: // Left drag start
+ InvDragStart();
+ break;
+
+ case BE_LDEND: // Left drag end
+ InvDragEnd();
+ break;
+
+// case BE_DLEFT: // Double click left (also ends left drag)
+// ButtonToInventory(LDEND);
+// break;
+
+ case BE_RDSTART:
+ case BE_RDEND:
+ case BE_UNKNOWN:
+ break;
+ default:
+ break;
+ }
+}
+
+void KeyToInventory(KEYEVENT ke) {
+ int i;
+
+ switch (ke) {
+ case ESC_KEY:
+ if (InventoryState == ACTIVE_INV && ino == INV_CONF && cd.Box != optionBox)
+ bOpenConf = true;
+ CloseInventory();
+ break;
+
+ case PGDN_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ ConfActionSpecial(IB_SLIDE_DOWN);
+ } else {
+ // This code is a copy of SLClick on IB_SLIDE_DOWN
+ // TODO: So share this duplicate code
+ if (InvD[ino].NoofVicons == 1)
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++) {
+ if (InvD[ino].FirstDisp + InvD[ino].NoofHicons*InvD[ino].NoofVicons < InvD[ino].NoofItems)
+ InvD[ino].FirstDisp += InvD[ino].NoofHicons;
+ }
+ ItemsChanged = true;
+ }
+ break;
+
+ case PGUP_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ ConfActionSpecial(IB_SLIDE_UP);
+ } else {
+ // This code is a copy of SLClick on I_SLIDE_UP
+ // TODO: So share this duplicate code
+ if (InvD[ino].NoofVicons == 1)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ for (i = 1; i < InvD[ino].NoofVicons; i++)
+ InvD[ino].FirstDisp -= InvD[ino].NoofHicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ case HOME_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ firstFile(0);
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ } else {
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ case END_KEY:
+ if (ino == INV_CONF) {
+ // Only act if load or save screen
+ if (cd.Box != loadBox && cd.Box != saveBox)
+ break;
+
+ firstFile(MAX_SFILES); // Will get reduced to appropriate value
+ AddBoxes(true);
+ cd.selBox = 0;
+ Select(cd.selBox, true);
+ } else {
+ InvD[ino].FirstDisp = InvD[ino].NoofItems - InvD[ino].NoofHicons*InvD[ino].NoofVicons;
+ if (InvD[ino].FirstDisp < 0)
+ InvD[ino].FirstDisp = 0;
+ ItemsChanged = true;
+ }
+ break;
+
+ default:
+ error("We're at KeyToInventory(), with default");
+ }
+}
+
+/**************************************************************************/
+/************************* Odds and Ends **********************************/
+/**************************************************************************/
+
+/**
+ * Called from Glitter function invdepict()
+ * Changes (permanently) the animation film for that object.
+ */
+
+void invObjectFilm(int object, SCNHANDLE hFilm) {
+ INV_OBJECT *invObj;
+
+ invObj = findInvObject(object);
+ invObj->hFilm = hFilm;
+
+ if (HeldItem != object)
+ ItemsChanged = true;
+}
+
+/**
+ * (Un)serialize the inventory data for save/restore game.
+ */
+
+void syncInvInfo(Serializer &s) {
+ for (int i = 0; i < NUM_INV; i++) {
+ s.syncAsSint32LE(InvD[i].MinHicons);
+ s.syncAsSint32LE(InvD[i].MinVicons);
+ s.syncAsSint32LE(InvD[i].MaxHicons);
+ s.syncAsSint32LE(InvD[i].MaxVicons);
+ s.syncAsSint32LE(InvD[i].NoofHicons);
+ s.syncAsSint32LE(InvD[i].NoofVicons);
+ for (int j = 0; j < MAX_ININV; j++) {
+ s.syncAsSint32LE(InvD[i].ItemOrder[j]);
+ }
+ s.syncAsSint32LE(InvD[i].NoofItems);
+ s.syncAsSint32LE(InvD[i].FirstDisp);
+ s.syncAsSint32LE(InvD[i].inventoryX);
+ s.syncAsSint32LE(InvD[i].inventoryY);
+ s.syncAsSint32LE(InvD[i].otherX);
+ s.syncAsSint32LE(InvD[i].otherY);
+ s.syncAsSint32LE(InvD[i].MaxInvObj);
+ s.syncAsSint32LE(InvD[i].hInvTitle);
+ s.syncAsSint32LE(InvD[i].resizable);
+ s.syncAsSint32LE(InvD[i].moveable);
+ s.syncAsSint32LE(InvD[i].sNoofHicons);
+ s.syncAsSint32LE(InvD[i].sNoofVicons);
+ s.syncAsSint32LE(InvD[i].bMax);
+ }
+}
+
+/**************************************************************************/
+/************************ Initialisation stuff ****************************/
+/**************************************************************************/
+
+/**
+ * Called from PlayGame(), stores handle to inventory objects' data -
+ * its id, animation film and Glitter script.
+ */
+// Note: the SCHANDLE type here has been changed to a void*
+void RegisterIcons(void *cptr, int num) {
+ numObjects = num;
+ pio = (INV_OBJECT *) cptr;
+}
+
+/**
+ * Called from Glitter function 'dec_invw()' - Declare the bits that the
+ * inventory windows are constructed from, and special cursors.
+ */
+
+void setInvWinParts(SCNHANDLE hf) {
+#ifdef DEBUG
+ const FILM *pfilm;
+#endif
+
+ winPartsf = hf;
+
+#ifdef DEBUG
+ pfilm = (const FILM *)LockMem(hf);
+ assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORREELS); // not as many reels as expected
+#endif
+}
+
+/**
+ * Called from Glitter function 'dec_flags()' - Declare the language
+ * flag films
+ */
+
+void setFlagFilms(SCNHANDLE hf) {
+#ifdef DEBUG
+ const FILM *pfilm;
+#endif
+
+ flagFilm = hf;
+
+#ifdef DEBUG
+ pfilm = (const FILM *)LockMem(hf);
+ assert(FROM_LE_32(pfilm->numreels) >= HOPEDFORFREELS); // not as many reels as expected
+#endif
+}
+
+void setConfigStrings(SCNHANDLE *tp) {
+ memcpy(configStrings, tp, sizeof(configStrings));
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv(int num, SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight,
+ int startx, int starty, bool moveable) {
+ if (MaxWidth > MAXHICONS)
+ MaxWidth = MAXHICONS; // Max window width
+ if (MaxHeight > MAXVICONS)
+ MaxHeight = MAXVICONS; // Max window height
+ if (MaxContents > MAX_ININV)
+ MaxContents = MAX_ININV; // Max contents
+
+ if (StartWidth > MaxWidth)
+ StartWidth = MaxWidth;
+ if (StartHeight > MaxHeight)
+ StartHeight = MaxHeight;
+
+ InventoryState = IDLE_INV;
+
+ InvD[num].MaxHicons = MaxWidth;
+ InvD[num].MinHicons = MinWidth;
+ InvD[num].MaxVicons = MaxHeight;
+ InvD[num].MinVicons = MinHeight;
+
+ InvD[num].NoofHicons = StartWidth;
+ InvD[num].NoofVicons = StartHeight;
+
+ memset(InvD[num].ItemOrder, 0, sizeof(InvD[num].ItemOrder));
+ InvD[num].NoofItems = 0;
+
+ InvD[num].FirstDisp = 0;
+
+ InvD[num].inventoryX = startx;
+ InvD[num].inventoryY = starty;
+ InvD[num].otherX = 21;
+ InvD[num].otherY = 15;
+
+ InvD[num].MaxInvObj = MaxContents;
+
+ InvD[num].hInvTitle = text;
+
+ if (MaxWidth != MinWidth && MaxHeight != MinHeight)
+ InvD[num].resizable = true;
+
+ InvD[num].moveable = moveable;
+
+ InvD[num].bMax = false;
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_convw(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_CONV, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 20, 8, true);
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv1(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_1, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 100, 100, true);
+}
+
+/**
+ * Called from Glitter functions: dec_convw()/dec_inv1()/dec_inv2()
+ * - Declare the heading text and dimensions etc.
+ */
+
+void idec_inv2(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv(INV_2, text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight,
+ 100, 100, true);
+}
+
+int InvGetLimit(int invno) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+
+ return InvD[invno].MaxInvObj;
+}
+
+void InvSetLimit(int invno, int MaxContents) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+ assert(MaxContents >= InvD[invno].NoofItems); // can't reduce maximum contents below current contents
+
+ if (MaxContents > MAX_ININV)
+ MaxContents = MAX_ININV; // Max contents
+
+ InvD[invno].MaxInvObj = MaxContents;
+}
+
+void InvSetSize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ assert(invno == INV_1 || invno == INV_2); // only INV_1 and INV_2 supported
+
+ if (StartWidth > MaxWidth)
+ StartWidth = MaxWidth;
+ if (StartHeight > MaxHeight)
+ StartHeight = MaxHeight;
+
+ InvD[invno].MaxHicons = MaxWidth;
+ InvD[invno].MinHicons = MinWidth;
+ InvD[invno].MaxVicons = MaxHeight;
+ InvD[invno].MinVicons = MinHeight;
+
+ InvD[invno].NoofHicons = StartWidth;
+ InvD[invno].NoofVicons = StartHeight;
+
+ if (MaxWidth != MinWidth && MaxHeight != MinHeight)
+ InvD[invno].resizable = true;
+ else
+ InvD[invno].resizable = false;
+
+ InvD[invno].bMax = false;
+}
+
+/**************************************************************************/
+
+bool IsTopWindow(void) {
+ return (InventoryState == BOGUS_INV);
+}
+
+
+bool IsConfWindow(void) {
+ return (InventoryState == ACTIVE_INV && ino == INV_CONF);
+}
+
+
+bool IsConvWindow(void) {
+ return (InventoryState == ACTIVE_INV && ino == INV_CONV);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/inventory.h b/engines/tinsel/inventory.h
new file mode 100644
index 0000000000..d83439c68f
--- /dev/null
+++ b/engines/tinsel/inventory.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Inventory related functions
+ */
+
+#ifndef TINSEL_INVENTORY_H // prevent multiple includes
+#define TINSEL_INVENTORY_H
+
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // for KEYEVENT, BUTEVENT
+
+namespace Tinsel {
+
+class Serializer;
+
+enum {
+ INV_OPEN = -1,
+ INV_CONV = 0,
+ INV_1 = 1,
+ INV_2 = 2,
+ INV_CONF = 3,
+
+ NUM_INV = 4
+};
+
+/** structure of each inventory object */
+struct INV_OBJECT {
+ int32 id; // inventory objects id
+ SCNHANDLE hFilm; // inventory objects animation film
+ SCNHANDLE hScript; // inventory objects event handling script
+ int32 attribute; // inventory object's attribute
+};
+
+void PopUpInventory(int invno);
+
+enum CONFTYPE {
+ SAVE, LOAD, QUIT, OPTION, RESTART, SOUND, CONTROLS, SUBT, TOPWIN
+};
+
+void PopUpConf(CONFTYPE type);
+
+
+void Xmovement(int x);
+void Ymovement(int y);
+
+void ButtonToInventory(BUTEVENT be);
+
+void KeyToInventory(KEYEVENT ke);
+
+
+int WhichItemHeld(void);
+
+void HoldItem(int item);
+void DropItem(int item);
+void AddToInventory(int invno, int icon, bool hold);
+bool RemFromInventory(int invno, int icon);
+
+
+void RegisterIcons(void *cptr, int num);
+
+void idec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+void idec_inv1(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+void idec_inv2(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+
+bool InventoryActive(void);
+
+void AddIconToPermanentDefaultList(int icon);
+
+void convPos(int bpos);
+void ConvPoly(HPOLYGON hp);
+int convIcon(void);
+void CloseDownConv(void);
+void convHide(bool hide);
+bool convHid(void);
+
+enum {
+ INV_NOICON = -1,
+ INV_CLOSEICON = -2,
+ INV_OPENICON = -3,
+ INV_HELDNOTIN = -4
+};
+
+void ConvAction(int index);
+
+void InventoryIconCursor(void);
+
+void setInvWinParts(SCNHANDLE hf);
+void setFlagFilms(SCNHANDLE hf);
+void setConfigStrings(SCNHANDLE *tp);
+
+int InvItem(int *x, int *y, bool update);
+int InvItemId(int x, int y);
+
+int InventoryPos(int num);
+
+bool IsInInventory(int object, int invnum);
+
+void KillInventory(void);
+
+void invObjectFilm(int object, SCNHANDLE hFilm);
+
+void syncInvInfo(Serializer &s);
+
+int InvGetLimit(int invno);
+void InvSetLimit(int invno, int n);
+void InvSetSize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight);
+
+int WhichInventoryOpen(void);
+
+bool IsTopWindow(void);
+bool IsConfWindow(void);
+bool IsConvWindow(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_INVENTRY_H */
diff --git a/engines/tinsel/mareels.cpp b/engines/tinsel/mareels.cpp
new file mode 100644
index 0000000000..4c64eaf091
--- /dev/null
+++ b/engines/tinsel/mareels.cpp
@@ -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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Functions to set up moving actors' reels.
+ */
+
+#include "tinsel/pcode.h" // For D_UP, D_DOWN
+#include "tinsel/rince.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+enum {
+ NUM_INTERVALS = NUM_MAINSCALES - 1,
+
+ // 2 for up and down, 3 allow enough entries for 3 fully subscribed moving actors' worth
+ MAX_SCRENTRIES = NUM_INTERVALS*2*3
+};
+
+struct SCIdataStruct {
+ int actor;
+ int scale;
+ int direction;
+ SCNHANDLE reels[4];
+};
+
+static SCIdataStruct SCIdata[MAX_SCRENTRIES];
+
+static int scrEntries = 0;
+
+/**
+ * Return handle to actor's talk reel at present scale and direction.
+ */
+SCNHANDLE GetMactorTalkReel(PMACTOR pActor, TFTYPE dirn) {
+ assert(1 <= pActor->scale && pActor->scale <= TOTAL_SCALES);
+ switch (dirn) {
+ case TF_NONE:
+ return pActor->TalkReels[pActor->scale-1][pActor->dirn];
+
+ case TF_UP:
+ return pActor->TalkReels[pActor->scale-1][AWAY];
+
+ case TF_DOWN:
+ return pActor->TalkReels[pActor->scale-1][FORWARD];
+
+ case TF_LEFT:
+ return pActor->TalkReels[pActor->scale-1][LEFTREEL];
+
+ case TF_RIGHT:
+ return pActor->TalkReels[pActor->scale-1][RIGHTREEL];
+
+ default:
+ error("GetMactorTalkReel() - illegal direction!");
+ }
+}
+
+/**
+ * scalingreels
+ */
+void setscalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) {
+ assert(scale >= 1 && scale <= NUM_MAINSCALES); // invalid scale
+ assert(!(scale == 1 && direction == D_UP) &&
+ !(scale == NUM_MAINSCALES && direction == D_DOWN)); // illegal direction from scale
+
+ assert(scrEntries < MAX_SCRENTRIES); // Scaling reels limit reached!
+
+ SCIdata[scrEntries].actor = actor;
+ SCIdata[scrEntries].scale = scale;
+ SCIdata[scrEntries].direction = direction;
+ SCIdata[scrEntries].reels[LEFTREEL] = left;
+ SCIdata[scrEntries].reels[RIGHTREEL] = right;
+ SCIdata[scrEntries].reels[FORWARD] = forward;
+ SCIdata[scrEntries].reels[AWAY] = away;
+ scrEntries++;
+}
+
+/**
+ * ScalingReel
+ */
+SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel) {
+ int d; // Direction
+
+ // The smaller the number, the bigger the scale
+ if (scale1 < scale2)
+ d = D_DOWN;
+ else
+ d = D_UP;
+
+ for (int i = 0; i < scrEntries; i++) {
+ if (SCIdata[i].actor == ano && SCIdata[i].scale == scale1 && SCIdata[i].direction == d) {
+ if (SCIdata[i].reels[reel] == TF_NONE)
+ return 0;
+ else
+ return SCIdata[i].reels[reel];
+ }
+ }
+ return 0;
+}
+
+/**
+ * RebootScalingReels
+ */
+void RebootScalingReels(void) {
+ scrEntries = 0;
+ memset(SCIdata, 0, sizeof(SCIdata));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/module.mk b/engines/tinsel/module.mk
new file mode 100644
index 0000000000..b00afcddbc
--- /dev/null
+++ b/engines/tinsel/module.mk
@@ -0,0 +1,52 @@
+MODULE := engines/tinsel
+
+MODULE_OBJS = \
+ actors.o \
+ anim.o \
+ background.o \
+ bg.o \
+ cliprect.o \
+ config.o \
+ cursor.o \
+ debugger.o \
+ detection.o \
+ effect.o \
+ events.o \
+ faders.o \
+ font.o \
+ graphics.o \
+ handle.o \
+ heapmem.o \
+ inventory.o \
+ mareels.o \
+ move.o \
+ multiobj.o \
+ music.o \
+ object.o \
+ palette.o \
+ pcode.o \
+ pdisplay.o \
+ play.o \
+ polygons.o \
+ rince.o \
+ saveload.o \
+ savescn.o \
+ scene.o \
+ sched.o \
+ scn.o \
+ scroll.o \
+ sound.o \
+ strres.o \
+ text.o \
+ timers.o \
+ tinlib.o \
+ tinsel.o \
+ token.o
+
+# This module can be built as a plugin
+ifeq ($(ENABLE_TINSEL), DYNAMIC_PLUGIN)
+PLUGIN := 1
+endif
+
+# Include common rules
+include $(srcdir)/rules.mk
diff --git a/engines/tinsel/move.cpp b/engines/tinsel/move.cpp
new file mode 100644
index 0000000000..803bc5fd7b
--- /dev/null
+++ b/engines/tinsel/move.cpp
@@ -0,0 +1,1618 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles walking and use of the path system.
+ *
+ * Contains the dodgiest code in the whole system.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/scroll.h"
+#include "tinsel/tinlib.h" // For stand()
+
+namespace Tinsel {
+
+//----------------- DEVELOPMENT OPTIONS --------------------
+
+#define SLOW_RINCE_DOWN 0
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+// in POLYGONS.C
+// Deliberatley defined here, and not in polygons.h
+HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta);
+
+//----------------- LOCAL DEFINES --------------------
+
+#define XMDIST 4
+#define XHMDIST 2
+#define YMDIST 2
+#define YHMDIST 2
+
+#define XTHERE 1
+#define XRESTRICT 2
+#define YTHERE 4
+#define YRESTRICT 8
+#define STUCK 16
+
+#define LEAVING_PATH 0x100
+#define ENTERING_BLOCK 0x200
+#define ENTERING_MBLOCK 0x400
+
+#define ALL_SORTED 1
+#define NOT_SORTED 0
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+#if SLOW_RINCE_DOWN
+static int Interlude = 0; // For slowing down walking, for testing
+static int BogusVar = 0; // For slowing down walking, for testing
+#endif
+
+static int32 DefaultRefer = 0;
+static int hSlowVar = 0; // used by MoveActor()
+
+
+//----------------- FORWARD REFERENCES --------------------
+
+static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY,
+ int *newx, int *newy, int *s1, int *s2, HPOLYGON *hS2p,
+ bool bOver, bool bBodge,
+ PMACTOR pActor, PMACTOR *collisionActor = 0);
+
+
+#if SLOW_RINCE_DOWN
+/**
+ * AddInterlude
+ */
+
+void AddInterlude(int n) {
+ Interlude += n;
+ if (Interlude < 0)
+ Interlude = 0;
+}
+#endif
+
+/**
+ * Given (x, y) of a click within a path polygon, checks that the
+ * co-ordinates are not within a blocking polygon. If it is not, the
+ * destination is the click point, otherwise tries to find a legal point
+ * below or above the click point.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnPath(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int Loffset, Toffset;
+ int i;
+
+ /*--------------------------------------
+ Clicked within a path,
+ go to where requested unless blocked.
+ --------------------------------------*/
+ if (InPolygon(clickX, clickY, BLOCKING) == NOPOLY) {
+ // Not in a blocking polygon - go to where requested.
+ *ptgtX = clickX;
+ *ptgtY = clickY;
+ } else {
+ /*------------------------------------------------------
+ In a Blocking polygon - try searching down and up.
+ If still nowhere (for now) give up!
+ ------------------------------------------------------*/
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ for (i = clickY+1; i < SCREEN_HEIGHT + Toffset; i++) {
+ // Don't leave the path system
+ if (InPolygon(clickX, i, PATH) == NOPOLY) {
+ i = SCREEN_HEIGHT;
+ break;
+ }
+ if (InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ }
+ if (i == SCREEN_HEIGHT) {
+ for (i = clickY-1; i >= Toffset; i--) {
+ // Don't leave the path system
+ if (InPolygon(clickX, i, PATH) == NOPOLY) {
+ i = -1;
+ break;
+ }
+ if (InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ }
+ }
+ if (i < 0) {
+ return ALL_SORTED;
+ }
+ }
+ return NOT_SORTED;
+}
+
+/**
+ * Given (x, y) of a click within a referral polygon, works out the
+ * destination according to the referral type.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnRefer(HPOLYGON hRefpoly, int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int i;
+ int end; // Extreme of the scene
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ *ptgtX = *ptgtY = -1;
+
+ switch (PolySubtype(hRefpoly)) {
+ case REF_POINT: // Go to specified node
+ getPolyNode(hRefpoly, ptgtX, ptgtY);
+ assert(InPolygon(*ptgtX, *ptgtY, PATH) != NOPOLY); // POINT Referral to illegal point
+ break;
+
+ case REF_DOWN: // Search downwards
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY
+ && InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ break;
+
+ case REF_UP: // Search upwards
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY
+ && InPolygon(clickX, i, BLOCKING) == NOPOLY) {
+ *ptgtX = clickX;
+ *ptgtY = i;
+ break;
+ }
+ break;
+
+ case REF_RIGHT: // Search to the right
+ end = BackgroundWidth();
+ for (i = clickX+1; i < end; i++)
+ if (InPolygon(i, clickY, PATH) != NOPOLY
+ && InPolygon(i, clickY, BLOCKING) == NOPOLY) {
+ *ptgtX = i;
+ *ptgtY = clickY;
+ break;
+ }
+ break;
+
+ case REF_LEFT: // Search to the left
+ for (i = clickX-1; i >= 0; i--)
+ if (InPolygon(i, clickY, PATH) != NOPOLY
+ && InPolygon(i, clickY, BLOCKING) == NOPOLY) {
+ *ptgtX = i;
+ *ptgtY = clickY;
+ break;
+ }
+ break;
+ }
+ if (*ptgtX != -1 && *ptgtY != -1) {
+ return NOT_SORTED;
+ } else
+ return ALL_SORTED;
+}
+
+/**
+ * Given (x, y) of a click, works out the destination according to the
+ * default referral type.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int ClickedOnNothing(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ int i;
+ int end; // Extreme of the scene
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ switch (DefaultRefer) {
+ case REF_DEFAULT:
+ // Try searching down and up (onscreen).
+ for (i = clickY+1; i < SCREEN_HEIGHT+Toffset; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ for (i = clickY-1; i >= Toffset; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ // Try searching down and up (offscreen).
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_UP:
+ for (i = clickY-1; i >= 0; i--)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_DOWN:
+ end = BackgroundHeight();
+ for (i = clickY+1; i < end; i++)
+ if (InPolygon(clickX, i, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, i, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_LEFT:
+ for (i = clickX-1; i >= 0; i--)
+ if (InPolygon(i, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(i, clickY, ptgtX, ptgtY);
+ }
+ break;
+
+ case REF_RIGHT:
+ end = BackgroundWidth();
+ for (i = clickX + 1; i < end; i++)
+ if (InPolygon(i, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(i, clickY, ptgtX, ptgtY);
+ }
+ break;
+ }
+
+ // Going nowhere!
+ return ALL_SORTED;
+}
+
+/**
+ * Given (x, y) of the click, ascertains whether the click is within a
+ * path, within a referral poly, or niether. The appropriate function
+ * then gets called to give us a revised destination.
+ * Returns:
+ * NOT_SORTED - if a destination is worked out (movement required)
+ * ALL_SORTED - no destination found (so no movement required)
+ */
+static int WorkOutDestination(int clickX, int clickY, int *ptgtX, int *ptgtY) {
+ HPOLYGON hPoly;
+
+ /*--------------------------------------
+ Clicked within a path?
+ if not, within a referral poly?
+ if not, try and sort something out.
+ ---------------------------------------*/
+ if (InPolygon(clickX, clickY, PATH) != NOPOLY) {
+ return ClickedOnPath(clickX, clickY, ptgtX, ptgtY);
+ } else if ((hPoly = InPolygon(clickX, clickY, REFER)) != NOPOLY) {
+ return ClickedOnRefer(hPoly, clickX, clickY, ptgtX, ptgtY);
+ } else {
+ return ClickedOnNothing(clickX, clickY, ptgtX, ptgtY);
+ }
+}
+
+/**
+ * Work out which reel to adopt for a section of movement.
+ */
+static DIRREEL GetDirectionReel(int fromx, int fromy, int tox, int toy, DIRREEL lastreel, HPOLYGON hPath) {
+ int xchange = 0, ychange = 0;
+ enum {X_NONE, X_LEFT, X_RIGHT, X_NO} xdir;
+ enum {Y_NONE, Y_UP, Y_DOWN, Y_NO} ydir;
+
+ DIRREEL reel = lastreel; // Leave alone if can't decide
+
+ /*
+ * Determine size and direction of X movement.
+ * i.e. left, right, none or not allowed.
+ */
+ if (getPolyReelType(hPath) == REEL_VERT)
+ xdir = X_NO;
+ else if (tox == -1)
+ xdir = X_NONE;
+ else {
+ xchange = tox - fromx;
+ if (xchange > 0)
+ xdir = X_RIGHT;
+ else if (xchange < 0) {
+ xchange = -xchange;
+ xdir = X_LEFT;
+ } else
+ xdir = X_NONE;
+ }
+
+ /*
+ * Determine size and direction of Y movement.
+ * i.e. up, down, none or not allowed.
+ */
+ if (getPolyReelType(hPath) == REEL_HORIZ)
+ ydir = Y_NO;
+ else if (toy == -1)
+ ydir = Y_NONE;
+ else {
+ ychange = toy - fromy;
+ if (ychange > 0)
+ ydir = Y_DOWN;
+ else if (ychange < 0) {
+ ychange = -ychange;
+ ydir = Y_UP;
+ } else
+ ydir = Y_NONE;
+ }
+
+ /*
+ * Some adjustment to allow for different x and y pixell sizes.
+ */
+ ychange += ychange; // Double y distance to cover
+
+ /*
+ * Determine which reel to use.
+ */
+ if (xdir == X_NO) {
+ // Forced to be FORWARD or AWAY
+ switch (ydir) {
+ case Y_DOWN:
+ reel = FORWARD;
+ break;
+ case Y_UP:
+ reel = AWAY;
+ break;
+ default:
+ if (reel != AWAY) // No gratuitous turn
+ reel = FORWARD;
+ break;
+ }
+ } else if (ydir == Y_NO) {
+ // Forced to be LEFTREEL or RIGHTREEL
+ switch (xdir) {
+ case X_LEFT:
+ reel = LEFTREEL;
+ break;
+ case X_RIGHT:
+ reel = RIGHTREEL;
+ break;
+ default:
+ if (reel != LEFTREEL) // No gratuitous turn
+ reel = RIGHTREEL;
+ break;
+ }
+ } else if (xdir != X_NONE || ydir != Y_NONE) {
+ if (xdir == X_NONE)
+ reel = (ydir == Y_DOWN) ? FORWARD : AWAY;
+ else if (ydir == Y_NONE)
+ reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL;
+ else {
+ bool DontBother = false;
+
+ if (xchange <= 4 && ychange <= 4) {
+ switch (reel) {
+ case LEFTREEL:
+ if (xdir == X_LEFT)
+ DontBother = true;
+ break;
+ case RIGHTREEL:
+ if (xdir == X_RIGHT)
+ DontBother = true;
+ break;
+ case FORWARD:
+ if (ydir == Y_DOWN)
+ DontBother = true;
+ break;
+ case AWAY:
+ if (ydir == Y_UP)
+ DontBother = true;
+ break;
+ }
+ }
+ if (!DontBother) {
+ if (xchange > ychange)
+ reel = (xdir == X_LEFT) ? LEFTREEL : RIGHTREEL;
+ else
+ reel = (ydir == Y_DOWN) ? FORWARD : AWAY;
+ }
+ }
+ }
+ return reel;
+}
+
+/**
+ * Haven't moved, look towards the cursor.
+ */
+static void GotThereWithoutMoving(PMACTOR pActor) {
+ int curX, curY;
+ DIRREEL reel;
+
+ if (!pActor->TagReelRunning) {
+ GetCursorXYNoWait(&curX, &curY, true);
+
+ reel = GetDirectionReel(pActor->objx, pActor->objy, curX, curY, pActor->dirn, pActor->hCpath);
+
+ if (reel != pActor->dirn)
+ SetMActorWalkReel(pActor, reel, pActor->scale, false);
+ }
+}
+
+/**
+ * Arrived at final destination.
+ */
+static void GotThere(PMACTOR pActor) {
+ pActor->targetX = pActor->targetY = -1; // 4/1/95
+ pActor->ItargetX = pActor->ItargetY = -1;
+ pActor->UtargetX = pActor->UtargetY = -1;
+
+ // Perhaps we have'nt moved.
+ if (pActor->objx == (int)pActor->fromx && pActor->objy == (int)pActor->fromy)
+ GotThereWithoutMoving(pActor);
+
+ ReTagActor(pActor->actorID); // Tag allowed while stationary
+
+ SetMActorStanding(pActor);
+
+ pActor->bMoving = false;
+}
+
+enum cgt { GT_NOTL, GT_NOTB, GT_NOT2, GT_OK, GT_MAY };
+
+/**
+ * Can we get straight there?
+ */
+static cgt CanGetThere(PMACTOR pActor, int tx, int ty) {
+ int s1, s2; // s2 not used here!
+ HPOLYGON hS2p; // nor is s2p!
+ int nextx, nexty;
+
+ int targetX = tx;
+ int targetY = ty; // Ultimate destination
+ int x = pActor->objx;
+ int y = pActor->objy; // Present position
+
+ while (targetX != -1 || targetY != -1) {
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, false, pActor);
+
+ if (s1 == (XTHERE | YTHERE)) {
+ return GT_OK; // Can get there directly.
+ } else if (s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) {
+ return GT_MAY; // Can't get there directly.
+ } else if (s1 & STUCK) {
+ if (s2 == LEAVING_PATH)
+ return GT_NOTL; // Can't get there.
+ else
+ return GT_NOTB; // Can't get there.
+ } else if (x == nextx && y == nexty) {
+ return GT_NOT2; // Can't get there.
+ }
+ x = nextx;
+ y = nexty;
+ }
+ return GT_MAY;
+}
+
+
+/**
+ * Set final destination.
+ */
+static void SetMoverUltDest(PMACTOR pActor, int x, int y) {
+ pActor->UtargetX = x;
+ pActor->UtargetY = y;
+ pActor->hUpath = InPolygon(x, y, PATH);
+
+ assert(pActor->hUpath != NOPOLY || pActor->bIgPath); // Invalid ultimate destination
+}
+
+/**
+ * Set intermediate destination.
+ *
+ * If in final destination path, go straight to target.
+ * If in a neighbouring path to the final destination, if the target path
+ * is a follow nodes path, head for the end node, otherwise head straight
+ * for the target.
+ * Otherwise, head towards the pseudo-centre or end node of the first
+ * en-route path.
+ */
+static void SetMoverIntDest(PMACTOR pActor, int x, int y) {
+ HPOLYGON hIpath, hTpath;
+ int node;
+
+ hTpath = InPolygon(x, y, PATH); // Target path
+#ifdef DEBUG
+ if (!pActor->bIgPath)
+ assert(hTpath != NOPOLY); // SetMoverIntDest() - target not in path
+#endif
+
+ if (pActor->hCpath == hTpath || pActor->bIgPath
+ || IsInPolygon(pActor->objx, pActor->objy, hTpath)) {
+ // In destination path - head straight for the target.
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ pActor->hIpath = hTpath;
+ } else if (IsAdjacentPath(pActor->hCpath, hTpath)) {
+ // In path adjacent to target
+ if (PolySubtype(hTpath) != NODE) {
+ // Target path is normal - head for target.
+ // Added 26/01/95, innroom
+ if (CanGetThere(pActor, x, y) == GT_NOTL) {
+ NearestCorner(&x, &y, pActor->hCpath, hTpath);
+ }
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ } else {
+ // Target path is node - head for end node.
+ node = NearestEndNode(hTpath, pActor->objx, pActor->objy);
+ getNpathNode(hTpath, node, &pActor->ItargetX, &pActor->ItargetY);
+
+ }
+ pActor->hIpath = hTpath;
+ } else {
+ assert(hTpath != NOPOLY); // Error 701
+ hIpath = getPathOnTheWay(pActor->hCpath, hTpath);
+
+ if (hIpath != NOPOLY) {
+ /* Head for an en-route path */
+ if (PolySubtype(hIpath) != NODE) {
+ /* En-route path is normal - head for pseudo centre. */
+ if (CanGetThere(pActor, x, y) == GT_OK) {
+ pActor->ItargetX = x;
+ pActor->ItargetY = y;
+ } else {
+ pActor->ItargetX = PolyCentreX(hIpath);
+ pActor->ItargetY = PolyCentreY(hIpath);
+ }
+ } else {
+ /* En-route path is node - head for end node. */
+ node = NearestEndNode(hIpath, pActor->objx, pActor->objy);
+ getNpathNode(hIpath, node, &pActor->ItargetX, &pActor->ItargetY);
+ }
+ pActor->hIpath = hIpath;
+ }
+ }
+
+ pActor->InDifficulty = NO_PROB;
+}
+
+/**
+ * Set short-term destination and adopt the appropriate reel.
+ */
+static void SetMoverDest(PMACTOR pActor, int x, int y) {
+ int scale;
+ DIRREEL reel;
+
+ // Set the co-ordinates requested.
+ pActor->targetX = x;
+ pActor->targetY = y;
+ pActor->InDifficulty = NO_PROB;
+
+ reel = GetDirectionReel(pActor->objx, pActor->objy, x, y, pActor->dirn, pActor->hCpath);
+ scale = GetScale(pActor->hCpath, pActor->objy);
+ if (scale != pActor->scale || reel != pActor->dirn) {
+ SetMActorWalkReel(pActor, reel, scale, false);
+ }
+}
+
+/**
+ * SetNextDest
+ */
+static void SetNextDest(PMACTOR pActor) {
+ int targetX, targetY; // Ultimate destination
+ int x, y; // Present position
+ int nextx, nexty;
+ int s1, lstatus = 0;
+ int s2;
+ HPOLYGON hS2p;
+ int i;
+ HPOLYGON hNpoly;
+ HPOLYGON hPath;
+ int znode;
+ int nx, ny;
+ int sx, sy;
+ HPOLYGON hEb;
+
+ int ss1, ss2;
+ HPOLYGON shS2p;
+ PMACTOR collisionActor;
+#if 1
+ int sTargetX, sTargetY;
+#endif
+
+ /*
+ * Desired destination (Itarget) is already set
+ */
+ x = pActor->objx; // Current position
+ y = pActor->objy;
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+
+ /*
+ * If we're where we're headed, end it all (the moving).
+ */
+// if (x == targetX && y == targetY)
+ if (ABS(x - targetX) < XMDIST && ABS(y - targetY) < YMDIST) {
+ if (targetX == pActor->UtargetX && targetY == pActor->UtargetY) {
+ // Desired position
+ GotThere(pActor);
+ return;
+ } else {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5001
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ }
+ }
+
+ if (pActor->bNoPath || pActor->bIgPath) {
+ /* Can get there directly. */
+ SetMoverDest(pActor, targetX, targetY);
+ pActor->over = false;
+ return;
+ }
+
+ /*----------------------------------------------------------------------
+ | Some work to do here if we're in a follow nodes polygon - basically
+ | head for the next node.
+ ----------------------------------------------------------------------*/
+ hNpoly = pActor->hFnpath; // The node path we're in (if any)
+ switch (pActor->npstatus) {
+ case NOT_IN:
+ break;
+
+ case ENTERING:
+ znode = NearestEndNode(hNpoly, x, y);
+ /* Hang on, we're probably here already! */
+ if (znode) {
+ pActor->npstatus = GOING_DOWN;
+ pActor->line = znode-1;
+ getNpathNode(hNpoly, znode - 1, &nx, &ny);
+ } else {
+ pActor->npstatus = GOING_UP;
+ pActor->line = znode;
+ getNpathNode(hNpoly, 1, &nx, &ny);
+ }
+ SetMoverDest(pActor, nx, ny);
+
+ // Test for pseudo-one-node npaths
+ if (numNodes(hNpoly) == 2 &&
+ ABS(pActor->objx - pActor->targetX) < XMDIST &&
+ ABS(pActor->objy - pActor->targetY) < YMDIST) {
+ // That's enough, we're leaving
+ pActor->npstatus = LEAVING;
+ } else {
+ // Normal situation
+ pActor->over = true;
+ return;
+ }
+ // Fall through for LEAVING
+
+ case LEAVING:
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5002
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+
+ case GOING_UP:
+ i = pActor->line; // The line we're on
+
+ // Is this the final target line?
+ if (i+1 == pActor->Tline && hNpoly == pActor->hUpath) {
+ // The final leg of the journey
+ pActor->line = i+1;
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->over = false;
+ return;
+ } else {
+ // Go to the next node unless we're at the last one
+ i++; // The node we're at
+ if (++i < numNodes(hNpoly)) {
+ getNpathNode(hNpoly, i, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->line = i-1;
+ if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST &&
+ ABS(pActor->UtargetY - pActor->targetY) < YMDIST)
+ pActor->over = false;
+ else
+ pActor->over = true;
+ return;
+ } else {
+ // Last node - we're off
+ pActor->npstatus = LEAVING;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5003
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+ }
+ }
+
+ case GOING_DOWN:
+ i = pActor->line; // The line we're on and the node we're at
+
+ // Is this the final target line?
+ if (i - 1 == pActor->Tline && hNpoly == pActor->hUpath) {
+ // The final leg of the journey
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->line = i-1;
+ pActor->over = false;
+ return;
+ } else {
+ // Go to the next node unless we're at the last one
+ if (--i >= 0) {
+ getNpathNode(hNpoly, i, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->line--; /* The next node to head for */
+ if (ABS(pActor->UtargetX - pActor->targetX) < XMDIST &&
+ ABS(pActor->UtargetY - pActor->targetY) < YMDIST)
+ pActor->over = false;
+ else
+ pActor->over = true;
+ return;
+ } else {
+ // Last node - we're off
+ pActor->npstatus = LEAVING;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5004
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ targetX = pActor->ItargetX; // Desired position
+ targetY = pActor->ItargetY;
+ break;
+ }
+ }
+ }
+
+
+
+
+ /*------------------------------------------------------
+ | See if it can get there directly. There may be an
+ | intermediate destination to head for.
+ ------------------------------------------------------*/
+
+ while (targetX != -1 || targetY != -1) {
+#if 1
+ // 'push' the target
+ sTargetX = targetX;
+ sTargetY = targetY;
+#endif
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, false, pActor, &collisionActor);
+
+ if (s1 != (XTHERE | YTHERE) && x == nextx && y == nexty) {
+ ss1 = s1;
+ ss2 = s2;
+ shS2p = hS2p;
+#if 1
+ // 'pop' the target
+ targetX = sTargetX;
+ targetY = sTargetY;
+#endif
+ // Note: this aint right - targetX/Y (may) have been
+ // nobbled by that last call to NewCoOrdinates()
+ // Re-instating them (can) leads to oscillation
+ NewCoOrdinates(x, y, &targetX, &targetY, &nextx, &nexty,
+ &s1, &s2, &hS2p, pActor->over, true, pActor, &collisionActor);
+
+ if (x == nextx && y == nexty) {
+ s1 = ss1;
+ s2 = ss2;
+ hS2p = shS2p;
+ }
+ }
+
+ if (s1 == (XTHERE | YTHERE)) {
+ /* Can get there directly. */
+ SetMoverDest(pActor, nextx, nexty);
+ pActor->over = false;
+ break;
+ } else if ((s1 & STUCK) || s1 == (XRESTRICT + YRESTRICT)
+ || s1 == (XTHERE | YRESTRICT) || s1 == (YTHERE | XRESTRICT)) {
+ /*-------------------------------------------------
+ Can't go any further in this direction. |
+ If it's because of a blocking polygon, try to do |
+ something about it. |
+ -------------------------------------------------*/
+ if (s2 & ENTERING_BLOCK) {
+ x = pActor->objx; // Current position
+ y = pActor->objy;
+ // Go to the nearest corner of the blocking polygon concerned
+ BlockingCorner(hS2p, &x, &y, pActor->ItargetX, pActor->ItargetY);
+ SetMoverDest(pActor, x, y);
+ pActor->over = false;
+ } else if (s2 & ENTERING_MBLOCK) {
+ if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) {
+ // The best we're going to achieve
+ SetMoverUltDest(pActor, x, y);
+ SetMoverDest(pActor, x, y);
+ } else {
+ sx = pActor->objx;
+ sy = pActor->objy;
+// pActor->objx = x;
+// pActor->objy = y;
+
+ hEb = InitExtraBlock(pActor, collisionActor);
+ x = pActor->objx;
+ y = pActor->objy;
+ BlockingCorner(hEb, &x, &y, pActor->ItargetX, pActor->ItargetY);
+
+ pActor->objx = sx;
+ pActor->objy = sy;
+ SetMoverDest(pActor, x, y);
+ pActor->over = false;
+ }
+ } else {
+ /*----------------------------------------
+ Currently, this is as far as we can go. |
+ Definitely room for improvement here! |
+ ----------------------------------------*/
+ hPath = InPolygon(pActor->ItargetX, pActor->ItargetY, PATH);
+ if (hPath != pActor->hIpath) {
+ if (IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hIpath))
+ hPath = pActor->hIpath;
+ }
+ assert(hPath == pActor->hIpath);
+
+ if (pActor->InDifficulty == NO_PROB) {
+ x = PolyCentreX(hPath);
+ y = PolyCentreY(hPath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_CENTRE;
+ pActor->over = false;
+ } else if (pActor->InDifficulty == TRY_CENTRE) {
+ NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_CORNER;
+ pActor->over = false;
+ } else if (pActor->InDifficulty == TRY_CORNER) {
+ NearestCorner(&x, &y, pActor->hCpath, pActor->hIpath);
+ SetMoverDest(pActor, x, y);
+ pActor->InDifficulty = TRY_NEXTCORNER;
+ pActor->over = false;
+ }
+ }
+ break;
+ }
+ else if (((lstatus & YRESTRICT) && !(s1 & YRESTRICT))
+ || ((lstatus & XRESTRICT) && !(s1 & XRESTRICT))) {
+ /*-----------------------------------------------
+ A restriction in a direction has been removed. |
+ Use this as an intermediate destination. |
+ -----------------------------------------------*/
+ SetMoverDest(pActor, nextx, nexty);
+ pActor->over = false;
+ break;
+ }
+
+ x = nextx;
+ y = nexty;
+
+ /*-------------------------
+ Change of path polygon? |
+ -------------------------*/
+ hPath = InPolygon(x, y, PATH);
+ if (pActor->hCpath != hPath &&
+ !IsInPolygon(x, y, pActor->hCpath) &&
+ !IsAdjacentPath(pActor->hCpath, pActor->hIpath)) {
+ /*----------------------------------------------------------
+ If just entering a follow nodes polygon, go to first node.|
+ Else if just going to pass through, go to pseudo-centre. |
+ ----------------------------------------------------------*/
+ if (PolySubtype(hPath) == NODE && pActor->hFnpath != hPath && pActor->npstatus != LEAVING) {
+ int node = NearestEndNode(hPath, x, y);
+ getNpathNode(hPath, node, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->over = true;
+ } else if (!IsInPolygon(pActor->ItargetX, pActor->ItargetY, hPath) &&
+ !IsInPolygon(pActor->ItargetX, pActor->ItargetY, pActor->hCpath)) {
+ SetMoverDest(pActor, PolyCentreX(hPath), PolyCentreY(hPath));
+ pActor->over = true;
+ } else {
+ SetMoverDest(pActor, pActor->ItargetX, pActor->ItargetY);
+ }
+ break;
+ }
+
+ lstatus = s1;
+ }
+}
+
+/**
+ * Work out where the next position should be.
+ * Check that it's in a path and not in a blocking polygon.
+ */
+static void NewCoOrdinates(int fromx, int fromy, int *targetX, int *targetY,
+ int *newx, int *newy, int *s1, int *s2,
+ HPOLYGON *hS2p, bool bOver, bool bBodge,
+ PMACTOR pActor, PMACTOR *collisionActor) {
+ HPOLYGON hPoly;
+ int sidem, depthm;
+ int sidesteps, depthsteps;
+ PMACTOR ma;
+
+ *s1 = *s2 = 0;
+
+ /*------------------------------------------------
+ Don't overrun if this is the final destination. |
+ ------------------------------------------------*/
+ if (*targetX == pActor->UtargetX && (*targetY == -1 || *targetY == pActor->UtargetY) ||
+ *targetY == pActor->UtargetY && (*targetX == -1 || *targetX == pActor->UtargetX))
+ bOver = false;
+
+ /*----------------------------------------------------
+ Decide how big a step to attempt in each direction. |
+ ----------------------------------------------------*/
+ sidesteps = *targetX == -1 ? 0 : *targetX - fromx;
+ sidesteps = ABS(sidesteps);
+
+ depthsteps = *targetY == -1 ? 0 : *targetY - fromy;
+ depthsteps = ABS(depthsteps);
+
+ if (sidesteps && depthsteps > sidesteps) {
+ depthm = YMDIST;
+ sidem = depthm * sidesteps/depthsteps;
+
+ if (!sidem)
+ sidem = 1;
+ } else if (depthsteps && sidesteps > depthsteps) {
+ sidem = XMDIST;
+ depthm = sidem * depthsteps/sidesteps;
+
+ if (!depthm) {
+ if (bBodge)
+ depthm = 1;
+ } else if (depthm > YMDIST)
+ depthm = YMDIST;
+ } else {
+ sidem = sidesteps ? XMDIST : 0;
+ depthm = depthsteps ? YMDIST : 0;
+ }
+
+ *newx = fromx;
+ *newy = fromy;
+
+ /*------------------------------------------------------------
+ If Left-Right movement is required - then make the move, |
+ but don't overshoot, and do notice when we're already there |
+ ------------------------------------------------------------*/
+ if (*targetX == -1)
+ *s1 |= XTHERE;
+ else {
+ if (*targetX > fromx) { /* To the right? */
+ *newx += sidem; // Move to the right...
+ if (*newx == *targetX)
+ *s1 |= XTHERE;
+ else if (*newx > *targetX) { // ...but don't overshoot
+ if (!bOver)
+ *newx = *targetX;
+ else
+ *targetX = *newx;
+ *s1 |= XTHERE;
+ }
+ } else if (*targetX < fromx) { /* To the left? */
+ *newx -= sidem; // Move to the left...
+ if (*newx == *targetX)
+ *s1 |= XTHERE;
+ else if (*newx < *targetX) { // ...but don't overshoot
+ if (!bOver)
+ *newx = *targetX;
+ else
+ *targetX = *newx;
+ *s1 |= XTHERE;
+ }
+ } else {
+ *targetX = -1; // We're already there!
+ *s1 |= XTHERE;
+ }
+ }
+
+ /*--------------------------------------------------------------
+ If Up-Down movement is required - then make the move,
+ but don't overshoot, and do notice when we're already there
+ --------------------------------------------------------------*/
+ if (*targetY == -1)
+ *s1 |= YTHERE;
+ else {
+ if (*targetY > fromy) { /* Downwards? */
+ *newy += depthm; // Move down...
+ if (*newy == *targetY) // ...but don't overshoot
+ *s1 |= YTHERE;
+ else if (*newy > *targetY) { // ...but don't overshoot
+ if (!bOver)
+ *newy = *targetY;
+ else
+ *targetY = *newy;
+ *s1 |= YTHERE;
+ }
+ } else if (*targetY < fromy) { /* Upwards? */
+ *newy -= depthm; // Move up...
+ if (*newy == *targetY) // ...but don't overshoot
+ *s1 |= YTHERE;
+ else if (*newy < *targetY) { // ...but don't overshoot
+ if (!bOver)
+ *newy = *targetY;
+ else
+ *targetY = *newy;
+ *s1 |= YTHERE;
+ }
+ } else {
+ *targetY = -1; // We're already there!
+ *s1 |= YTHERE;
+ }
+ }
+
+ /* Give over if this is it */
+ if (*s1 == (XTHERE | YTHERE))
+ return;
+
+ /*------------------------------------------------------
+ Have worked out where an optimum step would take us.
+ Must now check if it's in a legal spot.
+ ------------------------------------------------------*/
+
+ if (!pActor->bNoPath && !pActor->bIgPath) {
+ /*------------------------------
+ Must stay in a path polygon.
+ -------------------------------*/
+ hPoly = InPolygon(*newx, *newy, PATH);
+ if (hPoly == NOPOLY) {
+ *s2 = LEAVING_PATH; // Trying to leave the path polygons
+
+ if (*newx != fromx && InPolygon(*newx, fromy, PATH) != NOPOLY && InPolygon(*newx, fromy, BLOCKING) == NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InPolygon(fromx, *newy, PATH) != NOPOLY && InPolygon(fromx, *newy, BLOCKING) == NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ return;
+ }
+ }
+
+ /*--------------------------------------
+ Must stay out of blocking polygons.
+ --------------------------------------*/
+ hPoly = InPolygon(*newx, *newy, BLOCKING);
+ if (hPoly != NOPOLY) {
+ *s2 = ENTERING_BLOCK; // Trying to enter a blocking poly
+ *hS2p = hPoly;
+
+ if (*newx != fromx && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ }
+ }
+ /*------------------------------------------------------
+ Must stay out of moving actors' blocking polygons.
+ ------------------------------------------------------*/
+ ma = InMActorBlock(pActor, *newx, *newy);
+ if (ma != NULL) {
+ // Ignore if already in it (it may have just appeared)
+ if (!InMActorBlock(pActor, pActor->objx, pActor->objy)) {
+ *s2 = ENTERING_MBLOCK; // Trying to walk through an actor
+
+ *hS2p = -1;
+ if (collisionActor)
+ *collisionActor = ma;
+
+ if (*newx != fromx && InMActorBlock(pActor, *newx, fromy) == NULL
+ && InPolygon(*newx, fromy, BLOCKING) == NOPOLY && InPolygon(*newx, fromy, PATH) != NOPOLY) {
+ *newy = fromy;
+ *s1 |= YRESTRICT;
+ } else if (*newy != fromy && InMActorBlock(pActor, fromx, *newy) == NULL
+ && InPolygon(fromx, *newy, BLOCKING) == NOPOLY && InPolygon(fromx, *newy, PATH) != NOPOLY) {
+ *newx = fromx;
+ *s1 |= XRESTRICT;
+ } else {
+ *newx = fromx;
+ *newy = fromy;
+#if 1
+ *targetX = *targetY = -1;
+#endif
+ *s1 |= STUCK;
+ }
+ }
+ }
+ }
+}
+
+/**
+ * SetOffWithinNodePath
+ */
+static void SetOffWithinNodePath(PMACTOR pActor, HPOLYGON StartPath, HPOLYGON DestPath,
+ int targetX, int targetY) {
+ int endnode;
+ HPOLYGON hIpath;
+ int nx, ny;
+ int x, y;
+
+ if (StartPath == DestPath) {
+ if (pActor->line == pActor->Tline) {
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ pActor->over = false;
+ } else if (pActor->line < pActor->Tline) {
+ getNpathNode(StartPath, pActor->line+1, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_UP;
+ } else if (pActor->line > pActor->Tline) {
+ getNpathNode(StartPath, pActor->line, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_DOWN;
+ }
+ } else {
+ /*
+ * Leaving this path - work out
+ * which end of this path to head for.
+ */
+ assert(DestPath != NOPOLY); // Error 702
+ if ((hIpath = getPathOnTheWay(StartPath, DestPath)) == NOPOLY) {
+ // This should never happen!
+ // It's the old code that didn't always work.
+ endnode = NearestEndNode(StartPath, targetX, targetY);
+ } else {
+ if (PolySubtype(hIpath) != NODE) {
+ x = PolyCentreX(hIpath);
+ y = PolyCentreY(hIpath);
+ endnode = NearestEndNode(StartPath, x, y);
+ } else {
+ endnode = NearEndNode(StartPath, hIpath);
+ }
+ }
+
+#if 1
+ if ((pActor->npstatus == LEAVING) &&
+ endnode == NearestEndNode(StartPath, pActor->objx, pActor->objy)) {
+ // Leave it be
+ } else
+#endif
+ {
+ if (endnode) {
+ getNpathNode(StartPath, pActor->line+1, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_UP;
+ } else {
+ getNpathNode(StartPath, pActor->line, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ pActor->npstatus = GOING_DOWN;
+ }
+ }
+ }
+}
+
+/**
+ * Restore a movement, called from restoreMovement() in ACTORS.CPP
+ */
+void SSetActorDest(PMACTOR pActor) {
+ if (pActor->UtargetX != -1 && pActor->UtargetY != -1) {
+ stand(pActor->actorID, pActor->objx, pActor->objy, 0);
+
+ if (pActor->UtargetX != -1 && pActor->UtargetY != -1) {
+ SetActorDest(pActor, pActor->UtargetX, pActor->UtargetY,
+ pActor->bIgPath, 0);
+ }
+ } else {
+ stand(pActor->actorID, pActor->objx, pActor->objy, 0);
+ }
+}
+
+/**
+ * Initiate a movement, called from WalkTo_Event()
+ */
+void SetActorDest(PMACTOR pActor, int clickX, int clickY, bool igPath, SCNHANDLE film) {
+ HPOLYGON StartPath, DestPath = 0;
+ int targetX, targetY;
+
+ if (pActor->actorID == LeadId()) // Now only for lead actor
+ UnTagActor(pActor->actorID); // Tag not allowed while moving
+ pActor->ticket++;
+ pActor->stop = false;
+ pActor->over = false;
+ pActor->fromx = pActor->objx;
+ pActor->fromy = pActor->objy;
+ pActor->bMoving = true;
+ pActor->bIgPath = igPath;
+
+ // Use the supplied reel or restore the normal actor.
+ if (film != 0)
+ AlterMActor(pActor, film, AR_WALKREEL);
+ else
+ AlterMActor(pActor, 0, AR_NORMAL);
+
+ if (igPath) {
+ targetX = clickX;
+ targetY = clickY;
+ } else {
+ if (WorkOutDestination(clickX, clickY, &targetX, &targetY) == ALL_SORTED) {
+ GotThere(pActor);
+ return;
+ }
+ assert(InPolygon(targetX, targetY, PATH) != NOPOLY); // illegal destination!
+ assert(InPolygon(targetX, targetY, BLOCKING) == NOPOLY); // illegal destination!
+ }
+
+
+ /***** Now have a destination to aim for. *****/
+
+ /*----------------------------------
+ | Don't move if it's not worth it.
+ ----------------------------------*/
+ if (ABS(targetX - pActor->objx) < XMDIST && ABS(targetY - pActor->objy) < YMDIST) {
+ GotThere(pActor);
+ return;
+ }
+
+ /*------------------------------------------------------
+ | If the destiation is within a follow nodes polygon,
+ | set destination as the nearest node.
+ ------------------------------------------------------*/
+ if (!igPath) {
+ DestPath = InPolygon(targetX, targetY, PATH);
+ if (PolySubtype(DestPath) == NODE) {
+ // Find the nearest point on a line, or nearest node
+ FindBestPoint(DestPath, &targetX, &targetY, &pActor->Tline);
+ }
+ }
+
+ assert(pActor->bIgPath || InPolygon(targetX, targetY, PATH) != NOPOLY); // Error 5005
+ SetMoverUltDest(pActor, targetX, targetY);
+ SetMoverIntDest(pActor, targetX, targetY);
+
+ /*-------------------------------------------------------------------
+ | If in a follow nodes path, need to set off in the right direction! |
+ -------------------------------------------------------------------*/
+ if ((StartPath = pActor->hFnpath) != NOPOLY && !igPath) {
+ SetOffWithinNodePath(pActor, StartPath, DestPath, targetX, targetY);
+ } else {
+ // Set off!
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Change scale if appropriate.
+ */
+static void CheckScale(PMACTOR pActor, HPOLYGON hPath, int ypos) {
+ int scale;
+
+ scale = GetScale(hPath, ypos);
+ if (scale != pActor->scale) {
+ SetMActorWalkReel(pActor, pActor->dirn, scale, false);
+ }
+}
+
+/**
+ * Not going anywhere - Kick off again if not at final destination.
+ */
+static void NotMoving(PMACTOR pActor, int x, int y) {
+ pActor->targetX = pActor->targetY = -1;
+
+// if (x == pActor->UtargetX && y == pActor->UtargetY)
+ if (ABS(x - pActor->UtargetX) < XMDIST && ABS(y - pActor->UtargetY) < YMDIST) {
+ GotThere(pActor);
+ return;
+ }
+
+ if (pActor->ItargetX != -1 || pActor->ItargetY != -1) {
+ SetNextDest(pActor);
+ } else if (pActor->UtargetX != -1 || pActor->UtargetY != -1) {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5006
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Does the necessary business when entering a different path polygon.
+ */
+static void EnteringNewPath(PMACTOR pActor, HPOLYGON hPath, int x, int y) {
+ int firstnode; // First node to go to
+ int lastnode; // Last node to go to
+ HPOLYGON hIpath;
+ int nx, ny;
+ int nxl, nyl;
+
+ pActor->hCpath = hPath; // current path
+
+ if (hPath == NOPOLY) {
+ // Not proved this ever happens, but just in case
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ return;
+ }
+
+ // Is new path a node path?
+ if (PolySubtype(hPath) == NODE) {
+ // Node path - usually go to nearest end node
+ firstnode = NearestEndNode(hPath, x, y);
+ lastnode = -1;
+
+ // If this is not the destination path,
+ // find which end nodfe we wish to leave via
+ if (hPath != pActor->hUpath) {
+ if (pActor->bIgPath) {
+ lastnode = NearestEndNode(hPath, pActor->UtargetX, pActor->UtargetY);
+ } else {
+ assert(pActor->hUpath != NOPOLY); // Error 703
+ hIpath = getPathOnTheWay(hPath, pActor->hUpath);
+ assert(hIpath != NOPOLY); // No path on the way
+
+ if (PolySubtype(hIpath) != NODE) {
+ lastnode = NearestEndNode(hPath, PolyCentreX(hIpath), PolyCentreY(hIpath));
+ } else {
+ lastnode = NearEndNode(hPath, hIpath);
+ }
+ }
+ }
+ // Test for pseudo-one-node npaths
+ if (lastnode != -1 && numNodes(hPath) == 2) {
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ getNpathNode(hPath, lastnode, &nxl, &nyl);
+ if (nxl == nx && nyl == ny)
+ firstnode = lastnode;
+ }
+
+ // If leaving by same node as entering, don't bother.
+ if (lastnode == firstnode) {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5007
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ } else {
+ // Head for first node
+ pActor->over = true;
+ pActor->npstatus = ENTERING;
+ pActor->hFnpath = hPath;
+ pActor->line = firstnode ? firstnode - 1 : firstnode;
+ if (pActor->line == pActor->Tline && hPath == pActor->hUpath) {
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5008
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetMoverDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ } else {
+ // This doesn't seem right
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ if (ABS(pActor->objx - nx) < XMDIST
+ && ABS(pActor->objy - ny) < YMDIST) {
+ pActor->npstatus = ENTERING;
+ pActor->hFnpath = hPath;
+ SetNextDest(pActor);
+ } else {
+ getNpathNode(hPath, firstnode, &nx, &ny);
+ SetMoverDest(pActor, nx, ny);
+ }
+ }
+ }
+ return;
+ } else {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ assert(pActor->bIgPath || InPolygon(pActor->UtargetX, pActor->UtargetY, PATH) != NOPOLY); // Error 5009
+// Added 26/01/95
+ if (IsPolyCorner(hPath, pActor->ItargetX, pActor->ItargetY))
+ return;
+
+ SetMoverIntDest(pActor, pActor->UtargetX, pActor->UtargetY);
+ SetNextDest(pActor);
+ }
+}
+
+/**
+ * Move
+ */
+void Move(PMACTOR pActor, int newx, int newy, HPOLYGON hPath) {
+ MultiSetAniXY(pActor->actorObj, newx, newy);
+ MAsetZPos(pActor, newy, getPolyZfactor(hPath));
+ if (StepAnimScript(&pActor->actorAnim) == ScriptFinished) {
+ // The end of a scale-change reel
+ // Revert to normal walking reel
+ pActor->walkReel = false;
+ pActor->scount = 0;
+ SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true);
+ }
+ pActor->objx = newx;
+ pActor->objy = newy;
+
+ // Synchronised walking reels
+ if (++pActor->scount >= 6)
+ pActor->scount = 0;
+}
+
+/**
+ * Called from MActorProcess() on every tick.
+ *
+ * Moves the actor as appropriate.
+ */
+void MoveActor(PMACTOR pActor) {
+ int newx, newy;
+ HPOLYGON hPath;
+ int status, s2; // s2 not used here!
+ HPOLYGON hS2p; // nor is s2p!
+ HPOLYGON hEb;
+ PMACTOR ma;
+ int sTargetX, sTargetY;
+ bool bNewPath = false;
+
+ // Only do anything if the actor needs to move!
+ if (pActor->targetX == -1 && pActor->targetY == -1)
+ return;
+
+ if (pActor->stop) {
+ GotThere(pActor);
+ pActor->stop = false;
+ SetMActorStanding(pActor);
+ return;
+ }
+
+#if SLOW_RINCE_DOWN
+/**/ if (BogusVar++ < Interlude) // Temporary slow-down-the-action code
+/**/ return; //
+/**/ BogusVar = 0; //
+#endif
+
+ // During swalk()s, movement while hidden may be slowed down.
+ if (pActor->aHidden) {
+ if (++hSlowVar < pActor->SlowFactor)
+ return;
+ hSlowVar = 0;
+ }
+
+ // 'push' the target
+ sTargetX = pActor->targetX;
+ sTargetY = pActor->targetY;
+
+ NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY,
+ &newx, &newy, &status, &s2, &hS2p, pActor->over, false, pActor);
+
+ if (newx == pActor->objx && newy == pActor->objy) {
+ // 'pop' the target
+ pActor->targetX = sTargetX;
+ pActor->targetY = sTargetY;
+
+ NewCoOrdinates(pActor->objx, pActor->objy, &pActor->targetX, &pActor->targetY, &newx, &newy,
+ &status, &s2, &hS2p, pActor->over, true, pActor);
+ if (newx == pActor->objx && newy == pActor->objy) {
+ NotMoving(pActor, newx, newy);
+ return;
+ }
+ }
+
+ // Find out which path we're in now
+ hPath = InPolygon(newx, newy, PATH);
+ if (hPath == NOPOLY) {
+ if (pActor->bNoPath) {
+ Move(pActor, newx, newy, pActor->hCpath);
+ return;
+ } else {
+ // May be marginally outside!
+ // OR bIgPath may be set.
+ hPath = pActor->hCpath;
+ }
+ } else if (pActor->bNoPath) {
+ pActor->bNoPath = false;
+ bNewPath = true;
+ } else if (hPath != pActor->hCpath) {
+ if (IsInPolygon(newx, newy, pActor->hCpath))
+ hPath = pActor->hCpath;
+ }
+
+ CheckScale(pActor, hPath, newy);
+
+ /*
+ * Must stay out of moving actors' blocking polygons.
+ */
+ ma = InMActorBlock(pActor, newx, newy);
+ if (ma != NULL) {
+ // Stop if there's no chance of arriving
+ if (InMActorBlock(pActor, pActor->UtargetX, pActor->UtargetY)) {
+ GotThere(pActor);
+ return;
+ }
+
+ if (InMActorBlock(pActor, pActor->objx, pActor->objy))
+ ;
+ else {
+ hEb = InitExtraBlock(pActor, ma);
+ newx = pActor->objx;
+ newy = pActor->objy;
+ BlockingCorner(hEb, &newx, &newy, pActor->ItargetX, pActor->ItargetY);
+ SetMoverDest(pActor, newx, newy);
+ return;
+ }
+ }
+
+ /*--------------------------------------
+ This is where it actually gets moved.
+ --------------------------------------*/
+ Move(pActor, newx, newy, hPath);
+
+ // Entering a new path polygon?
+ if (hPath != pActor->hCpath || bNewPath)
+ EnteringNewPath(pActor, hPath, newx, newy);
+}
+
+/**
+ * Store the default refer type for the current scene.
+ */
+void SetDefaultRefer(int32 defRefer) {
+ DefaultRefer = defRefer;
+}
+
+/**
+ * DoMoveActor
+ */
+void DoMoveActor(PMACTOR pActor) {
+ int wasx, wasy;
+ int i;
+
+#define NUMBER 1
+
+ wasx = pActor->objx;
+ wasy = pActor->objy;
+
+ MoveActor(pActor);
+
+ if ((pActor->targetX != -1 || pActor->targetY != -1)
+ && (wasx == pActor->objx && wasy == pActor->objy)) {
+ for (i=0; i < NUMBER; i++) {
+ MoveActor(pActor);
+ if (wasx != pActor->objx || wasy != pActor->objy)
+ break;
+ }
+// assert(i<NUMBER);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/move.h b/engines/tinsel/move.h
new file mode 100644
index 0000000000..2c5f2cfe73
--- /dev/null
+++ b/engines/tinsel/move.h
@@ -0,0 +1,43 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_MOVE_H // prevent multiple includes
+#define TINSEL_MOVE_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+
+namespace Tinsel {
+
+struct MACTOR;
+
+void SetActorDest(MACTOR *pActor, int x, int y, bool igPath, SCNHANDLE film);
+void SSetActorDest(MACTOR *pActor);
+void DoMoveActor(MACTOR *pActor);
+
+void SetDefaultRefer(int32 defRefer);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_MOVE_H */
diff --git a/engines/tinsel/multiobj.cpp b/engines/tinsel/multiobj.cpp
new file mode 100644
index 0000000000..c60592069f
--- /dev/null
+++ b/engines/tinsel/multiobj.cpp
@@ -0,0 +1,533 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains utilities to handle multi-part objects.
+ */
+
+#include "tinsel/multiobj.h"
+#include "tinsel/handle.h"
+#include "tinsel/object.h"
+
+namespace Tinsel {
+
+// from object.c
+extern OBJECT *objectList;
+
+/**
+ * Initialise a multi-part object using a list of images to init
+ * each object piece. One object is created for each image in the list.
+ * All objects are given the same palette as the first image. A pointer
+ * to the first (master) object created is returned.
+ * @param pInitTbl Pointer to multi-object initialisation table
+ */
+OBJECT *MultiInitObject(const MULTI_INIT *pInitTbl) {
+ OBJ_INIT obj_init; // object init table
+ OBJECT *pFirst, *pObj; // object pointers
+ FRAME *pFrame; // list of images for the multi-part object
+
+ if (pInitTbl->hMulFrame) {
+ // we have a frame handle
+ pFrame = (FRAME *)LockMem(FROM_LE_32(pInitTbl->hMulFrame));
+
+ obj_init.hObjImg = READ_LE_UINT32(pFrame); // first objects shape
+ } else { // this must be a animation list for a NULL object
+ pFrame = NULL;
+ obj_init.hObjImg = 0; // first objects shape
+ }
+
+ // init the object init table
+ obj_init.objFlags = (int)FROM_LE_32(pInitTbl->mulFlags); // all objects have same flags
+ obj_init.objID = (int)FROM_LE_32(pInitTbl->mulID); // all objects have same ID
+ obj_init.objX = (int)FROM_LE_32(pInitTbl->mulX); // all objects have same X ani pos
+ obj_init.objY = (int)FROM_LE_32(pInitTbl->mulY); // all objects have same Y ani pos
+ obj_init.objZ = (int)FROM_LE_32(pInitTbl->mulZ); // all objects have same Z pos
+
+ // create and init the first object
+ pObj = pFirst = InitObject(&obj_init);
+
+ if (pFrame) {
+ // if we have any animation frames
+
+ pFrame++;
+
+ while (READ_LE_UINT32(pFrame) != 0) {
+ // set next objects shape
+ obj_init.hObjImg = READ_LE_UINT32(pFrame);
+
+ // create next object and link to previous
+ pObj = pObj->pSlave = InitObject(&obj_init);
+
+ pFrame++;
+ }
+ }
+
+ // null end of list for final object
+ pObj->pSlave = NULL;
+
+ // return master object
+ return pFirst;
+}
+
+/**
+ * Inserts the multi-part object onto the specified object list.
+ * @param pObjList List to insert multi-part object onto
+* @param pInsObj Head of multi-part object to insert
+
+ */
+
+void MultiInsertObject(OBJECT *pObjList, OBJECT *pInsObj) {
+ // validate object pointer
+ assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // add next part to the specified list
+ InsertObject(pObjList, pInsObj);
+
+ // next obj in list
+ pInsObj = pInsObj->pSlave;
+ } while (pInsObj != NULL);
+}
+
+/**
+ * Deletes all the pieces of a multi-part object from the
+ * specified object list.
+ * @param pObjList List to delete multi-part object from
+ * @param pMultiObj Multi-part object to be deleted
+ */
+
+void MultiDeleteObject(OBJECT *pObjList, OBJECT *pMultiObj) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // delete object
+ DelObject(pObjList, pMultiObj);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+ }
+ while (pMultiObj != NULL);
+}
+
+/**
+ * Hides a multi-part object by giving each object a "NullImage"
+ * image pointer.
+ * @param pMultiObj Multi-part object to be hidden
+ */
+
+void MultiHideObject(OBJECT *pMultiObj) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // set master shape to null animation frame
+ pMultiObj->hShape = 0;
+
+ // change all objects
+ MultiReshape(pMultiObj);
+}
+
+/**
+ * Horizontally flip a multi-part object.
+ * @param pFlipObj Head of multi-part object to flip
+ */
+
+void MultiHorizontalFlip(OBJECT *pFlipObj) {
+ // validate object pointer
+ assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // horizontally flip the next part
+ AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPH,
+ pFlipObj->hImg);
+
+ // next obj in list
+ pFlipObj = pFlipObj->pSlave;
+ } while (pFlipObj != NULL);
+}
+
+/**
+ * Vertically flip a multi-part object.
+ * @param pFlipObj Head of multi-part object to flip
+ */
+
+void MultiVerticalFlip(OBJECT *pFlipObj) {
+ // validate object pointer
+ assert(pFlipObj >= objectList && pFlipObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // vertically flip the next part
+ AnimateObjectFlags(pFlipObj, pFlipObj->flags ^ DMA_FLIPV,
+ pFlipObj->hImg);
+
+ // next obj in list
+ pFlipObj = pFlipObj->pSlave;
+ }
+ while (pFlipObj != NULL);
+}
+
+/**
+ * Adjusts the coordinates of a multi-part object. The adjustments
+ * take into account the orientation of the object.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param deltaX X adjustment
+ * @param deltaY Y adjustment
+ */
+
+void MultiAdjustXY(OBJECT *pMultiObj, int deltaX, int deltaY) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ if (deltaX == 0 && deltaY == 0)
+ return; // ignore no change
+
+ if (pMultiObj->flags & DMA_FLIPH) {
+ // image is flipped horizontally - flip the x direction
+ deltaX = -deltaX;
+ }
+
+ if (pMultiObj->flags & DMA_FLIPV) {
+ // image is flipped vertically - flip the y direction
+ deltaY = -deltaY;
+ }
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // adjust the x position
+ pMultiObj->xPos += intToFrac(deltaX);
+
+ // adjust the y position
+ pMultiObj->yPos += intToFrac(deltaY);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+
+ } while (pMultiObj != NULL);
+}
+
+/**
+ * Moves all the pieces of a multi-part object by the specified
+ * amount. Does not take into account the objects orientation.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param deltaX X movement
+ * @param deltaY Y movement
+ */
+
+void MultiMoveRelXY(OBJECT *pMultiObj, int deltaX, int deltaY) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ if (deltaX == 0 && deltaY == 0)
+ return; // ignore no change
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // adjust the x position
+ pMultiObj->xPos += intToFrac(deltaX);
+
+ // adjust the y position
+ pMultiObj->yPos += intToFrac(deltaY);
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+
+ } while (pMultiObj != NULL);
+}
+
+/**
+ * Sets the x & y anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose position is to be changed
+ * @param newAniX New x animation position
+ * @param newAniY New y animation position
+ */
+
+void MultiSetAniXY(OBJECT *pMultiObj, int newAniX, int newAniY) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc difference between current and new positions
+ newAniX -= curAniX;
+ newAniY -= curAniY;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, newAniX, newAniY);
+}
+
+/**
+ * Sets the x anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose x position is to be changed
+ * @param newAniX New x animation position
+ */
+
+void MultiSetAniX(OBJECT *pMultiObj, int newAniX) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc x difference between current and new positions
+ newAniX -= curAniX;
+ curAniY = 0;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, newAniX, curAniY);
+}
+
+/**
+ * Sets the y anim position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object whose x position is to be changed
+ * @param newAniX New y animation position
+ */
+
+void MultiSetAniY(OBJECT *pMultiObj, int newAniY) {
+ int curAniX, curAniY; // objects current animation position
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get master objects current animation position
+ GetAniPosition(pMultiObj, &curAniX, &curAniY);
+
+ // calc y difference between current and new positions
+ curAniX = 0;
+ newAniY -= curAniY;
+
+ // move all pieces by the difference
+ MultiMoveRelXY(pMultiObj, curAniX, newAniY);
+}
+
+/**
+ * Sets the Z position of all pieces of a multi-part object.
+ * @param pMultiObj Multi-part object to be adjusted
+ * @param newZ New Z order
+ */
+
+void MultiSetZPosition(OBJECT *pMultiObj, int newZ) {
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // for all the objects that make up this multi-part
+ do {
+ // signal a change in the object
+ pMultiObj->flags |= DMA_CHANGED;
+
+ // set the new z position
+ pMultiObj->zPos = newZ;
+
+ // next obj in list
+ pMultiObj = pMultiObj->pSlave;
+ }
+ while (pMultiObj != NULL);
+}
+
+/**
+ * Reshape a multi-part object.
+ * @param pMultiObj Multi-part object to re-shape
+ */
+
+void MultiReshape(OBJECT *pMultiObj) {
+ SCNHANDLE hFrame;
+
+ // validate object pointer
+ assert(pMultiObj >= objectList && pMultiObj <= objectList + NUM_OBJECTS - 1);
+
+ // get objects current anim frame
+ hFrame = pMultiObj->hShape;
+
+ if (hFrame != 0 && hFrame != pMultiObj->hMirror) {
+ // a valid shape frame which is different from previous
+
+ // get pointer to frame
+ const FRAME *pFrame = (const FRAME *)LockMem(hFrame);
+
+ // update previous
+ pMultiObj->hMirror = hFrame;
+
+ while (READ_LE_UINT32(pFrame) != 0 && pMultiObj != NULL) {
+ // a normal image - update the current object with this image
+ AnimateObject(pMultiObj, READ_LE_UINT32(pFrame));
+
+ // move to next image for this frame
+ pFrame++;
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+
+ // null the remaining object parts
+ while (pMultiObj != NULL) {
+ // set a null image for this object part
+ AnimateObject(pMultiObj, 0);
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+ } else if (hFrame == 0) {
+ // update previous
+ pMultiObj->hMirror = hFrame;
+
+ // null all the object parts
+ while (pMultiObj != NULL) {
+ // set a null image for this object part
+ AnimateObject(pMultiObj, 0);
+
+ // move to next part of object
+ pMultiObj = pMultiObj->pSlave;
+ }
+ }
+}
+
+/**
+ * Returns the left-most point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiLeftmost(OBJECT *pMulti) {
+ int left;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init leftmost point to first object
+ left = fracToInt(pMulti->xPos);
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->xPos) < left)
+ // this object is further left
+ left = fracToInt(pMulti->xPos);
+ }
+ }
+
+ // return left-most point
+ return left;
+}
+
+/**
+ * Returns the right-most point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiRightmost(OBJECT *pMulti) {
+ int right;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init right-most point to first object
+ right = fracToInt(pMulti->xPos) + pMulti->width;
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->xPos) + pMulti->width > right)
+ // this object is further right
+ right = fracToInt(pMulti->xPos) + pMulti->width;
+ }
+ }
+
+ // return right-most point
+ return right - 1;
+}
+
+/**
+ * Returns the highest point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiHighest(OBJECT *pMulti) {
+ int highest;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init highest point to first object
+ highest = fracToInt(pMulti->yPos);
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->yPos) < highest)
+ // this object is higher
+ highest = fracToInt(pMulti->yPos);
+ }
+ }
+
+ // return highest point
+ return highest;
+}
+
+/**
+ * Returns the lowest point of a multi-part object.
+ * @param pMulti Multi-part object
+ */
+
+int MultiLowest(OBJECT *pMulti) {
+ int lowest;
+
+ // validate object pointer
+ assert(pMulti >= objectList && pMulti <= objectList + NUM_OBJECTS - 1);
+
+ // init lowest point to first object
+ lowest = fracToInt(pMulti->yPos) + pMulti->height;
+
+ // for all the objects in this multi
+ while ((pMulti = pMulti->pSlave) != NULL) {
+ if (pMulti->hImg != 0) {
+ // non null object part
+
+ if (fracToInt(pMulti->yPos) + pMulti->height > lowest)
+ // this object is lower
+ lowest = fracToInt(pMulti->yPos) + pMulti->height;
+ }
+ }
+
+ // return lowest point
+ return lowest - 1;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/multiobj.h b/engines/tinsel/multiobj.h
new file mode 100644
index 0000000000..6d25600ea2
--- /dev/null
+++ b/engines/tinsel/multiobj.h
@@ -0,0 +1,124 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Multi-part object definitions
+ */
+
+#ifndef TINSEL_MULTIOBJ_H // prevent multiple includes
+#define TINSEL_MULTIOBJ_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+struct OBJECT;
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/**
+ * multi-object initialisation structure (parallels OBJ_INIT struct)
+ */
+struct MULTI_INIT {
+ SCNHANDLE hMulFrame; //!< multi-objects shape - NULL terminated list of IMAGE structures
+ int32 mulFlags; //!< multi-objects flags
+ int32 mulID; //!< multi-objects id
+ int32 mulX; //!< multi-objects initial x ani position
+ int32 mulY; //!< multi-objects initial y ani position
+ int32 mulZ; //!< multi-objects initial z position
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+/*----------------------------------------------------------------------*\
+|* Multi Object Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+OBJECT *MultiInitObject( // Initialise a multi-part object
+ const MULTI_INIT *pInitTbl); // pointer to multi-object initialisation table
+
+void MultiInsertObject( // Insert a multi-part object onto a object list
+ OBJECT *pObjList, // list to insert multi-part object onto
+ OBJECT *pInsObj); // head of multi-part object to insert
+
+void MultiDeleteObject( // Delete all the pieces of a multi-part object
+ OBJECT *pObjList, // list to delete multi-part object from
+ OBJECT *pMultiObj); // multi-part object to be deleted
+
+void MultiHideObject( // Hide a multi-part object
+ OBJECT *pMultiObj); // multi-part object to be hidden
+
+void MultiHorizontalFlip( // Hortizontally flip a multi-part object
+ OBJECT *pFlipObj); // head of multi-part object to flip
+
+void MultiVerticalFlip( // Vertically flip a multi-part object
+ OBJECT *pFlipObj); // head of multi-part object to flip
+
+void MultiAdjustXY( // Adjust coords of a multi-part object. Takes into account the orientation
+ OBJECT *pMultiObj, // multi-part object to be adjusted
+ int deltaX, // x adjustment
+ int deltaY); // y adjustment
+
+void MultiMoveRelXY( // Move multi-part object relative. Does not take into account the orientation
+ OBJECT *pMultiObj, // multi-part object to be moved
+ int deltaX, // x movement
+ int deltaY); // y movement
+
+void MultiSetAniXY( // Set the x & y anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose position is to be changed
+ int newAniX, // new x animation position
+ int newAniY); // new y animation position
+
+void MultiSetAniX( // Set the x anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose x position is to be changed
+ int newAniX); // new x animation position
+
+void MultiSetAniY( // Set the y anim position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object whose y position is to be adjusted
+ int newAniY); // new y animation position
+
+void MultiSetZPosition( // Sets the z position of a multi-part object
+ OBJECT *pMultiObj, // multi-part object to be adjusted
+ int newZ); // new Z order
+
+void MultiMatchAniPoints( // Matches a multi-parts pos and orientation to be the same as a reference object
+ OBJECT *pMoveObj, // multi-part object to be moved
+ OBJECT *pRefObj); // multi-part object to match with
+
+void MultiReshape( // Reshape a multi-part object
+ OBJECT *pMultiObj); // multi-part object to re-shape
+
+int MultiLeftmost( // Returns the left-most point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiRightmost( // Returns the right-most point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiHighest( // Returns the highest point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+int MultiLowest( // Returns the lowest point of a multi-part object
+ OBJECT *pMulti); // multi-part object
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_MULTIOBJ_H
diff --git a/engines/tinsel/music.cpp b/engines/tinsel/music.cpp
new file mode 100644
index 0000000000..7d4efd8079
--- /dev/null
+++ b/engines/tinsel/music.cpp
@@ -0,0 +1,551 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// FIXME: This code is taken from MADE and may need more work (e.g. setVolume).
+
+// MIDI and digital music class
+
+#include "sound/audiostream.h"
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "sound/audiocd.h"
+#include "common/config-manager.h"
+#include "common/file.h"
+
+#include "tinsel/config.h"
+#include "tinsel/sound.h"
+#include "tinsel/music.h"
+
+namespace Tinsel {
+
+//--------------------------- Midi data -------------------------------------
+
+// sound buffer structure used for MIDI data and samples
+struct SOUND_BUFFER {
+ uint8 *pDat; // pointer to actual buffer
+ uint32 size; // size of the buffer
+};
+
+// get set when music driver is installed
+//static MDI_DRIVER *mDriver;
+//static HSEQUENCE mSeqHandle;
+
+// if non-zero this is the index position of the next MIDI sequence to play
+static uint32 dwMidiIndex = 0;
+
+// MIDI buffer
+static SOUND_BUFFER midiBuffer = { 0, 0 };
+
+static SCNHANDLE currentMidi = 0;
+static bool currentLoop = false;
+
+const SCNHANDLE midiOffsetsGRAVersion[] = {
+ 4, 4534, 14298, 18828, 23358, 38888, 54418, 57172, 59926, 62450,
+ 62952, 67482, 72258, 74538, 79314, 87722, 103252, 115176, 127100, 127898,
+ 130256, 132614, 134972, 137330, 139688, 150196, 152554, 154912, 167422, 174762,
+ 182102, 194612, 198880, 199536, 206128, 206380, 216372, 226364, 235676, 244988,
+ 249098, 249606, 251160, 252714, 263116, 268706, 274296, 283562, 297986, 304566,
+ 312028, 313524, 319192, 324860, 331772, 336548, 336838, 339950, 343062, 346174,
+ 349286, 356246, 359358, 360434, 361510, 369966, 374366, 382822, 384202, 394946,
+ 396022, 396730, 399524, 401020, 403814, 418364, 419466, 420568, 425132, 433540,
+ 434384, 441504, 452132, 462760, 472804, 486772, 491302, 497722, 501260, 507680,
+ 509726, 521858, 524136, 525452, 533480, 538236, 549018, 559870, 564626, 565306,
+ 566734, 567616, 570144, 574102, 574900, 582518, 586350, 600736, 604734, 613812,
+ 616566, 619626, 623460, 627294, 631128, 634188, 648738, 663288, 667864, 681832,
+ 682048, 683014, 688908, 689124, 698888, 708652, 718416, 728180, 737944, 747708,
+ 752238, 765522, 766554, 772944, 774546, 776148, 776994, 781698, 786262, 789016,
+ 794630, 796422, 798998
+};
+
+const SCNHANDLE midiOffsetsSCNVersion[] = {
+ 4, 4504, 11762, 21532, 26070, 28754, 33254, 40512, 56310, 72108,
+ 74864, 77620, 80152, 80662, 85200, 89982, 92268, 97050, 105466, 121264,
+ 133194, 145124, 145928, 148294, 150660, 153026, 155392, 157758, 168272, 170638,
+ 173004, 185522, 192866, 200210, 212728, 217000, 217662, 224254, 224756, 234754,
+ 244752, 245256, 245950, 255256, 264562, 268678, 269192, 270752, 272312, 282712,
+ 288312, 293912, 303186, 317624, 324210, 331680, 333208, 338884, 344560, 351478,
+ 356262, 356552, 359670, 362788, 365906, 369024, 376014, 379132, 380214, 381296,
+ 389758, 394164, 402626, 404012, 414762, 415844, 416552, 419352, 420880, 423680,
+ 438236, 439338, 440440, 445010, 453426, 454276, 461398, 472032, 482666, 492716,
+ 506690, 511226, 517654, 521198, 527626, 529676, 541814, 546210, 547532, 555562,
+ 560316, 571104, 581962, 586716, 587402, 588836, 589718, 592246, 596212, 597016,
+ 604636, 608474, 622862, 626860, 635944, 638700, 641456, 645298, 649140, 652982,
+ 655738, 670294, 684850, 689432, 703628, 703850, 704816, 706350, 706572, 716342,
+ 726112, 735882, 745652, 755422, 765192, 774962, 784732, 794502, 804272, 814042,
+ 823812, 832996, 846286, 847324, 853714, 855324, 856934, 857786, 862496, 867066,
+ 869822, 875436, 877234, 879818
+};
+
+// TODO: finish this (currently unmapped tracks are 0)
+const int enhancedAudioSCNVersion[] = {
+ 0, 0, 2, 0, 0, 0, 0, 3, 3, 4,
+ 4, 0, 0, 0, 0, 0, 0, 10, 3, 11,
+ 11, 0, 13, 13, 13, 13, 13, 0, 13, 13,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 24, 0, 0, 27, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 55, 56, 56, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 4, 4, 83, 83, 83, 4,
+ 0, 0, 0, 0, 0, 0, 0, 0, 2, 2,
+ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
+ 0, 0, 0, 0, 0, 0, 0, 0, 52, 4,
+ 0, 0, 0, 0
+};
+
+int GetTrackNumber(SCNHANDLE hMidi) {
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ for (int i = 0; i < ARRAYSIZE(midiOffsetsSCNVersion); i++) {
+ if (midiOffsetsSCNVersion[i] == hMidi)
+ return i;
+ }
+ } else {
+ for (int i = 0; i < ARRAYSIZE(midiOffsetsGRAVersion); i++) {
+ if (midiOffsetsGRAVersion[i] == hMidi)
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+SCNHANDLE GetTrackOffset(int trackNumber) {
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ assert(trackNumber < ARRAYSIZE(midiOffsetsSCNVersion));
+ return midiOffsetsSCNVersion[trackNumber];
+ } else {
+ assert(trackNumber < ARRAYSIZE(midiOffsetsGRAVersion));
+ return midiOffsetsGRAVersion[trackNumber];
+ }
+}
+
+/**
+ * Plays the specified MIDI sequence through the sound driver.
+ * @param dwFileOffset File offset of MIDI sequence data
+ * @param bLoop Whether to loop the sequence
+ */
+bool PlayMidiSequence(uint32 dwFileOffset, bool bLoop) {
+ currentMidi = dwFileOffset;
+ currentLoop = bLoop;
+
+ if (volMidi != 0) {
+ SetMidiVolume(volMidi);
+ // Support for compressed music from the music enhancement project
+ AudioCD.stop();
+
+ int trackNumber = GetTrackNumber(dwFileOffset);
+ if (trackNumber >= 0) {
+#if 0
+ // TODO: GRA version
+ int track = enhancedAudioSCNVersion[trackNumber];
+ if (track > 0)
+ AudioCD.play(track, -1, 0, 0);
+#endif
+ } else {
+ warning("Unknown MIDI offset %d", dwFileOffset);
+ }
+
+ if (AudioCD.isPlaying())
+ return true;
+ }
+
+ // set file offset for this sequence
+ dwMidiIndex = dwFileOffset;
+
+ // the index and length of the last tune loaded
+ static uint32 dwLastMidiIndex;
+ static uint32 dwLastSeqLen;
+
+ uint32 dwSeqLen = 0; // length of the sequence
+
+ if (dwMidiIndex == 0)
+ return true;
+
+ if (dwMidiIndex != dwLastMidiIndex) {
+ Common::File midiStream;
+
+ // open MIDI sequence file in binary mode
+ if (!midiStream.open(MIDI_FILE))
+ error("Cannot find file %s", MIDI_FILE);
+
+ // update index of last tune loaded
+ dwLastMidiIndex = dwMidiIndex;
+
+ // move to correct position in the file
+ midiStream.seek(dwMidiIndex, SEEK_SET);
+
+ // read the length of the sequence
+ dwSeqLen = midiStream.readUint32LE();
+
+ // make sure buffer is large enough for this sequence
+ assert(dwSeqLen > 0 && dwSeqLen <= midiBuffer.size);
+
+ // stop any currently playing tune
+ _vm->_music->stop();
+
+ // read the sequence
+ if (midiStream.read(midiBuffer.pDat, dwSeqLen) != dwSeqLen)
+ error("File %s is corrupt", MIDI_FILE);
+
+ midiStream.close();
+
+ _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop);
+
+ // Store the length
+ dwLastSeqLen = dwSeqLen;
+ } else {
+ // dwMidiIndex == dwLastMidiIndex
+ _vm->_music->stop();
+ _vm->_music->playXMIDI(midiBuffer.pDat, dwSeqLen, bLoop);
+ }
+
+ // allow another sequence to play
+ dwMidiIndex = 0;
+
+ return true;
+}
+
+/**
+ * Returns TRUE if a Midi tune is currently playing.
+ */
+bool MidiPlaying(void) {
+ if (AudioCD.isPlaying()) return true;
+ return _vm->_music->isPlaying();
+}
+
+/**
+ * Stops any currently playing midi.
+ */
+bool StopMidi(void) {
+ currentMidi = 0;
+ currentLoop = false;
+
+ AudioCD.stop();
+ _vm->_music->stop();
+ return true;
+}
+
+
+/**
+ * Gets the volume of the MIDI music.
+ */
+int GetMidiVolume() {
+ return volMidi;
+}
+
+/**
+ * Sets the volume of the MIDI music.
+ * @param vol New volume - 0..MAXMIDIVOL
+ */
+void SetMidiVolume(int vol) {
+ assert(vol >= 0 && vol <= MAXMIDIVOL);
+
+ if (vol == 0 && volMidi == 0) {
+ // Nothing to do
+ } else if (vol == 0 && volMidi != 0) {
+ // Stop current midi sequence
+ AudioCD.stop();
+ StopMidi();
+ } else if (vol != 0 && volMidi == 0) {
+ // Perhaps restart last midi sequence
+ if (currentLoop) {
+ PlayMidiSequence(currentMidi, true);
+ _vm->_music->setVolume(vol);
+ }
+ } else if (vol != 0 && volMidi != 0) {
+ // Alter current volume
+ _vm->_music->setVolume(vol);
+ }
+
+ volMidi = vol;
+}
+
+/**
+ * Opens and inits all MIDI sequence files.
+ */
+void OpenMidiFiles(void) {
+ Common::File midiStream;
+
+ // Demo version has no midi file
+ if (_vm->getFeatures() & GF_DEMO)
+ return;
+
+ if (midiBuffer.pDat)
+ // already allocated
+ return;
+
+ // open MIDI sequence file in binary mode
+ if (!midiStream.open(MIDI_FILE))
+ error("Cannot find file %s", MIDI_FILE);
+
+ // gen length of the largest sequence
+ midiBuffer.size = midiStream.readUint32LE();
+ if (midiStream.ioFailed())
+ error("File %s is corrupt", MIDI_FILE);
+
+ if (midiBuffer.size) {
+ // allocate a buffer big enough for the largest MIDI sequence
+ if ((midiBuffer.pDat = (uint8 *)malloc(midiBuffer.size)) != NULL) {
+ // clear out the buffer
+ memset(midiBuffer.pDat, 0, midiBuffer.size);
+// VMM_lock(midiBuffer.pDat, midiBuffer.size);
+ } else {
+ //mSeqHandle = NULL;
+ }
+ }
+
+ midiStream.close();
+}
+
+void DeleteMidiBuffer() {
+ free(midiBuffer.pDat);
+ midiBuffer.pDat = NULL;
+}
+
+MusicPlayer::MusicPlayer(MidiDriver *driver) : _parser(0), _driver(driver), _looping(false), _isPlaying(false) {
+ memset(_channel, 0, sizeof(_channel));
+ _masterVolume = 0;
+ this->open();
+ _xmidiParser = MidiParser::createParser_XMIDI();
+}
+
+MusicPlayer::~MusicPlayer() {
+ _driver->setTimerCallback(NULL, NULL);
+ stop();
+ this->close();
+ _xmidiParser->setMidiDriver(NULL);
+ delete _xmidiParser;
+}
+
+void MusicPlayer::setVolume(int volume) {
+ Common::StackLock lock(_mutex);
+
+ // FIXME: Could we simply change MAXMIDIVOL to match ScummVM's range?
+ volume = CLIP((255 * volume) / MAXMIDIVOL, 0, 255);
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, volume);
+
+ if (_masterVolume == volume)
+ return;
+
+ _masterVolume = volume;
+
+ for (int i = 0; i < 16; ++i) {
+ if (_channel[i]) {
+ _channel[i]->volume(_channelVolume[i] * _masterVolume / 255);
+ }
+ }
+}
+
+int MusicPlayer::open() {
+ // Don't ever call open without first setting the output driver!
+ if (!_driver)
+ return 255;
+
+ int ret = _driver->open();
+ if (ret)
+ return ret;
+
+ _driver->setTimerCallback(this, &onTimer);
+ return 0;
+}
+
+void MusicPlayer::close() {
+ stop();
+ if (_driver)
+ _driver->close();
+ _driver = 0;
+}
+
+void MusicPlayer::send(uint32 b) {
+ byte channel = (byte)(b & 0x0F);
+ if ((b & 0xFFF0) == 0x07B0) {
+ // Adjust volume changes by master volume
+ byte volume = (byte)((b >> 16) & 0x7F);
+ _channelVolume[channel] = volume;
+ volume = volume * _masterVolume / 255;
+ b = (b & 0xFF00FFFF) | (volume << 16);
+ } else if ((b & 0xFFF0) == 0x007BB0) {
+ //Only respond to All Notes Off if this channel
+ //has currently been allocated
+ if (_channel[b & 0x0F])
+ return;
+ }
+
+ if (!_channel[channel])
+ _channel[channel] = (channel == 9) ? _driver->getPercussionChannel() : _driver->allocateChannel();
+
+ if (_channel[channel]) {
+ _channel[channel]->send(b);
+
+ if ((b & 0xFFF0) == 0x0079B0) {
+ // We've just Reset All Controllers, so we need to
+ // re-adjust the volume. Otherwise, volume is reset to
+ // default whenever the music changes.
+ _channel[channel]->send(0x000007B0 | (((_channelVolume[channel] * _masterVolume) / 255) << 16) | channel);
+ }
+ }
+}
+
+void MusicPlayer::metaEvent(byte type, byte *data, uint16 length) {
+ switch (type) {
+ case 0x2F: // End of Track
+ if (_looping)
+ _parser->jumpToTick(0);
+ else
+ stop();
+ break;
+ default:
+ //warning("Unhandled meta event: %02x", type);
+ break;
+ }
+}
+
+void MusicPlayer::onTimer(void *refCon) {
+ MusicPlayer *music = (MusicPlayer *)refCon;
+ Common::StackLock lock(music->_mutex);
+
+ if (music->_isPlaying)
+ music->_parser->onTimer();
+}
+
+void MusicPlayer::playXMIDI(byte *midiData, uint32 size, bool loop) {
+ if (_isPlaying)
+ return;
+
+ stop();
+
+ // It seems like not all music (the main menu music, for instance) set
+ // all the instruments explicitly. That means the music will sound
+ // different, depending on which music played before it. This appears
+ // to be a genuine glitch in the original. For consistency, reset all
+ // instruments to the default one (piano).
+
+ for (int i = 0; i < 16; i++) {
+ _driver->send(0xC0 | i, 0, 0);
+ }
+
+ // Load XMID resource data
+
+ if (_xmidiParser->loadMusic(midiData, size)) {
+ MidiParser *parser = _xmidiParser;
+ parser->setTrack(0);
+ parser->setMidiDriver(this);
+ parser->setTimerRate(getBaseTempo());
+ parser->property(MidiParser::mpCenterPitchWheelOnUnload, 1);
+
+ _parser = parser;
+
+ _looping = loop;
+ _isPlaying = true;
+ }
+}
+
+void MusicPlayer::stop() {
+ Common::StackLock lock(_mutex);
+
+ _isPlaying = false;
+ if (_parser) {
+ _parser->unloadMusic();
+ _parser = NULL;
+ }
+}
+
+void MusicPlayer::pause() {
+ setVolume(-1);
+ _isPlaying = false;
+}
+
+void MusicPlayer::resume() {
+ setVolume(GetMidiVolume());
+ _isPlaying = true;
+}
+
+void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop) {
+ *pMidi = currentMidi;
+ *pLoop = currentLoop;
+}
+
+void RestoreMidiFacts(SCNHANDLE Midi, bool Loop) {
+ AudioCD.stop();
+ StopMidi();
+
+ currentMidi = Midi;
+ currentLoop = Loop;
+
+ if (volMidi != 0 && Loop) {
+ PlayMidiSequence(currentMidi, true);
+ SetMidiVolume(volMidi);
+ }
+}
+
+#if 0
+// Dumps all of the game's music in external XMIDI *.xmi files
+void dumpMusic() {
+ Common::File midiFile;
+ Common::DumpFile outFile;
+ char outName[20];
+ midiFile.open(MIDI_FILE);
+ int outFileSize = 0;
+ char buffer[20000];
+
+ int total = (_vm->getFeatures() & GF_SCNFILES) ?
+ ARRAYSIZE(midiOffsetsSCNVersion) :
+ ARRAYSIZE(midiOffsetsGRAVersion);
+
+ for (int i = 0; i < total; i++) {
+ sprintf(outName, "track%03d.xmi", i + 1);
+ outFile.open(outName);
+
+ if (_vm->getFeatures() & GF_SCNFILES) {
+ if (i < total - 1)
+ outFileSize = midiOffsetsSCNVersion[i + 1] - midiOffsetsSCNVersion[i] - 4;
+ else
+ outFileSize = midiFile.size() - midiOffsetsSCNVersion[i] - 4;
+
+ midiFile.seek(midiOffsetsSCNVersion[i] + 4, SEEK_SET);
+ } else {
+ if (i < total - 1)
+ outFileSize = midiOffsetsGRAVersion[i + 1] - midiOffsetsGRAVersion[i] - 4;
+ else
+ outFileSize = midiFile.size() - midiOffsetsGRAVersion[i] - 4;
+
+ midiFile.seek(midiOffsetsGRAVersion[i] + 4, SEEK_SET);
+ }
+
+ assert(outFileSize < 20000);
+ midiFile.read(buffer, outFileSize);
+ outFile.write(buffer, outFileSize);
+
+ outFile.close();
+ }
+
+ midiFile.close();
+}
+#endif
+
+} // End of namespace Made
diff --git a/engines/tinsel/music.h b/engines/tinsel/music.h
new file mode 100644
index 0000000000..80456e2a76
--- /dev/null
+++ b/engines/tinsel/music.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+// Music class
+
+#ifndef TINSEL_MUSIC_H
+#define TINSEL_MUSIC_H
+
+#include "sound/mididrv.h"
+#include "sound/midiparser.h"
+#include "common/mutex.h"
+
+namespace Tinsel {
+
+#define MAXMIDIVOL 127
+
+bool PlayMidiSequence( // Plays the specified MIDI sequence through the sound driver
+ uint32 dwFileOffset, // handle of MIDI sequence data
+ bool bLoop); // Whether to loop the sequence
+
+bool MidiPlaying(void); // Returns TRUE if a Midi tune is currently playing
+
+bool StopMidi(void); // Stops any currently playing midi
+
+void SetMidiVolume( // Sets the volume of the MIDI music. Returns the old volume
+ int vol); // new volume - 0..MAXMIDIVOL
+
+int GetMidiVolume();
+
+void OpenMidiFiles();
+void DeleteMidiBuffer();
+
+void CurrentMidiFacts(SCNHANDLE *pMidi, bool *pLoop);
+void RestoreMidiFacts(SCNHANDLE Midi, bool Loop);
+
+int GetTrackNumber(SCNHANDLE hMidi);
+SCNHANDLE GetTrackOffset(int trackNumber);
+
+void dumpMusic();
+
+
+class MusicPlayer : public MidiDriver {
+public:
+ MusicPlayer(MidiDriver *driver);
+ ~MusicPlayer();
+
+ bool isPlaying() { return _isPlaying; }
+ void setPlaying(bool playing) { _isPlaying = playing; }
+
+ void setVolume(int volume);
+ int getVolume() { return _masterVolume; }
+
+ void playXMIDI(byte *midiData, uint32 size, bool loop);
+ void stop();
+ void pause();
+ void resume();
+ void setLoop(bool loop) { _looping = loop; }
+
+ //MidiDriver interface implementation
+ int open();
+ void close();
+ void send(uint32 b);
+
+ void metaEvent(byte type, byte *data, uint16 length);
+
+ void setTimerCallback(void *timerParam, void (*timerProc)(void *)) { }
+
+ // The original sets the "sequence timing" to 109 Hz, whatever that
+ // means. The default is 120.
+
+ uint32 getBaseTempo(void) { return _driver ? (109 * _driver->getBaseTempo()) / 120 : 0; }
+
+ //Channel allocation functions
+ MidiChannel *allocateChannel() { return 0; }
+ MidiChannel *getPercussionChannel() { return 0; }
+
+ MidiParser *_parser;
+ Common::Mutex _mutex;
+
+protected:
+
+ static void onTimer(void *data);
+
+ MidiChannel *_channel[16];
+ MidiDriver *_driver;
+ MidiParser *_xmidiParser;
+ byte _channelVolume[16];
+
+ bool _isPlaying;
+ bool _looping;
+ byte _masterVolume;
+};
+
+} // End of namespace Made
+
+#endif
diff --git a/engines/tinsel/object.cpp b/engines/tinsel/object.cpp
new file mode 100644
index 0000000000..709fa4fad9
--- /dev/null
+++ b/engines/tinsel/object.cpp
@@ -0,0 +1,530 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the Object Manager code.
+ */
+
+#include "tinsel/object.h"
+#include "tinsel/background.h"
+#include "tinsel/cliprect.h" // object clip rect defs
+#include "tinsel/graphics.h" // low level interface
+#include "tinsel/handle.h"
+
+#define OID_EFFECTS 0x2000 // generic special effects object id
+
+namespace Tinsel {
+
+/** screen clipping rectangle */
+static const Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
+
+// list of all objects
+OBJECT *objectList = 0;
+
+// pointer to free object list
+static OBJECT *pFreeObjects = 0;
+
+#ifdef DEBUG
+// diagnostic object counters
+static int numObj = 0;
+static int maxObj = 0;
+#endif
+
+void FreeObjectList(void) {
+ if (objectList) {
+ free(objectList);
+ objectList = NULL;
+ }
+}
+
+/**
+ * Kills all objects and places them on the free list.
+ */
+
+void KillAllObjects(void) {
+ int i;
+
+#ifdef DEBUG
+ // clear number of objects in use
+ numObj = 0;
+#endif
+
+ if (objectList == NULL) {
+ // first time - allocate memory for object list
+ objectList = (OBJECT *)calloc(NUM_OBJECTS, sizeof(OBJECT));
+
+ // make sure memory allocated
+ if (objectList == NULL) {
+ error("Cannot allocate memory for object data");
+ }
+ }
+
+ // place first object on free list
+ pFreeObjects = objectList;
+
+ // link all other objects after first
+ for (i = 1; i < NUM_OBJECTS; i++) {
+ objectList[i - 1].pNext = objectList + i;
+ }
+
+ // null the last object
+ objectList[NUM_OBJECTS - 1].pNext = NULL;
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of objects used at once.
+ */
+
+void ObjectStats(void) {
+ printf("%i objects of %i used.\n", maxObj, NUM_OBJECTS);
+}
+#endif
+
+/**
+ * Allocate a object from the free list.
+ */
+OBJECT *AllocObject(void) {
+ OBJECT *pObj = pFreeObjects; // get a free object
+
+ // check for no free objects
+ assert(pObj != NULL);
+
+ // a free object exists
+
+ // get link to next free object
+ pFreeObjects = pObj->pNext;
+
+ // clear out object
+ memset(pObj, 0, sizeof(OBJECT));
+
+ // set default drawing mode and set changed bit
+ pObj->flags = DMA_WNZ | DMA_CHANGED;
+
+#ifdef DEBUG
+ // one more object in use
+ if (++numObj > maxObj)
+ maxObj = numObj;
+#endif
+
+ // return new object
+ return pObj;
+}
+
+/**
+ * Copy one object to another.
+ * @param pDest Destination object
+ * @param pSrc Source object
+ */
+void CopyObject(OBJECT *pDest, OBJECT *pSrc) {
+ // save previous dimensions etc.
+ Common::Rect rcSave = pDest->rcPrev;
+
+ // make a copy
+ memcpy(pDest, pSrc, sizeof(OBJECT));
+
+ // restore previous dimensions etc.
+ pDest->rcPrev = rcSave;
+
+ // set changed flag in destination
+ pDest->flags |= DMA_CHANGED;
+
+ // null the links
+ pDest->pNext = pDest->pSlave = NULL;
+}
+
+/**
+ * Inserts an object onto the specified object list. The object
+ * lists are sorted in Z Y order.
+ * @param pObjList List to insert object onto
+ * @param pInsObj Object to insert
+ */
+
+void InsertObject(OBJECT *pObjList, OBJECT *pInsObj) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+
+ // validate object pointer
+ assert(pInsObj >= objectList && pInsObj <= objectList + NUM_OBJECTS - 1);
+
+ for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ // check Z order
+ if (pInsObj->zPos < pObj->zPos) {
+ // object Z is lower than list Z - insert here
+ break;
+ } else if (pInsObj->zPos == pObj->zPos) {
+ // Z values are the same - sort on Y
+ if (fracToDouble(pInsObj->yPos) <= fracToDouble(pObj->yPos)) {
+ // object Y is lower than or same as list Y - insert here
+ break;
+ }
+ }
+ }
+
+ // insert obj between pPrev and pObj
+ pInsObj->pNext = pObj;
+ pPrev->pNext = pInsObj;
+}
+
+
+/**
+ * Deletes an object from the specified object list and places it
+ * on the free list.
+ * @param pObjList List to delete object from
+ * @param pDelObj Object to delete
+ */
+void DelObject(OBJECT *pObjList, OBJECT *pDelObj) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+
+ // validate object pointer
+ assert(pDelObj >= objectList && pDelObj <= objectList + NUM_OBJECTS - 1);
+
+#ifdef DEBUG
+ // one less object in use
+ --numObj;
+ assert(numObj >= 0);
+#endif
+
+ for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ if (pObj == pDelObj) {
+ // found object to delete
+
+ if (IntersectRectangle(pDelObj->rcPrev, pDelObj->rcPrev, rcScreen)) {
+ // allocate a clipping rect for objects previous pos
+ AddClipRect(pDelObj->rcPrev);
+ }
+
+ // make PREV next = OBJ next - removes OBJ from list
+ pPrev->pNext = pObj->pNext;
+
+ // place free list in OBJ next
+ pObj->pNext = pFreeObjects;
+
+ // add OBJ to top of free list
+ pFreeObjects = pObj;
+
+ // delete objects palette
+ if (pObj->pPal)
+ FreePalette(pObj->pPal);
+
+ // quit
+ return;
+ }
+ }
+
+ // if we get to here - object has not been found on the list
+ error("DelObject(): formally 'assert(0)!'");
+}
+
+
+/**
+ * Sort the specified object list in Z Y order.
+ * @param pObjList List to sort
+ */
+void SortObjectList(OBJECT *pObjList) {
+ OBJECT *pPrev, *pObj; // object list traversal pointers
+ OBJECT head; // temporary head of list - because pObjList is not usually a OBJECT
+
+ // put at head of list
+ head.pNext = pObjList->pNext;
+
+ // set head of list dummy OBJ Z Y values to lowest possible
+ head.yPos = intToFrac(MIN_INT16);
+ head.zPos = MIN_INT;
+
+ for (pPrev = &head, pObj = head.pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) {
+ // check Z order
+ if (pObj->zPos < pPrev->zPos) {
+ // object Z is lower than previous Z
+
+ // remove object from list
+ pPrev->pNext = pObj->pNext;
+
+ // re-insert object on list
+ InsertObject(pObjList, pObj);
+
+ // back to beginning of list
+ pPrev = &head;
+ pObj = head.pNext;
+ } else if (pObj->zPos == pPrev->zPos) {
+ // Z values are the same - sort on Y
+ if (fracToDouble(pObj->yPos) < fracToDouble(pPrev->yPos)) {
+ // object Y is lower than previous Y
+
+ // remove object from list
+ pPrev->pNext = pObj->pNext;
+
+ // re-insert object on list
+ InsertObject(pObjList, pObj);
+
+ // back to beginning of list
+ pPrev = &head;
+ pObj = head.pNext;
+ }
+ }
+ }
+}
+
+/**
+ * Returns the animation offsets of a image, dependent on the
+ * images orientation flags.
+ * @param hImg Iimage to get animation offset of
+ * @param flags Images current flags
+ * @param pAniX Gets set to new X animation offset
+ * @param pAniY Gets set to new Y animation offset
+ */
+void GetAniOffset(SCNHANDLE hImg, int flags, int *pAniX, int *pAniY) {
+ if (hImg) {
+ const IMAGE *pImg = (const IMAGE *)LockMem(hImg);
+
+ // set ani X
+ *pAniX = (int16) FROM_LE_16(pImg->anioffX);
+
+ // set ani Y
+ *pAniY = (int16) FROM_LE_16(pImg->anioffY);
+
+ if (flags & DMA_FLIPH) {
+ // we are flipped horizontally
+
+ // set ani X = -ani X + width - 1
+ *pAniX = -*pAniX + FROM_LE_16(pImg->imgWidth) - 1;
+ }
+
+ if (flags & DMA_FLIPV) {
+ // we are flipped vertically
+
+ // set ani Y = -ani Y + height - 1
+ *pAniY = -*pAniY + FROM_LE_16(pImg->imgHeight) - 1;
+ }
+ } else
+ // null image
+ *pAniX = *pAniY = 0;
+}
+
+
+/**
+ * Returns the x,y position of an objects animation point.
+ * @param pObj Pointer to object
+ * @param pPosX Gets set to objects X animation position
+ * @param pPosY Gets set to objects Y animation position
+ */
+void GetAniPosition(OBJECT *pObj, int *pPosX, int *pPosY) {
+ // validate object pointer
+ assert(pObj >= objectList && pObj <= objectList + NUM_OBJECTS - 1);
+
+ // get the animation offset of the object
+ GetAniOffset(pObj->hImg, pObj->flags, pPosX, pPosY);
+
+ // from animation offset and objects position - determine objects animation point
+ *pPosX += fracToInt(pObj->xPos);
+ *pPosY += fracToInt(pObj->yPos);
+}
+
+/**
+ * Initialise a object using a OBJ_INIT structure to supply parameters.
+ * @param pInitTbl Pointer to object initialisation table
+ */
+OBJECT *InitObject(const OBJ_INIT *pInitTbl) {
+ // allocate a new object
+ OBJECT *pObj = AllocObject();
+
+ // make sure object created
+ assert(pObj != NULL);
+
+ // set objects shape
+ pObj->hImg = pInitTbl->hObjImg;
+
+ // set objects ID
+ pObj->oid = pInitTbl->objID;
+
+ // set objects flags
+ pObj->flags = DMA_CHANGED | pInitTbl->objFlags;
+
+ // set objects Z position
+ pObj->zPos = pInitTbl->objZ;
+
+ // get pointer to image
+ if (pInitTbl->hObjImg) {
+ int aniX, aniY; // objects animation offsets
+ PALQ *pPalQ; // palette queue pointer
+ const IMAGE *pImg = (const IMAGE *)LockMem(pInitTbl->hObjImg); // handle to image
+
+ // allocate a palette for this object
+ pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal));
+
+ // make sure palette allocated
+ assert(pPalQ != NULL);
+
+ // assign palette to object
+ pObj->pPal = pPalQ;
+
+ // set objects size
+ pObj->width = FROM_LE_16(pImg->imgWidth);
+ pObj->height = FROM_LE_16(pImg->imgHeight);
+
+ // set objects bitmap definition
+ pObj->hBits = FROM_LE_32(pImg->hImgBits);
+
+ // get animation offset of object
+ GetAniOffset(pObj->hImg, pInitTbl->objFlags, &aniX, &aniY);
+
+ // set objects X position - subtract ani offset
+ pObj->xPos = intToFrac(pInitTbl->objX - aniX);
+
+ // set objects Y position - subtract ani offset
+ pObj->yPos = intToFrac(pInitTbl->objY - aniY);
+ } else { // no image handle - null image
+
+ // set objects X position
+ pObj->xPos = intToFrac(pInitTbl->objX);
+
+ // set objects Y position
+ pObj->yPos = intToFrac(pInitTbl->objY);
+ }
+
+ // return new object
+ return pObj;
+}
+
+/**
+ * Give a object a new image and new orientation flags.
+ * @param pAniObj Object to be updated
+ * @param newflags Objects new flags
+ * @param hNewImg Objects new image
+ */
+void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) {
+ // validate object pointer
+ assert(pAniObj >= objectList && pAniObj <= objectList + NUM_OBJECTS - 1);
+
+ if (pAniObj->hImg != hNewImg
+ || (pAniObj->flags & DMA_HARDFLAGS) != (newflags & DMA_HARDFLAGS)) {
+ // something has changed
+
+ int oldAniX, oldAniY; // objects old animation offsets
+ int newAniX, newAniY; // objects new animation offsets
+
+ // get objects old animation offsets
+ GetAniOffset(pAniObj->hImg, pAniObj->flags, &oldAniX, &oldAniY);
+
+ // get objects new animation offsets
+ GetAniOffset(hNewImg, newflags, &newAniX, &newAniY);
+
+ if (hNewImg) {
+ // get pointer to image
+ const IMAGE *pNewImg = (IMAGE *)LockMem(hNewImg);
+
+ // setup new shape
+ pAniObj->width = FROM_LE_16(pNewImg->imgWidth);
+ pAniObj->height = FROM_LE_16(pNewImg->imgHeight);
+
+ // set objects bitmap definition
+ pAniObj->hBits = FROM_LE_32(pNewImg->hImgBits);
+ } else { // null image
+ pAniObj->width = 0;
+ pAniObj->height = 0;
+ pAniObj->hBits = 0;
+ }
+
+ // set objects flags and signal a change
+ pAniObj->flags = newflags | DMA_CHANGED;
+
+ // set objects image
+ pAniObj->hImg = hNewImg;
+
+ // adjust objects position - subtract new from old for difference
+ pAniObj->xPos += intToFrac(oldAniX - newAniX);
+ pAniObj->yPos += intToFrac(oldAniY - newAniY);
+ }
+}
+
+/**
+ * Give an object a new image.
+ * @param pAniObj Object to animate
+ * @param hNewImg Objects new image
+ */
+void AnimateObject(OBJECT *pAniObj, SCNHANDLE hNewImg) {
+ // dont change the objects flags
+ AnimateObjectFlags(pAniObj, pAniObj->flags, hNewImg);
+}
+
+/**
+ * Creates a rectangle object of the given dimensions and returns
+ * a pointer to the object.
+ * @param hPal Palette for the rectangle object
+ * @param colour Which colour offset from the above palette
+ * @param width Width of rectangle
+ * @param height Height of rectangle
+ */
+OBJECT *RectangleObject(SCNHANDLE hPal, int colour, int width, int height) {
+ // template for initialising the rectangle object
+ static const OBJ_INIT rectObj = {0, DMA_CONST, OID_EFFECTS, 0, 0, 0};
+ PALQ *pPalQ; // palette queue pointer
+
+ // allocate and init a new object
+ OBJECT *pRect = InitObject(&rectObj);
+
+ // allocate a palette for this object
+ pPalQ = AllocPalette(hPal);
+
+ // make sure palette allocated
+ assert(pPalQ != NULL);
+
+ // assign palette to object
+ pRect->pPal = pPalQ;
+
+ // set colour in the palette
+ pRect->constant = colour;
+
+ // set rectangle width
+ pRect->width = width;
+
+ // set rectangle height
+ pRect->height = height;
+
+ // return pointer to rectangle object
+ return pRect;
+}
+
+/**
+ * Creates a translucent rectangle object of the given dimensions
+ * and returns a pointer to the object.
+ * @param width Width of rectangle
+ * @param height Height of rectangle
+ */
+OBJECT *TranslucentObject(int width, int height) {
+ // template for initialising the rectangle object
+ static const OBJ_INIT rectObj = {0, DMA_TRANS, OID_EFFECTS, 0, 0, 0};
+
+ // allocate and init a new object
+ OBJECT *pRect = InitObject(&rectObj);
+
+ // set rectangle width
+ pRect->width = width;
+
+ // set rectangle height
+ pRect->height = height;
+
+ // return pointer to rectangle object
+ return pRect;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/object.h b/engines/tinsel/object.h
new file mode 100644
index 0000000000..8b61571a3e
--- /dev/null
+++ b/engines/tinsel/object.h
@@ -0,0 +1,206 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Object Manager data structures
+ */
+
+#ifndef TINSEL_OBJECT_H // prevent multiple includes
+#define TINSEL_OBJECT_H
+
+#include "tinsel/dw.h"
+#include "common/frac.h"
+#include "common/rect.h"
+
+namespace Tinsel {
+
+struct PALQ;
+
+enum {
+ /** the maximum number of objects */
+ NUM_OBJECTS = 256,
+
+ // object flags
+ DMA_WNZ = 0x0001, //!< write non-zero data
+ DMA_CNZ = 0x0002, //!< write constant on non-zero data
+ DMA_CONST = 0x0004, //!< write constant on both zero & non-zero data
+ DMA_WA = 0x0008, //!< write all data
+ DMA_FLIPH = 0x0010, //!< flip object horizontally
+ DMA_FLIPV = 0x0020, //!< flip object vertically
+ DMA_CLIP = 0x0040, //!< clip object
+ DMA_TRANS = 0x0084, //!< translucent rectangle object
+ DMA_ABS = 0x0100, //!< position of object is absolute
+ DMA_CHANGED = 0x0200, //!< object has changed in some way since the last frame
+ DMA_USERDEF = 0x0400, //!< user defined flags start here
+
+ /** flags that effect an objects appearance */
+ DMA_HARDFLAGS = (DMA_WNZ | DMA_CNZ | DMA_CONST | DMA_WA | DMA_FLIPH | DMA_FLIPV | DMA_TRANS)
+};
+
+/** structure for image */
+struct IMAGE {
+ short imgWidth; //!< image width
+ short imgHeight; //!< image height
+ short anioffX; //!< image x animation offset
+ short anioffY; //!< image y animation offset
+ SCNHANDLE hImgBits; //!< image bitmap handle
+ SCNHANDLE hImgPal; //!< image palette handle
+};
+
+
+/** a multi-object animation frame is a list of multi-image handles */
+typedef uint32 FRAME;
+
+
+// object structure
+struct OBJECT {
+ OBJECT *pNext; //!< pointer to next object in list
+ OBJECT *pSlave; //!< pointer to slave object (multi-part objects)
+// char *pOnDispList; //!< pointer to display list byte for background objects
+// frac_t xVel; //!< x velocity of object
+// frac_t yVel; //!< y velocity of object
+ frac_t xPos; //!< x position of object
+ frac_t yPos; //!< y position of object
+ int zPos; //!< z position of object
+ Common::Rect rcPrev; //!< previous screen coordinates of object bounding rectangle
+ int flags; //!< object flags - see above for list
+ PALQ *pPal; //!< objects palette Q position
+ int constant; //!< which colour in palette for monochrome objects
+ int width; //!< width of object
+ int height; //!< height of object
+ SCNHANDLE hBits; //!< image bitmap handle
+ SCNHANDLE hImg; //!< handle to object image definition
+ SCNHANDLE hShape; //!< objects current animation frame
+ SCNHANDLE hMirror; //!< objects previous animation frame
+ int oid; //!< object identifier
+};
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+// object initialisation structure
+struct OBJ_INIT {
+ SCNHANDLE hObjImg; // objects shape - handle to IMAGE structure
+ int32 objFlags; // objects flags
+ int32 objID; // objects id
+ int32 objX; // objects initial x position
+ int32 objY; // objects initial y position
+ int32 objZ; // objects initial z position
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/*----------------------------------------------------------------------*\
+|* Object Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void KillAllObjects(void); // kill all objects and place them on free list
+
+void FreeObjectList(void); // free the object list
+
+#ifdef DEBUG
+void ObjectStats(void); // Shows the maximum number of objects used at once
+#endif
+
+OBJECT *AllocObject(void); // allocate a object from the free list
+
+void FreeObject( // place a object back on the free list
+ OBJECT *pFreeObj); // object to free
+
+void CopyObject( // copy one object to another
+ OBJECT *pDest, // destination object
+ OBJECT *pSrc); // source object
+
+void InsertObject( // insert a object onto a sorted object list
+ OBJECT *pObjList, // list to insert object onto
+ OBJECT *pInsObj); // object to insert
+
+void DelObject( // delete a object from a object list and add to free list
+ OBJECT *pObjList, // list to delete object from
+ OBJECT *pDelObj); // object to delete
+
+void SortObjectList( // re-sort an object list
+ OBJECT *pObjList); // list to sort
+
+OBJECT *GetNextObject( // object list iterator - returns next obj in list
+ OBJECT *pObjList, // which object list
+ OBJECT *pStrtObj); // object to start from - when NULL will start from beginning of list
+
+OBJECT *FindObject( // Searches the specified obj list for a object matching the specified OID
+ OBJECT *pObjList, // object list to search
+ int oidDesired, // object identifer of object to find
+ int oidMask); // mask to apply to object identifiers before comparison
+
+void GetAniOffset( // returns the anim offsets of a image, takes into account orientation
+ SCNHANDLE hImg, // image to get animation offset of
+ int flags, // images current flags
+ int *pAniX, // gets set to new X animation offset
+ int *pAniY); // gets set to new Y animation offset
+
+void GetAniPosition( // Returns a objects x,y animation point
+ OBJECT *pObj, // pointer to object
+ int *pPosX, // gets set to objects X animation position
+ int *pPosY); // gets set to objects Y animation position
+
+OBJECT *InitObject( // Init a object using a OBJ_INIT struct
+ const OBJ_INIT *pInitTbl); // pointer to object initialisation table
+
+void AnimateObjectFlags( // Give a object a new image and new orientation flags
+ OBJECT *pAniObj, // object to be updated
+ int newflags, // objects new flags
+ SCNHANDLE hNewImg); // objects new image
+
+void AnimateObject( // give a object a new image
+ OBJECT *pAniObj, // object to animate
+ SCNHANDLE hNewImg); // objects new image
+
+void HideObject( // Hides a object by giving it a "NullImage" image pointer
+ OBJECT *pObj); // object to be hidden
+
+OBJECT *RectangleObject( // create a rectangle object of the given dimensions
+ SCNHANDLE hPal, // palette for the rectangle object
+ int colour, // which colour offset from the above palette
+ int width, // width of rectangle
+ int height); // height of rectangle
+
+OBJECT *TranslucentObject( // create a translucent rectangle object of the given dimensions
+ int width, // width of rectangle
+ int height); // height of rectangle
+
+void ResizeRectangle( // resizes a rectangle object
+ OBJECT *pRect, // rectangle object pointer
+ int width, // new width of rectangle
+ int height); // new height of rectangle
+
+
+// FIXME: This does not belong here
+struct FILM;
+struct FREEL;
+struct MULTI_INIT;
+IMAGE *GetImageFromReel(const FREEL *pfreel, const MULTI_INIT **ppmi = 0);
+IMAGE *GetImageFromFilm(SCNHANDLE hFilm, int reel, const FREEL **ppfr = 0,
+ const MULTI_INIT **ppmi = 0, const FILM **ppfilm = 0);
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_OBJECT_H
diff --git a/engines/tinsel/palette.cpp b/engines/tinsel/palette.cpp
new file mode 100644
index 0000000000..3bc2b514b5
--- /dev/null
+++ b/engines/tinsel/palette.cpp
@@ -0,0 +1,440 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Allocator for IBM PC.
+ */
+
+#include "tinsel/dw.h" // TBLUE1 definition
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h" // LockMem definition
+#include "tinsel/palette.h" // palette allocator structures etc.
+#include "tinsel/tinsel.h"
+
+#include "common/system.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+/** video DAC transfer Q structure */
+struct VIDEO_DAC_Q {
+ union {
+ SCNHANDLE hRGBarray; //!< handle of palette or
+ COLORREF *pRGBarray; //!< list of palette colours
+ } pal;
+ bool bHandle; //!< when set - use handle of palette
+ int destDACindex; //!< start index of palette in video DAC
+ int numColours; //!< number of colours in "hRGBarray"
+};
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+/** background colour */
+static COLORREF bgndColour = BLACK;
+
+/** palette allocator data */
+static PALQ palAllocData[NUM_PALETTES];
+
+
+/** video DAC transfer Q length */
+#define VDACQLENGTH (NUM_PALETTES+2)
+
+/** video DAC transfer Q */
+static VIDEO_DAC_Q vidDACdata[VDACQLENGTH];
+
+/** video DAC transfer Q head pointer */
+static VIDEO_DAC_Q *pDAChead;
+
+/** colour index of the 4 colours used for the translucent palette */
+#define COL_HILIGHT TBLUE1
+
+/** the translucent palette lookup table */
+uint8 transPalette[MAX_COLOURS]; // used in graphics.cpp
+
+#ifdef DEBUG
+// diagnostic palette counters
+static int numPals = 0;
+static int maxPals = 0;
+static int maxDACQ = 0;
+#endif
+
+/**
+ * Transfer palettes in the palette Q to Video DAC.
+ */
+void PalettesToVideoDAC(void) {
+ PALQ *pPalQ; // palette Q iterator
+ VIDEO_DAC_Q *pDACtail = vidDACdata; // set tail pointer
+ bool needUpdate = false;
+
+ // while Q is not empty
+ while (pDAChead != pDACtail) {
+ PALETTE *pPalette; // pointer to hardware palette
+ COLORREF *pColours; // pointer to list of RGB triples
+
+#ifdef DEBUG
+ // make sure palette does not overlap
+ assert(pDACtail->destDACindex + pDACtail->numColours <= MAX_COLOURS);
+#else
+ // make sure palette does not overlap
+ if (pDACtail->destDACindex + pDACtail->numColours > MAX_COLOURS)
+ pDACtail->numColours = MAX_COLOURS - pDACtail->destDACindex;
+#endif
+
+ if (pDACtail->bHandle) {
+ // we are using a palette handle
+
+ // get hardware palette pointer
+ pPalette = (PALETTE *)LockMem(pDACtail->pal.hRGBarray);
+
+ // get RGB pointer
+ pColours = pPalette->palRGB;
+ } else {
+ // we are using a palette pointer
+ pColours = pDACtail->pal.pRGBarray;
+ }
+
+ if (pDACtail->numColours > 0)
+ needUpdate = true;
+
+ // update the system palette
+ g_system->setPalette((byte *)pColours, pDACtail->destDACindex, pDACtail->numColours);
+
+ // update tail pointer
+ pDACtail++;
+
+ }
+
+ // reset video DAC transfer Q head pointer
+ pDAChead = vidDACdata;
+
+ // clear all palette moved bits
+ for (pPalQ = palAllocData; pPalQ < palAllocData + NUM_PALETTES; pPalQ++)
+ pPalQ->posInDAC &= ~PALETTE_MOVED;
+
+ if (needUpdate)
+ g_system->updateScreen();
+}
+
+/**
+ * Commpletely reset the palette allocator.
+ */
+void ResetPalAllocator(void) {
+#ifdef DEBUG
+ // clear number of palettes in use
+ numPals = 0;
+#endif
+
+ // wipe out the palette allocator data
+ memset(palAllocData, 0, sizeof(palAllocData));
+
+ // reset video DAC transfer Q head pointer
+ pDAChead = vidDACdata;
+}
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of palettes used at once.
+ */
+void PaletteStats(void) {
+ printf("%i palettes of %i used.\n", maxPals, NUM_PALETTES);
+ printf("%i DAC queue entries of %i used.\n", maxDACQ, VDACQLENGTH);
+}
+#endif
+
+/**
+ * Places a palette in the video DAC queue.
+ * @param posInDAC Position in video DAC
+ * @param numColours Number of colours in palette
+ * @param hPalette Handle to palette
+ */
+void UpdateDACqueueHandle(int posInDAC, int numColours, SCNHANDLE hPalette) {
+ // check Q overflow
+ assert(pDAChead < vidDACdata + VDACQLENGTH);
+
+ pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC
+ pDAChead->numColours = numColours; // set number of colours
+ pDAChead->pal.hRGBarray = hPalette; // set handle of palette
+ pDAChead->bHandle = true; // we are using a palette handle
+
+ // update head pointer
+ ++pDAChead;
+
+#ifdef DEBUG
+ if ((pDAChead-vidDACdata) > maxDACQ)
+ maxDACQ = pDAChead-vidDACdata;
+#endif
+}
+
+/**
+ * Places a palette in the video DAC queue.
+ * @param posInDAC Position in video DAC
+ * @param numColours, Number of colours in palette
+ * @param pColours List of RGB triples
+ */
+void UpdateDACqueue(int posInDAC, int numColours, COLORREF *pColours) {
+ // check Q overflow
+ assert(pDAChead < vidDACdata + NUM_PALETTES);
+
+ pDAChead->destDACindex = posInDAC & ~PALETTE_MOVED; // set index in video DAC
+ pDAChead->numColours = numColours; // set number of colours
+ pDAChead->pal.pRGBarray = pColours; // set addr of palette
+ pDAChead->bHandle = false; // we are not using a palette handle
+
+ // update head pointer
+ ++pDAChead;
+
+#ifdef DEBUG
+ if ((pDAChead-vidDACdata) > maxDACQ)
+ maxDACQ = pDAChead-vidDACdata;
+#endif
+}
+
+/**
+ * Allocate a palette.
+ * @param hNewPal Palette to allocate
+ */
+PALQ *AllocPalette(SCNHANDLE hNewPal) {
+ PALQ *pPrev, *p; // walks palAllocData
+ int iDAC; // colour index in video DAC
+ PALQ *pNxtPal; // next PALQ struct in palette allocator
+ PALETTE *pNewPal;
+
+ // get pointer to new palette
+ pNewPal = (PALETTE *)LockMem(hNewPal);
+
+ // search all structs in palette allocator - see if palette already allocated
+ for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) {
+ if (p->hPal == hNewPal) {
+ // found the desired palette in palette allocator
+ p->objCount++; // update number of objects using palette
+ return p; // return palette queue position
+ }
+ }
+
+ // search all structs in palette allocator - find a free slot
+ iDAC = FGND_DAC_INDEX; // init DAC index to first available foreground colour
+
+ for (p = palAllocData; p < palAllocData + NUM_PALETTES; p++) {
+ if (p->hPal == 0) {
+ // found a free slot in palette allocator
+ p->objCount = 1; // init number of objects using palette
+ p->posInDAC = iDAC; // set palettes start pos in video DAC
+ p->hPal = hNewPal; // set hardware palette data
+ p->numColours = FROM_LE_32(pNewPal->numColours); // set number of colours in palette
+
+#ifdef DEBUG
+ // one more palette in use
+ if (++numPals > maxPals)
+ maxPals = numPals;
+#endif
+
+ // Q the change to the video DAC
+ UpdateDACqueueHandle(p->posInDAC, p->numColours, p->hPal);
+
+ // move all palettes after this one down (if necessary)
+ for (pPrev = p, pNxtPal = pPrev + 1; pNxtPal < palAllocData + NUM_PALETTES; pNxtPal++) {
+ if (pNxtPal->hPal != 0) {
+ // palette slot is in use
+ if (pNxtPal->posInDAC >= pPrev->posInDAC + pPrev->numColours)
+ // no need to move palettes down
+ break;
+
+ // move palette down - indicate change
+ pNxtPal->posInDAC = pPrev->posInDAC
+ + pPrev->numColours | PALETTE_MOVED;
+
+ // Q the palette change in position to the video DAC
+ UpdateDACqueueHandle(pNxtPal->posInDAC,
+ pNxtPal->numColours,
+ pNxtPal->hPal);
+
+ // update previous palette to current palette
+ pPrev = pNxtPal;
+ }
+ }
+
+ // return palette pointer
+ return p;
+ }
+
+ // set new DAC index
+ iDAC = p->posInDAC + p->numColours;
+ }
+
+ // no free palettes
+ error("AllocPalette(): formally 'assert(0)!'");
+}
+
+/**
+ * Free a palette allocated with "AllocPalette".
+ * @param pFreePal Palette queue entry to free
+ */
+void FreePalette(PALQ *pFreePal) {
+ // validate palette Q pointer
+ assert(pFreePal >= palAllocData && pFreePal <= palAllocData + NUM_PALETTES - 1);
+
+ // reduce the palettes object reference count
+ pFreePal->objCount--;
+
+ // make sure palette has not been deallocated too many times
+ assert(pFreePal->objCount >= 0);
+
+ if (pFreePal->objCount == 0) {
+ pFreePal->hPal = 0; // palette is no longer in use
+
+#ifdef DEBUG
+ // one less palette in use
+ --numPals;
+ assert(numPals >= 0);
+#endif
+ }
+}
+
+/**
+ * Find the specified palette.
+ * @param hSrchPal Hardware palette to search for
+ */
+PALQ *FindPalette(SCNHANDLE hSrchPal) {
+ PALQ *pPal; // palette allocator iterator
+
+ // search all structs in palette allocator
+ for (pPal = palAllocData; pPal < palAllocData + NUM_PALETTES; pPal++) {
+ if (pPal->hPal == hSrchPal)
+ // found palette in palette allocator
+ return pPal;
+ }
+
+ // palette not found
+ return NULL;
+}
+
+/**
+ * Swaps the palettes at the specified palette queue position.
+ * @param pPalQ Palette queue position
+ * @param hNewPal New palette
+ */
+void SwapPalette(PALQ *pPalQ, SCNHANDLE hNewPal) {
+ // convert handle to palette pointer
+ PALETTE *pNewPal = (PALETTE *)LockMem(hNewPal);
+
+ // validate palette Q pointer
+ assert(pPalQ >= palAllocData && pPalQ <= palAllocData + NUM_PALETTES - 1);
+
+ if (pPalQ->numColours >= (int)FROM_LE_32(pNewPal->numColours)) {
+ // new palette will fit the slot
+
+ // install new palette
+ pPalQ->hPal = hNewPal;
+
+ // Q the change to the video DAC
+ UpdateDACqueueHandle(pPalQ->posInDAC, FROM_LE_32(pNewPal->numColours), hNewPal);
+ } else {
+ // # colours are different - will have to update all following palette entries
+
+ PALQ *pNxtPalQ; // next palette queue position
+
+ for (pNxtPalQ = pPalQ + 1; pNxtPalQ < palAllocData + NUM_PALETTES; pNxtPalQ++) {
+ if (pNxtPalQ->posInDAC >= pPalQ->posInDAC + pPalQ->numColours)
+ // no need to move palettes down
+ break;
+
+ // move palette down
+ pNxtPalQ->posInDAC = pPalQ->posInDAC
+ + pPalQ->numColours | PALETTE_MOVED;
+
+ // Q the palette change in position to the video DAC
+ UpdateDACqueueHandle(pNxtPalQ->posInDAC,
+ pNxtPalQ->numColours,
+ pNxtPalQ->hPal);
+
+ // update previous palette to current palette
+ pPalQ = pNxtPalQ;
+ }
+ }
+}
+
+/**
+ * Statless palette iterator. Returns the next palette in the list
+ * @param pStrtPal Palette to start from - when NULL will start from beginning of list
+ */
+PALQ *GetNextPalette(PALQ *pStrtPal) {
+ if (pStrtPal == NULL) {
+ // start of palette iteration - return 1st palette
+ return (palAllocData[0].objCount) ? palAllocData : NULL;
+ }
+
+ // validate palette Q pointer
+ assert(pStrtPal >= palAllocData && pStrtPal <= palAllocData + NUM_PALETTES - 1);
+
+ // return next active palette in list
+ while (++pStrtPal < palAllocData + NUM_PALETTES) {
+ if (pStrtPal->objCount)
+ // active palette found
+ return pStrtPal;
+ }
+
+ // non found
+ return NULL;
+}
+
+/**
+ * Sets the current background colour.
+ * @param colour Colour to set the background to
+ */
+void SetBgndColour(COLORREF colour) {
+ // update background colour struct
+ bgndColour = colour;
+
+ // Q the change to the video DAC
+ UpdateDACqueue(BGND_DAC_INDEX, 1, &bgndColour);
+}
+
+/**
+ * Builds the translucent palette from the current backgrounds palette.
+ * @param hPalette Handle to current background palette
+ */
+void CreateTranslucentPalette(SCNHANDLE hPalette) {
+ // get a pointer to the palette
+ PALETTE *pPal = (PALETTE *)LockMem(hPalette);
+
+ // leave background colour alone
+ transPalette[0] = 0;
+
+ for (uint i = 0; i < FROM_LE_32(pPal->numColours); i++) {
+ // get the RGB colour model values
+ uint8 red = GetRValue(pPal->palRGB[i]);
+ uint8 green = GetGValue(pPal->palRGB[i]);
+ uint8 blue = GetBValue(pPal->palRGB[i]);
+
+ // calculate the Value field of the HSV colour model
+ unsigned val = (red > green) ? red : green;
+ val = (val > blue) ? val : blue;
+
+ // map the Value field to one of the 4 colours reserved for the translucent palette
+ val /= 63;
+ transPalette[i + 1] = (uint8)((val == 0) ? 0 : val + COL_HILIGHT - 1);
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/palette.h b/engines/tinsel/palette.h
new file mode 100644
index 0000000000..fdc4826dbd
--- /dev/null
+++ b/engines/tinsel/palette.h
@@ -0,0 +1,144 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Palette Allocator Definitions
+ */
+
+#ifndef TINSEL_PALETTE_H // prevent multiple includes
+#define TINSEL_PALETTE_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+typedef uint32 COLORREF;
+
+#define RGB(r,g,b) ((COLORREF)TO_LE_32(((uint8)(r)|((uint16)(g)<<8))|(((uint32)(uint8)(b))<<16)))
+
+#define GetRValue(rgb) ((uint8)(FROM_LE_32(rgb)))
+#define GetGValue(rgb) ((uint8)(((uint16)(FROM_LE_32(rgb))) >> 8))
+#define GetBValue(rgb) ((uint8)((FROM_LE_32(rgb))>>16))
+
+enum {
+ MAX_COLOURS = 256, //!< maximum number of colours - for VGA 256
+ BITS_PER_PIXEL = 8, //!< number of bits per pixel for VGA 256
+ MAX_INTENSITY = 255, //!< the biggest value R, G or B can have
+ NUM_PALETTES = 3, //!< number of palettes
+
+ // Discworld has some fixed apportioned bits in the palette.
+ BGND_DAC_INDEX = 0, //!< index of background colour in Video DAC
+ FGND_DAC_INDEX = 1, //!< index of first foreground colour in Video DAC
+ TBLUE1 = 228, //!< Blue used in translucent rectangles
+ TBLUE2 = 229, //!< Blue used in translucent rectangles
+ TBLUE3 = 230, //!< Blue used in translucent rectangles
+ TBLUE4 = 231, //!< Blue used in translucent rectangles
+ TALKFONT_COL = 233
+};
+
+// some common colours
+
+#define BLACK (RGB(0, 0, 0))
+#define WHITE (RGB(MAX_INTENSITY, MAX_INTENSITY, MAX_INTENSITY))
+#define RED (RGB(MAX_INTENSITY, 0, 0))
+#define GREEN (RGB(0, MAX_INTENSITY, 0))
+#define BLUE (RGB(0, 0, MAX_INTENSITY))
+#define YELLOW (RGB(MAX_INTENSITY, MAX_INTENSITY, 0))
+#define MAGENTA (RGB(MAX_INTENSITY, 0, MAX_INTENSITY))
+#define CYAN (RGB(0, MAX_INTENSITY, MAX_INTENSITY))
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** hardware palette structure */
+struct PALETTE {
+ int32 numColours; //!< number of colours in the palette
+ COLORREF palRGB[MAX_COLOURS]; //!< actual palette colours
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/** palette queue structure */
+struct PALQ {
+ SCNHANDLE hPal; //!< handle to palette data struct
+ int objCount; //!< number of objects using this palette
+ int posInDAC; //!< palette position in the video DAC
+ int numColours; //!< number of colours in the palette
+};
+
+
+#define PALETTE_MOVED 0x8000 // when this bit is set in the "posInDAC"
+ // field - the palette entry has moved
+
+// Translucent objects have NULL pPal
+#define HasPalMoved(pPal) (((pPal) != NULL) && ((pPal)->posInDAC & PALETTE_MOVED))
+
+
+/*----------------------------------------------------------------------*\
+|* Palette Manager Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void ResetPalAllocator(void); // wipe out all palettes
+
+#ifdef DEBUG
+void PaletteStats(void); // Shows the maximum number of palettes used at once
+#endif
+
+void PalettesToVideoDAC(void); // Update the video DAC with palettes currently the the DAC queue
+
+void UpdateDACqueueHandle(
+ int posInDAC, // position in video DAC
+ int numColours, // number of colours in palette
+ SCNHANDLE hPalette); // handle to palette
+
+void UpdateDACqueue( // places a palette in the video DAC queue
+ int posInDAC, // position in video DAC
+ int numColours, // number of colours in palette
+ COLORREF *pColours); // list of RGB tripples
+
+PALQ *AllocPalette( // allocate a new palette
+ SCNHANDLE hNewPal); // palette to allocate
+
+void FreePalette( // free a palette allocated with "AllocPalette"
+ PALQ *pFreePal); // palette queue entry to free
+
+PALQ *FindPalette( // find a palette in the palette queue
+ SCNHANDLE hSrchPal); // palette to search for
+
+void SwapPalette( // swaps palettes at the specified palette queue position
+ PALQ *pPalQ, // palette queue position
+ SCNHANDLE hNewPal); // new palette
+
+PALQ *GetNextPalette( // returns the next palette in the queue
+ PALQ *pStrtPal); // queue position to start from - when NULL will start from beginning of queue
+
+COLORREF GetBgndColour(void); // returns current background colour
+
+void SetBgndColour( // sets current background colour
+ COLORREF colour); // colour to set the background to
+
+void CreateTranslucentPalette(SCNHANDLE BackPal);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PALETTE_H
diff --git a/engines/tinsel/pcode.cpp b/engines/tinsel/pcode.cpp
new file mode 100644
index 0000000000..023417fe3c
--- /dev/null
+++ b/engines/tinsel/pcode.cpp
@@ -0,0 +1,593 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Virtual processor.
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/events.h" // 'POINTED' etc.
+#include "tinsel/handle.h" // LockMem()
+#include "tinsel/inventory.h" // for inventory id's
+#include "tinsel/pcode.h" // opcodes etc.
+#include "tinsel/scn.h" // FindChunk()
+#include "tinsel/serializer.h"
+#include "tinsel/tinlib.h" // Library routines
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+extern int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState);
+
+//----------------- LOCAL DEFINES --------------------
+
+/** list of all opcodes */
+enum OPCODE {
+ OP_HALT = 0, //!< end of program
+ OP_IMM = 1, //!< loads signed immediate onto stack
+ OP_ZERO = 2, //!< loads zero onto stack
+ OP_ONE = 3, //!< loads one onto stack
+ OP_MINUSONE = 4, //!< loads minus one onto stack
+ OP_STR = 5, //!< loads string offset onto stack
+ OP_FILM = 6, //!< loads film offset onto stack
+ OP_FONT = 7, //!< loads font offset onto stack
+ OP_PAL = 8, //!< loads palette offset onto stack
+ OP_LOAD = 9, //!< loads local variable onto stack
+ OP_GLOAD = 10, //!< loads global variable onto stack - long offset to variable
+ OP_STORE = 11, //!< pops stack and stores in local variable - long offset to variable
+ OP_GSTORE = 12, //!< pops stack and stores in global variable - long offset to variable
+ OP_CALL = 13, //!< procedure call
+ OP_LIBCALL = 14, //!< library procedure call - long offset to procedure
+ OP_RET = 15, //!< procedure return
+ OP_ALLOC = 16, //!< allocate storage on stack
+ OP_JUMP = 17, //!< unconditional jump - signed word offset
+ OP_JMPFALSE = 18, //!< conditional jump - signed word offset
+ OP_JMPTRUE = 19, //!< conditional jump - signed word offset
+ OP_EQUAL = 20, //!< tests top two items on stack for equality
+ OP_LESS, //!< tests top two items on stack
+ OP_LEQUAL, //!< tests top two items on stack
+ OP_NEQUAL, //!< tests top two items on stack
+ OP_GEQUAL, //!< tests top two items on stack
+ OP_GREAT = 25, //!< tests top two items on stack
+ OP_PLUS, //!< adds top two items on stack and replaces with result
+ OP_MINUS, //!< subs top two items on stack and replaces with result
+ OP_LOR, //!< logical or of top two items on stack and replaces with result
+ OP_MULT, //!< multiplies top two items on stack and replaces with result
+ OP_DIV = 30, //!< divides top two items on stack and replaces with result
+ OP_MOD, //!< divides top two items on stack and replaces with modulus
+ OP_AND, //!< bitwise ands top two items on stack and replaces with result
+ OP_OR, //!< bitwise ors top two items on stack and replaces with result
+ OP_EOR, //!< bitwise exclusive ors top two items on stack and replaces with result
+ OP_LAND = 35, //!< logical ands top two items on stack and replaces with result
+ OP_NOT, //!< logical nots top item on stack
+ OP_COMP, //!< complements top item on stack
+ OP_NEG, //!< negates top item on stack
+ OP_DUP, //!< duplicates top item on stack
+ OP_ESCON = 40, //!< start of escapable sequence
+ OP_ESCOFF = 41, //!< end of escapable sequence
+ OP_CIMM, //!< loads signed immediate onto stack (special to case statements)
+ OP_CDFILM //!< loads film offset onto stack but not in current scene
+};
+
+// modifiers for the above opcodes
+#define OPSIZE8 0x40 //!< when this bit is set - the operand size is 8 bits
+#define OPSIZE16 0x80 //!< when this bit is set - the operand size is 16 bits
+
+#define OPMASK 0x3F //!< mask to isolate the opcode
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int32 *pGlobals = 0; // global vars
+
+static int numGlobals = 0; // How many global variables to save/restore
+
+static INT_CONTEXT *icList = 0;
+
+/**
+ * Keeps the code array pointer up to date.
+ */
+void LockCode(INT_CONTEXT *ic) {
+ if (ic->GSort == GS_MASTER)
+ ic->code = (byte *)FindChunk(MASTER_SCNHANDLE, CHUNK_PCODE);
+ else
+ ic->code = (byte *)LockMem(ic->hCode);
+}
+
+/**
+ * Find a free interpret context and allocate it to the calling process.
+ */
+static INT_CONTEXT *AllocateInterpretContext(GSORT gsort) {
+ INT_CONTEXT *pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort == GS_NONE) {
+ pic->pProc = g_scheduler->getCurrentProcess();
+ pic->GSort = gsort;
+ return pic;
+ }
+#ifdef DEBUG
+ else {
+ if (pic->pProc == g_scheduler->getCurrentProcess())
+ error("Found unreleased interpret context");
+ }
+#endif
+ }
+
+ error("Out of interpret contexts");
+}
+
+/**
+ * Normal release of an interpret context.
+ * Called from the end of Interpret().
+ */
+static void FreeInterpretContextPi(INT_CONTEXT *pic) {
+ pic->GSort = GS_NONE;
+}
+
+/**
+ * Free interpret context owned by a dying process.
+ * Ensures that interpret contexts don't get lost when an Interpret()
+ * call doesn't complete.
+ */
+void FreeInterpretContextPr(PROCESS *pProc) {
+ INT_CONTEXT *pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort != GS_NONE && pic->pProc == pProc) {
+ pic->GSort = GS_NONE;
+ break;
+ }
+ }
+}
+
+/**
+ * Free all interpret contexts except for the master script's
+ */
+void FreeMostInterpretContexts(void) {
+ INT_CONTEXT *pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort != GS_MASTER) {
+ pic->GSort = GS_NONE;
+ }
+ }
+}
+
+/**
+ * Free the master script's interpret context.
+ */
+void FreeMasterInterpretContext(void) {
+ INT_CONTEXT *pic;
+ int i;
+
+ for (i = 0, pic = icList; i < MAX_INTERPRET; i++, pic++) {
+ if (pic->GSort == GS_MASTER) {
+ pic->GSort = GS_NONE;
+ return;
+ }
+ }
+}
+
+/**
+ * Allocate and initialise an interpret context.
+ * Called from a process prior to Interpret().
+ * @param gsort which sort of code
+ * @param hCode Handle to code to execute
+ * @param event Causal event
+ * @param hpoly Associated polygon (if any)
+ * @param actorId Associated actor (if any)
+ * @param pinvo Associated inventory object
+ */
+INT_CONTEXT *InitInterpretContext(GSORT gsort, SCNHANDLE hCode, USER_EVENT event,
+ HPOLYGON hpoly, int actorid, INV_OBJECT *pinvo) {
+ INT_CONTEXT *ic;
+
+ ic = AllocateInterpretContext(gsort);
+
+ // Previously parameters to Interpret()
+ ic->hCode = hCode;
+ LockCode(ic);
+ ic->event = event;
+ ic->hpoly = hpoly;
+ ic->actorid = actorid;
+ ic->pinvo = pinvo;
+
+ // Previously local variables in Interpret()
+ ic->bHalt = false; // set to exit interpeter
+ ic->escOn = false;
+ ic->myescEvent = 0; // only initialised to prevent compiler warning!
+ ic->sp = 0;
+ ic->bp = ic->sp + 1;
+ ic->ip = 0; // start of code
+
+ ic->resumeState = RES_NOT;
+
+ return ic;
+}
+
+/**
+ * Allocate and initialise an interpret context with restored data.
+ */
+INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric) {
+ INT_CONTEXT *ic;
+
+ ic = AllocateInterpretContext(GS_NONE); // Sort will soon be overridden
+
+ memcpy(ic, ric, sizeof(INT_CONTEXT));
+ ic->pProc = g_scheduler->getCurrentProcess();
+ ic->resumeState = RES_1;
+
+ LockCode(ic);
+
+ return ic;
+}
+
+/**
+ * Allocates enough RAM to hold the global Glitter variables.
+ */
+void RegisterGlobals(int num) {
+ if (pGlobals == NULL) {
+ numGlobals = num;
+
+ // Allocate RAM for pGlobals and make sure it's allocated
+ pGlobals = (int32 *)calloc(numGlobals, sizeof(int32));
+ if (pGlobals == NULL) {
+ error("Cannot allocate memory for global data");
+ }
+
+ // Allocate RAM for interpret contexts and make sure it's allocated
+ icList = (INT_CONTEXT *)calloc(MAX_INTERPRET, sizeof(INT_CONTEXT));
+ if (icList == NULL) {
+ error("Cannot allocate memory for interpret contexts");
+ }
+
+ g_scheduler->setResourceCallback(FreeInterpretContextPr);
+ } else {
+ // Check size is still the same
+ assert(numGlobals == num);
+
+ memset(pGlobals, 0, numGlobals * sizeof(int32));
+ memset(icList, 0, MAX_INTERPRET * sizeof(INT_CONTEXT));
+ }
+}
+
+void FreeGlobals(void) {
+ free(pGlobals);
+ pGlobals = NULL;
+
+ free(icList);
+ icList = NULL;
+}
+
+/**
+ * (Un)serialize the global data for save/restore game.
+ */
+void syncGlobInfo(Serializer &s) {
+ for (int i = 0; i < numGlobals; i++) {
+ s.syncAsSint32LE(pGlobals[i]);
+ }
+}
+
+/**
+ * (Un)serialize an interpreter context for save/restore game.
+ */
+void INT_CONTEXT::syncWithSerializer(Serializer &s) {
+ if (s.isLoading()) {
+ // Null out the pointer fields
+ pProc = NULL;
+ code = NULL;
+ pinvo = NULL;
+ }
+ // Write out used fields
+ s.syncAsUint32LE(GSort);
+ s.syncAsUint32LE(hCode);
+ s.syncAsUint32LE(event);
+ s.syncAsSint32LE(hpoly);
+ s.syncAsSint32LE(actorid);
+
+ for (int i = 0; i < PCODE_STACK_SIZE; ++i)
+ s.syncAsSint32LE(stack[i]);
+
+ s.syncAsSint32LE(sp);
+ s.syncAsSint32LE(bp);
+ s.syncAsSint32LE(ip);
+ s.syncAsUint32LE(bHalt);
+ s.syncAsUint32LE(escOn);
+ s.syncAsSint32LE(myescEvent);
+}
+
+/**
+ * Return pointer to and size of global data for save/restore game.
+ */
+void SaveInterpretContexts(INT_CONTEXT *sICInfo) {
+ memcpy(sICInfo, icList, MAX_INTERPRET * sizeof(INT_CONTEXT));
+}
+
+/**
+ * Fetch (and sign extend, if necessary) a 8/16/32 bit value from the code
+ * stream and advance the instruction pointer accordingly.
+ */
+static int32 Fetch(byte opcode, byte *code, int &ip) {
+ int32 tmp;
+ if (opcode & OPSIZE8) {
+ // Fetch and sign extend a 8 bit value to 32 bits.
+ tmp = *(int8 *)(code + ip);
+ ip += 1;
+ } else if (opcode & OPSIZE16) {
+ // Fetch and sign extend a 16 bit value to 32 bits.
+ tmp = (int16)READ_LE_UINT16(code + ip);
+ ip += 2;
+ } else {
+ // Fetch a 32 bit value.
+ tmp = (int32)READ_LE_UINT32(code + ip);
+ ip += 4;
+ }
+ return tmp;
+}
+
+/**
+ * Interprets the PCODE instructions in the code array.
+ */
+void Interpret(CORO_PARAM, INT_CONTEXT *ic) {
+ do {
+ int tmp, tmp2;
+ int ip = ic->ip;
+ byte opcode = ic->code[ip++];
+ debug(7, " Opcode %d (-> %d)", opcode, opcode & OPMASK);
+ switch (opcode & OPMASK) {
+ case OP_HALT: // end of program
+
+ ic->bHalt = true;
+ break;
+
+ case OP_IMM: // loads immediate data onto stack
+ case OP_STR: // loads string handle onto stack
+ case OP_FILM: // loads film handle onto stack
+ case OP_CDFILM: // loads film handle onto stack
+ case OP_FONT: // loads font handle onto stack
+ case OP_PAL: // loads palette handle onto stack
+
+ ic->stack[++ic->sp] = Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_ZERO: // loads zero onto stack
+ ic->stack[++ic->sp] = 0;
+ break;
+
+ case OP_ONE: // loads one onto stack
+ ic->stack[++ic->sp] = 1;
+ break;
+
+ case OP_MINUSONE: // loads minus one onto stack
+ ic->stack[++ic->sp] = -1;
+ break;
+
+ case OP_LOAD: // loads local variable onto stack
+
+ ic->stack[++ic->sp] = ic->stack[ic->bp + Fetch(opcode, ic->code, ip)];
+ break;
+
+ case OP_GLOAD: // loads global variable onto stack
+
+ tmp = Fetch(opcode, ic->code, ip);
+ assert(0 <= tmp && tmp < numGlobals);
+ ic->stack[++ic->sp] = pGlobals[tmp];
+ break;
+
+ case OP_STORE: // pops stack and stores in local variable
+
+ ic->stack[ic->bp + Fetch(opcode, ic->code, ip)] = ic->stack[ic->sp--];
+ break;
+
+ case OP_GSTORE: // pops stack and stores in global variable
+
+ tmp = Fetch(opcode, ic->code, ip);
+ assert(0 <= tmp && tmp < numGlobals);
+ pGlobals[tmp] = ic->stack[ic->sp--];
+ break;
+
+ case OP_CALL: // procedure call
+
+ tmp = Fetch(opcode, ic->code, ip);
+ //assert(0 <= tmp && tmp < codeSize); // TODO: Verify jumps are not out of bounds
+ ic->stack[ic->sp + 1] = 0; // static link
+ ic->stack[ic->sp + 2] = ic->bp; // dynamic link
+ ic->stack[ic->sp + 3] = ip; // return address
+ ic->bp = ic->sp + 1; // set new base pointer
+ ip = tmp; // set ip to procedure address
+ break;
+
+ case OP_LIBCALL: // library procedure or function call
+
+ tmp = Fetch(opcode, ic->code, ip);
+ // NOTE: Interpret() itself is not using the coroutine facilities,
+ // but still accepts a CORO_PARAM, so from the outside it looks
+ // like a coroutine. In fact it may still acts as a kind of "proxy"
+ // for some underlying coroutine. To enable this, we just pass on
+ // 'coroParam' to CallLibraryRoutine(). If we then detect that
+ // coroParam was set to a non-zero value, this means that some
+ // coroutine code did run at some point, and we are now supposed
+ // to sleep or die -- hence, we 'return' if coroParam != 0.
+ //
+ // This works because Interpret() is fully re-entrant: If we return
+ // now and are later called again, then we will end up in the very
+ // same spot (i.e. here).
+ //
+ // The reasons we do it this way, instead of turning Interpret into
+ // a 'proper' coroutine are (1) we avoid implementation problems
+ // (CORO_INVOKE involves adding 'case' statements, but Interpret
+ // already has a huge switch/case, so that would not work out of the
+ // box), (2) we incurr less overhead, (3) it's easier to debug,
+ // (4) it's simply cool ;).
+ tmp2 = CallLibraryRoutine(coroParam, tmp, &ic->stack[ic->sp], ic, &ic->resumeState);
+ if (coroParam)
+ return;
+ ic->sp += tmp2;
+ LockCode(ic);
+ break;
+
+ case OP_RET: // procedure return
+
+ ic->sp = ic->bp - 1; // restore stack
+ ip = ic->stack[ic->sp + 3]; // return address
+ ic->bp = ic->stack[ic->sp + 2]; // restore previous base pointer
+ break;
+
+ case OP_ALLOC: // allocate storage on stack
+
+ ic->sp += Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_JUMP: // unconditional jump
+
+ ip = Fetch(opcode, ic->code, ip);
+ break;
+
+ case OP_JMPFALSE: // conditional jump
+
+ tmp = Fetch(opcode, ic->code, ip);
+ if (ic->stack[ic->sp--] == 0) {
+ // condition satisfied - do the jump
+ ip = tmp;
+ }
+ break;
+
+ case OP_JMPTRUE: // conditional jump
+
+ tmp = Fetch(opcode, ic->code, ip);
+ if (ic->stack[ic->sp--] != 0) {
+ // condition satisfied - do the jump
+ ip = tmp;
+ }
+ break;
+
+ case OP_EQUAL: // tests top two items on stack for equality
+ case OP_LESS: // tests top two items on stack
+ case OP_LEQUAL: // tests top two items on stack
+ case OP_NEQUAL: // tests top two items on stack
+ case OP_GEQUAL: // tests top two items on stack
+ case OP_GREAT: // tests top two items on stack
+ case OP_LOR: // logical or of top two items on stack and replaces with result
+ case OP_LAND: // logical ands top two items on stack and replaces with result
+
+ // pop one operand
+ ic->sp--;
+ assert(ic->sp >= 0);
+ tmp = ic->stack[ic->sp];
+ tmp2 = ic->stack[ic->sp + 1];
+
+ // replace other operand with result of operation
+ switch (opcode) {
+ case OP_EQUAL: tmp = (tmp == tmp2); break;
+ case OP_LESS: tmp = (tmp < tmp2); break;
+ case OP_LEQUAL: tmp = (tmp <= tmp2); break;
+ case OP_NEQUAL: tmp = (tmp != tmp2); break;
+ case OP_GEQUAL: tmp = (tmp >= tmp2); break;
+ case OP_GREAT: tmp = (tmp > tmp2); break;
+
+ case OP_LOR: tmp = (tmp || tmp2); break;
+ case OP_LAND: tmp = (tmp && tmp2); break;
+ }
+
+ ic->stack[ic->sp] = tmp;
+ break;
+
+ case OP_PLUS: // adds top two items on stack and replaces with result
+ case OP_MINUS: // subs top two items on stack and replaces with result
+ case OP_MULT: // multiplies top two items on stack and replaces with result
+ case OP_DIV: // divides top two items on stack and replaces with result
+ case OP_MOD: // divides top two items on stack and replaces with modulus
+ case OP_AND: // bitwise ands top two items on stack and replaces with result
+ case OP_OR: // bitwise ors top two items on stack and replaces with result
+ case OP_EOR: // bitwise exclusive ors top two items on stack and replaces with result
+
+ // pop one operand
+ ic->sp--;
+ assert(ic->sp >= 0);
+ tmp = ic->stack[ic->sp];
+ tmp2 = ic->stack[ic->sp + 1];
+
+ // replace other operand with result of operation
+ switch (opcode) {
+ case OP_PLUS: tmp += tmp2; break;
+ case OP_MINUS: tmp -= tmp2; break;
+ case OP_MULT: tmp *= tmp2; break;
+ case OP_DIV: tmp /= tmp2; break;
+ case OP_MOD: tmp %= tmp2; break;
+ case OP_AND: tmp &= tmp2; break;
+ case OP_OR: tmp |= tmp2; break;
+ case OP_EOR: tmp ^= tmp2; break;
+ }
+ ic->stack[ic->sp] = tmp;
+ break;
+
+ case OP_NOT: // logical nots top item on stack
+
+ ic->stack[ic->sp] = !ic->stack[ic->sp];
+ break;
+
+ case OP_COMP: // complements top item on stack
+ ic->stack[ic->sp] = ~ic->stack[ic->sp];
+ break;
+
+ case OP_NEG: // negates top item on stack
+ ic->stack[ic->sp] = -ic->stack[ic->sp];
+ break;
+
+ case OP_DUP: // duplicates top item on stack
+ ic->stack[ic->sp + 1] = ic->stack[ic->sp];
+ ic->sp++;
+ break;
+
+ case OP_ESCON:
+ ic->escOn = true;
+ ic->myescEvent = GetEscEvents();
+ break;
+
+ case OP_ESCOFF:
+ ic->escOn = false;
+ break;
+
+ default:
+ error("Interpret() - Unknown opcode");
+ }
+
+ // check for stack under-overflow
+ assert(ic->sp >= 0 && ic->sp < PCODE_STACK_SIZE);
+ ic->ip = ip;
+ } while (!ic->bHalt);
+
+ // make sure stack is unwound
+ assert(ic->sp == 0);
+
+ FreeInterpretContextPi(ic);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/pcode.h b/engines/tinsel/pcode.h
new file mode 100644
index 0000000000..1c7e0a942c
--- /dev/null
+++ b/engines/tinsel/pcode.h
@@ -0,0 +1,155 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Virtual processor definitions
+ */
+
+#ifndef TINSEL_PCODE_H // prevent multiple includes
+#define TINSEL_PCODE_H
+
+#include "tinsel/events.h" // for USER_EVENT
+#include "tinsel/sched.h" // for PROCESS
+
+namespace Tinsel {
+
+// forward declaration
+class Serializer;
+struct INV_OBJECT;
+
+enum RESUME_STATE {
+ RES_NOT, RES_1, RES_2
+};
+
+enum {
+ PCODE_STACK_SIZE = 128 //!< interpeters stack size
+};
+
+enum GSORT {
+ GS_NONE, GS_ACTOR, GS_MASTER, GS_POLYGON, GS_INVENTORY, GS_SCENE
+};
+
+struct INT_CONTEXT {
+
+ // Elements for interpret context management
+ PROCESS *pProc; //!< processes owning this context
+ GSORT GSort; //!< sort of this context
+
+ // Previously parameters to Interpret()
+ SCNHANDLE hCode; //!< scene handle of the code to execute
+ byte *code; //!< pointer to the code to execute
+ USER_EVENT event; //!< causal event
+ HPOLYGON hpoly; //!< associated polygon (if any)
+ int actorid; //!< associated actor (if any)
+ INV_OBJECT *pinvo; //!< associated inventory object
+
+ // Previously local variables in Interpret()
+ int32 stack[PCODE_STACK_SIZE]; //!< interpeters run time stack
+ int sp; //!< stack pointer
+ int bp; //!< base pointer
+ int ip; //!< instruction pointer
+ bool bHalt; //!< set to exit interpeter
+ bool escOn;
+ int myescEvent; //!< only initialised to prevent compiler warning!
+
+ RESUME_STATE resumeState;
+
+ void syncWithSerializer(Serializer &s);
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Interpreter Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+void Interpret(CORO_PARAM, INT_CONTEXT *ic); // Interprets the PCODE instructions in the code array
+
+INT_CONTEXT *InitInterpretContext(
+ GSORT gsort,
+ SCNHANDLE hCode, // code to execute
+ USER_EVENT event, // causal event
+ HPOLYGON hpoly, // associated polygon (if any)
+ int actorid, // associated actor (if any)
+ INV_OBJECT *pinvo); // associated inventory object
+
+INT_CONTEXT *RestoreInterpretContext(INT_CONTEXT *ric);
+
+void FreeMostInterpretContexts(void);
+void FreeMasterInterpretContext(void);
+
+void SaveInterpretContexts(INT_CONTEXT *sICInfo);
+
+void RegisterGlobals(int num);
+void FreeGlobals(void);
+
+
+#define MAX_INTERPRET (NUM_PROCESS - 20)
+
+/*----------------------------------------------------------------------*\
+|* Library Procedure and Function codes parameter enums *|
+\*----------------------------------------------------------------------*/
+
+#define TAG_DEF 0 // For tagactor()
+#define TAG_Q1TO3 1 // tag types
+#define TAG_Q1TO4 2 // tag types
+
+#define CONV_DEF 0 //
+#define CONV_BOTTOM 1 // conversation() parameter
+#define CONV_END 2 //
+
+#define CONTROL_OFF 0 // control()
+#define CONTROL_ON 1 // parameter
+#define CONTROL_OFFV 2 //
+#define CONTROL_OFFV2 3 //
+#define CONTROL_STARTOFF 4 //
+
+#define NULL_ACTOR (-1) // For actor parameters
+#define LEAD_ACTOR (-2) //
+
+#define RAND_NORM 0 // For random() frills
+#define RAND_NORPT 1 //
+
+#define D_UP 1
+#define D_DOWN 0
+
+#define TW_START 1 // topwindow() parameter
+#define TW_END 2 //
+
+#define MIDI_DEF 0
+#define MIDI_LOOP 1
+
+#define TRANS_DEF 0
+#define TRANS_CUT 1
+#define TRANS_FADE 2
+
+#define FM_IN 0 //
+#define FM_OUT 1 // fademidi()
+
+#define FG_ON 0 //
+#define FG_OFF 1 // FrameGrab()
+
+#define ST_ON 0 //
+#define ST_OFF 1 // SubTitles()
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PCODE_H
diff --git a/engines/tinsel/pdisplay.cpp b/engines/tinsel/pdisplay.cpp
new file mode 100644
index 0000000000..b5488da3e8
--- /dev/null
+++ b/engines/tinsel/pdisplay.cpp
@@ -0,0 +1,652 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * CursorPositionProcess()
+ * TagProcess()
+ * PointProcess()
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+//extern int Overrun; // The overrun counter, in DOS_DW.C
+
+extern int newestString; // The overrun counter, in STRRES.C
+#endif
+
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define LPOSX 295 // X-co-ord of lead actor's position display
+#define CPOSX 24 // X-co-ord of cursor's position display
+#define OPOSX SCRN_CENTRE_X // X-co-ord of overrun counter's display
+#define SPOSX SCRN_CENTRE_X // X-co-ord of string numbner's display
+
+#define POSY 0 // Y-co-ord of these position displays
+
+enum HotSpotTag {
+ NO_HOTSPOT_TAG,
+ POLY_HOTSPOT_TAG,
+ ACTOR_HOTSPOT_TAG
+};
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static bool DispPath = false;
+static bool bShowString = false;
+
+static int TaggedActor = 0;
+static HPOLYGON hTaggedPolygon = NOPOLY;
+
+static enum { TAGS_OFF, TAGS_ON } TagsActive = TAGS_ON;
+
+
+#ifdef DEBUG
+/**
+ * Displays the cursor and lead actor's co-ordinates and the overrun
+ * counter. Also which path polygon the cursor is in, if required.
+ *
+ * This process is only started up if a Glitter showpos() call is made.
+ * Obviously, this is for testing purposes only...
+ */
+void CursorPositionProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ int prevsX, prevsY; // Last screen top left
+ int prevcX, prevcY; // Last displayed cursor position
+ int prevlX, prevlY; // Last displayed lead actor position
+// int prevOver; // Last displayed overrun
+ int prevString; // Last displayed string number
+
+ OBJECT *cpText; // cursor position text object pointer
+ OBJECT *cpathText; // cursor path text object pointer
+ OBJECT *rpText; // text object pointer
+// OBJECT *opText; // text object pointer
+ OBJECT *spText; // string number text object pointer
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->prevsX = -1;
+ _ctx->prevsY = -1;
+ _ctx->prevcX = -1;
+ _ctx->prevcY = -1;
+ _ctx->prevlX = -1;
+ _ctx->prevlY = -1;
+// _ctx->prevOver = -1;
+ _ctx->prevString = -1;
+
+ _ctx->cpText = NULL;
+ _ctx->cpathText = NULL;
+ _ctx->rpText = NULL;
+// _ctx->opText = NULL;
+ _ctx->spText = NULL;
+
+
+ int aniX, aniY; // cursor/lead actor position
+ int Loffset, Toffset; // Screen top left
+
+ char PositionString[64]; // sprintf() things into here
+
+ PMACTOR pActor; // Lead actor
+
+ while (1) {
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*-----------------------------------*\
+ | Cursor's position and path display. |
+ \*-----------------------------------*/
+ GetCursorXY(&aniX, &aniY, false);
+
+ // Change in cursor position?
+ if (aniX != _ctx->prevcX || aniY != _ctx->prevcY ||
+ Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) {
+ // kill current text objects
+ if (_ctx->cpText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpText);
+ }
+ if (_ctx->cpathText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->cpathText);
+ _ctx->cpathText = NULL;
+ }
+
+ // New text objects
+ sprintf(PositionString, "%d %d", aniX + Loffset, aniY + Toffset);
+ _ctx->cpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, CPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+ if (DispPath) {
+ HPOLYGON hp = InPolygon(aniX + Loffset, aniY + Toffset, PATH);
+ if (hp == NOPOLY)
+ sprintf(PositionString, "No path");
+ else
+ sprintf(PositionString, "%d,%d %d,%d %d,%d %d,%d",
+ PolyCornerX(hp, 0), PolyCornerY(hp, 0),
+ PolyCornerX(hp, 1), PolyCornerY(hp, 1),
+ PolyCornerX(hp, 2), PolyCornerY(hp, 2),
+ PolyCornerX(hp, 3), PolyCornerY(hp, 3));
+ _ctx->cpathText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, 4, POSY+ 10, hTagFontHandle(), 0);
+ }
+
+ // update previous position
+ _ctx->prevcX = aniX;
+ _ctx->prevcY = aniY;
+ }
+
+#if 0
+ /*------------------------*\
+ | Overrun counter display. |
+ \*------------------------*/
+ if (Overrun != _ctx->prevOver) {
+ // kill current text objects
+ if (_ctx->opText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->opText);
+ }
+
+ sprintf(PositionString, "%d", Overrun);
+ _ctx->opText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, OPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+
+ // update previous value
+ _ctx->prevOver = Overrun;
+ }
+#endif
+
+ /*----------------------*\
+ | Lead actor's position. |
+ \*----------------------*/
+ pActor = GetMover(LEAD_ACTOR);
+ if (pActor && pActor->MActorState == NORM_MACTOR) {
+ // get lead's animation position
+ GetActorPos(LEAD_ACTOR, &aniX, &aniY);
+
+ // Change in position?
+ if (aniX != _ctx->prevlX || aniY != _ctx->prevlY ||
+ Loffset != _ctx->prevsX || Toffset != _ctx->prevsY) {
+ // Kill current text objects
+ if (_ctx->rpText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->rpText);
+ }
+
+ // create new text object list
+ sprintf(PositionString, "%d %d", aniX, aniY);
+ _ctx->rpText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, LPOSX, POSY, hTagFontHandle(), TXT_CENTRE);
+
+ // update previous position
+ _ctx->prevlX = aniX;
+ _ctx->prevlY = aniY;
+ }
+ }
+
+ /*-------------*\
+ | String number |
+ \*-------------*/
+ if (bShowString && newestString != _ctx->prevString) {
+ // kill current text objects
+ if (_ctx->spText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->spText);
+ }
+
+ sprintf(PositionString, "String: %d", newestString);
+ _ctx->spText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), PositionString,
+ 0, SPOSX, POSY+10, hTalkFontHandle(), TXT_CENTRE);
+
+ // update previous value
+ _ctx->prevString = newestString;
+ }
+
+ // update previous playfield position
+ _ctx->prevsX = Loffset;
+ _ctx->prevsY = Toffset;
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+ CORO_END_CODE;
+}
+#endif
+
+/**
+ * Tag process keeps us updated as to which tagged actor is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+static void SaveTaggedActor(int ano) {
+ TaggedActor = ano;
+}
+
+/**
+ * Tag process keeps us updated as to which tagged actor is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+int GetTaggedActor(void) {
+ return TaggedActor;
+}
+
+/**
+ * Tag process keeps us updated as to which polygon is currently tagged
+ * (if one is). Tag process asks us for this information, as does User_Event().
+ */
+static void SaveTaggedPoly(HPOLYGON hp) {
+ hTaggedPolygon = hp;
+}
+
+HPOLYGON GetTaggedPoly(void) {
+ return hTaggedPolygon;
+}
+
+/**
+ * Given cursor position and an actor number, ascertains whether the
+ * cursor is within the actor's tag area.
+ * Returns TRUE for a positive result, FALSE for negative.
+ * If TRUE, the mid-top co-ordinates of the actor's tag area are also
+ * returned.
+ */
+static bool InHotSpot(int ano, int aniX, int aniY, int *pxtext, int *pytext) {
+ int Top, Bot; // Top and bottom limits of active area
+ int left, right; // left and right of active area
+ int qrt = 0; // 1/4 of height (sometimes 1/2)
+
+ // First check if within x-range
+ if (aniX > (left = GetActorLeft(ano)) && aniX < (right = GetActorRight(ano))) {
+ Top = GetActorTop(ano);
+ Bot = GetActorBottom(ano);
+
+ // y-range varies according to tag-type
+ switch (TagType(ano)) {
+ case TAG_DEF:
+ // Next to bottom 1/4 of the actor's area
+ qrt = (Bot - Top) >> 1; // Half actor's height
+ Top += qrt; // Top = mid-height
+
+ qrt = qrt >> 1; // Quarter height
+ Bot -= qrt; // Bot = 1/4 way up
+ break;
+
+ case TAG_Q1TO3:
+ // Top 3/4 of the actor's area
+ qrt = (Bot - Top) >> 2; // 1/4 actor's height
+ Bot -= qrt; // Bot = 1/4 way up
+ break;
+
+ case TAG_Q1TO4:
+ // All the actor's area
+ break;
+
+ default:
+ error("illegal tag area type");
+ }
+
+ // Now check if within y-range
+ if (aniY >= Top && aniY <= Bot) {
+ if (TagType(ano) == TAG_Q1TO3)
+ *pytext = Top + qrt;
+ else
+ *pytext = Top;
+ *pxtext = (left + right) / 2;
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * See if the cursor is over a tagged actor's hot-spot. If so, display
+ * the tag or, if tag already displayed, maintain the tag's position on
+ * the screen.
+ */
+static bool ActorTag(int curX, int curY, HotSpotTag *pTag, OBJECT **ppText) {
+ static int Loffset = 0, Toffset = 0; // Values when tag was displayed
+ int nLoff, nToff; // new values, to keep tag in place
+ int ano;
+ int xtext, ytext;
+ bool newActor;
+
+ // For each actor with a tag....
+ FirstTaggedActor();
+ while ((ano = NextTaggedActor()) != 0) {
+ if (InHotSpot(ano, curX, curY, &xtext, &ytext)) {
+ // Put up or maintain actor tag
+ if (*pTag != ACTOR_HOTSPOT_TAG)
+ newActor = true;
+ else if (ano != GetTaggedActor())
+ newActor = true; // Different actor
+ else
+ newActor = false; // Same actor
+
+ if (newActor) {
+ // Display actor's tag
+
+ if (*ppText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+
+ *pTag = ACTOR_HOTSPOT_TAG;
+ SaveTaggedActor(ano); // This actor tagged
+ SaveTaggedPoly(NOPOLY); // No tagged polygon
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ LoadStringRes(GetActorTag(ano), tBufferAddr(), TBUFSZ);
+ *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, xtext - Loffset, ytext - Toffset, hTagFontHandle(), TXT_CENTRE);
+ assert(*ppText); // Actor tag string produced NULL text
+ MultiSetZPosition(*ppText, Z_TAG_TEXT);
+ } else {
+ // Maintain actor tag's position
+
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != Loffset || nToff != Toffset) {
+ MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff);
+ Loffset = nLoff;
+ Toffset = nToff;
+ }
+ }
+ return true;
+ }
+ }
+
+ // No tagged actor
+ if (*pTag == ACTOR_HOTSPOT_TAG) {
+ *pTag = NO_HOTSPOT_TAG;
+ SaveTaggedActor(0);
+ }
+ return false;
+}
+
+/**
+ * Perhaps some comment in due course.
+ *
+ * Under control of PointProcess(), when the cursor is over a TAG or
+ * EXIT polygon, its pointState flag is set to POINTING. If its Glitter
+ * code contains a printtag() call, its tagState flag gets set to TAG_ON.
+ */
+static bool PolyTag(HotSpotTag *pTag, OBJECT **ppText) {
+ static int Loffset = 0, Toffset = 0; // Values when tag was displayed
+ int nLoff, nToff; // new values, to keep tag in place
+ HPOLYGON hp;
+ bool newPoly;
+ int shift;
+
+ int tagx, tagy; // Tag display co-ordinates
+ SCNHANDLE hTagtext; // Tag text
+
+ // For each polgon with a tag....
+ for (int i = 0; i < MAX_POLY; i++) {
+ hp = GetPolyHandle(i);
+
+ // Added code for un-tagged tags
+ if (hp != NOPOLY && PolyPointState(hp) == POINTING && PolyTagState(hp) != TAG_ON) {
+ // This poly is entitled to be tagged
+ if (hp != GetTaggedPoly()) {
+ if (*ppText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+ *ppText = NULL;
+ }
+ *pTag = POLY_HOTSPOT_TAG;
+ SaveTaggedActor(0); // No tagged actor
+ SaveTaggedPoly(hp); // This polygon tagged
+ }
+ return true;
+ } else if (hp != NOPOLY && PolyTagState(hp) == TAG_ON) {
+ // Put up or maintain polygon tag
+ if (*pTag != POLY_HOTSPOT_TAG)
+ newPoly = true; // A new polygon (no current)
+ else if (hp != GetTaggedPoly())
+ newPoly = true; // Different polygon
+ else
+ newPoly = false; // Same polygon
+
+ if (newPoly) {
+ if (*ppText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), *ppText);
+
+ *pTag = POLY_HOTSPOT_TAG;
+ SaveTaggedActor(0); // No tagged actor
+ SaveTaggedPoly(hp); // This polygon tagged
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ getPolyTagInfo(hp, &hTagtext, &tagx, &tagy);
+
+ int strLen;
+ if (PolyTagHandle(hp) != 0)
+ strLen = LoadStringRes(PolyTagHandle(hp), tBufferAddr(), TBUFSZ);
+ else
+ strLen = LoadStringRes(hTagtext, tBufferAddr(), TBUFSZ);
+
+ if (strLen == 0)
+ // No valid string returned, so leave ppText as NULL
+ ppText = NULL;
+ else {
+ // Handle displaying the tag text on-screen
+ *ppText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, tagx - Loffset, tagy - Toffset,
+ hTagFontHandle(), TXT_CENTRE);
+ assert(*ppText); // Polygon tag string produced NULL text
+ MultiSetZPosition(*ppText, Z_TAG_TEXT);
+
+
+ /*
+ * New feature: Don't go off the side of the background
+ */
+ shift = MultiRightmost(*ppText) + Loffset + 2;
+ if (shift >= BackgroundWidth()) // Not off right
+ MultiMoveRelXY(*ppText, BackgroundWidth() - shift, 0);
+ shift = MultiLeftmost(*ppText) + Loffset - 1;
+ if (shift <= 0) // Not off left
+ MultiMoveRelXY(*ppText, -shift, 0);
+ shift = MultiLowest(*ppText) + Toffset;
+ if (shift > BackgroundHeight()) // Not off bottom
+ MultiMoveRelXY(*ppText, 0, BackgroundHeight() - shift);
+ }
+ } else {
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != Loffset || nToff != Toffset) {
+ MultiMoveRelXY(*ppText, Loffset - nLoff, Toffset - nToff);
+ Loffset = nLoff;
+ Toffset = nToff;
+ }
+ }
+ return true;
+ }
+ }
+
+ // No tagged polygon
+ if (*pTag == POLY_HOTSPOT_TAG) {
+ *pTag = NO_HOTSPOT_TAG;
+ SaveTaggedPoly(NOPOLY);
+ }
+ return false;
+}
+
+/**
+ * Handle display of tagged actor and polygon tags.
+ * Tagged actor's get priority over polygons.
+ */
+void TagProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ HotSpotTag Tag;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pText = NULL;
+ _ctx->Tag = NO_HOTSPOT_TAG;
+
+ SaveTaggedActor(0); // No tagged actor yet
+ SaveTaggedPoly(NOPOLY); // No tagged polygon yet
+
+ while (1) {
+ if (TagsActive == TAGS_ON) {
+ int curX, curY; // cursor position
+ while (!GetCursorXYNoWait(&curX, &curY, true))
+ CORO_SLEEP(1);
+
+ if (!ActorTag(curX, curY, &_ctx->Tag, &_ctx->pText)
+ && !PolyTag(&_ctx->Tag, &_ctx->pText)) {
+ // Nothing tagged. Remove tag, if there is one
+ if (_ctx->pText) {
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _ctx->pText = NULL;
+ }
+ }
+ } else {
+ SaveTaggedActor(0);
+ SaveTaggedPoly(NOPOLY);
+
+ // Remove tag, if there is one
+ if (_ctx->pText) {
+ // kill current text objects
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _ctx->pText = NULL;
+ _ctx->Tag = NO_HOTSPOT_TAG;
+ }
+ }
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Called from PointProcess() as appropriate.
+ */
+static void enteringpoly(HPOLYGON hp) {
+ SetPolyPointState(hp, POINTING);
+
+ RunPolyTinselCode(hp, POINTED, BE_NONE, false);
+}
+
+/**
+ * Called from PointProcess() as appropriate.
+ */
+static void leavingpoly(HPOLYGON hp) {
+ SetPolyPointState(hp, NOT_POINTING);
+
+ if (PolyTagState(hp) == TAG_ON) {
+ // Delete this tag entry
+ SetPolyTagState(hp, TAG_OFF);
+ }
+}
+
+/**
+ * For TAG and EXIT polygons, monitor cursor entering and leaving.
+ * Maintain the polygons' pointState and tagState flags accordingly.
+ * Also run the polygon's Glitter code when the cursor enters.
+ */
+void PointProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ int aniX, aniY; // cursor/tagged actor position
+ while (!GetCursorXYNoWait(&aniX, &aniY, true))
+ CORO_SLEEP(1);
+
+ /*----------------------------------*\
+ | For polygons of type TAG and EXIT. |
+ \*----------------------------------*/
+ for (int i = 0; i < MAX_POLY; i++) {
+ HPOLYGON hp = GetPolyHandle(i);
+
+ if (hp != NOPOLY && (PolyType(hp) == TAG || PolyType(hp) == EXIT)) {
+ if (PolyPointState(hp) == NOT_POINTING) {
+ if (IsInPolygon(aniX, aniY, hp)) {
+ enteringpoly(hp);
+ }
+ } else if (PolyPointState(hp) == POINTING) {
+ if (!IsInPolygon(aniX, aniY, hp)) {
+ leavingpoly(hp);
+ }
+ }
+ }
+ }
+
+ // allow re-scheduling
+ CORO_SLEEP(1);
+ }
+
+ CORO_END_CODE;
+}
+
+void DisableTags(void) {
+ TagsActive = TAGS_OFF;
+}
+
+void EnableTags(void) {
+ TagsActive = TAGS_ON;
+}
+
+bool DisableTagsIfEnabled(void) {
+ if (TagsActive == TAGS_OFF)
+ return false;
+ else {
+ TagsActive = TAGS_OFF;
+ return true;
+ }
+}
+
+/**
+ * For testing purposes only.
+ * Causes CursorPositionProcess() to display, or not, the path that the
+ * cursor is in.
+ */
+void TogglePathDisplay(void) {
+ DispPath ^= 1; // Toggle path display (XOR with true)
+}
+
+
+void setshowstring(void) {
+ bShowString = true;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/pid.h b/engines/tinsel/pid.h
new file mode 100644
index 0000000000..c2af1a5fcb
--- /dev/null
+++ b/engines/tinsel/pid.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * List of all process identifiers
+ */
+
+#ifndef TINSEL_PID_H // prevent multiple includes
+#define TINSEL_PID_H
+
+namespace Tinsel {
+
+#define PID_DESTROY 0x8000 // process id of any process that is to be destroyed between scenes
+
+#define PID_EFFECTS (0x0010 | PID_DESTROY) // generic special effects process id
+#define PID_FLASH (PID_EFFECTS + 1) // flash colour process
+#define PID_CYCLE (PID_EFFECTS + 2) // cycle colour range process
+#define PID_MORPH (PID_EFFECTS + 3) // morph process
+#define PID_FADER (PID_EFFECTS + 4) // fader process
+#define PID_FADE_BGND (PID_EFFECTS + 5) // fade background colour process
+
+#define PID_BACKGND (0x0020 | PID_DESTROY) // background update process id
+
+#define PID_MOUSE 0x0030 // mouse button checking process id
+
+#define PID_JOYSTICK 0x0040 // joystick button checking process id
+
+#define PID_KEYBOARD 0x0050 // keyboard scanning process
+
+#define PID_CURSOR 0x0060 // cursor process
+#define PID_CUR_TRAIL (PID_CURSOR + 1) // cursor trail process
+
+#define PID_SCROLL (0x0070 | PID_DESTROY) // scroll process
+
+#define PID_INVENTORY 0x0080 // inventory process
+
+#define PID_POSITION (0x0090 | PID_DESTROY) // cursor position process
+
+#define PID_TAG (0x00A0 | PID_DESTROY) // tag process
+
+#define PID_TCODE (0x00B0 | PID_DESTROY) // tinsel code process
+
+#define PID_MASTER_SCR 0x00C0 // tinsel master script process
+
+#define PID_MACTOR (0x00D0 | PID_DESTROY) // moving actor process
+
+#define PID_REEL (0x00E0 | PID_DESTROY) // process for each film reel
+
+#define PID_MIDI (0x00F0 | PID_DESTROY) // process to poll MIDI sound driver
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_PID_H
diff --git a/engines/tinsel/play.cpp b/engines/tinsel/play.cpp
new file mode 100644
index 0000000000..e32fc88d3d
--- /dev/null
+++ b/engines/tinsel/play.cpp
@@ -0,0 +1,507 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Plays films within a scene, takes into account the actor in each 'column'. |
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinlib.h" // stand()
+
+namespace Tinsel {
+
+/**
+ * Poke the background palette into an image.
+ */
+static void PokeInPalette(SCNHANDLE hMulFrame) {
+ const FRAME *pFrame; // Pointer to frame
+ IMAGE *pim; // Pointer to image
+
+ // Could be an empty column
+ if (hMulFrame) {
+ pFrame = (const FRAME *)LockMem(hMulFrame);
+
+ // get pointer to image
+ pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image
+
+ pim->hImgPal = TO_LE_32(BackPal());
+ }
+}
+
+
+int32 NoNameFunc(int actorID, bool bNewMover) {
+ PMACTOR pActor;
+ int32 retval;
+
+ pActor = GetMover(actorID);
+
+ if (pActor != NULL && !bNewMover) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath == NOPOLY)
+ retval = getPolyZfactor(FirstPathPoly());
+ else
+ retval = getPolyZfactor(pActor->hCpath);
+ } else {
+ switch (actorMaskType(actorID)) {
+ case ACT_DEFAULT:
+ retval = 0;
+ break;
+ case ACT_MASK:
+ retval = 0;
+ break;
+ case ACT_ALWAYS:
+ retval = 10;
+ break;
+ default:
+ retval = actorMaskType(actorID);
+ break;
+ }
+ }
+
+ return retval;
+}
+
+struct PPINIT {
+ SCNHANDLE hFilm; // The 'film'
+ int16 x; // } Co-ordinates from the play()
+ int16 y; // } - set to (-1, -1) if none.
+ int16 z; // normally 0, set if from restore
+ int16 speed; // Film speed
+ int16 actorid; // Set if called from an actor code block
+ uint8 splay; // Set if called from splay()
+ uint8 bTop; // Set if called from topplay()
+ int16 sf; // SlowFactor - only used for moving actors
+ int16 column; // Column number, first column = 0
+
+ uint8 escOn;
+ int32 myescEvent;
+};
+
+
+/**
+ * - Don't bother if this reel is already playing for this actor.
+ * - If explicit co-ordinates, use these, If embedded co-ordinates,
+ * leave alone, otherwise use actor's current position.
+ * - Moving actors get hidden during this play, other actors get
+ * _ctx->replaced by this play.
+ * - Column 0 of a film gets its appropriate Z-position, slave columns
+ * get slightly bigger Z-positions, in column order.
+ * - Play proceeds until the script finishes, another reel starts up for
+ * this actor, or the actor gets killed.
+ * - If called from an splay(), moving actor's co-ordinates are updated
+ * after the play, any walk still in progress will go on from there.
+ */
+void PlayReel(CORO_PARAM, const PPINIT *ppi) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pPlayObj; // Object
+ ANIM thisAnim; // Animation structure
+
+ bool mActor; // Gets set if this is a moving actor
+ bool lifeNoMatter;
+ bool replaced;
+
+ const FREEL *pfreel; // The 'column' to play
+ int stepCount;
+ int frameCount;
+ int reelActor;
+ CORO_END_CONTEXT(_ctx);
+
+ static int firstColZ = 0; // Z-position of column zero
+ static int32 fColZfactor = 0; // Z-factor of column zero's actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+ PMACTOR pActor;
+ bool bNewMover; // Gets set if a moving actor that isn't in scene yet
+
+ const FILM *pfilm;
+
+ _ctx->lifeNoMatter = false;
+ _ctx->replaced = false;
+ pActor = NULL;
+ bNewMover = false;
+
+ pfilm = (const FILM *)LockMem(ppi->hFilm);
+ _ctx->pfreel = &pfilm->reels[ppi->column];
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(_ctx->pfreel->mobj));
+
+ // Save actor's ID
+ _ctx->reelActor = (int32)FROM_LE_32(pmi->mulID);
+
+ /**** New (experimental? bit 5/1/95 ****/
+ if (!actorAlive(_ctx->reelActor))
+ return;
+ /**** Delete a bit down there if this stays ****/
+
+ updateActorEsc(_ctx->reelActor, ppi->escOn, ppi->myescEvent);
+
+ // To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios
+ if (ppi->hFilm != getActorLatestFilm(_ctx->reelActor)) {
+ // This in not the last film scheduled for this actor
+
+ // It may be the last non-talk film though
+ if (isActorTalking(_ctx->reelActor))
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk
+
+ return;
+ }
+ if (isActorTalking(_ctx->reelActor)) {
+ // Note: will delete this and there'll be no need to store the talk film!
+ if (ppi->hFilm != getActorTalkFilm(_ctx->reelActor)) {
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm); // Revert to this film after talk
+ return;
+ }
+ } else {
+ setActorPlayFilm(_ctx->reelActor, ppi->hFilm);
+ }
+
+ // If this reel is already playing for this actor, just forget it.
+ if (actorReel(_ctx->reelActor) == _ctx->pfreel)
+ return;
+
+ // Poke in the background palette
+ PokeInPalette(FROM_LE_32(pmi->hMulFrame));
+
+ // Set up and insert the multi-object
+ _ctx->pPlayObj = MultiInitObject(pmi);
+ if (!ppi->bTop)
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj);
+ else
+ MultiInsertObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj);
+
+ // If co-ordinates are specified, use specified.
+ // Otherwise, use actor's position if there are not embedded co-ords.
+ // Add this first test for nth columns with offsets
+ // in plays with (x,y)
+ int tmpX, tmpY;
+ tmpX = ppi->x;
+ tmpY = ppi->y;
+ if (ppi->column != 0 && (pmi->mulX || pmi->mulY)) {
+ } else if (tmpX != -1 || tmpY != -1) {
+ MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY);
+ } else if (!pmi->mulX && !pmi->mulY) {
+ GetActorPos(_ctx->reelActor, &tmpX, &tmpY);
+ MultiSetAniXY(_ctx->pPlayObj, tmpX, tmpY);
+ }
+
+ // If it's a moving actor, this hides the moving actor
+ // used to do this only if (actorid == 0) - I don't know why
+ _ctx->mActor = HideMovingActor(_ctx->reelActor, ppi->sf);
+
+ // If it's a moving actor, get its MACTOR structure.
+ // If it isn't in the scene yet, get its task running - using
+ // stand() - to prevent a glitch at the end of the play.
+ if (_ctx->mActor) {
+ pActor = GetMover(_ctx->reelActor);
+ if (getMActorState(pActor) == NO_MACTOR) {
+ stand(_ctx->reelActor, MAGICX, MAGICY, 0);
+ bNewMover = true;
+ }
+ }
+
+ // Register the fact that we're playing this for this actor
+ storeActorReel(_ctx->reelActor, _ctx->pfreel, ppi->hFilm, _ctx->pPlayObj, ppi->column, tmpX, tmpY);
+
+ /**** Will get rid of this if the above is kept ****/
+ // We may be temporarily resuscitating a dead actor
+ if (ppi->actorid == 0 && !actorAlive(_ctx->reelActor))
+ _ctx->lifeNoMatter = true;
+
+ InitStepAnimScript(&_ctx->thisAnim, _ctx->pPlayObj, FROM_LE_32(_ctx->pfreel->script), ppi->speed);
+
+ // If first column, set Z position as per
+ // Otherwise, column 0's + column number
+ // N.B. It HAS been ensured that the first column gets here first
+ if (ppi->z != 0) {
+ MultiSetZPosition(_ctx->pPlayObj, ppi->z);
+ storeActorZpos(_ctx->reelActor, ppi->z);
+ } else if (ppi->bTop) {
+ if (ppi->column == 0) {
+ firstColZ = Z_TOPPLAY + actorMaskType(_ctx->reelActor);
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ storeActorZpos(_ctx->reelActor, firstColZ);
+ } else {
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column);
+ storeActorZpos(_ctx->reelActor, firstColZ + ppi->column);
+ }
+ } else if (ppi->column == 0) {
+ if (_ctx->mActor && !bNewMover) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath == NOPOLY)
+ fColZfactor = getPolyZfactor(FirstPathPoly());
+ else
+ fColZfactor = getPolyZfactor(pActor->hCpath);
+ firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor);
+ } else {
+ switch (actorMaskType(_ctx->reelActor)) {
+ case ACT_DEFAULT:
+ fColZfactor = 0;
+ firstColZ = 2;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ case ACT_MASK:
+ fColZfactor = 0;
+ firstColZ = MultiLowest(_ctx->pPlayObj);
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ case ACT_ALWAYS:
+ fColZfactor = 10;
+ firstColZ = 10000;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ break;
+ default:
+ fColZfactor = actorMaskType(_ctx->reelActor);
+ firstColZ = AsetZPos(_ctx->pPlayObj, MultiLowest(_ctx->pPlayObj), fColZfactor);
+ if (firstColZ < 2) {
+ // This is an experiment!
+ firstColZ = 2;
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ);
+ }
+ break;
+ }
+ }
+ storeActorZpos(_ctx->reelActor, firstColZ);
+ } else {
+ if (NoNameFunc(_ctx->reelActor, bNewMover) > fColZfactor) {
+ fColZfactor = NoNameFunc(_ctx->reelActor, bNewMover);
+ firstColZ = fColZfactor << 10;
+ }
+ MultiSetZPosition(_ctx->pPlayObj, firstColZ + ppi->column);
+ storeActorZpos(_ctx->reelActor, firstColZ + ppi->column);
+ }
+
+ /*
+ * Play until the script finishes,
+ * another reel starts up for this actor,
+ * or the actor gets killed.
+ */
+ _ctx->stepCount = 0;
+ _ctx->frameCount = 0;
+ do {
+ if (_ctx->stepCount++ == 0) {
+ _ctx->frameCount++;
+ storeActorSteps(_ctx->reelActor, _ctx->frameCount);
+ }
+ if (_ctx->stepCount == ppi->speed)
+ _ctx->stepCount = 0;
+
+ if (StepAnimScript(&_ctx->thisAnim) == ScriptFinished)
+ break;
+
+ int x, y;
+ GetAniPosition(_ctx->pPlayObj, &x, &y);
+ storeActorPos(_ctx->reelActor, x, y);
+
+ CORO_SLEEP(1);
+
+ if (actorReel(_ctx->reelActor) != _ctx->pfreel) {
+ _ctx->replaced = true;
+ break;
+ }
+
+ if (actorEsc(_ctx->reelActor) && actorEev(_ctx->reelActor) != GetEscEvents())
+ break;
+
+ } while (_ctx->lifeNoMatter || actorAlive(_ctx->reelActor));
+
+ // Register the fact that we're NOT playing this for this actor
+ if (actorReel(_ctx->reelActor) == _ctx->pfreel)
+ storeActorReel(_ctx->reelActor, NULL, 0, NULL, 0, 0, 0);
+
+ // Ditch the object
+ if (!ppi->bTop)
+ MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), _ctx->pPlayObj);
+ else
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pPlayObj);
+
+ if (_ctx->mActor) {
+ if (!_ctx->replaced)
+ unHideMovingActor(_ctx->reelActor); // Restore moving actor
+
+ // Update it's co-ordinates if this is an splay()
+ if (ppi->splay)
+ restoreMovement(_ctx->reelActor);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Run all animations that comprise the play film.
+ */
+static void playProcess(CORO_PARAM, const void *param) {
+ // get the stuff copied to process when it was created
+ PPINIT *ppi = (PPINIT *)param;
+
+ PlayReel(coroParam, ppi);
+}
+
+// *******************************************************
+
+
+// To handle the play()-talk(), talk()-play(), talk()-talk() and play()-play() scenarios
+void newestFilm(SCNHANDLE film, const FREEL *reel) {
+ const MULTI_INIT *pmi; // MULTI_INIT structure
+
+ // Get the MULTI_INIT structure
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(reel->mobj));
+
+ setActorLatestFilm((int32)FROM_LE_32(pmi->mulID), film);
+}
+
+// *******************************************************
+
+/**
+ * Start up a play process for each column in a film.
+ *
+ * NOTE: The processes are started in reverse order so that the first
+ * column's process kicks in first.
+ */
+void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn,
+ int myescEvent, bool bTop) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ PPINIT ppi;
+
+ assert(film != 0); // Trying to play NULL film
+
+ // Now allowed empty films!
+ if (pfilm->numreels == 0)
+ return; // Nothing to do!
+
+ ppi.hFilm = film;
+ ppi.x = x;
+ ppi.y = y;
+ ppi.z = 0;
+ ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ ppi.actorid = actorid;
+ ppi.splay = splay;
+ ppi.bTop = bTop;
+ ppi.sf = sfact;
+ ppi.escOn = escOn;
+ ppi.myescEvent = myescEvent;
+
+ // Start display process for each reel in the film
+ for (int i = FROM_LE_32(pfilm->numreels) - 1; i >= 0; i--) {
+ newestFilm(film, &pfilm->reels[i]);
+
+ ppi.column = i;
+ g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(PPINIT));
+ }
+}
+
+/**
+ * Start up a play process for each slave column in a film.
+ * Play the first column directly from the parent process.
+ */
+void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact,
+ bool escOn, int myescEvent, bool bTop) {
+ CORO_BEGIN_CONTEXT;
+ PPINIT ppi;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(film != 0); // Trying to play NULL film
+ const FILM *pfilm;
+
+ pfilm = (const FILM *)LockMem(film);
+
+ // Now allowed empty films!
+ if (pfilm->numreels == 0)
+ return; // Already played to completion!
+
+ _ctx->ppi.hFilm = film;
+ _ctx->ppi.x = x;
+ _ctx->ppi.y = y;
+ _ctx->ppi.z = 0;
+ _ctx->ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ _ctx->ppi.actorid = actorid;
+ _ctx->ppi.splay = splay;
+ _ctx->ppi.bTop = bTop;
+ _ctx->ppi.sf = sfact;
+ _ctx->ppi.escOn = escOn;
+ _ctx->ppi.myescEvent = myescEvent;
+
+ // Start display process for each secondary reel in the film
+ for (int i = FROM_LE_32(pfilm->numreels) - 1; i > 0; i--) {
+ newestFilm(film, &pfilm->reels[i]);
+
+ _ctx->ppi.column = i;
+ g_scheduler->createProcess(PID_REEL, playProcess, &_ctx->ppi, sizeof(PPINIT));
+ }
+
+ newestFilm(film, &pfilm->reels[0]);
+
+ _ctx->ppi.column = 0;
+ CORO_INVOKE_1(PlayReel, &_ctx->ppi);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Start up a play process for a particular column in a film.
+ *
+ * NOTE: This is specifically for actors during a restore scene.
+ */
+void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ PPINIT ppi;
+
+ ppi.hFilm = film;
+ ppi.x = x;
+ ppi.y = y;
+ ppi.z = z;
+ ppi.speed = (ONE_SECOND / FROM_LE_32(pfilm->frate));
+ ppi.actorid = 0;
+ ppi.splay = false;
+ ppi.bTop = false;
+ ppi.sf = 0;
+ ppi.column = reelnum;
+
+ // FIXME: The PlayReel play loop was previously breaking out, and then deleting objects, when
+ // returning to a scene because escOn and myescEvent were undefined. Need to make sure whether
+ // restored objects should have any particular combination of these two values
+ ppi.escOn = false;
+ ppi.myescEvent = GetEscEvents();
+
+ assert(pfilm->numreels);
+
+ newestFilm(film, &pfilm->reels[reelnum]);
+
+ // Start display process for the reel
+ g_scheduler->createProcess(PID_REEL, playProcess, &ppi, sizeof(ppi));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/polygons.cpp b/engines/tinsel/polygons.cpp
new file mode 100644
index 0000000000..d73e290277
--- /dev/null
+++ b/engines/tinsel/polygons.cpp
@@ -0,0 +1,1862 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/font.h"
+#include "tinsel/handle.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/serializer.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+
+//----------------- LOCAL DEFINES --------------------
+
+/** different types of polygon */
+enum POLY_TYPE {
+ POLY_PATH, POLY_NPATH, POLY_BLOCK, POLY_REFER, POLY_EFFECT,
+ POLY_EXIT, POLY_TAG
+};
+
+
+// Note 7/10/94, with adjacency reduction ANKHMAP max is 3, UNSEEN max is 4
+// so reduced this back to 6 (from 12) for now.
+#define MAXADJ 6 // Max number of known adjacent paths
+
+struct POLYGON {
+
+ PTYPE polytype; // Polygon type
+
+ int subtype; // refer type in REFER polygons
+ // NODE/NORMAL in PATH polygons
+
+ int pIndex; // Index into compiled polygon data
+
+ /*
+ * Data duplicated from compiled polygon data
+ */
+ short cx[4]; // Corners (clockwise direction)
+ short cy[4];
+ int polyID;
+
+ /* For TAG and EXIT (and EFFECT in future?) polygons only */
+ TSTATE tagState;
+ PSTATE pointState;
+ SCNHANDLE oTagHandle; // Override tag.
+
+ /* For Path polygons only */
+ bool tried;
+
+ /*
+ * Internal derived data for speed and conveniance
+ * set up by FiddlyBit()
+ */
+ short ptop; //
+ short pbottom; // Enclosing external rectangle
+ short pleft; //
+ short pright; //
+
+ short ltop[4]; //
+ short lbottom[4]; // Rectangles enclosing each side
+ short lleft[4]; //
+ short lright[4]; //
+
+ int a[4]; // y1-y2 }
+ int b[4]; // x2-x1 } See IsInPolygon()
+ long c[4]; // y1x2 - x1y2 }
+
+ /*
+ * Internal derived data for speed and conveniance
+ * set up by PseudoCentre()
+ */
+ int pcentrex; // Pseudo-centre
+ int pcentrey; //
+
+ /**
+ * List of adjacent polygons. For Path polygons only.
+ * set up by SetPathAdjacencies()
+ */
+ POLYGON *adjpaths[MAXADJ];
+
+};
+
+
+#define MAXONROUTE 40
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** lineinfo struct - one per (node-1) in a node path */
+struct LINEINFO {
+
+ int32 a;
+ int32 b;
+ int32 c;
+
+ int32 a2; //!< a squared
+ int32 b2; //!< b squared
+ int32 a2pb2; //!< a squared + b squared
+ int32 ra2pb2; //!< root(a squared + b squared)
+
+ int32 ab;
+ int32 ac;
+ int32 bc;
+} PACKED_STRUCT;
+
+/** polygon struct - one per polygon */
+struct POLY {
+ int32 type; //!< type of polygon
+ int32 x[4], y[4]; // Polygon definition
+
+ int32 tagx, tagy; // } For tagged polygons
+ SCNHANDLE hTagtext; // } i.e. EXIT, TAG, EFFECT
+
+ int32 nodex, nodey; // EXIT, TAG, REFER
+ SCNHANDLE hFilm; //!< film reel handle for EXIT, TAG
+
+ int32 reftype; //!< Type of REFER
+
+ int32 id; // } EXIT and TAG
+
+ int32 scale1, scale2; // }
+ int32 reel; // } PATH and NPATH
+ int32 zFactor; // }
+
+ //The arrays now stored externally
+ int32 nodecount; //!<The number of nodes in this polygon
+ int32 pnodelistx,pnodelisty; //!<offset in chunk to this array if present
+ int32 plinelist;
+
+ SCNHANDLE hScript; //!< handle of code segment for polygon events
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int MaxPolys = MAX_POLY;
+
+static POLYGON *Polys[MAX_POLY+1];
+
+static POLYGON *Polygons = 0;
+
+static SCNHANDLE pHandle = 0; // } Set at start of each scene
+static int noofPolys = 0; // }
+
+static POLYGON extraBlock; // Used for dynamic blocking
+
+static int pathsOnRoute = 0;
+static const POLYGON *RoutePaths[MAXONROUTE];
+
+static POLYGON *RouteEnd = 0;
+
+#ifdef DEBUG
+int highestYet = 0;
+#endif
+
+
+
+//----------------- LOCAL MACROS --------------------
+
+// The str parameter is no longer used
+#define CHECK_HP_OR(mvar, str) assert((mvar >= 0 && mvar <= noofPolys) || mvar == MAX_POLY);
+#define CHECK_HP(mvar, str) assert(mvar >= 0 && mvar <= noofPolys);
+
+static HPOLYGON PolyIndex(const POLYGON *pp) {
+ for (int j = 0; j <= MAX_POLY; j++) {
+ if (Polys[j] == pp)
+ return j;
+ }
+
+ error("PolyIndex(): polygon not found");
+ return NOPOLY;
+}
+
+/**
+ * Returns TRUE if the point is within the polygon supplied.
+ *
+ * Firstly, the point must be within the smallest imaginary rectangle
+ * which encloses the polygon.
+ *
+ * Then, from each corner of the polygon, if the point is within an
+ * imaginary rectangle enclosing the clockwise-going side from that
+ * corner, the gradient of a line from the corner to the point must be
+ * less than (or more negative than) the gradient of that side:
+ *
+ * If the corners' coordinates are designated (x1, y1) and (x2, y2), and
+ * the point in question's (xt, yt), then:
+ * gradient (x1,y1)->(x2,y2) > gradient (x1,y1)->(xt,yt)
+ * (y1-y2)/(x2-x1) > (y1-yt)/(xt-x1)
+ * (y1-y2)*(xt-x1) > (y1-yt)*(x2-x1)
+ * xt(y1-y2) -x1y1 + x1y2 > -yt(x2-x1) + y1x2 - x1y1
+ * xt(y1-y2) + yt(x2-x1) > y1x2 - x1y2
+ *
+ * If the point passed one of the four 'side tests', and failed none,
+ * then it must be within the polygon. If the point was not tested, it
+ * may be within the internal rectangle not covered by the above tests.
+ *
+ * Most polygons contain an internal rectangle which does not fall into
+ * any of the above side-related tests. Such a rectangle will always
+ * have two polygon corners above it and two corners to the left of it.
+ */
+bool IsInPolygon(int xt, int yt, HPOLYGON hp) {
+ const POLYGON *pp;
+ int i;
+ bool BeenTested = false;
+ int pl = 0, pa = 0;
+
+ CHECK_HP_OR(hp, "Out of range polygon handle (1)");
+ pp = Polys[hp];
+ assert(pp != NULL); // Testing whether in a NULL polygon
+
+ /* Is point within the external rectangle? */
+ if (xt < pp->pleft || xt > pp->pright || yt < pp->ptop || yt > pp->pbottom)
+ return false;
+
+ // For each corner/side
+ for (i = 0; i < 4; i++) {
+ // If within this side's 'testable' area
+ // i.e. within the width of the line in y direction of end of line
+ // or within the height of the line in x direction of end of line
+ if ((xt >= pp->lleft[i] && xt <= pp->lright[i] && ((yt > pp->cy[i]) == (pp->cy[(i+1)%4] > pp->cy[i])))
+ || (yt >= pp->ltop[i] && yt <= pp->lbottom[i] && ((xt > pp->cx[i]) == (pp->cx[(i+1)%4] > pp->cx[i])))) {
+ if (((long)xt*pp->a[i] + (long)yt*pp->b[i]) < pp->c[i])
+ return false;
+ else
+ BeenTested = true;
+ }
+ }
+
+ if (BeenTested) {
+ // New dodgy code 29/12/94
+ if (pp->polytype == BLOCKING) {
+ // For each corner/side
+ for (i = 0; i < 4; i++) {
+ // Pretend the corners of blocking polys are not in the poly.
+ if (xt == pp->cx[i] && yt == pp->cy[i])
+ return false;
+ }
+ }
+ return true;
+ } else {
+ // Is point within the internal rectangle?
+ for (i = 0; i < 4; i++) {
+ if (pp->cx[i] < xt)
+ pl++;
+ if (pp->cy[i] < yt)
+ pa++;
+ }
+
+ if (pa == 2 && pl == 2)
+ return true;
+ else
+ return false;
+ }
+}
+
+/**
+ * Finds a polygon of the specified type containing the supplied point.
+ */
+
+HPOLYGON InPolygon(int xt, int yt, PTYPE type) {
+ for (int j = 0; j <= MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == type) {
+ if (IsInPolygon(xt, yt, j))
+ return j;
+ }
+ }
+ return NOPOLY;
+}
+
+/**
+ * Given a blocking polygon, current co-ordinates of an actor, and the
+ * co-ordinates of where the actor is heading, works out which corner of
+ * the blocking polygon to head around.
+ */
+
+void BlockingCorner(HPOLYGON hp, int *x, int *y, int tarx, int tary) {
+ const POLYGON *pp;
+ int i;
+ int xd, yd; // distance per axis
+ int ThisD, SmallestD = 1000;
+ int D1, D2;
+ int NearestToHere = 1000, NearestToTarget;
+ unsigned At = 10; // Corner already at
+
+ int bcx[4], bcy[4]; // Bogus corners
+
+ CHECK_HP_OR(hp, "Out of range polygon handle (2)");
+ pp = Polys[hp];
+
+ // Work out a point outside each corner
+ for (i = 0; i < 4; i++) {
+ int next, prev;
+
+ // X-direction
+ next = pp->cx[i] - pp->cx[(i+1)%4];
+ prev = pp->cx[i] - pp->cx[(i+3)%4];
+ if (next <= 0 && prev <= 0)
+ bcx[i] = pp->cx[i] - 4; // Both points to the right
+ else if (next >= 0 && prev >= 0)
+ bcx[i] = pp->cx[i] + 4; // Both points to the left
+ else
+ bcx[i] = pp->cx[i];
+
+ // Y-direction
+ next = pp->cy[i] - pp->cy[(i+1)%4];
+ prev = pp->cy[i] - pp->cy[(i+3)%4];
+ if (next <= 0 && prev <= 0)
+ bcy[i] = pp->cy[i] - 4; // Both points below
+ else if (next >= 0 && prev >= 0)
+ bcy[i] = pp->cy[i] + 4; // Both points above
+ else
+ bcy[i] = pp->cy[i];
+ }
+
+ // Find nearest corner to where we are,
+ // but not the one we're stood at.
+
+ for (i = 0; i < 4; i++) { // For 4 corners
+// ThisD = ABS(*x - pp->cx[i]) + ABS(*y - pp->cy[i]);
+ ThisD = ABS(*x - bcx[i]) + ABS(*y - bcy[i]);
+ if (ThisD < SmallestD) {
+ // Ignore this corner if it's not in a path
+ if (InPolygon(pp->cx[i], pp->cy[i], PATH) == NOPOLY ||
+ InPolygon(bcx[i], bcy[i], PATH) == NOPOLY)
+ continue;
+
+ // Are we stood at this corner?
+ if (ThisD > 4) {
+ // No - it's the nearest we've found yet.
+ NearestToHere = i;
+ SmallestD = ThisD;
+ } else {
+ // Stood at/next to this corner
+ At = i;
+ }
+ }
+ }
+
+ // If we're not already at a corner, go to the nearest corner
+
+ if (At == 10) {
+ // Not stood at a corner
+// assert(NearestToHere != 1000); // At blocking corner, not found near corner!
+ // Better to give up than to assert fail!
+ if (NearestToHere == 1000) {
+ // Send it to where it is now
+ // i.e. leave x and y alone
+ } else {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ }
+ } else {
+ // Already at a corner. Go to an adjacent corner.
+ // First, find out which adjacent corner is nearest the target.
+ xd = ABS(tarx - pp->cx[(At + 1) % 4]);
+ yd = ABS(tary - pp->cy[(At + 1) % 4]);
+ D1 = xd + yd;
+ xd = ABS(tarx - pp->cx[(At + 3) % 4]);
+ yd = ABS(tary - pp->cy[(At + 3) % 4]);
+ D2 = xd + yd;
+ NearestToTarget = (D2 > D1) ? (At + 1) % 4 : (At + 3) % 4;
+ if (NearestToTarget == NearestToHere) {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ } else {
+ // Need to decide whether it's better to go to the nearest to
+ // here and then on to the target, or to the nearest to the
+ // target and on from there.
+ xd = ABS(pp->cx[At] - pp->cx[NearestToHere]);
+ D1 = xd;
+ xd = ABS(pp->cx[NearestToHere] - tarx);
+ D1 += xd;
+
+ yd = ABS(pp->cy[At] - pp->cy[NearestToHere]);
+ D1 += yd;
+ yd = ABS(pp->cy[NearestToHere] - tary);
+ D1 += yd;
+
+ xd = ABS(pp->cx[At] - pp->cx[NearestToTarget]);
+ D2 = xd;
+ xd = ABS(pp->cx[NearestToTarget] - tarx);
+ D2 += xd;
+
+ yd = ABS(pp->cy[At] - pp->cy[NearestToTarget]);
+ D2 += yd;
+ yd = ABS(pp->cy[NearestToTarget] - tary);
+ D2 += yd;
+
+ if (D2 > D1) {
+ *x = bcx[NearestToHere];
+ *y = bcy[NearestToHere];
+ } else {
+ *x = bcx[NearestToTarget];
+ *y = bcy[NearestToTarget];
+ }
+ }
+ }
+}
+
+
+/**
+ * Try do drop a perpendicular to each inter-node line from the point
+ * and remember the shortest (if any).
+ * Find which node is nearest to the point.
+ * The shortest of these gives the best point in the node path.
+*/
+void FindBestPoint(HPOLYGON hp, int *x, int *y, int *pline) {
+ const POLYGON *pp;
+
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Compiled polygon data
+ int dropD; // length of perpendicular (i.e. distance of point from line)
+ int dropX, dropY; // (X, Y) where dropped perpendicular intersects the line
+ int d1, d2; // distance from perpendicular intersect to line's end nodes
+ int32 *nlistx, *nlisty;
+
+ int shortestD = 10000; // Shortest distance found
+ int nearestL = -1; // Nearest line
+ int nearestN; // Nearest Node
+
+ int h = *x; // For readability/conveniance
+ int k = *y; // - why aren't these #defines?
+ LINEINFO *llist; // Inter-node line structure
+
+ CHECK_HP(hp, "Out of range polygon handle (3)");
+ pp = Polys[hp];
+
+ // Pointer to polygon data
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + pp->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+ llist = (LINEINFO *)(pps + (int)FROM_LE_32(ptp->plinelist));
+
+ // Look for fit of perpendicular to lines between nodes
+ for (int i = 0; i < (int)FROM_LE_32(ptp->nodecount) - 1; i++) {
+ const int32 a = (int)FROM_LE_32(llist[i].a);
+ const int32 b = (int)FROM_LE_32(llist[i].b);
+ const int32 c = (int)FROM_LE_32(llist[i].c);
+
+#if 1
+ if (true) {
+ //printf("a %d, b %d, c %d, a^2+b^2 = %d\n", a, b, c, a*a+b*b);
+
+ // TODO: If the comments of the LINEINFO struct are correct, then it contains mostly
+ // duplicate data, probably in an effort to safe CPU cycles. Even on the slowest devices
+ // we support, calculatin a product of two ints is not an issue.
+ // So we can just load & endian convert a,b,c, then replace stuff like
+ // (int)FROM_LE_32(line->ab)
+ // by simply a*b, which makes it easier to understand what the code does, too.
+ // Just in case there is some bugged data, I leave this code here for verifying it.
+ // Let's leave it in for some time.
+ //
+ // One bad thing: We use sqrt to compute a square root. Might not be a good idea,
+ // speed wise. Maybe we should take Vicent's fp_sqroot. But that's a problem for later.
+
+ LINEINFO *line = &llist[i];
+ int32 a2 = (int)FROM_LE_32(line->a2); //!< a squared
+ int32 b2 = (int)FROM_LE_32(line->b2); //!< b squared
+ int32 a2pb2 = (int)FROM_LE_32(line->a2pb2); //!< a squared + b squared
+ int32 ra2pb2 = (int)FROM_LE_32(line->ra2pb2); //!< root(a squared + b squared)
+
+ int32 ab = (int)FROM_LE_32(line->ab);
+ int32 ac = (int)FROM_LE_32(line->ac);
+ int32 bc = (int)FROM_LE_32(line->bc);
+
+ assert(a*a == a2);
+ assert(b*b == b2);
+ assert(a*b == ab);
+ assert(a*c == ac);
+ assert(b*c == bc);
+
+ assert(a2pb2 == a*a + b*b);
+ assert(ra2pb2 == (int)sqrt((float)a*a + (float)b*b));
+ }
+#endif
+
+
+ if (a == 0 && b == 0)
+ continue; // Line is just a point!
+
+ // X position of dropped perpendicular intersection with line
+ dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b);
+
+ // X distances from intersection to end nodes
+ d1 = dropX - (int)FROM_LE_32(nlistx[i]);
+ d2 = dropX - (int)FROM_LE_32(nlistx[i+1]);
+
+ // if both -ve or both +ve, no fit
+ if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
+ continue;
+//#if 0
+ // Y position of sidweays perpendicular intersection with line
+ dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b);
+
+ // Y distances from intersection to end nodes
+ d1 = dropY - (int)FROM_LE_32(nlisty[i]);
+ d2 = dropY - (int)FROM_LE_32(nlisty[i+1]);
+
+ // if both -ve or both +ve, no fit
+ if ((d1 < 0 && d2 < 0) || (d1 > 0 && d2 > 0))
+ continue;
+//#endif
+ dropD = ((a * h) + (b * k) + c) / (int)sqrt((float)a*a + (float)b*b);
+ dropD = ABS(dropD);
+ if (dropD < shortestD) {
+ shortestD = dropD;
+ nearestL = i;
+ }
+ }
+
+ // Distance to nearest node
+ nearestN = NearestNodeWithin(hp, h, k);
+ dropD = ABS(h - (int)FROM_LE_32(nlistx[nearestN])) + ABS(k - (int)FROM_LE_32(nlisty[nearestN]));
+
+ // Go to a node or a point on a line
+ if (dropD < shortestD) {
+ // A node is nearest
+ *x = (int)FROM_LE_32(nlistx[nearestN]);
+ *y = (int)FROM_LE_32(nlisty[nearestN]);
+ *pline = nearestN;
+ } else {
+ assert(nearestL != -1);
+
+ // A point on a line is nearest
+ const int32 a = (int)FROM_LE_32(llist[nearestL].a);
+ const int32 b = (int)FROM_LE_32(llist[nearestL].b);
+ const int32 c = (int)FROM_LE_32(llist[nearestL].c);
+ dropX = ((b*b * h) - (a*b * k) - a*c) / (a*a + b*b);
+ dropY = ((a*a * k) - (a*b * h) - b*c) / (a*a + b*b);
+ *x = dropX;
+ *y = dropY;
+ *pline = nearestL;
+ }
+
+ assert(IsInPolygon(*x, *y, hp)); // Nearest point is not in polygon(!)
+}
+
+/**
+ * Returns TRUE if two paths are asdjacent.
+ */
+bool IsAdjacentPath(HPOLYGON hPath1, HPOLYGON hPath2) {
+ const POLYGON *pp1, *pp2;
+
+ CHECK_HP(hPath1, "Out of range polygon handle (4)");
+ CHECK_HP(hPath2, "Out of range polygon handle (500)");
+
+ if (hPath1 == hPath2)
+ return true;
+
+ pp1 = Polys[hPath1];
+ pp2 = Polys[hPath2];
+
+ for (int j = 0; j < MAXADJ; j++)
+ if (pp1->adjpaths[j] == pp2)
+ return true;
+
+ return false;
+}
+
+static const POLYGON *TryPath(POLYGON *last, POLYGON *whereto, POLYGON *current) {
+ POLYGON *x;
+
+ // For each path adjacent to this one
+ for (int j = 0; j < MAXADJ; j++) {
+ x = current->adjpaths[j]; // call the adj. path x
+ if (x == whereto) {
+ RoutePaths[pathsOnRoute++] = x;
+ return x; // Got there!
+ }
+
+ if (x == NULL)
+ break; // no more adj. paths to look at
+
+ if (x->tried)
+ continue; // don't double back
+
+ if (x == last)
+ continue; // don't double back
+
+ x->tried = true;
+ if (TryPath(current, whereto, x) != NULL) {
+ RoutePaths[pathsOnRoute++] = x;
+ assert(pathsOnRoute < MAXONROUTE);
+ return x; // Got there in this direction
+ } else
+ x->tried = false;
+ }
+
+ return NULL;
+}
+
+
+/**
+ * Sort out the first path to head to for the imminent leg of a walk.
+ */
+static HPOLYGON PathOnTheWay(HPOLYGON from, HPOLYGON to) {
+ // TODO: Fingolfin says: This code currently uses DFS (depth first search),
+ // in the TryPath function, to compute a path between 'from' and 'to'.
+ // However, a BFS (breadth first search) might yield more natural results,
+ // at least in cases where there are multiple possible paths.
+ // There is a small risk of regressions caused by such a change, though.
+ //
+ // Also, the overhead of computing a DFS again and again could be avoided
+ // by computing a path matrix (like we do in the SCUMM engine).
+ int i;
+
+ CHECK_HP(from, "Out of range polygon handle (501a)");
+ CHECK_HP(to, "Out of range polygon handle (501b)");
+
+ if (IsAdjacentPath(from, to))
+ return to;
+
+ for (i = 0; i < MAX_POLY; i++) { // For each polygon..
+ POLYGON *p = Polys[i];
+ if (p && p->polytype == PATH) //...if it's a path
+ p->tried = false;
+ }
+ Polys[from]->tried = true;
+ pathsOnRoute = 0;
+
+ const POLYGON *p = TryPath(Polys[from], Polys[to], Polys[from]);
+
+ assert(p != NULL); // Trying to find route between unconnected paths
+
+ // Don't go a roundabout way to an adjacent path.
+ for (i = 0; i < pathsOnRoute; i++) {
+ CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (502)");
+ if (IsAdjacentPath(from, PolyIndex(RoutePaths[i])))
+ return PolyIndex(RoutePaths[i]);
+ }
+ return PolyIndex(p);
+}
+
+/**
+ * Indirect method of calling PathOnTheWay(), to put the burden of
+ * recursion onto the main stack.
+ */
+HPOLYGON getPathOnTheWay(HPOLYGON hFrom, HPOLYGON hTo) {
+ CHECK_HP(hFrom, "Out of range polygon handle (6)");
+ CHECK_HP(hTo, "Out of range polygon handle (7)");
+
+ // Reuse already computed result
+ if (RouteEnd == Polys[hTo]) {
+ for (int i = 0; i < pathsOnRoute; i++) {
+ CHECK_HP(PolyIndex(RoutePaths[i]), "Out of range polygon handle (503)");
+ if (IsAdjacentPath(hFrom, PolyIndex(RoutePaths[i]))) {
+ return PolyIndex(RoutePaths[i]);
+ }
+ }
+ }
+
+ RouteEnd = Polys[hTo];
+ return PathOnTheWay(hFrom, hTo);
+}
+
+
+/**
+ * Given a node path, work out which end node is nearest the given point.
+ */
+
+int NearestEndNode(HPOLYGON hPath, int x, int y) {
+ const POLYGON *pp;
+
+ int d1, d2;
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hPath, "Out of range polygon handle (8)");
+ pp = Polys[hPath];
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + pp->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ const int nodecount = (int)FROM_LE_32(ptp->nodecount);
+
+ d1 = ABS(x - (int)FROM_LE_32(nlistx[0])) + ABS(y - (int)FROM_LE_32(nlisty[0]));
+ d2 = ABS(x - (int)FROM_LE_32(nlistx[nodecount - 1])) + ABS(y - (int)FROM_LE_32(nlisty[nodecount - 1]));
+
+ return (d2 > d1) ? 0 : nodecount - 1;
+}
+
+
+/**
+ * Given a start path and a destination path, find which pair of end
+ * nodes is nearest together.
+ * Return which node in the start path is part of the closest pair.
+ */
+
+int NearEndNode(HPOLYGON hSpath, HPOLYGON hDpath) {
+ const POLYGON *pSpath, *pDpath;
+
+ int ns, nd; // 'top' nodes in each path
+ int dist, NearDist;
+ int NearNode;
+ uint8 *pps; // Compiled polygon data
+ const POLY *ps, *pd; // Pointer to compiled polygon data
+ int32 *snlistx, *snlisty;
+ int32 *dnlistx, *dnlisty;
+
+ CHECK_HP(hSpath, "Out of range polygon handle (9)");
+ CHECK_HP(hDpath, "Out of range polygon handle (10)");
+ pSpath = Polys[hSpath];
+ pDpath = Polys[hDpath];
+
+ pps = LockMem(pHandle); // All polygons
+ ps = (const POLY *)pps + pSpath->pIndex; // Start polygon
+ pd = (const POLY *)pps + pDpath->pIndex; // Dest polygon
+
+ ns = (int)FROM_LE_32(ps->nodecount) - 1;
+ nd = (int)FROM_LE_32(pd->nodecount) - 1;
+
+ snlistx = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelistx));
+ snlisty = (int32 *)(pps + (int)FROM_LE_32(ps->pnodelisty));
+ dnlistx = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelistx));
+ dnlisty = (int32 *)(pps + (int)FROM_LE_32(pd->pnodelisty));
+
+ // start[0] to dest[0]
+ NearDist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[0]));
+ NearNode = 0;
+
+ // start[0] to dest[top]
+ dist = ABS((int)FROM_LE_32(snlistx[0]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[0]) - (int)FROM_LE_32(dnlisty[nd]));
+ if (dist < NearDist)
+ NearDist = dist;
+
+ // start[top] to dest[0]
+ dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[0])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[0]));
+ if (dist < NearDist) {
+ NearDist = dist;
+ NearNode = ns;
+ }
+
+ // start[top] to dest[top]
+ dist = ABS((int)FROM_LE_32(snlistx[ns]) - (int)FROM_LE_32(dnlistx[nd])) + ABS((int)FROM_LE_32(snlisty[ns]) - (int)FROM_LE_32(dnlisty[nd]));
+ if (dist < NearDist) {
+ NearNode = ns;
+ }
+
+ return NearNode;
+}
+
+/**
+ * Given a follow nodes path and a co-ordinate, finds which node in the
+ * path is nearest to the co-ordinate.
+ */
+int NearestNodeWithin(HPOLYGON hNpath, int x, int y) {
+ int ThisDistance, SmallestDistance = 1000;
+ int NumNodes; // Number of nodes in this follow nodes path
+ int NearestYet = 0; // Number of nearest node
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hNpath, "Out of range polygon handle (11)");
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ NumNodes = (int)FROM_LE_32(ptp->nodecount);
+
+ for (int i = 0; i < NumNodes; i++) {
+ ThisDistance = ABS(x - (int)FROM_LE_32(nlistx[i])) + ABS(y - (int)FROM_LE_32(nlisty[i]));
+
+ if (ThisDistance < SmallestDistance) {
+ NearestYet = i;
+ SmallestDistance = ThisDistance;
+ }
+ }
+
+ return NearestYet;
+}
+
+/**
+ * Given a point and start and destination paths, find the nearest
+ * corner (if any) of the start path which is within the destination
+ * path. If there is no such corner, find the nearest corner of the
+ * destination path which falls within the source path.
+ */
+void NearestCorner(int *x, int *y, HPOLYGON hStartPoly, HPOLYGON hDestPoly) {
+ const POLYGON *psp, *pdp;
+ int j;
+ int ncorn = 0; // nearest corner
+ HPOLYGON hNpath = NOPOLY; // path containing nearest corner
+ int ThisD, SmallestD = 1000;
+
+ CHECK_HP(hStartPoly, "Out of range polygon handle (12)");
+ CHECK_HP(hDestPoly, "Out of range polygon handle (13)");
+
+ psp = Polys[hStartPoly];
+ pdp = Polys[hDestPoly];
+
+ // Nearest corner of start path in destination path.
+
+ for (j = 0; j < 4; j++) {
+ if (IsInPolygon(psp->cx[j], psp->cy[j], hDestPoly)) {
+ ThisD = ABS(*x - psp->cx[j]) + ABS(*y - psp->cy[j]);
+ if (ThisD < SmallestD) {
+ hNpath = hStartPoly;
+ ncorn = j;
+ // Try to ignore it if virtually stood on it
+ if (ThisD > 4)
+ SmallestD = ThisD;
+ }
+ }
+ }
+ if (SmallestD == 1000) {
+ // Nearest corner of destination path in start path.
+ for (j = 0; j < 4; j++) {
+ if (IsInPolygon(pdp->cx[j], pdp->cy[j], hStartPoly)) {
+ ThisD = ABS(*x - pdp->cx[j]) + ABS(*y - pdp->cy[j]);
+ if (ThisD < SmallestD) {
+ hNpath = hDestPoly;
+ ncorn = j;
+ // Try to ignore it if virtually stood on it
+ if (ThisD > 4)
+ SmallestD = ThisD;
+ }
+ }
+ }
+ }
+
+ if (hNpath != NOPOLY) {
+ *x = Polys[hNpath]->cx[ncorn];
+ *y = Polys[hNpath]->cy[ncorn];
+ } else
+ error("NearestCorner() failure");
+}
+
+bool IsPolyCorner(HPOLYGON hPath, int x, int y) {
+ CHECK_HP(hPath, "Out of range polygon handle (37)");
+
+ for (int i = 0; i < 4; i++) {
+ if (Polys[hPath]->cx[i] == x && Polys[hPath]->cy[i] == y)
+ return true;
+ }
+ return false;
+}
+
+/**
+ * Given a path polygon and a Y co-ordinate, return a scale value.
+ */
+int GetScale(HPOLYGON hPath, int y) {
+ const POLY *ptp; // Pointer to compiled polygon data
+ int zones; // Number of different scales
+ int zlen; // Depth of each scale zone
+ int scale;
+ int top;
+
+ // To try and fix some unknown potential bug
+ if (hPath == NOPOLY)
+ return SCALE_LARGE;
+
+ CHECK_HP(hPath, "Out of range polygon handle (14)");
+
+ ptp = (const POLY *)LockMem(pHandle) + Polys[hPath]->pIndex;
+
+ // Path is of a constant scale?
+ if (FROM_LE_32(ptp->scale2) == 0)
+ return FROM_LE_32(ptp->scale1);
+
+ assert(FROM_LE_32(ptp->scale1) >= FROM_LE_32(ptp->scale2));
+
+ zones = FROM_LE_32(ptp->scale1) - FROM_LE_32(ptp->scale2) + 1;
+ zlen = (Polys[hPath]->pbottom - Polys[hPath]->ptop) / zones;
+
+ scale = FROM_LE_32(ptp->scale1);
+ top = Polys[hPath]->ptop;
+
+ do {
+ top += zlen;
+ if (y < top)
+ return scale;
+ } while (--scale);
+
+ return FROM_LE_32(ptp->scale2);
+}
+
+/**
+ * Give the co-ordinates of a node in a node path.
+ */
+void getNpathNode(HPOLYGON hNpath, int node, int *px, int *py) {
+ uint8 *pps; // Compiled polygon data
+ const POLY *ptp; // Pointer to compiled polygon data
+ int32 *nlistx, *nlisty;
+
+ CHECK_HP(hNpath, "Out of range polygon handle (15)");
+ assert(Polys[hNpath] != NULL && Polys[hNpath]->polytype == PATH && Polys[hNpath]->subtype == NODE); // must be given a node path!
+
+ pps = LockMem(pHandle); // All polygons
+ ptp = (const POLY *)pps + Polys[hNpath]->pIndex; // This polygon
+
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(ptp->pnodelisty));
+
+ // Might have just walked to the node from above.
+ if (node == (int)FROM_LE_32(ptp->nodecount))
+ node -= 1;
+
+ *px = (int)FROM_LE_32(nlistx[node]);
+ *py = (int)FROM_LE_32(nlisty[node]);
+}
+
+/**
+ * Get tag text handle and tag co-ordinates of a polygon.
+ */
+
+void getPolyTagInfo(HPOLYGON hp, SCNHANDLE *hTagText, int *tagx, int *tagy) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (16)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ *tagx = (int)FROM_LE_32(pp->tagx);
+ *tagy = (int)FROM_LE_32(pp->tagy);
+ *hTagText = FROM_LE_32(pp->hTagtext);
+}
+
+/**
+ * Get polygon's film reel handle.
+ */
+
+SCNHANDLE getPolyFilm(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (17)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return FROM_LE_32(pp->hFilm);
+}
+
+/**
+ * Get polygon's associated node.
+ */
+
+void getPolyNode(HPOLYGON hp, int *px, int *py) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (18)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ *px = (int)FROM_LE_32(pp->nodex);
+ *py = (int)FROM_LE_32(pp->nodey);
+}
+
+/**
+ * Get handle to polygon's glitter code.
+ */
+
+SCNHANDLE getPolyScript(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (19)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return FROM_LE_32(pp->hScript);
+}
+
+REEL getPolyReelType(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ // To try and fix some unknown potential bug (toyshop entrance)
+ if (hp == NOPOLY)
+ return REEL_ALL;
+
+ CHECK_HP(hp, "Out of range polygon handle (20)");
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (REEL)FROM_LE_32(pp->reel);
+}
+
+int32 getPolyZfactor(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (21)");
+ assert(Polys[hp] != NULL);
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (int)FROM_LE_32(pp->zFactor);
+}
+
+int numNodes(HPOLYGON hp) {
+ const POLY *pp; // Pointer to compiled polygon data
+
+ CHECK_HP(hp, "Out of range polygon handle (22)");
+ assert(Polys[hp] != NULL);
+
+ pp = (const POLY *)LockMem(pHandle) + Polys[hp]->pIndex;
+
+ return (int)FROM_LE_32(pp->nodecount);
+}
+
+// *************************************************************************
+//
+// Code concerned with killing and reviving TAG and EXIT polygons.
+// And code to enable this information to be saved and restored.
+//
+// *************************************************************************
+
+struct TAGSTATE {
+ int tid;
+ bool enabled;
+};
+
+#define MAX_SCENES 256
+#define MAX_TAGS 2048
+#define MAX_EXITS 512
+
+static struct {
+ SCNHANDLE sid;
+ int nooftags;
+ int offset;
+} SceneTags[MAX_SCENES], SceneExits[MAX_SCENES];
+
+static TAGSTATE TagStates[MAX_TAGS];
+static TAGSTATE ExitStates[MAX_EXITS];
+
+static int nextfreeT = 0, numScenesT = 0;
+static int nextfreeE = 0, numScenesE = 0;
+
+static int currentTScene = 0;
+static int currentEScene = 0;
+
+bool deadPolys[MAX_POLY]; // Currently just for dead blocks
+
+void RebootDeadTags(void) {
+ nextfreeT = numScenesT = 0;
+ nextfreeE = numScenesE = 0;
+
+ memset(SceneTags, 0, sizeof(SceneTags));
+ memset(SceneExits, 0, sizeof(SceneExits));
+ memset(TagStates, 0, sizeof(TagStates));
+ memset(ExitStates, 0, sizeof(ExitStates));
+ memset(deadPolys, 0, sizeof(deadPolys));
+}
+
+/**
+ * (Un)serialize the dead tag and exit data for save/restore game.
+ */
+void syncPolyInfo(Serializer &s) {
+ int i;
+
+ for (i = 0; i < MAX_SCENES; i++) {
+ s.syncAsUint32LE(SceneTags[i].sid);
+ s.syncAsSint32LE(SceneTags[i].nooftags);
+ s.syncAsSint32LE(SceneTags[i].offset);
+ }
+
+ for (i = 0; i < MAX_SCENES; i++) {
+ s.syncAsUint32LE(SceneExits[i].sid);
+ s.syncAsSint32LE(SceneExits[i].nooftags);
+ s.syncAsSint32LE(SceneExits[i].offset);
+ }
+
+ for (i = 0; i < MAX_TAGS; i++) {
+ s.syncAsUint32LE(TagStates[i].tid);
+ s.syncAsSint32LE(TagStates[i].enabled);
+ }
+
+ for (i = 0; i < MAX_EXITS; i++) {
+ s.syncAsUint32LE(ExitStates[i].tid);
+ s.syncAsSint32LE(ExitStates[i].enabled);
+ }
+
+ s.syncAsSint32LE(nextfreeT);
+ s.syncAsSint32LE(numScenesT);
+ s.syncAsSint32LE(nextfreeE);
+ s.syncAsSint32LE(numScenesE);
+}
+
+/**
+ * This is all totally different to the way the rest of the way polygon
+ * data is stored and restored, more specifically, different to how dead
+ * tags and exits are handled, because of the piecemeal design-by-just-
+ * thought-of-this approach employed.
+ */
+
+void SaveDeadPolys(bool *sdp) {
+ memcpy(sdp, deadPolys, MAX_POLY*sizeof(bool));
+}
+
+void RestoreDeadPolys(bool *sdp) {
+ memcpy(deadPolys, sdp, MAX_POLY*sizeof(bool));
+}
+
+/**
+ * Convert a BLOCKING to an EX_BLOCK poly.
+ */
+void DisableBlock(int blockno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == BLOCKING && Polys[i]->polyID == blockno) {
+ Polys[i]->polytype = EX_BLOCK;
+ deadPolys[i] = true;
+ }
+ }
+}
+
+/**
+ * Convert an EX_BLOCK to a BLOCKING poly.
+ */
+void EnableBlock(int blockno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_BLOCK && Polys[i]->polyID == blockno) {
+ Polys[i]->polytype = BLOCKING;
+ deadPolys[i] = false;
+ }
+ }
+}
+
+/**
+ * Convert an EX_TAG to a TAG poly.
+ */
+void EnableTag(int tagno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_TAG && Polys[i]->polyID == tagno) {
+ Polys[i]->polytype = TAG;
+ }
+ }
+
+ TAGSTATE *pts;
+ pts = &TagStates[SceneTags[currentTScene].offset];
+ for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) {
+ if (pts->tid == tagno) {
+ pts->enabled = true;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert an EX_EXIT to a EXIT poly.
+ */
+void EnableExit(int exitno) {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EX_EXIT && Polys[i]->polyID == exitno) {
+ Polys[i]->polytype = EXIT;
+ }
+ }
+
+ TAGSTATE *pts;
+ pts = &ExitStates[SceneExits[currentEScene].offset];
+ for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) {
+ if (pts->tid == exitno) {
+ pts->enabled = true;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert a TAG to an EX_TAG poly.
+ */
+void DisableTag(int tagno) {
+ TAGSTATE *pts;
+
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == TAG && Polys[i]->polyID == tagno) {
+ Polys[i]->polytype = EX_TAG;
+ Polys[i]->tagState = TAG_OFF;
+ Polys[i]->pointState = NOT_POINTING;
+ }
+ }
+
+ pts = &TagStates[SceneTags[currentTScene].offset];
+ for (int j = 0; j < SceneTags[currentTScene].nooftags; j++, pts++) {
+ if (pts->tid == tagno) {
+ pts->enabled = false;
+ break;
+ }
+ }
+}
+
+/**
+ * Convert a EXIT to an EX_EXIT poly.
+ */
+void DisableExit(int exitno) {
+ TAGSTATE *pts;
+
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (Polys[i] && Polys[i]->polytype == EXIT && Polys[i]->polyID == exitno) {
+ Polys[i]->polytype = EX_EXIT;
+ Polys[i]->tagState = TAG_OFF;
+ Polys[i]->pointState = NOT_POINTING;
+ }
+ }
+
+ pts = &ExitStates[SceneExits[currentEScene].offset];
+ for (int j = 0; j < SceneExits[currentEScene].nooftags; j++, pts++) {
+ if (pts->tid == exitno) {
+ pts->enabled = false;
+ break;
+ }
+ }
+}
+
+HPOLYGON FirstPathPoly(void) {
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]->polytype == PATH)
+ return i;
+ }
+ error("FirstPathPoly() - no PATH polygons!");
+ return NOPOLY;
+}
+
+HPOLYGON GetPolyHandle(int i) {
+ assert(i >= 0 && i <= MAX_POLY);
+
+ return (Polys[i] != NULL) ? i : NOPOLY;
+}
+
+// **************************************************************************
+//
+// Code called to initialise or wrap up a scene:
+//
+// **************************************************************************
+
+/**
+ * Called at the start of a scene, when all polygons have been
+ * initialised, to work out which paths are adjacent to which.
+ */
+static int DistinctCorners(HPOLYGON hp1, HPOLYGON hp2) {
+ const POLYGON *pp1, *pp2;
+ int i, j;
+ int retval = 0;
+
+ CHECK_HP(hp1, "Out of range polygon handle (23)");
+ CHECK_HP(hp2, "Out of range polygon handle (24)");
+ pp1 = Polys[hp1];
+ pp2 = Polys[hp2];
+
+ // Work out (how many of p1's corners is in p2) + (how many of p2's corners is in p1)
+ for (i = 0; i < 4; i++) {
+ if (IsInPolygon(pp1->cx[i], pp1->cy[i], hp2))
+ retval++;
+ if (IsInPolygon(pp2->cx[i], pp2->cy[i], hp1))
+ retval++;
+ }
+
+ // Common corners only count once
+ for (i = 0; i < 4; i++) {
+ for (j = 0; j < 4; j++) {
+ if (pp1->cx[i] == pp2->cx[j] && pp1->cy[i] == pp2->cy[j])
+ retval--;
+ }
+ }
+ return retval;
+}
+
+static void SetPathAdjacencies() {
+ POLYGON *p1, *p2; // Polygon pointers
+
+ // For each polygon..
+ for (int i1 = 0; i1 < MAX_POLY-1; i1++) {
+ // Get polygon, but only carry on if it's a path
+ p1 = Polys[i1];
+ if (!p1 || p1->polytype != PATH)
+ continue;
+
+ // For each subsequent polygon..
+ for (int i2 = i1 + 1; i2 < MAX_POLY; i2++) {
+ // Get polygon, but only carry on if it's a path
+ p2 = Polys[i2];
+ if (!p2 || p2->polytype != PATH)
+ continue;
+
+ int j = DistinctCorners(i1, i2);
+
+ if (j >= 2) {
+ // Paths are adjacent
+ for (j = 0; j < MAXADJ; j++)
+ if (p1->adjpaths[j] == NULL) {
+ p1->adjpaths[j] = p2;
+ break;
+ }
+#ifdef DEBUG
+ if (j > highestYet)
+ highestYet = j;
+#endif
+ assert(j < MAXADJ); // Number of adjacent paths limit
+ for (j = 0; j < MAXADJ; j++) {
+ if (p2->adjpaths[j] == NULL) {
+ p2->adjpaths[j] = p1;
+ break;
+ }
+ }
+#ifdef DEBUG
+ if (j > highestYet)
+ highestYet = j;
+#endif
+ assert(j < MAXADJ); // Number of adjacent paths limit
+ }
+ }
+ }
+}
+
+/**
+ * Ensure NPATH nodes are not inside another PATH/NPATH polygon.
+ * Only bother with end nodes for now.
+ */
+#ifdef DEBUG
+void CheckNPathIntegrity() {
+ uint8 *pps; // Compiled polygon data
+ const POLYGON *rp; // Run-time polygon structure
+ HPOLYGON hp;
+ const POLY *cp; // Compiled polygon structure
+ int i, j; // Loop counters
+ int n; // Last node in current path
+ int32 *nlistx, *nlisty;
+
+ pps = LockMem(pHandle); // All polygons
+
+ for (i = 0; i < MAX_POLY; i++) { // For each polygon..
+ rp = Polys[i];
+ if (rp && rp->polytype == PATH && rp->subtype == NODE) { //...if it's a node path
+ // Get compiled polygon structure
+ cp = (const POLY *)pps + rp->pIndex; // This polygon
+ nlistx = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelistx));
+ nlisty = (int32 *)(pps + (int)FROM_LE_32(cp->pnodelisty));
+
+ n = (int)FROM_LE_32(cp->nodecount) - 1; // Last node
+ assert(n >= 1); // Node paths must have at least 2 nodes
+
+ hp = PolyIndex(rp);
+ for (j = 0; j <= n; j++) {
+ if (!IsInPolygon((int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), hp)) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is not in its own path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[j]), (int)FROM_LE_32(nlisty[j]), rp->cx[0], rp->cy[0]);
+ error(tBufferAddr());
+ }
+ }
+
+ // Check end nodes are not in adjacent path
+ for (j = 0; j < MAXADJ; j++) { // For each adjacent path
+ if (rp->adjpaths[j] == NULL)
+ break;
+
+ if (IsInPolygon((int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), PolyIndex(rp->adjpaths[j]))) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[0]), (int)FROM_LE_32(nlisty[0]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]);
+ error(tBufferAddr())
+ }
+ if (IsInPolygon((int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), PolyIndex(rp->adjpaths[j]))) {
+ sprintf(tBufferAddr(), "Node (%d, %d) is in another path (starting (%d, %d))",
+ (int)FROM_LE_32(nlistx[n]), (int)FROM_LE_32(nlisty[n]), rp->adjpaths[j]->cx[0], rp->adjpaths[j]->cy[0]);
+ error(tBufferAddr())
+ }
+ }
+ }
+ }
+}
+#endif
+
+/**
+ * Called at the start of a scene, nobbles TAG polygons which should be dead.
+ */
+static void SetExBlocks() {
+ for (int i = 0; i < MAX_POLY; i++) {
+ if (deadPolys[i]) {
+ if (Polys[i] && Polys[i]->polytype == BLOCKING)
+ Polys[i]->polytype = EX_BLOCK;
+#ifdef DEBUG
+ else
+ error("Impossible message!");
+#endif
+ }
+ }
+}
+
+/**
+ * Called at the start of a scene, nobbles TAG polygons which should be dead.
+ */
+static void SetExTags(SCNHANDLE ph) {
+ TAGSTATE *pts;
+ int i, j;
+
+ for (i = 0; i < numScenesT; i++) {
+ if (SceneTags[i].sid == ph) {
+ currentTScene = i;
+
+ pts = &TagStates[SceneTags[i].offset];
+ for (j = 0; j < SceneTags[i].nooftags; j++, pts++) {
+ if (!pts->enabled)
+ DisableTag(pts->tid);
+ }
+ return;
+ }
+ }
+
+ i = numScenesT++;
+ currentTScene = i;
+ assert(numScenesT < MAX_SCENES); // Dead tag remembering: scene limit
+
+ SceneTags[i].sid = ph;
+ SceneTags[i].offset = nextfreeT;
+ SceneTags[i].nooftags = 0;
+
+ for (j = 0; j < MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == TAG) {
+ TagStates[nextfreeT].tid = Polys[j]->polyID;
+ TagStates[nextfreeT].enabled = true;
+ nextfreeT++;
+ assert(nextfreeT < MAX_TAGS); // Dead tag remembering: tag limit
+ SceneTags[i].nooftags++;
+ }
+ }
+}
+
+/**
+ * Called at the start of a scene, nobbles EXIT polygons which should be dead.
+ */
+static void SetExExits(SCNHANDLE ph) {
+ TAGSTATE *pts;
+ int i, j;
+
+ for (i = 0; i < numScenesE; i++) {
+ if (SceneExits[i].sid == ph) {
+ currentEScene = i;
+
+ pts = &ExitStates[SceneExits[i].offset];
+ for (j = 0; j < SceneExits[i].nooftags; j++, pts++) {
+ if (!pts->enabled)
+ DisableExit(pts->tid);
+ }
+ return;
+ }
+ }
+
+ i = numScenesE++;
+ currentEScene = i;
+ assert(numScenesE < MAX_SCENES); // Dead exit remembering: scene limit
+
+ SceneExits[i].sid = ph;
+ SceneExits[i].offset = nextfreeE;
+ SceneExits[i].nooftags = 0;
+
+ for (j = 0; j < MAX_POLY; j++) {
+ if (Polys[j] && Polys[j]->polytype == EXIT) {
+ ExitStates[nextfreeE].tid = Polys[j]->polyID;
+ ExitStates[nextfreeE].enabled = true;
+ nextfreeE++;
+ assert(nextfreeE < MAX_EXITS); // Dead exit remembering: exit limit
+ SceneExits[i].nooftags++;
+ }
+ }
+}
+
+/**
+ * Works out some fixed numbers for a polygon.
+ */
+static void FiddlyBit(POLYGON *p) {
+ int t1, t2; // General purpose temp. variables
+
+ // Enclosing external rectangle
+ t1 = MAX(p->cx[0], p->cx[1]);
+ t2 = MAX(p->cx[2], p->cx[3]);
+ p->pright = MAX(t1, t2);
+
+ t1 = MIN(p->cx[0], p->cx[1]);
+ t2 = MIN(p->cx[2], p->cx[3]);
+ p->pleft = MIN(t1, t2);
+
+ t1 = MAX(p->cy[0], p->cy[1]);
+ t2 = MAX(p->cy[2], p->cy[3]);
+ p->pbottom = MAX(t1, t2);
+
+ t1 = MIN(p->cy[0], p->cy[1]);
+ t2 = MIN(p->cy[2], p->cy[3]);
+ p->ptop = MIN(t1, t2);
+
+ // Rectangles enclosing each side and each side's magic numbers
+ for (t1 = 0; t1 < 4; t1++) {
+ p->lright[t1] = MAX(p->cx[t1], p->cx[(t1+1)%4]);
+ p->lleft[t1] = MIN(p->cx[t1], p->cx[(t1+1)%4]);
+
+ p->ltop[t1] = MIN(p->cy[t1], p->cy[(t1+1)%4]);
+ p->lbottom[t1] = MAX(p->cy[t1], p->cy[(t1+1)%4]);
+
+ p->a[t1] = p->cy[t1] - p->cy[(t1+1)%4];
+ p->b[t1] = p->cx[(t1+1)%4] - p->cx[t1];
+ p->c[t1] = (long)p->cy[t1]*p->cx[(t1+1)%4] - (long)p->cx[t1]*p->cy[(t1+1)%4];
+ }
+}
+
+/**
+ * Calculate a point approximating to the centre of a polygon.
+ * Not very sophisticated.
+ */
+static void PseudoCentre(POLYGON *p) {
+ p->pcentrex = (p->cx[0] + p->cx[1] + p->cx[2] + p->cx[3])/4;
+ p->pcentrey = (p->cy[0] + p->cy[1] + p->cy[2] + p->cy[3])/4;
+
+ if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) {
+ int i, top = 0, bot = 0;
+
+ for (i = p->ptop; i <= p->pbottom; i++) {
+ if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) {
+ top = i;
+ break;
+ }
+ }
+ for (i = p->pbottom; i >= p->ptop; i--) {
+ if (IsInPolygon(p->pcentrex, i, PolyIndex(p))) {
+ bot = i;
+ break;
+ }
+ }
+ p->pcentrex = (top+bot)/2;
+ }
+#ifdef DEBUG
+ // assert(IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))); // Pseudo-centre is not in path
+ if (!IsInPolygon(p->pcentrex, p->pcentrey, PolyIndex(p))) {
+ sprintf(tBufferAddr(), "Pseudo-centre is not in path (starting (%d, %d)) - polygon reversed?",
+ p->cx[0], p->cy[0]);
+ error(tBufferAddr());
+ }
+#endif
+}
+
+/**
+ * Allocate a POLYGON structure.
+ */
+static POLYGON *GetPolyEntry(PTYPE type, const POLY *pp, int pno) {
+ for (int i = 0; i < MaxPolys; i++) {
+ if (!Polys[i]) {
+ POLYGON *p = Polys[i] = &Polygons[i];
+ memset(p, 0, sizeof(POLYGON));
+
+ p->polytype = type; // Polygon type
+ p->pIndex = pno;
+ p->tagState = TAG_OFF;
+ p->pointState = NOT_POINTING;
+ p->polyID = FROM_LE_32(pp->id); // Identifier
+
+ for (int j = 0; j < 4; j++) { // Polygon definition
+ p->cx[j] = (short)FROM_LE_32(pp->x[j]);
+ p->cy[j] = (short)FROM_LE_32(pp->y[j]);
+ }
+
+ return p;
+ }
+ }
+
+ error("Exceeded MaxPolys");
+ return NULL;
+}
+
+/**
+ * Initialise an EXIT polygon.
+ */
+static void InitExit(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(EXIT, pp, pno));
+}
+
+/**
+ * Initialise a PATH or NPATH polygon.
+ */
+static void InitPath(const POLY *pp, bool NodePath, int pno) {
+ POLYGON *p;
+
+ p = GetPolyEntry(PATH, pp, pno); // Obtain a slot
+
+ if (NodePath) {
+ p->subtype = NODE;
+ } else {
+ p->subtype = NORMAL;
+ }
+
+ // Clear out ajacent path pointers
+ memset(p->adjpaths, 0, MAXADJ*sizeof(POLYGON *));
+
+ FiddlyBit(p);
+ PseudoCentre(p);
+}
+
+
+/**
+ * Initialise a BLOCKING polygon.
+ */
+static void InitBlock(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(BLOCKING, pp, pno));
+}
+
+/**
+ * Initialise an extra BLOCKING polygon related to a moving actor.
+ * The width of the polygon depends on the width of the actor which is
+ * trying to walk through the actor you first thought of.
+ * This is for dynamic blocking.
+ */
+HPOLYGON InitExtraBlock(PMACTOR ca, PMACTOR ta) {
+ int caX, caY; // Calling actor co-ords
+ int taX, taY; // Test actor co-ords
+ int left, right;
+
+ GetMActorPosition(ca, &caX, &caY); // Calling actor co-ords
+ GetMActorPosition(ta, &taX, &taY); // Test actor co-ords
+
+ left = GetMActorLeft(ta) - (GetMActorRight(ca) - caX);
+ right = GetMActorRight(ta) + (caX - GetMActorLeft(ca));
+
+ memset(&extraBlock, 0, sizeof(extraBlock));
+
+ // The 3s on the y co-ordinates used to be 10s
+ extraBlock.cx[0] = (short)(left - 2);
+ extraBlock.cy[0] = (short)(taY - 3);
+ extraBlock.cx[1] = (short)(right + 2);
+ extraBlock.cy[1] = (short)(taY - 3);
+ extraBlock.cx[2] = (short)(right + 2);
+ extraBlock.cy[2] = (short)(taY + 3);
+ extraBlock.cx[3] = (short)(left - 2);
+ extraBlock.cy[3] = (short)(taY + 3);
+
+ FiddlyBit(&extraBlock); // Is this necessary?
+
+ Polys[MAX_POLY] = &extraBlock;
+ return MAX_POLY;
+}
+
+/**
+ * Initialise an EFFECT polygon.
+ */
+static void InitEffect(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(EFFECT, pp, pno));
+}
+
+
+/**
+ * Initialise a REFER polygon.
+ */
+static void InitRefer(const POLY *pp, int pno) {
+ POLYGON *p = GetPolyEntry(REFER, pp, pno); // Obtain a slot
+
+ p->subtype = FROM_LE_32(pp->reftype); // Refer type
+
+ FiddlyBit(p);
+}
+
+
+/**
+ * Initialise a TAG polygon.
+ */
+static void InitTag(const POLY *pp, int pno) {
+ FiddlyBit(GetPolyEntry(TAG, pp, pno));
+}
+
+
+/**
+ * Called at the start of a scene to initialise the polys in that scene.
+ */
+void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart) {
+ const POLY *pp; // Pointer to compiled data polygon structure
+
+ pHandle = ph;
+ noofPolys = numPoly;
+
+ if (Polygons == NULL) {
+ // first time - allocate memory for process list
+ Polygons = (POLYGON *)calloc(MaxPolys, sizeof(POLYGON));
+
+ // make sure memory allocated
+ if (Polygons == NULL) {
+ error("Cannot allocate memory for polygon data");
+ }
+ }
+
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]) {
+ Polys[i]->pointState = NOT_POINTING;
+ Polys[i] = NULL;
+ }
+ }
+
+ memset(RoutePaths, 0, sizeof(RoutePaths));
+
+ if (!bRestart)
+ memset(deadPolys, 0, sizeof(deadPolys));
+
+ pp = (const POLY *)LockMem(ph);
+ for (int i = 0; i < numPoly; i++, pp++) {
+ switch (FROM_LE_32(pp->type)) {
+ case POLY_PATH:
+ InitPath(pp, false, i);
+ break;
+
+ case POLY_NPATH:
+ InitPath(pp, true, i);
+ break;
+
+ case POLY_BLOCK:
+ InitBlock(pp, i);
+ break;
+
+ case POLY_REFER:
+ InitRefer(pp, i);
+ break;
+
+ case POLY_EFFECT:
+ InitEffect(pp, i);
+ break;
+
+ case POLY_EXIT:
+ InitExit(pp, i);
+ break;
+
+ case POLY_TAG:
+ InitTag(pp, i);
+ break;
+
+ default:
+ error("Unknown polygon type");
+ }
+ }
+ SetPathAdjacencies(); // Paths need to know the facts
+#ifdef DEBUG
+ CheckNPathIntegrity();
+#endif
+ SetExTags(ph); // Some tags may have been killed
+ SetExExits(ph); // Some exits may have been killed
+
+ if (bRestart)
+ SetExBlocks(); // Some blocks may have been killed
+}
+
+/**
+ * Called at the end of a scene to ditch all polygons.
+ */
+void DropPolygons() {
+ pathsOnRoute = 0;
+ memset(RoutePaths, 0, sizeof(RoutePaths));
+ RouteEnd = NULL;
+
+ for (int i = 0; i < noofPolys; i++) {
+ if (Polys[i]) {
+ Polys[i]->pointState = NOT_POINTING;
+ Polys[i] = NULL;
+ }
+ }
+ noofPolys = 0;
+ free(Polygons);
+ Polygons = NULL;
+}
+
+
+
+PTYPE PolyType(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (25)");
+
+ return Polys[hp]->polytype;
+}
+
+int PolySubtype(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (26)");
+
+ return Polys[hp]->subtype;
+}
+
+int PolyCentreX(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (27)");
+
+ return Polys[hp]->pcentrex;
+}
+
+int PolyCentreY(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (28)");
+
+ return Polys[hp]->pcentrey;
+}
+
+int PolyCornerX(HPOLYGON hp, int n) {
+ CHECK_HP(hp, "Out of range polygon handle (29)");
+
+ return Polys[hp]->cx[n];
+}
+
+int PolyCornerY(HPOLYGON hp, int n) {
+ CHECK_HP(hp, "Out of range polygon handle (30)");
+
+ return Polys[hp]->cy[n];
+}
+
+PSTATE PolyPointState(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (31)");
+
+ return Polys[hp]->pointState;
+}
+
+TSTATE PolyTagState(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (32)");
+
+ return Polys[hp]->tagState;
+}
+
+SCNHANDLE PolyTagHandle(HPOLYGON hp) {
+ CHECK_HP(hp, "Out of range polygon handle (33)");
+
+ return Polys[hp]->oTagHandle;
+}
+
+void SetPolyPointState(HPOLYGON hp, PSTATE ps) {
+ CHECK_HP(hp, "Out of range polygon handle (34)");
+
+ Polys[hp]->pointState = ps;
+}
+
+void SetPolyTagState(HPOLYGON hp, TSTATE ts) {
+ CHECK_HP(hp, "Out of range polygon handle (35)");
+
+ Polys[hp]->tagState = ts;
+}
+
+void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th) {
+ CHECK_HP(hp, "Out of range polygon handle (36)");
+
+ Polys[hp]->oTagHandle = th;
+}
+
+void MaxPolygons(int numPolys) {
+ assert(numPolys <= MAX_POLY);
+
+ MaxPolys = numPolys;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/polygons.h b/engines/tinsel/polygons.h
new file mode 100644
index 0000000000..90c57d5f99
--- /dev/null
+++ b/engines/tinsel/polygons.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Definition of POLYGON structure and functions in POLYGONS.C
+ */
+
+#ifndef TINSEL_POLYGONS_H // prevent multiple includes
+#define TINSEL_POLYGONS_H
+
+#include "tinsel/dw.h" // for SCNHANDLE
+#include "tinsel/scene.h" // for PPOLY and REEL
+
+namespace Tinsel {
+
+
+// Polygon Types
+enum PTYPE {
+ TEST, PATH, EXIT, BLOCKING,
+ EFFECT, REFER, TAG, EX_TAG, EX_EXIT, EX_BLOCK
+};
+
+// subtype
+enum {
+ NORMAL = 0,
+ NODE = 1 // For paths
+};
+
+// tagState
+enum TSTATE {
+ TAG_OFF, TAG_ON
+};
+
+// pointState
+enum PSTATE {
+ NO_POINT, NOT_POINTING, POINTING
+};
+
+
+enum {
+ NOPOLY = -1
+};
+
+
+
+/*-------------------------------------------------------------------------*/
+
+bool IsInPolygon(int xt, int yt, HPOLYGON p);
+HPOLYGON InPolygon(int xt, int yt, PTYPE type);
+void BlockingCorner(HPOLYGON poly, int *x, int *y, int tarx, int tary);
+void FindBestPoint(HPOLYGON path, int *x, int *y, int *line);
+bool IsAdjacentPath(HPOLYGON path1, HPOLYGON path2);
+HPOLYGON getPathOnTheWay(HPOLYGON from, HPOLYGON to);
+int NearestEndNode(HPOLYGON path, int x, int y);
+int NearEndNode(HPOLYGON spath, HPOLYGON dpath);
+int NearestNodeWithin(HPOLYGON npath, int x, int y);
+void NearestCorner(int *x, int *y, HPOLYGON spath, HPOLYGON dpath);
+bool IsPolyCorner(HPOLYGON hPath, int x, int y);
+int GetScale(HPOLYGON path, int y);
+void getNpathNode(HPOLYGON npath, int node, int *px, int *py);
+void getPolyTagInfo(HPOLYGON p, SCNHANDLE *hTagText, int *tagx, int *tagy);
+SCNHANDLE getPolyFilm(HPOLYGON p);
+void getPolyNode(HPOLYGON p, int *px, int *py);
+SCNHANDLE getPolyScript(HPOLYGON p);
+REEL getPolyReelType(HPOLYGON p);
+int32 getPolyZfactor(HPOLYGON p);
+int numNodes(HPOLYGON pp);
+void RebootDeadTags(void);
+void DisableBlock(int blockno);
+void EnableBlock(int blockno);
+void DisableTag(int tagno);
+void EnableTag(int tagno);
+void DisableExit(int exitno);
+void EnableExit(int exitno);
+HPOLYGON FirstPathPoly(void);
+HPOLYGON GetPolyHandle(int i);
+void InitPolygons(SCNHANDLE ph, int numPoly, bool bRestart);
+void DropPolygons(void);
+
+
+void SaveDeadPolys(bool *sdp);
+void RestoreDeadPolys(bool *sdp);
+
+/*-------------------------------------------------------------------------*/
+
+PTYPE PolyType(HPOLYGON hp); // ->type
+int PolySubtype(HPOLYGON hp); // ->subtype
+int PolyCentreX(HPOLYGON hp); // ->pcentrex
+int PolyCentreY(HPOLYGON hp); // ->pcentrey
+int PolyCornerX(HPOLYGON hp, int n); // ->cx[n]
+int PolyCornerY(HPOLYGON hp, int n); // ->cy[n]
+PSTATE PolyPointState(HPOLYGON hp); // ->pointState
+TSTATE PolyTagState(HPOLYGON hp); // ->tagState
+SCNHANDLE PolyTagHandle(HPOLYGON hp); // ->oTagHandle
+
+void SetPolyPointState(HPOLYGON hp, PSTATE ps); // ->pointState
+void SetPolyTagState(HPOLYGON hp, TSTATE ts); // ->tagState
+void SetPolyTagHandle(HPOLYGON hp, SCNHANDLE th);// ->oTagHandle
+
+void MaxPolygons(int maxPolys);
+
+/*-------------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_POLYGONS_H */
diff --git a/engines/tinsel/rince.cpp b/engines/tinsel/rince.cpp
new file mode 100644
index 0000000000..a9b24bcac9
--- /dev/null
+++ b/engines/tinsel/rince.cpp
@@ -0,0 +1,709 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.c"
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h" // multi-part object defintions etc.
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/timers.h"
+#include "tinsel/token.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static MACTOR Movers[MAX_MOVERS];
+
+
+/**
+ * RebootMovers
+ */
+void RebootMovers(void) {
+ memset(Movers, 0, sizeof(Movers));
+}
+
+/**
+ * Given an actor number, return pointer to its moving actor structure,
+ * if it is a moving actor.
+ */
+PMACTOR GetMover(int ano) {
+ int i;
+
+ // Slot 0 is reserved for lead actor
+ if (ano == LeadId() || ano == LEAD_ACTOR)
+ return &Movers[0];
+
+ for (i = 1; i < MAX_MOVERS; i++)
+ if (Movers[i].actorID == ano)
+ return &Movers[i];
+
+ return NULL;
+}
+
+/**
+ * Register an actor as being a moving one.
+ */
+PMACTOR SetMover(int ano) {
+ int i;
+
+ // Slot 0 is reserved for lead actor
+ if (ano == LeadId() || ano == LEAD_ACTOR) {
+ Movers[0].actorToken = TOKEN_LEAD;
+ Movers[0].actorID = LeadId();
+ return &Movers[0];
+ }
+
+ // Check it hasn't already been declared
+ for (i = 1; i < MAX_MOVERS; i++) {
+ if (Movers[i].actorID == ano) {
+ // Actor is already a moving actor
+ return &Movers[i];
+ }
+ }
+
+ // Find an empty slot
+ for (i = 1; i < MAX_MOVERS; i++)
+ if (!Movers[i].actorID) {
+ Movers[i].actorToken = TOKEN_LEAD + i;
+ Movers[i].actorID = ano;
+ return &Movers[i];
+ }
+
+ error("Too many moving actors");
+}
+
+/**
+ * Given an index, returns the associated moving actor.
+ *
+ * At the time of writing, used by the effect process.
+ */
+PMACTOR GetLiveMover(int index) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ if (Movers[index].MActorState == NORM_MACTOR)
+ return &Movers[index];
+ else
+ return NULL;
+}
+
+bool IsMAinEffectPoly(int index) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ return Movers[index].InEffect;
+}
+
+void SetMAinEffectPoly(int index, bool tf) {
+ assert(index >= 0 && index < MAX_MOVERS); // out of range
+
+ Movers[index].InEffect = tf;
+}
+
+/**
+ * Remove a moving actor from the current scene.
+ */
+void KillMActor(PMACTOR pActor) {
+ if (pActor->MActorState == NORM_MACTOR) {
+ pActor->MActorState = NO_MACTOR;
+ MultiDeleteObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj);
+ pActor->actorObj = NULL;
+ assert(g_scheduler->getCurrentProcess() != pActor->pProc);
+ g_scheduler->killProcess(pActor->pProc);
+ }
+}
+
+/**
+ * getMActorState
+ */
+MAS getMActorState(PMACTOR pActor) {
+ return pActor->MActorState;
+}
+
+/**
+ * If the actor's object exists, move it behind the background.
+ * MultiHideObject() is deliberately not used, as StepAnimScript() calls
+ * cause the object to re-appear.
+ */
+void hideMActor(PMACTOR pActor, int sf) {
+ assert(pActor); // Hiding null moving actor
+
+ pActor->aHidden = true;
+ pActor->SlowFactor = sf;
+
+ if (pActor->actorObj)
+ MultiSetZPosition(pActor->actorObj, -1);
+}
+
+/**
+ * getMActorHideState
+ */
+bool getMActorHideState(PMACTOR pActor) {
+ if (pActor)
+ return pActor->aHidden;
+ else
+ return false;
+}
+
+/**
+ * unhideMActor
+ */
+void unhideMActor(PMACTOR pActor) {
+ assert(pActor); // unHiding null moving actor
+
+ pActor->aHidden = false;
+
+ // Make visible on the screen
+ if (pActor->actorObj) {
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+ }
+}
+
+/**
+ * Get it into our heads that there's nothing doing.
+ * Called at the end of a scene.
+ */
+void DropMActors(void) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ Movers[i].MActorState = NO_MACTOR;
+ Movers[i].objx = 0;
+ Movers[i].objy = 0;
+ Movers[i].actorObj = NULL; // No moving actor objects
+
+ Movers[i].hCpath = NOPOLY; // No moving actor path
+ }
+}
+
+
+/**
+ * Reposition a moving actor.
+ */
+void MoveMActor(PMACTOR pActor, int x, int y) {
+ int z;
+ int node;
+ HPOLYGON hPath;
+
+ assert(pActor); // Moving null moving actor
+ assert(pActor->actorObj);
+
+ pActor->objx = x;
+ pActor->objy = y;
+ MultiSetAniXY(pActor->actorObj, x, y);
+
+ hPath = InPolygon(x, y, PATH);
+ if (hPath != NOPOLY) {
+ pActor->hCpath = hPath;
+ if (PolySubtype(hPath) == NODE) {
+ node = NearestNodeWithin(hPath, x, y);
+ getNpathNode(hPath, node, &pActor->objx, &pActor->objy);
+ pActor->hFnpath = hPath;
+ pActor->line = node;
+ pActor->npstatus = GOING_UP;
+ } else {
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ }
+
+ z = GetScale(hPath, pActor->objy);
+ pActor->scale = z;
+ SetMActorStanding(pActor);
+ } else {
+ pActor->bNoPath = true;
+
+ pActor->hFnpath = NOPOLY; // Ain't in one
+ pActor->npstatus = NOT_IN;
+
+ // Ensure legal reel and scale
+ if (pActor->dirn < 0 || pActor->dirn > 3)
+ pActor->dirn = FORWARD;
+ if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES)
+ pActor->scale = 1;
+ }
+}
+
+/**
+ * Get position of a moving actor.
+ */
+void GetMActorPosition(PMACTOR pActor, int *paniX, int *paniY) {
+ assert(pActor); // Getting null moving actor's position
+
+ if (pActor->actorObj != NULL)
+ GetAniPosition(pActor->actorObj, paniX, paniY);
+ else {
+ *paniX = 0;
+ *paniY = 0;
+ }
+}
+
+/**
+ * Moving actor's mid-top position.
+ */
+void GetMActorMidTopPosition(PMACTOR pActor, int *aniX, int *aniY) {
+ assert(pActor); // Getting null moving actor's mid-top position
+ assert(pActor->actorObj); // Getting null moving actor's mid-top position
+
+ *aniX = (MultiLeftmost(pActor->actorObj) + MultiRightmost(pActor->actorObj))/2;
+ *aniY = MultiHighest(pActor->actorObj);
+}
+
+/**
+ * Moving actor's left-most co-ordinate.
+ */
+int GetMActorLeft(PMACTOR pActor) {
+ assert(pActor); // Getting null moving actor's leftmost position
+ assert(pActor->actorObj); // Getting null moving actor's leftmost position
+
+ return MultiLeftmost(pActor->actorObj);
+}
+
+/**
+ * Moving actor's right-most co-ordinate.
+ */
+int GetMActorRight(PMACTOR pActor) {
+ assert(pActor); // Getting null moving actor's rightmost position
+ assert(pActor->actorObj); // Getting null moving actor's rightmost position
+
+ return MultiRightmost(pActor->actorObj);
+}
+
+/**
+ * See if moving actor is stood within a polygon.
+ */
+bool MActorIsInPolygon(PMACTOR pActor, HPOLYGON hp) {
+ assert(pActor); // Checking if null moving actor is in polygon
+ assert(pActor->actorObj); // Checking if null moving actor is in polygon
+
+ int aniX, aniY;
+ GetAniPosition(pActor->actorObj, &aniX, &aniY);
+
+ return IsInPolygon(aniX, aniY, hp);
+}
+
+/**
+ * Change which reel is playing for a moving actor.
+ */
+void AlterMActor(PMACTOR pActor, SCNHANDLE film, AR_FUNCTION fn) {
+ const FILM *pfilm;
+
+ assert(pActor->actorObj); // Altering null moving actor's animation script
+
+ if (fn == AR_POPREEL) {
+ film = pActor->pushedfilm; // Use the saved film
+ }
+ if (fn == AR_PUSHREEL) {
+ // Save the one we're replacing
+ pActor->pushedfilm = (pActor->TagReelRunning) ? pActor->lastfilm : 0;
+ }
+
+ if (film == 0) {
+ if (pActor->TagReelRunning) {
+ // Revert to 'normal' actor
+ SetMActorWalkReel(pActor, pActor->dirn, pActor->scale, true);
+ pActor->TagReelRunning = false;
+ }
+ } else {
+ pActor->lastfilm = film; // Remember this one
+
+ pfilm = (const FILM *)LockMem(film);
+ assert(pfilm != NULL);
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ pActor->scount = 0;
+
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+
+ if (fn == AR_WALKREEL) {
+ pActor->TagReelRunning = false;
+ pActor->walkReel = true;
+ } else {
+ pActor->TagReelRunning = true;
+ pActor->walkReel = false;
+
+#ifdef DEBUG
+ assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished!
+#else
+ StepAnimScript(&pActor->actorAnim); // 04/01/95
+#endif
+ }
+
+ // Hang on, we may not want him yet! 04/01/95
+ if (pActor->aHidden)
+ MultiSetZPosition(pActor->actorObj, -1);
+ }
+}
+
+/**
+ * Return the actor's direction.
+ */
+DIRREEL GetMActorDirection(PMACTOR pActor) {
+ return pActor->dirn;
+}
+
+/**
+ * Return the actor's scale.
+ */
+int GetMActorScale(PMACTOR pActor) {
+ return pActor->scale;
+}
+
+/**
+ * Point actor in specified derection
+ */
+void SetMActorDirection(PMACTOR pActor, DIRREEL dirn) {
+ pActor->dirn = dirn;
+}
+
+/**
+ * MAmoving
+ */
+bool MAmoving(PMACTOR pActor) {
+ return pActor->bMoving;
+}
+
+/**
+ * Return an actor's walk ticket.
+ */
+int GetActorTicket(PMACTOR pActor) {
+ return pActor->ticket;
+}
+
+/**
+ * Get actor to adopt its appropriate standing reel.
+ */
+void SetMActorStanding(PMACTOR pActor) {
+ assert(pActor->actorObj);
+ AlterMActor(pActor, pActor->StandReels[pActor->scale-1][pActor->dirn], AR_NORMAL);
+}
+
+/**
+ * Get actor to adopt its appropriate walking reel.
+ */
+void SetMActorWalkReel(PMACTOR pActor, DIRREEL reel, int scale, bool force) {
+ SCNHANDLE whichReel;
+ const FILM *pfilm;
+
+ // Kill off any play that may be going on for this actor
+ // and restore the real actor
+ storeActorReel(pActor->actorID, NULL, 0, NULL, 0, 0, 0);
+ unhideMActor(pActor);
+
+ // Don't do it if using a special walk reel
+ if (pActor->walkReel)
+ return;
+
+ if (force || pActor->scale != scale || pActor->dirn != reel) {
+ assert(reel >= 0 && reel <= 3 && scale > 0 && scale <= TOTAL_SCALES); // out of range scale or reel
+
+ // If scale change and both are regular scales
+ // and there's a scaling reel in the right direction
+ if (pActor->scale != scale
+ && scale <= NUM_MAINSCALES && pActor->scale <= NUM_MAINSCALES
+ && (whichReel = ScalingReel(pActor->actorID, pActor->scale, scale, reel)) != 0) {
+// error("Cripes!");
+ ; // Use what is now in 'whichReel'
+ } else {
+ whichReel = pActor->WalkReels[scale-1][reel];
+ assert(whichReel); // no reel
+ }
+
+ pfilm = (const FILM *)LockMem(whichReel);
+ assert(pfilm != NULL); // no film
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), 1);
+
+ // Synchronised walking reels
+ SkipFrames(&pActor->actorAnim, pActor->scount);
+
+ pActor->scale = scale;
+ pActor->dirn = reel;
+ }
+}
+
+/**
+ * Sort some stuff out at actor start-up time.
+ */
+static void InitialPathChecks(PMACTOR pActor, int xpos, int ypos) {
+ HPOLYGON hPath;
+ int node;
+ int z;
+
+ pActor->objx = xpos;
+ pActor->objy = ypos;
+
+ /*--------------------------------------
+ | If Actor is in a follow nodes path, |
+ | position it at the nearest node. |
+ --------------------------------------*/
+ hPath = InPolygon(xpos, ypos, PATH);
+
+ if (hPath != NOPOLY) {
+ pActor->hCpath = hPath;
+ if (PolySubtype(hPath) == NODE) {
+ node = NearestNodeWithin(hPath, xpos, ypos);
+ getNpathNode(hPath, node, &pActor->objx, &pActor->objy);
+ pActor->hFnpath = hPath;
+ pActor->line = node;
+ pActor->npstatus = GOING_UP;
+ }
+
+ z = GetScale(hPath, pActor->objy);
+ } else {
+ pActor->bNoPath = true;
+
+ z = GetScale(FirstPathPoly(), pActor->objy);
+ }
+ SetMActorWalkReel(pActor, FORWARD, z, false);
+}
+
+/**
+ * Clear everything out at actor start-up time.
+ */
+static void InitMActor(PMACTOR pActor) {
+
+ pActor->objx = pActor->objy = 0;
+ pActor->targetX = pActor->targetY = -1;
+ pActor->ItargetX = pActor->ItargetY = -1;
+ pActor->hIpath = NOPOLY;
+ pActor->UtargetX = pActor->UtargetY = -1;
+ pActor->hUpath = NOPOLY;
+ pActor->hCpath = NOPOLY;
+
+ pActor->over = false;
+ pActor->InDifficulty = NO_PROB;
+
+ pActor->hFnpath = NOPOLY;
+ pActor->npstatus = NOT_IN;
+ pActor->line = 0;
+
+ pActor->Tline = 0;
+
+ pActor->TagReelRunning = false;
+
+ if (pActor->dirn != FORWARD || pActor->dirn != AWAY
+ || pActor->dirn != LEFTREEL || pActor->dirn != RIGHTREEL)
+ pActor->dirn = FORWARD;
+
+ if (pActor->scale < 0 || pActor->scale > TOTAL_SCALES)
+ pActor->scale = 1;
+
+ pActor->scount = 0;
+
+ pActor->fromx = pActor->fromy = 0;
+
+ pActor->bMoving = false;
+ pActor->bNoPath = false;
+ pActor->bIgPath = false;
+ pActor->walkReel = false;
+
+ pActor->actorObj = NULL;
+
+ pActor->lastfilm = 0;
+ pActor->pushedfilm = 0;
+
+ pActor->InEffect = false;
+ pActor->aHidden = false; // 20/2/95
+}
+
+static void MActorProcessHelper(int X, int Y, int id, PMACTOR pActor) {
+ const FILM *pfilm;
+ const MULTI_INIT *pmi;
+ const FRAME *pFrame;
+ IMAGE *pim;
+
+
+ assert(BackPal()); // Can't start actor without a background palette
+ assert(pActor->WalkReels[0][FORWARD]); // Starting actor process without walk reels
+
+ InitMActor(pActor);
+ InitialPathChecks(pActor, X, Y);
+
+ pfilm = (const FILM *)LockMem(pActor->WalkReels[0][FORWARD]);
+ pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(pfilm->reels[0].mobj));
+
+//---
+ pFrame = (const FRAME *)LockMem(FROM_LE_32(pmi->hMulFrame));
+
+ // get pointer to image
+ pim = (IMAGE *)LockMem(READ_LE_UINT32(pFrame)); // handle to image
+ pim->hImgPal = TO_LE_32(BackPal());
+//---
+ pActor->actorObj = MultiInitObject(pmi);
+
+/**/ assert(pActor->actorID == id);
+ pActor->actorID = id;
+
+ // add it to display list
+ MultiInsertObject(GetPlayfieldList(FIELD_WORLD), pActor->actorObj);
+ storeActorReel(id, NULL, 0, pActor->actorObj, 0, 0, 0);
+
+ InitStepAnimScript(&pActor->actorAnim, pActor->actorObj, FROM_LE_32(pfilm->reels[0].script), ONE_SECOND / FROM_LE_32(pfilm->frate));
+ pActor->scount = 0;
+
+ MultiSetAniXY(pActor->actorObj, pActor->objx, pActor->objy);
+
+ // If no path, just use first path in the scene
+ if (pActor->hCpath != NOPOLY)
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(pActor->hCpath));
+ else
+ MAsetZPos(pActor, pActor->objy, getPolyZfactor(FirstPathPoly()));
+
+ // Make him the right size
+ SetMActorStanding(pActor);
+
+//**** if added 18/11/94, am
+ if (X != MAGICX && Y != MAGICY) {
+ hideMActor(pActor, 0); // Allows a play to come in before this appears
+ pActor->aHidden = false; // ...but don't stay hidden
+ }
+
+ pActor->MActorState = NORM_MACTOR;
+}
+
+/**
+ * Moving actor process - 1 per moving actor in current scene.
+ */
+void MActorProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = *(PMACTOR *)param;
+
+ CORO_BEGIN_CODE(_ctx);
+
+ while (1) {
+ if (pActor->TagReelRunning) {
+ if (!pActor->aHidden)
+#ifdef DEBUG
+ assert(StepAnimScript(&pActor->actorAnim) != ScriptFinished); // Actor reel has finished!
+#else
+ StepAnimScript(&pActor->actorAnim);
+#endif
+ } else
+ DoMoveActor(pActor);
+
+ CORO_SLEEP(1); // allow rescheduling
+
+ }
+
+ CORO_END_CODE;
+}
+
+void MActorProcessCreate(int X, int Y, int id, PMACTOR pActor) {
+ MActorProcessHelper(X, Y, id, pActor);
+ pActor->pProc = g_scheduler->createProcess(PID_MACTOR, MActorProcess, &pActor, sizeof(PMACTOR));
+}
+
+
+/**
+ * Check for moving actor collision.
+ */
+PMACTOR InMActorBlock(PMACTOR pActor, int x, int y) {
+ int caX; // Calling actor's pos'n
+ int caL, caR; // Calling actor's left and right
+ int taX, taY; // Test actor's pos'n
+ int taL, taR; // Test actor's left and right
+
+ caX = pActor->objx;
+ if (pActor->hFnpath != NOPOLY || bNoBlocking)
+ return NULL;
+
+ caL = GetMActorLeft(pActor) + x - caX;
+ caR = GetMActorRight(pActor) + x - caX;
+
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ if (pActor == &Movers[i] || Movers[i].MActorState == NO_MACTOR)
+ continue;
+
+ // At around the same height?
+ GetMActorPosition(&Movers[i], &taX, &taY);
+ if (Movers[i].hFnpath != NOPOLY)
+ continue;
+
+ if (ABS(y - taY) > 2) // 2 was 8
+ continue;
+
+ // To the left?
+ taL = GetMActorLeft(&Movers[i]);
+ if (caR <= taL)
+ continue;
+
+ // To the right?
+ taR = GetMActorRight(&Movers[i]);
+ if (caL >= taR)
+ continue;
+
+ return &Movers[i];
+ }
+ return NULL;
+}
+
+/**
+ * Copies key information for savescn.c to store away.
+ */
+void SaveMovers(SAVED_MOVER *sMoverInfo) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ sMoverInfo[i].MActorState= Movers[i].MActorState;
+ sMoverInfo[i].actorID = Movers[i].actorID;
+ sMoverInfo[i].objx = Movers[i].objx;
+ sMoverInfo[i].objy = Movers[i].objy;
+ sMoverInfo[i].lastfilm = Movers[i].lastfilm;
+
+ memcpy(sMoverInfo[i].WalkReels, Movers[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(sMoverInfo[i].StandReels, Movers[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(sMoverInfo[i].TalkReels, Movers[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ }
+}
+
+void RestoreAuxScales(SAVED_MOVER *sMoverInfo) {
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ memcpy(Movers[i].WalkReels, sMoverInfo[i].WalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(Movers[i].StandReels, sMoverInfo[i].StandReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ memcpy(Movers[i].TalkReels, sMoverInfo[i].TalkReels, TOTAL_SCALES*4*sizeof(SCNHANDLE));
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/rince.h b/engines/tinsel/rince.h
new file mode 100644
index 0000000000..7ccf017c65
--- /dev/null
+++ b/engines/tinsel/rince.h
@@ -0,0 +1,209 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.h"
+ */
+
+#ifndef TINSEL_RINCE_H // prevent multiple includes
+#define TINSEL_RINCE_H
+
+#include "tinsel/anim.h" // for ANIM
+#include "tinsel/scene.h" // for TFTYPE
+
+namespace Tinsel {
+
+struct OBJECT;
+struct PROCESS;
+
+enum NPS {NOT_IN, GOING_UP, GOING_DOWN, LEAVING, ENTERING};
+
+enum IND {NO_PROB, TRY_CENTRE, TRY_CORNER, TRY_NEXTCORNER};
+
+enum MAS {NO_MACTOR, NORM_MACTOR};
+
+enum DIRREEL{ LEFTREEL, RIGHTREEL, FORWARD, AWAY };
+
+enum {
+ NUM_MAINSCALES = 5,
+ NUM_AUXSCALES = 5,
+ TOTAL_SCALES = NUM_MAINSCALES + NUM_AUXSCALES
+};
+
+struct MACTOR {
+
+ int objx; /* Co-ordinates object */
+ int objy;
+
+ int targetX, targetY;
+ int ItargetX, ItargetY; /* Intermediate destination */
+ HPOLYGON hIpath;
+ int UtargetX, UtargetY; /* Ultimate destination */
+ HPOLYGON hUpath;
+
+ HPOLYGON hCpath;
+
+ bool over;
+ int ticket;
+
+ IND InDifficulty;
+
+ /* For use in 'follow nodes' polygons */
+ HPOLYGON hFnpath;
+ NPS npstatus;
+ int line;
+
+ int Tline; // NEW
+
+ bool TagReelRunning;
+
+
+ /* Used internally */
+ DIRREEL dirn; // Current reel
+ int scale; // Current scale
+ int scount; // Step count for walking reel synchronisation
+
+ unsigned fromx;
+ unsigned fromy;
+
+ bool bMoving; // Set this to TRUE during a walk
+
+ bool bNoPath;
+ bool bIgPath;
+ bool walkReel;
+
+ OBJECT *actorObj; // Actor's object
+ ANIM actorAnim; // Actor's animation script
+
+ SCNHANDLE lastfilm; // } Used by AlterActor()
+ SCNHANDLE pushedfilm; // }
+
+ int actorID;
+ int actorToken;
+
+ SCNHANDLE WalkReels[TOTAL_SCALES][4];
+ SCNHANDLE StandReels[TOTAL_SCALES][4];
+ SCNHANDLE TalkReels[TOTAL_SCALES][4];
+
+ MAS MActorState;
+
+ bool aHidden;
+ int SlowFactor; // Slow down movement while hidden
+
+ bool stop;
+
+ /* NOTE: If effect polys can overlap, this needs improving */
+ bool InEffect;
+
+ PROCESS *pProc;
+};
+typedef MACTOR *PMACTOR;
+
+//---------------------------------------------------------------------------
+
+
+void MActorProcessCreate(int X, int Y, int id, MACTOR *pActor);
+
+
+enum AR_FUNCTION { AR_NORMAL, AR_PUSHREEL, AR_POPREEL, AR_WALKREEL };
+
+
+MACTOR *GetMover(int ano);
+MACTOR *SetMover(int ano);
+void KillMActor(MACTOR *pActor);
+MACTOR *GetLiveMover(int index);
+
+MAS getMActorState(MACTOR *psActor);
+
+void hideMActor(MACTOR *pActor, int sf);
+bool getMActorHideState(MACTOR *pActor);
+void unhideMActor(MACTOR *pActor);
+void DropMActors(void);
+void MoveMActor(MACTOR *pActor, int x, int y);
+
+void GetMActorPosition(MACTOR *pActor, int *aniX, int *aniY);
+void GetMActorMidTopPosition(MACTOR *pActor, int *aniX, int *aniY);
+int GetMActorLeft(MACTOR *pActor);
+int GetMActorRight(MACTOR *pActor);
+
+bool MActorIsInPolygon(MACTOR *pActor, HPOLYGON hPoly);
+void AlterMActor(MACTOR *actor, SCNHANDLE film, AR_FUNCTION fn);
+DIRREEL GetMActorDirection(MACTOR *pActor);
+int GetMActorScale(MACTOR *pActor);
+void SetMActorDirection(MACTOR *pActor, DIRREEL dirn);
+void SetMActorStanding(MACTOR *actor);
+void SetMActorWalkReel(MACTOR *actor, DIRREEL reel, int scale, bool force);
+
+MACTOR *InMActorBlock(MACTOR *pActor, int x, int y);
+
+void RebootMovers(void);
+
+bool IsMAinEffectPoly(int index);
+void SetMAinEffectPoly(int index, bool tf);
+
+bool MAmoving(MACTOR *pActor);
+
+int GetActorTicket(MACTOR *pActor);
+
+/*----------------------------------------------------------------------*/
+
+struct SAVED_MOVER {
+
+ MAS MActorState;
+ int actorID;
+ int objx;
+ int objy;
+ SCNHANDLE lastfilm;
+
+ SCNHANDLE WalkReels[TOTAL_SCALES][4];
+ SCNHANDLE StandReels[TOTAL_SCALES][4];
+ SCNHANDLE TalkReels[TOTAL_SCALES][4];
+
+};
+
+void SaveMovers(SAVED_MOVER *sMoverInfo);
+void RestoreAuxScales(SAVED_MOVER *sMoverInfo);
+
+/*----------------------------------------------------------------------*/
+
+/*
+* Dodgy bit...
+* These functions are now in MAREELS.C, but I can't be bothered to
+* create a new header file.
+*/
+SCNHANDLE GetMactorTalkReel(MACTOR *pAactor, TFTYPE dirn);
+
+void setscalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away);
+SCNHANDLE ScalingReel(int ano, int scale1, int scale2, DIRREEL reel);
+void RebootScalingReels(void);
+
+enum {
+ MAGICX = -101,
+ MAGICY = -102
+};
+
+/*----------------------------------------------------------------------*/
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_RINCE_H */
diff --git a/engines/tinsel/saveload.cpp b/engines/tinsel/saveload.cpp
new file mode 100644
index 0000000000..1a6cc1202a
--- /dev/null
+++ b/engines/tinsel/saveload.cpp
@@ -0,0 +1,475 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Save and restore scene and game.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/dw.h"
+#include "tinsel/inventory.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/serializer.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinlib.h"
+#include "tinsel/tinsel.h"
+
+#include "common/savefile.h"
+
+namespace Tinsel {
+
+
+/**
+ * The current savegame format version.
+ * Our save/load system uses an elaborate scheme to allow us to modify the
+ * savegame while keeping full backward compatibility, in the sense that newer
+ * ScummVM versions always are able to load old savegames.
+ * In order to achieve that, we store a version in the savegame files, and whenever
+ * the savegame layout is modified, the version is incremented.
+ *
+ * This roughly works by marking each savegame entry with a range of versions
+ * for which it is valid; the save/load code iterates over all entries, but
+ * only saves/loads those which are valid for the version of the savegame
+ * which is being loaded/saved currently.
+ */
+#define CURRENT_VER 1
+// TODO: Not yet used
+
+/**
+ * An auxillary macro, used to specify savegame versions. We use this instead
+ * of just writing the raw version, because this way they stand out more to
+ * the reading eye, making it a bit easier to navigate through the code.
+ */
+#define VER(x) x
+
+
+
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+// in DOS_DW.C
+extern void syncSCdata(Serializer &s);
+
+// in DOS_MAIN.C
+//char HardDriveLetter(void);
+
+// in PCODE.C
+extern void syncGlobInfo(Serializer &s);
+
+// in POLYGONS.C
+extern void syncPolyInfo(Serializer &s);
+
+// in SAVESCN.CPP
+extern void RestoreScene(SAVED_DATA *sd, bool bFadeOut);
+
+//----------------- LOCAL DEFINES --------------------
+
+struct SaveGameHeader {
+ uint32 id;
+ uint32 size;
+ uint32 ver;
+ char desc[SG_DESC_LEN];
+ struct tm dateTime;
+};
+
+enum {
+ SAVEGAME_ID = 0x44575399, // = 'DWSc' = "DiscWorld ScummVM"
+ SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7
+};
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int numSfiles = 0;
+static SFILES savedFiles[MAX_SFILES];
+
+static bool NeedLoad = true;
+
+static SAVED_DATA *srsd = 0;
+static int RestoreGameNumber = 0;
+static char *SaveSceneName = 0;
+static const char *SaveSceneDesc = 0;
+static int *SaveSceneSsCount = 0;
+static char *SaveSceneSsData = 0; // points to 'SAVED_DATA ssdata[MAX_NEST]'
+
+static SRSTATE SRstate = SR_IDLE;
+
+//------------- SAVE/LOAD SUPPORT METHODS ----------------
+
+static void syncTime(Serializer &s, struct tm &t) {
+ s.syncAsUint16LE(t.tm_year);
+ s.syncAsByte(t.tm_mon);
+ s.syncAsByte(t.tm_mday);
+ s.syncAsByte(t.tm_hour);
+ s.syncAsByte(t.tm_min);
+ s.syncAsByte(t.tm_sec);
+ if (s.isLoading()) {
+ t.tm_wday = 0;
+ t.tm_yday = 0;
+ t.tm_isdst = 0;
+ }
+}
+
+static bool syncSaveGameHeader(Serializer &s, SaveGameHeader &hdr) {
+ s.syncAsUint32LE(hdr.id);
+ s.syncAsUint32LE(hdr.size);
+ s.syncAsUint32LE(hdr.ver);
+
+ s.syncBytes((byte *)hdr.desc, SG_DESC_LEN);
+ hdr.desc[SG_DESC_LEN - 1] = 0;
+
+ syncTime(s, hdr.dateTime);
+
+ int tmp = hdr.size - s.bytesSynced();
+ // Perform sanity check
+ if (tmp < 0 || hdr.id != SAVEGAME_ID || hdr.ver > CURRENT_VER || hdr.size > 1024)
+ return false;
+ // Skip over any extra bytes
+ while (tmp-- > 0) {
+ byte b = 0;
+ s.syncAsByte(b);
+ }
+ return true;
+}
+
+static void syncSavedMover(Serializer &s, SAVED_MOVER &sm) {
+ SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.WalkReels, (SCNHANDLE *)&sm.StandReels, (SCNHANDLE *)&sm.TalkReels };
+
+ s.syncAsUint32LE(sm.MActorState);
+ s.syncAsSint32LE(sm.actorID);
+ s.syncAsSint32LE(sm.objx);
+ s.syncAsSint32LE(sm.objy);
+ s.syncAsUint32LE(sm.lastfilm);
+
+ for (int pIndex = 0; pIndex < 3; ++pIndex) {
+ SCNHANDLE *p = pList[pIndex];
+ for (int i = 0; i < TOTAL_SCALES * 4; ++i)
+ s.syncAsUint32LE(*p++);
+ }
+}
+
+static void syncSavedActor(Serializer &s, SAVED_ACTOR &sa) {
+ s.syncAsUint16LE(sa.actorID);
+ s.syncAsUint16LE(sa.z);
+ s.syncAsUint32LE(sa.bAlive);
+ s.syncAsUint32LE(sa.presFilm);
+ s.syncAsUint16LE(sa.presRnum);
+ s.syncAsUint16LE(sa.presX);
+ s.syncAsUint16LE(sa.presY);
+}
+
+extern void syncAllActorsAlive(Serializer &s);
+
+static void syncNoScrollB(Serializer &s, NOSCROLLB &ns) {
+ s.syncAsSint32LE(ns.ln);
+ s.syncAsSint32LE(ns.c1);
+ s.syncAsSint32LE(ns.c2);
+}
+
+static void syncSavedData(Serializer &s, SAVED_DATA &sd) {
+ s.syncAsUint32LE(sd.SavedSceneHandle);
+ s.syncAsUint32LE(sd.SavedBgroundHandle);
+ for (int i = 0; i < MAX_MOVERS; ++i)
+ syncSavedMover(s, sd.SavedMoverInfo[i]);
+ for (int i = 0; i < MAX_SAVED_ACTORS; ++i)
+ syncSavedActor(s, sd.SavedActorInfo[i]);
+
+ s.syncAsSint32LE(sd.NumSavedActors);
+ s.syncAsSint32LE(sd.SavedLoffset);
+ s.syncAsSint32LE(sd.SavedToffset);
+ for (int i = 0; i < MAX_INTERPRET; ++i)
+ sd.SavedICInfo[i].syncWithSerializer(s);
+ for (int i = 0; i < MAX_POLY; ++i)
+ s.syncAsUint32LE(sd.SavedDeadPolys[i]);
+ s.syncAsUint32LE(sd.SavedControl);
+ s.syncAsUint32LE(sd.SavedMidi);
+ s.syncAsUint32LE(sd.SavedLoop);
+ s.syncAsUint32LE(sd.SavedNoBlocking);
+
+ // SavedNoScrollData
+ for (int i = 0; i < MAX_VNOSCROLL; ++i)
+ syncNoScrollB(s, sd.SavedNoScrollData.NoVScroll[i]);
+ for (int i = 0; i < MAX_HNOSCROLL; ++i)
+ syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]);
+ s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV);
+ s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH);
+}
+
+
+/**
+ * Called when saving a game to a new file.
+ * Generates a new, unique, filename.
+ */
+static char *NewName(void) {
+ static char result[FNAMELEN];
+ int i;
+ int ano = 1; // Allocated number
+
+ while (1) {
+ Common::String fname = _vm->getSavegameFilename(ano);
+ strcpy(result, fname.c_str());
+
+ for (i = 0; i < numSfiles; i++)
+ if (!strcmp(savedFiles[i].name, result))
+ break;
+
+ if (i == numSfiles)
+ break;
+ ano++;
+ }
+
+ return result;
+}
+
+/**
+ * Interrogate the current DOS directory for saved game files.
+ * Store the file details, ordered by time, in savedFiles[] and return
+ * the number of files found).
+ */
+int getList(void) {
+ // No change since last call?
+ // TODO/FIXME: Just always reload this data? Be careful about slow downs!!!
+ if (!NeedLoad)
+ return numSfiles;
+
+ int i;
+
+ const Common::String pattern = _vm->getSavegamePattern();
+ Common::StringList files = _vm->getSaveFileMan()->listSavefiles(pattern.c_str());
+
+ numSfiles = 0;
+
+ for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) {
+ if (numSfiles >= MAX_SFILES)
+ break;
+
+ const Common::String &fname = *file;
+ Common::InSaveFile *f = _vm->getSaveFileMan()->openForLoading(fname.c_str());
+ if (f == NULL) {
+ continue;
+ }
+
+ // Try to load save game header
+ Serializer s(f, 0);
+ SaveGameHeader hdr;
+ bool validHeader = syncSaveGameHeader(s, hdr);
+ delete f;
+ if (!validHeader) {
+ continue; // Invalid header, or savegame too new -> skip it
+ // TODO: In SCUMM, we still show an entry for the save, but with description
+ // "incompatible version".
+ }
+
+ i = numSfiles;
+#ifndef DISABLE_SAVEGAME_SORTING
+ for (i = 0; i < numSfiles; i++) {
+ if (difftime(mktime(&hdr.dateTime), mktime(&savedFiles[i].dateTime)) > 0) {
+ Common::copy_backward(&savedFiles[i], &savedFiles[numSfiles], &savedFiles[numSfiles + 1]);
+ break;
+ }
+ }
+#endif
+
+ strncpy(savedFiles[i].name, fname.c_str(), FNAMELEN);
+ strncpy(savedFiles[i].desc, hdr.desc, SG_DESC_LEN);
+ savedFiles[i].desc[SG_DESC_LEN - 1] = 0;
+ savedFiles[i].dateTime = hdr.dateTime;
+
+ ++numSfiles;
+ }
+
+ // Next getList() needn't do its stuff again
+ NeedLoad = false;
+
+ return numSfiles;
+}
+
+
+char *ListEntry(int i, letype which) {
+ if (i == -1)
+ i = numSfiles;
+
+ assert(i >= 0);
+
+ if (i < numSfiles)
+ return which == LE_NAME ? savedFiles[i].name : savedFiles[i].desc;
+ else
+ return NULL;
+}
+
+static void DoSync(Serializer &s) {
+ int sg;
+
+ syncSavedData(s, *srsd);
+ syncGlobInfo(s); // Glitter globals
+ syncInvInfo(s); // Inventory data
+
+ // Held object
+ if (s.isSaving())
+ sg = WhichItemHeld();
+ s.syncAsSint32LE(sg);
+ if (s.isLoading())
+ HoldItem(sg);
+
+ syncTimerInfo(s); // Timer data
+ syncPolyInfo(s); // Dead polygon data
+ syncSCdata(s); // Hook Scene and delayed scene
+
+ s.syncAsSint32LE(*SaveSceneSsCount);
+
+ if (*SaveSceneSsCount != 0) {
+ SAVED_DATA *sdPtr = (SAVED_DATA *)SaveSceneSsData;
+ for (int i = 0; i < *SaveSceneSsCount; ++i, ++sdPtr)
+ syncSavedData(s, *sdPtr);
+ }
+
+ syncAllActorsAlive(s);
+}
+
+/**
+ * DoRestore
+ */
+static bool DoRestore(void) {
+ Common::InSaveFile *f;
+ uint32 id;
+
+ f = _vm->getSaveFileMan()->openForLoading(savedFiles[RestoreGameNumber].name);
+ if (f == NULL) {
+ return false;
+ }
+
+ Serializer s(f, 0);
+ SaveGameHeader hdr;
+ if (!syncSaveGameHeader(s, hdr)) {
+ delete f; // Invalid header, or savegame too new -> skip it
+ return false;
+ }
+
+ DoSync(s);
+
+ id = f->readSint32LE();
+ if (id != (uint32)0xFEEDFACE)
+ error("Incompatible saved game");
+
+ bool failed = f->ioFailed();
+
+ delete f;
+
+ return !failed;
+}
+
+/**
+ * DoSave
+ */
+static void DoSave(void) {
+ Common::OutSaveFile *f;
+ const char *fname;
+
+ // Next getList() must do its stuff again
+ NeedLoad = true;
+
+ if (SaveSceneName == NULL)
+ SaveSceneName = NewName();
+ if (SaveSceneDesc[0] == 0)
+ SaveSceneDesc = "unnamed";
+
+ fname = SaveSceneName;
+
+ f = _vm->getSaveFileMan()->openForSaving(fname);
+ if (f == NULL)
+ return;
+
+ Serializer s(0, f);
+
+ // Write out a savegame header
+ SaveGameHeader hdr;
+ hdr.id = SAVEGAME_ID;
+ hdr.size = SAVEGAME_HEADER_SIZE;
+ hdr.ver = CURRENT_VER;
+ memcpy(hdr.desc, SaveSceneDesc, SG_DESC_LEN);
+ hdr.desc[SG_DESC_LEN - 1] = 0;
+ g_system->getTimeAndDate(hdr.dateTime);
+ if (!syncSaveGameHeader(s, hdr) || f->ioFailed()) {
+ goto save_failure;
+ }
+
+ DoSync(s);
+
+ // Write out the special Id for Discworld savegames
+ f->writeUint32LE(0xFEEDFACE);
+ if (f->ioFailed())
+ goto save_failure;
+
+ f->finalize();
+ delete f;
+ return;
+
+save_failure:
+ delete f;
+ _vm->getSaveFileMan()->removeSavefile(fname);
+}
+
+/**
+ * ProcessSRQueue
+ */
+void ProcessSRQueue(void) {
+ switch (SRstate) {
+ case SR_DORESTORE:
+ if (DoRestore()) {
+ RestoreScene(srsd, false);
+ }
+ SRstate = SR_IDLE;
+ break;
+
+ case SR_DOSAVE:
+ DoSave();
+ SRstate = SR_IDLE;
+ break;
+ default:
+ break;
+ }
+}
+
+
+void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
+ assert(SRstate == SR_IDLE);
+
+ SaveSceneName = name;
+ SaveSceneDesc = desc;
+ SaveSceneSsCount = pSsCount;
+ SaveSceneSsData = (char *)pSsData;
+ srsd = sd;
+ SRstate = SR_DOSAVE;
+}
+
+void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
+ assert(num >= 0);
+
+ RestoreGameNumber = num;
+ SaveSceneSsCount = pSsCount;
+ SaveSceneSsData = (char *)pSsData;
+ srsd = sd;
+ SRstate = SR_DORESTORE;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/savescn.cpp b/engines/tinsel/savescn.cpp
new file mode 100644
index 0000000000..9f0d7b9039
--- /dev/null
+++ b/engines/tinsel/savescn.cpp
@@ -0,0 +1,336 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Save and restore scene and game.
+ */
+
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/dw.h"
+#include "tinsel/faders.h" // FadeOutFast()
+#include "tinsel/graphics.h" // ClearScreen()
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/music.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h"
+#include "tinsel/tinlib.h"
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- EXTERN FUNCTIONS --------------------
+
+// in BG.C
+extern void startupBackground(SCNHANDLE bfilm);
+extern SCNHANDLE GetBgroundHandle(void);
+extern void SetDoFadeIn(bool tf);
+
+// In DOS_DW.C
+void RestoreMasterProcess(INT_CONTEXT *pic);
+
+// in EVENTS.C (declared here and not in events.h because of strange goings-on)
+void RestoreProcess(INT_CONTEXT *pic);
+
+// in PLAY.C
+extern void playThisReel(SCNHANDLE film, short reelnum, short z, int x, int y);
+
+// in SCENE.C
+extern SCNHANDLE GetSceneHandle(void);
+extern void NewScene(SCNHANDLE scene, int entry);
+
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+enum {
+ RS_COUNT = 5, // Restore scene count
+
+ MAX_NEST = 4
+};
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static bool ASceneIsSaved = false;
+
+static int savedSceneCount = 0;
+
+//static SAVED_DATA s_ssData[MAX_NEST];
+static SAVED_DATA *s_ssData = 0;
+static SAVED_DATA sgData;
+
+static SAVED_DATA *s_rsd = 0;
+
+static int s_restoreSceneCount = 0;
+
+static bool bNoFade = false;
+
+//----------------- FORWARD REFERENCES --------------------
+
+
+
+void InitialiseSs(void) {
+ if (s_ssData == NULL) {
+ s_ssData = (SAVED_DATA *)calloc(MAX_NEST, sizeof(SAVED_DATA));
+ if (s_ssData == NULL) {
+ error("Cannot allocate memory for scene changes");
+ }
+ } else
+ savedSceneCount = 0;
+}
+
+void FreeSs(void) {
+ if (s_ssData) {
+ free(s_ssData);
+ s_ssData = NULL;
+ }
+}
+
+/**
+ * Save current scene.
+ * @param sd Pointer to the scene data
+ */
+void SaveScene(SAVED_DATA *sd) {
+ sd->SavedSceneHandle = GetSceneHandle();
+ sd->SavedBgroundHandle = GetBgroundHandle();
+ SaveMovers(sd->SavedMoverInfo);
+ sd->NumSavedActors = SaveActors(sd->SavedActorInfo);
+ PlayfieldGetPos(FIELD_WORLD, &sd->SavedLoffset, &sd->SavedToffset);
+ SaveInterpretContexts(sd->SavedICInfo);
+ SaveDeadPolys(sd->SavedDeadPolys);
+ sd->SavedControl = TestToken(TOKEN_CONTROL);
+ CurrentMidiFacts(&sd->SavedMidi, &sd->SavedLoop);
+ sd->SavedNoBlocking = bNoBlocking;
+ GetNoScrollData(&sd->SavedNoScrollData);
+
+ ASceneIsSaved = true;
+}
+
+/**
+ * Initiate restoration of the saved scene.
+ * @param sd Pointer to the scene data
+ * @param bFadeOut Flag to perform a fade out
+ */
+void RestoreScene(SAVED_DATA *sd, bool bFadeOut) {
+ s_rsd = sd;
+
+ if (bFadeOut)
+ s_restoreSceneCount = RS_COUNT + COUNTOUT_COUNT; // Set restore scene count
+ else
+ s_restoreSceneCount = RS_COUNT; // Set restore scene count
+}
+
+/**
+ * Checks that all non-moving actors are playing the same reel as when
+ * the scene was saved.
+ * Also 'stand' all the moving actors at their saved positions.
+ */
+void sortActors(SAVED_DATA *rsd) {
+ for (int i = 0; i < rsd->NumSavedActors; i++) {
+ ActorsLife(rsd->SavedActorInfo[i].actorID, rsd->SavedActorInfo[i].bAlive);
+
+ // Should be playing the same reel.
+ if (rsd->SavedActorInfo[i].presFilm != 0) {
+ if (!actorAlive(rsd->SavedActorInfo[i].actorID))
+ continue;
+
+ playThisReel(rsd->SavedActorInfo[i].presFilm, rsd->SavedActorInfo[i].presRnum, rsd->SavedActorInfo[i].z,
+ rsd->SavedActorInfo[i].presX, rsd->SavedActorInfo[i].presY);
+ }
+ }
+
+ RestoreAuxScales(rsd->SavedMoverInfo);
+ for (int i = 0; i < MAX_MOVERS; i++) {
+ if (rsd->SavedMoverInfo[i].MActorState == NORM_MACTOR)
+ stand(rsd->SavedMoverInfo[i].actorID, rsd->SavedMoverInfo[i].objx,
+ rsd->SavedMoverInfo[i].objy, rsd->SavedMoverInfo[i].lastfilm);
+ }
+}
+
+
+//---------------------------------------------------------------------------
+
+void ResumeInterprets(SAVED_DATA *rsd) {
+ // Master script only affected on restore game, not restore scene
+ if (rsd == &sgData) {
+ g_scheduler->killMatchingProcess(PID_MASTER_SCR, -1);
+ FreeMasterInterpretContext();
+ }
+
+ for (int i = 0; i < MAX_INTERPRET; i++) {
+ switch (rsd->SavedICInfo[i].GSort) {
+ case GS_NONE:
+ break;
+
+ case GS_INVENTORY:
+ if (rsd->SavedICInfo[i].event != POINTED) {
+ RestoreProcess(&rsd->SavedICInfo[i]);
+ }
+ break;
+
+ case GS_MASTER:
+ // Master script only affected on restore game, not restore scene
+ if (rsd == &sgData)
+ RestoreMasterProcess(&rsd->SavedICInfo[i]);
+ break;
+
+ case GS_ACTOR:
+ RestoreActorProcess(rsd->SavedICInfo[i].actorid, &rsd->SavedICInfo[i]);
+ break;
+
+ case GS_POLYGON:
+ case GS_SCENE:
+ RestoreProcess(&rsd->SavedICInfo[i]);
+ break;
+ }
+ }
+}
+
+/**
+ * Do restore scene
+ * @param n Id
+ */
+static int DoRestoreScene(SAVED_DATA *rsd, int n) {
+ switch (n) {
+ case RS_COUNT + COUNTOUT_COUNT:
+ // Trigger pre-load and fade and start countdown
+ FadeOutFast(NULL);
+ break;
+
+ case RS_COUNT:
+ _vm->_sound->stopAllSamples();
+ ClearScreen();
+ RestoreDeadPolys(rsd->SavedDeadPolys);
+ NewScene(rsd->SavedSceneHandle, NO_ENTRY_NUM);
+ SetDoFadeIn(!bNoFade);
+ bNoFade = false;
+ startupBackground(rsd->SavedBgroundHandle);
+ KillScroll();
+ PlayfieldSetPos(FIELD_WORLD, rsd->SavedLoffset, rsd->SavedToffset);
+ bNoBlocking = rsd->SavedNoBlocking;
+ RestoreNoScrollData(&rsd->SavedNoScrollData);
+/*
+ break;
+
+ case RS_COUNT - 1:
+*/
+ sortActors(rsd);
+ break;
+
+ case 2:
+ break;
+
+ case 1:
+ RestoreMidiFacts(rsd->SavedMidi, rsd->SavedLoop);
+ if (rsd->SavedControl)
+ control(CONTROL_ON); // TOKEN_CONTROL was free
+ ResumeInterprets(rsd);
+ }
+
+ return n - 1;
+}
+
+/**
+ * Restore game
+ * @param num num
+ */
+void RestoreGame(int num) {
+ KillInventory();
+
+ RequestRestoreGame(num, &sgData, &savedSceneCount, s_ssData);
+
+ // Actual restoring is performed by ProcessSRQueue
+}
+
+/**
+ * Save game
+ * @param name Name of savegame
+ * @param desc Description of savegame
+ */
+void SaveGame(char *name, char *desc) {
+ // Get current scene data
+ SaveScene(&sgData);
+
+ RequestSaveGame(name, desc, &sgData, &savedSceneCount, s_ssData);
+
+ // Actual saving is performed by ProcessSRQueue
+}
+
+
+//---------------------------------------------------------------------------------
+
+bool IsRestoringScene() {
+ if (s_restoreSceneCount) {
+ s_restoreSceneCount = DoRestoreScene(s_rsd, s_restoreSceneCount);
+ }
+
+ return s_restoreSceneCount ? true : false;
+}
+
+/**
+ * Please Restore Scene
+ */
+void PleaseRestoreScene(bool bFade) {
+ // only called by restore_scene PCODE
+ if (s_restoreSceneCount == 0) {
+ assert(savedSceneCount >= 1); // No saved scene to restore
+
+ if (ASceneIsSaved)
+ RestoreScene(&s_ssData[--savedSceneCount], bFade);
+ if (!bFade)
+ bNoFade = true;
+ }
+}
+
+/**
+ * Please Save Scene
+ */
+void PleaseSaveScene(CORO_PARAM) {
+ // only called by save_scene PCODE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(savedSceneCount < MAX_NEST); // nesting limit reached
+
+ // Don't save the same thing multiple times!
+ // FIXME/TODO: Maybe this can be changed to an assert?
+ if (savedSceneCount && s_ssData[savedSceneCount-1].SavedSceneHandle == GetSceneHandle())
+ CORO_KILL_SELF();
+
+ SaveScene(&s_ssData[savedSceneCount++]);
+
+ CORO_END_CODE;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/savescn.h b/engines/tinsel/savescn.h
new file mode 100644
index 0000000000..a999c9bffa
--- /dev/null
+++ b/engines/tinsel/savescn.h
@@ -0,0 +1,103 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Should really be called "moving actors.h"
+ */
+
+#ifndef TINSEL_SAVESCN_H
+#define TINSEL_SAVESCN_H
+
+#include <time.h> // for time_t struct
+
+#include "tinsel/actors.h" // SAVED_ACTOR
+#include "tinsel/dw.h" // SCNHANDLE
+#include "tinsel/rince.h" // SAVED_MOVER
+#include "tinsel/pcode.h" // INT_CONTEXT
+#include "tinsel/scroll.h" // SCROLLDATA
+
+namespace Tinsel {
+
+enum {
+ SG_DESC_LEN = 40, // Max. saved game description length
+ MAX_SFILES = 30,
+
+ // FIXME: Save file names in ScummVM can be longer than 8.3, overflowing the
+ // name field in savedFiles. Raising it to 256 as a preliminary fix.
+ FNAMELEN = 256 // 8.3
+};
+
+struct SFILES {
+ char name[FNAMELEN];
+ char desc[SG_DESC_LEN + 2];
+ struct tm dateTime;
+};
+
+struct SAVED_DATA {
+ SCNHANDLE SavedSceneHandle; // Scene handle
+ SCNHANDLE SavedBgroundHandle; // Background handle
+ SAVED_MOVER SavedMoverInfo[MAX_MOVERS]; // Moving actors
+ SAVED_ACTOR SavedActorInfo[MAX_SAVED_ACTORS]; // } Actors
+ int NumSavedActors; // }
+ int SavedLoffset, SavedToffset; // Screen offsets
+ INT_CONTEXT SavedICInfo[MAX_INTERPRET]; // Interpret contexts
+ bool SavedDeadPolys[MAX_POLY];
+ bool SavedControl;
+ SCNHANDLE SavedMidi; // }
+ bool SavedLoop; // } Midi
+ bool SavedNoBlocking;
+ SCROLLDATA SavedNoScrollData;
+};
+
+
+enum SRSTATE {
+ SR_IDLE, SR_DORESTORE, SR_DONERESTORE,
+ SR_DOSAVE, SR_DONESAVE, SR_ABORTED
+};
+
+void PleaseRestoreScene(bool bFade);
+void PleaseSaveScene(CORO_PARAM);
+
+bool IsRestoringScene();
+
+
+enum letype{
+ LE_NAME, LE_DESC
+};
+
+char *ListEntry(int i, letype which);
+int getList(void);
+
+void RestoreGame(int num);
+void SaveGame(char *name, char *desc);
+
+void ProcessSRQueue(void);
+
+void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData);
+void RequestRestoreGame(int num, SAVED_DATA *sd, int *ssCount, SAVED_DATA *ssData);
+
+void InitialiseSs(void);
+void FreeSs(void);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SAVESCN_H */
diff --git a/engines/tinsel/scene.cpp b/engines/tinsel/scene.cpp
new file mode 100644
index 0000000000..70700c16a3
--- /dev/null
+++ b/engines/tinsel/scene.cpp
@@ -0,0 +1,306 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Starts up new scenes.
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/anim.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/film.h"
+#include "tinsel/move.h"
+#include "tinsel/rince.h"
+#include "tinsel/sched.h"
+#include "tinsel/scn.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h" // stopAllSamples()
+#include "tinsel/object.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h" // process IDs
+#include "tinsel/polygons.h"
+#include "tinsel/token.h"
+
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern void DropBackground(void);
+
+// in EFFECT.C
+extern void EffectPolyProcess(CORO_PARAM, const void *);
+
+// in PDISPLAY.C
+#ifdef DEBUG
+extern void CursorPositionProcess(CORO_PARAM, const void *);
+#endif
+extern void TagProcess(CORO_PARAM, const void *);
+extern void PointProcess(CORO_PARAM, const void *);
+extern void EnableTags(void);
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/** scene structure - one per scene */
+struct SCENE_STRUC {
+ int32 numEntrance; //!< number of entrances in this scene
+ int32 numPoly; //!< number of various polygons in this scene
+ int32 numActor; //!< number of actors in this scene
+ int32 defRefer; //!< Default refer direction
+ SCNHANDLE hSceneScript; //!< handle to scene script
+ SCNHANDLE hEntrance; //!< handle to table of entrances
+ SCNHANDLE hPoly; //!< handle to table of polygons
+ SCNHANDLE hActor; //!< handle to table of actors
+} PACKED_STRUCT;
+
+/** entrance structure - one per entrance */
+struct ENTRANCE_STRUC {
+ int32 eNumber; //!< entrance number
+ SCNHANDLE hScript; //!< handle to entrance script
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+#ifdef DEBUG
+static bool ShowPosition = false; // Set when showpos() has been called
+#endif
+
+static SCNHANDLE SceneHandle = 0; // Current scene handle - stored in case of Save_Scene()
+
+
+/**
+ * Started up for scene script and entrance script.
+ */
+static void SceneTinselProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ // get the stuff copied to process when it was created
+ SCNHANDLE *ss = (SCNHANDLE *)param;
+ assert(*ss); // Must have some code to run
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pic = InitInterpretContext(GS_SCENE, READ_LE_UINT32(ss), NOEVENT, NOPOLY, 0, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Get the SCENE_STRUC
+ * Initialise polygons for the scene
+ * Initialise the actors for this scene
+ * Run the appropriate entrance code (if any)
+ * Get the default refer type
+ */
+static void LoadScene(SCNHANDLE scene, int entry) {
+ const SCENE_STRUC *ss;
+ const ENTRANCE_STRUC *es;
+ uint i;
+
+ // Scene structure
+ SceneHandle = scene; // Save scene handle in case of Save_Scene()
+
+ LockMem(SceneHandle); // Make sure scene is loaded
+ LockScene(SceneHandle); // Prevent current scene from being discarded
+
+ ss = (const SCENE_STRUC *)FindChunk(scene, CHUNK_SCENE);
+ assert(ss != NULL);
+
+ // Initialise all the polygons for this scene
+ InitPolygons(FROM_LE_32(ss->hPoly), FROM_LE_32(ss->numPoly), (entry == NO_ENTRY_NUM));
+
+ // Initialise the actors for this scene
+ StartActors(FROM_LE_32(ss->hActor), FROM_LE_32(ss->numActor), (entry != NO_ENTRY_NUM));
+
+ if (entry != NO_ENTRY_NUM) {
+
+ // Run the appropriate entrance code (if any)
+ es = (const ENTRANCE_STRUC *)LockMem(FROM_LE_32(ss->hEntrance));
+ for (i = 0; i < FROM_LE_32(ss->numEntrance); i++, es++) {
+ if (FROM_LE_32(es->eNumber) == (uint)entry) {
+ if (es->hScript)
+ g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &es->hScript, sizeof(es->hScript));
+ break;
+ }
+ }
+
+ if (i == FROM_LE_32(ss->numEntrance))
+ error("Non-existant scene entry number");
+
+ if (ss->hSceneScript)
+ g_scheduler->createProcess(PID_TCODE, SceneTinselProcess, &ss->hSceneScript, sizeof(ss->hSceneScript));
+ }
+
+ // Default refer type
+ SetDefaultRefer(FROM_LE_32(ss->defRefer));
+}
+
+
+/**
+ * Wrap up the last scene.
+ */
+void EndScene(void) {
+ if (SceneHandle != 0) {
+ UnlockScene(SceneHandle);
+ SceneHandle = 0;
+ }
+
+ KillInventory(); // Close down any open inventory
+
+ DropPolygons(); // No polygons
+ DropNoScrolls(); // No no-scrolls
+ DropBackground(); // No background
+ DropMActors(); // No moving actors
+ DropCursor(); // No cursor
+ DropActors(); // No actor reels running
+ FreeAllTokens(); // No-one has tokens
+ FreeMostInterpretContexts(); // Only master script still interpreting
+
+ _vm->_sound->stopAllSamples(); // Kill off any still-running sample
+
+ // init the palette manager
+ ResetPalAllocator();
+
+ // init the object manager
+ KillAllObjects();
+
+ // kill all destructable process
+ g_scheduler->killMatchingProcess(PID_DESTROY, PID_DESTROY);
+}
+
+/**
+ *
+ */
+void PrimeBackground(void) {
+ // structure for playfields
+ static PLAYFIELD playfield[] = {
+ { // FIELD WORLD
+ NULL, // display list
+ 0, // init field x
+ 0, // init field y
+ 0, // x vel
+ 0, // y vel
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect
+ false // moved flag
+ },
+ { // FIELD STATUS
+ NULL, // display list
+ 0, // init field x
+ 0, // init field y
+ 0, // x vel
+ 0, // y vel
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // clip rect
+ false // moved flag
+ }
+ };
+
+ // structure for background
+ static BACKGND backgnd = {
+ BLACK, // sky colour
+ Common::Point(0, 0), // initial world pos
+ Common::Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT), // scroll limits
+ 0, // no background update process
+ NULL, // no x scroll table
+ NULL, // no y scroll table
+ 2, // 2 playfields
+ playfield, // playfield pointer
+ false // no auto-erase
+ };
+
+ InitBackground(&backgnd);
+}
+
+/**
+ * Start up the standard stuff for the next scene.
+ */
+
+void PrimeScene(void) {
+
+ bNoBlocking = false;
+
+ RestartCursor(); // Restart the cursor
+ EnableTags(); // Next scene with tags enabled
+
+ g_scheduler->createProcess(PID_SCROLL, ScrollProcess, NULL, 0);
+ g_scheduler->createProcess(PID_SCROLL, EffectPolyProcess, NULL, 0);
+
+#ifdef DEBUG
+ if (ShowPosition)
+ g_scheduler->createProcess(PID_POSITION, CursorPositionProcess, NULL, 0);
+#endif
+
+ g_scheduler->createProcess(PID_TAG, TagProcess, NULL, 0);
+ g_scheduler->createProcess(PID_TAG, PointProcess, NULL, 0);
+
+ // init the current background
+ PrimeBackground();
+}
+
+/**
+ * Wrap up the last scene and start up the next scene.
+ */
+
+void NewScene(SCNHANDLE scene, int entry) {
+ EndScene(); // Wrap up the last scene.
+
+ PrimeScene(); // Start up the standard stuff for the next scene.
+
+ LoadScene(scene, entry);
+}
+
+#ifdef DEBUG
+/**
+ * Sets the ShowPosition flag, causing the cursor position process to be
+ * created in each scene.
+ */
+
+void setshowpos(void) {
+ ShowPosition = true;
+}
+#endif
+
+/**
+ * Return the current scene handle.
+ */
+
+SCNHANDLE GetSceneHandle(void) {
+ return SceneHandle;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scene.h b/engines/tinsel/scene.h
new file mode 100644
index 0000000000..d0fc6e1ae3
--- /dev/null
+++ b/engines/tinsel/scene.h
@@ -0,0 +1,73 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Scene parsing defines
+ */
+
+#ifndef TINSEL_SCENE_H
+#define TINSEL_SCENE_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+enum {
+ MAX_NODES = 32, //!< maximum nodes in a Node Path
+ MAX_NOSCROLL = 16, //!< maximum number of NoScroll commands in a scene
+ MAX_ENTRANCE = 25, //!< maximum number of entrances in a scene
+ MAX_POLY = 256, //!< maximum number of polygons in a scene
+ MAX_ACTOR = 32 //!< maximum number of actors in a scene
+};
+
+/** reference direction */
+enum REFTYPE {
+ REF_DEFAULT, REF_UP, REF_DOWN, REF_LEFT, REF_RIGHT, REF_POINT
+};
+
+enum TFTYPE {
+ TF_NONE, TF_UP, TF_DOWN, TF_LEFT, TF_RIGHT, TF_BOGUS
+};
+
+/** different actor masks */
+enum MASK_TYPE{
+ ACT_DEFAULT,
+ ACT_MASK = -1,
+ ACT_ALWAYS = -2
+};
+
+/** different scales */
+enum SCALE {
+ SCALE_DEFAULT, SCALE_LARGE, SCALE_MEDIUM, SCALE_SMALL,
+ SCALE_COMPACT, SCALE_TINY,
+ SCALE_AUX1, SCALE_AUX2, SCALE_AUX3,
+ SCALE_AUX4, SCALE_AUX5
+};
+
+/** different reels */
+enum REEL {
+ REEL_DEFAULT, REEL_ALL, REEL_HORIZ, REEL_VERT
+};
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SCENE_H
diff --git a/engines/tinsel/sched.cpp b/engines/tinsel/sched.cpp
new file mode 100644
index 0000000000..72cfeaf6b0
--- /dev/null
+++ b/engines/tinsel/sched.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Process scheduler.
+ */
+
+#include "tinsel/sched.h"
+
+#include "common/util.h"
+
+namespace Tinsel {
+
+Scheduler *g_scheduler = 0;
+
+/** process structure */
+struct PROCESS {
+ PROCESS *pNext; //!< pointer to next process in active or free list
+
+ CoroContext state; //!< the state of the coroutine
+ CORO_ADDR coroAddr; //!< the entry point of the coroutine
+
+ int sleepTime; //!< number of scheduler cycles to sleep
+ int pid; //!< process ID
+ char param[PARAM_SIZE]; //!< process specific info
+};
+
+
+Scheduler::Scheduler() {
+ processList = 0;
+ pFreeProcesses = 0;
+ pCurrent = 0;
+
+#ifdef DEBUG
+ // diagnostic process counters
+ numProcs = 0;
+ maxProcs = 0;
+#endif
+
+ pRCfunction = 0;
+
+ active = new PROCESS;
+
+ g_scheduler = this; // FIXME HACK
+}
+
+Scheduler::~Scheduler() {
+ free(processList);
+ processList = NULL;
+
+ delete active;
+ active = 0;
+}
+
+/**
+ * Kills all processes and places them on the free list.
+ */
+void Scheduler::reset() {
+
+#ifdef DEBUG
+ // clear number of process in use
+ numProcs = 0;
+#endif
+
+ if (processList == NULL) {
+ // first time - allocate memory for process list
+ processList = (PROCESS *)calloc(NUM_PROCESS, sizeof(PROCESS));
+
+ // make sure memory allocated
+ if (processList == NULL) {
+ error("Cannot allocate memory for process data");
+ }
+
+ // fill with garbage
+ memset(processList, 'S', NUM_PROCESS * sizeof(PROCESS));
+ }
+
+ // no active processes
+ pCurrent = active->pNext = NULL;
+
+ // place first process on free list
+ pFreeProcesses = processList;
+
+ // link all other processes after first
+ for (int i = 1; i < NUM_PROCESS; i++) {
+ processList[i - 1].pNext = processList + i;
+ }
+
+ // null the last process
+ processList[NUM_PROCESS - 1].pNext = NULL;
+}
+
+
+#ifdef DEBUG
+/**
+ * Shows the maximum number of process used at once.
+ */
+void Scheduler::printStats(void) {
+ printf("%i process of %i used.\n", maxProcs, NUM_PROCESS);
+}
+#endif
+
+
+/**
+ * Give all active processes a chance to run
+ */
+void Scheduler::schedule(void) {
+ // start dispatching active process list
+ PROCESS *pPrevProc = active;
+ PROCESS *pProc = active->pNext;
+ while (pProc != NULL) {
+ if (--pProc->sleepTime <= 0) {
+ // process is ready for dispatch, activate it
+ pCurrent = pProc;
+ pProc->coroAddr(pProc->state, pProc->param);
+ pCurrent = NULL;
+ if (!pProc->state || pProc->state->_sleep <= 0) {
+ // Coroutine finished
+ killProcess(pProc);
+ pProc = pPrevProc;
+ } else {
+ pProc->sleepTime = pProc->state->_sleep;
+ }
+ }
+ pPrevProc = pProc;
+ pProc = pProc->pNext;
+ }
+}
+
+
+/**
+ * Creates a new process.
+ *
+ * @param pid process identifier
+ * @param CORO_ADDR coroutine start address
+ * @param pParam process specific info
+ * @param sizeParam size of process specific info
+ */
+PROCESS *Scheduler::createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam) {
+ PROCESS *pProc;
+
+ // get a free process
+ pProc = pFreeProcesses;
+
+ // trap no free process
+ assert(pProc != NULL); // Out of processes
+
+#ifdef DEBUG
+ // one more process in use
+ if (++numProcs > maxProcs)
+ maxProcs = numProcs;
+#endif
+
+ // get link to next free process
+ pFreeProcesses = pProc->pNext;
+
+ if (pCurrent != NULL) {
+ // place new process before the next active process
+ pProc->pNext = pCurrent->pNext;
+
+ // make this new process the next active process
+ pCurrent->pNext = pProc;
+ } else { // no active processes, place process at head of list
+ pProc->pNext = active->pNext;
+ active->pNext = pProc;
+ }
+
+ // set coroutine entry point
+ pProc->coroAddr = coroAddr;
+
+ // clear coroutine state
+ pProc->state = 0;
+
+ // wake process up as soon as possible
+ pProc->sleepTime = 1;
+
+ // set new process id
+ pProc->pid = pid;
+
+ // set new process specific info
+ if (sizeParam) {
+ assert(sizeParam > 0 && sizeParam <= PARAM_SIZE);
+
+ // set new process specific info
+ memcpy(pProc->param, pParam, sizeParam);
+ }
+
+
+ // return created process
+ return pProc;
+}
+
+/**
+ * Kills the specified process.
+ *
+ * @param pKillProc which process to kill
+ */
+void Scheduler::killProcess(PROCESS *pKillProc) {
+ PROCESS *pProc, *pPrev; // process list pointers
+
+ // make sure a valid process pointer
+ assert(pKillProc >= processList && pKillProc <= processList + NUM_PROCESS - 1);
+
+ // can not kill the current process using killProcess !
+ assert(pCurrent != pKillProc);
+
+#ifdef DEBUG
+ // one less process in use
+ --numProcs;
+ assert(numProcs >= 0);
+#endif
+
+ // search the active list for the process
+ for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) {
+ if (pProc == pKillProc) {
+ // found process in active list
+
+ // Free process' resources
+ if (pRCfunction != NULL)
+ (pRCfunction)(pProc);
+
+ delete pProc->state;
+
+ // make prev point to next to unlink pProc
+ pPrev->pNext = pProc->pNext;
+
+ // link first free process after pProc
+ pProc->pNext = pFreeProcesses;
+
+ // make pProc the first free process
+ pFreeProcesses = pProc;
+
+ return;
+ }
+ }
+
+ // process not found in active list if we get to here
+ error("killProcess(): tried to kill a process not in the list of active processes");
+}
+
+
+
+/**
+ * Returns a pointer to the currently running process.
+ */
+PROCESS *Scheduler::getCurrentProcess(void) {
+ return pCurrent;
+}
+
+/**
+ * Returns the process identifier of the specified process.
+ *
+ * @param pProc which process
+ */
+int Scheduler::getCurrentPID() const {
+ PROCESS *pProc = pCurrent;
+
+ // make sure a valid process pointer
+ assert(pProc >= processList && pProc <= processList + NUM_PROCESS - 1);
+
+ // return processes PID
+ return pProc->pid;
+}
+
+/**
+ * Kills any process matching the specified PID. The current
+ * process cannot be killed.
+ *
+ * @param pidKill process identifier of process to kill
+ * @param pidMask mask to apply to process identifiers before comparison
+ * @return The number of processes killed is returned.
+ */
+int Scheduler::killMatchingProcess(int pidKill, int pidMask) {
+ int numKilled = 0;
+ PROCESS *pProc, *pPrev; // process list pointers
+
+ for (pProc = active->pNext, pPrev = active; pProc != NULL; pPrev = pProc, pProc = pProc->pNext) {
+ if ((pProc->pid & pidMask) == pidKill) {
+ // found a matching process
+
+ // dont kill the current process
+ if (pProc != pCurrent) {
+ // kill this process
+ numKilled++;
+
+ // make prev point to next to unlink pProc
+ pPrev->pNext = pProc->pNext;
+
+ // link first free process after pProc
+ pProc->pNext = pFreeProcesses;
+
+ // make pProc the first free process
+ pFreeProcesses = pProc;
+
+ // set to a process on the active list
+ pProc = pPrev;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ // adjust process in use
+ numProcs -= numKilled;
+ assert(numProcs >= 0);
+#endif
+
+ // return number of processes killed
+ return numKilled;
+}
+
+
+
+/**
+ * Set pointer to a function to be called by killProcess().
+ *
+ * May be called by a resource allocator, the function supplied is
+ * called by killProcess() to allow the resource allocator to free
+ * resources allocated to the dying process.
+ *
+ * @param pFunc Function to be called by killProcess()
+ */
+void Scheduler::setResourceCallback(VFPTRPP pFunc) {
+ pRCfunction = pFunc;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/sched.h b/engines/tinsel/sched.h
new file mode 100644
index 0000000000..0d90b3bb9f
--- /dev/null
+++ b/engines/tinsel/sched.h
@@ -0,0 +1,110 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Data structures used by the process scheduler
+ */
+
+#ifndef TINSEL_SCHED_H // prevent multiple includes
+#define TINSEL_SCHED_H
+
+#include "tinsel/dw.h" // new data types
+#include "tinsel/coroutine.h"
+
+namespace Tinsel {
+
+// the size of process specific info
+#define PARAM_SIZE 32
+
+// the maximum number of processes
+#define NUM_PROCESS 64
+
+typedef void (*CORO_ADDR)(CoroContext &, const void *);
+
+
+struct PROCESS;
+
+/**
+ * Create and manage "processes" (really coroutines).
+ */
+class Scheduler {
+public:
+ /** Pointer to a function of the form "void function(PPROCESS)" */
+ typedef void (*VFPTRPP)(PROCESS *);
+
+private:
+
+ /** list of all processes */
+ PROCESS *processList;
+
+ /** active process list - also saves scheduler state */
+ PROCESS *active;
+
+ /** pointer to free process list */
+ PROCESS *pFreeProcesses;
+
+ /** the currently active process */
+ PROCESS *pCurrent;
+
+#ifdef DEBUG
+ // diagnostic process counters
+ int numProcs;
+ int maxProcs;
+#endif
+
+ /**
+ * Called from killProcess() to enable other resources
+ * a process may be allocated to be released.
+ */
+ VFPTRPP pRCfunction;
+
+
+public:
+
+ Scheduler();
+ ~Scheduler();
+
+ void reset();
+
+ #ifdef DEBUG
+ void printStats();
+ #endif
+
+ void schedule();
+
+ PROCESS *createProcess(int pid, CORO_ADDR coroAddr, const void *pParam, int sizeParam);
+ void killProcess(PROCESS *pKillProc);
+
+ PROCESS *getCurrentProcess();
+ int getCurrentPID() const;
+ int killMatchingProcess(int pidKill, int pidMask);
+
+
+ void setResourceCallback(VFPTRPP pFunc);
+
+};
+
+extern Scheduler *g_scheduler; // FIXME: Temporary global var, to be used until everything has been OOifyied
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SCHED_H
diff --git a/engines/tinsel/scn.cpp b/engines/tinsel/scn.cpp
new file mode 100644
index 0000000000..8639979b41
--- /dev/null
+++ b/engines/tinsel/scn.cpp
@@ -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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * A (some would say very) small collection of utility functions.
+ */
+
+#include "common/endian.h"
+#include "common/util.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/scn.h"
+#include "tinsel/tinsel.h" // for _vm
+
+namespace Tinsel {
+
+/**
+ * Given a scene handle and a chunk id, gets the scene in RAM and
+ * locates the requested chunk.
+ * @param handle Scene handle
+ * @param chunk Chunk Id
+ */
+byte *FindChunk(SCNHANDLE handle, uint32 chunk) {
+ byte *bptr = LockMem(handle);
+ uint32 *lptr = (uint32 *)bptr;
+ uint32 add;
+
+ // V1 chunk types can be found by substracting 2 from the
+ // chunk type. Note that CHUNK_STRING and CHUNK_BITMAP are
+ // the same in V1 and V2
+ if (_vm->getVersion() == TINSEL_V0 &&
+ chunk != CHUNK_STRING && chunk != CHUNK_BITMAP)
+ chunk -= 0x2L;
+
+ while (1) {
+ if (READ_LE_UINT32(lptr) == chunk)
+ return (byte *)(lptr + 2);
+
+ ++lptr;
+ add = READ_LE_UINT32(lptr);
+ if (!add)
+ return NULL;
+
+ lptr = (uint32 *)(bptr + add);
+ }
+}
+
+/**
+ * Get the actor id from a film (column 0)
+ */
+int extractActor(SCNHANDLE film) {
+ const FILM *pfilm = (const FILM *)LockMem(film);
+ const FREEL *preel = &pfilm->reels[0];
+ const MULTI_INIT *pmi = (const MULTI_INIT *)LockMem(FROM_LE_32(preel->mobj));
+ return (int)FROM_LE_32(pmi->mulID);
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scn.h b/engines/tinsel/scn.h
new file mode 100644
index 0000000000..29f3dc51fc
--- /dev/null
+++ b/engines/tinsel/scn.h
@@ -0,0 +1,68 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_SCN_H // prevent multiple includes
+#define TINSEL_SCN_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+
+// chunk identifier numbers
+
+// V2 chunks
+
+#define CHUNK_STRING 0x33340001L // same in V1 and V2
+#define CHUNK_BITMAP 0x33340002L // same in V1 and V2
+#define CHUNK_CHARPTR 0x33340003L // not used!
+#define CHUNK_CHARMATRIX 0x33340004L // not used!
+#define CHUNK_PALETTE 0x33340005L // not used!
+#define CHUNK_IMAGE 0x33340006L // not used!
+#define CHUNK_ANI_FRAME 0x33340007L // not used!
+#define CHUNK_FILM 0x33340008L // not used!
+#define CHUNK_FONT 0x33340009L // not used!
+#define CHUNK_PCODE 0x3334000AL
+#define CHUNK_ENTRANCE 0x3334000BL // not used!
+#define CHUNK_POLYGONS 0x3334000CL // not used!
+#define CHUNK_ACTORS 0x3334000DL // not used!
+#define CHUNK_SCENE 0x3334000EL
+#define CHUNK_TOTAL_ACTORS 0x3334000FL
+#define CHUNK_TOTAL_GLOBALS 0x33340010L
+#define CHUNK_TOTAL_OBJECTS 0x33340011L
+#define CHUNK_OBJECTS 0x33340012L
+#define CHUNK_MIDI 0x33340013L // not used!
+#define CHUNK_SAMPLE 0x33340014L // not used!
+#define CHUNK_TOTAL_POLY 0x33340015L
+#define CHUNK_MBSTRING 0x33340022L // Multi-byte characters
+
+#define INDEX_FILENAME "index" // name of index file
+
+byte *FindChunk(SCNHANDLE handle, uint32 chunk);
+int extractActor(SCNHANDLE film);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SCN_H */
diff --git a/engines/tinsel/scroll.cpp b/engines/tinsel/scroll.cpp
new file mode 100644
index 0000000000..aa1bc67298
--- /dev/null
+++ b/engines/tinsel/scroll.cpp
@@ -0,0 +1,432 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles scrolling
+ */
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sched.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define LEFT 'L'
+#define RIGHT 'R'
+#define UP 'U'
+#define DOWN 'D'
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static int LeftScroll = 0, DownScroll = 0; // Number of iterations outstanding
+
+static int scrollActor = 0;
+static PMACTOR psActor = 0;
+static int oldx = 0, oldy = 0;
+
+/** Boundaries and numbers of boundaries */
+static SCROLLDATA sd = {
+ {
+ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0},
+ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}
+ },
+ {
+ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0},
+ {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}, {0,0,0}
+ },
+ 0,
+ 0
+ };
+
+static int ImageH = 0, ImageW = 0;
+
+static bool ScrollCursor = 0; // If a TAG or EXIT polygon is clicked on,
+ // the cursor is kept over that polygon
+ // whilst scrolling
+
+static int scrollPixels = SCROLLPIXELS;
+
+
+/**
+ * Reset the ScrollCursor flag
+ */
+void DontScrollCursor(void) {
+ ScrollCursor = false;
+}
+
+/**
+ * Set the ScrollCursor flag
+ */
+void DoScrollCursor(void) {
+ ScrollCursor = true;
+}
+
+/**
+ * Configure a no-scroll boundary for a scene.
+ */
+void SetNoScroll(int x1, int y1, int x2, int y2) {
+ if (x1 == x2) {
+ /* Vertical line */
+ assert(sd.NumNoH < MAX_HNOSCROLL);
+
+ sd.NoHScroll[sd.NumNoH].ln = x1; // X pos of vertical line
+ sd.NoHScroll[sd.NumNoH].c1 = y1;
+ sd.NoHScroll[sd.NumNoH].c2 = y2;
+ sd.NumNoH++;
+ } else if (y1 == y2) {
+ /* Horizontal line */
+ assert(sd.NumNoV < MAX_VNOSCROLL);
+
+ sd.NoVScroll[sd.NumNoV].ln = y1; // Y pos of horizontal line
+ sd.NoVScroll[sd.NumNoV].c1 = x1;
+ sd.NoVScroll[sd.NumNoV].c2 = x2;
+ sd.NumNoV++;
+ } else {
+ /* No-scroll lines must be horizontal or vertical */
+ }
+}
+
+/**
+ * Does the obvious - called at the end of a scene.
+ */
+void DropNoScrolls(void) {
+ sd.NumNoH = sd.NumNoV = 0;
+}
+
+/**
+ * Called from scroll process when it thinks that a scroll is in order.
+ * Checks for no-scroll boundaries and sets off a scroll if allowed.
+ */
+static void NeedScroll(int direction) {
+ uint i;
+ int BottomLine, RightCol;
+ int Loffset, Toffset;
+
+ // get background offsets
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ switch (direction) {
+ case LEFT: /* Picture will go left, 'camera' right */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoH; i++) {
+ if (RightCol >= sd.NoHScroll[i].ln - 1 && RightCol <= sd.NoHScroll[i].ln + 1 &&
+ ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) ||
+ (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) ||
+ (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine)))
+ return;
+ }
+
+ if (LeftScroll <= 0) {
+ scrollPixels = SCROLLPIXELS;
+ LeftScroll = RLSCROLL;
+ }
+ break;
+
+ case RIGHT: /* Picture will go right, 'camera' left */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+
+ for (i = 0; i < sd.NumNoH; i++) {
+ if (Loffset >= sd.NoHScroll[i].ln - 1 && Loffset <= sd.NoHScroll[i].ln + 1 &&
+ ((sd.NoHScroll[i].c1 >= Toffset && sd.NoHScroll[i].c1 <= BottomLine) ||
+ (sd.NoHScroll[i].c2 >= Toffset && sd.NoHScroll[i].c2 <= BottomLine) ||
+ (sd.NoHScroll[i].c1 < Toffset && sd.NoHScroll[i].c2 > BottomLine)))
+ return;
+ }
+
+ if (LeftScroll >= 0) {
+ scrollPixels = SCROLLPIXELS;
+ LeftScroll = -RLSCROLL;
+ }
+ break;
+
+ case UP: /* Picture will go upwards, 'camera' downwards */
+
+ BottomLine = Toffset + (SCREEN_HEIGHT - 1);
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoV; i++) {
+ if ((BottomLine >= sd.NoVScroll[i].ln - 1 && BottomLine <= sd.NoVScroll[i].ln + 1) &&
+ ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) ||
+ (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) ||
+ (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol)))
+ return;
+ }
+
+ if (DownScroll <= 0) {
+ scrollPixels = SCROLLPIXELS;
+ DownScroll = UDSCROLL;
+ }
+ break;
+
+ case DOWN: /* Picture will go downwards, 'camera' upwards */
+
+ RightCol = Loffset + (SCREEN_WIDTH - 1);
+
+ for (i = 0; i < sd.NumNoV; i++) {
+ if (Toffset >= sd.NoVScroll[i].ln - 1 && Toffset <= sd.NoVScroll[i].ln + 1 &&
+ ((sd.NoVScroll[i].c1 >= Loffset && sd.NoVScroll[i].c1 <= RightCol) ||
+ (sd.NoVScroll[i].c2 >= Loffset && sd.NoVScroll[i].c2 <= RightCol) ||
+ (sd.NoVScroll[i].c1 < Loffset && sd.NoVScroll[i].c2 > RightCol)))
+ return;
+ }
+
+ if (DownScroll >= 0) {
+ scrollPixels = SCROLLPIXELS;
+ DownScroll = -UDSCROLL;
+ }
+ break;
+ }
+}
+
+/**
+ * Called from scroll process - Scrolls the image as appropriate.
+ */
+static void ScrollImage(void) {
+ int OldLoffset = 0, OldToffset = 0; // Used when keeping cursor on a tag
+ int Loffset, Toffset;
+ int curX, curY;
+
+ // get background offsets
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*
+ * Keeping cursor on a tag?
+ */
+ if (ScrollCursor) {
+ GetCursorXY(&curX, &curY, true);
+ if (InPolygon(curX, curY, TAG) != NOPOLY || InPolygon(curX, curY, EXIT) != NOPOLY) {
+ OldLoffset = Loffset;
+ OldToffset = Toffset;
+ } else
+ ScrollCursor = false;
+ }
+
+ /*
+ * Horizontal scrolling
+ */
+ if (LeftScroll > 0) {
+ LeftScroll -= scrollPixels;
+ if (LeftScroll < 0) {
+ Loffset += LeftScroll;
+ LeftScroll = 0;
+ }
+ Loffset += scrollPixels; // Move right
+ if (Loffset > ImageW - SCREEN_WIDTH)
+ Loffset = ImageW - SCREEN_WIDTH;// Now at extreme right
+ } else if (LeftScroll < 0) {
+ LeftScroll += scrollPixels;
+ if (LeftScroll > 0) {
+ Loffset += LeftScroll;
+ LeftScroll = 0;
+ }
+ Loffset -= scrollPixels; // Move left
+ if (Loffset < 0)
+ Loffset = 0; // Now at extreme left
+ }
+
+ /*
+ * Vertical scrolling
+ */
+ if (DownScroll > 0) {
+ DownScroll -= scrollPixels;
+ if (DownScroll < 0) {
+ Toffset += DownScroll;
+ DownScroll = 0;
+ }
+ Toffset += scrollPixels; // Move down
+
+ if (Toffset > ImageH - SCREEN_HEIGHT)
+ Toffset = ImageH - SCREEN_HEIGHT;// Now at extreme bottom
+
+ } else if (DownScroll < 0) {
+ DownScroll += scrollPixels;
+ if (DownScroll > 0) {
+ Toffset += DownScroll;
+ DownScroll = 0;
+ }
+ Toffset -= scrollPixels; // Move up
+
+ if (Toffset < 0)
+ Toffset = 0; // Now at extreme top
+ }
+
+ /*
+ * Move cursor if keeping cursor on a tag.
+ */
+ if (ScrollCursor)
+ AdjustCursorXY(OldLoffset - Loffset, OldToffset - Toffset);
+
+ PlayfieldSetPos(FIELD_WORLD, Loffset, Toffset);
+}
+
+
+/**
+ * See if the actor on whom the camera is is approaching an edge.
+ * Request a scroll if he is.
+ */
+static void MonitorScroll(void) {
+ int newx, newy;
+ int Loffset, Toffset;
+
+ /*
+ * Only do it if the actor is there and is visible
+ */
+ if (!psActor || getMActorHideState(psActor)
+ || getMActorState(psActor) == NO_MACTOR)
+ return;
+
+ GetActorPos(scrollActor, &newx, &newy);
+
+ if (oldx == newx && oldy == newy)
+ return;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+
+ /*
+ * Approaching right side or left side of the screen?
+ */
+ if (newx > Loffset+SCREEN_WIDTH-RLDISTANCE && Loffset < ImageW-SCREEN_WIDTH) {
+ if (newx > oldx)
+ NeedScroll(LEFT);
+ } else if (newx < Loffset + RLDISTANCE && Loffset) {
+ if (newx < oldx)
+ NeedScroll(RIGHT);
+ }
+
+ /*
+ * Approaching bottom or top of the screen?
+ */
+ if (newy > Toffset+SCREEN_HEIGHT-UDDISTANCE && Toffset < ImageH-SCREEN_HEIGHT) {
+ if (newy > oldy)
+ NeedScroll(UP);
+ } else if (Toffset && newy < Toffset + UDDISTANCE + GetActorBottom(scrollActor) - GetActorTop(scrollActor)) {
+ if (newy < oldy)
+ NeedScroll(DOWN);
+ }
+
+ oldx = newx;
+ oldy = newy;
+}
+
+/**
+ * Decide when to scroll and scroll when decided to.
+ */
+void ScrollProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ ImageH = BackgroundHeight(); // Dimensions
+ ImageW = BackgroundWidth(); // of this scene.
+
+ // Give up if there'll be no purpose in this process
+ if (ImageW == SCREEN_WIDTH && ImageH == SCREEN_HEIGHT)
+ CORO_KILL_SELF();
+
+ LeftScroll = DownScroll = 0; // No iterations outstanding
+ oldx = oldy = 0;
+ scrollPixels = SCROLLPIXELS;
+
+ if (!scrollActor)
+ scrollActor = LeadId();
+
+ psActor = GetMover(scrollActor);
+
+ while (1) {
+ MonitorScroll(); // Set scroll requirement
+
+ if (LeftScroll || DownScroll) // Scroll if required
+ ScrollImage();
+
+ CORO_SLEEP(1); // allow re-scheduling
+ }
+
+ CORO_END_CODE;
+}
+
+/**
+ * Change which actor the camera is following.
+ */
+void ScrollFocus(int ano) {
+ if (scrollActor != ano) {
+ oldx = oldy = 0;
+ scrollActor = ano;
+
+ psActor = ano ? GetMover(scrollActor) : NULL;
+ }
+}
+
+/**
+ * Scroll to abslote position.
+ */
+void ScrollTo(int x, int y, int iter) {
+ int Loffset, Toffset; // for background offsets
+
+ scrollPixels = iter != 0 ? iter : SCROLLPIXELS;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset); // get background offsets
+
+ LeftScroll = x - Loffset;
+ DownScroll = y - Toffset;
+}
+
+/**
+ * Kill of any current scroll.
+ */
+void KillScroll(void) {
+ LeftScroll = DownScroll = 0;
+}
+
+
+void GetNoScrollData(SCROLLDATA *ssd) {
+ memcpy(ssd, &sd, sizeof(SCROLLDATA));
+}
+
+void RestoreNoScrollData(SCROLLDATA *ssd) {
+ memcpy(&sd, ssd, sizeof(SCROLLDATA));
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/scroll.h b/engines/tinsel/scroll.h
new file mode 100644
index 0000000000..ac903157f2
--- /dev/null
+++ b/engines/tinsel/scroll.h
@@ -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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_SCROLL_H // prevent multiple includes
+#define TINSEL_SCROLL_H
+
+namespace Tinsel {
+
+#define SCROLLPIXELS 8 // Number of pixels to scroll per iteration
+
+#define RLDISTANCE 50 // Distance from edge that triggers a scroll
+#define UDDISTANCE 20
+
+// Number of iterations to make
+#define RLSCROLL 160 // 20*8 = 160 = half a screen
+#define UDSCROLL 100 // 12.5*8 = 100 = half a screen
+
+
+// These structures defined here so boundaries can be saved
+struct NOSCROLLB {
+ int ln;
+ int c1;
+ int c2;
+};
+
+#define MAX_HNOSCROLL 10
+#define MAX_VNOSCROLL 10
+
+struct SCROLLDATA{
+ NOSCROLLB NoVScroll[MAX_VNOSCROLL]; // Vertical no-scroll boundaries
+ NOSCROLLB NoHScroll[MAX_HNOSCROLL]; // Horizontal no-scroll boundaries
+ unsigned NumNoV, NumNoH; // Counts of no-scroll boundaries
+};
+
+
+
+void DontScrollCursor(void);
+void DoScrollCursor(void);
+
+void SetNoScroll(int x1, int y1, int x2, int y2);
+void DropNoScrolls(void);
+
+void ScrollProcess(CORO_PARAM, const void *);
+
+void ScrollFocus(int actor);
+void ScrollTo(int x, int y, int iter);
+
+void KillScroll(void);
+
+void GetNoScrollData(SCROLLDATA *ssd);
+void RestoreNoScrollData(SCROLLDATA *ssd);
+
+} // end of namespace Tinsel
+
+#endif /* TINSEL_SCROLL_H */
diff --git a/engines/tinsel/serializer.h b/engines/tinsel/serializer.h
new file mode 100644
index 0000000000..98ee398ef8
--- /dev/null
+++ b/engines/tinsel/serializer.h
@@ -0,0 +1,131 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ */
+
+#ifndef TINSEL_SERIALIZER_H
+#define TINSEL_SERIALIZER_H
+
+#include "common/scummsys.h"
+#include "common/savefile.h"
+
+
+namespace Tinsel {
+
+
+#define SYNC_AS(SUFFIX,TYPE,SIZE) \
+ template <class T> \
+ void syncAs ## SUFFIX(T &val) { \
+ if (_loadStream) \
+ val = static_cast<T>(_loadStream->read ## SUFFIX()); \
+ else { \
+ TYPE tmp = val; \
+ _saveStream->write ## SUFFIX(tmp); \
+ } \
+ _bytesSynced += SIZE; \
+ }
+
+
+// TODO: Write comment for this
+// TODO: Inspired by the SCUMM engine -- move to common/ code and use in more engines?
+class Serializer {
+public:
+ Serializer(Common::SeekableReadStream *in, Common::OutSaveFile *out)
+ : _loadStream(in), _saveStream(out), _bytesSynced(0) {
+ assert(in || out);
+ }
+
+ bool isSaving() { return (_saveStream != 0); }
+ bool isLoading() { return (_loadStream != 0); }
+
+ uint bytesSynced() const { return _bytesSynced; }
+
+ void syncBytes(byte *buf, uint16 size) {
+ if (_loadStream)
+ _loadStream->read(buf, size);
+ else
+ _saveStream->write(buf, size);
+ _bytesSynced += size;
+ }
+
+ SYNC_AS(Byte, byte, 1)
+
+ SYNC_AS(Uint16LE, uint16, 2)
+ SYNC_AS(Uint16BE, uint16, 2)
+ SYNC_AS(Sint16LE, int16, 2)
+ SYNC_AS(Sint16BE, int16, 2)
+
+ SYNC_AS(Uint32LE, uint32, 4)
+ SYNC_AS(Uint32BE, uint32, 4)
+ SYNC_AS(Sint32LE, int32, 4)
+ SYNC_AS(Sint32BE, int32, 4)
+
+protected:
+ Common::SeekableReadStream *_loadStream;
+ Common::OutSaveFile *_saveStream;
+
+ uint _bytesSynced;
+};
+
+#undef SYNC_AS
+
+// TODO: Make a subclass "VersionedSerializer", which makes it easy to support
+// multiple versions of a savegame format (again inspired by SCUMM).
+/*
+class VersionedSerializer : public Serializer {
+public:
+ // "version" is the version of the savegame we are loading/creating
+ VersionedSerializer(Common::SeekableReadStream *in, Common::OutSaveFile *out, int version)
+ : Serializer(in, out), _version(version) {
+ assert(in || out);
+ }
+
+ void syncBytes(byte *buf, uint16 size, int minVersion = 0, int maxVersion = INF) {
+ if (_version < minVersion || _version > maxVersion)
+ return; // Do nothing if too old or too new
+ if (_loadStream) {
+ _loadStream->read(buf, size);
+ } else {
+ _saveStream->write(buf, size);
+ }
+ }
+ ...
+
+};
+
+*/
+
+// Mixin class / interface
+// TODO Maybe call it ISerializable or SerializableMixin ?
+// TODO: Taken from SCUMM engine -- move to common/ code?
+class Serializable {
+public:
+ virtual ~Serializable() {}
+ virtual void saveLoadWithSerializer(Serializer *ser) = 0;
+};
+
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/sound.cpp b/engines/tinsel/sound.cpp
new file mode 100644
index 0000000000..e2a24dbd47
--- /dev/null
+++ b/engines/tinsel/sound.cpp
@@ -0,0 +1,211 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ * sound functionality
+ */
+
+#include "tinsel/sound.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/config.h"
+#include "tinsel/music.h"
+#include "tinsel/strres.h"
+#include "tinsel/tinsel.h"
+
+#include "common/endian.h"
+#include "common/file.h"
+#include "common/system.h"
+
+#include "sound/mixer.h"
+#include "sound/audiocd.h"
+
+namespace Tinsel {
+
+//--------------------------- General data ----------------------------------
+
+SoundManager::SoundManager(TinselEngine *vm) :
+ //_vm(vm), // TODO: Enable this once global _vm var is gone
+ _sampleIndex(0), _sampleIndexLen(0) {
+}
+
+SoundManager::~SoundManager() {
+ free(_sampleIndex);
+}
+
+/**
+ * Plays the specified sample through the sound driver.
+ * @param id Identifier of sample to be played
+ * @param type type of sound (voice or sfx)
+ * @param handle sound handle
+ */
+bool SoundManager::playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle) {
+ // Floppy version has no sample file
+ if (_vm->getFeatures() & GF_FLOPPY)
+ return false;
+
+ // no sample driver?
+ if (!_vm->_mixer->isReady())
+ return false;
+
+ // stop any currently playing sample
+ _vm->_mixer->stopHandle(_handle);
+
+ // make sure id is in range
+ assert(id > 0 && id < _sampleIndexLen);
+
+ // get file offset for this sample
+ uint32 dwSampleIndex = _sampleIndex[id];
+
+ // move to correct position in the sample file
+ _sampleStream.seek(dwSampleIndex);
+ if (_sampleStream.ioFailed() || _sampleStream.pos() != dwSampleIndex)
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // read the length of the sample
+ uint32 sampleLen = _sampleStream.readUint32LE();
+ if (_sampleStream.ioFailed())
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // allocate a buffer
+ void *sampleBuf = malloc(sampleLen);
+ assert(sampleBuf);
+
+ // read all of the sample
+ if (_sampleStream.read(sampleBuf, sampleLen) != sampleLen)
+ error("File %s is corrupt", SAMPLE_FILE);
+
+ // FIXME: Should set this in a different place ;)
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, volSound);
+ //_vm->_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, soundVolumeMusic);
+ _vm->_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, volVoice);
+
+
+ // play it
+ _vm->_mixer->playRaw(type, &_handle, sampleBuf, sampleLen, 22050,
+ Audio::Mixer::FLAG_AUTOFREE | Audio::Mixer::FLAG_UNSIGNED);
+
+ if (handle)
+ *handle = _handle;
+
+ return true;
+}
+
+/**
+ * Returns TRUE if there is a sample for the specified sample identifier.
+ * @param id Identifier of sample to be checked
+ */
+bool SoundManager::sampleExists(int id) {
+ if (_vm->_mixer->isReady()) {
+ // make sure id is in range
+ if (id > 0 && id < _sampleIndexLen) {
+ // check for a sample index
+ if (_sampleIndex[id])
+ return true;
+ }
+ }
+
+ // no sample driver or no sample
+ return false;
+}
+
+/**
+ * Returns true if a sample is currently playing.
+ */
+bool SoundManager::sampleIsPlaying(void) {
+ return _vm->_mixer->isSoundHandleActive(_handle);
+}
+
+/**
+ * Stops any currently playing sample.
+ */
+void SoundManager::stopAllSamples(void) {
+ // stop currently playing sample
+ _vm->_mixer->stopHandle(_handle);
+}
+
+/**
+ * Opens and inits all sound sample files.
+ */
+void SoundManager::openSampleFiles(void) {
+ // Floppy and demo versions have no sample files
+ if (_vm->getFeatures() & GF_FLOPPY || _vm->getFeatures() & GF_DEMO)
+ return;
+
+ Common::File f;
+
+ if (_sampleIndex)
+ // already allocated
+ return;
+
+ // open sample index file in binary mode
+ if (f.open(SAMPLE_INDEX)) {
+ // get length of index file
+ f.seek(0, SEEK_END); // move to end of file
+ _sampleIndexLen = f.pos(); // get file pointer
+ f.seek(0, SEEK_SET); // back to beginning
+
+ if (_sampleIndex == NULL) {
+ // allocate a buffer for the indices
+ _sampleIndex = (uint32 *)malloc(_sampleIndexLen);
+
+ // make sure memory allocated
+ if (_sampleIndex == NULL) {
+ // disable samples if cannot alloc buffer for indices
+ // TODO: Disabled sound if we can't load the sample index?
+ return;
+ }
+ }
+
+ // load data
+ if (f.read(_sampleIndex, _sampleIndexLen) != (uint32)_sampleIndexLen)
+ // file must be corrupt if we get to here
+ error("File %s is corrupt", SAMPLE_FILE);
+
+#ifdef SCUMM_BIG_ENDIAN
+ // Convert all ids from LE to native format
+ for (uint i = 0; i < _sampleIndexLen / sizeof(uint32); ++i) {
+ _sampleIndex[i] = READ_LE_UINT32(_sampleIndex + i);
+ }
+#endif
+
+ // close the file
+ f.close();
+
+ // convert file size to size in DWORDs
+ _sampleIndexLen /= sizeof(uint32);
+ } else
+ error("Cannot find file %s", SAMPLE_INDEX);
+
+ // open sample file in binary mode
+ if (!_sampleStream.open(SAMPLE_FILE))
+ error("Cannot find file %s", SAMPLE_FILE);
+
+/*
+ // gen length of the largest sample
+ sampleBuffer.size = _sampleStream.readUint32LE();
+ if (_sampleStream.ioFailed())
+ error("File %s is corrupt", SAMPLE_FILE);
+*/
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/sound.h b/engines/tinsel/sound.h
new file mode 100644
index 0000000000..56618eeb8e
--- /dev/null
+++ b/engines/tinsel/sound.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * This file contains the Sound Driver data structures etc.
+ */
+
+#ifndef TINSEL_SOUND_H
+#define TINSEL_SOUND_H
+
+#include "common/file.h"
+#include "common/file.h"
+
+#include "sound/mixer.h"
+
+#include "tinsel/dw.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+#define MAXSAMPVOL 127
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+class SoundManager {
+protected:
+
+ //TinselEngine *_vm; // TODO: Enable this once global _vm var is gone
+
+ /** Sample handle */
+ Audio::SoundHandle _handle;
+
+ /** Sample index buffer and number of entries */
+ uint32 *_sampleIndex;
+
+ /** Number of entries in the sample index */
+ long _sampleIndexLen;
+
+ /** file stream for sample file */
+ Common::File _sampleStream;
+
+public:
+
+ SoundManager(TinselEngine *vm);
+ ~SoundManager();
+
+ bool playSample(int id, Audio::Mixer::SoundType type, Audio::SoundHandle *handle = 0);
+ void stopAllSamples(void); // Stops any currently playing sample
+
+ bool sampleExists(int id);
+ bool sampleIsPlaying(void);
+
+ // TODO: Internal method, make this protected?
+ void openSampleFiles(void);
+};
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_SOUND_H
diff --git a/engines/tinsel/strres.cpp b/engines/tinsel/strres.cpp
new file mode 100644
index 0000000000..abf5a880f6
--- /dev/null
+++ b/engines/tinsel/strres.cpp
@@ -0,0 +1,209 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * String resource managment routines
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "common/file.h"
+#include "common/endian.h"
+
+namespace Tinsel {
+
+#ifdef DEBUG
+// Diagnostic number
+int newestString;
+#endif
+
+// buffer for resource strings
+static uint8 *textBuffer = 0;
+
+// language resource string filenames
+static const char *languageFiles[] = {
+ "english.txt",
+ "french.txt",
+ "german.txt",
+ "italian.txt",
+ "spanish.txt"
+};
+
+// Set if we're handling 2-byte characters.
+bool bMultiByte = false;
+
+/**
+ * Called to load a resource file for a different language
+ * @param newLang The new language
+ */
+void ChangeLanguage(LANGUAGE newLang) {
+ Common::File f;
+ uint32 textLen = 0; // length of buffer
+
+ if (textBuffer) {
+ // free the previous buffer
+ free(textBuffer);
+ textBuffer = NULL;
+ }
+
+ // Try and open the specified language file. If it fails, and the language
+ // isn't English, try falling back on opening 'english.txt' - some foreign
+ // language versions reused it rather than their proper filename
+ if (!f.open(languageFiles[newLang])) {
+ if ((newLang == TXT_ENGLISH) || !f.open(languageFiles[TXT_ENGLISH]))
+ error("Cannot find file %s", languageFiles[newLang]);
+ }
+
+ // Check whether the file is compressed or not - for compressed files the
+ // first long is the filelength and for uncompressed files it is the chunk
+ // identifier
+ textLen = f.readUint32LE();
+ if (f.ioFailed())
+ error("File %s is corrupt", languageFiles[newLang]);
+
+ if (textLen == CHUNK_STRING || textLen == CHUNK_MBSTRING) {
+ // the file is uncompressed
+
+ bMultiByte = (textLen == CHUNK_MBSTRING);
+
+ // get length of uncompressed file
+ textLen = f.size();
+ f.seek(0, SEEK_SET); // Set to beginning of file
+
+ if (textBuffer == NULL) {
+ // allocate a text buffer for the strings
+ textBuffer = (uint8 *)malloc(textLen);
+
+ // make sure memory allocated
+ assert(textBuffer);
+ }
+
+ // load data
+ if (f.read(textBuffer, textLen) != textLen)
+ // file must be corrupt if we get to here
+ error("File %s is corrupt", languageFiles[newLang]);
+
+ // close the file
+ f.close();
+ } else { // the file must be compressed
+ error("Compression handling has been removed!");
+ }
+}
+
+/**
+ * Loads a string resource identified by id.
+ * @param id identifier of string to be loaded
+ * @param pBuffer points to buffer that receives the string
+ * @param bufferMax maximum number of chars to be copied to the buffer
+ */
+int LoadStringRes(int id, char *pBuffer, int bufferMax) {
+#ifdef DEBUG
+ // For diagnostics
+ newestString = id;
+#endif
+
+ // base of string resource table
+ uint8 *pText = textBuffer;
+
+ // index into text resource file
+ uint32 index = 0;
+
+ // number of chunks to skip
+ int chunkSkip = id / STRINGS_PER_CHUNK;
+
+ // number of strings to skip when in the correct chunk
+ int strSkip = id % STRINGS_PER_CHUNK;
+
+ // length of string
+ int len;
+
+ // skip to the correct chunk
+ while (chunkSkip-- != 0) {
+ // make sure chunk id is correct
+ assert(READ_LE_UINT32(pText + index) == CHUNK_STRING || READ_LE_UINT32(pText + index) == CHUNK_MBSTRING);
+
+ if (READ_LE_UINT32(pText + index + sizeof(uint32)) == 0) {
+ // TEMPORARY DIRTY BODGE
+ strcpy(pBuffer, "!! HIGH STRING !!");
+
+ // string does not exist
+ return 0;
+ }
+
+ // get index to next chunk
+ index = READ_LE_UINT32(pText + index + sizeof(uint32));
+ }
+
+ // skip over chunk id and offset
+ index += (2 * sizeof(uint32));
+
+ // pointer to strings
+ pText = pText + index;
+
+ // skip to the correct string
+ while (strSkip-- != 0) {
+ // skip to next string
+ pText += *pText + 1;
+ }
+
+ // get length of string
+ len = *pText;
+
+ if (len) {
+ // the string exists
+
+ // copy the string to the buffer
+ if (len < bufferMax) {
+ memcpy(pBuffer, pText + 1, len);
+
+ // null terminate
+ pBuffer[len] = 0;
+
+ // number of chars copied
+ return len + 1;
+ } else {
+ memcpy(pBuffer, pText + 1, bufferMax - 1);
+
+ // null terminate
+ pBuffer[bufferMax - 1] = 0;
+
+ // number of chars copied
+ return bufferMax;
+ }
+ }
+
+ // TEMPORARY DIRTY BODGE
+ strcpy(pBuffer, "!! NULL STRING !!");
+
+ // string does not exist
+ return 0;
+}
+
+void FreeTextBuffer() {
+ if (textBuffer) {
+ free(textBuffer);
+ textBuffer = NULL;
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/strres.h b/engines/tinsel/strres.h
new file mode 100644
index 0000000000..fac287492b
--- /dev/null
+++ b/engines/tinsel/strres.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * String resource managment routines
+ */
+
+#ifndef TINSEL_STRRES_H
+#define TINSEL_STRRES_H
+
+#include "common/scummsys.h"
+#include "tinsel/scn.h"
+
+namespace Tinsel {
+
+#define STRINGS_PER_CHUNK 64 // number of strings per chunk in the language text files
+#define FIRST_STR_ID 1 // id number of first string in string table
+#define MAX_STRING_SIZE 255 // maximum size of a string in the resource table
+#define MAX_STRRES_SIZE 300000 // maximum size of string resource file
+
+// Set if we're handling 2-byte characters.
+extern bool bMultiByte;
+
+/*----------------------------------------------------------------------*\
+|* Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+/**
+ * Called to load a resource file for a different language
+ * @param newLang The new language
+ */
+void ChangeLanguage(LANGUAGE newLang);
+
+/**
+ * Loads a string resource identified by id.
+ * @param id identifier of string to be loaded
+ * @param pBuffer points to buffer that receives the string
+ * @param bufferMax maximum number of chars to be copied to the buffer
+ */
+int LoadStringRes(int id, char *pBuffer, int bufferMax);
+
+/**
+ * Frees the text buffer allocated from ChangeLanguage()
+ */
+void FreeTextBuffer();
+
+} // end of namespace Tinsel
+
+#endif
+
diff --git a/engines/tinsel/text.cpp b/engines/tinsel/text.cpp
new file mode 100644
index 0000000000..dab97f7fdf
--- /dev/null
+++ b/engines/tinsel/text.cpp
@@ -0,0 +1,279 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utilities.
+ */
+
+#include "tinsel/dw.h"
+#include "tinsel/graphics.h" // object plotting
+#include "tinsel/handle.h"
+#include "tinsel/sched.h" // process scheduler defines
+#include "tinsel/strres.h" // bMultiByte
+#include "tinsel/text.h" // text defines
+
+namespace Tinsel {
+
+/**
+ * Returns the length of one line of a string in pixels.
+ * @param szStr String
+ * @param pFont Which font to use for dimensions
+ */
+int StringLengthPix(char *szStr, const FONT *pFont) {
+ int strLen; // accumulated length of string
+ byte c;
+ SCNHANDLE hImg;
+
+ // while not end of string or end of line
+ for (strLen = 0; (c = *szStr) != EOS_CHAR && c != LF_CHAR; szStr++) {
+ if (bMultiByte) {
+ if (c & 0x80)
+ c = ((c & ~0x80) << 8) + *++szStr;
+ }
+ hImg = FROM_LE_32(pFont->fontDef[c]);
+
+ if (hImg) {
+ // there is a IMAGE for this character
+ const IMAGE *pChar = (const IMAGE *)LockMem(hImg);
+
+ // add width of font bitmap
+ strLen += FROM_LE_16(pChar->imgWidth);
+ } else
+ // use width of space character
+ strLen += FROM_LE_32(pFont->spaceSize);
+
+ // finally add the inter-character spacing
+ strLen += FROM_LE_32(pFont->xSpacing);
+ }
+
+ // return length of line in pixels - minus inter-char spacing for last character
+ strLen -= FROM_LE_32(pFont->xSpacing);
+ return (strLen > 0) ? strLen : 0;
+}
+
+/**
+ * Returns the justified x start position of a line of text.
+ * @param szStr String to output
+ * @param xPos X position of string
+ * @param pFont Which font to use
+ * @param mode Mode flags for the string
+ */
+int JustifyText(char *szStr, int xPos, const FONT *pFont, int mode) {
+ if (mode & TXT_CENTRE) {
+ // centre justify the text
+
+ // adjust x positioning by half the length of line in pixels
+ xPos -= StringLengthPix(szStr, pFont) / 2;
+ } else if (mode & TXT_RIGHT) {
+ // right justify the text
+
+ // adjust x positioning by length of line in pixels
+ xPos -= StringLengthPix(szStr, pFont);
+ }
+
+ // return text line x start position
+ return xPos;
+}
+
+/**
+ * Main text outputting routine. If a object list is specified a
+ * multi-object is created for the whole text and a pointer to the head
+ * of the list is returned.
+ * @param pList Object list to add text to
+ * @param szStr String to output
+ * @param colour Colour for monochrome text
+ * @param xPos X position of string
+ * @param yPos Y position of string
+ * @param hFont Which font to use
+ * @param mode Mode flags for the string
+ */
+OBJECT *ObjectTextOut(OBJECT *pList, char *szStr, int colour, int xPos, int yPos,
+ SCNHANDLE hFont, int mode) {
+ int xJustify; // x position of text after justification
+ int yOffset; // offset to next line of text
+ OBJECT *pFirst; // head of multi-object text list
+ OBJECT *pChar = 0; // object ptr for the character
+ byte c;
+ SCNHANDLE hImg;
+ const IMAGE *pImg;
+
+ // make sure there is a linked list to add text to
+ assert(pList);
+
+ // get font pointer
+ const FONT *pFont = (const FONT *)LockMem(hFont);
+
+ // init head of text list
+ pFirst = NULL;
+
+ // get image for capital W
+ assert(pFont->fontDef[(int)'W']);
+ pImg = (const IMAGE *)LockMem(FROM_LE_32(pFont->fontDef[(int)'W']));
+
+ // get height of capital W for offset to next line
+ yOffset = FROM_LE_16(pImg->imgHeight);
+
+ while (*szStr) {
+ // x justify the text according to the mode flags
+ xJustify = JustifyText(szStr, xPos, pFont, mode);
+
+ // repeat until end of string or end of line
+ while ((c = *szStr) != EOS_CHAR && c != LF_CHAR) {
+ if (bMultiByte) {
+ if (c & 0x80)
+ c = ((c & ~0x80) << 8) + *++szStr;
+ }
+ hImg = FROM_LE_32(pFont->fontDef[c]);
+
+ if (hImg == 0) {
+ // no image for this character
+
+ // add font spacing for a space character
+ xJustify += FROM_LE_32(pFont->spaceSize);
+ } else { // printable character
+
+ int aniX, aniY; // char image animation offsets
+
+ OBJ_INIT oi;
+ oi.hObjImg = FROM_LE_32(pFont->fontInit.hObjImg);
+ oi.objFlags = FROM_LE_32(pFont->fontInit.objFlags);
+ oi.objID = FROM_LE_32(pFont->fontInit.objID);
+ oi.objX = FROM_LE_32(pFont->fontInit.objX);
+ oi.objY = FROM_LE_32(pFont->fontInit.objY);
+ oi.objZ = FROM_LE_32(pFont->fontInit.objZ);
+
+ // allocate and init a character object
+ if (pFirst == NULL)
+ // first time - init head of list
+ pFirst = pChar = InitObject(&oi); // FIXME: endian issue using fontInit!!!
+ else
+ // chain to multi-char list
+ pChar = pChar->pSlave = InitObject(&oi); // FIXME: endian issue using fontInit!!!
+
+ // convert image handle to pointer
+ pImg = (const IMAGE *)LockMem(hImg);
+
+ // fill in character object
+ pChar->hImg = hImg; // image def
+ pChar->width = FROM_LE_16(pImg->imgWidth); // width of chars bitmap
+ pChar->height = FROM_LE_16(pImg->imgHeight); // height of chars bitmap
+ pChar->hBits = FROM_LE_32(pImg->hImgBits); // bitmap
+
+ // check for absolute positioning
+ if (mode & TXT_ABSOLUTE)
+ pChar->flags |= DMA_ABS;
+
+ // set characters colour - only effective for mono fonts
+ pChar->constant = colour;
+
+ // get Y animation offset
+ GetAniOffset(hImg, pChar->flags, &aniX, &aniY);
+
+ // set x position - ignore animation point
+ pChar->xPos = intToFrac(xJustify);
+
+ // set y position - adjust for animation point
+ pChar->yPos = intToFrac(yPos - aniY);
+
+ if (mode & TXT_SHADOW) {
+ // we want to shadow the character
+ OBJECT *pShad;
+
+ // allocate a object for the shadow and chain to multi-char list
+ pShad = pChar->pSlave = AllocObject();
+
+ // copy the character for a shadow
+ CopyObject(pShad, pChar);
+
+ // add shadow offsets to characters position
+ pShad->xPos += intToFrac(FROM_LE_32(pFont->xShadow));
+ pShad->yPos += intToFrac(FROM_LE_32(pFont->yShadow));
+
+ // shadow is behind the character
+ pShad->zPos--;
+
+ // shadow is always mono
+ pShad->flags = DMA_CNZ | DMA_CHANGED;
+
+ // check for absolute positioning
+ if (mode & TXT_ABSOLUTE)
+ pShad->flags |= DMA_ABS;
+
+ // shadow always uses first palette entry
+ // should really alloc a palette here also ????
+ pShad->constant = 1;
+
+ // add shadow to object list
+ InsertObject(pList, pShad);
+ }
+
+ // add character to object list
+ InsertObject(pList, pChar);
+
+ // move to end of list
+ if (pChar->pSlave)
+ pChar = pChar->pSlave;
+
+ // add character spacing
+ xJustify += FROM_LE_16(pImg->imgWidth);
+ }
+
+ // finally add the inter-character spacing
+ xJustify += FROM_LE_32(pFont->xSpacing);
+
+ // next character in string
+ ++szStr;
+ }
+
+ // adjust the text y position and add the inter-line spacing
+ yPos += yOffset + FROM_LE_32(pFont->ySpacing);
+
+ // check for newline
+ if (c == LF_CHAR)
+ // next character in string
+ ++szStr;
+ }
+
+ // return head of list
+ return pFirst;
+}
+
+/**
+ * Is there an image for this character in this font?
+ * @param hFont which font to use
+ * @param c character to test
+ */
+bool IsCharImage(SCNHANDLE hFont, char c) {
+ byte c2 = (byte)c;
+
+ // Inventory save game name editor needs to be more clever for
+ // multi-byte characters. This bodge will stop it erring.
+ if (bMultiByte && (c2 & 0x80))
+ return false;
+
+ // get font pointer
+ const FONT *pFont = (const FONT *)LockMem(hFont);
+
+ return pFont->fontDef[c2] != 0;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/text.h b/engines/tinsel/text.h
new file mode 100644
index 0000000000..78998831a1
--- /dev/null
+++ b/engines/tinsel/text.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utility defines
+ */
+
+#ifndef TINSEL_TEXT_H // prevent multiple includes
+#define TINSEL_TEXT_H
+
+#include "tinsel/object.h" // object manager defines
+
+namespace Tinsel {
+
+/** text mode flags - defaults to left justify */
+enum {
+ TXT_CENTRE = 0x0001, //!< centre justify text
+ TXT_RIGHT = 0x0002, //!< right justify text
+ TXT_SHADOW = 0x0004, //!< shadow each character
+ TXT_ABSOLUTE = 0x0008 //!< position of text is absolute (only for object text)
+};
+
+/** maximum number of characters in a font */
+#define MAX_FONT_CHARS 256
+
+
+#include "common/pack-start.h" // START STRUCT PACKING
+
+/**
+ * Text font data structure.
+ * @note only the pointer is used so the size of fontDef[] is not important.
+ * It is currently set at 300 because it suited me for debugging.
+ */
+struct FONT {
+ int xSpacing; //!< x spacing between characters
+ int ySpacing; //!< y spacing between characters
+ int xShadow; //!< x shadow offset
+ int yShadow; //!< y shadow offset
+ int spaceSize; //!< x spacing to use for a space character
+ OBJ_INIT fontInit; //!< structure used to init text objects
+ SCNHANDLE fontDef[300]; //!< image handle array for all characters in the font
+} PACKED_STRUCT;
+
+#include "common/pack-end.h" // END STRUCT PACKING
+
+
+/** structure for passing the correct parameters to ObjectTextOut */
+struct TEXTOUT {
+ OBJECT *pList; //!< object list to add text to
+ char *szStr; //!< string to output
+ int colour; //!< colour for monochrome text
+ int xPos; //!< x position of string
+ int yPos; //!< y position of string
+ SCNHANDLE hFont; //!< which font to use
+ int mode; //!< mode flags for the string
+ int sleepTime; //!< sleep time between each character (if non-zero)
+};
+
+
+/*----------------------------------------------------------------------*\
+|* Text Function Prototypes *|
+\*----------------------------------------------------------------------*/
+
+OBJECT *ObjectTextOut( // output a string of text
+ OBJECT *pList, // object list to add text to
+ char *szStr, // string to output
+ int colour, // colour for monochrome text
+ int xPos, // x position of string
+ int yPos, // y position of string
+ SCNHANDLE hFont, // which font to use
+ int mode); // mode flags for the string
+
+OBJECT *ObjectTextOutIndirect( // output a string of text
+ TEXTOUT *pText); // pointer to TextOut struct with all parameters
+
+bool IsCharImage( // Is there an image for this character in this font?
+ SCNHANDLE hFont, // which font to use
+ char c); // character to test
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TEXT_H
diff --git a/engines/tinsel/timers.cpp b/engines/tinsel/timers.cpp
new file mode 100644
index 0000000000..c7b9d3708b
--- /dev/null
+++ b/engines/tinsel/timers.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ *
+ * Note: As part of the transition to ScummVM, the ticks field of a timer has been changed
+ * to a millisecond value, rather than ticks at 24Hz. Most places should be able to use
+ * the timers without change, since the ONE_SECOND constant has been set to be in MILLISECONDS
+ */
+
+#include "tinsel/timers.h"
+#include "tinsel/dw.h"
+#include "tinsel/serializer.h"
+
+#include "common/system.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL DEFINES --------------------
+
+#define MAX_TIMERS 16
+
+struct TIMER {
+ int tno; /**< Timer number */
+ int ticks; /**< Tick count */
+ int secs; /**< Second count */
+ int delta; /**< Increment/decrement value */
+ bool frame; /**< If set, in ticks, otherwise in seconds */
+};
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+static TIMER timers[MAX_TIMERS];
+
+
+//--------------------------------------------------------
+
+/**
+ * Gets the current time in number of ticks.
+ *
+ * DOS timer ticks is the number of 54.9254ms since midnight. Converting the
+ * millisecond count won't give the exact same 'since midnight' count, but I
+ * figure that as long as the timing interval is more or less accurate, it
+ * shouldn't be a problem.
+ */
+
+uint32 DwGetCurrentTime() {
+ return g_system->getMillis() * 55 / 1000;
+}
+
+/**
+ * Resets all of the timer slots
+ */
+
+void RebootTimers(void) {
+ memset(timers, 0, sizeof(timers));
+}
+
+/**
+ * (Un)serialize the timer data for save/restore game.
+ */
+void syncTimerInfo(Serializer &s) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ s.syncAsSint32LE(timers[i].tno);
+ s.syncAsSint32LE(timers[i].ticks);
+ s.syncAsSint32LE(timers[i].secs);
+ s.syncAsSint32LE(timers[i].delta);
+ s.syncAsSint32LE(timers[i].frame);
+ }
+}
+
+/**
+ * Find the timer numbered thus, if one is thus numbered.
+ * @param num number of the timer
+ * @return the timer with the specified number, or NULL if there is none
+ */
+static TIMER *findTimer(int num) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (timers[i].tno == num)
+ return &timers[i];
+ }
+ return NULL;
+}
+
+/**
+ * Find an empty timer slot.
+ */
+static TIMER *allocateTimer(int num) {
+ assert(num); // zero is not allowed as a timer number
+ assert(!findTimer(num)); // Allocating already existant timer
+
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (!timers[i].tno) {
+ timers[i].tno = num;
+ return &timers[i];
+ }
+ }
+
+ error("Too many timers");
+}
+
+/**
+ * Update all timers, as appropriate.
+ */
+void FettleTimers(void) {
+ for (int i = 0; i < MAX_TIMERS; i++) {
+ if (!timers[i].tno)
+ continue;
+
+ timers[i].ticks += timers[i].delta; // Update tick value
+
+ if (timers[i].frame) {
+ if (timers[i].ticks < 0)
+ timers[i].ticks = 0; // Have reached zero
+ } else {
+ if (timers[i].ticks < 0) {
+ timers[i].ticks = ONE_SECOND;
+ timers[i].secs--;
+ if (timers[i].secs < 0)
+ timers[i].secs = 0; // Have reached zero
+ } else if (timers[i].ticks == ONE_SECOND) {
+ timers[i].ticks = 0;
+ timers[i].secs++; // Another second has passed
+ }
+ }
+ }
+}
+
+/**
+ * Start a timer up.
+ */
+void DwSetTimer(int num, int sval, bool up, bool frame) {
+ TIMER *pt;
+
+ assert(num); // zero is not allowed as a timer number
+
+ pt = findTimer(num);
+ if (pt == NULL)
+ pt = allocateTimer(num);
+
+ pt->delta = up ? 1 : -1; // Increment/decrement value
+ pt->frame = frame;
+
+ if (frame) {
+ pt->secs = 0;
+ pt->ticks = sval;
+ } else {
+ pt->secs = sval;
+ pt->ticks = 0;
+ }
+}
+
+/**
+ * Return the current count of a timer.
+ */
+int Timer(int num) {
+ TIMER *pt;
+
+ pt = findTimer(num);
+
+ if (pt == NULL)
+ return -1;
+
+ if (pt->frame)
+ return pt->ticks;
+ else
+ return pt->secs;
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/timers.h b/engines/tinsel/timers.h
new file mode 100644
index 0000000000..75eb87ee2b
--- /dev/null
+++ b/engines/tinsel/timers.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Handles timers.
+ */
+
+#ifndef TINSEL_TIMERS_H // prevent multiple includes
+#define TINSEL_TIMERS_H
+
+#include "common/scummsys.h"
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+class Serializer;
+
+#define ONE_SECOND 24
+
+uint32 DwGetCurrentTime(void);
+
+void RebootTimers(void);
+
+void syncTimerInfo(Serializer &s);
+
+void FettleTimers(void);
+
+void DwSetTimer(int num, int sval, bool up, bool frame);
+
+int Timer(int num);
+
+} // end of namespace Tinsel
+
+#endif
diff --git a/engines/tinsel/tinlib.cpp b/engines/tinsel/tinlib.cpp
new file mode 100644
index 0000000000..e8364e20dd
--- /dev/null
+++ b/engines/tinsel/tinlib.cpp
@@ -0,0 +1,2980 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Glitter library functions.
+ *
+ * In the main called only from PCODE.C
+ * Function names are the same as Glitter code function names.
+ *
+ * To ensure exclusive use of resources and exclusive control responsibilities.
+ */
+
+#define BODGE
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/coroutine.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/film.h"
+#include "tinsel/font.h"
+#include "tinsel/graphics.h"
+#include "tinsel/handle.h"
+#include "tinsel/inventory.h"
+#include "tinsel/move.h"
+#include "tinsel/multiobj.h"
+#include "tinsel/music.h"
+#include "tinsel/object.h"
+#include "tinsel/palette.h"
+#include "tinsel/pcode.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/rince.h"
+#include "tinsel/savescn.h"
+#include "tinsel/sched.h"
+#include "tinsel/scn.h"
+#include "tinsel/scroll.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/text.h"
+#include "tinsel/timers.h" // For ONE_SECOND constant
+#include "tinsel/tinlib.h"
+#include "tinsel/tinsel.h"
+#include "tinsel/token.h"
+
+
+namespace Tinsel {
+
+//----------------- EXTERNAL GLOBAL DATA --------------------
+
+// In DOS_DW.C
+extern bool bRestart; // restart flag - set to restart the game
+extern bool bHasRestarted; // Set after a restart
+
+// In DOS_MAIN.C
+// TODO/FIXME: From dos_main.c: "Only used on PSX so far"
+int clRunMode = 0;
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// in BG.C
+extern void startupBackground(SCNHANDLE bfilm);
+extern void ChangePalette(SCNHANDLE hPal);
+extern int BackgroundWidth(void);
+extern int BackgroundHeight(void);
+
+// in DOS_DW.C
+extern void SetHookScene(SCNHANDLE scene, int entrance, int transition);
+extern void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+extern void UnHookScene(void);
+extern void SuspendHook(void);
+extern void UnSuspendHook(void);
+
+// in PDISPLAY.C
+extern void EnableTags(void);
+extern void DisableTags(void);
+bool DisableTagsIfEnabled(void);
+extern void setshowstring(void);
+
+// in PLAY.C
+extern void playFilm(SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop);
+extern void playFilmc(CORO_PARAM, SCNHANDLE film, int x, int y, int actorid, bool splay, int sfact, bool escOn, int myescEvent, bool bTop);
+
+// in SCENE.C
+extern void setshowpos(void);
+
+#ifdef BODGE
+// In DOS_HAND.C
+bool ValidHandle(SCNHANDLE offset);
+
+// In SCENE.C
+SCNHANDLE GetSceneHandle(void);
+#endif
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+bool bEnableF1;
+
+
+//----------------- LOCAL DEFINES --------------------
+
+#define JAP_TEXT_TIME (2*ONE_SECOND)
+
+/*----------------------------------------------------------------------*\
+|* Library Procedure and Function codes *|
+\*----------------------------------------------------------------------*/
+
+enum LIB_CODE {
+ ACTORATTR = 0, ACTORDIRECTION, ACTORREF, ACTORSCALE, ACTORXPOS = 4,
+ ACTORYPOS, ADDICON, ADDINV1, ADDINV2, ADDOPENINV, AUXSCALE = 10,
+ BACKGROUND, CAMERA, CLOSEINVENTORY, CONTROL, CONVERSATION = 15,
+ CONVICON, CURSORXPOS, CURSORYPOS, DEC_CONVW, DEC_CURSOR = 20,
+ DEC_INV1, DEC_INV2, DEC_INVW, DEC_LEAD, DEC_TAGFONT = 25,
+ DEC_TALKFONT, DELICON, DELINV, EFFECTACTOR, ESCAPE, EVENT = 31,
+ GETINVLIMIT, HELDOBJECT, HIDE, ININVENTORY, INVDEPICT = 36,
+ INVENTORY, KILLACTOR, KILLBLOCK, KILLEXIT, KILLTAG, LEFTOFFSET = 42,
+ MOVECURSOR, NEWSCENE, NOSCROLL, OBJECTHELD, OFFSET, PAUSE = 48,
+ PLAY, PLAYMIDI, PLAYSAMPLE, PREPARESCENE, PRINT, PRINTOBJ = 54,
+ PRINTTAG, RANDOM, RESTORE_SCENE, SAVE_SCENE, SCALINGREELS = 59,
+ SCANICON, SCROLL, SETACTOR, SETBLOCK, SETEXIT, SETINVLIMIT = 65,
+ SETPALETTE, SETTAG, SETTIMER, SHOWPOS, SHOWSTRING, SPLAY = 71,
+ STAND, STANDTAG, STOP, SWALK, TAGACTOR, TALK, TALKATTR, TIMER = 79,
+ TOPOFFSET, TOPPLAY, TOPWINDOW, UNTAGACTOR, VIBRATE, WAITKEY = 85,
+ WAITTIME, WALK, WALKED, WALKINGACTOR, WALKPOLY, WALKTAG = 91,
+ WHICHINVENTORY = 92,
+ ACTORSON, CUTSCENE, HOOKSCENE, IDLETIME, RESETIDLETIME = 97,
+ TALKAT, UNHOOKSCENE, WAITFRAME, DEC_CSTRINGS, STOPMIDI, STOPSAMPLE = 103,
+ TALKATS = 104,
+ DEC_FLAGS, FADEMIDI, CLEARHOOKSCENE, SETINVSIZE, INWHICHINV = 109,
+ NOBLOCKING, SAMPLEPLAYING, TRYPLAYSAMPLE, ENABLEF1 = 113,
+ RESTARTGAME, QUITGAME, FRAMEGRAB, PLAYRTF, CDPLAY, CDLOAD = 119,
+ HASRESTARTED, RESTORE_CUT, RUNMODE, SUBTITLES, SETLANGUAGE = 124
+};
+
+
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+// Saved cursor co-ordinates for control(on) to restore cursor position
+// as it was at control(off).
+// They are global so that movecursor(..) has a net effect if it
+// precedes control(on).
+static int controlX = 0, controlY = 0;
+
+static int offtype = 0; // used by control()
+static uint32 lastValue = 0; // used by dw_random()
+static int scrollCount = 0; // used by scroll()
+
+static bool NotPointedRunning = false; // Used in printobj and printobjPointed
+
+static COLORREF s_talkfontColor = 0;
+
+//----------------- FORWARD REFERENCES --------------------
+
+void resetidletime(void);
+void stopmidi(void);
+void stopsample(void);
+void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescTime);
+
+
+/**
+ * NOT A LIBRARY FUNCTION
+ *
+ * Poke supplied colours into the DAC queue.
+ */
+static void setTextPal(COLORREF col) {
+ s_talkfontColor = col;
+ UpdateDACqueue(TALKFONT_COL, 1, &s_talkfontColor);
+}
+
+
+static int TextTime(char *pTstring) {
+ if (isJapanMode())
+ return JAP_TEXT_TIME;
+ else if (!speedText)
+ return strlen(pTstring) + ONE_SECOND;
+ else
+ return strlen(pTstring) + ONE_SECOND + (speedText * 5 * ONE_SECOND) / 100;
+}
+
+/*--------------------------------------------------------------------------*/
+
+
+/**
+ * Set actor's attributes.
+ * - currently only the text colour.
+ */
+void actorattr(int actor, int r1, int g1, int b1) {
+ storeActorAttr(actor, r1, g1, b1);
+}
+
+/**
+ * Return the actor's direction.
+ */
+int actordirection(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor != NULL); // not a moving actor
+
+ return (int)GetMActorDirection(pActor);
+}
+
+/**
+ * Return the actor's scale.
+ */
+int actorscale(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor != NULL); // not a moving actor
+
+ return (int)GetMActorScale(pActor);
+}
+
+/**
+ * Returns the x or y position of an actor.
+ */
+int actorpos(int xory, int actor) {
+ int x, y;
+
+ GetActorPos(actor, &x, &y);
+ return (xory == ACTORXPOS) ? x : y;
+}
+
+/**
+ * Make all actors alive at the start of each scene.
+ */
+void actorson(void) {
+ setactorson();
+}
+
+/**
+ * Adds an icon to the conversation window.
+ */
+void addicon(int icon) {
+ AddToInventory(INV_CONV, icon, false);
+}
+
+/**
+ * Place the object in inventory 1 or 2.
+ */
+void addinv(int invno, int object) {
+ assert(invno == INV_1 || invno == INV_2 || invno == INV_OPEN); // illegal inventory number
+
+ AddToInventory(invno, object, false);
+}
+
+/**
+ * Define an actor's walk and stand reels for an auxilliary scale.
+ */
+void auxscale(int actor, int scale, SCNHANDLE *rp) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor); // Can't set aux scale for a non-moving actor
+
+ int j;
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[scale-1][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[scale-1][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->TalkReels[scale-1][j] = *rp++;
+}
+
+/**
+ * Defines the background image for a scene.
+ */
+void background(SCNHANDLE bfilm) {
+ startupBackground(bfilm);
+}
+
+/**
+ * Sets focus of the scroll process.
+ */
+void camera(int actor) {
+ ScrollFocus(actor);
+}
+
+/**
+ * A CDPLAY() is imminent.
+ */
+void cdload(SCNHANDLE start, SCNHANDLE next) {
+ assert(start && next && start != next); // cdload() fault
+
+// TODO/FIXME
+// LoadExtraGraphData(start, next);
+}
+
+/**
+ * Clear the hooked scene (if any)
+ */
+
+void clearhookscene() {
+ SetHookScene(0, 0, 0);
+}
+
+/**
+ * Guess what.
+ */
+
+void closeinventory(void) {
+ KillInventory();
+}
+
+/**
+ * Turn off cursor and take control from player - and variations on the theme.
+ * OR Restore cursor and return control to the player.
+ */
+
+void control(int param) {
+ bEnableF1 = false;
+
+ switch (param) {
+ case CONTROL_STARTOFF:
+ GetControlToken(); // Take control
+ DisableTags(); // Switch off tags
+ DwHideCursor(); // Blank out cursor
+ offtype = param;
+ break;
+
+ case CONTROL_OFF:
+ case CONTROL_OFFV:
+ case CONTROL_OFFV2:
+ if (TestToken(TOKEN_CONTROL)) {
+ GetControlToken(); // Take control
+
+ DisableTags(); // Switch off tags
+ GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position
+
+ // There may be a button timing out
+ GetToken(TOKEN_LEFT_BUT);
+ FreeToken(TOKEN_LEFT_BUT);
+ }
+
+ if (offtype == CONTROL_STARTOFF)
+ GetCursorXYNoWait(&controlX, &controlY, true); // Store cursor position
+
+ offtype = param;
+
+ if (param == CONTROL_OFF)
+ DwHideCursor(); // Blank out cursor
+ else if (param == CONTROL_OFFV) {
+ UnHideCursor();
+ FreezeCursor();
+ } else if (param == CONTROL_OFFV2) {
+ UnHideCursor();
+ }
+ break;
+
+ case CONTROL_ON:
+ if (offtype != CONTROL_OFFV2 && offtype != CONTROL_STARTOFF)
+ SetCursorXY(controlX, controlY);// ... where it was
+
+ FreeControlToken(); // Release control
+
+ if (!InventoryActive())
+ EnableTags(); // Tags back on
+
+ RestoreMainCursor(); // Re-instate cursor...
+ }
+}
+
+/**
+ * Open or close the conversation window.
+ */
+
+void conversation(int fn, HPOLYGON hp, bool escOn, int myescEvent) {
+ assert(hp != NOPOLY); // conversation() must (currently) be called from a polygon code block
+
+ switch (fn) {
+ case CONV_END: // Close down conversation
+ CloseDownConv();
+ break;
+
+ case CONV_DEF: // Default (i.e. TOP of screen)
+ case CONV_BOTTOM: // BOTTOM of screen
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+
+ if (IsConvWindow())
+ break;
+
+ KillInventory();
+ convPos(fn);
+ ConvPoly(hp);
+ PopUpInventory(INV_CONV); // Conversation window
+ ConvAction(INV_OPENICON); // CONVERSATION event
+ break;
+ }
+}
+
+/**
+ * Add icon to conversation window's permanent default list.
+ */
+
+void convicon(int icon) {
+ AddIconToPermanentDefaultList(icon);
+}
+
+/**
+ * Returns the x or y position of the cursor.
+ */
+
+int cursorpos(int xory) {
+ int x, y;
+
+ GetCursorXY(&x, &y, true);
+ return (xory == CURSORXPOS) ? x : y;
+}
+
+/**
+ * Declare conversation window.
+ */
+
+void dec_convw(SCNHANDLE text, int MaxContents, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ idec_convw(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare config strings.
+ */
+
+void dec_cstrings(SCNHANDLE *tp) {
+ setConfigStrings(tp);
+}
+
+/**
+ * Declare cursor's reels.
+ */
+
+void dec_cursor(SCNHANDLE bfilm) {
+ DwInitCursor(bfilm);
+}
+
+/**
+ * Declare the language flags.
+ */
+
+void dec_flags(SCNHANDLE hf) {
+ setFlagFilms(hf);
+}
+
+/**
+ * Declare inventory 1's parameters.
+ */
+
+void dec_inv1(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv1(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare inventory 2's parameters.
+ */
+
+void dec_inv2(SCNHANDLE text, int MaxContents,
+ int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight,
+ int MaxWidth, int MaxHeight) {
+ idec_inv2(text, MaxContents, MinWidth, MinHeight,
+ StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Declare the bits that the inventory windows are constructed from.
+ */
+
+void dec_invw(SCNHANDLE hf) {
+ setInvWinParts(hf);
+}
+
+/**
+ * Declare lead actor.
+ * - the actor's id, walk and stand reels for all the regular scales,
+ * and the tag text.
+ */
+
+void dec_lead(uint32 id, SCNHANDLE *rp, SCNHANDLE text) {
+ PMACTOR pActor; // Moving actor structure
+
+ Tag_Actor(id, text, TAG_DEF); // The lead actor is automatically tagged
+ setleadid(id); // Establish this as the lead
+ SetMover(id); // Establish as a moving actor
+
+ pActor = GetMover(id); // Get moving actor structure
+ assert(pActor);
+
+ // Store all those reels
+ int i, j;
+ for (i = 0; i < 5; ++i) {
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->TalkReels[i][j] = *rp++;
+ }
+
+
+ for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
+ for (j = 0; j < 4; ++j) {
+ pActor->WalkReels[i][j] = pActor->WalkReels[4][j];
+ pActor->StandReels[i][j] = pActor->StandReels[2][j];
+ pActor->TalkReels[i][j] = pActor->TalkReels[4][j];
+ }
+ }
+}
+
+/**
+ * Declare the text font.
+ */
+
+void dec_tagfont(SCNHANDLE hf) {
+ TagFontHandle(hf); // Store the font handle
+}
+
+/**
+ * Declare the text font.
+ */
+
+void dec_talkfont(SCNHANDLE hf) {
+ TalkFontHandle(hf); // Store the font handle
+}
+
+/**
+ * Remove an icon from the conversation window.
+ */
+
+void delicon(int icon) {
+ RemFromInventory(INV_CONV, icon);
+}
+
+/**
+ * Delete the object from inventory 1 or 2.
+ */
+
+void delinv(int object) {
+ if (!RemFromInventory(INV_1, object)) // Remove from inventory 1...
+ RemFromInventory(INV_2, object); // ...or 2 (whichever)
+
+ DropItem(object); // Stop holding it
+}
+
+/**
+ * enablef1
+ */
+
+void enablef1(void) {
+ bEnableF1 = true;
+}
+
+/**
+ * fademidi(in/out)
+ */
+
+void fademidi(CORO_PARAM, int inout) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ assert(inout == FM_IN || inout == FM_OUT);
+
+ // To prevent compiler complaining
+ if (inout == FM_IN || inout == FM_OUT)
+ CORO_SLEEP(1);
+ CORO_END_CODE;
+}
+
+/**
+ * Guess what.
+ */
+
+int getinvlimit(int invno) {
+ return InvGetLimit(invno);
+}
+
+/**
+ * Returns TRUE if the game has been restarted, FALSE if not.
+ */
+bool hasrestarted(void) {
+ return bHasRestarted;
+}
+
+/**
+ * Returns which object is currently held.
+ */
+
+int heldobject(void) {
+ return WhichItemHeld();
+}
+
+/**
+ * Removes a player from the screen, probably when he's about to be
+ * replaced by an animation.
+ *
+ * Not believed to work anymore! (hide() is not used).
+ */
+
+void hide(int actor) {
+ HideActor(actor);
+}
+
+/**
+ * hookscene(scene, entrance, transition)
+ */
+
+void hookscene(SCNHANDLE scene, int entrance, int transition) {
+ SetHookScene(scene, entrance, transition);
+}
+
+/**
+ * idletime
+ */
+
+int idletime(void) {
+ uint32 x;
+
+ x = getUserEventTime() / ONE_SECOND;
+
+ if (!TestToken(TOKEN_CONTROL))
+ resetidletime();
+
+ return (int)x;
+}
+
+/**
+ * invdepict
+ */
+void invdepict(int object, SCNHANDLE hFilm) {
+ invObjectFilm(object, hFilm);
+}
+
+/**
+ * See if an object is in the inventory.
+ */
+int ininventory(int object) {
+ return (InventoryPos(object) != INV_NOICON);
+}
+
+/**
+ * Open an inventory.
+ */
+void inventory(int invno, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ assert((invno == INV_1 || invno == INV_2)); // Trying to open illegal inventory
+
+ PopUpInventory(invno);
+}
+
+/**
+ * See if an object is in the inventory.
+ */
+int inwhichinv(int object) {
+ if (WhichItemHeld() == object)
+ return 0;
+
+ if (IsInInventory(object, INV_1))
+ return 1;
+
+ if (IsInInventory(object, INV_2))
+ return 2;
+
+ return -1;
+}
+
+/**
+ * Kill an actor.
+ */
+void killactor(int actor) {
+ DisableActor(actor);
+}
+
+/**
+ * Turn a blocking polygon off.
+ */
+void killblock(int block) {
+ DisableBlock(block);
+}
+
+/**
+ * Turn an exit off.
+ */
+void killexit(int exit) {
+ DisableExit(exit);
+}
+
+/**
+ * Turn a tag off.
+ */
+void killtag(int tagno) {
+ DisableTag(tagno);
+}
+
+/**
+ * Returns the left or top offset of the screen.
+ */
+int ltoffset(int lort) {
+ int Loffset, Toffset;
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ return (lort == LEFTOFFSET) ? Loffset : Toffset;
+}
+
+/**
+ * Set new cursor position.
+ */
+void movecursor(int x, int y) {
+ SetCursorXY(x, y);
+
+ controlX = x; // Save these values so that
+ controlY = y; // control(on) doesn't undo this
+}
+
+/**
+ * Triggers change to a new scene.
+ */
+void newscene(CORO_PARAM, SCNHANDLE scene, int entrance, int transition) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+#ifdef BODGE
+ if (!ValidHandle(scene)) {
+ scene = GetSceneHandle();
+ entrance = 1;
+ }
+ assert(scene); // Non-existant first scene!
+#endif
+
+ SetNewScene(scene, entrance, transition);
+
+#if 1
+ // Prevent tags and cursor re-appearing
+ GetControl(CONTROL_STARTOFF);
+#endif
+
+ // Prevent code subsequent to this call running before scene changes
+ if (g_scheduler->getCurrentPID() != PID_MASTER_SCR)
+ CORO_KILL_SELF();
+ CORO_END_CODE;
+}
+
+/**
+ * Disable dynamic blocking for current scene.
+ */
+void noblocking(void) {
+ bNoBlocking = true;
+}
+
+/**
+ * Define a no-scroll boundary for the current scene.
+ */
+void noscroll(int x1, int y1, int x2, int y2) {
+ SetNoScroll(x1, y1, x2, y2);
+}
+
+/**
+ * Hold the specified object.
+ */
+void objectheld(int object) {
+ HoldItem(object);
+}
+
+/**
+ * Set the top left offset of the screen.
+ */
+void offset(int x, int y) {
+ KillScroll();
+ PlayfieldSetPos(FIELD_WORLD, x, y);
+}
+
+/**
+ * Play a film.
+ */
+void play(CORO_PARAM, SCNHANDLE film, int x, int y, int compit, int actorid, bool splay, int sfact,
+ bool escOn, int myescEvent, bool bTop) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(film != 0); // play(): Trying to play NULL film
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // If this actor is dead, call a stop to the calling process
+ if (actorid && !actorAlive(actorid))
+ CORO_KILL_SELF();
+
+ // 7/4/95
+ if (!escOn)
+ myescEvent = GetEscEvents();
+
+ if (compit == 1) {
+ // Play to completion before returning
+ CORO_INVOKE_ARGS(playFilmc, (CORO_SUBCTX, film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop));
+ } else if (compit == 2) {
+ error("play(): compit == 2 - please advise John");
+ } else {
+ // Kick off the play and return.
+ playFilm(film, x, y, actorid, splay, sfact, escOn, myescEvent, bTop);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a midi file.
+ */
+void playmidi(CORO_PARAM, SCNHANDLE hMidi, int loop, bool complete) {
+ // FIXME: This is a workaround for the FIXME below
+ if (GetMidiVolume() == 0)
+ return;
+
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ assert(loop == MIDI_DEF || loop == MIDI_LOOP);
+
+ PlayMidiSequence(hMidi, loop == MIDI_LOOP);
+
+ // FIXME: The following check messes up the script arguments when
+ // entering the secret door in the bookshelf in the library,
+ // leading to a crash, when the music volume is set to 0 (MidiPlaying()
+ // always false then).
+ //
+ // Why exactly this happens is unclear. An analysis of the involved
+ // script(s) might reveal more.
+ //
+ // Note: This check&sleep was added in DW v2. It was most likely added
+ // to ensure that the MIDI song started playing before the next opcode
+ // is executed.
+ if (!MidiPlaying())
+ CORO_SLEEP(1);
+
+ if (complete) {
+ while (MidiPlaying())
+ CORO_SLEEP(1);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a sample.
+ */
+void playsample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ Audio::SoundHandle handle;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't play SFX if voice is already playing
+ if (_vm->_mixer->hasActiveChannelOfType(Audio::Mixer::kSpeechSoundType))
+ return;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents()) {
+ _vm->_sound->stopAllSamples(); // Stop any currently playing sample
+ return;
+ }
+
+ if (volSound != 0 && _vm->_sound->sampleExists(sample)) {
+ _vm->_sound->playSample(sample, Audio::Mixer::kSFXSoundType, &_ctx->handle);
+
+ if (complete) {
+ while (_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents()) {
+ _vm->_mixer->stopHandle(_ctx->handle);
+ break;
+ }
+
+ CORO_SLEEP(1);
+ }
+ }
+ } else {
+ // Prevent Glitter lock-up
+ CORO_SLEEP(1);
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Play a sample.
+ */
+void tryplaysample(CORO_PARAM, int sample, bool complete, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not appropriate
+ if (_vm->_sound->sampleIsPlaying()) {
+ // return, but prevent Glitter lock-up
+ CORO_SLEEP(1);
+ return;
+ }
+
+ CORO_INVOKE_ARGS(playsample, (CORO_SUBCTX, sample, complete, escOn, myescEvent));
+ CORO_END_CODE;
+}
+
+/**
+ * Trigger pre-loading of a scene's data.
+ */
+void preparescene(SCNHANDLE scene) {
+#ifdef BODGE
+ if (!ValidHandle(scene))
+ return;
+#endif
+}
+
+/**
+ * Print the given text at the given place for the given time.
+ *
+ * Print(....., h) -> hold = 1 (not used)
+ * Print(....., s) -> hold = 2 (sustain)
+ */
+void print(CORO_PARAM, int x, int y, SCNHANDLE text, int time, int hold, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ int myleftEvent;
+ bool bSample; // Set if a sample is playing
+ Audio::SoundHandle handle;
+ int timeout;
+ int time;
+ CORO_END_CONTEXT(_ctx);
+
+ bool bJapDoPrintText; // Bodge to get-around Japanese bodge
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->pText = NULL;
+ _ctx->bSample = false;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // Kick off the voice sample
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ }
+
+ // Calculate display time
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ bJapDoPrintText = false;
+ if (time == 0) {
+ // This is a 'talky' print
+ _ctx->time = TextTime(tBufferAddr());
+
+ // Cut short-able if sustain was not set
+ _ctx->myleftEvent = (hold == 2) ? 0 : GetLeftEvents();
+ } else {
+ _ctx->time = time * ONE_SECOND;
+ _ctx->myleftEvent = 0;
+ if (isJapanMode())
+ bJapDoPrintText = true;
+ }
+
+ // Print the text
+ if (bJapDoPrintText || (!isJapanMode() && (bSubtitles || !_ctx->bSample))) {
+ int Loffset, Toffset; // Screen position
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, x - Loffset, y - Toffset, hTalkFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // string produced NULL text
+ if (IsTopWindow())
+ MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
+
+ /*
+ * New feature: Don't go off the side of the background
+ */
+ int shift;
+ shift = MultiRightmost(_ctx->pText) + 2;
+ if (shift >= BackgroundWidth()) // Not off right
+ MultiMoveRelXY(_ctx->pText, BackgroundWidth() - shift, 0);
+ shift = MultiLeftmost(_ctx->pText) - 1;
+ if (shift <= 0) // Not off left
+ MultiMoveRelXY(_ctx->pText, -shift, 0);
+ shift = MultiLowest(_ctx->pText);
+ if (shift > BackgroundHeight()) // Not off bottom
+ MultiMoveRelXY(_ctx->pText, 0, BackgroundHeight() - shift);
+ }
+
+ // Give up if nothing printed and no sample
+ if (_ctx->pText == NULL && !_ctx->bSample)
+ return;
+
+ // Leave it up until time runs out or whatever
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ // Abort if left click - hardwired feature for talky-print!
+ // Will be ignored if myleftevent happens to be 0!
+ // Abort if sample times out
+ if ((escOn && myescEvent != GetEscEvents())
+ || (_ctx->myleftEvent && _ctx->myleftEvent != GetLeftEvents())
+ || (_ctx->bSample && --_ctx->timeout <= 0))
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Must wait for time
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->time-- <= 0)
+ break;
+ }
+
+ } while (1);
+
+ // Delete the text
+ if (_ctx->pText != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _vm->_mixer->stopHandle(_ctx->handle);
+
+ CORO_END_CODE;
+}
+
+
+static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item);
+static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText);
+
+/**
+ * Print the given inventory object's name or whatever.
+ */
+void printobj(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, const int event) {
+ CORO_BEGIN_CONTEXT;
+ OBJECT *pText; // text object pointer
+ int textx, texty;
+ int item;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ assert(pinvo != 0); // printobj() may only be called from an object code block
+
+ if (text == (SCNHANDLE)-1) { // 'OFF'
+ NotPointedRunning = true;
+ return;
+ }
+ if (text == (SCNHANDLE)-2) { // 'ON'
+ NotPointedRunning = false;
+ return;
+ }
+
+ GetCursorXY(&_ctx->textx, &_ctx->texty, false); // Cursor position..
+ _ctx->item = InvItem(&_ctx->textx, &_ctx->texty, true); // ..to text position
+
+ if (_ctx->item == INV_NOICON)
+ return;
+
+ if (event != POINTED) {
+ NotPointedRunning = true; // Get POINTED text to die
+ CORO_SLEEP(1); // Give it chance to
+ } else
+ NotPointedRunning = false; // There may have been an OFF without an ON
+
+ // Display the text and set it's Z position
+ if (event == POINTED || (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))) {
+ int xshift;
+
+ LoadStringRes(text, tBufferAddr(), TBUFSZ); // The text string
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, _ctx->textx, _ctx->texty, hTagFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // printobj() string produced NULL text
+ MultiSetZPosition(_ctx->pText, Z_INV_ITEXT);
+
+ // Don't go off the side of the screen
+ xshift = MultiLeftmost(_ctx->pText);
+ if (xshift < 0) {
+ MultiMoveRelXY(_ctx->pText, - xshift, 0);
+ _ctx->textx -= xshift;
+ }
+ xshift = MultiRightmost(_ctx->pText);
+ if (xshift > SCREEN_WIDTH) {
+ MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
+ _ctx->textx += SCREEN_WIDTH - xshift;
+ }
+ } else
+ _ctx->pText = NULL;
+
+ if (event == POINTED) {
+ // FIXME: Is there ever an associated sound if in POINTED mode???
+ assert(!_vm->_sound->sampleExists(text));
+ CORO_INVOKE_ARGS(printobjPointed, (CORO_SUBCTX, text, pinvo, _ctx->pText, _ctx->textx, _ctx->texty, _ctx->item));
+ } else {
+ CORO_INVOKE_2(printobjNonPointed, text, _ctx->pText);
+ }
+
+ // Delete the text, if haven't already
+ if (_ctx->pText)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+
+ CORO_END_CODE;
+}
+
+static void printobjPointed(CORO_PARAM, const SCNHANDLE text, const INV_OBJECT *pinvo, OBJECT *&pText, const int textx, const int texty, const int item) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Have to give way to non-POINTED-generated text
+ // and go away if the item gets picked up
+ int x, y;
+ do {
+ // Give up if this item gets picked up
+ if (WhichItemHeld() == pinvo->id)
+ break;
+
+ // Give way to non-POINTED-generated text
+ if (NotPointedRunning) {
+ // Delete the text, and wait for the all-clear
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), pText);
+ pText = NULL;
+ while (NotPointedRunning)
+ CORO_SLEEP(1);
+
+ GetCursorXY(&x, &y, false);
+ if (InvItem(&x, &y, false) != item)
+ break;
+
+ // Re-display in the same place
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, textx, texty, hTagFontHandle(), TXT_CENTRE);
+ assert(pText); // printobj() string produced NULL text
+ MultiSetZPosition(pText, Z_INV_ITEXT);
+ }
+
+ CORO_SLEEP(1);
+
+ // Carry on until the cursor leaves this icon
+ GetCursorXY(&x, &y, false);
+ } while (InvItemId(x, y) == pinvo->id);
+
+ CORO_END_CODE;
+}
+
+static void printobjNonPointed(CORO_PARAM, const SCNHANDLE text, const OBJECT *pText) {
+ CORO_BEGIN_CONTEXT;
+ bool bSample; // Set if a sample is playing
+ Audio::SoundHandle handle;
+
+ int myleftEvent;
+ bool took_control;
+ int ticks;
+ int timeout;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Kick off the voice sample
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ } else
+ _ctx->bSample = false;
+
+ _ctx->myleftEvent = GetLeftEvents();
+ _ctx->took_control = GetControl(CONTROL_OFF);
+
+ // Display for a time, but abort if conversation gets hidden
+ if (isJapanMode())
+ _ctx->ticks = JAP_TEXT_TIME;
+ else if (pText)
+ _ctx->ticks = TextTime(tBufferAddr());
+ else
+ _ctx->ticks = 0;
+
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ CORO_SLEEP(1);
+ --_ctx->timeout;
+
+ // Abort if left click - hardwired feature for talky-print!
+ // Abort if sample times out
+ // Abort if conversation hidden
+ if (_ctx->myleftEvent != GetLeftEvents() || _ctx->timeout <= 0 || convHid())
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Must wait for time
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->ticks-- <= 0)
+ break;
+ }
+ } while (1);
+
+ NotPointedRunning = false; // Let POINTED text back in
+
+ if (_ctx->took_control)
+ control(CONTROL_ON); // Free control if we took it
+
+ _vm->_mixer->stopHandle(_ctx->handle);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Register the fact that this poly would like its tag displayed.
+ */
+void printtag(HPOLYGON hp, SCNHANDLE text) {
+ assert(hp != NOPOLY); // printtag() may only be called from a polygon code block
+
+ if (PolyTagState(hp) == TAG_OFF) {
+ SetPolyTagState(hp, TAG_ON);
+ SetPolyTagHandle(hp, text);
+ }
+}
+
+/**
+ * quitgame
+ */
+void quitgame(void) {
+ stopmidi();
+ stopsample();
+ _vm->quitFlag = true;
+}
+
+/**
+ * Return a random number between optional limits.
+ */
+int dw_random(int n1, int n2, int norpt) {
+ int i = 0;
+ uint32 value;
+
+ do {
+ value = n1 + _vm->getRandomNumber(n2 - n1);
+ } while ((lastValue == value) && (norpt == RAND_NORPT) && (++i <= 10));
+
+ lastValue = value;
+ return value;
+}
+
+/**
+ * resetidletime
+ */
+void resetidletime(void) {
+ resetUserEventTime();
+}
+
+/**
+ * restartgame
+ */
+void restartgame(void) {
+ stopmidi();
+ stopsample();
+ bRestart = true;
+}
+
+/**
+ * Restore saved scene.
+ */
+void restore_scene(bool bFade) {
+ UnSuspendHook();
+ PleaseRestoreScene(bFade);
+}
+
+/**
+ * runmode
+ */
+int runmode(void) {
+ return clRunMode;
+}
+
+/**
+ * sampleplaying
+ */
+bool sampleplaying(bool escOn, int myescEvent) {
+ // escape effects introduced 14/12/95 to fix
+ // while (sampleplaying()) pause;
+
+ if (escOn && myescEvent != GetEscEvents())
+ return false;
+
+ return _vm->_sound->sampleIsPlaying();
+}
+
+/**
+ * Save current scene.
+ */
+void save_scene(CORO_PARAM) {
+ PleaseSaveScene(coroParam);
+ SuspendHook();
+}
+
+/**
+ * scalingreels
+ */
+void scalingreels(int actor, int scale, int direction,
+ SCNHANDLE left, SCNHANDLE right, SCNHANDLE forward, SCNHANDLE away) {
+
+ setscalingreels(actor, scale, direction, left, right, forward, away);
+}
+
+/**
+ * Return the icon that caused the CONVERSE event.
+ */
+
+int scanicon(void) {
+ return convIcon();
+}
+
+/**
+ * Scroll the screen to target co-ordinates.
+ */
+
+void scroll(CORO_PARAM, int x, int y, int iter, bool comp, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int mycount;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Instant completion!
+ offset(x, y);
+ } else {
+ _ctx->mycount = ++scrollCount;
+
+ ScrollTo(x, y, iter);
+
+ if (comp) {
+ int Loffset, Toffset;
+ do {
+ CORO_SLEEP(1);
+
+ // If escapable and ESCAPE is pressed...
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Instant completion!
+ offset(x, y);
+ break;
+ }
+
+ // give up if have been superseded
+ if (_ctx->mycount != scrollCount)
+ CORO_KILL_SELF();
+
+ PlayfieldGetPos(FIELD_WORLD, &Loffset, &Toffset);
+ } while (Loffset != x || Toffset != y);
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Un-kill an actor.
+ */
+void setactor(int actor) {
+ EnableActor(actor);
+}
+
+/**
+ * Turn a blocking polygon on.
+ */
+
+void setblock(int blockno) {
+ EnableBlock(blockno);
+}
+
+/**
+ * Turn an exit on.
+ */
+
+void setexit(int exitno) {
+ EnableExit(exitno);
+}
+
+/**
+ * Guess what.
+ */
+void setinvlimit(int invno, int n) {
+ InvSetLimit(invno, n);
+}
+
+/**
+ * Guess what.
+ */
+void setinvsize(int invno, int MinWidth, int MinHeight,
+ int StartWidth, int StartHeight, int MaxWidth, int MaxHeight) {
+ InvSetSize(invno, MinWidth, MinHeight, StartWidth, StartHeight, MaxWidth, MaxHeight);
+}
+
+/**
+ * Guess what.
+ */
+void setlanguage(LANGUAGE lang) {
+ assert(lang == TXT_ENGLISH || lang == TXT_FRENCH
+ || lang == TXT_GERMAN || lang == TXT_ITALIAN
+ || lang == TXT_SPANISH); // ensure language is valid
+
+ ChangeLanguage(lang);
+}
+
+/**
+ * Set palette
+ */
+void setpalette(SCNHANDLE hPal, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ ChangePalette(hPal);
+}
+
+/**
+ * Turn a tag on.
+ */
+void settag(int tagno) {
+ EnableTag(tagno);
+}
+
+/**
+ * Initialise a timer.
+ */
+void settimer(int timerno, int start, bool up, bool frame) {
+ DwSetTimer(timerno, start, up != 0, frame != 0);
+}
+
+#ifdef DEBUG
+/**
+ * Enable display of diagnostic co-ordinates.
+ */
+void showpos(void) {
+ setshowpos();
+}
+
+/**
+ * Enable display of diagnostic co-ordinates.
+ */
+void showstring(void) {
+ setshowstring();
+}
+#endif
+
+/**
+ * Special play - slow down associated actor's movement while the play
+ * is running. After the play, position the actor where the play left
+ * it and continue walking, if the actor still is.
+ */
+
+void splay(CORO_PARAM, int sf, SCNHANDLE film, int x, int y, bool complete, int actorid, bool escOn, int myescEvent) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ play(coroParam, film, x, y, complete, actorid, true, sf, escOn, myescEvent, false);
+}
+
+/**
+ * (Re)Position an actor.
+ * If moving actor is not around yet in this scene, start it up.
+ */
+
+void stand(int actor, int x, int y, SCNHANDLE film) {
+ PMACTOR pActor; // Moving actor structure
+
+ pActor = GetMover(actor);
+ if (pActor) {
+ if (pActor->MActorState == NO_MACTOR) {
+ // create a moving actor process
+ MActorProcessCreate(x, y, (actor == LEAD_ACTOR) ? LeadId() : actor, pActor);
+
+ if (film == TF_NONE) {
+ SetMActorStanding(pActor);
+ } else {
+ switch (film) {
+ case TF_NONE:
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ AlterMActor(pActor, film, AR_NORMAL);
+ break;
+ }
+ }
+ } else {
+ switch (film) {
+ case TF_NONE:
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ if (x != -1 && y != -1)
+ MoveMActor(pActor, x, y);
+ AlterMActor(pActor, film, AR_NORMAL);
+ break;
+ }
+ }
+ } else if (actor == NULL_ACTOR) {
+ //
+ } else {
+ assert(film != 0); // Trying to play NULL film
+
+ // Kick off the play and return.
+ playFilm(film, x, y, actor, false, 0, false, 0, false);
+ }
+}
+
+/**
+ * Position the actor at the polygon's tag node.
+ */
+void standtag(int actor, HPOLYGON hp) {
+ SCNHANDLE film;
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // standtag() may only be called from a polygon code block
+
+ // Lead actor uses tag node film
+ film = getPolyFilm(hp);
+ getPolyNode(hp, &pnodex, &pnodey);
+ if (film && (actor == LEAD_ACTOR || actor == LeadId()))
+ stand(actor, pnodex, pnodey, film);
+ else
+ stand(actor, pnodex, pnodey, 0);
+}
+
+/**
+ * Kill a moving actor's walk.
+ */
+void stop(int actor) {
+ PMACTOR pActor;
+
+ pActor = GetMover(actor);
+ assert(pActor); // Trying to stop a null actor
+
+ GetToken(pActor->actorToken); // Kill the walk process
+ pActor->stop = true; // Cause the actor to stop
+ FreeToken(pActor->actorToken);
+}
+
+void stopmidi(void) {
+ StopMidi(); // Stop any currently playing midi
+}
+
+void stopsample(void) {
+ _vm->_sound->stopAllSamples(); // Stop any currently playing sample
+}
+
+void subtitles(int onoff) {
+ assert (onoff == ST_ON || onoff == ST_OFF);
+
+ if (isJapanMode())
+ return; // Subtitles are always off in JAPAN version (?)
+
+ if (onoff == ST_ON)
+ bSubtitles = true;
+ else
+ bSubtitles = false;
+}
+
+/**
+ * Special walk.
+ * Walk into or out of a legal path.
+ */
+void swalk(CORO_PARAM, int actor, int x1, int y1, int x2, int y2, SCNHANDLE film, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ bool took_control; // Set if this function takes control
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ // For lead actor, lock out the user (if not already locked out)
+ if (actor == LeadId() || actor == LEAD_ACTOR)
+ _ctx->took_control = GetControl(CONTROL_OFFV2);
+ else
+ _ctx->took_control = false;
+
+ HPOLYGON hPath;
+
+ hPath = InPolygon(x1, y1, PATH);
+ if (hPath != NOPOLY) {
+ // Walking out of a path
+ stand(actor, x1, y1, 0);
+ } else {
+ hPath = InPolygon(x2, y2, PATH);
+ // One of them has to be in a path
+ assert(hPath != NOPOLY); //one co-ordinate must be in a legal path
+
+ // Walking into a path
+ stand(actor, x2, y2, 0); // Get path's characteristics
+ stand(actor, x1, y1, 0);
+ }
+
+ CORO_INVOKE_ARGS(walk, (CORO_SUBCTX, actor, x2, y2, film, 0, true, escOn, myescEvent));
+
+ // Free control if we took it
+ if (_ctx->took_control)
+ control(CONTROL_ON);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Define a tagged actor.
+ */
+
+void tagactor(int actor, SCNHANDLE text, int tp) {
+ Tag_Actor(actor, text, tp);
+}
+
+/**
+ * Text goes over actor's head while actor plays the talk reel.
+ */
+
+void FinishTalkingReel(PMACTOR pActor, int actor) {
+ if (pActor) {
+ SetMActorStanding(pActor);
+ AlterMActor(pActor, 0, AR_POPREEL);
+ } else {
+ setActorTalking(actor, false);
+ playFilm(getActorPlayFilm(actor), -1, -1, 0, false, 0, false, 0, false);
+ }
+}
+
+void talk(CORO_PARAM, SCNHANDLE film, const SCNHANDLE text, int actorid, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int Loffset, Toffset; // Top left of display
+ int actor; // The speaking actor
+ PMACTOR pActor; // For moving actors
+ int myleftEvent;
+ int ticks;
+ bool bTookControl; // Set if this function takes control
+ bool bTookTags; // Set if this function disables tags
+ OBJECT *pText; // text object pointer
+ bool bSample; // Set if a sample is playing
+ bool bTalkReel; // Set while talk reel is playing
+ Audio::SoundHandle handle;
+ int timeout;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->Loffset = 0;
+ _ctx->Toffset = 0;
+ _ctx->ticks = 0;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ _ctx->myleftEvent = GetLeftEvents();
+
+ // If this actor is dead, call a stop to the calling process
+ if (actorid && !actorAlive(actorid))
+ CORO_KILL_SELF();
+
+ /*
+ * Find out which actor is talking
+ * and with which direction if no film supplied
+ */
+ TFTYPE direction;
+ switch (film) {
+ case TF_NONE:
+ case TF_UP:
+ case TF_DOWN:
+ case TF_LEFT:
+ case TF_RIGHT:
+ _ctx->actor = LeadId(); // If no film, actor is lead actor
+ direction = (TFTYPE)film;
+ break;
+
+ default:
+ _ctx->actor = extractActor(film);
+ assert(_ctx->actor); // talk() - no actor ID in the reel
+ direction = TF_BOGUS;
+ break;
+ }
+
+ /*
+ * Lock out the user (for lead actor, if not already locked out)
+ * May need to disable tags for other actors
+ */
+ if (_ctx->actor == LeadId())
+ _ctx->bTookControl = GetControl(CONTROL_OFF);
+ else
+ _ctx->bTookControl = false;
+ _ctx->bTookTags = DisableTagsIfEnabled();
+
+ /*
+ * Kick off the voice sample
+ */
+ if (volVoice != 0 && _vm->_sound->sampleExists(text)) {
+ _vm->_sound->playSample(text, Audio::Mixer::kSpeechSoundType, &_ctx->handle);
+ _ctx->bSample = _vm->_mixer->isSoundHandleActive(_ctx->handle);
+ } else
+ _ctx->bSample = false;
+
+ /*
+ * Replace actor with the talk reel, saving the current one
+ */
+ _ctx->pActor = GetMover(_ctx->actor);
+ if (_ctx->pActor) {
+ if (direction != TF_BOGUS)
+ film = GetMactorTalkReel(_ctx->pActor, direction);
+ AlterMActor(_ctx->pActor, film, AR_PUSHREEL);
+ } else {
+ setActorTalking(_ctx->actor, true);
+ setActorTalkFilm(_ctx->actor, film);
+ playFilm(film, -1, -1, 0, false, 0, escOn, myescEvent, false);
+ }
+ _ctx->bTalkReel = true;
+ CORO_SLEEP(1); // Allow the play to come in
+
+ /*
+ * Display the text.
+ */
+ _ctx->pText = NULL;
+ if (isJapanMode()) {
+ _ctx->ticks = JAP_TEXT_TIME;
+ } else if (bSubtitles || !_ctx->bSample) {
+ int aniX, aniY; // actor position
+ int xshift, yshift;
+ /*
+ * Work out where to display the text
+ */
+ PlayfieldGetPos(FIELD_WORLD, &_ctx->Loffset, &_ctx->Toffset);
+ GetActorMidTop(_ctx->actor, &aniX, &aniY);
+ aniY -= _ctx->Toffset;
+
+ setTextPal(getActorTcol(_ctx->actor));
+ LoadStringRes(text, tBufferAddr(), TBUFSZ);
+ _ctx->pText = ObjectTextOut(GetPlayfieldList(FIELD_STATUS), tBufferAddr(),
+ 0, aniX - _ctx->Loffset, aniY, hTalkFontHandle(), TXT_CENTRE);
+ assert(_ctx->pText); // talk() string produced NULL text;
+ if (IsTopWindow())
+ MultiSetZPosition(_ctx->pText, Z_TOPW_TEXT);
+
+ /*
+ * Set bottom of text just above the speaker's head
+ * But don't go off the top of the screen
+ */
+ yshift = aniY - MultiLowest(_ctx->pText) - 2; // Just above head
+ MultiMoveRelXY(_ctx->pText, 0, yshift); //
+ yshift = MultiHighest(_ctx->pText);
+ if (yshift < 4)
+ MultiMoveRelXY(_ctx->pText, 0, 4 - yshift); // Not off top
+
+ /*
+ * Don't go off the side of the screen
+ */
+ xshift = MultiRightmost(_ctx->pText) + 2;
+ if (xshift >= SCREEN_WIDTH) // Not off right
+ MultiMoveRelXY(_ctx->pText, SCREEN_WIDTH - xshift, 0);
+ xshift = MultiLeftmost(_ctx->pText) - 1;
+ if (xshift <= 0) // Not off left
+ MultiMoveRelXY(_ctx->pText, -xshift, 0);
+ /*
+ * Work out how long to talk.
+ * During this time, reposition the text if the screen scrolls.
+ */
+ _ctx->ticks = TextTime(tBufferAddr());
+ }
+
+ _ctx->timeout = SAMPLETIMEOUT;
+ do {
+ // Keep text in place if scrolling
+ if (_ctx->pText != NULL) {
+ int nLoff, nToff;
+
+ PlayfieldGetPos(FIELD_WORLD, &nLoff, &nToff);
+ if (nLoff != _ctx->Loffset || nToff != _ctx->Toffset) {
+ MultiMoveRelXY(_ctx->pText, _ctx->Loffset - nLoff, _ctx->Toffset - nToff);
+ _ctx->Loffset = nLoff;
+ _ctx->Toffset = nToff;
+ }
+ }
+
+ CORO_SLEEP(1);
+ --_ctx->timeout;
+
+ // Abort if escapable and ESCAPE is pressed
+ // Abort if left click - hardwired feature for talk!
+ // Abort if sample times out
+ if ((escOn && myescEvent != GetEscEvents())
+ || (_ctx->myleftEvent != GetLeftEvents())
+ || (_ctx->timeout <= 0))
+ break;
+
+ if (_ctx->bSample) {
+ // Wait for sample to end whether or not
+ if (!_vm->_mixer->isSoundHandleActive(_ctx->handle)) {
+ if (_ctx->pText == NULL || speedText == DEFTEXTSPEED) {
+ // No text or speed modification - just depends on sample
+ break;
+ } else {
+ // Talk reel stops at end of speech
+ FinishTalkingReel(_ctx->pActor, _ctx->actor);
+ _ctx->bTalkReel = false;
+ _ctx->bSample = false;
+ }
+ }
+ } else {
+ // No sample - just depends on time
+ if (_ctx->ticks-- <= 0)
+ break;
+ }
+ } while (1);
+
+ /*
+ * The talk is over now - dump the text
+ * Stop the sample
+ * Restore the actor's film or standing reel
+ */
+ if (_ctx->pText != NULL)
+ MultiDeleteObject(GetPlayfieldList(FIELD_STATUS), _ctx->pText);
+ _vm->_mixer->stopHandle(_ctx->handle);
+ if (_ctx->bTalkReel)
+ FinishTalkingReel(_ctx->pActor, _ctx->actor);
+
+ /*
+ * Restore user control and tags, as appropriate
+ * And, finally, release the talk token.
+ */
+ if (_ctx->bTookControl)
+ control(CONTROL_ON);
+ if (_ctx->bTookTags)
+ EnableTags();
+
+ CORO_END_CODE;
+}
+
+/**
+ * talkat(actor, x, y, text)
+ */
+void talkat(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, bool escOn, int myescEvent) {
+ if (!coroParam) {
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!isJapanMode() && (bSubtitles || !_vm->_sound->sampleExists(text)))
+ setTextPal(getActorTcol(actor));
+ }
+
+ print(coroParam, x, y, text, 0, 0, escOn, myescEvent);
+}
+
+/**
+ * talkats(actor, x, y, text, sustain)
+ */
+void talkats(CORO_PARAM, int actor, int x, int y, SCNHANDLE text, int sustain, bool escOn, int myescEvent) {
+ if (!coroParam) {
+ assert(sustain == 2);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!isJapanMode())
+ setTextPal(getActorTcol(actor));
+ }
+
+ print(coroParam, x, y, text, 0, sustain, escOn, myescEvent);
+}
+
+/**
+ * Set talk font's palette entry.
+ */
+void talkattr(int r1, int g1, int b1, bool escOn, int myescEvent) {
+ if (isJapanMode())
+ return;
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (r1 > MAX_INTENSITY) r1 = MAX_INTENSITY; // } Ensure
+ if (g1 > MAX_INTENSITY) g1 = MAX_INTENSITY; // } within limits
+ if (b1 > MAX_INTENSITY) b1 = MAX_INTENSITY; // }
+
+ setTextPal(RGB(r1, g1, b1));
+}
+
+/**
+ * Get a timer's current count.
+ */
+int timer(int timerno) {
+ return Timer(timerno);
+}
+
+/**
+ * topplay(film, x, y, actor, hold, complete)
+ */
+void topplay(CORO_PARAM, SCNHANDLE film, int x, int y, int complete, int actorid, bool splay, int sfact, bool escOn, int myescTime) {
+ play(coroParam, film, x, y, complete, actorid, splay, sfact, escOn, myescTime, true);
+}
+
+/**
+ * Open or close the 'top window'
+ */
+
+void topwindow(int bpos) {
+ assert(bpos == TW_START || bpos == TW_END);
+
+ switch (bpos) {
+ case TW_END:
+ KillInventory();
+ break;
+
+ case TW_START:
+ KillInventory();
+ PopUpConf(TOPWIN);
+ break;
+ }
+}
+
+/**
+ * unhookscene
+ */
+
+void unhookscene(void) {
+ UnHookScene();
+}
+
+/**
+ * Un-define an actor as tagged.
+ */
+
+void untagactor(int actor) {
+ UnTagActor(actor);
+}
+
+/**
+ * vibrate
+ */
+
+void vibrate(void) {
+}
+
+/**
+ * waitframe(int actor, int frameNumber)
+ */
+
+void waitframe(CORO_PARAM, int actor, int frameNumber, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ while (getActorSteps(actor) < frameNumber) {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Return when a key pressed or button pushed.
+ */
+
+void waitkey(CORO_PARAM, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int startEvent;
+ int startX, startY;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ while (1) {
+ _ctx->startEvent = getUserEvents();
+ // Store cursor position
+ while (!GetCursorXYNoWait(&_ctx->startX, &_ctx->startY, false))
+ CORO_SLEEP(1);
+
+ while (_ctx->startEvent == getUserEvents()) {
+ CORO_SLEEP(1);
+
+ // Not necessary to monitor escape as it's an event anyway
+
+ int curX, curY;
+ GetCursorXY(&curX, &curY, false); // Store cursor position
+ if (curX != _ctx->startX || curY != _ctx->startY)
+ break;
+
+ if (IsConfWindow())
+ break;
+ }
+
+ if (!IsConfWindow())
+ return;
+
+ do {
+ CORO_SLEEP(1);
+ } while (IsConfWindow());
+
+ CORO_SLEEP(ONE_SECOND / 2); // Let it die down
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Pause for requested time.
+ */
+
+void waittime(CORO_PARAM, int time, bool frame, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ int time;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ // Don't do it if it's not wanted
+ if (escOn && myescEvent != GetEscEvents())
+ return;
+
+ if (!frame)
+ time *= ONE_SECOND;
+
+ _ctx->time = time;
+ do {
+ CORO_SLEEP(1);
+
+ // Abort if escapable and ESCAPE is pressed
+ if (escOn && myescEvent != GetEscEvents())
+ break;
+ } while (_ctx->time--);
+ CORO_END_CODE;
+}
+
+/**
+ * Set a moving actor off on a walk.
+ */
+void walk(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, int hold, bool igPath, bool escOn, int myescEvent) {
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ return;
+ }
+
+ assert(pActor->hCpath != NOPOLY); // moving actor not in path
+
+ GetToken(pActor->actorToken);
+ SetActorDest(pActor, x, y, igPath, film);
+ DontScrollCursor();
+
+ if (hold == 2) {
+ ;
+ } else {
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+ }
+ }
+ FreeToken(pActor->actorToken);
+ CORO_END_CODE;
+}
+
+/**
+ * Set a moving actor off on a walk.
+ * Wait to see if its aborted or completed.
+ */
+void walked(CORO_PARAM, int actor, int x, int y, SCNHANDLE film, bool escOn, int myescEvent, bool &retVal) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ int ticket;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ retVal = true;
+ return;
+ }
+
+ CORO_SLEEP(ONE_SECOND);
+
+ assert(pActor->hCpath != NOPOLY); // moving actor not in path
+
+ // Briefly aquire token to kill off any other normal walk
+ GetToken(pActor->actorToken);
+ FreeToken(pActor->actorToken);
+
+ SetActorDest(pActor, x, y, false, film);
+ DontScrollCursor();
+
+ _ctx->ticket = GetActorTicket(pActor);
+
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ if (_ctx->ticket != GetActorTicket(pActor)) {
+ retVal = false;
+ return;
+ }
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ stand(actor, x, y, 0);
+ retVal = true;
+ return;
+ }
+ }
+
+ int endx, endy;
+ GetMActorPosition(pActor, &endx, &endy);
+ retVal = (_ctx->ticket == GetActorTicket(pActor) && endx == x && endy == y);
+
+ CORO_END_CODE;
+}
+
+/**
+ * Declare a moving actor.
+ */
+void walkingactor(uint32 id, SCNHANDLE *rp) {
+ PMACTOR pActor; // Moving actor structure
+
+ SetMover(id); // Establish as a moving actor
+ pActor = GetMover(id);
+ assert(pActor);
+
+ // Store all those reels
+ int i, j;
+ for (i = 0; i < 5; ++i) {
+ for (j = 0; j < 4; ++j)
+ pActor->WalkReels[i][j] = *rp++;
+ for (j = 0; j < 4; ++j)
+ pActor->StandReels[i][j] = *rp++;
+ }
+
+
+ for (i = NUM_MAINSCALES; i < TOTAL_SCALES; i++) {
+ for (j = 0; j < 4; ++j) {
+ pActor->WalkReels[i][j] = pActor->WalkReels[4][j];
+ pActor->StandReels[i][j] = pActor->StandReels[2][j];
+ }
+ }
+}
+
+/**
+ * Walk a moving actor towards the polygon's tag, but return when the
+ * actor enters the polygon.
+ */
+
+void walkpoly(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int aniX, aniY; // cursor/actor position
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ standtag(actor, hp);
+ return;
+ }
+
+ GetToken(pActor->actorToken);
+ getPolyNode(hp, &pnodex, &pnodey);
+ SetActorDest(pActor, pnodex, pnodey, false, film);
+ DoScrollCursor();
+
+ do {
+ CORO_SLEEP(1);
+
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Straight there if escaped
+ standtag(actor, hp);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+
+ GetMActorPosition(pActor, &aniX, &aniY);
+ } while (!MActorIsInPolygon(pActor, hp) && MAmoving(pActor));
+
+ FreeToken(pActor->actorToken);
+
+ CORO_END_CODE;
+}
+
+/**
+ * walktag(actor, reel, hold)
+ */
+
+void walktag(CORO_PARAM, int actor, SCNHANDLE film, HPOLYGON hp, bool escOn, int myescEvent) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ PMACTOR pActor = GetMover(actor);
+ assert(pActor); // Can't walk a non-moving actor
+
+ CORO_BEGIN_CODE(_ctx);
+
+ int pnodex, pnodey;
+
+ assert(hp != NOPOLY); // walkpoly() may only be called from a polygon code block
+
+ // Straight there if escaped
+ if (escOn && myescEvent != GetEscEvents()) {
+ standtag(actor, hp);
+ return;
+ }
+
+ GetToken(pActor->actorToken);
+ getPolyNode(hp, &pnodex, &pnodey);
+ SetActorDest(pActor, pnodex, pnodey, false, film);
+ DoScrollCursor();
+
+ while (MAmoving(pActor)) {
+ CORO_SLEEP(1);
+
+ if (escOn && myescEvent != GetEscEvents()) {
+ // Straight there if escaped
+ standtag(actor, hp);
+ FreeToken(pActor->actorToken);
+ return;
+ }
+ }
+
+ // Adopt the tag-related reel
+ SCNHANDLE pfilm = getPolyFilm(hp);
+
+ switch (pfilm) {
+ case TF_NONE:
+ break;
+
+ case TF_UP:
+ SetMActorDirection(pActor, AWAY);
+ SetMActorStanding(pActor);
+ break;
+ case TF_DOWN:
+ SetMActorDirection(pActor, FORWARD);
+ SetMActorStanding(pActor);
+ break;
+ case TF_LEFT:
+ SetMActorDirection(pActor, LEFTREEL);
+ SetMActorStanding(pActor);
+ break;
+ case TF_RIGHT:
+ SetMActorDirection(pActor, RIGHTREEL);
+ SetMActorStanding(pActor);
+ break;
+
+ default:
+ if (actor == LEAD_ACTOR || actor == LeadId())
+ AlterMActor(pActor, pfilm, AR_NORMAL);
+ else
+ SetMActorStanding(pActor);
+ break;
+ }
+
+ FreeToken(pActor->actorToken);
+ CORO_END_CODE;
+}
+
+/**
+ * whichinventory
+ */
+
+int whichinventory(void) {
+ return WhichInventoryOpen();
+}
+
+
+/**
+ * Subtract one less that the number of parameters from pp
+ * pp then points to the first parameter.
+ *
+ * If the library function has no return value:
+ * return -(the number of parameters) to pop them from the stack
+ *
+ * If the library function has a return value:
+ * return -(the number of parameters - 1) to pop most of them from
+ * the stack, and stick the return value in pp[0]
+ * @param operand Library function
+ * @param pp Top of parameter stack
+ */
+int CallLibraryRoutine(CORO_PARAM, int operand, int32 *pp, const INT_CONTEXT *pic, RESUME_STATE *pResumeState) {
+ debug(7, "CallLibraryRoutine op %d (escOn %d, myescEvent %d)", operand, pic->escOn, pic->myescEvent);
+ switch (operand) {
+ case ACTORATTR:
+ pp -= 3; // 4 parameters
+ actorattr(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case ACTORDIRECTION:
+ pp[0] = actordirection(pp[0]);
+ return 0;
+
+ case ACTORREF:
+ error("actorref isn't a real function!");
+
+ case ACTORSCALE:
+ pp[0] = actorscale(pp[0]);
+ return 0;
+
+ case ACTORSON:
+ actorson();
+ return 0;
+
+ case ACTORXPOS:
+ pp[0] = actorpos(ACTORXPOS, pp[0]);
+ return 0;
+
+ case ACTORYPOS:
+ pp[0] = actorpos(ACTORYPOS, pp[0]);
+ return 0;
+
+ case ADDICON:
+ addicon(pp[0]);
+ return -1;
+
+ case ADDINV1:
+ addinv(INV_1, pp[0]);
+ return -1;
+
+ case ADDINV2:
+ addinv(INV_2, pp[0]);
+ return -1;
+
+ case ADDOPENINV:
+ addinv(INV_OPEN, pp[0]);
+ return -1;
+
+ case AUXSCALE:
+ pp -= 13; // 14 parameters
+ auxscale(pp[0], pp[1], (SCNHANDLE *)(pp+2));
+ return -14;
+
+ case BACKGROUND:
+ background(pp[0]);
+ return -1;
+
+ case CAMERA:
+ camera(pp[0]);
+ return -1;
+
+ case CDLOAD:
+ pp -= 1; // 2 parameters
+ cdload(pp[0], pp[1]);
+ return -2;
+
+ case CDPLAY:
+ error("cdplay isn't a real function!");
+
+ case CLEARHOOKSCENE:
+ clearhookscene();
+ return 0;
+
+ case CLOSEINVENTORY:
+ closeinventory();
+ return 0;
+
+ case CONTROL:
+ control(pp[0]);
+ return -1;
+
+ case CONVERSATION:
+ conversation(pp[0], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -1;
+
+ case CONVICON:
+ convicon(pp[0]);
+ return -1;
+
+ case CURSORXPOS:
+ pp[0] = cursorpos(CURSORXPOS);
+ return 0;
+
+ case CURSORYPOS:
+ pp[0] = cursorpos(CURSORYPOS);
+ return 0;
+
+ case CUTSCENE:
+ error("cutscene isn't a real function!");
+
+ case DEC_CONVW:
+ pp -= 7; // 8 parameters
+ dec_convw(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_CSTRINGS:
+ pp -= 19; // 20 parameters
+ dec_cstrings((SCNHANDLE *)pp);
+ return -20;
+
+ case DEC_CURSOR:
+ dec_cursor(pp[0]);
+ return -1;
+
+ case DEC_FLAGS:
+ dec_flags(pp[0]);
+ return -1;
+
+ case DEC_INV1:
+ pp -= 7; // 8 parameters
+ dec_inv1(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_INV2:
+ pp -= 7; // 8 parameters
+ dec_inv2(pp[0], pp[1], pp[2], pp[3],
+ pp[4], pp[5], pp[6], pp[7]);
+ return -8;
+
+ case DEC_INVW:
+ dec_invw(pp[0]);
+ return -1;
+
+ case DEC_LEAD:
+ pp -= 61; // 62 parameters
+ dec_lead(pp[0], (SCNHANDLE *)&pp[1], pp[61]);
+ return -62;
+
+ case DEC_TAGFONT:
+ dec_tagfont(pp[0]);
+ return -1;
+
+ case DEC_TALKFONT:
+ dec_talkfont(pp[0]);
+ return -1;
+
+ case DELICON:
+ delicon(pp[0]);
+ return -1;
+
+ case DELINV:
+ delinv(pp[0]);
+ return -1;
+
+ case EFFECTACTOR:
+ assert(pic->event == ENTER || pic->event == LEAVE); // effectactor() must be from effect poly code
+
+ pp[0] = pic->actorid;
+ return 0;
+
+ case ENABLEF1:
+ enablef1();
+ return 0;
+
+ case EVENT:
+ pp[0] = pic->event;
+ return 0;
+
+ case FADEMIDI:
+ fademidi(coroParam, pp[0]);
+ return -1;
+
+ case FRAMEGRAB:
+ return -1;
+
+ case GETINVLIMIT:
+ pp[0] = getinvlimit(pp[0]);
+ return 0;
+
+ case HASRESTARTED:
+ pp[0] = hasrestarted();
+ return 0;
+
+ case HELDOBJECT:
+ pp[0] = heldobject();
+ return 0;
+
+ case HIDE:
+ hide(pp[0]);
+ return -1;
+
+ case HOOKSCENE:
+ pp -= 2; // 3 parameters
+ hookscene(pp[0], pp[1], pp[2]);
+ return -3;
+
+ case IDLETIME:
+ pp[0] = idletime();
+ return 0;
+
+ case ININVENTORY:
+ pp[0] = ininventory(pp[0]);
+ return 0; // using return value
+
+ case INVDEPICT:
+ pp -= 1; // 2 parameters
+ invdepict(pp[0], pp[1]);
+ return -2;
+
+ case INVENTORY:
+ inventory(pp[0], pic->escOn, pic->myescEvent);
+ return -1;
+
+ case INWHICHINV:
+ pp[0] = inwhichinv(pp[0]);
+ return 0; // using return value
+
+ case KILLACTOR:
+ killactor(pp[0]);
+ return -1;
+
+ case KILLBLOCK:
+ killblock(pp[0]);
+ return -1;
+
+ case KILLEXIT:
+ killexit(pp[0]);
+ return -1;
+
+ case KILLTAG:
+ killtag(pp[0]);
+ return -1;
+
+ case LEFTOFFSET:
+ pp[0] = ltoffset(LEFTOFFSET);
+ return 0;
+
+ case MOVECURSOR:
+ pp -= 1; // 2 parameters
+ movecursor(pp[0], pp[1]);
+ return -2;
+
+ case NEWSCENE:
+ pp -= 2; // 3 parameters
+ if (*pResumeState == RES_2)
+ *pResumeState = RES_NOT;
+ else
+ newscene(coroParam, pp[0], pp[1], pp[2]);
+ return -3;
+
+ case NOBLOCKING:
+ noblocking();
+ return 0;
+
+ case NOSCROLL:
+ pp -= 3; // 4 parameters
+ noscroll(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case OBJECTHELD:
+ objectheld(pp[0]);
+ return -1;
+
+ case OFFSET:
+ pp -= 1; // 2 parameters
+ offset(pp[0], pp[1]);
+ return -2;
+
+ case PLAY:
+ pp -= 5; // 6 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ play(coroParam, pp[0], pp[1], pp[2], pp[5], 0, false, 0, pic->escOn, pic->myescEvent, false);
+ else
+ play(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent, false);
+ return -6;
+
+ case PLAYMIDI:
+ pp -= 2; // 3 parameters
+ playmidi(coroParam, pp[0], pp[1], pp[2]);
+ return -3;
+
+ case PLAYRTF:
+ error("playrtf only applies to cdi!");
+
+ case PLAYSAMPLE:
+ pp -= 1; // 2 parameters
+ playsample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case PREPARESCENE:
+ preparescene(pp[0]);
+ return -1;
+
+ case PRINT:
+ pp -= 5; // 6 parameters
+ /* pp[2] was intended to be attribute */
+ print(coroParam, pp[0], pp[1], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent);
+ return -6;
+
+ case PRINTOBJ:
+ printobj(coroParam, pp[0], pic->pinvo, pic->event);
+ return -1;
+
+ case PRINTTAG:
+ printtag(pic->hpoly, pp[0]);
+ return -1;
+
+ case QUITGAME:
+ quitgame();
+ return 0;
+
+ case RANDOM:
+ pp -= 2; // 3 parameters
+ pp[0] = dw_random(pp[0], pp[1], pp[2]);
+ return -2; // One holds return value
+
+ case RESETIDLETIME:
+ resetidletime();
+ return 0;
+
+ case RESTARTGAME:
+ restartgame();
+ return 0;
+
+ case RESTORE_CUT:
+ restore_scene(false);
+ return 0;
+
+ case RESTORE_SCENE:
+ restore_scene(true);
+ return 0;
+
+ case RUNMODE:
+ pp[0] = runmode();
+ return 0;
+
+ case SAMPLEPLAYING:
+ pp[0] = sampleplaying(pic->escOn, pic->myescEvent);
+ return 0;
+
+ case SAVE_SCENE:
+ if (*pResumeState == RES_1)
+ *pResumeState = RES_2;
+ else
+ save_scene(coroParam);
+ return 0;
+
+ case SCALINGREELS:
+ pp -= 6; // 7 parameters
+ scalingreels(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
+ return -7;
+
+ case SCANICON:
+ pp[0] = scanicon();
+ return 0;
+
+ case SCROLL:
+ pp -= 3; // 4 parameters
+ scroll(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent);
+ return -4;
+
+ case SETACTOR:
+ setactor(pp[0]);
+ return -1;
+
+ case SETBLOCK:
+ setblock(pp[0]);
+ return -1;
+
+ case SETEXIT:
+ setexit(pp[0]);
+ return -1;
+
+ case SETINVLIMIT:
+ pp -= 1; // 2 parameters
+ setinvlimit(pp[0], pp[1]);
+ return -2;
+
+ case SETINVSIZE:
+ pp -= 6; // 7 parameters
+ setinvsize(pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pp[6]);
+ return -7;
+
+ case SETLANGUAGE:
+ setlanguage((LANGUAGE)pp[0]);
+ return -1;
+
+ case SETPALETTE:
+ setpalette(pp[0], pic->escOn, pic->myescEvent);
+ return -1;
+
+ case SETTAG:
+ settag(pp[0]);
+ return -1;
+
+ case SETTIMER:
+ pp -= 3; // 4 parameters
+ settimer(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case SHOWPOS:
+#ifdef DEBUG
+ showpos();
+#endif
+ return 0;
+
+ case SHOWSTRING:
+#ifdef DEBUG
+ showstring();
+#endif
+ return 0;
+
+ case SPLAY:
+ pp -= 6; // 7 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], 0, pic->escOn, pic->myescEvent);
+ else
+ splay(coroParam, pp[0], pp[1], pp[2], pp[3], pp[6], pic->actorid, pic->escOn, pic->myescEvent);
+ return -7;
+
+ case STAND:
+ pp -= 3; // 4 parameters
+ stand(pp[0], pp[1], pp[2], pp[3]);
+ return -4;
+
+ case STANDTAG:
+ standtag(pp[0], pic->hpoly);
+ return -1;
+
+ case STOP:
+ stop(pp[0]);
+ return -1;
+
+ case STOPMIDI:
+ stopmidi();
+ return 0;
+
+ case STOPSAMPLE:
+ stopsample();
+ return 0;
+
+ case SUBTITLES:
+ subtitles(pp[0]);
+ return -1;
+
+ case SWALK:
+ pp -= 5; // 6 parameters
+ swalk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pp[5], pic->escOn, pic->myescEvent);
+ return -6;
+
+ case TAGACTOR:
+ pp -= 2; // 3 parameters
+ tagactor(pp[0], pp[1], pp[2]);
+ return -3;
+
+ case TALK:
+ pp -= 1; // 2 parameters
+
+ if (pic->event == ENTER || pic->event == LEAVE)
+ talk(coroParam, pp[0], pp[1], 0, pic->escOn, pic->myescEvent);
+ else
+ talk(coroParam, pp[0], pp[1], pic->actorid, pic->escOn, pic->myescEvent);
+ return -2;
+
+ case TALKAT:
+ pp -= 3; // 4 parameters
+ talkat(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent);
+ return -4;
+
+ case TALKATS:
+ pp -= 4; // 5 parameters
+ talkats(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], pic->escOn, pic->myescEvent);
+ return -5;
+
+ case TALKATTR:
+ pp -= 2; // 3 parameters
+ talkattr(pp[0], pp[1], pp[2], pic->escOn, pic->myescEvent);
+ return -3;
+
+ case TIMER:
+ pp[0] = timer(pp[0]);
+ return 0;
+
+ case TOPOFFSET:
+ pp[0] = ltoffset(TOPOFFSET);
+ return 0;
+
+ case TOPPLAY:
+ pp -= 5; // 6 parameters
+ topplay(coroParam, pp[0], pp[1], pp[2], pp[5], pic->actorid, false, 0, pic->escOn, pic->myescEvent);
+ return -6;
+
+ case TOPWINDOW:
+ topwindow(pp[0]);
+ return -1;
+
+ case TRYPLAYSAMPLE:
+ pp -= 1; // 2 parameters
+ tryplaysample(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case UNHOOKSCENE:
+ unhookscene();
+ return 0;
+
+ case UNTAGACTOR:
+ untagactor(pp[0]);
+ return -1;
+
+ case VIBRATE:
+ vibrate();
+ return 0;
+
+ case WAITKEY:
+ waitkey(coroParam, pic->escOn, pic->myescEvent);
+ return 0;
+
+ case WAITFRAME:
+ pp -= 1; // 2 parameters
+ waitframe(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case WAITTIME:
+ pp -= 1; // 2 parameters
+ waittime(coroParam, pp[0], pp[1], pic->escOn, pic->myescEvent);
+ return -2;
+
+ case WALK:
+ pp -= 4; // 5 parameters
+ walk(coroParam, pp[0], pp[1], pp[2], pp[3], pp[4], false, pic->escOn, pic->myescEvent);
+ return -5;
+
+ case WALKED: {
+ pp -= 3; // 4 parameters
+ bool tmp;
+ walked(coroParam, pp[0], pp[1], pp[2], pp[3], pic->escOn, pic->myescEvent, tmp);
+ if (!coroParam) {
+ // Only write the result to the stack if walked actually completed running.
+ pp[0] = tmp;
+ }
+ }
+ return -3;
+
+ case WALKINGACTOR:
+ pp -= 40; // 41 parameters
+ walkingactor(pp[0], (SCNHANDLE *)&pp[1]);
+ return -41;
+
+ case WALKPOLY:
+ pp -= 2; // 3 parameters
+ walkpoly(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -3;
+
+ case WALKTAG:
+ pp -= 2; // 3 parameters
+ walktag(coroParam, pp[0], pp[1], pic->hpoly, pic->escOn, pic->myescEvent);
+ return -3;
+
+ case WHICHINVENTORY:
+ pp[0] = whichinventory();
+ return 0;
+
+ default:
+ error("Unsupported library function");
+ }
+
+ error("Can't possibly get here");
+}
+
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/tinlib.h b/engines/tinsel/tinlib.h
new file mode 100644
index 0000000000..001de70896
--- /dev/null
+++ b/engines/tinsel/tinlib.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * Text utility defines
+ */
+
+#ifndef TINSEL_TINLIB_H // prevent multiple includes
+#define TINSEL_TINLIB_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// Library functions in TINLIB.C
+
+void control(int param);
+void stand(int actor, int x, int y, SCNHANDLE film);
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TINLIB_H
diff --git a/engines/tinsel/tinsel.cpp b/engines/tinsel/tinsel.cpp
new file mode 100644
index 0000000000..1f56385283
--- /dev/null
+++ b/engines/tinsel/tinsel.cpp
@@ -0,0 +1,999 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can 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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#include "common/endian.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "common/file.h"
+#include "common/savefile.h"
+#include "common/config-manager.h"
+#include "common/stream.h"
+
+#include "graphics/cursorman.h"
+
+#include "base/plugins.h"
+#include "base/version.h"
+
+#include "sound/mididrv.h"
+#include "sound/mixer.h"
+#include "sound/audiocd.h"
+
+#include "tinsel/actors.h"
+#include "tinsel/background.h"
+#include "tinsel/config.h"
+#include "tinsel/cursor.h"
+#include "tinsel/dw.h"
+#include "tinsel/events.h"
+#include "tinsel/faders.h"
+#include "tinsel/film.h"
+#include "tinsel/handle.h"
+#include "tinsel/heapmem.h" // MemoryInit
+#include "tinsel/inventory.h"
+#include "tinsel/music.h"
+#include "tinsel/object.h"
+#include "tinsel/pid.h"
+#include "tinsel/polygons.h"
+#include "tinsel/savescn.h"
+#include "tinsel/scn.h"
+#include "tinsel/serializer.h"
+#include "tinsel/sound.h"
+#include "tinsel/strres.h"
+#include "tinsel/timers.h"
+#include "tinsel/tinsel.h"
+
+namespace Tinsel {
+
+//----------------- EXTERNAL FUNCTIONS ---------------------
+
+// In BG.CPP
+extern void SetDoFadeIn(bool tf);
+extern void DropBackground(void);
+
+// In CURSOR.CPP
+extern void CursorProcess(CORO_PARAM, const void *);
+
+// In INVENTORY.CPP
+extern void InventoryProcess(CORO_PARAM, const void *);
+
+// In SCENE.CPP
+extern void PrimeBackground();
+extern void NewScene(SCNHANDLE scene, int entry);
+extern SCNHANDLE GetSceneHandle(void);
+
+// In TIMER.CPP
+extern void FettleTimers(void);
+extern void RebootTimers(void);
+
+//----------------- FORWARD DECLARATIONS ---------------------
+void SetNewScene(SCNHANDLE scene, int entrance, int transition);
+
+//----------------- GLOBAL GLOBAL DATA --------------------
+
+bool bRestart = false;
+bool bHasRestarted = false;
+
+#ifdef DEBUG
+bool bFast; // set to make it go ludicrously fast
+#endif
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+struct Scene {
+ SCNHANDLE scene; // Memory handle for scene
+ int entry; // Entrance number
+ int trans; // Transition - not yet used
+};
+
+static Scene NextScene = { 0, 0, 0 };
+static Scene HookScene = { 0, 0, 0 };
+static Scene DelayedScene = { 0, 0, 0 };
+
+static bool bHookSuspend = false;
+
+static PROCESS *pMouseProcess = 0;
+static PROCESS *pKeyboardProcess = 0;
+
+// Stack of pending mouse button events
+Common::List<Common::EventType> mouseButtons;
+
+// Stack of pending keypresses
+Common::List<Common::Event> keypresses;
+
+//----------------- LOCAL PROCEDURES --------------------
+
+/**
+ * Process to handle keypresses
+ */
+void KeyboardProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ while (true) {
+ if (keypresses.empty()) {
+ // allow scheduling
+ CORO_SLEEP(1);
+ continue;
+ }
+
+ // Get the next keyboard event off the stack
+ Common::Event evt = *keypresses.begin();
+ keypresses.erase(keypresses.begin());
+
+ // Switch for special keys
+ switch (evt.kbd.keycode) {
+ // Drag action
+ case Common::KEYCODE_LALT:
+ case Common::KEYCODE_RALT:
+ if (evt.type == Common::EVENT_KEYDOWN) {
+ if (!bSwapButtons)
+ ProcessButEvent(BE_RDSTART);
+ else
+ ProcessButEvent(BE_LDSTART);
+ } else {
+ if (!bSwapButtons)
+ ProcessButEvent(BE_LDEND);
+ else
+ ProcessButEvent(BE_RDEND);
+ }
+ continue;
+
+ case Common::KEYCODE_LCTRL:
+ case Common::KEYCODE_RCTRL:
+ if (evt.type == Common::EVENT_KEYDOWN) {
+ ProcessKeyEvent(LOOK_KEY);
+ } else {
+ // Control key release
+ }
+ continue;
+
+ default:
+ break;
+ }
+
+ // At this point only key down events need processing
+ if (evt.type == Common::EVENT_KEYUP)
+ continue;
+
+ if (_vm->_keyHandler != NULL)
+ // Keyboard is hooked, so pass it on to that handler first
+ if (!_vm->_keyHandler(evt.kbd))
+ continue;
+
+ switch (evt.kbd.keycode) {
+ /*** SPACE = WALKTO ***/
+ case Common::KEYCODE_SPACE:
+ ProcessKeyEvent(WALKTO_KEY);
+ continue;
+
+ /*** RETURN = ACTION ***/
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ ProcessKeyEvent(ACTION_KEY);
+ continue;
+
+ /*** l = LOOK ***/
+ case Common::KEYCODE_l: // LOOK
+ ProcessKeyEvent(LOOK_KEY);
+ continue;
+
+ case Common::KEYCODE_ESCAPE:
+ // WORKAROUND: Check if any of the starting logo screens are active, and if so
+ // manually skip to the title screen, allowing them to be bypassed
+ {
+ int sceneOffset = (_vm->getFeatures() & GF_SCNFILES) ? 1 : 0;
+ int sceneNumber = (GetSceneHandle() >> SCNHANDLE_SHIFT) - sceneOffset;
+ if ((language == TXT_GERMAN) &&
+ ((sceneNumber >= 25 && sceneNumber <= 27) || (sceneNumber == 17))) {
+ // Skip to title screen
+ // It seems the German CD version uses scenes 25,26,27,17 for the intro,
+ // instead of 13,14,15,11; also, the title screen is 11 instead of 10
+ SetNewScene((11 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT);
+ } else if ((sceneNumber >= 13) && (sceneNumber <= 15) || (sceneNumber == 11)) {
+ // Skip to title screen
+ SetNewScene((10 + sceneOffset) << SCNHANDLE_SHIFT, 1, TRANS_CUT);
+ } else {
+ // Not on an intro screen, so process the key normally
+ ProcessKeyEvent(ESC_KEY);
+ }
+ }
+ continue;
+
+#ifdef SLOW_RINCE_DOWN
+ case '>':
+ AddInterlude(1);
+ continue;
+ case '<':
+ AddInterlude(-1);
+ continue;
+#endif
+
+ case Common::KEYCODE_F1:
+ // Options dialog
+ ProcessKeyEvent(OPTION_KEY);
+ continue;
+ case Common::KEYCODE_F5:
+ // Save game
+ ProcessKeyEvent(SAVE_KEY);
+ continue;
+ case Common::KEYCODE_F7:
+ // Load game
+ ProcessKeyEvent(LOAD_KEY);
+ continue;
+ case Common::KEYCODE_q:
+ if ((evt.kbd.flags == Common::KBD_CTRL) || (evt.kbd.flags == Common::KBD_ALT))
+ ProcessKeyEvent(QUIT_KEY);
+ continue;
+ case Common::KEYCODE_PAGEUP:
+ case Common::KEYCODE_KP9:
+ ProcessKeyEvent(PGUP_KEY);
+ continue;
+ case Common::KEYCODE_PAGEDOWN:
+ case Common::KEYCODE_KP3:
+ ProcessKeyEvent(PGDN_KEY);
+ continue;
+ case Common::KEYCODE_HOME:
+ case Common::KEYCODE_KP7:
+ ProcessKeyEvent(HOME_KEY);
+ continue;
+ case Common::KEYCODE_END:
+ case Common::KEYCODE_KP1:
+ ProcessKeyEvent(END_KEY);
+ continue;
+ default:
+ ProcessKeyEvent(NOEVENT_KEY);
+ break;
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Process to handle changes in the mouse buttons.
+ */
+void MouseProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ bool lastLWasDouble;
+ bool lastRWasDouble;
+ uint32 lastLeftClick, lastRightClick;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ _ctx->lastLWasDouble = false;
+ _ctx->lastRWasDouble = false;
+ _ctx->lastLeftClick = _ctx->lastRightClick = DwGetCurrentTime();
+
+ while (true) {
+ // FIXME: I'm still keeping the ctrl/Alt handling in the ProcessKeyEvent method.
+ // Need to make sure that this works correctly
+ //DragKeys();
+
+ if (mouseButtons.empty()) {
+ // allow scheduling
+ CORO_SLEEP(1);
+ continue;
+ }
+
+ // get next mouse button event
+ Common::EventType type = *mouseButtons.begin();
+ mouseButtons.erase(mouseButtons.begin());
+
+ switch (type) {
+ case Common::EVENT_LBUTTONDOWN:
+ // left button press
+ if (DwGetCurrentTime() - _ctx->lastLeftClick < (uint32)dclickSpeed) {
+ // signal left drag start
+ ProcessButEvent(BE_LDSTART);
+
+ // signal left double click event
+ ProcessButEvent(BE_DLEFT);
+
+ _ctx->lastLWasDouble = true;
+ } else {
+ // signal left drag start
+ ProcessButEvent(BE_LDSTART);
+
+ // signal left single click event
+ ProcessButEvent(BE_SLEFT);
+
+ _ctx->lastLWasDouble = false;
+ }
+ break;
+
+ case Common::EVENT_LBUTTONUP:
+ // left button release
+
+ // update click timer
+ if (_ctx->lastLWasDouble == false)
+ _ctx->lastLeftClick = DwGetCurrentTime();
+ else
+ _ctx->lastLeftClick -= dclickSpeed;
+
+ // signal left drag end
+ ProcessButEvent(BE_LDEND);
+ break;
+
+ case Common::EVENT_RBUTTONDOWN:
+ // right button press
+
+ if (DwGetCurrentTime() - _ctx->lastRightClick < (uint32)dclickSpeed) {
+ // signal right drag start
+ ProcessButEvent(BE_RDSTART);
+
+ // signal right double click event
+ ProcessButEvent(BE_DRIGHT);
+
+ _ctx->lastRWasDouble = true;
+ } else {
+ // signal right drag start
+ ProcessButEvent(BE_RDSTART);
+
+ // signal right single click event
+ ProcessButEvent(BE_SRIGHT);
+
+ _ctx->lastRWasDouble = false;
+ }
+ break;
+
+ case Common::EVENT_RBUTTONUP:
+ // right button release
+
+ // update click timer
+ if (_ctx->lastRWasDouble == false)
+ _ctx->lastRightClick = DwGetCurrentTime();
+ else
+ _ctx->lastRightClick -= dclickSpeed;
+
+ // signal right drag end
+ ProcessButEvent(BE_RDEND);
+ break;
+
+ default:
+ break;
+ }
+ }
+ CORO_END_CODE;
+}
+
+/**
+ * Run the master script.
+ * Continues between scenes, or until Interpret() returns.
+ */
+static void MasterScriptProcess(CORO_PARAM, const void *) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+ _ctx->pic = InitInterpretContext(GS_MASTER, 0, NOEVENT, NOPOLY, 0, NULL);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+ CORO_END_CODE;
+}
+
+/**
+ * Store the facts pertaining to a scene change.
+ */
+
+void SetNewScene(SCNHANDLE scene, int entrance, int transition) {
+ if (HookScene.scene == 0 || bHookSuspend) {
+ // This scene comes next
+ NextScene.scene = scene;
+ NextScene.entry = entrance;
+ NextScene.trans = transition;
+ } else {
+ // This scene gets delayed
+ DelayedScene.scene = scene;
+ DelayedScene.entry = entrance;
+ DelayedScene.trans = transition;
+
+ // The hooked scene comes next
+ NextScene.scene = HookScene.scene;
+ NextScene.entry = HookScene.entry;
+ NextScene.trans = HookScene.trans;
+
+ HookScene.scene = 0;
+ }
+}
+
+void SetHookScene(SCNHANDLE scene, int entrance, int transition) {
+ assert(HookScene.scene == 0); // scene already hooked
+
+ HookScene.scene = scene;
+ HookScene.entry = entrance;
+ HookScene.trans = transition;
+}
+
+void UnHookScene(void) {
+ assert(DelayedScene.scene != 0); // no scene delayed
+
+ // The delayed scene can go now
+ NextScene.scene = DelayedScene.scene;
+ NextScene.entry = DelayedScene.entry;
+ NextScene.trans = DelayedScene.trans;
+
+ DelayedScene.scene = 0;
+}
+
+void SuspendHook(void) {
+ bHookSuspend = true;
+}
+
+void UnSuspendHook(void) {
+ bHookSuspend = false;
+}
+
+void syncSCdata(Serializer &s) {
+ s.syncAsUint32LE(HookScene.scene);
+ s.syncAsSint32LE(HookScene.entry);
+ s.syncAsSint32LE(HookScene.trans);
+
+ s.syncAsUint32LE(DelayedScene.scene);
+ s.syncAsSint32LE(DelayedScene.entry);
+ s.syncAsSint32LE(DelayedScene.trans);
+}
+
+
+//-----------------------------------------------------------------------
+
+static void RestoredProcess(CORO_PARAM, const void *param) {
+ // COROUTINE
+ CORO_BEGIN_CONTEXT;
+ INT_CONTEXT *pic;
+ CORO_END_CONTEXT(_ctx);
+
+ CORO_BEGIN_CODE(_ctx);
+
+ // get the stuff copied to process when it was created
+ _ctx->pic = *((INT_CONTEXT **)param);
+
+ _ctx->pic = RestoreInterpretContext(_ctx->pic);
+ CORO_INVOKE_1(Interpret, _ctx->pic);
+
+ CORO_END_CODE;
+}
+
+void RestoreProcess(INT_CONTEXT *pic) {
+ g_scheduler->createProcess(PID_TCODE, RestoredProcess, &pic, sizeof(pic));
+}
+
+void RestoreMasterProcess(INT_CONTEXT *pic) {
+ g_scheduler->createProcess(PID_MASTER_SCR, RestoredProcess, &pic, sizeof(pic));
+}
+
+// FIXME: CountOut is used by ChangeScene
+static int CountOut = 1; // == 1 for immediate start of first scene
+
+/**
+ * If a scene restore is going on, just return (we don't update the
+ * screen during this time).
+ * If a scene change is required, 'order' the data for the new scene and
+ * start a fade out and a countdown.
+ * When the count expires, the screen will have faded. Ensure the scene |
+ * is loaded, clear the screen, and start the new scene.
+ */
+void ChangeScene() {
+
+ if (IsRestoringScene())
+ return;
+
+ if (NextScene.scene != 0) {
+ if (!CountOut) {
+ switch (NextScene.trans) {
+ case TRANS_CUT:
+ CountOut = 1;
+ break;
+
+ case TRANS_FADE:
+ default:
+ // Trigger pre-load and fade and start countdown
+ CountOut = COUNTOUT_COUNT;
+ FadeOutFast(NULL);
+ break;
+ }
+ } else if (--CountOut == 0) {
+ ClearScreen();
+
+ NewScene(NextScene.scene, NextScene.entry);
+ NextScene.scene = 0;
+
+ switch (NextScene.trans) {
+ case TRANS_CUT:
+ SetDoFadeIn(false);
+ break;
+
+ case TRANS_FADE:
+ default:
+ SetDoFadeIn(true);
+ break;
+ }
+ }
+ }
+}
+
+/**
+ * LoadBasicChunks
+ */
+
+void LoadBasicChunks(void) {
+ byte *cptr;
+ int numObjects;
+
+ // Allocate RAM for savescene data
+ InitialiseSs();
+
+ // CHUNK_TOTAL_ACTORS seems to be missing in the released version, hard coding a value
+ // TODO: Would be nice to just change 511 to MAX_SAVED_ALIVES
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_ACTORS);
+ RegisterActors((cptr != NULL) ? READ_LE_UINT32(cptr) : 511);
+
+ // CHUNK_TOTAL_GLOBALS seems to be missing in some versions.
+ // So if it is missing, set a reasonably high value for the number of globals.
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_GLOBALS);
+ RegisterGlobals((cptr != NULL) ? READ_LE_UINT32(cptr) : 512);
+
+ cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_TOTAL_OBJECTS);
+ numObjects = (cptr != NULL) ? READ_LE_UINT32(cptr) : 0;
+
+ cptr = FindChunk(INV_OBJ_SCNHANDLE, CHUNK_OBJECTS);
+
+#ifdef SCUMM_BIG_ENDIAN
+ //convert to native endianness
+ INV_OBJECT *io = (INV_OBJECT *)cptr;
+ for (int i = 0; i < numObjects; i++, io++) {
+ io->id = FROM_LE_32(io->id);
+ io->hFilm = FROM_LE_32(io->hFilm);
+ io->hScript = FROM_LE_32(io->hScript);
+ io->attribute = FROM_LE_32(io->attribute);
+ }
+#endif
+
+ RegisterIcons(cptr, numObjects);
+
+ cptr = FindChunk(MASTER_SCNHANDLE, CHUNK_TOTAL_POLY);
+ if (cptr != NULL)
+ MaxPolygons(*cptr);
+}
+
+//----------------- TinselEngine --------------------
+
+// Global pointer to engine
+TinselEngine *_vm;
+
+struct GameSettings {
+ const char *gameid;
+ const char *description;
+ byte id;
+ uint32 features;
+ const char *detectname;
+};
+
+static const GameSettings tinselSettings[] = {
+ {"tinsel", "Tinsel game", 0, 0, 0},
+
+ {NULL, NULL, 0, 0, NULL}
+};
+
+TinselEngine::TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc) :
+ Engine(syst), _gameDescription(gameDesc) {
+ _vm = this;
+
+ // Setup mixer
+ _mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
+ _mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
+
+ const GameSettings *g;
+
+ const char *gameid = ConfMan.get("gameid").c_str();
+ for (g = tinselSettings; g->gameid; ++g)
+ if (!scumm_stricmp(g->gameid, gameid))
+ _gameId = g->id;
+
+ int cd_num = ConfMan.getInt("cdrom");
+ if (cd_num >= 0)
+ _system->openCD(cd_num);
+
+ int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
+ bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
+ //bool adlib = (midiDriver == MD_ADLIB);
+
+ MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+ if (native_mt32)
+ driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+
+ _music = new MusicPlayer(driver);
+ //_music->setNativeMT32(native_mt32);
+ //_music->setAdlib(adlib);
+
+ _musicVolume = ConfMan.getInt("music_volume");
+
+ _sound = new SoundManager(this);
+
+ _mousePos.x = 0;
+ _mousePos.y = 0;
+ _keyHandler = NULL;
+ _dosPlayerDir = 0;
+ quitFlag = false;
+}
+
+TinselEngine::~TinselEngine() {
+ delete _sound;
+ delete _music;
+ delete _console;
+ FreeSs();
+ FreeTextBuffer();
+ FreeHandleTable();
+ FreeActors();
+ FreeObjectList();
+ FreeGlobals();
+ delete _scheduler;
+}
+
+int TinselEngine::init() {
+ // Initialize backend
+ _system->beginGFXTransaction();
+ initCommonGFX(false);
+ _system->initSize(SCREEN_WIDTH, SCREEN_HEIGHT);
+ _system->endGFXTransaction();
+
+ _screenSurface.create(SCREEN_WIDTH, SCREEN_HEIGHT, 1);
+
+ g_system->getEventManager()->registerRandomSource(_random, "tinsel");
+
+ _console = new Console();
+
+ _scheduler = new Scheduler();
+
+ // init memory manager
+ MemoryInit();
+
+ // load user configuration
+ ReadConfig();
+
+#if 1
+ // FIXME: The following is taken from RestartGame().
+ // It may have to be adjusted a bit
+ RebootCursor();
+ RebootDeadTags();
+ RebootMovers();
+ RebootTimers();
+ RebootScalingReels();
+
+ DelayedScene.scene = HookScene.scene = 0;
+#endif
+
+ // Init palette and object managers, scheduler, keyboard and mouse
+ RestartDrivers();
+
+ // TODO: More stuff from dos_main.c may have to be added here
+
+ // Set language - we'll be clever here and use the ScummVM language setting
+ language = TXT_ENGLISH;
+ switch (getLanguage()) {
+ case Common::FR_FRA:
+ language = TXT_FRENCH;
+ break;
+ case Common::DE_DEU:
+ language = TXT_GERMAN;
+ break;
+ case Common::IT_ITA:
+ language = TXT_ITALIAN;
+ break;
+ case Common::ES_ESP:
+ language = TXT_SPANISH;
+ break;
+ default:
+ language = TXT_ENGLISH;
+ }
+ ChangeLanguage(language);
+
+ // load in graphics info
+ SetupHandleTable();
+
+ // Actors, globals and inventory icons
+ LoadBasicChunks();
+
+ return 0;
+}
+
+Common::String TinselEngine::getSavegamePattern() const {
+ return _targetName + ".???";
+}
+
+Common::String TinselEngine::getSavegameFilename(int16 saveNum) const {
+ char filename[256];
+ snprintf(filename, 256, "%s.%03d", getTargetName().c_str(), saveNum);
+ return filename;
+}
+
+#define GAME_FRAME_DELAY (1000 / ONE_SECOND)
+
+int TinselEngine::go() {
+ uint32 timerVal = 0;
+
+ // Continuous game processes
+ CreateConstProcesses();
+
+ // allow game to run in the background
+ //RestartBackgroundProcess(); // FIXME: is this still needed?
+
+ //dumpMusic(); // dumps all of the game's music in external XMIDI files
+
+#if 0
+ // Load game from specified slot, if any
+ // FIXME: Not working correctly right now
+ if (ConfMan.hasKey("save_slot")) {
+ getList();
+ RestoreGame(ConfMan.getInt("save_slot"));
+ }
+#endif
+
+ // Foreground loop
+
+ while (!quitFlag) {
+ assert(_console);
+ if (_console->isAttached())
+ _console->onFrame();
+
+ // Check for time to do next game cycle
+ if ((g_system->getMillis() > timerVal + GAME_FRAME_DELAY)) {
+ timerVal = g_system->getMillis();
+ AudioCD.updateCD();
+ NextGameCycle();
+ }
+
+ if (bRestart) {
+ RestartGame();
+ bRestart = false;
+ bHasRestarted = true; // Set restarted flag
+ }
+
+ // Save/Restore scene file transfers
+ ProcessSRQueue();
+
+#ifdef DEBUG
+ if (bFast)
+ continue; // run flat-out
+#endif
+ // Loop processing events while there are any pending
+ while (pollEvent());
+
+ g_system->delayMillis(10);
+ }
+
+ // Write configuration
+ WriteConfig();
+
+ return 0;
+}
+
+
+void TinselEngine::NextGameCycle(void) {
+ //
+ ChangeScene();
+
+ // Allow a user event for this schedule
+ ResetEcount();
+
+ // schedule process
+ _scheduler->schedule();
+
+ // redraw background
+ DrawBackgnd();
+
+ // Why waste resources on yet another process?
+ FettleTimers();
+}
+
+
+bool TinselEngine::pollEvent() {
+ Common::Event event;
+
+ if (!g_system->getEventManager()->pollEvent(event))
+ return false;
+
+ // Handle the various kind of events
+ switch (event.type) {
+ case Common::EVENT_QUIT:
+ quitFlag = true;
+ break;
+
+ case Common::EVENT_LBUTTONDOWN:
+ case Common::EVENT_LBUTTONUP:
+ case Common::EVENT_RBUTTONDOWN:
+ case Common::EVENT_RBUTTONUP:
+ // Add button to queue for the mouse process
+ mouseButtons.push_back(event.type);
+ break;
+
+ case Common::EVENT_MOUSEMOVE:
+ _mousePos = event.mouse;
+ break;
+
+ case Common::EVENT_KEYDOWN:
+ case Common::EVENT_KEYUP:
+ ProcessKeyEvent(event);
+ break;
+
+ default:
+ break;
+ }
+
+ return true;
+}
+
+/**
+ * Start the processes that continue between scenes.
+ */
+
+void TinselEngine::CreateConstProcesses(void) {
+ // Process to run the master script
+ _scheduler->createProcess(PID_MASTER_SCR, MasterScriptProcess, NULL, 0);
+
+ // Processes to run the cursor and inventory,
+ _scheduler->createProcess(PID_CURSOR, CursorProcess, NULL, 0);
+ _scheduler->createProcess(PID_INVENTORY, InventoryProcess, NULL, 0);
+}
+
+/**
+ * Restart the game
+ */
+
+void TinselEngine::RestartGame(void) {
+ HoldItem(INV_NOICON); // Holding nothing
+
+ DropBackground(); // No background
+
+ // Ditches existing infrastructure background
+ PrimeBackground();
+
+ // Next scene change won't need to fade out
+ // -> reset the count used by ChangeScene
+ CountOut = 1;
+
+ RebootCursor();
+ RebootDeadTags();
+ RebootMovers();
+ RebootTimers();
+ RebootScalingReels();
+
+ DelayedScene.scene = HookScene.scene = 0;
+
+ // remove keyboard, mouse and joystick drivers
+ ChopDrivers();
+
+ // Init palette and object managers, scheduler, keyboard and mouse
+ RestartDrivers();
+
+ // Actors, globals and inventory icons
+ LoadBasicChunks();
+
+ // Continuous game processes
+ CreateConstProcesses();
+}
+
+/**
+ * Init palette and object managers, scheduler, keyboard and mouse.
+ */
+
+void TinselEngine::RestartDrivers(void) {
+ // init the palette manager
+ ResetPalAllocator();
+
+ // init the object manager
+ KillAllObjects();
+
+ // init the process scheduler
+ _scheduler->reset();
+
+ // init the event handlers
+ pMouseProcess = _scheduler->createProcess(PID_MOUSE, MouseProcess, NULL, 0);
+ pKeyboardProcess = _scheduler->createProcess(PID_KEYBOARD, KeyboardProcess, NULL, 0);
+
+ // open MIDI files
+ OpenMidiFiles();
+
+ // open sample files (only if mixer is ready)
+ if (_mixer->isReady()) {
+ _sound->openSampleFiles();
+ }
+
+ // Set midi volume
+ SetMidiVolume(volMidi);
+}
+
+/**
+ * Remove keyboard, mouse and joystick drivers.
+ */
+
+void TinselEngine::ChopDrivers(void) {
+ // remove sound driver
+ StopMidi();
+ _sound->stopAllSamples();
+ DeleteMidiBuffer();
+
+ // remove event drivers
+ _scheduler->killProcess(pMouseProcess);
+ _scheduler->killProcess(pKeyboardProcess);
+}
+
+/**
+ * Process a keyboard event
+ */
+
+void TinselEngine::ProcessKeyEvent(const Common::Event &event) {
+
+ // Handle any special keys immediately
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_d:
+ if ((event.kbd.flags == Common::KBD_CTRL) && (event.type == Common::EVENT_KEYDOWN)) {
+ // Activate the debugger
+ assert(_console);
+ _console->attach();
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // Check for movement keys
+ int idx = 0;
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ idx = MSK_UP;
+ break;
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ idx = MSK_DOWN;
+ break;
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ idx = MSK_LEFT;
+ break;
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ idx = MSK_RIGHT;
+ break;
+ default:
+ break;
+ }
+ if (idx != 0) {
+ if (event.type == Common::EVENT_KEYDOWN)
+ _dosPlayerDir |= idx;
+ else
+ _dosPlayerDir &= ~idx;
+ return;
+ }
+
+ // All other keypresses add to the queue for processing in KeyboardProcess
+ keypresses.push_back(event);
+}
+
+} // End of namespace Tinsel
diff --git a/engines/tinsel/tinsel.h b/engines/tinsel/tinsel.h
new file mode 100644
index 0000000000..44cc83af9b
--- /dev/null
+++ b/engines/tinsel/tinsel.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_H
+#define TINSEL_H
+
+#include "common/scummsys.h"
+#include "common/system.h"
+#include "common/events.h"
+#include "common/keyboard.h"
+#include "common/util.h"
+
+#include "sound/mididrv.h"
+#include "sound/mixer.h"
+
+#include "engines/engine.h"
+#include "tinsel/debugger.h"
+#include "tinsel/graphics.h"
+#include "tinsel/sound.h"
+
+namespace Tinsel {
+
+class MusicPlayer;
+class Scheduler;
+class SoundManager;
+
+enum TinselGameID {
+ GID_DW1 = 0,
+ GID_DW2 = 1
+};
+
+enum TinselGameFeatures {
+ GF_DEMO = 1 << 0,
+ GF_CD = 1 << 1,
+ GF_FLOPPY = 1 << 2,
+ GF_SCNFILES = 1 << 3
+};
+
+enum TinselEngineVersion {
+ TINSEL_V0 = 1 << 0, // Used in the DW1 demo only
+ TINSEL_V1 = 1 << 1
+};
+
+struct TinselGameDescription;
+
+enum TinselKeyDirection {
+ MSK_LEFT = 1, MSK_RIGHT = 2, MSK_UP = 4, MSK_DOWN = 8,
+ MSK_DIRECTION = MSK_LEFT | MSK_RIGHT | MSK_UP | MSK_DOWN
+};
+
+typedef bool (*KEYFPTR)(const Common::KeyState &);
+
+class TinselEngine : public ::Engine {
+ int _gameId;
+ Common::KeyState _keyPressed;
+ Common::RandomSource _random;
+ Graphics::Surface _screenSurface;
+ Common::Point _mousePos;
+ uint8 _dosPlayerDir;
+ Console *_console;
+ Scheduler *_scheduler;
+
+protected:
+
+ int init();
+ int go();
+
+public:
+ TinselEngine(OSystem *syst, const TinselGameDescription *gameDesc);
+ virtual ~TinselEngine();
+ int getGameId() {
+ return _gameId;
+ }
+
+ const TinselGameDescription *_gameDescription;
+ uint32 getGameID() const;
+ uint32 getFeatures() const;
+ Common::Language getLanguage() const;
+ uint16 getVersion() const;
+ Common::Platform getPlatform() const;
+ bool quitFlag;
+
+ SoundManager *_sound;
+ MusicPlayer *_music;
+
+ KEYFPTR _keyHandler;
+private:
+ //MusicPlayer *_music;
+ int _musicVolume;
+
+ void NextGameCycle(void);
+ void CreateConstProcesses(void);
+ void RestartGame(void);
+ void RestartDrivers(void);
+ void ChopDrivers(void);
+ void ProcessKeyEvent(const Common::Event &event);
+ bool pollEvent();
+
+public:
+ const Common::String getTargetName() const { return _targetName; }
+ Common::String getSavegamePattern() const;
+ Common::String getSavegameFilename(int16 saveNum) const;
+ Common::SaveFileManager *getSaveFileMan() { return _saveFileMan; }
+ Graphics::Surface &screen() { return _screenSurface; }
+
+ Common::Point getMousePosition() const { return _mousePos; }
+ void setMousePosition(const Common::Point &pt) {
+ g_system->warpMouse(pt.x, pt.y);
+ _mousePos = pt;
+ }
+ void divertKeyInput(KEYFPTR fptr) { _keyHandler = fptr; }
+ int getRandomNumber(int maxNumber) { return _random.getRandomNumber(maxNumber); }
+ uint8 getKeyDirection() const { return _dosPlayerDir; }
+};
+
+// Global reference to the TinselEngine object
+extern TinselEngine *_vm;
+
+} // End of namespace Tinsel
+
+#endif /* TINSEL_H */
diff --git a/engines/tinsel/token.cpp b/engines/tinsel/token.cpp
new file mode 100644
index 0000000000..0bdac0d6eb
--- /dev/null
+++ b/engines/tinsel/token.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ * To ensure exclusive use of resources and exclusive control responsibilities.
+ */
+
+#include "common/util.h"
+
+#include "tinsel/sched.h"
+#include "tinsel/token.h"
+
+namespace Tinsel {
+
+//----------------- LOCAL GLOBAL DATA --------------------
+
+struct Token {
+ PROCESS *proc;
+};
+
+static Token tokens[NUMTOKENS];
+
+
+/**
+ * Release all tokens held by this process, and kill the process.
+ */
+static void TerminateProcess(PROCESS *tProc) {
+
+ // Release tokens held by the process
+ for (int i = 0; i < NUMTOKENS; i++) {
+ if (tokens[i].proc == tProc) {
+ tokens[i].proc = NULL;
+ }
+ }
+
+ // Kill the process
+ g_scheduler->killProcess(tProc);
+}
+
+/**
+ * Gain control of the CONTROL token if it is free.
+ */
+void GetControlToken() {
+ const int which = TOKEN_CONTROL;
+
+ if (tokens[which].proc == NULL) {
+ tokens[which].proc = g_scheduler->getCurrentProcess();
+ }
+}
+
+/**
+ * Release control of the CONTROL token.
+ */
+void FreeControlToken() {
+ // Allow anyone to free TOKEN_CONTROL
+ tokens[TOKEN_CONTROL].proc = NULL;
+}
+
+
+/**
+ * Gain control of a token. If the requested token is out of range, or
+ * is already held by the calling process, then the calling process
+ * will be killed off.
+ *
+ * Otherwise, the calling process will gain the token. If the token was
+ * held by another process, then the previous holder is killed off.
+ */
+void GetToken(int which) {
+ assert(TOKEN_LEAD <= which && which < NUMTOKENS);
+
+ if (tokens[which].proc != NULL) {
+ assert(tokens[which].proc != g_scheduler->getCurrentProcess());
+ TerminateProcess(tokens[which].proc);
+ }
+
+ tokens[which].proc = g_scheduler->getCurrentProcess();
+}
+
+/**
+ * Release control of a token. If the requested token is not owned by
+ * the calling process, then the calling process will be killed off.
+ */
+void FreeToken(int which) {
+ assert(TOKEN_LEAD <= which && which < NUMTOKENS);
+
+ assert(tokens[which].proc == g_scheduler->getCurrentProcess()); // we'd have been killed if some other proc had taken this token
+
+ tokens[which].proc = NULL;
+}
+
+/**
+ * If it's a valid token and it's free, returns true.
+ */
+bool TestToken(int which) {
+ if (which < 0 || which >= NUMTOKENS)
+ return false;
+
+ return (tokens[which].proc == NULL);
+}
+
+/**
+ * Call at the start of each scene.
+ */
+void FreeAllTokens(void) {
+ for (int i = 0; i < NUMTOKENS; i++) {
+ tokens[i].proc = NULL;
+ }
+}
+
+} // end of namespace Tinsel
diff --git a/engines/tinsel/token.h b/engines/tinsel/token.h
new file mode 100644
index 0000000000..4ab4775bfb
--- /dev/null
+++ b/engines/tinsel/token.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.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+#ifndef TINSEL_TOKEN_H
+#define TINSEL_TOKEN_H
+
+#include "tinsel/dw.h"
+
+namespace Tinsel {
+
+// Fixed tokens
+
+enum {
+ TOKEN_CONTROL = 0,
+ TOKEN_LEAD, // = TOKEN_CONTROL + 1
+ TOKEN_LEFT_BUT = TOKEN_LEAD + MAX_MOVERS,
+
+ NUMTOKENS // = TOKEN_LEFT_BUT + 1
+};
+
+// Token functions
+
+void GetControlToken();
+void FreeControlToken();
+
+void GetToken(int which);
+void FreeToken(int which);
+
+void FreeAllTokens(void);
+bool TestToken(int which);
+
+
+} // end of namespace Tinsel
+
+#endif // TINSEL_TOKEN_H
diff --git a/graphics/font.cpp b/graphics/font.cpp
index b5817e613a..0b0405b4b4 100644
--- a/graphics/font.cpp
+++ b/graphics/font.cpp
@@ -585,8 +585,8 @@ NewFont *NewFont::loadFont(Common::SeekableReadStream &stream) {
}
bool NewFont::cacheFontData(const NewFont &font, const Common::String &filename) {
- Common::File cacheFile;
- if (!cacheFile.open(filename, Common::File::kFileWriteMode)) {
+ Common::DumpFile cacheFile;
+ if (!cacheFile.open(filename)) {
warning("Couldn't open file '%s' for writing", filename.c_str());
return false;
}
diff --git a/gui/credits.h b/gui/credits.h
index 41544a2248..ca2fda811d 100644
--- a/gui/credits.h
+++ b/gui/credits.h
@@ -7,6 +7,12 @@ static const char *credits[] = {
"\\C\\c0""Max Horn",
"\\C\\c0""Eugene Sandulenko",
"\\C\\c0""",
+"\\C\\c1""Retired Project Leaders",
+"\\C\\c0""Vincent Hamm",
+"\\C\\c2""ScummVM co-founder, Original Cruise/CinE author",
+"\\C\\c0""Ludvig Strigeus",
+"\\C\\c2""Original ScummVM and SimonVM author",
+"\\C\\c0""",
"\\C\\c1""Engine Teams",
"\\C\\c1""SCUMM",
"\\C\\c0""Torbj\366rn Andersson",
@@ -98,10 +104,19 @@ static const char *credits[] = {
"\\C\\c0""",
"\\C\\c1""SAGA",
"\\C\\c0""Torbj\366rn Andersson",
+"\\C\\c0""Sven Hesse",
"\\C\\c0""Filippos Karapetis",
"\\C\\c0""Andrew Kurushin",
"\\C\\c0""Eugene Sandulenko",
"\\C\\c0""",
+"\\C\\c1""Tinsel;",
+"\\C\\c0""Torbj\366rn Andersson",
+"\\C\\c0""Paul Gilbert",
+"\\C\\c0""Sven Hesse",
+"\\C\\c0""Max Horn",
+"\\C\\c0""Filippos Karapetis",
+"\\C\\c0""Joost Peters",
+"\\C\\c0""",
"\\C\\c1""Touch\351",
"\\C\\c0""Gregory Montoir",
"\\C\\c0""",
@@ -190,8 +205,6 @@ static const char *credits[] = {
"\\C\\c2""iMUSE, MIDI, all things musical",
"\\C\\c0""R\374diger Hanke",
"\\C\\c2""Port: MorphOS",
-"\\C\\c0""Vincent Hamm",
-"\\C\\c2""ScummVM co-founder, Original Cruise/CinE author",
"\\C\\c0""Felix Jakschitsch",
"\\C\\c2""Zak256 reverse engineering",
"\\C\\c0""Mutwin Kraus",
@@ -200,8 +213,6 @@ static const char *credits[] = {
"\\C\\c2""Port: GP32",
"\\C\\c0""Jeremy Newman",
"\\C\\c2""Former webmaster",
-"\\C\\c0""Ludvig Strigeus",
-"\\C\\c2""Original ScummVM and SimonVM author",
"\\C\\c0""Lionel Ulmer",
"\\C\\c2""Port: X11",
"\\C\\c0""Won Star",
diff --git a/gui/launcher.cpp b/gui/launcher.cpp
index cf95e09101..2953988ed3 100644
--- a/gui/launcher.cpp
+++ b/gui/launcher.cpp
@@ -452,7 +452,10 @@ void EditGameDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 dat
// Write back changes made to config object
String newDomain(_domainWidget->getEditString());
if (newDomain != _domain) {
- if (newDomain.empty() || ConfMan.hasGameDomain(newDomain)) {
+ 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;
@@ -837,12 +840,7 @@ Common::String addGameToConf(const GameDescriptor &result) {
// The auto detector or the user made a choice.
// Pick a domain name which does not yet exist (after all, we
// are *adding* a game to the config, not replacing).
- String domain;
-
- if (result.contains("preferredtarget"))
- domain = result["preferredtarget"];
- else
- domain = result.gameid();
+ String domain = result.preferredtarget();
assert(!domain.empty());
if (ConfMan.hasGameDomain(domain)) {
diff --git a/gui/massadd.cpp b/gui/massadd.cpp
index 687d367516..6842466ad9 100644
--- a/gui/massadd.cpp
+++ b/gui/massadd.cpp
@@ -23,7 +23,9 @@
*/
#include "engines/metaengine.h"
+#include "common/algorithm.h"
#include "common/events.h"
+#include "common/func.h"
#include "common/config-manager.h"
#include "gui/launcher.h" // For addGameToConf()
@@ -113,10 +115,19 @@ MassAddDialog::MassAddDialog(const FilesystemNode &startDir)
}
}
+struct GameDescLess {
+ bool operator()(const GameDescriptor &x, const GameDescriptor &y) const {
+ return x.preferredtarget().compareToIgnoreCase(y.preferredtarget()) < 0;
+ }
+};
+
void MassAddDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) {
// FIXME: It's a really bad thing that we use two arbitrary constants
if (cmd == kOkCmd) {
+ // Sort the detected games. This is not strictly necessary, but nice for
+ // people who want to edit their config file by hand after a mass add.
+ sort(_games.begin(), _games.end(), GameDescLess());
// Add all the detected games to the config
for (GameList::const_iterator iter = _games.begin(); iter != _games.end(); ++iter) {
printf(" Added gameid '%s', desc '%s'\n",
diff --git a/ports.mk b/ports.mk
index 3d79384e19..80efcfacc4 100644
--- a/ports.mk
+++ b/ports.mk
@@ -118,7 +118,7 @@ iphone: $(OBJS)
$(CXX) $(LDFLAGS) -o scummvm $(OBJS) \
$(OSX_STATIC_LIBS) \
-framework UIKit -framework CoreGraphics -framework CoreSurface \
- -framework LayerKit -framework GraphicsServices -framework CoreFoundation \
+ -framework GraphicsServices -framework CoreFoundation -framework QuartzCore \
-framework Foundation -framework AudioToolbox -framework CoreAudio \
-lobjc -lz
diff --git a/sound/midiparser_xmidi.cpp b/sound/midiparser_xmidi.cpp
index 7cf114dcc6..abc1e20076 100644
--- a/sound/midiparser_xmidi.cpp
+++ b/sound/midiparser_xmidi.cpp
@@ -133,6 +133,9 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) {
}
}
}
+ } else if (info.basic.param1 >= 0x6e && info.basic.param1 <= 0x78) {
+ warning("Unsupported XMIDI controller %d (0x%2x)",
+ info.basic.param1, info.basic.param1);
}
break;
diff --git a/sound/softsynth/mt32.cpp b/sound/softsynth/mt32.cpp
index 053df544b1..360ef4539d 100644
--- a/sound/softsynth/mt32.cpp
+++ b/sound/softsynth/mt32.cpp
@@ -80,37 +80,41 @@ public:
};
class MT32File : public MT32Emu::File {
- Common::File file;
+ Common::File _in;
+ Common::DumpFile _out;
public:
bool open(const char *filename, OpenMode mode) {
- Common::File::AccessMode accessMode = mode == OpenMode_read ? Common::File::kFileReadMode : Common::File::kFileWriteMode;
- return file.open(filename, accessMode);
+ if (mode == OpenMode_read)
+ return _in.open(filename);
+ else
+ return _out.open(filename);
}
void close() {
- return file.close();
+ _in.close();
+ _out.close();
}
size_t read(void *in, size_t size) {
- return file.read(in, size);
+ return _in.read(in, size);
}
bool readLine(char *in, size_t size) {
- return file.readLine(in, size) != NULL;
+ return _in.readLine(in, size) != NULL;
}
bool readBit8u(MT32Emu::Bit8u *in) {
- byte b = file.readByte();
- if (file.eof())
+ byte b = _in.readByte();
+ if (_in.eof())
return false;
*in = b;
return true;
}
size_t write(const void *in, size_t size) {
- return file.write(in, size);
+ return _out.write(in, size);
}
bool writeBit8u(MT32Emu::Bit8u out) {
- file.writeByte(out);
- return !file.ioFailed();
+ _out.writeByte(out);
+ return !_out.ioFailed();
}
bool isEOF() {
- return file.eof();
+ return _in.isOpen() ? _in.eof() : _out.eof();
}
};
diff --git a/test/common/bufferedreadstream.h b/test/common/bufferedreadstream.h
new file mode 100644
index 0000000000..7733949d9a
--- /dev/null
+++ b/test/common/bufferedreadstream.h
@@ -0,0 +1,27 @@
+#include <cxxtest/TestSuite.h>
+
+#include "common/stream.h"
+
+class BufferedReadStreamTestSuite : public CxxTest::TestSuite {
+ public:
+ void test_traverse(void) {
+ byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ Common::MemoryReadStream ms(contents, 10);
+
+ // Use a buffer size of 4 -- note that 10 % 4 != 0,
+ // so we test what happens if the cache can't be completly
+ // refilled.
+ Common::BufferedReadStream srs(&ms, 4);
+
+ int i;
+ byte b;
+ for (i = 0; i < 10; ++i) {
+ TS_ASSERT( !srs.eos() );
+
+ b = srs.readByte();
+ TS_ASSERT_EQUALS( i, b );
+ }
+
+ TS_ASSERT( srs.eos() );
+ }
+};
diff --git a/test/common/bufferedseekablereadstream.h b/test/common/bufferedseekablereadstream.h
new file mode 100644
index 0000000000..63941904cd
--- /dev/null
+++ b/test/common/bufferedseekablereadstream.h
@@ -0,0 +1,65 @@
+#include <cxxtest/TestSuite.h>
+
+#include "common/stream.h"
+
+class BufferedSeekableReadStreamTestSuite : public CxxTest::TestSuite {
+ public:
+ void test_traverse(void) {
+ byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ Common::MemoryReadStream ms(contents, 10);
+
+ Common::BufferedSeekableReadStream ssrs(&ms, 4);
+
+ int i;
+ byte b;
+ for (i = 0; i < 10; ++i) {
+ TS_ASSERT( !ssrs.eos() );
+
+ TS_ASSERT_EQUALS( i, ssrs.pos() );
+
+ ssrs.read(&b, 1);
+ TS_ASSERT_EQUALS( i, b );
+ }
+
+ TS_ASSERT( ssrs.eos() );
+ }
+
+ void test_seek(void) {
+ byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
+ Common::MemoryReadStream ms(contents, 10);
+
+ Common::BufferedSeekableReadStream ssrs(&ms, 4);
+ byte b;
+
+ TS_ASSERT_EQUALS( ssrs.pos(), (uint32)0 );
+
+ ssrs.seek(1, SEEK_SET);
+ TS_ASSERT_EQUALS( ssrs.pos(), (uint32)1 );
+ b = ssrs.readByte();
+ TS_ASSERT_EQUALS( b, 1 );
+
+ ssrs.seek(5, SEEK_CUR);
+ TS_ASSERT_EQUALS( ssrs.pos(), (uint32)7 );
+ b = ssrs.readByte();
+ TS_ASSERT_EQUALS( b, 7 );
+
+ ssrs.seek(-3, SEEK_CUR);
+ TS_ASSERT_EQUALS( ssrs.pos(), (uint32)5 );
+ b = ssrs.readByte();
+ TS_ASSERT_EQUALS( b, 5 );
+
+ ssrs.seek(0, SEEK_END);
+ TS_ASSERT_EQUALS( ssrs.pos(), (uint32)10 );
+ TS_ASSERT( ssrs.eos() );
+
+ ssrs.seek(3, SEEK_END);
+ TS_ASSERT_EQUALS( ssrs.pos(), (uint32)7 );
+ b = ssrs.readByte();
+ TS_ASSERT_EQUALS( b, 7 );
+
+ ssrs.seek(8, SEEK_END);
+ TS_ASSERT_EQUALS( ssrs.pos(), (uint32)2 );
+ b = ssrs.readByte();
+ TS_ASSERT_EQUALS( b, 2 );
+ }
+};
diff --git a/test/common/func.h b/test/common/func.h
new file mode 100644
index 0000000000..bf9b2ddff9
--- /dev/null
+++ b/test/common/func.h
@@ -0,0 +1,60 @@
+#include <cxxtest/TestSuite.h>
+
+#include "common/func.h"
+
+void myFunction1(int &dst, const int src) { dst = src; }
+void myFunction2(const int src, int &dst) { dst = src; }
+
+class FuncTestSuite : public CxxTest::TestSuite
+{
+ public:
+ void test_bind1st() {
+ int dst = 0;
+ Common::bind1st(Common::ptr_fun(myFunction1), dst)(1);
+ TS_ASSERT_EQUALS(dst, 1);
+ }
+
+ void test_bind2nd() {
+ int dst = 0;
+ Common::bind2nd(Common::ptr_fun(myFunction2), dst)(1);
+ TS_ASSERT_EQUALS(dst, 1);
+ }
+
+ struct Foo {
+ void fooAdd(int &foo) {
+ ++foo;
+ }
+
+ void fooSub(int &foo) const {
+ --foo;
+ }
+ };
+
+ void test_mem_fun_ref() {
+ Foo myFoos[4];
+ int counter = 0;
+
+ Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun_ref(&Foo::fooAdd), counter));
+ TS_ASSERT_EQUALS(counter, 4);
+
+ Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun_ref(&Foo::fooSub), counter));
+ TS_ASSERT_EQUALS(counter, 0);
+ }
+
+ void test_mem_fun() {
+ Foo *myFoos[4];
+ for (int i = 0; i < 4; ++i)
+ myFoos[i] = new Foo;
+
+ int counter = 0;
+
+ Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun(&Foo::fooAdd), counter));
+ TS_ASSERT_EQUALS(counter, 4);
+
+ Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun(&Foo::fooSub), counter));
+ TS_ASSERT_EQUALS(counter, 0);
+
+ for (int i = 0; i < 4; ++i)
+ delete myFoos[i];
+ }
+};
diff --git a/test/common/ptr.h b/test/common/ptr.h
index 11ed52d4b9..986330057c 100644
--- a/test/common/ptr.h
+++ b/test/common/ptr.h
@@ -61,6 +61,9 @@ class PtrTestSuite : public CxxTest::TestSuite
TS_ASSERT(p1 != 0);
TS_ASSERT(p2 == 0);
+
+ p1.reset();
+ TS_ASSERT(!p1);
}
struct A {
diff --git a/test/common/seekablesubreadstream.h b/test/common/seekablesubreadstream.h
index c4b21667c7..4e517093a5 100644
--- a/test/common/seekablesubreadstream.h
+++ b/test/common/seekablesubreadstream.h
@@ -2,22 +2,19 @@
#include "common/stream.h"
-class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite
-{
+class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite {
public:
- void test_traverse( void )
- {
+ void test_traverse(void) {
byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
- Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10);
+ Common::MemoryReadStream ms(contents, 10);
int start = 2, end = 8;
- Common::SeekableSubReadStream ssrs = Common::SeekableSubReadStream(&ms, start, end);
+ Common::SeekableSubReadStream ssrs(&ms, start, end);
int i;
byte b;
- for (i = start; i < end; ++i)
- {
+ for (i = start; i < end; ++i) {
TS_ASSERT( !ssrs.eos() );
TS_ASSERT_EQUALS( uint32(i - start), ssrs.pos() );
@@ -29,12 +26,11 @@ class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite
TS_ASSERT( ssrs.eos() );
}
- void test_seek( void )
- {
+ void test_seek(void) {
byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
- Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10);
+ Common::MemoryReadStream ms(contents, 10);
- Common::SeekableSubReadStream ssrs = Common::SeekableSubReadStream(&ms, 1, 9);
+ Common::SeekableSubReadStream ssrs(&ms, 1, 9);
byte b;
TS_ASSERT_EQUALS( ssrs.pos(), (uint32)0 );
diff --git a/test/common/str.h b/test/common/str.h
index 76574ea70f..72d4df6f61 100644
--- a/test/common/str.h
+++ b/test/common/str.h
@@ -7,124 +7,154 @@ class StringTestSuite : public CxxTest::TestSuite
public:
void test_constructors(void) {
Common::String str("test-string");
- TS_ASSERT( str == "test-string" );
+ TS_ASSERT( str == "test-string");
str = Common::String(str.c_str()+5, 3);
- TS_ASSERT( str == "str" );
+ TS_ASSERT( str == "str");
str = "test-string";
- TS_ASSERT( str == "test-string" );
+ TS_ASSERT( str == "test-string");
str = Common::String(str.c_str()+5, str.c_str()+8);
- TS_ASSERT( str == "str" );
+ TS_ASSERT( str == "str");
+ }
+
+ void test_trim(void) {
+ Common::String str(" This is a s tring with spaces ");
+ Common::String str2 = str;
+ str.trim();
+ TS_ASSERT( str == "This is a s tring with spaces");
+ TS_ASSERT( str2 == " This is a s tring with spaces ");
}
void test_empty_clear(void) {
Common::String str("test");
- TS_ASSERT( !str.empty() );
+ TS_ASSERT( !str.empty());
str.clear();
- TS_ASSERT( str.empty() );
+ TS_ASSERT( str.empty());
}
void test_lastChar(void) {
Common::String str;
- TS_ASSERT_EQUALS( str.lastChar(), '\0' );
+ TS_ASSERT_EQUALS(str.lastChar(), '\0');
str = "test";
- TS_ASSERT_EQUALS( str.lastChar(), 't' );
+ TS_ASSERT_EQUALS(str.lastChar(), 't');
Common::String str2("bar");
- TS_ASSERT_EQUALS( str2.lastChar(), 'r' );
+ TS_ASSERT_EQUALS(str2.lastChar(), 'r');
}
void test_concat1(void) {
Common::String str("foo");
Common::String str2("bar");
str += str2;
- TS_ASSERT_EQUALS( str, "foobar" );
- TS_ASSERT_EQUALS( str2, "bar" );
+ TS_ASSERT_EQUALS(str, "foobar");
+ TS_ASSERT_EQUALS(str2, "bar");
}
void test_concat2(void) {
Common::String str("foo");
str += "bar";
- TS_ASSERT_EQUALS( str, "foobar" );
+ TS_ASSERT_EQUALS(str, "foobar");
}
void test_concat3(void) {
Common::String str("foo");
str += 'X';
- TS_ASSERT_EQUALS( str, "fooX" );
+ TS_ASSERT_EQUALS(str, "fooX");
}
void test_refCount(void) {
+ // using internal storage
Common::String foo1("foo");
- Common::String foo2("foo");
+ Common::String foo2(foo1);
Common::String foo3(foo2);
foo3 += 'X';
- TS_ASSERT_EQUALS( foo2, foo1 );
- TS_ASSERT_EQUALS( foo2, "foo" );
- TS_ASSERT_EQUALS( foo3, "foo""X" );
+ TS_ASSERT_EQUALS(foo1, "foo");
+ TS_ASSERT_EQUALS(foo2, "foo");
+ TS_ASSERT_EQUALS(foo3, "foo""X");
+ foo2 = 'x';
+ TS_ASSERT_EQUALS(foo1, "foo");
+ TS_ASSERT_EQUALS(foo2, "x");
+ TS_ASSERT_EQUALS(foo3, "foo""X");
}
void test_refCount2(void) {
+ // using external storage
Common::String foo1("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
- Common::String foo2("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
+ Common::String foo2(foo1);
Common::String foo3(foo2);
foo3 += 'X';
- TS_ASSERT_EQUALS( foo2, foo1 );
- TS_ASSERT_EQUALS( foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" );
- TS_ASSERT_EQUALS( foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X" );
+ TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
+ TS_ASSERT_EQUALS(foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
+ TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X");
+ foo2 = 'x';
+ TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
+ TS_ASSERT_EQUALS(foo2, "x");
+ TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X");
}
void test_refCount3(void) {
Common::String foo1("0123456789abcdefghijk");
- Common::String foo2("0123456789abcdefghijk");
+ Common::String foo2(foo1);
Common::String foo3(foo2);
foo3 += "0123456789abcdefghijk";
- TS_ASSERT_EQUALS( foo2, foo1 );
- TS_ASSERT_EQUALS( foo2, "0123456789abcdefghijk" );
- TS_ASSERT_EQUALS( foo3, "0123456789abcdefghijk""0123456789abcdefghijk" );
+ TS_ASSERT_EQUALS(foo1, foo2);
+ TS_ASSERT_EQUALS(foo2, "0123456789abcdefghijk");
+ TS_ASSERT_EQUALS(foo3, "0123456789abcdefghijk""0123456789abcdefghijk");
+ foo2 = 'x';
+ TS_ASSERT_EQUALS(foo1, "0123456789abcdefghijk");
+ TS_ASSERT_EQUALS(foo2, "x");
+ TS_ASSERT_EQUALS(foo3, "0123456789abcdefghijk""0123456789abcdefghijk");
}
void test_refCount4(void) {
Common::String foo1("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
- Common::String foo2("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
+ Common::String foo2(foo1);
Common::String foo3(foo2);
foo3 += "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd";
- TS_ASSERT_EQUALS( foo2, foo1 );
- TS_ASSERT_EQUALS( foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" );
- TS_ASSERT_EQUALS( foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" );
+ TS_ASSERT_EQUALS(foo1, foo2);
+ TS_ASSERT_EQUALS(foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
+ TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
+ foo2 = 'x';
+ TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
+ TS_ASSERT_EQUALS(foo2, "x");
+ TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd");
}
void test_hasPrefix(void) {
Common::String str("this/is/a/test, haha");
- TS_ASSERT_EQUALS( str.hasPrefix(""), true );
- TS_ASSERT_EQUALS( str.hasPrefix("this"), true );
- TS_ASSERT_EQUALS( str.hasPrefix("thit"), false );
- TS_ASSERT_EQUALS( str.hasPrefix("foo"), false );
+ TS_ASSERT_EQUALS(str.hasPrefix(""), true);
+ TS_ASSERT_EQUALS(str.hasPrefix("this"), true);
+ TS_ASSERT_EQUALS(str.hasPrefix("thit"), false);
+ TS_ASSERT_EQUALS(str.hasPrefix("foo"), false);
}
void test_hasSuffix(void) {
Common::String str("this/is/a/test, haha");
- TS_ASSERT_EQUALS( str.hasSuffix(""), true );
- TS_ASSERT_EQUALS( str.hasSuffix("haha"), true );
- TS_ASSERT_EQUALS( str.hasSuffix("hahb"), false );
- TS_ASSERT_EQUALS( str.hasSuffix("hahah"), false );
+ TS_ASSERT_EQUALS(str.hasSuffix(""), true);
+ TS_ASSERT_EQUALS(str.hasSuffix("haha"), true);
+ TS_ASSERT_EQUALS(str.hasSuffix("hahb"), false);
+ TS_ASSERT_EQUALS(str.hasSuffix("hahah"), false);
}
void test_contains(void) {
Common::String str("this/is/a/test, haha");
- TS_ASSERT_EQUALS( str.contains(""), true );
- TS_ASSERT_EQUALS( str.contains("haha"), true );
- TS_ASSERT_EQUALS( str.contains("hahb"), false );
- TS_ASSERT_EQUALS( str.contains("test"), true );
+ TS_ASSERT_EQUALS(str.contains(""), true);
+ TS_ASSERT_EQUALS(str.contains("haha"), true);
+ TS_ASSERT_EQUALS(str.contains("hahb"), false);
+ TS_ASSERT_EQUALS(str.contains("test"), true);
}
void test_toLowercase(void) {
Common::String str("Test it, NOW! 42");
+ Common::String str2 = str;
str.toLowercase();
- TS_ASSERT_EQUALS( str, "test it, now! 42" );
+ TS_ASSERT_EQUALS(str, "test it, now! 42");
+ TS_ASSERT_EQUALS(str2, "Test it, NOW! 42");
}
void test_toUppercase(void) {
Common::String str("Test it, NOW! 42");
+ Common::String str2 = str;
str.toUppercase();
- TS_ASSERT_EQUALS( str, "TEST IT, NOW! 42" );
+ TS_ASSERT_EQUALS(str, "TEST IT, NOW! 42");
+ TS_ASSERT_EQUALS(str2, "Test it, NOW! 42");
}
};
diff --git a/test/common/subreadstream.h b/test/common/subreadstream.h
index c48f57c3a8..4e14448c06 100644
--- a/test/common/subreadstream.h
+++ b/test/common/subreadstream.h
@@ -2,25 +2,22 @@
#include "common/stream.h"
-class SubReadStreamTestSuite : public CxxTest::TestSuite
-{
+class SubReadStreamTestSuite : public CxxTest::TestSuite {
public:
- void test_traverse( void )
- {
+ void test_traverse(void) {
byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
- Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10);
+ Common::MemoryReadStream ms(contents, 10);
int end = 5;
- Common::SubReadStream srs = Common::SubReadStream(&ms, end);
+ Common::SubReadStream srs(&ms, end);
int i;
byte b;
- for (i = 0; i < end; ++i)
- {
+ for (i = 0; i < end; ++i) {
TS_ASSERT( !srs.eos() );
- srs.read(&b, 1);
+ b = srs.readByte();
TS_ASSERT_EQUALS( i, b );
}
diff --git a/tools/create_kyradat/create_kyradat.cpp b/tools/create_kyradat/create_kyradat.cpp
index 173ba0f993..78de2b6bce 100644
--- a/tools/create_kyradat/create_kyradat.cpp
+++ b/tools/create_kyradat/create_kyradat.cpp
@@ -31,7 +31,7 @@
#include "md5.h"
enum {
- kKyraDatVersion = 28,
+ kKyraDatVersion = 31,
kIndexSize = 12
};
@@ -53,6 +53,8 @@ enum {
#include "malcolm.h"
+#include "lol_demo.h"
+
const Game kyra1FanTranslations[] = {
{ kKyra1, IT_ITA, kTalkieVersion, "d0f1752098236083d81b9497bd2b6989", kyra1FreCD },
GAME_DUMMY_ENTRY
@@ -251,6 +253,11 @@ const ExtractFilename extractFilenames[] = {
{ k3ItemMagicTable, k3TypeRaw16to8, "ITEMMAGIC.MAP" },
{ k3ItemStringMap, kTypeRawData, "ITEMSTRINGS.MAP" },
+ // LANDS OF LORE
+
+ // Demo Sequence Player
+ { lSeqplayIntroTracks, k2TypeSoundList, "S_INTRO.TRA" },
+
{ -1, 0, 0 }
};
@@ -288,7 +295,7 @@ bool getFilename(char *dstFilename, const Game *g, const int id) {
void createFilename(char *dstFilename, const int gid, const int lang, const int special, const char *filename) {
strcpy(dstFilename, filename);
- static const char *gidExtensions[] = { "", ".K2", ".K3" };
+ static const char *gidExtensions[] = { "", ".K2", ".K3", 0, ".LOL" };
strcat(dstFilename, gidExtensions[gid]);
for (const SpecialExtension *specialE = specialTable; specialE->special != -1; ++specialE) {
@@ -311,7 +318,7 @@ void createLangFilename(char *dstFilename, const int gid, const int lang, const
}
}
- static const char *gidExtensions[] = { "", ".K2", ".K3" };
+ static const char *gidExtensions[] = { "", ".K2", ".K3", 0, ".LOL" };
strcat(dstFilename, gidExtensions[gid]);
for (const SpecialExtension *specialE = specialTable; specialE->special != -1; ++specialE) {
@@ -723,11 +730,11 @@ bool extractHofSeqData(PAKFile &out, const Game *g, const byte *data, const uint
controlOffs = 0;
WRITE_BE_UINT16(output, controlOffs);
- if (g->special != k2DemoVersion)
+ if (g->special != k2DemoVersion && g->special != k2DemoLol)
ptr += 4;
output += 2;
- if (g->special != k2DemoVersion) {
+ if (g->special != k2DemoVersion && g->special != k2DemoLol) {
for (int w = 0; w < 2; w++) { //startupCommand, finalCommand
WRITE_BE_UINT16(output, READ_LE_UINT16(ptr));
ptr += 2;
@@ -1063,9 +1070,9 @@ enum {
uint32 getFeatures(const Game *g) {
uint32 features = 0;
- if (g->special == kTalkieVersion || g->special == k2CDFile1E || g->special == k2CDFile1F || g->special == k2CDFile1G || g->special == k2CDFile2E || g->special == k2CDFile2F || g->special == k2CDFile2G || g->game == kKyra3)
+ if (g->special == kTalkieVersion || g->special == k2CDFile1E || g->special == k2CDFile1F || g->special == k2CDFile1G || g->special == k2CDFile1I || g->special == k2CDFile2E || g->special == k2CDFile2F || g->special == k2CDFile2G || g->game == kKyra3)
features |= GF_TALKIE;
- else if (g->special == kDemoVersion || g->special == k2DemoVersion)
+ else if (g->special == kDemoVersion || g->special == k2DemoVersion || g->special == k2DemoLol)
features |= GF_DEMO;
else if (g->special == kFMTownsVersionE || g->special == kFMTownsVersionJ ||
g->special == k2TownsFile1E || g->special == k2TownsFile1J ||
@@ -1344,6 +1351,8 @@ const Game *gameDescs[] = {
kyra3Games,
+ lolDemos,
+
0
};
diff --git a/tools/create_kyradat/create_kyradat.h b/tools/create_kyradat/create_kyradat.h
index c8bcb7d583..8e985f9031 100644
--- a/tools/create_kyradat/create_kyradat.h
+++ b/tools/create_kyradat/create_kyradat.h
@@ -174,6 +174,8 @@ enum kExtractID {
k3ItemMagicTable,
k3ItemStringMap,
+ lSeqplayIntroTracks,
+
kMaxResIDs
};
@@ -202,20 +204,22 @@ enum kSpecial {
k2CDFile2E = 8,
k2CDFile2F = 9,
k2CDFile2G = 10,
-
- k2TownsFile1E = 11,
- k2TownsFile1J = 12,
- k2TownsFile2E = 13,
- k2TownsFile2J = 14,
-
- k2FloppyFile1 = 15,
- k2FloppyFile2 = 16,
-
- k2DemoVersion = 17,
-
- k2DemoVersionTlkE = 18,
- k2DemoVersionTlkF = 19,
- k2DemoVersionTlkG = 20
+ // Italian fan translation
+ k2CDFile1I = 11,
+
+ k2TownsFile1E = 12,
+ k2TownsFile1J = 13,
+ k2TownsFile2E = 14,
+ k2TownsFile2J = 15,
+
+ k2FloppyFile1 = 16,
+ k2FloppyFile2 = 17,
+
+ k2DemoVersion = 18,
+ k2DemoVersionTlkE = 19,
+ k2DemoVersionTlkF = 20,
+ k2DemoVersionTlkG = 21,
+ k2DemoLol = 22
};
struct SpecialExtension {
@@ -225,8 +229,9 @@ struct SpecialExtension {
enum kGame {
kKyra1 = 0,
- kKyra2,
- kKyra3
+ kKyra2 = 1,
+ kKyra3 = 2,
+ kLol = 4
};
struct Game {
diff --git a/tools/create_kyradat/hof_cd.h b/tools/create_kyradat/hof_cd.h
index 1393f74890..431adbb674 100644
--- a/tools/create_kyradat/hof_cd.h
+++ b/tools/create_kyradat/hof_cd.h
@@ -23,6 +23,12 @@ const ExtractEntry kyra2File1CDG[] = {
{ -1, 0, 0 }
};
+const ExtractEntry kyra2File1CDI[] = {
+ { k2SeqplayStrings, 0x0002C566, 0x0002CE7C },
+ { k2SeqplayTlkFiles, 0x0002A2AC, 0x0002A349 },
+ { -1, 0, 0 }
+};
+
const ExtractEntry kyra2File2CDE[] = {
{ k2IngameSfxFiles, 0x0002CB30, 0x0002D221 },
{ k2IngameSfxIndex, 0x000294F0, 0x00029848 },
@@ -47,5 +53,10 @@ const Game kyra2TalkieGames[] = {
{ kKyra2, EN_ANY, k2CDFile2E, "e20d0d2e500f01e399ec588247a7e213", kyra2File2CDE},
{ kKyra2, FR_FRA, k2CDFile2F, "e20d0d2e500f01e399ec588247a7e213", kyra2File2CDF},
{ kKyra2, DE_DEU, k2CDFile2G, "e20d0d2e500f01e399ec588247a7e213", kyra2File2CDG},
+
+ // Italian Fan Translation (using same offsets as English)
+ { kKyra2, IT_ITA, k2CDFile1I, "130795aa8f2333250c895dae9028b9bb", kyra2File1CDI},
+
GAME_DUMMY_ENTRY
};
+
diff --git a/tools/create_kyradat/hof_demo.h b/tools/create_kyradat/hof_demo.h
index f7b15ffa3d..2eaaa3c413 100644
--- a/tools/create_kyradat/hof_demo.h
+++ b/tools/create_kyradat/hof_demo.h
@@ -26,7 +26,8 @@ const Game kyra2Demos[] = {
{ kKyra2, EN_ANY, k2DemoVersion, "a620a37579dd44ab0403482285e3897f", kyra2Demo},
{ kKyra2, EN_ANY, k2CDFile2E, "fa54d8abfe05f9186c05f7de7eaf1480", kyra2DemoCDE},
{ kKyra2, FR_FRA, k2CDFile2F, "fa54d8abfe05f9186c05f7de7eaf1480", kyra2DemoCDF},
- { kKyra2, DE_DEU, k2CDFile2G, "fa54d8abfe05f9186c05f7de7eaf1480", kyra2DemoCDG},
-
+ { kKyra2, DE_DEU, k2CDFile2G, "fa54d8abfe05f9186c05f7de7eaf1480", kyra2DemoCDG},
GAME_DUMMY_ENTRY
};
+
+
diff --git a/tools/create_kyradat/lol_demo.h b/tools/create_kyradat/lol_demo.h
new file mode 100644
index 0000000000..ce114f4e73
--- /dev/null
+++ b/tools/create_kyradat/lol_demo.h
@@ -0,0 +1,15 @@
+const ExtractEntry lolDemo[] = {
+ { k2SeqplayPakFiles, 0x0001AC10, 0x0001AC1C },
+ { k2SeqplayStrings, 0x0001B5EE, 0x0001B6F0 },
+ { k2SeqplaySfxFiles, 0x0001B6F0, 0x0001B7B5 },
+ { k2SeqplaySeqData, 0x0001B320, 0x0001B56C },
+ { lSeqplayIntroTracks, 0x0001B7B5, 0x0001B7CF },
+ { -1, 0, 0 }
+};
+
+const Game lolDemos[] = {
+ { kLol, EN_ANY, k2DemoLol, "30bb5af87d38adb47d3e6ce06b1cb042", lolDemo},
+ GAME_DUMMY_ENTRY
+};
+
+
diff --git a/tools/create_kyradat/misc.h b/tools/create_kyradat/misc.h
index 1e1cd29cc9..f0de5283ad 100644
--- a/tools/create_kyradat/misc.h
+++ b/tools/create_kyradat/misc.h
@@ -363,6 +363,7 @@ const int kyra2CDFile1EngNeed[] = {
k2SeqplayCreditsSpecial,
k2SeqplayStrings,
k2SeqplaySfxFiles,
+ k2SeqplayTlkFiles,
k2SeqplaySeqData,
k2SeqplayIntroTracks,
k2SeqplayFinaleTracks,
@@ -371,11 +372,19 @@ const int kyra2CDFile1EngNeed[] = {
const int kyra2CDFile1FreNeed[] = {
k2SeqplayStrings,
+ k2SeqplayTlkFiles,
-1
};
const int kyra2CDFile1GerNeed[] = {
k2SeqplayStrings,
+ k2SeqplayTlkFiles,
+ -1
+};
+
+const int kyra2CDFile1ItaNeed[] = {
+ k2SeqplayStrings,
+ k2SeqplayTlkFiles,
-1
};
@@ -472,6 +481,15 @@ const int kyra3Need[] = {
-1
};
+const int lolDemoNeed[] = {
+ k2SeqplayPakFiles,
+ k2SeqplayStrings,
+ k2SeqplaySeqData,
+ k2SeqplaySfxFiles,
+ lSeqplayIntroTracks,
+ -1
+};
+
const GameNeed gameNeedTable[] = {
{ kKyra1, -1, kyra1FloppyNeed },
{ kKyra1, kTalkieVersion, kyra1CDNeed },
@@ -485,6 +503,7 @@ const GameNeed gameNeedTable[] = {
{ kKyra2, k2CDFile1E, kyra2CDFile1EngNeed },
{ kKyra2, k2CDFile1F, kyra2CDFile1FreNeed },
{ kKyra2, k2CDFile1G, kyra2CDFile1GerNeed },
+ { kKyra2, k2CDFile1I, kyra2CDFile1ItaNeed }, // Italian fan translation
{ kKyra2, k2CDFile2E, kyra2CDFile2EngNeed },
{ kKyra2, k2CDFile2F, kyra2CDFile2FreNeed },
{ kKyra2, k2CDFile2G, kyra2CDFile2GerNeed },
@@ -496,6 +515,7 @@ const GameNeed gameNeedTable[] = {
{ kKyra2, k2DemoVersionTlkE, kyra2TlkDemoNeed},
{ kKyra2, k2DemoVersionTlkF, kyra2TlkDemoNeed},
{ kKyra2, k2DemoVersionTlkG, kyra2TlkDemoNeed},
+ { kLol, k2DemoLol, lolDemoNeed},
{ kKyra3, -1, kyra3Need },
@@ -512,14 +532,17 @@ const SpecialExtension specialTable[] = {
{ k2CDFile1E, "CD" },
{ k2CDFile1F, "CD" },
{ k2CDFile1G, "CD" },
+ { k2CDFile1I, "CD" },
{ k2CDFile2E, "CD" },
{ k2CDFile2F, "CD" },
{ k2CDFile2G, "CD" },
+
{ k2TownsFile1E, "TNS" },
{ k2TownsFile1J, "TNS" },
{ k2TownsFile2E, "TNS" },
{ k2TownsFile2J, "TNS" },
{ k2DemoVersion, "DEM" },
+ { k2DemoLol, "DEM" },
{ -1, 0 }
};
diff --git a/tools/credits.pl b/tools/credits.pl
index fa806db7de..d1a73ab9fb 100755
--- a/tools/credits.pl
+++ b/tools/credits.pl
@@ -463,6 +463,12 @@ begin_credits("Credits");
end_persons();
end_section();
+ begin_section("Retired Project Leaders");
+ begin_persons();
+ add_person("Vincent Hamm", "yaz0r", "ScummVM co-founder, Original Cruise/CinE author");
+ add_person("Ludvig Strigeus", "ludde", "Original ScummVM and SimonVM author");
+ end_persons();
+ end_section();
begin_section("Engine Teams");
begin_section("SCUMM");
@@ -566,11 +572,21 @@ begin_credits("Credits");
begin_section("SAGA");
add_person("Torbj&ouml;rn Andersson", "eriktorbjorn", "");
+ add_person("Sven Hesse", "DrMcCoy", "");
add_person("Filippos Karapetis", "[md5]", "");
add_person("Andrew Kurushin", "ajax16384", "");
add_person("Eugene Sandulenko", "sev", "");
end_section();
+ begin_section("Tinsel;");
+ add_person("Torbj&ouml;rn Andersson", "eriktorbjorn", "");
+ add_person("Paul Gilbert", "dreammaster", "");
+ add_person("Sven Hesse", "DrMcCoy", "");
+ add_person("Max Horn", "Fingolfin", "");
+ add_person("Filippos Karapetis", "[md5]", "");
+ add_person("Joost Peters", "joostp", "");
+ end_section();
+
begin_section("Touch&eacute;");
add_person("Gregory Montoir", "cyx", "");
end_section();
@@ -669,12 +685,10 @@ begin_credits("Credits");
add_person("Ralph Brorsen", "painelf", "Help with GUI implementation");
add_person("Jamieson Christian", "jamieson630", "iMUSE, MIDI, all things musical");
add_person("R&uuml;diger Hanke", "", "Port: MorphOS");
- add_person("Vincent Hamm", "yaz0r", "ScummVM co-founder, Original Cruise/CinE author");
add_person("Felix Jakschitsch", "yot", "Zak256 reverse engineering");
add_person("Mutwin Kraus", "mutle", "Original MacOS porter");
add_person("Peter Moraliyski", "ph0x", "Port: GP32");
add_person("Jeremy Newman", "laxdragon", "Former webmaster");
- add_person("Ludvig Strigeus", "ludde", "Original ScummVM and SimonVM author");
add_person("Lionel Ulmer", "bbrox", "Port: X11");
add_person("Won Star", "wonst719", "Former GP32 porter");
end_persons();
diff --git a/tools/scumm-md5.txt b/tools/scumm-md5.txt
index 721c43b568..4da978914d 100644
--- a/tools/scumm-md5.txt
+++ b/tools/scumm-md5.txt
@@ -58,14 +58,14 @@ maniac Maniac Mansion
7f45ddd6dbfbf8f80c0c0efea4c295bc 1972 en DOS V1 V1 - Fingolfin
- d8d07efcb88f396bee0b402b10c3b1c9 262144 us NES NES - -
- 3905799e081b80a61d4460b7b733c206 262144 gb NES NES - -
+ d8d07efcb88f396bee0b402b10c3b1c9 262144 gb NES NES - -
+ 3905799e081b80a61d4460b7b733c206 262144 us NES NES - -
81bbfa181184cb494e7a81dcfa94fbd9 262144 fr NES NES - -
257f8c14d8c584f7ddd601bcb00920c7 262144 de NES NES - -
f163cf53f7850e43fb482471e5c52e1a 262144 es NES NES - -
22d07d6c386c9c25aca5dac2a0c0d94b 262144 se NES NES - -
- 17f7296f63c78642724f057fd8e736a7 -1 us NES NES extracted -
- 91d5db93187fab54d823f73bd6441cb6 -1 gb NES NES extracted -
+ 17f7296f63c78642724f057fd8e736a7 -1 gb NES NES extracted -
+ 91d5db93187fab54d823f73bd6441cb6 -1 us NES NES extracted -
1c7e7db2cfab1ad62746ab680a634204 -1 fr NES NES extracted -
3a5ec90d556d4920976c5578bfbfaf79 -1 de NES NES extracted -
b7d37d6b786b5a22deea3b038eca96ca 2082 es NES NES extracted -