diff --git a/AUTHORS b/AUTHORS index 2491f481a22..d7afb06dc9e 100644 --- a/AUTHORS +++ b/AUTHORS @@ -94,10 +94,19 @@ ScummVM Team SAGA: Torbjorn Andersson + Sven Hesse Filippos Karapetis Andrew Kurushin Eugene Sandulenko + Tinsel;: + Torbjorn Andersson + Paul Gilbert + Sven Hesse + Max Horn + Filippos Karapetis + Joost Peters + Touche: Gregory Montoir diff --git a/Makefile b/Makefile index 506265032a3..2395511ee38 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ CXXFLAGS:= -Wall $(CXXFLAGS) CXXFLAGS+= -Wno-long-long -Wno-multichar -Wno-unknown-pragmas -Wno-reorder # Enable even more warnings... CXXFLAGS+= -pedantic -Wpointer-arith -Wcast-qual -Wcast-align -CXXFLAGS+= -Wshadow -Wimplicit -Wundef -Wnon-virtual-dtor -Wwrite-strings +CXXFLAGS+= -Wshadow -Wimplicit -Wnon-virtual-dtor -Wwrite-strings # Disable RTTI and exceptions, and enabled checking of pointers returned by "new" CXXFLAGS+= -fno-rtti -fno-exceptions -fcheck-new diff --git a/NEWS b/NEWS index 6a3a59a49b5..eba57b9b43b 100644 --- a/NEWS +++ b/NEWS @@ -1,27 +1,44 @@ For a more comprehensive changelog for the latest experimental SVN code, see: http://scummvm.sourceforge.net/daily/ChangeLog +0.13.0 (????-??-??) + New Games: + - Added support for Discworld. + 0.12.0 (????-??-??) New Games: - - Added support for The Legend of Kyrandia: Book Two: Hand of Fate - - Added support for The Legend of Kyrandia: Book Three: Malcolm's Revenge - - Added support for Lost in Time - - Added support for The Bizarre Adventures of Woodruff and the Schnibble - - Added support for the PC version of Waxworks + - Added support for The Legend of Kyrandia: Book Two: Hand of Fate. + - Added support for The Legend of Kyrandia: Book Three: Malcolm's Revenge. + - Added support for Lost in Time. + - Added support for The Bizarre Adventures of Woodruff and the Schnibble. + - Added support for the PC version of Waxworks. - Added support for the Macintosh version of I Have no Mouth, and I - must Scream + must Scream. + - Added support for Drascula: The Vampire Strikes Back. General: - Added CAMD MIDI driver for AmigaOS4. - Revived the PS2 port (was already in 0.11.1 but was forgotten in the release notes). + - Plugged numerous memory leaks in all engines (part of GSoC'08 task), + - Added audio double buffering to the SDL backend, which fixes the + problems with the MT-32 emulator on Mac OS X (for now only enabled + on Mac OS X). AGOS: + - Fixed crashes during certain music in Amiga versions of Elvira 1 and + Simon the Sorcerer 1. - Fixed palette issues in Amiga versions of Simon the Sorcerer 1. + Queen: + - Speech is played at the correct sample rate. (It used to be pitched a bit + too low.) + SCUMM: - Rewrote parts of Digital iMUSE, fixing some bugs. - Rewrote the internal timer code, fixing some speed issues in e.g. COMI. + - Improved support for sound effects in Amiga version of Zak McKracken. + - Added support for mixed Adlib/MIDI mode in Monkey Island 1 (Floppy). 0.11.1 (2008-02-29) SCUMM: @@ -673,9 +690,10 @@ For a more comprehensive changelog for the latest experimental SVN code, see: OS X. - Loading COMI savegames for disk 2 doesn't anymore require disk 1 first. - Rewritten iMUSE engine, and many Music fixes (exp. Monkey Island 2). -- Support for music in Humongous games and simon2dos/simon2talkie (XMIDI +- Support for music in DOS versions of Humongous Entertainment games and + Simon the Sorcerer 2 (XMIDI format). +- Support for music in floppy demo of Simon the Sorcerer 1 (Proprietary format). -- Support for music in simon1demo (Proprietary format). - Complete music support for Simon the Sorcerer 2. - Improved music and sound support in Zak256. - Added Aspect Ratio option. diff --git a/backends/fs/windows/windows-fs.cpp b/backends/fs/windows/windows-fs.cpp index cbb93e8cd6a..ac2f521e219 100644 --- a/backends/fs/windows/windows-fs.cpp +++ b/backends/fs/windows/windows-fs.cpp @@ -245,7 +245,7 @@ WindowsFilesystemNode::WindowsFilesystemNode(const String &p, const bool current _isDirectory = ((fileAttribs & FILE_ATTRIBUTE_DIRECTORY) != 0); _isValid = true; // Add a trailing slash, if necessary. - if (_path.lastChar() != '\\') { + if (_isDirectory && _path.lastChar() != '\\') { _path += '\\'; } } diff --git a/backends/midi/quicktime.cpp b/backends/midi/quicktime.cpp index 568adf022b0..a18eadcb301 100644 --- a/backends/midi/quicktime.cpp +++ b/backends/midi/quicktime.cpp @@ -132,8 +132,7 @@ bail: return MERR_DEVICE_NOT_AVAILABLE; } -void MidiDriver_QT::close() -{ +void MidiDriver_QT::close() { MidiDriver_MPU401::close(); dispose(); } @@ -248,8 +247,7 @@ void MidiDriver_QT::setPitchBendRange (byte channel, uint range) { NASetController(qtNoteAllocator, qtNoteChannel[channel], kControllerPitchBend, theBend); } -void MidiDriver_QT::dispose() -{ +void MidiDriver_QT::dispose() { for (int i = 0; i < 16; i++) { if (qtNoteChannel[i] != 0) NADisposeNoteChannel(qtNoteAllocator, qtNoteChannel[i]); diff --git a/backends/platform/dc/Makefile b/backends/platform/dc/Makefile index 0a048c45e91..db5861903bf 100644 --- a/backends/platform/dc/Makefile +++ b/backends/platform/dc/Makefile @@ -16,6 +16,7 @@ LDFLAGS = -Wl,-Ttext,0x8c010000 -nostartfiles $(ronindir)/lib/crt0.o INCLUDES= -I./ -I$(srcdir) -I$(ronindir)/include/ -I$(srcdir)/engines LIBS = -L$(ronindir)/lib -lmad -lronin -lz -lm EXECUTABLE = scummvm.elf +DEPDIR = .deps PLUGIN_PREFIX = PLUGIN_SUFFIX = .plg PLUGIN_EXTRA_DEPS = plugin.x plugin.syms scummvm.elf @@ -32,12 +33,37 @@ ifdef DYNAMIC_MODULES DEFINES += -DDYNAMIC_MODULES PRE_OBJS_FLAGS = -Wl,--whole-archive POST_OBJS_FLAGS = -Wl,--no-whole-archive +ENABLED=DYNAMIC_PLUGIN +else +ENABLED=STATIC_PLUGIN endif +ENABLE_SCUMM = $(ENABLED) +ENABLE_SCUMM_7_8 = $(ENABLED) +ENABLE_HE = $(ENABLED) +ENABLE_AGI = $(ENABLED) +ENABLE_AGOS = $(ENABLED) +ENABLE_CINE = $(ENABLED) +ENABLE_CRUISE = $(ENABLED) +ENABLE_DRASCULA = $(ENABLED) +ENABLE_GOB = $(ENABLED) +ENABLE_IGOR = $(ENABLED) +ENABLE_KYRA = $(ENABLED) +ENABLE_LURE = $(ENABLED) +ENABLE_M4 = $(ENABLED) +ENABLE_MADE = $(ENABLED) +ENABLE_PARALLACTION = $(ENABLED) +ENABLE_QUEEN = $(ENABLED) +ENABLE_SAGA = $(ENABLED) +ENABLE_SKY = $(ENABLED) +ENABLE_SWORD1 = $(ENABLED) +ENABLE_SWORD2 = $(ENABLED) +ENABLE_TOUCHE = $(ENABLED) + OBJS := dcmain.o time.o display.o audio.o input.o selector.o icon.o \ label.o vmsave.o softkbd.o dcloader.o cache.o dc-fs.o -MODULE_DIRS += . +MODULE_DIRS += ./ include $(srcdir)/Makefile.common @@ -49,7 +75,7 @@ SCUMMVM.BIN : scummvm.bin plugin_dist : for p in plugins/*.plg; do \ - sh-elf-strip -g -o "`basename \"$$p\" | tr '[:lower:]' '[:upper:]'`" "$$p"; \ + sh-elf-strip -g -o "`basename \"$$p\" | LC_CTYPE=C tr '[:lower:]' '[:upper:]'`" "$$p"; \ done dist : SCUMMVM.BIN plugins plugin_dist diff --git a/backends/platform/dc/audio.cpp b/backends/platform/dc/audio.cpp index 5f95a836e82..b5667e74b02 100644 --- a/backends/platform/dc/audio.cpp +++ b/backends/platform/dc/audio.cpp @@ -25,17 +25,18 @@ #include #include "engines/engine.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include "dc.h" EXTERN_C void *memcpy4s(void *s1, const void *s2, unsigned int n); -void initSound() +uint OSystem_Dreamcast::initSound() { stop_sound(); do_sound_command(CMD_SET_FREQ_EXP(FREQ_22050_EXP)); do_sound_command(CMD_SET_STEREO(1)); do_sound_command(CMD_SET_BUFFER(SOUND_BUFFER_SHIFT)); + return read_sound_int(&SOUNDSTATUS->freq); } void OSystem_Dreamcast::checkSound() @@ -61,8 +62,8 @@ void OSystem_Dreamcast::checkSound() if (n<100) return; - Audio::Mixer::mixCallback(_mixer, (byte*)temp_sound_buffer, - 2*SAMPLES_TO_BYTES(n)); + _mixer->mixCallback((byte*)temp_sound_buffer, + 2*SAMPLES_TO_BYTES(n)); if (fillpos+n > curr_ring_buffer_samples) { int r = curr_ring_buffer_samples - fillpos; @@ -77,8 +78,4 @@ void OSystem_Dreamcast::checkSound() fillpos = 0; } -int OSystem_Dreamcast::getOutputSampleRate() const -{ - return read_sound_int(&SOUNDSTATUS->freq); -} diff --git a/backends/platform/dc/dc.h b/backends/platform/dc/dc.h index b7494a77bcf..9dfd4c331e5 100644 --- a/backends/platform/dc/dc.h +++ b/backends/platform/dc/dc.h @@ -28,6 +28,7 @@ #include #include "backends/timer/default/default-timer.h" #include "backends/fs/fs-factory.h" +#include "sound/mixer_intern.h" #define NUM_BUFFERS 4 #define SOUND_BUFFER_SHIFT 3 @@ -195,7 +196,7 @@ class OSystem_Dreamcast : public OSystem, public FilesystemFactory { private: Common::SaveFileManager *_savefile; - Audio::Mixer *_mixer; + Audio::MixerImpl *_mixer; DefaultTimerManager *_timer; SoftKeyboard _softkbd; @@ -223,6 +224,7 @@ class OSystem_Dreamcast : public OSystem, public FilesystemFactory { int temp_sound_buffer[RING_BUFFER_SAMPLES>>SOUND_BUFFER_SHIFT]; + uint initSound(); void checkSound(); void drawMouse(int xdraw, int ydraw, int w, int h, @@ -237,6 +239,5 @@ class OSystem_Dreamcast : public OSystem, public FilesystemFactory { extern int handleInput(struct mapledev *pad, int &mouse_x, int &mouse_y, byte &shiftFlags, Interactive *inter = NULL); -extern void initSound(); extern bool selectGame(char *&, char *&, class Icon &); diff --git a/backends/platform/dc/dcmain.cpp b/backends/platform/dc/dcmain.cpp index 913bc9948eb..0b114bee101 100644 --- a/backends/platform/dc/dcmain.cpp +++ b/backends/platform/dc/dcmain.cpp @@ -33,7 +33,7 @@ #include #include "backends/plugins/dc/dc-provider.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" Icon icon; @@ -54,8 +54,10 @@ OSystem_Dreamcast::OSystem_Dreamcast() void OSystem_Dreamcast::initBackend() { _savefile = createSavefileManager(); - _mixer = new Audio::Mixer(); + _mixer = new Audio::MixerImpl(this); _timer = new DefaultTimerManager(); + _mixer->setOutputRate(initSound()); + _mixer->setReady(true); } @@ -216,7 +218,6 @@ int main() static int argc = 1; dc_init_hardware(); - initSound(); g_system = new OSystem_Dreamcast(); assert(g_system); diff --git a/backends/platform/dc/selector.cpp b/backends/platform/dc/selector.cpp index 880fbc400f7..91c851506f3 100644 --- a/backends/platform/dc/selector.cpp +++ b/backends/platform/dc/selector.cpp @@ -25,7 +25,9 @@ #include #include +#include #include +#include #include #include #include "dc.h" @@ -149,15 +151,6 @@ struct Dir static Game the_game; -static void detectGames(FSList &files, GameList &candidates) -{ - const EnginePluginList &plugins = EngineMan.getPlugins(); - EnginePluginList::const_iterator iter = plugins.begin(); - for (iter = plugins.begin(); iter != plugins.end(); ++iter) { - candidates.push_back((*iter)->detectGames(files)); - } -} - static bool isIcon(const FilesystemNode &entry) { int l = entry.getDisplayName().size(); @@ -227,8 +220,7 @@ static int findGames(Game *games, int max) files.push_back(*entry); } - GameList candidates; - detectGames(files, candidates); + GameList candidates = EngineMan.detectGames(files); for (GameList::const_iterator ge = candidates.begin(); ge != candidates.end(); ++ge) diff --git a/backends/platform/ds/arm9/source/dsmain.cpp b/backends/platform/ds/arm9/source/dsmain.cpp index a130509e365..f4706807f70 100644 --- a/backends/platform/ds/arm9/source/dsmain.cpp +++ b/backends/platform/ds/arm9/source/dsmain.cpp @@ -168,7 +168,7 @@ bool displayModeIs8Bit = false; u8 gameID; bool snapToBorder = false; -bool consoleEnable = false; +bool consoleEnable = true; bool gameScreenSwap = false; bool isCpuScalerEnabled(); //#define HEAVY_LOGGING @@ -899,12 +899,6 @@ u16* get8BitBackBuffer() { return BG_GFX + 0x10000; // 16bit qty! } -void setSoundProc(OSystem_DS::SoundProc proc, void* param) { -// consolePrintf("Set sound callback"); - soundCallback = proc; - soundParam = param; -} - // The sound system in ScummVM seems to always return stereo interleaved samples. // Here, I'm treating an 11Khz stereo stream as a 22Khz mono stream, which works sorta ok, but is // a horrible bodge. Any advice on how to change the engine to output mono would be greatly @@ -914,7 +908,8 @@ void doSoundCallback() { consolePrintf("doSoundCallback..."); #endif - if (soundCallback) { + if (OSystem_DS::instance()) + if (OSystem_DS::instance()->getMixerImpl()) { lastCallbackFrame = frameCount; for (int r = IPC->playingSection; r < IPC->playingSection + 4; r++) { @@ -923,7 +918,7 @@ void doSoundCallback() { if (IPC->fillNeeded[chunk]) { IPC->fillNeeded[chunk] = false; DC_FlushAll(); - soundCallback(soundParam, (byte *) (soundBuffer + ((bufferSamples >> 2) * chunk)), bufferSamples >> 1); + OSystem_DS::instance()->getMixerImpl()->mixCallback((byte *) (soundBuffer + ((bufferSamples >> 2) * chunk)), bufferSamples >> 1); IPC->fillNeeded[chunk] = false; DC_FlushAll(); } diff --git a/backends/platform/ds/arm9/source/dsmain.h b/backends/platform/ds/arm9/source/dsmain.h index f20442b11e5..43258b5c5dc 100644 --- a/backends/platform/ds/arm9/source/dsmain.h +++ b/backends/platform/ds/arm9/source/dsmain.h @@ -88,7 +88,6 @@ int getMillis(); // Return the current runtime in milliseconds void doTimerCallback(); // Call callback function if required // Sound -void setSoundProc(OSystem_DS::SoundProc proc, void* param); // Setup a callback function for sound void doSoundCallback(); // Call function if sound buffers need more data void playSound(const void* data, u32 length, bool loop, bool adpcm = false, int rate = 22050); // Start a sound void stopSound(int channel); diff --git a/backends/platform/ds/arm9/source/osystem_ds.cpp b/backends/platform/ds/arm9/source/osystem_ds.cpp index 6e6b4571158..79b0c5390bc 100644 --- a/backends/platform/ds/arm9/source/osystem_ds.cpp +++ b/backends/platform/ds/arm9/source/osystem_ds.cpp @@ -67,10 +67,12 @@ void OSystem_DS::initBackend() { ConfMan.setInt("autosave_period", 0); ConfMan.setBool("FM_medium_quality", true); - _mixer = new DSAudioMixer; - _timer = new DSTimerManager; - DS::setSoundProc(Audio::Mixer::mixCallback, _mixer); - DS::setTimerCallback(&OSystem_DS::timerHandler, 10); + _mixer = new DSAudioMixer(this); + _timer = new DSTimerManager(); + DS::setTimerCallback(&OSystem_DS::timerHandler, 10); + + _mixer->setOutputRate(11025 /*DS::getSoundFrequency()*/); + _mixer->setReady(true); OSystem::initBackend(); } @@ -139,7 +141,7 @@ void OSystem_DS::setPalette(const byte *colors, uint start, uint num) { green >>= 3; blue >>= 3; -// if (r != 255) + if (r != 255) { BG_PALETTE[r] = red | (green << 5) | (blue << 10); if (!DS::getKeyboardEnable()) { @@ -158,13 +160,13 @@ bool OSystem_DS::grabRawScreen(Graphics::Surface* surf) { // Ensure we copy using 16 bit quantities due to limitation of VRAM addressing - u16* image = (u16 *) DS::get8BitBackBuffer(); + const u16* image = (const u16 *) DS::get8BitBackBuffer(); for (int y = 0; y < DS::getGameHeight(); y++) { DC_FlushRange(image + (y << 8), DS::getGameWidth()); for (int x = 0; x < DS::getGameWidth() >> 1; x++) { - *(((u16 *) (surf->pixels)) + y * (DS::getGameWidth() >> 1) + x) = image[y << 8 + x]; + *(((u16 *) (surf->pixels)) + y * (DS::getGameWidth() >> 1) + x) = image[(y << 8) + x]; } } @@ -277,7 +279,7 @@ void OSystem_DS::grabOverlay (OverlayColor *buf, int pitch) { void OSystem_DS::copyRectToOverlay (const OverlayColor *buf, int pitch, int x, int y, int w, int h) { u16* bg = (u16 *) DS::get16BitBackBuffer(); - u16* src = (u16 *) buf; + const u16* src = (const u16 *) buf; // if (x + w > 256) w = 256 - x; //if (x + h > 256) h = 256 - y; @@ -433,13 +435,7 @@ void OSystem_DS::unlockMutex(MutexRef mutex) { void OSystem_DS::deleteMutex(MutexRef mutex) { } -void OSystem_DS::clearSoundCallback() { -// consolePrintf("Clearing sound callback"); -// DS::setSoundProc(NULL, NULL); -} - -int OSystem_DS::getOutputSampleRate() const -{ +int OSystem_DS::getOutputSampleRate() const { return DS::getSoundFrequency(); } diff --git a/backends/platform/ds/arm9/source/osystem_ds.h b/backends/platform/ds/arm9/source/osystem_ds.h index 246797188fe..8c8d661ad81 100644 --- a/backends/platform/ds/arm9/source/osystem_ds.h +++ b/backends/platform/ds/arm9/source/osystem_ds.h @@ -30,10 +30,13 @@ #include "gbampsave.h" #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include "graphics/surface.h" -class DSAudioMixer : public Audio::Mixer { +class DSAudioMixer : public Audio::MixerImpl { + +public: + DSAudioMixer(OSystem* system) : Audio::MixerImpl(system) { } }; class DSTimerManager : public DefaultTimerManager { @@ -62,7 +65,7 @@ protected: Graphics::Surface* createTempFrameBuffer(); public: - typedef void (*SoundProc)(void *param, byte *buf, int len); + typedef void (*SoundProc)(byte *buf, int len); typedef int (*TimerProc)(int interval); OSystem_DS(); @@ -114,7 +117,6 @@ public: virtual void unlockMutex(MutexRef mutex); virtual void deleteMutex(MutexRef mutex); - virtual void clearSoundCallback(); virtual int getOutputSampleRate() const; virtual bool openCD(int drive); @@ -147,6 +149,8 @@ public: virtual void unlockScreen(); virtual Audio::Mixer* getMixer() { return _mixer; } + Audio::MixerImpl* getMixerImpl() { return _mixer; } + virtual Common::TimerManager* getTimerManager() { return _timer; } static int timerHandler(int t); diff --git a/backends/platform/ds/arm9/source/wordcompletion.cpp b/backends/platform/ds/arm9/source/wordcompletion.cpp index 9eeeb62410b..ff52572a40d 100644 --- a/backends/platform/ds/arm9/source/wordcompletion.cpp +++ b/backends/platform/ds/arm9/source/wordcompletion.cpp @@ -1,6 +1,6 @@ #include "wordcompletion.h" -#include "engines/agi/agi.h" #include "osystem_ds.h" +#include "engines/agi/agi.h" // Caution for #define for NUM_CHANNELS, causes problems in mixer_intern.h #ifdef ENABLE_AGI diff --git a/backends/platform/gp2x/build/README-GP2X b/backends/platform/gp2x/build/README-GP2X index cb1d043500c..7e10fc484ed 100644 --- a/backends/platform/gp2x/build/README-GP2X +++ b/backends/platform/gp2x/build/README-GP2X @@ -1,4 +1,4 @@ -ScummVM - GP2X SPECIFIC README - 0.10.0 SVN +ScummVM - GP2X SPECIFIC README - HEAD SVN ------------------------------------------------------------------------ Contents: diff --git a/backends/platform/gp2x/build/README-GP2X.html b/backends/platform/gp2x/build/README-GP2X.html index 5289d3d36c6..1b5f1a4173a 100644 --- a/backends/platform/gp2x/build/README-GP2X.html +++ b/backends/platform/gp2x/build/README-GP2X.html @@ -2,12 +2,15 @@ ScummVM - GP2X SPECIFIC README + + -ScummVM - GP2X SPECIFIC README - 0.10.0 SVN
+ScummVM - +GP2X SPECIFIC README - HEAD SVN


@@ -16,52 +19,65 @@ +
+
About the backend/port

-This is the readme for the offficial GP2X ScummVM backend (also known as the GP2X port).
+This is the readme for the offficial GP2X ScummVM backend (also known +as the GP2X port).

-This is an SVN test release of ScummVM for the GP2X, it would be appreciated +This is an SVN test release of ScummVM for the GP2X, it would be +appreciated if this SVN test distribution was not mirrored and that people be -directed to http://www.distant-earth.com/scummvm instead for updated SVN builds.
+directed to http://www.distant-earth.com/scummvm +instead for updated SVN builds.

Full supported official releases of the GP2X ScummVM backend are made -in line with main official releases and are avalalble from the ScummVM downloads page.
+in line with main official releases and are avalalble from the ScummVM +downloads page.

-This build is in an active state of development and as such no ‘expected’ behavior can be guaranteed ;).
+This build is in an active state of development and as such no +‘expected’ behavior can be guaranteed ;).

@@ -69,47 +85,59 @@ SVN builds are quickly tested with firmware 2.0.0 for reference.

-Please refer to the GP2X ScummVM forum and WiKi for the latest information on the port.
+Please refer to the GP2X +ScummVM forum and WiKi +for the latest information on the port.
+

Game compatibility

-For information on the compatability of a specific game please refer to the GP2X compatability section of the ScummVM WiKi.
+For information on the compatability of a specific game please refer to +the GP2X +compatability section of the ScummVM WiKi.

-Please note the version and date of the ScummVM build you are running when reviewing the above list.
+Please note the version and date of the ScummVM build you are running +when reviewing the above list.


Included engines

+ Just because an engine is included does not mean any/all of its games are supported. Please check game compatability for more infomation.
+
    -
  • -Scumm - (All games supported by ScummVM should work to some extent, using the hardware scalar if needed)
  • + +
  • Scumm - (All games supported by ScummVM should work to some +extent, using the hardware scalar if needed)
  • +
  • AGOS (AKA Simon) - (Simon the Sorcerer one and two).
  • +
  • Sky - (Beneath a Steel Sky)
  • -
  • -Sword - (Broken Sword 1) - This engine uses the hardware scalar to + +
  • Sword - (Broken Sword 1) - This engine uses the hardware +scalar to downsize the graphics to fix on the GP2X. It is NOT very nice to look at.
  • -
  • -Sword2 - (Broken Sword 2) - This engine uses the hardware + +
  • Sword2 - (Broken Sword 2) - This engine uses the hardware scalar to downsize the graphics to fix on the GP2X. It is NOT very nice to look at.
  • -
  • -Gob - (Goblins one)
  • -
  • -Queen - (Flight of the Amazon Queen)
  • -
  • -Kyra - (The Legend of Kyrandia)
  • -
+
  • Gob - (Goblins one)
  • + +
  • Queen - (Flight of the Amazon Queen)
  • + +
  • Kyra - (The Legend of Kyrandia)
  • + + All other game engines are disabled in this release.
    @@ -155,13 +183,18 @@ DXA video support will be added as soon as it is stable.
    Enabled hardware scalar code.
    -Now built using SDL 1.2.9 for the parts of the port that use SDL (some parts now hit the hardware directly).
    +Now built using SDL 1.2.9 for the parts of the port that use SDL (some +parts now hit the hardware directly).
    -Enabled new launcher - (Ensure defaulttheme.zip is in the same folder as the executable).
    +Enabled new launcher - (Ensure defaulttheme.zip is in the same folder +as the executable).
    -Aspect Ratio Correction can now be disabled ‘per game’. When adding a game you can find this option on the GFX tab.
    +Aspect Ratio Correction can now be disabled ‘per +game’. When adding a game you can find this option on the GFX +tab.
    -Note: This will cause the game to run with a black border at the bottom as it will be rendered to a 320*200 frame.
    +Note: This will cause the game to run with a black border at the bottom +as it will be rendered to a 320*200 frame.

    @@ -169,7 +202,9 @@ Note: This will cause the game to run with a black border at the bottom as it wi
    -NOTE: Everything is saved to the SD card, saves are stored in the saves folder under your main ScummVM executable unless you set another save location.
    +NOTE: +Everything is saved to the SD card, saves are stored in the saves folder under +your main ScummVM executable unless you set another save location.

    @@ -178,7 +213,8 @@ same place as the ScummVM executable.

    -The save process below is for Scumm engine games but the principle is the same for all.
    +The save process below is for Scumm engine games but the +principle is the same for all.

    @@ -198,11 +234,13 @@ In Game.

    -Basically the emulated keys you can use are equivelent to the values buttons are mapped to,
    +Basically the emulated keys you can use are equivelent to the values +buttons are mapped to,

    -I have a virtual keyboard like the GP32 one (left/right on the stick to pick chars) to add in at some point ;-)
    +I have a virtual keyboard like the GP32 one (left/right on the stick to +pick chars) to add in at some point ;-)

    @@ -262,9 +300,11 @@ Select: Exit ScummVM completely (and gracefully)

    -Possible random crash (well SegFault). I have had this happen twice and have not tracked down the cause.
    +Possible random crash (well SegFault). I have had this happen twice and +have not tracked down the cause.
    -It happens very infrequently, both times it was in the DOTT CD intro. Saving often is never a bad idea anyhow.
    +It happens very infrequently, both times it was in the DOTT CD intro. +Saving often is never a bad idea anyhow.

    @@ -272,11 +312,14 @@ It happens very infrequently, both times it was in the DOTT CD intro. Saving oft
    -Fix save support when using the Sky engine (Beneath a Steel Sky) - You CAN'T save at the moment but auto save works.
    +Fix save support when using the Sky engine (Beneath a Steel Sky) - You +CAN'T save at the moment but auto save works.
    -Look into inconsistencies with AGOS engine and map Y key to a button combination to allow clean quitting (Simon 1/2).
    +Look into inconsistencies with AGOS engine and map Y key to a button +combination to allow clean quitting (Simon 1/2).
    -Add splash-screen and pre-ScummVM config menu (CPU speed, LCD timings etc.) - Partly done.
    +Add splash-screen and pre-ScummVM config menu (CPU speed, LCD timings +etc.) - Partly done.
    Fix TV out, maybe make it an option in the pre-ScummVM config menu.
    @@ -284,17 +327,29 @@ Any help appreciated :).

    -
    Additional resources/links
    +
    Additional resources/links
    + +
    + +
    Note: +When providing feedback, +requests, forum posts, bug reports or anything like that always include +a mention of the version of ScummVM you are using (the build version, +date and time can be seen in the main game launcher window).
    diff --git a/backends/platform/gp2x/build/build.sh b/backends/platform/gp2x/build/build.sh old mode 100644 new mode 100755 diff --git a/backends/platform/gp2x/build/bundle.sh b/backends/platform/gp2x/build/bundle.sh old mode 100644 new mode 100755 diff --git a/backends/platform/gp2x/build/clean.sh b/backends/platform/gp2x/build/clean.sh old mode 100644 new mode 100755 diff --git a/backends/platform/gp2x/build/config.sh b/backends/platform/gp2x/build/config.sh old mode 100644 new mode 100755 index 4a30ed4a31d..17083d1ea48 --- a/backends/platform/gp2x/build/config.sh +++ b/backends/platform/gp2x/build/config.sh @@ -17,7 +17,7 @@ export DEFINES=-DNDEBUG # Edit the configure line to suit. cd ../../../.. -./configure --backend=gp2x --disable-mt32emu --host=gp2x --disable-flac --disable-nasm --disable-hq-scalers --with-sdl-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6/bin --with-mpeg2-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6 --enable-tremor --with-tremor-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6 --enable-zlib --with-zlib-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6 --enable-mad --with-mad-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6 -#--enable-plugins +./configure --backend=gp2x --disable-mt32emu --host=gp2x --disable-flac --disable-nasm --disable-hq-scalers --with-sdl-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6/bin --with-mpeg2-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6 --enable-tremor --with-tremor-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6 --enable-zlib --with-zlib-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6 --enable-mad --with-mad-prefix=/opt/open2x/gcc-4.1.1-glibc-2.3.6 --enable-made --enable-m4 +#--enable-plugins --default-dynamic echo Generating config for GP2X complete. Check for errors. diff --git a/backends/platform/gp2x/build/scummvm.gpe b/backends/platform/gp2x/build/scummvm.gpe old mode 100644 new mode 100755 diff --git a/backends/platform/gp2x/gp2x-common.h b/backends/platform/gp2x/gp2x-common.h index e8e128a2497..92f625bdc43 100644 --- a/backends/platform/gp2x/gp2x-common.h +++ b/backends/platform/gp2x/gp2x-common.h @@ -37,7 +37,7 @@ #include namespace Audio { - class Mixer; + class MixerImpl; } namespace Common { @@ -128,12 +128,10 @@ public: virtual bool pollEvent(Common::Event &event); // overloaded by CE backend // Set function that generates samples - typedef void (*SoundProc)(void *param, byte *buf, int len); - virtual bool setSoundCallback(SoundProc proc, void *param); // overloaded by CE backend + void setupMixer(); + static void mixCallback(void *s, byte *samples, int len); virtual Audio::Mixer *getMixer(); - void clearSoundCallback(); - // Poll CD status // Returns true if cd audio is playing bool pollCD(); @@ -181,7 +179,6 @@ public: int getGraphicsMode() const; bool openCD(int drive); - int getOutputSampleRate() const; bool hasFeature(Feature f); void setFeatureState(Feature f, bool enable); @@ -369,7 +366,7 @@ protected: Common::SaveFileManager *_savefile; FilesystemFactory *getFilesystemFactory(); - Audio::Mixer *_mixer; + Audio::MixerImpl *_mixer; SDL_TimerID _timerID; Common::TimerManager *_timer; diff --git a/backends/platform/gp2x/gp2x.cpp b/backends/platform/gp2x/gp2x.cpp index 2d2b4b80787..c138f6c54dc 100644 --- a/backends/platform/gp2x/gp2x.cpp +++ b/backends/platform/gp2x/gp2x.cpp @@ -40,7 +40,7 @@ #include "backends/timer/default/default-timer.h" #include "backends/plugins/posix/posix-provider.h" #include "backends/fs/posix/posix-fs-factory.h" // for getFilesystemFactory() -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include #include @@ -225,8 +225,7 @@ void OSystem_GP2X::initBackend() { // Create and hook up the mixer, if none exists yet (we check for this to // allow subclasses to provide their own). if (_mixer == 0) { - _mixer = new Audio::Mixer(); - setSoundCallback(Audio::Mixer::mixCallback, _mixer); + setupMixer(); } // Create and hook up the timer manager, if none exists yet (we check for @@ -445,45 +444,69 @@ void OSystem_GP2X::deleteMutex(MutexRef mutex) { #pragma mark --- Audio --- #pragma mark - -bool OSystem_GP2X::setSoundCallback(SoundProc proc, void *param) { +void OSystem_GP2X::mixCallback(void *sys, byte *samples, int len) { + OSystem_GP2X *this_ = (OSystem_GP2X *)sys; + assert(this_); + + if (this_->_mixer) + this_->_mixer->mixCallback(samples, len); +} + +void OSystem_GP2X::setupMixer() { SDL_AudioSpec desired; SDL_AudioSpec obtained; - memset(&desired, 0, sizeof(desired)); + //memset(&desired, 0, sizeof(desired)); + // Determine the desired output sampling frequency. _samplesPerSec = 0; - if (ConfMan.hasKey("output_rate")) _samplesPerSec = ConfMan.getInt("output_rate"); - if (_samplesPerSec <= 0) _samplesPerSec = SAMPLES_PER_SEC; + //Quick EVIL Hack - DJWillis _samplesPerSec = 11025; + // Determine the sample buffer size. We want it to store enough data for + // about 1/16th of a second. Note that it must be a power of two. + // So e.g. at 22050 Hz, we request a sample buffer size of 2048. + int samples = 8192; + while (16 * samples >= _samplesPerSec) { + samples >>= 1; + } + + memset(&desired, 0, sizeof(desired)); desired.freq = _samplesPerSec; desired.format = AUDIO_S16SYS; desired.channels = 2; //desired.samples = (uint16)samples; desired.samples = 128; // Samples hack - desired.callback = proc; - desired.userdata = param; + desired.callback = mixCallback; + desired.userdata = this; + + // Create the mixer instance + assert(!_mixer); + _mixer = new Audio::MixerImpl(this); + assert(_mixer); + if (SDL_OpenAudio(&desired, &obtained) != 0) { warning("Could not open audio device: %s", SDL_GetError()); - return false; + _samplesPerSec = 0; + _mixer->setReady(false); + } else { + // Note: This should be the obtained output rate, but it seems that at + // least on some platforms SDL will lie and claim it did get the rate + // even if it didn't. Probably only happens for "weird" rates, though. + _samplesPerSec = obtained.freq; + debug(1, "Output sample rate: %d Hz", _samplesPerSec); + + // Tell the mixer that we are ready and start the sound processing + _mixer->setOutputRate(_samplesPerSec); + _mixer->setReady(true); + SDL_PauseAudio(0); } - _samplesPerSec = obtained.freq; - SDL_PauseAudio(0); - return true; -} - -void OSystem_GP2X::clearSoundCallback() { - SDL_CloseAudio(); -} - -int OSystem_GP2X::getOutputSampleRate() const { - return _samplesPerSec; } Audio::Mixer *OSystem_GP2X::getMixer() { diff --git a/backends/platform/iphone/blit_arm.s b/backends/platform/iphone/blit_arm.s index ae31fdcce4f..417f3741cfd 100644 --- a/backends/platform/iphone/blit_arm.s +++ b/backends/platform/iphone/blit_arm.s @@ -36,47 +36,47 @@ _blitLandscapeScreenRect16bpp: @ r3 = h @ <> = _screenWidth @ <> = _screenHeight - MOV r12,r13 - STMFD r13!,{r4-r11,r14} - LDMFD r12,{r12,r14} @ r12 = _screenWidth + mov r12,r13 + stmfd r13!,{r4-r11,r14} + ldmfd r12,{r12,r14} @ r12 = _screenWidth @ r14 = _screenHeight - ADD r14,r14,r3 @ r14 = _screenHeight + h - MVN r11,#0 - MLA r11,r3,r12,r11 @ r11= _screenWidth*h-1 - ADD r12,r12,r12 + add r14,r14,r3 @ r14 = _screenHeight + h + mvn r11,#0 + mla r11,r3,r12,r11 @ r11= _screenWidth*h-1 + add r12,r12,r12 xloop: - SUBS r4,r3,#5 @ r4 = y = h - BLE thin + subs r4,r3,#5 @ r4 = y = h + ble thin yloop: - LDRH r5, [r1],r12 @ r5 = *src src += _screenWidth - LDRH r6, [r1],r12 @ r6 = *src src += _screenWidth - LDRH r7, [r1],r12 @ r7 = *src src += _screenWidth - LDRH r8, [r1],r12 @ r8 = *src src += _screenWidth - LDRH r9, [r1],r12 @ r9 = *src src += _screenWidth - LDRH r10,[r1],r12 @ r10= *src src += _screenWidth - SUBS r4,r4,#6 - STRH r5, [r0],#2 @ *dst++ = r5 - STRH r6, [r0],#2 @ *dst++ = r6 - STRH r7, [r0],#2 @ *dst++ = r7 - STRH r8, [r0],#2 @ *dst++ = r8 - STRH r9, [r0],#2 @ *dst++ = r9 - STRH r10,[r0],#2 @ *dst++ = r10 - BGT yloop + ldrh r5, [r1],r12 @ r5 = *src src += _screenWidth + ldrh r6, [r1],r12 @ r6 = *src src += _screenWidth + ldrh r7, [r1],r12 @ r7 = *src src += _screenWidth + ldrh r8, [r1],r12 @ r8 = *src src += _screenWidth + ldrh r9, [r1],r12 @ r9 = *src src += _screenWidth + ldrh r10,[r1],r12 @ r10= *src src += _screenWidth + subs r4,r4,#6 + strh r5, [r0],#2 @ *dst++ = r5 + strh r6, [r0],#2 @ *dst++ = r6 + strh r7, [r0],#2 @ *dst++ = r7 + strh r8, [r0],#2 @ *dst++ = r8 + strh r9, [r0],#2 @ *dst++ = r9 + strh r10,[r0],#2 @ *dst++ = r10 + bgt yloop thin: - ADDS r4,r4,#5 - BEQ lineend + adds r4,r4,#5 + beq lineend thin_loop: - LDRH r5,[r1],r12 @ r5 = *src src += _screenWidth - SUBS r4,r4,#1 - STRH r5,[r0],#2 @ *dst++ = r5 - BGT thin_loop + ldrh r5,[r1],r12 @ r5 = *src src += _screenWidth + subs r4,r4,#1 + strh r5,[r0],#2 @ *dst++ = r5 + bgt thin_loop lineend: - SUB r0,r0,r14,LSL #1 @ dst -= _screenHeight + h - SUB r1,r1,r11,LSL #1 @ src += 1-_screenWidth*h - SUBS r2,r2,#1 - BGT xloop + sub r0,r0,r14,LSL #1 @ dst -= _screenHeight + h + sub r1,r1,r11,LSL #1 @ src += 1-_screenWidth*h + subs r2,r2,#1 + bgt xloop - LDMFD r13!,{r4-r11,PC} + ldmfd r13!,{r4-r11,PC} _blitLandscapeScreenRect8bpp: @ r0 = dst @@ -86,55 +86,55 @@ _blitLandscapeScreenRect8bpp: @ <> = _palette @ <> = _screenWidth @ <> = _screenHeight - MOV r12,r13 - STMFD r13!,{r4-r11,r14} - LDMFD r12,{r11,r12,r14} @ r11 = _palette + mov r12,r13 + stmfd r13!,{r4-r11,r14} + ldmfd r12,{r11,r12,r14} @ r11 = _palette @ r12 = _screenWidth @ r14 = _screenHeight - ADD r14,r14,r3 @ r14 = _screenHeight + h - MVN r6,#0 - MLA r6,r3,r12,r6 @ r6 = _screenWidth*h-1 + add r14,r14,r3 @ r14 = _screenHeight + h + mvn r6,#0 + mla r6,r3,r12,r6 @ r6 = _screenWidth*h-1 xloop8: - MOV r4,r3 @ r4 = y = h - SUBS r4,r3,#4 @ r4 = y = h - BLE thin8 + mov r4,r3 @ r4 = y = h + subs r4,r3,#4 @ r4 = y = h + ble thin8 yloop8: - LDRB r5, [r1],r12 @ r5 = *src src += _screenWidth - LDRB r7, [r1],r12 @ r7 = *src src += _screenWidth - LDRB r8, [r1],r12 @ r8 = *src src += _screenWidth - LDRB r9, [r1],r12 @ r9 = *src src += _screenWidth - LDRB r10,[r1],r12 @ r10= *src src += _screenWidth - ADD r5, r5, r5 - ADD r7, r7, r7 - ADD r8, r8, r8 - ADD r9, r9, r9 - ADD r10,r10,r10 - LDRH r5, [r11,r5] - LDRH r7, [r11,r7] - LDRH r8, [r11,r8] - LDRH r9, [r11,r9] - LDRH r10,[r11,r10] - SUBS r4,r4,#5 - STRH r5, [r0],#2 @ *dst++ = r5 - STRH r7, [r0],#2 @ *dst++ = r7 - STRH r8, [r0],#2 @ *dst++ = r8 - STRH r9, [r0],#2 @ *dst++ = r9 - STRH r10,[r0],#2 @ *dst++ = r10 - BGT yloop8 + ldrb r5, [r1],r12 @ r5 = *src src += _screenWidth + ldrb r7, [r1],r12 @ r7 = *src src += _screenWidth + ldrb r8, [r1],r12 @ r8 = *src src += _screenWidth + ldrb r9, [r1],r12 @ r9 = *src src += _screenWidth + ldrb r10,[r1],r12 @ r10= *src src += _screenWidth + add r5, r5, r5 + add r7, r7, r7 + add r8, r8, r8 + add r9, r9, r9 + add r10,r10,r10 + ldrh r5, [r11,r5] + ldrh r7, [r11,r7] + ldrh r8, [r11,r8] + ldrh r9, [r11,r9] + ldrh r10,[r11,r10] + subs r4,r4,#5 + strh r5, [r0],#2 @ *dst++ = r5 + strh r7, [r0],#2 @ *dst++ = r7 + strh r8, [r0],#2 @ *dst++ = r8 + strh r9, [r0],#2 @ *dst++ = r9 + strh r10,[r0],#2 @ *dst++ = r10 + bgt yloop8 thin8: - ADDS r4,r4,#4 - BEQ lineend8 + adds r4,r4,#4 + beq lineend8 thin_loop8: - LDRB r5,[r1],r12 @ r5 = *src src += _screenWidth - ADD r5,r5,r5 - LDRH r5,[r11,r5] - SUBS r4,r4,#1 - STRH r5,[r0],#2 @ *dst++ = r5 - BGT thin_loop8 + ldrb r5,[r1],r12 @ r5 = *src src += _screenWidth + add r5,r5,r5 + ldrh r5,[r11,r5] + subs r4,r4,#1 + strh r5,[r0],#2 @ *dst++ = r5 + bgt thin_loop8 lineend8: - SUB r0,r0,r14,LSL #1 @ dst -= _screenHeight + h - SUB r1,r1,r6 @ src += 1-_screenWidth*h - SUBS r2,r2,#1 - BGT xloop8 + sub r0,r0,r14,LSL #1 @ dst -= _screenHeight + h + sub r1,r1,r6 @ src += 1-_screenWidth*h + subs r2,r2,#1 + bgt xloop8 - LDMFD r13!,{r4-r11,PC} + ldmfd r13!,{r4-r11,PC} diff --git a/backends/platform/iphone/iphone_keyboard.h b/backends/platform/iphone/iphone_keyboard.h index 17a3836efd3..6d381d561d2 100644 --- a/backends/platform/iphone/iphone_keyboard.h +++ b/backends/platform/iphone/iphone_keyboard.h @@ -26,11 +26,7 @@ #import #import -@protocol KeyboardInputProtocol -- (void)handleKeyPress:(unichar)c; -@end - -@interface SoftKeyboard : UIKeyboard { +@interface SoftKeyboard : UIView { id inputDelegate; UITextView* inputView; } diff --git a/backends/platform/iphone/iphone_keyboard.m b/backends/platform/iphone/iphone_keyboard.m index dc2d417746d..bd4948e30a4 100644 --- a/backends/platform/iphone/iphone_keyboard.m +++ b/backends/platform/iphone/iphone_keyboard.m @@ -25,19 +25,6 @@ #import "iphone_keyboard.h" -// Override settings of the default keyboard implementation -@implementation UIKeyboardImpl (DisableFeatures) - -- (BOOL)autoCapitalizationPreference { - return false; -} - -- (BOOL)autoCorrectionPreference { - return false; -} - -@end - @implementation TextInputHandler - (id)initWithKeyboard:(SoftKeyboard*)keyboard; { @@ -67,7 +54,8 @@ @implementation SoftKeyboard - (id)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; + //self = [super initWithFrame:frame]; + self = [super initWithFrame:CGRectMake(0.0f, 0.0f, 0.0f, 0.0f)]; inputDelegate = nil; inputView = [[TextInputHandler alloc] initWithKeyboard:self]; return self; diff --git a/backends/platform/iphone/iphone_main.m b/backends/platform/iphone/iphone_main.m index f7f5667bb5f..b01e9f3f348 100644 --- a/backends/platform/iphone/iphone_main.m +++ b/backends/platform/iphone/iphone_main.m @@ -46,9 +46,14 @@ int main(int argc, char** argv) { gArgc = argc; gArgv = argv; - [[NSAutoreleasePool alloc] init]; + NSAutoreleasePool *autoreleasePool = [ + [ NSAutoreleasePool alloc ] init + ]; - return UIApplicationMain(argc, argv, [iPhoneMain class]); + UIApplicationUseLegacyEvents(1); + int returnCode = UIApplicationMain(argc, argv, [iPhoneMain class]); + [ autoreleasePool release ]; + return returnCode; } @implementation iPhoneMain @@ -74,7 +79,10 @@ int main(int argc, char** argv) { - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { // hide the status bar [UIHardware _setStatusBarHeight:0.0f]; - [self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0]; + //[self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0]; + + //[self setStatusBarStyle:UIStatusBarStyleBlackTranslucent animated:NO]; + [self setStatusBarHidden:YES animated:YES]; _window = [[UIWindow alloc] initWithContentRect: [UIHardware fullScreenApplicationContentRect]]; [_window retain]; @@ -96,7 +104,7 @@ int main(int argc, char** argv) { - (void)applicationResume:(GSEventRef)event { [self removeApplicationBadge]; [UIHardware _setStatusBarHeight:0.0f]; - [self setStatusBarMode:2 orientation:0 duration:0.0f fenceID:0]; + [self setStatusBarHidden:YES animated:YES]; [_view applicationResume]; } diff --git a/backends/platform/iphone/iphone_video.h b/backends/platform/iphone/iphone_video.h index 615b2e53453..6e4b4469261 100644 --- a/backends/platform/iphone/iphone_video.h +++ b/backends/platform/iphone/iphone_video.h @@ -27,12 +27,11 @@ #define _IPHONE_VIDEO__H #import -#import #import #import #import -#import +#import #import "iphone_keyboard.h" @interface iPhoneView : UIView @@ -41,7 +40,7 @@ NSMutableArray* _events; NSLock* _lock; SoftKeyboard* _keyboardView; - LKLayer* _screenLayer; + CALayer* _screenLayer; int _fullWidth; int _fullHeight; diff --git a/backends/platform/iphone/iphone_video.m b/backends/platform/iphone/iphone_video.m index 6c6944045e0..89f159c1d90 100644 --- a/backends/platform/iphone/iphone_video.m +++ b/backends/platform/iphone/iphone_video.m @@ -31,8 +31,8 @@ #import #import #import -#import #import +#import static iPhoneView *sharedInstance = nil; static int _width = 0; @@ -53,8 +53,8 @@ void iPhone_updateScreen() { } void iPhone_updateScreenRect(int x1, int y1, int x2, int y2) { - NSRect rect = NSMakeRect(x1, y1, x2, y2); - [sharedInstance performSelectorOnMainThread:@selector(updateScreenRect:) withObject: [NSValue valueWithRect:rect] waitUntilDone: NO]; + //CGRect rect = CGRectMake(x1, y1, x2, y2); + //[sharedInstance performSelectorOnMainThread:@selector(updateScreenRect:) withObject: [NSValue valueWithRect:rect] waitUntilDone: NO]; } void iPhone_lockSurface() { @@ -146,9 +146,9 @@ bool getLocalMouseCoords(CGPoint *point) { } - (void)updateScreenRect:(id)rect { - NSRect nsRect = [rect rectValue]; - CGRect cgRect = CGRectMake(nsRect.origin.x, nsRect.origin.y, nsRect.size.width, nsRect.size.height); - [sharedInstance setNeedsDisplayInRect: cgRect]; + // NSRect nsRect = [rect rectValue]; + // CGRect cgRect = CGRectMake(nsRect.origin.x, nsRect.origin.y, nsRect.size.width, nsRect.size.height); + // [sharedInstance setNeedsDisplayInRect: cgRect]; } - (void)initSurface { @@ -178,7 +178,7 @@ bool getLocalMouseCoords(CGPoint *point) { //printf("Surface created.\n"); CoreSurfaceBufferLock(_screenSurface, 3); - LKLayer* screenLayer = [[LKLayer layer] retain]; + CALayer* screenLayer = [[CALayer layer] retain]; if (_keyboardView != nil) { [_keyboardView removeFromSuperview]; @@ -213,7 +213,7 @@ bool getLocalMouseCoords(CGPoint *point) { _keyboardView = [[SoftKeyboard alloc] initWithFrame:keyFrame]; [_keyboardView setInputDelegate:self]; } - + [self addSubview:[_keyboardView inputView]]; [self addSubview: _keyboardView]; [[_keyboardView inputView] becomeFirstResponder]; @@ -283,11 +283,13 @@ bool getLocalMouseCoords(CGPoint *point) { } - (void)mouseDown:(GSEvent*)event { - struct CGPoint point = GSEventGetLocationInWindow(event); - + //printf("mouseDown()\n"); + CGRect rect = GSEventGetLocationInWindow(event); + CGPoint point = CGPointMake(rect.origin.x, rect.origin.y); + if (!getLocalMouseCoords(&point)) return; - + [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseDown], @"type", @@ -298,12 +300,18 @@ bool getLocalMouseCoords(CGPoint *point) { ]; } +- (void)touchesBegan { + //printf("touchesBegan()\n"); +} + - (void)mouseUp:(GSEvent*)event { - struct CGPoint point = GSEventGetLocationInWindow(event); - + //printf("mouseUp()\n"); + CGRect rect = GSEventGetLocationInWindow(event); + CGPoint point = CGPointMake(rect.origin.x, rect.origin.y); + if (!getLocalMouseCoords(&point)) return; - + [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseUp], @"type", @@ -316,11 +324,12 @@ bool getLocalMouseCoords(CGPoint *point) { - (void)mouseDragged:(GSEvent*)event { //printf("mouseDragged()\n"); - struct CGPoint point = GSEventGetLocationInWindow(event); - + CGRect rect = GSEventGetLocationInWindow(event); + CGPoint point = CGPointMake(rect.origin.x, rect.origin.y); + if (!getLocalMouseCoords(&point)) return; - + [self addEvent: [[NSDictionary alloc] initWithObjectsAndKeys: [NSNumber numberWithInt:kInputMouseDragged], @"type", @@ -333,19 +342,21 @@ bool getLocalMouseCoords(CGPoint *point) { - (void)mouseEntered:(GSEvent*)event { //printf("mouseEntered()\n"); - // struct CGPoint point = GSEventGetLocationInWindow(event); - // - // if (!getLocalMouseCoords(&point)) - // return; - // - // [self addEvent: - // [[NSDictionary alloc] initWithObjectsAndKeys: - // [NSNumber numberWithInt:kInputMouseSecondToggled], @"type", - // [NSNumber numberWithFloat:point.x], @"x", - // [NSNumber numberWithFloat:point.y], @"y", - // nil - // ] - // ]; + CGRect rect = GSEventGetLocationInWindow(event); + CGPoint point = CGPointMake(rect.origin.x, rect.origin.y); + + + if (!getLocalMouseCoords(&point)) + return; + + [self addEvent: + [[NSDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithInt:kInputMouseSecondToggled], @"type", + [NSNumber numberWithFloat:point.x], @"x", + [NSNumber numberWithFloat:point.y], @"y", + nil + ] + ]; } - (void)mouseExited:(GSEvent*)event { @@ -361,19 +372,19 @@ bool getLocalMouseCoords(CGPoint *point) { - (void)mouseMoved:(GSEvent*)event { //printf("mouseMoved()\n"); - struct CGPoint point = GSEventGetLocationInWindow(event); - - if (!getLocalMouseCoords(&point)) - return; - - [self addEvent: - [[NSDictionary alloc] initWithObjectsAndKeys: - [NSNumber numberWithInt:kInputMouseSecondToggled], @"type", - [NSNumber numberWithFloat:point.x], @"x", - [NSNumber numberWithFloat:point.y], @"y", - nil - ] - ]; + // struct CGPoint point = GSEventGetLocationInWindow(event); + // + // if (!getLocalMouseCoords(&point)) + // return; + // + // [self addEvent: + // [[NSDictionary alloc] initWithObjectsAndKeys: + // [NSNumber numberWithInt:kInputMouseSecondToggled], @"type", + // [NSNumber numberWithFloat:point.x], @"x", + // [NSNumber numberWithFloat:point.y], @"y", + // nil + // ] + // ]; } - (void)handleKeyPress:(unichar)c { @@ -391,7 +402,7 @@ bool getLocalMouseCoords(CGPoint *point) { return TRUE; } -- (int)swipe:(UIViewSwipeDirection)num withEvent:(GSEvent*)event { +- (int)swipe:(int)num withEvent:(GSEvent*)event { //printf("swipe: %i\n", num); [self addEvent: diff --git a/backends/platform/iphone/osys_iphone.cpp b/backends/platform/iphone/osys_iphone.cpp index 3365c2da9da..fdef911d0aa 100644 --- a/backends/platform/iphone/osys_iphone.cpp +++ b/backends/platform/iphone/osys_iphone.cpp @@ -25,8 +25,6 @@ #if defined(IPHONE_BACKEND) -#include -#include #include #include @@ -41,10 +39,15 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" #include "sound/mixer.h" +#include "sound/mixer_intern.h" #include "gui/message.h" #include "osys_iphone.h" #include "blit_arm.h" +#include + +#include +#include const OSystem::GraphicsMode OSystem_IPHONE::s_supportedGraphicsModes[] = { {0, 0, 0} @@ -86,12 +89,12 @@ int OSystem_IPHONE::timerHandler(int t) { void OSystem_IPHONE::initBackend() { _savefile = new DefaultSaveFileManager(); - _mixer = new Audio::Mixer(); _timer = new DefaultTimerManager(); gettimeofday(&_startTime, NULL); - setSoundCallback(Audio::Mixer::mixCallback, _mixer); + setupMixer(); + setTimerCallback(&OSystem_IPHONE::timerHandler, 10); OSystem::initBackend(); @@ -871,7 +874,7 @@ bool OSystem_IPHONE::pollEvent(Common::Event &event) { suspendLoop(); break; - case kInputKeyPressed: + case kInputKeyPressed: { int keyPressed = (int)xUnit; int ascii = keyPressed; //printf("key: %i\n", keyPressed); @@ -932,6 +935,7 @@ bool OSystem_IPHONE::pollEvent(Common::Event &event) { event.kbd.ascii = _queuedInputEvent.kbd.ascii = ascii; _needEventRestPeriod = true; break; + } case kInputSwipe: { Common::KeyCode keycode = Common::KEYCODE_INVALID; @@ -1088,10 +1092,21 @@ void OSystem_IPHONE::AQBufferCallback(void *in, AudioQueueRef inQ, AudioQueueBuf AudioQueueStop(s_AudioQueue.queue, false); } -bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) { +void OSystem_IPHONE::mixCallback(void *sys, byte *samples, int len) { + OSystem_IPHONE *this_ = (OSystem_IPHONE *)sys; + assert(this_); + + if (this_->_mixer) + this_->_mixer->mixCallback(samples, len); +} + +void OSystem_IPHONE::setupMixer() { //printf("setSoundCallback()\n"); - s_soundCallback = proc; - s_soundParam = param; + _mixer = new Audio::MixerImpl(this); + + s_soundCallback = mixCallback; + s_soundParam = this; + s_AudioQueue.dataFormat.mSampleRate = AUDIO_SAMPLE_RATE; s_AudioQueue.dataFormat.mFormatID = kAudioFormatLinearPCM; @@ -1105,7 +1120,8 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) { if (AudioQueueNewOutput(&s_AudioQueue.dataFormat, AQBufferCallback, &s_AudioQueue, 0, kCFRunLoopCommonModes, 0, &s_AudioQueue.queue)) { printf("Couldn't set the AudioQueue callback!\n"); - return false; + _mixer->setReady(false); + return; } uint32 bufferBytes = s_AudioQueue.frameCount * s_AudioQueue.dataFormat.mBytesPerFrame; @@ -1113,7 +1129,8 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) { for (int i = 0; i < AUDIO_BUFFERS; i++) { if (AudioQueueAllocateBuffer(s_AudioQueue.queue, bufferBytes, &s_AudioQueue.buffers[i])) { printf("Error allocating AudioQueue buffer!\n"); - return false; + _mixer->setReady(false); + return; } AQBufferCallback(&s_AudioQueue, s_AudioQueue.queue, s_AudioQueue.buffers[i]); @@ -1122,14 +1139,12 @@ bool OSystem_IPHONE::setSoundCallback(SoundProc proc, void *param) { AudioQueueSetParameter(s_AudioQueue.queue, kAudioQueueParam_Volume, 1.0); if (AudioQueueStart(s_AudioQueue.queue, NULL)) { printf("Error starting the AudioQueue!\n"); - return false; + _mixer->setReady(false); + return; } - - return true; -} - -void OSystem_IPHONE::clearSoundCallback() { - debug("clearSoundCallback()\n"); + + _mixer->setOutputRate(AUDIO_SAMPLE_RATE); + _mixer->setReady(true); } int OSystem_IPHONE::getOutputSampleRate() const { @@ -1150,6 +1165,11 @@ void OSystem_IPHONE::setTimerCallback(TimerProc callback, int interval) { void OSystem_IPHONE::quit() { } +void OSystem_IPHONE::getTimeAndDate(struct tm &t) const { + time_t curTime = time(0); + t = *localtime(&curTime); +} + void OSystem_IPHONE::setWindowCaption(const char *caption) { } @@ -1173,17 +1193,11 @@ OSystem *OSystem_IPHONE_create() { } const char* OSystem_IPHONE::getConfigPath() { - if (s_is113OrHigher) - return SCUMMVM_PREFS_PATH; - else - return SCUMMVM_OLD_PREFS_PATH; + return SCUMMVM_PREFS_PATH; } const char* OSystem_IPHONE::getSavePath() { - if (s_is113OrHigher) - return SCUMMVM_SAVE_PATH; - else - return SCUMMVM_OLD_SAVE_PATH; + return SCUMMVM_SAVE_PATH; } void OSystem_IPHONE::migrateApp() { @@ -1197,7 +1211,7 @@ void OSystem_IPHONE::migrateApp() { if (!file.exists()) { system("mkdir " SCUMMVM_ROOT_PATH); system("mkdir " SCUMMVM_SAVE_PATH); - + // Copy over the prefs file system("cp " SCUMMVM_OLD_PREFS_PATH " " SCUMMVM_PREFS_PATH); @@ -1211,24 +1225,24 @@ void OSystem_IPHONE::migrateApp() { void iphone_main(int argc, char *argv[]) { - OSystem_IPHONE::migrateApp(); + //OSystem_IPHONE::migrateApp(); - // Redirect stdout and stderr if we're launching from the Springboard. - if (argc == 2 && strcmp(argv[1], "--launchedFromSB") == 0) { - FILE *newfp = fopen("/tmp/scummvm.log", "a"); - if (newfp != NULL) { - fclose(stdout); - fclose(stderr); - *stdout = *newfp; - *stderr = *newfp; - setbuf(stdout, NULL); - setbuf(stderr, NULL); + FILE *newfp = fopen("/var/mobile/.scummvm.log", "a"); + if (newfp != NULL) { + fclose(stdout); + fclose(stderr); + *stdout = *newfp; + *stderr = *newfp; + setbuf(stdout, NULL); + setbuf(stderr, NULL); - //extern int gDebugLevel; - //gDebugLevel = 10; - } + //extern int gDebugLevel; + //gDebugLevel = 10; } + system("mkdir " SCUMMVM_ROOT_PATH); + system("mkdir " SCUMMVM_SAVE_PATH); + g_system = OSystem_IPHONE_create(); assert(g_system); diff --git a/backends/platform/iphone/osys_iphone.h b/backends/platform/iphone/osys_iphone.h index 71272b73fe6..af883a62c91 100644 --- a/backends/platform/iphone/osys_iphone.h +++ b/backends/platform/iphone/osys_iphone.h @@ -29,6 +29,8 @@ #include "iphone_common.h" #include "common/system.h" #include "common/events.h" +#include "sound/mixer_intern.h" +#include "backends/fs/posix/posix-fs-factory.h" #include @@ -62,7 +64,7 @@ protected: static bool s_is113OrHigher; Common::SaveFileManager *_savefile; - Audio::Mixer *_mixer; + Audio::MixerImpl *_mixer; Common::TimerManager *_timer; Graphics::Surface _framebuffer; @@ -152,13 +154,16 @@ public: virtual void unlockMutex(MutexRef mutex); virtual void deleteMutex(MutexRef mutex); - virtual bool setSoundCallback(SoundProc proc, void *param); - virtual void clearSoundCallback(); + static void mixCallback(void *sys, byte *samples, int len); + virtual void setupMixer(void); virtual int getOutputSampleRate() const; virtual void setTimerCallback(TimerProc callback, int interval); virtual void quit(); + FilesystemFactory *getFilesystemFactory() { return &POSIXFilesystemFactory::instance(); } + virtual void getTimeAndDate(struct tm &t) const; + virtual void setWindowCaption(const char *caption); virtual Common::SaveFileManager *getSavefileManager(); diff --git a/backends/platform/null/null.cpp b/backends/platform/null/null.cpp index 194a7b68891..463e9d7b2d2 100644 --- a/backends/platform/null/null.cpp +++ b/backends/platform/null/null.cpp @@ -28,18 +28,37 @@ #if defined(USE_NULL_DRIVER) +#ifdef UNIX +#include +#include +#endif + #include "common/rect.h" #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" + +/* + * Include header files needed for the getFilesystemFactory() method. + */ +#if defined(__amigaos4__) + #include "backends/fs/amigaos4/amigaos4-fs-factory.h" +#elif defined(UNIX) + #include "backends/fs/posix/posix-fs-factory.h" +#elif defined(WIN32) + #include "backends/fs/windows/windows-fs-factory.h" +#endif + + class OSystem_NULL : public OSystem { protected: Common::SaveFileManager *_savefile; - Audio::Mixer *_mixer; + Audio::MixerImpl *_mixer; Common::TimerManager *_timer; + timeval _startTime; public: OSystem_NULL(); @@ -93,8 +112,6 @@ public: typedef void (*SoundProc)(void *param, byte *buf, int len); virtual bool setSoundCallback(SoundProc proc, void *param); - virtual void clearSoundCallback(); - virtual int getOutputSampleRate() const; virtual void quit(); @@ -102,7 +119,10 @@ public: virtual Common::SaveFileManager *getSavefileManager(); virtual Audio::Mixer *getMixer(); + virtual void getTimeAndDate(struct tm &t) const; virtual Common::TimerManager *getTimerManager(); + FilesystemFactory *getFilesystemFactory(); + }; static const OSystem::GraphicsMode s_supportedGraphicsModes[] = { @@ -123,9 +143,14 @@ OSystem_NULL::~OSystem_NULL() { void OSystem_NULL::initBackend() { _savefile = new DefaultSaveFileManager(); - _mixer = new Audio::Mixer(); + _mixer = new Audio::MixerImpl(this); _timer = new DefaultTimerManager(); + _mixer->setOutputRate(22050); + _mixer->setReady(false); + + gettimeofday(&_startTime, NULL); + // Note that both the mixer and the timer manager are useless // this way; they need to be hooked into the system somehow to // be functional. Of course, can't do that in a NULL backend :). @@ -244,10 +269,20 @@ bool OSystem_NULL::pollEvent(Common::Event &event) { } uint32 OSystem_NULL::getMillis() { +#ifdef UNIX + timeval curTime; + gettimeofday(&curTime, NULL); + return (uint32)(((curTime.tv_sec - _startTime.tv_sec) * 1000) + \ + ((curTime.tv_usec - _startTime.tv_usec) / 1000)); +#else return 0; +#endif } void OSystem_NULL::delayMillis(uint msecs) { +#ifdef UNIX + usleep(msecs * 1000); +#endif } OSystem::MutexRef OSystem_NULL::createMutex(void) { @@ -267,13 +302,6 @@ bool OSystem_NULL::setSoundCallback(SoundProc proc, void *param) { return true; } -void OSystem_NULL::clearSoundCallback() { -} - -int OSystem_NULL::getOutputSampleRate() const { - return 22050; -} - void OSystem_NULL::quit() { } @@ -295,6 +323,22 @@ Common::TimerManager *OSystem_NULL::getTimerManager() { return _timer; } +void OSystem_NULL::getTimeAndDate(struct tm &t) const { +} + +FilesystemFactory *OSystem_NULL::getFilesystemFactory() { + #if defined(__amigaos4__) + return &AmigaOSFilesystemFactory::instance(); + #elif defined(UNIX) + return &POSIXFilesystemFactory::instance(); + #elif defined(WIN32) + return &WindowsFilesystemFactory::instance(); + #else + #error Unknown and unsupported backend in OSystem_NULL::getFilesystemFactory + #endif +} + + OSystem *OSystem_NULL_create() { return new OSystem_NULL(); } diff --git a/backends/platform/ps2/Makefile.ps2 b/backends/platform/ps2/Makefile.ps2 index 22cd4eaa1b2..204f4f7a165 100644 --- a/backends/platform/ps2/Makefile.ps2 +++ b/backends/platform/ps2/Makefile.ps2 @@ -19,16 +19,17 @@ RM = rm -f srcdir = ../../.. VPATH = $(srcdir) INCDIR = ../../../ +DEPDIR = .deps DEFINES = -DUSE_VORBIS -DUSE_TREMOR -DUSE_MAD -DUSE_MPEG2 -DUSE_ZLIB -D_EE -D__PLAYSTATION2__ -O2 -Wall -Wno-multichar # PS2SDK-Ports from ps2dev.org's SVN repository for libmad, zlib and ucl -PS2SDK_PORTS = /home/robby/libStuffNew/ps2sdk-ports +PS2SDK_PORTS = /mnt/winxp/scummvm/ports PS2SDK_PORTS_INCS = /ucl /zlib/include /libmad/ee/include PS2SDK_PORTS_LIBS = /ucl /zlib/lib /libmad/ee/lib # we also need SjPcm, Tremor and libmpeg2 -MORE_LIBS_DIR = /home/robby/libStuff +MORE_LIBS_DIR = /mnt/winxp/scummvm/ports MORE_LIBS_INCS = /SjPcm/ee/src /mpeg2dec/include /tremor MORE_LIBS_LIBS = /SjPcm/ee/lib /mpeg2dec/libmpeg2 /tremor/tremor diff --git a/backends/platform/ps2/fileio.cpp b/backends/platform/ps2/fileio.cpp index bc310a43f45..b7fa8d03f1f 100644 --- a/backends/platform/ps2/fileio.cpp +++ b/backends/platform/ps2/fileio.cpp @@ -340,8 +340,6 @@ FILE *ps2_fopen(const char *fname, const char *mode) { assert(cacheListSema >= 0); } - //printf("ps2_fopen: %s, %s\n", fname, mode); - if (((mode[0] != 'r') && (mode[0] != 'w')) || ((mode[1] != '\0') && (mode[1] != 'b'))) { printf("unsupported mode \"%s\" for file \"%s\"\n", mode, fname); return NULL; @@ -363,6 +361,8 @@ FILE *ps2_fopen(const char *fname, const char *mode) { } else { // Regular access to one of the devices + printf("ps2_fopen = %s\n", fname); // romeo : temp + if (!rdOnly) return NULL; // we only provide readaccess for cd,dvd,hdd,usb @@ -378,19 +378,22 @@ FILE *ps2_fopen(const char *fname, const char *mode) { } int64 cacheId = -1; - if (rdOnly && tocManager.haveEntries()) + if (tocManager.haveEntries()) cacheId = tocManager.fileExists(fname); if (cacheId != 0) { Ps2File *file = findInCache(cacheId); - if (file) + if (file) { + printf(" findInCache(%x)\n", cacheId); // romeo : temp return (FILE*)file; + } bool isAudioFile = strstr(fname, ".bun") || strstr(fname, ".BUN") || strstr(fname, ".Bun"); file = new Ps2ReadFile(cacheId, isAudioFile); if (file->open(fname)) { openFileCount++; + printf(" new cacheID = %x\n", cacheId); // romeo : temp return (FILE*)file; } else delete file; @@ -579,7 +582,7 @@ void TocManager::readEntries(const char *root) { } char readPath[256]; sprintf(readPath, "%s/", _root); - printf("readDir: %s\n", readPath); + printf("readDir: %s (root: %s )\n", readPath, root); readDir(readPath, &_rootNode, 0); } @@ -587,28 +590,62 @@ void TocManager::readDir(const char *path, TocNode **node, int level) { if (level <= 2) { // we don't scan deeper than that iox_dirent_t dirent; int fd = fio.dopen(path); - if (fd >= 0) { - while (fio.dread(fd, &dirent) > 0) - if (dirent.name[0] != '.') { // skip '.' and '..' - *node = new TocNode; - (*node)->sub = (*node)->next = NULL; + TocNode *eNode = NULL; // = *node; // entry node + bool first = true; + printf("path=%s - level=%d fd=%d\n", path, level, fd); // romeo : temp + if (fd >= 0) { + while (fio.dread(fd, &dirent) > 0) { + if (dirent.name[0] != '.') { // skip '.' & '..' - romeo : check + // --- do we have them on PS2? + *node = new TocNode; + if (first) { + eNode = *node; + first = false; + } + (*node)->sub = (*node)->next = NULL; (*node)->nameLen = strlen(dirent.name); memcpy((*node)->name, dirent.name, (*node)->nameLen + 1); - if (dirent.stat.mode & FIO_S_IFDIR) { // directory + if (dirent.stat.mode & FIO_S_IFDIR) { (*node)->isDir = true; - char nextPath[256]; - sprintf(nextPath, "%s%s/", path, dirent.name); - readDir(nextPath, &((*node)->sub), level + 1); - } else + printf("dirent.name = %s [DIR]\n", dirent.name); + } + else { (*node)->isDir = false; + printf("dirent.name = %s\n", dirent.name); + } + node = &((*node)->next); } + } + fio.dclose(fd); - } else - printf("Can't open path: %s\n", path); + } + + TocNode *iNode = eNode; + char nextPath[256]; + + while (iNode) { + if (iNode->isDir == true) { + sprintf(nextPath, "%s%s/", path, iNode->name); + readDir(nextPath, &(iNode->sub), level + 1); + } + iNode = iNode->next; + } + } + + /* + ** Wizard of Oz' trick (to get all games running from USB on PS2): + + 1. Make a list of files / dirs in level #0 (dclose before continuing) + + 2. Go through the dirs : dopen / dread them / mark dirs / dclose + + It's a safe recursion, cause it recurses on 'isDir' nodes + after dclosing the higher hierarchy + */ } int64 TocManager::fileExists(const char *name) { diff --git a/backends/platform/ps2/iop/rpckbd/Makefile b/backends/platform/ps2/iop/rpckbd/Makefile new file mode 100644 index 00000000000..b25efa36607 --- /dev/null +++ b/backends/platform/ps2/iop/rpckbd/Makefile @@ -0,0 +1,30 @@ +# _____ ___ ____ ___ ____ +# ____| | ____| | | |____| +# | ___| |____ ___| ____| | \ PS2DEV Open Source Project. +#----------------------------------------------------------------------- +# Copyright 2001-2004, ps2dev - http://www.ps2dev.org +# Licenced under Academic Free License version 2.0 +# Review ps2sdk README & LICENSE files for further details. +# +# $Id$ + + +IOP_OBJS_DIR = obj/ +IOP_BIN_DIR = bin/ +IOP_SRC_DIR = src/ +IOP_INC_DIR = include/ + +IOP_BIN=bin/rpckbd.irx +IOP_OBJS=obj/ps2kbd.o obj/imports.o + +IOP_CFLAGS=-Wall +IOP_INCS += -I$(PS2SDKSRC)/iop/usb/usbd/include + +all: $(IOP_OBJS_DIR) $(IOP_BIN_DIR) $(IOP_BIN) + +clean: + rm -f -r $(IOP_OBJS_DIR) $(IOP_BIN_DIR) + +include $(PS2SDKSRC)/Defs.make +include $(PS2SDKSRC)/iop/Rules.make +include $(PS2SDKSRC)/iop/Rules.release diff --git a/backends/platform/ps2/iop/rpckbd/include/ps2kbd.h b/backends/platform/ps2/iop/rpckbd/include/ps2kbd.h new file mode 100644 index 00000000000..f16c7ca12b9 --- /dev/null +++ b/backends/platform/ps2/iop/rpckbd/include/ps2kbd.h @@ -0,0 +1,90 @@ +/* +# _____ ___ ____ ___ ____ +# ____| | ____| | | |____| +# | ___| |____ ___| ____| | \ PS2DEV Open Source Project. +#----------------------------------------------------------------------- +# Copyright 2001-2004, ps2dev - http://www.ps2dev.org +# Licenced under Academic Free License version 2.0 +# Review ps2sdk README & LICENSE files for further details. +# +# $Id$ +# USB Keyboard Driver for PS2 +*/ + +#ifndef __PS2KBD_H__ +#define __PS2KBD_H__ + +#define PS2KBD_RPC_ID 0xb0b0b80 + +#define PS2KBD_LED_NUMLOCK 1 +#define PS2KBD_LED_CAPSLOCK 2 +#define PS2KBD_LED_SCRLOCK 4 +#define PS2KBD_LED_COMPOSE 8 +#define PS2KBD_LED_KANA 16 + +#define PS2KBD_LED_MASK 0x1F; + +#define PS2KBD_ESCAPE_KEY 0x1B + +#define PS2KBD_LEFT_CTRL (1 << 0) +#define PS2KBD_LEFT_SHIFT (1 << 1) +#define PS2KBD_LEFT_ALT (1 << 2) +#define PS2KBD_LEFT_GUI (1 << 3) +#define PS2KBD_RIGHT_CTRL (1 << 4) +#define PS2KBD_RIGHT_SHIFT (1 << 5) +#define PS2KBD_RIGHT_ALT (1 << 6) +#define PS2KBD_RIGHT_GUI (1 << 7) + +#define PS2KBD_CTRL (PS2KBD_LEFT_CTRL | PS2KBD_RIGHT_CTRL) +#define PS2KBD_SHIFT (PS2KBD_LEFT_SHIFT | PS2KBD_RIGHT_SHIFT) +#define PS2KBD_ALT (PS2KBD_LEFT_ALT | PS2KBD_RIGHT_ALT) +#define PS2KBD_GUI (PS2KBD_LEFT_GUI | PS2KBD_RIGHT_GUI) + +#define PS2KBD_RAWKEY_UP 0xF0 +#define PS2KBD_RAWKEY_DOWN 0xF1 + +typedef struct _kbd_rawkey { + u8 state; + u8 key; +} kbd_rawkey __attribute__ ((packed)); + +#define PS2KBD_READMODE_NORMAL 1 +#define PS2KBD_READMODE_RAW 2 + +/* Notes on read mode */ +/* In normal readmode (default) read multiples of 1 character off the keyboard file. These are + processed by the keymaps so that you get back ASCII data */ +/* In raw readmode must read multiples of 2. First byte indicates state (i.e. Up or Down) + Second byte is the USB key code for that key. This table is presented in the USB HID Usage Tables manaual + from usb.org */ + +#define PS2KBD_KEYMAP_SIZE 256 + +typedef struct _kbd_keymap + +{ + u8 keymap[PS2KBD_KEYMAP_SIZE]; + u8 shiftkeymap[PS2KBD_KEYMAP_SIZE]; + u8 keycap[PS2KBD_KEYMAP_SIZE]; +} kbd_keymap; + + +/* IRPC function numbers */ +#define KBD_RPC_SETREADMODE 1 /* Sets up keymapped or raw mode */ +#define KBD_RPC_SETLEDS 2 /* Sets the LED state for ALL keyboards connected */ +#define KBD_RPC_SETREPEATRATE 3 /* Sets the repeat rate of the keyboard */ +#define KBD_RPC_SETKEYMAP 4 /* Sets the keymap for the standard keys, non shifted and shifted */ +#define KBD_RPC_SETCTRLMAP 5 /* Sets the control key mapping */ +#define KBD_RPC_SETALTMAP 6 /* Sets the alt key mapping */ +#define KBD_RPC_SETSPECIALMAP 7 /* Sets the special key mapping */ +#define KBD_RPC_FLUSHBUFFER 9 /* Flush the internal buffer, probably best after a keymap change */ +#define KBD_RPC_RESETKEYMAP 10 /* Reset keymaps to default states */ +#define KBD_RPC_READKEY 11 +#define KBD_RPC_READRAW 12 + +/* Note on keymaps. In normal keymap a 0 would indicate no key */ +/* Key maps are represented by 3 256*8bit tables. First table maps USB key to a char when not shifted */ +/* Second table maps USB key to a char when shifted */ +/* Third table contains boolean values. If 1 then the key is shifted/unshifted in capslock, else capslock is ignored */ + +#endif diff --git a/backends/platform/ps2/iop/rpckbd/src/imports.lst b/backends/platform/ps2/iop/rpckbd/src/imports.lst new file mode 100644 index 00000000000..41e13e6e737 --- /dev/null +++ b/backends/platform/ps2/iop/rpckbd/src/imports.lst @@ -0,0 +1,58 @@ + +sysclib_IMPORTS_start +I_memset +I_strcmp +I_memcpy +sysclib_IMPORTS_end + +loadcore_IMPORTS_start +I_FlushDcache +loadcore_IMPORTS_end + +sifcmd_IMPORTS_start +I_sceSifInitRpc +I_sceSifSetRpcQueue +I_sceSifRegisterRpc +I_sceSifRpcLoop +sifcmd_IMPORTS_end + +stdio_IMPORTS_start +I_printf +stdio_IMPORTS_end + +thsemap_IMPORTS_start +I_CreateSema +I_SignalSema +I_WaitSema +I_PollSema +I_DeleteSema +thsemap_IMPORTS_end + +thbase_IMPORTS_start +I_StartThread +I_CreateThread +I_USec2SysClock +I_iSetAlarm +I_SetAlarm +I_CancelAlarm +thbase_IMPORTS_end + +thevent_IMPORTS_start +I_WaitEventFlag +I_iSetEventFlag +I_CreateEventFlag +thevent_IMPORTS_end + +sysmem_IMPORTS_start +I_AllocSysMemory +I_FreeSysMemory +sysmem_IMPORTS_end + +usbd_IMPORTS_start +I_UsbGetDeviceStaticDescriptor +I_UsbOpenEndpoint +I_UsbSetDevicePrivateData +I_UsbTransfer +I_UsbRegisterDriver +usbd_IMPORTS_end + diff --git a/backends/platform/ps2/iop/rpckbd/src/irx_imports.h b/backends/platform/ps2/iop/rpckbd/src/irx_imports.h new file mode 100644 index 00000000000..6ecc8e58645 --- /dev/null +++ b/backends/platform/ps2/iop/rpckbd/src/irx_imports.h @@ -0,0 +1,35 @@ +/* +# _____ ___ ____ ___ ____ +# ____| | ____| | | |____| +# | ___| |____ ___| ____| | \ PS2DEV Open Source Project. +#----------------------------------------------------------------------- +# Copyright (c) 2003 Marcus R. Brown +# Licenced under Academic Free License version 2.0 +# Review ps2sdk README & LICENSE files for further details. +# +# $Id$ +# Defines all IRX imports. +*/ + +#ifndef IOP_IRX_IMPORTS_H +#define IOP_IRX_IMPORTS_H + +#include "irx.h" + +/* Please keep these in alphabetical order! */ +#include "dmacman.h" +#include "intrman.h" +#include "libsd.h" +#include "loadcore.h" +#include "sifcmd.h" +#include "stdio.h" +#include "sysclib.h" +#include "sysmem.h" +#include "thbase.h" +#include "thevent.h" +#include "thmsgbx.h" +#include "thsemap.h" +#include "usbd.h" +#include "vblank.h" + +#endif /* IOP_IRX_IMPORTS_H */ diff --git a/backends/platform/ps2/iop/rpckbd/src/ps2kbd.c b/backends/platform/ps2/iop/rpckbd/src/ps2kbd.c new file mode 100644 index 00000000000..f87a47f0cbd --- /dev/null +++ b/backends/platform/ps2/iop/rpckbd/src/ps2kbd.c @@ -0,0 +1,1199 @@ +/* +# _____ ___ ____ ___ ____ +# ____| | ____| | | |____| +# | ___| |____ ___| ____| | \ PS2DEV Open Source Project. +#----------------------------------------------------------------------- +# Copyright 2001-2005, ps2dev - http://www.ps2dev.org +# Licenced under Academic Free License version 2.0 +# Review ps2sdk README & LICENSE files for further details. +# +# $Id$ +# USB Keyboard Driver for PS2 +*/ + +#include "types.h" +#include "ioman.h" +#include "loadcore.h" +#include "stdio.h" +#include "sifcmd.h" +#include "sifrpc.h" +#include "sysclib.h" +#include "sysmem.h" +#include "usbd.h" +#include "usbd_macro.h" +#include "thbase.h" +#include "thevent.h" +#include "thsemap.h" + +#include "ps2kbd.h" +#include "us_keymap.h" + +#define PS2KBD_VERSION 0x100 + +#define USB_SUBCLASS_BOOT 1 +#define USB_HIDPROTO_KEYBOARD 1 + +#define PS2KBD_MAXDEV 2 +#define PS2KBD_MAXKEYS 6 + +#define PS2KBD_DEFLINELEN 4096 +#define PS2KBD_DEFREPEATRATE 100 +/* Default repeat rate in milliseconds */ +#define PS2KBD_REPEATWAIT 1000 +/* Number of milliseconds to wait before starting key repeat */ +#define USB_KEYB_NUMLOCK 0x53 +#define USB_KEYB_CAPSLOCK 0x39 +#define USB_KEYB_SCRLOCK 0x47 + +#define USB_KEYB_NUMPAD_START 0x54 +#define USB_KEYB_NUMPAD_END 0x63 + +#define SEMA_ZERO -419 +#define SEMA_DELETED -425 + +int ps2kbd_init(); +void ps2kbd_config_set(int resultCode, int bytes, void *arg); +void ps2kbd_idlemode_set(int resultCode, int bytes, void *arg); +void ps2kbd_data_recv(int resultCode, int bytes, void *arg); +int ps2kbd_probe(int devId); +int ps2kbd_connect(int devId); +int ps2kbd_disconnect(int devId); +void usb_getstring(int endp, int index, char *desc); + +typedef struct _kbd_data_recv + +{ + u8 mod_keys; + u8 reserved; + u8 keycodes[PS2KBD_MAXKEYS]; +} kbd_data_recv; + +typedef struct _keyb_dev + +{ + int configEndp; + int dataEndp; + int packetSize; + int devId; + int interfaceNo; /* Holds the interface number selected on this device */ + char repeatkeys[2]; + u32 eventmask; + u8 ledStatus; /* Maintains state on the led status */ + kbd_data_recv oldData; + kbd_data_recv data; /* Holds the data for the transfers */ +} kbd_dev; + +/* Global Variables */ + +int kbd_readmode; +u32 kbd_repeatrate; +kbd_dev *devices[PS2KBD_MAXDEV]; /* Holds a list of current devices */ +int dev_count; +UsbDriver kbd_driver = { NULL, NULL, "PS2Kbd", ps2kbd_probe, ps2kbd_connect, ps2kbd_disconnect }; +u8 *lineBuffer; +u32 lineStartP, lineEndP; +int lineSema; +int bufferSema; +u32 lineSize; +u8 keymap[PS2KBD_KEYMAP_SIZE]; /* Normal key map */ +u8 shiftkeymap[PS2KBD_KEYMAP_SIZE]; /* Shifted key map */ +u8 keycap[PS2KBD_KEYMAP_SIZE]; /* Does this key get shifted by capslock ? */ +u8 special_keys[PS2KBD_KEYMAP_SIZE]; +u8 control_map[PS2KBD_KEYMAP_SIZE]; +u8 alt_map[PS2KBD_KEYMAP_SIZE]; +//static struct fileio_driver kbd_fdriver; +iop_device_t kbd_filedrv; +u8 keyModValue[8] = { 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7 }; +int repeat_tid; +int eventid; /* Id of the repeat event */ + +int _start () +{ + FlushDcache(); + + ps2kbd_init(); + + printf("PS2KBD - USB Keyboard Library\n"); + + return 0; + +} + +int ps2kbd_probe(int devId) + +{ + UsbDeviceDescriptor *dev; + UsbConfigDescriptor *conf; + UsbInterfaceDescriptor *intf; + UsbEndpointDescriptor *endp; + //UsbStringDescriptor *str; + + if(dev_count >= PS2KBD_MAXDEV) + { + printf("ERROR: Maximum keyboard devices reached\n"); + return 0; + } + + //printf("PS2Kbd_probe devId %d\n", devId); + + dev = UsbGetDeviceStaticDescriptor(devId, NULL, USB_DT_DEVICE); /* Get device descriptor */ + if(!dev) + { + printf("ERROR: Couldn't get device descriptor\n"); + return 0; + } + + //printf("Device class %d, Size %d, Man %d, Product %d Cpnfigurations %d\n", dev->bDeviceClass, dev->bMaxPacketSize0, dev->iManufacturer, dev->iProduct, dev->bNumConfigurations); + /* Check that the device class is specified in the interfaces and it has at least one configuration */ + if((dev->bDeviceClass != USB_CLASS_PER_INTERFACE) || (dev->bNumConfigurations < 1)) + { + //printf("This is not the droid you're looking for\n"); + return 0; + } + + conf = UsbGetDeviceStaticDescriptor(devId, dev, USB_DT_CONFIG); + if(!conf) + { + printf("ERROR: Couldn't get configuration descriptor\n"); + return 0; + } + //printf("Config Length %d Total %d Interfaces %d\n", conf->bLength, conf->wTotalLength, conf->bNumInterfaces); + + if((conf->bNumInterfaces < 1) || (conf->wTotalLength < (sizeof(UsbConfigDescriptor) + sizeof(UsbInterfaceDescriptor)))) + { + printf("ERROR: No interfaces available\n"); + return 0; + } + + intf = (UsbInterfaceDescriptor *) ((char *) conf + conf->bLength); /* Get first interface */ +/* printf("Interface Length %d Endpoints %d Class %d Sub %d Proto %d\n", intf->bLength, */ +/* intf->bNumEndpoints, intf->bInterfaceClass, intf->bInterfaceSubClass, */ +/* intf->bInterfaceProtocol); */ + + if((intf->bInterfaceClass != USB_CLASS_HID) || (intf->bInterfaceSubClass != USB_SUBCLASS_BOOT) || + (intf->bInterfaceProtocol != USB_HIDPROTO_KEYBOARD) || (intf->bNumEndpoints < 1)) + + { + //printf("We came, we saw, we told it to fuck off\n"); + return 0; + } + + endp = (UsbEndpointDescriptor *) ((char *) intf + intf->bLength); + endp = (UsbEndpointDescriptor *) ((char *) endp + endp->bLength); /* Go to the data endpoint */ + + //printf("Endpoint 1 Addr %d, Attr %d, MaxPacket %d\n", endp->bEndpointAddress, endp->bmAttributes, endp->wMaxPacketSizeLB); + + if(((endp->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT) || + ((endp->bEndpointAddress & USB_ENDPOINT_DIR_MASK) != USB_DIR_IN)) + { + printf("ERROR: Endpoint not interrupt type and/or an input\n"); + return 0; + } + + printf("PS2KBD: Found a keyboard device\n"); + + return 1; +} + +int ps2kbd_connect(int devId) + +{ + /* Assume we can only get here if we have already checked the device is kosher */ + + UsbDeviceDescriptor *dev; + UsbConfigDescriptor *conf; + UsbInterfaceDescriptor *intf; + UsbEndpointDescriptor *endp; + kbd_dev *currDev; + int devLoop; + + //printf("PS2Kbd_connect devId %d\n", devId); + + dev = UsbGetDeviceStaticDescriptor(devId, NULL, USB_DT_DEVICE); /* Get device descriptor */ + if(!dev) + { + printf("ERROR: Couldn't get device descriptor\n"); + return 1; + } + + conf = UsbGetDeviceStaticDescriptor(devId, dev, USB_DT_CONFIG); + if(!conf) + { + printf("ERROR: Couldn't get configuration descriptor\n"); + return 1; + } + + intf = (UsbInterfaceDescriptor *) ((char *) conf + conf->bLength); /* Get first interface */ + endp = (UsbEndpointDescriptor *) ((char *) intf + intf->bLength); + endp = (UsbEndpointDescriptor *) ((char *) endp + endp->bLength); /* Go to the data endpoint */ + + for(devLoop = 0; devLoop < PS2KBD_MAXDEV; devLoop++) + { + if(devices[devLoop] == NULL) + { + break; + } + } + + if(devLoop == PS2KBD_MAXDEV) + { + /* How the f*** did we end up here ??? */ + printf("ERROR: Device Weirdness!!\n"); + return 1; + } + + currDev = (kbd_dev *) AllocSysMemory(0, sizeof(kbd_dev), NULL); + if(!currDev) + { + printf("ERROR: Couldn't allocate a device point for the kbd\n"); + return 1; + } + + devices[devLoop] = currDev; + memset(currDev, 0, sizeof(kbd_dev)); + currDev->configEndp = UsbOpenEndpoint(devId, NULL); + currDev->dataEndp = UsbOpenEndpoint(devId, endp); + currDev->packetSize = endp->wMaxPacketSizeLB | ((int) endp->wMaxPacketSizeHB << 8); + currDev->eventmask = (1 << devLoop); + if(currDev->packetSize > sizeof(kbd_data_recv)) + { + currDev->packetSize = sizeof(kbd_data_recv); + } + + if(dev->iManufacturer != 0) + { + usb_getstring(currDev->configEndp, dev->iManufacturer, "Keyboard Manufacturer"); + } + + if(dev->iProduct != 0) + { + usb_getstring(currDev->configEndp, dev->iProduct, "Keyboard Product"); + } + + currDev->devId = devId; + currDev->interfaceNo = intf->bInterfaceNumber; + currDev->ledStatus = 0; + + UsbSetDevicePrivateData(devId, currDev); /* Set the index for the device data */ + + //printf("Configuration value %d\n", conf->bConfigurationValue); + UsbSetDeviceConfiguration(currDev->configEndp, conf->bConfigurationValue, ps2kbd_config_set, currDev); + + dev_count++; /* Increment device count */ + printf("PS2KBD: Connected device\n"); + + return 0; +} + +int ps2kbd_disconnect(int devId) + +{ + int devLoop; + printf("PS2Kbd_disconnect devId %d\n", devId); + + for(devLoop = 0; devLoop < PS2KBD_MAXDEV; devLoop++) + { + if((devices[devLoop]) && (devices[devLoop]->devId == devId)) + { + dev_count--; + FreeSysMemory(devices[devLoop]); + devices[devLoop] = NULL; + printf("PS2KBD: Disconnected device\n"); + break; + } + } + + return 0; +} + +typedef struct _string_descriptor + +{ + u8 buf[200]; + char *desc; +} string_descriptor; + +void ps2kbd_getstring_set(int resultCode, int bytes, void *arg) + +{ + UsbStringDescriptor *str = (UsbStringDescriptor *) arg; + string_descriptor *strBuf = (string_descriptor *) arg; + char string[50]; + int strLoop; + +/* printf("=========getstring=========\n"); */ + +/* printf("PS2KEYBOARD: GET_DESCRIPTOR res %d, bytes %d, arg %p\n", resultCode, bytes, arg); */ + + if(resultCode == USB_RC_OK) + { + memset(string, 0, 50); + for(strLoop = 0; strLoop < ((bytes - 2) / 2); strLoop++) + { + string[strLoop] = str->wData[strLoop] & 0xFF; + } + printf("%s: %s\n", strBuf->desc, string); + } + + FreeSysMemory(arg); +} + +void usb_getstring(int endp, int index, char *desc) + +{ + u8 *data; + string_descriptor *str; + int ret; + + data = (u8 *) AllocSysMemory(0, sizeof(string_descriptor), NULL); + str = (string_descriptor *) data; + + if(data != NULL) + { + str->desc = desc; + ret = UsbControlTransfer(endp, 0x80, USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | index, + 0, sizeof(string_descriptor) - 4, data, ps2kbd_getstring_set, data); + if(ret != USB_RC_OK) + { + printf("PS2KBD: Error sending string descriptor request\n"); + FreeSysMemory(data); + } + } +} + +void ps2kbd_config_set(int resultCode, int bytes, void *arg) + /* Called when we have finished choosing our configuration */ + +{ + kbd_dev *dev; + + if(resultCode != USB_RC_OK) + { + printf("PS2KEYBOARD: Configuration set error res %d, bytes %d, arg %p\n", resultCode, bytes, arg); + return; + } + + //printf("PS2KEYBOARD: Configuration set res %d, bytes %d, arg %p\n", resultCode, bytes, arg); + /* Do a interrupt data transfer */ + + dev = (kbd_dev *) arg; + if(dev != NULL) + { + int ret; + + ret = UsbControlTransfer(dev->configEndp, 0x21, USB_REQ_SET_IDLE, 0, dev->interfaceNo, 0, NULL, ps2kbd_idlemode_set, arg); + } +} + +void ps2kbd_idlemode_set(int resultCode, int bytes, void *arg) + +{ + kbd_dev *dev; + + + + if(resultCode != USB_RC_OK) + { + printf("PS2KBD: Idlemode set error res %d, bytes %d, arg %p\n", resultCode, bytes, arg); + return; + } + + dev = (kbd_dev *) arg; + if(dev != NULL) + { + int ret; + + ret = UsbInterruptTransfer(dev->dataEndp, &dev->data, dev->packetSize, ps2kbd_data_recv, arg); + } +} + +void ps2kbd_led_set(int resultCode, int bytes, void *arg) + +{ + //printf("LED Set\n"); +} + +void ps2kbd_build_uniquekeys(u8 *res, const u8 *new, const u8 *old) + + /* Builds a list of unique keys */ + +{ + int loopNew, loopOld; + int loopRes = 0; + int foundKey; + + for(loopNew = 0; loopNew < PS2KBD_MAXKEYS; loopNew++) + { + if(new[loopNew] != 0) + { + foundKey = 0; + for(loopOld = 0; loopOld < PS2KBD_MAXKEYS; loopOld++) + { + if(new[loopNew] == old[loopOld]) + { + foundKey = 1; + break; + } + } + if(!foundKey) + { + res[loopRes++] = new[loopNew]; + } + } + } +} + +u32 ps2kbd_repeathandler(void *arg) + +{ + kbd_dev *dev = arg; + iop_sys_clock_t t; + //printf("Repeat handler\n"); + + iSetEventFlag(eventid, dev->eventmask); + + USec2SysClock(kbd_repeatrate * 1000, &t); + iSetAlarm(&t, ps2kbd_repeathandler, arg); + + return t.hi; +} + +void ps2kbd_getkeys(u8 keyMods, u8 ledStatus, const u8 *keys, kbd_dev *dev) + +{ + int loopKey; + int tempPos = 0; + int byteCount = 0; + u8 currChars[2]; + + if(lineStartP < lineEndP) + { + tempPos = lineStartP + lineSize; + } + else + { + tempPos = lineStartP; + } + + for(loopKey = 0; loopKey < PS2KBD_MAXKEYS; loopKey++) + { + u8 currKey = keys[loopKey]; + + currChars[0] = 0; + currChars[1] = 0; + + if(lineEndP == (tempPos - 1)) + { + break; + } + + if(currKey) /* If this is a valid key */ + { + if((currKey >= USB_KEYB_NUMPAD_START) && (currKey <= USB_KEYB_NUMPAD_END)) + /* Handle numpad specially */ + { + if(ledStatus & PS2KBD_LED_NUMLOCK) + { + if(keymap[currKey]) + { + currChars[0] = keymap[currKey]; + } + } + else + { + if(special_keys[currKey]) + { + currChars[0] = PS2KBD_ESCAPE_KEY; + currChars[1] = special_keys[currKey]; + } + else if(keymap[currKey] != '5') /* Make sure this isnt a 5 key :) */ + { + currChars[0] = keymap[currKey]; + } + } + } + else if(special_keys[currKey]) /* This is a special key */ + { + currChars[0] = PS2KBD_ESCAPE_KEY; + currChars[1] = special_keys[currKey]; + } + else if(keyMods & PS2KBD_CTRL) /* CTRL */ + { + if(control_map[currKey]) + { + currChars[0] = control_map[currKey]; + } + } + else if(keyMods & PS2KBD_ALT) /* ALT */ + { + if(alt_map[currKey]) + { + currChars[0] = alt_map[currKey]; + } + } + else if(keyMods & PS2KBD_SHIFT) /* SHIFT */ + { + if((ledStatus & PS2KBD_LED_CAPSLOCK) && (keycap[currKey])) + { + currChars[0] = keymap[currKey]; + } + else + { + currChars[0] = shiftkeymap[currKey]; + } + } + else /* Normal key */ + { + if(keymap[keys[loopKey]]) + { + if((ledStatus & PS2KBD_LED_CAPSLOCK) && (keycap[currKey])) + { + currChars[0] = shiftkeymap[currKey]; + } + else + { + currChars[0] = keymap[currKey]; + } + } + } + } + + if((currChars[0] == PS2KBD_ESCAPE_KEY) && (currChars[1] != 0)) + { + if(lineEndP != (tempPos - 2)) + { + lineBuffer[lineEndP++] = currChars[0]; + lineEndP %= lineSize; + lineBuffer[lineEndP++] = currChars[1]; + lineEndP %= lineSize; + byteCount += 2; + } + dev->repeatkeys[0] = currChars[0]; + dev->repeatkeys[1] = currChars[1]; + } + else if(currChars[0] != 0) + { + lineBuffer[lineEndP++] = currChars[0]; + lineEndP %= lineSize; + byteCount++; + dev->repeatkeys[0] = currChars[0]; + dev->repeatkeys[1] = 0; + } + } + + if(byteCount > 0) + { + iop_sys_clock_t t; + /* Set alarm to do repeat rate */ + //printf("repeatkeys %d %d\n", kbd_repeatkeys[0], kbd_repeatkeys[1]); + USec2SysClock(PS2KBD_REPEATWAIT * 1000, &t); + SetAlarm(&t, ps2kbd_repeathandler, dev); + } + + for(loopKey = 0; loopKey < byteCount; loopKey++) /* Signal the sema to indicate data */ + { + SignalSema(bufferSema); + } + +/* lineBuffer[PS2KBD_DEFLINELEN - 1] = 0; */ +/* printf(lineBuffer); */ + //printf("lineStart %d, lineEnd %d\n", lineStartP, lineEndP); +} + + +void ps2kbd_getkeys_raw(u8 newKeyMods, u8 oldKeyMods, u8 *new, const u8 *old) + +{ + int loopKey; + u8 currKey; + u8 keyMods = newKeyMods ^ oldKeyMods; + u8 keyModsMap = newKeyMods & keyMods; + int tempPos = 0; + int byteCount = 0; + + if(lineStartP < lineEndP) + { + tempPos = lineStartP + lineSize; + } + else + { + tempPos = lineStartP; + } + + for(loopKey = 0; loopKey < 8; loopKey++) + { + int currMod = (1 << loopKey); + if(keyMods & currMod) + { + if(lineEndP == (tempPos - 2)) + { + return; + } + + currKey = keyModValue[loopKey]; + + if(keyModsMap & currMod) /* If key pressed */ + { + lineBuffer[lineEndP++] = PS2KBD_RAWKEY_DOWN; + //printf("Key down\n"); + } + else + { + lineBuffer[lineEndP++] = PS2KBD_RAWKEY_UP; + //printf("Key up\n"); + } + + lineEndP %= lineSize; + lineBuffer[lineEndP++] = currKey; + lineEndP %= lineSize; + byteCount += 2; + //printf("Key %d\n", currKey); + } + } + + for(loopKey = 0; loopKey < PS2KBD_MAXKEYS; loopKey++) + { + if(lineEndP == (tempPos - 2)) + { + return; + } + + if(new[loopKey] != 0) + { + lineBuffer[lineEndP++] = PS2KBD_RAWKEY_DOWN; + lineEndP %= lineSize; + lineBuffer[lineEndP++] = new[loopKey]; + lineEndP %= lineSize; + byteCount += 2; + //printf("Key down\nKey %d\n", new[loopKey]); + } + + } + + for(loopKey = 0; loopKey < PS2KBD_MAXKEYS; loopKey++) + { + if(lineEndP == (tempPos - 2)) + { + return; + } + + if(old[loopKey] != 0) + { + lineBuffer[lineEndP++] = PS2KBD_RAWKEY_UP; + lineEndP %= lineSize; + lineBuffer[lineEndP++] = old[loopKey]; + lineEndP %= lineSize; + byteCount += 2; + //printf("Key up\nKey %d\n", old[loopKey]); + } + + } + + for(loopKey = 0; loopKey < byteCount; loopKey++) /* Signal the sema for the number of bytes read */ + { + SignalSema(bufferSema); + } +} + +void ps2kbd_data_recv(int resultCode, int bytes, void *arg) + +{ + kbd_dev *dev; + int ret; + int phantom; + int loop; + + if(resultCode != USB_RC_OK) + { + printf("PS2KEYBOARD: Data Recv set res %d, bytes %d, arg %p\n", resultCode, bytes, arg); + return; + } + + //printf("PS2KBD: Data Recv set res %d, bytes %d, arg %p\n", resultCode, bytes, arg); + + dev = (kbd_dev *) arg; + if(dev == NULL) + { + printf("PS2KBD: dev == NULL\n"); + return; + } + +/* printf("PS2KBD Modifiers %02X, Keys ", dev->data.mod_keys); */ +/* for(loop = 0; loop < PS2KBD_MAXKEYS; loop++) */ +/* { */ +/* printf("%02X ", dev->data.keycodes[loop]); */ +/* } */ +/* printf("\n"); */ + + CancelAlarm(ps2kbd_repeathandler, dev); /* Make sure repeat alarm is cancelled */ + + /* Check for phantom states */ + phantom = 1; + for(loop = 0; loop < PS2KBD_MAXKEYS; loop++) + { + if(dev->data.keycodes[loop] != 1) + { + phantom = 0; + break; + } + } + + if(!phantom) /* If not in a phantom state */ + { + u8 uniqueKeys[PS2KBD_MAXKEYS]; + u8 missingKeys[PS2KBD_MAXKEYS]; + int loopKey; + + memset(uniqueKeys, 0, PS2KBD_MAXKEYS); + memset(missingKeys, 0, PS2KBD_MAXKEYS); + ps2kbd_build_uniquekeys(uniqueKeys, dev->data.keycodes, dev->oldData.keycodes); + ps2kbd_build_uniquekeys(missingKeys, dev->oldData.keycodes, dev->data.keycodes); + /* Build new and missing key lists */ + +/* printf("Unique keys : "); */ +/* for(loopKey = 0; loopKey < PS2KBD_MAXKEYS; loopKey++) */ +/* { */ +/* printf("%02X ", uniqueKeys[loopKey]); */ +/* } */ +/* printf("\n"); */ + +/* printf("Missing keys : "); */ +/* for(loopKey = 0; loopKey < PS2KBD_MAXKEYS; loopKey++) */ +/* { */ +/* printf("%02X ", missingKeys[loopKey]); */ +/* } */ +/* printf("\n"); */ + + if(kbd_readmode == PS2KBD_READMODE_NORMAL) + { + u8 ledStatus; + + ledStatus = dev->ledStatus; + //printf("ledStatus %02X\n", ledStatus); + + for(loopKey = 0; loopKey < PS2KBD_MAXKEYS; loopKey++) /* Process key codes */ + { + switch(uniqueKeys[loopKey]) + { + case USB_KEYB_NUMLOCK : + ledStatus ^= PS2KBD_LED_NUMLOCK; + uniqueKeys[loopKey] = 0; + break; + case USB_KEYB_CAPSLOCK : + ledStatus ^= PS2KBD_LED_CAPSLOCK; + uniqueKeys[loopKey] = 0; + break; + case USB_KEYB_SCRLOCK : + ledStatus ^= PS2KBD_LED_SCRLOCK; + uniqueKeys[loopKey] = 0; + break; + } + } + + if(ledStatus != dev->ledStatus) + { + dev->ledStatus = ledStatus & PS2KBD_LED_MASK; + //printf("LEDS %02X\n", dev->ledStatus); + /* Call Set LEDS */ + UsbControlTransfer(dev->configEndp, 0x21, USB_REQ_SET_REPORT, 0x200, + dev->interfaceNo, 1, &dev->ledStatus, ps2kbd_led_set, arg); + } + + WaitSema(lineSema); /* Make sure no other thread is going to manipulate the buffer */ + ps2kbd_getkeys(dev->data.mod_keys, dev->ledStatus, uniqueKeys, dev); /* read in remaining keys */ + SignalSema(lineSema); + } + else /* RAW Mode */ + { + WaitSema(lineSema); + ps2kbd_getkeys_raw(dev->data.mod_keys, dev->oldData.mod_keys, uniqueKeys, missingKeys); + SignalSema(lineSema); + } + + memcpy(&dev->oldData, &dev->data, sizeof(kbd_data_recv)); + } + + ret = UsbInterruptTransfer(dev->dataEndp, &dev->data, dev->packetSize, ps2kbd_data_recv, arg); +} + +void flushbuffer() + +{ + iop_sema_t s; + + lineStartP = 0; + lineEndP = 0; + memset(lineBuffer, 0, lineSize); + + DeleteSema(bufferSema); + s.initial = 0; + s.max = lineSize; + s.option = 0; + s.attr = 0; + bufferSema = CreateSema(&s); /* Create a sema to maintain status of readable data */ + + if(bufferSema <= 0) + { + printf("Error creating buffer sema\n"); + } +} + +void ps2kbd_rpc_setreadmode(u32 readmode) + +{ + int devLoop; + + if(readmode == kbd_readmode) return; + + if((readmode == PS2KBD_READMODE_NORMAL) || (readmode == PS2KBD_READMODE_RAW)) + { + /* Reset line buffer */ + //printf("ioctl_setreadmode %d\n", readmode); + for(devLoop = 0; devLoop < PS2KBD_MAXDEV; devLoop++) + { + CancelAlarm(ps2kbd_repeathandler, devices[devLoop]); + } + + WaitSema(lineSema); + kbd_readmode = readmode; + flushbuffer(); + SignalSema(lineSema); + } +} + +void ps2kbd_rpc_setkeymap(kbd_keymap *keymaps) + +{ + //printf("ioctl_setkeymap %p\n", keymaps); + WaitSema(lineSema); /* Lock the input so you dont end up with weird results */ + memcpy(keymap, keymaps->keymap, PS2KBD_KEYMAP_SIZE); + memcpy(shiftkeymap, keymaps->shiftkeymap, PS2KBD_KEYMAP_SIZE); + memcpy(keycap, keymaps->keycap, PS2KBD_KEYMAP_SIZE); + SignalSema(lineSema); +} + +void ps2kbd_rpc_setctrlmap(u8 *ctrlmap) + +{ + //printf("ioctl_setctrlmap %p\n", ctrlmap); + WaitSema(lineSema); + memcpy(control_map, ctrlmap, PS2KBD_KEYMAP_SIZE); + SignalSema(lineSema); +} + +void ps2kbd_rpc_setaltmap(u8 *altmap) + +{ + //printf("ioctl_setaltmap %p\n", altmap); + WaitSema(lineSema); + memcpy(alt_map, altmap, PS2KBD_KEYMAP_SIZE); + SignalSema(lineSema); +} + +void ps2kbd_rpc_setspecialmap(u8 *special) + +{ + //printf("ioctl_setspecialmap %p\n", special); + WaitSema(lineSema); + memcpy(special_keys, special, PS2KBD_KEYMAP_SIZE); + SignalSema(lineSema); +} + +void ps2kbd_rpc_resetkeymap() + /* Reset keymap to default US variety */ + +{ + //printf("ioctl_resetkeymap()\n"); + WaitSema(lineSema); + memcpy(keymap, us_keymap, PS2KBD_KEYMAP_SIZE); + memcpy(shiftkeymap, us_shiftkeymap, PS2KBD_KEYMAP_SIZE); + memcpy(keycap, us_keycap, PS2KBD_KEYMAP_SIZE); + memcpy(special_keys, us_special_keys, PS2KBD_KEYMAP_SIZE); + memcpy(control_map, us_control_map, PS2KBD_KEYMAP_SIZE); + memcpy(alt_map, us_alt_map, PS2KBD_KEYMAP_SIZE); + SignalSema(lineSema); +} + +void ps2kbd_rpc_flushbuffer() + /* Flush the internal buffer */ + +{ + //printf("ioctl_flushbuffer()\n"); + WaitSema(lineSema); + flushbuffer(); + SignalSema(lineSema); +} + +void ps2kbd_rpc_setleds(u8 ledStatus) + +{ + int devLoop; + kbd_dev *dev; + + //printf("ioctl_setleds %d\n", ledStatus); + ledStatus &= PS2KBD_LED_MASK; + for(devLoop = 0; devLoop < PS2KBD_MAXDEV; devLoop++) + { + dev = devices[devLoop]; + if(dev) + { + if(ledStatus != dev->ledStatus) + { + dev->ledStatus = ledStatus & PS2KBD_LED_MASK; + UsbControlTransfer(dev->configEndp, 0x21, USB_REQ_SET_REPORT, 0x200, + dev->interfaceNo, 1, &dev->ledStatus, ps2kbd_led_set, dev); + } + } + } +} + +void ps2kbd_rpc_setrepeatrate(u32 rate) +{ + kbd_repeatrate = rate; +} + +int kbd_read(void *buf, int size) +{ + int count = 0; + char *data = (char *) buf; + + if(kbd_readmode == PS2KBD_READMODE_RAW) + size &= ~1; /* Ensure size of a multiple of 2 */ + + if (PollSema(bufferSema) >= 0) { + SignalSema(bufferSema); + if (WaitSema(lineSema) >= 0) { + while((count < size) && (lineStartP != lineEndP)) { + data[count] = lineBuffer[lineStartP++]; + lineStartP %= lineSize; + count++; + PollSema(bufferSema); /* Take off one count from the sema */ + } + SignalSema(lineSema); + } + } + return count; +} + +void repeat_thread(void *arg) + +{ + u32 eventmask; + int devLoop; + + for(;;) + { + WaitEventFlag(eventid, 0xFFFFFFFF, 0x01 | 0x10, &eventmask); + //printf("Recieved event %08X\n", eventmask); + for(devLoop = 0; devLoop < PS2KBD_MAXDEV; devLoop++) + { + if((eventmask & (1 << devLoop)) && (devices[devLoop])) + { + int tempPos = 0; + + WaitSema(lineSema); + if(lineStartP < lineEndP) + { + tempPos = lineStartP + lineSize; + } + else + { + tempPos = lineStartP; + } + + if((devices[devLoop]->repeatkeys[0]) && (devices[devLoop]->repeatkeys[1])) + { + if(lineEndP != (tempPos - 2)) + { + lineBuffer[lineEndP++] = devices[devLoop]->repeatkeys[0]; + lineEndP %= lineSize; + lineBuffer[lineEndP++] = devices[devLoop]->repeatkeys[1]; + lineEndP %= lineSize; + SignalSema(bufferSema); + SignalSema(bufferSema); + } + } + else if(devices[devLoop]->repeatkeys[0]) + { + if(lineEndP != (tempPos - 1)) + { + lineBuffer[lineEndP++] = devices[devLoop]->repeatkeys[0]; + lineEndP %= lineSize; + SignalSema(bufferSema); + } + } + + SignalSema(lineSema); + } + } + } +} + +int init_repeatthread() + /* Creates a thread to handle key repeats */ +{ + iop_thread_t param; + iop_event_t event; + + event.attr = 0; + event.option = 0; + event.bits = 0; + eventid = CreateEventFlag(&event); + + param.attr = TH_C; + param.thread = repeat_thread; + param.priority = 40; + param.stacksize = 0x800; + param.option = 0; + + repeat_tid = CreateThread(¶m); + if (repeat_tid > 0) { + StartThread(repeat_tid, 0); + return 0; + } + else + { + return 1; + } +} + +static unsigned long retKey; + +void *ps2kbd_rpc_server(int fno, void *data, int size) { + retKey = 0; + switch (fno) { + case KBD_RPC_SETREADMODE: + ps2kbd_rpc_setreadmode(*(u32 *)data); + break; + case KBD_RPC_SETKEYMAP: + ps2kbd_rpc_setkeymap((kbd_keymap *) data); + break; + case KBD_RPC_SETALTMAP: + ps2kbd_rpc_setaltmap((u8 *) data); + break; + case KBD_RPC_SETCTRLMAP: + ps2kbd_rpc_setctrlmap((u8 *) data); + break; + case KBD_RPC_SETSPECIALMAP: + ps2kbd_rpc_setspecialmap((u8 *) data); + break; + case KBD_RPC_FLUSHBUFFER: + ps2kbd_rpc_flushbuffer(); + break; + case KBD_RPC_SETLEDS: + ps2kbd_rpc_setleds(*(u8*) data); + break; + case KBD_RPC_RESETKEYMAP: + ps2kbd_rpc_resetkeymap(); + break; + case KBD_RPC_SETREPEATRATE: + ps2kbd_rpc_setrepeatrate(*(u32 *) data); + break; + case KBD_RPC_READRAW: + kbd_read(&retKey, 2); + return &retKey; + case KBD_RPC_READKEY: + kbd_read(&retKey, 1); + return &retKey; + default: + printf("Ps2Kbd: Unknown RPC command %d\n", fno); + break; + } + return NULL; +} + +struct t_SifRpcDataQueue qd; +struct t_SifRpcServerData sd0; +void *rpcRcvBuf; + +void ps2kbd_start_rpc(unsigned long tid) { + rpcRcvBuf = AllocSysMemory(0, 3 * PS2KBD_KEYMAP_SIZE, NULL); + printf("Ps2Kbd: starting RPC server\n"); + SifInitRpc(0); + + SifSetRpcQueue(&qd, tid); + SifRegisterRpc(&sd0, PS2KBD_RPC_ID, ps2kbd_rpc_server, rpcRcvBuf, 0, 0, &qd); + SifRpcLoop(&qd); +} + +int ps2kbd_init_rpc(void) { + struct _iop_thread param; + int th; + + param.attr = 0x02000000; + param.thread = (void*)ps2kbd_start_rpc; + param.priority = 40; + param.stacksize = 0x800; + param.option = 0; + + th = CreateThread(¶m); + + if (th > 0) { + StartThread(th, (void *)th); + return 0; + } else + return -1; +} + +int ps2kbd_init() { + int ret; + iop_sema_t s; + + s.initial = 1; + s.max = 1; + s.option = 0; + s.attr = 0; + lineSema = CreateSema(&s); + if(lineSema <= 0) + { + printf("Error creating sema\n"); + return 1; + } + + s.initial = 0; + s.max = PS2KBD_DEFLINELEN; + s.option = 0; + s.attr = 0; + bufferSema = CreateSema(&s); /* Create a sema to maintain status of readable data */ + if(bufferSema <= 0) + { + printf("Error creating buffer sema\n"); + return 1; + } + + lineBuffer = (u8 *) AllocSysMemory(0, PS2KBD_DEFLINELEN, NULL); + if(lineBuffer == NULL) + { + printf("Error allocating line buffer\n"); + return 1; + } + lineStartP = 0; + lineEndP = 0; + lineSize = PS2KBD_DEFLINELEN; + memset(lineBuffer, 0, PS2KBD_DEFLINELEN); + + memset(devices, 0, sizeof(kbd_dev *) * PS2KBD_MAXDEV); + dev_count = 0; + kbd_readmode = PS2KBD_READMODE_NORMAL; + kbd_repeatrate = PS2KBD_DEFREPEATRATE; + memcpy(keymap, us_keymap, PS2KBD_KEYMAP_SIZE); + memcpy(shiftkeymap, us_shiftkeymap, PS2KBD_KEYMAP_SIZE); + memcpy(keycap, us_keycap, PS2KBD_KEYMAP_SIZE); + memcpy(special_keys, us_special_keys, PS2KBD_KEYMAP_SIZE); + memcpy(control_map, us_control_map, PS2KBD_KEYMAP_SIZE); + memcpy(alt_map, us_alt_map, PS2KBD_KEYMAP_SIZE); + + ps2kbd_init_rpc(); + init_repeatthread(); + + ret = UsbRegisterDriver(&kbd_driver); + if(ret != USB_RC_OK) + { + printf("Error registering USB devices\n"); + return 1; + } + + printf("UsbRegisterDriver %d\n", ret); + + return 0; +} diff --git a/backends/platform/ps2/iop/rpckbd/src/us_keymap.h b/backends/platform/ps2/iop/rpckbd/src/us_keymap.h new file mode 100644 index 00000000000..57f0686cd3e --- /dev/null +++ b/backends/platform/ps2/iop/rpckbd/src/us_keymap.h @@ -0,0 +1,1579 @@ +/* +# _____ ___ ____ ___ ____ +# ____| | ____| | | |____| +# | ___| |____ ___| ____| | \ PS2DEV Open Source Project. +#----------------------------------------------------------------------- +# Copyright 2001-2004, ps2dev - http://www.ps2dev.org +# Licenced under Academic Free License version 2.0 +# Review ps2sdk README & LICENSE files for further details. +# +# $Id$ +# USB Keyboard Driver for PS2 +*/ + +#ifndef __US_KEYMAP_H__ +#define __US_KEYMAP_H__ + +/* Default US keymap */ + +u8 us_keymap[PS2KBD_KEYMAP_SIZE] = + { + 0, + 0, + 0, + 0, + 'a', + 'b', + 'c', + 'd', + 'e', + 'f', + 'g', + 'h', + 'i', + 'j', + 'k', + 'l', + 'm', + 'n', + 'o', + 'p', + 'q', + 'r', + 's', + 't', + 'u', + 'v', + 'w', + 'x', + 'y', + 'z', + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '0', + 10, /* line feed */ + 0, /* Esc */ + 0x7,/* BS */ + 0x9, /* TAB */ + 0x20, + '-', + '=', + '[', + ']', + '\\', + '#', + ';', + '\'', + '`', + ',', + '.', + '/', + 0, /* CL */ + 0, // F1 + 0, // F2 + 0, // F3 + 0, // F4 + 0, // F5 + 0, // F6 + 0, // F7 + 0, // F8 + 0, // F9 + 0, // F10 + 0, // F11 + 0, // F12 + 0, // PrintScr + 0, // Scroll Lock + 0, // Pause + 0, // Insert + 0, // Home + 0, // Pg Up + 0, // Delete + 0, // End + 0, // Pg Down + 0, // Right + 0, // Left + 0, // Down + 0, // Up + 0, // Numlock + '/', // Keypad + '*', + '-', + '+', + 10, + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '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, + 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, + 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 + }; + +u8 us_shiftkeymap[PS2KBD_KEYMAP_SIZE] = + { + 0, + 0, + 0, + 0, + 'A', + 'B', + 'C', + 'D', + 'E', + 'F', + 'G', + 'H', + 'I', + 'J', + 'K', + 'L', + 'M', + 'N', + 'O', + 'P', + 'Q', + 'R', + 'S', + 'T', + 'U', + 'V', + 'W', + 'X', + 'Y', + 'Z', + '!', + '@', + '#', + '$', + '%', + '^', + '&', + '*', + '(', + ')', + 10, /* line feed */ + 0, /* Esc */ + 0x7,/* BS */ + 0x9, /* TAB */ + 0x20, + '_', + '+', + '{', + '}', + '|', + '~', + ':', + '"', + '~', + '<', + '>', + '?', + 0, /* CL */ + 0, // F1 + 0, // F2 + 0, // F3 + 0, // F4 + 0, // F5 + 0, // F6 + 0, // F7 + 0, // F8 + 0, // F9 + 0, // F10 + 0, // F11 + 0, // F12 + 0, // PrintScr + 0, // Scroll Lock + 0, // Pause + 0, // Insert + 0, // Home + 0, // Pg Up + 0, // Delete + 0, // End + 0, // Pg Down + 0, // Right + 0, // Left + 0, // Down + 0, // Up + 0, // Numlock + '/', // Keypad + '*', + '-', + '+', + 10, + '1', + '2', + '3', + '4', + '5', + '6', + '7', + '8', + '9', + '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, + 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, + 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 + }; + +u8 us_keycap[PS2KBD_KEYMAP_SIZE] = + { + 0, + 0, + 0, + 0, + 1, //a + 1, //b + 1, //c + 1, //d + 1, //e + 1, //f + 1,//g + 1,//h + 1,//i + 1,//j + 1,//k + 1,//l + 1,//m + 1,//n + 1,//o + 1,//p + 1,//q + 1,//r + 1,//s + 1,//t + 1,//u + 1,//v + 1,//w + 1,//x + 1,//y + 1,//z + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, /* line feed */ + 0, /* Esc */ + 0,/* BS */ + 0, /* TAB */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, /* CL */ + 0, // F1 + 0, // F2 + 0, // F3 + 0, // F4 + 0, // F5 + 0, // F6 + 0, // F7 + 0, // F8 + 0, // F9 + 0, // F10 + 0, // F11 + 0, // F12 + 0, // PrintScr + 0, // Scroll Lock + 0, // Pause + 0, // Insert + 0, // Home + 0, // Pg Up + 0, // Delete + 0, // End + 0, // Pg Down + 0, // Right + 0, // Left + 0, // Down + 0, // Up + 0, // Numlock + 0, // Keypad + 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, + 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, + 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 + }; + +u8 us_special_keys[PS2KBD_KEYMAP_SIZE] = { + + 0, + 0, + 0, + 0, + 0, //a + 0, //b + 0, //c + 0, //d + 0, //e + 0, //f + 0,//g + 0,//h + 0,//i + 0,//j + 0,//k + 0,//l + 0,//m + 0,//n + 0,//o + 0,//p + 0,//q + 0,//r + 0,//s + 0,//t + 0,//u + 0,//v + 0,//w + 0,//x + 0,//y + 0,//z + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, /* line feed */ + 0x1B, /* Esc */ + 0,/* BS */ + 0, /* TAB */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, /* CL */ + 1, // F1 + 2, // F2 + 3, // F3 + 4, // F4 + 5, // F5 + 6, // F6 + 7, // F7 + 8, // F8 + 9, // F9 + 10, // F10 + 11, // F11 + 12, // F12 + 32, // PrintScr + 33, // Scroll Lock + 34, // Pause + 35, // Insert + 36, // Home + 37, // Pg Up + 38, // Delete + 39, // End + 40, // Pg Down + 41, // Right + 42, // Left + 43, // Down + 44, // Up + 0, // Numlock + 0, // Keypad / + 0, // Keypad * + 0, // Keypad - + 0, // Keypad + + 0, // Keypad Enter + 39, // Keypad 1/End + 43, // Keypad 2/Down + 40, // Keypad 3/PageDn + 42, // Keypad 4/Left + 0, // Keypad 5 + 41, // Keypad 6/Right + 36, // Keypad 7/Home + 44, // Keypad 8/Up + 37, // Keypad 9/PageUp + 35, // Keypad 0/Insert + 38, // Keypad ./Delete + 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, + 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, + 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 + }; + +u8 us_control_map[PS2KBD_KEYMAP_SIZE] = { + + 0, + 0, + 0, + 0, + 1, //a + 2, //b + 3, //c + 4, //d + 5, //e + 6, //f + 7,//g + 8,//h + 9,//i + 10,//j + 11,//k + 12,//l + 13,//m + 14,//n + 15,//o + 16,//p + 17,//q + 18,//r + 19,//s + 20,//t + 21,//u + 22,//v + 23,//w + 24,//x + 25,//y + 26,//z + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, /* line feed */ + 0, /* Esc */ + 0,/* BS */ + 0, /* TAB */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, /* CL */ + 0, // F1 + 0, // F2 + 0, // F3 + 0, // F4 + 0, // F5 + 0, // F6 + 0, // F7 + 0, // F8 + 0, // F9 + 0, // F10 + 0, // F11 + 0, // F12 + 0, // PrintScr + 0, // Scroll Lock + 0, // Pause + 0, // Insert + 0, // Home + 0, // Pg Up + 0, // Delete + 0, // End + 0, // Pg Down + 0, // Right + 0, // Left + 0, // Down + 0, // Up + 0, // Numlock + 0, // Keypad + 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, + 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, + 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 +}; + +u8 us_alt_map[PS2KBD_KEYMAP_SIZE] = { + + 0, + 0, + 0, + 0, + 128, //a + 129, //b + 130, //c + 131, //d + 132, //e + 133, //f + 134,//g + 135,//h + 136,//i + 137,//j + 138,//k + 139,//l + 140,//m + 141,//n + 142,//o + 143,//p + 144,//q + 145,//r + 146,//s + 147,//t + 148,//u + 149,//v + 150,//w + 151,//x + 152,//y + 154,//z + 155, + 156, + 157, + 158, + 159, + 160, + 161, + 162, + 163, + 164, + 165, /* line feed */ + 0, /* Esc */ + 0,/* BS */ + 0, /* TAB */ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, /* CL */ + 0, // F1 + 0, // F2 + 0, // F3 + 0, // F4 + 0, // F5 + 0, // F6 + 0, // F7 + 0, // F8 + 0, // F9 + 0, // F10 + 0, // F11 + 0, // F12 + 0, // PrintScr + 0, // Scroll Lock + 0, // Pause + 0, // Insert + 0, // Home + 0, // Pg Up + 0, // Delete + 0, // End + 0, // Pg Down + 0, // Right + 0, // Left + 0, // Down + 0, // Up + 0, // Numlock + 0, // Keypad + 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, + 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, + 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 + }; + +#endif diff --git a/backends/platform/ps2/irxboot.cpp b/backends/platform/ps2/irxboot.cpp index 327e5bdc713..65d1e243ebd 100644 --- a/backends/platform/ps2/irxboot.cpp +++ b/backends/platform/ps2/irxboot.cpp @@ -43,7 +43,7 @@ IrxFile irxFiles[] = { { "PADMAN", BIOS, NOTHING, NULL, 0 }, { "LIBSD", BIOS, NOTHING, NULL, 0 }, - { "IOMANX.IRX", SYSTEM | NOT_HOST, NOTHING, NULL, 0 }, // already loaded by ps2link + { "IOMANX.IRX", SYSTEM /*| NOT_HOST*/, NOTHING, NULL, 0 }, // already loaded by ps2link { "FILEXIO.IRX", SYSTEM, NOTHING, NULL, 0 }, { "CODYVDFS.IRX", SYSTEM, NOTHING, NULL, 0 }, { "SJPCM.IRX", SYSTEM, NOTHING, NULL, 0 }, diff --git a/backends/platform/ps2/systemps2.cpp b/backends/platform/ps2/systemps2.cpp index b2818ebfc08..d4dd9aedcf9 100644 --- a/backends/platform/ps2/systemps2.cpp +++ b/backends/platform/ps2/systemps2.cpp @@ -54,9 +54,11 @@ #include "graphics/surface.h" #include "graphics/font.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include "common/events.h" #include "backends/platform/ps2/ps2debug.h" +#include "backends/fs/ps2/ps2-fs-factory.h" + // asm("mfc0 %0, $9\n" : "=r"(tickStart)); extern void *_gp; @@ -309,7 +311,9 @@ OSystem_PS2::OSystem_PS2(const char *elfPath) { void OSystem_PS2::init(void) { sioprintf("Timer...\n"); _scummTimerManager = new DefaultTimerManager(); - _scummMixer = new Audio::Mixer(); + _scummMixer = new Audio::MixerImpl(this); + _scummMixer->setOutputRate(44100); + _scummMixer->setReady(true); initTimer(); sioprintf("Starting SavefileManager\n"); @@ -410,7 +414,8 @@ void OSystem_PS2::soundThread(void) { // we have to produce more samples, call sound mixer // the scratchpad at 0x70000000 is used as temporary soundbuffer //_scummSoundProc(_scummSoundParam, (uint8*)0x70000000, SMP_PER_BLOCK * 2 * sizeof(int16)); - Audio::Mixer::mixCallback(_scummMixer, (byte*)0x70000000, SMP_PER_BLOCK * 2 * sizeof(int16)); + // Audio::Mixer::mixCallback(_scummMixer, (byte*)0x70000000, SMP_PER_BLOCK * 2 * sizeof(int16)); + _scummMixer->mixCallback((byte*)0x70000000, SMP_PER_BLOCK * 2 * sizeof(int16)); // demux data into 2 buffers, L and R __asm__ ( @@ -534,10 +539,6 @@ Common::TimerManager *OSystem_PS2::getTimerManager() { return _scummTimerManager; } -int OSystem_PS2::getOutputSampleRate(void) const { - return 48000; -} - Audio::Mixer *OSystem_PS2::getMixer() { return _scummMixer; } @@ -546,6 +547,10 @@ Common::SaveFileManager *OSystem_PS2::getSavefileManager(void) { return _saveManager; } +FilesystemFactory *OSystem_PS2::getFilesystemFactory() { + return &Ps2FilesystemFactory::instance(); +} + void OSystem_PS2::setShakePos(int shakeOffset) { _screen->setShakePos(shakeOffset); } @@ -727,7 +732,6 @@ void OSystem_PS2::quit(void) { driveStandby(); fio.umount("pfs0:"); } - //clearSoundCallback(); //setTimerCallback(NULL, 0); _screen->wantAnim(false); _systemQuit = true; diff --git a/backends/platform/ps2/systemps2.h b/backends/platform/ps2/systemps2.h index 9dbe9be5539..08975ab2c85 100644 --- a/backends/platform/ps2/systemps2.h +++ b/backends/platform/ps2/systemps2.h @@ -33,6 +33,7 @@ class DefaultTimerManager; class Gs2dScreen; class Ps2Input; class Ps2SaveFileManager; +// class Ps2FilesystemFactory; struct IrxReference; #define MAX_MUTEXES 16 @@ -48,7 +49,7 @@ namespace Common { }; namespace Audio { - class Mixer; + class MixerImpl; }; class OSystem_PS2 : public OSystem { @@ -87,7 +88,6 @@ public: virtual bool pollEvent(Common::Event &event); virtual Audio::Mixer *getMixer(); - virtual int getOutputSampleRate(void) const; virtual bool openCD(int drive); virtual bool pollCD(); @@ -112,6 +112,7 @@ public: virtual void colorToRGB(OverlayColor color, uint8 &r, uint8 &g, uint8 &b); virtual Common::SaveFileManager *getSavefileManager(); + virtual FilesystemFactory *getFilesystemFactory(); virtual void getTimeAndDate(struct tm &t) const; @@ -133,7 +134,7 @@ private: void readRtcTime(void); DefaultTimerManager *_scummTimerManager; - Audio::Mixer *_scummMixer; + Audio::MixerImpl *_scummMixer; bool _mouseVisible; diff --git a/backends/platform/psp/Makefile b/backends/platform/psp/Makefile index aa27f388b95..ca09286c4d2 100644 --- a/backends/platform/psp/Makefile +++ b/backends/platform/psp/Makefile @@ -2,10 +2,36 @@ # $URL$ # $Id$ +ENABLED=STATIC_PLUGIN + #control build DISABLE_SCALERS = true DISABLE_HQ_SCALERS = true +ENABLE_SCUMM = $(ENABLED) +ENABLE_SCUMM_7_8 = $(ENABLED) +#ENABLE_HE = $(ENABLED) +ENABLE_AGI = $(ENABLED) +ENABLE_AGOS = $(ENABLED) +#ENABLE_CINE = $(ENABLED) +#ENABLE_CRUISE = $(ENABLED) +ENABLE_DRASCULA = $(ENABLED) +ENABLE_GOB = $(ENABLED) +#ENABLE_IGOR = $(ENABLED) +ENABLE_KYRA = $(ENABLED) +ENABLE_LURE = $(ENABLED) +#ENABLE_M4 = $(ENABLED) +#ENABLE_MADE = $(ENABLED) +#ENABLE_PARALLACTION = $(ENABLED) +#ENABLE_QUEEN = $(ENABLED) +#ENABLE_SAGA = $(ENABLED) +ENABLE_SKY = $(ENABLED) +ENABLE_SWORD1 = $(ENABLED) +ENABLE_SWORD2 = $(ENABLED) +#ENABLE_TINSEL = $(ENABLED) +#ENABLE_TOUCHE = $(ENABLED) + + srcdir = ../../.. VPATH = $(srcdir) HAVE_GCC3 = false @@ -63,6 +89,8 @@ OBJS := psp_main.o \ kbd_l_c.o \ trace.o +DEPDIR = .deps + include $(srcdir)/Makefile.common PSP_EBOOT_SFO = param.sfo diff --git a/backends/platform/psp/osys_psp.cpp b/backends/platform/psp/osys_psp.cpp index d5d59d4d6e6..6e9b5980d48 100644 --- a/backends/platform/psp/osys_psp.cpp +++ b/backends/platform/psp/osys_psp.cpp @@ -34,7 +34,7 @@ #include "backends/timer/default/default-timer.h" #include "graphics/surface.h" #include "graphics/scaler.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include @@ -72,7 +72,7 @@ const OSystem::GraphicsMode OSystem_PSP::s_supportedGraphicsModes[] = { }; -OSystem_PSP::OSystem_PSP() : _screenWidth(0), _screenHeight(0), _overlayWidth(0), _overlayHeight(0), _offscreen(0), _overlayBuffer(0), _overlayVisible(false), _shakePos(0), _mouseBuf(0), _prevButtons(0), _lastPadCheck(0), _padAccel(0) { +OSystem_PSP::OSystem_PSP() : _screenWidth(0), _screenHeight(0), _overlayWidth(0), _overlayHeight(0), _offscreen(0), _overlayBuffer(0), _overlayVisible(false), _shakePos(0), _mouseBuf(0), _prevButtons(0), _lastPadCheck(0), _padAccel(0), _mixer(0) { memset(_palette, 0, sizeof(_palette)); @@ -99,11 +99,11 @@ OSystem_PSP::~OSystem_PSP() { void OSystem_PSP::initBackend() { _savefile = new DefaultSaveFileManager(); - _mixer = new Audio::Mixer(); _timer = new DefaultTimerManager(); - setSoundCallback(Audio::Mixer::mixCallback, _mixer); setTimerCallback(&timer_handler, 10); + setupMixer(); + OSystem::initBackend(); } @@ -586,7 +586,15 @@ void OSystem_PSP::deleteMutex(MutexRef mutex) { SDL_DestroyMutex((SDL_mutex *)mutex); } -bool OSystem_PSP::setSoundCallback(SoundProc proc, void *param) { +void OSystem_PSP::mixCallback(void *sys, byte *samples, int len) { + OSystem_PSP *this_ = (OSystem_PSP *)sys; + assert(this_); + + if (this_->_mixer) + this_->_mixer->mixCallback(samples, len); +} + +void OSystem_PSP::setupMixer(void) { SDL_AudioSpec desired; SDL_AudioSpec obtained; @@ -613,29 +621,33 @@ bool OSystem_PSP::setSoundCallback(SoundProc proc, void *param) { desired.format = AUDIO_S16SYS; desired.channels = 2; desired.samples = samples; - desired.callback = proc; - desired.userdata = param; + desired.callback = mixCallback; + desired.userdata = this; + + assert(!_mixer); + _mixer = new Audio::MixerImpl(this); + assert(_mixer); + if (SDL_OpenAudio(&desired, &obtained) != 0) { - return false; + warning("Could not open audio: %s", SDL_GetError()); + _samplesPerSec = 0; + _mixer->setReady(false); + } else { + // Note: This should be the obtained output rate, but it seems that at + // least on some platforms SDL will lie and claim it did get the rate + // even if it didn't. Probably only happens for "weird" rates, though. + _samplesPerSec = obtained.freq; + + // Tell the mixer that we are ready and start the sound processing + _mixer->setOutputRate(_samplesPerSec); + _mixer->setReady(true); + + SDL_PauseAudio(0); } - // Note: This should be the obtained output rate, but it seems that at - // least on some platforms SDL will lie and claim it did get the rate - // even if it didn't. Probably only happens for "weird" rates, though. - _samplesPerSec = obtained.freq; - SDL_PauseAudio(0); - return true; -} - -void OSystem_PSP::clearSoundCallback() { - SDL_CloseAudio(); -} - -int OSystem_PSP::getOutputSampleRate() const { - return _samplesPerSec; } void OSystem_PSP::quit() { - clearSoundCallback(); + SDL_CloseAudio(); SDL_Quit(); sceGuTerm(); sceKernelExitGame(); diff --git a/backends/platform/psp/osys_psp.h b/backends/platform/psp/osys_psp.h index a59e927b972..dca6ccb0361 100644 --- a/backends/platform/psp/osys_psp.h +++ b/backends/platform/psp/osys_psp.h @@ -26,6 +26,7 @@ #include "common/scummsys.h" #include "common/system.h" #include "graphics/surface.h" +#include "sound/mixer_intern.h" #include "backends/fs/psp/psp-fs-factory.h" @@ -71,7 +72,7 @@ protected: SceCtrlData pad; Common::SaveFileManager *_savefile; - Audio::Mixer *_mixer; + Audio::MixerImpl *_mixer; Common::TimerManager *_timer; public: @@ -129,10 +130,8 @@ public: virtual void unlockMutex(MutexRef mutex); virtual void deleteMutex(MutexRef mutex); - typedef void (*SoundProc)(void *param, byte *buf, int len); - virtual bool setSoundCallback(SoundProc proc, void *param); - virtual void clearSoundCallback(); - virtual int getOutputSampleRate() const; + static void mixCallback(void *sys, byte *samples, int len); + virtual void setupMixer(void); Common::SaveFileManager *getSavefileManager() { return _savefile; } Audio::Mixer *getMixer() { return _mixer; } diff --git a/backends/platform/sdl/sdl.cpp b/backends/platform/sdl/sdl.cpp index be3aaad926a..d8394b5c9ce 100644 --- a/backends/platform/sdl/sdl.cpp +++ b/backends/platform/sdl/sdl.cpp @@ -30,7 +30,7 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include "icons/scummvm.xpm" @@ -131,9 +131,7 @@ void OSystem_SDL::initBackend() { // Create and hook up the mixer, if none exists yet (we check for this to // allow subclasses to provide their own). if (_mixer == 0) { - _mixer = new Audio::Mixer(); - bool result = setSoundCallback(Audio::Mixer::mixCallback, _mixer); - _mixer->setReady(result); + setupMixer(); } // Create and hook up the timer manager, if none exists yet (we check for @@ -172,6 +170,10 @@ OSystem_SDL::OSystem_SDL() _joystick(0), _currentShakePos(0), _newShakePos(0), _paletteDirtyStart(0), _paletteDirtyEnd(0), +#ifdef MIXER_DOUBLE_BUFFERING + _soundMutex(0), _soundCond(0), _soundThread(0), + _soundThreadIsRunning(false), _soundThreadShouldQuit(false), +#endif _savefile(0), _mixer(0), _timer(0), @@ -193,7 +195,7 @@ OSystem_SDL::OSystem_SDL() OSystem_SDL::~OSystem_SDL() { SDL_RemoveTimer(_timerID); - SDL_CloseAudio(); + closeMixer(); free(_dirtyChecksums); free(_currentPalette); @@ -201,7 +203,6 @@ OSystem_SDL::~OSystem_SDL() { free(_mouseData); delete _savefile; - delete _mixer; delete _timer; } @@ -308,7 +309,7 @@ void OSystem_SDL::quit() { SDL_ShowCursor(SDL_ENABLE); SDL_RemoveTimer(_timerID); - SDL_CloseAudio(); + closeMixer(); free(_dirtyChecksums); free(_currentPalette); @@ -316,7 +317,6 @@ void OSystem_SDL::quit() { free(_mouseData); delete _savefile; - delete _mixer; delete _timer; SDL_Quit(); @@ -391,7 +391,111 @@ void OSystem_SDL::deleteMutex(MutexRef mutex) { #pragma mark --- Audio --- #pragma mark - -bool OSystem_SDL::setSoundCallback(SoundProc proc, void *param) { +#ifdef MIXER_DOUBLE_BUFFERING + +void OSystem_SDL::mixerProducerThread() { + byte nextSoundBuffer; + + SDL_LockMutex(_soundMutex); + while (true) { + // Wait till we are allowed to produce data + SDL_CondWait(_soundCond, _soundMutex); + + if (_soundThreadShouldQuit) + break; + + // Generate samples and put them into the next buffer + nextSoundBuffer = _activeSoundBuf ^ 1; + _mixer->mixCallback(_soundBuffers[nextSoundBuffer], _soundBufSize); + + // Swap buffers + _activeSoundBuf = nextSoundBuffer; + } + SDL_UnlockMutex(_soundMutex); +} + +int SDLCALL OSystem_SDL::mixerProducerThreadEntry(void *arg) { + OSystem_SDL *this_ = (OSystem_SDL *)arg; + assert(this_); + this_->mixerProducerThread(); + return 0; +} + + +void OSystem_SDL::initThreadedMixer(Audio::MixerImpl *mixer, uint bufSize) { + _soundThreadIsRunning = false; + _soundThreadShouldQuit = false; + + // Create mutex and condition variable + _soundMutex = SDL_CreateMutex(); + _soundCond = SDL_CreateCond(); + + // Create two sound buffers + _activeSoundBuf = 0; + _soundBufSize = bufSize; + _soundBuffers[0] = (byte *)calloc(1, bufSize); + _soundBuffers[1] = (byte *)calloc(1, bufSize); + + _soundThreadIsRunning = true; + + // Finally start the thread + _soundThread = SDL_CreateThread(mixerProducerThreadEntry, this); +} + +void OSystem_SDL::deinitThreadedMixer() { + // Kill thread?? _soundThread + + if (_soundThreadIsRunning) { + // Signal the producer thread to end, and wait for it to actually finish. + _soundThreadShouldQuit = true; + SDL_CondBroadcast(_soundCond); + SDL_WaitThread(_soundThread, NULL); + + // Kill the mutex & cond variables. + // Attention: AT this point, the mixer callback must not be running + // anymore, else we will crash! + SDL_DestroyMutex(_soundMutex); + SDL_DestroyCond(_soundCond); + + _soundThreadIsRunning = false; + + free(_soundBuffers[0]); + free(_soundBuffers[1]); + } +} + + +void OSystem_SDL::mixCallback(void *arg, byte *samples, int len) { + OSystem_SDL *this_ = (OSystem_SDL *)arg; + assert(this_); + assert(this_->_mixer); + + assert((int)this_->_soundBufSize == len); + + // Lock mutex, to ensure our data is not overwritten by the producer thread + SDL_LockMutex(this_->_soundMutex); + + // Copy data from the current sound buffer + memcpy(samples, this_->_soundBuffers[this_->_activeSoundBuf], len); + + // Unlock mutex and wake up the produced thread + SDL_UnlockMutex(this_->_soundMutex); + SDL_CondSignal(this_->_soundCond); +} + +#else + +void OSystem_SDL::mixCallback(void *sys, byte *samples, int len) { + OSystem_SDL *this_ = (OSystem_SDL *)sys; + assert(this_); + assert(this_->_mixer); + + this_->_mixer->mixCallback(samples, len); +} + +#endif + +void OSystem_SDL::setupMixer() { SDL_AudioSpec desired; SDL_AudioSpec obtained; @@ -403,7 +507,7 @@ bool OSystem_SDL::setSoundCallback(SoundProc proc, void *param) { _samplesPerSec = SAMPLES_PER_SEC; // Determine the sample buffer size. We want it to store enough data for - // about 1/32th of a second. Note that it must be a power of two. + // about 1/16th of a second. Note that it must be a power of two. // So e.g. at 22050 Hz, we request a sample buffer size of 2048. int samples = 8192; while (16 * samples >= _samplesPerSec) { @@ -415,23 +519,51 @@ bool OSystem_SDL::setSoundCallback(SoundProc proc, void *param) { desired.format = AUDIO_S16SYS; desired.channels = 2; desired.samples = (uint16)samples; - desired.callback = proc; - desired.userdata = param; + desired.callback = mixCallback; + desired.userdata = this; + + // Create the mixer instance + assert(!_mixer); + _mixer = new Audio::MixerImpl(this); + assert(_mixer); + if (SDL_OpenAudio(&desired, &obtained) != 0) { warning("Could not open audio device: %s", SDL_GetError()); - return false; + _samplesPerSec = 0; + _mixer->setReady(false); + } else { + // Note: This should be the obtained output rate, but it seems that at + // least on some platforms SDL will lie and claim it did get the rate + // even if it didn't. Probably only happens for "weird" rates, though. + _samplesPerSec = obtained.freq; + debug(1, "Output sample rate: %d Hz", _samplesPerSec); + + // Tell the mixer that we are ready and start the sound processing + _mixer->setOutputRate(_samplesPerSec); + _mixer->setReady(true); + +#ifdef MIXER_DOUBLE_BUFFERING + initThreadedMixer(_mixer, obtained.samples * 4); +#endif + + // start the sound system + SDL_PauseAudio(0); } - // Note: This should be the obtained output rate, but it seems that at - // least on some platforms SDL will lie and claim it did get the rate - // even if it didn't. Probably only happens for "weird" rates, though. - _samplesPerSec = obtained.freq; - debug(1, "Output sample rate: %d Hz", _samplesPerSec); - SDL_PauseAudio(0); - return true; } -int OSystem_SDL::getOutputSampleRate() const { - return _samplesPerSec; +void OSystem_SDL::closeMixer() { + if (_mixer) + _mixer->setReady(false); + + SDL_CloseAudio(); + + delete _mixer; + _mixer = 0; + +#ifdef MIXER_DOUBLE_BUFFERING + deinitThreadedMixer(); +#endif + } Audio::Mixer *OSystem_SDL::getMixer() { diff --git a/backends/platform/sdl/sdl.h b/backends/platform/sdl/sdl.h index 2cbadae2f42..4ad588f5f55 100644 --- a/backends/platform/sdl/sdl.h +++ b/backends/platform/sdl/sdl.h @@ -38,7 +38,7 @@ namespace Audio { - class Mixer; + class MixerImpl; } namespace Common { @@ -51,6 +51,15 @@ namespace Common { #define USE_OSD 1 #endif +#if defined(MACOSX) +// On Mac OS X, we need to double buffer the audio buffer, else anything +// which produces sampled data with high latency (like the MT-32 emulator) +// will sound terribly. +// This could be enabled for more / most ports in the future, but needs some +// testing. +#define MIXER_DOUBLE_BUFFERING 1 +#endif + enum { GFX_NORMAL = 0, @@ -134,8 +143,11 @@ public: virtual bool pollEvent(Common::Event &event); // overloaded by CE backend // Set function that generates samples - typedef void (*SoundProc)(void *param, byte *buf, int len); - virtual bool setSoundCallback(SoundProc proc, void *param); // overloaded by CE backend + virtual void setupMixer(); + static void mixCallback(void *s, byte *samples, int len); + + virtual void closeMixer(); + virtual Audio::Mixer *getMixer(); // Poll CD status @@ -186,7 +198,6 @@ public: virtual void setWindowCaption(const char *caption); virtual bool openCD(int drive); - virtual int getOutputSampleRate() const; virtual bool hasFeature(Feature f); virtual void setFeatureState(Feature f, bool enable); @@ -369,15 +380,32 @@ protected: */ MutexRef _graphicsMutex; +#ifdef MIXER_DOUBLE_BUFFERING + SDL_mutex *_soundMutex; + SDL_cond *_soundCond; + SDL_Thread *_soundThread; + bool _soundThreadIsRunning; + bool _soundThreadShouldQuit; + + byte _activeSoundBuf; + uint _soundBufSize; + byte *_soundBuffers[2]; + + void mixerProducerThread(); + static int SDLCALL mixerProducerThreadEntry(void *arg); + void initThreadedMixer(Audio::MixerImpl *mixer, uint bufSize); + void deinitThreadedMixer(); +#endif + Common::SaveFileManager *_savefile; - Audio::Mixer *_mixer; + Audio::MixerImpl *_mixer; SDL_TimerID _timerID; Common::TimerManager *_timer; - +protected: void addDirtyRgnAuto(const byte *buf); void makeChecksums(const byte *buf); diff --git a/backends/platform/symbian/AdaptAllMMPs.pl b/backends/platform/symbian/AdaptAllMMPs.pl index 99c037379de..b576bb39937 100644 --- a/backends/platform/symbian/AdaptAllMMPs.pl +++ b/backends/platform/symbian/AdaptAllMMPs.pl @@ -6,31 +6,37 @@ chdir("../../../"); # list of project files to process @mmp_files = ( - "mmp/scummvm_scumm.mmp", - "mmp/scummvm_queen.mmp", - "mmp/scummvm_agos.mmp", - "mmp/scummvm_sky.mmp", - "mmp/scummvm_gob.mmp", - "mmp/scummvm_saga.mmp", - "mmp/scummvm_kyra.mmp", - "mmp/scummvm_sword1.mmp", - "mmp/scummvm_sword2.mmp", - "mmp/scummvm_lure.mmp", - "mmp/scummvm_cine.mmp", - "mmp/scummvm_agi.mmp", - "mmp/scummvm_touche.mmp", - "mmp/scummvm_parallaction.mmp", - "mmp/scummvm_cruise.mmp", - "mmp/scummvm_drascula.mmp", - "mmp/scummvm_igor.mmp", - "mmp/scummvm_made.mmp", + + # Engine Project files + "mmp/scummvm_agi.mmp", + "mmp/scummvm_agos.mmp", + "mmp/scummvm_cine.mmp", + "mmp/scummvm_cruise.mmp", + "mmp/scummvm_drascula.mmp", + "mmp/scummvm_gob.mmp", + "mmp/scummvm_igor.mmp", + "mmp/scummvm_kyra.mmp", + "mmp/scummvm_lure.mmp", "mmp/scummvm_m4.mmp", - "S60/ScummVM_S60.mmp", - "S60v3/ScummVM_S60v3.mmp", - "S80/ScummVM_S80.mmp", + "mmp/scummvm_made.mmp", + "mmp/scummvm_parallaction.mmp", + "mmp/scummvm_queen.mmp", + "mmp/scummvm_saga.mmp", + "mmp/scummvm_scumm.mmp", + "mmp/scummvm_sky.mmp", + "mmp/scummvm_sword1.mmp", + "mmp/scummvm_sword2.mmp", + "mmp/scummvm_touche.mmp", + + + # Target Platform Project Files + "S60/ScummVM_S60.mmp", + "S60v3/ScummVM_S60v3.mmp", + "S80/ScummVM_S80.mmp", "S90/ScummVM_S90.mmp", - "UIQ2/ScummVM_UIQ2.mmp", - "UIQ3/ScummVM_UIQ3.mmp" + "UIQ2/ScummVM_UIQ2.mmp", + "UIQ3/ScummVM_UIQ3.mmp" + ); # do this first to set all *.mmp & *.inf files to *.*.in states @@ -43,7 +49,7 @@ Updating slave MACRO settings in MMP files from master 'scummvm_base.mmp' "; -# do this first so we have @DisableDefines for correct inclusion of SOURCE files later +# do this first so we have @EnabledDefines and @DisabledDefines for correct inclusion of SOURCE files later UpdateSlaveMacros(); print " @@ -53,33 +59,40 @@ Preparing to update all the Symbian MMP project files with objects from module.m "; + +# some modules.mk files have #ifndef ENABLE_XXXX blocks: my @section_empty = (""); # section standard: no #ifdef's in module.mk files -my @sections_scumm = ("", "DISABLE_SCUMM_7_8", "DISABLE_HE"); # special sections for engine SCUMM +my @sections_scumm = ("", "ENABLE_SCUMM_7_8", "ENABLE_HE"); # special sections for engine SCUMM + # files excluded from build, case insensitive, will be matched in filename string only -my @excludes_snd = ( - "mt32", - "fluidsynth", - "i386", - "part.cpp", - "partial.cpp", - "partialmanager.cpp", +my @excludes_snd = ( + "mt32.*", + "fluidsynth.cpp", + "i386.cpp", + "part.*", "synth.cpp", "tables.cpp", - "freeverb.cpp" -); + "freeverb.cpp", + "rate.*" # not really needed, USE_ARM_SOUND_ASM currently not parsed correctly, + # "rate[_arm|_arm_asm].(cpp|s)" will be added later based on WINS/ARM build! + # These #defines for compile time are set in portdefs.h +); -my @excludes_graphics = ( -"iff.cpp" -); +my @excludes_graphics = ( + "iff.cpp" +); +# the USE_ARM_* defines not parsed correctly, exclude manually: my @excludes_scumm = ( - "codec47ARM.cpp", - "gfxARM.cpp", - "proc3arm.cpp" + ".*ARM.*", # the *ARM.s files are added in .mpp files based on WINS/ARM build! + # USE_ARM_SMUSH_ASM codec47ARM.s + # USE_ARM_GFX_ASM gfxARM.s + # USE_ARM_COSTUME_ASM proc3ARM.s compiled, linked?, but *not* used :P (portdefs.h) ); -#arseModule(mmpStr, dirStr, ifdefArray, [exclusionsArray]) + +#arseModule(mmpStr, dirStr, ifdefArray, [exclusionsArray]) ParseModule("_base", "base", \@section_empty); # now in ./TRG/ScummVM_TRG.mmp, these never change anyways... ParseModule("_base", "common", \@section_empty); ParseModule("_base", "gui", \@section_empty); @@ -98,14 +111,14 @@ ParseModule("_sword1", "sword1", \@section_empty); ParseModule("_sword2", "sword2", \@section_empty); ParseModule("_lure", "lure", \@section_empty); ParseModule("_cine", "cine", \@section_empty); -ParseModule("_agi", "agi", \@section_empty); +ParseModule("_agi", "agi", \@section_empty); ParseModule("_touche", "touche", \@section_empty); ParseModule("_parallaction","parallaction",\@section_empty); ParseModule("_cruise", "cruise", \@section_empty); ParseModule("_drascula","drascula", \@section_empty); ParseModule("_igor", "igor", \@section_empty); ParseModule("_made", "made", \@section_empty); -ParseModule("_m4", "m4", \@section_empty); +ParseModule("_m4", "m4", \@section_empty); print " ======================================================================================= Done. Enjoy :P @@ -115,7 +128,7 @@ Done. Enjoy :P ################################################################################################################## ################################################################################################################## -# parses multiple sections per mmp/module +# parses multiple sections per mmp/module sub ParseModule { my ($mmp,$module,$sections,$exclusions) = @_; @@ -140,7 +153,7 @@ sub CheckForModuleMK if (-d $item) { #print "$item\n"; - + opendir DIR, $item; #my @Files = readdir DIR; my @Files = grep s/^([^\.].*)$/$1/, readdir DIR; @@ -156,9 +169,10 @@ sub CheckForModuleMK if (-f $item and $item =~ /.*\/module.mk$/) { my $sec = ""; + my $isenable; my $ObjectsSelected = 0; my $ObjectsTotal = 0; - + print "$item for section '$section' ... "; open FILE, $item; @@ -167,14 +181,24 @@ sub CheckForModuleMK my $count = @lines; print "$count lines"; - + A: foreach $line (@lines) { - # found a section? reset + # all things we need are inside #ifdef sections, + # there is nothing we need in #ifndef sections: so ignore these for now + + # found a section? reset + if ($line =~ /^ifdef (.*)/) + { + $sec = $1; + $isenable = 1; + } if ($line =~ /^ifndef (.*)/) { $sec = $1; + $isenable = 0; } + # found an object? Not uncommented! if (!($line =~ /^#/) && $line =~ s/\.o/.cpp/) { @@ -187,27 +211,47 @@ sub CheckForModuleMK $line =~ s/ \\//; # remove possible trailing ' \' $line =~ s/\//\\/g; # replace / with \ chop($line); # remove \n - + # do we need to skip this file? According to our own @exclusions array foreach $exclusion (@exclusions) { if ($line =~ /$exclusion/i) { - print "\n ! $line (excluded, \@exclusions[$exclusion])"; + my $reason = "excluded, \@exclusions[$exclusion]"; + print "\n ! $line ($reason)"; + $output .= "//SOURCE $line ($reason)\n"; next A; } } - - # do we need to skip this file? According to MACROs in .MMPs - foreach $DisableDefine (@DisableDefines) + + # do we need to do this file? According to MACROs in .MMPs + my $found = 0; + foreach $EnableDefine (@EnabledDefines) { - if ($DisableDefine eq $section && $section ne '') + if (($EnableDefine eq $section) && ($section ne '')) { - print "\n !$line (excluded, MACRO $DisableDefine)"; - next A; + $found = 1; + last; } } - + foreach $DisableDefine (@DisabledDefines) + { + if (($DisableDefine eq $section) && ($section ne '')) + { + $found = 0; + last; + } + } + # always allow non-sections + $found = 1 if ($section eq ''); + if (!$found) + { + my $reason = "excluded, MACRO $section"; + print "\n !$line ($reason)"; + $output .= "//SOURCE $line ($reason)\n"; + next A; + } + $ObjectsSelected++; #print "\n $line"; $output .= "SOURCE $line\n"; @@ -230,7 +274,7 @@ sub UpdateProjectFile my $updated = " Updated @ ".localtime(); my $name; my @mmp_files_plus_one = @mmp_files; - unshift @mmp_files_plus_one, "mmp/scummvm_base.mmp"; + unshift @mmp_files_plus_one, "mmp/scummvm_base.mmp"; foreach $name (@mmp_files_plus_one) { @@ -239,23 +283,23 @@ sub UpdateProjectFile open FILE, "$file"; my @lines = ; close FILE; - + my $onestr = join("",@lines); - + if ($onestr =~ /$n/) { print " - $name @ $n updating ... "; - + $onestr =~ s/$a.*$b/$a$updated\n$output$b/s; open FILE, ">$file"; print FILE $onestr; close FILE; - + print "done.\n"; } } - + $output = ""; } @@ -265,7 +309,7 @@ sub UpdateSlaveMacros { my $updated = " Updated @ ".localtime(); - my $name = "mmp/scummvm_base.mmp"; + my $name = "mmp/scummvm_base.mmp"; my $file = "$buildDir/$name"; print "Reading master MACROS from backends/symbian/$name ... "; @@ -279,7 +323,7 @@ sub UpdateSlaveMacros my $b = "\/\/STOP_$n\/\/"; $onestr =~ /$a(.*)$b/s; my $macros = $1; - + my $libs_first = "\n// automagically enabled static libs from macros above\n"; my $libs_second = "STATICLIBRARY scummvm_base.lib // must be above USE_* .libs\n"; my $macro_counter = 0; @@ -292,32 +336,45 @@ sub UpdateSlaveMacros if ($line =~ /^.*MACRO\s*([0-9A-Z_]*)\s*\/\/\s*LIB\:(.*)$/) { my $macro = $1; my $lib = $2; - + # this macro enabled? then also add the .lib if ($line =~ /^\s*MACRO\s*$macro/m) { - # these are the USE_ libs + # add an USE_ lib? (these need to be added @ the beginning, before _base) $libs_second .= "STATICLIBRARY $lib\n" if ($macro =~ /^USE_/); + + # add an ENABLE_ lib? (these need to be added @ the end, after _base) + if ($macro =~ /^ENABLE_/) + { + $libs_first .= "STATICLIBRARY $lib\n"; + + # add projects for BLD.INF's + my $projectname = substr("$lib",0,-4); + $projects .= "..\\mmp\\$projectname.mmp\n"; + } } else { - # these are the non DISABLED_ libs - $libs_first .= "STATICLIBRARY $lib\n" if ($macro =~ /^DISABLE_/); - - # add projects for BLD.INF's - my $projectname = substr("$lib",0,-4); - $projects .= "..\\mmp\\$projectname.mmp\n" if ($macro =~ /^DISABLE_/); + # skip lines not beginning with "MACRO" (like "//MACRO") } $macro_counter++; } + # not commented out? then add the macro to output string if ($line =~ /^\s*MACRO\s*([0-9A-Z_]*)\s*/) { my $macro = $1; $macros2 .= "$line\n"; - push @DisableDefines, $macro; # used in CheckForModuleMK()!! + if ($macro =~ /^ENABLE_/) + { + push @EnabledDefines, $macro; # used in CheckForModuleMK()!! + } + elsif ($macro =~ /^DISABLE_/) + { + push @DisabledDefines, $macro; # used in CheckForModuleMK()!! + } } - } + } print "$macro_counter macro lines.\n"; @@ -328,23 +385,23 @@ sub UpdateSlaveMacros $m = "AUTO_PROJECTS"; $p = "\/\/START_$m\/\/"; $q = "\/\/STOP_$m\/\/"; - + foreach $name (@mmp_files) { $file = "$buildDir/$name"; $fileBLDINF = $buildDir .'/'. substr($name, 0, rindex($name, "/")) . "/BLD.INF"; print "Updating macros in $file ... "; #print "Updating macros in backends/symbian/$name ... "; - + open FILE, "$file"; @lines = ; close FILE; $onestr = join("",@lines); - + my $extralibs = ""; # output # slash in name means it's a phone specific build file: add LIBs $extralibs .= "$libs_first$libs_second" if (-e $fileBLDINF); - + $onestr =~ s/$a.*$b/$a$updated$macros2$extralibs$b/s; - + open FILE, ">$file"; print FILE $onestr; close FILE; my $count = @lines; @@ -358,13 +415,13 @@ sub UpdateSlaveMacros open FILE, "$fileBLDINF"; @lines = ; close FILE; $onestr = join("",@lines); - + $onestr =~ s/$p.*$q/$p$updated$projects$q/s; - + open FILE, ">$fileBLDINF"; print FILE $onestr; close FILE; } } -} +} ################################################################################################################## @@ -372,10 +429,10 @@ sub ResetProjectFiles() { my $onestr, @lines; my @mmp_files_plus_one = @mmp_files; -# unshift @mmp_files_plus_one, "mmp/scummvm_base.mmp"; - +# unshift @mmp_files_plus_one, "mmp/scummvm_base.mmp"; + print "Resetting project files: "; - + # we don't need to do mmp/scummvm_base.mmp", it was done in BuildPackageUpload.pl before the call to this script foreach $name (@mmp_files_plus_one) { @@ -395,7 +452,7 @@ sub ResetProjectFiles() $onestr = join("",@lines); open FILE, ">$fileBLDINF"; print FILE $onestr; close FILE; } - } + } print "... done.\n"; } diff --git a/backends/platform/symbian/BuildPackageUpload_AllVersions.pl b/backends/platform/symbian/BuildPackageUpload_AllVersions.pl index a40f8b7663a..94edbf4fcfe 100644 --- a/backends/platform/symbian/BuildPackageUpload_AllVersions.pl +++ b/backends/platform/symbian/BuildPackageUpload_AllVersions.pl @@ -8,6 +8,7 @@ require "BuildPackageUpload_LocalSettings.pl"; ################################################################################################################## # prep some vars +# the dir containing the build files: '.\backends\platforms\symbian\$SDK_BuildDir\' $SDK_BuildDirs{'UIQ2'} = "UIQ2"; $SDK_BuildDirs{'UIQ3'} = "UIQ3"; $SDK_BuildDirs{'S60v1'} = "S60"; @@ -16,6 +17,7 @@ $SDK_BuildDirs{'S60v3'} = "S60v3"; $SDK_BuildDirs{'S80'} = "S80"; $SDK_BuildDirs{'S90'} = "S90"; +# the target name inserted here: 'abld BUILD $SDK_TargetName UREL' $SDK_TargetName{'UIQ2'} = "armi"; $SDK_TargetName{'UIQ3'} = "gcce"; $SDK_TargetName{'S60v1'}= "armi"; @@ -24,6 +26,7 @@ $SDK_TargetName{'S60v3'}= "gcce"; $SDK_TargetName{'S80'} = "armi"; $SDK_TargetName{'S90'} = "armi"; +# Binaries are installed here: '$SDK_RootDirs\epoc32\release\$SDK_TargetDir\urel\' $SDK_TargetDir{'UIQ2'} = "armi"; $SDK_TargetDir{'UIQ3'} = "armv5"; $SDK_TargetDir{'S60v1'} = "armi"; @@ -34,11 +37,11 @@ $SDK_TargetDir{'S90'} = "armi"; $build_dir = getcwd(); $output_dir = "$build_dir/Packages"; -chdir("../../"); +chdir("../../../"); $base_dir = getcwd(); chdir($build_dir); -$build_log_out = "$build_dir/Build.out.log"; -$build_log_err = "$build_dir/Build.err.log"; +$build_log_out = "$build_dir/out.build.out.log"; # don't start these files with "Build" +$build_log_err = "$build_dir/out.build.err.log"; # so "B"+TAB completion works in 1 go :P $initial_path = $ENV{'PATH'}; # so we can start with a fresh PATH for each Build @@ -54,14 +57,27 @@ $PackagesUploaded = 0; @ErrorMessages = (); $ftp_url = "FTP://$FTP_User\@$FTP_Host/$FTP_Dir/"; -$ExtraMacros = "MACRO NONSTANDARD_PORT\n"; -$ExtraMacros .= "MACRO DISABLE_FANCY_THEMES\n"; +# these macros are always defined: +$ExtraMacros = "MACRO NONSTANDARD_PORT\n"; +$ExtraMacros .= "MACRO DISABLE_FANCY_THEMES\n"; +$ExtraMacros .= "MACRO DISABLE_SCALERS\n"; +$ExtraMacros .= "MACRO DISABLE_HQ_SCALERS\n"; # prep nice list of SDKs #while( ($SDK, $RootDir) = each(%SDK_RootDirs) ) foreach $SDK (sort keys(%SDK_RootDirs)) { - $SDKs .= "$SDK\t$SDK_RootDirs{$SDK}\n\t\t\t"; + # see if it exists! + if (-d $SDK_RootDirs{$SDK}) + { + $SDKs .= "$SDK\t$SDK_RootDirs{$SDK}\n\t\t\t"; + } + else # missing? + { + $SDKs .= "$SDK\t$SDK_RootDirs{$SDK}\t[MISSING: Skipping!]\n\t\t\t"; + # remove it from array, to prevent building! + delete $SDK_RootDirs{$SDK}; + } } # prep nice list of Libraries @@ -69,7 +85,16 @@ while( ($SDK, $Value) = each(%SDK_LibraryDirs) ) { while( ($Library, $Path) = each(%{$SDK_LibraryDirs{$SDK}}) ) { - $PresentLibs{$Library} = $Path; + # maybe it's already been built? + if (-e $SDK_RootDirs{$SDK}."\\epoc32\\release\\$SDK_TargetDir{$SDK}\\urel\\$Library") + { + $PresentLibs{$Library} = "$Path [EXISTS: Skipping!]"; + delete $SDK_LibraryDirs{$SDK}{$Library}; + } + else # make it! + { + $PresentLibs{$Library} = "$Path"; + } } } foreach $Library (sort keys(%PresentLibs)) @@ -78,10 +103,37 @@ foreach $Library (sort keys(%PresentLibs)) } # prep nice list of Variations -while( ($SDK, $Value) = each(%SDK_Variations) ) +#while( ($SDK, $Value) = each(%SDK_Variations) ) +#{ +# while( ($Variation, $Value2) = each(%{$SDK_Variations{$SDK}}) ) +# { +# $Extra = ($Variation ne '' ? "_$Variation" : ""); +# if ($SDK eq "ALL") +# { +# while( ($SDK2, $RootDir) = each(%SDK_RootDirs) ) +# { +# if ($SDK_RootDirs{$SDK2} ne '') # is this SDK listed as installed? (fails silently) +# { +# push @Packages, sprintf($file_tpl_sis, $version_tpl_sis, $SDK2, $Extra); +# $PackagesQueued++; +# } +# } +# } +# else +# { +# if ($SDK_RootDirs{$SDK} ne '') # is this SDK listed as installed? (fails silently) +# { +# push @Packages, sprintf($file_tpl_sis, $version_tpl_sis, $SDK, $Extra); +# $PackagesQueued++; +# } +# } +# } +#} +while( ($SDK, $Value) = each(%VariationSets) ) { - while( ($Variation, $Value2) = each(%{$SDK_Variations{$SDK}}) ) + while( ($Variation, $FeaturesBlock) = each(%{$VariationSets{$SDK}}) ) { +#my $MacroBlock = &MakeMppMacroDefs($FeaturesBlock); $Extra = ($Variation ne '' ? "_$Variation" : ""); if ($SDK eq "ALL") { @@ -92,7 +144,7 @@ while( ($SDK, $Value) = each(%SDK_Variations) ) push @Packages, sprintf($file_tpl_sis, $version_tpl_sis, $SDK2, $Extra); $PackagesQueued++; } - } + } } else { @@ -118,7 +170,7 @@ Preparing to Build, Package & Upload $PackagesQueued SymbianOS ScummVM variation SDKs inst'd \t$SDKs ".( %SDK_LibraryDirs ? " LIBs inst'd \t$LIBs " : "" )." - $PackagesQueued Variations \t$PackagesStr + $PackagesQueued Variations \t$PackagesStr DIR base \t$base_dir build \t$build_dir output \t$output_dir @@ -126,7 +178,7 @@ Preparing to Build, Package & Upload $PackagesQueued SymbianOS ScummVM variation FTP host \t$FTP_Host user \t$FTP_User pass \t"."*" x length($FTP_Pass)." - dir \t$FTP_Dir + dir \t$FTP_Dir " : "" )." ======================================================================================= Press Ctrl-C to abort or enter to continue Build, Package & Upload $PackagesQueued Variations... @@ -142,7 +194,7 @@ unlink($build_log_out); unlink($build_log_err); # init _base.mmp now, so we can start changing it without affecting the CVS version _base.mmp.in! -my $name = "mmp/scummvm_base.mmp"; +my $name = "mmp/scummvm_base.mmp"; my $file = "$build_dir/$name"; open FILE, "$file.in"; @lines = ; close FILE; my $onestr = join("",@lines); @@ -161,15 +213,21 @@ while( ($SDK, $Value) = each(%SDK_LibraryDirs) ) { if ($SDK_RootDirs{$SDK2} ne '') # is this SDK listed as installed? (fails silently) { + # do we already have this one? + next if (-e $SDK_RootDirs{$SDK2}."\\epoc32\\release\\$SDK_TargetDir{$SDK2}\\urel\\$Library"); + $LibrariesQueued++; DoLibrary($SDK2, $Library, $Path); } - } + } } else { if ($SDK_RootDirs{$SDK} ne '') # is this SDK listed as installed? (fails silently) { + # do we already have this one? + next if (-e $SDK_RootDirs{$SDK}."\\epoc32\\release\\$SDK_TargetDir{$SDK}\\urel\\$Library"); + $LibrariesQueued++; DoLibrary($SDK, $Library, $Path); } @@ -194,8 +252,33 @@ while( ($SDK, $VariationsHash) = each(%SDK_Variations) ) { DoVariation($SDK2, $Variation, $MacroBlock); } + } + } + else + { + if ($SDK_RootDirs{$SDK} ne '') + { + DoVariation($SDK, $Variation, $MacroBlock); } } + } +} + +while( ($SDK, $VariationsHash) = each(%VariationSets) ) +{ + while( ($Variation, $FeaturesBlock) = each(%{$VariationSets{$SDK}}) ) + { + my $MacroBlock = &MakeMppMacroDefs($FeaturesBlock); + if ($SDK eq "ALL") + { + while( ($SDK2, $RootDir) = each(%SDK_RootDirs) ) + { + if ($SDK_RootDirs{$SDK2} ne '') + { + DoVariation($SDK2, $Variation, $MacroBlock); + } + } + } else { if ($SDK_RootDirs{$SDK} ne '') @@ -248,7 +331,81 @@ print " SumthinWicked wishes you a ridiculously good and optimally happy d ################################################################################################################## ################################################################################################################## -# Build, Package & Upload a single Variation +# create a set of "MACRO xxx" definitions for use in the scummvm_base.mpp file +sub MakeMppMacroDefs +{ + my ($features) = @_; + + my %EnabledFeatures = (); + foreach (split(/\W|\r|\n/, $features)) + { + if ($_ ne "") + { + #print "FEATURE: $_\n"; + $EnabledFeatures{$_} = 1; + } + } + + my $MacroDefs = ""; + + $MacroDefs .= " // Features //\n"; + foreach my $e (sort keys %UseableFeatures) + { + my $E = uc($e); + if ($EnabledFeatures{$e}) + { + $MacroDefs .= "MACRO USE_$E // LIB:$UseableFeatures{$e}\n"; + # this one is used: remove it now + delete $EnabledFeatures{$e}; + # this will leave us with a list of unparsed options! + } + else + { + $MacroDefs .= "//MACRO USE_$E\n"; + } + } + + $MacroDefs .= " // Engines //\n"; + foreach my $e (sort @EnablableEngines) + { + my $E = uc($e); + if ($EnabledFeatures{$e}) + { + $MacroDefs .= "MACRO ENABLE_$E // LIB:scummvm_$e.lib\n"; + # this one is used: remove it now + delete $EnabledFeatures{$e}; + # this will leave us with a list of unparsed options! + } + else + { + $MacroDefs .= "//MACRO ENABLE_$E\n"; + } + } + + $MacroDefs .= " // SubEngines //\n"; + foreach my $e (sort @EnablableSubEngines) + { + my $E = uc($e); + if ($EnabledFeatures{$e}) + { + $MacroDefs .= "MACRO ENABLE_$E\n"; + # this one is used: remove it now + delete $EnabledFeatures{$e}; + # this will leave us with a list of unparsed options! + } + else + { + $MacroDefs .= "//MACRO ENABLE_$E\n"; + } + } + +#print "\n\n'$features' ==> $MacroDefs\n\n\n"; + return $MacroDefs; +} + +################################################################################################################## + +# Build, Package & Upload a single Variation sub DoLibrary { my ($SDK, $Library, $Path) = @_; @@ -278,19 +435,19 @@ my $header = " my $OK = 1; PrepSdkPaths($SDK); - + chdir($Path) or $OK=0; PrintErrorMessage("Changing to $Path failed!") if (!$OK); - return 0 if (!$OK); + return 0 if (!$OK); PrintMessage("Cleaning for $Target") if (!$ReallyQuiet); system("bldmake bldfiles > NUL 2> NUL"); PrintErrorMessage("'bldmake bldfiles' exited with value " . ($? >> 8)) if ($? >> 8); - system("abld clean $TargetName urel > NUL 2> NUL"); - PrintErrorMessage("'abld clean $TargetName urel' exited with value " . ($? >> 8)) if ($? >> 8); + system("abld CLEAN $TargetName UREL > NUL 2> NUL"); + PrintErrorMessage("'abld CLEAN $TargetName urel' exited with value " . ($? >> 8)) if ($? >> 8); # remove file so we are sure that after .lib generation we have a fresh copy! if (-e $TargetFilePath) { unlink($TargetFilePath) or PrintErrorMessage("Removing $TargetFilePath"); } - + my $Redirection = "OUT:file, ERR:".($RedirectSTDERR ? "file" : "screen"); my $Message = "Building $Target ($Redirection)"; PrintMessage($Message) if (!$ReallyQuiet); @@ -298,18 +455,18 @@ my $header = " my $OldSize = (-s $build_log_err); $Redirection = ($RedirectSTDERR ? "2>> $build_log_err" : ""); - system("abld build $TargetName urel $Redirection >> $build_log_out"); + system("abld BUILD $TargetName UREL $Redirection >> $build_log_out"); $OK = 0 if ($? >> 8); # print " STDERR: ".((-s $build_log_err)-$OldSize)." bytes output written to $build_log_err\n+--------------------------------------------------------------------------------------\n" if ($OldSize != (-s $build_log_err)); - PrintErrorMessage("'abld build $TargetName urel' exited with value " . ($? >> 8)) if ($? >> 8); - return 0 if (!$OK); # ABLD always returns ok :( grr + PrintErrorMessage("'abld BUILD $TargetName UREL' exited with value " . ($? >> 8)) if ($? >> 8); + return 0 if (!$OK); # ABLD always returns ok :( grr PrintMessage("Done.") if (!$ReallyQuiet); # did it work? :) if (-e $TargetFilePath) { $LibrariesSucceeded++; - + if ($TargetIntermediatePath ne '' && $TargetIntermediatePath =~ /\\EPOC32\\BUILD\\/i) # make really sure it's a valid path! { system("del /S /Q $TargetIntermediatePath > NUL"); @@ -318,9 +475,9 @@ my $header = " } else { - PrintErrorMessage("'abld build $TargetName urel' apparently failed."); + PrintErrorMessage("'abld BUILD $TargetName UREL' apparently failed."); if ($HaltOnError) - { + { PrintErrorMessage("Halting on error as requested!"); exit 1; } @@ -330,12 +487,12 @@ my $header = " ################################################################################################################## -# Build, Package & Upload a single Variation +# Build, Package & Upload a single Variation sub DoVariation { my ($SDK, $Variation, $MacroBlock) = @_; my $Extra = ($Variation ne '' ? "_$Variation" : ""); - my $Package = sprintf($file_tpl_sis, $version_tpl_sis, $SDK, $Extra); + my $Package = sprintf($file_tpl_sis, $version_tpl_sis, $SDK, $Extra); if ($SkipExistingPackages && -f "$output_dir/$Package") { @@ -366,7 +523,7 @@ my $header = " if ($OK) { $OK = BuildVariation($SDK, $Variation, $Package, $MacroBlock); - + if ($OK && $FTP_Host ne '') { UploadVariation($SDK, $Variation, $Package); @@ -382,17 +539,17 @@ sub PrepVariation() my $OK = 1; PrepSdkPaths($SDK); - + chdir($build_dir) or $OK=0; PrintErrorMessage("Changing to $build_dir failed!") if (!$OK); - return 0 if (!$OK); + return 0 if (!$OK); # insert $MacroBlock into AUTO_MACRO_MASTER in scummvm_base.mmp PrintMessage("Setting new AUTO_MACROS_MASTER in scummvm_base.mmp for '$Variation'") if (!$ReallyQuiet); my $n = "AUTO_MACROS_MASTER"; my $a = "\/\/START_$n\/\/"; my $b = "\/\/STOP_$n\/\/"; - my $name = "scummvm_base.mmp"; + my $name = "scummvm_base.mmp"; my $file = "$build_dir/mmp/$name"; my $updated = " Updated @ ".localtime(); @@ -401,11 +558,11 @@ sub PrepVariation() return 0 if (!$OK); my @lines = ; close FILE; - + my $onestr = join("",@lines); $MacroBlock =~ s/^\s*//gm; $onestr =~ s/$a(.*)$b/$a$updated\n$ExtraMacros$MacroBlock$b/s; - + open FILE, ">$file" or $OK=0; PrintErrorMessage("Writing file '$file'") if (!$OK); return 0 if (!$OK); @@ -418,7 +575,7 @@ sub PrepVariation() $OK = 0 if ($? >> 8); PrintErrorMessage("'AdaptAllMMPs.pl' exited with value " . ($? >> 8)) if ($? >> 8); return 0 if (!$OK); - + # we are here: so all is ok :) return 1; } @@ -431,7 +588,7 @@ sub BuildVariation() my $TargetName = $SDK_TargetName{$SDK}; my $TargetDir = $SDK_TargetDir{$SDK}; my $OK = 1; - + my $dir = $build_dir."/".$SDK_BuildDirs{$SDK}; $dir =~ s#/#\\#g; chdir($dir); @@ -449,13 +606,15 @@ sub BuildVariation() if (-e $UnlinkFile) { unlink($UnlinkFile) or PrintErrorMessage("Removing $UnlinkFile"); } $UnlinkFile = $SDK_RootDirs{$SDK}."/epoc32/release/$TargetDir/urel/ScummVM.exe"; if (-e $UnlinkFile) { unlink($UnlinkFile) or PrintErrorMessage("Removing $UnlinkFile"); } + # remove all libs here, note they are in another dir! + system("del ".$SDK_RootDirs{$SDK}."/epoc32/release/$TargetName/urel/scummvm_*.lib"); system("bldmake bldfiles 2> NUL > NUL"); PrintErrorMessage("'bldmake bldfiles' exited with value " . ($? >> 8)) if ($? >> 8); - system("abld clean $TargetName urel 2> NUL > NUL"); - PrintErrorMessage("'abld clean $TargetName urel' exited with value " . ($? >> 8)) if ($? >> 8); - + system("abld CLEAN $TargetName UREL 2> NUL > NUL"); + PrintErrorMessage("'abld CLEAN $TargetName UREL' exited with value " . ($? >> 8)) if ($? >> 8); + my $Redirection = "OUT:file, ERR:".($RedirectSTDERR ? "file" : "screen"); my $Message = "Building $Package ($Redirection)"; PrintMessage($Message) if (!$ReallyQuiet); @@ -463,11 +622,11 @@ sub BuildVariation() my $OldSize = (-s $build_log_err); $Redirection = ($RedirectSTDERR ? "2>> $build_log_err" : ""); - system("abld build $TargetName urel $Redirection >> $build_log_out"); + system("abld BUILD $TargetName UREL $Redirection >> $build_log_out"); $OK = 0 if ($? >> 8); print " STDERR: ".((-s $build_log_err)-$OldSize)." bytes output written to $build_log_err\n+--------------------------------------------------------------------------------------\n" if ($OldSize != (-s $build_log_err) && !$ReallyQuiet); - PrintErrorMessage("'abld build $TargetName urel' exited with value " . ($? >> 8)) if ($? >> 8); - return 0 if (!$OK); # ABLD always returns ok :( grr + PrintErrorMessage("'abld BUILD $TargetName UREL' exited with value " . ($? >> 8)) if ($? >> 8); + return 0 if (!$OK); # ABLD always returns ok :( grr PrintMessage("Done.") if (!$ReallyQuiet); # do we have an override suffix for the package name? @@ -491,7 +650,7 @@ sub BuildVariation() if (-e "$output_dir/$Package") { $PackagesCreated++; - + if ($TargetIntermediatePath ne '' && $TargetIntermediatePath =~ /\\EPOC32\\BUILD\\/i) # make really sure it's a valid path! { #PrintMessage("Cleaning $TargetIntermediatePath"); @@ -514,7 +673,7 @@ sub UploadVariation() use Net::FTP; my $newerr; - + PrintMessage("Connecting to FTP $FTP_Host") if (!$ReallyQuiet); $ftp = Net::FTP->new($FTP_Host,Timeout=>240) or $newerr=1; @@ -529,7 +688,7 @@ sub UploadVariation() { PrintMessage("Changing to dir $FTP_Dir"); $ftp->cwd($FTP_Dir) or $newerr=1; - + if ($newerr) { PrintErrorMessage("Changing to dir $FTP_Dir! Aborting!"); @@ -541,20 +700,20 @@ sub UploadVariation() # leave this for possible auto-deletion of old files? # @files = $ftp->dir or $newerr=1; # push @ERRORS, "Can't get file list $!\n" if $newerr; -# print "Got file list\n"; +# print "Got file list\n"; # foreach(@files) { # print "$_\n"; # } - + PrintMessage("Uploading $Package (".(-s "$output_dir/$Package")." bytes)"); - + $ftp->binary; $ftp->put("$output_dir/$Package") or $newerr=1; PrintErrorMessage("Uploading package! Aborting!") if $newerr; $PackagesUploaded++ if (!$newerr); - } + } - $ftp->quit; + $ftp->quit; } } @@ -574,6 +733,7 @@ sub PrepSdkPaths() # set env stuff PrintMessage("Prepending $SDK specific paths to %PATH%") if (!$ReallyQuiet); + $AdditionalPathEntries .= "$SDK_ToolchainDirs{$SDK};" if ($SDK_ToolchainDirs{$SDK} ne ''); $AdditionalPathEntries .= "$ECompXL_BinDir;" if ($ECompXL_BinDir ne '' && $SDK eq 'UIQ2'); $AdditionalPathEntries .= "$EPOC32RT\\include;"; $AdditionalPathEntries .= "$EPOC32RT\\tools;"; @@ -594,7 +754,7 @@ sub CleanupPath() { $path =~ s/\"\Q$ECompXL_BinDir\E\";//g; } - + while( ($SDK, $RootDir) = each(%SDK_RootDirs) ) { if ($SDK_RootDirs{$SDK} ne '') @@ -602,8 +762,8 @@ sub CleanupPath() my $path_component = "\"".$SDK_RootDirs{$SDK}."\\epoc32\\"; $path =~ s/\Q$path_component\E.*?\";//g; } - } - + } + return $path; } @@ -634,4 +794,5 @@ sub PrintMessage() } ################################################################################################################## + diff --git a/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl b/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl index 0b334c08ea9..12e5f8f0c43 100644 --- a/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl +++ b/backends/platform/symbian/BuildPackageUpload_LocalSettings.pl @@ -1,75 +1,118 @@ ################################################################################################################## + @WorkingEngines = qw( + scumm agos sky queen gob saga drascula + kyra lure agi touche parallaction + ); + @TestingEngines = qw( + cruise igor made m4 cine + ); + @BrokenEngines = qw( + sword1 + sword2 + ); + + @EnablableEngines = (@WorkingEngines, @TestingEngines); + + @EnablableSubEngines = qw( + scumm_7_8 + he + ); + + %UseableFeatures = ( + 'zlib' => 'zlib.lib', + 'mad' => 'libmad.lib', + 'tremor' => 'libtremor.lib', + 'mpeg2' => 'libmpeg2.lib' + ); + + # these are normally enabled for each variation + #$DefaultFeatures = qw(zlib,mad); + $DefaultFeatures = qw(zlib,mad,tremor); + + # you can use these below for speed & clarity or override with custom settings $DefaultTopMacros = " MACRO USE_ZLIB // LIB:zlib.lib MACRO USE_MAD // LIB:libmad.lib MACRO USE_TREMOR // LIB:libtremor.lib "; - + $DefaultBottomMacros = " MACRO DISABLE_SWORD1 // LIB:scummvm_sword1.lib MACRO DISABLE_SWORD2 // LIB:scummvm_sword2.lib "; - + +################################################################################################################## ## - ## General system information: + ## General system information, based on $COMPUTERNAME, so this way + ## you can use the same LocalSettings.pl file on multiple machines! ## +################################################################################################################## - # this way you can use the same LocalSettings.pl file on multiple machines! - if ($ENV{'COMPUTERNAME'} eq "BRAAMBOOK") + if ($ENV{'COMPUTERNAME'} eq "PC-21") ######################################################################### { # might use this string for file/dir naming in the future :) $Producer = "SumthinWicked"; $RedirectSTDERR = 0; - $HaltOnError = 1; + $HaltOnError = 0; $SkipExistingPackages = 0; $ReallyQuiet = 0; - + $DevBase = "C:\\S"; + # specify an optional FTP server to upload to after each Build+Package (can leave empty) - #$FTP_Host = "host.domain"; - #$FTP_User = "test"; - #$FTP_Pass = "test"; - #$FTP_Dir = "test"; - #$FTP_Host = "host.com"; $FTP_User = "something"; $FTP_Pass = "password"; $FTP_Dir = "cvsbuilds"; - + + # What Platform SDKs are installed on this machine? # possible SDKs: ("UIQ2", UIQ3", "S60v1", "S60v2", "S60v3", "S80", "S90") # Note1: the \epoc32 directory needs to be in these rootdirs # Note2: these paths do NOT end in a backslash! - $SDK_RootDirs{'UIQ2'} = "C:\\S\\UIQ_21"; - $SDK_RootDirs{'S60v1'} = "C:\\S\\S60v1"; - $SDK_RootDirs{'S60v2'} = "C:\\S\\S60v2"; - $SDK_RootDirs{'S80'} = "C:\\S\\S80"; - $SDK_RootDirs{'S90'} = "C:\\S\\S90"; - $ECompXL_BinDir = "C:\\S\\ECompXL\\bin"; # only needed for UIQ - # you need to specify each of the SDKs used in the blocks below! + # $SDK_RootDirs{'UIQ2'} = "$DevBase\\UIQ_21"; + $SDK_RootDirs{'UIQ3'} = "$DevBase\\UIQ3"; + # $SDK_RootDirs{'S60v1'} = "$DevBase\\S60v1"; + # $SDK_RootDirs{'S60v2'} = "$DevBase\\S60v2"; + $SDK_RootDirs{'S60v3'} = "$DevBase\\S60v3"; + # $SDK_RootDirs{'S80'} = "$DevBase\\S80"; + # $SDK_RootDirs{'S90'} = "$DevBase\\S90"; + + $SDK_ToolchainDirs{'S60v3'} = "$DevBase\\arm-symbianelf\\bin"; + $SDK_ToolchainDirs{'UIQ2'} = "$DevBase\\ECompXL\\bin"; # only needed for UIQ2/UIQ3 + $SDK_ToolchainDirs{'UIQ3'} = "$DevBase\\ECompXL\\bin"; # only needed for UIQ2/UIQ3 # these supporting libraries get built first, then all the Variations # Note: the string {'xxx.lib'} is used in checking in build success: so needs to be accurate! if (0) # so we can turn them on/off easily { - #$SDK_LibraryDirs{'ALL'}{'zlib.lib'} = "C:\\S\\zlib-1.2.2\\epoc"; - #$SDK_LibraryDirs{'ALL'}{'libmad.lib'} = "C:\\S\\libmad-0.15.1b\\group"; - #$SDK_LibraryDirs{'ALL'}{'libtremor.lib'}= "C:\\S\\tremor\\epoc"; - $SDK_LibraryDirs{'UIQ2'}{'esdl.lib'} = $SDK_LibraryDirs{'UIQ3'}{'esdl.lib'} = "C:\\S\\ESDL\\epoc\\UIQ"; - #$SDK_LibraryDirs{'S60v1'}{'esdl.lib'} = $SDK_LibraryDirs{'S60v2'}{'esdl.lib'} = $SDK_LibraryDirs{'S60v3'}{'esdl.lib'} = "C:\\S\\ESDL\\epoc\\S60"; - #$SDK_LibraryDirs{'S80'}{'esdl.lib'} = "C:\\S\\ESDL\\epoc\\S80"; - #$SDK_LibraryDirs{'S90'}{'esdl.lib'} = "C:\\S\\ESDL\\epoc\\S90"; - #$SDK_LibraryDirs{'ALL'}{'libmpeg2.lib'} = "C:\\S\\mpeg2dec-0.4.0\\epoc"; - } + ## Standard libraries + $SDK_LibraryDirs{'ALL'}{'zlib.lib'} = "$DevBase\\zlib-1.2.2\\epoc"; + #$SDK_LibraryDirs{'ALL'}{'libmad.lib'} = "$DevBase\\libmad-0.15.1b\\group"; + $SDK_LibraryDirs{'ALL'}{'libtremor.lib'}= "$DevBase\\tremor\\epoc"; + + ## SDL 1.2.12 / AnotherGuest / Symbian version + my $SdlBase = "$DevBase\\SDL-1.2.12-ag\\Symbian"; + #$SDK_LibraryDirs{'S60v1'}{'esdl.lib'} = "$SdlBase\\S60"; // unsupported? + #$SDK_LibraryDirs{'S60v2'}{'esdl.lib'} = "$SdlBase\\S60v2"; + $SDK_LibraryDirs{'S60v3'}{'esdl.lib'} = "$SdlBase\\S60v3"; + #$SDK_LibraryDirs{'S80'}{'esdl.lib'} = "$SdlBase\\S80"; + #$SDK_LibraryDirs{'S90'}{'esdl.lib'} = "$SdlBase\\S90"; + #$SDK_LibraryDirs{'UIQ2'}{'esdl.lib'} = "$SdlBase\\UIQ2" + #$SDK_LibraryDirs{'UIQ3'}{'esdl.lib'} = "$SdlBase\\UIQ3"; + + ## HardlySupported(TM) :P + #$SDK_LibraryDirs{'ALL'}{'libmpeg2.lib'} = "$DevBase\\mpeg2dec-0.4.0\\epoc"; + } + + # now you can add $VariationSets only built on this PC below this line :) + + #$VariationSets{'ALL'}{'scumm'} = "$DefaultFeatures scumm scumm_7_8 he"; + #$VariationSets{'ALL'}{'all'} = "$DefaultFeatures @WorkingEngines @EnablableSubEngines"; - # backup :P - #Path=C:\Progra~1\Active\Python24\.;C:\Program Files\Active\Tcl\bin;C:\Progra~1\Active\Perl\bin\;C:\WINDOWS\system32;C:\W - #INDOWS;C:\WINDOWS\System32\Wbem;C:\Program Files\ATI Technologies\ATI Control Panel;C:\Program Files\GNU\cvsnt;C:\Progra - #m Files\WinSCP3\;"C:\Program Files\Common Files\Microsoft Shared\VSA\8.0\VsaEnv\";"c:\Program Files\Microsoft Visual Stu - #dio 8\VC\bin";"C:\Program Files\UltraEdit-32" } - elsif ($ENV{'COMPUTERNAME'} eq "TSSLND0106") + elsif ($ENV{'COMPUTERNAME'} eq "TSSLND0106") ################################################################# { $Producer = "AnotherGuest"; $RedirectSTDERR = 1; @@ -90,7 +133,7 @@ #$SDK_RootDirs{'S80'}= "C:\\S80"; #$SDK_RootDirs{'S90'}= "C:\\S90"; $ECompXL_BinDir= "C:\\ECompXL\\"; -if (0) # so we can turn them on/off easily + if (0) # so we can turn them on/off easily { # $SDK_LibraryDirs{'ALL'}{'zlib.lib'} = "C:\\S\\zlib-1.2.2\\epoc"; # $SDK_LibraryDirs{'ALL'}{'libmad.lib'} = "C:\\S\\libmad-0.15.1b\\group"; @@ -101,9 +144,11 @@ if (0) # so we can turn them on/off easily # $SDK_LibraryDirs{'S90'}{'esdl.lib'} = "C:\\S\\ESDL\\epoc\\S90"; #$SDK_LibraryDirs{'ALL'}{'libmpeg2.lib'} = "C:\\S\\mpeg2dec-0.4.0\\epoc"; } - # now you can add $SDK_Variations only built on this PC here :) + + # now you can add $VariationSets only built on this PC below this line :) + } -elsif ($ENV{'COMPUTERNAME'} eq "BIGMACHINE") + elsif ($ENV{'COMPUTERNAME'} eq "BIGMACHINE") ################################################################# { $Producer = "AnotherGuest"; $RedirectSTDERR = 1; @@ -116,15 +161,15 @@ elsif ($ENV{'COMPUTERNAME'} eq "BIGMACHINE") #$FTP_Pass = "password"; #$FTP_Dir = "cvsbuilds"; - #$SDK_RootDirs{'UIQ2'}= "E:\\UIQ2"; - $SDK_RootDirs{'UIQ3'}= "E:\\UIQ3"; - #$SDK_RootDirs{'S60v1'}= "E:\\S60v1"; - #$SDK_RootDirs{'S60v2'}= "E:\\S60v2"; - $SDK_RootDirs{'S60v3'}= "E:\\S60v3"; - #$SDK_RootDirs{'S80'}= "E:\\S80"; - #$SDK_RootDirs{'S90'}= "E:\\S90"; - $ECompXL_BinDir= "E:\\ECompXL\\"; -if (0) # so we can turn them on/off easily + #$SDK_RootDirs{'UIQ2'}= "D:\\UIQ2"; + $SDK_RootDirs{'UIQ3'}= "D:\\UIQ3"; + #$SDK_RootDirs{'S60v1'}= "D:\\S60v1"; + #$SDK_RootDirs{'S60v2'}= "D:\\S60v2"; + $SDK_RootDirs{'S60v3'}= "D:\\S60v3"; + #$SDK_RootDirs{'S80'}= "D:\\S80"; + #$SDK_RootDirs{'S90'}= "D:\\S90"; + $ECompXL_BinDir= "D:\\ECompXL\\"; + if (0) # so we can turn them on/off easily { # $SDK_LibraryDirs{'ALL'}{'zlib.lib'} = "C:\\S\\zlib-1.2.2\\epoc"; # $SDK_LibraryDirs{'ALL'}{'libmad.lib'} = "C:\\S\\libmad-0.15.1b\\group"; @@ -137,28 +182,32 @@ if (0) # so we can turn them on/off easily $SDK_LibraryDirs{'UIQ3'}{'esdl.lib'} = "E:\\WICKED\\ESDL\\epoc\\UIQ\\UIQ3"; #$SDK_LibraryDirs{'ALL'}{'libmpeg2.lib'} = "C:\\S\\mpeg2dec-0.4.0\\epoc"; } - # now you can add $SDK_Variations only built on this PC here :) + + # now you can add $VariationSets only built on this PC below this line :) + } - else + else ######################################################################################################### { print "ERROR: Computer name ".$ENV{'COMPUTERNAME'}." not recognized! Plz edit _LocalSettings.pl!"; exit 1; } +################################################################################################################## ## ## Variation defines: ## +################################################################################################################## # second hash index = literal string used in .sis file created. # empty string also removes the trailing '_'. Some 051101 examples: - # $SDK_Variations{'UIQ2'}{''} would produce: + # $VariationSets{'UIQ2'}{''} would produce: # scummvm-051101-SymbianUIQ2.sis - # $SDK_Variations{'S60v2'}{'agos'} would produce: + # $VariationSets{'S60v2'}{'agos'} would produce: # scummvm-051101-SymbianS60v2_agos.sis - # $SDK_Variations{'ALL'}{'queen'} with all $SDK_RootDirs defined would produce: + # $VariationSets{'ALL'}{'queen'} with all $SDK_RootDirs defined would produce: # scummvm-051101-SymbianUIQ2_queen.sis # scummvm-051101-SymbianUIQ3_queen.sis # scummvm-051101-SymbianS60v1_queen.sis @@ -167,361 +216,50 @@ if (0) # so we can turn them on/off easily # scummvm-051101-SymbianS80_queen.sis # scummvm-051101-SymbianS90_queen.sis - #$SDK_Variations{'ALL'}{'test'} = "$DefaultTopMacro - # //MACRO USE_TREMOR // LIB:libtremor.lib - # //MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - # //MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - # //MACRO DISABLE_SKY // LIB:scummvm_sky.lib - # //MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - # //MACRO DISABLE_GOB // LIB:scummvm_gob.lib - # //MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - # //MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - #$DefaultBottomMacros"; - #$SDK_Variations{'S60v1'}{'test'} = $SDK_Variations{'UIQ2'}{'test'}; - -if (1) # all regular combo's -{ - # the first one includes all SDKs & release-ready engines - - $SDK_Variations{'ALL'}{'all'} = "$DefaultTopMacros - //MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - //MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - //MACRO DISABLE_SKY // LIB:scummvm_sky.lib - //MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - //MACRO DISABLE_GOB // LIB:scummvm_gob.lib - //MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - //MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - //MACRO DISABLE_AGI // LIB:scummvm_agi.lib - //MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - //MACRO DISABLE_CINE // LIB:scummvm_cine.lib - //MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - //MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib + # NOTE: empty $VariationSets{''} string instead of 'ALL' = easy way to disable pkg! - //MACRO DISABLE_LURE // LIB:scummvm_lure.lib - //MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - //MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - //MACRO DISABLE_MADE // LIB:scummvm_made.lib - //MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; + if (1) # all regular combo's + { + # the first one includes all SDKs & release-ready engines + + $VariationSets{'ALL'}{'all'} = "$DefaultFeatures @WorkingEngines @EnablableSubEngines"; + + # now one for each ready-for-release engine + if (0) + { + foreach (@WorkingEngines) + { + $VariationSets{'ALL'}{$_} = "$DefaultFeatures $_"; + } + # for scumm, we need to add 2 features: + #$VariationSets{'ALL'}{'scumm'} .= " scumm_7_8 he"; + } - # now one for each ready-for-release engine + # now one for each not-ready-for-release-or-testing engine + if (0) + { + foreach (@TestingEngines) + { + $VariationSets{'ALL'}{"test_$_"} = "$DefaultFeatures $_"; + } + } + # below here you could specify weird & experimental combinations, non-ready engines + + # a small version of the saga engine, because it is so big (no tremor,mad,zlib) + #$VariationSets{'ALL'}{'saga_mini'} = "saga"; + + # a smaller version of scumm without support for v7, v8 and HE games + #$VariationSets{'ALL'}{'scumm_no78he'} = "$DefaultFeatures scumm"; + + # maybe you feel lucky and want to test the sword engines? :P + #$VariationSets{'S60v2'}{'test_sword'} = "$DefaultFeatures mpeg2 sword1 sword2"; + #$VariationSets{'UIQ2'}{'test_sword'} = "$DefaultFeatures mpeg2 sword1 sword2"; + + # for mega-fast-testing only plz! Warning: contains to engines! + #$VariationSets{'ALL'}{'fast_empty'} = ""; + + } # end quick-n-fast if (1|0) - $SDK_Variations{'ALL'}{'scumm'} = "$DefaultTopMacros - //MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - $SDK_Variations{'ALL'}{'agos'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - //MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - $SDK_Variations{'ALL'}{'sky'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - //MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - $SDK_Variations{'ALL'}{'queen'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - //MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - $SDK_Variations{'ALL'}{'gob'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - //MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - $SDK_Variations{'ALL'}{'saga'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - //MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - $SDK_Variations{'ALL'}{'kyra'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - //MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - # below here you could specify weird & experimental combinations, non-ready engines - - $SDK_Variations{'ALL'}{'saga_mini'} = " - //MACRO USE_ZLIB // LIB:zlib.lib - //MACRO USE_MAD // LIB:libmad.lib - //MACRO USE_TREMOR // LIB:libtremor.lib - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - //MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - -# $SDK_Variations{'ALL'}{'scumm_no78he'} = " -# MACRO USE_ZLIB // LIB:zlib.lib -# MACRO USE_MAD // LIB:libmad.lib -# //MACRO USE_TREMOR // LIB:libtremor.lib -# MACRO DISABLE_SCUMM_7_8 -# MACRO DISABLE_SCUMM_HE -# //MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib -# MACRO DISABLE_AGOS // LIB:scummvm_agos.lib -# MACRO DISABLE_SKY // LIB:scummvm_sky.lib -# MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib -# MACRO DISABLE_GOB // LIB:scummvm_gob.lib -# MACRO DISABLE_SAGA // LIB:scummvm_saga.lib -# MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib -# MACRO DISABLE_AGI // LIB:scummvm_agi.lib -# MACRO DISABLE_LURE // LIB:scummvm_lure.lib -# MACRO DISABLE_CINE // LIB:scummvm_cine.lib -# MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib -# $DefaultBottomMacros"; - -# $SDK_Variations{'ALL'}{'all_vorbis'} = " -# MACRO USE_ZLIB // LIB:zlib.lib -# MACRO USE_MAD // LIB:libmad.lib -# MACRO USE_TREMOR // LIB:libtremor.lib -# -# //MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib -# //MACRO DISABLE_AGOS // LIB:scummvm_agos.lib -# //MACRO DISABLE_SKY // LIB:scummvm_sky.lib -# //MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib -# //MACRO DISABLE_GOB // LIB:scummvm_gob.lib -# //MACRO DISABLE_SAGA // LIB:scummvm_saga.lib -# //MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib -# $DefaultBottomMacros"; - - $SDK_Variations{'ALL'}{'lure'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - //MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_agi.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - # empty $SDK_Variations{''} string instead of 'ALL' = package disabled - $SDK_Variations{'ALL'}{'test_cine'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - //MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - $SDK_Variations{'ALL'}{'agi'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - //MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - $DefaultBottomMacros"; - - - $SDK_Variations{'ALL'}{'test_touche'} = "$DefaultTopMacros - MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - MACRO DISABLE_SKY // LIB:scummvm_sky.lib - MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - MACRO DISABLE_GOB // LIB:scummvm_gob.lib - MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - MACRO DISABLE_LURE // LIB:scummvm_lure.lib - MACRO DISABLE_CINE // LIB:scummvm_cine.lib - MACRO DISABLE_AGI // LIB:scummvm_agi.lib - MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - //MACRO DISABLE_TOUCHE // LIB:scummvm_touche.lib - MACRO DISABLE_SWORD1 // LIB:scummvm_sword1.lib - MACRO DISABLE_SWORD2 // LIB:scummvm_sword2.lib - MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib - MACRO DISABLE_IGOR // LIB:scummvm_igor.lib - MACRO DISABLE_MADE // LIB:scummvm_made.lib - MACRO DISABLE_M4 // LIB:scummvm_m4.lib - "; -} - -# -# $SDK_Variations{'S60v2'}{'test_sword'} = "$DefaultTopMacros -# MACRO USE_MPEG2 // LIB:libmpeg2.lib -# MACRO USE_TREMOR // LIB:libtremor.lib -# MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib -# MACRO DISABLE_AGOS // LIB:scummvm_agos.lib -# MACRO DISABLE_SKY // LIB:scummvm_sky.lib -# MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib -# MACRO DISABLE_GOB // LIB:scummvm_gob.lib -# MACRO DISABLE_SAGA // LIB:scummvm_saga.lib -# MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib -# MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib -# MACRO DISABLE_CINE // LIB:scummvm_cine.lib -# MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib -# //MACRO DISABLE_SWORD1 // LIB:scummvm_sword1.lib -# //MACRO DISABLE_SWORD2 // LIB:scummvm_sword2.lib -# "; -# $SDK_Variations{'UIQ2'}{'test_sword'} = $SDK_Variations{'S60v2'}{'test_sword'} -# - -# for mega-fast-testing only plz! -# $SDK_Variations{'ALL'}{'(fast_empty)'} = " -# //MACRO USE_ZLIB // LIB:zlib.lib -# //MACRO USE_MAD // LIB:libmad.lib -# //MACRO USE_TREMOR // LIB:libtremor.lib -# MACRO USE_UIQ_SE_VIBRA // LIB:vibration.lib -# MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib -# MACRO DISABLE_AGOS // LIB:scummvm_agos.lib -# MACRO DISABLE_SKY // LIB:scummvm_sky.lib -# //MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib -# MACRO DISABLE_GOB // LIB:scummvm_gob.lib -# MACRO DISABLE_SAGA // LIB:scummvm_saga.lib -# $DefaultBottomMacros"; ################################################################################################################## diff --git a/backends/platform/symbian/README b/backends/platform/symbian/README index 0f9df338c3d..8ab729a4d04 100644 --- a/backends/platform/symbian/README +++ b/backends/platform/symbian/README @@ -38,6 +38,7 @@ Building ScummVM Lets just say the framework needs quite some time to set up and takes a while to get used to. If you choose to continue you will need the following items: + - UIQ 3.0 SDK (To build for UIQ3 devices) - UIQ 2.1 SDK (To build for UIQ2 devices); http://www.symbian.com/developer/sdks_uiq.asp @@ -58,14 +59,14 @@ Building ScummVM - zlib, a massively spiffy yet delicately unobtrusive compression library http://www.zlib.net/ - - These are probably too heavy-duty for your phone: + - latest version of active perl (included with Symbian SDK does not work with the build scripts) - libogg, the free media file container format http://www.xiph.org/ogg/ - + - libvorbis, the free audio codec http://www.vorbis.com/ + These are probably too heavy-duty for your phone: - flac, the Free Lossless Audio Codec http://flac.sourceforge.net/ diff --git a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in index ff11657b348..3fea916e43c 100644 --- a/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in +++ b/backends/platform/symbian/S60v3/ScummVM_S60v3.mmp.in @@ -29,7 +29,7 @@ TARGET ScummVM.exe TARGETPATH sys\bin TARGETTYPE exe -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char UID 0x100039ce 0xA0000657 @@ -81,13 +81,6 @@ ALWAYS_BUILD_AS_ARM STATICLIBRARY esdl.lib -#if !defined(WINS) -staticlibrary rate_arm_asm.o -staticlibrary proc3arm.o -staticlibrary codec47ARM.o -staticlibrary gfxARM.o -#endif - // *** Include paths USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\engines diff --git a/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in b/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in index a1ac965148a..0013d061ca9 100644 --- a/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in +++ b/backends/platform/symbian/UIQ3/ScummVM_UIQ3.mmp.in @@ -30,7 +30,7 @@ TARGET ScummVM.exe TARGETPATH sys\bin TARGETTYPE exe -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char UID 0x100039ce 0xA0000657 @@ -82,13 +82,6 @@ ALWAYS_BUILD_AS_ARM STATICLIBRARY esdl.lib -#if !defined(WINS) -staticlibrary rate_arm_asm.o -staticlibrary proc3arm.o -staticlibrary codec47ARM.o -staticlibrary gfxARM.o -#endif - // *** Include paths USERINCLUDE ..\..\..\.. ..\..\..\..\common ..\..\..\..\gui ..\..\..\..\engines diff --git a/backends/platform/symbian/mmp/scummvm_agi.mmp.in b/backends/platform/symbian/mmp/scummvm_agi.mmp.in index 8432968f328..04cf068133f 100644 --- a/backends/platform/symbian/mmp/scummvm_agi.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_agi.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_agi.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_agos.mmp.in b/backends/platform/symbian/mmp/scummvm_agos.mmp.in index 528349c176e..d03a30d88bc 100644 --- a/backends/platform/symbian/mmp/scummvm_agos.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_agos.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_agos.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_base.mmp.in b/backends/platform/symbian/mmp/scummvm_base.mmp.in index 761d4867e7d..d1c8878d4b7 100644 --- a/backends/platform/symbian/mmp/scummvm_base.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_base.mmp.in @@ -30,36 +30,15 @@ TARGET scummvm_base.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM // Note: the LIB:*.lib statements are used by AdaptAllMMPs.pl, so don't remove them! //START_AUTO_MACROS_MASTER// // empty base file, will be updated by Perl build scripts - - // list of possible MACROs: (will be replaced when generating scummvm_base.mmp) - //MACRO USE_ZLIB // LIB:zlib.lib - //MACRO USE_MAD // LIB:libmad.lib - //MACRO USE_TREMOR // LIB:libtremor.lib - //MACRO USE_UIQ_SE_VIBRA // LIB:vibration.lib - //MACRO DISABLE_SCUMM_7_8 - //MACRO DISABLE_SCUMM_HE - //MACRO DISABLE_SCUMM // LIB:scummvm_scumm.lib - //MACRO DISABLE_AGOS // LIB:scummvm_agos.lib - //MACRO DISABLE_SKY // LIB:scummvm_sky.lib - //MACRO DISABLE_QUEEN // LIB:scummvm_queen.lib - //MACRO DISABLE_GOB // LIB:scummvm_gob.lib - //MACRO DISABLE_SAGA // LIB:scummvm_saga.lib - //MACRO DISABLE_KYRA // LIB:scummvm_kyra.lib - //MACRO DISABLE_SWORD1 // LIB:scummvm_sword1.lib - //MACRO DISABLE_SWORD2 // LIB:scummvm_sword2.lib - //MACRO DISABLE_LURE // LIB:scummvm_lure.lib - //MACRO DISABLE_CINE // LIB:scummvm_cine.lib - //MACRO DISABLE_AGI // LIB:scummvm_agi.lib - //MACRO DISABLE_PARALLACTION // LIB:scummvm_parallaction.lib - //MACRO DISABLE_CRUISE // LIB:scummvm_cruise.lib - //MACRO DISABLE_DRASCULA // LIB:scummvm_drascula.lib + // this file will be modified first, then from here all + // MACROs will be replicated to the other MPP files. //STOP_AUTO_MACROS_MASTER// @@ -77,6 +56,7 @@ SYSTEMINCLUDE ..\src // for portdefs.h // *** SOURCE files + SOURCEPATH ..\..\..\..\common //START_AUTO_OBJECTS_COMMON_// @@ -84,6 +64,7 @@ SOURCEPATH ..\..\..\..\common //STOP_AUTO_OBJECTS_COMMON_// + SOURCEPATH ..\..\..\..\graphics //START_AUTO_OBJECTS_GRAPHICS_// @@ -91,6 +72,7 @@ SOURCEPATH ..\..\..\..\graphics //STOP_AUTO_OBJECTS_GRAPHICS_// + SOURCEPATH ..\..\..\..\gui //START_AUTO_OBJECTS_GUI_// @@ -103,6 +85,7 @@ SOURCEPATH ..\..\..\..\gui //SOURCE KeysDialog.cpp //SOURCE Actions.cpp + SOURCEPATH ..\..\..\..\sound //START_AUTO_OBJECTS_SOUND_// @@ -110,16 +93,22 @@ SOURCEPATH ..\..\..\..\sound //STOP_AUTO_OBJECTS_SOUND_// #if defined (WINS) -SOURCE rate.cpp +SOURCE rate.cpp // WINS emulator version: add regular .cpp #else -SOURCE rate_arm.cpp +SOURCE rate_arm.cpp // ARM version: add ASM .cpp wrapper +SOURCE rate_arm_asm.s // ARM version: add ASM routines #endif -sourcepath ..\..\..\.. -source backends\events\default\default-events.cpp -source backends\timer\default\default-timer.cpp -source backends\saves\savefile.cpp -source backends\saves\default\default-saves.cpp -source backends\saves\compressed\compressed-saves.cpp -source engines\engine.cpp + + +// add a few files manually, since they are not parsed from modules.mk files +SOURCEPATH ..\..\..\.. +SOURCE backends\events\default\default-events.cpp +SOURCE backends\timer\default\default-timer.cpp +SOURCE backends\saves\savefile.cpp +SOURCE backends\saves\default\default-saves.cpp +SOURCE backends\saves\compressed\compressed-saves.cpp +SOURCE engines\engine.cpp + + // backend specific includes // backend specific includes diff --git a/backends/platform/symbian/mmp/scummvm_cine.mmp.in b/backends/platform/symbian/mmp/scummvm_cine.mmp.in index 052b62759b0..cc355b03184 100644 --- a/backends/platform/symbian/mmp/scummvm_cine.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_cine.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_CINE.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_cruise.mmp.in b/backends/platform/symbian/mmp/scummvm_cruise.mmp.in index ad363984c8f..718f80a95b7 100644 --- a/backends/platform/symbian/mmp/scummvm_cruise.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_cruise.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_cruise.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_drascula.mmp.in b/backends/platform/symbian/mmp/scummvm_drascula.mmp.in index 4c9d6144464..4979cc91a6c 100644 --- a/backends/platform/symbian/mmp/scummvm_drascula.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_drascula.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_drascula.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_gob.mmp.in b/backends/platform/symbian/mmp/scummvm_gob.mmp.in index 734f3c686cb..8522a937ab5 100644 --- a/backends/platform/symbian/mmp/scummvm_gob.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_gob.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_gob.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_kyra.mmp.in b/backends/platform/symbian/mmp/scummvm_kyra.mmp.in index 6c4e9a0a972..503a54ce1f4 100644 --- a/backends/platform/symbian/mmp/scummvm_kyra.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_kyra.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_kyra.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_lure.mmp.in b/backends/platform/symbian/mmp/scummvm_lure.mmp.in index 6b3410e95fa..24173c6a571 100644 --- a/backends/platform/symbian/mmp/scummvm_lure.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_lure.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_lure.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_m4.mmp.in b/backends/platform/symbian/mmp/scummvm_m4.mmp.in index 62a00143bec..4429582bd7e 100644 --- a/backends/platform/symbian/mmp/scummvm_m4.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_m4.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_m4.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_made.mmp.in b/backends/platform/symbian/mmp/scummvm_made.mmp.in index 7aac818f4ef..cb69cbf115f 100644 --- a/backends/platform/symbian/mmp/scummvm_made.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_made.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_made.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in b/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in index 910bb42ec99..4c117fba454 100644 --- a/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_parallaction.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_parallaction.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_queen.mmp.in b/backends/platform/symbian/mmp/scummvm_queen.mmp.in index 96b827a994b..6bee237f77f 100644 --- a/backends/platform/symbian/mmp/scummvm_queen.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_queen.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_queen.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_saga.mmp.in b/backends/platform/symbian/mmp/scummvm_saga.mmp.in index 9e8c658a8fa..c040de250cb 100644 --- a/backends/platform/symbian/mmp/scummvm_saga.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_saga.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_saga.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_scumm.mmp.in b/backends/platform/symbian/mmp/scummvm_scumm.mmp.in index 39490cfd297..7eecfd3ff6c 100644 --- a/backends/platform/symbian/mmp/scummvm_scumm.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_scumm.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_scumm.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// @@ -43,23 +43,34 @@ ALWAYS_BUILD_AS_ARM SOURCEPATH ..\..\..\..\engines\scumm + //START_AUTO_OBJECTS_SCUMM_// // empty base file, will be updated by Perl build scripts //STOP_AUTO_OBJECTS_SCUMM_// +#if !defined (WINS) +SOURCE gfxARM.s // ARM version: add ASM routines +SOURCE proc3ARM.s // ARM version: add ASM routines +#endif -//START_AUTO_OBJECTS_SCUMM_DISABLE_SCUMM_7_8// + +//START_AUTO_OBJECTS_SCUMM_ENABLE_SCUMM_7_8// // empty base file, will be updated by Perl build scripts -//STOP_AUTO_OBJECTS_SCUMM_DISABLE_SCUMM_7_8// +//STOP_AUTO_OBJECTS_SCUMM_ENABLE_SCUMM_7_8// +#if !defined (WINS) +SOURCE smush/codec47ARM.s // ARM version: add ASM routines +#endif -//START_AUTO_OBJECTS_SCUMM_DISABLE_HE// + +//START_AUTO_OBJECTS_SCUMM_ENABLE_HE// // empty base file, will be updated by Perl build scripts -//STOP_AUTO_OBJECTS_SCUMM_DISABLE_HE// +//STOP_AUTO_OBJECTS_SCUMM_ENABLE_HE// + // *** Include paths diff --git a/backends/platform/symbian/mmp/scummvm_sky.mmp.in b/backends/platform/symbian/mmp/scummvm_sky.mmp.in index e3598031945..9ae57aa2b69 100644 --- a/backends/platform/symbian/mmp/scummvm_sky.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sky.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_sky.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_sword1.mmp.in b/backends/platform/symbian/mmp/scummvm_sword1.mmp.in index 9bc97919c5f..ecf71c2d30d 100644 --- a/backends/platform/symbian/mmp/scummvm_sword1.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sword1.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_sword1.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_sword2.mmp.in b/backends/platform/symbian/mmp/scummvm_sword2.mmp.in index 4d25a41e894..91029b1f8dd 100644 --- a/backends/platform/symbian/mmp/scummvm_sword2.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_sword2.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_sword2.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/mmp/scummvm_touche.mmp.in b/backends/platform/symbian/mmp/scummvm_touche.mmp.in index 9ea7636af52..3b8a7e61052 100644 --- a/backends/platform/symbian/mmp/scummvm_touche.mmp.in +++ b/backends/platform/symbian/mmp/scummvm_touche.mmp.in @@ -30,7 +30,7 @@ TARGET scummvm_touche.lib TARGETTYPE lib OPTION MSVC /QIfist /Ob1 /Oy /GF // /QIfist disables use of __ftol2 to avoid linker probs with MS libc: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vccore/html/vcrefQIfistSuppress_ftol.asp OPTION GCC -Wno-multichar -Wno-reorder // don't optimize for ARM, platform way too sensitive for that :( just turn off some common warnings -OPTION GCCE -Wno-multichar -Wno-reorder -fsigned-char +OPTION GCCE -Wno-multichar -Wno-reorder -Wno-unused -Wno-format -fsigned-char ALWAYS_BUILD_AS_ARM //START_AUTO_MACROS_SLAVE// diff --git a/backends/platform/symbian/src/SymbianOS.cpp b/backends/platform/symbian/src/SymbianOS.cpp index d3e92731db2..e3a4027d798 100644 --- a/backends/platform/symbian/src/SymbianOS.cpp +++ b/backends/platform/symbian/src/SymbianOS.cpp @@ -33,7 +33,7 @@ #include "gui/Actions.h" #include "gui/Key.h" #include "gui/message.h" - +#include "sound/mixer_intern.h" #include "..\..\sdl\main.cpp" #ifdef SAMPLES_PER_SEC_8000 // the GreanSymbianMMP format cannot handle values for defines :( @@ -42,10 +42,22 @@ #define SAMPLES_PER_SEC 16000 #endif +#define KInputBufferLength 128 +// Symbian libc file functionality in order to provide shared file handles +struct TSymbianFileEntry { + RFile iFileHandle; + char iInputBuffer[KInputBufferLength]; + TInt iInputBufferLen; + TInt iInputPos; +}; + +#define FILE void ////////// extern "C" /////////////////////////////////////////////////// namespace Symbian { + + // Show a simple Symbian Info win with Msg & exit void FatalError(const char *msg) { TPtrC8 msgPtr((const TUint8 *)msg); @@ -173,11 +185,8 @@ void OSystem_SDL_Symbian::quit() { OSystem_SDL::quit(); } -bool OSystem_SDL_Symbian::setSoundCallback(SoundProc proc, void *param) { +void OSystem_SDL_Symbian::setupMixer() { - // First save the proc and param - _sound_proc_param = param; - _sound_proc = proc; SDL_AudioSpec desired; SDL_AudioSpec obtained; @@ -207,48 +216,53 @@ bool OSystem_SDL_Symbian::setSoundCallback(SoundProc proc, void *param) { desired.format = AUDIO_S16SYS; desired.channels = 2; desired.samples = (uint16)samples; -#ifdef S60 desired.callback = symbianMixCallback; desired.userdata = this; -#else - desired.callback = proc; - desired.userdata = param; -#endif + + // Create the mixer instance + assert(!_mixer); + _mixer = new Audio::MixerImpl(this); + assert(_mixer); + if (SDL_OpenAudio(&desired, &obtained) != 0) { warning("Could not open audio device: %s", SDL_GetError()); - return false; + _samplesPerSec = 0; + _mixer->setReady(false); + } else { + // Note: This should be the obtained output rate, but it seems that at + // least on some platforms SDL will lie and claim it did get the rate + // even if it didn't. Probably only happens for "weird" rates, though. + _samplesPerSec = obtained.freq; + _channels = obtained.channels; + + // Need to create mixbuffer for stereo mix to downmix + if (_channels != 2) { + _stereo_mix_buffer = new byte [obtained.size*2];//*2 for stereo values + } + + // Tell the mixer that we are ready and start the sound processing + _mixer->setOutputRate(_samplesPerSec); + _mixer->setReady(true); + SDL_PauseAudio(0); } - // Note: This should be the obtained output rate, but it seems that at - // least on some platforms SDL will lie and claim it did get the rate - // even if it didn't. Probably only happens for "weird" rates, though. - _samplesPerSec = obtained.freq; - _channels = obtained.channels; - - // Need to create mixbuffer for stereo mix to downmix - if (_channels != 2) { - _stereo_mix_buffer = new byte [obtained.size*2];//*2 for stereo values - } - - SDL_PauseAudio(0); - return true; } /** * The mixer callback function, passed on to OSystem::setSoundCallback(). * This simply calls the mix() method. */ -void OSystem_SDL_Symbian::symbianMixCallback(void *s, byte *samples, int len) { - static_cast (s)->symbianMix(samples,len); -} +void OSystem_SDL_Symbian::symbianMixCallback(void *sys, byte *samples, int len) { + OSystem_SDL_Symbian *this_ = (OSystem_SDL_Symbian *)sys; + assert(this_); + if (!this_->_mixer) + return; -/** - * Actual mixing implementation - */ -void OSystem_SDL_Symbian::symbianMix(byte *samples, int len) { +#if defined (S60) && !defined(S60V3) // If not stereo then we need to downmix - if (_channels != 2) { - _sound_proc(_sound_proc_param, _stereo_mix_buffer, len * 2); + if (this_->_mixer->_channels != 2) { + this_->_mixer->mixCallback(_stereo_mix_buffer, len * 2); + int16 *bitmixDst = (int16 *)samples; int16 *bitmixSrc = (int16 *)_stereo_mix_buffer; @@ -258,9 +272,12 @@ void OSystem_SDL_Symbian::symbianMix(byte *samples, int len) { bitmixSrc += 2; } } else - _sound_proc(_sound_proc_param, samples, len); +#else + this_->_mixer->mixCallback(samples, len); +#endif } + /** * This is an implementation by the remapKey function * @param SDL_Event to remap @@ -438,15 +455,9 @@ void OSystem_SDL_Symbian::initZones() { } } -// Symbian libc file functionality in order to provide shared file handles -struct TSymbianFileEntry { - RFile iFileHandle; -}; - -#define FILE void - FILE* symbian_fopen(const char* name, const char* mode) { TSymbianFileEntry* fileEntry = new TSymbianFileEntry; + fileEntry->iInputPos = KErrNotFound; if (fileEntry != NULL) { TInt modeLen = strlen(mode); @@ -504,9 +515,71 @@ void symbian_fclose(FILE* handle) { } size_t symbian_fread(const void* ptr, size_t size, size_t numItems, FILE* handle) { - TPtr8 pointer( (unsigned char*) ptr, size*numItems); + TSymbianFileEntry* entry = ((TSymbianFileEntry*)(handle)); + TUint32 totsize = size*numItems; + TPtr8 pointer ( (unsigned char*) ptr, totsize); - ((TSymbianFileEntry*)(handle))->iFileHandle.Read(pointer); + // Nothing cached and we want to load at least KInputBufferLength bytes + if(totsize >= KInputBufferLength) { + TUint32 totLength = 0; + if(entry->iInputPos != KErrNotFound) + { + TPtr8 cacheBuffer( (unsigned char*) entry->iInputBuffer+entry->iInputPos, entry->iInputBufferLen - entry->iInputPos, KInputBufferLength); + pointer.Append(cacheBuffer); + entry->iInputPos = KErrNotFound; + totLength+=pointer.Length(); + pointer.Set(totLength+(unsigned char*) ptr, 0, totsize-totLength); + } + + entry->iFileHandle.Read(pointer); + totLength+=pointer.Length(); + + pointer.Set((unsigned char*) ptr, totLength, totsize); + + } + else { + // Nothing in buffer + if(entry->iInputPos == KErrNotFound) { + TPtr8 cacheBuffer( (unsigned char*) entry->iInputBuffer, KInputBufferLength); + entry->iFileHandle.Read(cacheBuffer); + + if(cacheBuffer.Length() >= totsize) { + pointer.Copy(cacheBuffer.Left(totsize)); + entry->iInputPos = totsize; + entry->iInputBufferLen = cacheBuffer.Length(); + } + else { + pointer.Copy(cacheBuffer); + entry->iInputPos = KErrNotFound; + } + + } + else { + TPtr8 cacheBuffer( (unsigned char*) entry->iInputBuffer, entry->iInputBufferLen, KInputBufferLength); + + if(entry->iInputPos+totsize < entry->iInputBufferLen) { + pointer.Copy(cacheBuffer.Mid(entry->iInputPos, totsize)); + entry->iInputPos+=totsize; + } + else { + + pointer.Copy(cacheBuffer.Mid(entry->iInputPos, entry->iInputBufferLen-entry->iInputPos)); + cacheBuffer.SetLength(0); + entry->iFileHandle.Read(cacheBuffer); + + if(cacheBuffer.Length() >= totsize-pointer.Length()) { + TUint32 restSize = totsize-pointer.Length(); + pointer.Append(cacheBuffer.Left(restSize)); + entry->iInputPos = restSize; + entry->iInputBufferLen = cacheBuffer.Length(); + } + else { + pointer.Append(cacheBuffer); + entry->iInputPos = KErrNotFound; + } + } + } + } return pointer.Length()/size; } @@ -514,6 +587,7 @@ size_t symbian_fread(const void* ptr, size_t size, size_t numItems, FILE* handle size_t symbian_fwrite(const void* ptr, size_t size, size_t numItems, FILE* handle) { TPtrC8 pointer( (unsigned char*) ptr, size*numItems); + ((TSymbianFileEntry*)(handle))->iInputPos = KErrNotFound; if (((TSymbianFileEntry*)(handle))->iFileHandle.Write(pointer) == KErrNone) { return numItems; } @@ -523,12 +597,18 @@ size_t symbian_fwrite(const void* ptr, size_t size, size_t numItems, FILE* handl bool symbian_feof(FILE* handle) { TInt pos = 0; - if (((TSymbianFileEntry*)(handle))->iFileHandle.Seek(ESeekCurrent, pos) == KErrNone) { + TSymbianFileEntry* entry = ((TSymbianFileEntry*)(handle)); + + if (entry->iFileHandle.Seek(ESeekCurrent, pos) == KErrNone) { TInt size = 0; - if (((TSymbianFileEntry*)(handle))->iFileHandle.Size(size) == KErrNone) { - if (pos == size) + if (entry->iFileHandle.Size(size) == KErrNone) { + if(entry->iInputPos == KErrNotFound && pos == size) return true; + + if(entry->iInputPos != KErrNotFound && pos == size && entry->iInputPos == entry->iInputBufferLen) + return true; + return false; } } @@ -537,15 +617,21 @@ bool symbian_feof(FILE* handle) { long int symbian_ftell(FILE* handle) { TInt pos = 0; + TSymbianFileEntry* entry = ((TSymbianFileEntry*)(handle)); - ((TSymbianFileEntry*)(handle))->iFileHandle.Seek(ESeekCurrent, pos); - + entry->iFileHandle.Seek(ESeekCurrent, pos); + if(entry->iInputPos != KErrNotFound) + { + pos+=(entry->iInputPos - entry->iInputBufferLen); + } return pos; } int symbian_fseek(FILE* handle, long int offset, int whence) { + TSeek seekMode = ESeekStart; TInt pos = offset; + TSymbianFileEntry* entry = ((TSymbianFileEntry*)(handle)); switch(whence) { case SEEK_SET: @@ -553,14 +639,19 @@ int symbian_fseek(FILE* handle, long int offset, int whence) { break; case SEEK_CUR: seekMode = ESeekCurrent; + if(entry->iInputPos != KErrNotFound) { + pos+=(entry->iInputPos - entry->iInputBufferLen); + } break; case SEEK_END: seekMode = ESeekEnd; break; } + + entry->iInputPos = KErrNotFound; - return ((TSymbianFileEntry*)(handle))->iFileHandle.Seek(seekMode, pos); + return entry->iFileHandle.Seek(seekMode, pos); } void symbian_clearerr(FILE* /*handle*/) { diff --git a/backends/platform/symbian/src/SymbianOS.h b/backends/platform/symbian/src/SymbianOS.h index 77e42cd4760..71d24f62862 100644 --- a/backends/platform/symbian/src/SymbianOS.h +++ b/backends/platform/symbian/src/SymbianOS.h @@ -58,7 +58,7 @@ public: // This function is overridden by the symbian port in order to provide MONO audio // downmix is done by supplying our own audiocallback // - virtual bool setSoundCallback(SoundProc proc, void *param); // overloaded by CE backend + virtual void setupMixer(); // overloaded by CE backend // Overloaded from SDL_Commmon void quit(); @@ -70,11 +70,6 @@ protected: // static void symbianMixCallback(void *s, byte *samples, int len); - // - // Actual mixing implementation - // - void symbianMix(byte *samples, int len); - virtual FilesystemFactory *getFilesystemFactory(); public: // vibration support @@ -121,8 +116,6 @@ protected: // Audio int _channels; - SoundProc _sound_proc; - void *_sound_proc_param; byte *_stereo_mix_buffer; // Used to handle joystick navi zones diff --git a/backends/platform/symbian/src/portdefs.h b/backends/platform/symbian/src/portdefs.h index 2723c0bab05..06a4cf374cf 100644 --- a/backends/platform/symbian/src/portdefs.h +++ b/backends/platform/symbian/src/portdefs.h @@ -35,8 +35,8 @@ #include #include -#define DISABLE_SCALERS // we only need 1x -#define DISABLE_HQ_SCALERS +//#define DISABLE_SCALERS // we only need 1x +//#define DISABLE_HQ_SCALERS #if defined(USE_TREMOR) && !defined(USE_VORBIS) #define USE_VORBIS // make sure this one is defined together with USE_TREMOR! diff --git a/backends/platform/wii/osystem.cpp b/backends/platform/wii/osystem.cpp index 2667ecb69d6..9e708345c5f 100644 --- a/backends/platform/wii/osystem.cpp +++ b/backends/platform/wii/osystem.cpp @@ -91,7 +91,7 @@ void OSystem_Wii::initBackend() { _startup_time = gettime(); _savefile = new DefaultSaveFileManager(); - _mixer = new Audio::Mixer(); + _mixer = new Audio::MixerImpl(this); _timer = new DefaultTimerManager(); initGfx(); diff --git a/backends/platform/wii/osystem.h b/backends/platform/wii/osystem.h index 2a168fd0b75..7fbc560b1a9 100644 --- a/backends/platform/wii/osystem.h +++ b/backends/platform/wii/osystem.h @@ -31,7 +31,7 @@ #include "backends/saves/default/default-saves.h" #include "backends/timer/default/default-timer.h" #include "graphics/surface.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include #include @@ -96,7 +96,7 @@ private: protected: Common::SaveFileManager *_savefile; - Audio::Mixer *_mixer; + Audio::MixerImpl *_mixer; DefaultTimerManager *_timer; public: @@ -159,7 +159,6 @@ public: virtual void deleteMutex(MutexRef mutex); typedef void (*SoundProc)(void *param, byte *buf, int len); - virtual int getOutputSampleRate() const; virtual void quit(); diff --git a/backends/platform/wii/osystem_sfx.cpp b/backends/platform/wii/osystem_sfx.cpp index 16b2f3b0555..0a225e80c71 100644 --- a/backends/platform/wii/osystem_sfx.cpp +++ b/backends/platform/wii/osystem_sfx.cpp @@ -36,9 +36,6 @@ static bool sfx_thread_quit = false; static u8 sb = 0; static u8 *sound_buffer[2]; -static OSystem_Wii::SoundProc sound_proc = NULL; -static void *proc_param = NULL; - static void audio_switch_buffers() { AUDIO_StopDMA(); AUDIO_InitDMA((u32) sound_buffer[sb], SFX_THREAD_FRAG_SIZE); @@ -48,6 +45,7 @@ static void audio_switch_buffers() { } static void * sfx_thread_func(void *arg) { + Audio::MixerImpl *mixer = (Audio::MixerImpl *) arg; u8 next_sb; while (true) { @@ -57,7 +55,7 @@ static void * sfx_thread_func(void *arg) { break; next_sb = sb ^ 1; - sound_proc(proc_param, sound_buffer[next_sb], SFX_THREAD_FRAG_SIZE); + mixer->mixCallback(sound_buffer[next_sb], SFX_THREAD_FRAG_SIZE); DCFlushRange(sound_buffer[next_sb], SFX_THREAD_FRAG_SIZE); sb = next_sb; @@ -75,7 +73,7 @@ void OSystem_Wii::initSfx() { LWP_InitQueue(&sfx_queue); - s32 res = LWP_CreateThread(&sfx_thread, sfx_thread_func, NULL, sfx_stack, + s32 res = LWP_CreateThread(&sfx_thread, sfx_thread_func, _mixer, sfx_stack, SFX_THREAD_STACKSIZE, SFX_THREAD_PRIO); if (res) { @@ -95,9 +93,7 @@ void OSystem_Wii::initSfx() { DCFlushRange(sound_buffer[0], SFX_THREAD_FRAG_SIZE); DCFlushRange(sound_buffer[1], SFX_THREAD_FRAG_SIZE); - sound_proc = Audio::Mixer::mixCallback; - proc_param = _mixer; - + _mixer->setOutputRate(48000); _mixer->setReady(true); AUDIO_SetDSPSampleRate(AI_SAMPLERATE_48KHZ); @@ -127,7 +123,3 @@ void OSystem_Wii::deinitSfx() { } } -int OSystem_Wii::getOutputSampleRate() const { - return 48000; -} - diff --git a/backends/platform/wince/Makefile b/backends/platform/wince/Makefile index 734bf82eaa1..176971067e1 100644 --- a/backends/platform/wince/Makefile +++ b/backends/platform/wince/Makefile @@ -19,6 +19,8 @@ ## Enable whichever engines you want here ENABLE_SCUMM = STATIC_PLUGIN +ENABLE_SCUMM_7_8 = 1 +ENABLE_HE = 1 ENABLE_SKY = STATIC_PLUGIN ENABLE_QUEEN = STATIC_PLUGIN ENABLE_GOB = STATIC_PLUGIN @@ -163,7 +165,8 @@ endif EXECUTABLE = scummvm.exe CXXFLAGS := $(CFLAGS) OBJS := -MODULE_DIRS += . +MODULE_DIRS += ./ +DEPDIR = .deps OBJS += CEActionsPocket.o CEDevice.o CEScaler.o \ CEActionsSmartphone.o CELauncherDialog.o wince-sdl.o diff --git a/backends/platform/wince/missing/missing.cpp b/backends/platform/wince/missing/missing.cpp index 86d93dcb887..c760b1f7df3 100644 --- a/backends/platform/wince/missing/missing.cpp +++ b/backends/platform/wince/missing/missing.cpp @@ -1,8 +1,34 @@ -/* MISSING.C - Implementation for standard and semi-standard C library calls missing in WinCE - environment. - by Vasyl Tsvirkunov -*/ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public 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$ + * + */ + +/* Original code: + * Implementation for standard and semi-standard C library calls missing in WinCE + * environment. + * by Vasyl Tsvirkunov + */ + #include #include @@ -17,19 +43,8 @@ #include "time.h" #include "dirent.h" -/* forward declaration */ - -#if _WIN32_WCE < 300 - -#define _STDAFX_H -#include "portdefs.h" - -#else - char *strdup(const char *strSource); -#endif - #ifdef __GNUC__ #define EXT_C extern "C" #else @@ -40,19 +55,27 @@ char *strdup(const char *strSource); void *bsearch(const void *key, const void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *)) { - size_t i; + // Perform binary search + size_t lo = 0; + size_t hi = nmemb; + while (lo < hi) { + size_t mid = (lo + hi) / 2; + const void *p = ((const char *)base) + mid * size; + int tmp = (*compar)(key, p); + if (tmp < 0) + hi = mid; + else if (tmp > 0) + lo = mid + 1; + else + return (void *)p; + } - for (i=0; i= 2 && fname[len-1] == '.' && fname[len-2] == '.' && - (len == 2 || fname[len-3] == '\\')) - { + (len == 2 || fname[len-3] == '\\')) { /* That's everything implemented so far */ memset(ss, 0, sizeof(struct stat)); ss->st_size = 1024; @@ -74,6 +96,7 @@ int stat(const char *fname, struct stat *ss) MultiByteToWideChar(CP_ACP, 0, fname, -1, fnameUnc, MAX_PATH); handle = FindFirstFile(fnameUnc, &wfd); + FindClose(handle); if (handle == INVALID_HANDLE_VALUE) return -1; else @@ -83,20 +106,16 @@ int stat(const char *fname, struct stat *ss) ss->st_size = wfd.nFileSizeLow; if (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ss->st_mode |= S_IFDIR; - - FindClose(handle); } return 0; } char cwd[MAX_PATH+1] = ""; -EXT_C char *getcwd(char *buffer, int maxlen) -{ +EXT_C char *getcwd(char *buffer, int maxlen) { TCHAR fileUnc[MAX_PATH+1]; char* plast; - if (cwd[0] == 0) - { + if (cwd[0] == 0) { GetModuleFileName(NULL, fileUnc, MAX_PATH); WideCharToMultiByte(CP_ACP, 0, fileUnc, -1, cwd, MAX_PATH, NULL, NULL); plast = strrchr(cwd, '\\'); @@ -114,8 +133,7 @@ EXT_C char *getcwd(char *buffer, int maxlen) #ifdef __GNUC__ #undef GetCurrentDirectory #endif -EXT_C void GetCurrentDirectory(int len, char *buf) -{ +EXT_C void GetCurrentDirectory(int len, char *buf) { getcwd(buf,len); }; @@ -125,26 +143,22 @@ fully qualified paths refer to root folder rather than current folder (concept not implemented in CE). */ #undef fopen -EXT_C FILE *wce_fopen(const char* fname, const char* fmode) -{ +EXT_C FILE *wce_fopen(const char* fname, const char* fmode) { char fullname[MAX_PATH+1]; if (!fname || fname[0] == '\0') return NULL; - if (fname[0] != '\\' && fname[0] != '/') - { + if (fname[0] != '\\' && fname[0] != '/') { getcwd(fullname, MAX_PATH); strncat(fullname, "\\", MAX_PATH-strlen(fullname)-1); strncat(fullname, fname, MAX_PATH-strlen(fullname)-strlen(fname)); return fopen(fullname, fmode); - } - else + } else return fopen(fname, fmode); } /* Remove file by name */ -int remove(const char* path) -{ +int remove(const char* path) { TCHAR pathUnc[MAX_PATH+1]; MultiByteToWideChar(CP_ACP, 0, path, -1, pathUnc, MAX_PATH); return !DeleteFile(pathUnc); @@ -158,14 +172,22 @@ int _access(const char *path, int mode) { WIN32_FIND_DATA ffd; HANDLE h=FindFirstFile(fname, &ffd); + FindClose(h); if (h == INVALID_HANDLE_VALUE) return -1; //Can't find file - FindClose(h); - if (ffd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) + if (ffd.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY) { + // WORKAROUND: WinCE (or the emulator) sometimes returns bogus direcotry + // hits for files that don't exist. Checking for the same fname twice + // seems to weed out those false positives. + HANDLE h=FindFirstFile(fname, &ffd); + FindClose(h); + if (h == INVALID_HANDLE_VALUE) + return -1; //Can't find file + return 0; //Always return success if target is directory and exists - + } switch (mode) { case 00: //Check existence return 0; @@ -183,8 +205,7 @@ int _access(const char *path, int mode) { #ifndef __GNUC__ /* Limited dirent implementation. Used by UI.C and DEVICES.C */ -DIR* opendir(const char* fname) -{ +DIR* opendir(const char* fname) { DIR* pdir; char fnameMask[MAX_PATH+1]; TCHAR fnameUnc[MAX_PATH+1]; @@ -209,13 +230,10 @@ DIR* opendir(const char* fname) strcpy(pdir->dd_name, fname); /* it has exactly enough space for fname and nul char */ MultiByteToWideChar(CP_ACP, 0, fnameMask, -1, fnameUnc, MAX_PATH); - if ((pdir->dd_handle = (long)FindFirstFile(fnameUnc, &wfd)) == (long)INVALID_HANDLE_VALUE) - { + if ((pdir->dd_handle = (long)FindFirstFile(fnameUnc, &wfd)) == (long)INVALID_HANDLE_VALUE) { free(pdir); return NULL; - } - else - { + } else { WideCharToMultiByte(CP_ACP, 0, wfd.cFileName, -1, nameFound, MAX_PATH, NULL, NULL); pdir->dd_dir.d_name = strdup(nameFound); @@ -224,34 +242,25 @@ DIR* opendir(const char* fname) return pdir; } -struct dirent* readdir(DIR* dir) -{ +struct dirent* readdir(DIR* dir) { char nameFound[MAX_PATH+1]; static struct dirent dummy; - if (dir->dd_stat == 0) - { + if (dir->dd_stat == 0) { dummy.d_name = "."; dummy.d_namlen = 1; dir->dd_stat ++; return &dummy; - } - else if (dir->dd_stat == 1) - { + } else if (dir->dd_stat == 1) { dummy.d_name = ".."; dummy.d_namlen = 2; dir->dd_stat ++; return &dummy; - } - else if (dir->dd_stat == 2) - { + } else if (dir->dd_stat == 2) { dir->dd_stat++; return &dir->dd_dir; - } - else - { - if (FindNextFile((HANDLE)dir->dd_handle, &wfd) == 0) - { + } else { + if (FindNextFile((HANDLE)dir->dd_handle, &wfd) == 0) { dir->dd_stat = -1; return NULL; } @@ -283,12 +292,6 @@ int closedir(DIR* dir) return 1; } -/* in our case unlink is the same as remove */ -int unlink(const char* path) -{ - return remove(path); -} - /* Make directory, Unix style */ void mkdir(char* dirname, int mode) { @@ -299,10 +302,8 @@ void mkdir(char* dirname, int mode) if (*path == '/') *path = '\\'; /* Run through the string and attempt creating all subdirs on the path */ - for (ptr = path+1; *ptr; ptr ++) - { - if (*ptr == '\\' || *ptr == '/') - { + for (ptr = path+1; *ptr; ptr ++) { + if (*ptr == '\\' || *ptr == '/') { *ptr = 0; MultiByteToWideChar(CP_ACP, 0, path, -1, pathUnc, MAX_PATH); CreateDirectory(pathUnc, 0); @@ -313,373 +314,40 @@ void mkdir(char* dirname, int mode) CreateDirectory(pathUnc, 0); } -/* Used in DEVICES.C and UI.C for some purpose. Not critical in this port */ -int system(const char* path) { return 0; } - -#if 0 - -char *tmpnam(char *string) -{ - TCHAR pTemp[MAX_PATH+1]; - static char buffer[MAX_PATH+1]; - GetTempFileName(TEXT("."), TEXT("A8_"), 0, pTemp); - WideCharToMultiByte(CP_ACP, 0, pTemp, -1, buffer, MAX_PATH, NULL, NULL); - - if (string) - { - strcpy(string, buffer); - return string; - } - else - return buffer; -} - -FILE *tmpfile() -{ - TCHAR pTemp[MAX_PATH+1]; - if (!GetTempFileName(TEXT("."), TEXT("A8_"), 0, pTemp)) - return _wfopen(pTemp, TEXT("w+b")); - else - return 0; -} - -#endif - -void rewind(FILE *stream) -{ - fseek(stream, 0, SEEK_SET); -} - - -#if _WIN32_WCE < 300 - -int isalnum(int c) { - return ((c >= 'A' && c <= 'Z') || - (c >= 'a' && c <= 'z') || - (c >= '0' && c <= '9')); -} - -char *_strdup(const char *strSource) -#else char *strdup(const char *strSource) -#endif { char* buffer; - buffer = (char*)malloc(strlen(strSource)+1); + size_z len = strlen(strSource)+1; + buffer = (char*)malloc(len); if (buffer) - strcpy(buffer, strSource); + memcpy(buffer, strSource, len); return buffer; } -/* Very limited implementation of sys/time.h */ -void usleep(long usec) -{ - long msec = usec/1000; - if (msec <= 0) - Sleep(0); - else - Sleep(msec); -} - -/* This may provide for better sync mechanism */ -unsigned int clock() -{ - return GetTickCount(); -} - -/* And why do people use this? */ -#if _WIN32_WCE >= 300 -void abort() -{ - exit(1); -} -#endif - -/* -IMHO, no project should use this one, it is not portable at all. This implementation -at least allows some projects to work. -*/ -char* getenv(char* name) -{ - static char buffer[MAX_PATH+1]; - if (strcmp(name, "HOME") == 0 || strcmp(name, "HOMEDIR") == 0) - { - getcwd(buffer, MAX_PATH); - return buffer; - } - else - return ""; -} - -#if _WIN32_WCE < 300 || defined(_TEST_HPC_STDIO) - -void *calloc(size_t n, size_t s) { - void *result = malloc(n * s); - if (result) - memset(result, 0, n * s); - - return result; -} - -char *strpbrk(const char *s, const char *accept) { - int i; - - if (!s || !accept) - return NULL; - - for (i=0; i='0' && c <= '9'); -} - -int isprint(int c) { - return (c >= ' ' && c <= '~'); -} - -int isspace(int c) { - return (c == ' '); -} - -#endif - -#ifndef WIN32_PLATFORM_HPCPRO - - -int printf(const char *format, ...) { - // useless anyway :) - return 0; -} - -FILE *fopen(const char *path, const char *mode) { - TCHAR tempo[MAX_PATH]; - HANDLE result; - bool writeAccess = (mode[0] == 'W' || mode[0] == 'w'); - - MultiByteToWideChar(CP_ACP, 0, path, strlen(path) + 1, tempo, sizeof(tempo)); - - result = CreateFile(tempo, ( writeAccess ? GENERIC_WRITE : GENERIC_READ), 0, NULL, (writeAccess ? CREATE_ALWAYS : OPEN_EXISTING), FILE_ATTRIBUTE_NORMAL, NULL); - if (result == INVALID_HANDLE_VALUE) - return NULL; - else - return (FILE*)result; -} - -FILE * _wfopen(const TCHAR *path, const TCHAR *mode) { - HANDLE result; - bool writeAccess = (mode[0] == 'W' || mode[0] == 'w'); - result = CreateFile(path, ( writeAccess ? GENERIC_WRITE : GENERIC_READ), 0, NULL, (writeAccess ? CREATE_ALWAYS : OPEN_EXISTING), FILE_ATTRIBUTE_NORMAL, NULL); - if (result == INVALID_HANDLE_VALUE) - return NULL; - else - return (FILE*)result; -} - -FILE *_wfreopen(const TCHAR *path, const TCHAR *mode, FILE *stream) { - fclose(stream); - stream = _wfopen(path, mode); - return stream; -} - -int fclose(FILE *stream) { - CloseHandle((HANDLE)stream); - return 1; -} - -int fseek(FILE *stream, long offset, int whence) { - SetFilePointer((HANDLE)stream, offset, NULL, (whence == SEEK_CUR ? FILE_CURRENT : whence == SEEK_END ? FILE_END : FILE_BEGIN)); - return 0; -} - -long ftell(FILE *stream) { - return (SetFilePointer((HANDLE)stream, 0, NULL, FILE_CURRENT)); -} - -size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream) { - DWORD sizeWritten; - - WriteFile((HANDLE)stream, ptr, size * nmemb, &sizeWritten, NULL); - - if (size != 0) - return sizeWritten / size; - else - return 0; -} - -size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream) { - DWORD sizeRead; - - ReadFile((HANDLE)stream, ptr, size * nmemb, &sizeRead, NULL); - - if (size != 0) - return sizeRead / size; - else - return 0; -} - -int fgetc(FILE *stream) { - unsigned char c; - if (fread(&c, 1, 1, stream) != 1) - return -1; - else - return c; -} - -char *fgets(char *s, int size, FILE *stream) { - int i = 0; - char tempo[1]; - - memset(s, 0, size); - while (fread(tempo, 1, 1, stream)) { - //if (tempo[0] == '\r') - // break; - if (tempo[0] == '\r') - continue; - s[i++] = tempo[0]; - if (tempo[0] == '\n') - break; - if (i == size) - break; - } - if (!i) - return NULL; - else - return s; -} - -int feof(FILE *stream) { - DWORD fileSize; - DWORD filePos; - fileSize = GetFileSize((HANDLE)stream, NULL); - filePos = SetFilePointer((HANDLE)stream, 0, 0, FILE_CURRENT); - return (filePos == 0xFFFFFFFF || filePos > (fileSize - 1)); -} - -int ferror(FILE *stream) { - return 0; // FIXME ! -} - -int fprintf(FILE *stream, const char *format, ...) { - char buf[1024]; - va_list va; - - va_start(va, format); - vsnprintf(buf, 1024, format, va); - va_end(va); - - if (buf[strlen(buf) - 1] == '\n') { - int i = strlen(buf) - 1; - buf[i] = '\r'; - buf[i + 1] = '\n'; - buf[i + 2] = 0; - } - - return fwrite(buf, 1, strlen(buf), stream); -} - -FILE* _getstdfilex(int) { - return NULL; -} - -void clearerr(FILE *stream) { -} - -int fflush(FILE *stream) { - return 0; -} - -#endif - -int stricmp( const char *string1, const char *string2 ) { - char src[4096]; - char dest[4096]; - int i; - - for (i=0; i= 'A' && string1[i] <= 'Z') - src[i] = string1[i] + 32; - else - src[i] = string1[i]; - src[i] = 0; - - for (i=0; i= 'A' && string2[i] <= 'Z') - dest[i] = string2[i] + 32; - else - dest[i] = string2[i]; - dest[i] = 0; - - return strcmp(src, dest); -} - -char *strrchr(const char *s, int c) { - int i; - - for (i = strlen(s) - 1; i > 0; i--) - if (s[i] == c) - return (char*)(s + i); - - return NULL; -} - -long int strtol(const char *nptr, char **endptr, int base) { - // not correct but that's all we are using - - long int result; - sscanf(nptr, "%ld", &result); - return result; -} - - -#endif - - // gcc build only functions follow #else // defined(__GNUC__) #ifndef __MINGW32CE__ -int islower(int c) -{ +int islower(int c) { return (c>='a' && c<='z'); } -int isspace(int c) -{ +int isspace(int c) { return (c==' ' || c=='\f' || c=='\n' || c=='\r' || c=='\t' || c=='\v'); } -int isalpha(int c) -{ - return (islower(c) || (c>='A' && c<='Z')); +int isalpha(int c) { + return ((c>='a' && c<='z') || (c>='A' && c<='Z')); } -int isalnum(int c) -{ - return (isalpha(c) || (c>='0' && c<='9')); +int isalnum(int c) { + return ((c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9')); } -int isprint(int c) -{ - static char punct[] = "!\"#%&'();<=>?[\\]*+,-./:^_{|}~"; - int i = 0, flag = 0; - while ((punct[i] != 0) && (flag = (punct[i] != c))) - i++; - return (isalnum(c) || flag); -} - -extern "C" int atexit(void (*function)(void)) -{ - return 0; +int isprint(int c) { + //static const char punct[] = "!\"#%&'();<=>?[\\]*+,-./:^_{|}~"; + //return (isalnum(c) || strchr(punct, c)); + return (32 <= c && c <= 126); // based on BSD manpage } #endif diff --git a/backends/platform/wince/wince-sdl.cpp b/backends/platform/wince/wince-sdl.cpp index 8cf5fac2796..3f16b4fdd16 100644 --- a/backends/platform/wince/wince-sdl.cpp +++ b/backends/platform/wince/wince-sdl.cpp @@ -35,7 +35,7 @@ #include "base/main.h" #include "base/plugins.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include "sound/fmopl.h" #include "backends/timer/default/default-timer.h" @@ -94,7 +94,6 @@ static char stdout_fname[MAX_PATH], stderr_fname[MAX_PATH]; // Static member inits typedef void (*SoundProc)(void *param, byte *buf, int len); bool OSystem_WINCE3::_soundMaster = true; -SoundProc OSystem_WINCE3::_originalSoundProc = NULL; bool _isSmartphone = false; bool _hasSmartphoneResolution = false; @@ -403,9 +402,8 @@ void OSystem_WINCE3::initBackend() { // Instantiate our own sound mixer // mixer init is postponed until a game engine is selected. - if (_mixer == 0) { - _mixer = new Audio::Mixer(); - } + if (_mixer == 0) + _mixer = new Audio::MixerImpl(this); // Create the timer. CE SDL does not support multiple timers (SDL_AddTimer). // We work around this by using the SetTimer function, since we only use @@ -770,7 +768,7 @@ void OSystem_WINCE3::create_toolbar() { _toolbarHandler.setVisible(false); } -bool OSystem_WINCE3::setSoundCallback(SoundProc proc, void *param) { +void OSystem_WINCE3::setupMixer() { SDL_AudioSpec desired; int thread_priority; @@ -779,18 +777,20 @@ bool OSystem_WINCE3::setSoundCallback(SoundProc proc, void *param) { memset(&desired, 0, sizeof(desired)); - _originalSoundProc = proc; desired.freq = _sampleRate; desired.format = AUDIO_S16SYS; desired.channels = 2; desired.samples = 128; desired.callback = private_sound_proc; - desired.userdata = param; + desired.userdata = this; + + // Create the mixer instance + if (_mixer == 0) + _mixer = new Audio::MixerImpl(this); // Add sound thread priority - if (!ConfMan.hasKey("sound_thread_priority")) { + if (!ConfMan.hasKey("sound_thread_priority")) thread_priority = THREAD_PRIORITY_NORMAL; - } else thread_priority = ConfMan.getInt("sound_thread_priority"); @@ -799,16 +799,26 @@ bool OSystem_WINCE3::setSoundCallback(SoundProc proc, void *param) { SDL_CloseAudio(); if (SDL_OpenAudio(&desired, NULL) != 0) { warning("Could not open audio device: %s", SDL_GetError()); - return false; - } - else + _mixer->setReady(false); + + } else { debug(1, "Sound opened OK, mixing at %d Hz", _sampleRate); - SDL_PauseAudio(0); - return true; + + // Re-create mixer to match the output rate + delete(_mixer); + _mixer = new Audio::MixerImpl(this); + _mixer->setOutputRate(_sampleRate); + _mixer->setReady(true); + SDL_PauseAudio(0); + } } void OSystem_WINCE3::private_sound_proc(void *param, byte *buf, int len) { - (*_originalSoundProc)(param, buf, len); + OSystem_WINCE3 *this_ = (OSystem_WINCE3 *)param; + assert(this_); + + if (this_->_mixer) + this_->_mixer->mixCallback(buf, len); if (!_soundMaster) memset(buf, 0, len); } @@ -838,7 +848,7 @@ bool OSystem_WINCE3::checkOggHighSampleRate() { } #endif -void OSystem_WINCE3::get_sample_rate() { +void OSystem_WINCE3::compute_sample_rate() { // Force at least medium quality FM synthesis for FOTAQ Common::String gameid(ConfMan.get("gameid")); if (gameid == "queen") { @@ -875,9 +885,8 @@ void OSystem_WINCE3::setWindowCaption(const char *caption) { //update_game_settings(); // finalize mixer init - get_sample_rate(); - bool result = setSoundCallback(Audio::Mixer::mixCallback, _mixer); - _mixer->setReady(result); + compute_sample_rate(); + setupMixer(); // handle the actual event OSystem_SDL::setWindowCaption(caption); @@ -1050,7 +1059,7 @@ void OSystem_WINCE3::update_game_settings() { } } - get_sample_rate(); + compute_sample_rate(); } void OSystem_WINCE3::initSize(uint w, uint h) { diff --git a/backends/platform/wince/wince-sdl.h b/backends/platform/wince/wince-sdl.h index daa7e832f66..8853c156d87 100644 --- a/backends/platform/wince/wince-sdl.h +++ b/backends/platform/wince/wince-sdl.h @@ -82,7 +82,7 @@ public: // Overloaded from SDL_Commmon void quit(); // Overloaded from SDL_Commmon (master volume and sample rate subtleties) - bool setSoundCallback(SoundProc proc, void *param); + void setupMixer(); // Overloaded from OSystem //void engineInit(); void getTimeAndDate(struct tm &t) const; @@ -160,13 +160,12 @@ private: #endif static void private_sound_proc(void *param, byte *buf, int len); - static SoundProc _originalSoundProc; bool update_scalers(); void create_toolbar(); void update_game_settings(); void check_mappings(); - void get_sample_rate(); + void compute_sample_rate(); void retrieve_mouse_location(int &x, int &y); diff --git a/backends/platform/x11/module.mk b/backends/platform/x11/module.mk deleted file mode 100644 index 22015b53be4..00000000000 --- a/backends/platform/x11/module.mk +++ /dev/null @@ -1,10 +0,0 @@ -MODULE := backends/platform/x11 - -MODULE_OBJS := \ - x11.o - -MODULE_DIRS += \ - backends/platform/x11/ - -# We don't use the rules.mk here on purpose -OBJS := $(addprefix $(MODULE)/, $(MODULE_OBJS)) $(OBJS) diff --git a/backends/platform/x11/x11.cpp b/backends/platform/x11/x11.cpp deleted file mode 100644 index 329d9943353..00000000000 --- a/backends/platform/x11/x11.cpp +++ /dev/null @@ -1,1050 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -/* The bare pure X11 port done by Lionel 'BBrox' Ulmer */ - -#include "common/scummsys.h" -#include "common/events.h" -#include "common/system.h" -#include "common/util.h" -#include "base/main.h" -#include "backends/platform/x11/x11.h" -#include "backends/plugins/posix/posix-provider.h" - -#include -#include - -#include -#include - -#include -#include - -#include -#include -#include - -#ifdef __linux__ -#include -#else -#include -#endif - -#include -#include - -#include -#include -#include -#include - - -//#define SAMPLES_PER_SEC 11025 -#define SAMPLES_PER_SEC 22050 -//#define SAMPLES_PER_SEC 44100 - - -int main(int argc, char *argv[]) { - g_system = OSystem_X11::create(0, 0); - assert(g_system); - -#ifdef DYNAMIC_MODULES - PluginManager::instance().addPluginProvider(new POSIXPluginProvider()); -#endif - - // Invoke the actual ScummVM main entry point: - int res = scummvm_main(argc, argv); - g_system->quit(); // TODO: Consider removing / replacing this! - return res; -} - -OSystem *OSystem_X11::create(int gfx_mode, bool full_screen) { - OSystem_X11 *syst = new OSystem_X11(); - return syst; -} - -OSystem_X11::OSystem_X11() { - /* Some members initialization */ - _fake_right_mouse = 0; - _report_presses = 1; - _current_shake_pos = 0; - _new_shake_pos = 0; - _palette_changed = false; - _num_of_dirty_rects = 0; - _overlay_visible = false; - _mouse_state_changed = true; - _mouse_visible = true; - _ms_buf = NULL; - _curMouseState.x = 0; - _curMouseState.y = 0; - _curMouseState.hot_x = 0; - _curMouseState.hot_y = 0; - _curMouseState.w = 0; - _curMouseState.h = 0; - _palette16 = 0; - _palette32 = 0; - _bytesPerPixel = 0; - _image = 0; - _local_fb = 0; - _local_fb_overlay = 0; -} - -OSystem_X11::~OSystem_X11() { - XFree(_image); - if (_palette16) - free(_palette16); - - if (_palette32) - free(_palette32); - - if (_ms_buf) - free(_ms_buf); - - free(_local_fb_overlay); - free(_local_fb); -} - -void OSystem_X11::initBackend() { - char buf[512]; - XWMHints *wm_hints; - XGCValues values; - XTextProperty window_name; - char *name = (char *)&buf; - /* For the_window title */ - sprintf(buf, "ScummVM"); - - _display = XOpenDisplay(NULL); - if (_display == NULL) { - error("Could not open display !\n"); - exit(1); - } - - if (XShmQueryExtension(_display)!=True) - error("No Shared Memory Extension present"); - - _screen = DefaultScreen(_display); - _depth = DefaultDepth(_display, _screen); - switch (_depth) { - case 16 : - _bytesPerPixel = 2; - break; - case 24 : - case 32 : - _bytesPerPixel = 4; - break; - } - - if (!_bytesPerPixel) - error("Your screen depth is %ibit. Values other than 16, 24 and 32bit are currently not supported", _depth); - - _window_width = 320; - _window_height = 200; - _scumm_x = 0; - _scumm_y = 0; - _window = XCreateSimpleWindow(_display, XRootWindow(_display, _screen), 0, 0, 320, 200, 0, 0, 0); - wm_hints = XAllocWMHints(); - if (wm_hints == NULL) { - error("Not enough memory to allocate Hints !\n"); - exit(1); - } - wm_hints->flags = InputHint | StateHint; - wm_hints->input = True; - wm_hints->initial_state = NormalState; - XStringListToTextProperty(&name, 1, &window_name); - XSetWMProperties(_display, _window, &window_name, &window_name, - NULL /* argv */ , 0 /* argc */ , NULL /* size hints */ , - wm_hints, NULL /* class hints */ ); - XFree(wm_hints); - - XSelectInput(_display, _window, - ExposureMask | KeyPressMask | KeyReleaseMask | - PointerMotionMask | ButtonPressMask | ButtonReleaseMask | StructureNotifyMask); - - values.foreground = BlackPixel(_display, _screen); - _black_gc = XCreateGC(_display, _window, GCForeground, &values); - - XMapWindow(_display, _window); - XFlush(_display); - - _fb_width = 0; - _fb_height = 0; - - if (!_palette16) - _palette16 = (uint16 *)calloc(256, sizeof(uint16)); - if (!_palette32 && _bytesPerPixel == 4) - _palette32 = (uint32 *)calloc(256, sizeof(uint32)); - - while (1) { - XEvent event; - XNextEvent(_display, &event); - switch (event.type) { - case Expose: - goto out_of_loop; - } - } -out_of_loop: - create_empty_cursor(); - - /* Initialize the timer routines */ - _timer_active = false; - - /* And finally start the local timer */ - gettimeofday(&_start_time, NULL); - - OSystem::initBackend(); -} - -#undef CAPTURE_SOUND -#define FRAG_SIZE 4096 - -static void *sound_and_music_thread(void *params) { - /* Init sound */ - int sound_fd, param, frag_size; - uint8 sound_buffer[FRAG_SIZE]; - OSystem::SoundProc sound_proc = ((THREAD_PARAM *)params)->sound_proc; - void *proc_param = ((THREAD_PARAM *)params)->param; - -#ifdef CAPTURE_SOUND - FILE *f = fopen("sound.raw", "wb"); -#endif - - sound_fd = open("/dev/dsp", O_WRONLY); - audio_buf_info info; - if (sound_fd < 0) { - warning("Error opening sound device!\n"); - return NULL; - } - param = 0; - frag_size = FRAG_SIZE /* audio fragment size */ ; - while (frag_size) { - frag_size >>= 1; - param++; - } - param--; - param |= /* audio_fragment_num */ 3 << 16; - if (ioctl(sound_fd, SNDCTL_DSP_SETFRAGMENT, ¶m) != 0) { - warning("Error in the SNDCTL_DSP_SETFRAGMENT ioctl!\n"); - return NULL; - } - param = AFMT_S16_LE; - if (ioctl(sound_fd, SNDCTL_DSP_SETFMT, ¶m) == -1) { - warning("Error in the SNDCTL_DSP_SETFMT ioctl!\n"); - return NULL; - } - if (param != AFMT_S16_LE) { - warning("AFMT_S16_LE not supported!\n"); - return NULL; - } - param = 2; - if (ioctl(sound_fd, SNDCTL_DSP_CHANNELS, ¶m) == -1) { - warning("Error in the SNDCTL_DSP_CHANNELS ioctl!\n"); - return NULL; - } - if (param != 2) { - warning("Stereo mode not supported!\n"); - return NULL; - } - param = SAMPLES_PER_SEC; - if (ioctl(sound_fd, SNDCTL_DSP_SPEED, ¶m) == -1) { - warning("Error in the SNDCTL_DSP_SPEED ioctl!\n"); - return NULL; - } - if (param != SAMPLES_PER_SEC) { - warning("%d kHz not supported!\n", SAMPLES_PER_SEC); - return NULL; - } - if (ioctl(sound_fd, SNDCTL_DSP_GETOSPACE, &info) != 0) { - warning("SNDCTL_DSP_GETOSPACE"); - return NULL; - } - - sched_yield(); - while (1) { - uint8 *buf = (uint8 *)sound_buffer; - int size, written; - - sound_proc(proc_param, (byte *)sound_buffer, FRAG_SIZE); -#ifdef CAPTURE_SOUND - fwrite(buf, 2, FRAG_SIZE >> 1, f); - fflush(f); -#endif - size = FRAG_SIZE; - while (size > 0) { - written = write(sound_fd, buf, size); - buf += written; - size -= written; - } - } - - return NULL; -} - -/* Function used to hide the mouse cursor */ -void OSystem_X11::create_empty_cursor() { - XColor bg; - Pixmap pixmapBits; - Cursor cursor = None; - static const char data[] = { 0 }; - - bg.red = bg.green = bg.blue = 0x0000; - pixmapBits = XCreateBitmapFromData(_display, XRootWindow(_display, _screen), data, 1, 1); - if (pixmapBits) { - cursor = XCreatePixmapCursor(_display, pixmapBits, pixmapBits, &bg, &bg, 0, 0); - XFreePixmap(_display, pixmapBits); - } - XDefineCursor(_display, _window, cursor); -} - -bool OSystem_X11::hasFeature(Feature f) { - return false; -} - -void OSystem_X11::setFeatureState(Feature f, bool enable) { -} - -bool OSystem_X11::getFeatureState(Feature f) { - return false; -} - -const OSystem::GraphicsMode *OSystem_X11::getSupportedGraphicsModes() const { - static const OSystem::GraphicsMode mode = {"1x", "Normal mode", 0}; - return &mode; -} - -int OSystem_X11::getDefaultGraphicsMode() const { - return 0; -} - -bool OSystem_X11::setGraphicsMode(int mode) { - return (mode == 0); -} - -int OSystem_X11::getGraphicsMode() const { - return 0; -} - - -uint32 OSystem_X11::getMillis() { - struct timeval current_time; - gettimeofday(¤t_time, NULL); - return (uint32)(((current_time.tv_sec - _start_time.tv_sec) * 1000) + - ((current_time.tv_usec - _start_time.tv_usec) / 1000)); -} - -void OSystem_X11::initSize(uint w, uint h) { - //debug("initSize(%i, %i)", w, h); - static XShmSegmentInfo shminfo; - - if (((uint)_fb_width != w) || ((uint)_fb_height != w)) { - _fb_width = w; - _fb_height = h; - - /* We need to change the size of the X11_window */ - XWindowChanges new_values; - - new_values.width = _fb_width; - new_values.height = _fb_height; - - XConfigureWindow(_display,_window, CWWidth | CWHeight, &new_values); - - if (_image) - XFree(_image); - _image = XShmCreateImage(_display, DefaultVisual(_display, _screen), _depth, ZPixmap, NULL, &shminfo,_fb_width,_fb_height); - if (!_image) - error("Couldn't get image by XShmCreateImage()"); - - shminfo.shmid = shmget(IPC_PRIVATE, _image->bytes_per_line * _image->height, IPC_CREAT | 0700); - if (shminfo.shmid < 0) - error("Couldn't allocate image data by shmget()"); - - _image->data = shminfo.shmaddr = (char *)shmat(shminfo.shmid, 0, 0); - shminfo.readOnly = False; - if (XShmAttach(_display, &shminfo) == 0) { - error("Could not attach shared memory segment !\n"); - exit(1); - } - shmctl(shminfo.shmid, IPC_RMID, 0); - - if (_local_fb) - free(_local_fb); - if (_local_fb_overlay) - free(_local_fb_overlay); - /* Initialize the 'local' frame buffer and the palette */ - _local_fb = (uint8 *)calloc(_fb_width * _fb_height, sizeof(uint8)); - _local_fb_overlay = (uint16 *)calloc(_fb_width * _fb_height, sizeof(uint16)); - - } -} - -bool OSystem_X11::setSoundCallback(SoundProc proc, void *param) { - static THREAD_PARAM thread_param; - - /* And finally start the music thread */ - thread_param.param = param; - thread_param.sound_proc = proc; - - pthread_create(&_sound_thread, NULL, sound_and_music_thread, (void *)&thread_param); - - return true; -} - -void OSystem_X11::clearSoundCallback() { - // TODO implement this... - // The sound_thread has to be stopped in a nice way. In particular, - // using pthread_kill would be a bad idea. Rather, use pthread_cancel, - // or maybe a global variable, to achieve this. - // This method shouldn't return until the sound thread really has stopped. -} - - -void OSystem_X11::setPalette(const byte *colors, uint start, uint num) { - uint16 *pal = &(_palette16[start]); - const byte *data = colors; - - if (_bytesPerPixel == 4) { - for (uint i = start; i < start+num; i++) { - //_palette32[i] = ((uint32 *)colors)[i]; - _palette32[i] = (colors[i * 4 + 0] << 16) | (colors[i * 4 + 1] << 8) | (colors[i * 4 + 2] << 0); - } - } - - do { - *pal++ = ((data[0] & 0xF8) << 8) | ((data[1] & 0xFC) << 3) | (data[2] >> 3); - data += 4; - num--; - } while (num > 0); - - _palette_changed = true; -} - -#define AddDirtyRec(xi,yi,wi,hi) \ - if (_num_of_dirty_rects < MAX_NUMBER_OF_DIRTY_RECTS) { \ - _ds[_num_of_dirty_rects].x = xi; \ - _ds[_num_of_dirty_rects].y = yi; \ - _ds[_num_of_dirty_rects].w = wi; \ - _ds[_num_of_dirty_rects].h = hi; \ - _num_of_dirty_rects++; \ - } - -void OSystem_X11::copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h) { - uint8 *dst; - - if (y < 0) { - h += y; - buf -= y * pitch; - y = 0; - } - if (h > (_fb_height - y)) { - h = _fb_height - y; - } - - dst = _local_fb + _fb_width * y + x; - - if (h <= 0) - return; - - AddDirtyRec(x, y, w, h); - while (h-- > 0) { - memcpy(dst, buf, w); - dst +=_fb_width; - buf += pitch; - } -} - -void OSystem_X11::blit(const DirtyRect *d, uint16 *dst, int pitch) { - uint8 *ptr_src = _local_fb + (_fb_width * d->y) + d->x; - uint16 *ptr_dst = dst + ((_fb_width * d->y) + d->x); - int x, y; - - for (y = 0; y < d->h; y++) { - for (x = 0; x < d->w; x++) { - *ptr_dst++ = _palette16[*ptr_src++]; - } - ptr_dst += pitch - d->w; - ptr_src +=_fb_width - d->w; - } -} - -void OSystem_X11::blit_convert(const DirtyRect *d, uint8 *dst, int pitch) { - uint8 *ptr_src = _local_fb + (_fb_width * d->y) + d->x; - uint8 *ptr_dst = dst + ((_fb_width * d->y) + d->x) * _bytesPerPixel; - int x, y; - - switch (_bytesPerPixel) { - case 2: - for (y = 0; y < d->h; y++) { - for (x = 0; x < d->w; x++) { - *ptr_dst = _palette16[*ptr_src++]; - ptr_dst += _bytesPerPixel; - } - ptr_dst += (pitch - d->w) * _bytesPerPixel; - ptr_src +=_fb_width - d->w; - } - break; - case 4: - for (y = 0; y < d->h; y++) { - for (x = 0; x < d->w; x++) { - *(uint32 *)ptr_dst = _palette32[*ptr_src]; - ptr_dst += _bytesPerPixel; - ptr_src++; - } - ptr_dst += (pitch - d->w) * _bytesPerPixel; - ptr_src += _fb_width - d->w; - } - } -} - -void OSystem_X11::updateScreen_helper(const DirtyRect *d, DirtyRect *dout) { - - if (_overlay_visible == false) { - blit_convert(d, (uint8 *)_image->data, _fb_width); - } else { - uint16 *ptr_src = _local_fb_overlay + (_fb_width * d->y) + d->x; - uint8 *ptr_dst = (uint8 *)_image->data + ((_fb_width * d->y) + d->x) * _bytesPerPixel; - - int y; - - switch (_bytesPerPixel) { - case 2: - for (y = 0; y < d->h; y++) { - memcpy(ptr_dst, ptr_src, d->w * sizeof(uint16)); - ptr_dst += _fb_width * sizeof(uint16); - ptr_src += _fb_width; - } - break; - case 4: - uint16 currLine, x; - register uint16 currPixel; - for (y = d->y; y < d->y + d->h; y++) { - currLine = y * _fb_width; - for (x = d->x; x < d->x + d->w; x++) { - currPixel = _local_fb_overlay[(currLine + x)]; - *(uint32 *)ptr_dst = ((currPixel & 0xF800) << 8) + ((currPixel & 0x07E0) << 5) + - ((currPixel & 0x001F) << 3); - ptr_dst += sizeof(uint32); - } - ptr_dst += (_fb_width - d->w) * _bytesPerPixel; - } - - } - } - if (d->x < dout->x) - dout->x = d->x; - if (d->y < dout->y) - dout->y = d->y; - if ((d->x + d->w) > dout->w) - dout->w = d->x + d->w; - if ((d->y + d->h) > dout->h) - dout->h = d->y + d->h; -} - -void OSystem_X11::updateScreen() { - bool full_redraw = false; - bool need_redraw = false; - static const DirtyRect ds_full = { 0, 0, _fb_width, _fb_height }; - DirtyRect dout = {_fb_width, _fb_height, 0, 0 }; - - if (_palette_changed) { - full_redraw = true; - _num_of_dirty_rects = 0; - _palette_changed = false; - } else if (_num_of_dirty_rects >= MAX_NUMBER_OF_DIRTY_RECTS) { - full_redraw = true; - _num_of_dirty_rects = 0; - } - - if (full_redraw) { - updateScreen_helper(&ds_full, &dout); - need_redraw = true; - } else if ((_num_of_dirty_rects > 0) || (_mouse_state_changed == true)) { - need_redraw = true; - while (_num_of_dirty_rects > 0) { - _num_of_dirty_rects--; - updateScreen_helper(&(_ds[_num_of_dirty_rects]), &dout); - } - } - - /* Then 'overlay' the mouse on the image */ - draw_mouse(&dout); - - if (_current_shake_pos != _new_shake_pos) { - /* Redraw first the 'black borders' in case of resize */ - if (_current_shake_pos < _new_shake_pos) - XFillRectangle(_display,_window, _black_gc, 0, _current_shake_pos, _window_width, _new_shake_pos); - else - XFillRectangle(_display,_window, _black_gc, 0, _window_height - _current_shake_pos, - _window_width,_window_height - _new_shake_pos); - XShmPutImage(_display, _window, DefaultGC(_display, _screen), _image, - 0, 0, _scumm_x, _scumm_y + _new_shake_pos, _fb_width, _fb_height, 0); - _current_shake_pos = _new_shake_pos; - } else if (need_redraw == true) { - XShmPutImage(_display, _window, DefaultGC(_display, _screen), _image, - dout.x, dout.y, _scumm_x + dout.x, _scumm_y + dout.y + _current_shake_pos, - dout.w - dout.x, dout.h - dout.y, 0); - XFlush(_display); - } -} - -bool OSystem_X11::showMouse(bool visible) -{ - if (_mouse_visible == visible) - return visible; - - bool last = _mouse_visible; - _mouse_visible = visible; - - if ((visible == false) && (_mouse_state_changed == false)) { - undraw_mouse(); - } - _mouse_state_changed = true; - - return last; -} - -void OSystem_X11::quit() { - exit(0); -} - -void OSystem_X11::setWindowCaption(const char *caption) { - //debug("setWindowCaption('%s')", caption); -} - -void OSystem_X11::undraw_mouse() { - AddDirtyRec(_oldMouseState.x, _oldMouseState.y, _oldMouseState.w, _oldMouseState.h); -} - -void OSystem_X11::draw_mouse(DirtyRect *dout) { - //debug("draw_mouse()"); - _mouse_state_changed = false; - - if (_mouse_visible == false) - return; - - int xdraw = _curMouseState.x - _curMouseState.hot_x; - int ydraw = _curMouseState.y - _curMouseState.hot_y; - int w = _curMouseState.w; - int h = _curMouseState.h; - int real_w; - int real_h; - - uint8 *dst; - const byte *buf = _ms_buf; - - if (ydraw < 0) { - real_h = h + ydraw; - buf += (-ydraw) * w; - ydraw = 0; - } else { - real_h = (ydraw + h) > _fb_height ? (_fb_height - ydraw) : h; - } - if (xdraw < 0) { - real_w = w + xdraw; - buf += (-xdraw); - xdraw = 0; - } else { - real_w = (xdraw + w) > _fb_width ? (_fb_width - xdraw) : w; - } - - dst = (uint8 *)_image->data + ((ydraw *_fb_width) + xdraw) * _bytesPerPixel; - - if ((real_h == 0) || (real_w == 0)) { - return; - } - - - if (xdraw < dout->x) - dout->x = xdraw; - if (ydraw < dout->y) - dout->y = ydraw; - if ((xdraw + real_w) > dout->w) - dout->w = xdraw + real_w; - if ((ydraw + real_h) > dout->h) - dout->h = ydraw + real_h; - - _oldMouseState.x = xdraw; - _oldMouseState.y = ydraw; - _oldMouseState.w = real_w; - _oldMouseState.h = real_h; - - while (real_h > 0) { - int width = real_w; - while (width > 0) { - byte color = *buf; - if (color != _mouseKeycolor) { - if (_depth == 16) - *(uint16 *)dst = _palette16[color]; - else { - *(uint32 *)dst = _palette32[color]; - } - } - buf++; - dst += _bytesPerPixel; - width--; - } - buf += w - real_w; - dst += (_fb_width - real_w) * _bytesPerPixel; - real_h--; - } -} - -void OSystem_X11::set_mouse_pos(int x, int y) { - if ((x != _curMouseState.x) || (y != _curMouseState.y)) { - _curMouseState.x = x; - _curMouseState.y = y; - if (_mouse_state_changed == false) { - undraw_mouse(); - } - _mouse_state_changed = true; - } -} - -void OSystem_X11::warpMouse(int x, int y) { - set_mouse_pos(x, y); -} - -void OSystem_X11::setMouseCursor(const byte *buf, uint w, uint h, int hotspot_x, int hotspot_y, byte keycolor, int cursorTargetScale) { - _curMouseState.w = w; - _curMouseState.h = h; - _curMouseState.hot_x = hotspot_x; - _curMouseState.hot_y = hotspot_y; - - if (_ms_buf) - free(_ms_buf); - _ms_buf = (byte *) malloc(w * h); - memcpy(_ms_buf, buf, w * h); - - if (_mouse_state_changed == false) { - undraw_mouse(); - } - _mouseKeycolor = keycolor; - _mouse_state_changed = true; -} - -void OSystem_X11::setShakePos(int shake_pos) { - if (_new_shake_pos != shake_pos) { - if (_mouse_state_changed == false) { - undraw_mouse(); - } - _mouse_state_changed = true; - } - _new_shake_pos = shake_pos; -} - -int OSystem_X11::getOutputSampleRate() const { - return SAMPLES_PER_SEC; -} - -void OSystem_X11::delayMillis(uint msecs) { - usleep(msecs * 1000); -} - -bool OSystem_X11::pollEvent(Common::Event &scumm_event) { - /* First, handle timers */ - uint32 current_msecs = getMillis(); - - if (_timer_active && (current_msecs >= _timer_next_expiry)) { - _timer_duration = _timer_callback(_timer_duration); - _timer_next_expiry = current_msecs + _timer_duration; - } - - while (XPending(_display)) { - XEvent event; - - XNextEvent(_display, &event); - switch (event.type) { - case Expose:{ - int real_w, real_h; - int real_x, real_y; - real_x = event.xexpose.x; - real_y = event.xexpose.y; - real_w = event.xexpose.width; - real_h = event.xexpose.height; - if (real_x < _scumm_x) { - real_w -= _scumm_x - real_x; - real_x = 0; - } else { - real_x -= _scumm_x; - } - if (real_y < _scumm_y) { - real_h -= _scumm_y - real_y; - real_y = 0; - } else { - real_y -= _scumm_y; - } - if ((real_h <= 0) || (real_w <= 0)) - break; - if ((real_x >=_fb_width) || (real_y >=_fb_height)) - break; - - if ((real_x + real_w) >=_fb_width) { - real_w =_fb_width - real_x; - } - if ((real_y + real_h) >=_fb_height) { - real_h =_fb_height - real_y; - } - - /* Compute the intersection of the expose event with the real ScummVM display zone */ - AddDirtyRec(real_x, real_y, real_w, real_h); - } - break; - - case KeyPress:{ - /* I am using keycodes here and NOT keysyms to be sure that even if the user - remaps his iPAQ's keyboard, it will still work. - */ - int keycode = -1; - int ascii = -1; - byte mode = 0; - - if (event.xkey.state & 0x01) - mode |= Common::KBD_SHIFT; - if (event.xkey.state & 0x04) - mode |= Common::KBD_CTRL; - if (event.xkey.state & 0x08) - mode |= Common::KBD_ALT; - switch (event.xkey.keycode) { - - case 9: /* Escape on my PC */ - case 130: /* Calendar on the iPAQ */ - keycode = 27; - break; - - case 71: /* F5 on my PC */ - case 128: /* Record on the iPAQ */ - keycode = 319; - break; - - case 65: /* Space on my PC */ - case 131: /* Schedule on the iPAQ */ - keycode = 32; - break; - - case 132: - _report_presses = 0; - break; - - case 133: - _fake_right_mouse = 1; - break; - - default:{ - KeySym xsym; - xsym = XKeycodeToKeysym(_display, event.xkey.keycode, 0); - keycode = xsym; - if ((xsym >= 'a') && (xsym <= 'z') && (event.xkey.state & 0x01)) - xsym &= ~0x20; /* Handle shifted keys */ - ascii = xsym; - } - } - if (keycode != -1) { - scumm_event.type = Common::EVENT_KEYDOWN; - scumm_event.kbd.keycode = keycode; - scumm_event.kbd.ascii = (ascii != -1 ? ascii : keycode); - scumm_event.kbd.flags = mode; - return true; - } - } - break; - - case KeyRelease:{ - /* I am using keycodes here and NOT keysyms to be sure that even if the user - remaps his iPAQ's keyboard, it will still work. - */ - int keycode = -1; - int ascii = -1; - byte mode = 0; - - if (event.xkey.state & 0x01) - mode |= Common::KBD_SHIFT; - if (event.xkey.state & 0x04) - mode |= Common::KBD_CTRL; - if (event.xkey.state & 0x08) - mode |= Common::KBD_ALT; - switch (event.xkey.keycode) { - case 132: /* 'Q' on the iPAQ */ - _report_presses = 1; - break; - - case 133: /* Arrow on the iPAQ */ - _fake_right_mouse = 0; - break; - - default:{ - KeySym xsym; - xsym = XKeycodeToKeysym(_display, event.xkey.keycode, 0); - keycode = xsym; - if ((xsym >= 'a') && (xsym <= 'z') && (event.xkey.state & 0x01)) - xsym &= ~0x20; /* Handle shifted keys */ - ascii = xsym; - } - } - if (keycode != -1) { - scumm_event.type = Common::EVENT_KEYUP; - scumm_event.kbd.keycode = keycode; - scumm_event.kbd.ascii = (ascii != -1 ? ascii : keycode); - scumm_event.kbd.flags = mode; - return true; - } - } - break; - - case ButtonPress: - if (_report_presses != 0) { - if (event.xbutton.button == 1) { - if (_fake_right_mouse == 0) { - scumm_event.type = Common::EVENT_LBUTTONDOWN; - } else { - scumm_event.type = Common::EVENT_RBUTTONDOWN; - } - } else if (event.xbutton.button == 3) - scumm_event.type = Common::EVENT_RBUTTONDOWN; - scumm_event.mouse.x = event.xbutton.x - _scumm_x; - scumm_event.mouse.y = event.xbutton.y - _scumm_y; - return true; - } - break; - - case ButtonRelease: - if (_report_presses != 0) { - if (event.xbutton.button == 1) { - if (_fake_right_mouse == 0) { - scumm_event.type = Common::EVENT_LBUTTONUP; - } else { - scumm_event.type = Common::EVENT_RBUTTONUP; - } - } else if (event.xbutton.button == 3) - scumm_event.type = Common::EVENT_RBUTTONUP; - scumm_event.mouse.x = event.xbutton.x - _scumm_x; - scumm_event.mouse.y = event.xbutton.y - _scumm_y; - return true; - } - break; - - case MotionNotify: - scumm_event.type = Common::EVENT_MOUSEMOVE; - scumm_event.mouse.x = event.xmotion.x - _scumm_x; - scumm_event.mouse.y = event.xmotion.y - _scumm_y; - set_mouse_pos(scumm_event.mouse.x, scumm_event.mouse.y); - return true; - - case ConfigureNotify:{ - if ((_window_width != event.xconfigure.width) || (_window_height != event.xconfigure.height)) { - _window_width = event.xconfigure.width; - _window_height = event.xconfigure.height; - _scumm_x = (_window_width -_fb_width) / 2; - _scumm_y = (_window_height -_fb_height) / 2; - XFillRectangle(_display, _window, _black_gc, 0, 0, _window_width, _window_height); - } - } - break; - - default: - printf("Unhandled event : %d\n", event.type); - break; - } - } - - return false; -} - -void OSystem_X11::setTimerCallback(TimerProc callback, int interval) { - if (callback != NULL) { - _timer_duration = interval; - _timer_next_expiry = getMillis() + interval; - _timer_callback = callback; - _timer_active = true; - } else { - _timer_active = false; - } -} - -OSystem::MutexRef OSystem_X11::createMutex(void) { - pthread_mutex_t *mutex = (pthread_mutex_t *) malloc(sizeof(pthread_mutex_t)); - pthread_mutex_init(mutex, NULL); - return (MutexRef)mutex; -} - -void OSystem_X11::lockMutex(MutexRef mutex) { - pthread_mutex_lock((pthread_mutex_t *) mutex); -} - -void OSystem_X11::unlockMutex(MutexRef mutex) { - pthread_mutex_unlock((pthread_mutex_t *) mutex); -} - -void OSystem_X11::deleteMutex(MutexRef mutex) { - pthread_mutex_destroy((pthread_mutex_t *) mutex); - free(mutex); -} - -void OSystem_X11::showOverlay() { - _overlay_visible = true; -} - -void OSystem_X11::hideOverlay() { - _overlay_visible = false; - _palette_changed = true; // This is to force a full redraw to hide the overlay -} - -void OSystem_X11::clearOverlay() { - if (_overlay_visible == false) - return; - DirtyRect d = { 0, 0, _fb_width, _fb_height }; - AddDirtyRec(0, 0, _fb_width, _fb_height); - blit(&d, _local_fb_overlay, _fb_width); -} - -void OSystem_X11::grabOverlay(int16 *dest, int pitch) { - if (_overlay_visible == false) - return; - - DirtyRect d = { 0, 0, _fb_width, _fb_height }; - blit(&d, (uint16 *)dest, pitch); -} - -void OSystem_X11::copyRectToOverlay(const int16 *src, int pitch, int x, int y, int w, int h) { - if (_overlay_visible == false) - return; - uint16 *dst = _local_fb_overlay + x + (y * _fb_width); - AddDirtyRec(x, y, w, h); - while (h > 0) { - memcpy(dst, src, w * sizeof(*dst)); - dst +=_fb_width; - src += pitch; - h--; - } -} - -int16 OSystem_X11::getHeight() { - return _fb_height; -} - -int16 OSystem_X11::getWidth() { - return _fb_width; -} - -void OSystem_X11::grabPalette(byte *colors, uint start, uint num) { - warning("Dummy: grabPalette()"); -} diff --git a/backends/platform/x11/x11.h b/backends/platform/x11/x11.h deleted file mode 100644 index bf5da5745ac..00000000000 --- a/backends/platform/x11/x11.h +++ /dev/null @@ -1,197 +0,0 @@ -/* ScummVM - Graphic Adventure Engine - * - * ScummVM is the legal property of its developers, whose names - * are too numerous to list here. Please refer to the COPYRIGHT - * file distributed with this source distribution. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - * $URL$ - * $Id$ - * - */ - -class OSystem_X11:public OSystem { -public: - OSystem_X11(); - ~OSystem_X11(); - - void initBackend(); - - // Determine whether the backend supports the specified feature. - bool hasFeature(Feature f); - - // En-/disable the specified feature. - void setFeatureState(Feature f, bool enable); - - // Query the state of the specified feature. - bool getFeatureState(Feature f); - - // Retrieve a list of all graphics modes supported by this backend. - const GraphicsMode *getSupportedGraphicsModes() const; - - // Return the ID of the 'default' graphics mode. - int getDefaultGraphicsMode() const; - - // Switch to the specified graphics mode. - bool setGraphicsMode(int mode); - - // Determine which graphics mode is currently active. - int getGraphicsMode() const; - - // Set colors of the palette - void setPalette(const byte *colors, uint start, uint num); - - // Set the size of the video bitmap. - // Typically, 320x200 - void initSize(uint w, uint h); - - // Draw a bitmap to screen. - // The screen will not be updated to reflect the new bitmap - void copyRectToScreen(const byte *buf, int pitch, int x, int y, int w, int h); - - // Update the dirty areas of the screen - void updateScreen(); - - // Either show or hide the mouse cursor - bool showMouse(bool visible); - - // Set the position of the mouse cursor - void set_mouse_pos(int x, int y); - - // Warp the mouse cursor. Where set_mouse_pos() only informs the - // backend of the mouse cursor's current position, this function - // actually moves the cursor to the specified position. - void warpMouse(int x, int y); - - // Set the bitmap that's used when drawing the cursor. - void setMouseCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, byte keycolor = 255, int cursorTargetScale = 1); - - // Shaking is used in SCUMM. Set current shake position. - void setShakePos(int shake_pos); - - // Get the number of milliseconds since the program was started. - uint32 getMillis(); - - // Delay for a specified amount of milliseconds - void delayMillis(uint msecs); - - // Get the next event. - // Returns true if an event was retrieved. - bool pollEvent(Common::Event &event); - - // Set function that generates samples - bool setSoundCallback(SoundProc proc, void *param); - void clearSoundCallback(); - - // Determine the output sample rate. Audio data provided by the sound - // callback will be played using this rate. - int getOutputSampleRate() const; - - // Quit - void quit(); - - // Add a callback timer - void setTimerCallback(TimerProc callback, int interval); - - // Mutex handling - MutexRef createMutex(); - void lockMutex(MutexRef mutex); - void unlockMutex(MutexRef mutex); - void deleteMutex(MutexRef mutex); - - // Overlay handling for the new menu system - void showOverlay(); - void hideOverlay(); - void clearOverlay(); - void grabOverlay(int16 *, int); - void copyRectToOverlay(const int16 *, int, int, int, int, int); - virtual int16 getHeight(); - virtual int16 getWidth(); - - virtual void grabPalette(byte *colors, uint start, uint num); - - // Set a window caption or any other comparable status display to the - // given value. - void setWindowCaption(const char *caption); - - static OSystem *create(int gfx_mode, bool full_screen); - -private: - typedef struct { - int x, y; - int w, h; - int hot_x, hot_y; - } MouseState; - - typedef struct { - int x, y, w, h; - } DirtyRect; - - enum { - MAX_NUMBER_OF_DIRTY_RECTS = 32 - }; - - void create_empty_cursor(); - void draw_mouse(DirtyRect *dout); - void undraw_mouse(); - void updateScreen_helper(const DirtyRect *d, DirtyRect *dout); - void blit_convert(const DirtyRect *d, uint8 *dst, int pitch); - void blit(const DirtyRect *d, uint16 *dst, int pitch); - - uint8 *_local_fb; - uint16 *_local_fb_overlay; - bool _overlay_visible; - - int _window_width, _window_height; - int _fb_width, _fb_height; - int _scumm_x, _scumm_y; - - uint16 *_palette16; - uint32 *_palette32; - - bool _palette_changed; - Display *_display; - int _screen, _depth; - uint8 _bytesPerPixel; - Window _window; - GC _black_gc; - XImage *_image; - pthread_t _sound_thread; - - struct timeval _start_time; - - int _fake_right_mouse; - int _report_presses; - int _current_shake_pos; - int _new_shake_pos; - DirtyRect _ds[MAX_NUMBER_OF_DIRTY_RECTS]; - int _num_of_dirty_rects; - - MouseState _oldMouseState, _curMouseState; - byte *_ms_buf; - bool _mouse_visible; - bool _mouse_state_changed; - byte _mouseKeycolor; - - uint32 _timer_duration, _timer_next_expiry; - bool _timer_active; - int (*_timer_callback) (int); -}; - -typedef struct { - OSystem::SoundProc sound_proc; - void *param; -} THREAD_PARAM; diff --git a/backends/plugins/win32/win32-provider.cpp b/backends/plugins/win32/win32-provider.cpp index 64636d80963..b8fdd3d802b 100644 --- a/backends/plugins/win32/win32-provider.cpp +++ b/backends/plugins/win32/win32-provider.cpp @@ -50,21 +50,14 @@ protected: virtual VoidFunc findSymbol(const char *symbol) { #ifndef _WIN32_WCE - void *func = (void *)GetProcAddress((HMODULE)_dlHandle, symbol); + FARPROC func = GetProcAddress((HMODULE)_dlHandle, symbol); #else - void *func = (void *)GetProcAddress((HMODULE)_dlHandle, toUnicode(symbol)); + FARPROC func = GetProcAddress((HMODULE)_dlHandle, toUnicode(symbol)); #endif if (!func) debug("Failed loading symbol '%s' from plugin '%s'", symbol, _filename.c_str()); - // FIXME HACK: This is a HACK to circumvent a clash between the ISO C++ - // standard and POSIX: ISO C++ disallows casting between function pointers - // and data pointers, but dlsym always returns a void pointer. For details, - // see e.g. . - assert(sizeof(VoidFunc) == sizeof(func)); - VoidFunc tmp; - memcpy(&tmp, &func, sizeof(VoidFunc)); - return tmp; + return (void (*)())func; } public: diff --git a/backends/saves/default/default-saves.cpp b/backends/saves/default/default-saves.cpp index ec6d6842c01..21bc56e4414 100644 --- a/backends/saves/default/default-saves.cpp +++ b/backends/saves/default/default-saves.cpp @@ -120,7 +120,7 @@ Common::StringList DefaultSaveFileManager::listSavefiles(const char *pattern) { Common::StringList results; Common::String search(pattern); - if (savePath.lookupFile(savefiles, search, false, true)) { + if (savePath.lookupFile(savefiles, search, false, true, 0)) { for (FSList::const_iterator file = savefiles.begin(); file != savefiles.end(); ++file) { results.push_back(file->getName()); } diff --git a/backends/timer/default/default-timer.h b/backends/timer/default/default-timer.h index 3dbb89e24a6..e7ac3d122f4 100644 --- a/backends/timer/default/default-timer.h +++ b/backends/timer/default/default-timer.h @@ -38,14 +38,15 @@ private: void *_timerHandler; TimerSlot *_head; - public: DefaultTimerManager(); ~DefaultTimerManager(); bool installTimerProc(TimerProc proc, int32 interval, void *refCon); void removeTimerProc(TimerProc proc); - // Timer callback, to be invoked at regular time intervals by the backend. + /** + * Timer callback, to be invoked at regular time intervals by the backend. + */ void handler(); }; diff --git a/base/commandLine.cpp b/base/commandLine.cpp index b9fd4ecfb74..ea0e1465b65 100644 --- a/base/commandLine.cpp +++ b/base/commandLine.cpp @@ -228,6 +228,7 @@ void registerDefaults() { #elif defined(__SYMBIAN32__) strcpy(savePath, Symbian::GetExecutablePath()); strcat(savePath, DEFAULT_SAVE_PATH); + strcat(savePath, "\\"); ConfMan.registerDefault("savepath", savePath); #elif defined (IPHONE) ConfMan.registerDefault("savepath", OSystem_IPHONE::getSavePath()); @@ -684,9 +685,9 @@ static void runDetectorTest() { failure++; } else if (candidates.size() > 1) { if (gameidDiffers) { - printf(" FAILURE: Multiple games detected, some/all with wrong gameid\n"); + printf(" WARNING: Multiple games detected, some/all with wrong gameid\n"); } else { - printf(" FAILURE: Multiple games detected, but all have the same gameid\n"); + printf(" WARNING: Multiple games detected, but all have the same gameid\n"); } failure++; } else if (gameidDiffers) { diff --git a/base/game.cpp b/base/game.cpp index 96285436726..e65c891dc7b 100644 --- a/base/game.cpp +++ b/base/game.cpp @@ -32,10 +32,10 @@ const PlainGameDescriptor *findPlainGameDescriptor(const char *gameid, const Pla const PlainGameDescriptor *g = list; while (g->gameid) { if (0 == scumm_stricmp(gameid, g->gameid)) - break; + return g; g++; } - return g; + return 0; } void GameDescriptor::updateDesc(const char *extra) { diff --git a/base/game.h b/base/game.h index 18d79673888..d81f2afb8a0 100644 --- a/base/game.h +++ b/base/game.h @@ -48,7 +48,7 @@ struct PlainGameDescriptor { /** * Given a list of PlainGameDescriptors, returns the first PlainGameDescriptor * matching the given gameid. If not match is found return 0. - * The end of the list marked by a PlainGameDescriptor with gameid equal to 0. + * The end of the list must marked by a PlainGameDescriptor with gameid equal to 0. */ const PlainGameDescriptor *findPlainGameDescriptor(const char *gameid, const PlainGameDescriptor *list); @@ -92,6 +92,10 @@ public: const Common::String &description() const { return getVal("description"); } Common::Language language() const { return contains("language") ? Common::parseLanguage(getVal("language")) : Common::UNK_LANG; } Common::Platform platform() const { return contains("platform") ? Common::parsePlatform(getVal("platform")) : Common::kPlatformUnknown; } + + const Common::String &preferredtarget() const { + return contains("preferredtarget") ? getVal("preferredtarget") : getVal("gameid"); + } }; /** List of games. */ diff --git a/base/internal_version.h b/base/internal_version.h index 069c20b4c91..c867532dd9a 100644 --- a/base/internal_version.h +++ b/base/internal_version.h @@ -1 +1 @@ -#define SCUMMVM_VERSION "0.12.0svn" +#define SCUMMVM_VERSION "0.13.0svn" diff --git a/base/main.cpp b/base/main.cpp index 219fa0cdc58..666d4fdfe3c 100644 --- a/base/main.cpp +++ b/base/main.cpp @@ -112,35 +112,8 @@ static const EnginePlugin *detectPlugin() { // TODO: specify the possible return values here static int runGame(const EnginePlugin *plugin, OSystem &system, const Common::String &edebuglevels) { - Common::String gameDataPath(ConfMan.get("path")); - if (gameDataPath.empty()) { - } else if (gameDataPath.lastChar() != '/' -#if defined(__MORPHOS__) || defined(__amigaos4__) - && gameDataPath.lastChar() != ':' -#endif - && gameDataPath.lastChar() != '\\') { - gameDataPath += '/'; - ConfMan.set("path", gameDataPath, Common::ConfigManager::kTransientDomain); - } - - // We add the game "path" to the file search path via File::addDefaultDirectory(), - // so that MD5-based detection will be able to properly find files with mixed case - // filenames. - // FIXME/TODO: Fingolfin still doesn't like this; if those MD5-based detectors used - // FSNodes instead of File::open, they wouldn't have to do this. - Common::String path; - if (ConfMan.hasKey("path")) { - path = ConfMan.get("path"); - FilesystemNode dir(path); - if (!dir.isDirectory()) { - warning("Game directory does not exist (%s)", path.c_str()); - return 0; - } - } else { - path = "."; - warning("No path was provided. Assuming the data files are in the current directory"); - } - Common::File::addDefaultDirectory(path); + // Query the game data path, for messages + Common::String path = ConfMan.hasKey("path") ? ConfMan.get("path") : "."; // Create the game engine Engine *engine = 0; @@ -183,15 +156,14 @@ static int runGame(const EnginePlugin *plugin, OSystem &system, const Common::St system.setWindowCaption(caption.c_str()); } - if (ConfMan.hasKey("path")) - Common::File::addDefaultDirectory(ConfMan.get("path")); - else - Common::File::addDefaultDirectory("."); + // Add the game path to the directory search list + Common::File::addDefaultDirectory(path); // Add extrapath (if any) to the directory search list if (ConfMan.hasKey("extrapath")) Common::File::addDefaultDirectoryRecursive(ConfMan.get("extrapath")); + // If a second extrapath is specified on the app domain level, add that as well. if (ConfMan.hasKey("extrapath", Common::ConfigManager::kApplicationDomain)) Common::File::addDefaultDirectoryRecursive(ConfMan.get("extrapath", Common::ConfigManager::kApplicationDomain)); @@ -278,6 +250,7 @@ extern "C" int scummvm_main(int argc, char *argv[]) { // Load the plugins. PluginManager::instance().loadPlugins(); + EngineMan.getPlugins(); // Process the remaining command line settings. Must be done after the // config file and the plugins have been loaded. diff --git a/base/plugins.cpp b/base/plugins.cpp index dcd394495f7..216c6ef1af1 100644 --- a/base/plugins.cpp +++ b/base/plugins.cpp @@ -140,6 +140,9 @@ public: #if PLUGIN_ENABLED_STATIC(SWORD2) LINK_PLUGIN(SWORD2) #endif + #if PLUGIN_ENABLED_STATIC(TINSEL) + LINK_PLUGIN(TINSEL) + #endif #if PLUGIN_ENABLED_STATIC(TOUCHE) LINK_PLUGIN(TOUCHE) #endif diff --git a/base/plugins.h b/base/plugins.h index 9d3ce97c3ba..02116f6433f 100644 --- a/base/plugins.h +++ b/base/plugins.h @@ -81,17 +81,11 @@ extern int pluginTypeVersions[PLUGIN_TYPE_MAX]; #define STATIC_PLUGIN 1 #define DYNAMIC_PLUGIN 2 -// Note: The spaces around ENABLE_##ID have been added on purpose for -// MSVC. For some reason, MSVC tries to add the parenthesis after -// ENABLE_##ID to the check, thus making it false all the time. -// Please do NOT remove them, otherwise no engine plugins will be -// registered under MSVC - #define PLUGIN_ENABLED_STATIC(ID) \ - (defined( ENABLE_##ID ) && !PLUGIN_ENABLED_DYNAMIC(ID)) + (ENABLE_##ID && !PLUGIN_ENABLED_DYNAMIC(ID)) #define PLUGIN_ENABLED_DYNAMIC(ID) \ - (defined( ENABLE_##ID ) && (ENABLE_##ID == DYNAMIC_PLUGIN) && defined(DYNAMIC_MODULES)) + (ENABLE_##ID && (ENABLE_##ID == DYNAMIC_PLUGIN) && DYNAMIC_MODULES) /** * REGISTER_PLUGIN_STATIC is a convenience macro which is used to declare diff --git a/common/advancedDetector.cpp b/common/advancedDetector.cpp index 4387bd199ea..8bd019018a1 100644 --- a/common/advancedDetector.cpp +++ b/common/advancedDetector.cpp @@ -34,7 +34,11 @@ namespace Common { -using namespace AdvancedDetector; +/** + * A list of pointers to ADGameDescription structs (or subclasses thereof). + */ +typedef Array ADGameDescList; + /** * Detect games in specified directory. @@ -48,7 +52,7 @@ using namespace AdvancedDetector; * @param platform restrict results to specified platform only * @return list of ADGameDescription (or subclass) pointers corresponding to matched games */ -static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra); +static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra); /** @@ -194,7 +198,7 @@ static void updateGameDescriptor(GameDescriptor &desc, const ADGameDescription * } GameList AdvancedMetaEngine::detectGames(const FSList &fslist) const { - ADGameDescList matches = detectGame(&fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, ""); + ADGameDescList matches = detectGame(fslist, params, Common::UNK_LANG, Common::kPlatformUnknown, ""); GameList detectedGames; // Use fallback detector if there were no matches by other means @@ -233,7 +237,21 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c Common::String gameid = ConfMan.get("gameid"); - ADGameDescList matches = detectGame(0, params, language, platform, extra); + Common::String path; + if (ConfMan.hasKey("path")) { + path = ConfMan.get("path"); + } else { + path = "."; + warning("No path was provided. Assuming the data files are in the current directory"); + } + FilesystemNode dir(path); + FSList files; + if (!dir.isDirectory() || !dir.getChildren(files, FilesystemNode::kListAll)) { + warning("Game data path does not exist or is not a directory (%s)", path.c_str()); + return kNoGameDataFoundError; + } + + ADGameDescList matches = detectGame(files, params, language, platform, extra); if (params.singleid == NULL) { for (uint i = 0; i < matches.size(); i++) { @@ -268,10 +286,10 @@ PluginError AdvancedMetaEngine::createInstance(OSystem *syst, Engine **engine) c return kNoError; } -typedef HashMap StringSet; -typedef HashMap IntMap; +typedef HashMap StringSet; +typedef HashMap IntMap; -static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) { +static void reportUnknown(const StringMap &filesMD5, const IntMap &filesSize) { // TODO: This message should be cleaned up / made more specific. // For example, we should specify at least which engine triggered this. // @@ -287,96 +305,77 @@ static void reportUnknown(StringMap &filesMD5, IntMap &filesSize) { printf("\n"); } -static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra) { - StringSet filesList; +static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams ¶ms); +static ADGameDescList detectGame(const FSList &fslist, const Common::ADParams ¶ms, Language language, Platform platform, const Common::String extra) { + StringMap allFiles; + + StringSet detectFiles; StringMap filesMD5; IntMap filesSize; - IntMap allFiles; - File testFile; - - String tstr; - - uint i; - char md5str[32+1]; - - bool fileMissing; const ADGameFileDescription *fileDesc; const ADGameDescription *g; const byte *descPtr; debug(3, "Starting detection"); - // First we compose list of files which we need MD5s for + // First we compose an efficient to query set of all files in fslist. + // Includes nifty stuff like removing trailing dots and ignoring case. + for (FSList::const_iterator file = fslist.begin(); file != fslist.end(); ++file) { + if (file->isDirectory()) + continue; + + String tstr = file->getName(); + + // Strip any trailing dot + if (tstr.lastChar() == '.') + tstr.deleteLastChar(); + + allFiles[tstr] = file->getPath(); // Record the presence of this file + } + + // Compute the set of files for which we need MD5s for. I.e. files which are + // included in some ADGameDescription *and* present in fslist. for (descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize) { g = (const ADGameDescription *)descPtr; for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { - tstr = String(fileDesc->fileName); - tstr.toLowercase(); - filesList[tstr] = true; + String tstr = fileDesc->fileName; + if (allFiles.contains(tstr)) + detectFiles[tstr] = true; } } - // TODO/FIXME: Fingolfin says: It's not good that we have two different code paths here, - // one using a FSList, one using File::open, as that will lead to discrepancies and subtle - // problems caused by those. - if (fslist != 0) { - // Get the information of the existing files - for (FSList::const_iterator file = fslist->begin(); file != fslist->end(); ++file) { - if (file->isDirectory()) continue; - tstr = file->getName(); - tstr.toLowercase(); + // Get the information for all detection files, if they exist + for (StringSet::const_iterator file = detectFiles.begin(); file != detectFiles.end(); ++file) { + String fname = file->_key; - // Strip any trailing dot - if (tstr.lastChar() == '.') - tstr.deleteLastChar(); + debug(3, "+ %s", fname.c_str()); - allFiles[tstr] = true; + char md5str[32+1]; + if (!md5_file_string(allFiles[fname].c_str(), md5str, params.md5Bytes)) + continue; + filesMD5[fname] = md5str; - debug(3, "+ %s", tstr.c_str()); + debug(3, "> %s: %s", fname.c_str(), md5str); - if (!filesList.contains(tstr)) continue; - - if (!md5_file_string(*file, md5str, params.md5Bytes)) - continue; - filesMD5[tstr] = md5str; - - debug(3, "> %s: %s", tstr.c_str(), md5str); - - if (testFile.open(file->getPath())) { - filesSize[tstr] = (int32)testFile.size(); - testFile.close(); - } - } - } else { - // Get the information of the requested files - for (StringSet::const_iterator file = filesList.begin(); file != filesList.end(); ++file) { - tstr = file->_key; - - debug(3, "+ %s", tstr.c_str()); - if (!filesMD5.contains(tstr)) { - if (testFile.open(tstr) || testFile.open(tstr + ".")) { - filesSize[tstr] = (int32)testFile.size(); - testFile.close(); - - if (md5_file_string(file->_key.c_str(), md5str, params.md5Bytes)) { - filesMD5[tstr] = md5str; - debug(3, "> %s: %s", tstr.c_str(), md5str); - } - } - } + File testFile; + if (testFile.open(allFiles[fname])) { + filesSize[fname] = (int32)testFile.size(); + testFile.close(); } } + ADGameDescList matched; int maxFilesMatched = 0; // MD5 based matching + uint i; for (i = 0, descPtr = params.descs; ((const ADGameDescription *)descPtr)->gameid != 0; descPtr += params.descItemSize, ++i) { g = (const ADGameDescription *)descPtr; - fileMissing = false; + bool fileMissing = false; // Do not even bother to look at entries which do not have matching // language and platform (if specified). @@ -385,32 +384,28 @@ static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &p continue; } - if ((params.flags & kADFlagUseExtraAsHint) && extra != "" && g->extra != extra) + if ((params.flags & kADFlagUseExtraAsHint) && !extra.empty() && g->extra != extra) continue; // Try to match all files for this game for (fileDesc = g->filesDescriptions; fileDesc->fileName; fileDesc++) { - tstr = fileDesc->fileName; - tstr.toLowercase(); + String tstr = fileDesc->fileName; if (!filesMD5.contains(tstr)) { fileMissing = true; break; } - if (fileDesc->md5 != NULL) { - if (fileDesc->md5 != filesMD5[tstr]) { - debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str()); - fileMissing = true; - break; - } + + if (fileDesc->md5 != NULL && fileDesc->md5 != filesMD5[tstr]) { + debug(3, "MD5 Mismatch. Skipping (%s) (%s)", fileDesc->md5, filesMD5[tstr].c_str()); + fileMissing = true; + break; } - if (fileDesc->fileSize != -1) { - if (fileDesc->fileSize != filesSize[tstr]) { - debug(3, "Size Mismatch. Skipping"); - fileMissing = true; - break; - } + if (fileDesc->fileSize != -1 && fileDesc->fileSize != filesSize[tstr]) { + debug(3, "Size Mismatch. Skipping"); + fileMissing = true; + break; } debug(3, "Matched file: %s", tstr.c_str()); @@ -448,85 +443,68 @@ static ADGameDescList detectGame(const FSList *fslist, const Common::ADParams &p } } - // We've found a match - if (!matched.empty()) - return matched; + // We didn't find a match + if (matched.empty()) { + if (!filesMD5.empty()) + reportUnknown(filesMD5, filesSize); + + // Filename based fallback + if (params.fileBasedFallback != 0) + matched = detectGameFilebased(allFiles, params); + } - if (!filesMD5.empty()) - reportUnknown(filesMD5, filesSize); + return matched; +} - // Filename based fallback - if (params.fileBasedFallback != 0) { - const ADFileBasedFallback *ptr = params.fileBasedFallback; - const char* const* filenames = 0; +/** + * Check for each ADFileBasedFallback record whether all files listed + * in it are present. If multiple pass this test, we pick the one with + * the maximal number of matching files. In case of a tie, the entry + * coming first in the list is chosen. + */ +static ADGameDescList detectGameFilebased(const StringMap &allFiles, const Common::ADParams ¶ms) { + const ADFileBasedFallback *ptr; + const char* const* filenames; - // First we create list of files required for detection. - // The filenames can be different than the MD5 based match ones. - for (; ptr->desc; ptr++) { - filenames = ptr->filenames; - for (; *filenames; filenames++) { - tstr = String(*filenames); - tstr.toLowercase(); + int maxNumMatchedFiles = 0; + const ADGameDescription *matchedDesc = 0; - if (!allFiles.contains(tstr)) { - if (testFile.open(tstr) || testFile.open(tstr + ".")) { - allFiles[tstr] = true; - testFile.close(); - } - } + for (ptr = params.fileBasedFallback; ptr->desc; ++ptr) { + const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc; + int numMatchedFiles = 0; + bool fileMissing = false; + + for (filenames = ptr->filenames; *filenames; ++filenames) { + debug(3, "++ %s", *filenames); + if (!allFiles.contains(*filenames)) { + fileMissing = true; + break; } + + numMatchedFiles++; } - // Then we perform the actual filename matching. If there are - // several matches, only the one with the maximum numbers of - // files is considered. - int maxNumMatchedFiles = 0; - const ADGameDescription *matchedDesc = 0; - - ptr = params.fileBasedFallback; - - for (; ptr->desc; ptr++) { - const ADGameDescription *agdesc = (const ADGameDescription *)ptr->desc; - int numMatchedFiles = 0; - fileMissing = false; - - filenames = ptr->filenames; - for (; *filenames; filenames++) { - if (fileMissing) { - continue; - } - - tstr = String(*filenames); - tstr.toLowercase(); - - debug(3, "++ %s", *filenames); - if (!allFiles.contains(tstr)) { - fileMissing = true; - continue; - } - - numMatchedFiles++; - } - - if (!fileMissing) - debug(4, "Matched: %s", agdesc->gameid); - - if (!fileMissing && numMatchedFiles > maxNumMatchedFiles) { + if (!fileMissing) { + debug(4, "Matched: %s", agdesc->gameid); + + if (numMatchedFiles > maxNumMatchedFiles) { matchedDesc = agdesc; maxNumMatchedFiles = numMatchedFiles; - + debug(4, "and overriden"); } } + } - if (matchedDesc) { // We got a match - matched.push_back(matchedDesc); - if (params.flags & kADFlagPrintWarningOnFileBasedFallback) { - printf("Your game version has been detected using filename matching as a\n"); - printf("variant of %s.\n", matchedDesc->gameid); - printf("If this is an original and unmodified version, please report any\n"); - printf("information previously printed by ScummVM to the team.\n"); - } + ADGameDescList matched; + + if (matchedDesc) { // We got a match + matched.push_back(matchedDesc); + if (params.flags & kADFlagPrintWarningOnFileBasedFallback) { + printf("Your game version has been detected using filename matching as a\n"); + printf("variant of %s.\n", matchedDesc->gameid); + printf("If this is an original and unmodified version, please report any\n"); + printf("information previously printed by ScummVM to the team.\n"); } } diff --git a/common/advancedDetector.h b/common/advancedDetector.h index 48b9e213d7c..40f5823d1b7 100644 --- a/common/advancedDetector.h +++ b/common/advancedDetector.h @@ -68,11 +68,6 @@ struct ADGameDescription { uint32 flags; }; -/** - * A list of pointers to ADGameDescription structs (or subclasses thereof). - */ -typedef Array ADGameDescList; - /** * End marker for a table of ADGameDescription structs. Use this to * terminate a list to be passed to the AdvancedDetector API. diff --git a/common/algorithm.h b/common/algorithm.h index beae34245f2..3b6c63d55cd 100644 --- a/common/algorithm.h +++ b/common/algorithm.h @@ -29,6 +29,11 @@ namespace Common { +/** + * Copies data from the range [first, last) to [dst, dst + (last - first)). + * It requires the range [dst, dst + (last - first)) to be valid. + * It also requires dst not to be in the range [first, last). + */ template Out copy(In first, In last, Out dst) { while (first != last) @@ -36,6 +41,13 @@ Out copy(In first, In last, Out dst) { return dst; } +/** + * Copies data from the range [first, last) to [dst - (last - first), dst). + * It requires the range [dst - (last - first), dst) to be valid. + * It also requires dst not to be in the range [first, last). + * + * Unlike copy copy_backward copies the data from the end to the beginning. + */ template Out copy_backward(In first, In last, Out dst) { while (first != last) @@ -43,6 +55,15 @@ Out copy_backward(In first, In last, Out dst) { return dst; } +/** + * Copies data from the range [first, last) to [dst, dst + (last - first)). + * It requires the range [dst, dst + (last - first)) to be valid. + * It also requires dst not to be in the range [first, last). + * + * Unlike copy or copy_backward it does not copy all data. It only copies + * a data element when operator() of the op parameter returns true for the + * passed data element. + */ template Out copy_if(In first, In last, Out dst, Op op) { while (first != last) { @@ -76,6 +97,9 @@ char *set_to(char *first, char *last, Value val) { return last; } +/** + * Sets all elements in the range [first, last) to val. + */ template In set_to(In first, In last, Value val) { while (first != last) @@ -83,6 +107,10 @@ In set_to(In first, In last, Value val) { return first; } +/** + * Finds the first data value in the range [first, last) matching v. + * For data comperance it uses operator == of the data elements. + */ template In find(In first, In last, const T &v) { while (first != last) { @@ -93,6 +121,10 @@ In find(In first, In last, const T &v) { return last; } +/** + * Finds the first data value in the range [first, last) for which + * the specified predicate p returns true. + */ template In find_if(In first, In last, Pred p) { while (first != last) { @@ -103,15 +135,22 @@ In find_if(In first, In last, Pred p) { return last; } +/** + * Applies the function f on all elements of the range [first, last). + * The processing order is from beginning to end. + */ template Op for_each(In first, In last, Op f) { while (first != last) f(*first++); return f; } -// Simple sort function, modeled after std::sort. -// Use it like this: sort(container.begin(), container.end()). -// Also work on plain old int arrays etc. +/** + * Simple sort function, modeled after std::sort. + * Use it like this: sort(container.begin(), container.end()). + * Also works on plain old i.e. int arrays etc. For comperance + * operator < is used. + */ template void sort(T first, T last) { if (first == last) @@ -131,8 +170,13 @@ void sort(T first, T last) { } } -// Using this with: Common::Less from common/func.h -// will give the same results as the function above. +/** + * Simple sort function, modeled after std::sort. + * It compares data with the given comparator object comp. + * + * Note: Using this with: Common::Less from common/func.h + * will give the same results as the plain sort function. + */ template void sort(T first, T last, StrictWeakOrdering comp) { if (first == last) diff --git a/common/config-file.cpp b/common/config-file.cpp index fe827e32dc5..9f54c9dddee 100644 --- a/common/config-file.cpp +++ b/common/config-file.cpp @@ -58,7 +58,7 @@ void ConfigFile::clear() { bool ConfigFile::loadFromFile(const String &filename) { File file; - if (file.open(filename, File::kFileReadMode)) + if (file.open(filename)) return loadFromStream(file); else return false; @@ -171,8 +171,8 @@ bool ConfigFile::loadFromStream(SeekableReadStream &stream) { } bool ConfigFile::saveToFile(const String &filename) { - File file; - if (file.open(filename, File::kFileWriteMode)) + DumpFile file; + if (file.open(filename)) return saveToStream(file); else return false; diff --git a/common/config-manager.cpp b/common/config-manager.cpp index 5afbbad4aee..044474a9279 100644 --- a/common/config-manager.cpp +++ b/common/config-manager.cpp @@ -176,7 +176,6 @@ void ConfigManager::loadFile(const String &filename) { if (!cfg_file.open(filename)) { printf("Creating configuration file: %s\n", filename.c_str()); } else { - char buf[MAXLINELEN]; String domain; String comment; int lineno = 0; @@ -184,20 +183,28 @@ void ConfigManager::loadFile(const String &filename) { // TODO: Detect if a domain occurs multiple times (or likewise, if // a key occurs multiple times inside one domain). - while (!cfg_file.eof()) { + while (!cfg_file.eof() && !cfg_file.ioFailed()) { lineno++; - if (!cfg_file.readLine(buf, MAXLINELEN)) - break; - if (buf[0] == '#') { + // Read a line + String line; + while (line.lastChar() != '\n') { + char buf[MAXLINELEN]; + if (!cfg_file.readLine_NEW(buf, MAXLINELEN)) + break; + line += buf; + } + + if (line.size() == 0) { + // Do nothing + } else if (line[0] == '#') { // Accumulate comments here. Once we encounter either the start // of a new domain, or a key-value-pair, we associate the value // of the 'comment' variable with that entity. - comment += buf; - comment += '\n'; - } else if (buf[0] == '[') { + comment += line; + } else if (line[0] == '[') { // It's a new domain which begins here. - char *p = buf + 1; + const char *p = line.c_str() + 1; // Get the domain name, and check whether it's valid (that // is, verify that it only consists of alphanumerics, // dashes and underscores). @@ -209,8 +216,8 @@ void ConfigManager::loadFile(const String &filename) { error("Config file buggy: missing ] in line %d", lineno); break; case ']': - *p = 0; - domain = buf + 1; + domain = String(line.c_str() + 1, p - (line.c_str() + 1)); + //domain = String(line.c_str() + 1, p); // TODO: Pending Common::String changes break; default: error("Config file buggy: Invalid character '%c' occured in domain name in line %d", *p, lineno); @@ -226,10 +233,14 @@ void ConfigManager::loadFile(const String &filename) { _domainSaveOrder.push_back(domain); } else { - // Skip leading & trailing whitespaces - char *t = rtrim(ltrim(buf)); + // This line should be a line with a 'key=value' pair, or an empty one. + + // Skip leading whitespaces + const char *t = line.c_str(); + while (isspace(*t)) + t++; - // Skip empty lines + // Skip empty lines / lines with only whitespace if (*t == 0) continue; @@ -238,13 +249,30 @@ void ConfigManager::loadFile(const String &filename) { error("Config file buggy: Key/value pair found outside a domain in line %d", lineno); } - // Split string at '=' into 'key' and 'value'. - char *p = strchr(t, '='); + // Split string at '=' into 'key' and 'value'. First, find the "=" delimeter. + const char *p = strchr(t, '='); if (!p) error("Config file buggy: Junk found in line line %d: '%s'", lineno, t); - *p = 0; - String key = rtrim(t); - String value = ltrim(p + 1); + + // Trim spaces before the '=' to obtain the key + const char *p2 = p; + while (p2 > t && isspace(*(p2-1))) + p2--; + String key(t, p2 - t); + + // Skip spaces after the '=' + t = p + 1; + while (isspace(*t)) + t++; + + // Trim trailing spaces + p2 = t + strlen(t); + while (p2 > t && isspace(*(p2-1))) + p2--; + + String value(t, p2 - t); + + // Finally, store the key/value pair in the active domain set(key, value, domain); // Store comment @@ -261,13 +289,9 @@ void ConfigManager::loadFile(const String &filename) { void ConfigManager::flushToDisk() { #ifndef __DC__ - File cfg_file; + DumpFile cfg_file; -// TODO -// if (!willwrite) -// return; - - if (!cfg_file.open(_filename, File::kFileWriteMode)) { + if (!cfg_file.open(_filename)) { warning("Unable to write configuration file: %s", _filename.c_str()); } else { // First write the domains in _domainSaveOrder, in that order. @@ -614,6 +638,10 @@ void ConfigManager::addGameDomain(const String &domName) { // the given name already exists? _gameDomains[domName]; + + // Add it to the _domainSaveOrder, if it's not already in there + if (find(_domainSaveOrder.begin(), _domainSaveOrder.end(), domName) == _domainSaveOrder.end()) + _domainSaveOrder.push_back(domName); } void ConfigManager::removeGameDomain(const String &domName) { diff --git a/common/file.cpp b/common/file.cpp index 386777e2c5c..5b465b5e01c 100644 --- a/common/file.cpp +++ b/common/file.cpp @@ -104,7 +104,7 @@ //#define fgets(str, size, file) DS::std_fgets(str, size, file) // not used //#define getc(handle) DS::std_getc(handle) // not used //#define getcwd(dir, dunno) DS::std_getcwd(dir, dunno) // not used - //#define ferror(handle) DS::std_ferror(handle) // not used + #define ferror(handle) DS::std_ferror(handle) #endif @@ -273,26 +273,14 @@ File::File() : _handle(0), _ioFailed(false) { } -//#define DEBUG_FILE_REFCOUNT - File::~File() { -#ifdef DEBUG_FILE_REFCOUNT - warning("File::~File on file '%s'", _name.c_str()); -#endif close(); } -bool File::open(const String &filename, AccessMode mode) { - assert(mode == kFileReadMode || mode == kFileWriteMode); - - if (filename.empty()) { - error("File::open: No filename was specified"); - } - - if (_handle) { - error("File::open: This file object already is opened (%s), won't open '%s'", _name.c_str(), filename.c_str()); - } +bool File::open(const String &filename) { + assert(!filename.empty()); + assert(!_handle); _name.clear(); clearIOFailed(); @@ -300,32 +288,29 @@ bool File::open(const String &filename, AccessMode mode) { String fname(filename); fname.toLowercase(); - const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb"; - if (mode == kFileWriteMode) { - _handle = fopenNoCase(filename, "", modeStr); - } else if (_filesMap && _filesMap->contains(fname)) { + if (_filesMap && _filesMap->contains(fname)) { fname = (*_filesMap)[fname]; debug(3, "Opening hashed: %s", fname.c_str()); - _handle = fopen(fname.c_str(), modeStr); + _handle = fopen(fname.c_str(), "rb"); } else if (_filesMap && _filesMap->contains(fname + ".")) { // WORKAROUND: Bug #1458388: "SIMON1: Game Detection fails" // sometimes instead of "GAMEPC" we get "GAMEPC." (note trailing dot) fname = (*_filesMap)[fname + "."]; debug(3, "Opening hashed: %s", fname.c_str()); - _handle = fopen(fname.c_str(), modeStr); + _handle = fopen(fname.c_str(), "rb"); } else { if (_defaultDirectories) { // Try all default directories StringIntMap::const_iterator x(_defaultDirectories->begin()); for (; _handle == NULL && x != _defaultDirectories->end(); ++x) { - _handle = fopenNoCase(filename, x->_key, modeStr); + _handle = fopenNoCase(filename, x->_key, "rb"); } } // Last resort: try the current directory if (_handle == NULL) - _handle = fopenNoCase(filename, "", modeStr); + _handle = fopenNoCase(filename, "", "rb"); // Last last (really) resort: try looking inside the application bundle on Mac OS X for the lowercase file. #if defined(MACOSX) || defined(IPHONE) @@ -335,7 +320,7 @@ bool File::open(const String &filename, AccessMode mode) { if (fileUrl) { UInt8 buf[256]; if (CFURLGetFileSystemRepresentation(fileUrl, false, (UInt8 *)buf, 256)) { - _handle = fopen((char *)buf, modeStr); + _handle = fopen((char *)buf, "rb"); } CFRelease(fileUrl); } @@ -345,26 +330,15 @@ bool File::open(const String &filename, AccessMode mode) { } - if (_handle == NULL) { - if (mode == kFileReadMode) - debug(2, "File %s not found", filename.c_str()); - else - debug(2, "File %s not opened", filename.c_str()); - return false; - } + if (_handle == NULL) + debug(2, "File %s not opened", filename.c_str()); + else + _name = filename; - - _name = filename; - -#ifdef DEBUG_FILE_REFCOUNT - warning("File::open on file '%s'", _name.c_str()); -#endif - - return true; + return _handle != NULL; } -bool File::open(const FilesystemNode &node, AccessMode mode) { - assert(mode == kFileReadMode || mode == kFileWriteMode); +bool File::open(const FilesystemNode &node) { if (!node.exists()) { warning("File::open: Trying to open a FilesystemNode which does not exist"); @@ -389,25 +363,14 @@ bool File::open(const FilesystemNode &node, AccessMode mode) { clearIOFailed(); _name.clear(); - const char *modeStr = (mode == kFileReadMode) ? "rb" : "wb"; + _handle = fopen(node.getPath().c_str(), "rb"); - _handle = fopen(node.getPath().c_str(), modeStr); + if (_handle == NULL) + debug(2, "File %s not found", filename.c_str()); + else + _name = filename; - if (_handle == NULL) { - if (mode == kFileReadMode) - debug(2, "File %s not found", filename.c_str()); - else - debug(2, "File %s not opened", filename.c_str()); - return false; - } - - _name = filename; - -#ifdef DEBUG_FILE_REFCOUNT - warning("File::open on file '%s'", _name.c_str()); -#endif - - return true; + return _handle != NULL; } bool File::exists(const String &filename) { @@ -438,7 +401,7 @@ bool File::exists(const String &filename) { //Try opening the file inside the local directory as a last resort File tmp; - return tmp.open(filename, kFileReadMode); + return tmp.open(filename); } void File::close() { @@ -452,36 +415,29 @@ bool File::isOpen() const { } bool File::ioFailed() const { + // TODO/FIXME: Just use ferror() here? return _ioFailed != 0; } void File::clearIOFailed() { + // TODO/FIXME: Just use clearerr() here? _ioFailed = false; } bool File::eof() const { - if (_handle == NULL) { - error("File::eof: File is not open!"); - return false; - } + assert(_handle); return feof((FILE *)_handle) != 0; } uint32 File::pos() const { - if (_handle == NULL) { - error("File::pos: File is not open!"); - return 0; - } + assert(_handle); return ftell((FILE *)_handle); } uint32 File::size() const { - if (_handle == NULL) { - error("File::size: File is not open!"); - return 0; - } + assert(_handle); uint32 oldPos = ftell((FILE *)_handle); fseek((FILE *)_handle, 0, SEEK_END); @@ -492,10 +448,7 @@ uint32 File::size() const { } void File::seek(int32 offs, int whence) { - if (_handle == NULL) { - error("File::seek: File is not open!"); - return; - } + assert(_handle); if (fseek((FILE *)_handle, offs, whence) != 0) clearerr((FILE *)_handle); @@ -505,10 +458,7 @@ uint32 File::read(void *ptr, uint32 len) { byte *ptr2 = (byte *)ptr; uint32 real_len; - if (_handle == NULL) { - error("File::read: File is not open!"); - return 0; - } + assert(_handle); if (len == 0) return 0; @@ -521,20 +471,62 @@ uint32 File::read(void *ptr, uint32 len) { return real_len; } -uint32 File::write(const void *ptr, uint32 len) { - if (_handle == NULL) { - error("File::write: File is not open!"); - return 0; - } + +DumpFile::DumpFile() : _handle(0) { +} + +DumpFile::~DumpFile() { + close(); +} + +bool DumpFile::open(const String &filename) { + assert(!filename.empty()); + assert(!_handle); + + String fname(filename); + fname.toLowercase(); + + _handle = fopenNoCase(filename, "", "wb"); + + if (_handle == NULL) + debug(2, "Failed to open '%s' for writing", filename.c_str()); + + return _handle != NULL; +} + +void DumpFile::close() { + if (_handle) + fclose((FILE *)_handle); + _handle = NULL; +} + +bool DumpFile::isOpen() const { + return _handle != NULL; +} + +bool DumpFile::ioFailed() const { + assert(_handle); + return ferror((FILE *)_handle) != 0; +} + +void DumpFile::clearIOFailed() { + assert(_handle); + clearerr((FILE *)_handle); +} + +bool DumpFile::eof() const { + assert(_handle); + return feof((FILE *)_handle) != 0; +} + +uint32 DumpFile::write(const void *ptr, uint32 len) { + assert(_handle); if (len == 0) return 0; - if ((uint32)fwrite(ptr, 1, len, (FILE *)_handle) != len) { - _ioFailed = true; - } - - return len; + return (uint32)fwrite(ptr, 1, len, (FILE *)_handle); } + } // End of namespace Common diff --git a/common/file.h b/common/file.h index 8a693181282..3c2520b07c1 100644 --- a/common/file.h +++ b/common/file.h @@ -27,6 +27,7 @@ #define COMMON_FILE_H #include "common/scummsys.h" +#include "common/noncopyable.h" #include "common/str.h" #include "common/stream.h" @@ -34,7 +35,10 @@ class FilesystemNode; namespace Common { -class File : public SeekableReadStream, public WriteStream { +/** + * TODO: vital to document this core class properly!!! For both users and implementors + */ +class File : public SeekableReadStream, public NonCopyable { protected: /** File handle to the actual file; 0 if no file is open. */ void *_handle; @@ -45,19 +49,7 @@ protected: /** The name of this file, for debugging. */ String _name; -private: - // Disallow copying File objects. There is not strict reason for this, - // except that so far we never had real need for such a feature, and - // code that accidentally copied File objects tended to break in strange - // ways. - File(const File &f); - File &operator =(const File &f); - public: - enum AccessMode { - kFileReadMode = 1, - kFileWriteMode = 2 - }; static void addDefaultDirectory(const String &directory); static void addDefaultDirectoryRecursive(const String &directory, int level = 4, const String &prefix = ""); @@ -80,8 +72,8 @@ public: */ static bool exists(const String &filename); - virtual bool open(const String &filename, AccessMode mode = kFileReadMode); - virtual bool open(const FilesystemNode &node, AccessMode mode = kFileReadMode); + virtual bool open(const String &filename); + virtual bool open(const FilesystemNode &node); virtual void close(); @@ -114,9 +106,52 @@ public: virtual uint32 size() const; void seek(int32 offs, int whence = SEEK_SET); uint32 read(void *dataPtr, uint32 dataSize); +}; + + +/** + * TODO: document this class + * + * Some design ideas: + * - automatically drop all files into dumps/ dir? Might not be desired in all cases + */ +class DumpFile : public WriteStream, public NonCopyable { +protected: + /** File handle to the actual file; 0 if no file is open. */ + void *_handle; + +public: + DumpFile(); + virtual ~DumpFile(); + + virtual bool open(const String &filename); + //virtual bool open(const FilesystemNode &node); + + virtual void close(); + + /** + * Checks if the object opened a file successfully. + * + * @return: true if any file is opened, false otherwise. + */ + bool isOpen() const; + + + bool ioFailed() const; + void clearIOFailed(); + bool eos() const { return eof(); } + + /** + * Checks for end of file. + * + * @return: true if the end of file is reached, false otherwise. + */ + virtual bool eof() const; + uint32 write(const void *dataPtr, uint32 dataSize); }; + } // End of namespace Common #endif diff --git a/common/func.h b/common/func.h index 95df96123aa..6aa5b76ed45 100644 --- a/common/func.h +++ b/common/func.h @@ -29,12 +29,18 @@ namespace Common { +/** + * Generic unary function. + */ template struct UnaryFunction { typedef Arg ArgumenType; typedef Result ResultType; }; +/** + * Generic binary function. + */ template struct BinaryFunction { typedef Arg1 FirstArgumentType; @@ -42,16 +48,25 @@ struct BinaryFunction { typedef Result ResultType; }; +/** + * Predicate to check for equallity of two data elements. + */ template struct EqualTo : public BinaryFunction { bool operator()(const T &x, const T &y) const { return x == y; } }; +/** + * Predicate to check for x being less than y. + */ template struct Less : public BinaryFunction { bool operator()(const T &x, const T &y) const { return x < y; } }; +/** + * Predicate to check for x being greater than y. + */ template struct Greater : public BinaryFunction { bool operator()(const T &x, const T &y) const { return x > y; } @@ -63,15 +78,19 @@ private: Op _op; typename Op::FirstArgumentType _arg1; public: - Binder1st(const Op &op, const typename Op::FirstArgumentType &arg1) : _op(op), _arg1(arg1) {} + Binder1st(const Op &op, typename Op::FirstArgumentType arg1) : _op(op), _arg1(arg1) {} typename Op::ResultType operator()(typename Op::SecondArgumentType v) const { return _op(_arg1, v); } }; -template -inline Binder1st bind1st(const Op &op, const T &t) { +/** + * Transforms a binary function object into an unary function object. + * To achieve that the first parameter is bound to the passed value t. + */ +template +inline Binder1st bind1st(const Op &op, typename Op::FirstArgumentType t) { return Binder1st(op, t); } @@ -81,15 +100,19 @@ private: Op _op; typename Op::SecondArgumentType _arg2; public: - Binder2nd(const Op &op, const typename Op::SecondArgumentType &arg2) : _op(op), _arg2(arg2) {} + Binder2nd(const Op &op, typename Op::SecondArgumentType arg2) : _op(op), _arg2(arg2) {} typename Op::ResultType operator()(typename Op::FirstArgumentType v) const { return _op(v, _arg2); } }; -template -inline Binder2nd bind2nd(const Op &op, const T &t) { +/** + * Transforms a binary function object into an unary function object. + * To achieve that the first parameter is bound to the passed value t. + */ +template +inline Binder2nd bind2nd(const Op &op, typename Op::SecondArgumentType t) { return Binder2nd(op, t); } @@ -119,18 +142,24 @@ public: } }; +/** + * Creates an unary function object from a function pointer. + */ template inline PointerToUnaryFunc ptr_fun(Result (*func)(Arg)) { return PointerToUnaryFunc(func); } +/** + * Creates an binary function object from a function pointer. + */ template inline PointerToBinaryFunc ptr_fun(Result (*func)(Arg1, Arg2)) { return PointerToBinaryFunc(func); } template -class MemFunc0 : public UnaryFunction { +class MemFunc0 : public UnaryFunction { private: Result (T::*_func)(); public: @@ -143,20 +172,20 @@ public: }; template -class ConstMemFunc0 : public UnaryFunction { +class ConstMemFunc0 : public UnaryFunction { private: Result (T::*_func)() const; public: typedef Result (T::*FuncType)() const; ConstMemFunc0(const FuncType &func) : _func(func) {} - Result operator()(T *v) const { + Result operator()(const T *v) const { return (v->*_func)(); } }; template -class MemFunc1 : public BinaryFunction { +class MemFunc1 : public BinaryFunction { private: Result (T::*_func)(Arg); public: @@ -169,40 +198,166 @@ public: }; template -class ConstMemFunc1 : public BinaryFunction { +class ConstMemFunc1 : public BinaryFunction { private: Result (T::*_func)(Arg) const; public: typedef Result (T::*FuncType)(Arg) const; ConstMemFunc1(const FuncType &func) : _func(func) {} - Result operator()(T *v1, Arg v2) const { + Result operator()(const T *v1, Arg v2) const { return (v1->*_func)(v2); } }; +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the 'this' pointer to + * be used for the function call. + */ template inline MemFunc0 mem_fun(Result (T::*f)()) { return MemFunc0(f); } +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the 'this' pointer to + * be used for the function call. + */ template inline ConstMemFunc0 mem_fun(Result (T::*f)() const) { return ConstMemFunc0(f); } +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the 'this' pointer to + * be used for the function call. + * The second one is the parameter passed to the member function. + */ template inline MemFunc1 mem_fun(Result (T::*f)(Arg)) { return MemFunc1(f); } +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the 'this' pointer to + * be used for the function call. + * The second one is the parameter passed to the member function. + */ template inline ConstMemFunc1 mem_fun(Result (T::*f)(Arg) const) { return ConstMemFunc1(f); } +template +class MemFuncRef0 : public UnaryFunction { +private: + Result (T::*_func)(); +public: + typedef Result (T::*FuncType)(); + + MemFuncRef0(const FuncType &func) : _func(func) {} + Result operator()(T &v) const { + return (v.*_func)(); + } +}; + +template +class ConstMemFuncRef0 : public UnaryFunction { +private: + Result (T::*_func)() const; +public: + typedef Result (T::*FuncType)() const; + + ConstMemFuncRef0(const FuncType &func) : _func(func) {} + Result operator()(const T &v) const { + return (v.*_func)(); + } +}; + +template +class MemFuncRef1 : public BinaryFunction { +private: + Result (T::*_func)(Arg); +public: + typedef Result (T::*FuncType)(Arg); + + MemFuncRef1(const FuncType &func) : _func(func) {} + Result operator()(T &v1, Arg v2) const { + return (v1.*_func)(v2); + } +}; + +template +class ConstMemFuncRef1 : public BinaryFunction { +private: + Result (T::*_func)(Arg) const; +public: + typedef Result (T::*FuncType)(Arg) const; + + ConstMemFuncRef1(const FuncType &func) : _func(func) {} + Result operator()(const T &v1, Arg v2) const { + return (v1.*_func)(v2); + } +}; + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. Note unlike mem_fun, it takes a reference + * as parameter. + */ +template +inline MemFuncRef0 mem_fun_ref(Result (T::*f)()) { + return MemFuncRef0(f); +} + +/** + * Creates a unary function object from a class member function pointer. + * The parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + */ +template +inline ConstMemFuncRef0 mem_fun_Ref(Result (T::*f)() const) { + return ConstMemFuncRef0(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + * The second one is the parameter passed to the member function. + */ +template +inline MemFuncRef1 mem_fun_ref(Result (T::*f)(Arg)) { + return MemFuncRef1(f); +} + +/** + * Creates a binary function object from a class member function pointer. + * The first parameter passed to the function object is the object instance to + * be used for the function call. Note unlike mem_fun, it takes a reference + * as parameter. + * The second one is the parameter passed to the member function. + */ +template +inline ConstMemFuncRef1 mem_fun_ref(Result (T::*f)(Arg) const) { + return ConstMemFuncRef1(f); +} + // functor code +/** + * Generic functor object for function objects without parameters. + * + * @see Functor1 + */ template struct Functor0 { virtual ~Functor0() {} @@ -211,6 +366,18 @@ struct Functor0 { virtual Res operator()() const = 0; }; +/** + * Functor object for a class member function without parameter. + * + * Example creation: + * + * Foo bar; + * Functor0Men myFunctor(&bar, &Foo::myFunc); + * + * Example usage: + * + * myFunctor(); + */ template class Functor0Mem : public Functor0 { public: @@ -218,7 +385,7 @@ public: Functor0Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()() const { return (_t->*_func)(); } @@ -227,6 +394,38 @@ private: const FuncType _func; }; +/** + * Generic functor object for unary function objects. + * + * A typical usage for an unary function object is for executing opcodes + * in a script interpreter. To achieve that one can create an Common::Array + * object with 'Functor1 *' as type. Now after the right engine version + * has been determined and the opcode table to use is found one could easily + * add the opcode implementations like this: + * + * Common::Array *> opcodeTable; + * opcodeTable[0] = new Functor1Mem(&myEngine, &MyEngine_v1::o1_foo); + * opcodeTable[1] = new Functor1Mem(&myEngine, &MyEngine_v2::o2_foo); + * // unimplemented/unused opcode + * opcodeTable[2] = 0; + * etc. + * + * This makes it easy to add member functions of different classes as + * opcode functions to the function table. Since with the generic + * Functor1 object the only requirement for an + * function to be used is 'ScriptState' as argument and 'void' as return + * value. + * + * Now for calling the opcodes one has simple to do: + * if (opcodeTable[opcodeNum] && opcodeTable[opcodeNum]->isValid()) + * (*opcodeTable[opcodeNum])(scriptState); + * else + * warning("Unimplemented opcode %d", opcodeNum); + * + * If you want to see an real world example check the kyra engine. + * Files: engines/kyra/script.cpp and .h and engine/kyra/script_*.cpp + * are interesting for that matter. + */ template struct Functor1 : public Common::UnaryFunction { virtual ~Functor1() {} @@ -235,6 +434,13 @@ struct Functor1 : public Common::UnaryFunction { virtual Res operator()(Arg) const = 0; }; +/** + * Functor object for an unary class member function. + * Usage is like with Functor0Mem. The resulting functor object + * will take one parameter though. + * + * @see Functor0Men + */ template class Functor1Mem : public Functor1 { public: @@ -242,7 +448,7 @@ public: Functor1Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()(Arg v1) const { return (_t->*_func)(v1); } @@ -251,6 +457,11 @@ private: const FuncType _func; }; +/** + * Generic functor object for binary function objects. + * + * @see Functor1 + */ template struct Functor2 : public Common::BinaryFunction { virtual ~Functor2() {} @@ -259,6 +470,13 @@ struct Functor2 : public Common::BinaryFunction { virtual Res operator()(Arg1, Arg2) const = 0; }; +/** + * Functor object for a binary class member function. + * Usage is like with Functor0Mem. The resulting functor object + * will take two parameter though. + * + * @see Functor0Men + */ template class Functor2Mem : public Functor2 { public: @@ -266,7 +484,7 @@ public: Functor2Mem(T *t, const FuncType &func) : _t(t), _func(func) {} - bool isValid() const { return _func != 0; } + bool isValid() const { return _func != 0 && _t != 0; } Res operator()(Arg1 v1, Arg2 v2) const { return (_t->*_func)(v1, v2); } diff --git a/common/hashmap.h b/common/hashmap.h index 1bae44e98eb..ab6e737d745 100644 --- a/common/hashmap.h +++ b/common/hashmap.h @@ -58,7 +58,13 @@ #include "common/str.h" #include "common/util.h" +// FIXME: Since this define is very system dependant, +// it should be moved to the appropriate H file instead. +// Portdefs might be a good location for example +#if !defined(__SYMBIAN32__) #define USE_HASHMAP_MEMORY_POOL +#endif + #ifdef USE_HASHMAP_MEMORY_POOL #include "common/memorypool.h" // FIXME: we sadly can't assume standard C++ to be present diff --git a/common/ptr.h b/common/ptr.h index eea3c398829..c6fcaa4f75f 100644 --- a/common/ptr.h +++ b/common/ptr.h @@ -121,7 +121,7 @@ public: ~SharedPtr() { decRef(); } - SharedPtr &operator =(const SharedPtr &r) { + SharedPtr &operator=(const SharedPtr &r) { if (r._refCount) ++(*r._refCount); decRef(); @@ -134,7 +134,7 @@ public: } template - SharedPtr &operator =(const SharedPtr &r) { + SharedPtr &operator=(const SharedPtr &r) { if (r._refCount) ++(*r._refCount); decRef(); @@ -146,8 +146,8 @@ public: return *this; } - ValueType &operator *() const { assert(_pointer); return *_pointer; } - Pointer operator ->() const { assert(_pointer); return _pointer; } + ValueType &operator*() const { assert(_pointer); return *_pointer; } + Pointer operator->() const { assert(_pointer); return _pointer; } /** * Returns the plain pointer value. Be sure you know what you @@ -170,6 +170,16 @@ public: */ bool unique() const { return refCount() == 1; } + /** + * Resets the SharedPtr object to a NULL pointer. + */ + void reset() { + decRef(); + _deletion = 0; + _refCount = 0; + _pointer = 0; + } + /** * Returns the number of references to the assigned pointer. * This should just be used for debugging purposes. @@ -199,12 +209,12 @@ private: } // end of namespace Common template -bool operator ==(const Common::SharedPtr &l, const Common::SharedPtr &r) { +bool operator==(const Common::SharedPtr &l, const Common::SharedPtr &r) { return l.get() == r.get(); } template -bool operator !=(const Common::SharedPtr &l, const Common::SharedPtr &r) { +bool operator!=(const Common::SharedPtr &l, const Common::SharedPtr &r) { return l.get() != r.get(); } diff --git a/common/rect.h b/common/rect.h index 01a0a0d4313..962069bf487 100644 --- a/common/rect.h +++ b/common/rect.h @@ -187,6 +187,10 @@ struct Rect { clip(Rect(0, 0, maxw, maxh)); } + bool isEmpty() const { + return (left >= right || top >= bottom); + } + bool isValidRect() const { return (left <= right && top <= bottom); } diff --git a/common/scummsys.h b/common/scummsys.h index 939f34a5c3d..45462a69748 100644 --- a/common/scummsys.h +++ b/common/scummsys.h @@ -54,6 +54,7 @@ #pragma warning( disable : 4505 ) // turn off "unreferenced local function has been removed" #pragma warning( disable : 4510 ) // turn off "default constructor could not be generated" #pragma warning( disable : 4610 ) // turn off "struct can never be instantiated - user defined constructor required" + #pragma warning( disable : 4800 ) // turn off "forcing value to bool 'true' or 'false' (performance warning)" // vsnprintf is already defined in Visual Studio 2008 #if (_MSC_VER < 1500) diff --git a/common/str.cpp b/common/str.cpp index ad48ef6087e..5f8d4ffb7ee 100644 --- a/common/str.cpp +++ b/common/str.cpp @@ -43,32 +43,43 @@ static int computeCapacity(int len) { return ((len + 32 - 1) & ~0x1F) - 1; } -String::String(const char *str, uint32 len) -: _len(0), _str(_storage) { +String::String(const char *str) : _len(0), _str(_storage) { + if (str == 0) { + _storage[0] = 0; + _len = 0; + } else + initWithCStr(str, strlen(str)); +} + +String::String(const char *str, uint32 len) : _len(0), _str(_storage) { + initWithCStr(str, len); +} + +String::String(const char *beginP, const char *endP) : _len(0), _str(_storage) { + assert(endP >= beginP); + initWithCStr(beginP, endP - beginP); +} + +void String::initWithCStr(const char *str, uint32 len) { + assert(str); // Init _storage member explicitly (ie. without calling its constructor) // for GCC 2.95.x compatibility (see also tracker item #1602879). _storage[0] = 0; - if (str && *str) { - const uint32 tmp = strlen(str); - assert(len <= tmp); - if (len <= 0) - len = tmp; - _len = len; + _len = len; - if (len >= _builtinCapacity) { - // Not enough internal storage, so allocate more - _extern._capacity = computeCapacity(len); - _extern._refCount = 0; - _str = (char *)malloc(_extern._capacity+1); - assert(_str != 0); - } - - // Copy the string into the storage area - memcpy(_str, str, len); - _str[len] = 0; + if (len >= _builtinCapacity) { + // Not enough internal storage, so allocate more + _extern._capacity = computeCapacity(len); + _extern._refCount = 0; + _str = (char *)malloc(_extern._capacity+1); + assert(_str != 0); } + + // Copy the string into the storage area + memmove(_str, str, len); + _str[len] = 0; } String::String(const String &str) @@ -91,6 +102,8 @@ String::String(char c) _storage[0] = c; _storage[1] = 0; + // TODO/FIXME: There is no reason for the following check -- we *do* + // allow strings to contain 0 bytes! _len = (c == 0) ? 0 : 1; } @@ -98,6 +111,74 @@ String::~String() { decRefCount(_extern._refCount); } +void String::makeUnique() { + ensureCapacity(_len, true); +} + +/** + * Ensure that enough storage is available to store at least new_len + * characters plus a null byte. In addition, if we currently share + * the storage with another string, unshare it, so that we can safely + * write to the storage. + */ +void String::ensureCapacity(uint32 new_len, bool keep_old) { + bool isShared; + uint32 curCapacity, newCapacity; + char *newStorage; + int *oldRefCount = _extern._refCount; + + if (isStorageIntern()) { + isShared = false; + curCapacity = _builtinCapacity - 1; + } else { + isShared = (oldRefCount && *oldRefCount > 1); + curCapacity = _extern._capacity; + } + + // Special case: If there is enough space, and we do not share + // the storage, then there is nothing to do. + if (!isShared && new_len <= curCapacity) + return; + + if (isShared && new_len <= _builtinCapacity - 1) { + // We share the storage, but there is enough internal storage: Use that. + newStorage = _storage; + newCapacity = _builtinCapacity - 1; + } else { + // We need to allocate storage on the heap! + + // Compute a suitable new capacity limit + newCapacity = computeCapacity(new_len); + + // Allocate new storage + newStorage = (char *)malloc(newCapacity+1); + assert(newStorage); + } + + // Copy old data if needed, elsewise reset the new storage. + if (keep_old) { + assert(_len <= newCapacity); + memcpy(newStorage, _str, _len + 1); + } else { + _len = 0; + newStorage[0] = 0; + } + + // Release hold on the old storage ... + decRefCount(oldRefCount); + + // ... in favor of the new storage + _str = newStorage; + + if (!isStorageIntern()) { + // Set the ref count & capacity if we use an external storage. + // It is important to do this *after* copying any old content, + // else we would override data that has not yet been copied! + _extern._refCount = 0; + _extern._capacity = newCapacity; + } +} + void String::incRefCount() const { assert(!isStorageIntern()); if (_extern._refCount == 0) { @@ -130,11 +211,14 @@ String& String::operator =(const char *str) { uint32 len = strlen(str); ensureCapacity(len, false); _len = len; - memcpy(_str, str, len + 1); + memmove(_str, str, len + 1); return *this; } String &String::operator =(const String &str) { + if (&str == this) + return *this; + if (str.isStorageIntern()) { decRefCount(_extern._refCount); _len = str._len; @@ -154,7 +238,8 @@ String &String::operator =(const String &str) { } String& String::operator =(char c) { - ensureCapacity(1, false); + decRefCount(_extern._refCount); + _str = _storage; _len = 1; _str[0] = c; _str[1] = 0; @@ -237,10 +322,7 @@ void String::deleteLastChar() { void String::deleteChar(uint32 p) { assert(p < _len); - // Call ensureCapacity to make sure we actually *own* the storage - // to which _str points to -- we wouldn't want to modify a storage - // which other string objects are sharing, after all. - ensureCapacity(_len, true); + makeUnique(); while (p++ < _len) _str[p-1] = _str[p]; _len--; @@ -257,7 +339,7 @@ void String::clear() { void String::setChar(char c, uint32 p) { assert(p <= _len); - ensureCapacity(_len, true); + makeUnique(); _str[p] = c; } @@ -272,78 +354,36 @@ void String::insertChar(char c, uint32 p) { } void String::toLowercase() { - ensureCapacity(_len, true); + makeUnique(); for (uint32 i = 0; i < _len; ++i) _str[i] = tolower(_str[i]); } void String::toUppercase() { - ensureCapacity(_len, true); + makeUnique(); for (uint32 i = 0; i < _len; ++i) _str[i] = toupper(_str[i]); } -/** - * Ensure that enough storage is available to store at least new_len - * characters plus a null byte. In addition, if we currently share - * the storage with another string, unshare it, so that we can safely - * write to the storage. - */ -void String::ensureCapacity(uint32 new_len, bool keep_old) { - bool isShared; - uint32 curCapacity, newCapacity; - char *newStorage; - int *oldRefCount = _extern._refCount; - - if (isStorageIntern()) { - isShared = false; - curCapacity = _builtinCapacity - 1; - } else { - isShared = (oldRefCount && *oldRefCount > 1); - curCapacity = _extern._capacity; - } - - // Special case: If there is enough space, and we do not share - // the storage, then there is nothing to do. - if (!isShared && new_len <= curCapacity) +void String::trim() { + if (_len == 0) return; - if (isShared && new_len <= _builtinCapacity - 1) { - // We share the storage, but there is enough internal storage: Use that. - newStorage = _storage; - newCapacity = _builtinCapacity - 1; - } else { - // We need to allocate storage on the heap! + makeUnique(); - // Compute a suitable new capacity limit - newCapacity = computeCapacity(new_len); + // Trim trailing whitespace + while (_len >= 1 && isspace(_str[_len-1])) + _len--; + _str[_len] = 0; - // Allocate new storage - newStorage = (char *)malloc(newCapacity+1); - assert(newStorage); - } + // Trim leading whitespace + char *t = _str; + while (isspace(*t)) + t++; - // Copy old data if needed, elsewise reset the new storage. - if (keep_old) { - assert(_len <= newCapacity); - memcpy(newStorage, _str, _len + 1); - } else { - _len = 0; - newStorage[0] = 0; - } - - // Release hold on the old storage ... - decRefCount(oldRefCount); - - // ... in favor of the new storage - _str = newStorage; - - if (!isStorageIntern()) { - // Set the ref count & capacity if we use an external storage. - // It is important to do this *after* copying any old content, - // else we would override data that has not yet been copied! - _extern._refCount = 0; - _extern._capacity = newCapacity; + if (t != _str) { + _len -= t - _str; + memmove(_str, t, _len + 1); } } diff --git a/common/str.h b/common/str.h index a92ec34fff9..3479fee8e4a 100644 --- a/common/str.h +++ b/common/str.h @@ -96,10 +96,24 @@ public: static const char *emptyString; #endif + /** Construct a new empty string. */ String() : _len(0), _str(_storage) { _storage[0] = 0; } - String(const char *str, uint32 len = 0); + + /** Construct a new string from the given NULL-terminated C string. */ + String(const char *str); + + /** Construct a new string containing exactly len characters read from address str. */ + String(const char *str, uint32 len); + + /** Construct a new string containing the characters between beginP (including) and endP (excluding). */ + String(const char *beginP, const char *endP); + + /** Construct a copy of the given string. */ String(const String &str); + + /** Construct a string consisting of the given character. */ String(char c); + ~String(); String &operator =(const char *str); @@ -162,6 +176,8 @@ public: void toLowercase(); void toUppercase(); + + void trim(); uint hash() const; @@ -186,9 +202,11 @@ public: } protected: + void makeUnique(); void ensureCapacity(uint32 new_len, bool keep_old); void incRefCount() const; void decRefCount(int *oldRefCount); + void initWithCStr(const char *str, uint32 len); }; // Append two strings to form a new (temp) string diff --git a/common/stream.cpp b/common/stream.cpp index ab9804d7b65..e06cc284158 100644 --- a/common/stream.cpp +++ b/common/stream.cpp @@ -148,6 +148,61 @@ char *SeekableReadStream::readLine(char *buf, size_t bufSize) { return buf; } +char *SeekableReadStream::readLine_NEW(char *buf, size_t bufSize) { + assert(buf != 0 && bufSize > 1); + char *p = buf; + size_t len = 0; + char c = 0; + + // If end-of-file occurs before any characters are read, return NULL + // and the buffer contents remain unchanged. + if (eos() || ioFailed()) { + return 0; + } + + // Loop as long as the stream has not ended, there is still free + // space in the buffer, and the line has not ended + while (!eos() && len + 1 < bufSize && c != LF) { + c = readByte(); + + // If end-of-file occurs before any characters are read, return + // NULL and the buffer contents remain unchanged. If an error + /// occurs, return NULL and the buffer contents are indeterminate. + if (ioFailed() || (len == 0 && eos())) + return 0; + + // Check for CR or CR/LF + // * DOS and Windows use CRLF line breaks + // * Unix and OS X use LF line breaks + // * Macintosh before OS X used CR line breaks + if (c == CR) { + // Look at the next char -- is it LF? If not, seek back + c = readByte(); + if (c != LF && !eos()) + seek(-1, SEEK_CUR); + // Treat CR & CR/LF as plain LF + c = LF; + } + + *p++ = c; + len++; + } + + // FIXME: + // This should fix a bug while using readLine with Common::File + // it seems that it sets the eos flag after an invalid read + // and at the same time the ioFailed flag + // the config file parser fails out of that reason for the new themes + if (eos()) { + clearIOFailed(); + } + + // We always terminate the buffer if no error occured + *p = 0; + return buf; +} + + uint32 SubReadStream::read(void *dataPtr, uint32 dataSize) { dataSize = MIN(dataSize, _end - _pos); @@ -187,4 +242,83 @@ void SeekableSubReadStream::seek(int32 offset, int whence) { _parentStream->seek(_pos); } +BufferedReadStream::BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream) + : _parentStream(parentStream), + _disposeParentStream(disposeParentStream), + _pos(0), + _bufSize(0), + _realBufSize(bufSize) { + + assert(parentStream); + _buf = new byte[bufSize]; + assert(_buf); +} + +BufferedReadStream::~BufferedReadStream() { + if (_disposeParentStream) + delete _parentStream; + delete _buf; +} + +uint32 BufferedReadStream::read(void *dataPtr, uint32 dataSize) { + uint32 alreadyRead = 0; + const uint32 bufBytesLeft = _bufSize - _pos; + + // Check whether the data left in the buffer suffices.... + if (dataSize > bufBytesLeft) { + // Nope, we need to read more data + + // First, flush the buffer, if it is non-empty + if (0 < bufBytesLeft) { + memcpy(dataPtr, _buf + _pos, bufBytesLeft); + _pos = _bufSize; + alreadyRead += bufBytesLeft; + dataPtr = (byte *)dataPtr + bufBytesLeft; + dataSize -= bufBytesLeft; + } + + // At this point the buffer is empty. Now if the read request + // exceeds the buffer size, just satisfy it directly. + if (dataSize > _bufSize) + return alreadyRead + _parentStream->read(dataPtr, dataSize); + + // Refill the buffer. + // If we didn't read as many bytes as requested, the reason + // is EOF or an error. In that case we truncate the buffer + // size, as well as the number of bytes we are going to + // return to the caller. + _bufSize = _parentStream->read(_buf, _realBufSize); + _pos = 0; + if (dataSize > _bufSize) + dataSize = _bufSize; + } + + // Satisfy the request from the buffer + memcpy(dataPtr, _buf + _pos, dataSize); + _pos += dataSize; + return alreadyRead + dataSize; +} + +BufferedSeekableReadStream::BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream) + : BufferedReadStream(parentStream, bufSize, disposeParentStream), + _parentStream(parentStream) { +} + +void BufferedSeekableReadStream::seek(int32 offset, int whence) { + // If it is a "local" seek, we may get away with "seeking" around + // in the buffer only. + // Note: We could try to handle SEEK_END and SEEK_SET, too, but + // since they are rarely used, it seems not worth the effort. + if (whence == SEEK_CUR && (int)_pos + offset >= 0 && _pos + offset <= _bufSize) { + _pos += offset; + } else { + // Seek was not local enough, so we reset the buffer and + // just seeks normally in the parent stream. + if (whence == SEEK_CUR) + offset -= (_bufSize - _pos); + _pos = _bufSize; + _parentStream->seek(offset, whence); + } +} + } // End of namespace Common diff --git a/common/stream.h b/common/stream.h index 313a695e826..d07579c2d1a 100644 --- a/common/stream.h +++ b/common/stream.h @@ -304,13 +304,40 @@ public: * Read one line of text from a CR or CR/LF terminated plain text file. * This method is a rough analog of the (f)gets function. * + * @bug A main difference (and flaw) in this function is that there is no + * way to detect that a line exceeeds the length of the buffer. + * Code which needs this should use the new readLine_NEW() method instead. + * * @param buf the buffer to store into * @param bufSize the size of the buffer * @return a pointer to the read string, or NULL if an error occurred + * * @note The line terminator (CR or CR/LF) is stripped and not inserted * into the buffer. */ virtual char *readLine(char *buf, size_t bufSize); + + /** + * Reads at most one less than the number of characters specified + * by bufSize from the and stores them in the string buf. Reading + * stops when the end of a line is reached (CR, CR/LF or LF), at + * end-of-file or error. The newline, if any, is retained (CR and + * CR/LF are translated to LF = 0xA = '\n'). If any characters are + * read and there is no error, a `\0' character is appended to end + * the string. + * + * Upon successful completion, return a pointer to the string. If + * end-of-file occurs before any characters are read, returns NULL + * and the buffer contents remain unchanged. If an error occurs, + * returns NULL and the buffer contents are indeterminate. + * This method does not distinguish between end-of-file and error; + * callers muse use ioFailed() or eos() to determine which occurred. + * + * @param buf the buffer to store into + * @param bufSize the size of the buffer + * @return a pointer to the read string, or NULL if an error occurred + */ + virtual char *readLine_NEW(char *s, size_t bufSize); }; /** @@ -323,15 +350,17 @@ public: class SubReadStream : virtual public ReadStream { protected: ReadStream *_parentStream; + bool _disposeParentStream; uint32 _pos; uint32 _end; - bool _disposeParentStream; public: SubReadStream(ReadStream *parentStream, uint32 end, bool disposeParentStream = false) : _parentStream(parentStream), + _disposeParentStream(disposeParentStream), _pos(0), - _end(end), - _disposeParentStream(disposeParentStream) {} + _end(end) { + assert(parentStream); + } ~SubReadStream() { if (_disposeParentStream) delete _parentStream; } @@ -387,6 +416,48 @@ public: } }; +/** + * Wrapper class which adds buffering to any given ReadStream. + * Users can specify how big the buffer should be, and whether the + * wrapped stream should be disposed when the wrapper is disposed. + */ +class BufferedReadStream : virtual public ReadStream { +protected: + ReadStream *_parentStream; + bool _disposeParentStream; + byte *_buf; + uint32 _pos; + uint32 _bufSize; + uint32 _realBufSize; + +public: + BufferedReadStream(ReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false); + ~BufferedReadStream(); + + virtual bool eos() const { return (_pos == _bufSize) && _parentStream->eos(); } + virtual bool ioFailed() const { return _parentStream->ioFailed(); } + virtual void clearIOFailed() { _parentStream->clearIOFailed(); } + + virtual uint32 read(void *dataPtr, uint32 dataSize); +}; + +/** + * Wrapper class which adds buffering to any given SeekableReadStream. + * @see BufferedReadStream + */ +class BufferedSeekableReadStream : public BufferedReadStream, public SeekableReadStream { +protected: + SeekableReadStream *_parentStream; +public: + BufferedSeekableReadStream(SeekableReadStream *parentStream, uint32 bufSize, bool disposeParentStream = false); + + virtual uint32 pos() const { return _parentStream->pos() - (_bufSize - _pos); } + virtual uint32 size() const { return _parentStream->size(); } + + virtual void seek(int32 offset, int whence = SEEK_SET); +}; + + /** * Simple memory based 'stream', which implements the ReadStream interface for @@ -487,9 +558,9 @@ public: uint32 size() const { return _bufSize; } }; -/** +/** * A sort of hybrid between MemoryWriteStream and Array classes. A stream - * that grows as it's written to. + * that grows as it's written to. */ class MemoryWriteStreamDynamic : public Common::WriteStream { private: diff --git a/common/system.cpp b/common/system.cpp index 327b7246e2f..8d528258f44 100644 --- a/common/system.cpp +++ b/common/system.cpp @@ -121,28 +121,3 @@ void OSystem::clearScreen() { memset(screen->pixels, 0, screen->h * screen->pitch); unlockScreen(); } - -/* - * Include header files needed for the getFilesystemFactory() method. - * - * TODO: Remove these gradually and move the getFilesystemFactory() implementations - * to the respective backends. Then turn it into a pure virtual method of OSystem. - */ -#if defined(PALMOS_MODE) - #include "backends/fs/palmos/palmos-fs-factory.h" -#elif defined(__PLAYSTATION2__) - #include "backends/fs/ps2/ps2-fs-factory.h" -#endif - -FilesystemFactory *OSystem::getFilesystemFactory() { - #if defined(__amigaos4__) || defined(__DC__) || defined(__SYMBIAN32__) || defined(UNIX) || defined(WIN32) || defined(__WII__) || defined(__PSP__) || defined(__DS__) - // These ports already implement this function, so it should never be called. - return 0; - #elif defined(PALMOS_MODE) - return &PalmOSFilesystemFactory::instance(); - #elif defined(__PLAYSTATION2__) - return &Ps2FilesystemFactory::instance(); - #else - #error Unknown and unsupported backend in OSystem::getFilesystemFactory - #endif -} diff --git a/common/system.h b/common/system.h index 204ea9383af..b895a5cfbad 100644 --- a/common/system.h +++ b/common/system.h @@ -814,15 +814,6 @@ public: */ virtual Audio::Mixer *getMixer() = 0; - /** - * Determine the output sample rate. Audio data provided by the sound - * callback will be played using this rate. - * @note Client code other than the sound mixer should _not_ use this - * method. Instead, call Mixer::getOutputRate()! - * @return the output sample rate - */ - virtual int getOutputSampleRate() const = 0; - //@} @@ -911,7 +902,7 @@ public: * * @return FilesystemFactory* The specific factory for the current architecture. */ - virtual FilesystemFactory *getFilesystemFactory(); + virtual FilesystemFactory *getFilesystemFactory() = 0; /** diff --git a/common/unarj.cpp b/common/unarj.cpp index f3ac20c2858..da88c11fc95 100644 --- a/common/unarj.cpp +++ b/common/unarj.cpp @@ -231,7 +231,7 @@ ArjHeader *ArjFile::readHeader() { } -bool ArjFile::open(const Common::String &filename, AccessMode mode) { +bool ArjFile::open(const Common::String &filename) { if (_isOpen) error("Attempt to open another instance of archive"); diff --git a/common/unarj.h b/common/unarj.h index b015999671f..c8965968f6e 100644 --- a/common/unarj.h +++ b/common/unarj.h @@ -110,7 +110,7 @@ public: void registerArchive(const String &filename); - bool open(const Common::String &filename, AccessMode mode = kFileReadMode); + bool open(const Common::String &filename); void close(); uint32 read(void *dataPtr, uint32 dataSize); diff --git a/common/xmlparser.h b/common/xmlparser.h index 8b2d2ee956d..11028dbaa81 100644 --- a/common/xmlparser.h +++ b/common/xmlparser.h @@ -319,7 +319,7 @@ public: bool loadFile(Common::String filename) { Common::File *f = new Common::File; - if (!f->open(filename, Common::File::kFileReadMode)) + if (!f->open(filename)) return false; _fileName = filename; diff --git a/configure b/configure index 99da4e04785..037c0bf7867 100755 --- a/configure +++ b/configure @@ -100,6 +100,7 @@ add_engine saga "SAGA" yes add_engine sky "Beneath a Steel Sky" yes add_engine sword1 "Broken Sword 1" yes add_engine sword2 "Broken Sword 2" yes +add_engine tinsel "Tinsel" no add_engine touche "Touche: The Adventures of the Fifth Musketeer" yes _endian=unknown @@ -127,12 +128,12 @@ _srcdir=`dirname $0` # Determine a tmp file name, using mktemp(1) when available. if type mktemp > /dev/null 2>&1 ; then - TMPO=`mktemp` + TMPO=`mktemp /tmp/scummvm-conf.XXXXXXXXXX` else - TMPO=${_srcdir}/scummvm-conf + TMPO=scummvm-conf fi TMPC=${TMPO}.cpp -TMPLOG=${_srcdir}/config.log +TMPLOG=config.log # For cross compiling _host="" @@ -741,6 +742,10 @@ for ac_option in $@; do --enable-release) DEBFLAGS="-O2 -Wuninitialized" ;; + --enable-profiling) + CXXFLAGS="$CXXFLAGS -pg" + LDFLAGS="$LDFLAGS -pg" + ;; --with-sdl-prefix=*) arg=`echo $ac_option | cut -d '=' -f 2` _sdlpath="$arg:$arg/bin" @@ -806,6 +811,10 @@ iphone) _host_os=iphone _host_cpu=arm ;; +neuros) + _host_os=linux + _host_cpu=arm + ;; *) if test -z "$_host"; then guessed_host=`$_srcdir/config.guess` @@ -1118,6 +1127,19 @@ if test -n "$_host"; then _backend="gp2x" _build_hq_scalers="no" ;; + neuros) + echo "Cross-compiling to $_host, forcing endianness, alignment and type sizes" + DEFINES="$DEFINES -DUNIX" + _endian=little + _need_memalign=yes + add_line_to_config_h "#define NEUROS" + type_1_byte='char' + type_2_byte='short' + type_4_byte='int' + _backend='null' + _build_hq_scalers="no" + _mt32emu="no" + ;; ppc-amigaos) echo "Cross-compiling to $_host, forcing endianness, alignment and type sizes" _endian=big @@ -1624,7 +1646,7 @@ for engine in $_engines; do fi # Save the settings - defname="ENABLE_`echo $engine | tr [a-z] [A-Z]`" + defname="ENABLE_`echo $engine | tr '[a-z]' '[A-Z]'`" if test "$isbuilt" = "no" ; then add_line_to_config_mk "# $defname" else diff --git a/dists/engine-data/lure.dat b/dists/engine-data/lure.dat index dbbfc45c479..26afd45d79c 100644 Binary files a/dists/engine-data/lure.dat and b/dists/engine-data/lure.dat differ diff --git a/dists/msvc7/kyra.vcproj b/dists/msvc7/kyra.vcproj index d547288d21f..2e0e8669e9f 100644 --- a/dists/msvc7/kyra.vcproj +++ b/dists/msvc7/kyra.vcproj @@ -192,6 +192,12 @@ + + + + @@ -243,6 +249,12 @@ + + + + diff --git a/dists/msvc7/parallaction.vcproj b/dists/msvc7/parallaction.vcproj index 79ac1699952..2dac8737c2c 100644 --- a/dists/msvc7/parallaction.vcproj +++ b/dists/msvc7/parallaction.vcproj @@ -96,6 +96,9 @@ + + @@ -123,6 +126,9 @@ + + @@ -141,6 +147,12 @@ + + + + diff --git a/dists/msvc7/scummvm.sln b/dists/msvc7/scummvm.sln index e754435a8f7..24880538c6f 100644 --- a/dists/msvc7/scummvm.sln +++ b/dists/msvc7/scummvm.sln @@ -60,6 +60,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}" +EndProject Global GlobalSection(SolutionConfiguration) = preSolution Debug = Debug @@ -146,6 +148,10 @@ Global {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dists/msvc7/scummvm.vcproj b/dists/msvc7/scummvm.vcproj index 5efde227253..884cd58a5ae 100644 --- a/dists/msvc7/scummvm.vcproj +++ b/dists/msvc7/scummvm.vcproj @@ -21,7 +21,7 @@ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702" Optimization="0" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" MinimalRebuild="TRUE" ExceptionHandling="TRUE" BasicRuntimeChecks="3" @@ -38,7 +38,7 @@ Name="VCCustomBuildTool"/> - - + + @@ -416,6 +416,12 @@ + + + + diff --git a/dists/msvc71/kyra.vcproj b/dists/msvc71/kyra.vcproj index f37b1d85fce..efc37c251e6 100644 --- a/dists/msvc71/kyra.vcproj +++ b/dists/msvc71/kyra.vcproj @@ -206,6 +206,12 @@ + + + + @@ -257,6 +263,12 @@ + + + + diff --git a/dists/msvc71/parallaction.vcproj b/dists/msvc71/parallaction.vcproj index 440e7123e8b..c72fb68234f 100644 --- a/dists/msvc71/parallaction.vcproj +++ b/dists/msvc71/parallaction.vcproj @@ -110,6 +110,9 @@ + + @@ -137,6 +140,9 @@ + + @@ -155,6 +161,12 @@ + + + + diff --git a/dists/msvc71/scummvm.sln b/dists/msvc71/scummvm.sln index 71bd6a92191..0845fb04bad 100644 --- a/dists/msvc71/scummvm.sln +++ b/dists/msvc71/scummvm.sln @@ -98,6 +98,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29 ProjectSection(ProjectDependencies) = postProject EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}" + ProjectSection(ProjectDependencies) = postProject + EndProjectSection +EndProject Global GlobalSection(SolutionConfiguration) = preSolution Debug = Debug @@ -184,6 +188,10 @@ Global {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dists/msvc71/scummvm.vcproj b/dists/msvc71/scummvm.vcproj index 16f56e30045..6234c89dd58 100644 --- a/dists/msvc71/scummvm.vcproj +++ b/dists/msvc71/scummvm.vcproj @@ -21,7 +21,7 @@ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702" Optimization="0" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" MinimalRebuild="TRUE" ExceptionHandling="TRUE" BasicRuntimeChecks="3" @@ -38,7 +38,7 @@ Name="VCCustomBuildTool"/> - - + + @@ -430,6 +430,12 @@ + + + + diff --git a/dists/msvc8/kyra.vcproj b/dists/msvc8/kyra.vcproj index c99abdc5bc8..00f7749f4d7 100644 --- a/dists/msvc8/kyra.vcproj +++ b/dists/msvc8/kyra.vcproj @@ -288,6 +288,14 @@ RelativePath="..\..\engines\kyra\kyra_v2.h" > + + + + @@ -356,6 +364,14 @@ RelativePath="..\..\engines\kyra\screen_lok.h" > + + + + diff --git a/dists/msvc8/parallaction.vcproj b/dists/msvc8/parallaction.vcproj index 21a6d3bab37..e268fe1e6b1 100644 --- a/dists/msvc8/parallaction.vcproj +++ b/dists/msvc8/parallaction.vcproj @@ -160,6 +160,10 @@ + + @@ -196,6 +200,10 @@ RelativePath="..\..\engines\parallaction\disk_ns.cpp" > + + @@ -220,6 +228,14 @@ RelativePath="..\..\engines\parallaction\graphics.h" > + + + + diff --git a/dists/msvc8/scummvm.sln b/dists/msvc8/scummvm.sln index d3621cbe456..8138c77a2b1 100644 --- a/dists/msvc8/scummvm.sln +++ b/dists/msvc8/scummvm.sln @@ -61,6 +61,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -147,6 +149,10 @@ Global {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dists/msvc8/scummvm.vcproj b/dists/msvc8/scummvm.vcproj index bee416b5491..8b10a22eb60 100644 --- a/dists/msvc8/scummvm.vcproj +++ b/dists/msvc8/scummvm.vcproj @@ -42,7 +42,7 @@ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" Optimization="0" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" MinimalRebuild="true" ExceptionHandling="1" BasicRuntimeChecks="3" @@ -68,7 +68,7 @@ /> - - @@ -576,6 +572,10 @@ RelativePath="..\..\sound\mixer.h" > + + @@ -592,6 +592,14 @@ RelativePath="..\..\sound\mpu401.h" > + + + + diff --git a/dists/msvc8/tinsel.vcproj b/dists/msvc8/tinsel.vcproj new file mode 100644 index 00000000000..cb6ba0c2e89 --- /dev/null +++ b/dists/msvc8/tinsel.vcproj @@ -0,0 +1,486 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dists/msvc9/kyra.vcproj b/dists/msvc9/kyra.vcproj index f56a2733b5a..ba1b9eb9496 100644 --- a/dists/msvc9/kyra.vcproj +++ b/dists/msvc9/kyra.vcproj @@ -289,6 +289,14 @@ RelativePath="..\..\engines\kyra\kyra_v2.h" > + + + + @@ -357,6 +365,14 @@ RelativePath="..\..\engines\kyra\screen_lok.h" > + + + + diff --git a/dists/msvc9/parallaction.vcproj b/dists/msvc9/parallaction.vcproj index bdc93860f94..f901976c37a 100644 --- a/dists/msvc9/parallaction.vcproj +++ b/dists/msvc9/parallaction.vcproj @@ -161,6 +161,10 @@ + + @@ -197,6 +201,10 @@ RelativePath="..\..\engines\parallaction\disk_ns.cpp" > + + @@ -221,6 +229,14 @@ RelativePath="..\..\engines\parallaction\graphics.h" > + + + + diff --git a/dists/msvc9/scummvm.sln b/dists/msvc9/scummvm.sln index a880f0acb6b..72bfaaa8c44 100644 --- a/dists/msvc9/scummvm.sln +++ b/dists/msvc9/scummvm.sln @@ -61,6 +61,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "m4", "m4.vcproj", "{6D576A2 EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "made", "made.vcproj", "{E29B5D40-08F7-11DD-BD0B-0800200C9A66}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tinsel", "tinsel.vcproj", "{22AA7760-2C91-11DD-BD0B-0800200C9A66}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -147,6 +149,10 @@ Global {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 {E29B5D40-08F7-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.ActiveCfg = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Debug|Win32.Build.0 = Debug|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.ActiveCfg = Release|Win32 + {22AA7760-2C91-11DD-BD0B-0800200C9A66}.Release|Win32.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dists/msvc9/scummvm.vcproj b/dists/msvc9/scummvm.vcproj index a901a0c2770..3fecfefb908 100644 --- a/dists/msvc9/scummvm.vcproj +++ b/dists/msvc9/scummvm.vcproj @@ -43,7 +43,7 @@ AdditionalOptions="/wd4201 /wd4512 /wd4511 /wd4100 /wd4121 /wd4310 /wd4706 /wd4127 /wd4189 /wd4702 /wd4996" Optimization="0" AdditionalIncludeDirectories="../..;../../engines" - PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE" + PreprocessorDefinitions="WIN32;_DEBUG;_CONSOLE;USE_ZLIB;USE_MAD;USE_VORBIS;USE_MPEG2;USE_NASM;USE_MT32EMU;ENABLE_AGI;ENABLE_AGOS;ENABLE_CINE;ENABLE_CRUISE;ENABLE_DRASCULA;ENABLE_GOB;ENABLE_IGOR;ENABLE_KYRA;ENABLE_LURE;ENABLE_M4;ENABLE_MADE;ENABLE_PARALLACTION;ENABLE_QUEEN;ENABLE_SAGA;ENABLE_SCUMM;ENABLE_SKY;ENABLE_SWORD1;ENABLE_SWORD2;ENABLE_TOUCHE;ENABLE_SCUMM_7_8;ENABLE_HE;ENABLE_TINSEL" MinimalRebuild="true" ExceptionHandling="1" BasicRuntimeChecks="3" @@ -69,7 +69,7 @@ /> + + diff --git a/dists/msvc9/tinsel.vcproj b/dists/msvc9/tinsel.vcproj new file mode 100644 index 00000000000..7623290e80f --- /dev/null +++ b/dists/msvc9/tinsel.vcproj @@ -0,0 +1,487 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dists/redhat/scummvm.spec b/dists/redhat/scummvm.spec index efde789ce3d..1d3b2bce643 100644 --- a/dists/redhat/scummvm.spec +++ b/dists/redhat/scummvm.spec @@ -7,7 +7,7 @@ # Prologue information #------------------------------------------------------------------------------ Name : scummvm -Version : 0.12.0svn +Version : 0.13.0svn Release : 1 Summary : Graphic adventure game interpreter Group : Interpreters diff --git a/dists/scummvm.rc b/dists/scummvm.rc index 73763a1216a..0fb2b870712 100644 --- a/dists/scummvm.rc +++ b/dists/scummvm.rc @@ -7,8 +7,8 @@ IDI_ICON ICON DISCARDABLE "../../icons/scummvm.ico" #endif VS_VERSION_INFO VERSIONINFO - FILEVERSION 0,12,0,0 - PRODUCTVERSION 0,12,0,0 + FILEVERSION 0,13,0,0 + PRODUCTVERSION 0,13,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x1L @@ -25,13 +25,13 @@ BEGIN BEGIN VALUE "Comments", "Look! A three headed monkey (TM)! .. Nice use of the TM!\0" VALUE "FileDescription", "http://www.scummvm.org/\0" - VALUE "FileVersion", "0.12.0svn\0" + VALUE "FileVersion", "0.13.0svn\0" VALUE "InternalName", "scummvm\0" VALUE "LegalCopyright", "Copyright © 2001-2007 The ScummVM Team\0" VALUE "LegalTrademarks", "'SCUMM', and all SCUMM games are a TM of LucasArts. Simon The Sorcerer is a TM of AdventureSoft. Beneath a Steel Sky and Broken Sword are a TM of Revolution. Flight of the Amazon Queen is a TM of John Passfield and Steve Stamatiadis. \0" VALUE "OriginalFilename", "scummvm.exe\0" VALUE "ProductName", "ScummVM\0" - VALUE "ProductVersion", "0.12.0svn\0" + VALUE "ProductVersion", "0.13.0svn\0" END END BLOCK "VarFileInfo" diff --git a/dists/slackware/scummvm.SlackBuild b/dists/slackware/scummvm.SlackBuild index 415ee4c8671..6ed0ed6e57c 100755 --- a/dists/slackware/scummvm.SlackBuild +++ b/dists/slackware/scummvm.SlackBuild @@ -8,7 +8,7 @@ if [ "$TMP" = "" ]; then fi PKG=$TMP/package-scummvm -VERSION=0.12.0svn +VERSION=0.13.0svn ARCH=i486 BUILD=1 diff --git a/engines/agi/agi.cpp b/engines/agi/agi.cpp index 81aec3e351f..9d88dd73ef2 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 9b22240f83c..a9fd204d735 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 448d26a9d01..8ad5487b35a 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 fd78c650022..c92f834a3b7 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 76a0b8e76f4..2cf285d56a0 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 737f5317afd..d38a5ad33bc 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 250ff2fcfd9..010b331cf81 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 193b7347d67..9a3962ea21f 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; - h = 0; + state->x_skip *= 4; /* reached */ + + 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 { - color = (*src / 16); - if ((state->flags & kDFNonTrans) || color != 0) + 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 { + 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 add7eb96f08..d36549f1879 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 54cf4dba160..4479e2851e8 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 34e5f2cfebf..4a5c43e7063 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 44fbb4e9e0c..6758aec511b 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 94df21979c3..c7e1d6736e6 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 a1308b951de..51918b95150 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 44ada825856..cb71ed7efa6 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 055eb733c3a..8dbccebedf3 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; - } } } - ptr += frame * width * height; - currentPtr->load(ptr, type, width, height, foundFileIdx, frame, name, transparentColor); + 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(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); + } + 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 d63033e6705..b0ce55f7eed 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 c5b7fb4e3d3..2a4e7f0ab1c 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 5fa82091311..9f97bc467d8 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 cf25f1d3556..fddca078e55 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 1849d6ec3d9..9a402baee8e 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 efc33fadaf7..f6778b64573 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 710840c17e6..eaae5558128 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 47446f24104..cbddf0fc593 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::iterator &it) { int len; @@ -979,6 +1029,9 @@ void OSRenderer::renderOverlay(const Common::List::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::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 c63c79ac829..6a3aa1ef89e 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 cfb828cf3c3..e5e670c973a 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::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 7666f053525..c02e01c8ce9 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::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::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::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 e7de39649d0..7ad65eb75f3 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 70fcc0d98af..768cf0d27db 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 b39f1eff7d2..88f2dcef52d 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 402c97b1a64..27b10446201 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 f5129d28b1d..05bb2403729 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 eeac0e8809b..19576e4c1ad 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 845120c99ef..e761a0c8e4f 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 319fca5d3cd..a7642817587 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 e808de69225..f26032fe98d 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 9b4b83f4208..e4fd3349263 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 ae82832aead..f471c3c49e5 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 dcd31812427..5d85ff6cab9 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 9b98ddb2534..2fcb015fcd3 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::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::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::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 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,20 +607,294 @@ bool CineEngine::makeLoad(char *saveName) { checkForPendingDataLoadSwitch = 0; - broken = brokenSave(*fHandle); + if (g_cine->getGameType() == Cine::GType_OS) { + seqList.clear(); + currentAdditionalBgIdx = 0; + currentAdditionalBgIdx2 = 0; + // TODO: Add resetting of the following variables + // adBgVar1 = 0; + // adBgVar0 = 0; + // gfxFadeOutCompleted = 0; + } +} - 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(); +} + +bool loadScreenParams(Common::SeekableReadStream &in) { + // TODO: handle screen params (really required ?) + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + in.readUint16BE(); + return !in.ioFailed(); +} + +bool loadGlobalScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, true); + } + return !in.ioFailed(); +} + +bool loadObjectScripts(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadScriptFromSave(in, false); + } + return !in.ioFailed(); +} + +bool loadOverlayList(Common::SeekableReadStream &in) { + int size = in.readSint16BE(); + for (int i = 0; i < size; i++) { + loadOverlayFromSave(in); + } + return !in.ioFailed(); +} + +bool loadSeqList(Common::SeekableReadStream &in) { + uint size = in.readUint16BE(); + SeqListElement tmp; + for (uint i = 0; i < size; i++) { + tmp.var4 = in.readSint16BE(); + tmp.objIdx = in.readUint16BE(); + tmp.var8 = in.readSint16BE(); + tmp.frame = in.readSint16BE(); + tmp.varC = in.readSint16BE(); + tmp.varE = in.readSint16BE(); + tmp.var10 = in.readSint16BE(); + tmp.var12 = in.readSint16BE(); + tmp.var14 = in.readSint16BE(); + tmp.var16 = in.readSint16BE(); + tmp.var18 = in.readSint16BE(); + tmp.var1A = in.readSint16BE(); + tmp.var1C = in.readSint16BE(); + tmp.var1E = in.readSint16BE(); + seqList.push_back(tmp); + } + return !in.ioFailed(); +} + +bool loadZoneQuery(Common::SeekableReadStream &in) { + for (int i = 0; i < 16; i++) { + zoneQuery[i] = in.readUint16BE(); + } + return !in.ioFailed(); +} + +bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) { + char musicName[13]; + char bgNames[8][13]; + + // First check the temporary Operation Stealth savegame format header. + ChunkHeader hdr; + loadChunkHeader(in, hdr); + if (hdr.id != TEMP_OS_FORMAT_ID) { + warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame"); + return false; + } else if (hdr.version > CURRENT_OS_SAVE_VER) { + warning("loadTempSaveOS: Detected newer format version. Not loading savegame"); + return false; + } else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) { + warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break"); + } else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER + debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match)."); + } + + // There shouldn't be any data in the header's chunk currently so it's an error if there is. + if (hdr.size > 0) { + warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame"); + return false; + } + + // Ok, so we've got a correct header for a temporary Operation Stealth savegame. + // Let's start loading the plain savegame data then. + currentDisk = in.readUint16BE(); + in.read(currentPartName, 13); + in.read(currentPrcName, 13); + in.read(currentRelName, 13); + in.read(currentMsgName, 13); + + // Load the 8 background names. + for (uint i = 0; i < 8; i++) { + in.read(bgNames[i], 13); + } + + in.read(currentCtName, 13); + + // Moved the loading of current procedure, relation, + // backgrounds and Ct here because if they were at the + // end of this function then the global scripts loading + // made an array out of bounds access. In the original + // game's disassembly these aren't here but at the end. + // The difference is probably in how we handle loading + // the global scripts and some other things (i.e. the + // loading routines aren't exactly the same and subtle + // semantic differences result in having to do things + // in a different order). + { + // Not sure if this is needed with Operation Stealth... + checkDataDisk(currentDisk); + + if (strlen(currentPrcName)) { + loadPrc(currentPrcName); + } + + if (strlen(currentRelName)) { + loadRel(currentRelName); + } + + // Load first background (Uses loadBg) + if (strlen(bgNames[0])) { + loadBg(bgNames[0]); + } + + // Add backgrounds 1-7 (Uses addBackground) + for (int i = 1; i < 8; i++) { + if (strlen(bgNames[i])) { + addBackground(bgNames[i], i); + } + } + + if (strlen(currentCtName)) { + loadCtOS(currentCtName); + } + } + + loadObjectTable(in); + renderer->restorePalette(in); + globalVars.load(in, NUM_MAX_VAR); + loadZoneData(in); + loadCommandVariables(in); + in.read(commandBuffer, 0x50); + loadZoneQuery(in); + + // TODO: Use the loaded string (Current music name (String, 13 bytes)). + in.read(musicName, 13); + + // TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)). + in.readUint16BE(); + + // TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)). + in.readUint16BE(); + + renderer->_cmdY = in.readUint16BE(); + in.readUint16BE(); // Some unknown variable that seems to always be zero + allowPlayerInput = in.readUint16BE(); + playerCommand = in.readUint16BE(); + commandVar1 = in.readUint16BE(); + isDrawCommandEnabled = in.readUint16BE(); + var5 = in.readUint16BE(); + var4 = in.readUint16BE(); + var3 = in.readUint16BE(); + var2 = in.readUint16BE(); + commandVar2 = in.readUint16BE(); + renderer->_messageBg = in.readUint16BE(); + + // TODO: Use the loaded value (adBgVar1 (Uint16BE)). + in.readUint16BE(); + + currentAdditionalBgIdx = in.readSint16BE(); + currentAdditionalBgIdx2 = in.readSint16BE(); + + // TODO: Check whether the scroll value really gets used correctly after this. + // Note that the backgrounds are loaded only later than this value is set. + renderer->setScroll(in.readUint16BE()); + + // TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?). + in.readUint16BE(); + + disableSystemMenu = in.readUint16BE(); + + // TODO: adBgVar1 = 1 here + + // Load the animDataTable entries + in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth). + in.readUint16BE(); // Entry size (36 in the PC version of Operation Stealth). + loadResourcesFromSave(in, ANIMSIZE_30_PTRS_INTACT); + + loadScreenParams(in); + loadGlobalScripts(in); + loadObjectScripts(in); + loadSeqList(in); + loadOverlayList(in); + loadBgIncrustFromSave(in); + + // Left this here instead of moving it earlier in this function with + // the other current value loadings (e.g. loading of current procedure, + // current backgrounds etc). Mostly emulating the way we've handled + // Future Wars savegames and hoping that things work out. + if (strlen(currentMsgName)) { + loadMsg(currentMsgName); + } + + // TODO: Add current music loading and playing here + // TODO: Palette handling? + + if (in.pos() == in.size()) { + debug(3, "loadTempSaveOS: Loaded the whole savefile."); + } else { + warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over"); + } + + return !in.ioFailed(); +} + +bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) { + char bgName[13]; + + // At savefile position 0x0000: + currentDisk = in.readUint16BE(); + + // At 0x0002: + in.read(currentPartName, 13); + // At 0x000F: + in.read(currentDatName, 13); + + // At 0x001C: + saveVar2 = in.readSint16BE(); + + // 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); @@ -495,87 +918,70 @@ bool CineEngine::makeLoad(char *saveName) { loadCtFW(currentCtName); } - fHandle->readUint16BE(); - fHandle->readUint16BE(); + // At 0x005F: + loadObjectTable(in); - 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(); - } + // At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32): + renderer->restorePalette(in); - renderer->restorePalette(*fHandle); + // At 0x2083 (i.e. 0x2043 + 16 * 2 * 2): + globalVars.load(in, NUM_MAX_VAR); - globalVars.load(*fHandle, NUM_MAX_VAR - 1); + // At 0x2281 (i.e. 0x2083 + 255 * 2): + loadZoneData(in); - for (i = 0; i < 16; i++) { - zoneData[i] = fHandle->readUint16BE(); - } + // At 0x22A1 (i.e. 0x2281 + 16 * 2): + loadCommandVariables(in); - for (i = 0; i < 4; i++) { - commandVar3[i] = fHandle->readUint16BE(); - } - - fHandle->read(commandBuffer, 0x50); + // At 0x22A9 (i.e. 0x22A1 + 4 * 2): + in.read(commandBuffer, 0x50); renderer->setCommand(commandBuffer); - renderer->_cmdY = fHandle->readUint16BE(); + // At 0x22F9 (i.e. 0x22A9 + 0x50): + renderer->_cmdY = in.readUint16BE(); - 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(); + // 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(); - renderer->_messageBg = fHandle->readUint16BE(); + // At 0x230F: + renderer->_messageBg = in.readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); + // At 0x2311: + in.readUint16BE(); + // At 0x2313: + in.readUint16BE(); - loadResourcesFromSave(*fHandle, broken); + // At 0x2315: + loadResourcesFromSave(in, saveGameFormat); - // TODO: handle screen params (really required ?) - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - fHandle->readUint16BE(); - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, true); - } - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadScriptFromSave(fHandle, false); - } - - size = fHandle->readSint16BE(); - for (i = 0; i < size; i++) { - loadOverlayFromSave(*fHandle); - } - - loadBgIncrustFromSave(*fHandle); - - delete fHandle; + loadScreenParams(in); + loadGlobalScripts(in); + loadObjectScripts(in); + loadOverlayList(in); + loadBgIncrustFromSave(in); if (strlen(currentMsgName)) { loadMsg(currentMsgName); } - setMouseCursor(MOUSE_CURSOR_NORMAL); - if (strlen(currentDatName)) { /* i = saveVar2; saveVar2 = 0; @@ -585,135 +991,139 @@ bool CineEngine::makeLoad(char *saveName) { }*/ } - return true; + return !in.ioFailed(); } -void makeSave(char *saveFileName) { - int16 i; - Common::OutSaveFile *fHandle; +bool CineEngine::makeLoad(char *saveName) { + Common::SharedPtr saveFile(g_saveFileMan->openForLoading(saveName)); - fHandle = g_saveFileMan->openForSaving(saveFileName); + if (!saveFile) { + drawString(otherMessages[0], 0); + waitPlayerInput(); + // restoreScreen(); + checkDataDisk(-1); + return false; + } + + 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 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); + } + } + + setMouseCursor(MOUSE_CURSOR_NORMAL); + + return result; +} + +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); +} + +void CineEngine::makeSave(char *saveFileName) { + Common::SharedPtr fHandle(g_saveFileMan->openForSaving(saveFileName)); + + setMouseCursor(MOUSE_CURSOR_DISK); if (!fHandle) { drawString(otherMessages[1], 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]); - } - - for (i = 0; i < 4; i++) { - fHandle->writeUint16BE(commandVar3[i]); - } - - 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); - - for (i = 0; i < NUM_MAX_ANIMDATA; i++) { - animDataTable[i].save(*fHandle); - } - - fHandle->writeUint16BE(0); // Screen params, unhandled - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - fHandle->writeUint16BE(0); - - { - ScriptList::iterator it; - fHandle->writeUint16BE(globalScripts.size()); - for (it = globalScripts.begin(); it != globalScripts.end(); ++it) { - (*it)->save(*fHandle); - } - - fHandle->writeUint16BE(objectScripts.size()); - for (it = objectScripts.begin(); it != objectScripts.end(); ++it) { - (*it)->save(*fHandle); + } else { + if (g_cine->getGameType() == GType_FW) { + makeSaveFW(*fHandle); + } else { + makeSaveOS(*fHandle); } } - { - Common::List::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); - } - } - - Common::List::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::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::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; + debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d", + param1, objIdx, ptr, element.var8, element.var14, param3); + + // In the original an error string is set and 0 is returned if the following doesn't hold assert(ptr); - assert(param4); - - dummyU16 = READ_BE_UINT16((currentPtr + param1 * 2) + 8); + // 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::iterator it, bObjsCutPoint; + Common::List 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 91662c16ffd..d87679ca080 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 60afe5fa4c9..c9c27ada492 100644 --- a/engines/cruise/cruise_main.h +++ b/engines/cruise/cruise_main.h @@ -28,7 +28,7 @@ #include #include -#include +#include // 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 e4a3dde78f6..b2ff2631c07 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 feb6cb93ca4..06868494b5c 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 ce67cc2c0e7..8bb73d8dd1e 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 64591a856ef..67993bfb6c0 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 ad57bce6181..6a93f21e556 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 6fe28bdbdca..37dddf4b7ed 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 cfb8e69f3e1..4dba9131735 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 8ae11b87559..bcf566d1344 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 a990dbeda5b..4b4c79d1eb3 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 8351f2ecfb6..63a0f8f45bf 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 f68ce47783c..2d3a10b5704 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 a3fe0ebbe21..34443251d86 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 ae2b53bc310..041658baeac 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 e7aed0790e4..5add0b9cea0 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); + _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 = (int32 *)VAR_ADDRESS(114); + _itemInPocketVarPtr.set(*_vm->_inter->_variables, 456); - *_itemInPocketVarPtr = -2; + _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 3fd8a9f93be..2100bcbdacb 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; + VariableReference _some0ValPtr; - int32 *_gobRetVarPtr; - int32 *_curGobVarPtr; - int32 *_curGobXPosVarPtr; - int32 *_curGobYPosVarPtr; - int32 *_itemInPocketVarPtr; + VariableReference _gobRetVarPtr; + VariableReference _curGobVarPtr; + VariableReference _curGobXPosVarPtr; + VariableReference _curGobYPosVarPtr; + VariableReference _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; + 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; - 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 _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 9144e350708..d763aeb01c6 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 9c39653a1d1..02e7f99cbdb 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 60b3974d6d9..b684be6c075 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 e2b8d651124..865d188a2e0 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 ¶ms) { 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 ¶ms) { void Inter_v1::o1_setState(OpGobParams ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { void Inter_v1::o1_setNoTick(OpGobParams ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { - *params.retVarPtr = params.objDesc->state; + params.retVarPtr = (uint32) params.objDesc->state; } void Inter_v1::o1_getCurFrame(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->curFrame; + params.retVarPtr = (uint32) params.objDesc->curFrame; } void Inter_v1::o1_getNextState(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->nextState; + params.retVarPtr = (uint32) params.objDesc->nextState; } void Inter_v1::o1_getMultState(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->multState; + params.retVarPtr = (uint32) params.objDesc->multState; } void Inter_v1::o1_getOrder(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->order; + params.retVarPtr = (uint32) params.objDesc->order; } void Inter_v1::o1_getActionStartState(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->actionStartState; + params.retVarPtr = (uint32) params.objDesc->actionStartState; } void Inter_v1::o1_getCurLookDir(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->curLookDir; + params.retVarPtr = (uint32) params.objDesc->curLookDir; } void Inter_v1::o1_getType(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->type; + params.retVarPtr = (uint32) params.objDesc->type; } void Inter_v1::o1_getNoTick(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->noTick; + params.retVarPtr = (uint32) params.objDesc->noTick; } void Inter_v1::o1_getPickable(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->pickable; + params.retVarPtr = (uint32) params.objDesc->pickable; } void Inter_v1::o1_getObjMaxFrame(OpGobParams ¶ms) { - *params.retVarPtr = _vm->_goblin->getObjMaxFrame(params.objDesc); + params.retVarPtr = (uint32) _vm->_goblin->getObjMaxFrame(params.objDesc); } void Inter_v1::o1_getXPos(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->xPos; + params.retVarPtr = (uint32) params.objDesc->xPos; } void Inter_v1::o1_getYPos(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->yPos; + params.retVarPtr = (uint32) params.objDesc->yPos; } void Inter_v1::o1_getDoAnim(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->doAnim; + params.retVarPtr = (uint32) params.objDesc->doAnim; } void Inter_v1::o1_getRelaxTime(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->relaxTime; + params.retVarPtr = (uint32) params.objDesc->relaxTime; } void Inter_v1::o1_getMaxTick(OpGobParams ¶ms) { - *params.retVarPtr = params.objDesc->maxTick; + params.retVarPtr = (uint32) params.objDesc->maxTick; } void Inter_v1::o1_manipulateMap(OpGobParams ¶ms) { @@ -2435,9 +2444,9 @@ void Inter_v1::o1_getItem(OpGobParams ¶ms) { 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 ¶ms) { @@ -2460,9 +2469,9 @@ void Inter_v1::o1_getItemIndirect(OpGobParams ¶ms) { 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 ¶ms) { @@ -2500,11 +2509,11 @@ void Inter_v1::o1_setGoblinPosH(OpGobParams ¶ms) { 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 ¶ms) { void Inter_v1::o1_getGoblinPosXH(OpGobParams ¶ms) { 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 ¶ms) { 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 ¶ms) { @@ -2539,14 +2548,14 @@ void Inter_v1::o1_setGoblinMultState(OpGobParams ¶ms) { 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 ¶ms) { _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 ¶ms) { } void Inter_v1::o1_getItemIdInPocket(OpGobParams ¶ms) { - *params.retVarPtr = _vm->_goblin->_itemIdInPocket; + params.retVarPtr = (uint32) _vm->_goblin->_itemIdInPocket; } void Inter_v1::o1_getItemIndInPocket(OpGobParams ¶ms) { - *params.retVarPtr = _vm->_goblin->_itemIndInPocket; + params.retVarPtr = (uint32) _vm->_goblin->_itemIndInPocket; } void Inter_v1::o1_setGoblinPos(OpGobParams ¶ms) { @@ -2632,10 +2641,10 @@ void Inter_v1::o1_setGoblinPos(OpGobParams ¶ms) { 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 ¶ms) { 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 ¶ms) { 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 ¶ms) { void Inter_v1::o1_getGoblinPosX(OpGobParams ¶ms) { int16 item = load16(); - *params.retVarPtr = _vm->_goblin->_gobPositions[item].x; + params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].x; } void Inter_v1::o1_getGoblinPosY(OpGobParams ¶ms) { int16 item = load16(); - *params.retVarPtr = _vm->_goblin->_gobPositions[item].y; + params.retVarPtr = (uint32) _vm->_goblin->_gobPositions[item].y; } void Inter_v1::o1_clearPathExistence(OpGobParams ¶ms) { @@ -2741,9 +2750,9 @@ void Inter_v1::o1_getObjectIntersect(OpGobParams ¶ms) { 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 ¶ms) { @@ -2753,9 +2762,9 @@ void Inter_v1::o1_getGoblinIntersect(OpGobParams ¶ms) { 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 ¶ms) { @@ -2886,7 +2895,7 @@ void Inter_v1::o1_initGoblin(OpGobParams ¶ms) { _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 d8c33fcce66..2f1d2ec0be0 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 3d6a7942f91..b9373d48b34 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 aaf2e2826cf..3bb3af17b37 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 22683437e72..a369e7d2972 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 3a83ac1867e..20a81174e51 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 2788716858e..fa9f8ea7a9f 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 29f7ee2594b..52c3a9b2608 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 _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 a92fe8cf01f..fc11950368b 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 67879db3d14..dab5fd9385d 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 a6548dd82d2..0bd3dc03e67 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 6b52cdbbd6a..33e540ace4a 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 b59510e4bb6..07b5a737db4 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 5789885a996..3e8e6b5c1bc 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 0eea2f6547f..805aaeb839e 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 5989ed38ee4..32f160a6bd8 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 909d39a63b0..aa47e6cf84d 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 018709f34fc..4d4fb97762d 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 1baa78b203c..75d5537e4a3 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 344121b5034..2d592069d2e 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 555934cb7fc..7d56743af54 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 6fa30c9e9a7..35b343fc25c 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 607ef0b605f..49081c7ae2a 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 8eb62c20c2d..2c2903d0b30 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 57f0dcc24ae..d3de6217078 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 8a49b8e155e..a4e5b58364f 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 5af138373c7..5f9f6f91a3b 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 1cc1d728bfa..85c03dc1bbd 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 4f38ceca982..50cabc421e4 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 12da3388435..2e704f2aa26 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 24f7aad6143..6fdf30fff88 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 00000000000..ef1121baa09 --- /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::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::const_iterator i = _eventList.begin(); i != _eventList.end(); ++i) { + if (i->causedSkip) + return true; + } + return false; +} + +void LoLEngine::resetSkipFlag(bool removeEvent) { + for (Common::List::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 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 *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(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 00000000000..ee54f8abbb8 --- /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 _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 _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 ebb63b4b4ef..e059a8ce4b0 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 afd7eacfdad..92818aafe14 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) { @@ -98,6 +100,8 @@ bool Resource::reset() { loadFileList("FILEDATA.FDT"); + return true; + } else if (_vm->game() == GI_LOL) { return true; } @@ -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 1882386b03b..62df683ea2c 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 e4ae67f7519..53c269a926a 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 f00c47ceea1..74f7bc6de9e 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; - } + int refreshed = fadePalStep(palData, diff); - fadePal[i] = (uint8)c2; - } - } - - if (!needRefresh) - break; - - 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(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 f8c85a2bacf..99ba2d7c5f4 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 00000000000..c6b47a9ca9b --- /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 00000000000..38df3ca897e --- /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 e26ef87bad4..c6ea6a93e80 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 f624228445e..7bbdc4b6c3c 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 ef3e259cfa7..b10a4b32bf7 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 de52093f66f..2b97a832897 100644 --- a/engines/kyra/script.h +++ b/engines/kyra/script.h @@ -36,6 +36,8 @@ struct EMCState; typedef Common::Functor1 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 91fbfb3e49c..e3a8bf95bcd 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 OpcodeV2; typedef Common::Functor2Mem 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 *table = 0; diff --git a/engines/kyra/script_tim.cpp b/engines/kyra/script_tim.cpp index 4ad64644249..7993fb8de6c 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 *opcodes) { @@ -122,6 +139,8 @@ TIM *TIMInterpreter::load(const char *filename, const Common::Arrayfunc[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 cd715ff4efd..68ef23fd6c9 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 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 *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 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 73d69ef10ca..dfda5bf859e 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 b30568c7e22..3a497a258f0 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 f56c43aabdf..c8749dc06b5 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 1baeb3064a0..cebfdf491fd 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 68a2f0be9ce..0ceb288b8ab 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 8a1d16a6b15..b43d72ebce5 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 4265533507f..0f2b916c9d4 100644 --- a/engines/kyra/sound_towns.cpp +++ b/engines/kyra/sound_towns.cpp @@ -34,18 +34,16 @@ #include "common/util.h" -#include - #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 abdf115c1e2..9a4b40902e6 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 36cd75b1ab8..1db8ee84744 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 06d3b1984e5..ea760ddb4f1 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 d66f446247e..1c5b40e54b9 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 603102a0991..922e1207d02 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 cecc4154993..0b4ef060812 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 b5b7769e34e..fcc63083754 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 03161032c04..badc3c96b0e 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 1481e22775f..9420079346b 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 f2997d5d179..68de2600611 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 de09f982d12..92cea948f98 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 839298d1c5b..285f66e4e23 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 80b21119ff3..0488f17d8fa 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 024cd591f50..5b8bdab9d6c 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 12d9a24d37c..58c68979d14 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 a0133db2d6d..a80e8bf7104 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 beda178344a..1846f1c1e74 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 60e608c148d..84fc77656f9 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 9bf964ee960..777356467bd 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 3e80d0f1e0d..c51daa84c48 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 3a8b5d24a84..b74e598c6ce 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 55e0e90732d..3497b5b46f7 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 dc7dbdee870..e5870bfeec5 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 59ec487c375..dc45dc4d2f3 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,25 +206,21 @@ void MadeEngine::handleEvents() { break; case Common::EVENT_LBUTTONDOWN: + _eventNum = 2; + break; + + case Common::EVENT_LBUTTONUP: _eventNum = 1; break; - /* - case Common::EVENT_LBUTTONUP: - _eventNum = 2; // TODO: Is this correct? - break; - */ - case Common::EVENT_RBUTTONDOWN: + _eventNum = 4; + break; + + case Common::EVENT_RBUTTONUP: _eventNum = 3; break; - /* - case Common::EVENT_RBUTTONUP: - eventNum = 4; // TODO: Is this correct? - break; - */ - case Common::EVENT_KEYDOWN: _eventKey = event.kbd.ascii; // For unknown reasons, the game accepts ASCII code @@ -239,7 +239,7 @@ void MadeEngine::handleEvents() { } } - + AudioCD.updateCD(); } diff --git a/engines/made/made.h b/engines/made/made.h index 461941e5cfd..971961c8673 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 1a8ca9c50a5..831f1fab8e6 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 cecd0c8968f..0c22d40259e 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 932447a1ebe..d697e24b040 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 00000000000..81b32adb15c --- /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 68e6a70ffbd..761e11dc7df 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(); + cleanupGame(); + _gfx->setPalette(_gfx->_palette); - uint id[4]; + startEndPartSequence(); - 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(); - - _gfx->freeLabels(); - - if (allPartsComplete()) { - scheduleLocationSwitch("estgrotta.drki"); - } else { - selectStartLocation(); - } - - 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 3c90a76f615..f57976594ed 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 8841b9ca40d..0476b01454d 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 70db637699c..21584a05256 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,117 +41,126 @@ 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); + void runQuestion(); + void runAnswer(); + void nextQuestion(); + void nextAnswer(); + + bool checkPassword(); + void resetPassword(); + void accumPassword(uint16 ascii); }; -uint16 DialogueManager::askPassword() { - debugC(3, kDebugExec, "checkDialoguePassword()"); +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"); - uint16 passwordLen = 0; - _password[0] = '\0'; + _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; - _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); + _askPassword = false; + _q = _dialogue->_questions[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)) { - - 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))) { - - break; - - } else { - passwordLen = 0; - _password[0] = '\0'; - changed = true; - } - - } - - g_system->delayMillis(20); - - } - - _vm->_gfx->hideDialogueStuff(); - - return 0; + _cmdList = 0; + _answerId = 0; + _state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER; } - +DialogueManager::~DialogueManager() { + if (isNpc) { + delete _questioner; + } + _z = nullZonePtr; +} bool DialogueManager::displayAnswer(uint16 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() { +bool DialogueManager::displayQuestion() { + if (!scumm_stricmp(_q->_text, "NULL")) return false; - if (!scumm_stricmp(_q->_text, "NULL")) return; - - _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; + return true; } -uint16 DialogueManager::getAnswer() { - uint16 answer = 0; +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))); +} - if (_askPassword == false) { - answer = selectAnswer(); - } else { - answer = askPassword(); +void DialogueManager::resetPassword() { + _passwordLen = 0; + _password[0] = '\0'; + _passwordChanged = true; +} + +void DialogueManager::accumPassword(uint16 ascii) { + if (!isdigit(ascii)) { + return; } - debugC(3, kDebugExec, "runDialogue: user selected answer #%i", answer); - - return answer; + _password[_passwordLen] = ascii; + _passwordLen++; + _password[_passwordLen] = '\0'; + _passwordChanged = true; } -void DialogueManager::run() { +int16 DialogueManager::askPassword() { - _askPassword = false; - CommandList *cmdlist = NULL; + if (_isKeyDown) { + accumPassword(_downKey); + } - _q = _dialogue->_questions[0]; - int16 answer; + if (_passwordChanged) { + _vm->_balloonMan->setBalloonText(0, _q->_answers[0]->_text, 3); + _passwordChanged = false; + } - while (_q) { - - answer = 0; - - displayQuestion(); - if (_q->_answers[0] == NULL) break; - - if (scumm_stricmp(_q->_answers[0]->_text, "NULL")) { - if (!displayAnswers()) break; - answer = getAnswer(); - cmdlist = &_q->_answers[answer]->_commands; + if ((_passwordLen == MAX_PASSWORD_LENGTH) || ((_isKeyDown) && (_downKey == Common::KEYCODE_RETURN))) { + if (checkPassword()) { + return 0; + } else { + resetPassword(); } - - _q = _q->_answers[answer]->_following._question; } - if (cmdlist) - _vm->runCommands(*cmdlist); - + return -1; } -int16 DialogueManager::selectAnswer() { +int16 DialogueManager::selectAnswer1() { - int16 numAvailableAnswers = _numVisAnswers; - - int id = _vm->_gfx->setItem(_answerer, ANSWER_CHARACTER_X, ANSWER_CHARACTER_Y); - _vm->_gfx->setItemFrame(id, _q->_answers[0]->_mood & 0xF); - - if (numAvailableAnswers == 1) { - _vm->_gfx->setBalloonText(0, _q->_answers[0]->_text, 0); - _vm->_input->waitUntilLeftClick(); - _vm->_gfx->hideDialogueStuff(); + if (_mouseButtons == kMouseLeftUp) { return 0; } - int oldSelection = -1; - int selection; + return -1; +} - uint32 event; - Common::Point p; - while (true) { +int16 DialogueManager::selectAnswerN() { - _vm->_input->readInput(); - _vm->_input->getCursorPos(p); - event = _vm->_input->getLastButtonEvent(); - selection = _vm->_gfx->hitTestDialogueBalloon(p.x, p.y); + _selection = _vm->_balloonMan->hitTestDialogueBalloon(_mousePos.x, _mousePos.y); - if (selection != oldSelection) { - if (oldSelection != -1) { - _vm->_gfx->setBalloonText(oldSelection, _q->_answers[_visAnswers[oldSelection]]->_text, 3); - } - - if (selection != -1) { - _vm->_gfx->setBalloonText(selection, _q->_answers[_visAnswers[selection]]->_text, 0); - _vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[selection]]->_mood & 0xF); - } + if (_selection != _oldSelection) { + if (_oldSelection != -1) { + _vm->_balloonMan->setBalloonText(_oldSelection, _q->_answers[_visAnswers[_oldSelection]]->_text, 3); } - if ((selection != -1) && (event == kMouseLeftUp)) { - break; + if (_selection != -1) { + _vm->_balloonMan->setBalloonText(_selection, _q->_answers[_visAnswers[_selection]]->_text, 0); + _vm->_gfx->setItemFrame(0, _q->_answers[_visAnswers[_selection]]->_mood & 0xF); } - - _vm->_gfx->updateScreen(); - g_system->delayMillis(20); - - oldSelection = selection; } - _vm->_gfx->hideDialogueStuff(); + _oldSelection = _selection; + + if ((_mouseButtons == kMouseLeftUp) && (_selection != -1)) { + return _visAnswers[_selection]; + } + + return -1; +} + +void DialogueManager::runQuestion() { + debugC(9, kDebugDialogue, "runQuestion\n"); + + if (_mouseButtons == kMouseLeftUp) { + _vm->hideDialogueStuff(); + _state = NEXT_ANSWER; + } - return _visAnswers[selection]; } -void Parallaction::runDialogue(SpeakData *data) { - debugC(1, kDebugExec, "runDialogue: starting dialogue '%s'", data->_name); +void DialogueManager::nextAnswer() { + debugC(9, kDebugDialogue, "nextAnswer\n"); - DialogueManager man(this, data); - man.run(); + if (_q->_answers[0] == NULL) { + _state = DIALOGUE_OVER; + return; + } + + 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"); + + _q = _q->_answers[_answerId]->_following._question; + if (_q == 0) { + _state = DIALOGUE_OVER; + } else { + _state = displayQuestion() ? RUN_QUESTION : NEXT_ANSWER; + } +} + + +void DialogueManager::run() { + + // cache event data + _mouseButtons = _vm->_input->getLastButtonEvent(); + _vm->_input->getCursorPos(_mousePos); + _isKeyDown = _vm->_input->getLastKeyDown(_downKey); + + switch (_state) { + case RUN_QUESTION: + runQuestion(); + break; + + case NEXT_ANSWER: + nextAnswer(); + break; + + case NEXT_QUESTION: + nextQuestion(); + break; + + 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); + } + + // 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; + + _cmdExec->run(z->_commands, z); +} + +void Parallaction::runDialogueFrame() { + if (_input->_inputMode != Input::kInputModeDialogue) { + return; + } + + _dialogueMan->run(); + + if (_dialogueMan->isOver()) { + exitDialogueMode(); + } return; } diff --git a/engines/parallaction/disk.h b/engines/parallaction/disk.h index b76c66aead1..341229a649a 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 5e883278797..cd57ec8822c 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; + Common::String langs[4] = { "it", "fr", "en", "ge" }; + FilesystemNode locDir = _partDir.getChild(langs[_language]); - 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 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::String path(name); + path += ".scr"; + FilesystemNode node = _scrDir.getChild(path); + if (!node.exists()) { + errorFileNotFound(_scrDir, path); + } + Common::File *stream = new Common::File; - - char path[PATH_LEN]; - sprintf(path, "%s/scripts/%s.scr", _partPath, name); - if (!stream->open(path)) - errorFileNotFound(path); - + 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) { } +DosDemo_br::~DosDemo_br() { + +} + +Common::String DosDemo_br::selectArchive(const Common::String& name) { + debugC(5, kDebugDisk, "DosDemo_br::selectArchive"); + + Common::String oldPath; + if (_partDir.exists()) { + oldPath = _partDir.getDisplayName(); + } + + _partDir = _baseDir; + + _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; +} + + + + + + +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"); +} + + AmigaDisk_br::~AmigaDisk_br() { } -/* - FIXME: mask values are not computed correctly for level 1 and 2 - - 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) { - - 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 }; - - byte plane0[40]; - byte plane1[40]; - - 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]; - } - - } -} - -void AmigaDisk_br::loadBackground(BackgroundInfo& info, const char *name) { - - char path[PATH_LEN]; - sprintf(path, "%s", name); - - 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 cdbe3458a72..55e6fc5e771 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 00000000000..22e75744f13 --- /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 Opcode; +typedef Common::Array 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::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::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 3b67b4c3709..0b7400f0f7f 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 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 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 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 *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); +} - SetOpcodeTable(_instructionOpcodes); +CommandExec_br::CommandExec_br(Parallaction_br* vm) : CommandExec_ns(vm), _vm(vm) { + +} + +CommandExec_br::~CommandExec_br() { + +} + +void ProgramExec_br::init() { + + Common::Array *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 a4b372f42a9..99a492863b9 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 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 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 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); -void Parallaction_ns::runScripts() { + _ctxt.ip = script->_ip; + _ctxt.anim = a; + _ctxt.program = script; + _ctxt.suspend = false; + _ctxt.modCounter = _modCounter; + + InstructionList::iterator inst; + for ( ; (a->_flags & kFlagsActing) ; ) { + + inst = _ctxt.ip; + _ctxt.inst = inst; + _ctxt.ip++; + + debugC(9, kDebugExec, "inst [%02i] %s\n", (*inst)->_index, _instructionNames[(*inst)->_index - 1]); + + script->_status = kProgramRunning; + + (*_opcodes[(*inst)->_index])(); + + if (_ctxt.suspend) + break; + + } + script->_ip = _ctxt.ip; + +} + +void ProgramExec::runScripts(ProgramList::iterator first, ProgramList::iterator last) { if (_engineFlags & kEnginePauseJobs) { return; } - debugC(9, kDebugExec, "runScripts"); - - static uint16 modCounter = 0; - - 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; - - debugC(9, kDebugExec, "Animation: %s, instruction: %i", a->_name, (*inst)->_index); //_instructionNamesRes[(*inst)->_index - 1]); - - _instRunCtxt.inst = inst; - _instRunCtxt.anim = AnimationPtr(a); - _instRunCtxt.program = *it; - _instRunCtxt.modCounter = modCounter; - _instRunCtxt.suspend = false; - - (*_instructionOpcodes[(*inst)->_index])(); - - inst = _instRunCtxt.inst; // handles endloop correctly - - if (_instRunCtxt.suspend) - goto label1; - - inst++; - } - - (*it)->_ip = ++inst; - -label1: if (a->_flags & kFlagsCharacter) a->_z = a->_top + a->height(); } - _char._ani->_z = _char._ani->height() + _char._ani->_top; - if (_char._ani->gfxobj) { - _char._ani->gfxobj->z = _char._ani->_z; - } - modCounter++; + _modCounter++; return; } +void CommandExec::runList(CommandList::iterator first, CommandList::iterator last) { -void Parallaction::runCommands(CommandList& list, ZonePtr z) { - if (list.size() == 0) - return; + uint32 useFlags = 0; + bool useLocalFlags; - debugC(3, kDebugExec, "runCommands"); - - CommandList::iterator it = list.begin(); - for ( ; it != list.end(); it++) { - - CommandPtr cmd = *it; - uint32 v8 = getLocationFlags(); + _ctxt.suspend = false; + for ( ; first != last; first++) { if (_engineFlags & kEngineQuit) break; + CommandPtr cmd = *first; + if (cmd->_flagsOn & kFlagsGlobal) { - v8 = _commandFlags | kFlagsGlobal; + useFlags = _commandFlags | kFlagsGlobal; + useLocalFlags = false; + } else { + useFlags = _vm->getLocationFlags(); + useLocalFlags = true; } - if ((cmd->_flagsOn & v8) != cmd->_flagsOn) continue; - if ((cmd->_flagsOff & ~v8) != cmd->_flagsOff) continue; + bool onMatch = (cmd->_flagsOn & useFlags) == cmd->_flagsOn; + bool offMatch = (cmd->_flagsOff & ~useFlags) == cmd->_flagsOff; -// debugC(3, kDebugExec, "runCommands[%i]: %s (on: %x, off: %x)", cmd->_id, _commandsNamesRes[cmd->_id-1], cmd->_flagsOn, cmd->_flagsOff); + debugC(3, kDebugExec, "runCommands[%i] (on: %x, off: %x), (%s = %x)", cmd->_id, cmd->_flagsOn, cmd->_flagsOff, + useLocalFlags ? "LOCALFLAGS" : "GLOBALFLAGS", useFlags); - _cmdRunCtxt.z = z; - _cmdRunCtxt.cmd = cmd; + if (!onMatch || !offMatch) continue; - (*_commandOpcodes[cmd->_id])(); + _ctxt.z = _execZone; + _ctxt.cmd = cmd; + + (*_opcodes[cmd->_id])(); + + if (_ctxt.suspend) { + createSuspendList(++first, last); + return; + } } - debugC(3, kDebugExec, "runCommands completed"); - - return; - } +void CommandExec::run(CommandList& list, ZonePtr z) { + if (list.size() == 0) { + debugC(3, kDebugExec, "runCommands: nothing to do"); + return; + } + + _execZone = z; + + debugC(3, kDebugExec, "runCommands starting"); + runList(list.begin(), list.end()); + debugC(3, kDebugExec, "runCommands completed"); +} + +void CommandExec::createSuspendList(CommandList::iterator first, CommandList::iterator last) { + if (first == last) { + return; + } + + debugC(3, kDebugExec, "CommandExec::createSuspendList()"); + + _suspendedCtxt.valid = true; + _suspendedCtxt.first = first; + _suspendedCtxt.last = last; + _suspendedCtxt.zone = _execZone; +} + +void CommandExec::cleanSuspendedList() { + debugC(3, kDebugExec, "CommandExec::cleanSuspended()"); + + _suspendedCtxt.valid = false; + _suspendedCtxt.first = _suspendedCtxt.last; + _suspendedCtxt.zone = nullZonePtr; +} + +void CommandExec::runSuspended() { + if (_engineFlags & kEngineWalking) { + return; + } + + if (_suspendedCtxt.valid) { + debugC(3, kDebugExec, "CommandExec::runSuspended()"); + + _execZone = _suspendedCtxt.zone; + runList(_suspendedCtxt.first, _suspendedCtxt.last); + cleanSuspendedList(); + } +} + +CommandExec_ns::CommandExec_ns(Parallaction_ns* vm) : _vm(vm) { + +} + +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,33 +765,10 @@ ZonePtr Parallaction::hitZone(uint32 type, uint16 x, uint16 y) { } -void Parallaction_ns::initOpcodes() { - +void CommandExec_ns::init() { Common::Array *table = 0; - SetOpcodeTable(_instructionOpcodes); - INSTRUCTION_OPCODE(invalid); - INSTRUCTION_OPCODE(on); - INSTRUCTION_OPCODE(off); - INSTRUCTION_OPCODE(set); // x - INSTRUCTION_OPCODE(set); // y - INSTRUCTION_OPCODE(set); // z - INSTRUCTION_OPCODE(set); // f - INSTRUCTION_OPCODE(loop); - INSTRUCTION_OPCODE(endloop); - INSTRUCTION_OPCODE(null); - INSTRUCTION_OPCODE(inc); - INSTRUCTION_OPCODE(inc); // dec - INSTRUCTION_OPCODE(set); - INSTRUCTION_OPCODE(put); - INSTRUCTION_OPCODE(call); - INSTRUCTION_OPCODE(wait); - INSTRUCTION_OPCODE(start); - INSTRUCTION_OPCODE(sound); - INSTRUCTION_OPCODE(move); - INSTRUCTION_OPCODE(endscript); - - SetOpcodeTable(_commandOpcodes); + SetOpcodeTable(_opcodes); COMMAND_OPCODE(invalid); COMMAND_OPCODE(set); COMMAND_OPCODE(clear); @@ -698,5 +788,39 @@ void Parallaction_ns::initOpcodes() { COMMAND_OPCODE(stop); } +void ProgramExec_ns::init() { + + Common::Array *table = 0; + + SetOpcodeTable(_opcodes); + INSTRUCTION_OPCODE(invalid); + INSTRUCTION_OPCODE(on); + INSTRUCTION_OPCODE(off); + INSTRUCTION_OPCODE(set); // x + INSTRUCTION_OPCODE(set); // y + INSTRUCTION_OPCODE(set); // z + INSTRUCTION_OPCODE(set); // f + INSTRUCTION_OPCODE(loop); + INSTRUCTION_OPCODE(endloop); + INSTRUCTION_OPCODE(show); + INSTRUCTION_OPCODE(inc); + INSTRUCTION_OPCODE(inc); // dec + INSTRUCTION_OPCODE(set); + INSTRUCTION_OPCODE(put); + INSTRUCTION_OPCODE(call); + INSTRUCTION_OPCODE(wait); + INSTRUCTION_OPCODE(start); + INSTRUCTION_OPCODE(sound); + INSTRUCTION_OPCODE(move); + INSTRUCTION_OPCODE(endscript); + +} + +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 91848b30a4d..e84dad34aa2 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 6599a1f81cf..1c373dda445 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 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 (uint i = 0; i < 3; i++) { - - GfxObjList::iterator b = _gfxobjList[i].begin(); - GfxObjList::iterator e = _gfxobjList[i].end(); - - 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); - } - } - } + for (; b != e; b++) { + drawGfxObject(*b, surf, true); } } + + +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); +} + + +#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 58fb02a7506..c19d6ae5e54 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() { + _skipBackground = (_backgroundInfo->bg.pixels == 0); // don't render frame if background is missing - 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); + 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); + if (!_skipBackground) { + // background may not cover the whole screen, so adjust bulk update size + uint w = MIN(_vm->_screenWidth, (int32)_backgroundInfo->width); + uint h = MIN(_vm->_screenHeight, (int32)_backgroundInfo->height); - byte *backgroundData = 0; - uint16 backgroundPitch = 0; - switch (_varBackgroundMode) { - case 1: - backgroundData = (byte*)_backgroundInfo.bg.getBasePtr(_varScrollX, 0); - backgroundPitch = _backgroundInfo.bg.pitch; - break; - case 2: - backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0); - backgroundPitch = _bitmapMask.pitch; - break; + byte *backgroundData = 0; + uint16 backgroundPitch = 0; + switch (_varBackgroundMode) { + case 1: + backgroundData = (byte*)_backgroundInfo->bg.getBasePtr(_varScrollX, 0); + backgroundPitch = _backgroundInfo->bg.pitch; + break; + case 2: + backgroundData = (byte*)_bitmapMask.getBasePtr(_varScrollX, 0); + backgroundPitch = _bitmapMask.pitch; + break; + } + g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo->x, _backgroundInfo->y, w, h); } - g_system->copyRectToScreen(backgroundData, backgroundPitch, _backgroundInfo.x, _backgroundInfo.y, w, h); + if (_varDrawPathZones == 1) { + Graphics::Surface *surf = g_system->lockScreen(); + ZoneList::iterator b = _vm->_location._zones.begin(); + ZoneList::iterator e = _vm->_location._zones.end(); + for (; b != e; b++) { + ZonePtr z = *b; + if (z->_type & kZonePath) { + surf->frameRect(Common::Rect(z->_left, z->_top, z->_right, z->_bottom), 2); + } + } + g_system->unlockScreen(); + } _varRenderMode = _varAnimRenderMode; @@ -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,14 +559,74 @@ 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 id = _labels.size(); + _labels.insert_at(id, obj); + + return id; } -uint Gfx::createLabel(Font *font, const char *text, byte color) { - assert(_numLabels < MAX_NUM_LABELS); +void Gfx::showFloatingLabel(uint label) { + assert(label < _labels.size()); - Label *label = new Label; - Graphics::Surface *cnv = &label->_cnv; + hideFloatingLabel(); + + _labels[label]->x = -1000; + _labels[label]->y = -1000; + _labels[label]->setFlags(kGfxObjVisible); + + _floatingLabel = label; +} + +void Gfx::hideFloatingLabel() { + if (_floatingLabel != NO_FLOATING_LABEL) { + _labels[_floatingLabel]->clearFlags(kGfxObjVisible); + } + _floatingLabel = NO_FLOATING_LABEL; +} + + +void Gfx::updateFloatingLabel() { + if (_floatingLabel == NO_FLOATING_LABEL) { + return; + } + + int16 _si, _di; + + 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 - r.width()/2; + _di = cursor.y + 34; + } else { + _si = cursor.x + 8 - r.width()/2; + _di = cursor.y + 21; + } + + if (_si < 0) _si = 0; + if (_di > 190) _di = 190; + + if (r.width() + _si > _vm->_screenWidth) + _si = _vm->_screenWidth - r.width(); + + _labels[_floatingLabel]->x = _si; + _labels[_floatingLabel]->y = _di; +} + + + + +uint Gfx::createLabel(Font *font, const char *text, byte color) { + assert(_labels.size() < MAX_NUM_LABELS); + + Graphics::Surface *cnv = new Graphics::Surface; uint w, h; @@ -726,165 +647,65 @@ uint Gfx::createLabel(Font *font, const char *text, byte color) { drawText(font, cnv, 0, 0, text, color); } - uint id = _numLabels; - _labels[id] = label; - _numLabels++; + GfxObj *obj = new GfxObj(kGfxObjTypeLabel, new SurfaceToFrames(cnv), "label"); + obj->transparentKey = LABEL_TRANSPARENT_COLOR; + obj->layer = LAYER_FOREGROUND; + + int 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; + assert(id < _labels.size()); + _labels[id]->setFlags(kGfxObjVisible); + + Common::Rect r; + _labels[id]->getRect(0, r); if (x == CENTER_LABEL_HORIZONTAL) { - x = CLIP((_vm->_screenWidth - _labels[id]->_cnv.w) / 2, 0, _vm->_screenWidth/2); + x = CLIP((_vm->_screenWidth - r.width()) / 2, 0, _vm->_screenWidth/2); } if (y == CENTER_LABEL_VERTICAL) { - y = CLIP((_vm->_screenHeight - _labels[id]->_cnv.h) / 2, 0, _vm->_screenHeight/2); + y = CLIP((_vm->_screenHeight - r.height()) / 2, 0, _vm->_screenHeight/2); } - _labels[id]->_pos.x = x; - _labels[id]->_pos.y = y; + _labels[id]->x = x; + _labels[id]->y = y; } void Gfx::hideLabel(uint id) { - assert(id < _numLabels); - _labels[id]->_visible = false; + assert(id < _labels.size()); + _labels[id]->clearFlags(kGfxObjVisible); } void Gfx::freeLabels() { - for (uint i = 0; i < _numLabels; i++) { + for (uint i = 0; i < _labels.size(); i++) { delete _labels[i]; } - _numLabels = 0; -} - - -void Gfx::setFloatingLabel(Label *label) { - _floatingLabel = label; - - if (_floatingLabel) { - _floatingLabel->resetPosition(); - } -} - -void Gfx::updateFloatingLabel() { - if (!_floatingLabel) { - return; - } - - int16 _si, _di; - - Common::Point cursor; - _vm->_input->getCursorPos(cursor); - - if (_vm->_input->_activeItem._id != 0) { - _si = cursor.x + 16 - _floatingLabel->_cnv.w/2; - _di = cursor.y + 34; - } else { - _si = cursor.x + 8 - _floatingLabel->_cnv.w/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; - - _floatingLabel->_pos.x = _si; - _floatingLabel->_pos.y = _di; + _labels.clear(); + _floatingLabel = NO_FLOATING_LABEL; } void Gfx::drawLabels() { - if ((!_floatingLabel) && (_numLabels == 0)) { + if (_labels.size() == 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); + for (uint i = 0; i < _labels.size(); i++) { + drawGfxObject(_labels[i], *surf, false); } g_system->unlockScreen(); } -Label::Label() { - resetPosition(); - _visible = false; -} - -Label::~Label() { - free(); -} - -void Label::free() { - _cnv.free(); - resetPosition(); -} - -void Label::resetPosition() { - _pos.x = -1000; - _pos.y = -1000; -} - - -void Gfx::getStringExtent(Font *font, char *text, uint16 maxwidth, int16* width, int16* height) { - - uint16 lines = 0; - uint16 w = 0; - *width = 0; - - uint16 blankWidth = font->getStringWidth(" "); - uint16 tokenWidth = 0; - - char token[MAX_TOKEN_LEN]; - - while (strlen(text) != 0) { - - text = parseNextToken(text, token, MAX_TOKEN_LEN, " ", true); - tokenWidth = font->getStringWidth(token); - - w += tokenWidth; - - if (!scumm_stricmp(token, "%p")) { - lines++; - } else { - if (w > maxwidth) { - w -= tokenWidth; - lines++; - if (w > *width) - *width = w; - - w = tokenWidth; - } - } - - w += blankWidth; - text = Common::ltrim(text); - } - - if (*width < w) *width = w; - *width += 10; - - *height = lines * 10 + 20; - - return; -} void Gfx::copyRect(const Common::Rect &r, Graphics::Surface &src, Graphics::Surface &dst) { @@ -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); + _items[item].data->frame = f; + _items[item].data->setFlags(kGfxObjVisible); } -Gfx::Balloon* Gfx::getBalloon(uint id) { - assert(id < _numBalloons); - return &_balloons[id]; + +GfxObj* Gfx::registerBalloon(Frames *frames, const char *text) { + + GfxObj *obj = new GfxObj(kGfxObjTypeBalloon, frames, text); + + obj->layer = LAYER_FOREGROUND; + obj->frame = 0; + obj->setFlags(kGfxObjVisible); + + _balloons.push_back(obj); + + return obj; } -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); +void Gfx::destroyBalloons() { + for (uint i = 0; i < _balloons.size(); i++) { + delete _balloons[i]; } - - _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; -} - -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); -} - - -int Gfx::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); - Gfx::Balloon *balloon = &_balloons[id]; - drawWrappedText(_vm->_dialogueFont, &balloon->surface, text, 0, MAX_BALLOON_WIDTH); - - balloon->x = 5; - balloon->y = 5; - - return id; -} - -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; - } - - 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; - - if (linewidth > wrapwidth) { - // wrap line - lines++; - rx = 10; // x - ry = 4 + lines*10; // y - linewidth = tokenWidth; - } - - if (!scumm_stricmp(token, "%s")) { - sprintf(token, "%d", _score); - } - - } - - drawText(font, surf, rx, ry, token, color); - - rx += tokenWidth + blankWidth; - linewidth += blankWidth; - - text = Common::ltrim(text); - } - -} - -void Gfx::freeBackground() { - _backgroundInfo.free(); -} - -void Gfx::setBackground(uint type, const char* name, const char* mask, const char* path) { - - freeBackground(); +void Gfx::setBackground(uint type, BackgroundInfo *info) { + delete _backgroundInfo; + _backgroundInfo = info; if (type == kBackgroundLocation) { - _disk->loadScenery(_backgroundInfo, name, mask, path); - setPalette(_backgroundInfo.palette); - _palette.clone(_backgroundInfo.palette); - } else { - _disk->loadSlide(_backgroundInfo, name); - setPalette(_backgroundInfo.palette); - } + // The PC version of BRA needs the entries 20-31 of the palette to be constant, but + // the background resource files are screwed up. The right colors come from an unused + // bitmap (pointer.bmp). Nothing is known about the Amiga version so far. + if ((_vm->getGameType() == GType_BRA) && (_vm->getPlatform() == Common::kPlatformPC)) { + int r, g, b; + for (uint i = 16; i < 32; i++) { + _backupPal.getEntry(i, r, g, b); + _backgroundInfo->palette.setEntry(i, r, g, b); + } + } + setPalette(_backgroundInfo->palette); + _palette.clone(_backgroundInfo->palette); + } else { + 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 894e0fd6783..23b4569c6a7 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 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 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 00000000000..2dbe64fcf68 --- /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 00000000000..dc6d1bc71b0 --- /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 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 c515299a34f..33154337624 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; + + Parallaction_br *_vm; + int _fadeSteps; + +public: + SplashInputState_BR(Parallaction_br *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm) { + } + + 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; + } + + if (_fadeSteps == 0) { + _vm->freeBackground(); + return _helper->getState(_nextState); + } + + uint32 curTime = _vm->_system->getMillis(); + if (curTime - _startTime > _timeOut) { + _fadeSteps = 64; + pal.clone(_vm->_gfx->_backgroundInfo->palette); + } + return this; + } + + virtual void enter() { + _vm->_gfx->clearScreen(); + _vm->showSlide(_slideName.c_str(), CENTER_LABEL_HORIZONTAL, CENTER_LABEL_VERTICAL); + _vm->_input->setMouseState(MOUSE_DISABLED); + + _startTime = g_system->getMillis(); + _fadeSteps = -1; + } +}; + +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 { + +public: + SplashInputState1_BR(Parallaction_br *vm, MenuInputHelper *helper) : SplashInputState_BR(vm, "intro1", helper) { + _slideName = "core"; + _timeOut = 600; + _nextState = "mainmenu"; + } +}; + +class MainMenuInputState_BR : public MenuInputState { + Parallaction_br *_vm; + + #define MENUITEMS_X 250 + #define MENUITEMS_Y 200 + + #define MENUITEM_WIDTH 190 + #define MENUITEM_HEIGHT 18 + + 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 + + Graphics::Surface *surf = new Graphics::Surface; + surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1); + + // build first frame to be displayed when item is not selected + if (_vm->getPlatform() == Common::kPlatformPC) { + _vm->_menuFont->setColor(0); + } else { + _vm->_menuFont->setColor(7); + } + _vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2), MENUITEM_WIDTH, text); + + // build second frame to be displayed when item is selected + _vm->_menuFont->drawString((byte*)surf->getBasePtr(5, 2 + MENUITEM_HEIGHT), MENUITEM_WIDTH, text); + byte *s = (byte*)surf->getBasePtr(0, MENUITEM_HEIGHT); + for (int i = 0; i < surf->w * MENUITEM_HEIGHT; i++) { + *s++ ^= 0xD; + } + + // wrap the surface into the suitable Frames adapter + return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf); + } + + enum MenuOptions { + kMenuPart0 = 0, + kMenuPart1 = 1, + kMenuPart2 = 2, + kMenuPart3 = 3, + kMenuPart4 = 4, + kMenuLoadGame = 5, + kMenuQuit = 6 + }; + + #define NUM_MENULINES 7 + GfxObj *_lines[NUM_MENULINES]; + + static const char *_menuStrings[NUM_MENULINES]; + static const MenuOptions _options[NUM_MENULINES]; + + int _availItems; + int _selection; + + void cleanup() { + _vm->_system->showMouse(false); + _vm->hideDialogueStuff(); + + for (int i = 0; i < _availItems; i++) { + delete _lines[i]; + } + } + + void performChoice(int selectedItem) { + switch (selectedItem) { + case kMenuQuit: + _engineFlags |= kEngineQuit; + break; + + case kMenuLoadGame: + warning("loadgame not yet implemented"); + break; + + default: + _vm->startPart(selectedItem); + } + } + +public: + MainMenuInputState_BR(Parallaction_br *vm, MenuInputHelper *helper) : MenuInputState("mainmenu", helper), _vm(vm) { + } + + virtual MenuInputState* run() { + + 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)) { + _selection = (p.y - MENUITEMS_Y) / MENUITEM_HEIGHT; + + if (!(_selection < _availItems)) + _selection = -1; + } else + _selection = -1; + + for (int i = 0; i < _availItems; i++) { + _vm->_gfx->setItemFrame(i, _selection == i ? 1 : 0); + } + + + return this; + } + + 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); + } + +}; + +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::guiStart() { - - // TODO: load progress value from special save game - _progress = 3; - - int option = guiShowMenu(); - switch (option) { - case kMenuQuit: - _engineFlags |= kEngineQuit; - break; - - case kMenuLoadGame: - warning("loadgame not yet implemented"); - break; - - default: - _part = option; - _disk->selectArchive(_partNames[_part]); - startPart(); - } -} - -void Parallaction_br::guiSplash(const char *name) { - - _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); - - 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); - } - -} - -#define MENUITEMS_X 250 -#define MENUITEMS_Y 200 - -#define MENUITEM_WIDTH 190 -#define MENUITEM_HEIGHT 18 - -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 - - Graphics::Surface *surf = new Graphics::Surface; - surf->create(MENUITEM_WIDTH, MENUITEM_HEIGHT*2, 1); - - // 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); - - // 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; - } - - // wrap the surface into the suitable Frames adapter - return new SurfaceToMultiFrames(2, MENUITEM_WIDTH, MENUITEM_HEIGHT, surf); -} -int Parallaction_br::guiShowMenu() { - // TODO: filter menu entries according to progress in game - #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" - }; - MenuOptions 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); - _gfx->clearScreen(); - _gfx->setBackground(kBackgroundSlide, "tbra", 0, 0); - if (getPlatform() == Common::kPlatformPC) { - _gfx->_backgroundInfo.x = 20; - _gfx->_backgroundInfo.y = 50; - } - - int availItems = 4 + _progress; - - // TODO: keep track of and destroy menu item frames/surfaces - - 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); - } - - int selectedItem = -1; - - setMousePointer(0); - - uint32 event; - Common::Point p; - while (true) { + _menuHelper->setState("intro0"); + _input->_inputMode = Input::kInputModeMenu; + do { _input->readInput(); - - event = _input->getLastButtonEvent(); - if ((event == kMouseLeftUp) && selectedItem >= 0) - break; - - _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; - - if (!(selectedItem < availItems)) - selectedItem = -1; - } else - selectedItem = -1; - - for (i = 0; i < availItems; i++) { - _gfx->setItemFrame(i, selectedItem == i ? 1 : 0); - } - + if (!_menuHelper->run()) break; + _gfx->beginFrame(); _gfx->updateScreen(); - _system->delayMillis(20); - } + } while (true); - _system->showMouse(false); - _gfx->hideDialogueStuff(); + delete _menuHelper; + _menuHelper = 0; - for (i = 0; i < availItems; i++) { - delete _lines[i]; - } - - return options[selectedItem]; + _input->_inputMode = Input::kInputModeGame; } + + } // namespace Parallaction diff --git a/engines/parallaction/gui_ns.cpp b/engines/parallaction/gui_ns.cpp index 1d4d44fa460..815c27bd1cc 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,35 +34,233 @@ 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; + + Parallaction_ns *_vm; + +public: + SplashInputState_NS(Parallaction_ns *vm, const Common::String &name, MenuInputHelper *helper) : MenuInputState(name, helper), _vm(vm) { + } + + virtual MenuInputState* run() { + uint32 curTime = g_system->getMillis(); + if (curTime - _startTime > _timeOut) { + _vm->freeBackground(); + return _helper->getState(_nextState); + } + return this; + } + + virtual void enter() { + _vm->_input->setMouseState(MOUSE_DISABLED); + _vm->showSlide(_slideName.c_str()); + _startTime = g_system->getMillis(); + } }; -const char *introMsg2[] = { - "CODICE ERRATO", - "CODE ERRONE", - "WRONG CODE", - "GIB DEN KODE EIN" +class SplashInputState0_NS : public SplashInputState_NS { + +public: + SplashInputState0_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro0", helper) { + _slideName = "intro"; + _timeOut = 2000; + _nextState = "intro1"; + } }; -const char *introMsg3[] = { - "PRESS LEFT MOUSE BUTTON", - "TO SEE INTRO", - "PRESS RIGHT MOUSE BUTTON", - "TO START" +class SplashInputState1_NS : public SplashInputState_NS { + +public: + SplashInputState1_NS(Parallaction_ns *vm, MenuInputHelper *helper) : SplashInputState_NS(vm, "intro1", helper) { + _slideName = "minintro"; + _timeOut = 2000; + _nextState = "chooselanguage"; + } }; -const char *newGameMsg[] = { + +class ChooseLanguageInputState_NS : public MenuInputState { + #define BLOCK_WIDTH 16 + #define BLOCK_HEIGHT 24 + + #define BLOCK_X 112 + #define BLOCK_Y 130 + + #define BLOCK_SELECTION_X (BLOCK_X-1) + #define BLOCK_SELECTION_Y (BLOCK_Y-1) + + #define BLOCK_X_OFFSET (BLOCK_WIDTH+1) + #define BLOCK_Y_OFFSET 9 + + // destination slots for code blocks + // + #define SLOT_X 61 + #define SLOT_Y 64 + #define SLOT_WIDTH (BLOCK_WIDTH+2) + + int _language; + bool _allowChoice; + Common::String _nextState; + + static const Common::Rect _dosLanguageSelectBlocks[4]; + static const Common::Rect _amigaLanguageSelectBlocks[4]; + const Common::Rect *_blocks; + + Parallaction_ns *_vm; + +public: + ChooseLanguageInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("chooselanguage", helper), _vm(vm) { + _allowChoice = false; + _nextState = "selectgame"; + + if (_vm->getPlatform() == Common::kPlatformAmiga) { + if (!(_vm->getFeatures() & GF_LANG_MULT)) { + if (_vm->getFeatures() & GF_DEMO) { + _language = 1; // Amiga Demo supports English + _nextState = "startdemo"; + return; + } else { + _language = 0; // The only other non multi-lingual version just supports Italian + return; + } + } + + _blocks = _amigaLanguageSelectBlocks; + } else { + _blocks = _dosLanguageSelectBlocks; + } + + _language = -1; + _allowChoice = true; + } + + virtual MenuInputState* run() { + if (!_allowChoice) { + _vm->setInternLanguage(_language); + return _helper->getState(_nextState); + } + + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return this; + } + + Common::Point p; + _vm->_input->getCursorPos(p); + + for (uint16 i = 0; i < 4; i++) { + if (_blocks[i].contains(p)) { + _vm->setInternLanguage(i); + _vm->beep(); + _vm->_gfx->freeLabels(); + return _helper->getState(_nextState); + } + } + + return this; + } + + virtual void enter() { + if (!_allowChoice) { + return; + } + + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); + + // user can choose language in this version + _vm->showSlide("lingua"); + + uint id = _vm->_gfx->createLabel(_vm->_introFont, "SELECT LANGUAGE", 1); + _vm->_gfx->showLabel(id, 60, 30); + + _vm->setArrowCursor(); + } +}; + +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 +}; + +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 +}; + +class SelectGameInputState_NS : public MenuInputState { + + int _choice, _oldChoice; + Common::String _nextState[2]; + + uint _labels[2]; + + Parallaction_ns *_vm; + + static const char *newGameMsg[4]; + static const char *loadGameMsg[4]; + +public: + SelectGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("selectgame", helper), _vm(vm) { + _choice = 0; + _oldChoice = -1; + + _nextState[0] = "newgame"; + _nextState[1] = "loadgame"; + } + + + virtual MenuInputState *run() { + int event = _vm->_input->getLastButtonEvent(); + + if (event == kMouseLeftUp) { + _vm->_gfx->freeLabels(); + return _helper->getState(_nextState[_choice]); + } + + Common::Point p; + _vm->_input->getCursorPos(p); + _choice = (p.x > 160) ? 1 : 0; + + if (_choice != _oldChoice) { + if (_oldChoice != -1) + _vm->_gfx->hideLabel(_labels[_oldChoice]); + + if (_choice != -1) + _vm->_gfx->showLabel(_labels[_choice], 60, 30); + + _oldChoice = _choice; + } + + return this; + } + + virtual void enter() { + _vm->showSlide("restore"); + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); + + _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, newGameMsg[_vm->getInternLanguage()], 1); + _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, loadGameMsg[_vm->getInternLanguage()], 1); + } + +}; + +const char *SelectGameInputState_NS::newGameMsg[4] = { "NUOVO GIOCO", "NEUF JEU", "NEW GAME", "NEUES SPIEL" }; -const char *loadGameMsg[] = { +const char *SelectGameInputState_NS::loadGameMsg[4] = { "GIOCO SALVATO", "JEU SAUVE'", "SAVED GAME", @@ -68,275 +268,333 @@ const char *loadGameMsg[] = { }; -#define BLOCK_WIDTH 16 -#define BLOCK_HEIGHT 24 -#define BLOCK_X 112 -#define BLOCK_Y 130 +class LoadGameInputState_NS : public MenuInputState { + bool _result; + Parallaction_ns *_vm; -#define BLOCK_SELECTION_X (BLOCK_X-1) -#define BLOCK_SELECTION_Y (BLOCK_Y-1) +public: + LoadGameInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("loadgame", helper), _vm(vm) { } -#define BLOCK_X_OFFSET (BLOCK_WIDTH+1) -#define BLOCK_Y_OFFSET 9 + virtual MenuInputState* run() { + if (!_result) { + _vm->scheduleLocationSwitch("fogne.dough"); + } + return 0; + } -// destination slots for code blocks -// -#define SLOT_X 61 -#define SLOT_Y 64 -#define SLOT_WIDTH (BLOCK_WIDTH+2) + virtual void enter() { + _result = _vm->loadGame(); + } +}; -#define PASSWORD_LEN 6 -#define CHAR_DINO 0 -#define CHAR_DONNA 1 -#define CHAR_DOUGH 2 -static const uint16 _amigaKeys[][PASSWORD_LEN] = { +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"); + } + + 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) { + } + + virtual MenuInputState* run() { + _vm->scheduleLocationSwitch("fognedemo.dough"); + return 0; + } + + virtual void enter() { + _vm->_input->setMouseState(MOUSE_DISABLED); + } +}; + +class SelectCharacterInputState_NS : public MenuInputState { + + #define PASSWORD_LEN 6 + + #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; + } + + byte _points[3]; + bool _fail; + const uint16 (*_keys)[PASSWORD_LEN]; + Graphics::Surface _block; + Graphics::Surface _emptySlots; + + uint _labels[2]; + uint _len; + uint32 _startTime; + + enum { + CHOICE, + FAIL, + SUCCESS, + DELAY + }; + + uint _state; + + static const char *introMsg1[4]; + static const char *introMsg2[4]; + + static const uint16 _amigaKeys[3][PASSWORD_LEN]; + static const uint16 _pcKeys[3][PASSWORD_LEN]; + static const char *_charStartLocation[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); + } + + ~SelectCharacterInputState_NS() { + _block.free(); + _emptySlots.free(); + } + + 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; + } + + void delay() { + if (g_system->getMillis() - _startTime < 2000) { + return; + } + cleanup(); + _state = CHOICE; + } + + void choice() { + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return; + } + + Common::Point p; + _vm->_input->getCursorPos(p); + int _si = guiGetSelectedBlock(p); + + if (_si != -1) { + _vm->_gfx->grabBackground(codeTrueBlocks[_si], _block); + _vm->_gfx->patchBackground(_block, _len * SLOT_WIDTH + SLOT_X, SLOT_Y, false); + + if (_keys[0][_len] != _si && _keys[1][_len] != _si && _keys[2][_len] != _si) { + _fail = true; + } + + // 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; + } + } + + void fail() { + _vm->_gfx->patchBackground(_emptySlots, SLOT_X, SLOT_Y, false); + _vm->_gfx->hideLabel(_labels[0]); + _vm->_gfx->showLabel(_labels[1], 60, 30); + _startTime = g_system->getMillis(); + _state = DELAY; + } + + void success() { + _vm->_gfx->freeLabels(); + _vm->_gfx->setBlackPalette(); + _emptySlots.free(); + + // actually select character + int character = -1; + if (_points[0] >= _points[1] && _points[0] >= _points[2]) { + character = CHAR_DINO; + } else + if (_points[1] >= _points[0] && _points[1] >= _points[2]) { + character = CHAR_DONNA; + } else + if (_points[2] >= _points[0] && _points[2] >= _points[1]) { + character = CHAR_DOUGH; + } else { + error("If you read this, either your CPU or transivity is broken (we believe the former)."); + } + + _vm->_inTestResult = false; + _vm->cleanupGame(); + _vm->scheduleLocationSwitch(_charStartLocation[character]); + } + + virtual MenuInputState* run() { + MenuInputState* nextState = this; + + switch (_state) { + case DELAY: + delay(); + break; + + case CHOICE: + choice(); + break; + + case FAIL: + fail(); + break; + + case SUCCESS: + success(); + nextState = 0; + break; + + default: + error("unknown state in SelectCharacterInputState"); + } + + return nextState; + } + + virtual void enter() { + _vm->_soundMan->stopMusic(); + _vm->_disk->selectArchive((_vm->getFeatures() & GF_DEMO) ? "disk0" : "disk1"); + _vm->showSlide("password"); + + _emptySlots.create(BLOCK_WIDTH * 8, BLOCK_HEIGHT, 1); + Common::Rect rect(SLOT_X, SLOT_Y, SLOT_X + BLOCK_WIDTH * 8, SLOT_Y + BLOCK_HEIGHT); + _vm->_gfx->grabBackground(rect, _emptySlots); + + _labels[0] = _vm->_gfx->createLabel(_vm->_introFont, introMsg1[_vm->getInternLanguage()], 1); + _labels[1] = _vm->_gfx->createLabel(_vm->_introFont, introMsg2[_vm->getInternLanguage()], 1); + + cleanup(); + + _vm->setArrowCursor(); + _vm->_input->setMouseState(MOUSE_ENABLED_SHOW); + _state = CHOICE; + } +}; + +const char *SelectCharacterInputState_NS::introMsg1[4] = { + "INSERISCI IL CODICE", + "ENTREZ CODE", + "ENTER CODE", + "GIB DEN KODE EIN" +}; + +const char *SelectCharacterInputState_NS::introMsg2[4] = { + "CODICE ERRATO", + "CODE ERRONE", + "WRONG CODE", + "GIB DEN KODE EIN" +}; + +const uint16 SelectCharacterInputState_NS::_amigaKeys[][PASSWORD_LEN] = { { 5, 3, 6, 2, 2, 7 }, // dino { 0, 3, 6, 2, 2, 6 }, // donna { 1, 3 ,7, 2, 4, 6 } // dough }; -static const uint16 _pcKeys[][PASSWORD_LEN] = { +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 }; -static const char *_charStartLocation[] = { +const char *SelectCharacterInputState_NS::_charStartLocation[] = { "test.dino", "test.donna", "test.dough" }; -enum { - NEW_GAME, - LOAD_GAME -}; -enum { - START_DEMO, - START_INTRO, - GAME_LOADED, - SELECT_CHARACTER -}; - -void Parallaction_ns::guiStart() { - - _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); - - guiSplash(); - - _language = guiChooseLanguage(); - _disk->setLanguage(_language); - - int event; - - if (getFeatures() & GF_DEMO) { - event = START_DEMO; - } else { - if (guiSelectGame() == NEW_GAME) { - event = guiNewGame(); - } else { - event = loadGame() ? GAME_LOADED : START_INTRO; - } - } - - switch (event) { - case START_DEMO: - strcpy(_location._name, "fognedemo.dough"); - break; - - case START_INTRO: - strcpy(_location._name, "fogne.dough"); - break; - - case GAME_LOADED: - // nothing to do here - return; - - case SELECT_CHARACTER: - selectStartLocation(); - break; - - } - - return; -} - -void Parallaction_ns::selectStartLocation() { - int character = guiSelectCharacter(); - if (character == -1) - error("invalid character selected from menu screen"); - - scheduleLocationSwitch(_charStartLocation[character]); -} - - -void Parallaction_ns::guiSplash() { - - showSlide("intro"); - _gfx->updateScreen(); - g_system->delayMillis(2000); - freeBackground(); - - showSlide("minintro"); - _gfx->updateScreen(); - g_system->delayMillis(2000); - freeBackground(); -} - -int Parallaction_ns::guiNewGame() { - - const char **v14 = introMsg3; - - _disk->selectArchive("disk1"); - - setBackground("test", NULL, NULL); - - _gfx->updateScreen(); - - uint id[4]; - id[0] = _gfx->createLabel(_menuFont, v14[0], 1); - id[1] = _gfx->createLabel(_menuFont, v14[1], 1); - id[2] = _gfx->createLabel(_menuFont, v14[2], 1); - id[3] = _gfx->createLabel(_menuFont, v14[3], 1); - _gfx->showLabel(id[0], CENTER_LABEL_HORIZONTAL, 50); - _gfx->showLabel(id[1], CENTER_LABEL_HORIZONTAL, 70); - _gfx->showLabel(id[2], CENTER_LABEL_HORIZONTAL, 100); - _gfx->showLabel(id[3], CENTER_LABEL_HORIZONTAL, 120); - - _input->showCursor(false); - - _gfx->updateScreen(); - - _input->waitForButtonEvent(kMouseLeftUp | kMouseRightUp); - uint32 event = _input->getLastButtonEvent(); - - _input->showCursor(true); - - _gfx->freeLabels(); - - if (event != kMouseRightUp) { - return START_INTRO; - } - - 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 -}; - -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 -}; - - -uint16 Parallaction_ns::guiChooseLanguage() { - - const Common::Rect *blocks; - - 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 - } - } - - blocks = _amigaLanguageSelectBlocks; - } else { - blocks = _dosLanguageSelectBlocks; - } - - // user can choose language in dos version - showSlide("lingua"); - - uint id = _gfx->createLabel(_introFont, "SELECT LANGUAGE", 1); - _gfx->showLabel(id, 60, 30); - - setArrowCursor(); - - Common::Point p; - - int selection = -1; - while (selection == -1) { - _input->waitUntilLeftClick(); - _input->getCursorPos(p); - for (uint16 i = 0; i < 4; i++) { - if (blocks[i].contains(p)) { - selection = i; - break; - } - } - } - - beep(); - - _gfx->freeLabels(); - - return selection; -} - - - -uint16 Parallaction_ns::guiSelectGame() { -// printf("selectGame()\n"); - - showSlide("restore"); - - uint16 _si = 0; - uint16 _di = 3; - - uint id0, id1; - id0 = _gfx->createLabel(_introFont, loadGameMsg[_language], 1); - id1 = _gfx->createLabel(_introFont, newGameMsg[_language], 1); - - Common::Point p; - - _input->readInput(); - uint32 event = _input->getLastButtonEvent(); - - while (event != kMouseLeftUp) { - - _input->readInput(); - _input->getCursorPos(p); - event = _input->getLastButtonEvent(); - - _si = (p.x > 160) ? 1 : 0; - - 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); - } - _di = _si; - } - - _gfx->updateScreen(); - g_system->delayMillis(30); - } - - _gfx->freeLabels(); - - return _si ? LOAD_GAME : NEW_GAME; -} - -static const Common::Rect codeSelectBlocks[9] = { +const Common::Rect SelectCharacterInputState_NS::codeSelectBlocks[9] = { Common::Rect( 111, 129, 127, 153 ), // na Common::Rect( 128, 120, 144, 144 ), // wa Common::Rect( 145, 111, 161, 135 ), // ra @@ -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) { + } + + 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); + } + + + virtual MenuInputState* run() { + if (_current == -1) { + _startTime = g_system->getMillis(); + _current = 0; + drawCurrentLabel(); + return this; } - } - if ((selection != -1) && (getPlatform() == Common::kPlatformAmiga)) { - _gfx->invertBackground(codeTrueBlocks[selection]); - _gfx->updateScreen(); - beep(); - g_system->delayMillis(100); - _gfx->invertBackground(codeTrueBlocks[selection]); - _gfx->updateScreen(); - } + int event = _vm->_input->getLastButtonEvent(); + uint32 curTime = g_system->getMillis(); + if ((event == kMouseLeftUp) || (curTime - _startTime > 5500)) { + _current++; + _startTime = curTime; + _vm->_gfx->freeLabels(); - return selection; -} - - -// -// character selection and protection -// -int Parallaction_ns::guiSelectCharacter() { - debugC(1, kDebugMenu, "Parallaction_ns::guiselectCharacter()"); - - setArrowCursor(); - _soundMan->stopMusic(); - - _disk->selectArchive((getFeatures() & GF_DEMO) ? "disk0" : "disk1"); - - showSlide("password"); - - - const uint16 (*keys)[PASSWORD_LEN] = (getPlatform() == Common::kPlatformAmiga && (getFeatures() & GF_LANG_MULT)) ? _amigaKeys : _pcKeys; - uint16 _di = 0; - byte points[3] = { 0, 0, 0 }; - - bool fail; - - uint id[2]; - id[0] = _gfx->createLabel(_introFont, introMsg1[_language], 1); - id[1] = _gfx->createLabel(_introFont, introMsg2[_language], 1); - - 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); - - Graphics::Surface block; - block.create(BLOCK_WIDTH, BLOCK_HEIGHT, 1); - - Common::Point p; - - while (true) { - - points[0] = 0; - points[1] = 0; - points[2] = 0; - fail = false; - - _gfx->hideLabel(id[1]); - _gfx->showLabel(id[0], 60, 30); - - _di = 0; - while (_di < PASSWORD_LEN) { - - _input->waitUntilLeftClick(); - _input->getCursorPos(p); - - int _si = guiGetSelectedBlock(p); - - 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; - } - - // build user preference - points[0] += (keys[0][_di] == _si); - points[1] += (keys[1][_di] == _si); - points[2] += (keys[2][_di] == _si); - - _di++; + if (_current == 6) { + return _helper->getState("endintro"); } + drawCurrentLabel(); } - if (!fail) { - break; + return this; + } + + virtual void enter() { + _current = -1; + _vm->_input->setMouseState(MOUSE_DISABLED); + } +}; + +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"} +}; + +class EndIntroInputState_NS : public MenuInputState { + Parallaction_ns *_vm; + bool _isDemo; + +public: + EndIntroInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endintro", helper), _vm(vm) { + _isDemo = (_vm->getFeatures() & GF_DEMO) != 0; + } + + virtual MenuInputState* run() { + + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return this; } - _gfx->patchBackground(v14, SLOT_X, SLOT_Y, false); + if (_isDemo) { + _engineFlags |= kEngineQuit; + return 0; + } - _gfx->hideLabel(id[0]); - _gfx->showLabel(id[1], 60, 30); - - _gfx->updateScreen(); - - g_system->delayMillis(2000); + _vm->_gfx->freeLabels(); + return _helper->getState("selectcharacter"); } - _gfx->freeLabels(); + virtual void enter() { + _vm->_soundMan->stopMusic(); + _vm->_input->setMouseState(MOUSE_DISABLED); - _gfx->setBlackPalette(); - _gfx->updateScreen(); - - v14.free(); + if (!_isDemo) { + int label = _vm->_gfx->createLabel(_vm->_menuFont, "CLICK MOUSE BUTTON TO START", 1); + _vm->_gfx->showLabel(label, CENTER_LABEL_HORIZONTAL, 80); + } + } +}; - // actually select character +class EndPartInputState_NS : public MenuInputState { + Parallaction_ns *_vm; + bool _allPartsComplete; - 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)."); + // 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]; + + +public: + EndPartInputState_NS(Parallaction_ns *vm, MenuInputHelper *helper) : MenuInputState("endpart", helper), _vm(vm) { } - return character; + virtual MenuInputState* run() { + int event = _vm->_input->getLastButtonEvent(); + if (event != kMouseLeftUp) { + return this; + } + + _vm->_gfx->freeLabels(); + + if (_allPartsComplete) { + _vm->scheduleLocationSwitch("estgrotta.drki"); + return 0; + } + + 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); + } + + _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); + } +}; + +// 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"); + + _menuHelper = new MenuInputHelper; + assert(_menuHelper); + + 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"); + + _input->_inputMode = Input::kInputModeMenu; +} + +void Parallaction_ns::startCreditSequence() { + _menuHelper = new MenuInputHelper; + assert(_menuHelper); + + new ShowCreditsInputState_NS(this, _menuHelper); + new EndIntroInputState_NS(this, _menuHelper); + new SelectCharacterInputState_NS(this, _menuHelper); + _menuHelper->setState("showcredits"); + + _input->_inputMode = Input::kInputModeMenu; +} + +void Parallaction_ns::startEndPartSequence() { + _menuHelper = new MenuInputHelper; + assert(_menuHelper); + + new EndPartInputState_NS(this, _menuHelper); + new SelectCharacterInputState_NS(this, _menuHelper); + _menuHelper->setState("endpart"); + + _input->_inputMode = Input::kInputModeMenu; } diff --git a/engines/parallaction/input.cpp b/engines/parallaction/input.cpp index 28d0ad888da..287618e803c 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(); + readInput(); - debugC(3, kDebugInput, "translateInput: input flags (%i, %i, %i, %i)", - !_mouseHidden, - (_engineFlags & kEngineBlockInput) == 0, - (_engineFlags & kEngineWalking) == 0, - (_engineFlags & kEngineChangeLocation) == 0 - ); - - if ((_mouseHidden) || - (_engineFlags & kEngineBlockInput) || + if (!isMouseEnabled() || (_engineFlags & kEngineWalking) || (_engineFlags & kEngineChangeLocation)) { + debugC(3, kDebugInput, "updateGameInput: input flags (mouse: %i, walking: %i, changeloc: %i)", + isMouseEnabled(), + (_engineFlags & kEngineWalking) == 0, + (_engineFlags & kEngineChangeLocation) == 0 + ); + return; } - if (keyDown == kEvQuitGame) { - _inputData._event = kEvQuitGame; - } else - if (keyDown == kEvSaveGame) { - _inputData._event = kEvSaveGame; - } else - if (keyDown == kEvLoadGame) { - _inputData._event = kEvLoadGame; - } else { + if (_hasKeyPressEvent && (_vm->getFeatures() & GF_DEMO) == 0) { + if (_keyPressed.keycode == Common::KEYCODE_l) _inputData._event = kEvLoadGame; + if (_keyPressed.keycode == Common::KEYCODE_s) _inputData._event = kEvSaveGame; + } + + if (_inputData._event == kEvNone) { _inputData._mousePos = _mousePos; - _inputData._event = kEvNone; - if (!translateGameInput()) { - translateInventoryInput(); - } + translateGameInput(); } } -void Input::updateCommentInput() { - waitUntilLeftClick(); - - _vm->_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); } + } + + _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; } - if (_si == _transCurrentHoverItem) { - _inputData._event = kEvNone; - return true; + int16 _si = _vm->getHoverInventoryItem(_mousePos.x, _mousePos.y); + if (_si != _transCurrentHoverItem) { + _transCurrentHoverItem = _si; + _vm->highlightInventoryItem(_si); // enable } - _transCurrentHoverItem = _si; - _inputData._event = kEvHoverInventory; - _inputData._inventoryIndex = _si; 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 46dbb08c4e2..c1e912db743 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 58848196d77..7b92974205f 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); - - // FIXME: this will end up in a general blit function - - 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); + drawItem(name, d, _surf.pitch); +} - d += INVENTORY_WIDTH; - s += INVENTORYITEM_PITCH; +void InventoryRenderer::drawItem(ItemName name, byte *buffer, uint pitch) { + byte* s = _vm->_char._objs->getData(name); + byte* d = buffer; + for (uint i = 0; i < _props->_itemHeight; i++) { + memcpy(d, s, _props->_itemWidth); + + 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 8c32c09219d..f041627810c 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 2478b4b2e12..9d444225415 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 cac31911f41..c387484de79 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 c2c2c154b54..a3bf757bdbd 100644 --- a/engines/parallaction/objects.h +++ b/engines/parallaction/objects.h @@ -54,6 +54,7 @@ typedef Common::SharedPtr InstructionPtr; typedef Common::List InstructionList; extern InstructionPtr nullInstructionPtr; +typedef Common::List 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 d66b1af1f1a..bb306c32991 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); + _menuHelper = 0; + + setupBalloonManager(); + return 0; } - - - +void Parallaction::clearSet(OpcodeSet &opcodes) { + for (Common::Array::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(text), end); + _balloonMan->setLocationBalloon(const_cast(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(); + + if (_engineFlags & kEngineQuit) + return; + + if (_engineFlags & kEngineChangeLocation) { + changeLocation(_location._name); + } } - runPendingZones(); - - 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 6e5957d3cd5..e5c52214140 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,20 +224,21 @@ 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 class Parallaction : public Engine { @@ -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 0f5cc2a0c44..761c8d1b747 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 2cca3a6a4a4..e81492e655f 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) { +void Parallaction_ns::setInventoryCursor(ItemName name) { + assert(name > 0); - if (pos == -1) - return; - - 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); + _input->setMouseState(oldMouseState); + debugC(1, kDebugExec, "changeLocation() done"); - - return; - } @@ -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 f9de6eb4af0..6de0a7d7f53 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 d488cf9b58c..79e6cf66403 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 51da7eb3966..3b446805d70 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 2c4601c9384..ad0f714fdc6 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 dd74e8f7aab..df6867a90c3 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 0a8ded9e298..bf8f423fd53 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; - - walkData1++; - - // 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; + 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 { - walkData2 = (from->_y > pos.y) ? 14 : 17; - walkData1 %= 8; - v16 = walkData1 / 4; + _vm->_cmdExec->run(z->_commands, z); } - - } else { - - 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; - } - } - return v16; + 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; + } + } -uint16 Parallaction::checkDoor() { -// printf("checkDoor()..."); - - if (_currentLocationIndex != _doorData1) { - _doorData1 = _currentLocationIndex; - _zoneTrap = nullZonePtr; - } +void PathWalker_NS::finalizeWalk() { _engineFlags &= ~kEngineWalking; Common::Point foot; + _ch->getFoot(foot); + checkDoor(foot); - _char.getFoot(foot); - ZonePtr z = hitZone(kZoneDoor, foot.x, foot.y); - - if (z) { - - if ((z->_flags & kFlagsClosed) == 0) { - _location._startPosition = z->u.door->_startPos; - _location._startFrame = z->u.door->_startFrame; - - scheduleLocationSwitch(z->u.door->_location); - _zoneTrap = nullZonePtr; - - } else { - runCommands(z->_commands, z); - } - } - - _char.getFoot(foot); - z = hitZone(kZoneTrap, foot.x, foot.y); - - 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; - } - -// printf("done\n"); - - _char._ani->_frame = walkData2; - return _char._ani->_frame; + _ch->_walkPath.clear(); } - -void Parallaction::finalizeWalk(WalkNodeList *list) { - checkDoor(); - delete list; -} - -void Parallaction_ns::walk() { +void PathWalker_NS::walk() { if ((_engineFlags & kEngineWalking) == 0) { return; } - WalkNodeList *list = _char._walkPath; + Common::Point curPos; + _ch->getFoot(curPos); - _char._ani->_oldPos.x = _char._ani->_left; - _char._ani->_oldPos.y = _char._ani->_top; - - Common::Point pos; - _char.getFoot(pos); - - WalkNodeList::iterator it = list->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); + // 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); } } - if (it == list->end()) { + + // advance character towards the target + Common::Point targetPos; + if (it == _ch->_walkPath.end()) { debugC(1, kDebugWalk, "walk reached last node"); -// j->_finished = 1; - finalizeWalk(list); + finalizeWalk(); + targetPos = curPos; + } else { + // targetPos is saved to help setting character direction + targetPos = *it; + + 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 + } + } + + // 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); +} + + + +PathBuilder_NS::PathBuilder_NS(Character *ch) : PathBuilder(ch), _list(0) { +} + + +bool PathBuilder_BR::directPathExists(const Common::Point &from, const Common::Point &to) { + + Common::Point copy(from); + Common::Point p(copy); + + while (p != to) { + + if (p.x < to.x && IS_PATH_CLEAR(p.x + 1, p.y)) p.x++; + if (p.x > to.x && IS_PATH_CLEAR(p.x - 1, p.y)) p.x--; + if (p.y < to.y && IS_PATH_CLEAR(p.x, p.y + 1)) p.y++; + if (p.y > to.y && IS_PATH_CLEAR(p.x, p.y - 1)) p.y--; + + if (p == copy && p != to) { + return false; + } + + copy = p; + } + + return true; +} + +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; } - _char._walkPath = list; - // selectWalkFrame must be performed before position is changed by clipMove - int16 v16 = selectWalkFrame(pos, *it); - clipMove(pos, *it); - - _char.setFoot(pos); - - Common::Point newpos(_char._ani->_left, _char._ani->_top); - - 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; + // look for short circuit cases + ZonePtr z0 = _vm->hitZone(kZonePath, x, y); + if (!z0) { + _ch->_walkPath.push_back(dest); + debugC(3, kDebugWalk, "buildPath: corner case 0"); + return; } + ZonePtr z1 = _vm->hitZone(kZonePath, foot.x, foot.y); + if (!z1 || z1 == z0) { + _ch->_walkPath.push_back(dest); + debugC(3, kDebugWalk, "buildPath: corner case 1"); + return; + } + + // build complex path + int id = atoi(z0->_name); + + if (z1->u.path->_lists[id].empty()) { + _ch->_walkPath.clear(); + debugC(3, kDebugWalk, "buildPath: no path"); + return; + } + + PointList::iterator b = z1->u.path->_lists[id].begin(); + PointList::iterator e = z1->u.path->_lists[id].end(); + for ( ; b != e; b++) { + _ch->_walkPath.push_front(*b); + } + _ch->_walkPath.push_back(dest); + debugC(3, kDebugWalk, "buildPath: complex path"); + + return; +} + +PathBuilder_BR::PathBuilder_BR(Character *ch) : PathBuilder(ch) { +} + +void PathWalker_BR::finalizeWalk() { + _engineFlags &= ~kEngineWalking; + _first = true; + _fieldC = 1; + + Common::Point foot; + _ch->getFoot(foot); + + ZonePtr z = _vm->hitZone(kZoneDoor, foot.x, foot.y); + if (z && ((z->_flags & kFlagsClosed) == 0)) { + _vm->_location._startPosition = z->u.door->_startPos; // foot pos + _vm->_location._startFrame = z->u.door->_startFrame; + +#if 0 + // TODO: implement working follower. Must find out a location in which the code is + // used and which is stable enough. + _followerFootInit.x = -1; + if (_follower && z->u.door->startPos2.x != -1) { + _followerFootInit.x = z->u.door->startPos2.x; // foot pos + _followerFootInit.y = z->u.door->startPos2.y; // foot pos + } + _followerFootInit.z = -1; + if (_follower && z->u.door->startPos2.z != -1) { + _followerFootInit.z = z->u.door->startPos2.z; // foot pos + } +#endif + + _vm->scheduleLocationSwitch(z->u.door->_location); + _vm->_cmdExec->run(z->_commands, z); + } + +#if 0 + // TODO: Input::walkTo must be extended to support destination frame in addition to coordinates + // TODO: the frame argument must be passed to PathWalker through PathBuilder, so probably + // a merge between the two Path managers is the right solution + if (_engineFlags & FINAL_WALK_FRAME) { // this flag is set in readInput() + _engineFlags &= ~FINAL_WALK_FRAME; + _char.ani->_frame = _moveToF; // from readInput()... + } else { + _char.ani->_frame = _dirFrame; // from walk() + } + _char.setFoot(foot); +#endif + + _ch->_ani->_frame = _dirFrame; // temporary solution + +#if 0 + // TODO: support scrolling ;) + if (foot.x > _gfx->hscroll + 600) _gfx->scrollRight(78); + if (foot.x < _gfx->hscroll + 40) _gfx->scrollLeft(78); + if (foot.y > 350) _gfx->scrollDown(100); + if (foot.y < 80) _gfx->scrollUp(100); +#endif return; } -WalkNode::WalkNode() : _x(0), _y(0) { +void PathWalker_BR::walk() { + if ((_engineFlags & kEngineWalking) == 0) { + return; + } + +#if 0 + // TODO: support delays in walking. This requires extending Input::walkIo(). + if (ch._walkDelay > 0) { + ch._walkDelay--; + if (ch._walkDelay == 0 && _ch._ani->_scriptName) { + // stop script and reset + _ch._ani->_flags &= ~kFlagsActing; + Script *script = findScript(_ch._ani->_scriptName); + script->_nextCommand = script->firstCommand; + } + return; + } +#endif + + GfxObj *obj = _ch->_ani->gfxobj; + + Common::Rect rect; + obj->getRect(_ch->_ani->_frame, rect); + + uint scale; + if (rect.bottom > _vm->_location._zeta0) { + scale = 100; + } else + if (rect.bottom < _vm->_location._zeta1) { + scale = _vm->_location._zeta2; + } else { + scale = _vm->_location._zeta2 + ((rect.bottom - _vm->_location._zeta1) * (100 - _vm->_location._zeta2)) / (_vm->_location._zeta0 - _vm->_location._zeta1); + } + int xStep = (scale * 16) / 100 + 1; + int yStep = (scale * 10) / 100 + 1; + + debugC(9, kDebugWalk, "calculated step: (%i, %i)\n", xStep, yStep); + + if (_fieldC == 0) { + _ch->_walkPath.erase(_ch->_walkPath.begin()); + + if (_ch->_walkPath.empty()) { + finalizeWalk(); + debugC(3, kDebugWalk, "PathWalker_BR::walk, case 0\n"); + return; + } else { + debugC(3, kDebugWalk, "PathWalker_BR::walk, moving to next node\n"); + } + } + + _ch->getFoot(_startFoot); + + _fieldC = 0; + _step++; + _step %= 8; + + int walkFrame = _step; + _dirFrame = 0; + Common::Point newpos(_startFoot), delta; + + Common::Point p(*_ch->_walkPath.begin()); + + if (_startFoot.y < p.y && _startFoot.y < 400 && IS_PATH_CLEAR(_startFoot.x, yStep + _startFoot.y)) { + if (yStep + _startFoot.y <= p.y) { + _fieldC = 1; + delta.y = yStep; + newpos.y = yStep + _startFoot.y; + } else { + delta.y = p.y - _startFoot.y; + newpos.y = p.y; + } + _dirFrame = 9; + } else + if (_startFoot.y > p.y && _startFoot.y > 0 && IS_PATH_CLEAR(_startFoot.x, _startFoot.y - yStep)) { + if (_startFoot.y - yStep >= p.y) { + _fieldC = 1; + delta.y = yStep; + newpos.y = _startFoot.y - yStep; + } else { + delta.y = _startFoot.y - p.y; + newpos.y = p.y; + } + _dirFrame = 0; + } + + if (_startFoot.x < p.x && _startFoot.x < 640 && IS_PATH_CLEAR(_startFoot.x + xStep, _startFoot.y)) { + if (_startFoot.x + xStep <= p.x) { + _fieldC = 1; + delta.x = xStep; + newpos.x = xStep + _startFoot.x; + } else { + delta.x = p.x - _startFoot.x; + newpos.x = p.x; + } + if (delta.y < delta.x) { + _dirFrame = 18; // right + } + } else + if (_startFoot.x > p.x && _startFoot.x > 0 && IS_PATH_CLEAR(_startFoot.x - xStep, _startFoot.y)) { + if (_startFoot.x - xStep >= p.x) { + _fieldC = 1; + delta.x = xStep; + newpos.x = _startFoot.x - xStep; + } else { + delta.x = _startFoot.x - p.x; + newpos.x = p.x; + } + if (delta.y < delta.x) { + _dirFrame = 27; // left + } + } + + debugC(9, kDebugWalk, "foot (%i, %i) dest (%i, %i) deltas = %i/%i \n", _startFoot.x, _startFoot.y, p.x, p.y, delta.x, delta.y); + + if (_fieldC) { + debugC(9, kDebugWalk, "PathWalker_BR::walk, foot moved from (%i, %i) to (%i, %i)\n", _startFoot.x, _startFoot.y, newpos.x, newpos.y); + _ch->_ani->_frame = walkFrame + _dirFrame + 1; + _startFoot.x = newpos.x; + _startFoot.y = newpos.y; + _ch->setFoot(_startFoot); + _ch->_ani->_z = newpos.y; + } + + if (_fieldC || !_ch->_walkPath.empty()) { +// checkTrap(); + debugC(3, kDebugWalk, "PathWalker_BR::walk, case 1\n"); + return; + } + + debugC(3, kDebugWalk, "PathWalker_BR::walk, case 2\n"); + finalizeWalk(); + return; } -WalkNode::WalkNode(int16 x, int16 y) : _x(x), _y(y) { -} +PathWalker_BR::PathWalker_BR(Character *ch) : PathWalker(ch), _fieldC(1), _first(true) { -WalkNode::WalkNode(const WalkNode& w) : _x(w._x), _y(w._y) { -} - -void WalkNode::getPoint(Common::Point &p) const { - p.x = _x; - p.y = _y; -} - -PathBuilder::PathBuilder(AnimationPtr anim) : _anim(anim), _list(0) { } diff --git a/engines/parallaction/walk.h b/engines/parallaction/walk.h index 788a6e1375f..8d21e5ebbd6 100644 --- a/engines/parallaction/walk.h +++ b/engines/parallaction/walk.h @@ -29,44 +29,90 @@ #include "common/ptr.h" #include "common/list.h" +#include "parallaction/objects.h" + + namespace Parallaction { -struct Animation; - -struct WalkNode { - int16 _x; - int16 _y; - -public: - WalkNode(); - WalkNode(int16 x, int16 y); - WalkNode(const WalkNode& w); - - void getPoint(Common::Point &p) const; -}; - -typedef Common::SharedPtr WalkNodePtr; -typedef Common::List WalkNodeList; - +struct Character; class PathBuilder { - AnimationPtr _anim; +protected: + Character *_ch; - WalkNodeList *_list; - WalkNodeList _subPath; +public: + PathBuilder(Character *ch) : _ch(ch) { } + virtual ~PathBuilder() { } + + virtual void buildPath(uint16 x, uint16 y) = 0; +}; + + +class PathBuilder_NS : public PathBuilder { + + 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(); +}; + } #endif diff --git a/engines/queen/graphics.cpp b/engines/queen/graphics.cpp index f863f7663cd..6d0a11ccfe5 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 6f00111635d..7eadf9a1916 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 146e95bceff..9f03c341c9c 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 86092aeed69..43a57729c64 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 bfbcfa4e59d..0327fb74b89 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 d1a1247c46b..c95e44b4779 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 5a8db74e3bd..b3bd663baf3 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 d70fe7209da..27cf1bf6a2c 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 c2c1481cc63..331034f7465 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 3a1e5105293..9fffb0f8bfe 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 e936117894f..482b3a4c826 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 6b930ddca02..76c0f067254 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 6246cb71dac..6abaeea1510 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 7380570a99e..1d048baaade 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 5f1d0157d5c..6614f4098f3 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 b7d4f4f1bdd..e150caeca5c 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 40eb32b276c..fafbd02cece 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 4a5fae7ddb5..6b6eb6b3fb0 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 7664af314f1..088be34c720 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 e9d002819c9..be4f2a423d6 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 8f3175f0988..5a45fb7da90 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 9f9115e207d..23af1f96725 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 9359c6610ca..68d3010199c 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 6d1cf1bbd89..e4e2b2b620b 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 7c99a0ebcce..0d04d8faeac 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 bc5fc38225d..bf13308a0c2 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 7064654f89a..a2695cac59a 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 95f5eec4eae..8325436f87d 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 d601c2c496c..4d2d6de275f 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 cd3e5c7dad6..83aaa78927b 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 33e6748860d..f8fb1efca2a 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 4d5ec668a02..9429f8d0862 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 df3d8576421..6c3d0023d8d 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 393e1d3a8fd..39ec715d942 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 df472307eb4..f514449bff3 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 fa50eca604e..d3359fa33e3 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 1511b9aefcf..b18b0ba70fc 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 33147128cb8..2d4c673cf69 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 acdc2bc2226..6bd62c17610 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 36b82519e92..f9e4eb415cb 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 62d777aa33d..ce8f0a4d9a5 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 81bfdb2d222..73341c117fc 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 fdd05983785..7500b16c872 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 a2f7d57cb04..a30276f8bec 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 7c2b262d82a..4434f4cd682 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 d54e290b09f..adb84eee838 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 fd72ba8d529..3b5a09578b0 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 d6b8025cda8..8cddddff783 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 70bae6f851b..b89ef8f12bc 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 00000000000..c2f01addedc --- /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 00000000000..91f54519d56 --- /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 00000000000..95d834d88a3 --- /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 00000000000..5b25292a6b3 --- /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 00000000000..91d21b4e0b1 --- /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 00000000000..7b8d0994460 --- /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 00000000000..9c1e5f15400 --- /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 00000000000..b67ae7b17f6 --- /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(¤tObj, 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(¤tObj); + } +} + +} // end of namespace Tinsel diff --git a/engines/tinsel/cliprect.h b/engines/tinsel/cliprect.h new file mode 100644 index 00000000000..28a66c312c2 --- /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 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 00000000000..4c143f1b8d0 --- /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 00000000000..73cc411cb64 --- /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 00000000000..e0292735bb4 --- /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: + * . + * 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 00000000000..b95662cbfe9 --- /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 00000000000..15349dda26e --- /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 00000000000..dc37e6a9a1c --- /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 00000000000..219bc712242 --- /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 00000000000..a638dde2c5b --- /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 00000000000..d14dd43fa24 --- /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 00000000000..91645da71b0 --- /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 00000000000..bf9f428fd44 --- /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 00000000000..bc49d687172 --- /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 00000000000..0018727ccb4 --- /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 00000000000..1e9336fae8d --- /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 00000000000..c8bf4604bcb --- /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 00000000000..620298867ec --- /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 00000000000..b75c36191c3 --- /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 00000000000..cd0937d9448 --- /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 00000000000..85299d48739 --- /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 00000000000..11623516ec4 --- /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 00000000000..2cb1638d9d6 --- /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 00000000000..aff085d0035 --- /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 00000000000..7fb85985a96 --- /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 00000000000..2a0f3695c00 --- /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 00000000000..d83439c68f2 --- /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 00000000000..4c64eaf0916 --- /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 00000000000..b00afcddbcb --- /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 00000000000..803bc5fd7b3 --- /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(ihMulFrame) { + // 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 00000000000..6d25600ea2a --- /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 00000000000..7d4efd8079b --- /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 00000000000..80456e2a760 --- /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 00000000000..709fa4fad9b --- /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 00000000000..8b61571a3e2 --- /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 00000000000..3bc2b514b58 --- /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 00000000000..fdc4826dbdd --- /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 00000000000..023417fe3cd --- /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 00000000000..1c7e0a942ca --- /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 00000000000..b5488da3e87 --- /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 00000000000..c2af1a5fcbf --- /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 00000000000..e32fc88d3de --- /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 00000000000..d73e2902771 --- /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; //!= 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 00000000000..90c57d5f996 --- /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 00000000000..a9b24bcac98 --- /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 00000000000..7ccf017c654 --- /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 00000000000..1a6cc1202a3 --- /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 00000000000..9f0d7b90399 --- /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 00000000000..a999c9bffaa --- /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 // 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 00000000000..70700c16a38 --- /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 00000000000..d0fc6e1ae39 --- /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 00000000000..72cfeaf6b01 --- /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 00000000000..0d90b3bb9fc --- /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 00000000000..b14b1c59621 --- /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 00000000000..29f3dc51fc3 --- /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 00000000000..aa1bc672980 --- /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 00000000000..ac903157f2f --- /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 00000000000..98ee398ef80 --- /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 \ + void syncAs ## SUFFIX(T &val) { \ + if (_loadStream) \ + val = static_cast(_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 00000000000..e2a24dbd47a --- /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 00000000000..56618eeb8ee --- /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 00000000000..abf5a880f69 --- /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 00000000000..fac287492b9 --- /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 00000000000..dab97f7fdf5 --- /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 00000000000..78998831a1b --- /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 00000000000..c7b9d3708b8 --- /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 00000000000..75eb87ee2bf --- /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 00000000000..e8364e20dd6 --- /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 00000000000..001de708969 --- /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 00000000000..1f563852833 --- /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 mouseButtons; + +// Stack of pending keypresses +Common::List 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 00000000000..9ffadfe8c15 --- /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 00000000000..0bdac0d6eb5 --- /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 00000000000..4ab4775bfb7 --- /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 14cb85912a5..d77dbf5bfa2 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 3b128593dbc..a518a4bb298 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 eb647a1b422..4fcf6e114dc 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 -static void saveOrLoadCommonArray(S &s, A &array); - template 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 6520fb5e4a6..ac8e8a786a5 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")); diff --git a/graphics/cursorman.cpp b/graphics/cursorman.cpp index ba7c6dee9bc..fe5f653b942 100644 --- a/graphics/cursorman.cpp +++ b/graphics/cursorman.cpp @@ -83,6 +83,24 @@ void CursorManager::popCursor() { g_system->showMouse(isVisible()); } + +void CursorManager::popAllCursors() { + while (!_cursorStack.empty()) { + Cursor *cur = _cursorStack.pop(); + delete cur; + } + + if (g_system->hasFeature(OSystem::kFeatureCursorHasPalette)) { + while (!_cursorPaletteStack.empty()) { + Palette *pal = _cursorPaletteStack.pop(); + delete pal; + } + } + + g_system->showMouse(isVisible()); +} + + void CursorManager::replaceCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, byte keycolor, int targetScale) { if (_cursorStack.empty()) { pushCursor(buf, w, h, hotspotX, hotspotY, keycolor, targetScale); diff --git a/graphics/cursorman.h b/graphics/cursorman.h index 151038e1186..bf05ab762bb 100644 --- a/graphics/cursorman.h +++ b/graphics/cursorman.h @@ -79,6 +79,14 @@ public: */ void replaceCursor(const byte *buf, uint w, uint h, int hotspotX, int hotspotY, byte keycolor = 255, int targetScale = 1); + /** + * Pop all of the cursors and cursor palettes from their respective stacks. + * The purpose is to ensure that all unecessary cursors are removed from the + * stack when returning to the launcher from an engine. + * + */ + void popAllCursors(); + /** * Enable/Disable the current cursor palette. * diff --git a/graphics/font.cpp b/graphics/font.cpp index b5817e613a1..0b0405b4b4a 100644 --- a/graphics/font.cpp +++ b/graphics/font.cpp @@ -585,8 +585,8 @@ NewFont *NewFont::loadFont(Common::SeekableReadStream &stream) { } bool NewFont::cacheFontData(const NewFont &font, const Common::String &filename) { - Common::File cacheFile; - if (!cacheFile.open(filename, Common::File::kFileWriteMode)) { + Common::DumpFile cacheFile; + if (!cacheFile.open(filename)) { warning("Couldn't open file '%s' for writing", filename.c_str()); return false; } diff --git a/graphics/iff.cpp b/graphics/iff.cpp index ac51531eee3..514fba9cc09 100644 --- a/graphics/iff.cpp +++ b/graphics/iff.cpp @@ -139,6 +139,7 @@ void ILBMDecoder::readBODY(Common::IFFChunk& chunk) { out += _bitmapHeader.width; } + free(scan); break; } diff --git a/gui/credits.h b/gui/credits.h index 41544a22486..1afec250a68 100644 --- a/gui/credits.h +++ b/gui/credits.h @@ -98,10 +98,19 @@ static const char *credits[] = { "\\C\\c0""", "\\C\\c1""SAGA", "\\C\\c0""Torbj\366rn Andersson", +"\\C\\c0""Sven Hesse", "\\C\\c0""Filippos Karapetis", "\\C\\c0""Andrew Kurushin", "\\C\\c0""Eugene Sandulenko", "\\C\\c0""", +"\\C\\c1""Tinsel;", +"\\C\\c0""Torbj\366rn Andersson", +"\\C\\c0""Paul Gilbert", +"\\C\\c0""Sven Hesse", +"\\C\\c0""Max Horn", +"\\C\\c0""Filippos Karapetis", +"\\C\\c0""Joost Peters", +"\\C\\c0""", "\\C\\c1""Touch\351", "\\C\\c0""Gregory Montoir", "\\C\\c0""", diff --git a/gui/launcher.cpp b/gui/launcher.cpp index 2c1212b84ad..34c4ebf4747 100644 --- a/gui/launcher.cpp +++ b/gui/launcher.cpp @@ -44,6 +44,7 @@ #include "gui/ListWidget.h" #include "gui/TabWidget.h" #include "gui/PopUpWidget.h" +#include "graphics/cursorman.h" #include "sound/mididrv.h" @@ -549,6 +550,8 @@ void LauncherDialog::open() { // failure to launch a game. Otherwise, pressing ESC will attempt to // re-launch the same game again. ConfMan.setActiveDomain(""); + + CursorMan.popAllCursors(); Dialog::open(); updateButtons(); @@ -711,12 +714,7 @@ Common::String addGameToConf(const GameDescriptor &result) { // The auto detector or the user made a choice. // Pick a domain name which does not yet exist (after all, we // are *adding* a game to the config, not replacing). - String domain; - - if (result.contains("preferredtarget")) - domain = result["preferredtarget"]; - else - domain = result.gameid(); + String domain = result.preferredtarget(); assert(!domain.empty()); if (ConfMan.hasGameDomain(domain)) { diff --git a/gui/massadd.cpp b/gui/massadd.cpp index 687d3675168..6842466ad9e 100644 --- a/gui/massadd.cpp +++ b/gui/massadd.cpp @@ -23,7 +23,9 @@ */ #include "engines/metaengine.h" +#include "common/algorithm.h" #include "common/events.h" +#include "common/func.h" #include "common/config-manager.h" #include "gui/launcher.h" // For addGameToConf() @@ -113,10 +115,19 @@ MassAddDialog::MassAddDialog(const FilesystemNode &startDir) } } +struct GameDescLess { + bool operator()(const GameDescriptor &x, const GameDescriptor &y) const { + return x.preferredtarget().compareToIgnoreCase(y.preferredtarget()) < 0; + } +}; + void MassAddDialog::handleCommand(CommandSender *sender, uint32 cmd, uint32 data) { // FIXME: It's a really bad thing that we use two arbitrary constants if (cmd == kOkCmd) { + // Sort the detected games. This is not strictly necessary, but nice for + // people who want to edit their config file by hand after a mass add. + sort(_games.begin(), _games.end(), GameDescLess()); // Add all the detected games to the config for (GameList::const_iterator iter = _games.begin(); iter != _games.end(); ++iter) { printf(" Added gameid '%s', desc '%s'\n", diff --git a/gui/newgui.cpp b/gui/newgui.cpp index b9b4fa028fb..1c90c70f4b0 100644 --- a/gui/newgui.cpp +++ b/gui/newgui.cpp @@ -366,7 +366,7 @@ void NewGui::restoreState() { void NewGui::openDialog(Dialog *dialog) { _dialogStack.push(dialog); _redrawStatus = kRedrawOpenDialog; - + // We reflow the dialog just before opening it. If the screen changed // since the last time we looked, also refresh the loaded theme, // and reflow all other open dialogs, too. diff --git a/ports.mk b/ports.mk index 3d79384e19f..80efcfacc4a 100644 --- a/ports.mk +++ b/ports.mk @@ -118,7 +118,7 @@ iphone: $(OBJS) $(CXX) $(LDFLAGS) -o scummvm $(OBJS) \ $(OSX_STATIC_LIBS) \ -framework UIKit -framework CoreGraphics -framework CoreSurface \ - -framework LayerKit -framework GraphicsServices -framework CoreFoundation \ + -framework GraphicsServices -framework CoreFoundation -framework QuartzCore \ -framework Foundation -framework AudioToolbox -framework CoreAudio \ -lobjc -lz diff --git a/sound/midiparser_smf.cpp b/sound/midiparser_smf.cpp index ed5007ecb72..89b7d3f5253 100644 --- a/sound/midiparser_smf.cpp +++ b/sound/midiparser_smf.cpp @@ -85,16 +85,26 @@ void MidiParser_SMF::parseNextEvent(EventInfo &info) { _position._running_status = info.event; switch (info.command()) { - case 0xC: case 0xD: + case 0x9: // Note On + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + if (info.basic.param2 == 0) + info.event = info.channel() | 0x80; + info.length = 0; + break; + + case 0xC: + case 0xD: info.basic.param1 = *(_position._play_pos++); info.basic.param2 = 0; break; - case 0x8: case 0x9: case 0xA: case 0xB: case 0xE: + case 0x8: + case 0xA: + case 0xB: + case 0xE: info.basic.param1 = *(_position._play_pos++); info.basic.param2 = *(_position._play_pos++); - if (info.command() == 0x9 && info.basic.param2 == 0) - info.event = info.channel() | 0x80; info.length = 0; break; @@ -110,7 +120,12 @@ void MidiParser_SMF::parseNextEvent(EventInfo &info) { info.basic.param2 = 0; break; - case 0x6: case 0x8: case 0xA: case 0xB: case 0xC: case 0xE: + case 0x6: + case 0x8: + case 0xA: + case 0xB: + case 0xC: + case 0xE: info.basic.param1 = info.basic.param2 = 0; break; diff --git a/sound/midiparser_xmidi.cpp b/sound/midiparser_xmidi.cpp index d2aac493519..7cf114dcc63 100644 --- a/sound/midiparser_xmidi.cpp +++ b/sound/midiparser_xmidi.cpp @@ -38,6 +38,14 @@ protected: NoteTimer _notes_cache[32]; uint32 _inserted_delta; // Track simulated deltas for note-off events + struct Loop { + byte *pos; + byte repeat; + }; + + Loop _loop[4]; + int _loopCount; + protected: uint32 readVLQ2(byte * &data); void resetTracking(); @@ -78,16 +86,56 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) { } break; - case 0xC: case 0xD: + case 0xC: + case 0xD: info.basic.param1 = *(_position._play_pos++); info.basic.param2 = 0; break; - case 0x8: case 0xA: case 0xB: case 0xE: + case 0x8: + case 0xA: + case 0xE: info.basic.param1 = *(_position._play_pos++); info.basic.param2 = *(_position._play_pos++); break; + case 0xB: + info.basic.param1 = *(_position._play_pos++); + info.basic.param2 = *(_position._play_pos++); + + // Simplified XMIDI looping. + // + // I would really like to turn the loop events into some sort + // of NOP event (perhaps a dummy META event?), but for now we + // just pass them on to the MIDI driver. That has worked in the + // past, so it shouldn't cause any actual damage... + + if (info.basic.param1 == 0x74) { + // XMIDI_CONTROLLER_FOR_LOOP + byte *pos = _position._play_pos; + if (_loopCount < ARRAYSIZE(_loop) - 1) + _loopCount++; + + _loop[_loopCount].pos = pos; + _loop[_loopCount].repeat = info.basic.param2; + } else if (info.basic.param1 == 0x75) { + // XMIDI_CONTROLLER_NEXT_BREAK + if (_loopCount >= 0) { + if (info.basic.param2 < 64) { + // End the current loop. + _loopCount--; + } else { + _position._play_pos = _loop[_loopCount].pos; + // Repeat 0 means "loop forever". + if (_loop[_loopCount].repeat) { + if (--_loop[_loopCount].repeat == 0) + _loopCount--; + } + } + } + } + break; + case 0xF: // Meta or SysEx event switch (info.event & 0x0F) { case 0x2: // Song Position Pointer @@ -100,7 +148,12 @@ void MidiParser_XMIDI::parseNextEvent(EventInfo &info) { info.basic.param2 = 0; break; - case 0x6: case 0x8: case 0xA: case 0xB: case 0xC: case 0xE: + case 0x6: + case 0x8: + case 0xA: + case 0xB: + case 0xC: + case 0xE: info.basic.param1 = info.basic.param2 = 0; break; @@ -136,6 +189,8 @@ bool MidiParser_XMIDI::loadMusic(byte *data, uint32 size) { uint32 chunk_len; char buf[32]; + _loopCount = -1; + unloadMusic(); byte *pos = data; diff --git a/sound/mixer.cpp b/sound/mixer.cpp index fc5b21cf325..27e031f1088 100644 --- a/sound/mixer.cpp +++ b/sound/mixer.cpp @@ -27,7 +27,7 @@ #include "common/util.h" #include "common/system.h" -#include "sound/mixer.h" +#include "sound/mixer_intern.h" #include "sound/rate.h" #include "sound/audiostream.h" @@ -103,32 +103,38 @@ public: #pragma mark - -Mixer::Mixer() { - _syst = g_system; +MixerImpl::MixerImpl(OSystem *system) + : _syst(system), _sampleRate(0), _mixerReady(false), _handleSeed(0) { - _handleSeed = 0; - - int i = 0; + int i; for (i = 0; i < ARRAYSIZE(_volumeForSoundType); i++) _volumeForSoundType[i] = kMaxMixerVolume; for (i = 0; i != NUM_CHANNELS; i++) _channels[i] = 0; - - _mixerReady = false; } -Mixer::~Mixer() { +MixerImpl::~MixerImpl() { for (int i = 0; i != NUM_CHANNELS; i++) delete _channels[i]; } -uint Mixer::getOutputRate() const { - return (uint)_syst->getOutputSampleRate(); +void MixerImpl::setReady(bool ready) { + _mixerReady = ready; } -void Mixer::insertChannel(SoundHandle *handle, Channel *chan) { +uint MixerImpl::getOutputRate() const { + return _sampleRate; +} + +void MixerImpl::setOutputRate(uint sampleRate) { + if (_sampleRate != 0 && _sampleRate != sampleRate) + error("Changing the Audio::Mixer output sample rate is not supported"); + _sampleRate = sampleRate; +} + +void MixerImpl::insertChannel(SoundHandle *handle, Channel *chan) { int index = -1; for (int i = 0; i != NUM_CHANNELS; i++) { @@ -138,7 +144,7 @@ void Mixer::insertChannel(SoundHandle *handle, Channel *chan) { } } if (index == -1) { - warning("Mixer::out of mixer slots"); + warning("MixerImpl::out of mixer slots"); delete chan; return; } @@ -151,7 +157,7 @@ void Mixer::insertChannel(SoundHandle *handle, Channel *chan) { } } -void Mixer::playRaw( +void MixerImpl::playRaw( SoundType type, SoundHandle *handle, void *sound, @@ -166,7 +172,7 @@ void Mixer::playRaw( playInputStream(type, handle, input, id, volume, balance, true, false, ((flags & Mixer::FLAG_REVERSE_STEREO) != 0)); } -void Mixer::playInputStream( +void MixerImpl::playInputStream( SoundType type, SoundHandle *handle, AudioStream *input, @@ -198,8 +204,13 @@ void Mixer::playInputStream( insertChannel(handle, chan); } -void Mixer::mix(int16 *buf, uint len) { +void MixerImpl::mixCallback(byte *samples, uint len) { + assert(samples); + Common::StackLock lock(_mutex); + + int16 *buf = (int16 *)samples; + len >>= 2; // Since the mixer callback has been called, the mixer must be ready... _mixerReady = true; @@ -218,15 +229,7 @@ void Mixer::mix(int16 *buf, uint len) { } } -void Mixer::mixCallback(void *s, byte *samples, int len) { - assert(s); - assert(samples); - // Len is the number of bytes in the buffer; we divide it by - // four to get the number of samples (stereo 16 bit). - ((Mixer *)s)->mix((int16 *)samples, len >> 2); -} - -void Mixer::stopAll() { +void MixerImpl::stopAll() { Common::StackLock lock(_mutex); for (int i = 0; i != NUM_CHANNELS; i++) { if (_channels[i] != 0 && !_channels[i]->isPermanent()) { @@ -236,7 +239,7 @@ void Mixer::stopAll() { } } -void Mixer::stopID(int id) { +void MixerImpl::stopID(int id) { Common::StackLock lock(_mutex); for (int i = 0; i != NUM_CHANNELS; i++) { if (_channels[i] != 0 && _channels[i]->getId() == id) { @@ -246,7 +249,7 @@ void Mixer::stopID(int id) { } } -void Mixer::stopHandle(SoundHandle handle) { +void MixerImpl::stopHandle(SoundHandle handle) { Common::StackLock lock(_mutex); // Simply ignore stop requests for handles of sounds that already terminated @@ -258,7 +261,7 @@ void Mixer::stopHandle(SoundHandle handle) { _channels[index] = 0; } -void Mixer::setChannelVolume(SoundHandle handle, byte volume) { +void MixerImpl::setChannelVolume(SoundHandle handle, byte volume) { Common::StackLock lock(_mutex); const int index = handle._val % NUM_CHANNELS; @@ -268,7 +271,7 @@ void Mixer::setChannelVolume(SoundHandle handle, byte volume) { _channels[index]->setVolume(volume); } -void Mixer::setChannelBalance(SoundHandle handle, int8 balance) { +void MixerImpl::setChannelBalance(SoundHandle handle, int8 balance) { Common::StackLock lock(_mutex); const int index = handle._val % NUM_CHANNELS; @@ -278,7 +281,7 @@ void Mixer::setChannelBalance(SoundHandle handle, int8 balance) { _channels[index]->setBalance(balance); } -uint32 Mixer::getSoundElapsedTime(SoundHandle handle) { +uint32 MixerImpl::getSoundElapsedTime(SoundHandle handle) { Common::StackLock lock(_mutex); const int index = handle._val % NUM_CHANNELS; @@ -288,7 +291,7 @@ uint32 Mixer::getSoundElapsedTime(SoundHandle handle) { return _channels[index]->getElapsedTime(); } -void Mixer::pauseAll(bool paused) { +void MixerImpl::pauseAll(bool paused) { Common::StackLock lock(_mutex); for (int i = 0; i != NUM_CHANNELS; i++) { if (_channels[i] != 0) { @@ -297,7 +300,7 @@ void Mixer::pauseAll(bool paused) { } } -void Mixer::pauseID(int id, bool paused) { +void MixerImpl::pauseID(int id, bool paused) { Common::StackLock lock(_mutex); for (int i = 0; i != NUM_CHANNELS; i++) { if (_channels[i] != 0 && _channels[i]->getId() == id) { @@ -307,7 +310,7 @@ void Mixer::pauseID(int id, bool paused) { } } -void Mixer::pauseHandle(SoundHandle handle, bool paused) { +void MixerImpl::pauseHandle(SoundHandle handle, bool paused) { Common::StackLock lock(_mutex); // Simply ignore (un)pause requests for sounds that already terminated @@ -318,7 +321,7 @@ void Mixer::pauseHandle(SoundHandle handle, bool paused) { _channels[index]->pause(paused); } -bool Mixer::isSoundIDActive(int id) { +bool MixerImpl::isSoundIDActive(int id) { Common::StackLock lock(_mutex); for (int i = 0; i != NUM_CHANNELS; i++) if (_channels[i] && _channels[i]->getId() == id) @@ -326,7 +329,7 @@ bool Mixer::isSoundIDActive(int id) { return false; } -int Mixer::getSoundID(SoundHandle handle) { +int MixerImpl::getSoundID(SoundHandle handle) { Common::StackLock lock(_mutex); const int index = handle._val % NUM_CHANNELS; if (_channels[index] && _channels[index]->_handle._val == handle._val) @@ -334,13 +337,13 @@ int Mixer::getSoundID(SoundHandle handle) { return 0; } -bool Mixer::isSoundHandleActive(SoundHandle handle) { +bool MixerImpl::isSoundHandleActive(SoundHandle handle) { Common::StackLock lock(_mutex); const int index = handle._val % NUM_CHANNELS; return _channels[index] && _channels[index]->_handle._val == handle._val; } -bool Mixer::hasActiveChannelOfType(SoundType type) { +bool MixerImpl::hasActiveChannelOfType(SoundType type) { Common::StackLock lock(_mutex); for (int i = 0; i != NUM_CHANNELS; i++) if (_channels[i] && _channels[i]->_type == type) @@ -348,7 +351,7 @@ bool Mixer::hasActiveChannelOfType(SoundType type) { return false; } -void Mixer::setVolumeForSoundType(SoundType type, int volume) { +void MixerImpl::setVolumeForSoundType(SoundType type, int volume) { assert(0 <= type && type < ARRAYSIZE(_volumeForSoundType)); // Check range @@ -363,7 +366,7 @@ void Mixer::setVolumeForSoundType(SoundType type, int volume) { _volumeForSoundType[type] = volume; } -int Mixer::getVolumeForSoundType(SoundType type) const { +int MixerImpl::getVolumeForSoundType(SoundType type) const { assert(0 <= type && type < ARRAYSIZE(_volumeForSoundType)); return _volumeForSoundType[type]; @@ -443,7 +446,7 @@ uint32 Channel::getElapsedTime() { // Convert the number of samples into a time duration. To avoid // overflow, this has to be done in a somewhat non-obvious way. - uint rate = _mixer->getOutputRate(); + uint32 rate = _mixer->getOutputRate(); uint32 seconds = _samplesConsumed / rate; uint32 milliseconds = (1000 * (_samplesConsumed % rate)) / rate; diff --git a/sound/mixer.h b/sound/mixer.h index 26ae0898fd6..28988256b31 100644 --- a/sound/mixer.h +++ b/sound/mixer.h @@ -38,6 +38,7 @@ namespace Audio { class AudioStream; class Channel; class Mixer; +class MixerImpl; /** * A SoundHandle instances corresponds to a specific sound @@ -47,7 +48,7 @@ class Mixer; */ class SoundHandle { friend class Channel; - friend class Mixer; + friend class MixerImpl; uint32 _val; public: inline SoundHandle() : _val(0xFFFFFFFF) {} @@ -104,24 +105,9 @@ public: kMaxMixerVolume = 256 }; -private: - enum { - NUM_CHANNELS = 16 - }; - - OSystem *_syst; - Common::Mutex _mutex; - - int _volumeForSoundType[4]; - - uint32 _handleSeed; - Channel *_channels[NUM_CHANNELS]; - - bool _mixerReady; - public: - Mixer(); - ~Mixer(); + Mixer() {} + virtual ~Mixer() {} @@ -132,8 +118,10 @@ public: * sync with an audio stream. In particular, the Adlib MIDI emulation... * * @return whether the mixer is ready and setup + * + * @todo get rid of this? */ - bool isReady() const { return _mixerReady; } + virtual bool isReady() const = 0; @@ -143,12 +131,12 @@ public: * (using the makeLinearInputStream factory function), which is then * passed on to playInputStream. */ - void playRaw( + virtual void playRaw( SoundType type, SoundHandle *handle, void *sound, uint32 size, uint rate, byte flags, int id = -1, byte volume = kMaxChannelVolume, int8 balance = 0, - uint32 loopStart = 0, uint32 loopEnd = 0); + uint32 loopStart = 0, uint32 loopEnd = 0) = 0; /** * Start playing the given audio input stream. @@ -170,35 +158,35 @@ public: * not stop this particular stream * @param reverseStereo a flag indicating whether left and right channels shall be swapped */ - void playInputStream( + virtual void playInputStream( SoundType type, SoundHandle *handle, AudioStream *input, int id = -1, byte volume = kMaxChannelVolume, int8 balance = 0, bool autofreeStream = true, bool permanent = false, - bool reverseStereo = false); + bool reverseStereo = false) = 0; /** * Stop all currently playing sounds. */ - void stopAll(); + virtual void stopAll() = 0; /** * Stop playing the sound with given ID. * * @param id the ID of the sound to affect */ - void stopID(int id); + virtual void stopID(int id) = 0; /** * Stop playing the sound corresponding to the given handle. * * @param handle the sound to affect */ - void stopHandle(SoundHandle handle); + virtual void stopHandle(SoundHandle handle) = 0; @@ -208,7 +196,7 @@ public: * * @param paused true to pause everything, false to unpause */ - void pauseAll(bool paused); + virtual void pauseAll(bool paused) = 0; /** * Pause/unpause the sound with the given ID. @@ -216,7 +204,7 @@ public: * @param id the ID of the sound to affect * @param paused true to pause the sound, false to unpause it */ - void pauseID(int id, bool paused); + virtual void pauseID(int id, bool paused) = 0; /** * Pause/unpause the sound corresponding to the given handle. @@ -224,7 +212,7 @@ public: * @param handle the sound to affect * @param paused true to pause the sound, false to unpause it */ - void pauseHandle(SoundHandle handle, bool paused); + virtual void pauseHandle(SoundHandle handle, bool paused) = 0; @@ -234,7 +222,7 @@ public: * @param id the ID of the sound to query * @return true if the sound is active */ - bool isSoundIDActive(int id); + virtual bool isSoundIDActive(int id) = 0; /** * Get the sound ID of handle sound @@ -242,7 +230,7 @@ public: * @param handle sound to query * @return sound ID if active */ - int getSoundID(SoundHandle handle); + virtual int getSoundID(SoundHandle handle) = 0; /** * Check if a sound with the given handle is active. @@ -250,7 +238,7 @@ public: * @param handle sound to query * @return true if the sound is active */ - bool isSoundHandleActive(SoundHandle handle); + virtual bool isSoundHandleActive(SoundHandle handle) = 0; @@ -260,7 +248,7 @@ public: * @param handle the sound to affect * @param volume the new channel volume (0 - kMaxChannelVolume) */ - void setChannelVolume(SoundHandle handle, byte volume); + virtual void setChannelVolume(SoundHandle handle, byte volume) = 0; /** * Set the channel balance for the given handle. @@ -269,12 +257,12 @@ public: * @param balance the new channel balance: * (-127 ... 0 ... 127) corresponds to (left ... center ... right) */ - void setChannelBalance(SoundHandle handle, int8 balance); + virtual void setChannelBalance(SoundHandle handle, int8 balance) = 0; /** * Get approximation of for how long the channel has been playing. */ - uint32 getSoundElapsedTime(SoundHandle handle); + virtual uint32 getSoundElapsedTime(SoundHandle handle) = 0; /** * Check whether any channel of the given sound type is active. @@ -284,7 +272,7 @@ public: * @param type the sound type to look for * @return true if any channels of the specified type are active. */ - bool hasActiveChannelOfType(SoundType type); + virtual bool hasActiveChannelOfType(SoundType type) = 0; /** * Set the volume for the given sound type. @@ -292,7 +280,7 @@ public: * @param type the sound type * @param volume the new global volume, 0 - kMaxMixerVolume */ - void setVolumeForSoundType(SoundType type, int volume); + virtual void setVolumeForSoundType(SoundType type, int volume) = 0; /** * Query the global volume. @@ -300,7 +288,7 @@ public: * @param type the sound type * @return the global music volume, 0 - kMaxMixerVolume */ - int getVolumeForSoundType(SoundType type) const; + virtual int getVolumeForSoundType(SoundType type) const = 0; /** * Query the system's audio output sample rate. This returns @@ -308,26 +296,7 @@ public: * * @return the output sample rate in Hz */ - uint getOutputRate() const; - -protected: - void insertChannel(SoundHandle *handle, Channel *chan); - - /** - * Internal main method -- all the actual mixing work is done from here. - */ - void mix(int16 * buf, uint len); - - // FIXME: temporary "public" to allow access to mixCallback - // from within OSystem::makeMixer() -public: - /** - * The mixer callback function, passed on to OSystem::setSoundCallback(). - * This simply calls the mix() method. - */ - static void mixCallback(void *s, byte *samples, int len); - - void setReady(bool ready) { _mixerReady = ready; } + virtual uint getOutputRate() const = 0; }; diff --git a/sound/mixer_intern.h b/sound/mixer_intern.h new file mode 100644 index 00000000000..46ba97e66c0 --- /dev/null +++ b/sound/mixer_intern.h @@ -0,0 +1,154 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public 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 SOUND_MIXER_INTERN_H +#define SOUND_MIXER_INTERN_H + +#include "common/scummsys.h" +#include "common/mutex.h" +#include "sound/mixer.h" + +namespace Audio { + +/** + * The (default) implementation of the ScummVM audio mixing subsystem. + * + * Backends are responsible for allocating (and later releasing) an instance + * of this class, which engines can access via OSystem::getMixer(). + * + * Initialisation of instances of this class usually happens as follows: + * 1) Creat a new Audio::MixerImpl instance. + * 2) Set the hardware output sample rate via the setSampleRate() method. + * 3) Hook up the mixCallback() in a suitable audio processing thread/callback. + * 4) Change the mixer into ready mode via setReady(true). + * 5) Start audio processing (e.g. by resuming the audio thread, if applicable). + * + * In the future, we might make it possible for backends to provide + * (partial) alternative implementations of the mixer, e.g. to make + * better use of native sound mixing support on low-end devices. + * + * @see OSystem::getMixer() + */ +class MixerImpl : public Mixer { +private: + enum { + NUM_CHANNELS = 16 + }; + + OSystem *_syst; + Common::Mutex _mutex; + + uint _sampleRate; + bool _mixerReady; + uint32 _handleSeed; + + int _volumeForSoundType[4]; + Channel *_channels[NUM_CHANNELS]; + + +public: + MixerImpl(OSystem *system); + ~MixerImpl(); + + virtual bool isReady() const { return _mixerReady; } + + virtual void playRaw( + SoundType type, + SoundHandle *handle, + void *sound, uint32 size, uint rate, byte flags, + int id = -1, byte volume = 255, int8 balance = 0, + uint32 loopStart = 0, uint32 loopEnd = 0); + + virtual void playInputStream( + SoundType type, + SoundHandle *handle, + AudioStream *input, + int id = -1, byte volume = 255, int8 balance = 0, + bool autofreeStream = true, + bool permanent = false, + bool reverseStereo = false); + + + + virtual void stopAll(); + virtual void stopID(int id); + virtual void stopHandle(SoundHandle handle); + + virtual void pauseAll(bool paused); + virtual void pauseID(int id, bool paused); + virtual void pauseHandle(SoundHandle handle, bool paused); + + virtual bool isSoundIDActive(int id); + virtual int getSoundID(SoundHandle handle); + + virtual bool isSoundHandleActive(SoundHandle handle); + + virtual void setChannelVolume(SoundHandle handle, byte volume); + virtual void setChannelBalance(SoundHandle handle, int8 balance); + + virtual uint32 getSoundElapsedTime(SoundHandle handle); + + virtual bool hasActiveChannelOfType(SoundType type); + + virtual void setVolumeForSoundType(SoundType type, int volume); + virtual int getVolumeForSoundType(SoundType type) const; + + virtual uint getOutputRate() const; + +protected: + void insertChannel(SoundHandle *handle, Channel *chan); + +public: + /** + * The mixer callback function, to be called at regular intervals by + * the backend (e.g. from an audio mixing thread). All the actual mixing + * work is done from here. + */ + void mixCallback(byte *samples, uint len); + + /** + * Set the internal 'is ready' flag of the mixer. + * Backends should invoke Mixer::setReady(true) once initialisation of + * their audio system has been completed (and in particular, *after* + * setOutputRate() has been called). + */ + void setReady(bool ready); + + /** + * Set the output sample rate. + * + * @param sampleRate the new output sample rate + * + * @note Right now, this can be done exactly ONCE. That is, the mixer + * currently does not support changing the output sample rate after it + * has been set for the first time. This may change in the future. + */ + void setOutputRate(uint sampleRate); +}; + + +} // End of namespace Audio + +#endif diff --git a/sound/mp3.cpp b/sound/mp3.cpp index eea725ce3ae..70467bdb396 100644 --- a/sound/mp3.cpp +++ b/sound/mp3.cpp @@ -121,7 +121,7 @@ MP3InputStream::MP3InputStream(Common::SeekableReadStream *inStream, bool dispos mad_timer_add(&length, start); mad_timer_negate(&length); - if (mad_timer_sign(end) == 0) { + if (mad_timer_sign(end) != 0) { mad_timer_add(&length, end); } else { mad_stream_init(&_stream); diff --git a/sound/softsynth/mt32.cpp b/sound/softsynth/mt32.cpp index 053df544b18..360ef4539d9 100644 --- a/sound/softsynth/mt32.cpp +++ b/sound/softsynth/mt32.cpp @@ -80,37 +80,41 @@ public: }; class MT32File : public MT32Emu::File { - Common::File file; + Common::File _in; + Common::DumpFile _out; public: bool open(const char *filename, OpenMode mode) { - Common::File::AccessMode accessMode = mode == OpenMode_read ? Common::File::kFileReadMode : Common::File::kFileWriteMode; - return file.open(filename, accessMode); + if (mode == OpenMode_read) + return _in.open(filename); + else + return _out.open(filename); } void close() { - return file.close(); + _in.close(); + _out.close(); } size_t read(void *in, size_t size) { - return file.read(in, size); + return _in.read(in, size); } bool readLine(char *in, size_t size) { - return file.readLine(in, size) != NULL; + return _in.readLine(in, size) != NULL; } bool readBit8u(MT32Emu::Bit8u *in) { - byte b = file.readByte(); - if (file.eof()) + byte b = _in.readByte(); + if (_in.eof()) return false; *in = b; return true; } size_t write(const void *in, size_t size) { - return file.write(in, size); + return _out.write(in, size); } bool writeBit8u(MT32Emu::Bit8u out) { - file.writeByte(out); - return !file.ioFailed(); + _out.writeByte(out); + return !_out.ioFailed(); } bool isEOF() { - return file.eof(); + return _in.isOpen() ? _in.eof() : _out.eof(); } }; diff --git a/test/common/bufferedreadstream.h b/test/common/bufferedreadstream.h new file mode 100644 index 00000000000..7733949d9a5 --- /dev/null +++ b/test/common/bufferedreadstream.h @@ -0,0 +1,27 @@ +#include + +#include "common/stream.h" + +class BufferedReadStreamTestSuite : public CxxTest::TestSuite { + public: + void test_traverse(void) { + byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + Common::MemoryReadStream ms(contents, 10); + + // Use a buffer size of 4 -- note that 10 % 4 != 0, + // so we test what happens if the cache can't be completly + // refilled. + Common::BufferedReadStream srs(&ms, 4); + + int i; + byte b; + for (i = 0; i < 10; ++i) { + TS_ASSERT( !srs.eos() ); + + b = srs.readByte(); + TS_ASSERT_EQUALS( i, b ); + } + + TS_ASSERT( srs.eos() ); + } +}; diff --git a/test/common/bufferedseekablereadstream.h b/test/common/bufferedseekablereadstream.h new file mode 100644 index 00000000000..63941904cde --- /dev/null +++ b/test/common/bufferedseekablereadstream.h @@ -0,0 +1,65 @@ +#include + +#include "common/stream.h" + +class BufferedSeekableReadStreamTestSuite : public CxxTest::TestSuite { + public: + void test_traverse(void) { + byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + Common::MemoryReadStream ms(contents, 10); + + Common::BufferedSeekableReadStream ssrs(&ms, 4); + + int i; + byte b; + for (i = 0; i < 10; ++i) { + TS_ASSERT( !ssrs.eos() ); + + TS_ASSERT_EQUALS( i, ssrs.pos() ); + + ssrs.read(&b, 1); + TS_ASSERT_EQUALS( i, b ); + } + + TS_ASSERT( ssrs.eos() ); + } + + void test_seek(void) { + byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; + Common::MemoryReadStream ms(contents, 10); + + Common::BufferedSeekableReadStream ssrs(&ms, 4); + byte b; + + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)0 ); + + ssrs.seek(1, SEEK_SET); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)1 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 1 ); + + ssrs.seek(5, SEEK_CUR); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)7 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 7 ); + + ssrs.seek(-3, SEEK_CUR); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)5 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 5 ); + + ssrs.seek(0, SEEK_END); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)10 ); + TS_ASSERT( ssrs.eos() ); + + ssrs.seek(3, SEEK_END); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)7 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 7 ); + + ssrs.seek(8, SEEK_END); + TS_ASSERT_EQUALS( ssrs.pos(), (uint32)2 ); + b = ssrs.readByte(); + TS_ASSERT_EQUALS( b, 2 ); + } +}; diff --git a/test/common/func.h b/test/common/func.h new file mode 100644 index 00000000000..bf9b2ddff93 --- /dev/null +++ b/test/common/func.h @@ -0,0 +1,60 @@ +#include + +#include "common/func.h" + +void myFunction1(int &dst, const int src) { dst = src; } +void myFunction2(const int src, int &dst) { dst = src; } + +class FuncTestSuite : public CxxTest::TestSuite +{ + public: + void test_bind1st() { + int dst = 0; + Common::bind1st(Common::ptr_fun(myFunction1), dst)(1); + TS_ASSERT_EQUALS(dst, 1); + } + + void test_bind2nd() { + int dst = 0; + Common::bind2nd(Common::ptr_fun(myFunction2), dst)(1); + TS_ASSERT_EQUALS(dst, 1); + } + + struct Foo { + void fooAdd(int &foo) { + ++foo; + } + + void fooSub(int &foo) const { + --foo; + } + }; + + void test_mem_fun_ref() { + Foo myFoos[4]; + int counter = 0; + + Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun_ref(&Foo::fooAdd), counter)); + TS_ASSERT_EQUALS(counter, 4); + + Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun_ref(&Foo::fooSub), counter)); + TS_ASSERT_EQUALS(counter, 0); + } + + void test_mem_fun() { + Foo *myFoos[4]; + for (int i = 0; i < 4; ++i) + myFoos[i] = new Foo; + + int counter = 0; + + Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun(&Foo::fooAdd), counter)); + TS_ASSERT_EQUALS(counter, 4); + + Common::for_each(myFoos, myFoos+4, Common::bind2nd(Common::mem_fun(&Foo::fooSub), counter)); + TS_ASSERT_EQUALS(counter, 0); + + for (int i = 0; i < 4; ++i) + delete myFoos[i]; + } +}; diff --git a/test/common/ptr.h b/test/common/ptr.h index 11ed52d4b9c..986330057cd 100644 --- a/test/common/ptr.h +++ b/test/common/ptr.h @@ -61,6 +61,9 @@ class PtrTestSuite : public CxxTest::TestSuite TS_ASSERT(p1 != 0); TS_ASSERT(p2 == 0); + + p1.reset(); + TS_ASSERT(!p1); } struct A { diff --git a/test/common/seekablesubreadstream.h b/test/common/seekablesubreadstream.h index c4b21667c73..4e517093a58 100644 --- a/test/common/seekablesubreadstream.h +++ b/test/common/seekablesubreadstream.h @@ -2,22 +2,19 @@ #include "common/stream.h" -class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite -{ +class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite { public: - void test_traverse( void ) - { + void test_traverse(void) { byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10); + Common::MemoryReadStream ms(contents, 10); int start = 2, end = 8; - Common::SeekableSubReadStream ssrs = Common::SeekableSubReadStream(&ms, start, end); + Common::SeekableSubReadStream ssrs(&ms, start, end); int i; byte b; - for (i = start; i < end; ++i) - { + for (i = start; i < end; ++i) { TS_ASSERT( !ssrs.eos() ); TS_ASSERT_EQUALS( uint32(i - start), ssrs.pos() ); @@ -29,12 +26,11 @@ class SeekableSubReadStreamTestSuite : public CxxTest::TestSuite TS_ASSERT( ssrs.eos() ); } - void test_seek( void ) - { + void test_seek(void) { byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10); + Common::MemoryReadStream ms(contents, 10); - Common::SeekableSubReadStream ssrs = Common::SeekableSubReadStream(&ms, 1, 9); + Common::SeekableSubReadStream ssrs(&ms, 1, 9); byte b; TS_ASSERT_EQUALS( ssrs.pos(), (uint32)0 ); diff --git a/test/common/str.h b/test/common/str.h index dcba554b5a5..72d4df6f615 100644 --- a/test/common/str.h +++ b/test/common/str.h @@ -5,129 +5,156 @@ class StringTestSuite : public CxxTest::TestSuite { public: - void test_empty_clear( void ) - { + void test_constructors(void) { + Common::String str("test-string"); + TS_ASSERT( str == "test-string"); + str = Common::String(str.c_str()+5, 3); + TS_ASSERT( str == "str"); + str = "test-string"; + TS_ASSERT( str == "test-string"); + str = Common::String(str.c_str()+5, str.c_str()+8); + TS_ASSERT( str == "str"); + } + + void test_trim(void) { + Common::String str(" This is a s tring with spaces "); + Common::String str2 = str; + str.trim(); + TS_ASSERT( str == "This is a s tring with spaces"); + TS_ASSERT( str2 == " This is a s tring with spaces "); + } + + void test_empty_clear(void) { Common::String str("test"); - TS_ASSERT( !str.empty() ); + TS_ASSERT( !str.empty()); str.clear(); - TS_ASSERT( str.empty() ); + TS_ASSERT( str.empty()); } - void test_lastChar( void ) - { + void test_lastChar(void) { Common::String str; - TS_ASSERT_EQUALS( str.lastChar(), '\0' ); + TS_ASSERT_EQUALS(str.lastChar(), '\0'); str = "test"; - TS_ASSERT_EQUALS( str.lastChar(), 't' ); + TS_ASSERT_EQUALS(str.lastChar(), 't'); Common::String str2("bar"); - TS_ASSERT_EQUALS( str2.lastChar(), 'r' ); + TS_ASSERT_EQUALS(str2.lastChar(), 'r'); } - void test_concat1( void ) - { + void test_concat1(void) { Common::String str("foo"); Common::String str2("bar"); str += str2; - TS_ASSERT_EQUALS( str, "foobar" ); - TS_ASSERT_EQUALS( str2, "bar" ); + TS_ASSERT_EQUALS(str, "foobar"); + TS_ASSERT_EQUALS(str2, "bar"); } - void test_concat2( void ) - { + void test_concat2(void) { Common::String str("foo"); str += "bar"; - TS_ASSERT_EQUALS( str, "foobar" ); + TS_ASSERT_EQUALS(str, "foobar"); } - void test_concat3( void ) - { + void test_concat3(void) { Common::String str("foo"); str += 'X'; - TS_ASSERT_EQUALS( str, "fooX" ); + TS_ASSERT_EQUALS(str, "fooX"); } - void test_refCount( void ) - { + void test_refCount(void) { + // using internal storage Common::String foo1("foo"); - Common::String foo2("foo"); + Common::String foo2(foo1); Common::String foo3(foo2); foo3 += 'X'; - TS_ASSERT_EQUALS( foo2, foo1 ); - TS_ASSERT_EQUALS( foo2, "foo" ); - TS_ASSERT_EQUALS( foo3, "foo""X" ); + TS_ASSERT_EQUALS(foo1, "foo"); + TS_ASSERT_EQUALS(foo2, "foo"); + TS_ASSERT_EQUALS(foo3, "foo""X"); + foo2 = 'x'; + TS_ASSERT_EQUALS(foo1, "foo"); + TS_ASSERT_EQUALS(foo2, "x"); + TS_ASSERT_EQUALS(foo3, "foo""X"); } - void test_refCount2( void ) - { + void test_refCount2(void) { + // using external storage Common::String foo1("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); - Common::String foo2("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + Common::String foo2(foo1); Common::String foo3(foo2); foo3 += 'X'; - TS_ASSERT_EQUALS( foo2, foo1 ); - TS_ASSERT_EQUALS( foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" ); - TS_ASSERT_EQUALS( foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X" ); + TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X"); + foo2 = 'x'; + TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo2, "x"); + TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""X"); } - void test_refCount3( void ) - { + void test_refCount3(void) { Common::String foo1("0123456789abcdefghijk"); - Common::String foo2("0123456789abcdefghijk"); + Common::String foo2(foo1); Common::String foo3(foo2); foo3 += "0123456789abcdefghijk"; - TS_ASSERT_EQUALS( foo2, foo1 ); - TS_ASSERT_EQUALS( foo2, "0123456789abcdefghijk" ); - TS_ASSERT_EQUALS( foo3, "0123456789abcdefghijk""0123456789abcdefghijk" ); + TS_ASSERT_EQUALS(foo1, foo2); + TS_ASSERT_EQUALS(foo2, "0123456789abcdefghijk"); + TS_ASSERT_EQUALS(foo3, "0123456789abcdefghijk""0123456789abcdefghijk"); + foo2 = 'x'; + TS_ASSERT_EQUALS(foo1, "0123456789abcdefghijk"); + TS_ASSERT_EQUALS(foo2, "x"); + TS_ASSERT_EQUALS(foo3, "0123456789abcdefghijk""0123456789abcdefghijk"); } - void test_refCount4( void ) - { + void test_refCount4(void) { Common::String foo1("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); - Common::String foo2("fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + Common::String foo2(foo1); Common::String foo3(foo2); foo3 += "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"; - TS_ASSERT_EQUALS( foo2, foo1 ); - TS_ASSERT_EQUALS( foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" ); - TS_ASSERT_EQUALS( foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd" ); + TS_ASSERT_EQUALS(foo1, foo2); + TS_ASSERT_EQUALS(foo2, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + foo2 = 'x'; + TS_ASSERT_EQUALS(foo1, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); + TS_ASSERT_EQUALS(foo2, "x"); + TS_ASSERT_EQUALS(foo3, "fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd""fooasdkadklasdjklasdjlkasjdlkasjdklasjdlkjasdasd"); } - void test_hasPrefix( void ) - { + void test_hasPrefix(void) { Common::String str("this/is/a/test, haha"); - TS_ASSERT_EQUALS( str.hasPrefix(""), true ); - TS_ASSERT_EQUALS( str.hasPrefix("this"), true ); - TS_ASSERT_EQUALS( str.hasPrefix("thit"), false ); - TS_ASSERT_EQUALS( str.hasPrefix("foo"), false ); + TS_ASSERT_EQUALS(str.hasPrefix(""), true); + TS_ASSERT_EQUALS(str.hasPrefix("this"), true); + TS_ASSERT_EQUALS(str.hasPrefix("thit"), false); + TS_ASSERT_EQUALS(str.hasPrefix("foo"), false); } - void test_hasSuffix( void ) - { + void test_hasSuffix(void) { Common::String str("this/is/a/test, haha"); - TS_ASSERT_EQUALS( str.hasSuffix(""), true ); - TS_ASSERT_EQUALS( str.hasSuffix("haha"), true ); - TS_ASSERT_EQUALS( str.hasSuffix("hahb"), false ); - TS_ASSERT_EQUALS( str.hasSuffix("hahah"), false ); + TS_ASSERT_EQUALS(str.hasSuffix(""), true); + TS_ASSERT_EQUALS(str.hasSuffix("haha"), true); + TS_ASSERT_EQUALS(str.hasSuffix("hahb"), false); + TS_ASSERT_EQUALS(str.hasSuffix("hahah"), false); } - void test_contains( void ) - { + void test_contains(void) { Common::String str("this/is/a/test, haha"); - TS_ASSERT_EQUALS( str.contains(""), true ); - TS_ASSERT_EQUALS( str.contains("haha"), true ); - TS_ASSERT_EQUALS( str.contains("hahb"), false ); - TS_ASSERT_EQUALS( str.contains("test"), true ); + TS_ASSERT_EQUALS(str.contains(""), true); + TS_ASSERT_EQUALS(str.contains("haha"), true); + TS_ASSERT_EQUALS(str.contains("hahb"), false); + TS_ASSERT_EQUALS(str.contains("test"), true); } - void test_toLowercase( void ) - { + void test_toLowercase(void) { Common::String str("Test it, NOW! 42"); + Common::String str2 = str; str.toLowercase(); - TS_ASSERT_EQUALS( str, "test it, now! 42" ); + TS_ASSERT_EQUALS(str, "test it, now! 42"); + TS_ASSERT_EQUALS(str2, "Test it, NOW! 42"); } - void test_toUppercase( void ) - { + void test_toUppercase(void) { Common::String str("Test it, NOW! 42"); + Common::String str2 = str; str.toUppercase(); - TS_ASSERT_EQUALS( str, "TEST IT, NOW! 42" ); + TS_ASSERT_EQUALS(str, "TEST IT, NOW! 42"); + TS_ASSERT_EQUALS(str2, "Test it, NOW! 42"); } }; diff --git a/test/common/subreadstream.h b/test/common/subreadstream.h index c48f57c3a85..4e14448c06d 100644 --- a/test/common/subreadstream.h +++ b/test/common/subreadstream.h @@ -2,25 +2,22 @@ #include "common/stream.h" -class SubReadStreamTestSuite : public CxxTest::TestSuite -{ +class SubReadStreamTestSuite : public CxxTest::TestSuite { public: - void test_traverse( void ) - { + void test_traverse(void) { byte contents[10] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; - Common::MemoryReadStream ms = Common::MemoryReadStream(contents, 10); + Common::MemoryReadStream ms(contents, 10); int end = 5; - Common::SubReadStream srs = Common::SubReadStream(&ms, end); + Common::SubReadStream srs(&ms, end); int i; byte b; - for (i = 0; i < end; ++i) - { + for (i = 0; i < end; ++i) { TS_ASSERT( !srs.eos() ); - srs.read(&b, 1); + b = srs.readByte(); TS_ASSERT_EQUALS( i, b ); } diff --git a/tools/create_lure/create_lure_dat.cpp b/tools/create_lure/create_lure_dat.cpp index 09744fef1f7..92501043497 100644 --- a/tools/create_lure/create_lure_dat.cpp +++ b/tools/create_lure/create_lure_dat.cpp @@ -1312,14 +1312,14 @@ const char *englishTextStrings[NUM_TEXT_ENTRIES] = { const char *italianTextStrings[NUM_TEXT_ENTRIES] = { "Prendi", NULL, "Spingi", "Tira", "Aziona", "Apri", "Chiudi", "Blocca", - "Sblocca", "Usa", "Dai", "Parla con", "Ordina a", "Buy", "Guarda", "Osserva", - "Guarda tra", "Chiedi", NULL, "Bevi", "Stato", "Vai a", "Ritorna", + "Sblocca", "Usa", "Dai", "Parla con", "Ordina a", "Compra", "Guarda", "Osserva", + "Guarda attraverso", "Chiedi", NULL, "Bevi", "Stato", "Vai a", "Ritorna", "Corrompi", "Esamina", - "Inform", "Reavvia", "Salva gioco", "Ripristina", "Abbandona", "Testo lento\x8B", + "Crediti", "Ricomincia", "Salva gioco", "Ripristina", "Abbandona", "Testo lento\x8B", "Testo veloce\x8B", "Sonoro acceso", "Sonoro spento", "(niente)", " per ", " a ", " su ", "e poi", "finito", "Sei sicuro (s/n)?", - "Stai portando ", "niente", "e hai ", "soldi", "soldis", + "Stai portando ", "niente", "Hai ", "soldo", "soldi", NULL, "l' ", "la ", NULL, "le ", "i ", "il ", NULL, NULL, NULL }; diff --git a/tools/credits.pl b/tools/credits.pl index fa806db7de2..7df218e4c01 100755 --- a/tools/credits.pl +++ b/tools/credits.pl @@ -566,11 +566,21 @@ begin_credits("Credits"); begin_section("SAGA"); add_person("Torbjörn Andersson", "eriktorbjorn", ""); + add_person("Sven Hesse", "DrMcCoy", ""); add_person("Filippos Karapetis", "[md5]", ""); add_person("Andrew Kurushin", "ajax16384", ""); add_person("Eugene Sandulenko", "sev", ""); end_section(); + begin_section("Tinsel;"); + add_person("Torbjörn Andersson", "eriktorbjorn", ""); + add_person("Paul Gilbert", "dreammaster", ""); + add_person("Sven Hesse", "DrMcCoy", ""); + add_person("Max Horn", "Fingolfin", ""); + add_person("Filippos Karapetis", "[md5]", ""); + add_person("Joost Peters", "joostp", ""); + end_section(); + begin_section("Touché"); add_person("Gregory Montoir", "cyx", ""); end_section(); diff --git a/tools/scumm-md5.txt b/tools/scumm-md5.txt index 721c43b568c..4da978914d4 100644 --- a/tools/scumm-md5.txt +++ b/tools/scumm-md5.txt @@ -58,14 +58,14 @@ maniac Maniac Mansion 7f45ddd6dbfbf8f80c0c0efea4c295bc 1972 en DOS V1 V1 - Fingolfin - d8d07efcb88f396bee0b402b10c3b1c9 262144 us NES NES - - - 3905799e081b80a61d4460b7b733c206 262144 gb NES NES - - + d8d07efcb88f396bee0b402b10c3b1c9 262144 gb NES NES - - + 3905799e081b80a61d4460b7b733c206 262144 us NES NES - - 81bbfa181184cb494e7a81dcfa94fbd9 262144 fr NES NES - - 257f8c14d8c584f7ddd601bcb00920c7 262144 de NES NES - - f163cf53f7850e43fb482471e5c52e1a 262144 es NES NES - - 22d07d6c386c9c25aca5dac2a0c0d94b 262144 se NES NES - - - 17f7296f63c78642724f057fd8e736a7 -1 us NES NES extracted - - 91d5db93187fab54d823f73bd6441cb6 -1 gb NES NES extracted - + 17f7296f63c78642724f057fd8e736a7 -1 gb NES NES extracted - + 91d5db93187fab54d823f73bd6441cb6 -1 us NES NES extracted - 1c7e7db2cfab1ad62746ab680a634204 -1 fr NES NES extracted - 3a5ec90d556d4920976c5578bfbfaf79 -1 de NES NES extracted - b7d37d6b786b5a22deea3b038eca96ca 2082 es NES NES extracted - diff --git a/tools/svnpropset.sh b/tools/svnpropset.sh new file mode 100755 index 00000000000..46acba534e9 --- /dev/null +++ b/tools/svnpropset.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# This script adds common svn properties to files + +if [ $# -eq 0 ]; then + echo "Usage: $0 [FILE]..." + exit 1 +fi + +for filename in $@; do + if [ -f $filename ]; then + svn propset svn:mime-type text/plain $filename + svn propset svn:keywords "Date Rev Author URL Id" $filename + svn propset svn:eol-style native $filename + elif [ -e $filename ]; then + echo "$0: error: '$filename' is not a regular file" + else + echo "$0: error: '$filename' does not exist" + fi +done