aboutsummaryrefslogtreecommitdiff
path: root/engines/cine
diff options
context:
space:
mode:
Diffstat (limited to 'engines/cine')
-rw-r--r--engines/cine/anim.cpp213
-rw-r--r--engines/cine/anim.h5
-rw-r--r--engines/cine/bg.cpp16
-rw-r--r--engines/cine/cine.cpp55
-rw-r--r--engines/cine/cine.h6
-rw-r--r--engines/cine/detection.cpp87
-rw-r--r--engines/cine/gfx.cpp202
-rw-r--r--engines/cine/gfx.h10
-rw-r--r--engines/cine/main_loop.cpp122
-rw-r--r--engines/cine/main_loop.h5
-rw-r--r--engines/cine/msg.cpp45
-rw-r--r--engines/cine/object.cpp9
-rw-r--r--engines/cine/object.h15
-rw-r--r--engines/cine/pal.cpp36
-rw-r--r--engines/cine/pal.h2
-rw-r--r--engines/cine/part.cpp80
-rw-r--r--engines/cine/part.h8
-rw-r--r--engines/cine/prc.cpp7
-rw-r--r--engines/cine/script_fw.cpp23
-rw-r--r--engines/cine/script_os.cpp24
-rw-r--r--engines/cine/sound.cpp15
-rw-r--r--engines/cine/texte.cpp196
-rw-r--r--engines/cine/texte.h31
-rw-r--r--engines/cine/unpack.cpp8
-rw-r--r--engines/cine/unpack.h6
-rw-r--r--engines/cine/various.cpp274
-rw-r--r--engines/cine/various.h12
27 files changed, 1025 insertions, 487 deletions
diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp
index 8dbccebedf..f5cde579e6 100644
--- a/engines/cine/anim.cpp
+++ b/engines/cine/anim.cpp
@@ -49,11 +49,10 @@ struct AnimHeader2Struct {
uint16 field_E;
};
-AnimData animDataTable[NUM_MAX_ANIMDATA];
+Common::Array<AnimData> animDataTable;
static const AnimDataEntry transparencyData[] = {
{"ALPHA", 0xF},
- {"TITRE", 0xF},
{"TITRE2", 0xF},
{"ET", 0xC},
{"L311", 0x3},
@@ -511,14 +510,15 @@ int emptyAnimSpace(int start = 0) {
/*! \brief Load SPL data into animDataTable
* \param resourceName SPL filename
- * \param idx Target index in animDataTable
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded SPL data (-1 if error)
*/
-void loadSpl(const char *resourceName, int16 idx) {
+int loadSpl(const char *resourceName, int16 idx) {
int16 foundFileIdx = findFileInBundle(resourceName);
int entry;
if (foundFileIdx < 0) {
- return;
+ return -1;
}
byte *dataPtr = readBundleFile(foundFileIdx);
@@ -528,13 +528,20 @@ void loadSpl(const char *resourceName, int16 idx) {
animDataTable[entry].load(dataPtr, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize, 1, foundFileIdx, 0, currentPartName);
free(dataPtr);
+ return entry + 1;
}
/*! \brief Load 1bpp mask
* \param resourceName Mask filename
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded mask (-1 if error)
*/
-void loadMsk(const char *resourceName) {
+int loadMsk(const char *resourceName, int16 idx) {
int16 foundFileIdx = findFileInBundle(resourceName);
+ if (foundFileIdx < 0) {
+ return -1;
+ }
+
int entry = 0;
byte *dataPtr = readBundleFile(foundFileIdx);
byte *ptr;
@@ -544,21 +551,28 @@ void loadMsk(const char *resourceName) {
loadAnimHeader(animHeader, readS);
ptr = dataPtr + 0x16;
+ entry = idx < 0 ? emptyAnimSpace() : idx;
+ assert(entry >= 0);
for (int16 i = 0; i < animHeader.numFrames; i++, entry++) {
- entry = emptyAnimSpace(entry);
- assert(entry >= 0);
animDataTable[entry].load(ptr, ANIM_MASK, animHeader.frameWidth, animHeader.frameHeight, foundFileIdx, i, currentPartName);
ptr += animHeader.frameWidth * animHeader.frameHeight;
}
free(dataPtr);
+ return entry;
}
/*! \brief Load animation
* \param resourceName Animation filename
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded animation (-1 if error)
*/
-void loadAni(const char *resourceName) {
+int loadAni(const char *resourceName, int16 idx) {
int16 foundFileIdx = findFileInBundle(resourceName);
+ if (foundFileIdx < 0) {
+ return -1;
+ }
+
int entry = 0;
byte *dataPtr = readBundleFile(foundFileIdx);
byte *ptr;
@@ -571,10 +585,18 @@ void loadAni(const char *resourceName) {
transparentColor = getAnimTransparentColor(resourceName);
- for (int16 i = 0; i < animHeader.numFrames; i++, entry++) {
- entry = emptyAnimSpace(entry);
- assert(entry >= 0);
+ // TODO: Merge this special case hack into getAnimTransparentColor somehow.
+ // HACK: Versions of TITRE.ANI with height 37 use color 0xF for transparency.
+ // Versions of TITRE.ANI with height 57 use color 0x0 for transparency.
+ // Fixes bug #2057619: FW: Glitches in title display of demo (regression).
+ if (scumm_stricmp(resourceName, "TITRE.ANI") == 0 && animHeader.frameHeight == 37) {
+ transparentColor = 0xF;
+ }
+
+ entry = idx < 0 ? emptyAnimSpace() : idx;
+ assert(entry >= 0);
+ for (int16 i = 0; i < animHeader.numFrames; i++, entry++) {
// special case transparency handling
if (!strcmp(resourceName, "L2202.ANI")) {
transparentColor = i < 2 ? 0 : 7;
@@ -587,6 +609,7 @@ void loadAni(const char *resourceName) {
}
free(dataPtr);
+ return entry;
}
/*! \brief Decode 16 color image with palette
@@ -642,16 +665,21 @@ void convert8BBP2(byte *dest, byte *source, int16 width, int16 height) {
/*! \brief Load image set
* \param resourceName Image set filename
- * \param idx Target index in animDataTable
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded image set (-1 if error)
*/
-void loadSet(const char *resourceName, int16 idx) {
+int loadSet(const char *resourceName, int16 idx) {
AnimHeader2Struct header2;
uint16 numSpriteInAnim;
int16 foundFileIdx = findFileInBundle(resourceName);
- int16 entry = idx >= 0 ? idx : 0;
+ int16 entry;
byte *ptr, *startOfDataPtr, *dataPtr, *origDataPtr;
int type;
+ if (foundFileIdx < 0) {
+ return -1;
+ }
+
origDataPtr = dataPtr = readBundleFile(foundFileIdx);
assert(!memcmp(dataPtr, "SET", 3));
ptr = dataPtr + 4;
@@ -661,6 +689,9 @@ void loadSet(const char *resourceName, int16 idx) {
startOfDataPtr = ptr + numSpriteInAnim * 0x10;
+ entry = idx < 0 ? emptyAnimSpace() : idx;
+ assert(entry >= 0);
+
for (int16 i = 0; i < numSpriteInAnim; i++, entry++) {
Common::MemoryReadStream readS(ptr, 0x10);
@@ -674,9 +705,6 @@ void loadSet(const char *resourceName, int16 idx) {
ptr += 0x10;
- entry = idx < 0 ? emptyAnimSpace(entry) : idx + i;
- assert(entry >= 0);
-
dataPtr = startOfDataPtr + header2.field_0;
if (header2.type == 1) {
@@ -693,78 +721,59 @@ void loadSet(const char *resourceName, int16 idx) {
}
free(origDataPtr);
+ return entry;
}
/*! \brief Load SEQ data into animDataTable
* \param resourceName SEQ data filename
- * \param idx Target index in animDataTable
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded SEQ data (-1 if error)
*/
-void loadSeq(const char *resourceName, int16 idx) {
+int loadSeq(const char *resourceName, int16 idx) {
int16 foundFileIdx = findFileInBundle(resourceName);
+ if (foundFileIdx < 0) {
+ return -1;
+ }
+
byte *dataPtr = readBundleFile(foundFileIdx);
int entry = idx < 0 ? emptyAnimSpace() : idx;
animDataTable[entry].load(dataPtr+0x16, ANIM_RAW, partBuffer[foundFileIdx].unpackedSize-0x16, 1, foundFileIdx, 0, currentPartName);
free(dataPtr);
+ return entry + 1;
}
-void loadResource(const char *resourceName) {
- /* byte isMask = 0; */
- /* byte isSpl = 0; */
-
+/*! \brief Load a resource into animDataTable
+ * \param resourceName Resource's filename
+ * \param idx Target index in animDataTable (-1 if any empty space will do)
+ * \return The number of the animDataTable entry after the loaded resource (-1 if error)
+ * \todo Implement loading of all resource types
+ */
+int loadResource(const char *resourceName, int16 idx) {
+ int result = -1; // Return an error by default
if (strstr(resourceName, ".SPL")) {
- loadSpl(resourceName, -1);
- return;
+ result = loadSpl(resourceName, idx);
} else if (strstr(resourceName, ".MSK")) {
- loadMsk(resourceName);
- return;
+ result = loadMsk(resourceName, idx);
} else if (strstr(resourceName, ".ANI")) {
- loadAni(resourceName);
- return;
+ result = loadAni(resourceName, idx);
} else if (strstr(resourceName, ".ANM")) {
- loadAni(resourceName);
- return;
+ result = loadAni(resourceName, idx);
} else if (strstr(resourceName, ".SET")) {
- loadSet(resourceName, -1);
- return;
+ result = loadSet(resourceName, idx);
} else if (strstr(resourceName, ".SEQ")) {
- loadSeq(resourceName, -1);
- return;
- } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure
- exitEngine = 1;
- return;
- }
-
- error("loadResource: Cannot determine type for '%s'", resourceName);
-}
-
-/*! \todo There seems to be some additional resource file that is not loaded
- */
-void loadAbs(const char *resourceName, uint16 idx) {
- /* byte isMask = 0; */
- /* byte isSpl = 0; */
-
- if (strstr(resourceName, ".SET")) {
- loadSet(resourceName, idx);
- return;
+ result = loadSeq(resourceName, idx);
} else if (strstr(resourceName, ".H32")) {
- warning("Ignoring file %s (load at %d)", resourceName, idx);
- return;
- } else if (strstr(resourceName, ".SEQ")) {
- loadSeq(resourceName, idx);
- return;
- } else if (strstr(resourceName, ".SPL")) {
- loadSpl(resourceName, idx);
- return;
+ warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
} else if (strstr(resourceName, ".AMI")) {
- warning("Ignoring file %s (load at %d)", resourceName, idx);
- return;
- } else if (strstr(resourceName, ".ANI")) {
- warning("Ignoring file %s (load at %d)", resourceName, idx);
- return;
+ warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
+ } else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure
+ g_cine->quitGame();
+ } else {
+ error("loadResource: Cannot determine type for '%s'", resourceName);
}
- error("loadAbs: Cannot determine type for '%s'", resourceName);
+ return result;
}
/*! \brief Load animDataTable from save
@@ -776,17 +785,9 @@ void loadAbs(const char *resourceName, uint16 idx) {
* at a time.
*/
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;
- AnimHeaderStruct animHeader;
-
+ int16 currentAnim, foundFileIdx, frame;
+ char *animName, part[256], name[10];
uint16 width, height, bpp, var1;
- int16 frame;
- char name[10];
- int type;
strcpy(part, currentPartName);
@@ -795,11 +796,8 @@ void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGam
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;
-
+ currentAnim = 0;
+ while (currentAnim < NUM_MAX_ANIMDATA) {
// Seek to the start of the current animation's entry
fHandle.seek(fileStartPos + currentAnim * entrySize);
// Read in the current animation entry
@@ -826,6 +824,7 @@ void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGam
// Don't try to load invalid entries.
if (foundFileIdx < 0 || !validPtr) {
+ currentAnim++; // Jump over the invalid entry
continue;
}
@@ -836,52 +835,10 @@ void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGam
}
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;
- animHeader.numFrames = 1;
- type = ANIM_RAW;
- } else {
- Common::MemoryReadStream readS(ptr, 0x16);
- loadAnimHeader(animHeader, readS);
- ptr += 0x16;
-
- width = animHeader.frameWidth;
- height = animHeader.frameHeight;
-
- if (isMask) {
- type = ANIM_MASK;
- } else {
- type = ANIM_MASKSPRITE;
- }
- }
-
- loadRelatedPalette(animName);
- transparentColor = getAnimTransparentColor(animName);
- // Make sure we load at least one frame and also that we
- // don't overflow the animDataTable by writing beyond its end.
- animHeader.numFrames = CLIP<uint16>(animHeader.numFrames, 1, NUM_MAX_ANIMDATA - currentAnim);
-
- // Load the frames
- for (frame = 0; frame < animHeader.numFrames; frame++) {
- // special case transparency handling
- if (!strcmp(animName, "L2202.ANI")) {
- transparentColor = (frame < 2) ? 0 : 7;
- } else if (!strcmp(animName, "L4601.ANI")) {
- transparentColor = (frame < 1) ? 0xE : 0;
- }
-
- // Load a single frame
- animDataTable[currentAnim + frame].load(ptr + frame * width * height, type, width, height, foundFileIdx, frame, name, transparentColor);
- }
-
- free(dataPtr);
+ loadRelatedPalette(animName); // Is this for Future Wars only?
+ const int16 prevAnim = currentAnim;
+ currentAnim = loadResource(animName, currentAnim);
+ assert(currentAnim > prevAnim); // Make sure we advance forward
}
loadPart(part);
diff --git a/engines/cine/anim.h b/engines/cine/anim.h
index b0ce55f7ee..8b1541eb3f 100644
--- a/engines/cine/anim.h
+++ b/engines/cine/anim.h
@@ -150,12 +150,11 @@ public:
#define NUM_MAX_ANIMDATA 255
-extern AnimData animDataTable[NUM_MAX_ANIMDATA];
+extern Common::Array<AnimData> animDataTable;
void freeAnimDataTable(void);
void freeAnimDataRange(byte startIdx, byte numIdx);
-void loadResource(const char *resourceName);
-void loadAbs(const char *resourceName, uint16 idx);
+int loadResource(const char *resourceName, int16 idx = -1);
void loadResourcesFromSave(Common::SeekableReadStream &fHandle, enum CineSaveGameFormat saveGameFormat);
void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency);
diff --git a/engines/cine/bg.cpp b/engines/cine/bg.cpp
index 2a4e7f0ab1..cc7e843c2b 100644
--- a/engines/cine/bg.cpp
+++ b/engines/cine/bg.cpp
@@ -41,10 +41,18 @@ byte loadCtFW(const char *ctName) {
uint16 header[32];
byte *ptr, *dataPtr;
+ int16 foundFileIdx = findFileInBundle(ctName);
+ if (foundFileIdx == -1) {
+ warning("loadCtFW: Unable to find collision data file '%s'", ctName);
+ // FIXME: Rework this function's return value policy and return an appropriate value here.
+ // The return value isn't yet used for anything so currently it doesn't really matter.
+ return 0;
+ }
+
if (currentCtName != ctName)
strcpy(currentCtName, ctName);
- ptr = dataPtr = readBundleFile(findFileInBundle(ctName));
+ ptr = dataPtr = readBundleFile(foundFileIdx);
loadRelatedPalette(ctName);
@@ -56,7 +64,7 @@ byte loadCtFW(const char *ctName) {
header[i] = readS.readUint16BE();
}
- gfxConvertSpriteToRaw(page3Raw, ptr + 0x80, 160, 200);
+ gfxConvertSpriteToRaw(collisionPage, ptr + 0x80, 160, 200);
free(dataPtr);
return 0;
@@ -74,10 +82,10 @@ byte loadCtOS(const char *ctName) {
ptr += 2;
if (bpp == 8) {
- memcpy(page3Raw, ptr + 256 * 3, 320 * 200);
+ memcpy(collisionPage, ptr + 256 * 3, 320 * 200);
renderer->loadCt256(ptr, ctName);
} else {
- gfxConvertSpriteToRaw(page3Raw, ptr + 32, 160, 200);
+ gfxConvertSpriteToRaw(collisionPage, ptr + 32, 160, 200);
renderer->loadCt16(ptr, ctName);
}
diff --git a/engines/cine/cine.cpp b/engines/cine/cine.cpp
index f6778b6457..2c0fdc7d88 100644
--- a/engines/cine/cine.cpp
+++ b/engines/cine/cine.cpp
@@ -23,7 +23,6 @@
*
*/
-#include "common/events.h"
#include "common/file.h"
#include "common/savefile.h"
#include "common/config-manager.h"
@@ -57,6 +56,10 @@ CineEngine::CineEngine(OSystem *syst, const CINEGameDescription *gameDesc) : Eng
// Setup mixer
_mixer->setVolumeForSoundType(Audio::Mixer::kSFXSoundType, ConfMan.getInt("sfx_volume"));
_mixer->setVolumeForSoundType(Audio::Mixer::kMusicSoundType, ConfMan.getInt("music_volume"));
+ // Use music volume for plain sound types (At least the Adlib player uses a plain sound type
+ // so previously the music and sfx volume controls didn't affect it at all).
+ // FIXME: Make Adlib player differentiate between playing sound effects and music and remove this.
+ _mixer->setVolumeForSoundType(Audio::Mixer::kPlainSoundType, ConfMan.getInt("music_volume"));
g_cine = this;
@@ -65,14 +68,9 @@ CineEngine::CineEngine(OSystem *syst, const CINEGameDescription *gameDesc) : Eng
CineEngine::~CineEngine() {
if (g_cine->getGameType() == Cine::GType_OS) {
- freePoldatDat();
freeErrmessDat();
}
Common::clearAllSpecialDebugLevels();
-
- free(palPtr);
- free(partBuffer);
- free(textDataPtr);
}
int CineEngine::init() {
@@ -100,13 +98,44 @@ int CineEngine::go() {
mainLoop(1);
delete renderer;
- delete[] page3Raw;
+ delete[] collisionPage;
delete g_sound;
+
return 0;
}
+int CineEngine::getTimerDelay() const {
+ return (10923000 * _timerDelayMultiplier) / 1193180;
+}
+
+/*! \brief Modify game speed
+ * \param speedChange Negative values slow game down, positive values speed it up, zero does nothing
+ * \return Timer delay multiplier's value after the game speed change
+ */
+int CineEngine::modifyGameSpeed(int speedChange) {
+ // If we want more speed we decrement the timer delay multiplier and vice versa.
+ _timerDelayMultiplier = CLIP(_timerDelayMultiplier - speedChange, 1, 50);
+ return _timerDelayMultiplier;
+}
void CineEngine::initialize() {
+ // Resize object table to its correct size and reset all its elements
+ objectTable.resize(NUM_MAX_OBJECT);
+ resetObjectTable();
+
+ // Resize animation data table to its correct size and reset all its elements
+ animDataTable.resize(NUM_MAX_ANIMDATA);
+ freeAnimDataTable();
+
+ // Resize zone data table to its correct size and reset all its elements
+ zoneData.resize(NUM_MAX_ZONE);
+ Common::set_to(zoneData.begin(), zoneData.end(), 0);
+
+ // Resize zone query table to its correct size and reset all its elements
+ zoneQuery.resize(NUM_MAX_ZONE);
+ Common::set_to(zoneQuery.begin(), zoneQuery.end(), 0);
+
+ _timerDelayMultiplier = 12; // Set default speed
setupOpcodes();
initLanguage(g_cine->getLanguage());
@@ -117,16 +146,17 @@ void CineEngine::initialize() {
renderer = new FWRenderer;
}
- page3Raw = new byte[320 * 200];
- textDataPtr = (byte *)malloc(8000);
+ collisionPage = new byte[320 * 200];
- partBuffer = (PartBuffer *)malloc(NUM_MAX_PARTDATA * sizeof(PartBuffer));
+ // Clear part buffer as there's nothing loaded into it yet.
+ // Its size will change when loading data into it with the loadPart function.
+ partBuffer.clear();
if (g_cine->getGameType() == Cine::GType_OS) {
readVolCnf();
}
- loadTextData("texte.dat", textDataPtr);
+ loadTextData("texte.dat");
if (g_cine->getGameType() == Cine::GType_OS && !(g_cine->getFeatures() & GF_DEMO)) {
loadPoldatDat("poldat.dat");
@@ -142,8 +172,7 @@ void CineEngine::initialize() {
freeAnimDataTable();
overlayList.clear();
messageTable.clear();
-
- memset(objectTable, 0, sizeof(objectTable));
+ resetObjectTable();
var8 = 0;
diff --git a/engines/cine/cine.h b/engines/cine/cine.h
index eaae555812..6011036eb1 100644
--- a/engines/cine/cine.h
+++ b/engines/cine/cine.h
@@ -59,7 +59,8 @@ enum CineGameType {
enum CineGameFeatures {
GF_CD = 1 << 0,
GF_DEMO = 1 << 1,
- GF_ALT_FONT = 1 << 2
+ GF_ALT_FONT = 1 << 2,
+ GF_CRYPTED_BOOT_PRC = 1 << 3
};
struct CINEGameDescription;
@@ -86,6 +87,8 @@ public:
bool loadSaveDirectory(void);
void makeSystemMenu(void);
+ int modifyGameSpeed(int speedChange);
+ int getTimerDelay() const;
const CINEGameDescription *_gameDescription;
Common::File _partFileHandle;
@@ -109,6 +112,7 @@ private:
void readVolCnf();
bool _preLoad;
+ int _timerDelayMultiplier;
};
extern CineEngine *g_cine;
diff --git a/engines/cine/detection.cpp b/engines/cine/detection.cpp
index 8c940bcfd4..91ef964a0b 100644
--- a/engines/cine/detection.cpp
+++ b/engines/cine/detection.cpp
@@ -76,6 +76,25 @@ static const CINEGameDescription gameDescriptions[] = {
0,
},
+ // This is a CD version of Future Wars published by Sony.
+ // This version has a crypted AUTO00.PRC.
+ {
+ {
+ "fw",
+ "Sony CD version",
+ {
+ { "AUTO00.PRC", 0, "4fe1e7930b38e3c63f0f2474d471bf8f", -1},
+ { "PART01", 0, "61d003202d301c29dd399acfb1354310", -1},
+ { NULL, 0, NULL, 0}
+ },
+ Common::EN_USA,
+ Common::kPlatformPC,
+ Common::ADGF_CD
+ },
+ GType_FW,
+ GF_CD | GF_CRYPTED_BOOT_PRC,
+ },
+
{
// This is the version included in the UK "Classic Collection"
{
@@ -251,6 +270,21 @@ static const CINEGameDescription gameDescriptions[] = {
},
{
+ // This is a 16 color PC version (It came on three 720kB 3.5" disks).
+ // The protagonist is named John Glames in this version.
+ {
+ "os",
+ "",
+ AD_ENTRY1("procs1", "9629129b86979fa592c1787385bf3695"),
+ Common::EN_GRB,
+ Common::kPlatformPC,
+ Common::ADGF_NO_FLAGS
+ },
+ GType_OS,
+ 0,
+ },
+
+ {
{
"os",
"",
@@ -499,8 +533,17 @@ public:
}
virtual bool createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const;
+ virtual bool hasFeature(MetaEngineFeature f) const;
+ virtual SaveStateList listSaves(const char *target) const;
};
+bool CineMetaEngine::hasFeature(MetaEngineFeature f) const {
+ return
+ (f == kSupportsRTL) ||
+ (f == kSupportsListSaves) ||
+ (f == kSupportsDirectLoad);
+}
+
bool CineMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common::ADGameDescription *desc) const {
const Cine::CINEGameDescription *gd = (const Cine::CINEGameDescription *)desc;
if (gd) {
@@ -509,6 +552,50 @@ bool CineMetaEngine::createInstance(OSystem *syst, Engine **engine, const Common
return gd != 0;
}
+SaveStateList CineMetaEngine::listSaves(const char *target) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ SaveStateList saveList;
+
+ Common::String pattern = target;
+ pattern += ".?";
+ Common::StringList filenames = saveFileMan->listSavefiles(pattern.c_str());
+ sort(filenames.begin(), filenames.end());
+ Common::StringList::const_iterator file = filenames.begin();
+
+ Common::String filename = target;
+ filename += ".dir";
+ Common::InSaveFile *in = saveFileMan->openForLoading(filename.c_str());
+ if (in) {
+ int8 ch;
+ char saveDesc[20];
+ do {
+ // Obtain the last digit of the filename, since they correspond to the save slot
+ int slotNum = atoi(file->c_str() + file->size() - 1);
+
+ uint pos = 0;
+ do {
+ ch = in->readByte();
+ if (pos < (sizeof(saveDesc) - 1)) {
+ if (ch < 32 || in->eos()) {
+ saveDesc[pos++] = '\0';
+ }
+ else if (ch >= 32) {
+ saveDesc[pos++] = ch;
+ }
+ }
+ } while (ch >= 32 && !in->eos());
+ if (saveDesc[0] != 0) {
+ saveList.push_back(SaveStateDescriptor(slotNum, Common::String(saveDesc), *file));
+ file++;
+ }
+ } while (!in->eos());
+ }
+
+ delete in;
+
+ return saveList;
+}
+
#if PLUGIN_ENABLED_DYNAMIC(CINE)
REGISTER_PLUGIN_DYNAMIC(CINE, PLUGIN_TYPE_ENGINE, CineMetaEngine);
#else
diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp
index cbddf0fc59..e24b23f7f0 100644
--- a/engines/cine/gfx.cpp
+++ b/engines/cine/gfx.cpp
@@ -31,12 +31,13 @@
#include "common/endian.h"
#include "common/system.h"
+#include "common/events.h"
#include "graphics/cursorman.h"
namespace Cine {
-byte *page3Raw;
+byte *collisionPage;
FWRenderer *renderer = NULL;
static const byte mouseCursorNormal[] = {
@@ -91,7 +92,7 @@ static const byte cursorPalette[] = {
*/
FWRenderer::FWRenderer() : _background(NULL), _palette(NULL), _cmd(""),
_cmdY(0), _messageBg(0), _backBuffer(new byte[_screenSize]),
- _activeLowPal(NULL), _changePal(0) {
+ _activeLowPal(NULL), _changePal(0), _showCollisionPage(false) {
assert(_backBuffer);
@@ -125,6 +126,7 @@ void FWRenderer::clear() {
_cmdY = 0;
_messageBg = 0;
_changePal = 0;
+ _showCollisionPage = false;
}
/*! \brief Draw 1bpp sprite using selected color
@@ -198,9 +200,15 @@ void FWRenderer::incrustSprite(const objectStruct &obj) {
width = animDataTable[obj.frame]._realWidth;
height = animDataTable[obj.frame]._height;
- assert(mask);
-
- drawSpriteRaw(data, mask, width, height, _background, x, y);
+ // There was an assert(mask) here before but it made savegame loading
+ // in Future Wars sometimes fail the assertion (e.g. see bug #2055912).
+ // Not drawing sprites that have no mask seems to work, but not sure
+ // if this is really a correct way to fix this.
+ if (mask) {
+ drawSpriteRaw(data, mask, width, height, _background, x, y);
+ } else { // mask == NULL
+ warning("FWRenderer::incrustSprite: Skipping maskless sprite (frame=%d)", obj.frame);
+ }
}
/*! \brief Draw command box on screen
@@ -225,14 +233,18 @@ void FWRenderer::drawCommand() {
* \param x Top left message box corner coordinate
* \param y Top left message box corner coordinate
* \param width Message box width
- * \param color Message box background color
+ * \param color Message box background color (Or if negative draws only the text)
+ * \note Negative colors are used in Operation Stealth's timed cutscenes
+ * (e.g. when first meeting The Movement for the Liberation of Santa Paragua).
*/
-void FWRenderer::drawMessage(const char *str, int x, int y, int width, byte color) {
+void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color) {
int i, tx, ty, tw;
int line = 0, words = 0, cw = 0;
int space = 0, extraSpace = 0;
- drawPlainBox(x, y, width, 4, color);
+ if (color >= 0) {
+ drawPlainBox(x, y, width, 4, color);
+ }
tx = x + 4;
ty = str[0] ? y - 5 : y + 4;
tw = width - 8;
@@ -252,7 +264,9 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, byte colo
}
ty += 9;
- drawPlainBox(x, ty, width, 9, color);
+ if (color >= 0) {
+ drawPlainBox(x, ty, width, 9, color);
+ }
tx = x + 4;
}
@@ -269,33 +283,56 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, byte colo
}
ty += 9;
- drawPlainBox(x, ty, width, 4, color);
- drawDoubleBorder(x, y, width, ty - y + 4, 2);
+ if (color >= 0) {
+ drawPlainBox(x, ty, width, 4, color);
+ drawDoubleBorder(x, y, width, ty - y + 4, 2);
+ }
}
/*! \brief Draw rectangle on screen
* \param x Top left corner coordinate
* \param y Top left corner coordinate
- * \param width Rectangle width
- * \param height Rectangle height
+ * \param width Rectangle width (Negative values draw the box horizontally flipped)
+ * \param height Rectangle height (Negative values draw the box vertically flipped)
* \param color Fill color
+ * \note An on-screen rectangle's drawn width is always at least one.
+ * \note An on-screen rectangle's drawn height is always at least one.
*/
void FWRenderer::drawPlainBox(int x, int y, int width, int height, byte color) {
- int i;
- byte *dest = _backBuffer + y * 320 + x;
+ // Make width's and height's absolute values at least one
+ // which forces this function to always draw something if the
+ // drawing position is inside screen bounds. This fixes at least
+ // the showing of the oxygen gauge meter in Operation Stealth's
+ // first arcade sequence where this function is called with a
+ // height of zero.
+ if (width == 0) {
+ width = 1;
+ }
+ if (height == 0) {
+ height = 1;
+ }
+ // Handle horizontally flipped boxes
if (width < 0) {
- x += width;
- width = -width;
+ width = ABS(width);
+ x -= width;
}
+ // Handle vertically flipped boxes
if (height < 0) {
- y += height;
- height = -height;
+ height = ABS(height);
+ y -= height;
}
- for (i = 0; i < height; i++) {
- memset(dest + i * 320, color, width);
+ // Clip the rectangle to screen dimensions
+ Common::Rect boxRect(x, y, x + width, y + height);
+ Common::Rect screenRect(320, 200);
+ boxRect.clip(screenRect);
+
+ // Draw the filled rectangle
+ byte *dest = _backBuffer + boxRect.top * 320 + boxRect.left;
+ for (int i = 0; i < boxRect.height(); i++) {
+ memset(dest + i * 320, color, boxRect.width());
}
}
@@ -335,9 +372,9 @@ int FWRenderer::drawChar(char character, int x, int y) {
if (character == ' ') {
x += 5;
- } else if ((width = fontParamTable[(unsigned char)character].characterWidth)) {
- idx = fontParamTable[(unsigned char)character].characterIdx;
- drawSpriteRaw(g_cine->_textHandler.textTable[idx][0], g_cine->_textHandler.textTable[idx][1], 16, 8, _backBuffer, x, y);
+ } else if ((width = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterWidth)) {
+ idx = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterIdx;
+ drawSpriteRaw(g_cine->_textHandler.textTable[idx][FONT_DATA], g_cine->_textHandler.textTable[idx][FONT_MASK], FONT_WIDTH, FONT_HEIGHT, _backBuffer, x, y);
x += width + 1;
}
@@ -405,7 +442,10 @@ void FWRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
switch (it->type) {
// color sprite
case 0:
- sprite = animDataTable + objectTable[it->objIdx].frame;
+ if (objectTable[it->objIdx].frame < 0) {
+ return;
+ }
+ sprite = &animDataTable[objectTable[it->objIdx].frame];
len = sprite->_realWidth * sprite->_height;
mask = new byte[len];
memcpy(mask, sprite->mask(), len);
@@ -422,6 +462,7 @@ void FWRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
_messageLen += messageTable[it->objIdx].size();
drawMessage(messageTable[it->objIdx].c_str(), it->x, it->y, it->width, it->color);
+ waitForPlayerClick = 1;
break;
// action failure message
@@ -433,12 +474,13 @@ void FWRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
width = width > 300 ? 300 : width;
drawMessage(failureMessages[idx], (320 - width) / 2, 80, width, 4);
+ waitForPlayerClick = 1;
break;
// bitmap
case 4:
assert(it->objIdx < NUM_MAX_OBJECT);
- obj = objectTable + it->objIdx;
+ obj = &objectTable[it->objIdx];
if (obj->frame < 0) {
return;
@@ -480,16 +522,28 @@ void FWRenderer::drawFrame() {
blit();
}
+/*!
+ * \brief Turn on or off the showing of the collision page.
+ * If turned on the blitting routine shows the collision page instead of the back buffer.
+ * \note Useful for debugging collision page related problems.
+ */
+void FWRenderer::showCollisionPage(bool state) {
+ _showCollisionPage = state;
+}
+
/*! \brief Update screen
*/
void FWRenderer::blit() {
- g_system->copyRectToScreen(_backBuffer, 320, 0, 0, 320, 200);
+ // Show the back buffer or the collision page. Normally the back
+ // buffer but showing the collision page is useful for debugging.
+ byte *source = (_showCollisionPage ? collisionPage : _backBuffer);
+ g_system->copyRectToScreen(source, 320, 0, 0, 320, 200);
}
/*! \brief Set player command string
* \param cmd New command string
*/
-void FWRenderer::setCommand(const char *cmd) {
+void FWRenderer::setCommand(Common::String cmd) {
_cmd = cmd;
}
@@ -617,6 +671,11 @@ void FWRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
fHandle.write(_bgName, 13);
}
+const char *FWRenderer::getBgName(uint idx) const {
+ assert(idx == 0);
+ return _bgName;
+}
+
/*! \brief Restore active and backup palette from save
* \param fHandle Savefile open for reading
*/
@@ -985,9 +1044,9 @@ int OSRenderer::drawChar(char character, int x, int y) {
if (character == ' ') {
x += 5;
- } else if ((width = fontParamTable[(unsigned char)character].characterWidth)) {
- idx = fontParamTable[(unsigned char)character].characterIdx;
- drawSpriteRaw2(g_cine->_textHandler.textTable[idx][0], 0, 16, 8, _backBuffer, x, y);
+ } else if ((width = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterWidth)) {
+ idx = g_cine->_textHandler.fontParamTable[(unsigned char)character].characterIdx;
+ drawSpriteRaw2(g_cine->_textHandler.textTable[idx][FONT_DATA], 0, FONT_WIDTH, FONT_HEIGHT, _backBuffer, x, y);
x += width + 1;
}
@@ -1011,8 +1070,12 @@ void OSRenderer::drawBackground() {
assert(scroll);
- memcpy(_backBuffer, main + mainShift, mainSize);
- memcpy(_backBuffer + mainSize, scroll, mainShift);
+ if (mainSize > 0) { // Just a precaution
+ memcpy(_backBuffer, main + mainShift, mainSize);
+ }
+ if (mainShift > 0) { // Just a precaution
+ memcpy(_backBuffer + mainSize, scroll, mainShift);
+ }
}
}
@@ -1021,10 +1084,11 @@ void OSRenderer::drawBackground() {
* \todo Add handling of type 22 overlays
*/
void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
- int len;
+ int len, idx, width, height;
objectStruct *obj;
AnimData *sprite;
byte *mask;
+ byte color;
switch (it->type) {
// color sprite
@@ -1032,7 +1096,7 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
if (objectTable[it->objIdx].frame < 0) {
break;
}
- sprite = animDataTable + objectTable[it->objIdx].frame;
+ sprite = &animDataTable[objectTable[it->objIdx].frame];
len = sprite->_realWidth * sprite->_height;
mask = new byte[len];
generateMask(sprite->data(), mask, len, objectTable[it->objIdx].part);
@@ -1041,6 +1105,32 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
delete[] mask;
break;
+ // game message
+ case 2:
+ if (it->objIdx >= messageTable.size()) {
+ return;
+ }
+
+ _messageLen += messageTable[it->objIdx].size();
+ drawMessage(messageTable[it->objIdx].c_str(), it->x, it->y, it->width, it->color);
+ if (it->color >= 0) { // This test isn't in Future Wars's implementation
+ waitForPlayerClick = 1;
+ }
+ break;
+
+ // action failure message
+ case 3:
+ idx = it->objIdx * 4 + g_cine->_rnd.getRandomNumber(3);
+ len = strlen(failureMessages[idx]);
+ _messageLen += len;
+ width = 6 * len + 20;
+ width = width > 300 ? 300 : width;
+
+ // The used color here differs from Future Wars
+ drawMessage(failureMessages[idx], (320 - width) / 2, 80, width, _messageBg);
+ waitForPlayerClick = 1;
+ break;
+
// bitmap
case 4:
if (objectTable[it->objIdx].frame >= 0) {
@@ -1051,16 +1141,37 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
// masked background
case 20:
assert(it->objIdx < NUM_MAX_OBJECT);
- obj = objectTable + it->objIdx;
- sprite = animDataTable + obj->frame;
+ var5 = it->x; // A global variable updated here!
+ obj = &objectTable[it->objIdx];
+ sprite = &animDataTable[obj->frame];
- if (obj->frame < 0 || it->x > 8 || !_bgTable[it->x].bg || sprite->_bpp != 1) {
+ if (obj->frame < 0 || it->x < 0 || it->x > 8 || !_bgTable[it->x].bg || sprite->_bpp != 1) {
break;
}
maskBgOverlay(_bgTable[it->x].bg, sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, obj->x, obj->y);
break;
+ // FIXME: Implement correct drawing of type 21 overlays.
+ // Type 21 overlays aren't just filled rectangles, I found their drawing routine
+ // from Operation Stealth's drawSprite routine. So they're likely some kind of sprites
+ // and it's just a coincidence that the oxygen meter during the first arcade sequence
+ // works even somehow currently. I tried the original under DOSBox and the oxygen gauge
+ // is a long red bar that gets shorter as the air runs out.
+ case 21:
+ // A filled rectangle:
+ case 22:
+ // TODO: Check it this implementation really works correctly (Some things might be wrong, needs testing).
+ assert(it->objIdx < NUM_MAX_OBJECT);
+ obj = &objectTable[it->objIdx];
+ color = obj->part & 0x0F;
+ width = obj->frame;
+ height = obj->costume;
+ drawPlainBox(obj->x, obj->y, width, height, color);
+ debug(5, "renderOverlay: type=%d, x=%d, y=%d, width=%d, height=%d, color=%d",
+ it->type, obj->x, obj->y, width, height, color);
+ break;
+
// something else
default:
FWRenderer::renderOverlay(it);
@@ -1332,6 +1443,11 @@ void OSRenderer::saveBgNames(Common::OutSaveFile &fHandle) {
}
}
+const char *OSRenderer::getBgName(uint idx) const {
+ assert(idx < 9);
+ return _bgTable[idx].name;
+}
+
/*! \brief Fade to black
* \bug Operation Stealth sometimes seems to fade to black using
* transformPalette resulting in double fadeout
@@ -1557,6 +1673,16 @@ void gfxResetRawPage(byte *pageRaw) {
}
void gfxConvertSpriteToRaw(byte *dst, const byte *src, uint16 w, uint16 h) {
+ // Output is 4 bits per pixel.
+ // Pixels are in 16 pixel chunks (8 bytes of source per 16 pixels of output).
+ // The source data is interleaved so that
+ // 1st big-endian 16-bit value contains all bit position 0 values for 16 pixels,
+ // 2nd big-endian 16-bit value contains all bit position 1 values for 16 pixels,
+ // 3rd big-endian 16-bit value contains all bit position 2 values for 16 pixels,
+ // 4th big-endian 16-bit value contains all bit position 3 values for 16 pixels.
+ // 1st pixel's bits are in the 16th bits,
+ // 2nd pixel's bits are in the 15th bits,
+ // 3rd pixel's bits are in the 14th bits etc.
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w / 8; ++x) {
for (int bit = 0; bit < 16; ++bit) {
diff --git a/engines/cine/gfx.h b/engines/cine/gfx.h
index 6a3aa1ef89..c07214028c 100644
--- a/engines/cine/gfx.h
+++ b/engines/cine/gfx.h
@@ -62,13 +62,14 @@ protected:
byte *_backBuffer; ///< Screen backbuffer
uint16 *_activeLowPal; ///< Active 16 color palette
int _changePal; ///< Load active palette to video backend on next frame
+ bool _showCollisionPage; ///< Should we show the collision page instead of the back buffer? Used for debugging.
void fillSprite(const objectStruct &obj, uint8 color = 0);
void drawMaskedSprite(const objectStruct &obj, const byte *mask);
virtual void drawSprite(const objectStruct &obj);
void drawCommand();
- void drawMessage(const char *str, int x, int y, int width, byte color);
+ void drawMessage(const char *str, int x, int y, int width, int color);
void drawPlainBox(int x, int y, int width, int height, byte color);
void drawBorder(int x, int y, int width, int height, byte color);
void drawDoubleBorder(int x, int y, int width, int height, byte color);
@@ -94,7 +95,7 @@ public:
void drawFrame();
void blit();
- void setCommand(const char *cmd);
+ void setCommand(Common::String cmd);
virtual void incrustMask(const objectStruct &obj, uint8 color = 0);
virtual void incrustSprite(const objectStruct &obj);
@@ -111,6 +112,7 @@ public:
virtual uint getScroll() const;
virtual void removeBg(unsigned int idx);
virtual void saveBgNames(Common::OutSaveFile &fHandle);
+ virtual const char *getBgName(uint idx = 0) const;
virtual void refreshPalette();
virtual void reloadPalette();
@@ -123,6 +125,7 @@ public:
void drawInputBox(const char *info, const char *input, int cursor, int x, int y, int width);
virtual void fadeToBlack();
+ void showCollisionPage(bool state);
};
/*! \brief Operation Stealth renderer
@@ -168,6 +171,7 @@ public:
uint getScroll() const;
void removeBg(unsigned int idx);
void saveBgNames(Common::OutSaveFile &fHandle);
+ const char *getBgName(uint idx = 0) const;
void refreshPalette();
void reloadPalette();
@@ -181,7 +185,7 @@ public:
void gfxDrawSprite(byte *src4, uint16 sw, uint16 sh, byte *dst4, int16 sx, int16 sy);
-extern byte *page3Raw;
+extern byte *collisionPage;
extern FWRenderer *renderer;
void setMouseCursor(int cursor);
diff --git a/engines/cine/main_loop.cpp b/engines/cine/main_loop.cpp
index e5e670c973..04c6f5c769 100644
--- a/engines/cine/main_loop.cpp
+++ b/engines/cine/main_loop.cpp
@@ -25,7 +25,6 @@
#include "common/scummsys.h"
-#include "common/events.h"
#include "common/system.h"
#include "cine/main_loop.h"
@@ -61,9 +60,6 @@ static void processEvent(Common::Event &event) {
break;
case Common::EVENT_MOUSEMOVE:
break;
- case Common::EVENT_QUIT:
- exitEngine = 1;
- break;
case Common::EVENT_KEYDOWN:
switch (event.kbd.keycode) {
case Common::KEYCODE_RETURN:
@@ -125,11 +121,74 @@ static void processEvent(Common::Event &event) {
g_cine->makeSystemMenu();
}
break;
+ case Common::KEYCODE_F11:
+ renderer->showCollisionPage(true);
+ break;
+ case Common::KEYCODE_MINUS:
+ case Common::KEYCODE_KP_MINUS:
+ g_cine->modifyGameSpeed(-1); // Slower
+ break;
+ case Common::KEYCODE_PLUS:
+ case Common::KEYCODE_KP_PLUS:
+ g_cine->modifyGameSpeed(+1); // Faster
+ break;
+ case Common::KEYCODE_LEFT:
+ case Common::KEYCODE_KP4:
+ moveUsingKeyboard(-1, 0); // Left
+ break;
+ case Common::KEYCODE_RIGHT:
+ case Common::KEYCODE_KP6:
+ moveUsingKeyboard(+1, 0); // Right
+ break;
+ case Common::KEYCODE_UP:
+ case Common::KEYCODE_KP8:
+ moveUsingKeyboard(0, +1); // Up
+ break;
+ case Common::KEYCODE_DOWN:
+ case Common::KEYCODE_KP2:
+ moveUsingKeyboard(0, -1); // Down
+ break;
+ case Common::KEYCODE_KP9:
+ moveUsingKeyboard(+1, +1); // Up & Right
+ break;
+ case Common::KEYCODE_KP7:
+ moveUsingKeyboard(-1, +1); // Up & Left
+ break;
+ case Common::KEYCODE_KP1:
+ moveUsingKeyboard(-1, -1); // Down & Left
+ break;
+ case Common::KEYCODE_KP3:
+ moveUsingKeyboard(+1, -1); // Down & Right
+ break;
default:
lastKeyStroke = event.kbd.keycode;
break;
}
break;
+ case Common::EVENT_KEYUP:
+ switch (event.kbd.keycode) {
+ case Common::KEYCODE_F11:
+ renderer->showCollisionPage(false);
+ break;
+ case Common::KEYCODE_KP5: // Emulated left mouse button click
+ case Common::KEYCODE_LEFT: // Left
+ case Common::KEYCODE_KP4: // Left
+ case Common::KEYCODE_RIGHT: // Right
+ case Common::KEYCODE_KP6: // Right
+ case Common::KEYCODE_UP: // Up
+ case Common::KEYCODE_KP8: // Up
+ case Common::KEYCODE_DOWN: // Down
+ case Common::KEYCODE_KP2: // Down
+ case Common::KEYCODE_KP9: // Up & Right
+ case Common::KEYCODE_KP7: // Up & Left
+ case Common::KEYCODE_KP1: // Down & Left
+ case Common::KEYCODE_KP3: // Down & Right
+ // Stop ego movement made with keyboard when releasing a known key
+ moveUsingKeyboard(0, 0);
+ break;
+ default:
+ break;
+ }
default:
break;
}
@@ -138,7 +197,7 @@ static void processEvent(Common::Event &event) {
void manageEvents() {
Common::EventManager *eventMan = g_system->getEventManager();
- uint32 nextFrame = g_system->getMillis() + kGameTimerDelay * kGameSpeed;
+ uint32 nextFrame = g_system->getMillis() + g_cine->getTimerDelay();
do {
Common::Event event;
while (eventMan->pollEvent(event)) {
@@ -195,13 +254,9 @@ void purgeSeqList() {
void CineEngine::mainLoop(int bootScriptIdx) {
bool playerAction;
- uint16 quitFlag;
byte di;
uint16 mouseButton;
- quitFlag = 0;
- exitEngine = 0;
-
if (_preLoad == false) {
resetBgIncrustList();
@@ -227,7 +282,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
menuCommandLen = 0;
playerCommand = -1;
- strcpy(commandBuffer, "");
+ commandBuffer = "";
globalVars[VAR_MOUSE_X_POS] = 0;
globalVars[VAR_MOUSE_Y_POS] = 0;
@@ -247,14 +302,44 @@ void CineEngine::mainLoop(int bootScriptIdx) {
}
do {
+ // HACK: Force amount of oxygen left to maximum during Operation Stealth's first arcade sequence.
+ // This makes it possible to pass the arcade sequence for now.
+ // FIXME: Remove the hack and make the first arcade sequence normally playable.
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ Common::String bgName(renderer->getBgName());
+ // Check if the background is one of the three backgrounds
+ // that are only used during the first arcade sequence.
+ if (bgName == "28.PI1" || bgName == "29.PI1" || bgName == "30.PI1") {
+ static const uint oxygenObjNum = 202, maxOxygen = 264;
+ // Force the amount of oxygen left to the maximum.
+ objectTable[oxygenObjNum].x = maxOxygen;
+ }
+ }
+
+ // HACK: In Operation Stealth after the first arcade sequence jump player's position to avoid getting stuck.
+ // After the first arcade sequence the player comes up stairs from
+ // the water in Santa Paragua's downtown in front of the flower shop.
+ // Previously he was completely stuck after getting up the stairs.
+ // If the background is the one used in the flower shop scene ("21.PI1")
+ // and the player is at the exact location after getting up the stairs
+ // then we just nudge him a tiny bit away from the stairs and voila, he's free!
+ // Maybe the real problem behind all this is collision data related as it looks
+ // like there's some boundary right there near position (204, 110) which we can
+ // jump over by moving the character to (204, 109). The script handling the
+ // flower shop scene is AIRPORT.PRC's 13th script.
+ // FIXME: Remove the hack and solve what's really causing the problem in the first place.
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ if (scumm_stricmp(renderer->getBgName(), "21.PI1") == 0 && objectTable[1].x == 204 && objectTable[1].y == 110) {
+ objectTable[1].y--; // Move the player character upward on-screen by one pixel
+ }
+ }
+
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;
- }
+ Common::set_to(zoneQuery.begin(), zoneQuery.end(), 0);
}
if (g_cine->getGameType() == Cine::GType_OS) {
@@ -279,6 +364,11 @@ void CineEngine::mainLoop(int bootScriptIdx) {
renderer->drawFrame();
}
+ // NOTE: In the original Future Wars and Operation Stealth messages
+ // were removed when running the drawOverlays function which is
+ // currently called from the renderer's drawFrame function.
+ removeMessages();
+
if (waitForPlayerClick) {
playerAction = false;
@@ -308,8 +398,6 @@ void CineEngine::mainLoop(int bootScriptIdx) {
} while (mouseButton != 0);
waitForPlayerClick = 0;
-
- removeMessages();
}
if (checkForPendingDataLoadSwitch) {
@@ -322,7 +410,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
if ("quit"[menuCommandLen] == (char)di) {
++menuCommandLen;
if (menuCommandLen == 4) {
- quitFlag = 1;
+ quitGame();
}
} else {
menuCommandLen = 0;
@@ -331,7 +419,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
manageEvents();
- } while (!exitEngine && !quitFlag && _danKeysPressed != 7);
+ } while (!quit() && _danKeysPressed != 7);
hideMouse();
g_sound->stopMusic();
diff --git a/engines/cine/main_loop.h b/engines/cine/main_loop.h
index a2f828fd34..c729b324ca 100644
--- a/engines/cine/main_loop.h
+++ b/engines/cine/main_loop.h
@@ -28,11 +28,6 @@
namespace Cine {
-enum {
- kGameTimerDelay = 1000 / (1193180 / 10923),
- kGameSpeed = 12
-};
-
void mainLoop(int bootScriptIdx);
void manageEvents();
diff --git a/engines/cine/msg.cpp b/engines/cine/msg.cpp
index 55eb627309..c826db3bf3 100644
--- a/engines/cine/msg.cpp
+++ b/engines/cine/msg.cpp
@@ -34,29 +34,40 @@ namespace Cine {
Common::StringList messageTable;
void loadMsg(char *pMsgName) {
- int i, count, len;
- byte *ptr, *dataPtr;
- const char *messagePtr;
+ uint32 sourceSize;
checkDataDisk(-1);
-
messageTable.clear();
-
- ptr = dataPtr = readBundleFile(findFileInBundle(pMsgName));
+ byte *dataPtr = readBundleFile(findFileInBundle(pMsgName), &sourceSize);
setMouseCursor(MOUSE_CURSOR_DISK);
- count = READ_BE_UINT16(ptr);
- ptr += 2;
-
- messagePtr = (const char*)(ptr + 2 * count);
-
- for (i = 0; i < count; i++) {
- len = READ_BE_UINT16(ptr);
- ptr += 2;
-
- messageTable.push_back(messagePtr);
- messagePtr += len;
+ uint count = READ_BE_UINT16(dataPtr);
+ uint messageLenPos = 2;
+ uint messageDataPos = messageLenPos + 2 * count;
+
+ // Read in the messages
+ for (uint i = 0; i < count; i++) {
+ // Read message's length
+ uint messageLen = READ_BE_UINT16(dataPtr + messageLenPos);
+ messageLenPos += 2;
+
+ // Store the read message.
+ // This code works around input data that has empty strings residing outside the input
+ // buffer (e.g. message indexes 58-254 in BATEAU.MSG in PROCS08 in Operation Stealth).
+ if (messageDataPos < sourceSize) {
+ messageTable.push_back((const char *)(dataPtr + messageDataPos));
+ } else {
+ if (messageLen > 0) { // Only warn about overflowing non-empty strings
+ warning("loadMsg(%s): message (%d. / %d) is overflowing the input buffer. Replacing it with an empty string", pMsgName, i + 1, count);
+ } else {
+ debugC(5, kCineDebugPart, "loadMsg(%s): empty message (%d. / %d) resides outside input buffer", pMsgName, i + 1, count);
+ }
+ // Message resides outside the input buffer so we replace it with an empty string
+ messageTable.push_back("");
+ }
+ // Jump to the next message
+ messageDataPos += messageLen;
}
free(dataPtr);
diff --git a/engines/cine/object.cpp b/engines/cine/object.cpp
index c02e01c8ce..9781975f7c 100644
--- a/engines/cine/object.cpp
+++ b/engines/cine/object.cpp
@@ -35,9 +35,16 @@
namespace Cine {
-objectStruct objectTable[NUM_MAX_OBJECT];
+Common::Array<objectStruct> objectTable;
Common::List<overlay> overlayList;
+/*! \brief Resets all elements in the object table. */
+void resetObjectTable() {
+ for (Common::Array<objectStruct>::iterator it = objectTable.begin(); it != objectTable.end(); it++) {
+ it->clear();
+ }
+}
+
void loadObject(char *pObjectName) {
uint16 numEntry;
uint16 entrySize;
diff --git a/engines/cine/object.h b/engines/cine/object.h
index 7ad65eb75f..3bf6cdcc42 100644
--- a/engines/cine/object.h
+++ b/engines/cine/object.h
@@ -38,6 +38,17 @@ struct objectStruct {
int16 costume;
char name[20];
uint16 part;
+
+ /*! \brief Sets all member variables to zero. */
+ void clear() {
+ this->x = 0;
+ this->y = 0;
+ this->mask = 0;
+ this->frame = 0;
+ this->costume = 0;
+ memset(this->name, 0, sizeof(this->name));
+ this->part = 0;
+ }
};
struct overlay {
@@ -52,10 +63,10 @@ struct overlay {
#define NUM_MAX_OBJECT 255
#define NUM_MAX_VAR 255
-extern objectStruct objectTable[NUM_MAX_OBJECT];
-
+extern Common::Array<objectStruct> objectTable;
extern Common::List<overlay> overlayList;
+void resetObjectTable();
void loadObject(char *pObjectName);
void setupObject(byte objIdx, uint16 param1, uint16 param2, uint16 param3, uint16 param4);
void modifyObjectParam(byte objIdx, byte paramIdx, int16 newValue);
diff --git a/engines/cine/pal.cpp b/engines/cine/pal.cpp
index 3e6f5adf40..7f6307c640 100644
--- a/engines/cine/pal.cpp
+++ b/engines/cine/pal.cpp
@@ -28,10 +28,7 @@
namespace Cine {
-uint16 palEntriesCount;
-
-PalEntry *palPtr = NULL;
-
+Common::Array<PalEntry> palArray;
static byte paletteBuffer1[16];
static byte paletteBuffer2[16];
@@ -41,27 +38,20 @@ void loadPal(const char *fileName) {
removeExtention(buffer, fileName);
strcat(buffer, ".PAL");
-
- if (palPtr) {
- free(palPtr);
- palPtr = NULL;
- }
-
- palEntriesCount = 0;
+ palArray.clear();
Common::File palFileHandle;
if (!palFileHandle.open(buffer))
error("loadPal(): Cannot open file %s", fileName);
- palEntriesCount = palFileHandle.readUint16LE();
+ uint16 palEntriesCount = palFileHandle.readUint16LE();
palFileHandle.readUint16LE(); // entry size
- palPtr = (PalEntry *)malloc(palEntriesCount * sizeof(PalEntry));
- assert(palPtr);
- for (int i = 0; i < palEntriesCount; ++i) {
- palFileHandle.read(palPtr[i].name, 10);
- palFileHandle.read(palPtr[i].pal1, 16);
- palFileHandle.read(palPtr[i].pal2, 16);
+ palArray.resize(palEntriesCount);
+ for (uint i = 0; i < palArray.size(); ++i) {
+ palFileHandle.read(palArray[i].name, 10);
+ palFileHandle.read(palArray[i].pal1, 16);
+ palFileHandle.read(palArray[i].pal2, 16);
}
palFileHandle.close();
}
@@ -81,8 +71,8 @@ int16 findPaletteFromName(const char *fileName) {
position++;
}
- for (i = 0; i < palEntriesCount; i++) {
- if (!strcmp(buffer, palPtr[i].name)) {
+ for (i = 0; i < palArray.size(); i++) {
+ if (!strcmp(buffer, palArray[i].name)) {
return i;
}
}
@@ -105,9 +95,9 @@ void loadRelatedPalette(const char *fileName) {
paletteBuffer1[i] = paletteBuffer2[i] = (i << 4) + i;
}
} else {
- assert(paletteIndex < palEntriesCount);
- memcpy(paletteBuffer1, palPtr[paletteIndex].pal1, 16);
- memcpy(paletteBuffer2, palPtr[paletteIndex].pal2, 16);
+ assert(paletteIndex < (int32)palArray.size());
+ memcpy(paletteBuffer1, palArray[paletteIndex].pal1, 16);
+ memcpy(paletteBuffer2, palArray[paletteIndex].pal2, 16);
}
}
diff --git a/engines/cine/pal.h b/engines/cine/pal.h
index 768cf0d27d..819680973c 100644
--- a/engines/cine/pal.h
+++ b/engines/cine/pal.h
@@ -34,7 +34,7 @@ struct PalEntry {
byte pal2[16];
};
-extern PalEntry *palPtr;
+extern Common::Array<PalEntry> palArray;
void loadPal(const char *fileName);
diff --git a/engines/cine/part.cpp b/engines/cine/part.cpp
index 88f2dcef52..7679d9d380 100644
--- a/engines/cine/part.cpp
+++ b/engines/cine/part.cpp
@@ -31,13 +31,10 @@
namespace Cine {
-uint16 numElementInPart;
-
-PartBuffer *partBuffer;
+Common::Array<PartBuffer> partBuffer;
void loadPart(const char *partName) {
- memset(partBuffer, 0, sizeof(PartBuffer) * NUM_MAX_PARTDATA);
- numElementInPart = 0;
+ partBuffer.clear();
g_cine->_partFileHandle.close();
@@ -48,13 +45,14 @@ void loadPart(const char *partName) {
setMouseCursor(MOUSE_CURSOR_DISK);
- numElementInPart = g_cine->_partFileHandle.readUint16BE();
+ uint16 numElementInPart = g_cine->_partFileHandle.readUint16BE();
+ partBuffer.resize(numElementInPart);
g_cine->_partFileHandle.readUint16BE(); // entry size
if (currentPartName != partName)
strcpy(currentPartName, partName);
- for (uint16 i = 0; i < numElementInPart; i++) {
+ for (uint16 i = 0; i < partBuffer.size(); i++) {
g_cine->_partFileHandle.read(partBuffer[i].partName, 14);
partBuffer[i].offset = g_cine->_partFileHandle.readUint32BE();
partBuffer[i].packedSize = g_cine->_partFileHandle.readUint32BE();
@@ -125,13 +123,13 @@ void CineEngine::readVolCnf() {
unpackedSize = packedSize = f.size();
}
uint8 *buf = new uint8[unpackedSize];
- f.read(buf, packedSize);
- if (packedSize != unpackedSize) {
- CineUnpacker cineUnpacker;
- if (!cineUnpacker.unpack(buf, packedSize, buf, unpackedSize)) {
- error("Error while unpacking 'vol.cnf' data");
- }
+ uint8 *packedBuf = new uint8[packedSize];
+ f.read(packedBuf, packedSize);
+ CineUnpacker cineUnpacker;
+ if (!cineUnpacker.unpack(packedBuf, packedSize, buf, unpackedSize)) {
+ error("Error while unpacking 'vol.cnf' data");
}
+ delete[] packedBuf;
uint8 *p = buf;
int resourceFilesCount = READ_BE_UINT16(p); p += 2;
int entrySize = READ_BE_UINT16(p); p += 2;
@@ -190,7 +188,7 @@ void CineEngine::readVolCnf() {
int16 findFileInBundle(const char *fileName) {
if (g_cine->getGameType() == Cine::GType_OS) {
// look first in currently loaded resource file
- for (int i = 0; i < numElementInPart; i++) {
+ for (uint i = 0; i < partBuffer.size(); i++) {
if (!scumm_stricmp(fileName, partBuffer[i].partName)) {
return i;
}
@@ -204,7 +202,7 @@ int16 findFileInBundle(const char *fileName) {
const char *part = (*it)._value;
loadPart(part);
}
- for (int i = 0; i < numElementInPart; i++) {
+ for (uint i = 0; i < partBuffer.size(); i++) {
if (!scumm_stricmp(fileName, partBuffer[i].partName)) {
return i;
}
@@ -212,26 +210,32 @@ int16 findFileInBundle(const char *fileName) {
return -1;
}
-void readFromPart(int16 idx, byte *dataPtr) {
+void readFromPart(int16 idx, byte *dataPtr, uint32 maxSize) {
+ assert(maxSize >= partBuffer[idx].packedSize);
setMouseCursor(MOUSE_CURSOR_DISK);
g_cine->_partFileHandle.seek(partBuffer[idx].offset, SEEK_SET);
g_cine->_partFileHandle.read(dataPtr, partBuffer[idx].packedSize);
}
-byte *readBundleFile(int16 foundFileIdx) {
- assert(foundFileIdx >= 0 && foundFileIdx < numElementInPart);
+byte *readBundleFile(int16 foundFileIdx, uint32 *size) {
+ assert(foundFileIdx >= 0 && foundFileIdx < (int32)partBuffer.size());
+ bool error = false;
byte *dataPtr = (byte *)calloc(partBuffer[foundFileIdx].unpackedSize, 1);
- if (partBuffer[foundFileIdx].unpackedSize != partBuffer[foundFileIdx].packedSize) {
- byte *unpackBuffer = (byte *)malloc(partBuffer[foundFileIdx].packedSize);
- readFromPart(foundFileIdx, unpackBuffer);
- CineUnpacker cineUnpacker;
- if (!cineUnpacker.unpack(unpackBuffer, partBuffer[foundFileIdx].packedSize, dataPtr, partBuffer[foundFileIdx].unpackedSize)) {
- warning("Error unpacking '%s' from bundle file '%s'", partBuffer[foundFileIdx].partName, currentPartName);
- }
- free(unpackBuffer);
- } else {
- readFromPart(foundFileIdx, dataPtr);
+ byte *packedData = (byte *)calloc(partBuffer[foundFileIdx].packedSize, 1);
+ assert(dataPtr && packedData);
+ readFromPart(foundFileIdx, packedData, partBuffer[foundFileIdx].packedSize);
+ CineUnpacker cineUnpacker;
+ error = !cineUnpacker.unpack(packedData, partBuffer[foundFileIdx].packedSize, dataPtr, partBuffer[foundFileIdx].unpackedSize);
+ free(packedData);
+
+ if (error) {
+ warning("Error unpacking '%s' from bundle file '%s'", partBuffer[foundFileIdx].partName, currentPartName);
+ }
+
+ // Set the size variable if a pointer to it has been given
+ if (size != NULL) {
+ *size = partBuffer[foundFileIdx].unpackedSize;
}
return dataPtr;
@@ -259,7 +263,13 @@ byte *readBundleSoundFile(const char *entryName, uint32 *size) {
return data;
}
-byte *readFile(const char *filename) {
+/*! \brief Rotate byte value to the left by n bits */
+byte rolByte(byte value, uint n) {
+ n %= 8;
+ return (byte) ((value << n) | (value >> (8 - n)));
+}
+
+byte *readFile(const char *filename, bool crypted) {
Common::File in;
in.open(filename);
@@ -272,6 +282,16 @@ byte *readFile(const char *filename) {
byte *dataPtr = (byte *)malloc(size);
in.read(dataPtr, size);
+ // The Sony published CD version of Future Wars has its
+ // AUTO00.PRC file's bytes rotated to the right by one.
+ // So we decode the so called crypting by rotating all
+ // the bytes to the left by one.
+ if (crypted) {
+ for (uint index = 0; index < size; index++) {
+ dataPtr[index] = rolByte(dataPtr[index], 1);
+ }
+ }
+
return dataPtr;
}
@@ -284,7 +304,7 @@ void dumpBundle(const char *fileName) {
strcpy(tmpPart, currentPartName);
loadPart(fileName);
- for (int i = 0; i < numElementInPart; i++) {
+ for (uint i = 0; i < partBuffer.size(); i++) {
byte *data = readBundleFile(i);
debug(0, "%s", partBuffer[i].partName);
diff --git a/engines/cine/part.h b/engines/cine/part.h
index 2a979e4879..755f843b4a 100644
--- a/engines/cine/part.h
+++ b/engines/cine/part.h
@@ -37,18 +37,18 @@ struct PartBuffer {
#define NUM_MAX_PARTDATA 255
-extern PartBuffer *partBuffer;
+extern Common::Array<PartBuffer> partBuffer;
void loadPart(const char *partName);
void closePart(void);
int16 findFileInBundle(const char *fileName);
-void readFromPart(int16 idx, byte *dataPtr);
+void readFromPart(int16 idx, byte *dataPtr, uint32 maxSize);
-byte *readBundleFile(int16 foundFileIdx);
+byte *readBundleFile(int16 foundFileIdx, uint32 *size = NULL);
byte *readBundleSoundFile(const char *entryName, uint32 *size = 0);
-byte *readFile(const char *filename);
+byte *readFile(const char *filename, bool crypted = false);
void checkDataDisk(int16 param);
diff --git a/engines/cine/prc.cpp b/engines/cine/prc.cpp
index 27b1044620..797a354c4f 100644
--- a/engines/cine/prc.cpp
+++ b/engines/cine/prc.cpp
@@ -25,6 +25,7 @@
#include "common/endian.h"
+#include "common/events.h"
#include "cine/cine.h"
#include "cine/various.h"
@@ -54,14 +55,16 @@ bool loadPrc(const char *pPrcName) {
// This is copy protection. Used to hang the machine
if (!scumm_stricmp(pPrcName, COPY_PROT_FAIL_PRC_NAME)) {
- exitEngine = 1;
+ Common::Event event;
+ event.type = Common::EVENT_RTL;
+ g_system->getEventManager()->pushEvent(event);
return false;
}
checkDataDisk(-1);
if ((g_cine->getGameType() == Cine::GType_FW) &&
(!scumm_stricmp(pPrcName, BOOT_PRC_NAME) || !scumm_stricmp(pPrcName, "demo.prc"))) {
- scriptPtr = dataPtr = readFile(pPrcName);
+ scriptPtr = dataPtr = readFile(pPrcName, (g_cine->getFeatures() & GF_CRYPTED_BOOT_PRC) != 0);
} else {
scriptPtr = dataPtr = readBundleFile(findFileInBundle(pPrcName));
}
diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp
index e761a0c8e4..6c13647ff3 100644
--- a/engines/cine/script_fw.cpp
+++ b/engines/cine/script_fw.cpp
@@ -1319,6 +1319,7 @@ int FWScript::o1_loadBg() {
return 0;
}
+/*! \brief Load collision table data */
int FWScript::o1_loadCt() {
const char *param = getNextString();
@@ -1500,7 +1501,18 @@ int FWScript::o1_compareGlobalVar() {
debugC(5, kCineDebugScript, "Line: %d: compare globalVars[%d] and %d", _line, varIdx, value);
- _compare = compareVars(_globalVars[varIdx], value);
+ // WORKAROUND for bug #2054882. Without this, the monks will always
+ // kill you as an impostor, even if you enter the monastery in disguise.
+ //
+ // TODO: Check whether this might be worked around in some other way
+ // like setting global variable 255 to 143 in Future Wars (This is
+ // supposedly what Future Wars checks for from time to time during
+ // gameplay to verify that copy protection was successfully passed).
+ if (varIdx == 255 && (g_cine->getGameType() == Cine::GType_FW)) {
+ _compare = kCmpEQ;
+ } else {
+ _compare = compareVars(_globalVars[varIdx], value);
+ }
}
return 0;
@@ -1789,7 +1801,14 @@ int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneI
int16 result = 0;
for (int16 i = 0; i < numZones; i++) {
- idx = getZoneFromPositionRaw(page3Raw, lx + i, ly, 320);
+ // Don't try to read data in Operation Stealth if position isn't in 320x200 screen bounds.
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ if ((lx + i) < 0 || (lx + i) > 319 || ly < 0 || ly > 199) {
+ continue;
+ }
+ }
+
+ idx = getZoneFromPositionRaw(collisionPage, lx + i, ly, 320);
assert(idx >= 0 && idx < NUM_MAX_ZONE);
diff --git a/engines/cine/script_os.cpp b/engines/cine/script_os.cpp
index a764281758..e8f16ebfcc 100644
--- a/engines/cine/script_os.cpp
+++ b/engines/cine/script_os.cpp
@@ -365,6 +365,7 @@ FWScript *OSScriptInfo::create(const RawObjectScript &script, int16 index, const
// OPERATION STEALTH opcodes
// ------------------------------------------------------------------------
+/*! \brief Load collision table data */
int FWScript::o2_loadCt() {
const char *param = getNextString();
@@ -636,7 +637,28 @@ int FWScript::o2_loadAbs() {
const char *param2 = getNextString();
debugC(5, kCineDebugScript, "Line: %d: loadABS(%d,%s)", _line, param1, param2);
- loadAbs(param2, param1);
+ // Load the resource to an absolute position
+ if (loadResource(param2, param1) == -1) { // Check if the loading failed
+ // WORKAROUND: In the 256 color PC version of Operation Stealth when
+ // walking out of the airport in Santa Paragua to the street the
+ // player character should be seen as a grey silhuette while walking
+ // behind the glass. But actually the player character is completely
+ // invisible when walking behind the glass because the animation files
+ // used are wrongly loaded. In AIRPORT.PRC's 6th script there are
+ // calls loadAbs("JOHN01.ANI", 73) and loadAbs("JOHN02.ANI", 37) to
+ // load the animations involved but no such files are found with the
+ // game. Corresponding SET-files are found though. As it worked and
+ // looked fine when I tried loading them instead of the missing ANI
+ // files I'm doing so here. NOTE: At least the German Amiga version
+ // of Operation Stealth seems to have all the files involved
+ // (JOHN01.ANI, JOHN02.ANI, JOHN01.SET and JOHN02.SET).
+ if (scumm_stricmp(param2, "JOHN01.ANI") == 0 && param1 == 73) {
+ loadResource("JOHN01.SET", param1);
+ } else if (scumm_stricmp(param2, "JOHN02.ANI") == 0 && param1 == 37) {
+ loadResource("JOHN02.SET", param1);
+ }
+ }
+
return 0;
}
diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp
index f26032fe98..164c5a9ca5 100644
--- a/engines/cine/sound.cpp
+++ b/engines/cine/sound.cpp
@@ -79,13 +79,13 @@ const int PCSoundDriver::_noteTable[] = {
const int PCSoundDriver::_noteTableCount = ARRAYSIZE(_noteTable);
struct AdlibRegisterSoundInstrument {
- uint16 vibrato;
- uint16 attackDecay;
- uint16 sustainRelease;
- uint16 feedbackStrength;
- uint16 keyScaling;
- uint16 outputLevel;
- uint16 freqMod;
+ uint8 vibrato;
+ uint8 attackDecay;
+ uint8 sustainRelease;
+ uint8 feedbackStrength;
+ uint8 keyScaling;
+ uint8 outputLevel;
+ uint8 freqMod;
};
struct AdlibSoundInstrument {
@@ -604,6 +604,7 @@ bool PCSoundFxPlayer::load(const char *song) {
_instrumentsData[i] = NULL;
char instrument[64];
+ memset(instrument, 0, 64); // Clear the data first
memcpy(instrument, _sfxData + 20 + i * 30, 12);
instrument[63] = '\0';
diff --git a/engines/cine/texte.cpp b/engines/cine/texte.cpp
index e4fd334926..ffc36b4b1a 100644
--- a/engines/cine/texte.cpp
+++ b/engines/cine/texte.cpp
@@ -29,70 +29,59 @@
namespace Cine {
-byte *textDataPtr;
-
const char **failureMessages;
const CommandeType *defaultActionCommand;
const CommandeType *systemMenu;
const CommandeType *confirmMenu;
const char **otherMessages;
-const char *commandPrepositionOn;
+const char *defaultCommandPreposition;
+const char **commandPrepositionTable;
void generateMask(const byte *sprite, byte *mask, uint16 size, byte transparency);
-void loadTextData(const char *pFileName, byte *pDestinationBuffer) {
- Common::File pFileHandle;
- uint16 entrySize;
- uint16 numEntry;
- uint16 i;
- byte *tempBuffer;
- uint16 dataSize;
-
- assert(pFileName);
- assert(pDestinationBuffer);
-
- if (!pFileHandle.open(pFileName))
- error("loadTextData(): Cannot open file %s", pFileName);
-
- entrySize = pFileHandle.readUint16BE();
- numEntry = pFileHandle.readUint16BE();
-
- dataSize = numEntry * entrySize;
- pFileHandle.read(pDestinationBuffer, numEntry * entrySize);
+/*! \brief Loads font data from the given file.
+ * The number of characters used in the font varies between game versions:
+ * 78 (Most PC, Amiga and Atari ST versions of Future Wars, but also Operation Stealth's Amiga demo),
+ * 85 (All observed versions of German Future Wars (Amiga and PC), possibly Spanish Future Wars too),
+ * 90 (Most PC, Amiga and Atari ST versions of Operation Stealth),
+ * 93 (All observed versions of German Operation Stealth (Amiga and PC)).
+ */
+void loadTextData(const char *filename) {
+ Common::File fileHandle;
+ assert(filename);
+
+ if (!fileHandle.open(filename))
+ error("loadTextData(): Cannot open file %s", filename);
+
+ static const uint headerSize = 2 + 2; // The entry size (16-bit) and entry count (16-bit).
+ const uint entrySize = fileHandle.readUint16BE(); // Observed values: 8.
+ const uint entryCount = fileHandle.readUint16BE(); // Observed values: 624, 680, 720, 744.
+ const uint fontDataSize = entryCount * entrySize; // Observed values: 4992, 5440, 5760, 5952.
+ const uint numChars = entryCount / entrySize; // Observed values: 78, 85, 90, 93.
+ const uint bytesPerChar = fontDataSize / numChars; // Observed values: 64.
+ static const uint bytesPerRow = FONT_WIDTH / 2; // The input font data is 4-bit so it takes only half the space
+
+ if (headerSize + fontDataSize != (uint)fileHandle.size()) {
+ warning("loadTextData: file '%s' (entrySize = %d, entryCount = %d) is of incorrect size %d", filename, entrySize, entryCount, fileHandle.size());
+ }
- tempBuffer = pDestinationBuffer;
+ Common::Array<byte> source;
+ source.resize(fontDataSize);
+ fileHandle.read(source.begin(), fontDataSize);
if (g_cine->getGameType() == Cine::GType_FW) {
- int numCharacters;
- if (g_cine->getFeatures() & GF_ALT_FONT) {
- numCharacters = 85;
- } else {
- numCharacters = 78;
- }
-
- dataSize = dataSize / numCharacters;
-
- loadRelatedPalette(pFileName);
+ loadRelatedPalette(filename);
+ }
- for (i = 0; i < numCharacters; i++) {
- 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(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;
- }
+ for (uint i = 0; i < numChars; i++) {
+ gfxConvertSpriteToRaw(g_cine->_textHandler.textTable[i][FONT_DATA], &source[i * bytesPerChar], bytesPerRow, FONT_HEIGHT);
+ generateMask(g_cine->_textHandler.textTable[i][FONT_DATA], g_cine->_textHandler.textTable[i][FONT_MASK], FONT_WIDTH * FONT_HEIGHT, 0);
}
- pFileHandle.close();
+ fileHandle.close();
}
-const CharacterEntry *fontParamTable;
-
-const CharacterEntry fontParamTable_standard[256] = {
+static const CharacterEntry fontParamTable_standard[NUM_FONT_CHARS] = {
{ 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0},
{ 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0},
{ 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0},
@@ -129,7 +118,7 @@ const CharacterEntry fontParamTable_standard[256] = {
{ 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}
};
-const CharacterEntry fontParamTable_alt[256] = {
+static const CharacterEntry fontParamTable_alt[NUM_FONT_CHARS] = {
{ 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0},
{ 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0},
{ 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0}, { 0, 0},
@@ -208,6 +197,16 @@ void initLanguage(Common::Language lang) {
"NOACTION"
};
+ static const char *commandPrepositionTable_EN[] = {
+ "", // EXAMINE
+ "", // TAKE
+ "", // INVENTORY
+ "on", // USE
+ "", // OPERATE
+ "to", // SPEAK
+ "" // NOACTION
+ };
+
static const CommandeType systemMenu_EN[] = {
"Pause",
"Restart Game",
@@ -223,9 +222,8 @@ void initLanguage(Common::Language lang) {
"PAUSE",
"Loading | %s",
"Loading canceled ...",
- "No baclup in the drive...",
- "Please enter the backup name",
- "on"
+ "No backup in the drive...",
+ "Please enter the backup name"
};
static const CommandeType confirmMenu_EN[] = {
@@ -276,6 +274,16 @@ void initLanguage(Common::Language lang) {
"NOACTION"
};
+ static const char *commandPrepositionTable_FR[] = {
+ "", // EXAMINER
+ "", // PRENDRE
+ "", // INVENTAIRE
+ "sur", // UTILISER
+ "", // ACTIONNER
+ "a", // PARLER
+ "" // NOACTION
+ };
+
static const CommandeType systemMenu_FR[] = {
"Pause",
"Nouvelle partie",
@@ -297,8 +305,7 @@ void initLanguage(Common::Language lang) {
"Sauvegarde de | %s",
"Sauvegarde Annul\x82""e ...",
"Aucune sauvegarde dans le lecteur ...",
- "Veuillez entrer le Nom de la Sauvegarde .",
- "sur"
+ "Veuillez entrer le Nom de la Sauvegarde ."
};
static const char *failureMessages_ES[] = {
@@ -344,6 +351,16 @@ void initLanguage(Common::Language lang) {
"NOACTION"
};
+ static const char *commandPrepositionTable_ES[] = {
+ "", // EXAMINAR
+ "", // COGER
+ "", // INVENTARIO
+ "donde", // USAR
+ "", // ACCIONAR
+ "a", // HABLAR
+ "" // NOACTION
+ };
+
static const CommandeType systemMenu_ES[] = {
"Pause",
"Nueva partida",
@@ -365,8 +382,7 @@ void initLanguage(Common::Language lang) {
"Gabacion de| %s",
"Rrabacion anulada",
"No hay partidas grabadas en este disco...",
- "Teclea el nombre de la partida grabada",
- "donde"
+ "Teclea el nombre de la partida grabada"
};
static const char *failureMessages_DE[] = {
@@ -403,15 +419,25 @@ void initLanguage(Common::Language lang) {
};
static const CommandeType defaultActionCommand_DE[] = {
- "Pr\x81""fe",
+ "Pr\x81""fe", // FIXME? The third letter should be Latin Small Letter U with diaeresis
"Nimm",
"Bestand",
"Benutze",
- "Bet\x84tige",
+ "Bet\x84tige", // FIXME? The fourth letter should be Latin Small Letter A with diaeresis
"Sprich",
"NOACTION"
};
+ static const char *commandPrepositionTable_DE[] = {
+ "", // Prufe
+ "", // Nimm
+ "", // Bestand
+ "gegen", // Benutze
+ "", // Betatige
+ "a", // Sprich
+ "" // NOACTION
+ };
+
static const CommandeType systemMenu_DE[] = {
"Pause",
"Spiel Neu Starten",
@@ -433,8 +459,7 @@ void initLanguage(Common::Language lang) {
"Er L\x84""dt | %s",
"Ladevorgang Abgebrochen...",
"Kein Backup im Laufwerk...",
- "Geben Sie den Namen|der Sicherungsdiskette ein",
- "gegen"
+ "Geben Sie den Namen|der Sicherungsdiskette ein"
};
static const char *failureMessages_IT[] = {
@@ -480,6 +505,16 @@ void initLanguage(Common::Language lang) {
"NOACTION"
};
+ static const char *commandPrepositionTable_IT[] = {
+ "", // ESAMINARE
+ "", // PRENDERE
+ "", // INVENTARIO
+ "su", // UTILIZZARE
+ "", // AZIONARE
+ "a", // PARLARE
+ "" // NOACTION
+ };
+
static const CommandeType systemMenu_IT[] = {
"Pausa",
"Parte nuova",
@@ -501,8 +536,7 @@ void initLanguage(Common::Language lang) {
"Caricamento di| %s",
"Caricamento annullato...",
"Nessun salvataggio su questo disco...",
- "Vogliate accedere con il nome del salvataggio",
- "su"
+ "Vogliate accedere con il nome del salvataggio"
};
switch (lang) {
@@ -512,7 +546,8 @@ void initLanguage(Common::Language lang) {
systemMenu = systemMenu_FR;
confirmMenu = confirmMenu_FR;
otherMessages = otherMessages_FR;
- commandPrepositionOn = otherMessages_FR[7];
+ defaultCommandPreposition = commandPrepositionTable_FR[3];
+ commandPrepositionTable = commandPrepositionTable_FR;
break;
case Common::ES_ESP:
@@ -521,7 +556,8 @@ void initLanguage(Common::Language lang) {
systemMenu = systemMenu_ES;
confirmMenu = confirmMenu_ES;
otherMessages = otherMessages_ES;
- commandPrepositionOn = otherMessages_ES[7];
+ defaultCommandPreposition = commandPrepositionTable_ES[3];
+ commandPrepositionTable = commandPrepositionTable_ES;
break;
case Common::DE_DEU:
@@ -530,7 +566,8 @@ void initLanguage(Common::Language lang) {
systemMenu = systemMenu_DE;
confirmMenu = confirmMenu_DE;
otherMessages = otherMessages_DE;
- commandPrepositionOn = otherMessages_DE[7];
+ defaultCommandPreposition = commandPrepositionTable_DE[3];
+ commandPrepositionTable = commandPrepositionTable_DE;
break;
case Common::IT_ITA:
@@ -539,7 +576,8 @@ void initLanguage(Common::Language lang) {
systemMenu = systemMenu_IT;
confirmMenu = confirmMenu_IT;
otherMessages = otherMessages_IT;
- commandPrepositionOn = otherMessages_IT[7];
+ defaultCommandPreposition = commandPrepositionTable_IT[3];
+ commandPrepositionTable = commandPrepositionTable_IT;
break;
default:
@@ -548,14 +586,17 @@ void initLanguage(Common::Language lang) {
systemMenu = systemMenu_EN;
confirmMenu = confirmMenu_EN;
otherMessages = otherMessages_EN;
- commandPrepositionOn = otherMessages_EN[7];
+ defaultCommandPreposition = commandPrepositionTable_EN[3];
+ commandPrepositionTable = commandPrepositionTable_EN;
break;
}
if (g_cine->getFeatures() & GF_ALT_FONT) {
- fontParamTable = fontParamTable_alt;
+ // Copy alternative font parameter table to the current font parameter table
+ Common::copy(fontParamTable_alt, fontParamTable_alt + NUM_FONT_CHARS, g_cine->_textHandler.fontParamTable);
} else {
- fontParamTable = fontParamTable_standard;
+ // Copy standard font parameter to the current font parameter table
+ Common::copy(fontParamTable_standard, fontParamTable_standard + NUM_FONT_CHARS, g_cine->_textHandler.fontParamTable);
}
}
@@ -590,25 +631,16 @@ void loadPoldatDat(const char *fname) {
in.open(fname);
if (in.isOpen()) {
- CharacterEntry *ptr = (CharacterEntry *)malloc(sizeof(CharacterEntry) * 256);
-
- for (int i = 0; i < 256; i++) {
- ptr[i].characterIdx = (int)in.readByte();
- ptr[i].characterWidth = (int)in.readByte();
+ for (int i = 0; i < NUM_FONT_CHARS; i++) {
+ g_cine->_textHandler.fontParamTable[i].characterIdx = in.readByte();
+ g_cine->_textHandler.fontParamTable[i].characterWidth = in.readByte();
}
- fontParamTable = ptr;
-
in.close();
} else {
error("Cannot open file %s for reading", fname);
}
}
-void freePoldatDat() {
- free(const_cast<Cine::CharacterEntry *>(fontParamTable));
- fontParamTable = 0;
-}
-
/*! \brief Fit a substring of text into one line of fixed width text box
* \param str Text to fit
* \param maxWidth Text box width
@@ -633,7 +665,7 @@ int fitLine(const char *str, int maxWidth, int &words, int &width) {
bkpWidth = width;
bkpLen = i + 1;
} else {
- charWidth = fontParamTable[(unsigned char)str[i]].characterWidth + 1;
+ charWidth = g_cine->_textHandler.fontParamTable[(unsigned char)str[i]].characterWidth + 1;
width += charWidth;
}
diff --git a/engines/cine/texte.h b/engines/cine/texte.h
index f471c3c49e..0b1fc88e86 100644
--- a/engines/cine/texte.h
+++ b/engines/cine/texte.h
@@ -33,10 +33,24 @@ namespace Cine {
typedef char CommandeType[20];
-extern byte *textDataPtr;
+// Number of characters in a font
+#define NUM_FONT_CHARS 256
+
+#define FONT_WIDTH 16
+#define FONT_HEIGHT 8
+
+// Used for choosing between font's data and font's mask
+#define FONT_DATA 0
+#define FONT_MASK 1
+
+struct CharacterEntry {
+ byte characterIdx;
+ byte characterWidth;
+};
struct TextHandler {
- byte textTable[256][2][16 * 8];
+ byte textTable[NUM_FONT_CHARS][2][FONT_WIDTH * FONT_HEIGHT];
+ CharacterEntry fontParamTable[NUM_FONT_CHARS];
};
extern const char **failureMessages;
@@ -44,20 +58,13 @@ extern const CommandeType *defaultActionCommand;
extern const CommandeType *systemMenu;
extern const CommandeType *confirmMenu;
extern const char **otherMessages;
-extern const char *commandPrepositionOn;
-
-struct CharacterEntry {
- byte characterIdx;
- byte characterWidth;
-};
-
-extern const CharacterEntry *fontParamTable;
+extern const char *defaultCommandPreposition;
+extern const char **commandPrepositionTable;
-void loadTextData(const char *pFileName, byte *pDestinationBuffer);
+void loadTextData(const char *filename);
void loadErrmessDat(const char *fname);
void freeErrmessDat(void);
void loadPoldatDat(const char *fname);
-void freePoldatDat(void);
int fitLine(const char *ptr, int maxWidth, int &words, int &width);
diff --git a/engines/cine/unpack.cpp b/engines/cine/unpack.cpp
index 5d85ff6cab..7915fd1cf8 100644
--- a/engines/cine/unpack.cpp
+++ b/engines/cine/unpack.cpp
@@ -100,6 +100,14 @@ bool CineUnpacker::unpack(const byte *src, uint srcLen, byte *dst, uint dstLen)
_dstBegin = dst;
_dstEnd = dst + dstLen;
+ // Handle already unpacked data here
+ if (srcLen == dstLen) {
+ // Source length is same as destination length so the source
+ // data is already unpacked. Let's just copy it then.
+ memcpy(dst, src, srcLen);
+ return true;
+ }
+
// Initialize other variables
_src = _srcBegin + srcLen - 4;
uint32 unpackedLength = readSource(); // Unpacked length in bytes
diff --git a/engines/cine/unpack.h b/engines/cine/unpack.h
index e16cb594a9..2355df5ee1 100644
--- a/engines/cine/unpack.h
+++ b/engines/cine/unpack.h
@@ -35,14 +35,14 @@ namespace Cine {
* A LZ77 style decompressor for Delphine's data files
* used in at least Future Wars and Operation Stealth.
* @note Works backwards in the source and destination buffers.
- * @note Can work with source and destination in the same buffer if there's space.
+ * @warning Having the source and destination in the same buffer when unpacking can cause errors!
*/
class CineUnpacker {
public:
/**
* Unpacks packed data from the source buffer to the destination buffer.
- * @warning Do NOT call this on data that is not packed.
- * @note Source and destination buffer pointers can be the same as long as there's space for the unpacked data.
+ * @note You may call this on already unpacked data but then source length must be equal to destination length.
+ * @warning The source and destination should not point to the same buffer. If they do, errors may occur!
* @param src Pointer to the source buffer.
* @param srcLen Length of the source buffer.
* @param dst Pointer to the destination buffer.
diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp
index 2fcb015fcd..92fd35d865 100644
--- a/engines/cine/various.cpp
+++ b/engines/cine/various.cpp
@@ -25,7 +25,6 @@
#include "common/endian.h"
-#include "common/events.h"
#include "common/savefile.h"
#include "cine/cine.h"
@@ -78,7 +77,7 @@ byte _danKeysPressed;
int16 playerCommand;
-char commandBuffer[80];
+Common::String commandBuffer;
char currentPrcName[20];
char currentRelName[20];
char currentObjectName[20];
@@ -95,11 +94,23 @@ 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;
+/*! \brief Values used by the xMoveKeyb variable */
+enum xMoveKeybEnums {
+ kKeybMoveCenterX = 0,
+ kKeybMoveRight = 1,
+ kKeybMoveLeft = 2
+};
+
+/*! \brief Values used by the yMoveKeyb variable */
+enum yMoveKeybEnums {
+ kKeybMoveCenterY = 0,
+ kKeybMoveDown = 1,
+ kKeybMoveUp = 2
+};
+
+uint16 xMoveKeyb = kKeybMoveCenterX;
+bool egoMovedWithKeyboard = false;
+uint16 yMoveKeyb = kKeybMoveCenterY;
SelectedObjStruct currentSelectedObject;
@@ -113,10 +124,34 @@ static const int16 canUseOnItemTable[] = { 1, 0, 0, 1, 1, 0, 0 };
CommandeType objectListCommand[20];
int16 objListTab[20];
-uint16 exitEngine;
-uint16 zoneData[NUM_MAX_ZONE];
-uint16 zoneQuery[NUM_MAX_ZONE]; //!< Only exists in Operation Stealth
+Common::Array<uint16> zoneData;
+Common::Array<uint16> zoneQuery; //!< Only exists in Operation Stealth
+
+/*! \brief Move the player character using the keyboard
+ * \param x Negative values move left, positive right, zero not at all
+ * \param y Negative values move down, positive up, zero not at all
+ * NOTE: If both x and y are zero then the character stops
+ * FIXME: This seems to only work in Operation Stealth. May need code changes somewhere else...
+ */
+void moveUsingKeyboard(int x, int y) {
+ if (x > 0) {
+ xMoveKeyb = kKeybMoveRight;
+ } else if (x < 0) {
+ xMoveKeyb = kKeybMoveLeft;
+ } else {
+ xMoveKeyb = kKeybMoveCenterX;
+ }
+
+ if (y > 0) {
+ yMoveKeyb = kKeybMoveUp;
+ } else if (y < 0) {
+ yMoveKeyb = kKeybMoveDown;
+ } else {
+ yMoveKeyb = kKeybMoveCenterY;
+ }
+ egoMovedWithKeyboard = x || y;
+}
void stopMusicAfterFadeOut(void) {
// if (g_sfxPlayer->_fadeOutCounter != 0 && g_sfxPlayer->_fadeOutCounter < 100) {
@@ -141,7 +176,6 @@ void addPlayerCommandMessage(int16 cmd) {
tmp.type = 3;
overlayList.push_back(tmp);
- waitForPlayerClick = 1;
}
int16 getRelEntryForObject(uint16 param1, uint16 param2, SelectedObjStruct *pSelectedObject) {
@@ -189,6 +223,15 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) {
frame = ABS((int16)(objectTable[it->objIdx].frame));
part = objectTable[it->objIdx].part;
+ // Additional case for negative frame values in Operation Stealth
+ if (g_cine->getGameType() == Cine::GType_OS && objectTable[it->objIdx].frame < 0) {
+ if ((it->type == 1) && (x >= objX) && (objX + frame >= x) && (y >= objY) && (objY + part >= y)) {
+ return it->objIdx;
+ } else {
+ continue;
+ }
+ }
+
if (it->type == 0) {
threshold = animDataTable[frame]._var1;
} else {
@@ -201,16 +244,19 @@ int16 getObjectUnderCursor(uint16 x, uint16 y) {
xdif = x - objX;
ydif = y - objY;
- if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif < 0) || (ydif >= height) || !animDataTable[frame].data()) {
+ if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif <= 0) || (ydif >= height) || !animDataTable[frame].data()) {
continue;
}
if (g_cine->getGameType() == Cine::GType_OS) {
+ // This test isn't present in Operation Stealth's PC version's disassembly
+ // but removing it makes things crash sometimes (e.g. when selecting a verb
+ // and moving the mouse cursor around the floor in the airport's bathroom).
if (xdif >= width) {
continue;
}
- if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != part) {
+ if (it->type == 0 && animDataTable[frame].getColor(xdif, ydif) != (part & 0x0F)) {
return it->objIdx;
} else if (it->type == 1 && gfxGetBit(xdif, ydif, animDataTable[frame].data(), animDataTable[frame]._width * 4)) {
return it->objIdx;
@@ -270,6 +316,18 @@ void saveCommandVariables(Common::OutSaveFile &out) {
}
}
+/*! \brief Save the 80 bytes long command buffer padded to that length with zeroes. */
+void saveCommandBuffer(Common::OutSaveFile &out) {
+ // Let's make sure there's space for the trailing zero
+ // (That's why we subtract one from the maximum command buffer size here).
+ uint32 size = MIN<uint32>(commandBuffer.size(), kMaxCommandBufferSize - 1);
+ out.write(commandBuffer.c_str(), size);
+ // Write the rest as zeroes (Here we also write the string's trailing zero)
+ for (uint i = 0; i < kMaxCommandBufferSize - size; i++) {
+ out.writeByte(0);
+ }
+}
+
void saveAnimDataTable(Common::OutSaveFile &out) {
out.writeUint16BE(NUM_MAX_ANIMDATA); // Entry count
out.writeUint16BE(0x1E); // Entry size
@@ -439,7 +497,7 @@ enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle
uint animEntrySize = animEntrySizeChoices[i];
// Jump over the animDataTable entries and the screen parameters
- uint32 newPos = animDataTableStart + animEntrySize * animEntriesCount + sizeofScreenParams;
+ int32 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;
@@ -570,16 +628,7 @@ void CineEngine::resetEngine() {
relTable.clear();
scriptTable.clear();
messageTable.clear();
-
- 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;
- objectTable[i].mask = 0;
- objectTable[i].costume = 0;
- }
+ resetObjectTable();
globalVars.reset();
@@ -596,7 +645,7 @@ void CineEngine::resetEngine() {
playerCommand = -1;
isDrawCommandEnabled = 0;
- strcpy(commandBuffer, "");
+ commandBuffer = "";
globalVars[VAR_MOUSE_X_POS] = 0;
globalVars[VAR_MOUSE_Y_POS] = 0;
@@ -797,7 +846,10 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
globalVars.load(in, NUM_MAX_VAR);
loadZoneData(in);
loadCommandVariables(in);
- in.read(commandBuffer, 0x50);
+ char tempCommandBuffer[kMaxCommandBufferSize];
+ in.read(tempCommandBuffer, kMaxCommandBufferSize);
+ commandBuffer = tempCommandBuffer;
+ renderer->setCommand(commandBuffer);
loadZoneQuery(in);
// TODO: Use the loaded string (Current music name (String, 13 bytes)).
@@ -934,7 +986,9 @@ bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFor
loadCommandVariables(in);
// At 0x22A9 (i.e. 0x22A1 + 4 * 2):
- in.read(commandBuffer, 0x50);
+ char tempCommandBuffer[kMaxCommandBufferSize];
+ in.read(tempCommandBuffer, kMaxCommandBufferSize);
+ commandBuffer = tempCommandBuffer;
renderer->setCommand(commandBuffer);
// At 0x22F9 (i.e. 0x22A9 + 0x50):
@@ -1038,7 +1092,7 @@ bool CineEngine::makeLoad(char *saveName) {
// 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.
+ // Therefore we 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) {
@@ -1082,7 +1136,7 @@ void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
globalVars.save(out, NUM_MAX_VAR);
saveZoneData(out);
saveCommandVariables(out);
- out.write(commandBuffer, 0x50);
+ saveCommandBuffer(out);
out.writeUint16BE(renderer->_cmdY);
out.writeUint16BE(bgVar0);
@@ -1167,7 +1221,7 @@ void CineEngine::makeSystemMenu(void) {
{
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
- exitEngine = 1;
+ quitGame();
}
break;
}
@@ -1297,7 +1351,7 @@ void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
globalVars.save(out, NUM_MAX_VAR);
saveZoneData(out);
saveCommandVariables(out);
- out.write(commandBuffer, 0x50);
+ saveCommandBuffer(out);
saveZoneQuery(out);
// FIXME: Save a proper name here, saving an empty string currently.
@@ -1360,19 +1414,38 @@ void drawDoubleMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 c
}
void processInventory(int16 x, int16 y) {
- int16 listSize = buildObjectListCommand(-2);
uint16 button;
+ int menuWidth;
+ int listSize;
+ int commandParam;
+
+ if (g_cine->getGameType() == Cine::GType_FW) {
+ menuWidth = 140;
+ commandParam = -2;
+ } else { // Operation Stealth
+ menuWidth = 160;
+ commandParam = -3;
+ }
+
+ listSize = buildObjectListCommand(commandParam);
if (!listSize)
return;
- renderer->drawMenu(objectListCommand, listSize, x, y, 140, -1);
+ renderer->drawMenu(objectListCommand, listSize, x, y, menuWidth, -1);
renderer->blit();
do {
manageEvents();
getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
} while (!button);
+
+ do {
+ manageEvents();
+ getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
+ } while (button);
+
+ // TODO: Both Future Wars and Operation Stealth call showMouse, drawMouse or something similar here.
}
int16 buildObjectListCommand(int16 param) {
@@ -1416,6 +1489,8 @@ int16 selectSubObject(int16 x, int16 y, int16 param) {
return objListTab[selectedObject];
}
+// TODO: Make separate functions for Future Wars's and Operation Stealth's version of this function, this is getting too messy
+// TODO: Add support for using the different prepositions for different verbs (Doesn't work currently)
void makeCommandLine(void) {
uint16 x, y;
@@ -1423,9 +1498,9 @@ void makeCommandLine(void) {
commandVar2 = -10;
if (playerCommand != -1) {
- strcpy(commandBuffer, defaultActionCommand[playerCommand]);
+ commandBuffer = defaultActionCommand[playerCommand];
} else {
- strcpy(commandBuffer, "");
+ commandBuffer = "";
}
if ((playerCommand != -1) && (choiceResultTable[playerCommand] == 2)) { // need object selection ?
@@ -1440,8 +1515,12 @@ void makeCommandLine(void) {
}
if (si < 0) {
- playerCommand = -1;
- strcpy(commandBuffer, "");
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ canUseOnObject = 0;
+ } else { // Future Wars
+ playerCommand = -1;
+ commandBuffer = "";
+ }
} else {
if (g_cine->getGameType() == Cine::GType_OS) {
if (si >= 8000) {
@@ -1454,23 +1533,28 @@ void makeCommandLine(void) {
commandVar3[0] = si;
commandVar1 = 1;
-
- strcat(commandBuffer, " ");
- strcat(commandBuffer, objectTable[commandVar3[0]].name);
- strcat(commandBuffer, " ");
- strcat(commandBuffer, commandPrepositionOn);
+ commandBuffer += " ";
+ commandBuffer += objectTable[commandVar3[0]].name;
+ commandBuffer += " ";
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ commandBuffer += commandPrepositionTable[playerCommand];
+ } else { // Future Wars
+ commandBuffer += defaultCommandPreposition;
+ }
}
- } else {
+ }
+
+ if (g_cine->getGameType() == Cine::GType_OS || !(playerCommand != -1 && choiceResultTable[playerCommand] == 2)) {
if (playerCommand == 2) {
getMouseData(mouseUpdateStatus, &dummyU16, &x, &y);
processInventory(x, y + 8);
playerCommand = -1;
commandVar1 = 0;
- strcpy(commandBuffer, "");
+ commandBuffer = "";
}
}
- if (g_cine->getGameType() == Cine::GType_OS) {
+ if (g_cine->getGameType() == Cine::GType_OS && playerCommand != 2) {
if (playerCommand != -1 && canUseOnObject != 0) { // call use on sub object
int16 si;
@@ -1478,34 +1562,38 @@ void makeCommandLine(void) {
si = selectSubObject(x, y + 8, -subObjectUseTable[playerCommand]);
- if (si) {
+ if (si >= 0) {
if (si >= 8000) {
si -= 8000;
}
commandVar3[commandVar1] = si;
-
commandVar1++;
-
- // TODO: add command message draw
+ commandBuffer += " ";
+ commandBuffer += objectTable[si].name;
}
+ }
- isDrawCommandEnabled = 1;
+ isDrawCommandEnabled = 1;
- if (playerCommand != -1 && choiceResultTable[playerCommand] == commandVar1) {
- SelectedObjStruct obj;
- obj.idx = commandVar3[0];
- obj.param = commandVar3[1];
- int16 di = getRelEntryForObject(playerCommand, commandVar1, &obj);
+ if (playerCommand != -1 && choiceResultTable[playerCommand] == commandVar1) {
+ SelectedObjStruct obj;
+ obj.idx = commandVar3[0];
+ obj.param = commandVar3[1];
+ int16 di = getRelEntryForObject(playerCommand, commandVar1, &obj);
- if (di != -1) {
- runObjectScript(di);
- }
- }
+ if (di != -1) {
+ runObjectScript(di);
+ } // TODO: else addFailureMessage(playerCommand)
+
+ playerCommand = -1;
+ commandVar1 = 0;
+ commandBuffer = "";
}
}
- if (!disableSystemMenu) {
+ if (g_cine->getGameType() == Cine::GType_OS || !disableSystemMenu) {
+ isDrawCommandEnabled = 1;
renderer->setCommand(commandBuffer);
}
}
@@ -1678,6 +1766,11 @@ uint16 executePlayerInput(void) {
}
if (allowPlayerInput) {
+ if (isDrawCommandEnabled) {
+ renderer->setCommand(commandBuffer);
+ isDrawCommandEnabled = 0;
+ }
+
getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY);
while (mouseButton && currentEntry < 200) {
@@ -1716,8 +1809,9 @@ uint16 executePlayerInput(void) {
commandVar3[commandVar1] = si;
commandVar1++;
- strcat(commandBuffer, " ");
- strcat(commandBuffer, objectTable[si].name);
+ commandBuffer += " ";
+ commandBuffer += objectTable[si].name;
+
isDrawCommandEnabled = 1;
@@ -1739,8 +1833,8 @@ uint16 executePlayerInput(void) {
playerCommand = -1;
commandVar1 = 0;
- strcpy(commandBuffer, "");
- renderer->setCommand("");
+ commandBuffer = "";
+ renderer->setCommand(commandBuffer);
}
} else {
globalVars[VAR_MOUSE_X_POS] = mouseX;
@@ -1761,13 +1855,7 @@ uint16 executePlayerInput(void) {
if (commandVar2 != objIdx) {
if (objIdx != -1) {
- char command[256];
-
- strcpy(command, commandBuffer);
- strcat(command, " ");
- strcat(command, objectTable[objIdx].name);
-
- renderer->setCommand(command);
+ renderer->setCommand(commandBuffer + " " + objectTable[objIdx].name);
} else {
isDrawCommandEnabled = 1;
}
@@ -1858,8 +1946,8 @@ uint16 executePlayerInput(void) {
var_2 = 0;
}
- if (inputVar1 && allowPlayerInput) { // use keyboard
- inputVar1 = 0;
+ if (egoMovedWithKeyboard && allowPlayerInput) { // use keyboard
+ egoMovedWithKeyboard = false;
switch (globalVars[VAR_MOUSE_X_MODE]) {
case 1:
@@ -1891,8 +1979,8 @@ uint16 executePlayerInput(void) {
globalVars[VAR_MOUSE_X_POS] = mouseX;
globalVars[VAR_MOUSE_Y_POS] = mouseY;
} else {
- if (inputVar2) {
- if (inputVar2 == 2) {
+ if (xMoveKeyb) {
+ if (xMoveKeyb == kKeybMoveLeft) {
globalVars[VAR_MOUSE_X_POS] = 1;
} else {
globalVars[VAR_MOUSE_X_POS] = 320;
@@ -1901,8 +1989,8 @@ uint16 executePlayerInput(void) {
globalVars[VAR_MOUSE_X_POS] = mouseX;
}
- if (inputVar3) {
- if (inputVar3 == 2) {
+ if (yMoveKeyb) {
+ if (yMoveKeyb == kKeybMoveUp) {
globalVars[VAR_MOUSE_Y_POS] = 1;
} else {
globalVars[VAR_MOUSE_Y_POS] = 200;
@@ -1943,6 +2031,14 @@ uint16 executePlayerInput(void) {
}
}
+ // Update Operation Stealth specific global variables.
+ // This fixes swimming at the bottom of the ocean after
+ // having been thrown into it with the girl.
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ globalVars[251] = globalVars[VAR_MOUSE_X_POS];
+ globalVars[252] = globalVars[VAR_MOUSE_Y_POS];
+ }
+
return var_5E;
}
@@ -1984,9 +2080,22 @@ void drawSprite(Common::List<overlay>::iterator it, const byte *spritePtr, const
void removeMessages() {
Common::List<overlay>::iterator it;
+ bool remove;
for (it = overlayList.begin(); it != overlayList.end(); ) {
- if (it->type == 2 || it->type == 3) {
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ // NOTE: These are really removeOverlay calls that have been deferred.
+ // In Operation Stealth's disassembly elements are removed from the
+ // overlay list right in the drawOverlays function (And actually in
+ // some other places too) and that's where incrementing a the overlay's
+ // last parameter by one if it's negative and testing it for positivity
+ // comes from too.
+ remove = it->type == 3 || (it->type == 2 && (it->color >= 0 || ++it->color >= 0));
+ } else { // Future Wars
+ remove = it->type == 2 || it->type == 3;
+ }
+
+ if (remove) {
it = overlayList.erase(it);
} else {
++it;
@@ -2069,7 +2178,6 @@ void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 par
tmp.color = param5;
overlayList.push_back(tmp);
- waitForPlayerClick = 1;
}
Common::List<SeqListElement> seqList;
@@ -2328,9 +2436,9 @@ void processSeqListElement(SeqListElement &element) {
}
computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, x2, y2);
} else {
- if (inputVar0 && allowPlayerInput) {
+ if (xMoveKeyb && allowPlayerInput) {
int16 adder = param1 + 1;
- if (inputVar0 != 1) {
+ if (xMoveKeyb != kKeybMoveRight) {
adder = -adder;
}
// FIXME: In Operation Stealth's disassembly global variable 251 is used here
@@ -2339,9 +2447,9 @@ void processSeqListElement(SeqListElement &element) {
globalVars[VAR_MOUSE_X_POS] = globalVars[251] = ptr1[4] + x + adder;
}
- if (inputVar1 && allowPlayerInput) {
+ if (yMoveKeyb && allowPlayerInput) {
int16 adder = param2 + 1;
- if (inputVar1 != 1) {
+ if (yMoveKeyb != kKeybMoveDown) {
adder = -adder;
}
// TODO: Name currently unnamed global variable 252
diff --git a/engines/cine/various.h b/engines/cine/various.h
index d87679ca08..b841908c65 100644
--- a/engines/cine/various.h
+++ b/engines/cine/various.h
@@ -33,6 +33,9 @@
namespace Cine {
+// Maximum size of the command buffer including the trailing zero
+#define kMaxCommandBufferSize 80
+
void initLanguage(Common::Language lang);
int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, bool recheckValue = false);
@@ -85,7 +88,7 @@ extern byte _danKeysPressed;
extern int16 playerCommand;
-extern char commandBuffer[80];
+extern Common::String commandBuffer;
extern char currentPrcName[20];
extern char currentRelName[20];
@@ -117,8 +120,6 @@ void mainLoopSub6(void);
void checkForPendingDataLoad(void);
-extern uint16 exitEngine;
-
void hideMouse(void);
void removeExtention(char *dest, const char *source);
@@ -129,8 +130,8 @@ struct SelectedObjStruct {
};
#define NUM_MAX_ZONE 16
-extern uint16 zoneData[NUM_MAX_ZONE];
-extern uint16 zoneQuery[NUM_MAX_ZONE];
+extern Common::Array<uint16> zoneData;
+extern Common::Array<uint16> zoneQuery;
void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 param5);
@@ -145,6 +146,7 @@ void processSeqList(void);
void resetGfxEntityEntry(uint16 objIdx);
bool makeTextEntryMenu(const char *caption, char *string, int strLen, int y);
+void moveUsingKeyboard(int x, int y);
} // End of namespace Cine