aboutsummaryrefslogtreecommitdiff
path: root/engines
diff options
context:
space:
mode:
Diffstat (limited to 'engines')
-rw-r--r--engines/agi/agi.cpp7
-rw-r--r--engines/agos/agos.cpp32
-rw-r--r--engines/agos/agos.h4
-rw-r--r--engines/agos/animation.cpp2
-rw-r--r--engines/agos/debug.cpp4
-rw-r--r--engines/agos/draw.cpp2
-rw-r--r--engines/agos/event.cpp6
-rw-r--r--engines/agos/gfx.cpp131
-rw-r--r--engines/agos/input.cpp4
-rw-r--r--engines/agos/intern.h1
-rw-r--r--engines/agos/saveload.cpp8
-rw-r--r--engines/agos/script.cpp9
-rw-r--r--engines/agos/script_e1.cpp4
-rw-r--r--engines/agos/script_s1.cpp4
-rw-r--r--engines/agos/subroutine.cpp4
-rw-r--r--engines/cine/anim.cpp74
-rw-r--r--engines/cine/anim.h57
-rw-r--r--engines/cine/bg.cpp2
-rw-r--r--engines/cine/bg.h3
-rw-r--r--engines/cine/bg_list.cpp4
-rw-r--r--engines/cine/bg_list.h2
-rw-r--r--engines/cine/cine.cpp5
-rw-r--r--engines/cine/cine.h8
-rw-r--r--engines/cine/gfx.cpp81
-rw-r--r--engines/cine/gfx.h12
-rw-r--r--engines/cine/main_loop.cpp39
-rw-r--r--engines/cine/object.cpp75
-rw-r--r--engines/cine/object.h10
-rw-r--r--engines/cine/pal.h2
-rw-r--r--engines/cine/part.cpp4
-rw-r--r--engines/cine/prc.cpp9
-rw-r--r--engines/cine/prc.h2
-rw-r--r--engines/cine/script.h30
-rw-r--r--engines/cine/script_fw.cpp88
-rw-r--r--engines/cine/script_os.cpp87
-rw-r--r--engines/cine/sound.cpp1
-rw-r--r--engines/cine/texte.cpp10
-rw-r--r--engines/cine/texte.h5
-rw-r--r--engines/cine/unpack.cpp2
-rw-r--r--engines/cine/various.cpp1385
-rw-r--r--engines/cine/various.h8
-rw-r--r--engines/cruise/cruise_main.h2
-rw-r--r--engines/cruise/volume.cpp4
-rw-r--r--engines/drascula/animation.cpp8
-rw-r--r--engines/drascula/drascula.h6
-rw-r--r--engines/drascula/graphics.cpp31
-rw-r--r--engines/drascula/palette.cpp8
-rw-r--r--engines/drascula/rooms.cpp16
-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/detection.cpp13
-rw-r--r--engines/gob/driver_vga.cpp2
-rw-r--r--engines/gob/gob.cpp9
-rw-r--r--engines/gob/gob.h6
-rw-r--r--engines/gob/goblin.cpp330
-rw-r--r--engines/gob/goblin.h103
-rw-r--r--engines/gob/goblin_v2.cpp2
-rw-r--r--engines/gob/inter.cpp24
-rw-r--r--engines/gob/inter.h2
-rw-r--r--engines/gob/inter_v1.cpp173
-rw-r--r--engines/gob/inter_v2.cpp12
-rw-r--r--engines/gob/mult.cpp26
-rw-r--r--engines/gob/mult.h9
-rw-r--r--engines/gob/mult_v1.cpp33
-rw-r--r--engines/gob/mult_v2.cpp26
-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/gob/scenery.cpp4
-rw-r--r--engines/gob/sound/sound.h2
-rw-r--r--engines/gob/sound/soundmixer.h2
-rw-r--r--engines/gob/variables.cpp58
-rw-r--r--engines/gob/variables.h26
-rw-r--r--engines/gob/videoplayer.cpp14
-rw-r--r--engines/igor/igor.cpp2
-rw-r--r--engines/kyra/animator_lok.cpp4
-rw-r--r--engines/kyra/detection.cpp229
-rw-r--r--engines/kyra/gui_hof.cpp22
-rw-r--r--engines/kyra/gui_lok.cpp10
-rw-r--r--engines/kyra/gui_lok.h2
-rw-r--r--engines/kyra/items_lok.cpp2
-rw-r--r--engines/kyra/kyra_hof.cpp10
-rw-r--r--engines/kyra/kyra_mr.cpp11
-rw-r--r--engines/kyra/kyra_mr.h5
-rw-r--r--engines/kyra/kyra_v1.cpp50
-rw-r--r--engines/kyra/kyra_v1.h9
-rw-r--r--engines/kyra/kyra_v2.cpp32
-rw-r--r--engines/kyra/kyra_v2.h3
-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.cpp16
-rw-r--r--engines/kyra/scene_hof.cpp4
-rw-r--r--engines/kyra/scene_lok.cpp4
-rw-r--r--engines/kyra/screen.cpp125
-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.cpp8
-rw-r--r--engines/kyra/script.h2
-rw-r--r--engines/kyra/script_hof.cpp12
-rw-r--r--engines/kyra/script_tim.cpp419
-rw-r--r--engines/kyra/script_tim.h73
-rw-r--r--engines/kyra/seqplayer.cpp4
-rw-r--r--engines/kyra/sequences_lok.cpp2
-rw-r--r--engines/kyra/sound.cpp23
-rw-r--r--engines/kyra/sound.h57
-rw-r--r--engines/kyra/sound_adlib.cpp9
-rw-r--r--engines/kyra/sound_lok.cpp16
-rw-r--r--engines/kyra/sound_towns.cpp2101
-rw-r--r--engines/kyra/staticres.cpp169
-rw-r--r--engines/kyra/wsamovie.h2
-rw-r--r--engines/lure/lure.cpp5
-rw-r--r--engines/lure/lure.h1
-rw-r--r--engines/lure/luredefs.h2
-rw-r--r--engines/lure/menu.cpp5
-rw-r--r--engines/lure/menu.h1
-rw-r--r--engines/lure/palette.cpp6
-rw-r--r--engines/lure/palette.h1
-rw-r--r--engines/lure/res.cpp1
-rw-r--r--engines/lure/res_struct.cpp6
-rw-r--r--engines/lure/sound.cpp6
-rw-r--r--engines/m4/assets.cpp1
-rw-r--r--engines/m4/converse.cpp2
-rw-r--r--engines/m4/globals.cpp10
-rw-r--r--engines/m4/globals.h2
-rw-r--r--engines/m4/graphics.cpp6
-rw-r--r--engines/m4/graphics.h2
-rw-r--r--engines/m4/m4_views.cpp2
-rw-r--r--engines/m4/mads_anim.cpp16
-rw-r--r--engines/m4/viewmgr.cpp2
-rw-r--r--engines/made/database.cpp13
-rw-r--r--engines/made/detection.cpp4
-rw-r--r--engines/made/made.cpp26
-rw-r--r--engines/made/made.h1
-rw-r--r--engines/made/pmvplayer.cpp5
-rw-r--r--engines/made/screen.cpp4
-rw-r--r--engines/made/scriptfuncs.cpp10
-rw-r--r--engines/parallaction/balloons.cpp728
-rw-r--r--engines/parallaction/callables_ns.cpp130
-rw-r--r--engines/parallaction/debug.cpp14
-rw-r--r--engines/parallaction/detection.cpp36
-rw-r--r--engines/parallaction/dialogue.cpp430
-rw-r--r--engines/parallaction/disk.h87
-rw-r--r--engines/parallaction/disk_br.cpp591
-rw-r--r--engines/parallaction/disk_ns.cpp38
-rw-r--r--engines/parallaction/exec.h255
-rw-r--r--engines/parallaction/exec_br.cpp180
-rw-r--r--engines/parallaction/exec_ns.cpp494
-rw-r--r--engines/parallaction/font.cpp140
-rw-r--r--engines/parallaction/gfxbase.cpp234
-rw-r--r--engines/parallaction/graphics.cpp784
-rw-r--r--engines/parallaction/graphics.h132
-rw-r--r--engines/parallaction/gui.cpp92
-rw-r--r--engines/parallaction/gui.h93
-rw-r--r--engines/parallaction/gui_br.cpp338
-rw-r--r--engines/parallaction/gui_ns.cpp941
-rw-r--r--engines/parallaction/input.cpp272
-rw-r--r--engines/parallaction/input.h54
-rw-r--r--engines/parallaction/inventory.cpp142
-rw-r--r--engines/parallaction/inventory.h27
-rw-r--r--engines/parallaction/module.mk2
-rw-r--r--engines/parallaction/objects.cpp24
-rw-r--r--engines/parallaction/objects.h29
-rw-r--r--engines/parallaction/parallaction.cpp261
-rw-r--r--engines/parallaction/parallaction.h246
-rw-r--r--engines/parallaction/parallaction_br.cpp154
-rw-r--r--engines/parallaction/parallaction_ns.cpp88
-rw-r--r--engines/parallaction/parser.cpp3
-rw-r--r--engines/parallaction/parser.h18
-rw-r--r--engines/parallaction/parser_br.cpp82
-rw-r--r--engines/parallaction/parser_ns.cpp23
-rw-r--r--engines/parallaction/sound.cpp4
-rw-r--r--engines/parallaction/walk.cpp568
-rw-r--r--engines/parallaction/walk.h84
-rw-r--r--engines/queen/graphics.cpp11
-rw-r--r--engines/queen/graphics.h4
-rw-r--r--engines/queen/input.cpp9
-rw-r--r--engines/queen/input.h4
-rw-r--r--engines/queen/journal.cpp4
-rw-r--r--engines/queen/queen.cpp2
-rw-r--r--engines/queen/resource.cpp2
-rw-r--r--engines/queen/sound.cpp60
-rw-r--r--engines/queen/sound.h1
-rw-r--r--engines/saga/animation.cpp1
-rw-r--r--engines/saga/font.cpp15
-rw-r--r--engines/saga/font.h3
-rw-r--r--engines/saga/font_map.cpp131
-rw-r--r--engines/saga/interface.cpp14
-rw-r--r--engines/saga/introproc_ihnm.cpp2
-rw-r--r--engines/saga/rscfile.cpp2
-rw-r--r--engines/saga/saga.cpp8
-rw-r--r--engines/saga/saga.h2
-rw-r--r--engines/saga/script.cpp1
-rw-r--r--engines/saga/sprite.cpp3
-rw-r--r--engines/scumm/charset.cpp2
-rw-r--r--engines/scumm/debugger.cpp2
-rw-r--r--engines/scumm/detection.cpp4
-rw-r--r--engines/scumm/dialogs.cpp27
-rw-r--r--engines/scumm/dialogs.h2
-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/gfxARM.s2
-rw-r--r--engines/scumm/he/resource_he.cpp5
-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/imuse_digi/dimuse_sndmgr.cpp6
-rw-r--r--engines/scumm/imuse_digi/dimuse_track.h4
-rw-r--r--engines/scumm/resource.cpp4
-rw-r--r--engines/scumm/saveload.cpp6
-rw-r--r--engines/scumm/scumm-md5.h10
-rw-r--r--engines/scumm/smush/codec47ARM.s8
-rw-r--r--engines/scumm/sound.cpp1
-rw-r--r--engines/sky/disk.cpp4
-rw-r--r--engines/sky/music/adlibmusic.cpp1
-rw-r--r--engines/sword1/resman.cpp4
-rw-r--r--engines/sword2/music.cpp21
-rw-r--r--engines/sword2/resman.cpp5
-rw-r--r--engines/sword2/sound.h2
-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.cpp278
-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--engines/touche/midi.cpp10
-rw-r--r--engines/touche/midi.h2
-rw-r--r--engines/touche/saveload.cpp3
-rw-r--r--engines/touche/touche.cpp5
314 files changed, 40770 insertions, 4521 deletions
diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp
index 81aec3e351..9d88dd73ef 100644
--- a/engines/agi/agi.cpp
+++ b/engines/agi/agi.cpp
@@ -62,9 +62,7 @@ void AgiEngine::processEvents() {
while (_eventMan->pollEvent(event)) {
switch (event.type) {
case Common::EVENT_QUIT:
- _gfx->deinitVideo();
- _gfx->deinitMachine();
- _system->quit();
+ _game.quitProgNow = true;
break;
case Common::EVENT_PREDICTIVE_DIALOG:
if (_predictiveDialogRunning)
@@ -766,12 +764,15 @@ AgiEngine::~AgiEngine() {
}
agiDeinit();
+ delete _loader;
_sound->deinitSound();
delete _sound;
_gfx->deinitVideo();
delete _sprites;
+ delete _picture;
free(_game.sbufOrig);
_gfx->deinitMachine();
+ delete _gfx;
delete _rnd;
delete _console;
diff --git a/engines/agos/agos.cpp b/engines/agos/agos.cpp
index 9b22240f83..a9fd204d73 100644
--- a/engines/agos/agos.cpp
+++ b/engines/agos/agos.cpp
@@ -37,6 +37,7 @@
#include "sound/mididrv.h"
#include "sound/mods/protracker.h"
+#include "sound/audiocd.h"
using Common::File;
@@ -96,6 +97,8 @@ AGOSEngine::AGOSEngine(OSystem *syst)
_vc_get_out_of_code = 0;
_gameOffsetsPtr = 0;
+ _quit = false;
+
_debugger = 0;
_gameFile = 0;
@@ -556,14 +559,17 @@ int AGOSEngine::init() {
// Setup midi driver
int midiDriver = MidiDriver::detectMusicDriver(MDT_ADLIB | MDT_MIDI);
_nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
- MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+
+ _driver = MidiDriver::createMidi(midiDriver);
+
if (_nativeMT32) {
- driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+ _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
}
_midi.mapMT32toGM (getGameType() != GType_SIMON2 && !_nativeMT32);
- _midi.setDriver(driver);
+ _midi.setDriver(_driver);
+
int ret = _midi.open();
if (ret)
warning("MIDI Player init failed: \"%s\"", _midi.getErrorName (ret));
@@ -572,6 +578,8 @@ int AGOSEngine::init() {
_midiEnabled = true;
+ } else {
+ _driver = NULL;
}
// allocate buffers
@@ -875,6 +883,10 @@ AGOSEngine::~AGOSEngine() {
delete _gameFile;
_midi.close();
+ if (_driver)
+ delete _driver;
+
+ AudioCD.destroy();
for (uint i = 0; i < _itemHeap.size(); i++) {
delete[] _itemHeap[i];
@@ -883,6 +895,8 @@ AGOSEngine::~AGOSEngine() {
free(_tablesHeapPtr - _tablesHeapCurPos);
+ free(_mouseData);
+
free(_gameOffsetsPtr);
free(_iconFilePtr);
free(_itemArrayPtr);
@@ -894,6 +908,7 @@ AGOSEngine::~AGOSEngine() {
free(_backGroundBuf);
free(_backBuf);
free(_scaleBuf);
+ free(_zoneBuffers);
free(_window4BackScn);
free(_window6BackScn);
@@ -937,7 +952,7 @@ void AGOSEngine::pauseEngineIntern(bool pauseIt) {
void AGOSEngine::pause() {
pauseEngine(true);
- while (_pause) {
+ while (_pause && !_quit) {
delay(1);
if (_keyPressed.keycode == Common::KEYCODE_p)
pauseEngine(false);
@@ -974,7 +989,7 @@ int AGOSEngine::go() {
(getFeatures() & GF_DEMO)) {
int i;
- while (1) {
+ while (!_quit) {
for (i = 0; i < 4; i++) {
setWindowImage(3, 9902 + i);
debug(0, "Displaying image %d", 9902 + i);
@@ -1003,7 +1018,7 @@ int AGOSEngine::go() {
runSubroutine101();
permitInput();
- while (1) {
+ while (!_quit) {
waitForInput();
handleVerbClicked(_verbHitArea);
delay(100);
@@ -1012,6 +1027,9 @@ int AGOSEngine::go() {
return 0;
}
+
+/* I do not think that this will be used
+ *
void AGOSEngine::shutdown() {
// Sync with AGOSEngine::~AGOSEngine()
// In Simon 2, this gets deleted along with _sound further down
@@ -1019,6 +1037,7 @@ void AGOSEngine::shutdown() {
delete _gameFile;
_midi.close();
+ delete _driver;
for (uint i = 0; i < _itemHeap.size(); i++) {
delete[] _itemHeap[i];
@@ -1058,6 +1077,7 @@ void AGOSEngine::shutdown() {
_system->quit();
}
+*/
uint32 AGOSEngine::getTime() const {
// FIXME: calling time() is not portable, use OSystem::getMillis instead
diff --git a/engines/agos/agos.h b/engines/agos/agos.h
index 448d26a9d0..8ad5487b35 100644
--- a/engines/agos/agos.h
+++ b/engines/agos/agos.h
@@ -269,6 +269,7 @@ protected:
uint16 _marks;
+ bool _quit;
bool _scriptVar2;
bool _runScriptReturn1;
bool _runScriptCondition[40];
@@ -523,6 +524,7 @@ protected:
byte _lettersToPrintBuf[80];
MidiPlayer _midi;
+ MidiDriver *_driver;
bool _midiEnabled;
bool _nativeMT32;
@@ -1073,6 +1075,8 @@ protected:
virtual void drawImage(VC10_state *state);
void drawBackGroundImage(VC10_state *state);
void drawVertImage(VC10_state *state);
+ void drawVertImageCompressed(VC10_state *state);
+ void drawVertImageUncompressed(VC10_state *state);
void setMoveRect(uint16 x, uint16 y, uint16 width, uint16 height);
diff --git a/engines/agos/animation.cpp b/engines/agos/animation.cpp
index fd78c65002..c92f834a3b 100644
--- a/engines/agos/animation.cpp
+++ b/engines/agos/animation.cpp
@@ -280,7 +280,7 @@ void MoviePlayer::handleNextFrame() {
_rightButtonDown = false;
break;
case Common::EVENT_QUIT:
- _vm->_system->quit();
+ _vm->_quit = true;
break;
default:
break;
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/draw.cpp b/engines/agos/draw.cpp
index 737f5317af..d38a5ad33b 100644
--- a/engines/agos/draw.cpp
+++ b/engines/agos/draw.cpp
@@ -473,7 +473,7 @@ void AGOSEngine::restoreBackGround() {
animTable = animTableTmp = _screenAnim1;
while (animTable->srcPtr) {
if (!(animTable->windowNum & 0x8000)) {
- memcpy(animTableTmp, animTable, sizeof(AnimTable));
+ memmove(animTableTmp, animTable, sizeof(AnimTable));
animTableTmp++;
}
animTable++;
diff --git a/engines/agos/event.cpp b/engines/agos/event.cpp
index 250ff2fcfd..010b331cf8 100644
--- a/engines/agos/event.cpp
+++ b/engines/agos/event.cpp
@@ -142,7 +142,7 @@ bool AGOSEngine::kickoffTimeEvents() {
cur_time = getTime() - _gameStoppedClock;
- while ((te = _firstTimeStruct) != NULL && te->time <= cur_time) {
+ while ((te = _firstTimeStruct) != NULL && te->time <= cur_time && !_quit) {
result = true;
_pendingDeleteTimeEvent = te;
invokeTimeEvent(te);
@@ -521,7 +521,7 @@ void AGOSEngine::delay(uint amount) {
_rightButtonDown++;
break;
case Common::EVENT_QUIT:
- shutdown();
+ _quit = true;
return;
default:
break;
@@ -544,7 +544,7 @@ void AGOSEngine::delay(uint amount) {
_system->delayMillis(this_delay);
cur = _system->getMillis();
- } while (cur < start + amount);
+ } while (cur < start + amount && !_quit);
}
void AGOSEngine::timer_callback() {
diff --git a/engines/agos/gfx.cpp b/engines/agos/gfx.cpp
index 193b7347d6..9a3962ea21 100644
--- a/engines/agos/gfx.cpp
+++ b/engines/agos/gfx.cpp
@@ -744,10 +744,6 @@ void AGOSEngine_Simon1::drawImage(VC10_state *state) {
}
void AGOSEngine::drawBackGroundImage(VC10_state *state) {
- const byte *src;
- byte *dst;
- uint h, i;
-
state->width = _screenWidth;
if (_window3Flag == 1) {
state->width = 0;
@@ -755,15 +751,19 @@ void AGOSEngine::drawBackGroundImage(VC10_state *state) {
state->y_skip = 0;
}
- src = state->srcPtr + (state->width * state->y_skip) + (state->x_skip * 8);
- dst = state->surf_addr;
+ const byte* src = state->srcPtr + (state->width * state->y_skip) + (state->x_skip * 8);
+ byte* dst = state->surf_addr;
state->draw_width *= 2;
- h = state->draw_height;
+ uint h = state->draw_height;
+ const uint w = state->draw_width;
+ const byte paletteMod = state->paletteMod;
do {
- for (i = 0; i != state->draw_width; i++)
- dst[i] = src[i] + state->paletteMod;
+ for (uint i = 0; i != w; i+=2) {
+ dst[i] = src[i] + paletteMod;
+ dst[i+1] = src[i+1] + paletteMod;
+ }
dst += state->surf_pitch;
src += state->width;
} while (--h);
@@ -771,63 +771,86 @@ void AGOSEngine::drawBackGroundImage(VC10_state *state) {
void AGOSEngine::drawVertImage(VC10_state *state) {
if (state->flags & kDFCompressed) {
- uint w, h;
- byte *src, *dst, *dstPtr;
+ drawVertImageCompressed(state);
+ } else {
+ drawVertImageUncompressed(state);
+ }
+}
- state->x_skip *= 4; /* reached */
+void AGOSEngine::drawVertImageUncompressed(VC10_state *state) {
+ assert ((state->flags & kDFCompressed) == 0) ;
- state->dl = state->width;
- state->dh = state->height;
+ const byte *src;
+ byte *dst;
+ uint count;
- vc10_skip_cols(state);
+ src = state->srcPtr + (state->width * state->y_skip) * 8;
+ dst = state->surf_addr;
+ state->x_skip *= 4;
- dstPtr = state->surf_addr;
- if (!(state->flags & kDFNonTrans) && (state->flags & 0x40)) { /* reached */
- dstPtr += vcReadVar(252);
- }
- w = 0;
- do {
+ do {
+ for (count = 0; count != state->draw_width; count++) {
byte color;
+ color = (src[count + state->x_skip] / 16) + state->paletteMod;
+ if ((state->flags & kDFNonTrans) || color)
+ dst[count * 2] = color | state->palette;
+ color = (src[count + state->x_skip] & 15) + state->paletteMod;
+ if ((state->flags & kDFNonTrans) || color)
+ dst[count * 2 + 1] = color | state->palette;
+ }
+ dst += state->surf_pitch;
+ src += state->width * 8;
+ } while (--state->draw_height);
+}
- src = vc10_depackColumn(state);
- dst = dstPtr;
+void AGOSEngine::drawVertImageCompressed(VC10_state *state) {
+ assert (state->flags & kDFCompressed) ;
+ uint w, h;
+
+ state->x_skip *= 4; /* reached */
- h = 0;
+ state->dl = state->width;
+ state->dh = state->height;
+
+ vc10_skip_cols(state);
+
+ byte *dstPtr = state->surf_addr;
+ if (!(state->flags & kDFNonTrans) && (state->flags & 0x40)) { /* reached */
+ dstPtr += vcReadVar(252);
+ }
+ w = 0;
+ do {
+ byte color;
+
+ const byte *src = vc10_depackColumn(state);
+ byte *dst = dstPtr;
+
+ h = 0;
+ if (state->flags & kDFNonTrans) {
+ do {
+ byte colors = *src;
+ color = (colors / 16);
+ dst[0] = color | state->palette;
+ color = (colors & 15);
+ dst[1] = color | state->palette;
+ dst += state->surf_pitch;
+ src++;
+ } while (++h != state->draw_height);
+ } else {
do {
- color = (*src / 16);
- if ((state->flags & kDFNonTrans) || color != 0)
+ byte colors = *src;
+ color = (colors / 16);
+ if (color != 0)
dst[0] = color | state->palette;
- color = (*src & 15);
- if ((state->flags & kDFNonTrans) || color != 0)
+ color = (colors & 15);
+ if (color != 0)
dst[1] = color | state->palette;
dst += state->surf_pitch;
src++;
} while (++h != state->draw_height);
- dstPtr += 2;
- } while (++w != state->draw_width);
- } else {
- const byte *src;
- byte *dst;
- uint count;
-
- src = state->srcPtr + (state->width * state->y_skip) * 8;
- dst = state->surf_addr;
- state->x_skip *= 4;
-
- do {
- for (count = 0; count != state->draw_width; count++) {
- byte color;
- color = (src[count + state->x_skip] / 16) + state->paletteMod;
- if ((state->flags & kDFNonTrans) || color)
- dst[count * 2] = color | state->palette;
- color = (src[count + state->x_skip] & 15) + state->paletteMod;
- if ((state->flags & kDFNonTrans) || color)
- dst[count * 2 + 1] = color | state->palette;
- }
- dst += state->surf_pitch;
- src += state->width * 8;
- } while (--state->draw_height);
- }
+ }
+ dstPtr += 2;
+ } while (++w != state->draw_width);
}
void AGOSEngine::drawImage(VC10_state *state) {
@@ -1263,7 +1286,7 @@ void AGOSEngine::setWindowImageEx(uint16 mode, uint16 vga_res) {
if (getGameType() == GType_WW && (mode == 6 || mode == 8 || mode == 9)) {
setWindowImage(mode, vga_res);
} else {
- while (_copyScnFlag)
+ while (_copyScnFlag && !_quit)
delay(1);
setWindowImage(mode, vga_res);
diff --git a/engines/agos/input.cpp b/engines/agos/input.cpp
index add7eb96f0..d36549f187 100644
--- a/engines/agos/input.cpp
+++ b/engines/agos/input.cpp
@@ -189,12 +189,12 @@ void AGOSEngine::waitForInput() {
resetVerbs();
}
- for (;;) {
+ while (!_quit) {
_lastHitArea = NULL;
_lastHitArea3 = NULL;
_dragAccept = 1;
- for (;;) {
+ while (!_quit) {
if ((getGameType() == GType_SIMON1 || getGameType() == GType_SIMON2) &&
_keyPressed.keycode == Common::KEYCODE_F10)
displayBoxStars();
diff --git a/engines/agos/intern.h b/engines/agos/intern.h
index 54cf4dba16..4479e2851e 100644
--- a/engines/agos/intern.h
+++ b/engines/agos/intern.h
@@ -161,6 +161,7 @@ struct WindowBlock {
uint8 fill_color, text_color;
IconBlock *iconPtr;
WindowBlock() { memset(this, 0, sizeof(*this)); }
+ ~WindowBlock() { free (iconPtr); }
};
// note on text offset:
// the actual x-coordinate is: textColumn * 8 + textColumnOffset
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/agos/script.cpp b/engines/agos/script.cpp
index 44fbb4e9e0..6758aec511 100644
--- a/engines/agos/script.cpp
+++ b/engines/agos/script.cpp
@@ -410,7 +410,7 @@ void AGOSEngine::o_msg() {
void AGOSEngine::o_end() {
// 68: exit interpreter
- shutdown();
+ _quit = true;
}
void AGOSEngine::o_done() {
@@ -965,6 +965,9 @@ void AGOSEngine::writeVariable(uint16 variable, uint16 contents) {
int AGOSEngine::runScript() {
bool flag;
+ if (_quit)
+ return 1;
+
do {
if (_continousMainScript)
dumpOpcode(_codePtr);
@@ -1007,7 +1010,7 @@ int AGOSEngine::runScript() {
error("Invalid opcode '%d' encountered", _opcode);
executeOpcode(_opcode);
- } while (getScriptCondition() != flag && !getScriptReturn());
+ } while (getScriptCondition() != flag && !getScriptReturn() && !_quit);
return getScriptReturn();
}
@@ -1063,7 +1066,7 @@ void AGOSEngine::waitForSync(uint a) {
_exitCutscene = false;
_rightButtonDown = false;
- while (_vgaWaitFor != 0) {
+ while (_vgaWaitFor != 0 && !_quit) {
if (_rightButtonDown) {
if (_vgaWaitFor == 200 && (getGameType() == GType_FF || !getBitFlag(14))) {
skipSpeech();
diff --git a/engines/agos/script_e1.cpp b/engines/agos/script_e1.cpp
index 94df21979c..c7e1d6736e 100644
--- a/engines/agos/script_e1.cpp
+++ b/engines/agos/script_e1.cpp
@@ -565,7 +565,7 @@ void AGOSEngine_Elvira1::oe1_look() {
lobjFunc(l, "You can see "); /* Show objects */
}
if (r && (r->flags & 4) && levelOf(i) < 10000) {
- shutdown();
+ _quit = true;
}
}
@@ -944,7 +944,7 @@ restart:
windowPutChar(window, *message2);
if (confirmYesOrNo(120, 62) == 0x7FFF) {
- shutdown();
+ _quit = true;
} else {
goto restart;
}
diff --git a/engines/agos/script_s1.cpp b/engines/agos/script_s1.cpp
index a1308b951d..51918b9515 100644
--- a/engines/agos/script_s1.cpp
+++ b/engines/agos/script_s1.cpp
@@ -345,14 +345,14 @@ void AGOSEngine_Simon1::os1_pauseGame() {
if (isSmartphone()) {
if (_keyPressed.keycode) {
if (_keyPressed.keycode == Common::KEYCODE_RETURN)
- shutdown();
+ _quit = true;
else
break;
}
}
#endif
if (_keyPressed.keycode == keyYes)
- shutdown();
+ _quit = true;
else if (_keyPressed.keycode == keyNo)
break;
}
diff --git a/engines/agos/subroutine.cpp b/engines/agos/subroutine.cpp
index 44ada82585..cb71ed7efa 100644
--- a/engines/agos/subroutine.cpp
+++ b/engines/agos/subroutine.cpp
@@ -554,6 +554,10 @@ int AGOSEngine::startSubroutine(Subroutine *sub) {
_currentTable = sub;
restart:
+
+ if (_quit)
+ return result;
+
while ((byte *)sl != (byte *)sub) {
_currentLine = sl;
if (checkIfToRunSubroutineLine(sl, sub)) {
diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp
index 055eb733c3..8dbccebedf 100644
--- a/engines/cine/anim.cpp
+++ b/engines/cine/anim.cpp
@@ -769,19 +769,18 @@ void loadAbs(const char *resourceName, uint16 idx) {
/*! \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) {
+void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat) {
int16 currentAnim, foundFileIdx;
int8 isMask = 0, isSpl = 0;
byte *dataPtr, *ptr;
char *animName, part[256];
byte transparentColor = 0;
- AnimData *currentPtr;
AnimHeaderStruct animHeader;
uint16 width, height, bpp, var1;
@@ -791,30 +790,46 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {
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();
+ for (currentAnim = 0; currentAnim < NUM_MAX_ANIMDATA; currentAnim += animHeader.numFrames) {
+ // Initialize the number of frames variable to a sane number.
+ // This is needed when using continue later in this function.
+ animHeader.numFrames = 1;
+
+ // 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) {
continue;
}
+ // Alright, the animation entry looks to be valid so let's start handling it...
if (strcmp(currentPartName, name)) {
closePart();
loadPart(name);
@@ -823,13 +838,14 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {
animName = partBuffer[foundFileIdx].partName;
ptr = dataPtr = readBundleFile(foundFileIdx);
+ // isSpl and isMask are mutually exclusive cases
isSpl = (strstr(animName, ".SPL")) ? 1 : 0;
isMask = (strstr(animName, ".MSK")) ? 1 : 0;
if (isSpl) {
width = (uint16) partBuffer[foundFileIdx].unpackedSize;
height = 1;
- frame = 0;
+ animHeader.numFrames = 1;
type = ANIM_RAW;
} else {
Common::MemoryReadStream readS(ptr, 0x16);
@@ -843,25 +859,35 @@ void loadResourcesFromSave(Common::InSaveFile &fHandle, bool broken) {
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;
- }
+ loadRelatedPalette(animName);
+ transparentColor = getAnimTransparentColor(animName);
+ // Make sure we load at least one frame and also that we
+ // don't overflow the animDataTable by writing beyond its end.
+ animHeader.numFrames = CLIP<uint16>(animHeader.numFrames, 1, NUM_MAX_ANIMDATA - currentAnim);
+
+ // Load the frames
+ for (frame = 0; frame < animHeader.numFrames; frame++) {
+ // 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;
}
+
+ // Load a single frame
+ animDataTable[currentAnim + frame].load(ptr + frame * width * height, type, width, height, foundFileIdx, frame, name, transparentColor);
}
- ptr += frame * width * height;
- currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor);
free(dataPtr);
}
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..b0ce55f7ee 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;
@@ -101,7 +156,7 @@ 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);
+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 cf25f1d355..fddca078e5 100644
--- a/engines/cine/bg_list.cpp
+++ b/engines/cine/bg_list.cpp
@@ -63,6 +63,7 @@ void addSpriteFilledToBGList(int16 objIdx) {
void createBgIncrustListElement(int16 objIdx, int16 param) {
BGIncrust tmp;
+ tmp.unkPtr = 0;
tmp.objIdx = objIdx;
tmp.param = param;
tmp.x = objectTable[objIdx].x;
@@ -82,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();
@@ -90,6 +91,7 @@ void loadBgIncrustFromSave(Common::InSaveFile &fHandle) {
fHandle.readUint32BE();
fHandle.readUint32BE();
+ tmp.unkPtr = 0;
tmp.objIdx = fHandle.readUint16BE();
tmp.param = fHandle.readUint16BE();
tmp.x = fHandle.readUint16BE();
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.cpp b/engines/cine/cine.cpp
index efc33fadaf..f6778b6457 100644
--- a/engines/cine/cine.cpp
+++ b/engines/cine/cine.cpp
@@ -42,7 +42,6 @@
#include "cine/sound.h"
#include "cine/various.h"
-
namespace Cine {
Sound *g_sound;
@@ -70,6 +69,10 @@ CineEngine::~CineEngine() {
freeErrmessDat();
}
Common::clearAllSpecialDebugLevels();
+
+ free(palPtr);
+ free(partBuffer);
+ free(textDataPtr);
}
int CineEngine::init() {
diff --git a/engines/cine/cine.h b/engines/cine/cine.h
index 710840c17e..eaae555812 100644
--- a/engines/cine/cine.h
+++ b/engines/cine/cine.h
@@ -94,10 +94,17 @@ public:
Common::StringList _volumeResourceFiles;
StringPtrHashMap _volumeEntriesMap;
+ TextHandler _textHandler;
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();
@@ -107,6 +114,7 @@ private:
extern CineEngine *g_cine;
#define BOOT_PRC_NAME "AUTO00.PRC"
+#define COPY_PROT_FAIL_PRC_NAME "L201.ANI"
enum {
VAR_MOUSE_X_MODE = 253,
diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp
index 47446f2410..cbddf0fc59 100644
--- a/engines/cine/gfx.cpp
+++ b/engines/cine/gfx.cpp
@@ -337,7 +337,7 @@ int FWRenderer::drawChar(char character, int x, int y) {
x += 5;
} else if ((width = fontParamTable[(unsigned char)character].characterWidth)) {
idx = fontParamTable[(unsigned char)character].characterIdx;
- drawSpriteRaw(textTable[idx][0], textTable[idx][1], 16, 8, _backBuffer, x, y);
+ drawSpriteRaw(g_cine->_textHandler.textTable[idx][0], g_cine->_textHandler.textTable[idx][1], 16, 8, _backBuffer, x, y);
x += width + 1;
}
@@ -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
@@ -938,7 +987,7 @@ int OSRenderer::drawChar(char character, int x, int y) {
x += 5;
} else if ((width = fontParamTable[(unsigned char)character].characterWidth)) {
idx = fontParamTable[(unsigned char)character].characterIdx;
- drawSpriteRaw2(textTable[idx][0], 0, 16, 8, _backBuffer, x, y);
+ drawSpriteRaw2(g_cine->_textHandler.textTable[idx][0], 0, 16, 8, _backBuffer, x, y);
x += width + 1;
}
@@ -969,6 +1018,7 @@ void OSRenderer::drawBackground() {
/*! \brief Draw one overlay
* \param it Overlay info
+ * \todo Add handling of type 22 overlays
*/
void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
int len;
@@ -979,6 +1029,9 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
switch (it->type) {
// color sprite
case 0:
+ if (objectTable[it->objIdx].frame < 0) {
+ break;
+ }
sprite = animDataTable + objectTable[it->objIdx].frame;
len = sprite->_realWidth * sprite->_height;
mask = new byte[len];
@@ -988,6 +1041,13 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
delete[] mask;
break;
+ // bitmap
+ case 4:
+ if (objectTable[it->objIdx].frame >= 0) {
+ FWRenderer::renderOverlay(it);
+ }
+ break;
+
// masked background
case 20:
assert(it->objIdx < NUM_MAX_OBJECT);
@@ -1236,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
*/
@@ -1259,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 cfb828cf3c..e5e670c973 100644
--- a/engines/cine/main_loop.cpp
+++ b/engines/cine/main_loop.cpp
@@ -179,6 +179,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;
uint16 quitFlag;
@@ -186,6 +200,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
uint16 mouseButton;
quitFlag = 0;
+ exitEngine = 0;
if (_preLoad == false) {
resetBgIncrustList();
@@ -194,7 +209,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
errorVar = 0;
- addScriptToList0(bootScriptIdx);
+ addScriptToGlobalScripts(bootScriptIdx);
menuVar = 0;
@@ -234,13 +249,25 @@ void CineEngine::mainLoop(int bootScriptIdx) {
do {
stopMusicAfterFadeOut();
di = executePlayerInput();
+
+ // Clear the zoneQuery table (Operation Stealth specific)
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ for (uint i = 0; i < NUM_MAX_ZONE; i++) {
+ zoneQuery[i] = 0;
+ }
+ }
- 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.cpp b/engines/cine/object.cpp
index 7666f05352..c02e01c8ce 100644
--- a/engines/cine/object.cpp
+++ b/engines/cine/object.cpp
@@ -99,21 +99,36 @@ int removeOverlay(uint16 objIdx, uint16 param) {
/*! \brief Add new overlay sprite to the list
* \param objIdx Associate the overlay with this object
- * \param param Type of new overlay
+ * \param type Type of new overlay
* \todo Why are x, y, width and color left uninitialized?
*/
-void addOverlay(uint16 objIdx, uint16 param) {
+void addOverlay(uint16 objIdx, uint16 type) {
Common::List<overlay>::iterator it;
overlay tmp;
for (it = overlayList.begin(); it != overlayList.end(); ++it) {
+ // This is done for both Future Wars and Operation Stealth
if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask) {
break;
}
+
+ // There are additional checks in Operation Stealth's implementation
+ if (g_cine->getGameType() == Cine::GType_OS && (it->type == 2 || it->type == 3)) {
+ break;
+ }
+ }
+
+ // In Operation Stealth's implementation we might bail out early
+ if (g_cine->getGameType() == Cine::GType_OS && it != overlayList.end() && it->objIdx == objIdx && it->type == type) {
+ return;
}
tmp.objIdx = objIdx;
- tmp.type = param;
+ tmp.type = type;
+ tmp.x = 0;
+ tmp.y = 0;
+ tmp.width = 0;
+ tmp.color = 0;
overlayList.insert(it, tmp);
}
@@ -122,24 +137,22 @@ void addOverlay(uint16 objIdx, uint16 param) {
* \param objIdx Associate the overlay with this object
* \param param source background index
*/
-void addGfxElementA0(int16 objIdx, int16 param) {
+void addGfxElement(int16 objIdx, int16 param, int16 type) {
Common::List<overlay>::iterator it;
overlay tmp;
for (it = overlayList.begin(); it != overlayList.end(); ++it) {
- // wtf?!
- if (objectTable[it->objIdx].mask == objectTable[objIdx].mask &&
- (it->type == 2 || it->type == 3)) {
+ if (objectTable[it->objIdx].mask >= objectTable[objIdx].mask || it->type == 2 || it->type == 3) {
break;
}
}
- if (it != overlayList.end() && it->objIdx == objIdx && it->type == 20 && it->x == param) {
+ if (it != overlayList.end() && it->objIdx == objIdx && it->type == type && it->x == param) {
return;
}
tmp.objIdx = objIdx;
- tmp.type = 20;
+ tmp.type = type;
tmp.x = param;
tmp.y = 0;
tmp.width = 0;
@@ -153,11 +166,11 @@ void addGfxElementA0(int16 objIdx, int16 param) {
* \param param Remove overlay using this background
* \todo Check that it works
*/
-void removeGfxElementA0(int16 objIdx, int16 param) {
+void removeGfxElement(int16 objIdx, int16 param, int16 type) {
Common::List<overlay>::iterator it;
for (it = overlayList.begin(); it != overlayList.end(); ++it) {
- if (it->objIdx == objIdx && it->type == 20 && it->x == param) {
+ if (it->objIdx == objIdx && it->type == type && it->x == param) {
overlayList.erase(it);
return;
}
@@ -170,8 +183,12 @@ void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint1
objectTable[objIdx].mask = param3;
objectTable[objIdx].frame = param4;
- if (removeOverlay(objIdx, 0)) {
- addOverlay(objIdx, 0);
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ resetGfxEntityEntry(objIdx);
+ } else { // Future Wars
+ if (removeOverlay(objIdx, 0)) {
+ addOverlay(objIdx, 0);
+ }
}
}
@@ -199,9 +216,12 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) {
case 3:
objectTable[objIdx].mask = newValue;
- // TODO: Check this part against disassembly
- if (removeOverlay(objIdx, 0)) {
- addOverlay(objIdx, 0);
+ if (g_cine->getGameType() == Cine::GType_OS) { // Operation Stealth specific
+ resetGfxEntityEntry(objIdx);
+ } else { // Future Wars specific
+ if (removeOverlay(objIdx, 0)) {
+ addOverlay(objIdx, 0);
+ }
}
break;
case 4:
@@ -221,6 +241,29 @@ void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue) {
}
}
+/**
+ * Check if at least one of the range B's endpoints is inside range A,
+ * not counting the starting and ending points of range A.
+ * Used at least by Operation Stealth's opcode 0x8D i.e. 141.
+ */
+bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd) {
+ return (bStart > aStart && bStart < aEnd) || (bEnd > aStart && bEnd < aEnd);
+}
+
+uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2) {
+ assert(objIdx1 < NUM_MAX_OBJECT && objIdx2 < NUM_MAX_OBJECT);
+ const objectStruct &obj1 = objectTable[objIdx1];
+ const objectStruct &obj2 = objectTable[objIdx2];
+
+ if (compareRanges(obj1.x, obj1.x + xAdd1, obj2.x, obj2.x + xAdd2) &&
+ compareRanges(obj1.y, obj1.y + yAdd1, obj2.y, obj2.y + yAdd2) &&
+ compareRanges(obj1.mask, obj1.mask + maskAdd1, obj2.mask, obj2.mask + maskAdd2)) {
+ return kCmpEQ;
+ } else {
+ return 0;
+ }
+}
+
uint16 compareObjectParam(byte objIdx, byte type, int16 value) {
uint16 compareResult = 0;
int16 objectParam = getObjectParam(objIdx, type);
diff --git a/engines/cine/object.h b/engines/cine/object.h
index e7de39649d..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];
@@ -60,15 +60,17 @@ void loadObject(char *pObjectName);
void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint16 param4);
void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue);
-void addOverlay(uint16 objIdx, uint16 param);
+void addOverlay(uint16 objIdx, uint16 type);
int removeOverlay(uint16 objIdx, uint16 param);
-void addGfxElementA0(int16 objIdx, int16 param);
-void removeGfxElementA0(int16 objIdx, int16 param);
+void addGfxElement(int16 objIdx, int16 param, int16 type);
+void removeGfxElement(int16 objIdx, int16 param, int16 type);
int16 getObjectParam(uint16 objIdx, uint16 paramIdx);
void addObjectParam(byte objIdx, byte paramIdx, int16 newValue);
void subObjectParam(byte objIdx, byte paramIdx, int16 newValue);
+bool compareRanges(uint16 aStart, uint16 aEnd, uint16 bStart, uint16 bEnd);
+uint16 compareObjectParamRanges(uint16 objIdx1, uint16 xAdd1, uint16 yAdd1, uint16 maskAdd1, uint16 objIdx2, uint16 xAdd2, uint16 yAdd2, uint16 maskAdd2);
uint16 compareObjectParam(byte objIdx, byte param1, int16 param2);
} // End of namespace Cine
diff --git a/engines/cine/pal.h b/engines/cine/pal.h
index 70fcc0d98a..768cf0d27d 100644
--- a/engines/cine/pal.h
+++ b/engines/cine/pal.h
@@ -34,6 +34,8 @@ struct PalEntry {
byte pal2[16];
};
+extern PalEntry *palPtr;
+
void loadPal(const char *fileName);
void loadRelatedPalette(const char *fileName);
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/prc.cpp b/engines/cine/prc.cpp
index 402c97b1a6..27b1044620 100644
--- a/engines/cine/prc.cpp
+++ b/engines/cine/prc.cpp
@@ -40,8 +40,9 @@ ScriptList objectScripts;
/*! \todo Is script size of 0 valid?
* \todo Fix script dump code
+ * @return Was the loading successful?
*/
-void loadPrc(const char *pPrcName) {
+bool loadPrc(const char *pPrcName) {
byte i;
uint16 numScripts;
byte *scriptPtr, *dataPtr;
@@ -52,9 +53,9 @@ void loadPrc(const char *pPrcName) {
scriptTable.clear();
// This is copy protection. Used to hang the machine
- if (!scumm_stricmp(pPrcName, "L201.ANI")) {
+ if (!scumm_stricmp(pPrcName, COPY_PROT_FAIL_PRC_NAME)) {
exitEngine = 1;
- return;
+ return false;
}
checkDataDisk(-1);
@@ -107,6 +108,8 @@ void loadPrc(const char *pPrcName) {
}
}
#endif
+
+ return true;
}
} // End of namespace Cine
diff --git a/engines/cine/prc.h b/engines/cine/prc.h
index f5129d28b1..05bb240372 100644
--- a/engines/cine/prc.h
+++ b/engines/cine/prc.h
@@ -31,7 +31,7 @@ namespace Cine {
extern ScriptList globalScripts;
extern ScriptList objectScripts;
-void loadPrc(const char *pPrcName);
+bool loadPrc(const char *pPrcName);
} // End of namespace Cine
diff --git a/engines/cine/script.h b/engines/cine/script.h
index eeac0e8809..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();
@@ -237,7 +237,7 @@ protected:
int o2_playSample();
int o2_playSampleAlt();
int o2_op81();
- int o2_op82();
+ int o2_modifySeqListElement();
int o2_isSeqRunning();
int o2_gotoIfSupNearest();
int o2_gotoIfSupEquNearest();
@@ -258,10 +258,10 @@ protected:
int o2_useBgScroll();
int o2_setAdditionalBgVScroll();
int o2_op9F();
- int o2_addGfxElementA0();
- int o2_removeGfxElementA0();
- int o2_opA2();
- int o2_opA3();
+ int o2_addGfxElementType20();
+ int o2_removeGfxElementType20();
+ int o2_addGfxElementType21();
+ int o2_removeGfxElementType21();
int o2_loadMask22();
int o2_unloadMask22();
@@ -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 845120c99e..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++) {
@@ -1019,6 +1025,20 @@ int FWScript::o1_divVar() {
}
int FWScript::o1_compareVar() {
+ // WORKAROUND: A workaround for a script bug in script file CODE2.PRC
+ // in at least some of the Amiga and Atari ST versions of Future Wars.
+ // Fixes bug #2016647 (FW: crash with italian amiga version). A local
+ // variable 251 is compared against value 0 although it's quite apparent
+ // from the context in the script that instead global variable 251 should
+ // be compared against value 0. So looks like someone made a typo when
+ // making the scripts. Therefore we change that particular comparison
+ // from using the local variable 251 to using the global variable 251.
+ if (g_cine->getGameType() == Cine::GType_FW && scumm_stricmp(currentPrcName, "CODE2.PRC") == 0 &&
+ (g_cine->getPlatform() == Common::kPlatformAmiga || g_cine->getPlatform() == Common::kPlatformAtariST) &&
+ _script.getByte(_pos) == 251 && _script.getByte(_pos + 1) == 0 && _script.getWord(_pos + 2) == 0) {
+ return o1_compareGlobalVar();
+ }
+
byte varIdx = getNextByte();
byte varType = getNextByte();
@@ -1259,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;
}
@@ -1385,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;
@@ -1554,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;
@@ -1732,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);
@@ -1764,18 +1786,32 @@ int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneI
int16 lx = objectTable[objIdx].x + x;
int16 ly = objectTable[objIdx].y + y;
int16 idx;
+ int16 result = 0;
for (int16 i = 0; i < numZones; i++) {
idx = getZoneFromPositionRaw(page3Raw, lx + i, ly, 320);
- assert(idx >= 0 && idx <= NUM_MAX_ZONE);
+ assert(idx >= 0 && idx < NUM_MAX_ZONE);
+
+ // The zoneQuery table is updated here only in Operation Stealth
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ if (zoneData[idx] < NUM_MAX_ZONE) {
+ zoneQuery[zoneData[idx]]++;
+ }
+ }
if (zoneData[idx] == zoneIdx) {
- return 1;
+ result = 1;
+ // Future Wars breaks out early on the first match, but
+ // Operation Stealth doesn't because it needs to update
+ // the zoneQuery table for the whole loop's period.
+ if (g_cine->getGameType() == Cine::GType_FW) {
+ break;
+ }
}
}
- return 0;
+ return result;
}
uint16 compareVars(int16 a, int16 b) {
@@ -1792,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) {
@@ -1803,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) {
@@ -1814,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) {
}
////////////////////////////////////
@@ -2352,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;
}
@@ -2502,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;
}
@@ -2917,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 319fca5d3c..a764281758 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. */
@@ -202,7 +202,7 @@ const Opcode OSScript::_opcodeTable[] = {
/* 80 */
{ &FWScript::o2_removeSeq, "bb" },
{ &FWScript::o2_op81, "" }, /* TODO: Name this opcode properly. */
- { &FWScript::o2_op82, "bbwwb" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_modifySeqListElement, "bbwwb" },
{ &FWScript::o2_isSeqRunning, "bb" },
/* 84 */
{ &FWScript::o2_gotoIfSupNearest, "b" },
@@ -240,10 +240,10 @@ const Opcode OSScript::_opcodeTable[] = {
{ &FWScript::o2_setAdditionalBgVScroll, "c" },
{ &FWScript::o2_op9F, "ww" }, /* TODO: Name this opcode properly. */
/* A0 */
- { &FWScript::o2_addGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */
- { &FWScript::o2_removeGfxElementA0, "ww" }, /* TODO: Name this opcode properly. */
- { &FWScript::o2_opA2, "ww" }, /* TODO: Name this opcode properly. */
- { &FWScript::o2_opA3, "ww" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_addGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_removeGfxElementType20, "ww" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_addGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_removeGfxElementType21, "ww" }, /* TODO: Name this opcode properly. */
/* A4 */
{ &FWScript::o2_loadMask22, "b" }, /* TODO: Name this opcode properly. */
{ &FWScript::o2_unloadMask22, "b" }, /* TODO: Name this opcode properly. */
@@ -442,6 +442,7 @@ int FWScript::o2_removeSeq() {
}
/*! \todo Implement this instruction
+ * \note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
int FWScript::o2_op81() {
warning("STUB: o2_op81()");
@@ -449,23 +450,25 @@ int FWScript::o2_op81() {
return 0;
}
-/*! \todo Implement this instruction
- */
-int FWScript::o2_op82() {
+int FWScript::o2_modifySeqListElement() {
byte a = getNextByte();
byte b = getNextByte();
uint16 c = getNextWord();
uint16 d = getNextWord();
byte e = getNextByte();
- warning("STUB: o2_op82(%x, %x, %x, %x, %x)", a, b, c, d, e);
+ debugC(5, kCineDebugScript, "Line: %d: o2_modifySeqListElement(%d,%d,%d,%d,%d)", _line, a, b, c, d, e);
+
+ modifySeqListElement(a, 0, b, c, d, e);
return 0;
}
+/*! \todo Check whether this opcode's name is backwards (i.e. should it be o2_isSeqNotRunning?)
+ */
int FWScript::o2_isSeqRunning() {
byte a = getNextByte();
byte b = getNextByte();
- debugC(5, kCineDebugScript, "Line: %d: OP83(%d,%d) -> TODO", _line, a, b);
+ debugC(5, kCineDebugScript, "Line: %d: o2_isSeqRunning(%d,%d)", _line, a, b);
if (isSeqRunning(a, 0, b)) {
_compare = 1;
@@ -593,19 +596,18 @@ int FWScript::o2_stopObjectScript() {
return 0;
}
-/*! \todo Implement this instruction
- */
int FWScript::o2_op8D() {
- uint16 a = getNextWord();
- uint16 b = getNextWord();
- uint16 c = getNextWord();
- uint16 d = getNextWord();
- uint16 e = getNextWord();
- uint16 f = getNextWord();
- uint16 g = getNextWord();
- uint16 h = getNextWord();
- warning("STUB: o2_op8D(%x, %x, %x, %x, %x, %x, %x, %x)", a, b, c, d, e, f, g, h);
- // _currentScriptElement->compareResult = ...
+ uint16 objIdx1 = getNextWord();
+ uint16 xAdd1 = getNextWord();
+ uint16 yAdd1 = getNextWord();
+ uint16 maskAdd1 = getNextWord();
+ uint16 objIdx2 = getNextWord();
+ uint16 xAdd2 = getNextWord();
+ uint16 yAdd2 = getNextWord();
+ uint16 maskAdd2 = getNextWord();
+ debugC(5, kCineDebugScript, "Line: %d: o2_op8D(%d, %d, %d, %d, %d, %d, %d, %d)", _line, objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2);
+
+ _compare = compareObjectParamRanges(objIdx1, xAdd1, yAdd1, maskAdd1, objIdx2, xAdd2, yAdd2, maskAdd2);
return 0;
}
@@ -649,16 +651,15 @@ int FWScript::o2_loadBg() {
return 0;
}
-/*! \todo Check the current implementation for correctness
- */
int FWScript::o2_wasZoneChecked() {
byte param = getNextByte();
- _compare = (param < 16 && zoneData[param]);
+ _compare = (param < NUM_MAX_ZONE && zoneQuery[param]) ? 1 : 0;
debugC(5, kCineDebugScript, "Line: %d: o2_wasZoneChecked(%d)", _line, param);
return 0;
}
/*! \todo Implement this instruction
+ * \note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
int FWScript::o2_op9B() {
uint16 a = getNextWord();
@@ -674,6 +675,7 @@ int FWScript::o2_op9B() {
}
/*! \todo Implement this instruction
+ * \note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
int FWScript::o2_op9C() {
uint16 a = getNextWord();
@@ -713,6 +715,7 @@ int FWScript::o2_setAdditionalBgVScroll() {
}
/*! \todo Implement this instruction
+ * \note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
int FWScript::o2_op9F() {
warning("o2_op9F()");
@@ -721,42 +724,36 @@ int FWScript::o2_op9F() {
return 0;
}
-int FWScript::o2_addGfxElementA0() {
+int FWScript::o2_addGfxElementType20() {
uint16 param1 = getNextWord();
uint16 param2 = getNextWord();
- debugC(5, kCineDebugScript, "Line: %d: addGfxElementA0(%d,%d)", _line, param1, param2);
- addGfxElementA0(param1, param2);
+ debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType20(%d,%d)", _line, param1, param2);
+ addGfxElement(param1, param2, 20);
return 0;
}
-/*! \todo Implement this instruction
- */
-int FWScript::o2_removeGfxElementA0() {
+int FWScript::o2_removeGfxElementType20() {
uint16 idx = getNextWord();
uint16 param = getNextWord();
- warning("STUB? o2_removeGfxElementA0(%x, %x)", idx, param);
- removeGfxElementA0(idx, param);
+ debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType20(%d,%d)", _line, idx, param);
+ removeGfxElement(idx, param, 20);
return 0;
}
-/*! \todo Implement this instruction
- */
-int FWScript::o2_opA2() {
+int FWScript::o2_addGfxElementType21() {
uint16 a = getNextWord();
uint16 b = getNextWord();
- warning("STUB: o2_opA2(%x, %x)", a, b);
- // addGfxElementA2();
+ debugC(5, kCineDebugScript, "Line: %d: o2_addGfxElementType21(%d,%d)", _line, a, b);
+ addGfxElement(a, b, 21);
return 0;
}
-/*! \todo Implement this instruction
- */
-int FWScript::o2_opA3() {
+int FWScript::o2_removeGfxElementType21() {
uint16 a = getNextWord();
uint16 b = getNextWord();
- warning("STUB: o2_opA3(%x, %x)", a, b);
- // removeGfxElementA2();
+ debugC(5, kCineDebugScript, "Line: %d: o2_removeGfxElementType21(%d,%d)", _line, a, b);
+ removeGfxElement(a, b, 21);
return 0;
}
diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp
index e808de6922..f26032fe98 100644
--- a/engines/cine/sound.cpp
+++ b/engines/cine/sound.cpp
@@ -249,6 +249,7 @@ AdlibSoundDriver::AdlibSoundDriver(Audio::Mixer *mixer)
AdlibSoundDriver::~AdlibSoundDriver() {
_mixer->stopHandle(_soundHandle);
+ OPLDestroy(_opl);
}
void AdlibSoundDriver::setupChannel(int channel, const byte *data, int instrument, int volume) {
diff --git a/engines/cine/texte.cpp b/engines/cine/texte.cpp
index 9b4b83f420..e4fd334926 100644
--- a/engines/cine/texte.cpp
+++ b/engines/cine/texte.cpp
@@ -31,8 +31,6 @@ namespace Cine {
byte *textDataPtr;
-byte textTable[256][2][16 * 8];
-
const char **failureMessages;
const CommandeType *defaultActionCommand;
const CommandeType *systemMenu;
@@ -77,14 +75,14 @@ void loadTextData(const char *pFileName, byte *pDestinationBuffer) {
loadRelatedPalette(pFileName);
for (i = 0; i < numCharacters; i++) {
- gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 16, 8);
- generateMask(textTable[i][0], textTable[i][1], 16 * 8, 0);
+ gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 16, 8);
+ generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 16 * 8, 0);
tempBuffer += dataSize;
}
} else {
for (i = 0; i < 90; i++) {
- gfxConvertSpriteToRaw(textTable[i][0], tempBuffer, 8, 8);
- generateMask(textTable[i][0], textTable[i][1], 8 * 8, 0);
+ gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][0], tempBuffer, 8, 8);
+ generateMask(g_cine->_textHandler.textTable[i][0], g_cine->_textHandler.textTable[i][1], 8 * 8, 0);
tempBuffer += 0x40;
}
}
diff --git a/engines/cine/texte.h b/engines/cine/texte.h
index ae82832aea..f471c3c49e 100644
--- a/engines/cine/texte.h
+++ b/engines/cine/texte.h
@@ -34,7 +34,10 @@ namespace Cine {
typedef char CommandeType[20];
extern byte *textDataPtr;
-extern byte textTable[256][2][16 * 8];
+
+struct TextHandler {
+ byte textTable[256][2][16 * 8];
+};
extern const char **failureMessages;
extern const CommandeType *defaultActionCommand;
diff --git a/engines/cine/unpack.cpp b/engines/cine/unpack.cpp
index dcd3181242..5d85ff6cab 100644
--- a/engines/cine/unpack.cpp
+++ b/engines/cine/unpack.cpp
@@ -111,7 +111,7 @@ bool CineUnpacker::unpack(const byte *src, uint srcLen, byte *dst, uint dstLen)
while (_dst >= _dstBegin && !_error) {
/*
Bits => Action:
- 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..9)
+ 0 0 => unpackRawBytes(3 bits + 1) i.e. unpackRawBytes(1..8)
1 1 1 => unpackRawBytes(8 bits + 9) i.e. unpackRawBytes(9..264)
0 1 => copyRelocatedBytes(8 bits, 2) i.e. copyRelocatedBytes(0..255, 2)
1 0 0 => copyRelocatedBytes(9 bits, 3) i.e. copyRelocatedBytes(0..511, 3)
diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp
index 9b98ddb253..2fcb015fcd 100644
--- a/engines/cine/various.cpp
+++ b/engines/cine/various.cpp
@@ -95,6 +95,9 @@ int16 saveVar2;
byte isInPause = 0;
+// TODO: Implement inputVar0's changes in the program
+// Currently inputVar0 isn't updated anywhere even though it's used at least in processSeqListElement.
+uint16 inputVar0 = 0;
byte inputVar1 = 0;
uint16 inputVar2 = 0, inputVar3 = 0;
@@ -112,6 +115,7 @@ int16 objListTab[20];
uint16 exitEngine;
uint16 zoneData[NUM_MAX_ZONE];
+uint16 zoneQuery[NUM_MAX_ZONE]; //!< Only exists in Operation Stealth
void stopMusicAfterFadeOut(void) {
@@ -132,6 +136,7 @@ void runObjectScript(int16 entryIdx) {
*/
void addPlayerCommandMessage(int16 cmd) {
overlay tmp;
+ memset(&tmp, 0, sizeof(tmp));
tmp.objIdx = cmd;
tmp.type = 3;
@@ -224,6 +229,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];
@@ -241,21 +383,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) {
@@ -278,7 +542,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();
@@ -294,127 +558,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
- */
-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();
@@ -424,7 +571,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;
@@ -458,124 +607,381 @@ 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;
+ }
+}
- currentDisk = fHandle->readUint16BE();
+bool loadObjectTable(Common::SeekableReadStream &in) {
+ in.readUint16BE(); // Entry count
+ in.readUint16BE(); // Entry size
- fHandle->read(currentPartName, 13);
- fHandle->read(currentDatName, 13);
+ 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();
+}
- saveVar2 = fHandle->readSint16BE();
+bool loadZoneData(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 16; i++) {
+ zoneData[i] = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
- fHandle->read(currentPrcName, 13);
- fHandle->read(currentRelName, 13);
- fHandle->read(currentMsgName, 13);
- fHandle->read(bgName, 13);
- fHandle->read(currentCtName, 13);
+bool loadCommandVariables(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 4; i++) {
+ commandVar3[i] = in.readUint16BE();
+ }
+ return !in.ioFailed();
+}
- checkDataDisk(currentDisk);
+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();
+}
- if (strlen(currentPartName)) {
- loadPart(currentPartName);
+bool loadGlobalScripts(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadScriptFromSave(in, true);
}
+ return !in.ioFailed();
+}
- if (strlen(currentPrcName)) {
- loadPrc(currentPrcName);
+bool loadObjectScripts(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadScriptFromSave(in, false);
}
+ return !in.ioFailed();
+}
- if (strlen(currentRelName)) {
- loadRel(currentRelName);
+bool loadOverlayList(Common::SeekableReadStream &in) {
+ int size = in.readSint16BE();
+ for (int i = 0; i < size; i++) {
+ loadOverlayFromSave(in);
}
+ return !in.ioFailed();
+}
- if (strlen(bgName)) {
- loadBg(bgName);
- }
+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();
+}
- if (strlen(currentCtName)) {
- loadCtFW(currentCtName);
+bool loadZoneQuery(Common::SeekableReadStream &in) {
+ for (int i = 0; i < 16; i++) {
+ zoneQuery[i] = in.readUint16BE();
}
+ return !in.ioFailed();
+}
- fHandle->readUint16BE();
- fHandle->readUint16BE();
+bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
+ char musicName[13];
+ char bgNames[8][13];
- for (i = 0; i < 255; i++) {
- objectTable[i].x = fHandle->readSint16BE();
- objectTable[i].y = fHandle->readSint16BE();
- objectTable[i].mask = fHandle->readUint16BE();
- objectTable[i].frame = fHandle->readSint16BE();
- objectTable[i].costume = fHandle->readSint16BE();
- fHandle->read(objectTable[i].name, 20);
- objectTable[i].part = fHandle->readUint16BE();
+ // 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;
}
- renderer->restorePalette(*fHandle);
+ // 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]);
+ }
- globalVars.load(*fHandle, NUM_MAX_VAR - 1);
+ // Add backgrounds 1-7 (Uses addBackground)
+ for (int i = 1; i < 8; i++) {
+ if (strlen(bgNames[i])) {
+ addBackground(bgNames[i], i);
+ }
+ }
- for (i = 0; i < 16; i++) {
- zoneData[i] = fHandle->readUint16BE();
+ if (strlen(currentCtName)) {
+ loadCtOS(currentCtName);
+ }
}
- for (i = 0; i < 4; i++) {
- commandVar3[i] = fHandle->readUint16BE();
+ 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);
}
- fHandle->read(commandBuffer, 0x50);
- renderer->setCommand(commandBuffer);
+ // TODO: Add current music loading and playing here
+ // TODO: Palette handling?
- renderer->_cmdY = fHandle->readUint16BE();
+ 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");
+ }
- bgVar0 = fHandle->readUint16BE();
- allowPlayerInput = fHandle->readUint16BE();
- playerCommand = fHandle->readSint16BE();
- commandVar1 = fHandle->readSint16BE();
- isDrawCommandEnabled = fHandle->readUint16BE();
- var5 = fHandle->readUint16BE();
- var4 = fHandle->readUint16BE();
- var3 = fHandle->readUint16BE();
- var2 = fHandle->readUint16BE();
- commandVar2 = fHandle->readSint16BE();
+ return !in.ioFailed();
+}
- renderer->_messageBg = fHandle->readUint16BE();
+bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) {
+ char bgName[13];
- fHandle->readUint16BE();
- fHandle->readUint16BE();
+ // At savefile position 0x0000:
+ currentDisk = in.readUint16BE();
- loadResourcesFromSave(*fHandle, broken);
+ // At 0x0002:
+ in.read(currentPartName, 13);
+ // At 0x000F:
+ in.read(currentDatName, 13);
- // TODO: handle screen params (really required ?)
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
- fHandle->readUint16BE();
+ // At 0x001C:
+ saveVar2 = in.readSint16BE();
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadScriptFromSave(fHandle, true);
+ // At 0x001E:
+ in.read(currentPrcName, 13);
+ // At 0x002B:
+ in.read(currentRelName, 13);
+ // At 0x0038:
+ in.read(currentMsgName, 13);
+ // At 0x0045:
+ in.read(bgName, 13);
+ // At 0x0052:
+ in.read(currentCtName, 13);
+
+ checkDataDisk(currentDisk);
+
+ if (strlen(currentPartName)) {
+ loadPart(currentPartName);
}
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadScriptFromSave(fHandle, false);
+ if (strlen(currentPrcName)) {
+ loadPrc(currentPrcName);
}
- size = fHandle->readSint16BE();
- for (i = 0; i < size; i++) {
- loadOverlayFromSave(*fHandle);
+ if (strlen(currentRelName)) {
+ loadRel(currentRelName);
}
- loadBgIncrustFromSave(*fHandle);
+ if (strlen(bgName)) {
+ loadBg(bgName);
+ }
- delete fHandle;
+ if (strlen(currentCtName)) {
+ loadCtFW(currentCtName);
+ }
+
+ // At 0x005F:
+ loadObjectTable(in);
+
+ // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32):
+ renderer->restorePalette(in);
+
+ // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2):
+ globalVars.load(in, NUM_MAX_VAR);
+
+ // At 0x2281 (i.e. 0x2083 + 255 * 2):
+ loadZoneData(in);
+
+ // At 0x22A1 (i.e. 0x2281 + 16 * 2):
+ loadCommandVariables(in);
+
+ // At 0x22A9 (i.e. 0x22A1 + 4 * 2):
+ in.read(commandBuffer, 0x50);
+ renderer->setCommand(commandBuffer);
+
+ // At 0x22F9 (i.e. 0x22A9 + 0x50):
+ renderer->_cmdY = in.readUint16BE();
+
+ // At 0x22FB:
+ bgVar0 = in.readUint16BE();
+ // At 0x22FD:
+ allowPlayerInput = in.readUint16BE();
+ // At 0x22FF:
+ playerCommand = in.readSint16BE();
+ // At 0x2301:
+ commandVar1 = in.readSint16BE();
+ // At 0x2303:
+ isDrawCommandEnabled = in.readUint16BE();
+ // At 0x2305:
+ var5 = in.readUint16BE();
+ // At 0x2307:
+ var4 = in.readUint16BE();
+ // At 0x2309:
+ var3 = in.readUint16BE();
+ // At 0x230B:
+ var2 = in.readUint16BE();
+ // At 0x230D:
+ commandVar2 = in.readSint16BE();
+
+ // At 0x230F:
+ renderer->_messageBg = in.readUint16BE();
+
+ // At 0x2311:
+ in.readUint16BE();
+ // At 0x2313:
+ in.readUint16BE();
+
+ // At 0x2315:
+ loadResourcesFromSave(in, saveGameFormat);
+
+ 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;
@@ -585,135 +991,139 @@ bool CineEngine::makeLoad(char *saveName) {
}*/
}
- return true;
+ return !in.ioFailed();
}
-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);
}
@@ -854,6 +1264,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
@@ -1510,12 +2003,22 @@ void mainLoopSub6(void) {
void checkForPendingDataLoad(void) {
if (newPrcName[0] != 0) {
- loadPrc(newPrcName);
+ bool loadPrcOk = loadPrc(newPrcName);
strcpy(currentPrcName, newPrcName);
strcpy(newPrcName, "");
- addScriptToList0(1);
+ // Check that the loading of the script file was successful before
+ // trying to add script 1 from it to the global scripts list. This
+ // fixes a crash when failing copy protection in Amiga or Atari ST
+ // versions of Future Wars.
+ if (loadPrcOk) {
+ 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).
+ warning("checkForPendingDataLoad: loadPrc(%s) failed", currentPrcName);
+ }
}
if (newRelName[0] != 0) {
@@ -1582,16 +2085,19 @@ void removeSeq(uint16 param1, uint16 param2, uint16 param3) {
}
}
-uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {
+bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {
Common::List<SeqListElement>::iterator it;
for (it = seqList.begin(); it != seqList.end(); ++it) {
if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) {
- return 1;
+ // Just to be on the safe side there's a restriction of the
+ // addition's result to 16-bit arithmetic here like in the
+ // original. It's possible that it's not strictly needed.
+ return ((it->var14 + it->var16) & 0xFFFF) == 0;
}
}
- return 0;
+ return true;
}
void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8) {
@@ -1618,6 +2124,19 @@ void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, i
seqList.insert(it, tmp);
}
+void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4) {
+ // Find a suitable list element and modify it
+ for (Common::List<SeqListElement>::iterator it = seqList.begin(); it != seqList.end(); ++it) {
+ if (it->objIdx == objIdx && it->var4 == var4Test) {
+ it->varC = param1;
+ it->var18 = param2;
+ it->var1A = param3;
+ it->var10 = it->var12 = param4;
+ break;
+ }
+ }
+}
+
void computeMove1(SeqListElement &element, int16 x, int16 y, int16 param1,
int16 param2, int16 x2, int16 y2) {
element.var16 = 0;
@@ -1662,105 +2181,51 @@ uint16 computeMove2(SeqListElement &element) {
return returnVar;
}
-// sort all the gfx stuff...
-
-void resetGfxEntityEntry(uint16 objIdx) {
-#if 0
- overlayHeadElement* tempHead = &overlayHead;
- byte* var_16 = NULL;
- uint16 var_10 = 0;
- uint16 var_12 = 0;
- overlayHeadElement* currentHead = tempHead->next;
- byte* var_1A = NULL;
- overlayHeadElement* var1E = &overlayHead;
-
- while (currentHead) {
- tempHead2 = currentHead->next;
-
- if (currentHead->objIdx == objIdx && currentHead->type!=2 && currentHead->type!=3 && currentHead->type!=0x14) {
- tempHead->next = tempHead2;
-
- if (tempHead2) {
- tempHead2->previous = currentHead->previous;
- } else {
- seqVar0 = currentHead->previous;
- }
-
- var_22 = var_16;
-
- if (!var_22) {
- // todo: goto?
- }
-
- var_22->previous = currentHead;
- } else {
- }
-
- if (currentHead->type == 0x14) {
- } else {
- }
-
- if (currentHead->type == 0x2 || currentHead->type == 0x3) {
- si = 10000;
- } else {
- si = objectTable[currentHead->objIdx];
- }
-
- if (objectTable[objIdx]>si) {
- var1E = currentHead;
- }
-
- tempHead = tempHead->next;
-
- }
-
- if (var_1A) {
- currentHead = var_16;
- var_22 = var_1E->next;
- var_1E->next = currentHead;
- var_1A->next = var_22;
-
- if (var_1E != &gfxEntityHead) {
- currentHead->previous = var_1E;
- }
-
- if (!var_22) {
- seqVar0 = var_1A;
- } else {
- var_22->previous = var_1A;
- }
-
- }
-#endif
-}
-
-uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &element, uint16 param3, int16 *param4) {
- const byte *currentPtr = ptr;
- const byte *ptrData;
- const byte *ptr2;
+uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &element, uint16 param3, int16 *param4) {
+ const int8 *ptrData;
+ const int8 *ptr2;
int16 di;
- assert(ptr);
- assert(param4);
+ 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);
- dummyU16 = READ_BE_UINT16((currentPtr + param1 * 2) + 8);
+ // In the original an error string is set and 0 is returned if the following doesn't hold
+ assert(ptr);
+ // We probably could just use a local variable here instead of the dummyU16 but
+ // haven't checked if this has any side-effects so keeping it this way still.
+ dummyU16 = READ_BE_UINT16(ptr + param1 * 2 + 8);
ptrData = ptr + dummyU16;
+ // In the original an error string is set and 0 is returned if the following doesn't hold
assert(*ptrData);
di = (objectTable[objIdx].costume + 1) % (*ptrData);
- ptr2 = (ptrData + (di * 8)) + 1;
-
+ ++ptrData; // Jump over the just read byte
+ // Here ptr2 seems to be indexing a table of structs (8 bytes per struct):
+ // struct {
+ // int8 x; // 0 (Used with checkCollision)
+ // int8 y; // 1 (Used with checkCollision)
+ // int8 numZones; // 2 (Used with checkCollision)
+ // int8 var3; // 3 (Not used in this function)
+ // int8 xAdd; // 4 (Used with an object)
+ // int8 yAdd; // 5 (Used with an object)
+ // int8 maskAdd; // 6 (Used with an object)
+ // int8 frameAdd; // 7 (Used with an object)
+ // };
+ ptr2 = ptrData + di * 8;
+
+ // We might probably safely discard the AND by 1 here because
+ // at least in the original checkCollision returns always 0 or 1.
if ((checkCollision(objIdx, ptr2[0], ptr2[1], ptr2[2], ptr[0]) & 1)) {
return 0;
}
- objectTable[objIdx].x += (int8)ptr2[4];
- objectTable[objIdx].y += (int8)ptr2[5];
- objectTable[objIdx].mask += (int8)ptr2[6];
+ objectTable[objIdx].x += ptr2[4];
+ objectTable[objIdx].y += ptr2[5];
+ objectTable[objIdx].mask += ptr2[6];
- if (objectTable[objIdx].frame) {
+ if (ptr2[6]) {
resetGfxEntityEntry(objIdx);
}
@@ -1769,19 +2234,79 @@ uint16 addAni(uint16 param1, uint16 objIdx, const byte *ptr, SeqListElement &ele
if (param3 || !element.var14) {
objectTable[objIdx].costume = di;
} else {
+ assert(param4);
*param4 = di;
}
return 1;
}
+/*!
+ * Permutates the overlay list into a different order according to some logic.
+ * \todo Check this function for correctness (Wasn't very easy to reverse engineer so there may be errors)
+ */
+void resetGfxEntityEntry(uint16 objIdx) {
+ Common::List<overlay>::iterator it, bObjsCutPoint;
+ Common::List<overlay> aReverseObjs, bObjs;
+ bool foundCutPoint = false;
+
+ // Go through the overlay list and partition the whole list into two categories (Type A and type B objects)
+ for (it = overlayList.begin(); it != overlayList.end(); ++it) {
+ if (it->objIdx == objIdx && it->type != 2 && it->type != 3) { // Type A object
+ aReverseObjs.push_front(*it);
+ } else { // Type B object
+ bObjs.push_back(*it);
+ uint16 objectMask;
+ if (it->type == 2 || it->type == 3) {
+ objectMask = 10000;
+ } else {
+ objectMask = objectTable[it->objIdx].mask;
+ }
+
+ if (objectTable[objIdx].mask > objectMask) { // Check for B objects' cut point
+ bObjsCutPoint = bObjs.reverse_begin();
+ foundCutPoint = true;
+ }
+ }
+ }
+
+ // Recreate the overlay list in a different order.
+ overlayList.clear();
+ if (foundCutPoint) {
+ // If a cut point was found the order is:
+ // B objects before the cut point, the cut point, A objects in reverse order, B objects after cut point.
+ ++bObjsCutPoint; // Include the cut point in the first list insertion
+ overlayList.insert(overlayList.end(), bObjs.begin(), bObjsCutPoint);
+ overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end());
+ overlayList.insert(overlayList.end(), bObjsCutPoint, bObjs.end());
+ } else {
+ // If no cut point was found the order is:
+ // A objects in reverse order, B objects.
+ overlayList.insert(overlayList.end(), aReverseObjs.begin(), aReverseObjs.end());
+ overlayList.insert(overlayList.end(), bObjs.begin(), bObjs.end());
+ }
+}
+
void processSeqListElement(SeqListElement &element) {
int16 x = objectTable[element.objIdx].x;
int16 y = objectTable[element.objIdx].y;
- const byte *ptr1 = animDataTable[element.frame].data();
+ const int8 *ptr1 = (const int8 *) animDataTable[element.frame].data();
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++;
@@ -1791,22 +2316,44 @@ void processSeqListElement(SeqListElement &element) {
element.var12 = 0;
if (ptr1) {
- uint16 param1 = ptr1[1];
- uint16 param2 = ptr1[2];
+ int16 param1 = ptr1[1];
+ int16 param2 = ptr1[2];
if (element.varC != 255) {
- // FIXME: Why is this here? Fingolfin gets lots of these
- // in his copy of Operation Stealth (value 0 or 236) under
- // Mac OS X. Maybe it's a endian issue? At least the graphics
- // in the copy protection screen are partially messed up.
- warning("processSeqListElement: varC = %d", element.varC);
- }
-
- if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) {
- computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]);
+ int16 x2 = element.var18;
+ int16 y2 = element.var1A;
+ if (element.varC) {
+ x2 += objectTable[element.varC].x;
+ y2 += objectTable[element.varC].y;
+ }
+ computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, x2, y2);
} else {
- element.var16 = 0;
- element.var14 = 0;
+ if (inputVar0 && allowPlayerInput) {
+ int16 adder = param1 + 1;
+ if (inputVar0 != 1) {
+ adder = -adder;
+ }
+ // FIXME: In Operation Stealth's disassembly global variable 251 is used here
+ // but it's named as VAR_MOUSE_Y_MODE in ScummVM. Is it correct or a
+ // left over from Future Wars's reverse engineering?
+ globalVars[VAR_MOUSE_X_POS] = globalVars[251] = ptr1[4] + x + adder;
+ }
+
+ if (inputVar1 && allowPlayerInput) {
+ int16 adder = param2 + 1;
+ if (inputVar1 != 1) {
+ adder = -adder;
+ }
+ // TODO: Name currently unnamed global variable 252
+ globalVars[VAR_MOUSE_Y_POS] = globalVars[252] = ptr1[5] + y + adder;
+ }
+
+ if (globalVars[VAR_MOUSE_X_POS] || globalVars[VAR_MOUSE_Y_POS]) {
+ computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, globalVars[VAR_MOUSE_X_POS], globalVars[VAR_MOUSE_Y_POS]);
+ } else {
+ element.var16 = 0;
+ element.var14 = 0;
+ }
}
var_10 = computeMove2(element);
@@ -1847,14 +2394,14 @@ void processSeqListElement(SeqListElement &element) {
}
}
- if (element.var16 + element.var14) {
+ if (element.var16 + element.var14 == 0) {
if (element.var1C) {
if (element.var1E) {
objectTable[element.objIdx].costume = 0;
element.var1E = 0;
}
- addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, (int16 *) & var2);
+ addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, &var_2);
}
}
diff --git a/engines/cine/various.h b/engines/cine/various.h
index 91662c16ff..d87679ca08 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;
@@ -130,16 +130,20 @@ struct SelectedObjStruct {
#define NUM_MAX_ZONE 16
extern uint16 zoneData[NUM_MAX_ZONE];
+extern uint16 zoneQuery[NUM_MAX_ZONE];
void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 param5);
void removeMessages();
void removeSeq(uint16 param1, uint16 param2, uint16 param3);
-uint16 isSeqRunning(uint16 param1, uint16 param2, uint16 param3);
+bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3);
void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8);
+void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4);
void processSeqList(void);
+void resetGfxEntityEntry(uint16 objIdx);
+
bool makeTextEntryMenu(const char *caption, char *string, int strLen, int y);
} // End of namespace Cine
diff --git a/engines/cruise/cruise_main.h b/engines/cruise/cruise_main.h
index 60afe5fa4c..c9c27ada49 100644
--- a/engines/cruise/cruise_main.h
+++ b/engines/cruise/cruise_main.h
@@ -28,7 +28,7 @@
#include <string.h>
#include <stdlib.h>
-#include <assert.h>
+#include <assert.h> // FIXME: WINCE: this is not needed/not portable (probably applies to all above includes)
#include "common/scummsys.h"
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/animation.cpp b/engines/drascula/animation.cpp
index feb6cb93ca..06868494b5 100644
--- a/engines/drascula/animation.cpp
+++ b/engines/drascula/animation.cpp
@@ -372,7 +372,11 @@ void DrasculaEngine::animation_1_1() {
break;
clearRoom();
- playMusic(2);
+ if (_lang == kSpanish)
+ playMusic(31);
+ else
+ playMusic(2);
+
pause(5);
playFLI("intro.bin", 12);
term_int = 1;
@@ -1669,7 +1673,7 @@ void DrasculaEngine::animation_12_5() {
const int frusky_x[] = {100, 139, 178, 217, 100, 178, 217, 139, 100, 139};
const int elfrusky_x[] = {1, 68, 135, 1, 68, 135, 1, 68, 135, 68, 1, 135, 68, 135, 68};
int color, component;
- char fade;
+ signed char fade;
playMusic(26);
updateRoom();
diff --git a/engines/drascula/drascula.h b/engines/drascula/drascula.h
index ce67cc2c0e..8bb73d8dd1 100644
--- a/engines/drascula/drascula.h
+++ b/engines/drascula/drascula.h
@@ -245,7 +245,7 @@ public:
void loadPic(const char *NamePcc, byte *targetSurface, int colorCount = 1);
- typedef char DacPalette256[256][3];
+ typedef signed char DacPalette256[256][3];
void setRGB(byte *pal, int plt);
void assignDefaultPalette();
@@ -328,7 +328,7 @@ public:
int curHeight, curWidth, feetHeight;
int talkHeight, talkWidth;
int floorX1, floorY1, floorX2, floorY2;
- int near, far;
+ int lowerLimit, upperLimit;
int trackFinal, walkToObject;
int objExit;
int timeDiff, startTime;
@@ -397,7 +397,7 @@ public:
void playFLI(const char *filefli, int vel);
void fadeFromBlack(int fadeSpeed);
void fadeToBlack(int fadeSpeed);
- char adjustToVGA(char value);
+ signed char adjustToVGA(signed char value);
void color_abc(int cl);
void centerText(const char *,int,int);
void playSound(int soundNum);
diff --git a/engines/drascula/graphics.cpp b/engines/drascula/graphics.cpp
index 64591a856e..67993bfb6c 100644
--- a/engines/drascula/graphics.cpp
+++ b/engines/drascula/graphics.cpp
@@ -89,31 +89,21 @@ void DrasculaEngine::setCursorTable() {
}
void DrasculaEngine::loadPic(const char *NamePcc, byte *targetSurface, int colorCount) {
- unsigned int con, x = 0;
- unsigned int fExit = 0;
- byte ch, rep;
+ uint dataSize = 0;
+ byte *pcxData;
_arj.open(NamePcc);
if (!_arj.isOpen())
error("missing game data %s %c", NamePcc, 7);
- _arj.seek(128);
- while (!fExit) {
- ch = _arj.readByte();
- rep = 1;
- if ((ch & 192) == 192) {
- rep = (ch & 63);
- ch = _arj.readByte();
- }
- for (con = 0; con < rep; con++) {
- x++;
- if (x > 64000) {
- fExit = 1;
- break;
- }
- *targetSurface++ = ch;
- }
- }
+ dataSize = _arj.size() - 128 - (256 * 3);
+ pcxData = (byte *)malloc(dataSize);
+
+ _arj.seek(128, SEEK_SET);
+ _arj.read(pcxData, dataSize);
+
+ decodeRLE(pcxData, targetSurface);
+ free(pcxData);
for (int i = 0; i < 256; i++) {
cPal[i * 3 + 0] = _arj.readByte();
@@ -141,7 +131,6 @@ void DrasculaEngine::showFrame(bool firstFrame) {
memcpy(prevFrame, VGA, 64000);
decodeRLE(pcxData, VGA);
-
free(pcxData);
if (!firstFrame)
diff --git a/engines/drascula/palette.cpp b/engines/drascula/palette.cpp
index ad57bce618..6a93f21e55 100644
--- a/engines/drascula/palette.cpp
+++ b/engines/drascula/palette.cpp
@@ -87,12 +87,12 @@ void DrasculaEngine::color_abc(int cl) {
setPalette((byte *)&gamePalette);
}
-char DrasculaEngine::adjustToVGA(char value) {
+signed char DrasculaEngine::adjustToVGA(signed char value) {
return (value & 0x3F) * (value > 0);
}
void DrasculaEngine::fadeToBlack(int fadeSpeed) {
- char fade;
+ signed char fade;
unsigned int color, component;
DacPalette256 palFade;
@@ -110,7 +110,7 @@ void DrasculaEngine::fadeToBlack(int fadeSpeed) {
}
void DrasculaEngine::fadeFromBlack(int fadeSpeed) {
- char fade;
+ signed char fade;
unsigned int color, component;
DacPalette256 palFade;
@@ -186,7 +186,7 @@ void DrasculaEngine::setDarkPalette() {
}
void DrasculaEngine::setPaletteBase(int darkness) {
- char fade;
+ signed char fade;
unsigned int color, component;
for (fade = darkness; fade >= 0; fade--) {
diff --git a/engines/drascula/rooms.cpp b/engines/drascula/rooms.cpp
index 6fe28bdbdc..37dddf4b7e 100644
--- a/engines/drascula/rooms.cpp
+++ b/engines/drascula/rooms.cpp
@@ -1672,8 +1672,8 @@ void DrasculaEngine::enterRoom(int roomIndex) {
getIntFromLine(buffer, size, &floorY2);
if (currentChapter != 2) {
- getIntFromLine(buffer, size, &far);
- getIntFromLine(buffer, size, &near);
+ getIntFromLine(buffer, size, &upperLimit);
+ getIntFromLine(buffer, size, &lowerLimit);
}
_arj.close();
@@ -1732,27 +1732,27 @@ void DrasculaEngine::enterRoom(int roomIndex) {
if (currentChapter != 2) {
for (l = 0; l <= floorY1; l++)
- factor_red[l] = far;
+ factor_red[l] = upperLimit;
for (l = floorY1; l <= 201; l++)
- factor_red[l] = near;
+ factor_red[l] = lowerLimit;
- chiquez = (float)(near - far) / (float)(floorY2 - floorY1);
+ chiquez = (float)(lowerLimit - upperLimit) / (float)(floorY2 - floorY1);
for (l = floorY1; l <= floorY2; l++) {
- factor_red[l] = (int)(far + pequegnez);
+ factor_red[l] = (int)(upperLimit + pequegnez);
pequegnez = pequegnez + chiquez;
}
}
if (roomNumber == 24) {
for (l = floorY1 - 1; l > 74; l--) {
- factor_red[l] = (int)(far - pequegnez);
+ factor_red[l] = (int)(upperLimit - pequegnez);
pequegnez = pequegnez + chiquez;
}
}
if (currentChapter == 5 && roomNumber == 54) {
for (l = floorY1 - 1; l > 84; l--) {
- factor_red[l] = (int)(far - pequegnez);
+ factor_red[l] = (int)(upperLimit - pequegnez);
pequegnez = pequegnez + chiquez;
}
}
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/detection.cpp b/engines/gob/detection.cpp
index 8351f2ecfb..63a0f8f45b 100644
--- a/engines/gob/detection.cpp
+++ b/engines/gob/detection.cpp
@@ -277,6 +277,19 @@ static const GOBGameDescription gameDescriptions[] = {
kFeaturesNone,
"intro"
},
+ { // Supplied by raina in the forums
+ {
+ "gob1",
+ "",
+ AD_ENTRY1s("intro.stk", "6d837c6380d8f4d984c9f6cc0026df4f", 192712),
+ EN_ANY,
+ kPlatformMacintosh,
+ Common::ADGF_NO_FLAGS
+ },
+ kGameTypeGob1,
+ kFeaturesNone,
+ "intro"
+ },
{ // Supplied by paul66 in bug report #1652352
{
"gob1",
diff --git a/engines/gob/driver_vga.cpp b/engines/gob/driver_vga.cpp
index f68ce47783..2d3a10b570 100644
--- a/engines/gob/driver_vga.cpp
+++ b/engines/gob/driver_vga.cpp
@@ -112,7 +112,7 @@ void VGAVideoDriver::drawSprite(SurfaceDesc *source, SurfaceDesc *dest,
if ((width < 1) || (height < 1))
return;
- byte *srcPos = source->getVidMem() + (top * source->getWidth()) + left;
+ const byte *srcPos = source->getVidMem() + (top * source->getWidth()) + left;
byte *destPos = dest->getVidMem() + (y * dest->getWidth()) + x;
uint32 size = width * height;
diff --git a/engines/gob/gob.cpp b/engines/gob/gob.cpp
index a3fe0ebbe2..34443251d8 100644
--- a/engines/gob/gob.cpp
+++ b/engines/gob/gob.cpp
@@ -147,6 +147,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 ae2b53bc31..041658baea 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,
@@ -230,6 +235,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/goblin.cpp b/engines/gob/goblin.cpp
index e7aed0790e..5add0b9cea 100644
--- a/engines/gob/goblin.cpp
+++ b/engines/gob/goblin.cpp
@@ -78,58 +78,6 @@ Goblin::Goblin(GobEngine *vm) : _vm(vm) {
_pressedMapY = 0;
_pathExistence = 0;
- _some0ValPtr = 0;
-
- _gobRetVarPtr = 0;
- _curGobVarPtr = 0;
- _curGobXPosVarPtr = 0;
- _curGobYPosVarPtr = 0;
- _itemInPocketVarPtr = 0;
-
- _curGobStateVarPtr = 0;
- _curGobFrameVarPtr = 0;
- _curGobMultStateVarPtr = 0;
- _curGobNextStateVarPtr = 0;
- _curGobScrXVarPtr = 0;
- _curGobScrYVarPtr = 0;
- _curGobLeftVarPtr = 0;
- _curGobTopVarPtr = 0;
- _curGobRightVarPtr = 0;
- _curGobBottomVarPtr = 0;
- _curGobDoAnimVarPtr = 0;
- _curGobOrderVarPtr = 0;
- _curGobNoTickVarPtr = 0;
- _curGobTypeVarPtr = 0;
- _curGobMaxTickVarPtr = 0;
- _curGobTickVarPtr = 0;
- _curGobActStartStateVarPtr = 0;
- _curGobLookDirVarPtr = 0;
- _curGobPickableVarPtr = 0;
- _curGobRelaxVarPtr = 0;
- _curGobMaxFrameVarPtr = 0;
-
- _destItemStateVarPtr = 0;
- _destItemFrameVarPtr = 0;
- _destItemMultStateVarPtr = 0;
- _destItemNextStateVarPtr = 0;
- _destItemScrXVarPtr = 0;
- _destItemScrYVarPtr = 0;
- _destItemLeftVarPtr = 0;
- _destItemTopVarPtr = 0;
- _destItemRightVarPtr = 0;
- _destItemBottomVarPtr = 0;
- _destItemDoAnimVarPtr = 0;
- _destItemOrderVarPtr = 0;
- _destItemNoTickVarPtr = 0;
- _destItemTypeVarPtr = 0;
- _destItemMaxTickVarPtr = 0;
- _destItemTickVarPtr = 0;
- _destItemActStartStVarPtr = 0;
- _destItemLookDirVarPtr = 0;
- _destItemPickableVarPtr = 0;
- _destItemRelaxVarPtr = 0;
- _destItemMaxFrameVarPtr = 0;
-
_destItemType = 0;
_destItemState = 0;
for (int i = 0; i < 20; i++) {
@@ -690,7 +638,7 @@ void Goblin::switchGoblin(int16 index) {
_gobDestY = tmp;
_vm->_map->_curGoblinY = tmp;
- *_curGobVarPtr = _currentGoblin;
+ _curGobVarPtr = (uint32) _currentGoblin;
_pathExistence = 0;
_readyToAct = 0;
}
@@ -1250,172 +1198,172 @@ void Goblin::loadObjects(const char *source) {
void Goblin::saveGobDataToVars(int16 xPos, int16 yPos, int16 someVal) {
Gob_Object *obj;
- *_some0ValPtr = someVal;
- *_curGobXPosVarPtr = xPos;
- *_curGobYPosVarPtr = yPos;
- *_itemInPocketVarPtr = _itemIndInPocket;
+ _some0ValPtr = (uint32) someVal;
+ _curGobXPosVarPtr = (uint32) xPos;
+ _curGobYPosVarPtr = (uint32) yPos;
+ _itemInPocketVarPtr = (uint32) _itemIndInPocket;
obj = _goblins[_currentGoblin];
- *_curGobStateVarPtr = obj->state;
- *_curGobFrameVarPtr = obj->curFrame;
- *_curGobMultStateVarPtr = obj->multState;
- *_curGobNextStateVarPtr = obj->nextState;
- *_curGobScrXVarPtr = obj->xPos;
- *_curGobScrYVarPtr = obj->yPos;
- *_curGobLeftVarPtr = obj->left;
- *_curGobTopVarPtr = obj->top;
- *_curGobRightVarPtr = obj->right;
- *_curGobBottomVarPtr = obj->bottom;
- *_curGobDoAnimVarPtr = obj->doAnim;
- *_curGobOrderVarPtr = obj->order;
- *_curGobNoTickVarPtr = obj->noTick;
- *_curGobTypeVarPtr = obj->type;
- *_curGobMaxTickVarPtr = obj->maxTick;
- *_curGobTickVarPtr = obj->tick;
- *_curGobActStartStateVarPtr = obj->actionStartState;
- *_curGobLookDirVarPtr = obj->curLookDir;
- *_curGobPickableVarPtr = obj->pickable;
- *_curGobRelaxVarPtr = obj->relaxTime;
- *_curGobMaxFrameVarPtr = getObjMaxFrame(obj);
+ _curGobStateVarPtr = (uint32) obj->state;
+ _curGobFrameVarPtr = (uint32) obj->curFrame;
+ _curGobMultStateVarPtr = (uint32) obj->multState;
+ _curGobNextStateVarPtr = (uint32) obj->nextState;
+ _curGobScrXVarPtr = (uint32) obj->xPos;
+ _curGobScrYVarPtr = (uint32) obj->yPos;
+ _curGobLeftVarPtr = (uint32) obj->left;
+ _curGobTopVarPtr = (uint32) obj->top;
+ _curGobRightVarPtr = (uint32) obj->right;
+ _curGobBottomVarPtr = (uint32) obj->bottom;
+ _curGobDoAnimVarPtr = (uint32) obj->doAnim;
+ _curGobOrderVarPtr = (uint32) obj->order;
+ _curGobNoTickVarPtr = (uint32) obj->noTick;
+ _curGobTypeVarPtr = (uint32) obj->type;
+ _curGobMaxTickVarPtr = (uint32) obj->maxTick;
+ _curGobTickVarPtr = (uint32) obj->tick;
+ _curGobActStartStateVarPtr = (uint32) obj->actionStartState;
+ _curGobLookDirVarPtr = (uint32) obj->curLookDir;
+ _curGobPickableVarPtr = (uint32) obj->pickable;
+ _curGobRelaxVarPtr = (uint32) obj->relaxTime;
+ _curGobMaxFrameVarPtr = (uint32) getObjMaxFrame(obj);
if (_actDestItemDesc == 0)
return;
obj = _actDestItemDesc;
- *_destItemStateVarPtr = obj->state;
- *_destItemFrameVarPtr = obj->curFrame;
- *_destItemMultStateVarPtr = obj->multState;
- *_destItemNextStateVarPtr = obj->nextState;
- *_destItemScrXVarPtr = obj->xPos;
- *_destItemScrYVarPtr = obj->yPos;
- *_destItemLeftVarPtr = obj->left;
- *_destItemTopVarPtr = obj->top;
- *_destItemRightVarPtr = obj->right;
- *_destItemBottomVarPtr = obj->bottom;
- *_destItemDoAnimVarPtr = obj->doAnim;
- *_destItemOrderVarPtr = obj->order;
- *_destItemNoTickVarPtr = obj->noTick;
- *_destItemTypeVarPtr = obj->type;
- *_destItemMaxTickVarPtr = obj->maxTick;
- *_destItemTickVarPtr = obj->tick;
- *_destItemActStartStVarPtr = obj->actionStartState;
- *_destItemLookDirVarPtr = obj->curLookDir;
- *_destItemPickableVarPtr = obj->pickable;
- *_destItemRelaxVarPtr = obj->relaxTime;
- *_destItemMaxFrameVarPtr = getObjMaxFrame(obj);
+ _destItemStateVarPtr = (uint32) obj->state;
+ _destItemFrameVarPtr = (uint32) obj->curFrame;
+ _destItemMultStateVarPtr = (uint32) obj->multState;
+ _destItemNextStateVarPtr = (uint32) obj->nextState;
+ _destItemScrXVarPtr = (uint32) obj->xPos;
+ _destItemScrYVarPtr = (uint32) obj->yPos;
+ _destItemLeftVarPtr = (uint32) obj->left;
+ _destItemTopVarPtr = (uint32) obj->top;
+ _destItemRightVarPtr = (uint32) obj->right;
+ _destItemBottomVarPtr = (uint32) obj->bottom;
+ _destItemDoAnimVarPtr = (uint32) obj->doAnim;
+ _destItemOrderVarPtr = (uint32) obj->order;
+ _destItemNoTickVarPtr = (uint32) obj->noTick;
+ _destItemTypeVarPtr = (uint32) obj->type;
+ _destItemMaxTickVarPtr = (uint32) obj->maxTick;
+ _destItemTickVarPtr = (uint32) obj->tick;
+ _destItemActStartStVarPtr = (uint32) obj->actionStartState;
+ _destItemLookDirVarPtr = (uint32) obj->curLookDir;
+ _destItemPickableVarPtr = (uint32) obj->pickable;
+ _destItemRelaxVarPtr = (uint32) obj->relaxTime;
+ _destItemMaxFrameVarPtr = (uint32) getObjMaxFrame(obj);
_destItemState = obj->state;
_destItemType = obj->type;
}
void Goblin::initVarPointers(void) {
- _gobRetVarPtr = (int32 *)VAR_ADDRESS(59);
- _curGobStateVarPtr = (int32 *)VAR_ADDRESS(60);
- _curGobFrameVarPtr = (int32 *)VAR_ADDRESS(61);
- _curGobMultStateVarPtr = (int32 *)VAR_ADDRESS(62);
- _curGobNextStateVarPtr = (int32 *)VAR_ADDRESS(63);
- _curGobScrXVarPtr = (int32 *)VAR_ADDRESS(64);
- _curGobScrYVarPtr = (int32 *)VAR_ADDRESS(65);
- _curGobLeftVarPtr = (int32 *)VAR_ADDRESS(66);
- _curGobTopVarPtr = (int32 *)VAR_ADDRESS(67);
- _curGobRightVarPtr = (int32 *)VAR_ADDRESS(68);
- _curGobBottomVarPtr = (int32 *)VAR_ADDRESS(69);
- _curGobDoAnimVarPtr = (int32 *)VAR_ADDRESS(70);
- _curGobOrderVarPtr = (int32 *)VAR_ADDRESS(71);
- _curGobNoTickVarPtr = (int32 *)VAR_ADDRESS(72);
- _curGobTypeVarPtr = (int32 *)VAR_ADDRESS(73);
- _curGobMaxTickVarPtr = (int32 *)VAR_ADDRESS(74);
- _curGobTickVarPtr = (int32 *)VAR_ADDRESS(75);
- _curGobActStartStateVarPtr = (int32 *)VAR_ADDRESS(76);
- _curGobLookDirVarPtr = (int32 *)VAR_ADDRESS(77);
- _curGobPickableVarPtr = (int32 *)VAR_ADDRESS(80);
- _curGobRelaxVarPtr = (int32 *)VAR_ADDRESS(81);
- _destItemStateVarPtr = (int32 *)VAR_ADDRESS(82);
- _destItemFrameVarPtr = (int32 *)VAR_ADDRESS(83);
- _destItemMultStateVarPtr = (int32 *)VAR_ADDRESS(84);
- _destItemNextStateVarPtr = (int32 *)VAR_ADDRESS(85);
- _destItemScrXVarPtr = (int32 *)VAR_ADDRESS(86);
- _destItemScrYVarPtr = (int32 *)VAR_ADDRESS(87);
- _destItemLeftVarPtr = (int32 *)VAR_ADDRESS(88);
- _destItemTopVarPtr = (int32 *)VAR_ADDRESS(89);
- _destItemRightVarPtr = (int32 *)VAR_ADDRESS(90);
- _destItemBottomVarPtr = (int32 *)VAR_ADDRESS(91);
- _destItemDoAnimVarPtr = (int32 *)VAR_ADDRESS(92);
- _destItemOrderVarPtr = (int32 *)VAR_ADDRESS(93);
- _destItemNoTickVarPtr = (int32 *)VAR_ADDRESS(94);
- _destItemTypeVarPtr = (int32 *)VAR_ADDRESS(95);
- _destItemMaxTickVarPtr = (int32 *)VAR_ADDRESS(96);
- _destItemTickVarPtr = (int32 *)VAR_ADDRESS(97);
- _destItemActStartStVarPtr = (int32 *)VAR_ADDRESS(98);
- _destItemLookDirVarPtr = (int32 *)VAR_ADDRESS(99);
- _destItemPickableVarPtr = (int32 *)VAR_ADDRESS(102);
- _destItemRelaxVarPtr = (int32 *)VAR_ADDRESS(103);
- _destItemMaxFrameVarPtr = (int32 *)VAR_ADDRESS(105);
- _curGobVarPtr = (int32 *)VAR_ADDRESS(106);
- _some0ValPtr = (int32 *)VAR_ADDRESS(107);
- _curGobXPosVarPtr = (int32 *)VAR_ADDRESS(108);
- _curGobYPosVarPtr = (int32 *)VAR_ADDRESS(109);
- _curGobMaxFrameVarPtr = (int32 *)VAR_ADDRESS(110);
-
- _itemInPocketVarPtr = (int32 *)VAR_ADDRESS(114);
-
- *_itemInPocketVarPtr = -2;
+ _gobRetVarPtr.set(*_vm->_inter->_variables, 236);
+ _curGobStateVarPtr.set(*_vm->_inter->_variables, 240);
+ _curGobFrameVarPtr.set(*_vm->_inter->_variables, 244);
+ _curGobMultStateVarPtr.set(*_vm->_inter->_variables, 248);
+ _curGobNextStateVarPtr.set(*_vm->_inter->_variables, 252);
+ _curGobScrXVarPtr.set(*_vm->_inter->_variables, 256);
+ _curGobScrYVarPtr.set(*_vm->_inter->_variables, 260);
+ _curGobLeftVarPtr.set(*_vm->_inter->_variables, 264);
+ _curGobTopVarPtr.set(*_vm->_inter->_variables, 268);
+ _curGobRightVarPtr.set(*_vm->_inter->_variables, 272);
+ _curGobBottomVarPtr.set(*_vm->_inter->_variables, 276);
+ _curGobDoAnimVarPtr.set(*_vm->_inter->_variables, 280);
+ _curGobOrderVarPtr.set(*_vm->_inter->_variables, 284);
+ _curGobNoTickVarPtr.set(*_vm->_inter->_variables, 288);
+ _curGobTypeVarPtr.set(*_vm->_inter->_variables, 292);
+ _curGobMaxTickVarPtr.set(*_vm->_inter->_variables, 296);
+ _curGobTickVarPtr.set(*_vm->_inter->_variables, 300);
+ _curGobActStartStateVarPtr.set(*_vm->_inter->_variables, 304);
+ _curGobLookDirVarPtr.set(*_vm->_inter->_variables, 308);
+ _curGobPickableVarPtr.set(*_vm->_inter->_variables, 320);
+ _curGobRelaxVarPtr.set(*_vm->_inter->_variables, 324);
+ _destItemStateVarPtr.set(*_vm->_inter->_variables, 328);
+ _destItemFrameVarPtr.set(*_vm->_inter->_variables, 332);
+ _destItemMultStateVarPtr.set(*_vm->_inter->_variables, 336);
+ _destItemNextStateVarPtr.set(*_vm->_inter->_variables, 340);
+ _destItemScrXVarPtr.set(*_vm->_inter->_variables, 344);
+ _destItemScrYVarPtr.set(*_vm->_inter->_variables, 348);
+ _destItemLeftVarPtr.set(*_vm->_inter->_variables, 352);
+ _destItemTopVarPtr.set(*_vm->_inter->_variables, 356);
+ _destItemRightVarPtr.set(*_vm->_inter->_variables, 360);
+ _destItemBottomVarPtr.set(*_vm->_inter->_variables, 364);
+ _destItemDoAnimVarPtr.set(*_vm->_inter->_variables, 368);
+ _destItemOrderVarPtr.set(*_vm->_inter->_variables, 372);
+ _destItemNoTickVarPtr.set(*_vm->_inter->_variables, 376);
+ _destItemTypeVarPtr.set(*_vm->_inter->_variables, 380);
+ _destItemMaxTickVarPtr.set(*_vm->_inter->_variables, 384);
+ _destItemTickVarPtr.set(*_vm->_inter->_variables, 388);
+ _destItemActStartStVarPtr.set(*_vm->_inter->_variables, 392);
+ _destItemLookDirVarPtr.set(*_vm->_inter->_variables, 396);
+ _destItemPickableVarPtr.set(*_vm->_inter->_variables, 408);
+ _destItemRelaxVarPtr.set(*_vm->_inter->_variables, 412);
+ _destItemMaxFrameVarPtr.set(*_vm->_inter->_variables, 420);
+ _curGobVarPtr.set(*_vm->_inter->_variables, 424);
+ _some0ValPtr.set(*_vm->_inter->_variables, 428);
+ _curGobXPosVarPtr.set(*_vm->_inter->_variables, 432);
+ _curGobYPosVarPtr.set(*_vm->_inter->_variables, 436);
+ _curGobMaxFrameVarPtr.set(*_vm->_inter->_variables, 440);
+
+ _itemInPocketVarPtr.set(*_vm->_inter->_variables, 456);
+
+ _itemInPocketVarPtr = (uint32) -2;
}
void Goblin::loadGobDataFromVars(void) {
Gob_Object *obj;
- _itemIndInPocket = *_itemInPocketVarPtr;
+ _itemIndInPocket = (int32) _itemInPocketVarPtr;
obj = _goblins[_currentGoblin];
- obj->state = *_curGobStateVarPtr;
- obj->curFrame = *_curGobFrameVarPtr;
- obj->multState = *_curGobMultStateVarPtr;
- obj->nextState = *_curGobNextStateVarPtr;
- obj->xPos = *_curGobScrXVarPtr;
- obj->yPos = *_curGobScrYVarPtr;
- obj->left = *_curGobLeftVarPtr;
- obj->top = *_curGobTopVarPtr;
- obj->right = *_curGobRightVarPtr;
- obj->bottom = *_curGobBottomVarPtr;
- obj->doAnim = *_curGobDoAnimVarPtr;
- obj->order = *_curGobOrderVarPtr;
- obj->noTick = *_curGobNoTickVarPtr;
- obj->type = *_curGobTypeVarPtr;
- obj->maxTick = *_curGobMaxTickVarPtr;
- obj->tick = *_curGobTickVarPtr;
- obj->actionStartState = *_curGobActStartStateVarPtr;
- obj->curLookDir = *_curGobLookDirVarPtr;
- obj->pickable = *_curGobPickableVarPtr;
- obj->relaxTime = *_curGobRelaxVarPtr;
+ obj->state = (int32) _curGobStateVarPtr;
+ obj->curFrame = (int32) _curGobFrameVarPtr;
+ obj->multState = (int32) _curGobMultStateVarPtr;
+ obj->nextState = (int32) _curGobNextStateVarPtr;
+ obj->xPos = (int32) _curGobScrXVarPtr;
+ obj->yPos = (int32) _curGobScrYVarPtr;
+ obj->left = (int32) _curGobLeftVarPtr;
+ obj->top = (int32) _curGobTopVarPtr;
+ obj->right = (int32) _curGobRightVarPtr;
+ obj->bottom = (int32) _curGobBottomVarPtr;
+ obj->doAnim = (int32) _curGobDoAnimVarPtr;
+ obj->order = (int32) _curGobOrderVarPtr;
+ obj->noTick = (int32) _curGobNoTickVarPtr;
+ obj->type = (int32) _curGobTypeVarPtr;
+ obj->maxTick = (int32) _curGobMaxTickVarPtr;
+ obj->tick = (int32) _curGobTickVarPtr;
+ obj->actionStartState = (int32) _curGobActStartStateVarPtr;
+ obj->curLookDir = (int32) _curGobLookDirVarPtr;
+ obj->pickable = (int32) _curGobPickableVarPtr;
+ obj->relaxTime = (int32) _curGobRelaxVarPtr;
if (_actDestItemDesc == 0)
return;
obj = _actDestItemDesc;
- obj->state = *_destItemStateVarPtr;
- obj->curFrame = *_destItemFrameVarPtr;
- obj->multState = *_destItemMultStateVarPtr;
- obj->nextState = *_destItemNextStateVarPtr;
- obj->xPos = *_destItemScrXVarPtr;
- obj->yPos = *_destItemScrYVarPtr;
- obj->left = *_destItemLeftVarPtr;
- obj->top = *_destItemTopVarPtr;
- obj->right = *_destItemRightVarPtr;
- obj->bottom = *_destItemBottomVarPtr;
- obj->doAnim = *_destItemDoAnimVarPtr;
- obj->order = *_destItemOrderVarPtr;
- obj->noTick = *_destItemNoTickVarPtr;
- obj->type = *_destItemTypeVarPtr;
- obj->maxTick = *_destItemMaxTickVarPtr;
- obj->tick = *_destItemTickVarPtr;
- obj->actionStartState = *_destItemActStartStVarPtr;
- obj->curLookDir = *_destItemLookDirVarPtr;
- obj->pickable = *_destItemPickableVarPtr;
- obj->relaxTime = *_destItemRelaxVarPtr;
+ obj->state = (int32) _destItemStateVarPtr;
+ obj->curFrame = (int32) _destItemFrameVarPtr;
+ obj->multState = (int32) _destItemMultStateVarPtr;
+ obj->nextState = (int32) _destItemNextStateVarPtr;
+ obj->xPos = (int32) _destItemScrXVarPtr;
+ obj->yPos = (int32) _destItemScrYVarPtr;
+ obj->left = (int32) _destItemLeftVarPtr;
+ obj->top = (int32) _destItemTopVarPtr;
+ obj->right = (int32) _destItemRightVarPtr;
+ obj->bottom = (int32) _destItemBottomVarPtr;
+ obj->doAnim = (int32) _destItemDoAnimVarPtr;
+ obj->order = (int32) _destItemOrderVarPtr;
+ obj->noTick = (int32) _destItemNoTickVarPtr;
+ obj->type = (int32) _destItemTypeVarPtr;
+ obj->maxTick = (int32) _destItemMaxTickVarPtr;
+ obj->tick = (int32) _destItemTickVarPtr;
+ obj->actionStartState = (int32) _destItemActStartStVarPtr;
+ obj->curLookDir = (int32) _destItemLookDirVarPtr;
+ obj->pickable = (int32) _destItemPickableVarPtr;
+ obj->relaxTime = (int32) _destItemRelaxVarPtr;
if (obj->type != _destItemType)
obj->toRedraw = 1;
diff --git a/engines/gob/goblin.h b/engines/gob/goblin.h
index 3fd8a9f93b..2100bcbdac 100644
--- a/engines/gob/goblin.h
+++ b/engines/gob/goblin.h
@@ -28,6 +28,7 @@
#include "gob/util.h"
#include "gob/mult.h"
+#include "gob/variables.h"
#include "gob/sound/sounddesc.h"
namespace Gob {
@@ -115,57 +116,57 @@ public:
char _pathExistence;
// Pointers to interpreter variables
- int32 *_some0ValPtr;
-
- int32 *_gobRetVarPtr;
- int32 *_curGobVarPtr;
- int32 *_curGobXPosVarPtr;
- int32 *_curGobYPosVarPtr;
- int32 *_itemInPocketVarPtr;
-
- int32 *_curGobStateVarPtr;
- int32 *_curGobFrameVarPtr;
- int32 *_curGobMultStateVarPtr;
- int32 *_curGobNextStateVarPtr;
- int32 *_curGobScrXVarPtr;
- int32 *_curGobScrYVarPtr;
- int32 *_curGobLeftVarPtr;
- int32 *_curGobTopVarPtr;
- int32 *_curGobRightVarPtr;
- int32 *_curGobBottomVarPtr;
- int32 *_curGobDoAnimVarPtr;
- int32 *_curGobOrderVarPtr;
- int32 *_curGobNoTickVarPtr;
- int32 *_curGobTypeVarPtr;
- int32 *_curGobMaxTickVarPtr;
- int32 *_curGobTickVarPtr;
- int32 *_curGobActStartStateVarPtr;
- int32 *_curGobLookDirVarPtr;
- int32 *_curGobPickableVarPtr;
- int32 *_curGobRelaxVarPtr;
- int32 *_curGobMaxFrameVarPtr;
-
- int32 *_destItemStateVarPtr;
- int32 *_destItemFrameVarPtr;
- int32 *_destItemMultStateVarPtr;
- int32 *_destItemNextStateVarPtr;
- int32 *_destItemScrXVarPtr;
- int32 *_destItemScrYVarPtr;
- int32 *_destItemLeftVarPtr;
- int32 *_destItemTopVarPtr;
- int32 *_destItemRightVarPtr;
- int32 *_destItemBottomVarPtr;
- int32 *_destItemDoAnimVarPtr;
- int32 *_destItemOrderVarPtr;
- int32 *_destItemNoTickVarPtr;
- int32 *_destItemTypeVarPtr;
- int32 *_destItemMaxTickVarPtr;
- int32 *_destItemTickVarPtr;
- int32 *_destItemActStartStVarPtr;
- int32 *_destItemLookDirVarPtr;
- int32 *_destItemPickableVarPtr;
- int32 *_destItemRelaxVarPtr;
- int32 *_destItemMaxFrameVarPtr;
+ VariableReference _some0ValPtr;
+
+ VariableReference _gobRetVarPtr;
+ VariableReference _curGobVarPtr;
+ VariableReference _curGobXPosVarPtr;
+ VariableReference _curGobYPosVarPtr;
+ VariableReference _itemInPocketVarPtr;
+
+ VariableReference _curGobStateVarPtr;
+ VariableReference _curGobFrameVarPtr;
+ VariableReference _curGobMultStateVarPtr;
+ VariableReference _curGobNextStateVarPtr;
+ VariableReference _curGobScrXVarPtr;
+ VariableReference _curGobScrYVarPtr;
+ VariableReference _curGobLeftVarPtr;
+ VariableReference _curGobTopVarPtr;
+ VariableReference _curGobRightVarPtr;
+ VariableReference _curGobBottomVarPtr;
+ VariableReference _curGobDoAnimVarPtr;
+ VariableReference _curGobOrderVarPtr;
+ VariableReference _curGobNoTickVarPtr;
+ VariableReference _curGobTypeVarPtr;
+ VariableReference _curGobMaxTickVarPtr;
+ VariableReference _curGobTickVarPtr;
+ VariableReference _curGobActStartStateVarPtr;
+ VariableReference _curGobLookDirVarPtr;
+ VariableReference _curGobPickableVarPtr;
+ VariableReference _curGobRelaxVarPtr;
+ VariableReference _curGobMaxFrameVarPtr;
+
+ VariableReference _destItemStateVarPtr;
+ VariableReference _destItemFrameVarPtr;
+ VariableReference _destItemMultStateVarPtr;
+ VariableReference _destItemNextStateVarPtr;
+ VariableReference _destItemScrXVarPtr;
+ VariableReference _destItemScrYVarPtr;
+ VariableReference _destItemLeftVarPtr;
+ VariableReference _destItemTopVarPtr;
+ VariableReference _destItemRightVarPtr;
+ VariableReference _destItemBottomVarPtr;
+ VariableReference _destItemDoAnimVarPtr;
+ VariableReference _destItemOrderVarPtr;
+ VariableReference _destItemNoTickVarPtr;
+ VariableReference _destItemTypeVarPtr;
+ VariableReference _destItemMaxTickVarPtr;
+ VariableReference _destItemTickVarPtr;
+ VariableReference _destItemActStartStVarPtr;
+ VariableReference _destItemLookDirVarPtr;
+ VariableReference _destItemPickableVarPtr;
+ VariableReference _destItemRelaxVarPtr;
+ VariableReference _destItemMaxFrameVarPtr;
int16 _destItemType;
int16 _destItemState;
diff --git a/engines/gob/goblin_v2.cpp b/engines/gob/goblin_v2.cpp
index 9144e35070..d763aeb01c 100644
--- a/engines/gob/goblin_v2.cpp
+++ b/engines/gob/goblin_v2.cpp
@@ -88,7 +88,7 @@ void Goblin_v2::placeObject(Gob_Object *objDesc, char animated,
(_vm->_scenery->_animBottom - _vm->_scenery->_animTop) - (y + 1) / 2;
*obj->pPosX = x * _vm->_map->_tilesWidth;
} else {
- if (obj->goblinStates[state] != 0) {
+ if ((obj->goblinStates != 0) && (obj->goblinStates[state] != 0)) {
layer = obj->goblinStates[state][0].layer;
animation = obj->goblinStates[state][0].animation;
objAnim->state = state;
diff --git a/engines/gob/inter.cpp b/engines/gob/inter.cpp
index 9c39653a1d..02e7f99cbd 100644
--- a/engines/gob/inter.cpp
+++ b/engines/gob/inter.cpp
@@ -212,25 +212,35 @@ void Inter::funcBlock(int16 retFlag) {
break;
// WORKAROUND:
- // The EGA version of gob1 doesn't add a delay after showing
+ // The EGA and Mac versions of gob1 doesn't add a delay after showing
// images between levels. We manually add it here.
- if ((_vm->getGameType() == kGameTypeGob1) && _vm->isEGA()) {
+ if ((_vm->getGameType() == kGameTypeGob1) &&
+ (_vm->isEGA() || (_vm->getPlatform() == Common::kPlatformMacintosh))) {
+
int addr = _vm->_global->_inter_execPtr-_vm->_game->_totFileData;
- if ((startaddr == 0x18B4 && addr == 0x1A7F && // Zombie
+
+ if ((startaddr == 0x18B4 && addr == 0x1A7F && // Zombie, EGA
+ !strncmp(_vm->_game->_curTotFile, "avt005.tot", 10)) ||
+ (startaddr == 0x188D && addr == 0x1A58 && // Zombie, Mac
!strncmp(_vm->_game->_curTotFile, "avt005.tot", 10)) ||
(startaddr == 0x1299 && addr == 0x139A && // Dungeon
!strncmp(_vm->_game->_curTotFile, "avt006.tot", 10)) ||
- (startaddr == 0x11C0 && addr == 0x12C9 && // Cauldron
+ (startaddr == 0x11C0 && addr == 0x12C9 && // Cauldron, EGA
+ !strncmp(_vm->_game->_curTotFile, "avt012.tot", 10)) ||
+ (startaddr == 0x11C8 && addr == 0x1341 && // Cauldron, Mac
!strncmp(_vm->_game->_curTotFile, "avt012.tot", 10)) ||
(startaddr == 0x09F2 && addr == 0x0AF3 && // Statue
!strncmp(_vm->_game->_curTotFile, "avt016.tot", 10)) ||
(startaddr == 0x0B92 && addr == 0x0C93 && // Castle
!strncmp(_vm->_game->_curTotFile, "avt019.tot", 10)) ||
- (startaddr == 0x17D9 && addr == 0x18DA && // Finale
+ (startaddr == 0x17D9 && addr == 0x18DA && // Finale, EGA
+ !strncmp(_vm->_game->_curTotFile, "avt022.tot", 10)) ||
+ (startaddr == 0x17E9 && addr == 0x19A8 && // Finale, Mac
!strncmp(_vm->_game->_curTotFile, "avt022.tot", 10))) {
_vm->_util->longDelay(5000);
}
+
} // End of workaround
cmd = *_vm->_global->_inter_execPtr;
@@ -286,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/inter.h b/engines/gob/inter.h
index 60b3974d6d..b684be6c07 100644
--- a/engines/gob/inter.h
+++ b/engines/gob/inter.h
@@ -79,7 +79,7 @@ protected:
};
struct OpGobParams {
int16 extraData;
- int32 *retVarPtr;
+ VariableReference retVarPtr;
Goblin::Gob_Object *objDesc;
};
diff --git a/engines/gob/inter_v1.cpp b/engines/gob/inter_v1.cpp
index e2b8d65112..865d188a2e 100644
--- a/engines/gob/inter_v1.cpp
+++ b/engines/gob/inter_v1.cpp
@@ -912,12 +912,21 @@ void Inter_v1::o1_initMult() {
animDataVar = _vm->_parse->parseVarIndex();
if (_vm->_mult->_objects && (oldObjCount != _vm->_mult->_objCount)) {
+
warning("Initializing new objects without having "
"cleaned up the old ones at first");
+
+ for (int i = 0; i < _vm->_mult->_objCount; i++) {
+ delete _vm->_mult->_objects[i].pPosX;
+ delete _vm->_mult->_objects[i].pPosY;
+ }
+
delete[] _vm->_mult->_objects;
delete[] _vm->_mult->_renderData;
+
_vm->_mult->_objects = 0;
_vm->_mult->_renderObjs = 0;
+
}
if (_vm->_mult->_objects == 0) {
@@ -933,8 +942,8 @@ void Inter_v1::o1_initMult() {
uint32 offPosY = i * 4 + (posYVar / 4) * 4;
uint32 offAnim = animDataVar + i * 4 * _vm->_global->_inter_animDataSize;
- _vm->_mult->_objects[i].pPosX = (int32 *) _variables->getAddressOff32(offPosX);
- _vm->_mult->_objects[i].pPosY = (int32 *) _variables->getAddressOff32(offPosY);
+ _vm->_mult->_objects[i].pPosX = new VariableReference(*_vm->_inter->_variables, offPosX);
+ _vm->_mult->_objects[i].pPosY = new VariableReference(*_vm->_inter->_variables, offPosY);
_vm->_mult->_objects[i].pAnimData =
(Mult::Mult_AnimData *) _variables->getAddressOff8(offAnim,
@@ -1774,7 +1783,7 @@ bool Inter_v1::o1_goblinFunc(OpFuncParams &params) {
gobParams.extraData = 0;
gobParams.objDesc = 0;
- gobParams.retVarPtr = (int32 *) VAR_ADDRESS(59);
+ gobParams.retVarPtr.set(*_vm->_inter->_variables, 236);
cmd = load16();
_vm->_global->_inter_execPtr += 2;
@@ -2268,49 +2277,49 @@ bool Inter_v1::o1_manageDataFile(OpFuncParams &params) {
void Inter_v1::o1_setState(OpGobParams &params) {
params.objDesc->state = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemStateVarPtr = params.extraData;
+ _vm->_goblin->_destItemStateVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setCurFrame(OpGobParams &params) {
params.objDesc->curFrame = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemFrameVarPtr = params.extraData;
+ _vm->_goblin->_destItemFrameVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setNextState(OpGobParams &params) {
params.objDesc->nextState = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemNextStateVarPtr = params.extraData;
+ _vm->_goblin->_destItemNextStateVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setMultState(OpGobParams &params) {
params.objDesc->multState = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemMultStateVarPtr = params.extraData;
+ _vm->_goblin->_destItemMultStateVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setOrder(OpGobParams &params) {
params.objDesc->order = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemOrderVarPtr = params.extraData;
+ _vm->_goblin->_destItemOrderVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setActionStartState(OpGobParams &params) {
params.objDesc->actionStartState = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemActStartStVarPtr = params.extraData;
+ _vm->_goblin->_destItemActStartStVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setCurLookDir(OpGobParams &params) {
params.objDesc->curLookDir = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemLookDirVarPtr = params.extraData;
+ _vm->_goblin->_destItemLookDirVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setType(OpGobParams &params) {
params.objDesc->type = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemTypeVarPtr = params.extraData;
+ _vm->_goblin->_destItemTypeVarPtr = (uint32) params.extraData;
if (params.extraData == 0)
params.objDesc->toRedraw = 1;
@@ -2319,107 +2328,107 @@ void Inter_v1::o1_setType(OpGobParams &params) {
void Inter_v1::o1_setNoTick(OpGobParams &params) {
params.objDesc->noTick = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemNoTickVarPtr = params.extraData;
+ _vm->_goblin->_destItemNoTickVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setPickable(OpGobParams &params) {
params.objDesc->pickable = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemPickableVarPtr = params.extraData;
+ _vm->_goblin->_destItemPickableVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setXPos(OpGobParams &params) {
params.objDesc->xPos = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemScrXVarPtr = params.extraData;
+ _vm->_goblin->_destItemScrXVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setYPos(OpGobParams &params) {
params.objDesc->yPos = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemScrYVarPtr = params.extraData;
+ _vm->_goblin->_destItemScrYVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setDoAnim(OpGobParams &params) {
params.objDesc->doAnim = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemDoAnimVarPtr = params.extraData;
+ _vm->_goblin->_destItemDoAnimVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setRelaxTime(OpGobParams &params) {
params.objDesc->relaxTime = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemRelaxVarPtr = params.extraData;
+ _vm->_goblin->_destItemRelaxVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_setMaxTick(OpGobParams &params) {
params.objDesc->maxTick = params.extraData;
if (params.objDesc == _vm->_goblin->_actDestItemDesc)
- *_vm->_goblin->_destItemMaxTickVarPtr = params.extraData;
+ _vm->_goblin->_destItemMaxTickVarPtr = (uint32) params.extraData;
}
void Inter_v1::o1_getState(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->state;
+ params.retVarPtr = (uint32) params.objDesc->state;
}
void Inter_v1::o1_getCurFrame(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->curFrame;
+ params.retVarPtr = (uint32) params.objDesc->curFrame;
}
void Inter_v1::o1_getNextState(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->nextState;
+ params.retVarPtr = (uint32) params.objDesc->nextState;
}
void Inter_v1::o1_getMultState(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->multState;
+ params.retVarPtr = (uint32) params.objDesc->multState;
}
void Inter_v1::o1_getOrder(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->order;
+ params.retVarPtr = (uint32) params.objDesc->order;
}
void Inter_v1::o1_getActionStartState(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->actionStartState;
+ params.retVarPtr = (uint32) params.objDesc->actionStartState;
}
void Inter_v1::o1_getCurLookDir(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->curLookDir;
+ params.retVarPtr = (uint32) params.objDesc->curLookDir;
}
void Inter_v1::o1_getType(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->type;
+ params.retVarPtr = (uint32) params.objDesc->type;
}
void Inter_v1::o1_getNoTick(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->noTick;
+ params.retVarPtr = (uint32) params.objDesc->noTick;
}
void Inter_v1::o1_getPickable(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->pickable;
+ params.retVarPtr = (uint32) params.objDesc->pickable;
}
void Inter_v1::o1_getObjMaxFrame(OpGobParams &params) {
- *params.retVarPtr = _vm->_goblin->getObjMaxFrame(params.objDesc);
+ params.retVarPtr = (uint32) _vm->_goblin->getObjMaxFrame(params.objDesc);
}
void Inter_v1::o1_getXPos(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->xPos;
+ params.retVarPtr = (uint32) params.objDesc->xPos;
}
void Inter_v1::o1_getYPos(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->yPos;
+ params.retVarPtr = (uint32) params.objDesc->yPos;
}
void Inter_v1::o1_getDoAnim(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->doAnim;
+ params.retVarPtr = (uint32) params.objDesc->doAnim;
}
void Inter_v1::o1_getRelaxTime(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->relaxTime;
+ params.retVarPtr = (uint32) params.objDesc->relaxTime;
}
void Inter_v1::o1_getMaxTick(OpGobParams &params) {
- *params.retVarPtr = params.objDesc->maxTick;
+ params.retVarPtr = (uint32) params.objDesc->maxTick;
}
void Inter_v1::o1_manipulateMap(OpGobParams &params) {
@@ -2435,9 +2444,9 @@ void Inter_v1::o1_getItem(OpGobParams &params) {
int16 yPos = load16();
if ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) != 0)
- *params.retVarPtr = (_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8;
+ params.retVarPtr = (uint32) ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8);
else
- *params.retVarPtr = _vm->_map->_itemsMap[yPos][xPos];
+ params.retVarPtr = (uint32) _vm->_map->_itemsMap[yPos][xPos];
}
void Inter_v1::o1_manipulateMapIndirect(OpGobParams &params) {
@@ -2460,9 +2469,9 @@ void Inter_v1::o1_getItemIndirect(OpGobParams &params) {
yPos = VAR(yPos);
if ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) != 0)
- *params.retVarPtr = (_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8;
+ params.retVarPtr = (uint32) ((_vm->_map->_itemsMap[yPos][xPos] & 0xFF00) >> 8);
else
- *params.retVarPtr = _vm->_map->_itemsMap[yPos][xPos];
+ params.retVarPtr = (uint32) _vm->_map->_itemsMap[yPos][xPos];
}
void Inter_v1::o1_setPassMap(OpGobParams &params) {
@@ -2500,11 +2509,11 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams &params) {
params.objDesc->curFrame = 0;
params.objDesc->state = 21;
if (_vm->_goblin->_currentGoblin == item) {
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = 18;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = 18;
_vm->_goblin->_pressedMapX = _vm->_goblin->_gobPositions[item].x;
_vm->_goblin->_pressedMapY = _vm->_goblin->_gobPositions[item].y;
}
@@ -2512,12 +2521,12 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams &params) {
void Inter_v1::o1_getGoblinPosXH(OpGobParams &params) {
int16 item = load16();
- *params.retVarPtr = _vm->_goblin->_gobPositions[item].x >> 1;
+ params.retVarPtr = (uint32) (_vm->_goblin->_gobPositions[item].x >> 1);
}
void Inter_v1::o1_getGoblinPosYH(OpGobParams &params) {
int16 item = load16();
- *params.retVarPtr = _vm->_goblin->_gobPositions[item].y >> 1;
+ params.retVarPtr = (uint32) (_vm->_goblin->_gobPositions[item].y >> 1);
}
void Inter_v1::o1_setGoblinMultState(OpGobParams &params) {
@@ -2539,14 +2548,14 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams &params) {
params.objDesc->xPos = animLayer->posX;
params.objDesc->yPos = animLayer->posY;
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = params.objDesc->state;
- *_vm->_goblin->_curGobNextStateVarPtr = params.objDesc->nextState;
- *_vm->_goblin->_curGobMultStateVarPtr = params.objDesc->multState;
- *_vm->_goblin->_curGobMaxFrameVarPtr =
- _vm->_goblin->getObjMaxFrame(params.objDesc);
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = (uint32) params.objDesc->state;
+ _vm->_goblin->_curGobNextStateVarPtr = (uint32) params.objDesc->nextState;
+ _vm->_goblin->_curGobMultStateVarPtr = (uint32) params.objDesc->multState;
+ _vm->_goblin->_curGobMaxFrameVarPtr =
+ (uint32) _vm->_goblin->getObjMaxFrame(params.objDesc);
_vm->_goblin->_noPick = 1;
return;
}
@@ -2573,12 +2582,12 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams &params) {
_vm->_goblin->_pressedMapY = yPos;
_vm->_map->_curGoblinY = yPos;
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = 21;
- *_vm->_goblin->_curGobNextStateVarPtr = 21;
- *_vm->_goblin->_curGobMultStateVarPtr = -1;
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = 21;
+ _vm->_goblin->_curGobNextStateVarPtr = 21;
+ _vm->_goblin->_curGobMultStateVarPtr = (uint32) -1;
_vm->_goblin->_noPick = 0;
}
@@ -2598,11 +2607,11 @@ void Inter_v1::o1_setItemIndInPocket(OpGobParams &params) {
}
void Inter_v1::o1_getItemIdInPocket(OpGobParams &params) {
- *params.retVarPtr = _vm->_goblin->_itemIdInPocket;
+ params.retVarPtr = (uint32) _vm->_goblin->_itemIdInPocket;
}
void Inter_v1::o1_getItemIndInPocket(OpGobParams &params) {
- *params.retVarPtr = _vm->_goblin->_itemIndInPocket;
+ params.retVarPtr = (uint32) _vm->_goblin->_itemIndInPocket;
}
void Inter_v1::o1_setGoblinPos(OpGobParams &params) {
@@ -2632,10 +2641,10 @@ void Inter_v1::o1_setGoblinPos(OpGobParams &params) {
params.objDesc->state = 21;
if (_vm->_goblin->_currentGoblin == item) {
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = 18;
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = 18;
_vm->_goblin->_pressedMapX = _vm->_goblin->_gobPositions[item].x;
_vm->_goblin->_pressedMapY = _vm->_goblin->_gobPositions[item].y;
@@ -2659,11 +2668,11 @@ void Inter_v1::o1_setGoblinState(OpGobParams &params) {
params.objDesc->yPos = animLayer->posY;
if (item == _vm->_goblin->_currentGoblin) {
- *_vm->_goblin->_curGobScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_curGobScrYVarPtr = params.objDesc->yPos;
- *_vm->_goblin->_curGobFrameVarPtr = 0;
- *_vm->_goblin->_curGobStateVarPtr = params.objDesc->state;
- *_vm->_goblin->_curGobMultStateVarPtr = params.objDesc->multState;
+ _vm->_goblin->_curGobScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_curGobScrYVarPtr = (uint32) params.objDesc->yPos;
+ _vm->_goblin->_curGobFrameVarPtr = 0;
+ _vm->_goblin->_curGobStateVarPtr = (uint32) params.objDesc->state;
+ _vm->_goblin->_curGobMultStateVarPtr = (uint32) params.objDesc->multState;
}
}
@@ -2686,13 +2695,13 @@ void Inter_v1::o1_setGoblinStateRedraw(OpGobParams &params) {
params.objDesc->toRedraw = 1;
params.objDesc->type = 0;
if (params.objDesc == _vm->_goblin->_actDestItemDesc) {
- *_vm->_goblin->_destItemScrXVarPtr = params.objDesc->xPos;
- *_vm->_goblin->_destItemScrYVarPtr = params.objDesc->yPos;
+ _vm->_goblin->_destItemScrXVarPtr = (uint32) params.objDesc->xPos;
+ _vm->_goblin->_destItemScrYVarPtr = (uint32) params.objDesc->yPos;
- *_vm->_goblin->_destItemStateVarPtr = params.objDesc->state;
- *_vm->_goblin->_destItemNextStateVarPtr = -1;
- *_vm->_goblin->_destItemMultStateVarPtr = -1;
- *_vm->_goblin->_destItemFrameVarPtr = 0;
+ _vm->_goblin->_destItemStateVarPtr = (uint32) params.objDesc->state;
+ _vm->_goblin->_destItemNextStateVarPtr = (uint32) -1;
+ _vm->_goblin->_destItemMultStateVarPtr = (uint32) -1;
+ _vm->_goblin->_destItemFrameVarPtr = 0;
}
}
@@ -2712,12 +2721,12 @@ void Inter_v1::o1_decRelaxTime(OpGobParams &params) {
void Inter_v1::o1_getGoblinPosX(OpGobParams &params) {
int16 item = load16();
- *params.retVarPtr = _vm->_goblin->_gobPositions[item].x;
+ params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].x;
}
void Inter_v1::o1_getGoblinPosY(OpGobParams &params) {
int16 item = load16();
- *params.retVarPtr = _vm->_goblin->_gobPositions[item].y;
+ params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].y;
}
void Inter_v1::o1_clearPathExistence(OpGobParams &params) {
@@ -2741,9 +2750,9 @@ void Inter_v1::o1_getObjectIntersect(OpGobParams &params) {
params.objDesc = _vm->_goblin->_objects[params.extraData];
if (_vm->_goblin->objIntersected(params.objDesc,
_vm->_goblin->_goblins[item]))
- *params.retVarPtr = 1;
+ params.retVarPtr = 1;
else
- *params.retVarPtr = 0;
+ params.retVarPtr = 0;
}
void Inter_v1::o1_getGoblinIntersect(OpGobParams &params) {
@@ -2753,9 +2762,9 @@ void Inter_v1::o1_getGoblinIntersect(OpGobParams &params) {
params.objDesc = _vm->_goblin->_goblins[params.extraData];
if (_vm->_goblin->objIntersected(params.objDesc,
_vm->_goblin->_goblins[item]))
- *params.retVarPtr = 1;
+ params.retVarPtr = 1;
else
- *params.retVarPtr = 0;
+ params.retVarPtr = 0;
}
void Inter_v1::o1_setItemPos(OpGobParams &params) {
@@ -2886,7 +2895,7 @@ void Inter_v1::o1_initGoblin(OpGobParams &params) {
_vm->_map->_destY = _vm->_goblin->_gobPositions[0].y;
_vm->_goblin->_gobDestY = _vm->_goblin->_gobPositions[0].y;
- *_vm->_goblin->_curGobVarPtr = 0;
+ _vm->_goblin->_curGobVarPtr = 0;
_vm->_goblin->_pathExistence = 0;
_vm->_goblin->_readyToAct = 0;
}
diff --git a/engines/gob/inter_v2.cpp b/engines/gob/inter_v2.cpp
index d8c33fcce6..2f1d2ec0be 100644
--- a/engines/gob/inter_v2.cpp
+++ b/engines/gob/inter_v2.cpp
@@ -880,9 +880,15 @@ void Inter_v2::o2_initMult() {
_vm->_mult->clearObjectVideos();
+ for (int i = 0; i < _vm->_mult->_objCount; i++) {
+ delete _vm->_mult->_objects[i].pPosX;
+ delete _vm->_mult->_objects[i].pPosY;
+ }
+
delete[] _vm->_mult->_objects;
delete[] _vm->_mult->_renderObjs;
delete[] _vm->_mult->_orderArray;
+
_vm->_mult->_objects = 0;
_vm->_mult->_renderObjs = 0;
_vm->_mult->_orderArray = 0;
@@ -907,8 +913,8 @@ void Inter_v2::o2_initMult() {
uint32 offPosY = i * 4 + (posYVar / 4) * 4;
uint32 offAnim = animDataVar + i * 4 * _vm->_global->_inter_animDataSize;
- _vm->_mult->_objects[i].pPosX = (int32 *) _variables->getAddressOff32(offPosX);
- _vm->_mult->_objects[i].pPosY = (int32 *) _variables->getAddressOff32(offPosY);
+ _vm->_mult->_objects[i].pPosX = new VariableReference(*_vm->_inter->_variables, offPosX);
+ _vm->_mult->_objects[i].pPosY = new VariableReference(*_vm->_inter->_variables, offPosY);
_vm->_mult->_objects[i].pAnimData =
(Mult::Mult_AnimData *) _variables->getAddressOff8(offAnim,
@@ -1046,7 +1052,7 @@ void Inter_v2::o2_loadMultObject() {
} else if ((objAnim.animType != 100) && (objAnim.animType != 101)) {
- if ((*(obj.pPosX) == -1234) && (*(obj.pPosY) == -4321)) {
+ if ((((int32) *(obj.pPosX)) == -1234) && (((int32) *(obj.pPosY)) == -4321)) {
if (obj.videoSlot > 0)
_vm->_vidPlayer->slotClose(obj.videoSlot - 1);
diff --git a/engines/gob/mult.cpp b/engines/gob/mult.cpp
index 3d6a7942f9..b9373d48b3 100644
--- a/engines/gob/mult.cpp
+++ b/engines/gob/mult.cpp
@@ -93,12 +93,18 @@ Mult::Mult(GobEngine *vm) : _vm(vm) {
}
Mult::~Mult() {
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
delete[] _objects;
delete[] _orderArray;
delete[] _renderData;
delete[] _renderObjs;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
delete _multData;
}
@@ -123,6 +129,12 @@ void Mult::freeAll(void) {
void Mult::freeMult() {
clearObjectVideos();
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
delete[] _objects;
delete[] _renderData;
delete[] _renderObjs;
@@ -203,11 +215,17 @@ void Mult::playMult(int16 startFrame, int16 endFrame, char checkEscape,
if (_animDataAllocated) {
clearObjectVideos();
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
delete[] _objects;
delete[] _renderData;
delete[] _renderObjs;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
delete[] _orderArray;
diff --git a/engines/gob/mult.h b/engines/gob/mult.h
index aaf2e2826c..3bb3af17b3 100644
--- a/engines/gob/mult.h
+++ b/engines/gob/mult.h
@@ -27,6 +27,7 @@
#define GOB_MULT_H
#include "gob/video.h"
+#include "gob/variables.h"
namespace Gob {
@@ -77,8 +78,8 @@ public:
} PACKED_STRUCT;
struct Mult_Object {
- int32 *pPosX;
- int32 *pPosY;
+ VariableReference *pPosX;
+ VariableReference *pPosY;
Mult_AnimData *pAnimData;
int16 tick;
int16 lastLeft;
@@ -267,8 +268,8 @@ protected:
bool _doPalSubst;
- int32 *_animArrayX;
- int32 *_animArrayY;
+ Variables *_animArrayX;
+ Variables *_animArrayY;
Mult_AnimData *_animArrayData;
int16 _palKeyIndex;
diff --git a/engines/gob/mult_v1.cpp b/engines/gob/mult_v1.cpp
index 22683437e7..a369e7d297 100644
--- a/engines/gob/mult_v1.cpp
+++ b/engines/gob/mult_v1.cpp
@@ -216,10 +216,16 @@ void Mult_v1::freeMultKeys() {
if (_animDataAllocated) {
clearObjectVideos();
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
delete[] _objects;
delete[] _renderData;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
_objects = 0;
@@ -263,6 +269,14 @@ void Mult_v1::playMultInit() {
_oldPalette = _vm->_global->_pPaletteDesc->vgaPal;
if (!_animSurf) {
+ if (_objects)
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
+ delete[] _objects;
+
_vm->_util->setFrameRate(_multData->frameRate);
_animTop = 0;
_animLeft = 0;
@@ -270,30 +284,27 @@ void Mult_v1::playMultInit() {
_animHeight = 200;
_objCount = 4;
- delete[] _objects;
delete[] _renderData;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
_objects = new Mult_Object[_objCount];
_renderData = new int16[9 * _objCount];
- _animArrayX = new int32[_objCount];
- _animArrayY = new int32[_objCount];
+ _animArrayX = new VariablesLE(_objCount * 4);
+ _animArrayY = new VariablesLE(_objCount * 4);
_animArrayData = new Mult_AnimData[_objCount];
memset(_objects, 0, _objCount * sizeof(Mult_Object));
memset(_renderData, 0, _objCount * 9 * sizeof(int16));
- memset(_animArrayX, 0, _objCount * sizeof(int32));
- memset(_animArrayY, 0, _objCount * sizeof(int32));
memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData));
for (_counter = 0; _counter < _objCount; _counter++) {
Mult_Object &multObj = _objects[_counter];
Mult_AnimData &animData = _animArrayData[_counter];
- multObj.pPosX = (int32 *) &_animArrayX[_counter];
- multObj.pPosY = (int32 *) &_animArrayY[_counter];
+ multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4);
+ multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4);
multObj.pAnimData = &animData;
animData.isStatic = 1;
diff --git a/engines/gob/mult_v2.cpp b/engines/gob/mult_v2.cpp
index 3a83ac1867..20a81174e5 100644
--- a/engines/gob/mult_v2.cpp
+++ b/engines/gob/mult_v2.cpp
@@ -329,8 +329,8 @@ void Mult_v2::freeMultKeys() {
if (_animDataAllocated) {
freeMult();
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
_animArrayX = 0;
@@ -510,6 +510,13 @@ void Mult_v2::playMultInit() {
if (!_animSurf) {
int16 width, height;
+ for (int i = 0; i < _objCount; i++) {
+ delete _objects[i].pPosX;
+ delete _objects[i].pPosY;
+ }
+
+ delete[] _objects;
+
_vm->_util->setFrameRate(_multData->frameRate);
_animTop = 0;
_animLeft = 0;
@@ -517,33 +524,30 @@ void Mult_v2::playMultInit() {
_animHeight = _vm->_video->_surfHeight;
_objCount = 4;
- delete[] _objects;
delete[] _orderArray;
delete[] _renderObjs;
- delete[] _animArrayX;
- delete[] _animArrayY;
+ delete _animArrayX;
+ delete _animArrayY;
delete[] _animArrayData;
_objects = new Mult_Object[_objCount];
_orderArray = new int8[_objCount];
_renderObjs = new Mult_Object*[_objCount];
- _animArrayX = new int32[_objCount];
- _animArrayY = new int32[_objCount];
+ _animArrayX = new VariablesLE(_objCount * 4);
+ _animArrayY = new VariablesLE(_objCount * 4);
_animArrayData = new Mult_AnimData[_objCount];
memset(_objects, 0, _objCount * sizeof(Mult_Object));
memset(_orderArray, 0, _objCount * sizeof(int8));
memset(_renderObjs, 0, _objCount * sizeof(Mult_Object *));
- memset(_animArrayX, 0, _objCount * sizeof(int32));
- memset(_animArrayY, 0, _objCount * sizeof(int32));
memset(_animArrayData, 0, _objCount * sizeof(Mult_AnimData));
for (_counter = 0; _counter < _objCount; _counter++) {
Mult_Object &multObj = _objects[_counter];
Mult_AnimData &animData = _animArrayData[_counter];
- multObj.pPosX = (int32 *) &_animArrayX[_counter];
- multObj.pPosY = (int32 *) &_animArrayY[_counter];
+ multObj.pPosX = new VariableReference(*_animArrayX, _counter * 4);
+ multObj.pPosY = new VariableReference(*_animArrayY, _counter * 4);
multObj.pAnimData = &animData;
animData.isStatic = 1;
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/gob/scenery.cpp b/engines/gob/scenery.cpp
index 6b52cdbbd6..33e540ace4 100644
--- a/engines/gob/scenery.cpp
+++ b/engines/gob/scenery.cpp
@@ -595,7 +595,7 @@ void Scenery::updateAnim(int16 layer, int16 frame, int16 animation, int16 flags,
int16 destX;
int16 destY;
- if (animation < 0) {
+ if ((_vm->getGameType() == kGameTypeWoodruff) && (animation < 0)) {
// Object video
if (flags & 1) { // Do capture
@@ -736,6 +736,8 @@ void Scenery::updateAnim(int16 layer, int16 frame, int16 animation, int16 flags,
return;
}
+ if ((animation < 0) || (animation >= 10))
+ return;
if ((_animPictCount[animation] == 0) || (layer < 0))
return;
if (layer >= _animations[animation].layersCount)
diff --git a/engines/gob/sound/sound.h b/engines/gob/sound/sound.h
index b59510e4bb..07b5a737db 100644
--- a/engines/gob/sound/sound.h
+++ b/engines/gob/sound/sound.h
@@ -144,4 +144,4 @@ private:
} // End of namespace Gob
-#endif // GOB_SOUND_H
+#endif // GOB_SOUND_SOUND_H
diff --git a/engines/gob/sound/soundmixer.h b/engines/gob/sound/soundmixer.h
index 5789885a99..3e8e6b5c1b 100644
--- a/engines/gob/sound/soundmixer.h
+++ b/engines/gob/sound/soundmixer.h
@@ -37,7 +37,7 @@ namespace Gob {
class SoundMixer : public Audio::AudioStream {
public:
- SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type = Audio::Mixer::kPlainSoundType);
+ SoundMixer(Audio::Mixer &mixer, Audio::Mixer::SoundType type);
~SoundMixer();
virtual void play(SoundDesc &sndDesc, int16 repCount,
diff --git a/engines/gob/variables.cpp b/engines/gob/variables.cpp
index 0eea2f6547..805aaeb839 100644
--- a/engines/gob/variables.cpp
+++ b/engines/gob/variables.cpp
@@ -308,4 +308,62 @@ uint32 VariablesBE::read32(const byte *buf) const {
return READ_BE_UINT32(buf);
}
+VariableReference::VariableReference() {
+ _vars = 0;
+ _offset = 0;
+}
+
+VariableReference::VariableReference(Variables &vars, uint32 offset, Variables::Type type) {
+ set(vars, offset, type);
+}
+
+VariableReference::~VariableReference() {
+}
+
+void VariableReference::set(Variables &vars, uint32 offset, Variables::Type type) {
+ _vars = &vars;
+ _offset = offset;
+ _type = type;
+}
+
+VariableReference &VariableReference::operator=(uint32 value) {
+ if (_vars) {
+ switch (_type) {
+ case Variables::kVariableType8:
+ _vars->writeOff8(_offset, (uint8) value);
+ break;
+ case Variables::kVariableType16:
+ _vars->writeOff16(_offset, (uint16) value);
+ break;
+ case Variables::kVariableType32:
+ _vars->writeOff32(_offset, value);
+ break;
+ }
+ }
+ return *this;
+}
+
+VariableReference::operator uint32() {
+ if (_vars) {
+ switch (_type) {
+ case Variables::kVariableType8:
+ return (uint32) _vars->readOff8(_offset);
+ case Variables::kVariableType16:
+ return (uint32) _vars->readOff16(_offset);
+ case Variables::kVariableType32:
+ return _vars->readOff32(_offset);
+ }
+ }
+
+ return 0;
+}
+
+VariableReference &VariableReference::operator+=(uint32 value) {
+ return (*this = (*this + value));
+}
+
+VariableReference &VariableReference::operator*=(uint32 value) {
+ return (*this = (*this * value));
+}
+
} // End of namespace Gob
diff --git a/engines/gob/variables.h b/engines/gob/variables.h
index 5989ed38ee..32f160a6bd 100644
--- a/engines/gob/variables.h
+++ b/engines/gob/variables.h
@@ -30,6 +30,12 @@ namespace Gob {
class Variables {
public:
+ enum Type {
+ kVariableType8,
+ kVariableType16,
+ kVariableType32
+ };
+
Variables(uint32 size);
virtual ~Variables();
@@ -142,6 +148,26 @@ protected:
uint32 read32(const byte *buf) const;
};
+class VariableReference {
+ public:
+ VariableReference();
+ VariableReference(Variables &vars, uint32 offset,
+ Variables::Type type = Variables::kVariableType32);
+ ~VariableReference();
+
+ void set(Variables &vars, uint32 offset, Variables::Type type = Variables::kVariableType32);
+
+ VariableReference &operator=(uint32 value);
+ VariableReference &operator+=(uint32 value);
+ VariableReference &operator*=(uint32 value);
+ operator uint32();
+
+ private:
+ Variables *_vars;
+ uint32 _offset;
+ Variables::Type _type;
+};
+
} // End of namespace Gob
#endif // GOB_VARIABLES_H
diff --git a/engines/gob/videoplayer.cpp b/engines/gob/videoplayer.cpp
index 909d39a63b..aa47e6cf84 100644
--- a/engines/gob/videoplayer.cpp
+++ b/engines/gob/videoplayer.cpp
@@ -588,12 +588,14 @@ bool VideoPlayer::doPlay(int16 frame, int16 breakKey,
}
void VideoPlayer::copyPalette(CoktelVideo &video, int16 palStart, int16 palEnd) {
- if ((palStart != -1) && (palEnd != -1))
- memcpy(((char *) (_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3,
- video.getPalette() + palStart * 3,
- (palEnd - palStart + 1) * 3);
- else
- memcpy((char *) _vm->_global->_pPaletteDesc->vgaPal, video.getPalette(), 768);
+ if (palStart < 0)
+ palStart = 0;
+ if (palEnd < 0)
+ palEnd = 255;
+
+ memcpy(((char *) (_vm->_global->_pPaletteDesc->vgaPal)) + palStart * 3,
+ video.getPalette() + palStart * 3,
+ (palEnd - palStart + 1) * 3);
}
void VideoPlayer::writeVideoInfo(const char *videoFile, int16 varX, int16 varY,
diff --git a/engines/igor/igor.cpp b/engines/igor/igor.cpp
index 018709f34f..4d4fb97762 100644
--- a/engines/igor/igor.cpp
+++ b/engines/igor/igor.cpp
@@ -404,7 +404,7 @@ void IgorEngine::playSound(int num, int type) {
debugC(9, kDebugEngine, "playSound() %d", num);
--num;
int soundOffset = -1;
- Audio::Mixer::SoundType soundType = Audio::Mixer::kPlainSoundType;
+ Audio::Mixer::SoundType soundType;
Audio::SoundHandle *soundHandle = 0;
if (type == 1) {
if (_mixer->isSoundHandleActive(_sfxHandle)) {
diff --git a/engines/kyra/animator_lok.cpp b/engines/kyra/animator_lok.cpp
index 1baa78b203..75d5537e4a 100644
--- a/engines/kyra/animator_lok.cpp
+++ b/engines/kyra/animator_lok.cpp
@@ -665,7 +665,7 @@ void Animator_LoK::animRefreshNPC(int character) {
void Animator_LoK::setCharacterDefaultFrame(int character) {
debugC(9, kDebugLevelAnimator, "Animator_LoK::setCharacterDefaultFrame()");
- static uint16 initFrameTable[] = {
+ static const uint16 initFrameTable[] = {
7, 41, 77, 0, 0
};
assert(character < ARRAYSIZE(initFrameTable));
@@ -678,7 +678,7 @@ void Animator_LoK::setCharacterDefaultFrame(int character) {
void Animator_LoK::setCharactersHeight() {
debugC(9, kDebugLevelAnimator, "Animator_LoK::setCharactersHeight()");
- static int8 initHeightTable[] = {
+ static const int8 initHeightTable[] = {
48, 40, 48, 47, 56,
44, 42, 47, 38, 35,
40
diff --git a/engines/kyra/detection.cpp b/engines/kyra/detection.cpp
index 344121b503..2d592069d2 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"
@@ -41,9 +42,11 @@ struct KYRAGameDescription {
namespace {
-#define FLAGS(x, y, z, a, b, c, id) { Common::UNK_LANG, Common::kPlatformUnknown, x, y, z, a, b, c, id }
+#define FLAGS(x, y, z, a, b, c, id) { Common::UNK_LANG, Common::UNK_LANG, Common::UNK_LANG, Common::kPlatformUnknown, x, y, z, a, b, c, id }
+#define FLAGS_FAN(fanLang, repLang, x, y, z, a, b, c, id) { Common::UNK_LANG, fanLang, repLang, Common::kPlatformUnknown, x, y, z, a, b, c, id }
#define KYRA1_FLOPPY_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA1)
+#define KYRA1_FLOPPY_CMP_FLAGS FLAGS(false, false, false, false, false, true, Kyra::GI_KYRA1)
#define KYRA1_AMIGA_FLAGS FLAGS(false, false, false, false, false, false, Kyra::GI_KYRA1)
#define KYRA1_TOWNS_FLAGS FLAGS(false, true, false, false, false, false, Kyra::GI_KYRA1)
#define KYRA1_TOWNS_SJIS_FLAGS FLAGS(false, true, false, true, false, false, Kyra::GI_KYRA1)
@@ -59,13 +62,38 @@ namespace {
#define KYRA2_TOWNS_SJIS_FLAGS FLAGS(false, false, false, true, false, false, Kyra::GI_KYRA2)
#define KYRA3_CD_FLAGS FLAGS(false, false, true, false, true, true, Kyra::GI_KYRA3)
-#define KYRA3_CD_INS_FLAGS FLAGS(false, false, true, false, true, true, Kyra::GI_KYRA3)
+#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)
const KYRAGameDescription adGameDescs[] = {
{
{
"kyra1",
0,
+ AD_ENTRY1("DISK1.EXE", "c8641d0414d6c966d0a3dad79db07bf4"),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ KYRA1_FLOPPY_CMP_FLAGS
+ },
+ {
+ {
+ "kyra1",
+ 0,
+ AD_ENTRY1("DISK1.EXE", "5d5cee4c3d0b68d586788b74243d254a"),
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ KYRA1_FLOPPY_CMP_FLAGS
+ },
+ {
+ {
+ "kyra1",
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "3c244298395520bb62b5edfe41688879"),
Common::EN_ANY,
Common::kPlatformPC,
@@ -76,7 +104,7 @@ const KYRAGameDescription adGameDescs[] = {
{
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "796e44863dd22fa635b042df1bf16673"),
Common::EN_ANY,
Common::kPlatformPC,
@@ -87,7 +115,7 @@ const KYRAGameDescription adGameDescs[] = {
{
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "abf8eb360e79a6c2a837751fbd4d3d24"),
Common::FR_FRA,
Common::kPlatformPC,
@@ -98,7 +126,7 @@ const KYRAGameDescription adGameDescs[] = {
{
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "6018e1dfeaca7fe83f8d0b00eb0dd049"),
Common::DE_DEU,
Common::kPlatformPC,
@@ -109,7 +137,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // from Arne.F
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "f0b276781f47c130f423ec9679fe9ed9"),
Common::DE_DEU,
Common::kPlatformPC,
@@ -120,7 +148,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // from VooD
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "8909b41596913b3f5deaf3c9f1017b01"),
Common::ES_ESP,
Common::kPlatformPC,
@@ -131,7 +159,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // floppy 1.8 from clemmy
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "747861d2a9c643c59fdab570df5b9093"),
Common::ES_ESP,
Common::kPlatformPC,
@@ -142,7 +170,7 @@ const KYRAGameDescription adGameDescs[] = {
{ // from gourry
{
"kyra1",
- 0,
+ "Extracted",
AD_ENTRY1("GEMCUT.EMC", "ef08c8c237ee1473fd52578303fc36df"),
Common::IT_ITA,
Common::kPlatformPC,
@@ -323,7 +351,7 @@ const KYRAGameDescription adGameDescs[] = {
KYRA2_FLOPPY_CMP_FLAGS
},
- { // // Floppy version extracted
+ { // Floppy version extracted
{
"kyra2",
"Extracted",
@@ -463,6 +491,28 @@ const KYRAGameDescription adGameDescs[] = {
},
KYRA2_TOWNS_SJIS_FLAGS
},
+ { // PC-9821
+ {
+ "kyra2",
+ 0,
+ AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"),
+ Common::EN_ANY,
+ Common::kPlatformPC98,
+ Common::ADGF_NO_FLAGS
+ },
+ KYRA2_TOWNS_FLAGS
+ },
+ {
+ {
+ "kyra2",
+ 0,
+ AD_ENTRY1("WSCORE.PAK", "c44de1302b67f27d4707409987b7a685"),
+ Common::JA_JPN,
+ Common::kPlatformPC98,
+ Common::ADGF_NO_FLAGS
+ },
+ KYRA2_TOWNS_SJIS_FLAGS
+ },
// Kyra3
@@ -480,7 +530,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_INS_FLAGS
+ KYRA3_CD_FLAGS
},
{
{
@@ -495,7 +545,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_INS_FLAGS
+ KYRA3_CD_FLAGS
},
{
{
@@ -510,7 +560,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_INS_FLAGS
+ KYRA3_CD_FLAGS
},
// installed version
@@ -527,7 +577,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_FLAGS
+ KYRA3_CD_INS_FLAGS
},
{
{
@@ -542,7 +592,7 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_FLAGS
+ KYRA3_CD_INS_FLAGS
},
{
{
@@ -557,9 +607,152 @@ const KYRAGameDescription adGameDescs[] = {
Common::kPlatformPC,
Common::ADGF_DROPLANGUAGE
},
- KYRA3_CD_FLAGS
+ KYRA3_CD_INS_FLAGS
+ },
+
+ // Spanish fan translation, see fr#1994040 "KYRA3: Add support for Spanish fan translation"
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::ES_ESP,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY)
+ },
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY)
+ },
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "9aaca21d2a205ca02ec53132f2911794", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::FR_FRA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::ES_ESP, Common::EN_ANY)
},
+ // Itlian fan translation, see fr#2003504 "KYRA: add support for Italian version of Kyrandia 2&3"
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA)
+ },
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::DE_DEU,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ KYRA3_CD_FAN_FLAGS(Common::IT_ITA, Common::FR_FRA)
+ },
+ {
+ {
+ "kyra3",
+ 0,
+ {
+ { "ONETIME.PAK", 0, "ee2d4d056a5de5333a3c6bda055b3cb4", -1 },
+ { "AUD.PAK", 0, 0, -1 },
+ { 0, 0, 0, 0 }
+ },
+ Common::IT_ITA,
+ Common::kPlatformPC,
+ Common::ADGF_DROPLANGUAGE
+ },
+ 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
+ },
+
{ AD_TABLE_END_MARKER, FLAGS(0, 0, 0, 0, 0, 0, 0) }
};
@@ -567,6 +760,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 }
};
@@ -639,6 +833,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_hof.cpp b/engines/kyra/gui_hof.cpp
index 555934cb7f..7d56743af5 100644
--- a/engines/kyra/gui_hof.cpp
+++ b/engines/kyra/gui_hof.cpp
@@ -454,14 +454,26 @@ void KyraEngine_HoF::loadBookBkgd() {
void KyraEngine_HoF::showBookPage() {
char filename[16];
- sprintf(filename, "PAGE%.01X.", _bookCurPage);
- strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT");
+ sprintf(filename, "PAGE%.01X.%s", _bookCurPage, _languageExtension[_lang]);
uint8 *leftPage = _res->fileData(filename, 0);
+ if (!leftPage) {
+ // some floppy version use a TXT extension
+ sprintf(filename, "PAGE%.01X.TXT", _bookCurPage);
+ leftPage = _res->fileData(filename, 0);
+ }
+
int leftPageY = _bookPageYOffset[_bookCurPage];
- sprintf(filename, "PAGE%.01X.", _bookCurPage+1);
- strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT");
- uint8 *rightPage = (_bookCurPage != _bookMaxPage) ? _res->fileData(filename, 0) : 0;
+ sprintf(filename, "PAGE%.01X.%s", _bookCurPage+1, _languageExtension[_lang]);
+ uint8 *rightPage = 0;
+ if (_bookCurPage != _bookMaxPage) {
+ rightPage = _res->fileData(filename, 0);
+ if (!rightPage) {
+ sprintf(filename, "PAGE%.01X.TXT", _bookCurPage);
+ rightPage = _res->fileData(filename, 0);
+ }
+ }
+
int rightPageY = _bookPageYOffset[_bookCurPage+1];
_screen->hideMouse();
diff --git a/engines/kyra/gui_lok.cpp b/engines/kyra/gui_lok.cpp
index 6fa30c9e9a..35b343fc25 100644
--- a/engines/kyra/gui_lok.cpp
+++ b/engines/kyra/gui_lok.cpp
@@ -188,6 +188,7 @@ int KyraEngine_LoK::buttonAmuletCallback(Button *caller) {
#pragma mark -
GUI_LoK::GUI_LoK(KyraEngine_LoK *vm, Screen_LoK *screen) : GUI(vm), _vm(vm), _screen(screen) {
+ _lastScreenUpdate = 0;
_menu = 0;
initStaticResource();
_scrollUpFunctor = BUTTON_FUNCTOR(GUI_LoK, this, &GUI_LoK::scrollUp);
@@ -479,7 +480,6 @@ int GUI_LoK::buttonMenuCallback(Button *caller) {
void GUI_LoK::getInput() {
Common::Event event;
- static uint32 lastScreenUpdate = 0;
uint32 now = _vm->_system->getMillis();
_mouseWheel = 0;
@@ -496,7 +496,7 @@ void GUI_LoK::getInput() {
break;
case Common::EVENT_MOUSEMOVE:
_vm->_system->updateScreen();
- lastScreenUpdate = now;
+ _lastScreenUpdate = now;
break;
case Common::EVENT_WHEELUP:
_mouseWheel = -1;
@@ -512,9 +512,9 @@ void GUI_LoK::getInput() {
}
}
- if (now - lastScreenUpdate > 50) {
+ if (now - _lastScreenUpdate > 50) {
_vm->_system->updateScreen();
- lastScreenUpdate = now;
+ _lastScreenUpdate = now;
}
_vm->_system->delayMillis(3);
@@ -1056,7 +1056,7 @@ void GUI_LoK::fadePalette() {
if (_vm->gameFlags().platform == Common::kPlatformAmiga)
return;
- static int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1};
+ static const int16 menuPalIndexes[] = {248, 249, 250, 251, 252, 253, 254, -1};
int index = 0;
memcpy(_screen->getPalette(2), _screen->_currentPalette, 768);
diff --git a/engines/kyra/gui_lok.h b/engines/kyra/gui_lok.h
index 607ef0b605..49081c7ae2 100644
--- a/engines/kyra/gui_lok.h
+++ b/engines/kyra/gui_lok.h
@@ -157,6 +157,8 @@ private:
KyraEngine_LoK *_vm;
Screen_LoK *_screen;
+ uint32 _lastScreenUpdate;
+
bool _menuRestoreScreen;
uint8 _toplevelMenu;
int _savegameOffset;
diff --git a/engines/kyra/items_lok.cpp b/engines/kyra/items_lok.cpp
index 8eb62c20c2..2c2903d0b3 100644
--- a/engines/kyra/items_lok.cpp
+++ b/engines/kyra/items_lok.cpp
@@ -39,7 +39,7 @@
namespace Kyra {
int KyraEngine_LoK::findDuplicateItemShape(int shape) {
- static uint8 dupTable[] = {
+ static const uint8 dupTable[] = {
0x48, 0x46, 0x49, 0x47, 0x4a, 0x46, 0x4b, 0x47,
0x4c, 0x46, 0x4d, 0x47, 0x5b, 0x5a, 0x5c, 0x5a,
0x5d, 0x5a, 0x5e, 0x5a, 0xFF, 0xFF
diff --git a/engines/kyra/kyra_hof.cpp b/engines/kyra/kyra_hof.cpp
index 57f0dcc24a..d3de621707 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) {
@@ -296,6 +296,9 @@ int KyraEngine_HoF::go() {
_res->loadFileList("FILEDATA.FDT");
else
_res->loadFileList(_ingamePakList, _ingamePakListSize);
+
+ if (_flags.platform == Common::kPlatformPC98)
+ _res->loadPakFile("AUDIO.PAK");
}
_menuDirectlyToLoad = (_menuChoice == 3) ? true : false;
@@ -1561,7 +1564,7 @@ void KyraEngine_HoF::snd_playSoundEffect(int track, int volume) {
int16 vocIndex = (int16)READ_LE_UINT16(&_ingameSoundIndex[track * 2]);
if (vocIndex != -1)
_sound->voicePlay(_ingameSoundList[vocIndex], true);
- else if (_flags.platform == Common::kPlatformPC)
+ else if (_flags.platform != Common::kPlatformFMTowns)
// TODO ?? Maybe there is a way to let users select whether they want
// voc, midi or adl sfx (even though it makes no sense to choose anything but voc).
KyraEngine_v1::snd_playSoundEffect(track);
@@ -2021,6 +2024,9 @@ void KyraEngine_HoF::writeSettings() {
break;
}
+ if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG)
+ _flags.lang = _flags.fanLang;
+
ConfMan.set("language", Common::getLanguageCode(_flags.lang));
KyraEngine_v1::writeSettings();
diff --git a/engines/kyra/kyra_mr.cpp b/engines/kyra/kyra_mr.cpp
index 8a49b8e155..a4e5b58364 100644
--- a/engines/kyra/kyra_mr.cpp
+++ b/engines/kyra/kyra_mr.cpp
@@ -343,6 +343,14 @@ void KyraEngine_MR::initMainMenu() {
0x80, 0xFF
};
+ if (_flags.lang == Common::ES_ESP) {
+ for (int i = 0; i < 4; ++i)
+ data.strings[i] = _mainMenuSpanishFan[i];
+ } else if (_flags.lang == Common::IT_ITA) {
+ for (int i = 0; i < 4; ++i)
+ data.strings[i] = _mainMenuItalianFan[i];
+ }
+
MainMenu::Animation anim;
anim.anim = _menuAnim;
anim.startFrame = 29;
@@ -1543,6 +1551,9 @@ void KyraEngine_MR::writeSettings() {
break;
}
+ if (_flags.lang == _flags.replacedLang && _flags.fanLang != Common::UNK_LANG)
+ _flags.lang = _flags.fanLang;
+
ConfMan.set("language", Common::getLanguageCode(_flags.lang));
ConfMan.setBool("studio_audience", _configStudio);
diff --git a/engines/kyra/kyra_mr.h b/engines/kyra/kyra_mr.h
index 5af138373c..5f9f6f91a3 100644
--- a/engines/kyra/kyra_mr.h
+++ b/engines/kyra/kyra_mr.h
@@ -184,9 +184,12 @@ private:
private:
// main menu
- const char *const *_mainMenuStrings;
+ const char * const *_mainMenuStrings;
int _mainMenuStringsSize;
+ static const char * const _mainMenuSpanishFan[];
+ static const char * const _mainMenuItalianFan[];
+
// animator
uint8 *_gamePlayBuffer;
void restorePage3();
diff --git a/engines/kyra/kyra_v1.cpp b/engines/kyra/kyra_v1.cpp
index 1cc1d728bf..85c03dc1bb 100644
--- a/engines/kyra/kyra_v1.cpp
+++ b/engines/kyra/kyra_v1.cpp
@@ -107,14 +107,16 @@ int KyraEngine_v1::init() {
// "KYRA1: Crash on exceeded polyphony" for more information).
int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB/* | MDT_PREFER_MIDI*/);
- if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) {
- // TODO: currently we don't support the PC98 sound data,
- // but since it has the FM-Towns data files, we just use the
- // FM-Towns driver
+ if (_flags.platform == Common::kPlatformFMTowns) {
if (_flags.gameID == GI_KYRA1)
_sound = new SoundTowns(this, _mixer);
else
- _sound = new SoundTowns_v2(this, _mixer);
+ _sound = new SoundTownsPC98_v2(this, _mixer);
+ } else if (_flags.platform == Common::kPlatformPC98) {
+ if (_flags.gameID == GI_KYRA1)
+ _sound = new SoundTowns/*SoundPC98*/(this, _mixer);
+ else
+ _sound = new SoundTownsPC98_v2(this, _mixer);
} else if (midiDriver == MD_ADLIB) {
_sound = new SoundAdlibPC(this, _mixer);
assert(_sound);
@@ -171,36 +173,6 @@ int KyraEngine_v1::init() {
_gameToLoad = -1;
}
- _lang = 0;
- Common::Language lang = Common::parseLanguage(ConfMan.get("language"));
-
- if (_flags.gameID == GI_KYRA2 || _flags.gameID == GI_KYRA3) {
- switch (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;
-
- case Common::JA_JPN:
- _lang = 3;
- break;
-
- default:
- warning("unsupported language, switching back to English");
- _lang = 0;
- break;
- }
- }
-
return 0;
}
@@ -275,6 +247,14 @@ void KyraEngine_v1::delayWithTicks(int ticks) {
void KyraEngine_v1::registerDefaultSettings() {
if (_flags.gameID != GI_KYRA3)
ConfMan.registerDefault("cdaudio", (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98));
+ if (_flags.fanLang != Common::UNK_LANG) {
+ // HACK/WORKAROUND: Since we can't use registerDefault here to overwrite
+ // the global subtitles settings, we're using this hack to enable subtitles
+ // for fan translations
+ const Common::ConfigManager::Domain *cur = ConfMan.getActiveDomain();
+ if (!cur || (cur && cur->get("subtitles").empty()))
+ ConfMan.setBool("subtitles", true);
+ }
}
void KyraEngine_v1::readSettings() {
diff --git a/engines/kyra/kyra_v1.h b/engines/kyra/kyra_v1.h
index 4f38ceca98..50cabc421e 100644
--- a/engines/kyra/kyra_v1.h
+++ b/engines/kyra/kyra_v1.h
@@ -44,6 +44,11 @@ namespace Kyra {
struct GameFlags {
Common::Language lang;
+
+ // language overwrites of fan translations (only needed for multilingual games)
+ Common::Language fanLang;
+ Common::Language replacedLang;
+
Common::Platform platform;
bool isDemo : 1;
@@ -59,7 +64,8 @@ struct GameFlags {
enum {
GI_KYRA1 = 0,
GI_KYRA2 = 1,
- GI_KYRA3 = 2
+ GI_KYRA3 = 2,
+ GI_LOL = 4
};
struct AudioDataStruct {
@@ -212,7 +218,6 @@ protected:
// detection
GameFlags _flags;
- int _lang;
// opcode
virtual void setupOpcodeTable() = 0;
diff --git a/engines/kyra/kyra_v2.cpp b/engines/kyra/kyra_v2.cpp
index 12da338843..2e704f2aa2 100644
--- a/engines/kyra/kyra_v2.cpp
+++ b/engines/kyra/kyra_v2.cpp
@@ -23,6 +23,8 @@
*
*/
+#include "common/config-manager.h"
+
#include "kyra/kyra_v2.h"
#include "kyra/screen_v2.h"
#include "kyra/debugger.h"
@@ -70,6 +72,36 @@ KyraEngine_v2::KyraEngine_v2(OSystem *system, const GameFlags &flags, const Engi
memset(&_mainCharacter.inventory, -1, sizeof(_mainCharacter.inventory));
_pauseStart = 0;
+
+ _lang = 0;
+ Common::Language lang = Common::parseLanguage(ConfMan.get("language"));
+ if (lang == _flags.fanLang && _flags.replacedLang != Common::UNK_LANG)
+ lang = _flags.replacedLang;
+
+ switch (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;
+
+ case Common::JA_JPN:
+ _lang = 3;
+ break;
+
+ default:
+ warning("unsupported language, switching back to English");
+ _lang = 0;
+ break;
+ }
}
KyraEngine_v2::~KyraEngine_v2() {
diff --git a/engines/kyra/kyra_v2.h b/engines/kyra/kyra_v2.h
index 24f7aad614..6fdf30fff8 100644
--- a/engines/kyra/kyra_v2.h
+++ b/engines/kyra/kyra_v2.h
@@ -94,6 +94,9 @@ protected:
virtual void update() = 0;
virtual void updateWithText() = 0;
+ // detection
+ int _lang;
+
// MainMenu
MainMenu *_menu;
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..92818aafe1 100644
--- a/engines/kyra/resource.cpp
+++ b/engines/kyra/resource.cpp
@@ -55,10 +55,12 @@ bool Resource::reset() {
if (!dir.exists() || !dir.isDirectory())
error("invalid game path '%s'", dir.getPath().c_str());
- if (!loadPakFile(StaticResource::staticDataFilename()) || !StaticResource::checkKyraDat()) {
- Common::String errorMessage = "You're missing the '" + StaticResource::staticDataFilename() + "' file or it got corrupted, (re)get it from the ScummVM website";
- _vm->GUIErrorMessage(errorMessage);
- error(errorMessage.c_str());
+ if (_vm->game() != GI_LOL) {
+ if (!loadPakFile(StaticResource::staticDataFilename()) || !StaticResource::checkKyraDat()) {
+ Common::String errorMessage = "You're missing the '" + StaticResource::staticDataFilename() + "' file or it got corrupted, (re)get it from the ScummVM website";
+ _vm->GUIErrorMessage(errorMessage);
+ error(errorMessage.c_str());
+ }
}
if (_vm->game() == GI_KYRA1) {
@@ -99,6 +101,8 @@ bool Resource::reset() {
loadFileList("FILEDATA.FDT");
return true;
+ } else if (_vm->game() == GI_LOL) {
+ return true;
}
FSList fslist;
@@ -1120,7 +1124,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 +1189,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/scene_hof.cpp b/engines/kyra/scene_hof.cpp
index 1882386b03..62df683ea2 100644
--- a/engines/kyra/scene_hof.cpp
+++ b/engines/kyra/scene_hof.cpp
@@ -723,7 +723,7 @@ void KyraEngine_HoF::fadeScenePal(int srcIndex, int delayTime) {
bool KyraEngine_HoF::lineIsPassable(int x, int y) {
debugC(9, kDebugLevelMain, "KyraEngine_HoF::lineIsPassable(%d, %d)", x, y);
- static int unkTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 };
+ static const int widthTable[] = { 1, 1, 1, 1, 1, 2, 4, 6, 8 };
if (_pathfinderFlag & 2) {
if (x >= 320)
@@ -743,7 +743,7 @@ bool KyraEngine_HoF::lineIsPassable(int x, int y) {
if (y > 143)
return false;
- int unk1 = unkTable[getScale(x, y) >> 5];
+ int unk1 = widthTable[getScale(x, y) >> 5];
if (y < 0)
y = 0;
diff --git a/engines/kyra/scene_lok.cpp b/engines/kyra/scene_lok.cpp
index e4ae67f751..53c269a926 100644
--- a/engines/kyra/scene_lok.cpp
+++ b/engines/kyra/scene_lok.cpp
@@ -1144,11 +1144,11 @@ void KyraEngine_LoK::setCharactersInDefaultScene() {
}
void KyraEngine_LoK::setCharactersPositions(int character) {
- static uint16 initXPosTable[] = {
+ static const uint16 initXPosTable[] = {
0x3200, 0x0024, 0x2230, 0x2F00, 0x0020, 0x002B,
0x00CA, 0x00F0, 0x0082, 0x00A2, 0x0042
};
- static uint8 initYPosTable[] = {
+ static const uint8 initYPosTable[] = {
0x00, 0xA2, 0x00, 0x42, 0x00,
0x67, 0x67, 0x60, 0x5A, 0x71,
0x76
diff --git a/engines/kyra/screen.cpp b/engines/kyra/screen.cpp
index f00c47ceea..74f7bc6de9 100644
--- a/engines/kyra/screen.cpp
+++ b/engines/kyra/screen.cpp
@@ -92,9 +92,19 @@ bool Screen::init() {
if (_useSJIS) {
if (!_sjisFontData) {
- _sjisFontData = _vm->resource()->fileData("FMT_FNT.ROM", 0);
- if (!_sjisFontData)
- error("missing font rom ('FMT_FNT.ROM') required for this version");
+ // we use the FM-Towns font rom for PC-98, too, until we feel
+ // like adding support for the PC-98 font
+ //if (_vm->gameFlags().platform == Common::kPlatformFMTowns) {
+ // FM-Towns
+ _sjisFontData = _vm->resource()->fileData("FMT_FNT.ROM", 0);
+ if (!_sjisFontData)
+ error("missing font rom ('FMT_FNT.ROM') required for this version");
+ /*} else {
+ // PC-98
+ _sjisFontData = _vm->resource()->fileData("FONT.ROM", 0);
+ if (!_sjisFontData)
+ error("missing font rom ('FONT.ROM') required for this version");
+ }*/
}
if (!_sjisTempPage) {
@@ -370,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;
}
@@ -438,6 +410,61 @@ 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;
+ for (int i = 0; i < 768; ++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;
+ for (int i = 0; i < 768; ++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;
@@ -2432,7 +2459,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.cpp b/engines/kyra/script.cpp
index ef3e259cfa..b10a4b32bf 100644
--- a/engines/kyra/script.cpp
+++ b/engines/kyra/script.cpp
@@ -35,7 +35,7 @@
namespace Kyra {
EMCInterpreter::EMCInterpreter(KyraEngine_v1 *vm) : _vm(vm) {
#define COMMAND(x) { &EMCInterpreter::x, #x }
- static CommandEntry commandProcs[] = {
+ static const CommandEntry commandProcs[] = {
// 0x00
COMMAND(cmd_jmpTo),
COMMAND(cmd_setRetValue),
@@ -132,6 +132,8 @@ bool EMCInterpreter::load(const char *filename, EMCData *scriptData, const Commo
scriptData->opcodes = opcodes;
+ strncpy(scriptData->filename, filename, 13);
+
return true;
}
@@ -205,7 +207,7 @@ bool EMCInterpreter::run(EMCState *script) {
}
if (opcode > 18) {
- error("Script unknown command: %d", opcode);
+ error("Script unknown command: %d in file '%s' at offset 0x%.08X", opcode, script->dataPtr->filename, instOffset);
} else {
debugC(5, kDebugLevelScript, "[0x%.08X] EMCInterpreter::%s([%d/%u])", instOffset, _commands[opcode].desc, _parameter, (uint)_parameter);
(this->*(_commands[opcode].proc))(script);
@@ -388,7 +390,7 @@ void EMCInterpreter::cmd_execOpcode(EMCState* script) {
script->retValue = (*(*script->dataPtr->opcodes)[opcode])(script);
} else {
script->retValue = 0;
- warning("calling unimplemented opcode(0x%.02X/%d)", opcode, opcode);
+ warning("Calling unimplemented opcode(0x%.02X/%d) from file '%s'", opcode, opcode, script->dataPtr->filename);
}
}
diff --git a/engines/kyra/script.h b/engines/kyra/script.h
index de52093f66..2b97a83289 100644
--- a/engines/kyra/script.h
+++ b/engines/kyra/script.h
@@ -36,6 +36,8 @@ struct EMCState;
typedef Common::Functor1<EMCState*, int> Opcode;
struct EMCData {
+ char filename[13];
+
byte *text;
uint16 *data;
uint16 *ordr;
diff --git a/engines/kyra/script_hof.cpp b/engines/kyra/script_hof.cpp
index 91fbfb3e49..e3a8bf95bc 100644
--- a/engines/kyra/script_hof.cpp
+++ b/engines/kyra/script_hof.cpp
@@ -799,10 +799,14 @@ int KyraEngine_HoF::o2_showLetter(EMCState *script) {
_screen->fadeToBlack(0x14);
- sprintf(filename, "LETTER%.1d.", letter);
- strcat(filename, (_flags.isTalkie || _flags.platform == Common::kPlatformFMTowns || _lang) ? _languageExtension[_lang] : "TXT");
-
+ sprintf(filename, "LETTER%.1d.%s", letter, _languageExtension[_lang]);
uint8 *letterBuffer = _res->fileData(filename, 0);
+ if (!letterBuffer) {
+ // some floppy versions use a TXT extension
+ sprintf(filename, "LETTER%.1d.TXT", letter);
+ letterBuffer = _res->fileData(filename, 0);
+ }
+
if (letterBuffer) {
bookDecodeText(letterBuffer);
bookPrintText(2, letterBuffer, 0xC, 0xA, 0x20);
@@ -1488,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 4ad6464424..7993fb8de6 100644
--- a/engines/kyra/script_tim.cpp
+++ b/engines/kyra/script_tim.cpp
@@ -26,58 +26,75 @@
#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 }
- static CommandEntry commandProcs[] = {
+#define cmd_return(n) cmd_return_##n
+ static const CommandEntry commandProcs[] = {
// 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>),
+ COMMAND(cmd_return(1)),
COMMAND(cmd_execOpcode),
COMMAND(cmd_initFuncNow),
COMMAND(cmd_stopFuncNow),
// 0x1C
- COMMAND(cmd_return<1>),
- COMMAND(cmd_return<1>),
- COMMAND(cmd_return<-1>)
+ COMMAND(cmd_return(1)),
+ COMMAND(cmd_return(1)),
+ COMMAND(cmd_return(n1))
};
+#undef cmd_return
_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) {
@@ -122,6 +139,8 @@ TIM *TIMInterpreter::load(const char *filename, const Common::Array<const TIMOpc
for (int i = 0; i < num; ++i)
tim->func[i].avtl = tim->avtl + tim->avtl[i];
+ strncpy(tim->filename, filename, 13);
+
return tim;
}
@@ -135,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;
@@ -171,6 +195,10 @@ void TIMInterpreter::exec(TIM *tim, bool loop) {
_currentTim->procFunc = _currentFunc;
break;
+ case 22:
+ cur.loopIp = 0;
+ break;
+
default:
break;
}
@@ -197,22 +225,224 @@ 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", cmd);
+ warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename);
return 0;
}
if (_commands[cmd].proc == 0) {
- warning("Calling unimplemented TIM command %d", cmd);
+ warning("Calling unimplemented TIM command %d from file '%s'", cmd, _currentTim->filename);
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;
@@ -226,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);
@@ -243,6 +513,97 @@ 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)));
+
+ static char * fileList[] = { 0 };
+ fileList[0] = _audioFilename;
+ static AudioDataStruct audioList = { fileList, 1, 0, 0 };
+
+ strncpy(_audioFilename, file, sizeof(_audioFilename));
+
+ _vm->sound()->setSoundList(&audioList);
+ _vm->sound()->loadSoundFile(0);
+ 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)
@@ -252,14 +613,20 @@ 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)", opcode, opcode);
+ 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;
}
diff --git a/engines/kyra/script_tim.h b/engines/kyra/script_tim.h
index cd715ff4ef..68ef23fd6c 100644
--- a/engines/kyra/script_tim.h
+++ b/engines/kyra/script_tim.h
@@ -30,13 +30,18 @@
#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;
struct TIM {
+ char filename[13];
+
int16 procFunc;
uint16 procParam;
@@ -50,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;
@@ -61,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; }
@@ -72,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;
@@ -83,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 *);
@@ -96,14 +145,30 @@ 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);
int cmd_stopFuncNow(const uint16 *param);
- template<int T>
- int cmd_return(const uint16 *) { return T; }
+#define cmd_return(n, v) \
+ int cmd_return_##n(const uint16 *) { return v; }
+
+ cmd_return( 1, 1);
+ cmd_return(n1, -1);
+#undef cmd_return
};
} // end of namespace Kyra
diff --git a/engines/kyra/seqplayer.cpp b/engines/kyra/seqplayer.cpp
index 73d69ef10c..dfda5bf859 100644
--- a/engines/kyra/seqplayer.cpp
+++ b/engines/kyra/seqplayer.cpp
@@ -500,7 +500,7 @@ bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) {
debugC(9, kDebugLevelSequence, "SeqPlayer::seq_playSequence(%p, %d)", (const void *)seqData, skipSeq);
assert(seqData);
- static SeqEntry floppySeqProcs[] = {
+ static const SeqEntry floppySeqProcs[] = {
// 0x00
SEQOP(3, s1_wsaOpen),
SEQOP(2, s1_wsaClose),
@@ -541,7 +541,7 @@ bool SeqPlayer::playSequence(const uint8 *seqData, bool skipSeq) {
SEQOP(1, s1_endOfScript)
};
- static SeqEntry cdromSeqProcs[] = {
+ static const SeqEntry cdromSeqProcs[] = {
// 0x00
SEQOP(3, s1_wsaOpen),
SEQOP(2, s1_wsaClose),
diff --git a/engines/kyra/sequences_lok.cpp b/engines/kyra/sequences_lok.cpp
index b30568c7e2..3a497a258f 100644
--- a/engines/kyra/sequences_lok.cpp
+++ b/engines/kyra/sequences_lok.cpp
@@ -1083,7 +1083,7 @@ void KyraEngine_LoK::seq_playCredits() {
_screen->_charWidth = -1;
// we only need this for the fm-towns version
- if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98)
+ if (_flags.platform == Common::kPlatformFMTowns && _configMusic == 1)
snd_playWanderScoreViaMap(53, 1);
uint8 *buffer = 0;
diff --git a/engines/kyra/sound.cpp b/engines/kyra/sound.cpp
index f56c43aabd..c8749dc06b 100644
--- a/engines/kyra/sound.cpp
+++ b/engines/kyra/sound.cpp
@@ -202,8 +202,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);
}
@@ -243,27 +243,14 @@ int SoundMidiPC::open() {
}
void SoundMidiPC::close() {
- if (_driver)
+ if (_driver) {
_driver->close();
+ delete _driver;
+ }
_driver = 0;
}
void SoundMidiPC::send(uint32 b) {
- // HACK: For Kyrandia, we make the simplifying assumption that a song
- // either loops in its entirety, or not at all. So if we see a FOR_LOOP
- // controller event, we turn on looping even if there isn't any
- // corresponding NEXT_BREAK event.
- //
- // This is a gross over-simplification of how XMIDI handles loops. If
- // anyone feels like doing a proper implementation, please refer to
- // the Exult project, and do it in midiparser_xmidi.cpp
-
- if ((b & 0xFFF0) == 0x74B0 && _eventFromMusic) {
- debugC(9, kDebugLevelMain | kDebugLevelSound, "SoundMidiPC: Looping song");
- _musicParser->property(MidiParser::mpAutoLoop, true);
- return;
- }
-
if (_passThrough) {
if ((b & 0xFFF0) == 0x007BB0)
return;
diff --git a/engines/kyra/sound.h b/engines/kyra/sound.h
index 1baeb3064a..cebfdf491f 100644
--- a/engines/kyra/sound.h
+++ b/engines/kyra/sound.h
@@ -73,7 +73,8 @@ public:
kAdlib,
kMidiMT32,
kMidiGM,
- kTowns
+ kTowns,
+ kPC98
};
virtual kType getMusicType() const = 0;
@@ -382,7 +383,9 @@ private:
Common::Mutex _mutex;
};
-class SoundTowns_EuphonyDriver;
+class Towns_EuphonyDriver;
+class TownsPC98_OpnDriver;
+
class SoundTowns : public MidiDriver, public Sound {
public:
SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer);
@@ -417,6 +420,7 @@ public:
static float semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiToneRootkey,
uint32 sampleRate, uint32 outputRate, int32 pitchWheel);
+
private:
bool loadInstruments();
void playEuphonyTrack(uint32 offset, int loop);
@@ -430,7 +434,7 @@ private:
uint _sfxFileIndex;
uint8 *_sfxFileData;
- SoundTowns_EuphonyDriver * _driver;
+ Towns_EuphonyDriver * _driver;
MidiParser * _parser;
Common::Mutex _mutex;
@@ -439,13 +443,38 @@ private:
const uint8 *_sfxWDTable;
};
-//class SoundTowns_v2_TwnDriver;
-class SoundTowns_v2 : public Sound {
+class SoundPC98 : public Sound {
public:
- SoundTowns_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer);
- ~SoundTowns_v2();
+ SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ ~SoundPC98();
- kType getMusicType() const { return kTowns; }
+ virtual kType getMusicType() const { return kPC98; }
+
+ bool init();
+
+ void process() {}
+ void loadSoundFile(uint file) {}
+
+ void playTrack(uint8 track);
+ void haltTrack();
+ void beginFadeOut();
+
+ int32 voicePlay(const char *file, bool isSfx = false) { return -1; }
+ void playSoundEffect(uint8);
+
+protected:
+ int _lastTrack;
+ uint8 *_musicTrackData;
+ uint8 *_sfxTrackData;
+ TownsPC98_OpnDriver *_driver;
+};
+
+class SoundTownsPC98_v2 : public Sound {
+public:
+ SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer);
+ ~SoundTownsPC98_v2();
+
+ kType getMusicType() const { return _vm->gameFlags().platform == Common::kPlatformFMTowns ? kTowns : kPC98; }
bool init();
void process();
@@ -457,15 +486,15 @@ public:
void beginFadeOut();
int32 voicePlay(const char *file, bool isSfx = false);
- void playSoundEffect(uint8) {}
-
-private:
- int _lastTrack;
+ void playSoundEffect(uint8 track);
+protected:
Audio::AudioStream *_currentSFX;
+ int _lastTrack;
+ bool _useFmSfx;
- //SoundTowns_v2_TwnDriver *_driver;
- uint8 *_twnTrackData;
+ uint8 *_musicTrackData;
+ TownsPC98_OpnDriver *_driver;
};
class MixedSoundDriver : public Sound {
diff --git a/engines/kyra/sound_adlib.cpp b/engines/kyra/sound_adlib.cpp
index 68a2f0be9c..0ceb288b8a 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,7 +2220,7 @@ 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);
diff --git a/engines/kyra/sound_lok.cpp b/engines/kyra/sound_lok.cpp
index 8a1d16a6b1..b43d72ebce 100644
--- a/engines/kyra/sound_lok.cpp
+++ b/engines/kyra/sound_lok.cpp
@@ -43,19 +43,29 @@ void KyraEngine_LoK::snd_playWanderScoreViaMap(int command, int restart) {
if (restart)
_lastMusicCommand = -1;
- if (_flags.platform == Common::kPlatformFMTowns || _flags.platform == Common::kPlatformPC98) {
+ if (_flags.platform == Common::kPlatformFMTowns) {
if (command == 1) {
_sound->beginFadeOut();
} else if (command >= 35 && command <= 38) {
snd_playSoundEffect(command-20);
} else if (command >= 2) {
- if (_lastMusicCommand != command) {
+ if (_lastMusicCommand != command)
// the original does -2 here we handle this inside _sound->playTrack()
_sound->playTrack(command);
- }
} else {
_sound->haltTrack();
}
+ _lastMusicCommand = command;
+ } else if (_flags.platform == Common::kPlatformPC98) {
+ if (command == 1) {
+ _sound->beginFadeOut();
+ } else if (command >= 2) {
+ if (_lastMusicCommand != command)
+ _sound->playTrack(command);
+ } else {
+ _sound->haltTrack();
+ }
+ _lastMusicCommand = command;
} else {
KyraEngine_v1::snd_playWanderScoreViaMap(command, restart);
}
diff --git a/engines/kyra/sound_towns.cpp b/engines/kyra/sound_towns.cpp
index 4265533507..0f2b916c9d 100644
--- a/engines/kyra/sound_towns.cpp
+++ b/engines/kyra/sound_towns.cpp
@@ -34,18 +34,16 @@
#include "common/util.h"
-#include <math.h>
-
#define EUPHONY_FADEOUT_TICKS 600
namespace Kyra {
-enum ChannelState { _s_ready, _s_attacking, _s_decaying, _s_sustaining, _s_releasing };
+enum EnvelopeState { s_ready, s_attacking, s_decaying, s_sustaining, s_releasing };
-class MidiChannel_EuD : public MidiChannel {
+class Towns_EuphonyChannel : public MidiChannel {
public:
- MidiChannel_EuD() {}
- ~MidiChannel_EuD() {}
+ Towns_EuphonyChannel() {}
+ ~Towns_EuphonyChannel() {}
virtual void nextTick(int32 *outbuf, int buflen) = 0;
virtual void rate(uint16 r) = 0;
@@ -54,10 +52,10 @@ protected:
uint16 _rate;
};
-class MidiChannel_EuD_FM : public MidiChannel_EuD {
+class Towns_EuphonyFmChannel : public Towns_EuphonyChannel {
public:
- MidiChannel_EuD_FM();
- virtual ~MidiChannel_EuD_FM();
+ Towns_EuphonyFmChannel();
+ virtual ~Towns_EuphonyFmChannel();
void nextTick(int32 *outbuf, int buflen);
void rate(uint16 r);
@@ -79,13 +77,13 @@ protected:
Voice2612 *_voice;
};
-class MidiChannel_EuD_WAVE : public MidiChannel_EuD {
+class Towns_EuphonyPcmChannel : public Towns_EuphonyChannel {
public:
void nextTick(int32 *outbuf, int buflen);
void rate(uint16 r);
- MidiChannel_EuD_WAVE();
- virtual ~MidiChannel_EuD_WAVE();
+ Towns_EuphonyPcmChannel();
+ virtual ~Towns_EuphonyPcmChannel();
// MidiChannel interface
MidiDriver *device() { return 0; }
@@ -126,9 +124,9 @@ protected:
int32 keyOffset;
int32 keyNote;
const int8 *_samples;
- } * _snd[8];
+ } *_snd[8];
struct Env {
- ChannelState state;
+ EnvelopeState state;
int32 currentLevel;
int32 rate;
int32 tickCount;
@@ -141,40 +139,39 @@ protected:
int32 releaseRate;
int32 rootKeyOffset;
int32 size;
- } * _env[8];
- } * _voice;
+ } *_env[8];
+ } *_voice;
};
-class SoundTowns_EuphonyTrackQueue {
+class Towns_EuphonyTrackQueue {
public:
- SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDriver *driver, SoundTowns_EuphonyTrackQueue *last);
- ~SoundTowns_EuphonyTrackQueue() {}
+ Towns_EuphonyTrackQueue(Towns_EuphonyDriver *driver, Towns_EuphonyTrackQueue *last);
+ ~Towns_EuphonyTrackQueue() {}
- void release();
+ Towns_EuphonyTrackQueue *release();
void initDriver();
- void loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop = 0);
- void loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop = 0);
+ void loadDataToCurrentPosition(uint8 *trackdata, uint32 size, bool loop = 0);
+ void loadDataToEndOfQueue(uint8 *trackdata, uint32 size, bool loop = 0);
void setPlayBackStatus(bool playing);
- SoundTowns_EuphonyTrackQueue * reset();
bool isPlaying() {return _playing; }
- uint8 * trackData() {return _trackData; }
+ uint8 *trackData() {return _trackData; }
bool _loop;
- SoundTowns_EuphonyTrackQueue * _next;
+ Towns_EuphonyTrackQueue *_next;
private:
- uint8 * _trackData;
- uint8 * _used;
- uint8 * _fchan;
- uint8 * _wchan;
+ uint8 *_trackData;
+ uint8 *_used;
+ uint8 *_fchan;
+ uint8 *_wchan;
bool _playing;
- SoundTowns_EuphonyDriver * _driver;
- SoundTowns_EuphonyTrackQueue * _last;
+ Towns_EuphonyDriver *_driver;
+ Towns_EuphonyTrackQueue *_last;
};
-class MidiParser_EuD : public MidiParser {
+class Towns_EuphonyParser : public MidiParser {
public:
- MidiParser_EuD(SoundTowns_EuphonyTrackQueue * queue);
+ Towns_EuphonyParser(Towns_EuphonyTrackQueue * queue);
bool loadMusic (byte *data, uint32 size);
int32 calculateTempo(int16 val);
@@ -183,11 +180,11 @@ protected:
void resetTracking();
void setup();
- byte * _enable;
- byte * _mode;
- byte * _channel;
- byte * _adjVelo;
- int8 * _adjNote;
+ byte *_enable;
+ byte *_mode;
+ byte *_channel;
+ byte *_adjVelo;
+ int8 *_adjNote;
uint8 _firstBaseTickStep;
uint8 _nextBaseTickStep;
@@ -195,13 +192,13 @@ protected:
uint32 _baseTick;
byte _tempo[3];
- SoundTowns_EuphonyTrackQueue * _queue;
+ Towns_EuphonyTrackQueue *_queue;
};
-class SoundTowns_EuphonyDriver : public MidiDriver_Emulated {
+class Towns_EuphonyDriver : public MidiDriver_Emulated {
public:
- SoundTowns_EuphonyDriver(Audio::Mixer *mixer);
- virtual ~SoundTowns_EuphonyDriver();
+ Towns_EuphonyDriver(Audio::Mixer *mixer);
+ virtual ~Towns_EuphonyDriver();
int open();
void close();
@@ -213,7 +210,7 @@ public:
void loadFmInstruments(const byte *instr);
void loadWaveInstruments(const byte *instr);
- SoundTowns_EuphonyTrackQueue * queue() { return _queue; }
+ Towns_EuphonyTrackQueue *queue() { return _queue; }
MidiChannel *allocateChannel() { return 0; }
MidiChannel *getPercussionChannel() { return 0; }
@@ -237,10 +234,10 @@ protected:
void generateSamples(int16 *buf, int len);
- MidiChannel_EuD_FM *_fChannel[6];
- MidiChannel_EuD_WAVE *_wChannel[8];
- MidiChannel_EuD * _channel[16];
- SoundTowns_EuphonyTrackQueue * _queue;
+ Towns_EuphonyFmChannel *_fChannel[6];
+ Towns_EuphonyPcmChannel *_wChannel[8];
+ Towns_EuphonyChannel *_channel[16];
+ Towns_EuphonyTrackQueue *_queue;
int _volume;
bool _fading;
@@ -251,23 +248,23 @@ protected:
int8 * _waveSounds[10];
};
-MidiChannel_EuD_FM::MidiChannel_EuD_FM() {
+Towns_EuphonyFmChannel::Towns_EuphonyFmChannel() {
_voice = new Voice2612;
}
-MidiChannel_EuD_FM::~MidiChannel_EuD_FM() {
+Towns_EuphonyFmChannel::~Towns_EuphonyFmChannel() {
delete _voice;
}
-void MidiChannel_EuD_FM::noteOn(byte note, byte onVelo) {
+void Towns_EuphonyFmChannel::noteOn(byte note, byte onVelo) {
_voice->noteOn(note, onVelo);
}
-void MidiChannel_EuD_FM::noteOff(byte note) {
+void Towns_EuphonyFmChannel::noteOff(byte note) {
_voice->noteOff(note);
}
-void MidiChannel_EuD_FM::controlChange(byte control, byte value) {
+void Towns_EuphonyFmChannel::controlChange(byte control, byte value) {
if (control == 121) {
// Reset controller
delete _voice;
@@ -279,25 +276,25 @@ void MidiChannel_EuD_FM::controlChange(byte control, byte value) {
}
}
-void MidiChannel_EuD_FM::sysEx_customInstrument(uint32, const byte *fmInst) {
+void Towns_EuphonyFmChannel::sysEx_customInstrument(uint32, const byte *fmInst) {
_voice->_rate = _rate;
_voice->setInstrument(fmInst);
}
-void MidiChannel_EuD_FM::pitchBend(int16 value) {
+void Towns_EuphonyFmChannel::pitchBend(int16 value) {
_voice->pitchBend(value);
}
-void MidiChannel_EuD_FM::nextTick(int32 *outbuf, int buflen) {
+void Towns_EuphonyFmChannel::nextTick(int32 *outbuf, int buflen) {
_voice->nextTick((int*) outbuf, buflen);
}
-void MidiChannel_EuD_FM::rate(uint16 r) {
+void Towns_EuphonyFmChannel::rate(uint16 r) {
_rate = r;
_voice->_rate = r;
}
-MidiChannel_EuD_WAVE::MidiChannel_EuD_WAVE() {
+Towns_EuphonyPcmChannel::Towns_EuphonyPcmChannel() {
_voice = new Voice;
for (uint8 i = 0; i < 8; i++) {
_voice->_env[i] = new Voice::Env;
@@ -310,7 +307,7 @@ MidiChannel_EuD_WAVE::MidiChannel_EuD_WAVE() {
_current = -1;
}
-MidiChannel_EuD_WAVE::~MidiChannel_EuD_WAVE() {
+Towns_EuphonyPcmChannel::~Towns_EuphonyPcmChannel() {
for (uint8 i = 0; i < 8; i++) {
if (_voice->_snd[i])
delete _voice->_snd[i];
@@ -319,7 +316,7 @@ MidiChannel_EuD_WAVE::~MidiChannel_EuD_WAVE() {
delete _voice;
}
-void MidiChannel_EuD_WAVE::noteOn(byte note, byte onVelo) {
+void Towns_EuphonyPcmChannel::noteOn(byte note, byte onVelo) {
_note = note;
velocity(onVelo);
_phase = 0;
@@ -329,24 +326,24 @@ void MidiChannel_EuD_WAVE::noteOn(byte note, byte onVelo) {
break;
}
- _voice->_env[_current]->state = _s_attacking;
+ _voice->_env[_current]->state = s_attacking;
_voice->_env[_current]->currentLevel = 0;
_voice->_env[_current]->rate = _rate;
_voice->_env[_current]->tickCount = 0;
}
-void MidiChannel_EuD_WAVE::noteOff(byte note) {
+void Towns_EuphonyPcmChannel::noteOff(byte note) {
if (_current == -1)
return;
- if (_voice->_env[_current]->state == _s_ready)
+ if (_voice->_env[_current]->state == s_ready)
return;
- _voice->_env[_current]->state = _s_releasing;
+ _voice->_env[_current]->state = s_releasing;
_voice->_env[_current]->releaseLevel = _voice->_env[_current]->currentLevel;
_voice->_env[_current]->tickCount = 0;
}
-void MidiChannel_EuD_WAVE::controlChange(byte control, byte value) {
+void Towns_EuphonyPcmChannel::controlChange(byte control, byte value) {
switch (control) {
case 0x07:
// volume
@@ -377,7 +374,7 @@ void MidiChannel_EuD_WAVE::controlChange(byte control, byte value) {
}
}
-void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmInst) {
+void Towns_EuphonyPcmChannel::sysEx_customInstrument(uint32 type, const byte *fmInst) {
if (type == 0x80) {
for (uint8 i = 0; i < 8; i++) {
const byte * const* pos = (const byte * const*) fmInst;
@@ -406,7 +403,7 @@ void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmIns
_voice->split[i] = READ_LE_UINT16(fmInst + 16 + 2 * i);
_voice->id[i] = READ_LE_UINT32(fmInst + 32 + 4 * i);
_voice->_snd[i] = 0;
- _voice->_env[i]->state = _s_ready;
+ _voice->_env[i]->state = s_ready;
_voice->_env[i]->currentLevel = 0;
_voice->_env[i]->totalLevel = *(fmInst + 64 + 8 * i);
_voice->_env[i]->attackRate = *(fmInst + 65 + 8 * i) * 10;
@@ -419,11 +416,11 @@ void MidiChannel_EuD_WAVE::sysEx_customInstrument(uint32 type, const byte *fmIns
}
}
-void MidiChannel_EuD_WAVE::pitchBend(int16 value) {
+void Towns_EuphonyPcmChannel::pitchBend(int16 value) {
_frequencyOffs = value;
}
-void MidiChannel_EuD_WAVE::nextTick(int32 *outbuf, int buflen) {
+void Towns_EuphonyPcmChannel::nextTick(int32 *outbuf, int buflen) {
if (_current == -1 || !_voice->_snd[_current] || !_voice->_env[_current]->state || !_velocity) {
velocity(0);
_current = -1;
@@ -475,13 +472,13 @@ void MidiChannel_EuD_WAVE::nextTick(int32 *outbuf, int buflen) {
}
}
-void MidiChannel_EuD_WAVE::evpNextTick() {
+void Towns_EuphonyPcmChannel::evpNextTick() {
switch (_voice->_env[_current]->state) {
- case _s_ready:
+ case s_ready:
_voice->_env[_current]->currentLevel = 0;
return;
- case _s_attacking:
+ case s_attacking:
if (_voice->_env[_current]->attackRate == 0)
_voice->_env[_current]->currentLevel = _voice->_env[_current]->totalLevel;
else if (_voice->_env[_current]->attackRate >= 1270)
@@ -493,12 +490,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
if (_voice->_env[_current]->currentLevel >= _voice->_env[_current]->totalLevel) {
_voice->_env[_current]->currentLevel = _voice->_env[_current]->totalLevel;
- _voice->_env[_current]->state = _s_decaying;
+ _voice->_env[_current]->state = s_decaying;
_voice->_env[_current]->tickCount = 0;
}
break;
- case _s_decaying:
+ case s_decaying:
if (_voice->_env[_current]->decayRate == 0)
_voice->_env[_current]->currentLevel = _voice->_env[_current]->sustainLevel;
else if (_voice->_env[_current]->decayRate >= 1270)
@@ -512,12 +509,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
if (_voice->_env[_current]->currentLevel <= _voice->_env[_current]->sustainLevel) {
_voice->_env[_current]->currentLevel = _voice->_env[_current]->sustainLevel;
- _voice->_env[_current]->state = _s_sustaining;
+ _voice->_env[_current]->state = s_sustaining;
_voice->_env[_current]->tickCount = 0;
}
break;
- case _s_sustaining:
+ case s_sustaining:
if (_voice->_env[_current]->sustainRate == 0)
_voice->_env[_current]->currentLevel = 0;
else if (_voice->_env[_current]->sustainRate >= 2540)
@@ -531,12 +528,12 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
if (_voice->_env[_current]->currentLevel <= 0) {
_voice->_env[_current]->currentLevel = 0;
- _voice->_env[_current]->state = _s_ready;
+ _voice->_env[_current]->state = s_ready;
_voice->_env[_current]->tickCount = 0;
}
break;
- case _s_releasing:
+ case s_releasing:
if (_voice->_env[_current]->releaseRate == 0)
_voice->_env[_current]->currentLevel = 0;
else if (_voice->_env[_current]->releaseRate >= 1270)
@@ -550,7 +547,7 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
if (_voice->_env[_current]->currentLevel <= 0) {
_voice->_env[_current]->currentLevel = 0;
- _voice->_env[_current]->state = _s_ready;
+ _voice->_env[_current]->state = s_ready;
}
break;
@@ -559,15 +556,15 @@ void MidiChannel_EuD_WAVE::evpNextTick() {
}
}
-void MidiChannel_EuD_WAVE::rate(uint16 r) {
+void Towns_EuphonyPcmChannel::rate(uint16 r) {
_rate = r;
}
-void MidiChannel_EuD_WAVE::velocity(int velo) {
+void Towns_EuphonyPcmChannel::velocity(int velo) {
_velocity = velo;
}
-SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer)
+Towns_EuphonyDriver::Towns_EuphonyDriver(Audio::Mixer *mixer)
: MidiDriver_Emulated(mixer) {
_volume = 255;
_fadestate = EUPHONY_FADEOUT_TICKS;
@@ -576,9 +573,9 @@ SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer)
MidiDriver_YM2612::createLookupTables();
for (uint8 i = 0; i < 6; i++)
- _channel[i] = _fChannel[i] = new MidiChannel_EuD_FM;
+ _channel[i] = _fChannel[i] = new Towns_EuphonyFmChannel;
for (uint8 i = 0; i < 8; i++)
- _channel[i + 6] = _wChannel[i] = new MidiChannel_EuD_WAVE;
+ _channel[i + 6] = _wChannel[i] = new Towns_EuphonyPcmChannel;
_channel[14] = _channel[15] = 0;
_fmInstruments = _waveInstruments = 0;
@@ -587,10 +584,10 @@ SoundTowns_EuphonyDriver::SoundTowns_EuphonyDriver(Audio::Mixer *mixer)
rate(getRate());
fading(0);
- _queue = new SoundTowns_EuphonyTrackQueue(this, 0);
+ _queue = new Towns_EuphonyTrackQueue(this, 0);
}
-SoundTowns_EuphonyDriver::~SoundTowns_EuphonyDriver() {
+Towns_EuphonyDriver::~Towns_EuphonyDriver() {
for (int i = 0; i < 6; i++)
delete _fChannel[i];
for (int i = 0; i < 8; i++)
@@ -622,7 +619,7 @@ SoundTowns_EuphonyDriver::~SoundTowns_EuphonyDriver() {
}
}
-int SoundTowns_EuphonyDriver::open() {
+int Towns_EuphonyDriver::open() {
if (_isOpen)
return MERR_ALREADY_OPEN;
MidiDriver_Emulated::open();
@@ -633,18 +630,18 @@ int SoundTowns_EuphonyDriver::open() {
return 0;
}
-void SoundTowns_EuphonyDriver::close() {
+void Towns_EuphonyDriver::close() {
if (!_isOpen)
return;
_isOpen = false;
_mixer->stopHandle(_mixerSoundHandle);
}
-void SoundTowns_EuphonyDriver::send(uint32 b) {
+void Towns_EuphonyDriver::send(uint32 b) {
send(b & 0xF, b & 0xFFFFFFF0);
}
-void SoundTowns_EuphonyDriver::send(byte chan, uint32 b) {
+void Towns_EuphonyDriver::send(byte chan, uint32 b) {
byte param2 = (byte) ((b >> 16) & 0xFF);
byte param1 = (byte) ((b >> 8) & 0xFF);
byte cmd = (byte) (b & 0xF0);
@@ -703,18 +700,18 @@ void SoundTowns_EuphonyDriver::send(byte chan, uint32 b) {
_channel[chan]->pitchBend((param1 | (param2 << 7)) - 0x2000);
break;
default:
- warning("SoundTowns_EuphonyDriver: Unknown send() command 0x%02X", cmd);
+ warning("Towns_EuphonyDriver: Unknown send() command 0x%02X", cmd);
}
}
-void SoundTowns_EuphonyDriver::loadFmInstruments(const byte *instr) {
+void Towns_EuphonyDriver::loadFmInstruments(const byte *instr) {
if (_fmInstruments)
delete[] _fmInstruments;
_fmInstruments = new uint8[0x1800];
memcpy(_fmInstruments, instr, 0x1800);
}
-void SoundTowns_EuphonyDriver::loadWaveInstruments(const byte *instr) {
+void Towns_EuphonyDriver::loadWaveInstruments(const byte *instr) {
if (_waveInstruments)
delete[] _waveInstruments;
_waveInstruments = new uint8[0x1000];
@@ -739,24 +736,24 @@ void SoundTowns_EuphonyDriver::loadWaveInstruments(const byte *instr) {
}
-void SoundTowns_EuphonyDriver::assignFmChannel(uint8 midiChannelNumber, uint8 fmChannelNumber) {
+void Towns_EuphonyDriver::assignFmChannel(uint8 midiChannelNumber, uint8 fmChannelNumber) {
_channel[midiChannelNumber] = _fChannel[fmChannelNumber];
}
-void SoundTowns_EuphonyDriver::assignWaveChannel(uint8 midiChannelNumber, uint8 waveChannelNumber) {
+void Towns_EuphonyDriver::assignWaveChannel(uint8 midiChannelNumber, uint8 waveChannelNumber) {
_channel[midiChannelNumber] = _wChannel[waveChannelNumber];
}
-void SoundTowns_EuphonyDriver::removeChannel(uint8 midiChannelNumber) {
+void Towns_EuphonyDriver::removeChannel(uint8 midiChannelNumber) {
_channel[midiChannelNumber] = 0;
}
-void SoundTowns_EuphonyDriver::generateSamples(int16 *data, int len) {
+void Towns_EuphonyDriver::generateSamples(int16 *data, int len) {
memset(data, 0, 2 * sizeof(int16) * len);
nextTick(data, len);
}
-void SoundTowns_EuphonyDriver::nextTick(int16 *buf1, int buflen) {
+void Towns_EuphonyDriver::nextTick(int16 *buf1, int buflen) {
int32 *buf0 = (int32 *)buf1;
for (int i = 0; i < ARRAYSIZE(_channel); i++) {
@@ -779,26 +776,26 @@ void SoundTowns_EuphonyDriver::nextTick(int16 *buf1, int buflen) {
}
}
-void SoundTowns_EuphonyDriver::rate(uint16 r) {
+void Towns_EuphonyDriver::rate(uint16 r) {
for (uint8 i = 0; i < 16; i++) {
if (_channel[i])
_channel[i]->rate(r);
}
}
-void SoundTowns_EuphonyDriver::fading(bool status) {
+void Towns_EuphonyDriver::fading(bool status) {
_fading = status;
if (!_fading)
_fadestate = EUPHONY_FADEOUT_TICKS;
}
-MidiParser_EuD::MidiParser_EuD(SoundTowns_EuphonyTrackQueue * queue) : MidiParser(),
+Towns_EuphonyParser::Towns_EuphonyParser(Towns_EuphonyTrackQueue * queue) : MidiParser(),
_firstBaseTickStep(0x33), _nextBaseTickStep(0x33) {
_initialTempo = calculateTempo(0x5a);
_queue = queue;
}
-void MidiParser_EuD::parseNextEvent(EventInfo &info) {
+void Towns_EuphonyParser::parseNextEvent(EventInfo &info) {
byte *pos = _position._play_pos;
if (_queue->_next) {
@@ -878,7 +875,7 @@ void MidiParser_EuD::parseNextEvent(EventInfo &info) {
pos += 6;
}
} else if (cmd == 0xF2) {
- static uint16 tickTable [] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 };
+ static const uint16 tickTable[] = { 0x180, 0xC0, 0x80, 0x60, 0x40, 0x30, 0x20, 0x18 };
_baseTick += tickTable[_nextBaseTickStep >> 4] * ((_nextBaseTickStep & 0x0f) + 1);
_nextBaseTickStep = pos[1];
pos += 6;
@@ -920,15 +917,14 @@ void MidiParser_EuD::parseNextEvent(EventInfo &info) {
_position._play_pos = pos;
}
-bool MidiParser_EuD::loadMusic(byte *data, uint32 size) {
+bool Towns_EuphonyParser::loadMusic(byte *data, uint32 size) {
bool loop = _autoLoop;
if (_queue->isPlaying() && !_queue->_loop) {
_queue->loadDataToEndOfQueue(data, size, loop);
} else {
unloadMusic();
- _queue = _queue->reset();
- _queue->release();
+ _queue = _queue->release();
_queue->loadDataToCurrentPosition(data, size, loop);
setup();
setTrack(0);
@@ -937,7 +933,7 @@ bool MidiParser_EuD::loadMusic(byte *data, uint32 size) {
return true;
}
-int32 MidiParser_EuD::calculateTempo(int16 val) {
+int32 Towns_EuphonyParser::calculateTempo(int16 val) {
int32 tempo = val;
if (tempo < 0)
@@ -953,7 +949,7 @@ int32 MidiParser_EuD::calculateTempo(int16 val) {
return tempo;
}
-void MidiParser_EuD::resetTracking() {
+void Towns_EuphonyParser::resetTracking() {
MidiParser::resetTracking();
_nextBaseTickStep = _firstBaseTickStep;
@@ -962,7 +958,7 @@ void MidiParser_EuD::resetTracking() {
_queue->setPlayBackStatus(false);
}
-void MidiParser_EuD::setup() {
+void Towns_EuphonyParser::setup() {
uint8 *data = _queue->trackData();
if (!data)
return;
@@ -984,7 +980,7 @@ void MidiParser_EuD::setup() {
_tracks[0] = data + 0x806;
}
-SoundTowns_EuphonyTrackQueue::SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDriver * driver, SoundTowns_EuphonyTrackQueue * last) {
+Towns_EuphonyTrackQueue::Towns_EuphonyTrackQueue(Towns_EuphonyDriver * driver, Towns_EuphonyTrackQueue * last) {
_trackData = 0;
_next = 0;
_driver = driver;
@@ -993,22 +989,15 @@ SoundTowns_EuphonyTrackQueue::SoundTowns_EuphonyTrackQueue(SoundTowns_EuphonyDri
_playing = false;
}
-void SoundTowns_EuphonyTrackQueue::setPlayBackStatus(bool playing) {
- SoundTowns_EuphonyTrackQueue * i = this;
+void Towns_EuphonyTrackQueue::setPlayBackStatus(bool playing) {
+ Towns_EuphonyTrackQueue * i = this;
do {
i->_playing = playing;
i = i->_next;
} while (i);
}
-SoundTowns_EuphonyTrackQueue * SoundTowns_EuphonyTrackQueue::reset() {
- SoundTowns_EuphonyTrackQueue * i = this;
- while (i->_last)
- i = i->_last;
- return i;
-}
-
-void SoundTowns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop) {
+void Towns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata, uint32 size, bool loop) {
if (_trackData)
delete[] _trackData;
_trackData = new uint8[0xC58A];
@@ -1022,17 +1011,17 @@ void SoundTowns_EuphonyTrackQueue::loadDataToCurrentPosition(uint8 * trackdata,
_playing = false;
}
-void SoundTowns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop) {
+void Towns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint32 size, bool loop) {
if (!_trackData) {
loadDataToCurrentPosition(trackdata, size, loop);
return;
}
- SoundTowns_EuphonyTrackQueue * i = this;
+ Towns_EuphonyTrackQueue * i = this;
while (i->_next)
i = i->_next;
- i = i->_next = new SoundTowns_EuphonyTrackQueue(_driver, i);
+ i = i->_next = new Towns_EuphonyTrackQueue(_driver, i);
i->_trackData = new uint8[0xC58A];
memset(i->_trackData, 0, 0xC58A);
Screen::decodeFrame4(trackdata, i->_trackData, size);
@@ -1044,29 +1033,39 @@ void SoundTowns_EuphonyTrackQueue::loadDataToEndOfQueue(uint8 * trackdata, uint3
i->_playing = _playing;
}
-void SoundTowns_EuphonyTrackQueue::release() {
- SoundTowns_EuphonyTrackQueue * i = _next;
- _next = 0;
- _playing = false;
- _used = _fchan = _wchan = 0;
+Towns_EuphonyTrackQueue *Towns_EuphonyTrackQueue::release() {
+ Towns_EuphonyTrackQueue *i = this;
+ while (i->_next)
+ i = i->_next;
- if (_trackData) {
- delete[] _trackData;
- _trackData = 0;
- }
+ Towns_EuphonyTrackQueue *res = i;
while (i) {
+ i->_playing = false;
+ i->_used = i->_fchan = i->_wchan = 0;
if (i->_trackData) {
delete[] i->_trackData;
i->_trackData = 0;
}
- i = i->_next;
- if (i)
- delete i->_last;
+ i = i->_last;
+ if (i) {
+ res = i;
+ if (i->_next) {
+ delete i->_next;
+ i->_next = 0;
+ }
+ }
}
+
+ if (res->_trackData) {
+ delete[] res->_trackData;
+ res->_trackData = 0;
+ }
+
+ return res;
}
-void SoundTowns_EuphonyTrackQueue::initDriver() {
+void Towns_EuphonyTrackQueue::initDriver() {
for (uint8 i = 0; i < 6; i++) {
if (_used[_fchan[i]])
_driver->assignFmChannel(_fchan[i], i);
@@ -1084,11 +1083,1685 @@ void SoundTowns_EuphonyTrackQueue::initDriver() {
_driver->send(0x79B0);
}
+class TownsPC98_OpnOperator {
+public:
+ TownsPC98_OpnOperator(double rate, const uint8 *rateTable,
+ const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable,
+ const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable);
+ ~TownsPC98_OpnOperator() {}
+
+ void keyOn();
+ void keyOff();
+ void frequency(int freq);
+ void updatePhaseIncrement();
+ void recalculateRates();
+ void generateOutput(int phasebuf, int *_feedbuf, int &out);
+
+ void feedbackLevel(int32 level) {_feedbackLevel = level ? level + 6 : 0; }
+ void detune(int value) { _detn = &_detnTbl[value << 5]; }
+ void multiple(uint32 value) { _multiple = value ? (value << 1) : 1; }
+ void attackRate(uint32 value) { _specifiedAttackRate = value; }
+ bool scaleRate(uint8 value);
+ void decayRate(uint32 value) { _specifiedDecayRate = value; recalculateRates(); }
+ void sustainRate(uint32 value) { _specifiedSustainRate = value; recalculateRates(); }
+ void sustainLevel(uint32 value) { _sustainLevel = (value == 0x0f) ? 0x3e0 : value << 5; }
+ void releaseRate(uint32 value) { _specifiedReleaseRate = value; recalculateRates(); }
+ void totalLevel(uint32 value) { _totalLevel = value << 3; }
+ void reset();
+
+protected:
+ EnvelopeState _state;
+ uint32 _feedbackLevel;
+ uint32 _multiple;
+ uint32 _totalLevel;
+ uint8 _keyScale1;
+ uint8 _keyScale2;
+ uint32 _specifiedAttackRate;
+ uint32 _specifiedDecayRate;
+ uint32 _specifiedSustainRate;
+ uint32 _specifiedReleaseRate;
+ uint32 _tickCount;
+ uint32 _sustainLevel;
+
+ uint32 _frequency;
+ uint8 _kcode;
+ uint32 _phase;
+ uint32 _phaseIncrement;
+ const int32 *_detn;
+
+ const uint8 *_rateTbl;
+ const uint8 *_rshiftTbl;
+ const uint8 *_adTbl;
+ const uint32 *_fTbl;
+ const uint32 *_sinTbl;
+ const int32 *_tLvlTbl;
+ const int32 *_detnTbl;
+
+ const double _tickLength;
+ double _tick;
+ int32 _currentLevel;
+
+ struct EvpState {
+ uint8 rate;
+ uint8 shift;
+ } fs_a, fs_d, fs_s, fs_r;
+};
+
+TownsPC98_OpnOperator::TownsPC98_OpnOperator(double rate, const uint8 *rateTable,
+ const uint8 *shiftTable, const uint8 *attackDecayTable, const uint32 *frqTable,
+ const uint32 *sineTable, const int32 *tlevelOut, const int32 *detuneTable) :
+ _rateTbl(rateTable), _rshiftTbl(shiftTable), _adTbl(attackDecayTable), _fTbl(frqTable),
+ _sinTbl(sineTable), _tLvlTbl(tlevelOut), _detnTbl(detuneTable), _tickLength(rate * 65536.0),
+ _specifiedAttackRate(0), _specifiedDecayRate(0), _specifiedReleaseRate(0), _specifiedSustainRate(0),
+ _phase(0), _state(s_ready) {
+
+ reset();
+}
+
+void TownsPC98_OpnOperator::keyOn() {
+ _state = s_attacking;
+ _phase = 0;
+}
+
+void TownsPC98_OpnOperator::keyOff() {
+ if (_state != s_ready)
+ _state = s_releasing;
+}
+
+void TownsPC98_OpnOperator::frequency(int freq) {
+ uint8 block = (freq >> 11);
+ uint16 pos = (freq & 0x7ff);
+ uint8 c = pos >> 7;
+ _kcode = (block << 2) | ((c < 7) ? 0 : ((c > 8) ? 3 : c - 6 ));
+ _frequency = _fTbl[pos << 1] >> (7 - block);
+}
+
+void TownsPC98_OpnOperator::updatePhaseIncrement() {
+ _phaseIncrement = ((_frequency + _detn[_kcode]) * _multiple) >> 1;
+ uint8 keyscale = _kcode >> _keyScale1;
+ if (_keyScale2 != keyscale) {
+ _keyScale2 = keyscale;
+ recalculateRates();
+ }
+}
+
+void TownsPC98_OpnOperator::recalculateRates() {
+ int k = _keyScale2;
+ int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0;
+ fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136;
+ fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0;
+
+ r = _specifiedDecayRate ? (_specifiedDecayRate << 1) + 0x20 : 0;
+ fs_d.rate = _rateTbl[r + k];
+ fs_d.shift = _rshiftTbl[r + k];
+
+ r = _specifiedSustainRate ? (_specifiedSustainRate << 1) + 0x20 : 0;
+ fs_s.rate = _rateTbl[r + k];
+ fs_s.shift = _rshiftTbl[r + k];
+
+ r = (_specifiedReleaseRate << 2) + 0x22;
+ fs_r.rate = _rateTbl[r + k];
+ fs_r.shift = _rshiftTbl[r + k];
+}
+
+void TownsPC98_OpnOperator::generateOutput(int phasebuf, int *_feedbuf, int &out) {
+ if (_state == s_ready)
+ return;
+
+ _tick += _tickLength;
+ while (_tick > 0x30000) {
+ _tick -= 0x30000;
+ ++_tickCount;
+
+ int32 levelIncrement = 0;
+ uint32 targetTime = 0;
+ int32 targetLevel = 0;
+ EnvelopeState next_state = s_ready;
+
+ switch (_state) {
+ case s_ready:
+ return;
+ case s_attacking:
+ next_state = s_decaying;
+ targetTime = (1 << fs_a.shift) - 1;
+ targetLevel = 0;
+ levelIncrement = (~_currentLevel * _adTbl[fs_a.rate + ((_tickCount >> fs_a.shift) & 7)]) >> 4;
+ break;
+ case s_decaying:
+ targetTime = (1 << fs_d.shift) - 1;
+ next_state = s_sustaining;
+ targetLevel = _sustainLevel;
+ levelIncrement = _adTbl[fs_d.rate + ((_tickCount >> fs_d.shift) & 7)];
+ break;
+ case s_sustaining:
+ targetTime = (1 << fs_s.shift) - 1;
+ next_state = s_ready;
+ targetLevel = 1023;
+ levelIncrement = _adTbl[fs_s.rate + ((_tickCount >> fs_s.shift) & 7)];
+ break;
+ case s_releasing:
+ targetTime = (1 << fs_r.shift) - 1;
+ next_state = s_ready;
+ targetLevel = 1023;
+ levelIncrement = _adTbl[fs_r.rate + ((_tickCount >> fs_r.shift) & 7)];
+ break;
+ }
+
+ if (!(_tickCount & targetTime)) {
+ _currentLevel += levelIncrement;
+ if ((!targetLevel && _currentLevel <= targetLevel) || (targetLevel && _currentLevel >= targetLevel)) {
+ if (_state != s_decaying)
+ _currentLevel = targetLevel;
+ if (_state != s_sustaining)
+ _state = next_state;
+ }
+ }
+ }
+
+ uint32 lvlout = _totalLevel + (uint32) _currentLevel;
+
+ int outp = 0;
+ int *i = &outp, *o = &outp;
+ int phaseShift = 0;
+
+ if (_feedbuf) {
+ o = &_feedbuf[0];
+ i = &_feedbuf[1];
+ phaseShift = _feedbackLevel ? ((_feedbuf[0] + _feedbuf[1]) << _feedbackLevel) : 0;
+ if (phasebuf == -1)
+ *i = 0;
+ *o = *i;
+ } else {
+ phaseShift = phasebuf << 15;
+ }
+
+ if (lvlout < 832) {
+ uint32 index = (lvlout << 3) + _sinTbl[(((int32)((_phase & 0xffff0000)
+ + phaseShift)) >> 16) & 0x3ff];
+ *i = ((index < 6656) ? _tLvlTbl[index] : 0);
+ } else {
+ *i = 0;
+ }
+
+ _phase += _phaseIncrement;
+ out += *o;
+ if (out > 32767)
+ out = 32767;
+ if (out < -32767)
+ out = -32767;
+}
+
+void TownsPC98_OpnOperator::reset(){
+ keyOff();
+ _tick = 0;
+ _keyScale2 = 0;
+ _currentLevel = 1023;
+
+ frequency(0);
+ detune(0);
+ scaleRate(0);
+ multiple(0);
+ updatePhaseIncrement();
+ attackRate(0);
+ decayRate(0);
+ releaseRate(0);
+ sustainRate(0);
+ feedbackLevel(0);
+ totalLevel(127);
+}
+
+bool TownsPC98_OpnOperator::scaleRate(uint8 value) {
+ value = 3 - value;
+ if (_keyScale1 != value) {
+ _keyScale1 = value;
+ return true;
+ }
+
+ int k = _keyScale2;
+ int r = _specifiedAttackRate ? (_specifiedAttackRate << 1) + 0x20 : 0;
+ fs_a.rate = ((r + k) < 94) ? _rateTbl[r + k] : 136;
+ fs_a.shift = ((r + k) < 94) ? _rshiftTbl[r + k] : 0;
+ return false;
+}
+
+class TownsPC98_OpnDriver;
+class TownsPC98_OpnChannel {
+public:
+ TownsPC98_OpnChannel(TownsPC98_OpnDriver *driver, uint8 regOffs, uint8 flgs, uint8 num,
+ uint8 key, uint8 prt, uint8 id);
+ virtual ~TownsPC98_OpnChannel();
+ virtual void init();
+
+ typedef bool (TownsPC98_OpnChannel::*ControlEventFunc)(uint8 para);
+
+ typedef enum channelState {
+ CHS_RECALCFREQ = 0x01,
+ CHS_KEYOFF = 0x02,
+ CHS_SSG = 0x04,
+ CHS_PITCHWHEELOFF = 0x08,
+ CHS_ALL_BUT_EOT = 0x0f,
+ CHS_EOT = 0x80
+ } ChannelState;
+
+ virtual void loadData(uint8 *data);
+ virtual void processEvents();
+ virtual void processFrequency();
+ bool processControlEvent(uint8 cmd);
+ void writeReg(uint8 regAdress, uint8 value);
+
+ virtual void keyOn();
+ virtual void keyOff();
+
+ void setOutputLevel();
+ void fadeStep();
+ void reset();
+
+ void updateEnv();
+ void generateOutput(int16 &leftSample, int16 &rightSample, int *del, int *feed);
+
+ bool _enableLeft;
+ bool _enableRight;
+ bool _updateEnvelopes;
+ const uint8 _idFlag;
+ int _feedbuf[3];
+
+protected:
+ bool control_dummy(uint8 para);
+ bool control_f0_setPatch(uint8 para);
+ bool control_f1_presetOutputLevel(uint8 para);
+ bool control_f2_setKeyOffTime(uint8 para);
+ bool control_f3_setFreqLSB(uint8 para);
+ bool control_f4_setOutputLevel(uint8 para);
+ bool control_f5_setTempo(uint8 para);
+ bool control_f6_repeatSection(uint8 para);
+ bool control_f7_setupPitchWheel(uint8 para);
+ bool control_f8_togglePitchWheel(uint8 para);
+ bool control_fa_writeReg(uint8 para);
+ bool control_fb_incOutLevel(uint8 para);
+ bool control_fc_decOutLevel(uint8 para);
+ bool control_fd_jump(uint8 para);
+ bool control_ff_endOfTrack(uint8 para);
+
+ bool control_f0_setPatchSSG(uint8 para);
+ bool control_f1_setTotalLevel(uint8 para);
+ bool control_f4_setAlgorithm(uint8 para);
+ bool control_f9_unkSSG(uint8 para);
+ bool control_fb_incOutLevelSSG(uint8 para);
+ bool control_fc_decOutLevelSSG(uint8 para);
+ bool control_ff_endOfTrackSSG(uint8 para);
+
+ uint8 _ticksLeft;
+ uint8 _algorithm;
+ uint8 _instrID;
+ uint8 _totalLevel;
+ uint8 _frqBlockMSB;
+ int8 _frqLSB;
+ uint8 _keyOffTime;
+ bool _protect;
+ uint8 *_dataPtr;
+ uint8 _ptchWhlInitDelayLo;
+ uint8 _ptchWhlInitDelayHi;
+ int16 _ptchWhlModInitVal;
+ uint8 _ptchWhlDuration;
+ uint8 _ptchWhlCurDelay;
+ int16 _ptchWhlModCurVal;
+ uint8 _ptchWhlDurLeft;
+ uint16 frequency;
+ uint8 _regOffset;
+ uint8 _flags;
+ uint8 _ssg1;
+ uint8 _ssg2;
+
+ const uint8 _chanNum;
+ const uint8 _keyNum;
+ const uint8 _part;
+
+ TownsPC98_OpnDriver *_drv;
+ TownsPC98_OpnOperator **_opr;
+ uint16 _frqTemp;
+
+ const ControlEventFunc *controlEvents;
+};
+
+class TownsPC98_OpnChannelSSG : public TownsPC98_OpnChannel {
+public:
+ TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id);
+ ~TownsPC98_OpnChannelSSG() {}
+ void init();
+
+ void processEvents();
+ void processFrequency();
+
+ void keyOn();
+ void keyOff();
+ void loadData(uint8 *data);
+
+private:
+ void opn_SSG_UNK(uint8 a);
+};
+
+
+class TownsPC98_OpnDriver : public Audio::AudioStream {
+friend class TownsPC98_OpnChannel;
+friend class TownsPC98_OpnChannelSSG;
+public:
+ enum OpnType {
+ OD_TOWNS,
+ OD_TYPE26,
+ OD_TYPE86
+ };
+
+ TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type);
+ ~TownsPC98_OpnDriver();
+
+ bool init();
+ void loadData(uint8 *data, bool loadPaused = false);
+ void reset();
+ void fadeOut();
+
+ void pause() { _playing = false; }
+ void cont() { _playing = true; }
+
+ void callback();
+ void nextTick(int16 *buffer, uint32 bufferSize);
+
+ bool looping() { return _looping == _updateChannelsFlag ? true : false; }
+
+ // AudioStream interface
+ int inline readBuffer(int16 *buffer, const int numSamples);
+ bool isStereo() const { return true; }
+ bool endOfData() const { return false; }
+ int getRate() const { return _mixer->getOutputRate(); }
+
+protected:
+ void generateTables();
+
+ TownsPC98_OpnChannel **_channels;
+ TownsPC98_OpnChannelSSG **_ssgChannels;
+ //TownsPC98_OpnChannel *_adpcmChannel;
+
+ void setTempo(uint8 tempo);
+
+ void lock() { _mutex.lock(); }
+ void unlock() { _mutex.unlock(); }
+
+ Audio::Mixer *_mixer;
+ Common::Mutex _mutex;
+ Audio::SoundHandle _soundHandle;
+
+ const uint8 *_opnCarrier;
+ const uint8 *_opnFreqTable;
+ const uint8 *_opnFxCmdLen;
+ const uint8 *_opnLvlPresets;
+
+ uint8 *_oprRates;
+ uint8 *_oprRateshift;
+ uint8 *_oprAttackDecay;
+ uint32 *_oprFrq;
+ uint32 *_oprSinTbl;
+ int32 *_oprLevelOut;
+ int32 *_oprDetune;
+
+ uint8 *_trackData;
+ uint8 *_patches;
+
+ uint8 _cbCounter;
+ uint8 _updateChannelsFlag;
+ uint8 _finishedChannelsFlag;
+ uint16 _tempo;
+ bool _playing;
+ bool _fading;
+ uint8 _looping;
+ uint32 _tickCounter;
+
+ bool _updateEnvelopes;
+ int _ssgFlag;
+
+ int32 _samplesTillCallback;
+ int32 _samplesTillCallbackRemainder;
+ int32 _samplesPerCallback;
+ int32 _samplesPerCallbackRemainder;
+
+ const int _numChan;
+ const int _numSSG;
+ const bool _hasADPCM;
+ const bool _hasStereo;
+
+ double _baserate;
+ static const uint8 _drvTables[];
+ static const uint32 _adtStat[];
+ bool _ready;
+};
+
+TownsPC98_OpnChannel::TownsPC98_OpnChannel(TownsPC98_OpnDriver *driver, uint8 regOffs, uint8 flgs, uint8 num,
+ uint8 key, uint8 prt, uint8 id) : _drv(driver), _regOffset(regOffs), _flags(flgs), _chanNum(num), _keyNum(key),
+ _part(prt), _idFlag(id) {
+
+ _ticksLeft = _algorithm = _instrID = _totalLevel = _frqBlockMSB = _keyOffTime = _ssg1 = _ssg2 = 0;
+ _ptchWhlInitDelayLo = _ptchWhlInitDelayHi = _ptchWhlDuration = _ptchWhlCurDelay = _ptchWhlDurLeft = 0;
+ _frqLSB = 0;
+ _protect = _updateEnvelopes = false;
+ _enableLeft = _enableRight = true;
+ _dataPtr = 0;
+ _ptchWhlModInitVal = _ptchWhlModCurVal = 0;
+ frequency = _frqTemp = 0;
+ memset(&_feedbuf, 0, sizeof(int) * 3);
+ _opr = 0;
+}
+
+TownsPC98_OpnChannel::~TownsPC98_OpnChannel() {
+ if (_opr) {
+ for (int i = 0; i < 4; i++)
+ delete _opr[i];
+ delete [] _opr;
+ }
+}
+
+void TownsPC98_OpnChannel::init() {
+
+ _opr = new TownsPC98_OpnOperator*[4];
+ for (int i = 0; i < 4; i++)
+ _opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift,
+ _drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune);
+
+ #define Control(x) &TownsPC98_OpnChannel::control_##x
+ static const ControlEventFunc ctrlEvents[] = {
+ Control(f0_setPatch),
+ Control(f1_presetOutputLevel),
+ Control(f2_setKeyOffTime),
+ Control(f3_setFreqLSB),
+ Control(f4_setOutputLevel),
+ Control(f5_setTempo),
+ Control(f6_repeatSection),
+ Control(f7_setupPitchWheel),
+ Control(f8_togglePitchWheel),
+ Control(dummy),
+ Control(fa_writeReg),
+ Control(fb_incOutLevel),
+ Control(fc_decOutLevel),
+ Control(fd_jump),
+ Control(dummy),
+ Control(ff_endOfTrack)
+ };
+ #undef Control
+
+ controlEvents = ctrlEvents;
+}
+
+void TownsPC98_OpnChannel::keyOff() {
+ // all operators off
+ uint8 value = _keyNum & 0x0f;
+ uint8 regAdress = 0x28;
+ writeReg(regAdress, value);
+ _flags |= CHS_KEYOFF;
+}
+
+void TownsPC98_OpnChannel::keyOn() {
+ // all operators on
+ uint8 value = _keyNum | 0xf0;
+ uint8 regAdress = 0x28;
+ writeReg(regAdress, value);
+}
+
+void TownsPC98_OpnChannel::loadData(uint8 *data) {
+ _flags = (_flags & ~CHS_EOT) | CHS_ALL_BUT_EOT;
+ _ticksLeft = 1;
+ _dataPtr = data;
+ _totalLevel = 0x7F;
+
+ uint8 *src_b = _dataPtr;
+ int loop = 1;
+ uint8 cmd = 0;
+ while (loop) {
+ if (loop == 1) {
+ cmd = *src_b++;
+ if (cmd < 0xf0) {
+ src_b++;
+ loop = 1;
+ } else {
+ if (cmd == 0xff) {
+ loop = *src_b ? 2 : 0;
+ if (READ_LE_UINT16(src_b))
+ _drv->_looping |= _idFlag;
+ } else if (cmd == 0xf6) {
+ loop = 3;
+ } else {
+ loop = 2;
+ }
+ }
+ } else if (loop == 2) {
+ src_b += _drv->_opnFxCmdLen[cmd - 240];
+ loop = 1;
+ } else if (loop == 3) {
+ src_b[0] = src_b[1];
+ src_b += 4;
+ loop = 1;
+ }
+ }
+}
+
+void TownsPC98_OpnChannel::processEvents() {
+ if (_flags & CHS_EOT)
+ return;
+
+ if (_protect == false && _ticksLeft == _keyOffTime)
+ keyOff();
+
+ if (--_ticksLeft)
+ return;
+
+ if (_protect == false)
+ keyOff();
+
+ uint8 cmd = 0;
+ bool loop = true;
+
+ while (loop) {
+ cmd = *_dataPtr++;
+ if (cmd < 0xf0)
+ loop = false;
+ else if (!processControlEvent(cmd))
+ return;
+ }
+
+ uint8 para = *_dataPtr++;
+
+ if (cmd == 0x80) {
+ keyOff();
+ _protect = false;
+ } else {
+ keyOn();
+
+ if (_protect == false || cmd != _frqBlockMSB)
+ _flags |= CHS_RECALCFREQ;
+
+ _protect = (para & 0x80) ? true : false;
+ _frqBlockMSB = cmd;
+ }
+
+ _ticksLeft = para & 0x7f;
+}
+
+void TownsPC98_OpnChannel::processFrequency() {
+ if (_flags & CHS_RECALCFREQ) {
+ uint8 block = (_frqBlockMSB & 0x70) >> 1;
+ uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f];
+ frequency = (bfreq + _frqLSB) | (block << 8);
+
+ writeReg(_regOffset + 0xa4, (frequency >> 8));
+ writeReg(_regOffset + 0xa0, (frequency & 0xff));
+
+ _ptchWhlCurDelay = _ptchWhlInitDelayHi;
+ if (_flags & CHS_KEYOFF) {
+ _ptchWhlModCurVal = _ptchWhlModInitVal;
+ _ptchWhlCurDelay += _ptchWhlInitDelayLo;
+ }
+
+ _ptchWhlDurLeft = (_ptchWhlDuration >> 1);
+ _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
+ }
+
+ if (!(_flags & CHS_PITCHWHEELOFF)) {
+ if (--_ptchWhlCurDelay)
+ return;
+ _ptchWhlCurDelay = _ptchWhlInitDelayHi;
+ frequency += _ptchWhlModCurVal;
+
+ writeReg(_regOffset + 0xa4, (frequency >> 8));
+ writeReg(_regOffset + 0xa0, (frequency & 0xff));
+
+ if(!--_ptchWhlDurLeft) {
+ _ptchWhlDurLeft = _ptchWhlDuration;
+ _ptchWhlModCurVal = -_ptchWhlModCurVal;
+ }
+ }
+}
+
+bool TownsPC98_OpnChannel::processControlEvent(uint8 cmd) {
+ uint8 para = *_dataPtr++;
+ return (this->*controlEvents[cmd & 0x0f])(para);
+}
+
+void TownsPC98_OpnChannel::setOutputLevel() {
+ uint8 outopr = _drv->_opnCarrier[_algorithm];
+ uint8 reg = 0x40 + _regOffset;
+
+ for (int i = 0; i < 4; i++) {
+ if (outopr & 1)
+ writeReg(reg, _totalLevel);
+ outopr >>= 1;
+ reg += 4;
+ }
+}
+
+void TownsPC98_OpnChannel::fadeStep() {
+ _totalLevel += 3;
+ if (_totalLevel > 0x7f)
+ _totalLevel = 0x7f;
+ setOutputLevel();
+}
+
+void TownsPC98_OpnChannel::reset() {
+ for (int i = 0; i < 4; i++)
+ _opr[i]->reset();
+
+ _updateEnvelopes = false;
+ _enableLeft = _enableRight = true;
+ memset(&_feedbuf, 0, sizeof(int) * 3);
+}
+
+void TownsPC98_OpnChannel::updateEnv() {
+ for (int i = 0; i < 4 ; i++)
+ _opr[i]->updatePhaseIncrement();
+}
+
+void TownsPC98_OpnChannel::generateOutput(int16 &leftSample, int16 &rightSample, int *del, int *feed) {
+ int phbuf1, phbuf2, output;
+ phbuf1 = phbuf2 = output = 0;
+
+ switch (_algorithm) {
+ case 0:
+ _opr[0]->generateOutput(0, feed, phbuf1);
+ _opr[2]->generateOutput(*del, 0, phbuf2);
+ *del = 0;
+ _opr[1]->generateOutput(phbuf1, 0, *del);
+ _opr[3]->generateOutput(phbuf2, 0, output);
+ break;
+ case 1:
+ _opr[0]->generateOutput(0, feed, phbuf1);
+ _opr[2]->generateOutput(*del, 0, phbuf2);
+ _opr[1]->generateOutput(0, 0, phbuf1);
+ _opr[3]->generateOutput(phbuf2, 0, output);
+ *del = phbuf1;
+ break;
+ case 2:
+ _opr[0]->generateOutput(0, feed, phbuf2);
+ _opr[2]->generateOutput(*del, 0, phbuf2);
+ _opr[1]->generateOutput(0, 0, phbuf1);
+ _opr[3]->generateOutput(phbuf2, 0, output);
+ *del = phbuf1;
+ break;
+ case 3:
+ _opr[0]->generateOutput(0, feed, phbuf2);
+ _opr[2]->generateOutput(0, 0, *del);
+ _opr[1]->generateOutput(phbuf2, 0, phbuf1);
+ _opr[3]->generateOutput(*del, 0, output);
+ *del = phbuf1;
+ break;
+ case 4:
+ _opr[0]->generateOutput(0, feed, phbuf1);
+ _opr[2]->generateOutput(0, 0, phbuf2);
+ _opr[1]->generateOutput(phbuf1, 0, output);
+ _opr[3]->generateOutput(phbuf2, 0, output);
+ *del = 0;
+ break;
+ case 5:
+ *del = feed[1];
+ _opr[0]->generateOutput(-1, feed, phbuf1);
+ _opr[2]->generateOutput(*del, 0, output);
+ _opr[1]->generateOutput(*del, 0, output);
+ _opr[3]->generateOutput(*del, 0, output);
+ break;
+ case 6:
+ _opr[0]->generateOutput(0, feed, phbuf1);
+ _opr[2]->generateOutput(0, 0, output);
+ _opr[1]->generateOutput(phbuf1, 0, output);
+ _opr[3]->generateOutput(0, 0, output);
+ *del = 0;
+ break;
+ case 7:
+ _opr[0]->generateOutput(0, feed, output);
+ _opr[2]->generateOutput(0, 0, output);
+ _opr[1]->generateOutput(0, 0, output);
+ _opr[3]->generateOutput(0, 0, output);
+ *del = 0;
+ break;
+ };
+
+ if (_enableLeft) {
+ int l = output + leftSample;
+ if (l > 32767)
+ l = 32767;
+ if (l < -32767)
+ l = -32767;
+ leftSample = (int16) l;
+ }
+
+ if (_enableRight) {
+ int r = output + rightSample;
+ if (r > 32767)
+ r = 32767;
+ if (r < -32767)
+ r = -32767;
+ rightSample = (int16) r;
+ }
+}
+
+void TownsPC98_OpnChannel::writeReg(uint8 regAdress, uint8 value) {
+ uint8 h = regAdress & 0xf0;
+ uint8 l = (regAdress & 0x0f);
+ static const uint8 oprOrdr[] = { 0, 2, 1, 3 };
+ uint8 o = oprOrdr[(l - _regOffset) >> 2];
+
+ switch (h) {
+ case 0x00:
+ // ssg
+ warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+ break;
+ case 0x10:
+ // adpcm
+ warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+ break;
+ case 0x20:
+ if (l == 8) {
+ // Key on/off
+ for (int i = 0; i < 4; i++) {
+ if ((value >> (4 + i)) & 1)
+ _opr[i]->keyOn();
+ else
+ _opr[i]->keyOff();
+ }
+ } else if (l == 2) {
+ // LFO
+ warning("TownsPC98_OpnDriver: TRYING TO USE LFO (NOT SUPPORTED)");
+ } else if (l == 7) {
+ // Timers; Ch 3/6 special mode
+ warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE (NOT SUPPORTED)");
+ } else if (l == 4 || l == 5) {
+ // Timer A
+ warning("TownsPC98_OpnDriver: TRYING TO USE TIMER_A (NOT SUPPORTED)");
+ } else if (l == 6) {
+ // Timer B
+ warning("TownsPC98_OpnDriver: TRYING TO USE TIMER_B (NOT SUPPORTED)");
+ } else if (l == 10 || l == 11) {
+ // DAC
+ warning("TownsPC98_OpnDriver: TRYING TO USE DAC (NOT SUPPORTED)");
+ }
+ break;
+
+ case 0x30:
+ // detune, multiple
+ _opr[o]->detune((value >> 4) & 7);
+ _opr[o]->multiple(value & 0x0f);
+ _updateEnvelopes = true;
+ break;
+
+ case 0x40:
+ // total level
+ _opr[o]->totalLevel(value & 0x7f);
+ break;
+
+ case 0x50:
+ // rate scaling, attack rate
+ _opr[o]->attackRate(value & 0x1f);
+ if (_opr[o]->scaleRate(value >> 6))
+ _updateEnvelopes = true;
+ break;
+
+ case 0x60:
+ // first decay rate, amplitude modulation
+ _opr[o]->decayRate(value & 0x1f);
+ if (value & 0x80)
+ warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION (NOT SUPPORTED)");
+
+ break;
+
+ case 0x70:
+ // secondary decay rate
+ _opr[o]->sustainRate(value & 0x1f);
+ break;
+
+ case 0x80:
+ // secondary amplitude, release rate;
+ _opr[o]->sustainLevel(value >> 4);
+ _opr[o]->releaseRate(value & 0x0f);
+ break;
+
+ case 0x90:
+ // ssg
+ warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+ break;
+
+ case 0xa0:
+ // frequency
+ l -= _regOffset;
+ if (l == 0) {
+ _frqTemp = (_frqTemp & 0xff00) | value;
+ _updateEnvelopes = true;
+ for (int i = 0; i < 4; i++)
+ _opr[i]->frequency(_frqTemp);
+ } else if (l == 4) {
+ _frqTemp = (_frqTemp & 0xff) | (value << 8);
+ } else if (l == 8) {
+ // Ch 3/6 special mode frq
+ warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)");
+ } else if (l == 12) {
+ // Ch 3/6 special mode frq
+ warning("TownsPC98_OpnDriver: TRYING TO USE CH 3/6 SPECIAL MODE FREQ (NOT SUPPORTED)");
+ }
+ break;
+
+ case 0xb0:
+ l -= _regOffset;
+ if (l == 0) {
+ // feedback, _algorithm
+ _opr[0]->feedbackLevel((value >> 3) & 7);
+ _opr[1]->feedbackLevel(0);
+ _opr[2]->feedbackLevel(0);
+ _opr[3]->feedbackLevel(0);
+ } else if (l == 4) {
+ // stereo, LFO sensitivity
+ _enableLeft = value & 0x80 ? true : false;
+ _enableRight = value & 0x40 ? true : false;
+ uint8 ams = (value & 0x3F) >> 3;
+ if (ams)
+ warning("TownsPC98_OpnDriver: TRYING TO USE AMP MODULATION SENSITIVITY (NOT SUPPORTED)");
+ uint8 fms = value & 3;
+ if (fms)
+ warning("TownsPC98_OpnDriver: TRYING TO USE FREQ MODULATION SENSITIVITY (NOT SUPPORTED)");
+ }
+ break;
+
+ default:
+ warning("TownsPC98_OpnDriver: UNKNOWN ADDRESS %d", regAdress);
+ break;
+ }
+}
+
+bool TownsPC98_OpnChannel::control_f0_setPatch(uint8 para) {
+ _instrID = para;
+ uint8 reg = _regOffset + 0x80;
+
+ for (int i = 0; i < 4; i++) {
+ // set release rate for each operator
+ writeReg(reg, 0x0f);
+ reg += 4;
+ }
+
+ const uint8 *tptr = _drv->_patches + ((uint32)_instrID << 5);
+ reg = _regOffset + 0x30;
+
+ // write registers 0x30 to 0x8f
+ for (int i = 0; i < 6; i++) {
+ writeReg(reg, tptr[0]);
+ reg += 4;
+ writeReg(reg, tptr[2]);
+ reg += 4;
+ writeReg(reg, tptr[1]);
+ reg += 4;
+ writeReg(reg, tptr[3]);
+ reg += 4;
+ tptr += 4;
+ }
+
+ reg = _regOffset + 0xB0;
+ _algorithm = tptr[0] & 7;
+ // set feedback and algorithm
+ writeReg(reg, tptr[0]);
+
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f1_presetOutputLevel(uint8 para) {
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel = _drv->_opnLvlPresets[para];
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f2_setKeyOffTime(uint8 para) {
+ _keyOffTime = para;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f3_setFreqLSB(uint8 para) {
+ _frqLSB = (int8) para;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f4_setOutputLevel(uint8 para) {
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel = para;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f5_setTempo(uint8 para) {
+ _drv->setTempo(para);
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f6_repeatSection(uint8 para) {
+ _dataPtr--;
+ _dataPtr[0]--;
+
+ if (*_dataPtr) {
+ // repeat section until counter has reached zero
+ _dataPtr = _drv->_trackData + READ_LE_UINT16(_dataPtr + 2);
+ } else {
+ // reset counter, advance to next section
+ _dataPtr[0] = _dataPtr[1];
+ _dataPtr += 4;
+ }
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f7_setupPitchWheel(uint8 para) {
+ _ptchWhlInitDelayLo = _dataPtr[0];
+ _ptchWhlInitDelayHi = para;
+ _ptchWhlModInitVal = (int16) READ_LE_UINT16(_dataPtr + 1);
+ _ptchWhlDuration = _dataPtr[3];
+ _dataPtr += 4;
+ _flags = (_flags & ~CHS_PITCHWHEELOFF) | CHS_KEYOFF | CHS_RECALCFREQ;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f8_togglePitchWheel(uint8 para) {
+ if (para == 0x10) {
+ if (*_dataPtr++) {
+ _flags = (_flags & ~CHS_PITCHWHEELOFF) | CHS_KEYOFF;
+ } else {
+ _flags |= CHS_PITCHWHEELOFF;
+ }
+ } else {
+ //uint8 skipChannels = para / 36;
+ //uint8 entry = para % 36;
+ //TownsPC98_OpnDriver::TownsPC98_OpnChannel *t = &chan[skipChannels];
+ ////// NOT IMPLEMENTED
+ //t->unnamedEntries[entry] = *_dataPtr++;
+ }
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fa_writeReg(uint8 para) {
+ writeReg(para, *_dataPtr++);
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fb_incOutLevel(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ uint8 val = (_totalLevel + 3);
+ if (val > 0x7f)
+ val = 0x7f;
+
+ _totalLevel = val;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fc_decOutLevel(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ int8 val = (int8) (_totalLevel - 3);
+ if (val < 0)
+ val = 0;
+
+ _totalLevel = (uint8) val;
+ setOutputLevel();
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fd_jump(uint8 para) {
+ uint8 *tmp = _drv->_trackData + READ_LE_UINT16(_dataPtr - 1);
+ _dataPtr = (tmp[1] == 1) ? tmp : ++_dataPtr;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_dummy(uint8 para) {
+ _dataPtr--;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_ff_endOfTrack(uint8 para) {
+ uint16 val = READ_LE_UINT16(--_dataPtr);
+ if (val) {
+ // loop
+ _dataPtr = _drv->_trackData + val;
+ return true;
+ } else {
+ // quit parsing for active channel
+ --_dataPtr;
+ _flags |= CHS_EOT;
+ _drv->_finishedChannelsFlag |= _idFlag;
+ keyOff();
+ return false;
+ }
+}
+
+bool TownsPC98_OpnChannel::control_f0_setPatchSSG(uint8 para) {
+ _instrID = para << 4;
+ para = (para >> 3) & 0x1e;
+ if (para)
+ return control_f4_setAlgorithm(para | 0x40);
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f1_setTotalLevel(uint8 para) {
+ if (!_drv->_fading)
+ _totalLevel = para;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f4_setAlgorithm(uint8 para) {
+ _algorithm = para;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_f9_unkSSG(uint8 para) {
+ _dataPtr += 5;
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fb_incOutLevelSSG(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ _totalLevel--;
+ if ((int8)_totalLevel < 0)
+ _totalLevel = 0;
+
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_fc_decOutLevelSSG(uint8 para) {
+ _dataPtr--;
+ if (_drv->_fading)
+ return true;
+
+ if(_totalLevel + 1 < 0x10)
+ _totalLevel++;
+
+ return true;
+}
+
+bool TownsPC98_OpnChannel::control_ff_endOfTrackSSG(uint8 para) {
+ uint16 val = READ_LE_UINT16(--_dataPtr);
+ if (val) {
+ // loop
+ _dataPtr = _drv->_trackData + val;
+ return true;
+ } else {
+ // quit parsing for active channel
+ --_dataPtr;
+ _flags |= CHS_EOT;
+ //_finishedChannelsFlag |= _idFlag;
+ keyOff();
+ return false;
+ }
+}
+
+TownsPC98_OpnChannelSSG::TownsPC98_OpnChannelSSG(TownsPC98_OpnDriver *driver, uint8 regOffs,
+ uint8 flgs, uint8 num, uint8 key, uint8 prt, uint8 id) :
+ TownsPC98_OpnChannel(driver, regOffs, flgs, num, key, prt, id) {
+}
+
+void TownsPC98_OpnChannelSSG::init() {
+ _algorithm = 0x80;
+
+ _opr = new TownsPC98_OpnOperator*[4];
+ for (int i = 0; i < 4; i++)
+ _opr[i] = new TownsPC98_OpnOperator(_drv->_baserate, _drv->_oprRates, _drv->_oprRateshift,
+ _drv->_oprAttackDecay, _drv->_oprFrq, _drv->_oprSinTbl, _drv->_oprLevelOut, _drv->_oprDetune);
+
+ #define Control(x) &TownsPC98_OpnChannelSSG::control_##x
+ static const ControlEventFunc ctrlEventsSSG[] = {
+ Control(f0_setPatchSSG),
+ Control(f1_setTotalLevel),
+ Control(f2_setKeyOffTime),
+ Control(f3_setFreqLSB),
+ Control(f4_setOutputLevel),
+ Control(f5_setTempo),
+ Control(f6_repeatSection),
+ Control(f7_setupPitchWheel),
+ Control(f8_togglePitchWheel),
+ Control(f9_unkSSG),
+ Control(fa_writeReg),
+ Control(fb_incOutLevelSSG),
+ Control(fc_decOutLevelSSG),
+ Control(fd_jump),
+ Control(dummy),
+ Control(ff_endOfTrackSSG)
+ };
+ #undef Control
+
+ controlEvents = ctrlEventsSSG;
+}
+
+void TownsPC98_OpnChannelSSG::processEvents() {
+ if (_flags & CHS_EOT)
+ return;
+
+ _drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0;
+
+ if (_protect == false && _ticksLeft == _keyOffTime)
+ keyOff();
+
+ if (--_ticksLeft)
+ return;
+
+ if (_protect == false)
+ keyOff();
+
+ uint8 cmd = 0;
+ bool loop = true;
+
+ while (loop) {
+ cmd = *_dataPtr++;
+ if (cmd < 0xf0)
+ loop = false;
+ else if (!processControlEvent(cmd))
+ return;
+ }
+
+ uint8 para = *_dataPtr++;
+
+ if (cmd == 0x80) {
+ keyOff();
+ _protect = false;
+ } else {
+ keyOn();
+
+ if (_protect == false || cmd != _frqBlockMSB)
+ _flags |= CHS_RECALCFREQ;
+
+ _protect = (para & 0x80) ? true : false;
+ _frqBlockMSB = cmd;
+ }
+
+ _ticksLeft = para & 0x7f;
+
+ if (!(_flags & CHS_SSG)) {
+
+ }
+}
+
+void TownsPC98_OpnChannelSSG::processFrequency() {
+ if (_flags & CHS_RECALCFREQ) {
+ uint8 block = (_frqBlockMSB & 0x70) >> 1;
+ uint16 bfreq = ((const uint16*)_drv->_opnFreqTable)[_frqBlockMSB & 0x0f];
+ frequency = (bfreq + _frqLSB) | (block << 8);
+
+ writeReg(_regOffset + 0xa4, (frequency >> 8));
+ writeReg(_regOffset + 0xa0, (frequency & 0xff));
+
+ _ptchWhlCurDelay = _ptchWhlInitDelayHi;
+ if (_flags & CHS_KEYOFF) {
+ _ptchWhlModCurVal = _ptchWhlModInitVal;
+ _ptchWhlCurDelay += _ptchWhlInitDelayLo;
+ }
+
+ _ptchWhlDurLeft = (_ptchWhlDuration >> 1);
+ _flags &= ~(CHS_KEYOFF | CHS_RECALCFREQ);
+ }
+
+ if (!(_flags & CHS_PITCHWHEELOFF)) {
+ if (--_ptchWhlCurDelay)
+ return;
+ _ptchWhlCurDelay = _ptchWhlInitDelayHi;
+ frequency += _ptchWhlModCurVal;
+
+ writeReg(_regOffset + 0xa4, (frequency >> 8));
+ writeReg(_regOffset + 0xa0, (frequency & 0xff));
+
+ if(!--_ptchWhlDurLeft) {
+ _ptchWhlDurLeft = _ptchWhlDuration;
+ _ptchWhlModCurVal = -_ptchWhlModCurVal;
+ }
+ }
+}
+
+void TownsPC98_OpnChannelSSG::keyOff() {
+ // all operators off
+ uint8 value = _keyNum & 0x0f;
+ uint8 regAdress = 0x28;
+ writeReg(regAdress, value);
+ _flags |= CHS_KEYOFF;
+}
+
+void TownsPC98_OpnChannelSSG::keyOn() {
+ // all operators on
+ uint8 value = _keyNum | 0xf0;
+ uint8 regAdress = 0x28;
+ writeReg(regAdress, value);
+}
+
+void TownsPC98_OpnChannelSSG::loadData(uint8 *data) {
+ _drv->_ssgFlag = (_flags & CHS_SSG) ? -1 : 0;
+ opn_SSG_UNK(0);
+ TownsPC98_OpnChannel::loadData(data);
+ _algorithm = 0x80;
+}
+
+void TownsPC98_OpnChannelSSG::opn_SSG_UNK(uint8 a) {
+ _ssg1 = a;
+ uint16 h = (_totalLevel + 1) * a;
+ if ((h >> 8) == _ssg2)
+ return;
+ _ssg2 = (h >> 8);
+ writeReg(8 + _regOffset, _ssg2);
+}
+
+TownsPC98_OpnDriver::TownsPC98_OpnDriver(Audio::Mixer *mixer, OpnType type) :
+ _mixer(mixer), _trackData(0), _playing(false), _fading(false), _channels(0), _ssgChannels(0),
+ _looping(0), _opnCarrier(_drvTables + 76), _opnFreqTable(_drvTables + 84),
+ _opnFxCmdLen(_drvTables + 36), _opnLvlPresets(_drvTables + (type == OD_TOWNS ? 52 : 220)) ,
+ _oprRates(0), _oprRateshift(0), _oprAttackDecay(0), _oprFrq(0), _oprSinTbl(0), _oprLevelOut(0),
+ _oprDetune(0), _cbCounter(4), _tickCounter(0), _updateChannelsFlag(type == OD_TYPE26 ? 0x07 : 0x3F),
+ _finishedChannelsFlag(0), _samplesTillCallback(0), _samplesTillCallbackRemainder(0), _ready(false),
+ _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;
+}
+
+TownsPC98_OpnDriver::~TownsPC98_OpnDriver() {
+ _mixer->stopHandle(_soundHandle);
+
+ if (_channels) {
+ for (int i = 0; i < _numChan; i++)
+ delete _channels[i];
+ delete [] _channels;
+ }
+
+ if (_ssgChannels) {
+ for (int i = 0; i < _numSSG; i++)
+ delete _ssgChannels[i];
+ delete [] _ssgChannels;
+ }
+
+ delete [] _oprRates;
+ delete [] _oprRateshift;
+ delete [] _oprFrq;
+ delete [] _oprAttackDecay;
+ delete [] _oprSinTbl;
+ delete [] _oprLevelOut;
+ delete [] _oprDetune;
+}
+
+bool TownsPC98_OpnDriver::init() {
+ if (_ready) {
+ reset();
+ return true;
+ }
+
+ generateTables();
+
+ if (_channels) {
+ for (int i = 0; i < _numChan; i++) {
+ if (_channels[i])
+ delete _channels[i];
+ }
+ delete [] _channels;
+ }
+ _channels = new TownsPC98_OpnChannel*[_numChan];
+ for (int i = 0; i < _numChan; i++) {
+ int ii = i * 6;
+ _channels[i] = new TownsPC98_OpnChannel(this, _drvTables[ii], _drvTables[ii + 1],
+ _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
+ _channels[i]->init();
+ }
+
+ if (_ssgChannels) {
+ for (int i = 0; i < _numSSG; i++) {
+ if (_ssgChannels[i])
+ delete _ssgChannels[i];
+ }
+ delete [] _ssgChannels;
+ }
+ if (_numSSG) {
+ _ssgChannels = new TownsPC98_OpnChannelSSG*[_numSSG];
+ for (int i = 0; i < _numSSG; i++) {
+ int ii = i * 6;
+ _ssgChannels[i] = new TownsPC98_OpnChannelSSG(this, _drvTables[ii], _drvTables[ii + 1],
+ _drvTables[ii + 2], _drvTables[ii + 3], _drvTables[ii + 4], _drvTables[ii + 5]);
+ _ssgChannels[i]->init();
+ }
+ }
+
+ _mixer->playInputStream(Audio::Mixer::kMusicSoundType,
+ &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, false, true);
+
+ _ready = true;
+ return true;
+}
+
+int inline TownsPC98_OpnDriver::readBuffer(int16 *buffer, const int numSamples) {
+ memset(buffer, 0, sizeof(int16) * numSamples);
+ int32 samplesLeft = numSamples >> 1;
+ while (samplesLeft) {
+ if (!_samplesTillCallback) {
+ callback();
+ _samplesTillCallback = _samplesPerCallback;
+ _samplesTillCallbackRemainder += _samplesPerCallbackRemainder;
+ if (_samplesTillCallbackRemainder >= _tempo) {
+ _samplesTillCallback++;
+ _samplesTillCallbackRemainder -= _tempo;
+ }
+ }
+
+ int32 render = MIN(samplesLeft, _samplesTillCallback);
+ samplesLeft -= render;
+ _samplesTillCallback -= render;
+
+ nextTick(buffer, render);
+
+ for (int i = 0; i < render; ++i) {
+ buffer[i << 1] <<= 2;
+ buffer[(i << 1) + 1] <<= 2;
+ }
+
+ buffer += (render << 1);
+ }
+
+ return numSamples;
+}
+
+void TownsPC98_OpnDriver::loadData(uint8 *data, bool loadPaused) {
+ if (!_ready) {
+ warning("TownsPC98_OpnDriver: Driver must be initialized before loading data");
+ return;
+ }
+
+ if (!data) {
+ warning("TownsPC98_OpnDriver: Invalid music file data");
+ return;
+ }
+
+ lock();
+ _trackData = data;
+
+ reset();
+
+ uint8 *src_a = data;
+
+ for (uint8 i = 0; i < 3; i++) {
+ _channels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ for (int i = 0; i < _numSSG; i++) {
+ _ssgChannels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ for (uint8 i = 3; i < _numChan; i++) {
+ _channels[i]->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ if (_hasADPCM) {
+ //_adpcmChannel->loadData(data + READ_LE_UINT16(src_a));
+ src_a += 2;
+ }
+
+ _ssgFlag = 0;
+
+ _patches = src_a + 4;
+ _cbCounter = 4;
+ _finishedChannelsFlag = 0;
+
+ // AH 0x17
+ unlock();
+ _playing = (loadPaused ? false : true);
+}
+
+void TownsPC98_OpnDriver::reset() {
+ for (int i = 0; i < (_numChan); i++)
+ _channels[i]->reset();
+ for (int i = 0; i < (_numSSG); i++)
+ _ssgChannels[i]->reset();
+
+ _playing = _fading = false;
+ _looping = 0;
+ _tickCounter = 0;
+}
+
+void TownsPC98_OpnDriver::fadeOut() {
+ if (!_playing)
+ return;
+
+ _fading = true;
+
+ for (int i = 0; i < 20; i++) {
+ lock();
+ uint32 dTime = _tickCounter + 2;
+ for (int j = 0; j < _numChan; j++) {
+ if (_updateChannelsFlag & _channels[j]->_idFlag)
+ _channels[j]->fadeStep();
+ }
+ for (int j = 0; j < _numSSG; j++)
+ _ssgChannels[j]->fadeStep();
+
+ unlock();
+
+ while (_playing) {
+ if (_tickCounter >= dTime)
+ break;
+ }
+ }
+
+ _fading = false;
+
+ reset();
+}
+
+void TownsPC98_OpnDriver::callback() {
+ if (!_playing || --_cbCounter)
+ return;
+
+ _cbCounter = 4;
+ _tickCounter++;
+
+ lock();
+
+ for (int i = 0; i < _numChan; i++) {
+ if (_updateChannelsFlag & _channels[i]->_idFlag) {
+ _channels[i]->processEvents();
+ _channels[i]->processFrequency();
+ }
+ }
+
+ if (_numSSG) {
+ for (int i = 0; i < _numSSG; i++) {
+ _ssgChannels[i]->processEvents();
+ _ssgChannels[i]->processFrequency();
+ }
+ }
+
+ _ssgFlag = 0;
+
+ unlock();
+
+ if (_finishedChannelsFlag == _updateChannelsFlag)
+ reset();
+}
+
+void TownsPC98_OpnDriver::nextTick(int16 *buffer, uint32 bufferSize) {
+ if (!_playing)
+ return;
+
+ for (int i = 0; i < _numChan ; i++) {
+ if (_channels[i]->_updateEnvelopes) {
+ _channels[i]->_updateEnvelopes = false;
+ _channels[i]->updateEnv();
+ }
+
+ for (uint32 ii = 0; ii < bufferSize ; ii++)
+ _channels[i]->generateOutput(buffer[ii * 2],
+ buffer[ii * 2 + 1], &_channels[i]->_feedbuf[2], _channels[i]->_feedbuf);
+ }
+
+ for (int i = 0; i < _numSSG ; i++) {
+ if (_ssgChannels[i]->_updateEnvelopes) {
+ _ssgChannels[i]->_updateEnvelopes = false;
+ _ssgChannels[i]->updateEnv();
+ }
+
+ for (uint32 ii = 0; ii < bufferSize ; ii++)
+ _ssgChannels[i]->generateOutput(buffer[ii * 2],
+ buffer[ii * 2 + 1], &_ssgChannels[i]->_feedbuf[2], _ssgChannels[i]->_feedbuf);
+ }
+}
+
+void TownsPC98_OpnDriver::generateTables() {
+ delete [] _oprRates;
+ _oprRates = new uint8[128];
+ memset(_oprRates, 0x90, 32);
+ uint8 *dst = (uint8*) _oprRates + 32;
+ for (int i = 0; i < 48; i += 4)
+ WRITE_BE_UINT32(dst + i, 0x00081018);
+ dst += 48;
+ for (uint8 i = 0; i < 16; i ++) {
+ uint8 v = (i < 12) ? i : 12;
+ *dst++ = ((4 + v) << 3);
+ }
+ memset(dst, 0x80, 32);
+
+ delete [] _oprRateshift;
+ _oprRateshift = new uint8[128];
+ memset(_oprRateshift, 0, 128);
+ dst = (uint8*) _oprRateshift + 32;
+ for (int i = 11; i; i--) {
+ memset(dst, i, 4);
+ dst += 4;
+ }
+
+ delete [] _oprFrq;
+ _oprFrq = new uint32[0x1000];
+ for (uint32 i = 0; i < 0x1000; i++)
+ _oprFrq[i] = (uint32)(_baserate * (double)(i << 11));
+
+ delete [] _oprAttackDecay;
+ _oprAttackDecay = new uint8[152];
+ memset(_oprAttackDecay, 0, 152);
+ for (int i = 0; i < 36; i++)
+ WRITE_BE_UINT32(_oprAttackDecay + (i << 2), _adtStat[i]);
+
+ delete [] _oprSinTbl;
+ _oprSinTbl = new uint32[1024];
+ for (int i = 0; i < 1024; i++) {
+ double val = sin((double) (((i << 1) + 1) * PI / 1024.0));
+ double d_dcb = log(1.0 / (double)ABS(val)) / log(2.0) * 256.0;
+ int32 i_dcb = (int32)(2.0 * d_dcb);
+ i_dcb = (i_dcb & 1) ? (i_dcb >> 1) + 1 : (i_dcb >> 1);
+ _oprSinTbl[i] = (i_dcb << 1) + (val >= 0.0 ? 0 : 1);
+ }
+
+ delete [] _oprLevelOut;
+ _oprLevelOut = new int32[0x1a00];
+ for (int i = 0; i < 256; i++) {
+ double val = floor(65536.0 / pow(2.0, 0.00390625 * (double)(1 + i)));
+ int32 val_int = ((int32) val) >> 4;
+ _oprLevelOut[i << 1] = (val_int & 1) ? ((val_int >> 1) + 1) << 2 : (val_int >> 1) << 2;
+ _oprLevelOut[(i << 1) + 1] = -_oprLevelOut[i << 1];
+ for (int ii = 1; ii < 13; ii++) {
+ _oprLevelOut[(i << 1) + (ii << 9)] = _oprLevelOut[i << 1] >> ii;
+ _oprLevelOut[(i << 1) + (ii << 9) + 1] = -_oprLevelOut[(i << 1) + (ii << 9)];
+ }
+ }
+
+ uint8 *dtt = new uint8[128];
+ memset(dtt, 0, 36);
+ memset(dtt + 36, 1, 8);
+ memcpy(dtt + 44, _drvTables + 144, 84);
+
+ delete [] _oprDetune;
+ _oprDetune = new int32[256];
+ for (int i = 0; i < 128; i++) {
+ _oprDetune[i] = (int32) ((double)dtt[i] * _baserate * 64.0);
+ _oprDetune[i + 128] = -_oprDetune[i];
+ }
+
+ delete [] dtt;
+}
+
+void TownsPC98_OpnDriver::setTempo(uint8 tempo) {
+ _tempo = tempo;
+ _samplesPerCallback = getRate() / _tempo;
+ _samplesPerCallbackRemainder = getRate() % _tempo;
+}
+
+const uint8 TownsPC98_OpnDriver::_drvTables[] = {
+ // channel presets
+ 0x00, 0x80, 0x00, 0x00, 0x00, 0x01,
+ 0x01, 0x80, 0x01, 0x01, 0x00, 0x02,
+ 0x02, 0x80, 0x02, 0x02, 0x00, 0x04,
+ 0x00, 0x80, 0x03, 0x04, 0x01, 0x08,
+ 0x01, 0x80, 0x04, 0x05, 0x01, 0x10,
+ 0x02, 0x80, 0x05, 0x06, 0x01, 0x20,
+
+ // control event size
+ 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x04, 0x05,
+ 0x02, 0x06, 0x02, 0x00, 0x00, 0x02, 0x00, 0x02,
+
+ // fmt level presets
+ 0x54, 0x50, 0x4C, 0x48, 0x44, 0x40, 0x3C, 0x38,
+ 0x34, 0x30, 0x2C, 0x28, 0x24, 0x20, 0x1C, 0x18,
+ 0x14, 0x10, 0x0C, 0x08, 0x04, 0x90, 0x90, 0x90,
+
+ // carriers
+ 0x08, 0x08, 0x08, 0x08, 0x0C, 0x0E, 0x0E, 0x0F,
+
+ // frequencies
+ 0x6A, 0x02, 0x8F, 0x02, 0xB6, 0x02, 0xDF, 0x02,
+ 0x0B, 0x03, 0x39, 0x03, 0x6A, 0x03, 0x9E, 0x03,
+ 0xD5, 0x03, 0x10, 0x04, 0x4E, 0x04, 0x8F, 0x04,
+ 0x00, 0x00, 0x00, 0x00,
+
+ // unused
+ 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
+ 0x04, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
+ 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
+
+ // detune
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
+ 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07,
+ 0x08, 0x08, 0x08, 0x08, 0x01, 0x01, 0x01, 0x01,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03,
+ 0x04, 0x04, 0x04, 0x05, 0x05, 0x06, 0x06, 0x07,
+ 0x08, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+ 0x10, 0x10, 0x10, 0x10, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x03, 0x03, 0x03, 0x04, 0x04, 0x04, 0x05,
+ 0x05, 0x06, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0a,
+ 0x0b, 0x0c, 0x0d, 0x0e, 0x10, 0x11, 0x13, 0x14,
+ 0x16, 0x16, 0x16, 0x16,
+
+ // pc98 level presets
+ 0x40, 0x3B, 0x38, 0x34, 0x30, 0x2A, 0x28, 0x25,
+ 0x22, 0x20, 0x1D, 0x1A, 0x18, 0x15, 0x12, 0x10,
+ 0x0D, 0x0A, 0x08, 0x05, 0x02, 0x90, 0x90, 0x90
+};
+
+const uint32 TownsPC98_OpnDriver::_adtStat[] = {
+ 0x00010001, 0x00010001, 0x00010001, 0x01010001,
+ 0x00010101, 0x00010101, 0x00010101, 0x01010101,
+ 0x01010101, 0x01010101, 0x01010102, 0x01010102,
+ 0x01020102, 0x01020102, 0x01020202, 0x01020202,
+ 0x02020202, 0x02020202, 0x02020204, 0x02020204,
+ 0x02040204, 0x02040204, 0x02040404, 0x02040404,
+ 0x04040404, 0x04040404, 0x04040408, 0x04040408,
+ 0x04080408, 0x04080408, 0x04080808, 0x04080808,
+ 0x08080808, 0x08080808, 0x10101010, 0x10101010
+};
+
SoundTowns::SoundTowns(KyraEngine_v1 *vm, Audio::Mixer *mixer)
: Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), _sfxFileData(0),
_sfxFileIndex((uint)-1), _sfxWDTable(0), _sfxBTTable(0), _parser(0) {
- _driver = new SoundTowns_EuphonyDriver(_mixer);
+ _driver = new Towns_EuphonyDriver(_mixer);
int ret = open();
if (ret != MERR_ALREADY_OPEN && ret != 0)
error("couldn't open midi driver");
@@ -1124,7 +2797,7 @@ void SoundTowns::playTrack(uint8 track) {
return;
track -= 2;
- const int32 * const tTable = (const int32 * const) cdaData();
+ const int32 *const tTable = (const int32 *const) cdaData();
int tTableIndex = 3 * track;
int trackNum = (int) READ_LE_UINT32(&tTable[tTableIndex + 2]);
@@ -1195,12 +2868,12 @@ void SoundTowns::playSoundEffect(uint8 track) {
}
}
- uint8 * fileBody = _sfxFileData + 0x01b8;
+ uint8 *fileBody = _sfxFileData + 0x01b8;
int32 offset = (int32)READ_LE_UINT32(_sfxFileData + (track - 0x0b) * 4);
if (offset == -1)
return;
- uint32 * sfxHeader = (uint32*)(fileBody + offset);
+ uint32 *sfxHeader = (uint32*)(fileBody + offset);
uint32 sfxHeaderID = READ_LE_UINT32(sfxHeader);
uint32 sfxHeaderInBufferSize = READ_LE_UINT32(&sfxHeader[1]);
@@ -1220,7 +2893,7 @@ void SoundTowns::playSoundEffect(uint8 track) {
} else if (sfxHeaderID == 1) {
Screen::decodeFrame4(sfxBody, sfxPlaybackBuffer, playbackBufferSize);
} else if (_sfxWDTable) {
- uint8 * tgt = sfxPlaybackBuffer;
+ uint8 *tgt = sfxPlaybackBuffer;
uint32 sfx_BtTable_Offset = 0;
uint32 sfx_WdTable_Offset = 0;
uint32 sfx_WdTable_Number = 5;
@@ -1285,7 +2958,7 @@ uint32 SoundTowns::getBaseTempo(void) {
}
bool SoundTowns::loadInstruments() {
- uint8 * twm = _vm->resource()->fileData("twmusic.pak", 0);
+ uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0);
if (!twm)
return false;
_driver->queue()->loadDataToCurrentPosition(twm, 0x8BF0);
@@ -1300,11 +2973,11 @@ bool SoundTowns::loadInstruments() {
}
void SoundTowns::playEuphonyTrack(uint32 offset, int loop) {
- uint8 * twm = _vm->resource()->fileData("twmusic.pak", 0);
+ uint8 *twm = _vm->resource()->fileData("twmusic.pak", 0);
Common::StackLock lock(_mutex);
if (!_parser) {
- _parser = new MidiParser_EuD(_driver->queue());
+ _parser = new Towns_EuphonyParser(_driver->queue());
_parser->setMidiDriver(this);
_parser->setTimerRate(getBaseTempo());
}
@@ -1315,7 +2988,7 @@ void SoundTowns::playEuphonyTrack(uint32 offset, int loop) {
delete[] twm;
}
-void SoundTowns::onTimer(void * data) {
+void SoundTowns::onTimer(void *data) {
SoundTowns *music = (SoundTowns *)data;
Common::StackLock lock(music->_mutex);
if (music->_parser)
@@ -1356,22 +3029,75 @@ float SoundTowns::semitoneAndSampleRate_to_sampleStep(int8 semiTone, int8 semiTo
return (float) sampleRate * 10.0f * rateshift / outputRate;
}
+SoundPC98::SoundPC98(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
+ Sound(vm, mixer), _musicTrackData(0), _sfxTrackData(0), _lastTrack(-1), _driver(0) {
+}
+
+SoundPC98::~SoundPC98() {
+ delete[] _musicTrackData;
+ delete[] _sfxTrackData;
+ delete _driver;
+}
+
+bool SoundPC98::init() {
+ _driver = new TownsPC98_OpnDriver(_mixer, TownsPC98_OpnDriver::OD_TYPE26);
+ _sfxTrackData = _vm->resource()->fileData("se.dat", 0);
+ if (!_sfxTrackData)
+ return false;
+ return _driver->init();
+}
+
+void SoundPC98::playTrack(uint8 track) {
+ if (--track >= 56)
+ track -= 55;
+
+ if (track == _lastTrack && _musicEnabled)
+ return;
+
+ haltTrack();
+
+ char musicfile[13];
+ sprintf(musicfile, fileListEntry(0), track);
+ delete[] _musicTrackData;
+ _musicTrackData = _vm->resource()->fileData(musicfile, 0);
+ if (_musicEnabled)
+ _driver->loadData(_musicTrackData);
+
+ _lastTrack = track;
+}
+
+void SoundPC98::haltTrack() {
+ _lastTrack = -1;
+ AudioCD.stop();
+ AudioCD.updateCD();
+ _driver->reset();
+}
+
+void SoundPC98::beginFadeOut() {
+ _driver->fadeOut();
+ haltTrack();
+}
+
+void SoundPC98::playSoundEffect(uint8) {
+ /// TODO ///
+}
+
+
// KYRA 2
-SoundTowns_v2::SoundTowns_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer)
- : Sound(vm, mixer), _lastTrack(-1), _currentSFX(0), /*_driver(0),*/
- _twnTrackData(0) {
+SoundTownsPC98_v2::SoundTownsPC98_v2(KyraEngine_v1 *vm, Audio::Mixer *mixer) :
+ Sound(vm, mixer), _currentSFX(0), _musicTrackData(0), _lastTrack(-1), _driver(0), _useFmSfx(false) {
}
-SoundTowns_v2::~SoundTowns_v2() {
- /*if (_driver)
- delete _driver;*/
- if (_twnTrackData)
- delete[] _twnTrackData;
+SoundTownsPC98_v2::~SoundTownsPC98_v2() {
+ delete[] _musicTrackData;
+ delete _driver;
}
-bool SoundTowns_v2::init() {
- //_driver = new SoundTowns_v2_TwnDriver(_mixer);
+bool SoundTownsPC98_v2::init() {
+ _driver = new TownsPC98_OpnDriver(_mixer, /*_vm->gameFlags().platform == Common::kPlatformPC98 ?
+ TownsPC98_OpnDriver::OD_TYPE86 :*/ TownsPC98_OpnDriver::OD_TOWNS);
+ _useFmSfx = _vm->gameFlags().platform == Common::kPlatformPC98 ? true : false;
_vm->checkCD();
// FIXME: While checking for 'track1.XXX(X)' looks like
// a good idea, we should definitely not be doing this
@@ -1384,55 +3110,61 @@ bool SoundTowns_v2::init() {
(Common::File::exists("track1.mp3") || Common::File::exists("track1.ogg") ||
Common::File::exists("track1.flac") || Common::File::exists("track1.fla")))
_musicEnabled = 2;
- return true;//_driver->init();
+ return _driver->init();
}
-void SoundTowns_v2::process() {
+void SoundTownsPC98_v2::process() {
AudioCD.updateCD();
}
-void SoundTowns_v2::playTrack(uint8 track) {
+void SoundTownsPC98_v2::playTrack(uint8 track) {
if (track == _lastTrack && _musicEnabled)
return;
- const uint16 * const cdaTracks = (const uint16 * const) cdaData();
+ const uint16 *const cdaTracks = (const uint16 *const) cdaData();
int trackNum = -1;
- for (int i = 0; i < cdaTrackNum(); i++) {
- if (track == (uint8) READ_LE_UINT16(&cdaTracks[i * 2])) {
- trackNum = (int) READ_LE_UINT16(&cdaTracks[i * 2 + 1]) - 1;
- break;
+ if (_vm->gameFlags().platform == Common::kPlatformFMTowns) {
+ for (int i = 0; i < cdaTrackNum(); i++) {
+ if (track == (uint8) READ_LE_UINT16(&cdaTracks[i * 2])) {
+ trackNum = (int) READ_LE_UINT16(&cdaTracks[i * 2 + 1]) - 1;
+ break;
+ }
}
}
- haltTrack();
+ beginFadeOut();
- // TODO: figure out when to loop and when not for CD Audio
- bool loop = false;
+ char musicfile[13];
+ sprintf(musicfile, fileListEntry(0), track);
+ delete[] _musicTrackData;
+
+ _musicTrackData = _vm->resource()->fileData(musicfile, 0);
+ _driver->loadData(_musicTrackData, true);
if (_musicEnabled == 2 && trackNum != -1) {
- AudioCD.play(trackNum+1, loop ? -1 : 1, 0, 0);
+ AudioCD.play(trackNum+1, _driver->looping() ? -1 : 1, 0, 0);
AudioCD.updateCD();
} else if (_musicEnabled) {
- char musicfile[13];
- sprintf(musicfile, fileListEntry(0), track);
- if (_twnTrackData)
- delete[] _twnTrackData;
- _twnTrackData = _vm->resource()->fileData(musicfile, 0);
- //_driver->loadData(_twnTrackData);
+ _driver->cont();
}
_lastTrack = track;
}
-void SoundTowns_v2::haltTrack() {
+void SoundTownsPC98_v2::haltTrack() {
_lastTrack = -1;
AudioCD.stop();
AudioCD.updateCD();
- //_driver->reset();
+ _driver->reset();
}
-int32 SoundTowns_v2::voicePlay(const char *file, bool) {
+void SoundTownsPC98_v2::beginFadeOut() {
+ _driver->fadeOut();
+ haltTrack();
+}
+
+int32 SoundTownsPC98_v2::voicePlay(const char *file, bool) {
static const uint16 rates[] = { 0x10E1, 0x0CA9, 0x0870, 0x0654, 0x0438, 0x032A, 0x021C, 0x0194 };
int h = 0;
@@ -1443,11 +3175,11 @@ int32 SoundTowns_v2::voicePlay(const char *file, bool) {
return 0;
}
- char filename [13];
+ char filename[13];
sprintf(filename, "%s.PCM", file);
- uint8 * data = _vm->resource()->fileData(filename, 0);
- uint8 * src = data;
+ uint8 *data = _vm->resource()->fileData(filename, 0);
+ uint8 *src = data;
uint16 sfxRate = rates[READ_LE_UINT16(src)];
src += 2;
@@ -1500,9 +3232,16 @@ int32 SoundTowns_v2::voicePlay(const char *file, bool) {
return 1;
}
-void SoundTowns_v2::beginFadeOut() {
- //_driver->fadeOut();
- haltTrack();
+void SoundTownsPC98_v2::playSoundEffect(uint8 track) {
+ if (!_useFmSfx)
+ return;
+
+ uint8 *sd = _vm->resource()->fileData("sound.dat", 0);
+
+
+ //TODO
+
+ delete [] sd;
}
} // end of namespace Kyra
diff --git a/engines/kyra/staticres.cpp b/engines/kyra/staticres.cpp
index abdf115c1e..9a4b40902e 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"
@@ -287,8 +288,10 @@ bool StaticResource::init() {
} else if (_vm->game() == GI_KYRA3) {
_builtIn = 0;
_filenameTable = kyra3StaticRes;
+ } else if (_vm->game() == GI_LOL) {
+ return true;
} else {
- error("unknown game ID");
+ error("StaticResource: Unknown game ID");
}
char errorBuffer[100];
@@ -1034,6 +1037,11 @@ 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 },
{ _soundFiles, _soundFilesSize, 0, 0 },
@@ -1045,7 +1053,22 @@ void KyraEngine_LoK::initStaticResource() {
{ _soundFiles, _soundFilesSize, _cdaTrackTable, _cdaTrackTableSize },
{ 0, 0, 0, 0}
};
- _soundData = (_flags.platform == Common::kPlatformPC) ? soundData_PC : soundData_TOWNS;
+
+#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*/;
+
}
void KyraEngine_LoK::loadMouseShapes() {
@@ -1243,6 +1266,12 @@ 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 },
{ _musicFileListIngame, _musicFileListIngameSize, 0, 0},
@@ -1254,7 +1283,21 @@ void KyraEngine_HoF::initStaticResource() {
{ fmtMusicFileListIngame, 1, _cdaTrackTableIngame, _cdaTrackTableIngameSize >> 1 },
{ fmtMusicFileListFinale, 1, _cdaTrackTableFinale, _cdaTrackTableFinaleSize >> 1 }
};
- _soundData = (_flags.platform == Common::kPlatformPC) ? soundData_PC : soundData_TOWNS;
+
+#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*/;
// setup sequence data
_sequences = _staticres->loadHofSequenceData(k2SeqplaySeqData, tmpSize);
@@ -1944,12 +1987,26 @@ const char *KyraEngine_MR::_languageExtension[] = {
"TRE",
"TRF",
"TRG"/*,
- "TRI", Italian and Spanish were never included
- "TRS"*/
+ "TRI", Italian and Spanish were never included, the supported fan translations are using
+ "TRS" English/French extensions thus overwriting these languages */
};
const int KyraEngine_MR::_languageExtensionSize = ARRAYSIZE(KyraEngine_MR::_languageExtension);
+const char * const KyraEngine_MR::_mainMenuSpanishFan[] = {
+ "Nueva Partida",
+ "Ver Intro",
+ "Restaurar",
+ "Finalizar"
+};
+
+const char * const KyraEngine_MR::_mainMenuItalianFan[] = {
+ "Nuova Partita",
+ "Introduzione",
+ "Carica una partita",
+ "Esci dal gioco"
+};
+
const KyraEngine_MR::ShapeDesc KyraEngine_MR::_shapeDescs[] = {
{ 57, 91, -31, -82 },
{ 57, 91, -31, -82 },
@@ -2182,5 +2239,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/lure/lure.cpp b/engines/lure/lure.cpp
index 06d3b1984e..ea760ddb4f 100644
--- a/engines/lure/lure.cpp
+++ b/engines/lure/lure.cpp
@@ -103,6 +103,7 @@ LureEngine::~LureEngine() {
if (_initialised) {
// Delete and deinitialise subsystems
Surface::deinitialise();
+ Sound.destroy();
delete _fights;
delete _room;
delete _menu;
@@ -164,10 +165,6 @@ void LureEngine::pauseEngineIntern(bool pause) {
}
}
-void LureEngine::quitGame() {
- _system->quit();
-}
-
const char *LureEngine::generateSaveName(int slotNumber) {
static char buffer[15];
diff --git a/engines/lure/lure.h b/engines/lure/lure.h
index d66f446247..1c5b40e54b 100644
--- a/engines/lure/lure.h
+++ b/engines/lure/lure.h
@@ -70,7 +70,6 @@ public:
virtual int init();
virtual int go();
virtual void pauseEngineIntern(bool pause);
- void quitGame();
Disk &disk() { return *_disk; }
diff --git a/engines/lure/luredefs.h b/engines/lure/luredefs.h
index 603102a099..922e1207d0 100644
--- a/engines/lure/luredefs.h
+++ b/engines/lure/luredefs.h
@@ -36,7 +36,7 @@ namespace Lure {
#define LURE_DAT_MAJOR 1
#define LURE_DAT_MINOR 29
#define LURE_MIN_SAVEGAME_MINOR 25
-#define LURE_SAVEGAME_MINOR 32
+#define LURE_SAVEGAME_MINOR 33
#define LURE_DEBUG 1
diff --git a/engines/lure/menu.cpp b/engines/lure/menu.cpp
index cecc415499..0b4ef06081 100644
--- a/engines/lure/menu.cpp
+++ b/engines/lure/menu.cpp
@@ -57,6 +57,11 @@ MenuRecord::MenuRecord(const MenuRecordBounds *bounds, int numParams, ...) {
_width = (bounds->contentsWidth + 3) << 3;
}
+MenuRecord::~MenuRecord() {
+ free(_entries);
+ _entries = NULL;
+}
+
const char *MenuRecord::getEntry(uint8 index) {
if (index >= _numEntries) error("Invalid menuitem index specified: %d", index);
return _entries[index];
diff --git a/engines/lure/menu.h b/engines/lure/menu.h
index b5b7769e34..fcc6308375 100644
--- a/engines/lure/menu.h
+++ b/engines/lure/menu.h
@@ -56,6 +56,7 @@ private:
uint8 _numEntries;
public:
MenuRecord(const MenuRecordBounds *bounds, int numParams, ...);
+ ~MenuRecord();
uint16 xstart() { return _xstart; }
uint16 width() { return _width; }
diff --git a/engines/lure/palette.cpp b/engines/lure/palette.cpp
index 03161032c0..badc3c96b0 100644
--- a/engines/lure/palette.cpp
+++ b/engines/lure/palette.cpp
@@ -106,6 +106,12 @@ Palette::Palette(uint16 resourceId, PaletteSource paletteSource) {
delete srcData;
}
+// Destructor
+
+Palette::~Palette() {
+ delete _palette;
+}
+
void Palette::convertRgb64Palette(const byte *srcPalette, uint16 srcNumEntries) {
byte *pDest = _palette->data();
const byte *pSrc = srcPalette;
diff --git a/engines/lure/palette.h b/engines/lure/palette.h
index 1481e22775..9420079346 100644
--- a/engines/lure/palette.h
+++ b/engines/lure/palette.h
@@ -46,6 +46,7 @@ public:
Palette(uint16 srcNumEntries, const byte *srcData, PaletteSource paletteSource);
Palette(Palette &src);
Palette(uint16 resourceId, PaletteSource paletteSource = DEFAULT);
+ ~Palette();
uint8 *data() { return _palette->data(); }
MemoryBlock *palette() { return _palette; }
diff --git a/engines/lure/res.cpp b/engines/lure/res.cpp
index f2997d5d17..68de260061 100644
--- a/engines/lure/res.cpp
+++ b/engines/lure/res.cpp
@@ -349,6 +349,7 @@ void Resources::reloadData() {
_indexedRoomExitHospots.push_back(RoomExitIndexedHotspotList::value_type(new RoomExitIndexedHotspotData(indexedRec)));
indexedRec++;
}
+ delete mb;
// Initialise delay list
_delayList.clear(true);
diff --git a/engines/lure/res_struct.cpp b/engines/lure/res_struct.cpp
index de09f982d1..92cea948f9 100644
--- a/engines/lure/res_struct.cpp
+++ b/engines/lure/res_struct.cpp
@@ -456,6 +456,8 @@ void HotspotData::saveToStream(WriteStream *stream) {
stream->writeSint16LE(startY);
stream->writeUint16LE(roomNumber);
stream->writeByte(layer);
+ stream->writeUint16LE(walkX);
+ stream->writeUint16LE(walkY);
stream->writeUint16LE(width);
stream->writeUint16LE(height);
@@ -503,6 +505,10 @@ void HotspotData::loadFromStream(ReadStream *stream) {
uint8 saveVersion = LureEngine::getReference().saveVersion();
if (saveVersion >= 29)
layer = stream->readByte();
+ if (saveVersion >= 33) {
+ walkX = stream->readUint16LE();
+ walkY = stream->readUint16LE();
+ }
width = stream->readUint16LE();
height = stream->readUint16LE();
diff --git a/engines/lure/sound.cpp b/engines/lure/sound.cpp
index 839298d1c5..285f66e4e2 100644
--- a/engines/lure/sound.cpp
+++ b/engines/lure/sound.cpp
@@ -85,8 +85,10 @@ SoundManager::~SoundManager() {
if (_soundData)
delete _soundData;
- if (_driver)
+ if (_driver) {
_driver->close();
+ delete _driver;
+ }
_driver = NULL;
g_system->deleteMutex(_soundMutex);
@@ -143,7 +145,7 @@ void SoundManager::bellsBodge() {
Room &room = Room::getReference();
RoomData *roomData = res.getRoom(room.roomNumber());
- if (roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) {
+ if (roomData && roomData->areaFlag != res.fieldList().getField(AREA_FLAG)) {
res.fieldList().setField(AREA_FLAG, roomData->areaFlag);
switch (roomData->areaFlag) {
diff --git a/engines/m4/assets.cpp b/engines/m4/assets.cpp
index 80b21119ff..0488f17d8f 100644
--- a/engines/m4/assets.cpp
+++ b/engines/m4/assets.cpp
@@ -201,6 +201,7 @@ void SpriteAsset::loadMadsSpriteAsset(M4Engine *vm, Common::SeekableReadStream*
Common::SeekableReadStream *spriteDataStream = sprite.getItemStream(3);
SpriteAssetFrame frame;
for (curFrame = 0; curFrame < _frameCount; curFrame++) {
+ frame.stream = 0;
frame.comp = 0;
frameOffset = spriteStream->readUint32LE();
_frameOffsets.push_back(frameOffset);
diff --git a/engines/m4/converse.cpp b/engines/m4/converse.cpp
index 024cd591f5..5b8bdab9d6 100644
--- a/engines/m4/converse.cpp
+++ b/engines/m4/converse.cpp
@@ -153,7 +153,7 @@ void ConversationView::setNode(int32 nodeIndex) {
void ConversationView::onRefresh(RectList *rects, M4Surface *destSurface) {
//if (!this->isVisible())
// return;
- empty();
+ clear();
if (_entriesShown) {
// Write out the conversation options
diff --git a/engines/m4/globals.cpp b/engines/m4/globals.cpp
index 12d9a24d37..58c68979d1 100644
--- a/engines/m4/globals.cpp
+++ b/engines/m4/globals.cpp
@@ -75,7 +75,7 @@ bool Kernel::sendTrigger(int32 triggerNum) {
bool Kernel::handleTrigger(int32 triggerNum) {
- printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, triggerNum);
+ printf("betweenRooms = %d; triggerNum = %08X\n", betweenRooms, (uint)triggerNum);
if (betweenRooms)
return true;
@@ -271,11 +271,13 @@ Globals::Globals(M4Engine *vm): _vm(vm) {
}
Globals::~Globals() {
- for(uint32 i = 0; i < _madsVocab.size(); i++)
+ uint32 i;
+
+ for(i = 0; i < _madsVocab.size(); i++)
free(_madsVocab[i]);
_madsVocab.clear();
- for(uint32 i = 0; i < _madsQuotes.size(); i++)
+ for(i = 0; i < _madsQuotes.size(); i++)
free(_madsQuotes[i]);
_madsQuotes.clear();
@@ -351,7 +353,7 @@ void Globals::loadMadsMessagesInfo() {
_vm->res()->toss("messages.dat");
}
-char* Globals::loadMessage(uint32 index) {
+char* Globals::loadMessage(uint index) {
if (index > _madsMessages.size() - 1) {
warning("Invalid message index: %i", index);
return NULL;
diff --git a/engines/m4/globals.h b/engines/m4/globals.h
index a0133db2d6..a80e8bf710 100644
--- a/engines/m4/globals.h
+++ b/engines/m4/globals.h
@@ -177,7 +177,7 @@ public:
void loadMadsMessagesInfo();
uint32 getMessagesSize() { return _madsMessages.size(); }
- char* loadMessage(uint32 index);
+ char* loadMessage(uint index);
};
#define PLAYER_FIELD_LENGTH 40
diff --git a/engines/m4/graphics.cpp b/engines/m4/graphics.cpp
index beda178344..1846f1c1e7 100644
--- a/engines/m4/graphics.cpp
+++ b/engines/m4/graphics.cpp
@@ -320,7 +320,7 @@ byte *M4Surface::getBasePtr(int x, int y) {
void M4Surface::freeData() {
}
-void M4Surface::empty() {
+void M4Surface::clear() {
Common::set_to((byte *) pixels, (byte *) pixels + w * h, _vm->_palette->BLACK);
}
@@ -389,7 +389,7 @@ void M4Surface::loadBackgroundRiddle(const char *sceneName) {
}
void M4Surface::loadBackground(int sceneNumber, RGBList **palData) {
- this->empty(); // clear previous scene
+ clear(); // clear previous scene
if (_vm->isM4() || (_vm->getGameType() == GType_RexNebular)) {
char resourceName[20];
@@ -502,7 +502,7 @@ void M4Surface::madsLoadBackground(int roomNumber, RGBList **palData) {
//printf("Tile: %i, compressed size: %i\n", i, compressedTileDataSize);
- newTile->empty();
+ newTile->clear();
byte *compressedTileData = new byte[compressedTileDataSize];
diff --git a/engines/m4/graphics.h b/engines/m4/graphics.h
index 60e608c148..84fc77656f 100644
--- a/engines/m4/graphics.h
+++ b/engines/m4/graphics.h
@@ -128,7 +128,7 @@ public:
byte *getData();
byte *getBasePtr(int x, int y);
void freeData();
- void empty();
+ void clear();
void frameRect(const Common::Rect &r, uint8 color);
void fillRect(const Common::Rect &r, uint8 color);
void copyFrom(M4Surface *src, const Common::Rect &srcBounds, int destX, int destY,
diff --git a/engines/m4/m4_views.cpp b/engines/m4/m4_views.cpp
index 9bf964ee96..777356467b 100644
--- a/engines/m4/m4_views.cpp
+++ b/engines/m4/m4_views.cpp
@@ -331,7 +331,7 @@ bool GameInterfaceView::onEvent(M4EventType eventType, int param, int x, int y,
}
void GameInterfaceView::onRefresh(RectList *rects, M4Surface *destSurface) {
- empty();
+ clear();
_statusText.onRefresh();
_inventory.onRefresh();
diff --git a/engines/m4/mads_anim.cpp b/engines/m4/mads_anim.cpp
index 3e80d0f1e0..c51daa84c4 100644
--- a/engines/m4/mads_anim.cpp
+++ b/engines/m4/mads_anim.cpp
@@ -61,9 +61,9 @@ TextviewView::TextviewView(M4Engine *vm):
_vm->_font->setColors(5, 6, 4);
- empty();
- _bgSurface.empty();
- _textSurface.empty();
+ clear();
+ _bgSurface.clear();
+ _textSurface.clear();
int y = (height() - MADS_SURFACE_HEIGHT) / 2;
setColor(2);
@@ -83,8 +83,8 @@ TextviewView::~TextviewView() {
}
void TextviewView::reset() {
- _bgSurface.empty();
- _textSurface.empty();
+ _bgSurface.clear();
+ _textSurface.clear();
_animating = false;
_panX = 0;
_panY = 0;
@@ -456,8 +456,8 @@ AnimviewView::AnimviewView(M4Engine *vm):
// Set up system palette colors
_vm->_palette->setMadsSystemPalette();
- empty();
- _bgSurface.empty();
+ clear();
+ _bgSurface.clear();
int y = (height() - MADS_SURFACE_HEIGHT) / 2;
setColor(2);
@@ -471,7 +471,7 @@ AnimviewView::~AnimviewView() {
}
void AnimviewView::reset() {
- _bgSurface.empty();
+ _bgSurface.clear();
_soundDriverLoaded = false;
}
diff --git a/engines/m4/viewmgr.cpp b/engines/m4/viewmgr.cpp
index 3a8b5d24a8..b74e598c6c 100644
--- a/engines/m4/viewmgr.cpp
+++ b/engines/m4/viewmgr.cpp
@@ -380,7 +380,7 @@ void ViewManager::updateState() {
}
void ViewManager::refreshAll() {
- _vm->_screen->empty();
+ _vm->_screen->clear();
for (ListIterator i = _views.begin(); i != _views.end(); ++i) {
View *v = *i;
diff --git a/engines/made/database.cpp b/engines/made/database.cpp
index 55e0e90732..3497b5b46f 100644
--- a/engines/made/database.cpp
+++ b/engines/made/database.cpp
@@ -88,10 +88,7 @@ int16 Object::getVectorItem(int16 index) {
if (getClass() == 0x7FFF) {
byte *vector = (byte*)getData();
return vector[index];
- } else if (getClass() == 0x7FFE) {
- int16 *vector = (int16*)getData();
- return READ_LE_UINT16(&vector[index]);
- } else if (getClass() < 0x7FFE) {
+ } else if (getClass() <= 0x7FFE) {
int16 *vector = (int16*)getData();
return READ_LE_UINT16(&vector[index]);
} else {
@@ -372,7 +369,7 @@ void GameDatabaseV2::load(Common::SeekableReadStream &sourceS) {
debug(2, "textOffs = %08X; textSize = %08X; objectCount = %d; varObjectCount = %d; gameStateSize = %d; objectsOffs = %08X; objectsSize = %d\n", textOffs, textSize, objectCount, varObjectCount, _gameStateSize, objectsOffs, objectsSize);
_gameState = new byte[_gameStateSize + 2];
- memset(_gameState, 0, _gameStateSize);
+ memset(_gameState, 0, _gameStateSize + 2);
setVar(1, objectCount);
sourceS.seek(textOffs);
@@ -441,7 +438,7 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i
int16 *propPtr2 = prop + count2;
// First see if the property exists in the given object
- while (count2-- > 0) {
+ while (count2--) {
if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {
propertyFlag = obj->getFlags() & 1;
return propPtr1;
@@ -467,8 +464,8 @@ int16 *GameDatabaseV2::findObjectProperty(int16 objectIndex, int16 propertyId, i
propPtr1 = propPtr2 + count1 - count2;
int16 *propertyPtr = prop + count1;
- while (count2-- > 0) {
- if (!(READ_LE_UINT16(prop) & 0x8000)) {
+ while (count2--) {
+ if ((READ_LE_UINT16(prop) & 0x8000) == 0) {
if ((READ_LE_UINT16(prop) & 0x7FFF) == propertyId) {
propertyFlag = obj->getFlags() & 1;
return propPtr1;
diff --git a/engines/made/detection.cpp b/engines/made/detection.cpp
index dc7dbdee87..e5870bfeec 100644
--- a/engines/made/detection.cpp
+++ b/engines/made/detection.cpp
@@ -65,7 +65,7 @@ static const PlainGameDescriptor madeGames[] = {
{"manhole", "The Manhole"},
{"rtz", "Return to Zork"},
{"lgop2", "Leather Goddesses of Phobos 2"},
- {"rodney", "Rodney's Fun Screen"},
+ {"rodney", "Rodney's Funscreen"},
{0, 0}
};
@@ -278,7 +278,7 @@ static const MadeGameDescription gameDescriptions[] = {
},
{
- // Rodney's Fun Screen
+ // Rodney's Funscreen
{
"rodney",
"",
diff --git a/engines/made/made.cpp b/engines/made/made.cpp
index 59ec487c37..dc45dc4d2f 100644
--- a/engines/made/made.cpp
+++ b/engines/made/made.cpp
@@ -148,27 +148,31 @@ int MadeEngine::init() {
return 0;
}
+int16 MadeEngine::getTicks() {
+ return g_system->getMillis() * 30 / 1000;
+}
+
int16 MadeEngine::getTimer(int16 timerNum) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers) && _timers[timerNum - 1] != -1)
- return (_system->getMillis() - _timers[timerNum - 1]) / kTimerResolution;
+ return (getTicks() - _timers[timerNum - 1]);
else
return 32000;
}
void MadeEngine::setTimer(int16 timerNum, int16 value) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers))
- _timers[timerNum - 1] = value * kTimerResolution;
+ _timers[timerNum - 1] = value;
}
void MadeEngine::resetTimer(int16 timerNum) {
if (timerNum > 0 && timerNum <= ARRAYSIZE(_timers))
- _timers[timerNum - 1] = _system->getMillis();
+ _timers[timerNum - 1] = getTicks();
}
int16 MadeEngine::allocTimer() {
for (int i = 0; i < ARRAYSIZE(_timers); i++) {
if (_timers[i] == -1) {
- _timers[i] = _system->getMillis();
+ _timers[i] = getTicks();
return i + 1;
}
}
@@ -202,24 +206,20 @@ void MadeEngine::handleEvents() {
break;
case Common::EVENT_LBUTTONDOWN:
- _eventNum = 1;
+ _eventNum = 2;
break;
- /*
case Common::EVENT_LBUTTONUP:
- _eventNum = 2; // TODO: Is this correct?
+ _eventNum = 1;
break;
- */
case Common::EVENT_RBUTTONDOWN:
- _eventNum = 3;
+ _eventNum = 4;
break;
- /*
case Common::EVENT_RBUTTONUP:
- eventNum = 4; // TODO: Is this correct?
+ _eventNum = 3;
break;
- */
case Common::EVENT_KEYDOWN:
_eventKey = event.kbd.ascii;
@@ -239,7 +239,7 @@ void MadeEngine::handleEvents() {
}
}
-
+
AudioCD.updateCD();
}
diff --git a/engines/made/made.h b/engines/made/made.h
index 461941e5cf..971961c867 100644
--- a/engines/made/made.h
+++ b/engines/made/made.h
@@ -120,6 +120,7 @@ public:
int _engineVersion;
int32 _timers[50];
+ int16 getTicks();
int16 getTimer(int16 timerNum);
void setTimer(int16 timerNum, int16 value);
void resetTimer(int16 timerNum);
diff --git a/engines/made/pmvplayer.cpp b/engines/made/pmvplayer.cpp
index 1a8ca9c50a..831f1fab8e 100644
--- a/engines/made/pmvplayer.cpp
+++ b/engines/made/pmvplayer.cpp
@@ -40,7 +40,10 @@ void PmvPlayer::play(const char *filename) {
_surface = NULL;
_fd = new Common::File();
- _fd->open(filename);
+ if (!_fd->open(filename)) {
+ delete _fd;
+ return;
+ }
uint32 chunkType, chunkSize;
diff --git a/engines/made/screen.cpp b/engines/made/screen.cpp
index cecd0c8968..0c22d40259 100644
--- a/engines/made/screen.cpp
+++ b/engines/made/screen.cpp
@@ -688,7 +688,7 @@ void Screen::printText(const char *text) {
for (int textPos = 0; textPos < textLen; textPos++) {
- uint c = text[textPos];
+ uint c = ((byte*)text)[textPos];
int charWidth = _font->getCharWidth(c);
if (c == 9) {
@@ -822,6 +822,8 @@ SpriteListItem Screen::getFromSpriteList(int16 index) {
if (((uint) index) > _spriteList.size()) {
SpriteListItem emptyItem;
emptyItem.index = 0;
+ emptyItem.xofs = 0;
+ emptyItem.yofs = 0;
return emptyItem;
} else {
return _spriteList[index - 1];
diff --git a/engines/made/scriptfuncs.cpp b/engines/made/scriptfuncs.cpp
index 932447a1eb..d697e24b04 100644
--- a/engines/made/scriptfuncs.cpp
+++ b/engines/made/scriptfuncs.cpp
@@ -106,7 +106,7 @@ void ScriptFunctions::setupExternalsTable() {
External(sfStopSound);
External(sfPlayVoice);
- if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ) {
+ if (_vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RTZ || _vm->getGameID() == GID_RODNEY) {
External(sfPlayCd);
External(sfStopCd);
External(sfGetCdStatus);
@@ -332,7 +332,7 @@ int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) {
if (_vm->getGameID() == GID_RTZ) {
// Unused in RTZ
return 0;
- } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) {
+ } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
return _vm->_screen->addToSpriteList(argv[2], argv[1], argv[0]);
} else {
return 0;
@@ -341,7 +341,7 @@ int16 ScriptFunctions::sfAddSprite(int16 argc, int16 *argv) {
int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) {
_vm->_screen->clearChannels();
- if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) {
+ if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
_vm->_screen->clearSpriteList();
}
return 0;
@@ -350,7 +350,7 @@ int16 ScriptFunctions::sfFreeAnim(int16 argc, int16 *argv) {
int16 ScriptFunctions::sfDrawSprite(int16 argc, int16 *argv) {
if (_vm->getGameID() == GID_RTZ) {
return _vm->_screen->drawSprite(argv[2], argv[1], argv[0]);
- } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) {
+ } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
SpriteListItem item = _vm->_screen->getFromSpriteList(argv[2]);
int16 channelIndex = _vm->_screen->drawSprite(item.index, argv[1] - item.xofs, argv[0] - item.yofs);
_vm->_screen->setChannelUseMask(channelIndex);
@@ -409,7 +409,7 @@ int16 ScriptFunctions::sfDrawText(int16 argc, int16 *argv) {
if (_vm->getGameID() == GID_RTZ) {
text = _vm->_dat->getObjectString(argv[argc - 1]);
- } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE) {
+ } if (_vm->getGameID() == GID_LGOP2 || _vm->getGameID() == GID_MANHOLE || _vm->getGameID() == GID_RODNEY) {
text = _vm->_dat->getString(argv[argc - 1]);
}
diff --git a/engines/parallaction/balloons.cpp b/engines/parallaction/balloons.cpp
new file mode 100644
index 0000000000..81b32adb15
--- /dev/null
+++ b/engines/parallaction/balloons.cpp
@@ -0,0 +1,728 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public 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/util.h"
+
+#include "parallaction/graphics.h"
+#include "parallaction/parallaction.h"
+
+namespace Parallaction {
+
+
+#define BALLOON_TRANSPARENT_COLOR_NS 2
+#define BALLOON_TRANSPARENT_COLOR_BR 0
+
+#define BALLOON_TAIL_WIDTH 12
+#define BALLOON_TAIL_HEIGHT 10
+
+
+byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = {
+ {
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
+ 0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ },
+ {
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
+ 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02,
+ 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02
+ }
+};
+
+class BalloonManager_ns : public BalloonManager {
+
+ static int16 _dialogueBalloonX[5];
+
+ struct Balloon {
+ Common::Rect outerBox;
+ Common::Rect innerBox;
+ Graphics::Surface *surface;
+ GfxObj *obj;
+ } _intBalloons[5];
+
+ 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);
+
+ Gfx *_gfx;
+
+public:
+ BalloonManager_ns(Gfx *gfx);
+ ~BalloonManager_ns();
+
+ void freeBalloons();
+ int setLocationBalloon(char *text, bool endGame);
+ int setDialogueBalloon(char *text, uint16 winding, byte textColor);
+ int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor);
+ void setBalloonText(uint id, char *text, byte textColor);
+ int hitTestDialogueBalloon(int x, int y);
+};
+
+int16 BalloonManager_ns::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 };
+
+BalloonManager_ns::BalloonManager_ns(Gfx *gfx) : _numBalloons(0), _gfx(gfx) {
+
+}
+
+BalloonManager_ns::~BalloonManager_ns() {
+
+}
+
+
+BalloonManager_ns::Balloon* BalloonManager_ns::getBalloon(uint id) {
+ assert(id < _numBalloons);
+ return &_intBalloons[id];
+}
+
+int BalloonManager_ns::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) {
+ assert(_numBalloons < 5);
+
+ int id = _numBalloons;
+
+ Balloon *balloon = &_intBalloons[id];
+
+ int16 real_h = (winding == -1) ? h : h + 9;
+ balloon->surface = new Graphics::Surface;
+ balloon->surface->create(w, real_h, 1);
+ balloon->surface->fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR_NS);
+
+ Common::Rect r(w, h);
+ balloon->surface->fillRect(r, 0);
+ balloon->outerBox = r;
+
+ r.grow(-borderThickness);
+ balloon->surface->fillRect(r, 1);
+ balloon->innerBox = r;
+
+ if (winding != -1) {
+ // draws tail
+ // TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill.
+ winding = (winding == 0 ? 1 : 0);
+ Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT);
+ s.moveTo(r.width()/2 - 5, r.bottom - 1);
+ _gfx->blt(s, _resBalloonTail[winding], balloon->surface, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR_NS);
+ }
+
+ _numBalloons++;
+
+ return id;
+}
+
+
+int BalloonManager_ns::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) {
+
+ int16 w, h;
+
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+
+ int id = createBalloon(w+5, h, winding, 1);
+ Balloon *balloon = &_intBalloons[id];
+
+ 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->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
+
+ return id;
+}
+
+int BalloonManager_ns::setDialogueBalloon(char *text, uint16 winding, byte textColor) {
+
+ int16 w, h;
+
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+
+ int id = createBalloon(w+5, h, winding, 1);
+ Balloon *balloon = &_intBalloons[id];
+
+ 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 = _dialogueBalloonX[id];
+ balloon->obj->y = 10;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
+
+ if (id > 0) {
+ balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].outerBox.height();
+ }
+
+
+ return id;
+}
+
+void BalloonManager_ns::setBalloonText(uint id, char *text, byte textColor) {
+ Balloon *balloon = getBalloon(id);
+ balloon->surface->fillRect(balloon->innerBox, 1);
+ drawWrappedText(_vm->_dialogueFont, balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
+}
+
+
+int BalloonManager_ns::setLocationBalloon(char *text, bool endGame) {
+
+ int16 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];
+ 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);
+ balloon->obj->x = 5;
+ balloon->obj->y = 5;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_NS;
+
+ return id;
+}
+
+int BalloonManager_ns::hitTestDialogueBalloon(int x, int y) {
+
+ Common::Point p;
+
+ for (uint i = 0; i < _numBalloons; i++) {
+ p.x = x - _intBalloons[i].obj->x;
+ p.y = y - _intBalloons[i].obj->y;
+
+ if (_intBalloons[i].innerBox.contains(p))
+ return i;
+ }
+
+ return -1;
+}
+
+void BalloonManager_ns::freeBalloons() {
+ _gfx->destroyBalloons();
+
+ for (uint i = 0; i < _numBalloons; i++) {
+ _intBalloons[i].obj = 0;
+ _intBalloons[i].surface = 0; // no need to delete surface, since it is done by destroyBalloons
+ }
+
+ _numBalloons = 0;
+}
+
+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);
+ }
+
+}
+
+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;
+}
+
+
+
+
+
+class BalloonManager_br : public BalloonManager {
+
+ struct Balloon {
+ Common::Rect box;
+ Graphics::Surface *surface;
+ GfxObj *obj;
+ } _intBalloons[3];
+
+ uint _numBalloons;
+
+ 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);
+ ~BalloonManager_br();
+
+ void freeBalloons();
+ int setLocationBalloon(char *text, bool endGame);
+ int setDialogueBalloon(char *text, uint16 winding, byte textColor);
+ int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor);
+ void setBalloonText(uint id, char *text, byte textColor);
+ int hitTestDialogueBalloon(int x, int y);
+};
+
+
+
+BalloonManager_br::Balloon* BalloonManager_br::getBalloon(uint id) {
+ assert(id < _numBalloons);
+ return &_intBalloons[id];
+}
+
+Graphics::Surface *BalloonManager_br::expandBalloon(Frames *data, int frameNum) {
+
+ Common::Rect rect;
+ data->getRect(frameNum, rect);
+
+ rect.translate(-rect.left, -rect.top);
+
+ Graphics::Surface *surf = new Graphics::Surface;
+ surf->create(rect.width(), rect.height(), 1);
+
+ _gfx->unpackBlt(rect, data->getData(frameNum), data->getRawSize(frameNum), surf, 0, BALLOON_TRANSPARENT_COLOR_BR);
+
+ return surf;
+}
+
+int BalloonManager_br::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) {
+ cacheAnims();
+
+ int id = _numBalloons;
+ Frames *src = 0;
+ int srcFrame = 0;
+
+ Balloon *balloon = &_intBalloons[id];
+
+ if (winding == 0) {
+ src = _rightBalloon;
+ srcFrame = 0;
+ } else
+ if (winding == 1) {
+ src = _leftBalloon;
+ srcFrame = 0;
+ }
+
+ assert(src);
+
+ balloon->surface = expandBalloon(src, srcFrame);
+ src->getRect(srcFrame, balloon->box);
+
+ 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->box.left;
+ balloon->obj->y = y + balloon->box.top;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
+
+ printf("balloon (%i, %i)\n", balloon->obj->x, balloon->obj->y);
+
+ _numBalloons++;
+
+ return id;
+}
+
+int BalloonManager_br::setDialogueBalloon(char *text, uint16 winding, byte textColor) {
+ cacheAnims();
+
+ int id = _numBalloons;
+ Frames *src = 0;
+ int srcFrame = 0;
+
+ Balloon *balloon = &_intBalloons[id];
+
+ if (winding == 0) {
+ src = _rightBalloon;
+ srcFrame = id;
+ } else
+ if (winding == 1) {
+ src = _leftBalloon;
+ srcFrame = 0;
+ }
+
+ assert(src);
+
+ balloon->surface = expandBalloon(src, srcFrame);
+ src->getRect(srcFrame, balloon->box);
+
+ 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 = balloon->box.left;
+ balloon->obj->y = balloon->box.top;
+ balloon->obj->transparentKey = BALLOON_TRANSPARENT_COLOR_BR;
+
+ if (id > 0) {
+ balloon->obj->y += _intBalloons[id - 1].obj->y + _intBalloons[id - 1].box.height();
+ }
+
+ _numBalloons++;
+
+ return id;
+}
+
+void BalloonManager_br::setBalloonText(uint id, char *text, byte textColor) { }
+
+int BalloonManager_br::setLocationBalloon(char *text, bool endGame) {
+/*
+ int16 w, h;
+
+ getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+
+ int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR);
+ Balloon *balloon = &_intBalloons[id];
+ 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);
+ balloon->obj->x = 5;
+ balloon->obj->y = 5;
+*/
+ return 0;
+}
+
+int BalloonManager_br::hitTestDialogueBalloon(int x, int y) {
+
+ Common::Point p;
+
+ for (uint i = 0; i < _numBalloons; i++) {
+ p.x = x - _intBalloons[i].obj->x;
+ p.y = y - _intBalloons[i].obj->y;
+
+ if (_intBalloons[i].box.contains(p))
+ return i;
+ }
+
+ return -1;
+}
+
+void BalloonManager_br::freeBalloons() {
+ _gfx->destroyBalloons();
+
+ for (uint i = 0; i < _numBalloons; i++) {
+ _intBalloons[i].obj = 0;
+ _intBalloons[i].surface = 0; // no need to delete surface, since it is done by destroyBalloons
+ }
+
+ _numBalloons = 0;
+}
+
+void BalloonManager_br::cacheAnims() {
+ if (!_leftBalloon) {
+ _leftBalloon = _disk->loadFrames("fumetto.ani");
+ _rightBalloon = _disk->loadFrames("fumdx.ani");
+ }
+}
+
+
+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) {
+}
+
+BalloonManager_br::~BalloonManager_br() {
+ delete _leftBalloon;
+ delete _rightBalloon;
+}
+
+void Parallaction::setupBalloonManager() {
+ if (_vm->getGameType() == GType_Nippon) {
+ _balloonMan = new BalloonManager_ns(_vm->_gfx);
+ } else
+ if (_vm->getGameType() == GType_BRA) {
+ _balloonMan = new BalloonManager_br(_vm->_disk, _vm->_gfx);
+ } else {
+ error("Unknown game type");
+ }
+}
+
+
+
+} // namespace Parallaction
diff --git a/engines/parallaction/callables_ns.cpp b/engines/parallaction/callables_ns.cpp
index 68e6a70ffb..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,8 +312,8 @@ void Parallaction_ns::_c_endComment(void *param) {
g_system->delayMillis(20);
}
- _input->waitUntilLeftClick();
- _gfx->freeBalloons();
+ _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;
}
@@ -417,6 +364,14 @@ void Parallaction_ns::_c_ridux(void *parm) {
}
void Parallaction_ns::_c_testResult(void *parm) {
+ if (_inTestResult) { // NOTE: _inTestResult has been added because the scripts call _c_testResult multiple times to cope with
+ // the multiple buffering that was used in the original engine. _inTestResult now prevents the engine
+ // from crashing when the scripts are executed.
+ return;
+ }
+ _inTestResult = true;
+
+ _gfx->freeLabels();
_gfx->updateScreen();
_disk->selectArchive("disk1");
@@ -459,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) {
@@ -588,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/debug.cpp b/engines/parallaction/debug.cpp
index 3c90a76f61..f57976594e 100644
--- a/engines/parallaction/debug.cpp
+++ b/engines/parallaction/debug.cpp
@@ -188,17 +188,15 @@ bool Debugger::Cmd_GfxObjects(int argc, const char **argv) {
const char *objType[] = { "DOOR", "GET", "ANIM" };
DebugPrintf("+--------------------+-----+-----+-----+-----+--------+--------+\n"
- "| name | x | y | z | f | type | flag |\n"
+ "| name | x | y | z | f | type | visi |\n"
"+--------------------+-----+-----+-----+-----+--------+--------+\n");
- for (uint i = 0; i < 3; i++) {
- GfxObjList::iterator b = _vm->_gfx->_gfxobjList[i].begin();
- GfxObjList::iterator e = _vm->_gfx->_gfxobjList[i].end();
+ GfxObjList::iterator b = _vm->_gfx->_gfxobjList.begin();
+ GfxObjList::iterator e = _vm->_gfx->_gfxobjList.end();
- for ( ; b != e; b++) {
- GfxObj *obj = *b;
- DebugPrintf("|%-20s|%5i|%5i|%5i|%5i|%8s|%8x|\n", obj->getName(), obj->x, obj->y, obj->z, obj->frame, objType[obj->type], 6 );
- }
+ for ( ; b != e; b++) {
+ GfxObj *obj = *b;
+ DebugPrintf("|%-20s|%5i|%5i|%5i|%5i|%8s|%8x|\n", obj->getName(), obj->x, obj->y, obj->z, obj->frame, objType[obj->type], obj->isVisible() );
}
DebugPrintf("+--------------------+-----+-----+-----+-----+--------+--------+\n");
diff --git a/engines/parallaction/detection.cpp b/engines/parallaction/detection.cpp
index 8841b9ca40..0476b01454 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 70db637699..21584a0525 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,118 +41,127 @@ 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 {
+ enum {
+ RUN_QUESTION,
+ RUN_ANSWER,
+ NEXT_QUESTION,
+ NEXT_ANSWER,
+ DIALOGUE_OVER
+ } _state;
+
Parallaction *_vm;
- SpeakData *_data;
Dialogue *_dialogue;
bool _askPassword;
+ int _passwordLen;
+ bool _passwordChanged;
bool isNpc;
- Frames *_questioner;
- Frames *_answerer;
+ GfxObj *_questioner;
+ GfxObj *_answerer;
Question *_q;
uint16 _visAnswers[5];
int _numVisAnswers;
+ int _answerId;
+
+ int _selection, _oldSelection;
+
+ uint32 _mouseButtons;
+ Common::Point _mousePos;
+ bool _isKeyDown;
+ uint16 _downKey;
+
+ BalloonPositions _ballonPos;
+
public:
- DialogueManager(Parallaction *vm, SpeakData *data) : _vm(vm), _data(data) {
- _dialogue = _data->_dialogue;
- isNpc = scumm_stricmp(_data->_name, "yourself") && _data->_name[0] != '\0';
- _questioner = isNpc ? _vm->_disk->loadTalk(_data->_name) : _vm->_char._talk;
- _answerer = _vm->_char._talk;
- }
+ DialogueManager(Parallaction *vm, ZonePtr z);
+ ~DialogueManager();
- ~DialogueManager() {
- if (isNpc) {
- delete _questioner;
- }
+ bool isOver() {
+ return _state == DIALOGUE_OVER;
}
-
void run();
+ ZonePtr _z;
+ CommandList *_cmdList;
+
protected:
- void displayQuestion();
+ bool displayQuestion();
bool displayAnswers();
bool displayAnswer(uint16 i);
- uint16 getAnswer();
- int16 selectAnswer();
- uint16 askPassword();
+ int16 selectAnswer1();
+ int16 selectAnswerN();
+ int16 askPassword();
int16 getHoverAnswer(int16 x, int16 y);
-};
-
-uint16 DialogueManager::askPassword() {
- debugC(3, kDebugExec, "checkDialoguePassword()");
-
- uint16 passwordLen = 0;
- _password[0] = '\0';
-
- _vm->_gfx->setDialogueBalloon(_q->_answers[0]->_text, 1, 3);
- int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y);
- _vm->_gfx->setItemFrame(id, 0);
-
- Common::Event e;
- bool changed = true; // force first refresh
-
- while (true) {
- e.kbd.ascii = 0;
-
- if (g_system->getEventManager()->pollEvent(e)) {
- if (e.type == Common::EVENT_QUIT) {
- // TODO: don't quit() here, just have caller routines to check
- // on kEngineQuit and exit gracefully to allow the engine to shut down
- _engineFlags |= kEngineQuit;
- g_system->quit();
- }
-
- if ((e.type == Common::EVENT_KEYDOWN) && isdigit(e.kbd.ascii)) {
- _password[passwordLen] = e.kbd.ascii;
- passwordLen++;
- _password[passwordLen] = '\0';
- changed = true;
- }
- }
-
- if (changed) {
- _vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 3);
- _vm->_gfx->updateScreen();
- changed = false;
- }
-
- if ((passwordLen == MAX_PASSWORD_LENGTH) || (e.kbd.ascii == Common::KEYCODE_RETURN)) {
+ void runQuestion();
+ void runAnswer();
+ void nextQuestion();
+ void nextAnswer();
- if ((!scumm_stricmp(_vm->_char.getBaseName(), _doughName) && !scumm_strnicmp(_password, "1732461", 7)) ||
- (!scumm_stricmp(_vm->_char.getBaseName(), _donnaName) && !scumm_strnicmp(_password, "1622", 4)) ||
- (!scumm_stricmp(_vm->_char.getBaseName(), _dinoName) && !scumm_strnicmp(_password, "179", 3))) {
+ bool checkPassword();
+ void resetPassword();
+ void accumPassword(uint16 ascii);
+};
- break;
+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;
+ _answerer = _vm->_char._talk;
- } else {
- passwordLen = 0;
- _password[0] = '\0';
- changed = true;
- }
+ _askPassword = false;
+ _q = _dialogue->_questions[0];
- }
+ _cmdList = 0;
+ _answerId = 0;
- g_system->delayMillis(20);
+ _state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER;
+}
+DialogueManager::~DialogueManager() {
+ if (isNpc) {
+ delete _questioner;
}
-
- _vm->_gfx->hideDialogueStuff();
-
- return 0;
-
+ _z = nullZonePtr;
}
-
-
bool DialogueManager::displayAnswer(uint16 i) {
Answer *a = _q->_answers[i];
@@ -164,11 +173,11 @@ bool DialogueManager::displayAnswer(uint16 i) {
// display suitable answers
if (((a->_yesFlags & flags) == a->_yesFlags) && ((a->_noFlags & ~flags) == a->_noFlags)) {
- int id = _vm->_gfx->setDialogueBalloon(a->_text, 1, 3);
+ int id = _vm->_balloonMan->setDialogueBalloon(a->_text, 1, 3);
assert(id >= 0);
_visAnswers[id] = i;
- _askPassword = (strstr(a->_text, "%p") != NULL);
+ _askPassword = (strstr(a->_text, "%P") != NULL);
_numVisAnswers++;
return true;
@@ -185,126 +194,243 @@ bool DialogueManager::displayAnswers() {
displayAnswer(i);
}
+ if (_askPassword) {
+ resetPassword();
+// _vm->_balloonMan->setDialogueBalloon(_q->_answers[0]->_text, 1, 3);
+ 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, _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, _ballonPos._answerChar.x, _ballonPos._answerChar.y);
+ _vm->_gfx->setItemFrame(id, _q->_answers[_visAnswers[0]]->_mood & 0xF);
+ _oldSelection = -1;
+ _selection = 0;
+ }
+
return _numVisAnswers > 0;
}
-void DialogueManager::displayQuestion() {
-
- if (!scumm_stricmp(_q->_text, "NULL")) return;
+bool DialogueManager::displayQuestion() {
+ if (!scumm_stricmp(_q->_text, "NULL")) return false;
- _vm->_gfx->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);
- _vm->_gfx->updateScreen();
- _vm->_input->waitUntilLeftClick();
- _vm->_gfx->hideDialogueStuff();
+ return true;
+}
- return;
+
+bool DialogueManager::checkPassword() {
+ return ((!scumm_stricmp(_vm->_char.getBaseName(), _doughName) && !scumm_strnicmp(_password, "1732461", 7)) ||
+ (!scumm_stricmp(_vm->_char.getBaseName(), _donnaName) && !scumm_strnicmp(_password, "1622", 4)) ||
+ (!scumm_stricmp(_vm->_char.getBaseName(), _dinoName) && !scumm_strnicmp(_password, "179", 3)));
}
-uint16 DialogueManager::getAnswer() {
+void DialogueManager::resetPassword() {
+ _passwordLen = 0;
+ _password[0] = '\0';
+ _passwordChanged = true;
+}
+
+void DialogueManager::accumPassword(uint16 ascii) {
+ if (!isdigit(ascii)) {
+ return;
+ }
- uint16 answer = 0;
+ _password[_passwordLen] = ascii;
+ _passwordLen++;
+ _password[_passwordLen] = '\0';
+ _passwordChanged = true;
+}
- if (_askPassword == false) {
- answer = selectAnswer();
- } else {
- answer = askPassword();
+int16 DialogueManager::askPassword() {
+
+ if (_isKeyDown) {
+ accumPassword(_downKey);
+ }
+
+ if (_passwordChanged) {
+ _vm->_balloonMan->setBalloonText(0, _q->_answers[0]->_text, 3);
+ _passwordChanged = false;
}
- debugC(3, kDebugExec, "runDialogue: user selected answer #%i", answer);
+ if ((_passwordLen == MAX_PASSWORD_LENGTH) || ((_isKeyDown) && (_downKey == Common::KEYCODE_RETURN))) {
+ if (checkPassword()) {
+ return 0;
+ } else {
+ resetPassword();
+ }
+ }
- return answer;
+ return -1;
}
-void DialogueManager::run() {
+int16 DialogueManager::selectAnswer1() {
- _askPassword = false;
- CommandList *cmdlist = NULL;
+ if (_mouseButtons == kMouseLeftUp) {
+ return 0;
+ }
- _q = _dialogue->_questions[0];
- int16 answer;
+ return -1;
+}
- while (_q) {
+int16 DialogueManager::selectAnswerN() {
- answer = 0;
+ _selection = _vm->_balloonMan->hitTestDialogueBalloon(_mousePos.x, _mousePos.y);
- displayQuestion();
- if (_q->_answers[0] == NULL) break;
+ if (_selection != _oldSelection) {
+ if (_oldSelection != -1) {
+ _vm->_balloonMan->setBalloonText(_oldSelection, _q->_answers[_visAnswers[_oldSelection]]->_text, 3);
+ }
- if (scumm_stricmp(_q->_answers[0]->_text, "NULL")) {
- if (!displayAnswers()) break;
- answer = getAnswer();
- cmdlist = &_q->_answers[answer]->_commands;
+ if (_selection != -1) {
+ _vm->_balloonMan->setBalloonText(_selection, _q->_answers[_visAnswers[_selection]]->_text, 0);
+ _vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[_selection]]->_mood & 0xF);
}
+ }
+
+ _oldSelection = _selection;
- _q = _q->_answers[answer]->_following._question;
+ if ((_mouseButtons == kMouseLeftUp) && (_selection != -1)) {
+ return _visAnswers[_selection];
}
- if (cmdlist)
- _vm->runCommands(*cmdlist);
+ return -1;
+}
+
+void DialogueManager::runQuestion() {
+ debugC(9, kDebugDialogue, "runQuestion\n");
+
+ if (_mouseButtons == kMouseLeftUp) {
+ _vm->hideDialogueStuff();
+ _state = NEXT_ANSWER;
+ }
}
-int16 DialogueManager::selectAnswer() {
- int16 numAvailableAnswers = _numVisAnswers;
+void DialogueManager::nextAnswer() {
+ debugC(9, kDebugDialogue, "nextAnswer\n");
- int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y);
- _vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF);
+ if (_q->_answers[0] == NULL) {
+ _state = DIALOGUE_OVER;
+ return;
+ }
- if (numAvailableAnswers == 1) {
- _vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 0);
- _vm->_input->waitUntilLeftClick();
- _vm->_gfx->hideDialogueStuff();
- return 0;
+ if (!scumm_stricmp(_q->_answers[0]->_text, "NULL")) {
+ _answerId = 0;
+ _state = NEXT_QUESTION;
+ return;
+ }
+
+ _state = displayAnswers() ? RUN_ANSWER : DIALOGUE_OVER;
+}
+
+void DialogueManager::runAnswer() {
+ debugC(9, kDebugDialogue, "runAnswer\n");
+
+ if (_askPassword) {
+ _answerId = askPassword();
+ } else
+ if (_numVisAnswers == 1) {
+ _answerId = selectAnswer1();
+ } else {
+ _answerId = selectAnswerN();
+ }
+
+ if (_answerId != -1) {
+ _cmdList = &_q->_answers[_answerId]->_commands;
+ _vm->hideDialogueStuff();
+ _state = NEXT_QUESTION;
}
+}
+
+void DialogueManager::nextQuestion() {
+ debugC(9, kDebugDialogue, "nextQuestion\n");
- int oldSelection = -1;
- int selection;
+ _q = _q->_answers[_answerId]->_following._question;
+ if (_q == 0) {
+ _state = DIALOGUE_OVER;
+ } else {
+ _state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER;
+ }
+}
- uint32 event;
- Common::Point p;
- while (true) {
- _vm->_input->readInput();
- _vm->_input->getCursorPos(p);
- event = _vm->_input->getLastButtonEvent();
- selection = _vm->_gfx->hitTestDialogueBalloon(p.x, p.y);
+void DialogueManager::run() {
- if (selection != oldSelection) {
- if (oldSelection != -1) {
- _vm->_gfx->setBalloonText(oldSelection, _q->_answers[_visAnswers[oldSelection]]->_text, 3);
- }
+ // cache event data
+ _mouseButtons = _vm->_input->getLastButtonEvent();
+ _vm->_input->getCursorPos(_mousePos);
+ _isKeyDown = _vm->_input->getLastKeyDown(_downKey);
- if (selection != -1) {
- _vm->_gfx->setBalloonText(selection, _q->_answers[_visAnswers[selection]]->_text, 0);
- _vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[selection]]->_mood & 0xF);
- }
- }
+ switch (_state) {
+ case RUN_QUESTION:
+ runQuestion();
+ break;
- if ((selection != -1) && (event == kMouseLeftUp)) {
- break;
- }
+ case NEXT_ANSWER:
+ nextAnswer();
+ break;
- _vm->_gfx->updateScreen();
- g_system->delayMillis(20);
+ case NEXT_QUESTION:
+ nextQuestion();
+ break;
- oldSelection = selection;
+ case RUN_ANSWER:
+ runAnswer();
+ break;
+
+ case DIALOGUE_OVER:
+ break;
+
+ default:
+ error("unknown state in DialogueManager");
+
+ }
+
+}
+
+void Parallaction::enterDialogueMode(ZonePtr z) {
+ debugC(1, kDebugDialogue, "Parallaction::enterDialogueMode(%s)", z->u.speak->_name);
+ _dialogueMan = new DialogueManager(this, z);
+ _input->_inputMode = Input::kInputModeDialogue;
+}
+
+void Parallaction::exitDialogueMode() {
+ debugC(1, kDebugDialogue, "Parallaction::exitDialogueMode()");
+ _input->_inputMode = Input::kInputModeGame;
+
+ if (_dialogueMan->_cmdList) {
+ _vm->_cmdExec->run(*_dialogueMan->_cmdList);
}
- _vm->_gfx->hideDialogueStuff();
+ // 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).
+ ZonePtr z = _dialogueMan->_z;
+ delete _dialogueMan;
+ _dialogueMan = 0;
- return _visAnswers[selection];
+ _cmdExec->run(z->_commands, z);
}
+void Parallaction::runDialogueFrame() {
+ if (_input->_inputMode != Input::kInputModeDialogue) {
+ return;
+ }
-void Parallaction::runDialogue(SpeakData *data) {
- debugC(1, kDebugExec, "runDialogue: starting dialogue '%s'", data->_name);
+ _dialogueMan->run();
- DialogueManager man(this, data);
- man.run();
+ if (_dialogueMan->isOver()) {
+ exitDialogueMode();
+ }
return;
}
diff --git a/engines/parallaction/disk.h b/engines/parallaction/disk.h
index b76c66aead..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"
@@ -55,12 +57,12 @@ public:
virtual Script* loadLocation(const char *name) = 0;
virtual Script* loadScript(const char* name) = 0;
- virtual Frames* loadTalk(const char *name) = 0;
- virtual Frames* loadObjects(const char *name) = 0;
+ virtual GfxObj* loadTalk(const char *name) = 0;
+ virtual GfxObj* loadObjects(const char *name) = 0;
virtual Frames* loadPointer(const char *name) = 0;
- virtual Frames* loadHead(const char* name) = 0;
+ virtual GfxObj* loadHead(const char* name) = 0;
virtual Font* loadFont(const char* name) = 0;
- virtual Frames* loadStatic(const char* name) = 0;
+ virtual GfxObj* loadStatic(const char* name) = 0;
virtual Frames* loadFrames(const char* name) = 0;
virtual void loadSlide(BackgroundInfo& info, const char *filename) = 0;
virtual void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path) = 0;
@@ -147,12 +149,12 @@ public:
Script* loadLocation(const char *name);
Script* loadScript(const char* name);
- Frames* loadTalk(const char *name);
- Frames* loadObjects(const char *name);
+ GfxObj* loadTalk(const char *name);
+ GfxObj* loadObjects(const char *name);
Frames* loadPointer(const char *name);
- Frames* loadHead(const char* name);
+ GfxObj* loadHead(const char* name);
Font* loadFont(const char* name);
- Frames* loadStatic(const char* name);
+ GfxObj* loadStatic(const char* name);
Frames* loadFrames(const char* name);
void loadSlide(BackgroundInfo& info, const char *filename);
void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path);
@@ -181,12 +183,12 @@ public:
Script* loadLocation(const char *name);
Script* loadScript(const char* name);
- Frames* loadTalk(const char *name);
- Frames* loadObjects(const char *name);
+ GfxObj* loadTalk(const char *name);
+ GfxObj* loadObjects(const char *name);
Frames* loadPointer(const char *name);
- Frames* loadHead(const char* name);
+ GfxObj* loadHead(const char* name);
Font* loadFont(const char* name);
- Frames* loadStatic(const char* name);
+ GfxObj* loadStatic(const char* name);
Frames* loadFrames(const char* name);
void loadSlide(BackgroundInfo& info, const char *filename);
void loadScenery(BackgroundInfo& info, const char* background, const char* mask, const char* path);
@@ -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);
@@ -220,12 +236,12 @@ public:
void setLanguage(uint16 language);
Script* loadLocation(const char *name);
Script* loadScript(const char* name);
- Frames* loadTalk(const char *name);
- Frames* loadObjects(const char *name);
+ GfxObj* loadTalk(const char *name);
+ GfxObj* loadObjects(const char *name);
Frames* loadPointer(const char *name);
- Frames* loadHead(const char* name);
+ GfxObj* loadHead(const char* name);
Font* loadFont(const char* name);
- Frames* loadStatic(const char* name);
+ GfxObj* loadStatic(const char* name);
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);
@@ -234,26 +250,49 @@ 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);
virtual ~AmigaDisk_br();
- Frames* loadTalk(const char *name);
+ GfxObj* loadTalk(const char *name);
Font* loadFont(const char* name);
- Frames* loadStatic(const char* name);
+ GfxObj* loadStatic(const char* name);
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 5e88327879..cd57ec8822 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"
@@ -58,7 +59,7 @@ struct Sprites : public Frames {
}
~Sprites() {
- delete _sprites;
+ delete[] _sprites;
}
uint16 getNum() {
@@ -90,101 +91,110 @@ 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() {
}
-Frames* DosDisk_br::loadTalk(const char *name) {
+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 createSprites(stream);
+ 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);
}
// there are no Head resources in Big Red Adventure
-Frames* DosDisk_br::loadHead(const char* name) {
+GfxObj* DosDisk_br::loadHead(const char* name) {
debugC(5, kDebugDisk, "DosDisk_br::loadHead");
return 0;
}
@@ -192,6 +202,7 @@ Frames* 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,39 +238,53 @@ 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);
}
-Frames* DosDisk_br::loadObjects(const char *name) {
+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) {
sprintf(path, "%s.bmp", name);
}
-Frames* DosDisk_br::loadStatic(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 SurfaceToFrames(surf);
+ return new GfxObj(0, new SurfaceToFrames(surf), name);
}
Sprites* DosDisk_br::createSprites(Common::ReadStream &stream) {
@@ -283,14 +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::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;
- if (!stream.open(path))
- errorFileNotFound(path);
-
-
+ stream.open(node);
return createSprites(stream);
}
@@ -302,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];
@@ -325,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];
@@ -347,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
@@ -360,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
@@ -377,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;
@@ -407,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;
@@ -480,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);
@@ -545,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;
}
-Frames* AmigaDisk_br::loadStatic(const char* name) {
+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;
@@ -570,16 +636,10 @@ Frames* AmigaDisk_br::loadStatic(const char* name) {
free(pal);
- return new SurfaceToFrames(surf);
+ 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);
@@ -603,32 +663,165 @@ 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);
}
-Frames* AmigaDisk_br::loadTalk(const char *name) {
+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 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();
+
+ printf("fontDir = %s, fontFile = %s\n", fontDir.c_str(), fontFile.c_str());
+
+ 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/disk_ns.cpp b/engines/parallaction/disk_ns.cpp
index cdbe3458a7..55e6fc5e77 100644
--- a/engines/parallaction/disk_ns.cpp
+++ b/engines/parallaction/disk_ns.cpp
@@ -385,12 +385,12 @@ Cnv* DosDisk_ns::loadCnv(const char *filename) {
return new Cnv(numFrames, width, height, data);
}
-Frames* DosDisk_ns::loadTalk(const char *name) {
+GfxObj* DosDisk_ns::loadTalk(const char *name) {
const char *ext = strstr(name, ".talk");
if (ext != NULL) {
// npc talk
- return loadCnv(name);
+ return new GfxObj(0, loadCnv(name), name);
}
@@ -401,7 +401,7 @@ Frames* DosDisk_ns::loadTalk(const char *name) {
sprintf(v20, "%stal", name);
}
- return loadExternalCnv(v20);
+ return new GfxObj(0, loadExternalCnv(v20), name);
}
Script* DosDisk_ns::loadLocation(const char *name) {
@@ -434,14 +434,14 @@ Script* DosDisk_ns::loadScript(const char* name) {
return new Script(new DummyArchiveStream(_resArchive), true);
}
-Frames* DosDisk_ns::loadHead(const char* name) {
+GfxObj* DosDisk_ns::loadHead(const char* name) {
char path[PATH_LEN];
sprintf(path, "%shead", name);
path[8] = '\0';
- return loadExternalStaticCnv(path);
+ return new GfxObj(0, loadExternalStaticCnv(path));
}
@@ -457,15 +457,15 @@ Font* DosDisk_ns::loadFont(const char* name) {
}
-Frames* DosDisk_ns::loadObjects(const char *name) {
+GfxObj* DosDisk_ns::loadObjects(const char *name) {
char path[PATH_LEN];
sprintf(path, "%sobj", name);
- return loadExternalCnv(path);
+ return new GfxObj(0, loadExternalCnv(path), name);
}
-Frames* DosDisk_ns::loadStatic(const char* name) {
+GfxObj* DosDisk_ns::loadStatic(const char* name) {
char path[PATH_LEN];
@@ -487,7 +487,7 @@ Frames* DosDisk_ns::loadStatic(const char* name) {
Graphics::PackBitsReadStream decoder(_resArchive);
decoder.read(cnv->pixels, w*h);
- return new SurfaceToFrames(cnv);
+ return new GfxObj(0, new SurfaceToFrames(cnv), name);
}
Frames* DosDisk_ns::loadFrames(const char* name) {
@@ -1025,7 +1025,7 @@ Frames* AmigaDisk_ns::loadPointer(const char* name) {
return makeStaticCnv(stream);
}
-Frames* AmigaDisk_ns::loadStatic(const char* name) {
+GfxObj* AmigaDisk_ns::loadStatic(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadStatic '%s'", name);
Common::SeekableReadStream *s = openArchivedFile(name, true);
@@ -1033,7 +1033,7 @@ Frames* AmigaDisk_ns::loadStatic(const char* name) {
delete s;
- return cnv;
+ return new GfxObj(0, cnv, name);
}
Common::SeekableReadStream *AmigaDisk_ns::openArchivedFile(const char* name, bool errorOnFileNotFound) {
@@ -1276,7 +1276,7 @@ Frames* AmigaDisk_ns::loadFrames(const char* name) {
return cnv;
}
-Frames* AmigaDisk_ns::loadHead(const char* name) {
+GfxObj* AmigaDisk_ns::loadHead(const char* name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadHead '%s'", name);
char path[PATH_LEN];
@@ -1287,11 +1287,11 @@ Frames* AmigaDisk_ns::loadHead(const char* name) {
delete s;
- return cnv;
+ return new GfxObj(0, cnv, name);
}
-Frames* AmigaDisk_ns::loadObjects(const char *name) {
+GfxObj* AmigaDisk_ns::loadObjects(const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadObjects");
char path[PATH_LEN];
@@ -1305,11 +1305,11 @@ Frames* AmigaDisk_ns::loadObjects(const char *name) {
Cnv *cnv = makeCnv(*s);
delete s;
- return cnv;
+ return new GfxObj(0, cnv, name);
}
-Frames* AmigaDisk_ns::loadTalk(const char *name) {
+GfxObj* AmigaDisk_ns::loadTalk(const char *name) {
debugC(1, kDebugDisk, "AmigaDisk_ns::loadTalk '%s'", name);
Common::SeekableReadStream *s;
@@ -1328,7 +1328,7 @@ Frames* AmigaDisk_ns::loadTalk(const char *name) {
Cnv *cnv = makeCnv(*s);
delete s;
- return cnv;
+ return new GfxObj(0, cnv, name);
}
Table* AmigaDisk_ns::loadTable(const char* name) {
@@ -1395,9 +1395,7 @@ Common::ReadStream* AmigaDisk_ns::loadSound(const char* name) {
char path[PATH_LEN];
sprintf(path, "%s.snd", name);
- openArchivedFile(path);
-
- return new DummyArchiveStream(_resArchive);
+ return openArchivedFile(path);
}
} // namespace Parallaction
diff --git a/engines/parallaction/exec.h b/engines/parallaction/exec.h
new file mode 100644
index 0000000000..22e75744f1
--- /dev/null
+++ b/engines/parallaction/exec.h
@@ -0,0 +1,255 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $URL$
+ * $Id$
+ *
+ */
+
+
+#ifndef PARALLACTION_EXEC_H
+#define PARALLACTION_EXEC_H
+
+#include "common/util.h"
+#include "parallaction/objects.h"
+
+
+namespace Parallaction {
+
+typedef Common::Functor0<void> Opcode;
+typedef Common::Array<const Opcode*> OpcodeSet;
+
+#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op()
+#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op()
+
+class Parallaction_ns;
+class Parallaction_br;
+
+class CommandExec {
+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)
+ delete *i;
+ _opcodes.clear();
+ }
+};
+
+class CommandExec_ns : public CommandExec {
+
+ Parallaction_ns *_vm;
+
+protected:
+ void updateGetZone(ZonePtr z, bool visible);
+
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(set);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(get);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
+
+public:
+ void init();
+
+ CommandExec_ns(Parallaction_ns* vm);
+ ~CommandExec_ns();
+};
+
+class CommandExec_br : public CommandExec_ns {
+
+protected:
+ Parallaction_br *_vm;
+
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(character);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(add);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(let);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(music);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(give);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(text);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(part);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave);
+ DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave);
+
+public:
+ void init();
+
+ CommandExec_br(Parallaction_br* vm);
+ ~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;
+ virtual void runScripts(ProgramList::iterator first, ProgramList::iterator last);
+ ProgramExec() : _modCounter(0) {
+ }
+ virtual ~ProgramExec() {
+ for (Common::Array<const Opcode*>::iterator i = _opcodes.begin(); i != _opcodes.end(); ++i)
+ delete *i;
+ _opcodes.clear();
+ }
+};
+
+class ProgramExec_ns : public ProgramExec {
+
+ Parallaction_ns *_vm;
+
+protected:
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(show);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
+
+public:
+ void init();
+
+ ProgramExec_ns(Parallaction_ns *vm);
+ ~ProgramExec_ns();
+};
+
+class ProgramExec_br : public ProgramExec_ns {
+
+ Parallaction_br *_vm;
+
+protected:
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(dec);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif);
+ DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop);
+
+public:
+ void init();
+ ProgramExec_br(Parallaction_br *vm);
+ ~ProgramExec_br();
+};
+
+} // namespace Parallaction
+
+#endif
diff --git a/engines/parallaction/exec_br.cpp b/engines/parallaction/exec_br.cpp
index 3b67b4c370..0b7400f0f7 100644
--- a/engines/parallaction/exec_br.cpp
+++ b/engines/parallaction/exec_br.cpp
@@ -23,6 +23,7 @@
*
*/
+#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
@@ -60,16 +61,17 @@ namespace Parallaction {
#define INST_STOP 30
#define INST_ENDSCRIPT 31
-
-
#define SetOpcodeTable(x) table = &x;
-typedef Common::Functor0Mem<void, Parallaction_br> OpcodeV2;
-#define COMMAND_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_br::cmdOp_##op))
-#define DECLARE_COMMAND_OPCODE(op) void Parallaction_br::cmdOp_##op()
+typedef Common::Functor0Mem<void, CommandExec_br> OpcodeV1;
+#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_br::cmdOp_##op))
+#define DECLARE_COMMAND_OPCODE(op) void CommandExec_br::cmdOp_##op()
-#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_br::instOp_##op))
-#define DECLARE_INSTRUCTION_OPCODE(op) void Parallaction_br::instOp_##op()
+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);
@@ -100,8 +102,13 @@ void Parallaction_br::setupSubtitles(char *s, char *s2, int y) {
}
void Parallaction_br::clearSubtitles() {
- _gfx->freeLabels();
- _subtitle[0] = _subtitle[1] = -1;
+ if (_subtitle[0] != -1) {
+ _gfx->hideLabel(_subtitle[0]);
+ }
+
+ if (_subtitle[1] != -1) {
+ _gfx->hideLabel(_subtitle[1]);
+ }
}
@@ -109,22 +116,30 @@ DECLARE_COMMAND_OPCODE(location) {
warning("Parallaction_br::cmdOp_location command not yet implemented");
// TODO: handle startPos and startPos2
- scheduleLocationSwitch(_cmdRunCtxt.cmd->u._string);
+ _vm->scheduleLocationSwitch(_ctxt.cmd->u._string);
}
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);
+ }
}
DECLARE_COMMAND_OPCODE(on) {
- CommandData *data = &_cmdRunCtxt.cmd->u;
+ CommandData *data = &_ctxt.cmd->u;
ZonePtr z = data->_zone;
if (z) {
@@ -132,52 +147,53 @@ DECLARE_COMMAND_OPCODE(on) {
z->_flags &= ~kFlagsRemove;
if ((z->_type & 0xFFFF) & kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, true);
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, true);
}
}
}
DECLARE_COMMAND_OPCODE(off) {
- CommandData *data = &_cmdRunCtxt.cmd->u;
+ CommandData *data = &_ctxt.cmd->u;
ZonePtr z = data->_zone;
if (z) {
z->_flags |= kFlagsRemove;
if ((z->_type & 0xFFFF) & kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, false);
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, false);
}
}
}
DECLARE_COMMAND_OPCODE(call) {
- callFunction(_cmdRunCtxt.cmd->u._callable, &_cmdRunCtxt.z);
+ _vm->callFunction(_ctxt.cmd->u._callable, &_ctxt.z);
}
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) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsActing;
+ _ctxt.cmd->u._zone->_flags |= kFlagsActing;
}
DECLARE_COMMAND_OPCODE(stop) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsActing;
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsActing;
}
DECLARE_COMMAND_OPCODE(character) {
- debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", _cmdRunCtxt.cmd->u._string);
- changeCharacter(_cmdRunCtxt.cmd->u._string);
+ debugC(9, kDebugExec, "Parallaction_br::cmdOp_character(%s)", _ctxt.cmd->u._string);
+ _vm->changeCharacter(_ctxt.cmd->u._string);
}
@@ -187,17 +203,17 @@ DECLARE_COMMAND_OPCODE(followme) {
DECLARE_COMMAND_OPCODE(onmouse) {
- _input->showCursor(true);
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
}
DECLARE_COMMAND_OPCODE(offmouse) {
- _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);
}
@@ -207,42 +223,42 @@ DECLARE_COMMAND_OPCODE(leave) {
DECLARE_COMMAND_OPCODE(inc) {
- _counters[_cmdRunCtxt.cmd->u._lvalue] += _cmdRunCtxt.cmd->u._rvalue;
+ _vm->_counters[_ctxt.cmd->u._lvalue] += _ctxt.cmd->u._rvalue;
}
DECLARE_COMMAND_OPCODE(dec) {
- _counters[_cmdRunCtxt.cmd->u._lvalue] -= _cmdRunCtxt.cmd->u._rvalue;
+ _vm->_counters[_ctxt.cmd->u._lvalue] -= _ctxt.cmd->u._rvalue;
}
DECLARE_COMMAND_OPCODE(ifeq) {
- if (_counters[_cmdRunCtxt.cmd->u._lvalue] == _cmdRunCtxt.cmd->u._rvalue) {
- setLocationFlags(kFlagsTestTrue);
+ if (_vm->_counters[_ctxt.cmd->u._lvalue] == _ctxt.cmd->u._rvalue) {
+ _vm->setLocationFlags(kFlagsTestTrue);
} else {
- clearLocationFlags(kFlagsTestTrue);
+ _vm->clearLocationFlags(kFlagsTestTrue);
}
}
DECLARE_COMMAND_OPCODE(iflt) {
- if (_counters[_cmdRunCtxt.cmd->u._lvalue] < _cmdRunCtxt.cmd->u._rvalue) {
- setLocationFlags(kFlagsTestTrue);
+ if (_vm->_counters[_ctxt.cmd->u._lvalue] < _ctxt.cmd->u._rvalue) {
+ _vm->setLocationFlags(kFlagsTestTrue);
} else {
- clearLocationFlags(kFlagsTestTrue);
+ _vm->clearLocationFlags(kFlagsTestTrue);
}
}
DECLARE_COMMAND_OPCODE(ifgt) {
- if (_counters[_cmdRunCtxt.cmd->u._lvalue] > _cmdRunCtxt.cmd->u._rvalue) {
- setLocationFlags(kFlagsTestTrue);
+ if (_vm->_counters[_ctxt.cmd->u._lvalue] > _ctxt.cmd->u._rvalue) {
+ _vm->setLocationFlags(kFlagsTestTrue);
} else {
- clearLocationFlags(kFlagsTestTrue);
+ _vm->clearLocationFlags(kFlagsTestTrue);
}
}
DECLARE_COMMAND_OPCODE(let) {
- _counters[_cmdRunCtxt.cmd->u._lvalue] = _cmdRunCtxt.cmd->u._rvalue;
+ _vm->_counters[_ctxt.cmd->u._lvalue] = _ctxt.cmd->u._rvalue;
}
@@ -252,25 +268,25 @@ DECLARE_COMMAND_OPCODE(music) {
DECLARE_COMMAND_OPCODE(fix) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsFixed;
+ _ctxt.cmd->u._zone->_flags |= kFlagsFixed;
}
DECLARE_COMMAND_OPCODE(unfix) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsFixed;
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsFixed;
}
DECLARE_COMMAND_OPCODE(zeta) {
- _location._zeta0 = _cmdRunCtxt.cmd->u._zeta0;
- _location._zeta1 = _cmdRunCtxt.cmd->u._zeta1;
- _location._zeta2 = _cmdRunCtxt.cmd->u._zeta2;
+ _vm->_location._zeta0 = _ctxt.cmd->u._zeta0;
+ _vm->_location._zeta1 = _ctxt.cmd->u._zeta1;
+ _vm->_location._zeta2 = _ctxt.cmd->u._zeta2;
}
DECLARE_COMMAND_OPCODE(scroll) {
warning("Parallaction_br::cmdOp_scroll not yet implemented");
- _gfx->setVar("scroll_x", _cmdRunCtxt.cmd->u._rvalue );
+ _vm->_gfx->setVar("scroll_x", _ctxt.cmd->u._rvalue );
}
@@ -285,8 +301,8 @@ DECLARE_COMMAND_OPCODE(give) {
DECLARE_COMMAND_OPCODE(text) {
- CommandData *data = &_cmdRunCtxt.cmd->u;
- setupSubtitles(data->_string, data->_string2, data->_zeta0);
+ CommandData *data = &_ctxt.cmd->u;
+ _vm->setupSubtitles(data->_string, data->_string2, data->_zeta0);
}
@@ -297,7 +313,7 @@ DECLARE_COMMAND_OPCODE(part) {
DECLARE_COMMAND_OPCODE(testsfx) {
warning("Parallaction_br::cmdOp_testsfx not completely implemented");
- clearLocationFlags(kFlagsTestTrue); // should test if sfx are enabled
+ _vm->clearLocationFlags(kFlagsTestTrue); // should test if sfx are enabled
}
@@ -319,7 +335,7 @@ DECLARE_COMMAND_OPCODE(offsave) {
DECLARE_INSTRUCTION_OPCODE(on) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
ZonePtr z = inst->_z;
if (z) {
@@ -327,28 +343,28 @@ DECLARE_INSTRUCTION_OPCODE(on) {
z->_flags &= ~kFlagsRemove;
if ((z->_type & 0xFFFF) & kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, true);
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, true);
}
}
}
DECLARE_INSTRUCTION_OPCODE(off) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
ZonePtr z = inst->_z;
if (z) {
z->_flags |= kFlagsRemove;
if ((z->_type & 0xFFFF) & kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, false);
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, false);
}
}
}
DECLARE_INSTRUCTION_OPCODE(set) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 rvalue = inst->_opB.getRValue();
int16* lvalue = inst->_opA.getLValue();
@@ -358,22 +374,15 @@ DECLARE_INSTRUCTION_OPCODE(set) {
}
-DECLARE_INSTRUCTION_OPCODE(loop) {
- InstructionPtr inst = *_instRunCtxt.inst;
-
- _instRunCtxt.program->_loopCounter = inst->_opB.getRValue();
- _instRunCtxt.program->_loopStart = _instRunCtxt.inst;
-}
-
DECLARE_INSTRUCTION_OPCODE(inc) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 rvalue = inst->_opB.getRValue();
if (inst->_flags & kInstMod) { // mod
int16 _bx = (rvalue > 0 ? rvalue : -rvalue);
- if (_instRunCtxt.modCounter % _bx != 0) return;
+ if (_ctxt.modCounter % _bx != 0) return;
rvalue = (rvalue > 0 ? 1 : -1);
}
@@ -420,12 +429,12 @@ DECLARE_INSTRUCTION_OPCODE(wait) {
DECLARE_INSTRUCTION_OPCODE(start) {
- (*_instRunCtxt.inst)->_z->_flags |= kFlagsActing;
+ (*_ctxt.inst)->_z->_flags |= kFlagsActing;
}
DECLARE_INSTRUCTION_OPCODE(process) {
- _activeZone2 = (*_instRunCtxt.inst)->_z;
+ _vm->_activeZone2 = (*_ctxt.inst)->_z;
}
@@ -435,18 +444,18 @@ DECLARE_INSTRUCTION_OPCODE(move) {
DECLARE_INSTRUCTION_OPCODE(color) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 entry = inst->_opB.getRValue();
- _gfx->_palette.setEntry(entry, inst->_colors[0], inst->_colors[1], inst->_colors[2]);
+ _vm->_gfx->_palette.setEntry(entry, inst->_colors[0], inst->_colors[1], inst->_colors[2]);
}
DECLARE_INSTRUCTION_OPCODE(mask) {
#if 0
- Instruction *inst = *_instRunCtxt.inst;
+ Instruction *inst = *_ctxt.inst;
_gfx->_bgLayers[0] = inst->_opA.getRValue();
_gfx->_bgLayers[1] = inst->_opB.getRValue();
_gfx->_bgLayers[2] = inst->_opC.getRValue();
@@ -459,8 +468,8 @@ DECLARE_INSTRUCTION_OPCODE(print) {
}
DECLARE_INSTRUCTION_OPCODE(text) {
- InstructionPtr inst = (*_instRunCtxt.inst);
- setupSubtitles(inst->_text, inst->_text2, inst->_y);
+ InstructionPtr inst = (*_ctxt.inst);
+ _vm->setupSubtitles(inst->_text, inst->_text2, inst->_y);
}
@@ -488,22 +497,11 @@ DECLARE_INSTRUCTION_OPCODE(stop) {
warning("Parallaction_br::instOp_stop not yet implemented");
}
-DECLARE_INSTRUCTION_OPCODE(endscript) {
- if ((_instRunCtxt.anim->_flags & kFlagsLooping) == 0) {
- _instRunCtxt.anim->_flags &= ~kFlagsActing;
- runCommands(_instRunCtxt.anim->_commands, _instRunCtxt.anim);
- _instRunCtxt.program->_status = kProgramDone;
- }
- _instRunCtxt.program->_ip = _instRunCtxt.program->_instructions.begin();
-
- _instRunCtxt.suspend = true;
-}
-
-void Parallaction_br::initOpcodes() {
+void CommandExec_br::init() {
Common::Array<const Opcode*> *table = 0;
- SetOpcodeTable(_commandOpcodes);
+ SetOpcodeTable(_opcodes);
COMMAND_OPCODE(invalid);
COMMAND_OPCODE(set);
COMMAND_OPCODE(clear);
@@ -546,8 +544,21 @@ void Parallaction_br::initOpcodes() {
COMMAND_OPCODE(ret);
COMMAND_OPCODE(onsave);
COMMAND_OPCODE(offsave);
+}
+
+CommandExec_br::CommandExec_br(Parallaction_br* vm) : CommandExec_ns(vm), _vm(vm) {
+
+}
+
+CommandExec_br::~CommandExec_br() {
+
+}
- SetOpcodeTable(_instructionOpcodes);
+void ProgramExec_br::init() {
+
+ Common::Array<const Opcode*> *table = 0;
+
+ SetOpcodeTable(_opcodes);
INSTRUCTION_OPCODE(invalid);
INSTRUCTION_OPCODE(on);
INSTRUCTION_OPCODE(off);
@@ -557,7 +568,7 @@ void Parallaction_br::initOpcodes() {
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);
@@ -582,6 +593,13 @@ void Parallaction_br::initOpcodes() {
INSTRUCTION_OPCODE(endscript);
}
+ProgramExec_br::ProgramExec_br(Parallaction_br *vm) : ProgramExec_ns(vm), _vm(vm) {
+ _instructionNames = _instructionNamesRes_br;
+}
+
+ProgramExec_br::~ProgramExec_br() {
+}
+
#if 0
void Parallaction_br::jobWaitRemoveLabelJob(void *parm, Job *job) {
diff --git a/engines/parallaction/exec_ns.cpp b/engines/parallaction/exec_ns.cpp
index a4b372f42a..99a492863b 100644
--- a/engines/parallaction/exec_ns.cpp
+++ b/engines/parallaction/exec_ns.cpp
@@ -23,6 +23,7 @@
*
*/
+#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/parallaction.h"
#include "parallaction/sound.h"
@@ -52,18 +53,19 @@ namespace Parallaction {
#define SetOpcodeTable(x) table = &x;
-typedef Common::Functor0Mem<void, Parallaction_ns> OpcodeV2;
-#define COMMAND_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_ns::cmdOp_##op))
-#define DECLARE_COMMAND_OPCODE(op) void Parallaction_ns::cmdOp_##op()
-
-#define INSTRUCTION_OPCODE(op) table->push_back(new OpcodeV2(this, &Parallaction_ns::instOp_##op))
-#define DECLARE_INSTRUCTION_OPCODE(op) void Parallaction_ns::instOp_##op()
+typedef Common::Functor0Mem<void, CommandExec_ns> OpcodeV1;
+#define COMMAND_OPCODE(op) table->push_back(new OpcodeV1(this, &CommandExec_ns::cmdOp_##op))
+#define DECLARE_COMMAND_OPCODE(op) void CommandExec_ns::cmdOp_##op()
+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) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
inst->_a->_flags |= kFlagsActive;
inst->_a->_flags &= ~kFlagsRemove;
@@ -71,31 +73,31 @@ DECLARE_INSTRUCTION_OPCODE(on) {
DECLARE_INSTRUCTION_OPCODE(off) {
- (*_instRunCtxt.inst)->_a->_flags |= kFlagsRemove;
+ (*_ctxt.inst)->_a->_flags |= kFlagsRemove;
}
DECLARE_INSTRUCTION_OPCODE(loop) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
- _instRunCtxt.program->_loopCounter = inst->_opB.getRValue();
- _instRunCtxt.program->_loopStart = _instRunCtxt.inst;
+ _ctxt.program->_loopCounter = inst->_opB.getRValue();
+ _ctxt.program->_loopStart = _ctxt.ip;
}
DECLARE_INSTRUCTION_OPCODE(endloop) {
- if (--_instRunCtxt.program->_loopCounter > 0) {
- _instRunCtxt.inst = _instRunCtxt.program->_loopStart;
+ if (--_ctxt.program->_loopCounter > 0) {
+ _ctxt.ip = _ctxt.program->_loopStart;
}
}
DECLARE_INSTRUCTION_OPCODE(inc) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 _si = inst->_opB.getRValue();
if (inst->_flags & kInstMod) { // mod
int16 _bx = (_si > 0 ? _si : -_si);
- if (_instRunCtxt.modCounter % _bx != 0) return;
+ if (_ctxt.modCounter % _bx != 0) return;
_si = (_si > 0 ? 1 : -1);
}
@@ -116,7 +118,7 @@ DECLARE_INSTRUCTION_OPCODE(inc) {
DECLARE_INSTRUCTION_OPCODE(set) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
int16 _si = inst->_opB.getRValue();
int16 *lvalue = inst->_opA.getLValue();
@@ -127,7 +129,7 @@ DECLARE_INSTRUCTION_OPCODE(set) {
DECLARE_INSTRUCTION_OPCODE(put) {
- InstructionPtr inst = *_instRunCtxt.inst;
+ InstructionPtr inst = *_ctxt.inst;
Graphics::Surface v18;
v18.w = inst->_a->width();
v18.h = inst->_a->height();
@@ -137,162 +139,175 @@ DECLARE_INSTRUCTION_OPCODE(put) {
int16 y = inst->_opB.getRValue();
bool mask = (inst->_flags & kInstMaskedPut) == kInstMaskedPut;
- _gfx->patchBackground(v18, x, y, mask);
+ _vm->_gfx->patchBackground(v18, x, y, mask);
}
-DECLARE_INSTRUCTION_OPCODE(null) {
-
+DECLARE_INSTRUCTION_OPCODE(show) {
+ _ctxt.suspend = true;
}
DECLARE_INSTRUCTION_OPCODE(invalid) {
- error("Can't execute invalid opcode %i", (*_instRunCtxt.inst)->_index);
+ error("Can't execute invalid opcode %i", (*_ctxt.inst)->_index);
}
DECLARE_INSTRUCTION_OPCODE(call) {
- callFunction((*_instRunCtxt.inst)->_immediate, 0);
+ _vm->callFunction((*_ctxt.inst)->_immediate, 0);
}
DECLARE_INSTRUCTION_OPCODE(wait) {
- if (_engineFlags & kEngineWalking)
- _instRunCtxt.suspend = true;
+ if (_engineFlags & kEngineWalking) {
+ _ctxt.ip--;
+ _ctxt.suspend = true;
+ }
}
DECLARE_INSTRUCTION_OPCODE(start) {
- (*_instRunCtxt.inst)->_a->_flags |= (kFlagsActing | kFlagsActive);
+ (*_ctxt.inst)->_a->_flags |= (kFlagsActing | kFlagsActive);
}
DECLARE_INSTRUCTION_OPCODE(sound) {
- _activeZone = (*_instRunCtxt.inst)->_z;
+ _vm->_activeZone = (*_ctxt.inst)->_z;
}
DECLARE_INSTRUCTION_OPCODE(move) {
- InstructionPtr inst = (*_instRunCtxt.inst);
+ InstructionPtr inst = (*_ctxt.inst);
int16 x = inst->_opA.getRValue();
int16 y = inst->_opB.getRValue();
- _char.scheduleWalk(x, y);
+ _vm->_char.scheduleWalk(x, y);
}
DECLARE_INSTRUCTION_OPCODE(endscript) {
- if ((_instRunCtxt.anim->_flags & kFlagsLooping) == 0) {
- _instRunCtxt.anim->_flags &= ~kFlagsActing;
- runCommands(_instRunCtxt.anim->_commands, _instRunCtxt.anim);
- _instRunCtxt.program->_status = kProgramDone;
+ if ((_ctxt.anim->_flags & kFlagsLooping) == 0) {
+ _ctxt.anim->_flags &= ~kFlagsActing;
+ _vm->_cmdExec->run(_ctxt.anim->_commands, _ctxt.anim);
+ _ctxt.program->_status = kProgramDone;
}
- _instRunCtxt.program->_ip = _instRunCtxt.program->_instructions.begin();
- _instRunCtxt.suspend = true;
+ _ctxt.ip = _ctxt.program->_instructions.begin();
+ _ctxt.suspend = true;
}
DECLARE_COMMAND_OPCODE(invalid) {
- error("Can't execute invalid command '%i'", _cmdRunCtxt.cmd->_id);
+ error("Can't execute invalid command '%i'", _ctxt.cmd->_id);
}
DECLARE_COMMAND_OPCODE(set) {
- if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) {
- _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal;
- _commandFlags |= _cmdRunCtxt.cmd->u._flags;
+ if (_ctxt.cmd->u._flags & kFlagsGlobal) {
+ _ctxt.cmd->u._flags &= ~kFlagsGlobal;
+ _commandFlags |= _ctxt.cmd->u._flags;
} else {
- setLocationFlags(_cmdRunCtxt.cmd->u._flags);
+ _vm->setLocationFlags(_ctxt.cmd->u._flags);
}
}
DECLARE_COMMAND_OPCODE(clear) {
- if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) {
- _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal;
- _commandFlags &= ~_cmdRunCtxt.cmd->u._flags;
+ if (_ctxt.cmd->u._flags & kFlagsGlobal) {
+ _ctxt.cmd->u._flags &= ~kFlagsGlobal;
+ _commandFlags &= ~_ctxt.cmd->u._flags;
} else {
- clearLocationFlags(_cmdRunCtxt.cmd->u._flags);
+ _vm->clearLocationFlags(_ctxt.cmd->u._flags);
}
}
DECLARE_COMMAND_OPCODE(start) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsActing;
+ _ctxt.cmd->u._zone->_flags |= kFlagsActing;
}
DECLARE_COMMAND_OPCODE(speak) {
- _activeZone = _cmdRunCtxt.cmd->u._zone;
+ if ((_ctxt.cmd->u._zone->_type & 0xFFFF) == kZoneSpeak) {
+ _vm->enterDialogueMode(_ctxt.cmd->u._zone);
+ } else {
+ _vm->_activeZone = _ctxt.cmd->u._zone;
+ }
}
DECLARE_COMMAND_OPCODE(get) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsFixed;
- runZone(_cmdRunCtxt.cmd->u._zone);
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsFixed;
+ _vm->runZone(_ctxt.cmd->u._zone);
}
DECLARE_COMMAND_OPCODE(location) {
- scheduleLocationSwitch(_cmdRunCtxt.cmd->u._string);
+ _vm->scheduleLocationSwitch(_ctxt.cmd->u._string);
}
DECLARE_COMMAND_OPCODE(open) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsClosed;
- if (_cmdRunCtxt.cmd->u._zone->u.door->gfxobj) {
- updateDoor(_cmdRunCtxt.cmd->u._zone);
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsClosed;
+ if (_ctxt.cmd->u._zone->u.door->gfxobj) {
+ _vm->updateDoor(_ctxt.cmd->u._zone);
}
}
DECLARE_COMMAND_OPCODE(close) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsClosed;
- if (_cmdRunCtxt.cmd->u._zone->u.door->gfxobj) {
- updateDoor(_cmdRunCtxt.cmd->u._zone);
+ _ctxt.cmd->u._zone->_flags |= kFlagsClosed;
+ if (_ctxt.cmd->u._zone->u.door->gfxobj) {
+ _vm->updateDoor(_ctxt.cmd->u._zone);
}
}
+void CommandExec_ns::updateGetZone(ZonePtr z, bool visible) {
+ if (!z) {
+ return;
+ }
+
+ if ((z->_type & 0xFFFF) == kZoneGet) {
+ _vm->_gfx->showGfxObj(z->u.get->gfxobj, visible);
+ }
+}
DECLARE_COMMAND_OPCODE(on) {
- ZonePtr z = _cmdRunCtxt.cmd->u._zone;
- // WORKAROUND: the original DOS-based engine didn't check u->_zone before dereferencing
- // the pointer to get structure members, thus leading to crashes in systems with memory
- // protection.
- // As a side note, the overwritten address is the 5th entry in the DOS interrupt table
- // (print screen handler): this suggests that a system would hang when the print screen
- // key is pressed after playing Nippon Safes, provided that this code path is taken.
+ ZonePtr z = _ctxt.cmd->u._zone;
+
if (z) {
z->_flags &= ~kFlagsRemove;
z->_flags |= kFlagsActive;
- if ((z->_type & 0xFFFF) == kZoneGet) {
- _gfx->showGfxObj(z->u.get->gfxobj, true);
- }
+ updateGetZone(z, true);
}
}
DECLARE_COMMAND_OPCODE(off) {
- _cmdRunCtxt.cmd->u._zone->_flags |= kFlagsRemove;
+ ZonePtr z = _ctxt.cmd->u._zone;
+
+ if (z) {
+ _ctxt.cmd->u._zone->_flags |= kFlagsRemove;
+ updateGetZone(z, false);
+ }
}
DECLARE_COMMAND_OPCODE(call) {
- callFunction(_cmdRunCtxt.cmd->u._callable, &_cmdRunCtxt.z);
+ _vm->callFunction(_ctxt.cmd->u._callable, &_ctxt.z);
}
DECLARE_COMMAND_OPCODE(toggle) {
- if (_cmdRunCtxt.cmd->u._flags & kFlagsGlobal) {
- _cmdRunCtxt.cmd->u._flags &= ~kFlagsGlobal;
- _commandFlags ^= _cmdRunCtxt.cmd->u._flags;
+ if (_ctxt.cmd->u._flags & kFlagsGlobal) {
+ _ctxt.cmd->u._flags &= ~kFlagsGlobal;
+ _commandFlags ^= _ctxt.cmd->u._flags;
} else {
- toggleLocationFlags(_cmdRunCtxt.cmd->u._flags);
+ _vm->toggleLocationFlags(_ctxt.cmd->u._flags);
}
}
DECLARE_COMMAND_OPCODE(drop){
- dropItem( _cmdRunCtxt.cmd->u._object );
+ _vm->dropItem( _ctxt.cmd->u._object );
}
@@ -302,70 +317,103 @@ DECLARE_COMMAND_OPCODE(quit) {
DECLARE_COMMAND_OPCODE(move) {
- _char.scheduleWalk(_cmdRunCtxt.cmd->u._move.x, _cmdRunCtxt.cmd->u._move.y);
+ _vm->_char.scheduleWalk(_ctxt.cmd->u._move.x, _ctxt.cmd->u._move.y);
}
DECLARE_COMMAND_OPCODE(stop) {
- _cmdRunCtxt.cmd->u._zone->_flags &= ~kFlagsActing;
+ _ctxt.cmd->u._zone->_flags &= ~kFlagsActing;
}
void Parallaction_ns::drawAnimations() {
+ debugC(9, kDebugExec, "Parallaction_ns::drawAnimations()\n");
uint16 layer = 0;
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;
- if ((v18->_flags & kFlagsActive) && ((v18->_flags & kFlagsRemove) == 0)) {
+ // 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();
- int16 frame = CLIP((int)v18->_frame, 0, v18->getFrameNum()-1);
- if (v18->_flags & kFlagsNoMasked)
+ if ((anim->_flags & kFlagsActive) && ((anim->_flags & kFlagsRemove) == 0)) {
+
+ 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);
}
}
}
+ debugC(9, kDebugExec, "Parallaction_ns::drawAnimations done()\n");
+
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;
-void Parallaction_ns::runScripts() {
- if (_engineFlags & kEnginePauseJobs) {
- return;
}
+ script->_ip = _ctxt.ip;
- debugC(9, kDebugExec, "runScripts");
+}
- static uint16 modCounter = 0;
+void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator last) {
+ if (_engineFlags & kEnginePauseJobs) {
+ return;
+ }
- for (ProgramList::iterator it = _location._programs.begin(); it != _location._programs.end(); it++) {
+ for (ProgramList::iterator it = first; it != last; it++) {
AnimationPtr a = (*it)->_anim;
@@ -375,116 +423,182 @@ void Parallaction_ns::runScripts() {
if ((a->_flags & kFlagsActing) == 0)
continue;
- InstructionList::iterator inst = (*it)->_ip;
- while (((*inst)->_index != INST_SHOW) && (a->_flags & kFlagsActing)) {
+ runScript(*it, a);
- (*it)->_status = kProgramRunning;
+ if (a->_flags & kFlagsCharacter)
+ a->_z = a->_top + a->height();
+ }
- debugC(9, kDebugExec, "Animation: %s, instruction: %i", a->_name, (*inst)->_index); //_instructionNamesRes[(*inst)->_index - 1]);
+ _modCounter++;
- _instRunCtxt.inst = inst;
- _instRunCtxt.anim = AnimationPtr(a);
- _instRunCtxt.program = *it;
- _instRunCtxt.modCounter = modCounter;
- _instRunCtxt.suspend = false;
+ return;
+}
+
+void CommandExec::runList(CommandList::iterator first, CommandList::iterator last) {
- (*_instructionOpcodes[(*inst)->_index])();
+ uint32 useFlags = 0;
+ bool useLocalFlags;
- inst = _instRunCtxt.inst; // handles endloop correctly
+ _ctxt.suspend = false;
- if (_instRunCtxt.suspend)
- goto label1;
+ for ( ; first != last; first++) {
+ if (_engineFlags & kEngineQuit)
+ break;
- inst++;
+ CommandPtr cmd = *first;
+
+ if (cmd->_flagsOn & kFlagsGlobal) {
+ useFlags = _commandFlags | kFlagsGlobal;
+ useLocalFlags = false;
+ } else {
+ useFlags = _vm->getLocationFlags();
+ useLocalFlags = true;
}
- (*it)->_ip = ++inst;
+ bool onMatch = (cmd->_flagsOn & useFlags) == cmd->_flagsOn;
+ bool offMatch = (cmd->_flagsOff & ~useFlags) == cmd->_flagsOff;
-label1:
- if (a->_flags & kFlagsCharacter)
- a->_z = a->_top + a->height();
- }
+ debugC(3, kDebugExec, "runCommands[%i] (on: %x, off: %x), (%s = %x)", cmd->_id, cmd->_flagsOn, cmd->_flagsOff,
+ useLocalFlags ? "LOCALFLAGS" : "GLOBALFLAGS", useFlags);
+
+ if (!onMatch || !offMatch) continue;
+
+ _ctxt.z = _execZone;
+ _ctxt.cmd = cmd;
+
+ (*_opcodes[cmd->_id])();
- _char._ani->_z = _char._ani->height() + _char._ani->_top;
- if (_char._ani->gfxobj) {
- _char._ani->gfxobj->z = _char._ani->_z;
+ if (_ctxt.suspend) {
+ createSuspendList(++first, last);
+ return;
+ }
}
- modCounter++;
- return;
}
-
-void Parallaction::runCommands(CommandList& list, ZonePtr z) {
- if (list.size() == 0)
+void CommandExec::run(CommandList& list, ZonePtr z) {
+ if (list.size() == 0) {
+ debugC(3, kDebugExec, "runCommands: nothing to do");
return;
+ }
- debugC(3, kDebugExec, "runCommands");
-
- CommandList::iterator it = list.begin();
- for ( ; it != list.end(); it++) {
+ _execZone = z;
- CommandPtr cmd = *it;
- uint32 v8 = getLocationFlags();
+ debugC(3, kDebugExec, "runCommands starting");
+ runList(list.begin(), list.end());
+ debugC(3, kDebugExec, "runCommands completed");
+}
- if (_engineFlags & kEngineQuit)
- break;
+void CommandExec::createSuspendList(CommandList::iterator first, CommandList::iterator last) {
+ if (first == last) {
+ return;
+ }
- if (cmd->_flagsOn & kFlagsGlobal) {
- v8 = _commandFlags | kFlagsGlobal;
- }
+ debugC(3, kDebugExec, "CommandExec::createSuspendList()");
- if ((cmd->_flagsOn & v8) != cmd->_flagsOn) continue;
- if ((cmd->_flagsOff & ~v8) != cmd->_flagsOff) continue;
+ _suspendedCtxt.valid = true;
+ _suspendedCtxt.first = first;
+ _suspendedCtxt.last = last;
+ _suspendedCtxt.zone = _execZone;
+}
-// debugC(3, kDebugExec, "runCommands[%i]: %s (on: %x, off: %x)", cmd->_id, _commandsNamesRes[cmd->_id-1], cmd->_flagsOn, cmd->_flagsOff);
+void CommandExec::cleanSuspendedList() {
+ debugC(3, kDebugExec, "CommandExec::cleanSuspended()");
- _cmdRunCtxt.z = z;
- _cmdRunCtxt.cmd = cmd;
+ _suspendedCtxt.valid = false;
+ _suspendedCtxt.first = _suspendedCtxt.last;
+ _suspendedCtxt.zone = nullZonePtr;
+}
- (*_commandOpcodes[cmd->_id])();
+void CommandExec::runSuspended() {
+ if (_engineFlags & kEngineWalking) {
+ return;
}
- debugC(3, kDebugExec, "runCommands completed");
+ if (_suspendedCtxt.valid) {
+ debugC(3, kDebugExec, "CommandExec::runSuspended()");
- return;
+ _execZone = _suspendedCtxt.zone;
+ runList(_suspendedCtxt.first, _suspendedCtxt.last);
+ cleanSuspendedList();
+ }
+}
+
+CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : _vm(vm) {
}
+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);
- _gfx->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 {
- _gfx->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) {
@@ -496,8 +610,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;
@@ -518,14 +632,13 @@ uint16 Parallaction::runZone(ZonePtr z) {
break;
case kZoneSpeak:
- runDialogue(z->u.speak);
- break;
-
+ enterDialogueMode(z);
+ return 0;
}
debugC(3, kDebugExec, "runZone completed");
- runCommands(z->_commands, z);
+ _cmdExec->run(z->_commands, z);
return 0;
}
@@ -652,11 +765,34 @@ ZonePtr Parallaction::hitZone(uint32 type, uint16 x, uint16 y) {
}
-void Parallaction_ns::initOpcodes() {
+void CommandExec_ns::init() {
+ Common::Array<const Opcode*> *table = 0;
+
+ SetOpcodeTable(_opcodes);
+ COMMAND_OPCODE(invalid);
+ COMMAND_OPCODE(set);
+ COMMAND_OPCODE(clear);
+ COMMAND_OPCODE(start);
+ COMMAND_OPCODE(speak);
+ COMMAND_OPCODE(get);
+ COMMAND_OPCODE(location);
+ COMMAND_OPCODE(open);
+ COMMAND_OPCODE(close);
+ COMMAND_OPCODE(on);
+ COMMAND_OPCODE(off);
+ COMMAND_OPCODE(call);
+ COMMAND_OPCODE(toggle);
+ COMMAND_OPCODE(drop);
+ COMMAND_OPCODE(quit);
+ COMMAND_OPCODE(move);
+ COMMAND_OPCODE(stop);
+}
+
+void ProgramExec_ns::init() {
Common::Array<const Opcode*> *table = 0;
- SetOpcodeTable(_instructionOpcodes);
+ SetOpcodeTable(_opcodes);
INSTRUCTION_OPCODE(invalid);
INSTRUCTION_OPCODE(on);
INSTRUCTION_OPCODE(off);
@@ -666,7 +802,7 @@ void Parallaction_ns::initOpcodes() {
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);
@@ -678,25 +814,13 @@ void Parallaction_ns::initOpcodes() {
INSTRUCTION_OPCODE(move);
INSTRUCTION_OPCODE(endscript);
- SetOpcodeTable(_commandOpcodes);
- COMMAND_OPCODE(invalid);
- COMMAND_OPCODE(set);
- COMMAND_OPCODE(clear);
- COMMAND_OPCODE(start);
- COMMAND_OPCODE(speak);
- COMMAND_OPCODE(get);
- COMMAND_OPCODE(location);
- COMMAND_OPCODE(open);
- COMMAND_OPCODE(close);
- COMMAND_OPCODE(on);
- COMMAND_OPCODE(off);
- COMMAND_OPCODE(call);
- COMMAND_OPCODE(toggle);
- COMMAND_OPCODE(drop);
- COMMAND_OPCODE(quit);
- COMMAND_OPCODE(move);
- COMMAND_OPCODE(stop);
}
+ProgramExec_ns::ProgramExec_ns(Parallaction_ns *vm) : _vm(vm) {
+ _instructionNames = _instructionNamesRes_ns;
+}
+
+ProgramExec_ns::~ProgramExec_ns() {
+}
} // namespace Parallaction
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 6599a1f81c..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(0), _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 {
@@ -86,93 +86,265 @@ void GfxObj::clearFlags(uint32 flags) {
}
GfxObj* Gfx::loadAnim(const char *name) {
- Frames *frames = _disk->loadFrames(name);
+ Frames* frames = _disk->loadFrames(name);
+ assert(frames);
+
GfxObj *obj = new GfxObj(kGfxObjTypeAnim, frames, name);
assert(obj);
+ // animation Z is not set here, but controlled by game scripts and user interaction.
+ // it is always >=0 and <screen height
+ obj->transparentKey = 0;
+ _gfxobjList.push_back(obj);
return obj;
}
GfxObj* Gfx::loadGet(const char *name) {
- Frames *frames = _disk->loadStatic(name);
- GfxObj *obj = new GfxObj(kGfxObjTypeGet, frames, name);
+ GfxObj *obj = _disk->loadStatic(name);
assert(obj);
+ obj->z = kGfxObjGetZ; // this preset Z value ensures that get zones are drawn after doors but before animations
+ obj->type = kGfxObjTypeGet;
+ obj->transparentKey = 0;
+ _gfxobjList.push_back(obj);
return obj;
}
GfxObj* Gfx::loadDoor(const char *name) {
Frames *frames = _disk->loadFrames(name);
+ assert(frames);
+
GfxObj *obj = new GfxObj(kGfxObjTypeDoor, frames, name);
assert(obj);
+ obj->z = kGfxObjDoorZ; // this preset Z value ensures that doors are drawn first
+ obj->transparentKey = 0;
+ _gfxobjList.push_back(obj);
return obj;
}
-void Gfx::clearGfxObjects() {
- _gfxobjList[0].clear();
- _gfxobjList[1].clear();
- _gfxobjList[2].clear();
+void Gfx::clearGfxObjects(uint filter) {
+
+ GfxObjList::iterator b = _gfxobjList.begin();
+ GfxObjList::iterator e = _gfxobjList.end();
+
+ for ( ; b != e; ) {
+ if (((*b)->_flags & filter) != 0) {
+ b = _gfxobjList.erase(b);
+ } else {
+ b++;
+ }
+ }
+
}
void Gfx::showGfxObj(GfxObj* obj, bool visible) {
- if (!obj || obj->isVisible() == visible) {
+ if (!obj) {
return;
}
if (visible) {
obj->setFlags(kGfxObjVisible);
- _gfxobjList[obj->type].push_back(obj);
} else {
obj->clearFlags(kGfxObjVisible);
- _gfxobjList[obj->type].remove(obj);
}
-
}
-bool compareAnimationZ(const GfxObj* a1, const GfxObj* a2) {
+bool compareZ(const GfxObj* a1, const GfxObj* a2) {
return a1->z < a2->z;
}
void Gfx::sortAnimations() {
- GfxObjList::iterator first = _gfxobjList[kGfxObjTypeAnim].begin();
- GfxObjList::iterator last = _gfxobjList[kGfxObjTypeAnim].end();
+ GfxObjList::iterator first = _gfxobjList.begin();
+ GfxObjList::iterator last = _gfxobjList.end();
- Common::sort(first, last, compareAnimationZ);
+ Common::sort(first, last, compareZ);
}
-void Gfx::drawGfxObjects(Graphics::Surface &surf) {
+
+void Gfx::drawGfxObject(GfxObj *obj, Graphics::Surface &surf, bool scene) {
+ if (!obj->isVisible()) {
+ return;
+ }
Common::Rect rect;
byte *data;
+ uint scrollX = (scene) ? -_varScrollX : 0;
+
+ obj->getRect(obj->frame, rect);
+ rect.translate(obj->x + scrollX, obj->y);
+ data = obj->getData(obj->frame);
+
+ if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) {
+ blt(rect, data, &surf, obj->layer, obj->transparentKey);
+ } else {
+ unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, obj->transparentKey);
+ }
+
+}
+
+
+void Gfx::drawGfxObjects(Graphics::Surface &surf) {
+
sortAnimations();
// TODO: some zones don't appear because of wrong masking (3 or 0?)
- // TODO: Dr.Ki is not visible inside the club
+
+ GfxObjList::iterator b = _gfxobjList.begin();
+ GfxObjList::iterator e = _gfxobjList.end();
+
+ for (; b != e; b++) {
+ drawGfxObject(*b, surf, true);
+ }
+}
- for (uint i = 0; i < 3; i++) {
- GfxObjList::iterator b = _gfxobjList[i].begin();
- GfxObjList::iterator e = _gfxobjList[i].end();
+void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color) {
+ byte *dst = (byte*)surf->getBasePtr(x, y);
+ font->setColor(color);
+ font->drawString(dst, surf->w, text);
+}
+
- for (; b != e; b++) {
- GfxObj *obj = *b;
- if (obj->isVisible()) {
- obj->getRect(obj->frame, rect);
- rect.translate(obj->x - _varScrollX, obj->y);
- data = obj->getData(obj->frame);
- if (obj->getSize(obj->frame) == obj->getRawSize(obj->frame)) {
- blt(rect, data, &surf, obj->layer, 0);
- } else {
- unpackBlt(rect, data, obj->getRawSize(obj->frame), &surf, obj->layer, 0);
+#if 0
+void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
+
+ byte *d = _unpackedBitmap;
+
+ while (size > 0) {
+
+ uint8 p = *data++;
+ size--;
+ uint8 color = p & 0xF;
+ uint8 repeat = (p & 0xF0) >> 4;
+ if (repeat == 0) {
+ repeat = *data++;
+ size--;
+ }
+
+ memset(d, color, repeat);
+ d += repeat;
+ }
+
+ blt(r, _unpackedBitmap, surf, z, transparentColor);
+}
+#endif
+void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
+
+ byte *d = _unpackedBitmap;
+ uint pixelsLeftInLine = r.width();
+
+ while (size > 0) {
+ uint8 p = *data++;
+ size--;
+ uint8 color = p & 0xF;
+ uint8 repeat = (p & 0xF0) >> 4;
+ if (repeat == 0) {
+ repeat = *data++;
+ size--;
+ }
+ if (repeat == 0) {
+ // end of line
+ repeat = pixelsLeftInLine;
+ pixelsLeftInLine = r.width();
+ } else {
+ pixelsLeftInLine -= repeat;
+ }
+
+ memset(d, color, repeat);
+ d += repeat;
+ }
+
+ blt(r, _unpackedBitmap, surf, z, transparentColor);
+}
+
+
+void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) {
+
+ Common::Point dp;
+ Common::Rect q(r);
+
+ Common::Rect clipper(surf->w, surf->h);
+
+ q.clip(clipper);
+ if (!q.isValidRect()) return;
+
+ dp.x = q.left;
+ dp.y = q.top;
+
+ q.translate(-r.left, -r.top);
+
+ byte *s = data + q.left + q.top * r.width();
+ byte *d = (byte*)surf->getBasePtr(dp.x, dp.y);
+
+ uint sPitch = r.width() - q.width();
+ uint dPitch = surf->w - q.width();
+
+
+ if (_varRenderMode == 2) {
+
+ for (uint16 i = 0; i < q.height(); i++) {
+
+ 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 (z >= v) *d = 5;
+ } else {
+ *d = 5;
+ }
}
+
+ s++;
+ d++;
}
+
+ s += sPitch;
+ d += dPitch;
+ }
+
+ } else {
+ 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);
+ if (z >= v) *d = *s;
+ }
+
+ s++;
+ d++;
+ }
+
+ s += sPitch;
+ d += dPitch;
+ }
+
+ } else {
+
+ for (uint16 i = q.top; i < q.bottom; i++) {
+ for (uint16 j = q.left; j < q.right; j++) {
+ if (*s != transparentColor)
+ *d = *s;
+
+ s++;
+ d++;
+ }
+
+ s += sPitch;
+ d += dPitch;
+ }
+
}
}
+
}
+
} // namespace Parallaction
diff --git a/engines/parallaction/graphics.cpp b/engines/parallaction/graphics.cpp
index 58fb02a750..c19d6ae5e5 100644
--- a/engines/parallaction/graphics.cpp
+++ b/engines/parallaction/graphics.cpp
@@ -33,6 +33,11 @@
namespace Parallaction {
+// this is the size of the receiving buffer for unpacked frames,
+// since BRA uses some insanely big animations.
+#define MAXIMUM_UNPACKED_BITMAP_SIZE 640*401
+
+
void Gfx::registerVar(const Common::String &name, int32 initialValue) {
if (_vars.contains(name)) {
warning("Variable '%s' already registered, ignoring initial value.\n", name.c_str());
@@ -64,10 +69,6 @@ int32 Gfx::getVar(const Common::String &name) {
#define LABEL_TRANSPARENT_COLOR 0xFF
-#define BALLOON_TRANSPARENT_COLOR 2
-
-
-int16 Gfx::_dialogueBalloonX[5] = { 80, 120, 150, 150, 150 };
void halfbritePixel(int x, int y, int color, void *data) {
byte *buffer = (byte*)data;
@@ -152,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++) {
@@ -238,37 +246,6 @@ void Palette::rotate(uint first, uint last, bool forward) {
}
-#define BALLOON_TAIL_WIDTH 12
-#define BALLOON_TAIL_HEIGHT 10
-
-
-byte _resBalloonTail[2][BALLOON_TAIL_WIDTH*BALLOON_TAIL_HEIGHT] = {
- {
- 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
- 0x02, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x00, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x00, 0x00, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- },
- {
- 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02,
- 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x01, 0x01, 0x00, 0x02, 0x02,
- 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x00, 0x02, 0x02
- }
-};
-
void Gfx::setPalette(Palette pal) {
byte sysPal[256*4];
@@ -292,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
@@ -337,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);
@@ -356,21 +337,19 @@ void Gfx::drawItems() {
Graphics::Surface *surf = g_system->lockScreen();
for (uint i = 0; i < _numItems; i++) {
- blt(_items[i].rect, _items[i].data->getData(_items[i].frame), surf, LAYER_FOREGROUND, _items[i].transparentColor);
+ drawGfxObject(_items[i].data, *surf, false);
}
g_system->unlockScreen();
}
void Gfx::drawBalloons() {
- if (_numBalloons == 0) {
+ if (_balloons.size() == 0) {
return;
}
Graphics::Surface *surf = g_system->lockScreen();
- for (uint i = 0; i < _numBalloons; i++) {
- Common::Rect r(_balloons[i].surface.w, _balloons[i].surface.h);
- r.moveTo(_balloons[i].x, _balloons[i].y);
- blt(r, (byte*)_balloons[i].surface.getBasePtr(0, 0), surf, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR);
+ for (uint i = 0; i < _balloons.size(); i++) {
+ drawGfxObject(_balloons[i], *surf, false);
}
g_system->unlockScreen();
}
@@ -380,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");
@@ -427,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);
-
- 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;
+ 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;
+ }
+ 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;
@@ -498,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++) {
@@ -516,146 +517,7 @@ void Gfx::invertBackground(const Common::Rect& r) {
d++;
}
- d += (_backgroundInfo.bg.pitch - r.width());
- }
-
-}
-
-// this is the maximum size of an unpacked frame in BRA
-byte _unpackedBitmap[640*401];
-
-#if 0
-void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
-
- byte *d = _unpackedBitmap;
-
- while (size > 0) {
-
- uint8 p = *data++;
- size--;
- uint8 color = p & 0xF;
- uint8 repeat = (p & 0xF0) >> 4;
- if (repeat == 0) {
- repeat = *data++;
- size--;
- }
-
- memset(d, color, repeat);
- d += repeat;
- }
-
- blt(r, _unpackedBitmap, surf, z, transparentColor);
-}
-#endif
-void Gfx::unpackBlt(const Common::Rect& r, byte *data, uint size, Graphics::Surface *surf, uint16 z, byte transparentColor) {
-
- byte *d = _unpackedBitmap;
- uint pixelsLeftInLine = r.width();
-
- while (size > 0) {
- uint8 p = *data++;
- size--;
- uint8 color = p & 0xF;
- uint8 repeat = (p & 0xF0) >> 4;
- if (repeat == 0) {
- repeat = *data++;
- size--;
- }
- if (repeat == 0) {
- // end of line
- repeat = pixelsLeftInLine;
- pixelsLeftInLine = r.width();
- } else {
- pixelsLeftInLine -= repeat;
- }
-
- memset(d, color, repeat);
- d += repeat;
- }
-
- blt(r, _unpackedBitmap, surf, z, transparentColor);
-}
-
-
-void Gfx::blt(const Common::Rect& r, byte *data, Graphics::Surface *surf, uint16 z, byte transparentColor) {
-
- Common::Point dp;
- Common::Rect q(r);
-
- Common::Rect clipper(surf->w, surf->h);
-
- q.clip(clipper);
- if (!q.isValidRect()) return;
-
- dp.x = q.left;
- dp.y = q.top;
-
- q.translate(-r.left, -r.top);
-
- byte *s = data + q.left + q.top * r.width();
- byte *d = (byte*)surf->getBasePtr(dp.x, dp.y);
-
- uint sPitch = r.width() - q.width();
- uint dPitch = surf->w - q.width();
-
-
- if (_varRenderMode == 2) {
-
- for (uint16 i = 0; i < q.height(); i++) {
-
- 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 (z >= v) *d = 5;
- } else {
- *d = 5;
- }
- }
-
- s++;
- d++;
- }
-
- s += sPitch;
- d += dPitch;
- }
-
- } else {
- 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);
- if (z >= v) *d = *s;
- }
-
- s++;
- d++;
- }
-
- s += sPitch;
- d += dPitch;
- }
-
- } else {
-
- for (uint16 i = q.top; i < q.bottom; i++) {
- for (uint16 j = q.left; j < q.right; j++) {
- if (*s != transparentColor)
- *d = *s;
-
- s++;
- d++;
- }
-
- s += sPitch;
- d += dPitch;
- }
-
- }
+ d += (_backgroundInfo->bg.pitch - r.width());
}
}
@@ -669,10 +531,9 @@ void setupLabelSurface(Graphics::Surface &surf, uint w, uint h) {
surf.fillRect(Common::Rect(w,h), LABEL_TRANSPARENT_COLOR);
}
-Label *Gfx::renderFloatingLabel(Font *font, char *text) {
+uint Gfx::renderFloatingLabel(Font *font, char *text) {
- Label *label = new Label;
- Graphics::Surface *cnv = &label->_cnv;
+ Graphics::Surface *cnv = new Graphics::Surface;
uint w, h;
@@ -698,80 +559,38 @@ Label *Gfx::renderFloatingLabel(Font *font, char *text) {
drawText(font, cnv, 0, 0, text, 0);
}
- return label;
-}
+ GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "floatingLabel");
+ obj->transparentKey = LABEL_TRANSPARENT_COLOR;
+ obj->layer = LAYER_FOREGROUND;
-uint Gfx::createLabel(Font *font, const char *text, byte color) {
- assert(_numLabels < MAX_NUM_LABELS);
-
- Label *label = new Label;
- Graphics::Surface *cnv = &label->_cnv;
-
- uint w, h;
-
- if (_vm->getPlatform() == Common::kPlatformAmiga) {
- w = font->getStringWidth(text) + 2;
- h = font->height() + 2;
-
- setupLabelSurface(*cnv, w, h);
-
- drawText(font, cnv, 0, 2, text, 0);
- drawText(font, cnv, 2, 0, text, color);
- } else {
- w = font->getStringWidth(text);
- h = font->height();
-
- setupLabelSurface(*cnv, w, h);
-
- drawText(font, cnv, 0, 0, text, color);
- }
-
- uint id = _numLabels;
- _labels[id] = label;
- _numLabels++;
+ uint id = _labels.size();
+ _labels.insert_at(id, obj);
return id;
}
-void Gfx::showLabel(uint id, int16 x, int16 y) {
- assert(id < _numLabels);
- _labels[id]->_visible = true;
+void Gfx::showFloatingLabel(uint label) {
+ assert(label < _labels.size());
- if (x == CENTER_LABEL_HORIZONTAL) {
- x = CLIP<int16>((_vm->_screenWidth - _labels[id]->_cnv.w) / 2, 0, _vm->_screenWidth/2);
- }
-
- if (y == CENTER_LABEL_VERTICAL) {
- y = CLIP<int16>((_vm->_screenHeight - _labels[id]->_cnv.h) / 2, 0, _vm->_screenHeight/2);
- }
+ hideFloatingLabel();
- _labels[id]->_pos.x = x;
- _labels[id]->_pos.y = y;
-}
+ _labels[label]->x = -1000;
+ _labels[label]->y = -1000;
+ _labels[label]->setFlags(kGfxObjVisible);
-void Gfx::hideLabel(uint id) {
- assert(id < _numLabels);
- _labels[id]->_visible = false;
+ _floatingLabel = label;
}
-void Gfx::freeLabels() {
- for (uint i = 0; i < _numLabels; i++) {
- delete _labels[i];
+void Gfx::hideFloatingLabel() {
+ if (_floatingLabel != NO_FLOATING_LABEL) {
+ _labels[_floatingLabel]->clearFlags(kGfxObjVisible);
}
- _numLabels = 0;
+ _floatingLabel = NO_FLOATING_LABEL;
}
-void Gfx::setFloatingLabel(Label *label) {
- _floatingLabel = label;
-
- if (_floatingLabel) {
- _floatingLabel->resetPosition();
- }
-}
-
void Gfx::updateFloatingLabel() {
- if (!_floatingLabel) {
+ if (_floatingLabel == NO_FLOATING_LABEL) {
return;
}
@@ -780,113 +599,115 @@ void Gfx::updateFloatingLabel() {
Common::Point cursor;
_vm->_input->getCursorPos(cursor);
+ Common::Rect r;
+ _labels[_floatingLabel]->getRect(0, r);
+
if (_vm->_input->_activeItem._id != 0) {
- _si = cursor.x + 16 - _floatingLabel->_cnv.w/2;
+ _si = cursor.x + 16 - r.width()/2;
_di = cursor.y + 34;
} else {
- _si = cursor.x + 8 - _floatingLabel->_cnv.w/2;
+ _si = cursor.x + 8 - r.width()/2;
_di = cursor.y + 21;
}
if (_si < 0) _si = 0;
if (_di > 190) _di = 190;
- if (_floatingLabel->_cnv.w + _si > _vm->_screenWidth)
- _si = _vm->_screenWidth - _floatingLabel->_cnv.w;
+ if (r.width() + _si > _vm->_screenWidth)
+ _si = _vm->_screenWidth - r.width();
- _floatingLabel->_pos.x = _si;
- _floatingLabel->_pos.y = _di;
+ _labels[_floatingLabel]->x = _si;
+ _labels[_floatingLabel]->y = _di;
}
-void Gfx::drawLabels() {
- if ((!_floatingLabel) && (_numLabels == 0)) {
- return;
- }
- updateFloatingLabel();
- Graphics::Surface* surf = g_system->lockScreen();
- for (uint i = 0; i < _numLabels; i++) {
- if (_labels[i]->_visible) {
- Common::Rect r(_labels[i]->_cnv.w, _labels[i]->_cnv.h);
- r.moveTo(_labels[i]->_pos);
- blt(r, (byte*)_labels[i]->_cnv.getBasePtr(0, 0), surf, LAYER_FOREGROUND, LABEL_TRANSPARENT_COLOR);
- }
- }
- if (_floatingLabel) {
- Common::Rect r(_floatingLabel->_cnv.w, _floatingLabel->_cnv.h);
- r.moveTo(_floatingLabel->_pos);
- blt(r, (byte*)_floatingLabel->_cnv.getBasePtr(0, 0), surf, LAYER_FOREGROUND, LABEL_TRANSPARENT_COLOR);
- }
+uint Gfx::createLabel(Font *font, const char *text, byte color) {
+ assert(_labels.size() < MAX_NUM_LABELS);
- g_system->unlockScreen();
-}
+ Graphics::Surface *cnv = new Graphics::Surface;
-Label::Label() {
- resetPosition();
- _visible = false;
-}
+ uint w, h;
-Label::~Label() {
- free();
-}
+ if (_vm->getPlatform() == Common::kPlatformAmiga) {
+ w = font->getStringWidth(text) + 2;
+ h = font->height() + 2;
-void Label::free() {
- _cnv.free();
- resetPosition();
-}
+ setupLabelSurface(*cnv, w, h);
-void Label::resetPosition() {
- _pos.x = -1000;
- _pos.y = -1000;
-}
+ drawText(font, cnv, 0, 2, text, 0);
+ drawText(font, cnv, 2, 0, text, color);
+ } else {
+ w = font->getStringWidth(text);
+ h = font->height();
+ setupLabelSurface(*cnv, w, h);
-void Gfx::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) {
+ drawText(font, cnv, 0, 0, text, color);
+ }
- uint16 lines = 0;
- uint16 w = 0;
- *width = 0;
+ GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "label");
+ obj->transparentKey = LABEL_TRANSPARENT_COLOR;
+ obj->layer = LAYER_FOREGROUND;
- uint16 blankWidth = font->getStringWidth(" ");
- uint16 tokenWidth = 0;
+ int id = _labels.size();
- char token[MAX_TOKEN_LEN];
+ _labels.insert_at(id, obj);
- while (strlen(text) != 0) {
+ return id;
+}
- text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true);
- tokenWidth = font->getStringWidth(token);
+void Gfx::showLabel(uint id, int16 x, int16 y) {
+ assert(id < _labels.size());
+ _labels[id]->setFlags(kGfxObjVisible);
- w += tokenWidth;
+ Common::Rect r;
+ _labels[id]->getRect(0, r);
- if (!scumm_stricmp(token, "%p")) {
- lines++;
- } else {
- if (w > maxwidth) {
- w -= tokenWidth;
- lines++;
- if (w > *width)
- *width = w;
+ if (x == CENTER_LABEL_HORIZONTAL) {
+ x = CLIP<int16>((_vm->_screenWidth - r.width()) / 2, 0, _vm->_screenWidth/2);
+ }
- w = tokenWidth;
- }
- }
+ if (y == CENTER_LABEL_VERTICAL) {
+ y = CLIP<int16>((_vm->_screenHeight - r.height()) / 2, 0, _vm->_screenHeight/2);
+ }
- w += blankWidth;
- text = Common::ltrim(text);
+ _labels[id]->x = x;
+ _labels[id]->y = y;
+}
+
+void Gfx::hideLabel(uint id) {
+ assert(id < _labels.size());
+ _labels[id]->clearFlags(kGfxObjVisible);
+}
+
+void Gfx::freeLabels() {
+ for (uint i = 0; i < _labels.size(); i++) {
+ delete _labels[i];
}
+ _labels.clear();
+ _floatingLabel = NO_FLOATING_LABEL;
+}
- if (*width < w) *width = w;
- *width += 10;
+void Gfx::drawLabels() {
+ if (_labels.size() == 0) {
+ return;
+ }
- *height = lines * 10 + 20;
+ updateFloatingLabel();
- return;
+ Graphics::Surface* surf = g_system->lockScreen();
+
+ for (uint i = 0; i < _labels.size(); i++) {
+ drawGfxObject(_labels[i], *surf, false);
+ }
+
+ g_system->unlockScreen();
}
+
void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) {
byte *s = (byte*)src.getBasePtr(r.left, r.top);
@@ -903,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);
}
@@ -917,17 +738,20 @@ Gfx::Gfx(Parallaction* vm) :
setPalette(_palette);
- _numBalloons = 0;
_numItems = 0;
- _numLabels = 0;
- _floatingLabel = 0;
+ _floatingLabel = NO_FLOATING_LABEL;
_screenX = 0;
_screenY = 0;
+ _backgroundInfo = 0;
+
_halfbrite = false;
_hbCircleRadius = 0;
+ _unpackedBitmap = new byte[MAXIMUM_UNPACKED_BITMAP_SIZE];
+ assert(_unpackedBitmap);
+
registerVar("background_mode", 1);
_varBackgroundMode = 1;
@@ -937,26 +761,39 @@ 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;
return;
}
-int Gfx::setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor) {
+int Gfx::setItem(GfxObj* frames, uint16 x, uint16 y, byte transparentColor) {
int id = _numItems;
_items[id].data = frames;
- _items[id].x = x;
- _items[id].y = y;
-
- _items[id].transparentColor = transparentColor;
+ _items[id].data->x = x;
+ _items[id].data->y = y;
+ _items[id].data->layer = LAYER_FOREGROUND;
+ _items[id].data->transparentKey = transparentColor;
_numItems++;
@@ -965,223 +802,58 @@ int Gfx::setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor) {
void Gfx::setItemFrame(uint item, uint16 f) {
assert(item < _numItems);
- _items[item].frame = f;
- _items[item].data->getRect(f, _items[item].rect);
- _items[item].rect.moveTo(_items[item].x, _items[item].y);
-}
-
-Gfx::Balloon* Gfx::getBalloon(uint id) {
- assert(id < _numBalloons);
- return &_balloons[id];
-}
-
-int Gfx::createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness) {
- assert(_numBalloons < 5);
-
- int id = _numBalloons;
-
- Gfx::Balloon *balloon = &_balloons[id];
-
- int16 real_h = (winding == -1) ? h : h + 9;
- balloon->surface.create(w, real_h, 1);
- balloon->surface.fillRect(Common::Rect(w, real_h), BALLOON_TRANSPARENT_COLOR);
-
- Common::Rect r(w, h);
- balloon->surface.fillRect(r, 0);
- balloon->outerBox = r;
-
- r.grow(-borderThickness);
- balloon->surface.fillRect(r, 1);
- balloon->innerBox = r;
-
- if (winding != -1) {
- // draws tail
- // TODO: this bitmap tail should only be used for Dos games. Amiga should use a polygon fill.
- winding = (winding == 0 ? 1 : 0);
- Common::Rect s(BALLOON_TAIL_WIDTH, BALLOON_TAIL_HEIGHT);
- s.moveTo(r.width()/2 - 5, r.bottom - 1);
- blt(s, _resBalloonTail[winding], &balloon->surface, LAYER_FOREGROUND, BALLOON_TRANSPARENT_COLOR);
- }
-
- _numBalloons++;
-
- return id;
-}
-
-int Gfx::setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) {
-
- int16 w, h;
-
- getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
-
- int id = createBalloon(w+5, h, winding, 1);
- Gfx::Balloon *balloon = &_balloons[id];
-
- drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
-
- balloon->x = x;
- balloon->y = y;
-
- return id;
-}
-
-int Gfx::setDialogueBalloon(char *text, uint16 winding, byte textColor) {
-
- int16 w, h;
-
- getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
-
- int id = createBalloon(w+5, h, winding, 1);
- Gfx::Balloon *balloon = &_balloons[id];
-
- drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
-
- balloon->x = _dialogueBalloonX[id];
- balloon->y = 10;
-
- if (id > 0) {
- balloon->y += _balloons[id - 1].y + _balloons[id - 1].outerBox.height();
- }
-
-
- return id;
+ _items[item].data->frame = f;
+ _items[item].data->setFlags(kGfxObjVisible);
}
-void Gfx::setBalloonText(uint id, char *text, byte textColor) {
- Gfx::Balloon *balloon = getBalloon(id);
- balloon->surface.fillRect(balloon->innerBox, 1);
- drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, textColor, MAX_BALLOON_WIDTH);
-}
+GfxObj* Gfx::registerBalloon(Frames *frames, const char *text) {
-int Gfx::setLocationBalloon(char *text, bool endGame) {
+ GfxObj *obj = new GfxObj(kGfxObjTypeBalloon, frames, text);
- int16 w, h;
+ obj->layer = LAYER_FOREGROUND;
+ obj->frame = 0;
+ obj->setFlags(kGfxObjVisible);
- getStringExtent(_vm->_dialogueFont, text, MAX_BALLOON_WIDTH, &w, &h);
+ _balloons.push_back(obj);
- int id = createBalloon(w+(endGame ? 5 : 10), h+5, -1, BALLOON_TRANSPARENT_COLOR);
- Gfx::Balloon *balloon = &_balloons[id];
- drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, 0, MAX_BALLOON_WIDTH);
-
- balloon->x = 5;
- balloon->y = 5;
-
- return id;
+ return obj;
}
-int Gfx::hitTestDialogueBalloon(int x, int y) {
-
- Common::Point p;
-
- for (uint i = 0; i < _numBalloons; i++) {
- p.x = x - _balloons[i].x;
- p.y = y - _balloons[i].y;
-
- if (_balloons[i].innerBox.contains(p))
- return i;
+void Gfx::destroyBalloons() {
+ for (uint i = 0; i < _balloons.size(); i++) {
+ delete _balloons[i];
}
-
- return -1;
-}
-
-
-void Gfx::freeBalloons() {
- for (uint i = 0; i < _numBalloons; i++) {
- _balloons[i].surface.free();
- }
- _numBalloons = 0;
+ _balloons.clear();
}
void Gfx::freeItems() {
_numItems = 0;
}
-void Gfx::hideDialogueStuff() {
- freeItems();
- freeBalloons();
-}
-
-void Gfx::drawText(Font *font, Graphics::Surface* surf, uint16 x, uint16 y, const char *text, byte color) {
- byte *dst = (byte*)surf->getBasePtr(x, y);
- font->setColor(color);
- 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;
+void Gfx::setBackground(uint type, BackgroundInfo *info) {
+ delete _backgroundInfo;
+ _backgroundInfo = info;
- 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);
+ if (type == kBackgroundLocation) {
+ // 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);
}
-
}
- drawText(font, surf, rx, ry, token, color);
-
- rx += tokenWidth + blankWidth;
- linewidth += blankWidth;
-
- text = Common::ltrim(text);
- }
-
-}
-
-void Gfx::freeBackground() {
- _backgroundInfo.free();
-}
-
-void Gfx::setBackground(uint type, const char* name, const char* mask, const char* path) {
-
- freeBackground();
-
- if (type == kBackgroundLocation) {
- _disk->loadScenery(_backgroundInfo, name, mask, path);
- setPalette(_backgroundInfo.palette);
- _palette.clone(_backgroundInfo.palette);
+ 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 894e0fd678..23b4569c6a 100644
--- a/engines/parallaction/graphics.h
+++ b/engines/parallaction/graphics.h
@@ -95,6 +95,7 @@ public:
}
~SurfaceToFrames() {
+ _surf->free();
delete _surf;
}
@@ -156,11 +157,11 @@ struct SurfaceToMultiFrames : public Frames {
r.setHeight(_height);
}
uint getRawSize(uint16 index) {
- assert(index == 0);
+ assert(index < _num);
return getSize(index);
}
uint getSize(uint16 index) {
- assert(index == 0);
+ assert(index < _num);
return _width * _height;
}
@@ -260,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);
@@ -325,20 +327,6 @@ public:
#define CENTER_LABEL_HORIZONTAL -1
#define CENTER_LABEL_VERTICAL -1
-struct Label {
- Graphics::Surface _cnv;
-
- Common::Point _pos;
- bool _visible;
-
- Label();
- ~Label();
-
- void free();
- void resetPosition();
-};
-
-
#define MAX_BALLOON_WIDTH 130
@@ -353,25 +341,39 @@ class Disk;
enum {
kGfxObjVisible = 1,
+ kGfxObjNormal = 2,
+ kGfxObjCharacter = 4,
kGfxObjTypeDoor = 0,
kGfxObjTypeGet = 1,
- kGfxObjTypeAnim = 2
+ kGfxObjTypeAnim = 2,
+ kGfxObjTypeLabel = 3,
+ kGfxObjTypeBalloon = 4,
+ kGfxObjTypeCharacter = 8
+};
+
+enum {
+ kGfxObjDoorZ = -200,
+ kGfxObjGetZ = -100
};
class GfxObj {
char *_name;
Frames *_frames;
- uint32 _flags;
bool _keep;
public:
int16 x, y;
- uint16 z;
+
+ int32 z;
+
+ uint32 _flags;
+
uint type;
uint frame;
uint layer;
+ uint transparentKey;
GfxObj(uint type, Frames *frames, const char *name = NULL);
virtual ~GfxObj();
@@ -434,7 +436,7 @@ struct BackgroundInfo {
return LAYER_FOREGROUND;
}
- void free() {
+ ~BackgroundInfo() {
bg.free();
mask.free();
path.free();
@@ -452,49 +454,65 @@ enum {
kBackgroundSlide = 2
};
+
+class BalloonManager {
+public:
+ virtual ~BalloonManager() { }
+
+ virtual void freeBalloons() = 0;
+ virtual int setLocationBalloon(char *text, bool endGame) = 0;
+ virtual int setDialogueBalloon(char *text, uint16 winding, byte textColor) = 0;
+ virtual int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor) = 0;
+ virtual void setBalloonText(uint id, char *text, byte textColor) = 0;
+ virtual int hitTestDialogueBalloon(int x, int y) = 0;
+};
+
+
typedef Common::HashMap<Common::String, int32, Common::IgnoreCase_Hash, Common::IgnoreCase_EqualTo> VarMap;
class Gfx {
+protected:
+ Parallaction* _vm;
+
public:
Disk *_disk;
VarMap _vars;
- GfxObjList _gfxobjList[3];
+ GfxObjList _gfxobjList;
GfxObj* loadAnim(const char *name);
GfxObj* loadGet(const char *name);
GfxObj* loadDoor(const char *name);
void drawGfxObjects(Graphics::Surface &surf);
void showGfxObj(GfxObj* obj, bool visible);
- void clearGfxObjects();
+ void clearGfxObjects(uint filter);
void sortAnimations();
+
// labels
- void setFloatingLabel(Label *label);
- Label *renderFloatingLabel(Font *font, char *text);
+ void showFloatingLabel(uint label);
+ void hideFloatingLabel();
+
+ uint renderFloatingLabel(Font *font, char *text);
uint createLabel(Font *font, const char *text, byte color);
void showLabel(uint id, int16 x, int16 y);
void hideLabel(uint id);
void freeLabels();
// dialogue balloons
- int setLocationBalloon(char *text, bool endGame);
- int setDialogueBalloon(char *text, uint16 winding, byte textColor);
- int setSingleBalloon(char *text, uint16 x, uint16 y, uint16 winding, byte textColor);
- void setBalloonText(uint id, char *text, byte textColor);
- int hitTestDialogueBalloon(int x, int y);
- void getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height);
+ GfxObj* registerBalloon(Frames *frames, const char *text);
+ void destroyBalloons();
// other items
- int setItem(Frames* frames, uint16 x, uint16 y, byte transparentColor = 0);
+ int setItem(GfxObj* obj, uint16 x, uint16 y, byte transparentColor = 0);
void setItemFrame(uint item, uint16 f);
void hideDialogueStuff();
void freeBalloons();
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);
@@ -532,52 +550,45 @@ public:
uint _screenX; // scrolling position
uint _screenY;
+ 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);
-protected:
- static int16 _dialogueBalloonX[5];
-
- struct Balloon {
- uint16 x;
- uint16 y;
- Common::Rect outerBox;
- Common::Rect innerBox;
- uint16 winding;
- Graphics::Surface surface;
- } _balloons[5];
-
- uint _numBalloons;
+public:
struct Item {
- uint16 x;
- uint16 y;
- uint16 frame;
- Frames *data;
-
- byte transparentColor;
- Common::Rect rect;
+ GfxObj *data;
} _items[14];
uint _numItems;
- #define MAX_NUM_LABELS 5
- Label* _labels[MAX_NUM_LABELS];
- uint _numLabels;
- Label *_floatingLabel;
+ #define MAX_NUM_LABELS 20
+ #define NO_FLOATING_LABEL 1000
+
+ typedef Common::Array<GfxObj*> GfxObjArray;
+ GfxObjArray _labels;
+ GfxObjArray _balloons;
+
+ uint _floatingLabel;
void drawInventory();
void updateFloatingLabel();
@@ -587,13 +598,10 @@ protected:
void copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst);
- int createBalloon(int16 w, int16 h, int16 winding, uint16 borderThickness);
- Balloon *getBalloon(uint id);
-
// 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);
void unpackBlt(const Common::Rect& r, byte *data, uint size, 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 c515299a34..3315433762 100644
--- a/engines/parallaction/gui_br.cpp
+++ b/engines/parallaction/gui_br.cpp
@@ -25,184 +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:
- _engineFlags |= kEngineQuit;
- 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;
+ }
-int Parallaction_br::guiShowMenu() {
- // TODO: filter menu entries according to progress in game
+ // wrap the surface into the suitable Frames adapter
+ return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf);
+ }
- #define NUM_MENULINES 7
- Frames *_lines[NUM_MENULINES];
-
- const char *menuStrings[NUM_MENULINES] = {
- "SEE INTRO",
- "NEW GAME",
- "SAVED GAME",
- "EXIT TO DOS",
- "PART 2",
- "PART 3",
- "PART 4"
+ enum MenuOptions {
+ kMenuPart0 = 0,
+ kMenuPart1 = 1,
+ kMenuPart2 = 2,
+ kMenuPart3 = 3,
+ kMenuPart4 = 4,
+ kMenuLoadGame = 5,
+ kMenuQuit = 6
};
- MenuOptions options[NUM_MENULINES] = {
- kMenuPart0,
- kMenuPart1,
- kMenuLoadGame,
- kMenuQuit,
- kMenuPart2,
- kMenuPart3,
- kMenuPart4
- };
+ #define NUM_MENULINES 7
+ GfxObj *_lines[NUM_MENULINES];
- _gfx->clearScreen();
- _gfx->setBackground(kBackgroundSlide, "tbra", 0, 0);
- if (getPlatform() == Common::kPlatformPC) {
- _gfx->_backgroundInfo.x = 20;
- _gfx->_backgroundInfo.y = 50;
- }
+ static const char *_menuStrings[NUM_MENULINES];
+ static const MenuOptions _options[NUM_MENULINES];
- int availItems = 4 + _progress;
+ int _availItems;
+ int _selection;
- // TODO: keep track of and destroy menu item frames/surfaces
+ void cleanup() {
+ _vm->_system->showMouse(false);
+ _vm->hideDialogueStuff();
- int i;
- for (i = 0; i < availItems; i++) {
- _lines[i] = guiRenderMenuItem(menuStrings[i]);
- uint id = _gfx->setItem(_lines[i], MENUITEMS_X, MENUITEMS_Y + MENUITEM_HEIGHT * i, 0xFF);
- _gfx->setItemFrame(id, 0);
+ for (int i = 0; i < _availItems; i++) {
+ delete _lines[i];
+ }
}
- int selectedItem = -1;
+ void performChoice(int selectedItem) {
+ switch (selectedItem) {
+ case kMenuQuit:
+ _engineFlags |= kEngineQuit;
+ break;
- setMousePointer(0);
+ case kMenuLoadGame:
+ warning("loadgame not yet implemented");
+ break;
- uint32 event;
- Common::Point p;
- while (true) {
+ default:
+ _vm->startPart(selectedItem);
+ }
+ }
- _input->readInput();
+public:
+ MainMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("mainmenu", helper), _vm(vm) {
+ }
- event = _input->getLastButtonEvent();
- if ((event == kMouseLeftUp) && selectedItem >= 0)
- break;
+ virtual MenuInputState* run() {
- _input->getCursorPos(p);
+ int event = _vm->_input->getLastButtonEvent();
+ if ((event == kMouseLeftUp) && _selection >= 0) {
+ cleanup();
+ performChoice(_options[_selection]);
+ return 0;
+ }
+
+ 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);
- _gfx->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 1d4d44fa46..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,311 +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) {
+ }
-const char *newGameMsg[] = {
- "NUOVO GIOCO",
- "NEUF JEU",
- "NEW GAME",
- "NEUES SPIEL"
+ 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 *loadGameMsg[] = {
- "GIOCO SALVATO",
- "JEU SAUVE'",
- "SAVED GAME",
- "SPIEL GESPEICHERT"
+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";
+ }
};
+class SplashInputState1_NS : public SplashInputState_NS {
-#define BLOCK_WIDTH 16
-#define BLOCK_HEIGHT 24
+public:
+ SplashInputState1_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro1", helper) {
+ _slideName = "minintro";
+ _timeOut = 2000;
+ _nextState = "chooselanguage";
+ }
+};
-#define BLOCK_X 112
-#define BLOCK_Y 130
-#define BLOCK_SELECTION_X (BLOCK_X-1)
-#define BLOCK_SELECTION_Y (BLOCK_Y-1)
+class ChooseLanguageInputState_NS : public MenuInputState {
+ #define BLOCK_WIDTH 16
+ #define BLOCK_HEIGHT 24
-#define BLOCK_X_OFFSET (BLOCK_WIDTH+1)
-#define BLOCK_Y_OFFSET 9
+ #define BLOCK_X 112
+ #define BLOCK_Y 130
-// destination slots for code blocks
-//
-#define SLOT_X 61
-#define SLOT_Y 64
-#define SLOT_WIDTH (BLOCK_WIDTH+2)
+ #define BLOCK_SELECTION_X (BLOCK_X-1)
+ #define BLOCK_SELECTION_Y (BLOCK_Y-1)
-#define PASSWORD_LEN 6
+ #define BLOCK_X_OFFSET (BLOCK_WIDTH+1)
+ #define BLOCK_Y_OFFSET 9
-#define CHAR_DINO 0
-#define CHAR_DONNA 1
-#define CHAR_DOUGH 2
+ // destination slots for code blocks
+ //
+ #define SLOT_X 61
+ #define SLOT_Y 64
+ #define SLOT_WIDTH (BLOCK_WIDTH+2)
-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
-};
+ int _language;
+ bool _allowChoice;
+ Common::String _nextState;
-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
-};
+ static const Common::Rect _dosLanguageSelectBlocks[4];
+ static const Common::Rect _amigaLanguageSelectBlocks[4];
+ const Common::Rect *_blocks;
-static const char *_charStartLocation[] = {
- "test.dino",
- "test.donna",
- "test.dough"
-};
+ Parallaction_ns *_vm;
-enum {
- NEW_GAME,
- LOAD_GAME
-};
+public:
+ ChooseLanguageInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("chooselanguage", helper), _vm(vm) {
+ _allowChoice = false;
+ _nextState = "selectgame";
-enum {
- START_DEMO,
- START_INTRO,
- GAME_LOADED,
- SELECT_CHARACTER
-};
+ 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;
+ }
+ }
-void Parallaction_ns::guiStart() {
+ _blocks = _amigaLanguageSelectBlocks;
+ } else {
+ _blocks = _dosLanguageSelectBlocks;
+ }
- _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
+ _language = -1;
+ _allowChoice = true;
+ }
- guiSplash();
+ virtual MenuInputState* run() {
+ if (!_allowChoice) {
+ _vm->setInternLanguage(_language);
+ return _helper->getState(_nextState);
+ }
- _language = guiChooseLanguage();
- _disk->setLanguage(_language);
+ int event = _vm->_input->getLastButtonEvent();
+ if (event != kMouseLeftUp) {
+ return this;
+ }
- int event;
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
- if (getFeatures() & GF_DEMO) {
- event = START_DEMO;
- } else {
- if (guiSelectGame() == NEW_GAME) {
- event = guiNewGame();
- } else {
- event = loadGame() ? GAME_LOADED : START_INTRO;
+ 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;
}
- switch (event) {
- case START_DEMO:
- strcpy(_location._name, "fognedemo.dough");
- break;
+ virtual void enter() {
+ if (!_allowChoice) {
+ return;
+ }
- case START_INTRO:
- strcpy(_location._name, "fogne.dough");
- break;
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
- case GAME_LOADED:
- // nothing to do here
- return;
+ // user can choose language in this version
+ _vm->showSlide("lingua");
- case SELECT_CHARACTER:
- selectStartLocation();
- break;
+ uint id = _vm->_gfx->createLabel(_vm->_introFont, "SELECT LANGUAGE", 1);
+ _vm->_gfx->showLabel(id, 60, 30);
+ _vm->setArrowCursor();
}
+};
- return;
-}
+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
+};
-void Parallaction_ns::selectStartLocation() {
- int character = guiSelectCharacter();
- if (character == -1)
- error("invalid character selected from menu screen");
+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
+};
- scheduleLocationSwitch(_charStartLocation[character]);
-}
+class SelectGameInputState_NS : public MenuInputState {
+ int _choice, _oldChoice;
+ Common::String _nextState[2];
-void Parallaction_ns::guiSplash() {
+ uint _labels[2];
- showSlide("intro");
- _gfx->updateScreen();
- g_system->delayMillis(2000);
- freeBackground();
+ Parallaction_ns *_vm;
- showSlide("minintro");
- _gfx->updateScreen();
- g_system->delayMillis(2000);
- freeBackground();
-}
+ static const char *newGameMsg[4];
+ static const char *loadGameMsg[4];
-int Parallaction_ns::guiNewGame() {
+public:
+ SelectGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectgame", helper), _vm(vm) {
+ _choice = 0;
+ _oldChoice = -1;
- const char **v14 = introMsg3;
+ _nextState[0] = "newgame";
+ _nextState[1] = "loadgame";
+ }
- _disk->selectArchive("disk1");
- setBackground("test", NULL, NULL);
+ virtual MenuInputState *run() {
+ int event = _vm->_input->getLastButtonEvent();
- _gfx->updateScreen();
+ if (event == kMouseLeftUp) {
+ _vm->_gfx->freeLabels();
+ return _helper->getState(_nextState[_choice]);
+ }
- 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);
+ Common::Point p;
+ _vm->_input->getCursorPos(p);
+ _choice = (p.x > 160) ? 1 : 0;
- _input->showCursor(false);
+ if (_choice != _oldChoice) {
+ if (_oldChoice != -1)
+ _vm->_gfx->hideLabel(_labels[_oldChoice]);
- _gfx->updateScreen();
+ if (_choice != -1)
+ _vm->_gfx->showLabel(_labels[_choice], 60, 30);
- _input->waitForButtonEvent(kMouseLeftUp | kMouseRightUp);
- uint32 event = _input->getLastButtonEvent();
+ _oldChoice = _choice;
+ }
- _input->showCursor(true);
+ return this;
+ }
- _gfx->freeLabels();
+ virtual void enter() {
+ _vm->showSlide("restore");
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
- if (event != kMouseRightUp) {
- return START_INTRO;
+ _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, newGameMsg[_vm->getInternLanguage()], 1);
+ _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, loadGameMsg[_vm->getInternLanguage()], 1);
}
- return SELECT_CHARACTER;
-}
+};
-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
+const char *SelectGameInputState_NS::newGameMsg[4] = {
+ "NUOVO GIOCO",
+ "NEUF JEU",
+ "NEW GAME",
+ "NEUES SPIEL"
};
-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 *SelectGameInputState_NS::loadGameMsg[4] = {
+ "GIOCO SALVATO",
+ "JEU SAUVE'",
+ "SAVED GAME",
+ "SPIEL GESPEICHERT"
};
-uint16 Parallaction_ns::guiChooseLanguage() {
- const Common::Rect *blocks;
+class LoadGameInputState_NS : public MenuInputState {
+ bool _result;
+ Parallaction_ns *_vm;
+
+public:
+ LoadGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("loadgame", helper), _vm(vm) { }
+
+ virtual MenuInputState* run() {
+ if (!_result) {
+ _vm->scheduleLocationSwitch("fogne.dough");
+ }
+ return 0;
+ }
+
+ virtual void enter() {
+ _result = _vm->loadGame();
+ }
+};
+
+
- 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
+class NewGameInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+
+ static const char *introMsg3[4];
+
+public:
+ NewGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("newgame", helper), _vm(vm) {
+ }
+
+ virtual MenuInputState* run() {
+ int event = _vm->_input->getLastButtonEvent();
+
+ if (event == kMouseLeftUp || event == kMouseRightUp) {
+ _vm->_input->setMouseState(MOUSE_ENABLED_SHOW);
+ _vm->_gfx->freeLabels();
+
+ if (event == kMouseLeftUp) {
+ _vm->scheduleLocationSwitch("fogne.dough");
+ return 0;
}
+
+ return _helper->getState("selectcharacter");
}
- blocks = _amigaLanguageSelectBlocks;
- } else {
- blocks = _dosLanguageSelectBlocks;
+ return this;
+ }
+
+ 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);
+ }
+};
+
+const char *NewGameInputState_NS::introMsg3[4] = {
+ "PRESS LEFT MOUSE BUTTON",
+ "TO SEE INTRO",
+ "PRESS RIGHT MOUSE BUTTON",
+ "TO START"
+};
+
+
+
+class StartDemoInputState_NS : public MenuInputState {
+ Parallaction_ns *_vm;
+
+public:
+ StartDemoInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("startdemo", helper), _vm(vm) {
}
- // user can choose language in dos version
- showSlide("lingua");
+ virtual MenuInputState* run() {
+ _vm->scheduleLocationSwitch("fognedemo.dough");
+ return 0;
+ }
- uint id = _gfx->createLabel(_introFont, "SELECT LANGUAGE", 1);
- _gfx->showLabel(id, 60, 30);
+ virtual void enter() {
+ _vm->_input->setMouseState(MOUSE_DISABLED);
+ }
+};
- setArrowCursor();
+class SelectCharacterInputState_NS : public MenuInputState {
- Common::Point p;
+ #define PASSWORD_LEN 6
- int selection = -1;
- while (selection == -1) {
- _input->waitUntilLeftClick();
- _input->getCursorPos(p);
- for (uint16 i = 0; i < 4; i++) {
- if (blocks[i].contains(p)) {
+ #define CHAR_DINO 0
+ #define CHAR_DONNA 1
+ #define CHAR_DOUGH 2
+
+ 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++;
+ }
+
+ if (_len == PASSWORD_LEN) {
+ _state = _fail ? FAIL : SUCCESS;
}
+ }
- _gfx->updateScreen();
- g_system->delayMillis(30);
+ 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;
}
- _gfx->freeLabels();
+ 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).");
+ }
- return _si ? LOAD_GAME : NEW_GAME;
-}
+ _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");
-static const Common::Rect codeSelectBlocks[9] = {
+ _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"
+};
+
+
+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
@@ -348,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 ),
@@ -361,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 28d0ad888d..287618e803 100644
--- a/engines/parallaction/input.cpp
+++ b/engines/parallaction/input.cpp
@@ -36,23 +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;
+ _hasKeyPressEvent = false;
Common::EventManager *eventMan = _vm->_system->getEventManager();
while (eventMan->pollEvent(e)) {
switch (e.type) {
case Common::EVENT_KEYDOWN:
+ _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:
@@ -80,11 +80,8 @@ uint16 Input::readInput() {
break;
case Common::EVENT_QUIT:
- // TODO: don't quit() here, just have caller routines to check
- // on kEngineQuit and exit gracefully to allow the engine to shut down
_engineFlags |= kEngineQuit;
- _vm->_system->quit();
- break;
+ return;
default:
break;
@@ -96,10 +93,15 @@ uint16 Input::readInput() {
if (_vm->_debugger->isAttached())
_vm->_debugger->onFrame();
- return KeyDown;
+ return;
}
+bool Input::getLastKeyDown(uint16 &ascii) {
+ ascii = _keyPressed.ascii;
+ return (_hasKeyPressEvent);
+}
+
// FIXME: see comment for readInput()
void Input::waitForButtonEvent(uint32 buttonEventMask, int32 timeout) {
@@ -124,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();
-
- debugC(3, kDebugInput, "translateInput: input flags (%i, %i, %i, %i)",
- !_mouseHidden,
- (_engineFlags & kEngineBlockInput) == 0,
- (_engineFlags & kEngineWalking) == 0,
- (_engineFlags & kEngineChangeLocation) == 0
- );
+ readInput();
- 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->_gfx->hideDialogueStuff();
- _vm->_gfx->setHalfbriteMode(false);
-
- _inputMode = kInputModeGame;
-}
InputData* Input::updateInput() {
@@ -189,84 +163,109 @@ InputData* Input::updateInput() {
switch (_inputMode) {
case kInputModeComment:
- updateCommentInput();
+ case kInputModeDialogue:
+ case kInputModeMenu:
+ readInput();
break;
case kInputModeGame:
updateGameInput();
break;
+
+ case kInputModeInventory:
+ readInput();
+ updateInventoryInput();
+ break;
}
return &_inputData;
}
+void Input::trackMouse(ZonePtr z) {
+ if ((z != _hoverZone) && (_hoverZone)) {
+ stopHovering();
+ return;
+ }
+
+ if (!z) {
+ return;
+ }
+
+ if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) {
+ _hoverZone = z;
+ _vm->_gfx->showFloatingLabel(_hoverZone->_label);
+ return;
+ }
+}
+
+void Input::stopHovering() {
+ _hoverZone = nullZonePtr;
+ _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;
}
- if ((z != _hoverZone) && (_hoverZone)) {
- _hoverZone = nullZonePtr;
- _inputData._event = kEvExitZone;
- return true;
- }
-
- if (!z) {
- _inputData._event = kEvNone;
- return true;
- }
-
- if ((!_hoverZone) && ((z->_flags & kFlagsNoName) == 0)) {
- _hoverZone = z;
- _inputData._event = kEvEnterZone;
- _inputData._label = z->_label;
- return true;
- }
+ trackMouse(z);
+ if (!z) {
+ return true;
+ }
if ((_mouseButtons == kMouseLeftUp) && ((_activeItem._id != 0) || ((z->_type & 0xFFFF) == kZoneCommand))) {
_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();
@@ -275,58 +274,103 @@ 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);
_vm->dropItem(z->u.merge->_obj2);
_vm->addInventoryItem(z->u.merge->_obj3);
- _vm->runCommands(z->_commands);
+ _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 46dbb08c4e..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"
@@ -44,52 +46,68 @@ struct InputData {
Common::Point _mousePos;
int16 _inventoryIndex;
ZonePtr _zone;
- Label* _label;
+ 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;
- bool _mouseHidden;
ZonePtr _hoverZone;
+ void enterInventoryMode();
+ void exitInventoryMode();
+
public:
enum {
kInputModeGame = 0,
- kInputModeComment = 1
+ kInputModeComment = 1,
+ 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;
}
@@ -97,16 +115,20 @@ public:
int _inputMode;
InventoryItem _activeItem;
- uint16 readInput();
+ void readInput();
InputData* updateInput();
- void waitUntilLeftClick();
+ void trackMouse(ZonePtr z);
void waitForButtonEvent(uint32 buttonEventMask, int32 timeout = -1);
uint32 getLastButtonEvent() { return _mouseButtons; }
+ bool getLastKeyDown(uint16 &ascii);
- void stopHovering() {
- _hoverZone = nullZonePtr;
- }
+ 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 2478b4b2e1..9d44422541 100644
--- a/engines/parallaction/module.mk
+++ b/engines/parallaction/module.mk
@@ -1,6 +1,7 @@
MODULE := engines/parallaction
MODULE_OBJS := \
+ balloons.o \
callables_br.o \
callables_ns.o \
debug.o \
@@ -13,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 cac31911f4..c387484de7 100644
--- a/engines/parallaction/objects.cpp
+++ b/engines/parallaction/objects.cpp
@@ -54,19 +54,20 @@ Animation::Animation() {
Animation::~Animation() {
free(_scriptName);
+ gfxobj->release();
}
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();
}
@@ -80,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];
@@ -182,7 +189,8 @@ Zone::~Zone() {
break;
}
- delete _label;
+
+ free(_linkedName);
}
void Zone::getRect(Common::Rect& r) const {
@@ -207,6 +215,16 @@ uint16 Zone::height() const {
return _bottom - _top;
}
+Dialogue::Dialogue() {
+ memset(_questions, 0, sizeof(_questions));
+}
+
+Dialogue::~Dialogue() {
+ for (int i = 0; i < NUM_QUESTIONS; i++) {
+ delete _questions[i];
+ }
+}
+
Answer::Answer() {
_text = NULL;
_mood = 0;
diff --git a/engines/parallaction/objects.h b/engines/parallaction/objects.h
index c2c2c154b5..a3bf757bdb 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
};
@@ -181,6 +187,9 @@ struct Question {
struct Dialogue {
Question *_questions[NUM_QUESTIONS];
+
+ Dialogue();
+ ~Dialogue();
};
struct GetData { // size = 24
@@ -206,7 +215,7 @@ struct SpeakData { // size = 36
}
};
struct ExamineData { // size = 28
- Frames *_cnv;
+ GfxObj *_cnv;
uint16 _opBase; // unused
uint16 field_12; // unused
char* _description;
@@ -253,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;
@@ -261,6 +279,8 @@ struct TypeData {
DoorData *door;
HearData *hear;
MergeData *merge;
+ // BRA specific field
+ PathData *path;
TypeData() {
get = NULL;
@@ -269,6 +289,7 @@ struct TypeData {
door = NULL;
hear = NULL;
merge = NULL;
+ path = NULL;
}
};
@@ -284,7 +305,7 @@ struct Zone {
int16 _bottom;
uint32 _type;
uint32 _flags;
- Label *_label;
+ uint _label;
uint16 field_2C; // unused
uint16 field_2E; // unused
TypeData u;
@@ -429,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 d66b1af1f1..bb306c3299 100644
--- a/engines/parallaction/parallaction.cpp
+++ b/engines/parallaction/parallaction.cpp
@@ -85,20 +85,28 @@ Parallaction::Parallaction(OSystem *syst, const PARALLACTIONGameDescription *gam
Parallaction::~Parallaction() {
delete _debugger;
-
delete _globalTable;
-
delete _callableNames;
- delete _localFlagNames;
+ delete _cmdExec;
+ delete _programExec;
+ _gfx->clearGfxObjects(kGfxObjCharacter | kGfxObjNormal);
+ hideDialogueStuff();
+ delete _balloonMan;
freeLocation();
freeCharacter();
destroyInventory();
+ cleanupGui();
+
+ delete _comboArrow;
+
+ delete _localFlagNames;
delete _gfx;
delete _soundMan;
delete _disk;
+ delete _input;
}
@@ -132,18 +140,24 @@ int Parallaction::init() {
_debugger = new Debugger(this);
- return 0;
-}
-
+ _menuHelper = 0;
+ setupBalloonManager();
+ return 0;
+}
+void Parallaction::clearSet(OpcodeSet &opcodes) {
+ for (Common::Array<const Opcode*>::iterator i = opcodes.begin(); i != opcodes.end(); ++i)
+ delete *i;
+ opcodes.clear();
+}
void Parallaction::updateView() {
- if ((_engineFlags & kEnginePauseJobs) && (_engineFlags & kEngineInventory) == 0) {
+ if ((_engineFlags & kEnginePauseJobs) && (_input->_inputMode != Input::kInputModeInventory)) {
return;
}
@@ -153,6 +167,11 @@ void Parallaction::updateView() {
}
+void Parallaction::hideDialogueStuff() {
+ _gfx->freeItems();
+ _balloonMan->freeBalloons();
+}
+
void Parallaction::freeCharacter() {
debugC(1, kDebugExec, "freeCharacter()");
@@ -160,6 +179,8 @@ void Parallaction::freeCharacter() {
delete _objectsNames;
_objectsNames = 0;
+ _gfx->clearGfxObjects(kGfxObjCharacter);
+
_char.free();
return;
@@ -189,6 +210,9 @@ AnimationPtr Parallaction::findAnimation(const char *name) {
}
void Parallaction::freeAnimations() {
+ for (AnimationList::iterator it = _location._animations.begin(); it != _location._animations.end(); it++) {
+ (*it)->_commands.clear(); // See comment for freeZones(), about circular references.
+ }
_location._animations.clear();
return;
}
@@ -237,10 +261,9 @@ void Parallaction::freeLocation() {
_localFlagNames->clear();
- _location._walkNodes.clear();
+ _location._walkPoints.clear();
- _gfx->clearGfxObjects();
- freeBackground();
+ _gfx->clearGfxObjects(kGfxObjNormal);
_location._programs.clear();
freeZones();
@@ -260,76 +283,32 @@ 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;
}
void Parallaction::showLocationComment(const char *text, bool end) {
- _gfx->setLocationBalloon(const_cast<char*>(text), end);
+ _balloonMan->setLocationBalloon(const_cast<char*>(text), end);
}
void Parallaction::processInput(InputData *data) {
+ if (!data) {
+ return;
+ }
switch (data->_event) {
- case kEvEnterZone:
- debugC(2, kDebugInput, "processInput: kEvEnterZone");
- _gfx->setFloatingLabel(data->_label);
- break;
-
- case kEvExitZone:
- debugC(2, kDebugInput, "processInput: kEvExitZone");
- _gfx->setFloatingLabel(0);
- break;
-
- case kEvAction:
- debugC(2, kDebugInput, "processInput: kEvAction");
- _input->stopHovering();
- pauseJobs();
- runZone(data->_zone);
- resumeJobs();
- break;
-
- case kEvOpenInventory:
- _input->stopHovering();
- _gfx->setFloatingLabel(0);
- 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:
- _engineFlags |= kEngineQuit;
- break;
-
case kEvSaveGame:
_input->stopHovering();
saveGame();
@@ -350,28 +329,39 @@ void Parallaction::processInput(InputData *data) {
void Parallaction::runGame() {
InputData *data = _input->updateInput();
- if (data->_event != kEvNone) {
+ if (_engineFlags & kEngineQuit)
+ return;
+
+ runGuiFrame();
+ runDialogueFrame();
+ runCommentFrame();
+
+ if (_input->_inputMode == Input::kInputModeGame) {
processInput(data);
- }
+ runPendingZones();
- runPendingZones();
+ if (_engineFlags & kEngineQuit)
+ return;
- if (_engineFlags & kEngineChangeLocation) {
- changeLocation(_location._name);
+ if (_engineFlags & kEngineChangeLocation) {
+ changeLocation(_location._name);
+ }
}
-
_gfx->beginFrame();
if (_input->_inputMode == Input::kInputModeGame) {
- runScripts();
- walk();
+ _programExec->runScripts(_location._programs.begin(), _location._programs.end());
+ _char._ani->_z = _char._ani->height() + _char._ani->_top;
+ if (_char._ani->gfxobj) {
+ _char._ani->gfxobj->z = _char._ani->_z;
+ }
+ _char._walker->walk();
drawAnimations();
}
// change this to endFrame?
updateView();
-
}
@@ -400,14 +390,13 @@ void Parallaction::doLocationEnterTransition() {
pal.makeGrayscale();
_gfx->setPalette(pal);
- runScripts();
+ _programExec->runScripts(_location._programs.begin(), _location._programs.end());
drawAnimations();
-
+ showLocationComment(_location._comment, false);
_gfx->updateScreen();
- showLocationComment(_location._comment, false);
- _input->waitUntilLeftClick();
- _gfx->freeBalloons();
+ _input->waitForButtonEvent(kMouseLeftUp);
+ _balloonMan->freeBalloons();
// fades maximum intensity palette towards approximation of main palette
for (uint16 _si = 0; _si<6; _si++) {
@@ -467,6 +456,9 @@ void Parallaction::freeZones() {
debugC(2, kDebugExec, "freeZones preserving zone '%s'", z->_name);
it++;
} else {
+ (*it)->_commands.clear(); // Since commands may reference zones, and both commands and zones are kept stored into
+ // SharedPtr's, we need to kill commands explicitly to destroy any potential circular
+ // reference.
it = _location._zones.erase(it);
}
}
@@ -475,16 +467,47 @@ void Parallaction::freeZones() {
}
+enum {
+ WALK_LEFT = 0,
+ WALK_RIGHT = 1,
+ WALK_DOWN = 2,
+ WALK_UP = 3
+};
+
+struct WalkFrames {
+ int16 stillFrame[4];
+ int16 firstWalkFrame[4];
+ int16 numWalkFrames[4];
+ int16 frameRepeat[4];
+};
+
+WalkFrames _char20WalkFrames = {
+ { 0, 7, 14, 17 },
+ { 1, 8, 15, 18 },
+ { 6, 6, 2, 2 },
+ { 2, 2, 4, 4 }
+};
+
+WalkFrames _char24WalkFrames = {
+ { 0, 9, 18, 21 },
+ { 1, 10, 19, 22 },
+ { 8, 8, 2, 2 },
+ { 2, 2, 4, 4 }
+};
+
const char Character::_prefixMini[] = "mini";
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;
+ _direction = WALK_DOWN;
+ _step = 0;
+
_dummy = false;
_ani->_left = 150;
@@ -496,24 +519,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;
}
@@ -522,11 +582,12 @@ void Character::free() {
delete _talk;
delete _head;
delete _objs;
+ delete _ani->gfxobj;
- _ani->gfxobj = NULL;
_talk = NULL;
_head = NULL;
_objs = NULL;
+ _ani->gfxobj = NULL;
return;
}
@@ -548,10 +609,14 @@ void Character::setName(const char *name) {
const char *end = begin + strlen(name);
_prefix = _empty;
+ _suffix = _empty;
_dummy = IS_DUMMY_CHARACTER(name);
if (!_dummy) {
+ if (!strstr(name, "donna")) {
+ _engineFlags &= ~kEngineTransformedDonna;
+ } else
if (_engineFlags & kEngineTransformedDonna) {
_suffix = _suffixTras;
} else {
@@ -560,8 +625,6 @@ void Character::setName(const char *name) {
_engineFlags |= kEngineTransformedDonna;
_suffix = _suffixTras;
end = s;
- } else {
- _suffix = _empty;
}
}
if (IS_MINI_CHARACTER(name)) {
@@ -597,9 +660,35 @@ void Parallaction::beep() {
}
void Parallaction::scheduleLocationSwitch(const char *location) {
+ debugC(9, kDebugExec, "scheduleLocationSwitch(%s)\n", location);
strcpy(_location._name, location);
_engineFlags |= kEngineChangeLocation;
}
+
+
+
+void Character::updateDirection(const Common::Point& pos, const Common::Point& to) {
+
+ Common::Point dist(to.x - pos.x, to.y - pos.y);
+ WalkFrames *frames = (_ani->getFrameNum() == 20) ? &_char20WalkFrames : &_char24WalkFrames;
+
+ _step++;
+
+ if (dist.x == 0 && dist.y == 0) {
+ _ani->_frame = frames->stillFrame[_direction];
+ return;
+ }
+
+ if (dist.x < 0)
+ dist.x = -dist.x;
+ if (dist.y < 0)
+ dist.y = -dist.y;
+
+ _direction = (dist.x > dist.y) ? ((to.x > pos.x) ? WALK_LEFT : WALK_RIGHT) : ((to.y > pos.y) ? WALK_DOWN : WALK_UP);
+ _ani->_frame = frames->firstWalkFrame[_direction] + (_step / frames->frameRepeat[_direction]) % frames->numWalkFrames[_direction];
+}
+
+
} // namespace Parallaction
diff --git a/engines/parallaction/parallaction.h b/engines/parallaction/parallaction.h
index 6e5957d3cd..e5c5221414 100644
--- a/engines/parallaction/parallaction.h
+++ b/engines/parallaction/parallaction.h
@@ -33,6 +33,7 @@
#include "engines/engine.h"
+#include "parallaction/exec.h"
#include "parallaction/input.h"
#include "parallaction/inventory.h"
#include "parallaction/parser.h"
@@ -100,10 +101,8 @@ enum {
enum EngineFlags {
kEngineQuit = (1 << 0),
kEnginePauseJobs = (1 << 1),
- kEngineInventory = (1 << 2),
kEngineWalking = (1 << 3),
kEngineChangeLocation = (1 << 4),
- kEngineBlockInput = (1 << 5),
kEngineDragging = (1 << 6),
kEngineTransformedDonna = (1 << 7),
@@ -113,14 +112,6 @@ enum EngineFlags {
enum {
kEvNone = 0,
- kEvEnterZone = 1,
- kEvExitZone = 2,
- kEvAction = 3,
- kEvOpenInventory = 4,
- kEvCloseInventory = 5,
- kEvHoverInventory = 6,
- kEvWalk = 7,
- kEvQuitGame = 1000,
kEvSaveGame = 2000,
kEvLoadGame = 4000
};
@@ -164,6 +155,8 @@ class Debugger;
class Gfx;
class SoundMan;
class Input;
+class DialogueManager;
+class MenuInputHelper;
struct Location {
@@ -184,7 +177,7 @@ struct Location {
char _soundFile[50];
// NS specific
- WalkNodeList _walkNodes;
+ PointList _walkPoints;
char _slideText[2][MAX_TOKEN_LEN];
// BRA specific
@@ -202,13 +195,16 @@ struct Character {
AnimationPtr _ani;
- Frames *_head;
- Frames *_talk;
- Frames *_objs;
- PathBuilder _builder;
- WalkNodeList *_walkPath;
+ GfxObj *_head;
+ GfxObj *_talk;
+ GfxObj *_objs;
+ 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);
@@ -228,18 +224,19 @@ protected:
static const char _suffixTras[];
static const char _empty[];
+ int16 _direction, _step;
+
public:
void setName(const char *name);
const char *getName() const;
const char *getBaseName() const;
const char *getFullName() const;
bool dummy() const;
-};
+ void updateDirection(const Common::Point& pos, const Common::Point& to);
+};
-#define DECLARE_UNQUALIFIED_COMMAND_OPCODE(op) void cmdOp_##op()
-#define DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(op) void instOp_##op()
#define NUM_LOCATIONS 120
@@ -259,41 +256,16 @@ public:
Input *_input;
- OpcodeSet _commandOpcodes;
-
- struct ParallactionStruct1 {
- CommandPtr cmd;
- ZonePtr z;
- } _cmdRunCtxt;
-
- OpcodeSet _instructionOpcodes;
-
- struct ParallactionStruct2 {
- AnimationPtr anim;
- ProgramPtr program;
- InstructionList::iterator inst;
- uint16 modCounter;
- bool suspend;
- } _instRunCtxt;
-
void processInput(InputData* data);
void pauseJobs();
void resumeJobs();
- void finalizeWalk(WalkNodeList *list);
- int16 selectWalkFrame(const Common::Point& pos, const WalkNodePtr from);
- void clipMove(Common::Point& pos, const WalkNodePtr from);
-
ZonePtr findZone(const char *name);
ZonePtr hitZone(uint32 type, uint16 x, uint16 y);
uint16 runZone(ZonePtr z);
void freeZones();
- void runDialogue(SpeakData*);
-
- void runCommands(CommandList& list, ZonePtr z = nullZonePtr);
-
AnimationPtr findAnimation(const char *name);
void freeAnimations();
@@ -327,6 +299,8 @@ public:
Gfx* _gfx;
Disk* _disk;
+ CommandExec* _cmdExec;
+ ProgramExec* _programExec;
Character _char;
void setLocationFlags(uint32 flags);
@@ -351,6 +325,7 @@ public:
Common::RandomSource _rnd;
Debugger *_debugger;
+ Frames *_comboArrow;
protected: // data
@@ -367,10 +342,8 @@ protected: // members
void runGame();
void updateView();
- void scheduleLocationSwitch(const char *location);
void doLocationEnterTransition();
virtual void changeLocation(char *location) = 0;
- virtual void changeCharacter(const char *name) = 0;
virtual void runPendingZones() = 0;
void allocateLocationSlot(const char *name);
void finalizeLocationParsing();
@@ -379,28 +352,33 @@ protected: // members
void displayComment(ExamineData *data);
- uint16 checkDoor();
-
void freeCharacter();
int16 pickupItem(ZonePtr z);
+ void clearSet(OpcodeSet &opcodes);
+
+
public:
+ void scheduleLocationSwitch(const char *location);
+ virtual void changeCharacter(const char *name) = 0;
+
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 runScripts() = 0;
- virtual void walk() = 0;
virtual void drawAnimations() = 0;
void beep();
+ ZonePtr _zoneTrap;
+ PathBuilder* getPathBuilder(Character *ch);
+
public:
// const char **_zoneFlagNamesRes;
// const char **_zoneTypeNamesRes;
@@ -425,6 +403,27 @@ public:
Inventory *_inventory;
InventoryRenderer *_inventoryRenderer;
+ BalloonManager *_balloonMan;
+
+ void setupBalloonManager();
+
+ void hideDialogueStuff();
+ DialogueManager *_dialogueMan;
+ void enterDialogueMode(ZonePtr z);
+ 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();
};
@@ -483,12 +482,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;
@@ -500,17 +505,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);
@@ -520,11 +522,9 @@ private:
void initResources();
void initCursors();
- void initParsers();
static byte _resMouseArrow[256];
byte *_mouseArrow;
- Frames *_mouseComposedArrow;
static const Callable _dosCallables[25];
static const Callable _amigaCallables[25];
@@ -580,60 +580,16 @@ private:
const Callable *_callables;
protected:
- void runScripts();
- void walk();
void drawAnimations();
void parseLocation(const char *filename);
void loadProgram(AnimationPtr a, const char *filename);
- void initOpcodes();
-
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(invalid);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(set);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(clear);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(speak);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(get);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(toggle);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(quit);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
-
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(invalid);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(on);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(off);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(loop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endloop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(null);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(call);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(inc);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(set);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(sound);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
-
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();
};
@@ -655,6 +611,9 @@ public:
typedef void (Parallaction_br::*Callable)(void*);
virtual void callFunction(uint index, void* parm);
void changeCharacter(const char *name);
+ void setupSubtitles(char *s, char *s2, int y);
+ void clearSubtitles();
+
public:
Table *_countersNames;
@@ -674,7 +633,8 @@ public:
int32 _counters[32];
uint32 _zoneFlags[NUM_LOCATIONS][NUM_ZONES];
-
+ void startPart(uint part);
+ void setArrowCursor();
private:
LocationParser_br *_locationParser;
ProgramParser_br *_programParser;
@@ -682,20 +642,15 @@ private:
void initResources();
void initFonts();
void freeFonts();
- void initOpcodes();
- void initParsers();
- 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;
@@ -706,10 +661,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];
@@ -725,68 +677,6 @@ private:
void parseLocation(const char* name);
void loadProgram(AnimationPtr a, const char *filename);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(location);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(open);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(close);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(on);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(off);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(call);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(drop);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(move);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(start);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(stop);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(character);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(followme);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(onmouse);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(offmouse);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(add);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(leave);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(inc);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(dec);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifeq);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(iflt);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(ifgt);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(let);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(music);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(fix);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(unfix);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(zeta);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(scroll);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(swap);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(give);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(text);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(part);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(testsfx);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(ret);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(onsave);
- DECLARE_UNQUALIFIED_COMMAND_OPCODE(offsave);
-
- 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);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(put);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(wait);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(start);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(process);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(move);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(color);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mask);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(print);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(text);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(mul);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(div);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifeq);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(iflt);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(ifgt);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endif);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(stop);
- DECLARE_UNQUALIFIED_INSTRUCTION_OPCODE(endscript);
-
- void setupSubtitles(char *s, char *s2, int y);
- void clearSubtitles();
#if 0
void jobWaitRemoveLabelJob(void *parm, Job *job);
void jobPauseSfx(void *parm, Job *job);
diff --git a/engines/parallaction/parallaction_br.cpp b/engines/parallaction/parallaction_br.cpp
index 0f5cc2a0c4..761c8d1b74 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 {
@@ -72,14 +97,21 @@ int Parallaction_br::init() {
initResources();
initFonts();
initCursors();
- initOpcodes();
_locationParser = new LocationParser_br(this);
_locationParser->init();
_programParser = new ProgramParser_br(this);
_programParser->init();
+ _cmdExec = new CommandExec_br(this);
+ _cmdExec->init();
+ _programExec = new ProgramExec_br(this);
+ _programExec->init();
+
_part = -1;
+ _subtitle[0] = -1;
+ _subtitle[1] = -1;
+
Parallaction::init();
return 0;
@@ -92,6 +124,7 @@ Parallaction_br::~Parallaction_br() {
delete _dougCursor;
delete _donnaCursor;
+ delete _mouseArrow;
}
void Parallaction_br::callFunction(uint index, void* parm) {
@@ -102,13 +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 ((_engineFlags & kEngineQuit) == 0) {
- guiStart();
-
// initCharacter();
_input->_inputMode = Input::kInputModeGame;
@@ -142,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?
@@ -149,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));
@@ -170,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");
+ }
}
@@ -185,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);
@@ -199,16 +237,26 @@ void Parallaction_br::startPart() {
void Parallaction_br::runPendingZones() {
ZonePtr z;
+ _cmdExec->runSuspended();
+
if (_activeZone) {
z = _activeZone; // speak Zone or sound
_activeZone = nullZonePtr;
- runZone(z); // FIXME: BRA doesn't handle sound yet
+ if ((z->_type & 0xFFFF) == kZoneSpeak) {
+ enterDialogueMode(z);
+ } else {
+ runZone(z); // FIXME: BRA doesn't handle sound yet
+ }
}
if (_activeZone2) {
z = _activeZone2; // speak Zone or sound
_activeZone2 = nullZonePtr;
- runZone(z);
+ if ((z->_type & 0xFFFF) == kZoneSpeak) {
+ enterDialogueMode(z);
+ } else {
+ runZone(z); // FIXME: BRA doesn't handle sound yet
+ }
}
}
@@ -218,21 +266,35 @@ void Parallaction_br::changeLocation(char *location) {
// free open location stuff
clearSubtitles();
freeBackground();
- _gfx->clearGfxObjects();
+ _gfx->clearGfxObjects(kGfxObjNormal);
+ _gfx->freeLabels();
+ _subtitle[0] = _subtitle[1] = -1;
+
_location._programs.clear();
+
+ _location._animations.remove(_char._ani);
+
freeZones();
freeAnimations();
+
+ _location._animations.push_front(_char._ani);
+
// free(_location._comment);
// _location._comment = 0;
-// _location._commands.clear();
-// _location._aCommands.clear();
-
+ _location._commands.clear();
+ _location._aCommands.clear();
// load new location
parseLocation(location);
- runCommands(_location._commands);
+
+ // 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();
- runCommands(_location._aCommands);
+ _cmdExec->run(_location._aCommands);
_engineFlags &= ~kEngineChangeLocation;
}
@@ -282,25 +344,45 @@ void Parallaction_br::loadProgram(AnimationPtr a, const char *filename) {
void Parallaction_br::changeCharacter(const char *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._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 2cca3a6a4a..e81492e655 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
@@ -135,18 +136,24 @@ int Parallaction_ns::init() {
initResources();
initFonts();
initCursors();
- initOpcodes();
_locationParser = new LocationParser_ns(this);
_locationParser->init();
_programParser = new ProgramParser_ns(this);
_programParser->init();
+ _cmdExec = new CommandExec_ns(this);
+ _cmdExec->init();
+ _programExec = new ProgramExec_ns(this);
+ _programExec->init();
+
_introSarcData1 = 0;
_introSarcData2 = 1;
_introSarcData3 = 200;
num_foglie = 0;
+ _inTestResult = false;
+
_location._animations.push_front(_char._ani);
Parallaction::init();
@@ -157,7 +164,8 @@ int Parallaction_ns::init() {
Parallaction_ns::~Parallaction_ns() {
freeFonts();
- delete _mouseComposedArrow;
+ delete _locationParser;
+ delete _programParser;
_location._animations.remove(_char._ani);
@@ -174,7 +182,7 @@ void Parallaction_ns::freeFonts() {
}
void Parallaction_ns::initCursors() {
- _mouseComposedArrow = _disk->loadPointer("pointer");
+ _comboArrow = _disk->loadPointer("pointer");
_mouseArrow = _resMouseArrow;
}
@@ -183,40 +191,20 @@ void Parallaction_ns::setArrowCursor() {
debugC(1, kDebugInput, "setting mouse cursor to arrow");
// this stuff is needed to avoid artifacts with labels and selected items when switching cursors
- _gfx->setFloatingLabel(0);
+ _input->stopHovering();
_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;
+void Parallaction_ns::setInventoryCursor(ItemName name) {
+ assert(name > 0);
- const InventoryItem *item = getInventoryItem(pos);
- if (item->_index == 0)
- return;
-
- _input->_activeItem._id = item->_id;
-
- 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);
-
}
@@ -232,11 +220,8 @@ int Parallaction_ns::go() {
_globalTable = _disk->loadTable("global");
- guiStart();
+ startGui();
- changeLocation(_location._name);
-
- _input->_inputMode = Input::kInputModeGame;
while ((_engineFlags & kEngineQuit) == 0) {
runGame();
}
@@ -268,8 +253,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() {
@@ -286,16 +277,19 @@ 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);
- _gfx->setFloatingLabel(0);
+ _input->stopHovering();
_gfx->freeLabels();
- _input->stopHovering();
- if (_engineFlags & kEngineBlockInput) {
- setArrowCursor();
- }
+ _zoneTrap = nullZonePtr;
+ setArrowCursor();
+
+ _gfx->showGfxObj(_char._ani->gfxobj, false);
_location._animations.remove(_char._ani);
freeLocation();
@@ -307,7 +301,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();
}
@@ -317,6 +313,7 @@ void Parallaction_ns::changeLocation(char *location) {
}
_location._animations.push_front(_char._ani);
+ _gfx->showGfxObj(_char._ani->gfxobj, true);
strcpy(_saveData1, locname.location());
parseLocation(_saveData1);
@@ -341,19 +338,18 @@ void Parallaction_ns::changeLocation(char *location) {
// and acommands are executed, so that it can be set again if needed.
_engineFlags &= ~kEngineChangeLocation;
- runCommands(_location._commands);
+ _cmdExec->run(_location._commands);
doLocationEnterTransition();
- runCommands(_location._aCommands);
+ _cmdExec->run(_location._aCommands);
if (_location._hasSound)
_soundMan->playSfx(_location._soundFile, 0, true);
- debugC(1, kDebugExec, "changeLocation() done");
-
- return;
+ _input->setMouseState(oldMouseState);
+ debugC(1, kDebugExec, "changeLocation() done");
}
@@ -401,6 +397,8 @@ void Parallaction_ns::changeCharacter(const char *name) {
Common::String oldArchive = _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1");
_char._ani->gfxobj = _gfx->loadAnim(_char.getFullName());
+ _char._ani->gfxobj->setFlags(kGfxObjCharacter);
+ _char._ani->gfxobj->clearFlags(kGfxObjNormal);
if (!_char.dummy()) {
if (getPlatform() == Common::kPlatformAmiga) {
diff --git a/engines/parallaction/parser.cpp b/engines/parallaction/parser.cpp
index f9de6eb4af..6de0a7d7f5 100644
--- a/engines/parallaction/parser.cpp
+++ b/engines/parallaction/parser.cpp
@@ -30,8 +30,7 @@ namespace Parallaction {
char _tokens[20][MAX_TOKEN_LEN];
-Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {
-}
+Script::Script(Common::ReadStream *input, bool disposeSource) : _input(input), _disposeSource(disposeSource), _line(0) {}
Script::~Script() {
if (_disposeSource)
diff --git a/engines/parallaction/parser.h b/engines/parallaction/parser.h
index d488cf9b58..79e6cf6640 100644
--- a/engines/parallaction/parser.h
+++ b/engines/parallaction/parser.h
@@ -128,6 +128,7 @@ protected:
// BRA specific
int numZones;
+ BackgroundInfo *info;
char *bgName;
char *maskName;
char *pathName;
@@ -171,7 +172,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 +193,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();
@@ -221,13 +222,14 @@ public:
virtual void init();
virtual ~LocationParser_ns() {
+ delete _parser;
delete _commandsNames;
delete _locationStmt;
+ delete _locationZoneStmt;
+ delete _locationAnimStmt;
delete _zoneTypeNames;
delete _zoneFlagNames;
- delete _parser;
-
clearSet(_commandParsers);
clearSet(_locationAnimParsers);
clearSet(_locationZoneParsers);
@@ -283,6 +285,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) {
}
@@ -306,6 +311,7 @@ protected:
Parser *_parser;
Parallaction_ns *_vm;
+
Script *_script;
ProgramPtr _program;
@@ -356,7 +362,9 @@ public:
virtual void init();
virtual ~ProgramParser_ns() {
+ delete _parser;
delete _instructionNames;
+
clearSet(_instructionParsers);
}
diff --git a/engines/parallaction/parser_br.cpp b/engines/parallaction/parser_br.cpp
index 51da7eb396..3b446805d7 100644
--- a/engines/parallaction/parser_br.cpp
+++ b/engines/parallaction/parser_br.cpp
@@ -390,7 +390,7 @@ DECLARE_LOCATION_PARSER(flags) {
if ((_vm->getLocationFlags() & kFlagsVisited) == 0) {
// only for 1st visit
- _vm->clearLocationFlags(kFlagsAll);
+ _vm->clearLocationFlags((uint32)kFlagsAll);
int _si = 1;
do {
@@ -442,7 +442,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 +464,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 +750,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 +1044,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 +1052,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
@@ -1114,11 +1175,14 @@ void LocationParser_br::parse(Script *script) {
ctxt.maskName = 0;
ctxt.pathName = 0;
ctxt.characterName = 0;
+ ctxt.info = new BackgroundInfo;
LocationParser_ns::parse(script);
- _vm->_gfx->setBackground(kBackgroundLocation, ctxt.bgName, ctxt.maskName, ctxt.pathName);
- _vm->_pathBuffer = &_vm->_gfx->_backgroundInfo.path;
+ _vm->_disk->loadScenery(*ctxt.info, ctxt.bgName, ctxt.maskName, ctxt.pathName);
+ _vm->_gfx->setBackground(kBackgroundLocation, ctxt.info);
+ _vm->_pathBuffer = &ctxt.info->path;
+
if (ctxt.characterName) {
_vm->changeCharacter(ctxt.characterName);
diff --git a/engines/parallaction/parser_ns.cpp b/engines/parallaction/parser_ns.cpp
index 2c4601c938..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);
}
@@ -1059,7 +1060,7 @@ DECLARE_LOCATION_PARSER(flags) {
if ((_vm->getLocationFlags() & kFlagsVisited) == 0) {
// only for 1st visit
- _vm->clearLocationFlags(kFlagsAll);
+ _vm->clearLocationFlags((uint32)kFlagsAll);
int _si = 1;
do {
@@ -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
diff --git a/engines/parallaction/sound.cpp b/engines/parallaction/sound.cpp
index dd74e8f7aa..df6867a90c 100644
--- a/engines/parallaction/sound.cpp
+++ b/engines/parallaction/sound.cpp
@@ -175,6 +175,7 @@ void MidiPlayer::close() {
_mutex.lock();
_driver->setTimerCallback(NULL, NULL);
_driver->close();
+ delete _driver;
_driver = 0;
_parser->setMidiDriver(NULL);
delete _parser;
@@ -249,6 +250,9 @@ void DosSoundMan::stopMusic() {
}
void DosSoundMan::playCharacterMusic(const char *character) {
+ if (character == NULL) {
+ return;
+ }
if (!scumm_stricmp(_vm->_location._name, "night") ||
!scumm_stricmp(_vm->_location._name, "intsushi")) {
diff --git a/engines/parallaction/walk.cpp b/engines/parallaction/walk.cpp
index 0a8ded9e29..bf8f423fd5 100644
--- a/engines/parallaction/walk.cpp
+++ b/engines/parallaction/walk.cpp
@@ -27,33 +27,43 @@
namespace Parallaction {
-static uint16 _doorData1 = 1000;
-static ZonePtr _zoneTrap;
-static uint16 walkData1 = 0;
-static uint16 walkData2 = 0; // next walk frame
+#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;
@@ -62,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;
@@ -90,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;
@@ -103,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);
@@ -124,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;
}
@@ -208,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) {
@@ -234,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;
@@ -245,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;
@@ -259,190 +245,390 @@ uint16 PathBuilder::walkFunc1(int16 x, int16 y, WalkNodePtr Node) {
return 1;
}
-void Parallaction::clipMove(Common::Point& pos, const WalkNodePtr from) {
+void PathWalker_NS::clipMove(Common::Point& pos, const Common::Point& to) {
- if ((pos.x < from->_x) && (pos.x < _pathBuffer->w) && (_pathBuffer->getValue(pos.x + 2, pos.y) != 0)) {
- pos.x = (pos.x + 2 < from->_x) ? pos.x + 2 : from->_x;
+ 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 > from->_x) && (pos.x > 0) && (_pathBuffer->getValue(pos.x - 2, pos.y) != 0)) {
- pos.x = (pos.x - 2 > from->_x) ? pos.x - 2 : from->_x;
+ 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 < from->_y) && (pos.y < _pathBuffer->h) && (_pathBuffer->getValue(pos.x, pos.y + 2) != 0)) {
- pos.y = (pos.y + 2 <= from->_y) ? pos.y + 2 : from->_y;
+ 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 > from->_y) && (pos.y > 0) && (_pathBuffer->getValue(pos.x, pos.y - 2) != 0)) {
- pos.y = (pos.y - 2 >= from->_y) ? pos.y - 2 :from->_y;
+ 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;
}
return;
}
-int16 Parallaction::selectWalkFrame(const Common::Point& pos, const WalkNodePtr from) {
- Common::Point dist(from->_x - pos.x, from->_y - pos.y);
+void PathWalker_NS::checkDoor(const Common::Point &foot) {
- if (dist.x < 0)
- dist.x = -dist.x;
- if (dist.y < 0)
- dist.y = -dist.y;
+ ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y);
+ if (z) {
+ if ((z->_flags & kFlagsClosed) == 0) {
+ _vm->_location._startPosition = z->u.door->_startPos;
+ _vm->_location._startFrame = z->u.door->_startFrame;
+ _vm->scheduleLocationSwitch(z->u.door->_location);
+ _vm->_zoneTrap = nullZonePtr;
+ } else {
+ _vm->_cmdExec->run(z->_commands, z);
+ }
+ }
- walkData1++;
+ z = _vm->hitZone(kZoneTrap, foot.x, foot.y);
+ if (z) {
+ _vm->setLocationFlags(kFlagsEnter);
+ _vm->_cmdExec->run(z->_commands, z);
+ _vm->clearLocationFlags(kFlagsEnter);
+ _vm->_zoneTrap = z;
+ } else
+ if (_vm->_zoneTrap) {
+ _vm->setLocationFlags(kFlagsExit);
+ _vm->_cmdExec->run(_vm->_zoneTrap->_commands, _vm->_zoneTrap);
+ _vm->clearLocationFlags(kFlagsExit);
+ _vm->_zoneTrap = nullZonePtr;
+ }
- // walk frame selection
- int16 v16;
- if (_char._ani->getFrameNum() == 20) {
+}
- if (dist.x > dist.y) {
- walkData2 = (from->_x > pos.x) ? 0 : 7;
- walkData1 %= 12;
- v16 = walkData1 / 2;
- } else {
- walkData2 = (from->_y > pos.y) ? 14 : 17;
- walkData1 %= 8;
- v16 = walkData1 / 4;
+
+void PathWalker_NS::finalizeWalk() {
+ _engineFlags &= ~kEngineWalking;
+
+ Common::Point foot;
+ _ch->getFoot(foot);
+ checkDoor(foot);
+
+ _ch->_walkPath.clear();
+}
+
+void PathWalker_NS::walk() {
+ if ((_engineFlags & kEngineWalking) == 0) {
+ return;
+ }
+
+ Common::Point curPos;
+ _ch->getFoot(curPos);
+
+ // update target, if previous was reached
+ 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 == _ch->_walkPath.end()) {
+ debugC(1, kDebugWalk, "walk reached last node");
+ finalizeWalk();
+ targetPos = curPos;
} else {
+ // targetPos is saved to help setting character direction
+ targetPos = *it;
- if (dist.x > dist.y) {
- walkData2 = (from->_x > pos.x) ? 0 : 9;
- walkData1 %= 16;
- v16 = walkData1 / 2;
- } else {
- walkData2 = (from->_y > pos.y) ? 18 : 21;
- walkData1 %= 8;
- v16 = walkData1 / 4;
- }
+ Common::Point newPos(curPos);
+ clipMove(newPos, targetPos);
+ _ch->setFoot(newPos);
+ if (newPos == curPos) {
+ debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle");
+ finalizeWalk();
+ targetPos = newPos; // when walking is interrupted, targetPos must be hacked so that a still frame can be selected
+ }
}
- return v16;
+ // targetPos is used to select the direction (and the walkFrame) of a character,
+ // since it doesn't cause the sudden changes in orientation that newPos would.
+ // Since newPos is 'adjusted' according to walkable areas, an imaginary line drawn
+ // 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.
+ _ch->updateDirection(curPos, targetPos);
}
-uint16 Parallaction::checkDoor() {
-// printf("checkDoor()...");
- if (_currentLocationIndex != _doorData1) {
- _doorData1 = _currentLocationIndex;
- _zoneTrap = nullZonePtr;
- }
- _engineFlags &= ~kEngineWalking;
+PathBuilder_NS::PathBuilder_NS(Character *ch) : PathBuilder(ch), _list(0) {
+}
- Common::Point foot;
- _char.getFoot(foot);
- ZonePtr z = hitZone(kZoneDoor, foot.x, foot.y);
+bool PathBuilder_BR::directPathExists(const Common::Point &from, const Common::Point &to) {
- if (z) {
+ Common::Point copy(from);
+ Common::Point p(copy);
- if ((z->_flags & kFlagsClosed) == 0) {
- _location._startPosition = z->u.door->_startPos;
- _location._startFrame = z->u.door->_startFrame;
+ while (p != to) {
- scheduleLocationSwitch(z->u.door->_location);
- _zoneTrap = nullZonePtr;
+ 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--;
- } else {
- runCommands(z->_commands, z);
+ if (p == copy && p != to) {
+ return false;
}
+
+ copy = p;
}
- _char.getFoot(foot);
- z = hitZone(kZoneTrap, foot.x, foot.y);
+ return true;
+}
- if (z) {
- setLocationFlags(kFlagsEnter);
- runCommands(z->_commands, z);
- clearLocationFlags(kFlagsEnter);
- _zoneTrap = z;
- } else
- if (_zoneTrap) {
- setLocationFlags(kFlagsExit);
- runCommands(_zoneTrap->_commands, _zoneTrap);
- clearLocationFlags(kFlagsExit);
- _zoneTrap = nullZonePtr;
+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;
}
-// printf("done\n");
+ 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");
- _char._ani->_frame = walkData2;
- return _char._ani->_frame;
+ 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
-void Parallaction::finalizeWalk(WalkNodeList *list) {
- checkDoor();
- delete list;
+ return;
}
-void Parallaction_ns::walk() {
+
+void PathWalker_BR::walk() {
if ((_engineFlags & kEngineWalking) == 0) {
return;
}
- WalkNodeList *list = _char._walkPath;
+#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
- _char._ani->_oldPos.x = _char._ani->_left;
- _char._ani->_oldPos.y = _char._ani->_top;
+ GfxObj *obj = _ch->_ani->gfxobj;
- Common::Point pos;
- _char.getFoot(pos);
+ Common::Rect rect;
+ obj->getRect(_ch->_ani->_frame, rect);
- WalkNodeList::iterator it = list->begin();
+ 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 (it != list->end()) {
- if ((*it)->_x == pos.x && (*it)->_y == pos.y) {
- debugC(1, kDebugWalk, "walk reached node (%i, %i)", (*it)->_x, (*it)->_y);
- it = list->erase(it);
+ 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");
}
}
- if (it == list->end()) {
- debugC(1, kDebugWalk, "walk reached last node");
-// j->_finished = 1;
- finalizeWalk(list);
- return;
- }
- _char._walkPath = list;
- // selectWalkFrame must be performed before position is changed by clipMove
- int16 v16 = selectWalkFrame(pos, *it);
- clipMove(pos, *it);
+ _ch->getFoot(_startFoot);
- _char.setFoot(pos);
+ _fieldC = 0;
+ _step++;
+ _step %= 8;
- Common::Point newpos(_char._ani->_left, _char._ani->_top);
+ int walkFrame = _step;
+ _dirFrame = 0;
+ Common::Point newpos(_startFoot), delta;
- if (newpos == _char._ani->_oldPos) {
- debugC(1, kDebugWalk, "walk was blocked by an unforeseen obstacle");
-// j->_finished = 1;
- finalizeWalk(list);
- } else {
- _char._ani->_frame = v16 + walkData2 + 1;
+ 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;
}
- return;
-}
+ 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);
-WalkNode::WalkNode() : _x(0), _y(0) {
-}
+ 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;
+ }
-WalkNode::WalkNode(int16 x, int16 y) : _x(x), _y(y) {
-}
+ if (_fieldC || !_ch->_walkPath.empty()) {
+// checkTrap();
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, case 1\n");
+ return;
+ }
-WalkNode::WalkNode(const WalkNode& w) : _x(w._x), _y(w._y) {
+ debugC(3, kDebugWalk, "PathWalker_BR::walk, case 2\n");
+ finalizeWalk();
+ return;
}
-void WalkNode::getPoint(Common::Point &p) const {
- p.x = _x;
- p.y = _y;
-}
+PathWalker_BR::PathWalker_BR(Character *ch) : PathWalker(ch), _fieldC(1), _first(true) {
-PathBuilder::PathBuilder(AnimationPtr anim) : _anim(anim), _list(0) {
}
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/graphics.cpp b/engines/queen/graphics.cpp
index f863f7663c..6d0a11ccfe 100644
--- a/engines/queen/graphics.cpp
+++ b/engines/queen/graphics.cpp
@@ -1175,15 +1175,8 @@ BamScene::BamScene(QueenEngine *vm)
}
void BamScene::playSfx() {
- // Don't try to play all the sounds. This is only necessary for the
- // fight bam, in which the number of 'sfx bam frames' is too much
- // important / too much closer. The original game does not have
- // this problem since its playSfx() function returns immediately
- // if a sound is already being played.
- if (_lastSoundIndex == 0 || _index - _lastSoundIndex >= SFX_SKIP) {
- _vm->sound()->playSfx(_vm->logic()->currentRoomSfx());
- _lastSoundIndex = _index;
- }
+ _vm->sound()->playSfx(_vm->logic()->currentRoomSfx());
+ _lastSoundIndex = _index;
}
void BamScene::prepareAnimation() {
diff --git a/engines/queen/graphics.h b/engines/queen/graphics.h
index 6f00111635..7eadf9a191 100644
--- a/engines/queen/graphics.h
+++ b/engines/queen/graphics.h
@@ -248,10 +248,6 @@ public:
F_REQ_STOP = 2
};
- enum {
- SFX_SKIP = 8
- };
-
uint16 _flag, _index;
private:
diff --git a/engines/queen/input.cpp b/engines/queen/input.cpp
index 146e95bcef..9f03c341c9 100644
--- a/engines/queen/input.cpp
+++ b/engines/queen/input.cpp
@@ -27,6 +27,7 @@
#include "common/events.h"
#include "common/system.h"
+#include "queen/queen.h"
#include "queen/input.h"
namespace Queen {
@@ -51,12 +52,12 @@ const Verb Input::_verbKeys[] = {
VERB_USE
};
-Input::Input(Common::Language language, OSystem *system) :
+Input::Input(Common::Language language, OSystem *system, QueenEngine *vm) :
_system(system), _eventMan(system->getEventManager()), _fastMode(false),
_keyVerb(VERB_NONE), _cutawayRunning(false), _canQuit(false),
_cutawayQuit(false), _dialogueRunning(false), _talkQuit(false),
_quickSave(false), _quickLoad(false), _debugger(false), _inKey(Common::KEYCODE_INVALID),
- _mouseButton(0), _idleTime(0) {
+ _mouseButton(0), _idleTime(0) , _vm(vm) {
switch (language) {
case Common::EN_ANY:
@@ -119,8 +120,8 @@ void Input::delay(uint amount) {
break;
case Common::EVENT_QUIT:
- _system->quit();
- break;
+ _vm->quitGame();
+ return;
default:
break;
diff --git a/engines/queen/input.h b/engines/queen/input.h
index 86092aeed6..43a57729c6 100644
--- a/engines/queen/input.h
+++ b/engines/queen/input.h
@@ -49,7 +49,7 @@ public:
MOUSE_RBUTTON = 2
};
- Input(Common::Language language, OSystem *system);
+ Input(Common::Language language, OSystem *system, QueenEngine *vm);
void delay(uint amount);
@@ -99,6 +99,8 @@ private:
Common::EventManager *_eventMan;
+ QueenEngine *_vm;
+
//! some cutaways require update() run faster
bool _fastMode;
diff --git a/engines/queen/journal.cpp b/engines/queen/journal.cpp
index bfbcfa4e59..0327fb74b8 100644
--- a/engines/queen/journal.cpp
+++ b/engines/queen/journal.cpp
@@ -85,8 +85,8 @@ void Journal::use() {
handleMouseWheel(1);
break;
case Common::EVENT_QUIT:
- _system->quit();
- break;
+ _vm->quitGame();
+ return;
default:
break;
}
diff --git a/engines/queen/queen.cpp b/engines/queen/queen.cpp
index d1a1247c46..c95e44b477 100644
--- a/engines/queen/queen.cpp
+++ b/engines/queen/queen.cpp
@@ -418,7 +418,7 @@ int QueenEngine::init() {
_display = new Display(this, _system);
_graphics = new Graphics(this);
_grid = new Grid(this);
- _input = new Input(_resource->getLanguage(), _system);
+ _input = new Input(_resource->getLanguage(), _system, this);
if (_resource->isDemo()) {
_logic = new LogicDemo(this);
diff --git a/engines/queen/resource.cpp b/engines/queen/resource.cpp
index 5a8db74e3b..b3bd663baf 100644
--- a/engines/queen/resource.cpp
+++ b/engines/queen/resource.cpp
@@ -106,7 +106,7 @@ ResourceEntry *Resource::resourceEntry(const char *filename) const {
re = &_resourceTable[cur];
break;
}
- } while (cur++ < _resourceEntries);
+ } while (++cur < _resourceEntries);
#endif
return re;
}
diff --git a/engines/queen/sound.cpp b/engines/queen/sound.cpp
index d70fe7209d..27cf1bf6a2 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
@@ -229,11 +266,6 @@ void PCSound::setVolume(int vol) {
_music->setVolume(vol);
}
-void PCSound::waitFinished(bool isSpeech) {
- while (_mixer->isSoundHandleActive(isSpeech ? _speechHandle : _sfxHandle))
- _vm->input()->delay(10);
-}
-
void PCSound::playSound(const char *base, bool isSpeech) {
char name[13];
strcpy(name, base);
@@ -243,7 +275,13 @@ void PCSound::playSound(const char *base, bool isSpeech) {
name[i] = '0';
}
strcat(name, ".SB");
- waitFinished(isSpeech);
+ if (isSpeech) {
+ while (_mixer->isSoundHandleActive(_speechHandle)) {
+ _vm->input()->delay(10);
+ }
+ } else {
+ _mixer->stopHandle(_sfxHandle);
+ }
uint32 size;
Common::File *f = _vm->resource()->findSound(name, &size);
if (f) {
@@ -255,6 +293,8 @@ void PCSound::playSound(const char *base, bool isSpeech) {
}
void SBSound::playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) {
+ // In order to simplify the code, we don't parse the .sb header but hard-code the
+ // values. Refer to tracker item #1876741 for details on the format/fields.
int headerSize;
f->seek(2, SEEK_CUR);
uint16 version = f->readUint16LE();
@@ -276,7 +316,7 @@ void SBSound::playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *so
if (sound) {
f->read(sound, size);
byte flags = Audio::Mixer::FLAG_UNSIGNED | Audio::Mixer::FLAG_AUTOFREE;
- _mixer->playRaw(Audio::Mixer::kSFXSoundType, soundHandle, sound, size, 11025, flags);
+ _mixer->playRaw(Audio::Mixer::kSFXSoundType, soundHandle, sound, size, 11840, flags);
}
}
diff --git a/engines/queen/sound.h b/engines/queen/sound.h
index c2c1481cc6..331034f746 100644
--- a/engines/queen/sound.h
+++ b/engines/queen/sound.h
@@ -143,7 +143,6 @@ public:
void setVolume(int vol);
protected:
- void waitFinished(bool isSpeech);
void playSound(const char *base, bool isSpeech);
virtual void playSoundData(Common::File *f, uint32 size, Audio::SoundHandle *soundHandle) = 0;
diff --git a/engines/saga/animation.cpp b/engines/saga/animation.cpp
index 3a1e510529..9fffb0f8bf 100644
--- a/engines/saga/animation.cpp
+++ b/engines/saga/animation.cpp
@@ -55,6 +55,7 @@ Anim::Anim(SagaEngine *vm) : _vm(vm) {
Anim::~Anim(void) {
reset();
+ freeCutawayList();
}
void Anim::loadCutawayList(const byte *resourcePointer, size_t resourceLength) {
diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp
index e936117894..482b3a4c82 100644
--- a/engines/saga/font.cpp
+++ b/engines/saga/font.cpp
@@ -63,6 +63,8 @@ Font::~Font(void) {
free(_fonts[i]);
}
+
+ free(_fonts);
}
@@ -238,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'.
@@ -257,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;
}
@@ -336,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 7380570a99..1d048baaad 100644
--- a/engines/saga/interface.cpp
+++ b/engines/saga/interface.cpp
@@ -334,7 +334,21 @@ Interface::Interface(SagaEngine *vm) : _vm(vm) {
Interface::~Interface(void) {
free(_inventory);
+ free(_mainPanel.image);
+ free(_conversePanel.image);
+ free(_optionPanel.image);
+ free(_quitPanel.image);
+ free(_loadPanel.image);
+ free(_savePanel.image);
+
_mainPanel.sprites.freeMem();
+ _conversePanel.sprites.freeMem();
+ _optionPanel.sprites.freeMem();
+ _quitPanel.sprites.freeMem();
+ _loadPanel.sprites.freeMem();
+ _savePanel.sprites.freeMem();
+ _protectPanel.sprites.freeMem();
+
_defPortraits.freeMem();
_scenePortraits.freeMem();
}
diff --git a/engines/saga/introproc_ihnm.cpp b/engines/saga/introproc_ihnm.cpp
index 5f1d0157d5..6614f4098f 100644
--- a/engines/saga/introproc_ihnm.cpp
+++ b/engines/saga/introproc_ihnm.cpp
@@ -132,6 +132,8 @@ void Scene::IHNMLoadCutaways() {
// Load the cutaways for the title screens
_vm->_anim->loadCutawayList(resourcePointer, resourceLength);
+
+ free(resourcePointer);
}
bool Scene::checkKey() {
diff --git a/engines/saga/rscfile.cpp b/engines/saga/rscfile.cpp
index b7d4f4f1bd..e150caeca5 100644
--- a/engines/saga/rscfile.cpp
+++ b/engines/saga/rscfile.cpp
@@ -769,6 +769,7 @@ void Resource::loadGlobalResources(int chapter, int actorsEntrance) {
_vm->_sprite->_mainSprites.freeMem();
_vm->_sprite->loadList(_metaResource.mainSpritesID, _vm->_sprite->_mainSprites);
+
_vm->_actor->loadObjList(_metaResource.objectCount, _metaResource.objectsResourceID);
_vm->_resource->loadResource(resourceContext, _metaResource.cutawayListResourceID, resourcePointer, resourceLength);
@@ -806,6 +807,7 @@ void Resource::loadGlobalResources(int chapter, int actorsEntrance) {
// The IHNM demo has a fixed music track and doesn't load a song table
_vm->_music->setVolume(_vm->_musicVolume == 10 ? -1 : _vm->_musicVolume * 25, 1);
_vm->_music->play(3, MUSIC_LOOP);
+ free(resourcePointer);
}
int voiceLUTResourceID = 0;
diff --git a/engines/saga/saga.cpp b/engines/saga/saga.cpp
index 40eb32b276..fafbd02cec 100644
--- a/engines/saga/saga.cpp
+++ b/engines/saga/saga.cpp
@@ -79,6 +79,7 @@ SagaEngine::SagaEngine(OSystem *syst, const SAGAGameDescription *gameDesc)
_scene = NULL;
_isoMap = NULL;
_gfx = NULL;
+ _driver = NULL;
_console = NULL;
_render = NULL;
_music = NULL;
@@ -133,6 +134,7 @@ SagaEngine::~SagaEngine() {
delete _render;
delete _music;
delete _sound;
+ delete _driver;
delete _gfx;
delete _console;
@@ -188,11 +190,11 @@ int SagaEngine::init() {
bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
bool adlib = (midiDriver == MD_ADLIB);
- MidiDriver *driver = MidiDriver::createMidi(midiDriver);
+ _driver = MidiDriver::createMidi(midiDriver);
if (native_mt32)
- driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
+ _driver->property(MidiDriver::PROP_CHANNEL_MASK, 0x03FE);
- _music = new Music(this, _mixer, driver, _musicVolume);
+ _music = new Music(this, _mixer, _driver, _musicVolume);
_music->setNativeMT32(native_mt32);
_music->setAdlib(adlib);
diff --git a/engines/saga/saga.h b/engines/saga/saga.h
index 4a5fae7ddb..6b6eb6b3fb 100644
--- a/engines/saga/saga.h
+++ b/engines/saga/saga.h
@@ -29,6 +29,7 @@
#include "engines/engine.h"
#include "common/stream.h"
+#include "sound/mididrv.h"
#include "saga/gfx.h"
#include "saga/list.h"
@@ -531,6 +532,7 @@ public:
SndRes *_sndRes;
Sound *_sound;
Music *_music;
+ MidiDriver *_driver;
Anim *_anim;
Render *_render;
IsoMap *_isoMap;
diff --git a/engines/saga/script.cpp b/engines/saga/script.cpp
index 7664af314f..088be34c72 100644
--- a/engines/saga/script.cpp
+++ b/engines/saga/script.cpp
@@ -150,6 +150,7 @@ Script::~Script() {
debug(8, "Shutting down scripting subsystem.");
_mainStrings.freeMem();
+ _globalVoiceLUT.freeMem();
freeModules();
free(_modules);
diff --git a/engines/saga/sprite.cpp b/engines/saga/sprite.cpp
index e9d002819c..be4f2a423d 100644
--- a/engines/saga/sprite.cpp
+++ b/engines/saga/sprite.cpp
@@ -74,6 +74,9 @@ Sprite::Sprite(SagaEngine *vm) : _vm(vm) {
Sprite::~Sprite(void) {
debug(8, "Shutting down sprite subsystem...");
_mainSprites.freeMem();
+ _inventorySprites.freeMem();
+ _arrowSprites.freeMem();
+ _saveReminderSprites.freeMem();
free(_decodeBuf);
}
diff --git a/engines/scumm/charset.cpp b/engines/scumm/charset.cpp
index 8f3175f098..5a45fb7da9 100644
--- a/engines/scumm/charset.cpp
+++ b/engines/scumm/charset.cpp
@@ -56,7 +56,7 @@ void ScummEngine::loadCJKFont() {
_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];
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/detection.cpp b/engines/scumm/detection.cpp
index 9359c6610c..68d3010199 100644
--- a/engines/scumm/detection.cpp
+++ b/engines/scumm/detection.cpp
@@ -492,7 +492,7 @@ static bool testGame(const GameSettings *g, const DescMap &fileMD5Map, const Com
// Note that GF_OLD_BUNDLE is true if and only if GF_OLD256 is false.
// Candidates: maniac enhanced, zak enhanced, indy3ega, loom
- if (g->version != 2 && g->version != 3 || (g->features & GF_OLD256))
+ if ((g->version != 2 && g->version != 3) || (g->features & GF_OLD256))
return false;
/* We distinguish the games by the presence/absence of
@@ -949,7 +949,7 @@ SaveStateList ScummMetaEngine::listSaves(const char *target) const {
sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)
SaveStateList saveList;
- for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++) {
+ for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
// Obtain the last 2 digits of the filename, since they correspond to the save slot
int slotNum = atoi(file->c_str() + file->size() - 2);
diff --git a/engines/scumm/dialogs.cpp b/engines/scumm/dialogs.cpp
index 6d1cf1bbd8..e4e2b2b620 100644
--- a/engines/scumm/dialogs.cpp
+++ b/engines/scumm/dialogs.cpp
@@ -297,7 +297,7 @@ void SaveLoadChooser::handleCommand(CommandSender *sender, uint32 cmd, uint32 da
break;
case GUI::kListSelectionChangedCmd: {
if (_gfxWidget) {
- updateInfos();
+ updateInfos(true);
}
if (_saveMode) {
@@ -350,7 +350,7 @@ void SaveLoadChooser::reflowLayout() {
_fillR = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillR");
_fillG = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillG");
_fillB = g_gui.evaluator()->getVar("scummsaveload_thumbnail.fillB");
- updateInfos();
+ updateInfos(false);
} else {
_container->setFlags(GUI::WIDGET_INVISIBLE);
_gfxWidget->setFlags(GUI::WIDGET_INVISIBLE);
@@ -362,7 +362,7 @@ void SaveLoadChooser::reflowLayout() {
Dialog::reflowLayout();
}
-void SaveLoadChooser::updateInfos() {
+void SaveLoadChooser::updateInfos(bool redraw) {
int selItem = _list->getSelected();
Graphics::Surface *thumb;
thumb = _vm->loadThumbnailFromSlot(_saveMode ? selItem + 1 : selItem);
@@ -376,7 +376,8 @@ void SaveLoadChooser::updateInfos() {
}
delete thumb;
- _gfxWidget->draw();
+ if (redraw)
+ _gfxWidget->draw();
InfoStuff infos;
memset(&infos, 0, sizeof(InfoStuff));
@@ -386,12 +387,14 @@ void SaveLoadChooser::updateInfos() {
(infos.date >> 24) & 0xFF, (infos.date >> 16) & 0xFF,
infos.date & 0xFFFF);
_date->setLabel(buffer);
- _date->draw();
+ if (redraw)
+ _date->draw();
snprintf(buffer, 32, "Time: %.2d:%.2d",
(infos.time >> 8) & 0xFF, infos.time & 0xFF);
_time->setLabel(buffer);
- _time->draw();
+ if (redraw)
+ _time->draw();
int minutes = infos.playtime / 60;
int hours = minutes / 60;
@@ -400,19 +403,23 @@ void SaveLoadChooser::updateInfos() {
snprintf(buffer, 32, "Playtime: %.2d:%.2d",
hours & 0xFF, minutes & 0xFF);
_playtime->setLabel(buffer);
- _playtime->draw();
+ if (redraw)
+ _playtime->draw();
} else {
snprintf(buffer, 32, "No date saved");
_date->setLabel(buffer);
- _date->draw();
+ if (redraw)
+ _date->draw();
snprintf(buffer, 32, "No time saved");
_time->setLabel(buffer);
- _time->draw();
+ if (redraw)
+ _time->draw();
snprintf(buffer, 32, "No playtime saved");
_playtime->setLabel(buffer);
- _playtime->draw();
+ if (redraw)
+ _playtime->draw();
}
}
diff --git a/engines/scumm/dialogs.h b/engines/scumm/dialogs.h
index 7c99a0ebcc..0d04d8faea 100644
--- a/engines/scumm/dialogs.h
+++ b/engines/scumm/dialogs.h
@@ -69,7 +69,7 @@ protected:
uint8 _fillR, _fillG, _fillB;
- void updateInfos();
+ void updateInfos(bool redraw);
public:
SaveLoadChooser(const String &title, const String &buttonLabel, bool saveMode, ScummEngine *engine);
~SaveLoadChooser();
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/gfxARM.s b/engines/scumm/gfxARM.s
index cd3e5c7dad..83aaa78927 100644
--- a/engines/scumm/gfxARM.s
+++ b/engines/scumm/gfxARM.s
@@ -59,7 +59,7 @@ asmDrawStripToScreen:
CMP r1,#4 @ If width<4
BLT end @ return
- @ Width &= ~4 ? What's that about then? Width &= ~3 I could have
+ @ Width &= ~4 ? What''s that about then? Width &= ~3 I could have
@ understood...
BIC r1,r1,#4
diff --git a/engines/scumm/he/resource_he.cpp b/engines/scumm/he/resource_he.cpp
index 33e6748860..f8fb1efca2 100644
--- a/engines/scumm/he/resource_he.cpp
+++ b/engines/scumm/he/resource_he.cpp
@@ -522,12 +522,13 @@ int Win32ResExtractor::do_resources_recurs(WinLibrary *fi, WinResource *base,
/* get a list of all resources at this level */
wr = list_resources(fi, base, &rescnt);
- if (wr == NULL)
+ if (wr == NULL) {
if (size != 0)
return size;
else
return 0;
-
+ }
+
/* process each resource listed */
for (c = 0 ; c < rescnt ; c++) {
/* (over)write the corresponding WinResource holder with the current */
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 df3d857642..6c3d0023d8 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/imuse_digi/dimuse_sndmgr.cpp b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
index 1511b9aefc..b18b0ba70f 100644
--- a/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
+++ b/engines/scumm/imuse_digi/dimuse_sndmgr.cpp
@@ -102,10 +102,10 @@ void ImuseDigiSndMgr::prepareSoundFromRMAP(Common::File *file, SoundDesc *sound,
int32 version = file->readUint32BE();
if (version != 3) {
if (version == 2) {
- warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2.");
- warning("Suggested to recompress with latest tool from daily builds.");
+ warning("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version of compressed *.bun file, expected 3, but it's 2");
+ warning("Suggested to recompress with latest tool from daily builds");
} else
- error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d.", version);
+ error("ImuseDigiSndMgr::prepareSoundFromRMAP: Wrong version number, expected 3, but it's: %d", version);
}
sound->bits = file->readUint32BE();
sound->freq = file->readUint32BE();
diff --git a/engines/scumm/imuse_digi/dimuse_track.h b/engines/scumm/imuse_digi/dimuse_track.h
index 33147128cb..2d4c673cf6 100644
--- a/engines/scumm/imuse_digi/dimuse_track.h
+++ b/engines/scumm/imuse_digi/dimuse_track.h
@@ -85,13 +85,15 @@ struct Track {
int getPan() const { return (pan != 64) ? 2 * pan - 127 : 0; }
int getVol() const { return vol / 1000; }
Audio::Mixer::SoundType getType() const {
- Audio::Mixer::SoundType type = Audio::Mixer::kPlainSoundType;
+ Audio::Mixer::SoundType type;
if (volGroupId == 1)
type = Audio::Mixer::kSpeechSoundType;
else if (volGroupId == 2)
type = Audio::Mixer::kSFXSoundType;
else if (volGroupId == 3)
type = Audio::Mixer::kMusicSoundType;
+ else
+ error("Track::getType(): invalid sound type");
return type;
}
};
diff --git a/engines/scumm/resource.cpp b/engines/scumm/resource.cpp
index acdc2bc222..6bd62c1761 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 36b82519e9..f9e4eb415c 100644
--- a/engines/scumm/saveload.cpp
+++ b/engines/scumm/saveload.cpp
@@ -411,15 +411,15 @@ void ScummEngine::listSavegames(bool *marks, int num) {
char prefix[256];
char slot[3];
int slotNum;
- Common::StringList filenames;
+ Common::StringList files;
makeSavegameName(prefix, 99, false);
prefix[strlen(prefix)-2] = '*';
prefix[strlen(prefix)-1] = 0;
memset(marks, false, num * sizeof(bool)); //assume no savegames for this title
- filenames = _saveFileMan->listSavefiles(prefix);
+ files = _saveFileMan->listSavefiles(prefix);
- for (Common::StringList::const_iterator file = filenames.begin(); file != filenames.end(); file++){
+ for (Common::StringList::const_iterator file = files.begin(); file != files.end(); ++file) {
//Obtain the last 2 digits of the filename, since they correspond to the save slot
slot[0] = file->c_str()[file->size()-2];
slot[1] = file->c_str()[file->size()-1];
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/smush/codec47ARM.s b/engines/scumm/smush/codec47ARM.s
index 81bfdb2d22..73341c117f 100644
--- a/engines/scumm/smush/codec47ARM.s
+++ b/engines/scumm/smush/codec47ARM.s
@@ -80,7 +80,7 @@ level1codeFD:
LDRB r9,[r8,#384] @ r9 = l = tmp_ptr[384]
LDRB r6,[r1],#1 @ r6 = val = *_d_src++
ADD r12,r8,#384 @ r12= &tmp_ptr[384]
- @ I don't really believe the next 2 lines are necessary, but...
+ @ I don''t really believe the next 2 lines are necessary, but...
CMP r9,#0
BEQ level1codeFD_over1
level1codeFD_loop1:
@@ -94,7 +94,7 @@ level1codeFD_over1:
LDRB r9,[r12,#1] @ r9 = l = tmp_ptr[385]
LDRB r6,[r1],#1 @ r6 = val = *_d_src++
SUB r12,r12,#256 @ r12= &tmp_ptr[128] (256 = 384-128)
- @ I don't really believe the next 2 lines are necessary, but...
+ @ I don''t really believe the next 2 lines are necessary, but...
CMP r9,#0
BEQ level1codeFD_over2
level1codeFD_loop2:
@@ -219,7 +219,7 @@ level2codeFD:
LDRB r9,[r8,#96] @ r9 = l = tmp_ptr[96]
LDRB r6,[r1],#1 @ r6 = val = *_d_src++
ADD r12,r8,#32 @ r12 = tmp_ptr + 32
- @ I don't really believe the next 2 lines are necessary, but...
+ @ I don''t really believe the next 2 lines are necessary, but...
CMP r9,#0
BEQ level2codeFD_over1
level2codeFD_loop1:
@@ -232,7 +232,7 @@ level2codeFD_loop1:
level2codeFD_over1:
LDRB r9,[r12,#65] @ r9 = l = tmp_ptr[97] (65 = 97-32)
LDRB r6,[r1],#1 @ r6 = val = *_d_src++
- @ I don't really believe the next 2 lines are necessary, but...
+ @ I don''t really believe the next 2 lines are necessary, but...
CMP r9,#0
MOVEQ PC,R14
level2codeFD_loop2:
diff --git a/engines/scumm/sound.cpp b/engines/scumm/sound.cpp
index fdd0598378..7500b16c87 100644
--- a/engines/scumm/sound.cpp
+++ b/engines/scumm/sound.cpp
@@ -89,6 +89,7 @@ Sound::Sound(ScummEngine *parent, Audio::Mixer *mixer)
Sound::~Sound() {
stopCDTimer();
+ AudioCD.destroy();
delete _sfxFile;
}
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/music/adlibmusic.cpp b/engines/sky/music/adlibmusic.cpp
index 7c2b262d82..4434f4cd68 100644
--- a/engines/sky/music/adlibmusic.cpp
+++ b/engines/sky/music/adlibmusic.cpp
@@ -47,6 +47,7 @@ AdlibMusic::AdlibMusic(Audio::Mixer *pMixer, Disk *pDisk)
AdlibMusic::~AdlibMusic(void) {
+ OPLDestroy(_opl);
_mixer->stopHandle(_soundHandle);
}
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/music.cpp b/engines/sword2/music.cpp
index fd72ba8d52..3b5a09578b 100644
--- a/engines/sword2/music.cpp
+++ b/engines/sword2/music.cpp
@@ -52,9 +52,11 @@ namespace Sword2 {
static Audio::AudioStream *makeCLUStream(Common::File *fp, int size);
static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base, int cd, uint32 id, uint32 *numSamples) {
- debug(3, "Playing %s from CD %d", base, cd);
+ bool alreadyOpen;
if (!fh->file.isOpen()) {
+ alreadyOpen = false;
+
struct {
const char *ext;
int mode;
@@ -75,16 +77,14 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,
char filename[20];
for (int i = 0; i < ARRAYSIZE(file_types); i++) {
- Common::File f;
-
sprintf(filename, "%s%d.%s", base, cd, file_types[i].ext);
- if (f.open(filename)) {
+ if (Common::File::exists(filename)) {
soundMode = file_types[i].mode;
break;
}
sprintf(filename, "%s.%s", base, file_types[i].ext);
- if (f.open(filename)) {
+ if (Common::File::exists(filename)) {
soundMode = file_types[i].mode;
break;
}
@@ -105,7 +105,8 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,
fh->idxTab = NULL;
}
}
- }
+ } else
+ alreadyOpen = true;
uint32 entrySize = (fh->fileType == kCLUMode) ? 2 : 3;
@@ -134,7 +135,13 @@ static Audio::AudioStream *getAudioStream(SoundFileHandle *fh, const char *base,
*numSamples = len;
if (!pos || !len) {
- fh->file.close();
+ // We couldn't find the sound. Possibly as a result of a bad
+ // installation (e.g. using the music file from CD 2 as the
+ // first music file). Don't close the file if it was already
+ // open though, because something is playing from it.
+ warning("getAudioStream: Could not find %s ID %d! Possibly the wrong file", base, id);
+ if (!alreadyOpen)
+ fh->file.close();
return NULL;
}
diff --git a/engines/sword2/resman.cpp b/engines/sword2/resman.cpp
index d6b8025cda..8cddddff78 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/sword2/sound.h b/engines/sword2/sound.h
index 70bae6f851..b89ef8f12b 100644
--- a/engines/sword2/sound.h
+++ b/engines/sword2/sound.h
@@ -106,7 +106,7 @@ private:
void refill();
inline bool eosIntern() const {
- return _pos >= _bufferEnd;
+ return !_file->isOpen() || _pos >= _bufferEnd;
}
public:
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..a638dde2c5
--- /dev/null
+++ b/engines/tinsel/detection.cpp
@@ -0,0 +1,278 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * $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[] = {
+
+ // Note: versions with *.gra files use tinsel v1 (28/2/1995), whereas
+ // versions with *.scn files tinsel v2 (7/5/1995)
+ // Update: this is not entirely true, there were some versions released
+ // with *.gra files and used tinsel v2
+
+ {
+ { // This version has *.gra files but uses tinsel v2
+ "dw",
+ "Floppy",
+ AD_ENTRY1s("dw.gra", "c8808ccd988d603dd35dff42013ae7fd", 781656),
+ Common::EN_ANY,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GID_DW1,
+ 0,
+ GF_FLOPPY,
+ TINSEL_V2,
+ },
+
+ { // English CD v1. This version has *.gra files but uses tinsel v2
+ {
+ "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_V2,
+ },
+
+ { // English CD v2
+ {
+ "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_V2,
+ },
+
+#if 0
+ { // English Saturn CD
+ {
+ "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_V2,
+ },
+#endif
+
+ { // 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_V1,
+ },
+
+ { // 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_V2,
+ },
+
+ { 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..b14b1c5962
--- /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_V1 &&
+ 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..9ffadfe8c1
--- /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_V1 = 1 << 0,
+ TINSEL_V2 = 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/engines/touche/midi.cpp b/engines/touche/midi.cpp
index 14cb85912a..d77dbf5bfa 100644
--- a/engines/touche/midi.cpp
+++ b/engines/touche/midi.cpp
@@ -23,6 +23,7 @@
*
*/
+#include "common/config-manager.h"
#include "common/stream.h"
#include "sound/midiparser.h"
@@ -31,9 +32,8 @@
namespace Touche {
-MidiPlayer::MidiPlayer(MidiDriver *driver, bool nativeMT32)
- : _driver(driver), _parser(0), _midiData(0), _isLooping(false), _isPlaying(false), _masterVolume(0), _nativeMT32(nativeMT32) {
- assert(_driver);
+MidiPlayer::MidiPlayer()
+ : _driver(0), _parser(0), _midiData(0), _isLooping(false), _isPlaying(false), _masterVolume(0) {
memset(_channelsTable, 0, sizeof(_channelsTable));
memset(_channelsVolume, 0, sizeof(_channelsVolume));
open();
@@ -92,6 +92,9 @@ void MidiPlayer::setVolume(int volume) {
}
int MidiPlayer::open() {
+ int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
+ _nativeMT32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
+ _driver = MidiDriver::createMidi(midiDriver);
int ret = _driver->open();
if (ret == 0) {
_parser = MidiParser::createParser_SMF();
@@ -107,6 +110,7 @@ void MidiPlayer::close() {
_mutex.lock();
_driver->setTimerCallback(NULL, NULL);
_driver->close();
+ delete _driver;
_driver = 0;
_parser->setMidiDriver(NULL);
delete _parser;
diff --git a/engines/touche/midi.h b/engines/touche/midi.h
index 3b128593db..a518a4bb29 100644
--- a/engines/touche/midi.h
+++ b/engines/touche/midi.h
@@ -46,7 +46,7 @@ public:
NUM_CHANNELS = 16
};
- MidiPlayer(MidiDriver *driver, bool nativeMT32);
+ MidiPlayer();
~MidiPlayer();
void play(Common::ReadStream &stream, int size, bool loop = false);
diff --git a/engines/touche/saveload.cpp b/engines/touche/saveload.cpp
index eb647a1b42..4fcf6e114d 100644
--- a/engines/touche/saveload.cpp
+++ b/engines/touche/saveload.cpp
@@ -200,9 +200,6 @@ static void saveOrLoad(S &s, ProgramPointData &data) {
saveOrLoad(s, data.order);
}
-template <class S, class A>
-static void saveOrLoadCommonArray(S &s, A &array);
-
template <class A>
static void saveOrLoadCommonArray(Common::WriteStream &stream, A &array) {
uint count = array.size();
diff --git a/engines/touche/touche.cpp b/engines/touche/touche.cpp
index 6520fb5e4a..ac8e8a786a 100644
--- a/engines/touche/touche.cpp
+++ b/engines/touche/touche.cpp
@@ -91,10 +91,7 @@ int ToucheEngine::init() {
setupOpcodes();
- int midiDriver = MidiDriver::detectMusicDriver(MDT_MIDI | MDT_ADLIB | MDT_PREFER_MIDI);
- bool native_mt32 = ((midiDriver == MD_MT32) || ConfMan.getBool("native_mt32"));
- MidiDriver *driver = MidiDriver::createMidi(midiDriver);
- _midiPlayer = new MidiPlayer(driver, native_mt32);
+ _midiPlayer = new MidiPlayer;
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
_mixer->setVolumeForSoundType(Audio::Mixer::kSpeechSoundType, ConfMan.getInt("speech_volume"));