[Scummvm-git-logs] scummvm master -> 580a7e6568839a6505b0d8a10244a814dd16c03b
sev-
sev at scummvm.org
Fri Jul 24 22:33:48 UTC 2020
This automated email contains information about 3 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
2ca52fc007 CINE: Add support for Operation Stealth.
9c68b62eb9 CINE: Fix clang compilation and DeepCode warning.
580a7e6568 CINE: Fix formatting, string copy and signedness.
Commit: 2ca52fc00790a3803110d866771bd2aef61c5914
https://github.com/scummvm/scummvm/commit/2ca52fc00790a3803110d866771bd2aef61c5914
Author: Kari Salminen (kari.salminen at gmail.com)
Date: 2020-07-25T00:33:42+02:00
Commit Message:
CINE: Add support for Operation Stealth.
Add support for Operation Stealth PC 16 and 256 color versions with
AdLib and Roland MT-32 sound. Add support for 20 extended savegames
(Thumbnails, playtime etc) for both Future Wars and Operation Stealth
(20 because it fits on screen using the original save/load interface).
Details:
- Add versioning to Future Wars and Operation Stealth savegames.
- Add fade in effect to both Future Wars and Operation Stealth.
- Add mouse wheel support and keyboard support to moving in menus.
- Map middle mouse button to pressing both left and right buttons.
- Make interface more responsive (See manageEvents() and drawFrame()).
- Amiga versions should be completable but sound may or may not work.
- Atari ST versions completely untested.
Game options currently supported:
- Using original save/load interface
- Using transparent dialog boxes in 16 color scenes (Also for PC)
Console commands currently supported:
- labyrinthCheat (For cheating in Operation Stealth's labyrinths)
- disableLabyrinthCheat (Disabling labyrinth cheat)
- disableHacks (Disabling hacks, useful for testing)
- enableHacks (Enabling hacks, useful for testing. On by default)
Changed paths:
engines/cine/anim.cpp
engines/cine/anim.h
engines/cine/bg.cpp
engines/cine/bg.h
engines/cine/bg_list.cpp
engines/cine/bg_list.h
engines/cine/cine.cpp
engines/cine/cine.h
engines/cine/console.cpp
engines/cine/console.h
engines/cine/detection.cpp
engines/cine/detection_tables.h
engines/cine/gfx.cpp
engines/cine/gfx.h
engines/cine/main_loop.cpp
engines/cine/main_loop.h
engines/cine/msg.cpp
engines/cine/msg.h
engines/cine/object.cpp
engines/cine/object.h
engines/cine/pal.cpp
engines/cine/pal.h
engines/cine/part.cpp
engines/cine/prc.cpp
engines/cine/saveload.cpp
engines/cine/saveload.h
engines/cine/script.h
engines/cine/script_fw.cpp
engines/cine/script_os.cpp
engines/cine/sound.cpp
engines/cine/sound.h
engines/cine/texte.cpp
engines/cine/texte.h
engines/cine/various.cpp
engines/cine/various.h
diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp
index 6ecf07fe15..bcef2ddc0d 100644
--- a/engines/cine/anim.cpp
+++ b/engines/cine/anim.cpp
@@ -27,6 +27,7 @@
#include "common/endian.h"
#include "common/memstream.h"
#include "common/textconsole.h"
+#include "audio/mididrv.h"
#include "cine/cine.h"
#include "cine/anim.h"
@@ -39,14 +40,23 @@ namespace Cine {
struct AnimHeader2Struct {
uint32 field_0;
- uint16 width;
- uint16 height;
- uint16 type;
+ int16 width;
+ int16 height;
+ int16 type;
uint16 field_A;
uint16 field_C;
uint16 field_E;
};
+static const AnimDataMapping resNameMapping[] = {
+ {"PLONGEON", "PLONG110"},
+ {"PNEUMATI", "PNEUMA05"},
+ {"RELAITRE", "RIDEAU__"},
+ {"TIRROIR_", "PORTE___"},
+ {"VERREDO_", "EAU_____"},
+ {"ZODIAC__", "TAXIGO__"}
+};
+
static const AnimDataEntry transparencyData[] = {
{"ALPHA", 0xF},
{"TITRE2", 0xF},
@@ -184,6 +194,7 @@ static const AnimDataEntry transparencyData[] = {
void convertMask(byte *dest, const byte *source, int16 width, int16 height);
void convert8BBP(byte *dest, const byte *source, int16 width, int16 height);
void convert8BBP2(byte *dest, byte *source, int16 width, int16 height);
+int loadSet(const char *resourceName, int16 idx, int16 frameIndex = -1);
AnimData::AnimData() : _width(0), _height(0), _bpp(0), _var1(0), _data(NULL),
_mask(NULL), _fileIdx(-1), _frameIdx(-1), _realWidth(0), _size(0) {
@@ -483,19 +494,17 @@ void convert4BBP(byte *dest, const byte *source, int16 width, int16 height) {
* @param readS Input stream open for reading
*/
void loadAnimHeader(AnimHeaderStruct &animHeader, Common::SeekableReadStream &readS) {
- animHeader.field_0 = readS.readByte();
- animHeader.field_1 = readS.readByte();
- animHeader.field_2 = readS.readByte();
- animHeader.field_3 = readS.readByte();
- animHeader.frameWidth = readS.readUint16BE();
- animHeader.frameHeight = readS.readUint16BE();
+ readS.read(animHeader.idString, sizeof(animHeader.idString));
+ animHeader.idString[sizeof(animHeader.idString) - 1] = 0;
+ animHeader.frameWidth = readS.readSint16BE();
+ animHeader.frameHeight = readS.readSint16BE();
animHeader.field_8 = readS.readByte();
animHeader.field_9 = readS.readByte();
animHeader.field_A = readS.readByte();
animHeader.field_B = readS.readByte();
animHeader.field_C = readS.readByte();
animHeader.field_D = readS.readByte();
- animHeader.numFrames = readS.readUint16BE();
+ animHeader.numFrames = readS.readSint16BE();
animHeader.field_10 = readS.readByte();
animHeader.field_11 = readS.readByte();
animHeader.field_12 = readS.readByte();
@@ -559,9 +568,9 @@ int loadMsk(const char *resourceName, int16 idx, int16 frameIndex) {
byte *ptr;
AnimHeaderStruct animHeader;
- Common::MemoryReadStream readS(dataPtr, 0x16);
+ Common::MemoryReadStream readS(dataPtr, ANIM_HEADER_SIZE);
loadAnimHeader(animHeader, readS);
- ptr = dataPtr + 0x16;
+ ptr = dataPtr + ANIM_HEADER_SIZE;
int16 startFrame = 0;
int16 endFrame = animHeader.numFrames;
@@ -602,9 +611,18 @@ int loadAni(const char *resourceName, int16 idx, int16 frameIndex) {
byte transparentColor;
AnimHeaderStruct animHeader;
- Common::MemoryReadStream readS(dataPtr, 0x16);
+ Common::MemoryReadStream readS(dataPtr, ANIM_HEADER_SIZE);
loadAnimHeader(animHeader, readS);
- ptr = dataPtr + 0x16;
+ ptr = dataPtr + ANIM_HEADER_SIZE;
+
+ // HACK: If the underlying resource is really a ".SET" then use that loading routine.
+ // Try to detect door animations in SP11_01.ANI and SP11_02.ANI that are .SET files.
+ // These are on Dr. Why's island the opening and closing doors.
+ if (hacksEnabled && scumm_stricmp(animHeader.idString, "SET") == 0 &&
+ idx >= 161 && idx <= 164 && animHeader.frameHeight == 0) {
+ free(dataPtr);
+ return loadSet(resourceName, idx, frameIndex);
+ }
int16 startFrame = 0;
int16 endFrame = animHeader.numFrames;
@@ -621,7 +639,7 @@ int loadAni(const char *resourceName, int16 idx, int16 frameIndex) {
// 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) {
+ if (hacksEnabled && scumm_stricmp(resourceName, "TITRE.ANI") == 0 && animHeader.frameHeight == 37) {
transparentColor = 0xF;
}
@@ -704,7 +722,7 @@ void convert8BBP2(byte *dest, byte *source, int16 width, int16 height) {
* @param frameIndex frame of animation to load (-1 for all frames)
* @return The number of the animDataTable entry after the loaded image set (-1 if error)
*/
-int loadSet(const char *resourceName, int16 idx, int16 frameIndex = -1) {
+int loadSet(const char *resourceName, int16 idx, int16 frameIndex) {
AnimHeader2Struct header2;
uint16 numSpriteInAnim;
int16 foundFileIdx = findFileInBundle(resourceName);
@@ -784,7 +802,7 @@ int loadSeq(const char *resourceName, int16 idx) {
byte *dataPtr = readBundleFile(foundFileIdx);
int entry = idx < 0 ? emptyAnimSpace() : idx;
- g_cine->_animDataTable[entry].load(dataPtr + 0x16, ANIM_RAW, g_cine->_partBuffer[foundFileIdx].unpackedSize - 0x16, 1, foundFileIdx, 0, currentPartName);
+ g_cine->_animDataTable[entry].load(dataPtr + ANIM_HEADER_SIZE, ANIM_RAW, g_cine->_partBuffer[foundFileIdx].unpackedSize - 0x16, 1, foundFileIdx, 0, currentPartName);
free(dataPtr);
return entry + 1;
}
@@ -798,8 +816,34 @@ int loadSeq(const char *resourceName, int16 idx) {
*/
int loadResource(const char *resourceName, int16 idx, int16 frameIndex) {
int result = -1; // Return an error by default
+
+ if (g_cine->getGameType() == Cine::GType_OS &&
+ g_cine->getPlatform() == Common::kPlatformDOS &&
+ g_sound->musicType() != MT_MT32 &&
+ (strstr(resourceName, ".SPL") || strstr(resourceName, ".H32")))
+ {
+ char base[20];
+ removeExtention(base, resourceName);
+
+ for (int i = 0; i < ARRAYSIZE(resNameMapping); i++) {
+ if (scumm_stricmp(base, resNameMapping[i].from) == 0) {
+ strncpy(base, resNameMapping[i].to, sizeof(base));
+ break;
+ }
+ }
+
+ const char* ext = (g_sound->musicType() == MT_ADLIB) ? ".ADL" : ".HP";
+ strcat(base, ext);
+ return loadResource(base, idx, frameIndex);
+ }
+
+ bool preferSeq = (g_cine->getGameType() == Cine::GType_OS && g_sound->musicType() == MT_MT32);
+
if (strstr(resourceName, ".SPL")) {
- result = loadSpl(resourceName, idx);
+ if (preferSeq)
+ result = loadSeq(resourceName, idx);
+ else
+ result = loadSpl(resourceName, idx);
} else if (strstr(resourceName, ".MSK")) {
result = loadMsk(resourceName, idx, frameIndex);
} else if (strstr(resourceName, ".ANI")) {
@@ -811,9 +855,16 @@ int loadResource(const char *resourceName, int16 idx, int16 frameIndex) {
} else if (strstr(resourceName, ".SEQ")) {
result = loadSeq(resourceName, idx);
} else if (strstr(resourceName, ".H32")) {
- warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
+ if (preferSeq)
+ result = loadSeq(resourceName, idx);
+ else
+ result = loadSpl(resourceName, idx);
+ } else if (strstr(resourceName, ".HP")) {
+ result = loadSpl(resourceName, idx);
+ } else if (strstr(resourceName, ".ADL")) {
+ result = loadSpl(resourceName, idx);
} else if (strstr(resourceName, ".AMI")) {
- warning("loadResource: Ignoring file '%s' (Load at %d)", resourceName, idx);
+ result = loadAni(resourceName, idx, frameIndex);
} else if (strstr(resourceName, "ECHEC")) { // Echec (French) means failure
g_cine->quitGame();
} else {
diff --git a/engines/cine/anim.h b/engines/cine/anim.h
index 7b4daafa88..d4d07133e6 100644
--- a/engines/cine/anim.h
+++ b/engines/cine/anim.h
@@ -29,20 +29,20 @@
namespace Cine {
+// Size of AnimHeaderStruct payload in bytes
+#define ANIM_HEADER_SIZE 0x16
+
struct AnimHeaderStruct {
- byte field_0;
- byte field_1;
- byte field_2;
- byte field_3;
- uint16 frameWidth;
- uint16 frameHeight;
+ char idString[4];
+ int16 frameWidth;
+ int16 frameHeight;
byte field_8;
byte field_9;
byte field_A;
byte field_B;
byte field_C;
byte field_D;
- uint16 numFrames;
+ int16 numFrames;
byte field_10;
byte field_11;
byte field_12;
@@ -55,6 +55,11 @@ struct AnimDataEntry {
byte color;
};
+struct AnimDataMapping {
+ char from[9];
+ char to[9];
+};
+
#define ANIM_RAW 0 // memcpy
#define ANIM_MASK 1 // convertMask
#define ANIM_SPRITE 2 // gfxConvertSpriteToRaw
@@ -84,6 +89,8 @@ public:
AnimData &operator=(const AnimData &src);
+ int size() { return _size; }
+ int16 frameIndex() { return _frameIdx; }
const byte *data() const { return _data; } ///< Image data
const byte *mask() const { return _mask; } ///< Image mask (may be NULL)
byte getColor(int x, int y);
diff --git a/engines/cine/bg.cpp b/engines/cine/bg.cpp
index ce808e0f6a..26e076ec04 100644
--- a/engines/cine/bg.cpp
+++ b/engines/cine/bg.cpp
@@ -33,18 +33,15 @@ namespace Cine {
uint16 bgVar0;
byte *additionalBgTable[9];
-int16 currentAdditionalBgIdx = 0, currentAdditionalBgIdx2 = 0;
-byte loadCtFW(const char *ctName) {
+int16 loadCtFW(const char *ctName) {
debugC(1, kCineDebugCollision, "loadCtFW(\"%s\")", ctName);
byte *ptr, *dataPtr;
int16 foundFileIdx = findFileInBundle(ctName);
- if (foundFileIdx == -1) {
+ if (foundFileIdx < 0) {
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;
+ return -1;
}
if (currentCtName != ctName)
@@ -62,16 +59,14 @@ byte loadCtFW(const char *ctName) {
return 0;
}
-byte loadCtOS(const char *ctName) {
+int16 loadCtOS(const char *ctName) {
debugC(1, kCineDebugCollision, "loadCtOS(\"%s\")", ctName);
byte *ptr, *dataPtr;
int16 foundFileIdx = findFileInBundle(ctName);
- if (foundFileIdx == -1) {
+ if (foundFileIdx < 0) {
warning("loadCtOS: 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;
+ return -1;
}
if (currentCtName != ctName)
@@ -85,7 +80,6 @@ byte loadCtOS(const char *ctName) {
if (bpp == 8) {
renderer->loadCt256(ptr, ctName);
} else {
- gfxConvertSpriteToRaw(collisionPage, ptr + 32, 160, 200);
renderer->loadCt16(ptr, ctName);
}
@@ -93,10 +87,15 @@ byte loadCtOS(const char *ctName) {
return 0;
}
-byte loadBg(const char *bgName) {
+int16 loadBg(const char *bgName) {
byte *ptr, *dataPtr;
- byte fileIdx = findFileInBundle(bgName);
+ int16 fileIdx = findFileInBundle(bgName);
+ if (fileIdx < 0) {
+ warning("loadBg(\"%s\"): Could not find background in file bundle.", bgName);
+ return -1;
+ }
+ checkDataDisk(-1);
ptr = dataPtr = readBundleFile(fileIdx);
uint16 bpp = READ_BE_UINT16(ptr);
@@ -112,23 +111,8 @@ byte loadBg(const char *bgName) {
renderer->loadBg16(ptr, bgName);
}
free(dataPtr);
- return 0;
-}
-
-void addBackground(const char *bgName, uint16 bgIdx) {
- byte *ptr, *dataPtr;
-
- byte fileIdx = findFileInBundle(bgName);
- ptr = dataPtr = readBundleFile(fileIdx);
- uint16 bpp = READ_BE_UINT16(ptr); ptr += 2;
-
- if (bpp == 8) {
- renderer->loadBg256(ptr, bgName, bgIdx);
- } else {
- renderer->loadBg16(ptr, bgName, bgIdx);
- }
- free(dataPtr);
+ return 0;
}
} // End of namespace Cine
diff --git a/engines/cine/bg.h b/engines/cine/bg.h
index ac884463cb..c35654c8fb 100644
--- a/engines/cine/bg.h
+++ b/engines/cine/bg.h
@@ -24,17 +24,12 @@
#define CINE_BG_H
namespace Cine {
-byte loadBg(const char *bgName);
-byte loadCtFW(const char *bgName);
-byte loadCtOS(const char *bgName);
-
-void addBackground(const char *bgName, uint16 bgIdx);
+int16 loadBg(const char *bgName);
+int16 loadCtFW(const char *bgName);
+int16 loadCtOS(const char *bgName);
extern uint16 bgVar0;
-extern int16 currentAdditionalBgIdx;
-extern int16 currentAdditionalBgIdx2;
-
} // End of namespace Cine
#endif
diff --git a/engines/cine/bg_list.cpp b/engines/cine/bg_list.cpp
index d2f05a3fbd..304781012d 100644
--- a/engines/cine/bg_list.cpp
+++ b/engines/cine/bg_list.cpp
@@ -54,6 +54,17 @@ void addSpriteFilledToBGList(int16 objIdx) {
renderer->incrustMask(g_cine->_bgIncrustList.back());
}
+void removeBgIncrustsWithBgIdx(int16 bgIdx) {
+ Common::List<BGIncrust>::iterator it;
+ for (it = g_cine->_bgIncrustList.begin(); it != g_cine->_bgIncrustList.end();) {
+ if (it->bgIdx == bgIdx) {
+ it = g_cine->_bgIncrustList.erase(it);
+ } else {
+ ++it;
+ }
+ }
+}
+
/**
* Add new element to incrust list
* @param objIdx Element description
@@ -68,7 +79,8 @@ void createBgIncrustListElement(int16 objIdx, int16 param) {
tmp.x = g_cine->_objectTable[objIdx].x;
tmp.y = g_cine->_objectTable[objIdx].y;
tmp.frame = g_cine->_objectTable[objIdx].frame;
- tmp.part = g_cine->_objectTable[objIdx].part;
+ tmp.part = g_cine->_objectTable[objIdx].part & 0x0f;
+ tmp.bgIdx = renderer->currentBg();
g_cine->_bgIncrustList.push_back(tmp);
}
@@ -84,7 +96,7 @@ void resetBgIncrustList() {
* Restore incrust list from savefile
* @param fHandle Savefile open for reading
*/
-void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) {
+void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle, bool hasBgIdx) {
BGIncrust tmp;
int size = fHandle.readSint16BE();
@@ -99,6 +111,7 @@ void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle) {
tmp.y = fHandle.readUint16BE();
tmp.frame = fHandle.readUint16BE();
tmp.part = fHandle.readUint16BE();
+ tmp.bgIdx = (hasBgIdx ? fHandle.readUint16BE() : 0);
g_cine->_bgIncrustList.push_back(tmp);
diff --git a/engines/cine/bg_list.h b/engines/cine/bg_list.h
index 3cfc33e274..0a0366d5df 100644
--- a/engines/cine/bg_list.h
+++ b/engines/cine/bg_list.h
@@ -37,16 +37,18 @@ struct BGIncrust {
int16 y;
int16 frame;
int16 part;
+ int16 bgIdx;
};
extern uint32 var8;
void addToBGList(int16 objIdx);
void addSpriteFilledToBGList(int16 idx);
+void removeBgIncrustsWithBgIdx(int16 bgIdx);
void createBgIncrustListElement(int16 objIdx, int16 param);
void resetBgIncrustList();
-void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle);
+void loadBgIncrustFromSave(Common::SeekableReadStream &fHandle, bool hasBgIdx = false);
} // End of namespace Cine
diff --git a/engines/cine/cine.cpp b/engines/cine/cine.cpp
index a84a6575b8..82e8038376 100644
--- a/engines/cine/cine.cpp
+++ b/engines/cine/cine.cpp
@@ -67,7 +67,7 @@ CineEngine::CineEngine(OSystem *syst, const CINEGameDescription *gameDesc)
}
_restartRequested = false;
_preLoad = false;
- _timerDelayMultiplier = 12;
+ setDefaultGameSpeed();
}
CineEngine::~CineEngine() {
@@ -124,7 +124,7 @@ Common::Error CineEngine::run() {
_restartRequested = false;
CursorMan.showMouse(true);
- mainLoop(1);
+ mainLoop(BOOT_SCRIPT_INDEX);
delete renderer;
delete[] collisionPage;
@@ -135,7 +135,7 @@ Common::Error CineEngine::run() {
return Common::kNoError;
}
-int CineEngine::getTimerDelay() const {
+uint32 CineEngine::getTimerDelay() const {
return (10923000 * _timerDelayMultiplier) / 1193180;
}
@@ -150,7 +150,12 @@ int CineEngine::modifyGameSpeed(int speedChange) {
return _timerDelayMultiplier;
}
+void CineEngine::setDefaultGameSpeed() {
+ _timerDelayMultiplier = 12;
+}
+
void CineEngine::initialize() {
+ setTotalPlayTime(0); // Reset total play time
_globalVars.reinit(NUM_MAX_VAR + 1);
// Initialize all savegames' descriptions to empty strings
@@ -172,7 +177,7 @@ void CineEngine::initialize() {
g_cine->_zoneQuery.resize(NUM_MAX_ZONE);
Common::fill(g_cine->_zoneQuery.begin(), g_cine->_zoneQuery.end(), 0);
- _timerDelayMultiplier = 12; // Set default speed
+ setDefaultGameSpeed();
setupOpcodes();
initLanguage(getLanguage());
@@ -184,6 +189,11 @@ void CineEngine::initialize() {
}
renderer->initialize();
+ forbidBgPalReload = 0;
+ reloadBgPalOnNextFlip = 0;
+ gfxFadeOutCompleted = 0;
+ gfxFadeInRequested = 0;
+ currentDisk = 1;
collisionPage = new byte[320 * 200];
memset(collisionPage, 0, 320 * 200);
@@ -213,6 +223,7 @@ void CineEngine::initialize() {
g_cine->_overlayList.clear();
g_cine->_messageTable.clear();
resetObjectTable();
+ g_cine->_seqList.clear();
if (getGameType() == Cine::GType_OS) {
disableSystemMenu = 1;
@@ -228,21 +239,32 @@ void CineEngine::initialize() {
}
var8 = 0;
-
- var2 = var3 = var4 = var5 = 0;
-
+ bgVar0 = 0;
+ var2 = var3 = var4 = lastType20OverlayBgIdx = 0;
musicIsPlaying = 0;
currentDatName[0] = 0;
+ _keyInputList.clear();
+
+ // Used for making sound effects work using Roland MT-32 and AdLib in
+ // Operation Stealth after loading a savegame. The sound effects are loaded
+ // in AUTO00.PRC using a combination of o2_loadAbs and o2_playSample(1, ...)
+ // before checking if _globalVars[255] == 0. In the original game AUTO00.PRC
+ // was run when starting or restarting the game and one could not load a savegame
+ // before passing the copy protection. Thus, we try to emulate that behaviour by
+ // running at least part of AUTO00.PRC before loading a savegame.
+ if (getGameType() == Cine::GType_OS && !(getFeatures() & GF_DEMO)) {
+ loadPrc(BOOT_PRC_NAME);
+ strcpy(currentPrcName, BOOT_PRC_NAME);
+ addScriptToGlobalScripts(BOOT_SCRIPT_INDEX);
+ runOnlyUntilCopyProtectionCheck = true;
+ executeGlobalScripts();
+ }
_preLoad = false;
if (ConfMan.hasKey("save_slot") && !_restartRequested) {
- char saveNameBuffer[256];
-
- sprintf(saveNameBuffer, "%s.%1d", _targetName.c_str(), ConfMan.getInt("save_slot"));
-
- bool res = makeLoad(saveNameBuffer);
+ Common::Error loadError = loadGameState(ConfMan.getInt("save_slot"));
- if (res)
+ if (loadError.getCode() == Common::kNoError)
_preLoad = true;
}
diff --git a/engines/cine/cine.h b/engines/cine/cine.h
index fdd6d39fd6..6bc04b3c5d 100644
--- a/engines/cine/cine.h
+++ b/engines/cine/cine.h
@@ -47,6 +47,7 @@
#include "cine/bg_list.h"
#include "cine/various.h"
#include "cine/console.h"
+#include "cine/sound.h"
//#define DUMP_SCRIPTS
@@ -95,7 +96,14 @@ enum CineGameFeatures {
struct CINEGameDescription;
struct SeqListElement;
-typedef Common::HashMap<Common::String, const char *> StringPtrHashMap;
+struct VolumeResource {
+ char name[10];
+ uint32 pNamesList;
+ int16 diskNum;
+ int32 sizeOfNamesList;
+};
+
+typedef Common::HashMap<Common::String, Common::Array<VolumeResource> > StringToVolumeResourceArrayHashMap;
class CineConsole;
@@ -116,6 +124,7 @@ public:
void syncSoundSettings() override;
+ bool mayHave256Colors() const;
int getGameType() const;
uint32 getFeatures() const;
Common::Language getLanguage() const;
@@ -125,7 +134,8 @@ public:
void makeSystemMenu();
int scummVMSaveLoadDialog(bool isSave);
int modifyGameSpeed(int speedChange);
- int getTimerDelay() const;
+ void setDefaultGameSpeed();
+ uint32 getTimerDelay() const;
Common::Error loadGameState(int slot) override;
Common::Error saveGameState(int slot, const Common::String &desc, bool isAutosave = false) override;
virtual Common::String getSaveStateName(int slot) const override;
@@ -137,8 +147,8 @@ public:
Common::RandomSource _rnd;
- Common::StringArray _volumeResourceFiles;
- StringPtrHashMap _volumeEntriesMap;
+ StringToVolumeResourceArrayHashMap _volumeEntriesMap;
+
TextHandler _textHandler;
bool _restartRequested;
@@ -147,14 +157,19 @@ private:
void initialize();
void showSplashScreen();
void resetEngine();
- bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat);
- bool loadTempSaveOS(Common::SeekableReadStream &in);
+ bool checkSaveHeaderData(const ChunkHeader& hdr);
+ bool loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat, uint32 version);
+ bool loadVersionedSaveFW(Common::SeekableReadStream &in);
+ bool loadVersionedSaveOS(Common::SeekableReadStream &in);
bool makeLoad(const Common::String &saveName);
+ void writeSaveHeader(Common::OutSaveFile &out, uint32 headerId);
void makeSaveFW(Common::OutSaveFile &out);
void makeSaveOS(Common::OutSaveFile &out);
- void makeSave(const Common::String &saveFileName);
+ void makeSave(const Common::String &saveFileName, uint32 playtime,
+ Common::String desc, bool isAutosave);
void mainLoop(int bootScriptIdx);
void readVolCnf();
+ Common::String getTargetSaveStateName(Common::String target, int slot) const;
bool _preLoad;
int _timerDelayMultiplier;
@@ -182,17 +197,20 @@ public:
ScriptVars _globalVars;
RawScriptArray _scriptTable; ///< Table of script bytecode
- Common::Array<uint16> _zoneData;
+ Common::Array<int16> _zoneData;
Common::Array<uint16> _zoneQuery; ///< Only exists in Operation Stealth
Common::List<SeqListElement> _seqList;
Common::String _commandBuffer;
+ Common::Array<Common::KeyState> _keyInputList;
};
extern CineEngine *g_cine;
+extern Sound *g_sound;
#define BOOT_PRC_NAME "AUTO00.PRC"
+#define BOOT_SCRIPT_INDEX 1
#define COPY_PROT_FAIL_PRC_NAME "L201.ANI"
enum {
diff --git a/engines/cine/console.cpp b/engines/cine/console.cpp
index 4646bdf280..39264c31fb 100644
--- a/engines/cine/console.cpp
+++ b/engines/cine/console.cpp
@@ -26,12 +26,17 @@
namespace Cine {
bool labyrinthCheat;
+bool hacksEnabled;
CineConsole::CineConsole(CineEngine *vm) : GUI::Debugger(), _vm(vm) {
assert(_vm);
registerCmd("labyrinthCheat", WRAP_METHOD(CineConsole, Cmd_LabyrinthCheat));
+ registerCmd("disableLabyrinthCheat", WRAP_METHOD(CineConsole, Cmd_DisableLabyrinthCheat));
+ registerCmd("disableHacks", WRAP_METHOD(CineConsole, Cmd_DisableHacks));
+ registerCmd("enableHacks", WRAP_METHOD(CineConsole, Cmd_EnableHacks));
labyrinthCheat = false;
+ hacksEnabled = true;
}
CineConsole::~CineConsole() {
@@ -44,4 +49,19 @@ bool CineConsole::Cmd_LabyrinthCheat(int argc, const char **argv) {
return true;
}
+bool CineConsole::Cmd_DisableLabyrinthCheat(int argc, const char **argv) {
+ labyrinthCheat = false;
+ return true;
+}
+
+bool CineConsole::Cmd_DisableHacks(int argc, const char **argv) {
+ hacksEnabled = false;
+ return true;
+}
+
+bool CineConsole::Cmd_EnableHacks(int argc, const char **argv) {
+ hacksEnabled = true;
+ return true;
+}
+
} // End of namespace Cine
diff --git a/engines/cine/console.h b/engines/cine/console.h
index 48afec597e..478e3d5031 100644
--- a/engines/cine/console.h
+++ b/engines/cine/console.h
@@ -28,6 +28,7 @@
namespace Cine {
extern bool labyrinthCheat;
+extern bool hacksEnabled;
class CineEngine;
@@ -40,6 +41,9 @@ private:
CineEngine *_vm;
bool Cmd_LabyrinthCheat(int argc, const char **argv);
+ bool Cmd_DisableLabyrinthCheat(int argc, const char **argv);
+ bool Cmd_DisableHacks(int argc, const char **argv);
+ bool Cmd_EnableHacks(int argc, const char **argv);
};
} // End of namespace Cine
diff --git a/engines/cine/detection.cpp b/engines/cine/detection.cpp
index fdc6bf6cfc..79f797527f 100644
--- a/engines/cine/detection.cpp
+++ b/engines/cine/detection.cpp
@@ -27,12 +27,17 @@
#include "common/system.h"
#include "common/textconsole.h"
#include "common/translation.h"
+#include "common/util.h"
#include "cine/cine.h"
#include "cine/various.h"
namespace Cine {
+#define MAX_SAVEGAMES (ARRAYSIZE(Cine::currentSaveName))
+#define SAVEGAME_NAME_LEN (sizeof(Cine::currentSaveName[0]))
+#define SAVELIST_SIZE (MAX_SAVEGAMES * SAVEGAME_NAME_LEN)
+
struct CINEGameDescription {
ADGameDescription desc;
@@ -40,6 +45,7 @@ struct CINEGameDescription {
uint32 features;
};
+bool CineEngine::mayHave256Colors() const { return getGameType() == Cine::GType_OS && getPlatform() == Common::kPlatformDOS; }
int CineEngine::getGameType() const { return _gameDescription->gameType; }
uint32 CineEngine::getFeatures() const { return _gameDescription->features; }
Common::Language CineEngine::getLanguage() const { return _gameDescription->desc.language; }
@@ -65,6 +71,15 @@ static const ADExtraGuiOptionsMap optionsList[] = {
false
}
},
+ {
+ GAMEOPTION_TRANSPARENT_DIALOG_BOXES,
+ {
+ _s("Use transparent dialog boxes in 16 color scenes"),
+ _s("Use transparent dialog boxes in 16 color scenes even if the original game version did not support them"),
+ "transparentdialogboxes",
+ false
+ }
+ },
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
@@ -72,7 +87,7 @@ static const ADExtraGuiOptionsMap optionsList[] = {
class CineMetaEngine : public AdvancedMetaEngine {
public:
CineMetaEngine() : AdvancedMetaEngine(Cine::gameDescriptions, sizeof(Cine::CINEGameDescription), cineGames, optionsList) {
- _guiOptions = GUIO2(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVELOAD);
+ _guiOptions = GUIO3(GUIO_NOSPEECH, GAMEOPTION_ORIGINAL_SAVELOAD, GAMEOPTION_TRANSPARENT_DIALOG_BOXES);
}
const char *getEngineId() const override {
@@ -96,13 +111,20 @@ public:
SaveStateList listSaves(const char *target) const override;
int getMaximumSaveSlot() const override;
void removeSaveState(const char *target, int slot) const override;
+ const char *getSavegameFile(int saveGameIdx, const char *target = nullptr) const override;
+ SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
};
bool CineMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves) ||
(f == kSupportsLoadingDuringStartup) ||
- (f == kSupportsDeleteSave);
+ (f == kSupportsDeleteSave) ||
+ (f == kSavesSupportMetaInfo) ||
+ (f == kSavesSupportThumbnail) ||
+ (f == kSavesSupportCreationDate) ||
+ (f == kSavesSupportPlayTime) ||
+ (f == kSavesUseExtendedFormat);
}
bool Cine::CineEngine::hasFeature(EngineFeature f) const {
@@ -124,51 +146,159 @@ SaveStateList CineMetaEngine::listSaves(const char *target) const {
Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
SaveStateList saveList;
- Common::String pattern = target;
- pattern += ".#";
- Common::StringArray filenames = saveFileMan->listSavefiles(pattern);
+ Common::String pattern;
+
Common::StringArray::const_iterator file;
Common::String filename = target;
filename += ".dir";
Common::InSaveFile *in = saveFileMan->openForLoading(filename);
+ bool foundAutosave = false;
if (in) {
- typedef char CommandeType[20];
- CommandeType saveNames[10];
+ typedef char CommandeType[SAVEGAME_NAME_LEN];
+ CommandeType saveNames[MAX_SAVEGAMES];
// Initialize all savegames' descriptions to empty strings
// so that if the savegames' descriptions can only be partially read from file
// then the missing ones are correctly set to empty strings.
memset(saveNames, 0, sizeof(saveNames));
- in->read(saveNames, 10 * 20);
+ in->read(saveNames, SAVELIST_SIZE);
CommandeType saveDesc;
- for (file = filenames.begin(); file != filenames.end(); ++file) {
- // Obtain the last digit of the filename, since they correspond to the save slot
- int slotNum = atoi(file->c_str() + file->size() - 1);
-
- // Copy the savegame description making sure it ends with a trailing zero
- strncpy(saveDesc, saveNames[slotNum], 20);
- saveDesc[sizeof(CommandeType) - 1] = 0;
+ pattern = target;
+ pattern += ".#*";
+ Common::StringArray filenames = saveFileMan->listSavefiles(pattern);
- saveList.push_back(SaveStateDescriptor(slotNum, saveDesc));
+ for (file = filenames.begin(); file != filenames.end(); ++file) {
+ // Obtain the extension part of the filename, since it corresponds to the save slot number
+ Common::String ext = Common::lastPathComponent(*file, '.');
+ int slotNum = (int)ext.asUint64();
+
+ if (ext.equals(Common::String::format("%d", slotNum)) &&
+ slotNum >= 0 && slotNum < MAX_SAVEGAMES) {
+ // Copy the savegame description making sure it ends with a trailing zero
+ strncpy(saveDesc, saveNames[slotNum], SAVEGAME_NAME_LEN);
+ saveDesc[sizeof(CommandeType) - 1] = 0;
+
+ SaveStateDescriptor saveStateDesc(slotNum, saveDesc);
+ saveStateDesc.setAutosave(slotNum == getAutosaveSlot());
+ saveStateDesc.setWriteProtectedFlag(saveStateDesc.isAutosave());
+
+ if (saveStateDesc.getDescription().empty()) {
+ if (saveStateDesc.isAutosave()) {
+ saveStateDesc.setDescription(_("Unnamed autosave"));
+ } else {
+ saveStateDesc.setDescription(_("Unnamed savegame"));
+ }
+ }
+
+ if (saveStateDesc.isAutosave()) {
+ foundAutosave = true;
+ }
+
+ saveList.push_back(saveStateDesc);
+ }
}
}
delete in;
+ // No saving on empty autosave slot
+ if (!foundAutosave) {
+ SaveStateDescriptor desc;
+ desc.setDescription(_("Empty autosave"));
+ desc.setSaveSlot(getAutosaveSlot());
+ desc.setWriteProtectedFlag(true);
+ saveList.push_back(desc);
+ }
+
// Sort saves based on slot number.
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
return saveList;
}
-int CineMetaEngine::getMaximumSaveSlot() const { return 9; }
+int CineMetaEngine::getMaximumSaveSlot() const { return MAX_SAVEGAMES - 1; }
+
+const char *CineMetaEngine::getSavegameFile(int saveGameIdx, const char *target) const {
+ static char buffer[200];
+
+ snprintf(buffer, sizeof(buffer), "%s.%d", target == nullptr ? getEngineId() : target, saveGameIdx);
+
+ return buffer;
+}
+
+SaveStateDescriptor CineMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ if (slot < 0 || slot > getMaximumSaveSlot()) {
+ // HACK: Try to make SaveLoadChooserGrid::open() not use save slot
+ // numbers over the maximum save slot number for "New save".
+ SaveStateDescriptor desc;
+ desc.setWriteProtectedFlag(true);
+ return desc;
+ }
+
+ Common::ScopedPtr<Common::InSaveFile> f(g_system->getSavefileManager()->openForLoading(
+ getSavegameFile(slot, target)));
+
+ if (f) {
+ // Create the return descriptor
+ SaveStateDescriptor desc;
+
+ ExtendedSavegameHeader header;
+ if (readSavegameHeader(f.get(), &header, false)) {
+ parseSavegameHeader(&header, &desc);
+ desc.setThumbnail(header.thumbnail);
+ } else {
+ // Load savegame descriptions from index file
+ typedef char CommandeType[SAVEGAME_NAME_LEN];
+ CommandeType saveNames[MAX_SAVEGAMES];
+ memset(saveNames, 0, sizeof(saveNames));
+
+ Common::InSaveFile *in;
+ in = g_system->getSavefileManager()->openForLoading(Common::String::format("%s.dir", target));
+
+ if (in) {
+ in->read(saveNames, SAVELIST_SIZE);
+ delete in;
+ }
+
+ saveNames[slot][SAVEGAME_NAME_LEN - 1] = 0;
+ Common::String saveNameStr((const char *)saveNames[slot]);
+ desc.setDescription(saveNameStr);
+ }
+
+ if (desc.getDescription().empty()) {
+ desc.setDescription(_("Unnamed savegame"));
+ }
+
+ desc.setSaveSlot(slot);
+ desc.setAutosave(slot == getAutosaveSlot());
+ desc.setWriteProtectedFlag(desc.isAutosave());
+
+ return desc;
+ }
+
+ // No saving on empty autosave slot
+ if (slot == getAutosaveSlot()) {
+ SaveStateDescriptor desc;
+ desc.setDescription(_("Empty autosave"));
+ desc.setSaveSlot(slot);
+ desc.setAutosave(true);
+ desc.setWriteProtectedFlag(true);
+ return desc;
+ }
+
+ return SaveStateDescriptor();
+}
void CineMetaEngine::removeSaveState(const char *target, int slot) const {
+ if (slot < 0 || slot >= MAX_SAVEGAMES) {
+ return;
+ }
+
// Load savegame descriptions from index file
- typedef char CommandeType[20];
- CommandeType saveNames[10];
+ typedef char CommandeType[SAVEGAME_NAME_LEN];
+ CommandeType saveNames[MAX_SAVEGAMES];
// Initialize all savegames' descriptions to empty strings
// so that if the savegames' descriptions can only be partially read from file
@@ -181,13 +311,13 @@ void CineMetaEngine::removeSaveState(const char *target, int slot) const {
if (!in)
return;
- in->read(saveNames, 10 * 20);
+ in->read(saveNames, SAVELIST_SIZE);
delete in;
// Set description for selected slot
- char slotName[20];
+ char slotName[SAVEGAME_NAME_LEN];
slotName[0] = 0;
- Common::strlcpy(saveNames[slot], slotName, 20);
+ Common::strlcpy(saveNames[slot], slotName, SAVEGAME_NAME_LEN);
// Update savegame descriptions
Common::String indexFile = Common::String::format("%s.dir", target);
@@ -197,12 +327,11 @@ void CineMetaEngine::removeSaveState(const char *target, int slot) const {
return;
}
- out->write(saveNames, 10 * 20);
+ out->write(saveNames, SAVELIST_SIZE);
delete out;
// Delete save file
- char saveFileName[256];
- sprintf(saveFileName, "%s.%1d", target, slot);
+ const char * saveFileName = getSavegameFile(slot, target);
g_system->getSavefileManager()->removeSavefile(saveFileName);
}
@@ -222,11 +351,15 @@ Common::Error CineEngine::loadGameState(int slot) {
}
Common::Error CineEngine::saveGameState(int slot, const Common::String &desc, bool isAutosave) {
+ if (slot < 0 || slot >= MAX_SAVEGAMES) {
+ return Common::kCreatingFileFailed;
+ }
+
// Load savegame descriptions from index file
loadSaveDirectory();
// Set description for selected slot making sure it ends with a trailing zero
- strncpy(currentSaveName[slot], desc.c_str(), 20);
+ strncpy(currentSaveName[slot], desc.c_str(), sizeof(CommandeType));
currentSaveName[slot][sizeof(CommandeType) - 1] = 0;
// Update savegame descriptions
@@ -238,11 +371,11 @@ Common::Error CineEngine::saveGameState(int slot, const Common::String &desc, bo
return Common::kUnknownError;
}
- fHandle->write(currentSaveName, 10 * 20);
+ fHandle->write(currentSaveName, SAVELIST_SIZE);
delete fHandle;
// Save game
- makeSave(getSaveStateName(slot));
+ makeSave(getSaveStateName(slot), getTotalPlayTime() / 1000, desc, isAutosave);
checkDataDisk(-1);
@@ -250,7 +383,7 @@ Common::Error CineEngine::saveGameState(int slot, const Common::String &desc, bo
}
Common::String CineEngine::getSaveStateName(int slot) const {
- return Common::String::format("%s.%1d", _targetName.c_str(), slot);
+ return getMetaEngine().getSavegameFile(slot, _targetName.c_str());
}
bool CineEngine::canLoadGameStateCurrently() {
diff --git a/engines/cine/detection_tables.h b/engines/cine/detection_tables.h
index c85aff86ce..7a6a52e27d 100644
--- a/engines/cine/detection_tables.h
+++ b/engines/cine/detection_tables.h
@@ -23,6 +23,7 @@
namespace Cine {
#define GAMEOPTION_ORIGINAL_SAVELOAD GUIO_GAMEOPTIONS1
+#define GAMEOPTION_TRANSPARENT_DIALOG_BOXES GUIO_GAMEOPTIONS2
static const CINEGameDescription gameDescriptions[] = {
{
diff --git a/engines/cine/gfx.cpp b/engines/cine/gfx.cpp
index 981d73e539..ca5bf5dac6 100644
--- a/engines/cine/gfx.cpp
+++ b/engines/cine/gfx.cpp
@@ -26,18 +26,24 @@
#include "cine/various.h"
#include "cine/pal.h"
+#include "common/config-manager.h"
#include "common/endian.h"
#include "common/events.h"
+#include "common/str.h"
#include "common/system.h"
#include "common/textconsole.h"
#include "graphics/cursorman.h"
+#include "graphics/primitives.h"
namespace Cine {
byte *collisionPage;
FWRenderer *renderer = NULL;
+#define DEFAULT_MESSAGE_BG 1
+#define DEFAULT_CMD_Y 185
+
// Constants related to kLowPalFormat
#define kLowPalBytesPerColor 2
#define kLowPalNumColors 16
@@ -103,12 +109,19 @@ static const byte cursorPalette[] = {
0xff, 0xff, 0xff, 0xff
};
+void plotPoint(int x, int y, int color, void *data) {
+ byte *output = (byte *)data;
+ if (x >= 0 && x < 320 && y >= 0 && y < 200) {
+ output[y * 320 + x] = (byte)color;
+ }
+}
+
/**
* Initialize renderer
*/
-FWRenderer::FWRenderer() : _background(NULL), _backupPal(), _cmd(""),
- _cmdY(0), _messageBg(0), _backBuffer(new byte[_screenSize]),
- _activePal(), _changePal(0), _showCollisionPage(false) {
+FWRenderer::FWRenderer() : _savedBackBuffers(), _background(NULL), _backupPal(), _cmd(""),
+ _messageBg(DEFAULT_MESSAGE_BG), _cmdY(DEFAULT_CMD_Y), _backBuffer(new byte[_screenSize]),
+ _activePal(), _changePal(0), _showCollisionPage(false), _fadeToBlackLastCalledMs(0) {
assert(_backBuffer);
@@ -116,6 +129,14 @@ FWRenderer::FWRenderer() : _background(NULL), _backupPal(), _cmd(""),
memset(_bgName, 0, sizeof(_bgName));
}
+void FWRenderer::removeSavedBackBuffers() {
+ for (int i = 0; i < ARRAYSIZE(_savedBackBuffers); i++) {
+ if (_savedBackBuffers[i]) {
+ delete[] _savedBackBuffers[i];
+ _savedBackBuffers[i] = nullptr;
+ }
+ }
+}
/**
* Destroy renderer
@@ -124,11 +145,13 @@ FWRenderer::~FWRenderer() {
delete[] _background;
delete[] _backBuffer;
+ removeSavedBackBuffers();
+
clearMenuStack();
}
bool FWRenderer::initialize() {
- _activePal = Palette(kLowPalFormat, kLowPalNumColors);
+ _backupPal = _activePal = Palette(kLowPalFormat, kLowPalNumColors);
return true;
}
@@ -144,14 +167,19 @@ void FWRenderer::clear() {
_activePal.clear();
memset(_backBuffer, 0, _screenSize);
+ removeSavedBackBuffers();
_cmd = "";
- _cmdY = 0;
- _messageBg = 0;
+ _cmdY = DEFAULT_CMD_Y;
+ _messageBg = DEFAULT_MESSAGE_BG;
_changePal = 0;
_showCollisionPage = false;
}
+const Cine::Palette& FWRenderer::getFadeInSourcePalette() {
+ return _backupPal;
+}
+
/**
* Draw 1bpp sprite using selected color
* @param obj Object info
@@ -263,11 +291,34 @@ void FWRenderer::drawCommand() {
void FWRenderer::drawString(const char *string, byte param) {
int width;
+ byte minBrightnessColorIndex = 4;
+
+ bool useEnsureContrast = true;
+ if (useEnsureContrast && g_cine->getGameType() == Cine::GType_OS) {
+ bool paletteChanged = _activePal.ensureContrast(minBrightnessColorIndex);
+ if (paletteChanged) {
+ clearBackBuffer();
+ setPalette();
+ }
+ }
+
+ // Both Future Wars and Operation Stealth 16 color PC versions do this
+ int y = 80;
+ if (param == 1) {
+ y = 20;
+ } else if (param == 2) {
+ y = 140;
+ }
- width = getStringWidth(string) + 10;
- width = width > 300 ? 300 : width;
+ width = getStringWidth(string);
+
+ if (width == 0) {
+ return;
+ }
- drawMessage(string, (320 - width) / 2, 80, width, 4);
+ width = MIN<int>(width + 20, 300);
+
+ drawMessage(string, (320 - width) / 2, y, width, minBrightnessColorIndex);
blit();
}
@@ -287,10 +338,8 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color
int line = 0, words = 0, cw = 0;
int space = 0, extraSpace = 0;
- const bool isAmiga = (g_cine->getPlatform() == Common::kPlatformAmiga);
-
if (color >= 0) {
- if (isAmiga)
+ if (useTransparentDialogBoxes())
drawTransparentBox(x, y, width, 4);
else
drawPlainBox(x, y, width, 4, color);
@@ -316,7 +365,7 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color
ty += 9;
if (color >= 0) {
- if (isAmiga)
+ if (useTransparentDialogBoxes())
drawTransparentBox(x, ty, width, 9);
else
drawPlainBox(x, ty, width, 9, color);
@@ -338,11 +387,11 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color
ty += 9;
if (color >= 0) {
- if (isAmiga)
+ if (useTransparentDialogBoxes())
drawTransparentBox(x, ty, width, 4);
else
drawPlainBox(x, ty, width, 4, color);
- drawDoubleBorder(x, y, width, ty - y + 4, isAmiga ? 18 : 2);
+ drawDoubleBorder(x, y, width, ty - y + 4, (useTransparentDialogBoxes() ? transparentDialogBoxStartColor() : 0) + 2);
}
}
@@ -357,19 +406,6 @@ void FWRenderer::drawMessage(const char *str, int x, int y, int width, int color
* @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) {
- // 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) {
width = ABS(width);
@@ -393,7 +429,19 @@ void FWRenderer::drawPlainBox(int x, int y, int width, int height, byte color) {
}
}
+bool FWRenderer::useTransparentDialogBoxes() {
+ return _activePal.colorCount() == 16 &&
+ ((g_cine->getPlatform() == Common::kPlatformAmiga) ||
+ ConfMan.getBool("transparentdialogboxes"));
+}
+
+byte FWRenderer::transparentDialogBoxStartColor() {
+ return 16;
+}
+
void FWRenderer::drawTransparentBox(int x, int y, int width, int height) {
+ byte startColor = transparentDialogBoxStartColor();
+
// Handle horizontally flipped boxes
if (width < 0) {
width = ABS(width);
@@ -415,8 +463,8 @@ void FWRenderer::drawTransparentBox(int x, int y, int width, int height) {
const int lineAdd = 320 - boxRect.width();
for (int i = 0; i < boxRect.height(); ++i) {
for (int j = 0; j < boxRect.width(); ++j, ++dest) {
- if (*dest < 16)
- *dest += 16;
+ if (*dest < startColor)
+ *dest += startColor;
}
dest += lineAdd;
}
@@ -506,17 +554,25 @@ int FWRenderer::undrawChar(char character, int x, int y) {
}
int FWRenderer::getStringWidth(const char *str) {
+ int padding = (g_cine->getGameType() == Cine::GType_OS) ? 2 : 1;
const char *p = str;
int width = 0;
+ int maxWidth = 0;
while (*p) {
- if (*p == ' ')
+ unsigned char currChar = (unsigned char)*p;
+ if (currChar == '|') {
+ maxWidth = MAX<int>(width, maxWidth);
+ width = 0;
+ } else if (currChar == ' ')
width += 5;
else
- width += g_cine->_textHandler.fontParamTable[(unsigned char)*p].characterWidth;
+ width += g_cine->_textHandler.fontParamTable[currChar].characterWidth + padding;
p++;
}
+ maxWidth = MAX<int>(width, maxWidth);
+
return width;
}
@@ -572,6 +628,12 @@ void FWRenderer::drawBackground() {
memcpy(_backBuffer, _background, _screenSize);
}
+void FWRenderer::clearBackBuffer() {
+ if (_backBuffer) {
+ memset(_backBuffer, 0, _screenSize);
+ }
+}
+
/**
* Draw one overlay
* @param it Overlay info
@@ -645,6 +707,20 @@ void FWRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
* Draw overlays
*/
void FWRenderer::drawOverlays() {
+ // WORKAROUND: Show player behind stairs by moving him behind everything
+ // in the scene right after leaving Dr. Why's control room.
+ if (g_cine->getGameType() == Cine::GType_OS &&
+ g_cine->_overlayList.size() >= 2 &&
+ g_cine->_overlayList.back().objIdx == 1 &&
+ g_cine->_objectTable.size() >= 2 &&
+ g_cine->_objectTable[1].x == 231 &&
+ g_cine->_objectTable[1].y >= 142 &&
+ scumm_stricmp(renderer->getBgName(), "56VIDE.PI1") == 0) {
+ Cine::overlay playerOverlay = g_cine->_overlayList.back();
+ g_cine->_overlayList.pop_back();
+ g_cine->_overlayList.push_front(playerOverlay);
+ }
+
Common::List<overlay>::iterator it;
for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
@@ -655,7 +731,7 @@ void FWRenderer::drawOverlays() {
/**
* Draw another frame
*/
-void FWRenderer::drawFrame() {
+void FWRenderer::drawFrame(bool wait) {
drawBackground();
drawOverlays();
@@ -663,15 +739,36 @@ void FWRenderer::drawFrame() {
drawCommand();
}
- if (_changePal) {
- refreshPalette();
+ // DIFFERENCE FROM DISASSEMBLY:
+ // Waiting for g_cine->getTimerDelay() since last call to this function
+ // from mainLoop() was in Future Wars and Operation Stealth disassembly here.
+ // The wait did nothing else but simply wait for the waiting period to end.
+ // It has been moved to manageEvents() function call in executePlayerInput()
+ // to make better use of the waiting period. Now it is used to read mouse button
+ // status and possibly update the command line while moving the mouse
+ // (e.g. "EXAMINE DOOR" -> "EXAMINE BUTTON").
+
+ if (reloadBgPalOnNextFlip) {
+ _activePal = getFadeInSourcePalette();
+ reloadBgPalOnNextFlip = 0;
+ _changePal = 1; // From disassembly
+ }
+
+ if (_changePal) { // From disassembly
+ setPalette();
+ _changePal = 0; // From disassembly
}
const int menus = _menuStack.size();
for (int i = 0; i < menus; ++i)
_menuStack[i]->drawMenu(*this, (i == menus - 1));
- blit();
+ blit();
+
+ if (gfxFadeInRequested) {
+ fadeFromBlack();
+ gfxFadeInRequested = 0;
+ }
}
/**
@@ -683,14 +780,53 @@ void FWRenderer::showCollisionPage(bool state) {
_showCollisionPage = state;
}
+void FWRenderer::blitBackBuffer() {
+ blit(false);
+}
+
+void FWRenderer::blit(bool useCollisionPage) {
+ // Show the back buffer or the collision page. Normally the back
+ // buffer but showing the collision page is useful for debugging.
+ byte *source = (useCollisionPage ? collisionPage : _backBuffer);
+ g_system->copyRectToScreen(source, 320, 0, 0, 320, 200);
+ g_system->updateScreen();
+}
+
/**
* Update screen
*/
void FWRenderer::blit() {
- // 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);
+ blit(_showCollisionPage);
+}
+
+bool FWRenderer::hasSavedBackBuffer(BackBufferSource source) {
+ return source >= 0 && source < MAX_BACK_BUFFER_SOURCES && _savedBackBuffers[source];
+}
+
+void FWRenderer::saveBackBuffer(BackBufferSource source) {
+ if (_backBuffer && source >= 0 && source < MAX_BACK_BUFFER_SOURCES) {
+ if (!_savedBackBuffers[source]) {
+ _savedBackBuffers[source] = new byte[_screenSize];
+ }
+ memcpy(_savedBackBuffers[source], _backBuffer, _screenSize);
+ }
+}
+
+void FWRenderer::popSavedBackBuffer(BackBufferSource source) {
+ restoreSavedBackBuffer(source);
+ removeSavedBackBuffer(source);
+}
+
+void FWRenderer::restoreSavedBackBuffer(BackBufferSource source) {
+ if (_backBuffer && hasSavedBackBuffer(source)) {
+ memcpy(_backBuffer, _savedBackBuffers[source], _screenSize);
+ blitBackBuffer();
+ }
+}
+
+void FWRenderer::removeSavedBackBuffer(BackBufferSource source) {
+ delete[] _savedBackBuffers[source];
+ _savedBackBuffers[source] = nullptr;
}
/**
@@ -701,22 +837,24 @@ void FWRenderer::setCommand(Common::String cmd) {
_cmd = cmd;
}
-/**
- * Refresh current palette
- */
-void FWRenderer::refreshPalette() {
+Common::String FWRenderer::getCommand() {
+ return _cmd;
+}
+
+void FWRenderer::setBlackPalette(bool updateChangePal) {
+ _activePal.fillWithBlack();
+ if (updateChangePal) {
+ _changePal = 1; // From disassembly when called from main loop's initialization section
+ }
+}
+
+void FWRenderer::setPalette() {
assert(_activePal.isValid() && !_activePal.empty());
_activePal.setGlobalOSystemPalette();
- _changePal = 0;
}
-/**
- * Load palette of current background
- */
-void FWRenderer::reloadPalette() {
- assert(_backupPal.isValid() && !_backupPal.empty());
- _activePal = _backupPal;
- _changePal = 1;
+int16 FWRenderer::addBackground(const char *bgName, uint16 bgIdx) {
+ error("Future Wars renderer doesn't support multiple backgrounds");
}
/**
@@ -824,7 +962,7 @@ void FWRenderer::restorePalette(Common::SeekableReadStream &fHandle, int version
fHandle.read(buf, kLowPalNumBytes);
_backupPal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
- _changePal = 1;
+ _changePal = 1; // From disassembly
}
/**
@@ -867,8 +1005,8 @@ void OSRenderer::savePalette(Common::OutSaveFile &fHandle) {
_activePal.save(buf, sizeof(buf), CINE_LITTLE_ENDIAN);
fHandle.write(buf, kHighPalNumBytes);
- // Write the active 256 color palette a second time.
- // FIXME: The backup 256 color palette should be saved here instead of the active one.
+ // Write the backup 256 color palette.
+ _backupPal.save(buf, sizeof(buf), CINE_LITTLE_ENDIAN);
fHandle.write(buf, kHighPalNumBytes);
}
@@ -878,22 +1016,29 @@ void OSRenderer::savePalette(Common::OutSaveFile &fHandle) {
*/
void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle, int version) {
byte buf[kHighPalNumBytes];
- uint colorCount = (version > 0) ? fHandle.readUint16LE() : kHighPalNumBytes;
+ uint colorCount = (version > 0) ? fHandle.readUint16LE() : kHighPalNumColors;
+ // Load the active color palette
fHandle.read(buf, kHighPalNumBytes);
if (colorCount == kHighPalNumColors) {
// Load the active 256 color palette from file
_activePal.load(buf, sizeof(buf), kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
} else {
+ // Load the active 16 color palette from file
_activePal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_LITTLE_ENDIAN);
}
- // Jump over the backup 256 color palette.
- // FIXME: Load the backup 256 color palette and use it properly.
- fHandle.seek(kHighPalNumBytes, SEEK_CUR);
+ // Load the backup color palette
+ fHandle.read(buf, kHighPalNumBytes);
- _changePal = 1;
+ if (colorCount == kHighPalNumColors) {
+ _backupPal.load(buf, sizeof(buf), kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+ } else {
+ _backupPal.load(buf, sizeof(buf), kLowPalFormat, kLowPalNumColors, CINE_LITTLE_ENDIAN);
+ }
+
+ _changePal = 1; // From disassembly
}
/**
@@ -902,9 +1047,28 @@ void OSRenderer::restorePalette(Common::SeekableReadStream &fHandle, int version
* @param b Last color to rotate
* @param c Possibly rotation step, must be 0 or 1 at the moment
*/
-void FWRenderer::rotatePalette(int a, int b, int c) {
- _activePal.rotateRight(a, b, c);
- refreshPalette();
+void FWRenderer::rotatePalette(int firstIndex, int lastIndex, int mode) {
+ if (mode == 1) {
+ _activePal.rotateRight(firstIndex, lastIndex);
+ } else if (mode == 2) {
+ _activePal.rotateLeft(firstIndex, lastIndex);
+ } else {
+ _activePal = _backupPal;
+ }
+ setPalette();
+}
+
+void OSRenderer::rotatePalette(int firstIndex, int lastIndex, int mode) {
+ if (mode == 1) {
+ _activePal.rotateRight(firstIndex, lastIndex);
+ } else if (mode == 2) {
+ _activePal.rotateLeft(firstIndex, lastIndex);
+ } else if (_currentBg > 0 && _currentBg < 8) {
+ _activePal = _bgTable[_currentBg].pal;
+ } else { // background indices 0 and 8 use backup palette
+ _activePal = _backupPal;
+ }
+ setPalette();
}
/**
@@ -921,7 +1085,24 @@ void FWRenderer::transformPalette(int first, int last, int r, int g, int b) {
}
_backupPal.saturatedAddColor(_activePal, first, last, r, g, b);
- refreshPalette();
+ _changePal = 1; // From disassembly
+ gfxFadeOutCompleted = 0;
+}
+
+uint FWRenderer::fadeDelayMs() {
+ // For PC wait for vertical retrace and wait for three timer interrupt ticks.
+ // On PC vertical retrace is 70Hz (1000ms / 70 ~= 14.29ms) and
+ // timer interrupt tick is set to (10923000ms / 1193180) ~= 9.15ms.
+ // So 14.29ms + 3 * 9.15ms ~= 41.74ms ~= 42ms. That's the maximum to wait for PC.
+ // Because the vertical retrace might come earlier the minimum to wait is
+ // 0ms + 3 * 9.15ms (The wait for three timer ticks is absolute) = 27.45ms ~= 27ms.
+ // So the wait on PC is something between 27ms and 42ms.
+ // Probably something else on Amiga (Didn't they have 50Hz or 60Hz monitors?).
+ return 42;
+}
+
+uint FWRenderer::fadeToBlackMinMs() {
+ return 1000;
}
/**
@@ -932,15 +1113,59 @@ void FWRenderer::transformPalette(int first, int last, int r, int g, int b) {
void FWRenderer::fadeToBlack() {
assert(_activePal.isValid() && !_activePal.empty());
- for (int i = 0; i < 8; i++) {
+ bool skipFade = false;
+ uint32 now = g_system->getMillis();
+
+ // HACK: Try to cirmumvent double fade outs by throttling function call.
+ if (hacksEnabled && _fadeToBlackLastCalledMs != 0 && (now - _fadeToBlackLastCalledMs) < fadeToBlackMinMs()) {
+ skipFade = true;
+ warning("Skipping fade to black (Time since last called = %d ms < throttling value of %d ms)",
+ now - _fadeToBlackLastCalledMs, fadeToBlackMinMs());
+ } else {
+ _fadeToBlackLastCalledMs = now;
+ }
+
+ for (int i = (skipFade ? 7 : 0); i < 8; i++) {
// Fade out the whole palette by 1/7th
// (Operation Stealth used 36 / 252, which is 1 / 7. Future Wars used 1 / 7 directly).
_activePal.saturatedAddNormalizedGray(_activePal, 0, _activePal.colorCount() - 1, -1, 7);
- refreshPalette();
+ setPalette();
+ g_system->updateScreen();
+ g_system->delayMillis(fadeDelayMs());
+ }
+
+ clearBackBuffer();
+ forbidBgPalReload = gfxFadeOutCompleted = 1;
+
+ // HACK: This is not present in disassembly
+ // but this is an attempt to prevent flashing a
+ // normally illuminated screen and then fading it in by
+ // resetting possible pending background palette reload.
+ if (hacksEnabled) {
+ reloadBgPalOnNextFlip = 0;
+ }
+}
+
+void FWRenderer::fadeFromBlack() {
+ assert(_activePal.isValid() && !_activePal.empty());
+
+ const Palette& sourcePalette = getFadeInSourcePalette();
+
+ // Initialize active palette to source palette's format and size if they differ
+ if (_activePal.colorFormat() != sourcePalette.colorFormat() || _activePal.colorCount() != sourcePalette.colorCount()) {
+ _activePal = Cine::Palette(sourcePalette.colorFormat(), sourcePalette.colorCount());
+ }
+
+ for (int i = 7; i >= 0; i--) {
+ sourcePalette.saturatedAddNormalizedGray(_activePal, 0, _activePal.colorCount() - 1, -i, 7);
+
+ setPalette();
g_system->updateScreen();
- g_system->delayMillis(50);
+ g_system->delayMillis(fadeDelayMs());
}
+
+ forbidBgPalReload = gfxFadeOutCompleted = 0;
}
// Menu implementation
@@ -988,14 +1213,14 @@ void SelectionMenu::drawMenu(FWRenderer &r, bool top) {
if (y + height > 199)
y = 199 - height;
- const bool isAmiga = (g_cine->getPlatform() == Common::kPlatformAmiga);
+ byte doubleBorderColor = (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0) + 2;
- if (isAmiga) {
+ if (r.useTransparentDialogBoxes()) {
r.drawTransparentBox(x, y, _width, height);
- r.drawDoubleBorder(x, y, _width, height, 18);
+ r.drawDoubleBorder(x, y, _width, height, doubleBorderColor);
} else {
r.drawPlainBox(x, y, _width, height, r._messageBg);
- r.drawDoubleBorder(x, y, _width, height, 2);
+ r.drawDoubleBorder(x, y, _width, height, doubleBorderColor);
}
int lineY = y + 4;
@@ -1005,16 +1230,10 @@ void SelectionMenu::drawMenu(FWRenderer &r, bool top) {
int charX = x + 4;
if (i == _selection) {
- int color;
+ int color = (r.useTransparentDialogBoxes() ? 2 : 0);
- if (isAmiga) {
- if (top) {
- color = 2;
- } else {
- color = 18;
- }
- } else {
- color = 0;
+ if (!top) {
+ color += (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0);
}
r.drawPlainBox(x + 2, lineY - 1, _width - 3, 9, color);
@@ -1022,7 +1241,7 @@ void SelectionMenu::drawMenu(FWRenderer &r, bool top) {
const int size = _elements[i].size();
for (int j = 0; j < size; ++j) {
- if (isAmiga && i == _selection) {
+ if (r.useTransparentDialogBoxes() && i == _selection) {
charX = r.undrawChar(_elements[i][j], charX, lineY);
} else {
charX = r.drawChar(_elements[i][j], charX, lineY);
@@ -1048,9 +1267,7 @@ void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
int line = 0, words = 0, cw = 0;
int space = 0, extraSpace = 0;
- const bool isAmiga = (g_cine->getPlatform() == Common::kPlatformAmiga);
-
- if (isAmiga)
+ if (r.useTransparentDialogBoxes())
r.drawTransparentBox(x, y, _width, 4);
else
r.drawPlainBox(x, y, _width, 4, r._messageBg);
@@ -1075,7 +1292,7 @@ void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
}
ty += 9;
- if (isAmiga)
+ if (r.useTransparentDialogBoxes())
r.drawTransparentBox(x, ty, _width, 9);
else
r.drawPlainBox(x, ty, _width, 9, r._messageBg);
@@ -1096,7 +1313,7 @@ void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
// input area background
ty += 9;
- if (isAmiga)
+ if (r.useTransparentDialogBoxes())
r.drawTransparentBox(x, ty, _width, 9);
else
r.drawPlainBox(x, ty, _width, 9, r._messageBg);
@@ -1118,11 +1335,11 @@ void TextInputMenu::drawMenu(FWRenderer &r, bool top) {
}
ty += 9;
- if (isAmiga)
+ if (r.useTransparentDialogBoxes())
r.drawTransparentBox(x, ty, _width, 4);
else
r.drawPlainBox(x, ty, _width, 4, r._messageBg);
- r.drawDoubleBorder(x, y, _width, ty - y + 4, isAmiga ? 18 : 2);
+ r.drawDoubleBorder(x, y, _width, ty - y + 4, (r.useTransparentDialogBoxes() ? r.transparentDialogBoxStartColor() : 0) + 2);
}
// -------------------
@@ -1146,7 +1363,7 @@ OSRenderer::~OSRenderer() {
}
bool OSRenderer::initialize() {
- _activePal = Palette(kHighPalFormat, kHighPalNumColors);
+ _backupPal = _activePal = Palette(kHighPalFormat, kHighPalNumColors);
return true;
}
@@ -1175,13 +1392,23 @@ void OSRenderer::incrustMask(const BGIncrust &incrust, uint8 color) {
const byte *data = g_cine->_animDataTable[obj.frame].data();
int x, y, width, height;
- x = obj.x;
- y = obj.y;
+ x = incrust.x;
+ y = incrust.y;
width = g_cine->_animDataTable[obj.frame]._realWidth;
height = g_cine->_animDataTable[obj.frame]._height;
- if (_bgTable[_currentBg].bg) {
- gfxFillSprite(data, width, height, _bgTable[_currentBg].bg, x, y, color);
+ if (_bgTable[incrust.bgIdx].bg) {
+ gfxFillSprite(data, width, height, _bgTable[incrust.bgIdx].bg, x, y, color);
+ }
+}
+
+const Cine::Palette& OSRenderer::getFadeInSourcePalette() {
+ assert(_currentBg >= 0 && _currentBg <= 8);
+
+ if (_currentBg == 0) {
+ return _backupPal;
+ } else {
+ return _bgTable[_currentBg].pal;
}
}
@@ -1217,8 +1444,16 @@ void OSRenderer::incrustSprite(const BGIncrust &incrust) {
width = g_cine->_animDataTable[incrust.frame]._realWidth;
height = g_cine->_animDataTable[incrust.frame]._height;
- if (_bgTable[_currentBg].bg) {
- drawSpriteRaw2(data, transColor, width, height, _bgTable[_currentBg].bg, x, y);
+ if (_bgTable[incrust.bgIdx].bg) {
+ // HACK: Fix transparency colors of shadings near walls
+ // in labyrinth scene in Operation Stealth after loading a savegame
+ // saved in the labyrinth.
+ if (hacksEnabled && incrust.objIdx == 1 && incrust.frame < 16 && transColor == 5 &&
+ scumm_stricmp(currentPrcName, "LABY.PRC") == 0) {
+ transColor = 0;
+ }
+
+ drawSpriteRaw2(data, transColor, width, height, _bgTable[incrust.bgIdx].bg, x, y);
}
}
@@ -1254,17 +1489,22 @@ void OSRenderer::drawBackground() {
if (!_bgShift) {
memcpy(_backBuffer, main, _screenSize);
} else {
+ unsigned int rowShift = _bgShift % 200;
byte *scroll = _bgTable[_scrollBg].bg;
- int mainShift = _bgShift * _screenWidth;
- int mainSize = _screenSize - mainShift;
-
assert(scroll);
- if (mainSize > 0) { // Just a precaution
- memcpy(_backBuffer, main + mainShift, mainSize);
- }
- if (mainShift > 0) { // Just a precaution
- memcpy(_backBuffer + mainSize, scroll, mainShift);
+ if (!rowShift) {
+ memcpy(_backBuffer, scroll, _screenSize);
+ } else {
+ int mainShift = rowShift * _screenWidth;
+ int mainSize = _screenSize - mainShift;
+
+ if (mainSize > 0) { // Just a precaution
+ memcpy(_backBuffer, main + mainShift, mainSize);
+ }
+ if (mainShift > 0) { // Just a precaution
+ memcpy(_backBuffer + mainSize, scroll, mainShift);
+ }
}
}
}
@@ -1278,7 +1518,8 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
int len, idx, width, height;
ObjectStruct *obj;
AnimData *sprite;
- byte color;
+ byte color, transparentColor;
+ bool useTopLeftForTransCol = false;
switch (it->type) {
// color sprite
@@ -1286,8 +1527,44 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
if (g_cine->_objectTable[it->objIdx].frame < 0) {
break;
}
+
sprite = &g_cine->_animDataTable[g_cine->_objectTable[it->objIdx].frame];
- drawSprite(&(*it), sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, g_cine->_objectTable[it->objIdx].x, g_cine->_objectTable[it->objIdx].y, g_cine->_objectTable[it->objIdx].part, sprite->_bpp);
+ obj = &g_cine->_objectTable[it->objIdx];
+ transparentColor = obj->part & 0x0F;
+
+ // HACK: Correct transparency color from 6 to 0 for the first frame of sea animation
+ // in 16 color DOS version of Operation Stealth in the flower shop scene
+ // (The scene in which the player arrives immediately after leaving the airport).
+ if (hacksEnabled && it->objIdx == 141 && obj->frame == 100 && obj->part == 6 && sprite->_bpp == 4 &&
+ scumm_stricmp(currentPrcName, "AIRPORT.PRC") == 0 &&
+ scumm_stricmp(renderer->getBgName(), "21.PI1") == 0) {
+ useTopLeftForTransCol = true;
+ }
+
+ // HACK: Correct transparency color from 8 to 51 for the player's walking animation
+ // in 256 color DOS version of Operation Stealth in the scene right after
+ // leaving Dr. Why's control room.
+ if (hacksEnabled && it->objIdx == 1 && obj->part == 8 && sprite->_bpp == 5 &&
+ scumm_stricmp(currentPrcName, "ILE.PRC") == 0 &&
+ scumm_stricmp(renderer->getBgName(), "56VIDE.PI1") == 0) {
+ useTopLeftForTransCol = true;
+ }
+
+ // HACK: Correct transparency color from 1 to 3 for the player emerging from a manhole
+ // in 256 color DOS version of Operation Stealth when entering the Dr. Why's island.
+ if (hacksEnabled && it->objIdx == 43 && obj->frame >= 100 && obj->frame <= 102 &&
+ obj->part == 1 && sprite->_bpp == 5 &&
+ scumm_stricmp(currentPrcName, "SOUSMAR2.PRC") == 0 &&
+ scumm_stricmp(renderer->getBgName(), "56.PI1") == 0) {
+ useTopLeftForTransCol = true;
+ }
+
+ if (useTopLeftForTransCol) {
+ // Use top left corner value for transparency
+ transparentColor = sprite->getColor(0, 0);
+ }
+
+ drawSprite(&(*it), sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, g_cine->_objectTable[it->objIdx].x, g_cine->_objectTable[it->objIdx].y, transparentColor, sprite->_bpp);
break;
// game message
@@ -1326,7 +1603,7 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
// masked background
case 20:
assert(it->objIdx < NUM_MAX_OBJECT);
- var5 = it->x; // A global variable updated here!
+ lastType20OverlayBgIdx = it->x; // A global variable updated here!
obj = &g_cine->_objectTable[it->objIdx];
sprite = &g_cine->_animDataTable[obj->frame];
@@ -1334,19 +1611,18 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
break;
}
- maskBgOverlay(_bgTable[it->x].bg, sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, obj->x, obj->y);
+ maskBgOverlay(it->x, _bgTable[it->x].bg, sprite->data(), sprite->_realWidth, sprite->_height, _backBuffer, obj->x, obj->y);
break;
+ // line drawing
case 22:
- // TODO: Check it this implementation really works correctly (Some things might be wrong, needs testing).
assert(it->objIdx < NUM_MAX_OBJECT);
obj = &g_cine->_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);
+ // Using Bresenham's algorithm, looks good enough for visual purposes in Operation Stealth
+ Graphics::drawLine(obj->x, obj->y, width, height, color, plotPoint, _backBuffer);
break;
// something else
@@ -1356,20 +1632,6 @@ void OSRenderer::renderOverlay(const Common::List<overlay>::iterator &it) {
}
}
-/**
- * Load palette of current background
- */
-void OSRenderer::reloadPalette() {
- // selected background in plane takeoff scene has swapped colors 12
- // and 14, shift background has it right
- palBg *bg = _bgShift ? &_bgTable[_scrollBg] : &_bgTable[_currentBg];
-
- assert(bg->pal.isValid() && !(bg->pal.empty()));
-
- _activePal = bg->pal;
- _changePal = 1;
-}
-
/**
* Copy part of backup palette to active palette and transform
* @param first First color to transform
@@ -1379,15 +1641,55 @@ void OSRenderer::reloadPalette() {
* @param b Blue channel transformation
*/
void OSRenderer::transformPalette(int first, int last, int r, int g, int b) {
- palBg *bg = _bgShift ? &_bgTable[_scrollBg] : &_bgTable[_currentBg];
+ // Background indices 0 and 8 use backup palette
+ const Cine::Palette& srcPal =
+ (_currentBg > 0 && _currentBg < 8) ? _bgTable[_currentBg].pal : _backupPal;
// Initialize active palette to current background's palette format and size if they differ
- if (_activePal.colorFormat() != bg->pal.colorFormat() || _activePal.colorCount() != bg->pal.colorCount()) {
- _activePal = Cine::Palette(bg->pal.colorFormat(), bg->pal.colorCount());
+ if (_activePal.colorFormat() != srcPal.colorFormat() || _activePal.colorCount() != srcPal.colorCount()) {
+ _activePal = Cine::Palette(srcPal.colorFormat(), srcPal.colorCount());
+ }
+
+ // If asked to change whole 16 color palette then
+ // assume it means the whole palette regardless of size.
+ // In Operation Stealth DOS 16 color and 256 color disassembly mapping was from 0-15 to 0-255.
+ if (first == 0 && last == 15) {
+ last = srcPal.colorCount() - 1;
+ }
+
+ srcPal.saturatedAddColor(_activePal, first, last, r, g, b, kLowPalFormat);
+ _changePal = 1; // From disassembly
+ gfxFadeOutCompleted = 0;
+}
+
+int16 OSRenderer::addBackground(const char *bgName, uint16 bgIdx) {
+ byte *ptr, *dataPtr;
+
+ int16 fileIdx = findFileInBundle(bgName);
+ if (fileIdx < 0) {
+ warning("OSRenderer::addBackground(\"%s\", %d): Could not find background in file bundle.", bgName, bgIdx);
+ return -1;
+ }
+ checkDataDisk(-1);
+ ptr = dataPtr = readBundleFile(fileIdx);
+
+ uint16 bpp = READ_BE_UINT16(ptr); ptr += 2;
+
+ if (!_bgTable[bgIdx].bg) {
+ _bgTable[bgIdx].bg = new byte[_screenSize];
}
- bg->pal.saturatedAddColor(_activePal, first, last, r, g, b, kLowPalFormat);
- refreshPalette();
+ Common::strlcpy(_bgTable[bgIdx].name, bgName, sizeof(_bgTable[bgIdx].name));
+
+ if (bpp == 8) {
+ _bgTable[bgIdx].pal.load(ptr, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+ memcpy(_bgTable[bgIdx].bg, ptr + kHighPalNumBytes, _screenSize);
+ } else {
+ _bgTable[bgIdx].pal.load(ptr, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
+ gfxConvertSpriteToRaw(_bgTable[bgIdx].bg, ptr + kLowPalNumBytes, 160, 200);
+ }
+ free(dataPtr);
+ return 0;
}
/**
@@ -1409,7 +1711,7 @@ void OSRenderer::loadBg16(const byte *bg, const char *name, unsigned int idx) {
Common::strlcpy(_bgTable[idx].name, name, sizeof(_bgTable[idx].name));
// Load the 16 color palette
- _bgTable[idx].pal.load(bg, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
+ _backupPal.load(bg, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
// Jump over the palette data to the background data
bg += kLowPalNumBytes;
@@ -1423,10 +1725,21 @@ void OSRenderer::loadBg16(const byte *bg, const char *name, unsigned int idx) {
* @param name Background filename
*/
void OSRenderer::loadCt16(const byte *ct, const char *name) {
+ assert(collisionPage);
+
// Make the 9th background point directly to the collision page
// and load the picture into it.
+ setBackground8ToCollisionPage();
+ _bgTable[kCollisionPageBgIdxAlias].pal.load(ct, kLowPalNumBytes, kLowPalFormat, kLowPalNumColors, CINE_BIG_ENDIAN);
+ gfxConvertSpriteToRaw(_bgTable[kCollisionPageBgIdxAlias].bg, ct + kLowPalNumBytes, 160, 200);
+}
+
+void OSRenderer::setBackground8ToCollisionPage() {
+ byte* oldBg = _bgTable[kCollisionPageBgIdxAlias].bg;
+ if (oldBg && oldBg != collisionPage) {
+ delete[] _bgTable[kCollisionPageBgIdxAlias].bg;
+ }
_bgTable[kCollisionPageBgIdxAlias].bg = collisionPage;
- loadBg16(ct, name, kCollisionPageBgIdxAlias);
}
/**
@@ -1445,7 +1758,8 @@ void OSRenderer::loadBg256(const byte *bg, const char *name, unsigned int idx) {
assert(_bgTable[idx].bg);
Common::strlcpy(_bgTable[idx].name, name, sizeof(_bgTable[idx].name));
- _bgTable[idx].pal.load(bg, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+ _backupPal.load(bg, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+
memcpy(_bgTable[idx].bg, bg + kHighPalNumBytes, _screenSize);
}
@@ -1455,10 +1769,13 @@ void OSRenderer::loadBg256(const byte *bg, const char *name, unsigned int idx) {
* @param name Background filename
*/
void OSRenderer::loadCt256(const byte *ct, const char *name) {
+ assert(collisionPage);
+
// Make the 9th background point directly to the collision page
// and load the picture into it.
- _bgTable[kCollisionPageBgIdxAlias].bg = collisionPage;
- loadBg256(ct, name, kCollisionPageBgIdxAlias);
+ setBackground8ToCollisionPage();
+ _bgTable[kCollisionPageBgIdxAlias].pal.load(ct, kHighPalNumBytes, kHighPalFormat, kHighPalNumColors, CINE_LITTLE_ENDIAN);
+ memcpy(_bgTable[kCollisionPageBgIdxAlias].bg, ct + kHighPalNumBytes, _screenSize);
}
/**
@@ -1468,12 +1785,13 @@ void OSRenderer::loadCt256(const byte *ct, const char *name) {
void OSRenderer::selectBg(unsigned int idx) {
assert(idx < 9);
- if (_bgTable[idx].bg) {
- assert(_bgTable[idx].pal.isValid() && !(_bgTable[idx].pal.empty()));
+ if (idx <= 8 && _bgTable[idx].bg) {
_currentBg = idx;
+ if (!forbidBgPalReload) {
+ reloadBgPalOnNextFlip = 1;
+ }
} else
warning("OSRenderer::selectBg(%d) - attempt to select null background", idx);
- reloadPalette();
}
/**
@@ -1483,10 +1801,9 @@ void OSRenderer::selectBg(unsigned int idx) {
void OSRenderer::selectScrollBg(unsigned int idx) {
assert(idx < 9);
- if (_bgTable[idx].bg) {
+ if (idx <= 8 && _bgTable[idx].bg) {
_scrollBg = idx;
}
- reloadPalette();
}
/**
@@ -1494,9 +1811,7 @@ void OSRenderer::selectScrollBg(unsigned int idx) {
* @param shift Background scroll in pixels
*/
void OSRenderer::setScroll(unsigned int shift) {
- assert(shift <= 200);
-
- _bgShift = shift;
+ _bgShift = shift % 400;
}
/**
@@ -1884,7 +2199,7 @@ void drawSpriteRaw2(const byte *spritePtr, byte transColor, int16 width, int16 h
}
}
-void maskBgOverlay(const byte *bgPtr, const byte *maskPtr, int16 width, int16 height,
+void maskBgOverlay(int targetBgIdx, const byte *bgPtr, const byte *maskPtr, int16 width, int16 height,
byte *page, int16 x, int16 y) {
int16 i, j, tmpWidth, tmpHeight;
Common::List<BGIncrust>::iterator it;
@@ -1915,6 +2230,12 @@ void maskBgOverlay(const byte *bgPtr, const byte *maskPtr, int16 width, int16 he
// incrust pass
for (it = g_cine->_bgIncrustList.begin(); it != g_cine->_bgIncrustList.end(); ++it) {
+ // HACK: Remove drawing of red corners around doors in rat maze in Operation Stealth
+ // by skipping drawing of possible collision table data to non-collision table page.
+ if (hacksEnabled && it->bgIdx == kCollisionPageBgIdxAlias && targetBgIdx != kCollisionPageBgIdxAlias) {
+ continue;
+ }
+
tmpWidth = g_cine->_animDataTable[it->frame]._realWidth;
tmpHeight = g_cine->_animDataTable[it->frame]._height;
byte *mask = (byte *)malloc(tmpWidth * tmpHeight);
diff --git a/engines/cine/gfx.h b/engines/cine/gfx.h
index ad6e4b20ef..5349f300d0 100644
--- a/engines/cine/gfx.h
+++ b/engines/cine/gfx.h
@@ -34,6 +34,12 @@ namespace Cine {
extern byte *collisionPage;
static const int kCollisionPageBgIdxAlias = 8;
+enum BackBufferSource {
+ BEFORE_OPENING_MENU = 0,
+ BEFORE_TAKING_THUMBNAIL,
+ MAX_BACK_BUFFER_SOURCES
+};
+
/**
* Background with palette
*/
@@ -124,6 +130,7 @@ class FWRenderer : public Common::NonCopyable {
friend class SelectionMenu;
friend class TextInputMenu;
private:
+ byte * _savedBackBuffers[MAX_BACK_BUFFER_SOURCES];
byte *_background; ///< Current background
char _bgName[13]; ///< Background filename
@@ -140,14 +147,16 @@ protected:
Common::Stack<Menu *> _menuStack; ///< All displayed menus
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.
+ uint32 _fadeToBlackLastCalledMs;
+ virtual const Cine::Palette& getFadeInSourcePalette();
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, int color);
void drawPlainBox(int x, int y, int width, int height, byte color);
+ byte transparentDialogBoxStartColor();
void drawTransparentBox(int x, int y, int width, int height);
void drawBorder(int x, int y, int width, int height, byte color);
void drawDoubleBorder(int x, int y, int width, int height, byte color);
@@ -156,11 +165,12 @@ protected:
void drawLine(int x, int y, int width, int height, byte color);
void remaskSprite(byte *mask, Common::List<overlay>::iterator it);
virtual void drawBackground();
+ virtual void clearBackBuffer();
+ virtual void removeSavedBackBuffers();
virtual void renderOverlay(const Common::List<overlay>::iterator &it);
void drawOverlays();
-
- void blit();
+ virtual void blit(bool useCollisionPage);
public:
uint16 _messageBg; ///< Message box background color
@@ -173,15 +183,35 @@ public:
/** Test if renderer is ready to draw */
virtual bool ready() { return _background != NULL; }
+ virtual unsigned int currentBg() { return 0; };
+ virtual unsigned int scrollBg() { return 0; }
+ virtual bool useTransparentDialogBoxes();
virtual void clear();
- void drawFrame();
+ void drawFrame(bool wait = false);
+ void drawCommand();
void setCommand(Common::String cmd);
+ Common::String getCommand();
+ virtual void blit();
+ virtual void blitBackBuffer();
virtual void incrustMask(const BGIncrust &incrust, uint8 color = 0);
virtual void incrustSprite(const BGIncrust &incrust);
+ virtual bool hasSavedBackBuffer(BackBufferSource source);
+
+ /** Saves back buffer content without palette. */
+ virtual void saveBackBuffer(BackBufferSource source);
+
+ virtual void popSavedBackBuffer(BackBufferSource source);
+
+ /** Restores back buffer content without palette. */
+ virtual void restoreSavedBackBuffer(BackBufferSource source);
+
+ virtual void removeSavedBackBuffer(BackBufferSource source);
+
+ virtual int16 addBackground(const char *bgName, uint16 bgIdx);
virtual void loadBg16(const byte *bg, const char *name, unsigned int idx = 0);
virtual void loadCt16(const byte *ct, const char *name);
virtual void loadBg256(const byte *bg, const char *name, unsigned int idx = 0);
@@ -192,20 +222,23 @@ 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 const char *getBgName(uint idx = 0) const;
- virtual void refreshPalette();
- virtual void reloadPalette();
+ virtual void setBlackPalette(bool updateChangePal);
+ virtual void setPalette();
virtual void restorePalette(Common::SeekableReadStream &fHandle, int version);
virtual void savePalette(Common::OutSaveFile &fHandle);
- virtual void rotatePalette(int a, int b, int c);
+ virtual void rotatePalette(int firstIndex, int lastIndex, int mode);
virtual void transformPalette(int first, int last, int r, int g, int b);
void pushMenu(Menu *menu);
Menu *popMenu();
void clearMenuStack();
+ virtual uint fadeDelayMs();
+ virtual uint fadeToBlackMinMs();
virtual void fadeToBlack();
+ virtual void fadeFromBlack();
void showCollisionPage(bool state);
void drawString(const char *string, byte param);
@@ -223,7 +256,8 @@ private:
unsigned int _bgShift; ///< Background shift
protected:
-
+ void setBackground8ToCollisionPage();
+ const Cine::Palette& getFadeInSourcePalette() override;
void drawSprite(const ObjectStruct &obj) override;
void drawSprite(overlay *overlayPtr, const byte *spritePtr, int16 width, int16 height, byte *page, int16 x, int16 y, byte transparentColor, byte bpp);
int drawChar(char character, int x, int y) override;
@@ -238,12 +272,15 @@ public:
/** Test if renderer is ready to draw */
bool ready() override { return _bgTable[_currentBg].bg != NULL; }
+ unsigned int currentBg() override { return _currentBg; };
+ unsigned int scrollBg() override { return _scrollBg; }
void clear() override;
void incrustMask(const BGIncrust &incrust, uint8 color = 0) override;
void incrustSprite(const BGIncrust &incrust) override;
+ int16 addBackground(const char *bgName, uint16 bgIdx) override;
void loadBg16(const byte *bg, const char *name, unsigned int idx = 0) override;
void loadCt16(const byte *ct, const char *name) override;
void loadBg256(const byte *bg, const char *name, unsigned int idx = 0) override;
@@ -256,11 +293,10 @@ public:
void saveBgNames(Common::OutSaveFile &fHandle) override;
const char *getBgName(uint idx = 0) const override;
- void reloadPalette() override;
void restorePalette(Common::SeekableReadStream &fHandle, int version) override;
void savePalette(Common::OutSaveFile &fHandle) override;
+ void rotatePalette(int firstIndex, int lastIndex, int mode) override;
void transformPalette(int first, int last, int r, int g, int b) override;
-
};
void gfxDrawSprite(byte *src4, uint16 sw, uint16 sh, byte *dst4, int16 sx, int16 sy);
@@ -293,7 +329,7 @@ void gfxFlipRawPage(byte *frontBuffer);
void drawSpriteRaw(const byte *spritePtr, const byte *maskPtr, int16 width, int16 height, byte *page, int16 x, int16 y);
void gfxDrawPlainBoxRaw(int16 x1, int16 y1, int16 x2, int16 y2, byte color, byte *page);
void drawSpriteRaw2(const byte *spritePtr, byte transColor, int16 width, int16 height, byte *page, int16 x, int16 y);
-void maskBgOverlay(const byte *spritePtr, const byte *maskPtr, int16 width, int16 height, byte *page, int16 x, int16 y);
+void maskBgOverlay(int targetBgIdx, const byte *spritePtr, const byte *maskPtr, int16 width, int16 height, byte *page, int16 x, int16 y);
void fadeFromBlack();
void fadeToBlack();
diff --git a/engines/cine/main_loop.cpp b/engines/cine/main_loop.cpp
index 6cf7c5c43b..27878c8475 100644
--- a/engines/cine/main_loop.cpp
+++ b/engines/cine/main_loop.cpp
@@ -45,8 +45,6 @@ MouseStatusStruct mouseData;
uint16 mouseRight = 0;
uint16 mouseLeft = 0;
-int lastKeyStroke = 0;
-
uint16 mouseUpdateStatus;
uint16 dummyU16;
@@ -58,14 +56,26 @@ static void processEvent(Common::Event &event) {
case Common::EVENT_RBUTTONDOWN:
mouseRight = 1;
break;
+ case Common::EVENT_MBUTTONDOWN:
+ mouseLeft = mouseRight = 1;
+ break;
case Common::EVENT_LBUTTONUP:
mouseLeft = 0;
break;
case Common::EVENT_RBUTTONUP:
mouseRight = 0;
break;
+ case Common::EVENT_MBUTTONUP:
+ mouseLeft = mouseRight = 0;
+ break;
case Common::EVENT_MOUSEMOVE:
break;
+ case Common::EVENT_WHEELUP:
+ g_cine->_keyInputList.push_back(Common::KeyState(Common::KEYCODE_UP));
+ break;
+ case Common::EVENT_WHEELDOWN:
+ g_cine->_keyInputList.push_back(Common::KeyState(Common::KEYCODE_DOWN));
+ break;
case Common::EVENT_KEYDOWN:
switch (event.kbd.keycode) {
case Common::KEYCODE_RETURN:
@@ -130,6 +140,9 @@ static void processEvent(Common::Event &event) {
case Common::KEYCODE_F11:
renderer->showCollisionPage(true);
break;
+ case Common::KEYCODE_KP0:
+ g_cine->setDefaultGameSpeed();
+ break;
case Common::KEYCODE_MINUS:
case Common::KEYCODE_KP_MINUS:
g_cine->modifyGameSpeed(-1); // Slower
@@ -138,19 +151,15 @@ static void processEvent(Common::Event &event) {
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;
@@ -166,24 +175,39 @@ static void processEvent(Common::Event &event) {
case Common::KEYCODE_KP3:
moveUsingKeyboard(+1, -1); // Down & Right
break;
+ case Common::KEYCODE_LEFT:
+ // fall through
+ case Common::KEYCODE_RIGHT:
+ // fall through
+ case Common::KEYCODE_UP:
+ // fall through
+ case Common::KEYCODE_DOWN:
+ // fall through
default:
- lastKeyStroke = event.kbd.keycode;
+ g_cine->_keyInputList.push_back(event.kbd);
break;
}
break;
case Common::EVENT_KEYUP:
switch (event.kbd.keycode) {
+ case Common::KEYCODE_RETURN:
+ case Common::KEYCODE_KP_ENTER:
+ case Common::KEYCODE_KP5:
+ if (allowPlayerInput) {
+ mouseLeft = 0;
+ }
+ break;
+ case Common::KEYCODE_ESCAPE:
+ if (allowPlayerInput) {
+ mouseRight = 0;
+ }
+ break;
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
@@ -200,23 +224,166 @@ static void processEvent(Common::Event &event) {
}
}
-void manageEvents() {
+void manageEvents(CallSource callSource, EventTarget eventTarget, bool useMaxMouseButtonState, Common::Array<Common::Rect> rects) {
Common::EventManager *eventMan = g_system->getEventManager();
+ Common::Point mousePos;
+ uint keysPressed = g_cine->_keyInputList.size();
+ bool foundTarget = false;
+ int eventsChecked = 0;
+ uint16 maxMouseLeft = mouseLeft;
+ uint16 maxMouseRight = mouseRight;
+ uint32 waitStart = g_system->getMillis();
+ uint32 waitEnd = waitStart + g_cine->getTimerDelay();
+ uint32 frameEnd = waitStart + 20;
+ bool frameEnded = false;
+ bool waitEnded = false;
+ bool checkWaitEnd = (eventTarget == UNTIL_WAIT_ENDED);
+ bool updateScreen = false;
+ bool updateAudio = false;
- uint32 nextFrame = g_system->getMillis() + g_cine->getTimerDelay();
do {
Common::Event event;
- while (eventMan->pollEvent(event)) {
+ int eventsCheckedBeforePolling = eventsChecked;
+ while (!foundTarget && !frameEnded && eventMan->pollEvent(event)) {
processEvent(event);
+ eventsChecked++;
+ maxMouseLeft = MAX<uint16>(mouseLeft, maxMouseLeft);
+ maxMouseRight = MAX<uint16>(mouseRight, maxMouseRight);
+
+ bool mouseButtonDown = (mouseLeft != 0 || mouseRight != 0);
+ bool mouseButtonUp = !mouseButtonDown;
+
+ switch (eventTarget) {
+ case UNTIL_MOUSE_BUTTON_UP_DOWN_UP:
+ // fall through
+ case UNTIL_MOUSE_BUTTON_UP_DOWN:
+ // fall through
+ case UNTIL_MOUSE_BUTTON_UP:
+ // fall through
+ case UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED:
+ foundTarget = mouseButtonUp;
+ break;
+ case UNTIL_MOUSE_BUTTON_DOWN_UP:
+ // fall through
+ case UNTIL_MOUSE_BUTTON_DOWN:
+ foundTarget = mouseButtonDown;
+ break;
+ case UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS:
+ foundTarget = mouseButtonDown;
+ if (!g_cine->_keyInputList.empty()) {
+ Common::KeyState key = g_cine->_keyInputList.back();
+ if (key.keycode == Common::KEYCODE_UP || key.keycode == Common::KEYCODE_DOWN) {
+ foundTarget = true;
+ }
+ }
+ mousePos = g_system->getEventManager()->getMousePos();
+ for (Common::Array<Common::Rect>::iterator it = rects.begin(); it != rects.end(); ++it) {
+ if (it->contains(mousePos)) {
+ foundTarget = true;
+ break;
+ }
+ }
+ break;
+ case UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT:
+ foundTarget = mouseButtonDown || keysPressed < g_cine->_keyInputList.size();
+ break;
+ }
+
+ uint32 now = g_system->getMillis();
+ frameEnded = (now >= frameEnd);
+ waitEnded = (now >= waitEnd);
+
+ if (foundTarget) {
+ switch (eventTarget) {
+ case UNTIL_MOUSE_BUTTON_UP_DOWN_UP:
+ eventTarget = UNTIL_MOUSE_BUTTON_DOWN_UP;
+ foundTarget = false;
+ break;
+ case UNTIL_MOUSE_BUTTON_UP_DOWN:
+ eventTarget = UNTIL_MOUSE_BUTTON_DOWN;
+ foundTarget = false;
+ break;
+ case UNTIL_MOUSE_BUTTON_DOWN_UP:
+ eventTarget = UNTIL_MOUSE_BUTTON_UP;
+ foundTarget = false;
+ break;
+ case UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED:
+ eventTarget = UNTIL_WAIT_ENDED;
+ checkWaitEnd = true;
+ foundTarget = false;
+ break;
+ }
+ }
+
+ foundTarget |= (checkWaitEnd && waitEnded);
}
- g_system->updateScreen();
- g_system->delayMillis(20);
- } while (g_system->getMillis() < nextFrame);
+ int eventsCheckedAfterPolling = eventsChecked;
+
+ bool eventQueueEmpty = (eventsCheckedBeforePolling == eventsCheckedAfterPolling);
- mouseData.left = mouseLeft;
- mouseData.right = mouseRight;
+ if (eventQueueEmpty) {
+ uint32 now = g_system->getMillis();
+ frameEnded = (now >= frameEnd);
+ waitEnded = (now >= waitEnd);
+ }
- g_system->getAudioCDManager()->update();
+ if (eventTarget == UNTIL_WAIT_ENDED) {
+ foundTarget = waitEnded;
+ }
+
+ if (eventTarget == EMPTY_EVENT_QUEUE) {
+ foundTarget = eventQueueEmpty;
+ }
+
+ foundTarget |= (checkWaitEnd && waitEnded);
+
+ if (!foundTarget && eventsChecked == 0) {
+ // If there are no events to check then
+ // add an empty event to check the current state.
+ eventMan->pushEvent(Common::Event());
+ continue;
+ }
+
+ updateScreen = updateAudio = (foundTarget || frameEnded);
+
+ if (updateScreen) {
+ if (callSource != EXECUTE_PLAYER_INPUT) {
+ g_system->updateScreen();
+ } else {
+ // Make the command line (e.g. "EXAMINE DOOR" -> "EXAMINE BUTTON")
+ // responsive by updating it here.
+ if (allowPlayerInput && playerCommand != -1 && !mouseLeft && !mouseRight) {
+ // A player command is given, left and right mouse buttons are up
+ Common::String oldCommand = renderer->getCommand();
+ mousePos = eventMan->getMousePos();
+ playerCommandMouseLeftRightUp(mousePos.x, mousePos.y);
+ if (!oldCommand.equals(renderer->getCommand())) {
+ renderer->drawCommand();
+ }
+ }
+
+ renderer->blit();
+ }
+ }
+
+ if (updateAudio) {
+ g_system->getAudioCDManager()->update(); // For Future Wars CD version
+ }
+
+ if (frameEnded) {
+ frameEnd += 20;
+ }
+
+ g_system->delayMillis(10);
+ } while (!foundTarget && !g_cine->shouldQuit());
+
+ if (useMaxMouseButtonState) {
+ mouseData.left = maxMouseLeft;
+ mouseData.right = maxMouseRight;
+ } else {
+ mouseData.left = mouseLeft;
+ mouseData.right = mouseRight;
+ }
}
void getMouseData(uint16 param, uint16 *pButton, uint16 *pX, uint16 *pY) {
@@ -235,14 +402,6 @@ void getMouseData(uint16 param, uint16 *pButton, uint16 *pX, uint16 *pY) {
}
}
-int getKeyData() {
- int k = lastKeyStroke;
-
- lastKeyStroke = -1;
-
- return k;
-}
-
/** Removes elements from seqList that have their member variable var4 set to value -1. */
void purgeSeqList() {
Common::List<SeqListElement>::iterator it = g_cine->_seqList.begin();
@@ -260,7 +419,6 @@ void purgeSeqList() {
void CineEngine::mainLoop(int bootScriptIdx) {
bool playerAction;
byte di;
- uint16 mouseButton;
if (_preLoad == false) {
resetBgIncrustList();
@@ -281,7 +439,10 @@ void CineEngine::mainLoop(int bootScriptIdx) {
allowPlayerInput = 0;
checkForPendingDataLoadSwitch = 0;
- fadeRequired = false;
+ reloadBgPalOnNextFlip = 0;
+ forbidBgPalReload = 0;
+ gfxFadeOutCompleted = 0;
+ gfxFadeInRequested = 0;
isDrawCommandEnabled = 0;
waitForPlayerClick = 0;
menuCommandLen = 0;
@@ -298,6 +459,8 @@ void CineEngine::mainLoop(int bootScriptIdx) {
g_cine->_globalVars[VAR_LOW_MEMORY] = 0; // set to 1 to disable some animations, sounds etc.
}
+ renderer->setBlackPalette(true); // Sets _changePal = true
+
strcpy(newPrcName, "");
strcpy(newRelName, "");
strcpy(newObjectName, "");
@@ -336,7 +499,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
// 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 (hacksEnabled && g_cine->getGameType() == Cine::GType_OS) {
if (scumm_stricmp(renderer->getBgName(), "21.PI1") == 0 && g_cine->_objectTable[1].x == 204 && g_cine->_objectTable[1].y == 110) {
g_cine->_objectTable[1].y--; // Move the player character upward on-screen by one pixel
}
@@ -368,8 +531,12 @@ void CineEngine::mainLoop(int bootScriptIdx) {
setMouseCursor(MOUSE_CURSOR_CROSS);
}
+ if (gfxFadeInRequested) {
+ gfxFadeOutCompleted = 0;
+ }
+
if (renderer->ready()) {
- renderer->drawFrame();
+ renderer->drawFrame(true);
}
// NOTE: In the original Future Wars and Operation Stealth messages
@@ -384,27 +551,7 @@ void CineEngine::mainLoop(int bootScriptIdx) {
if (_messageLen < 800)
_messageLen = 800;
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
- } while (mouseButton != 0 && !shouldQuit());
-
- menuVar = 0;
-
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
- playerAction = (mouseButton != 0) || processKeyboard(menuVar);
- mainLoopSub6();
- } while (!playerAction && !shouldQuit());
-
- menuVar = 0;
-
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
- } while (mouseButton != 0 && !shouldQuit());
-
+ manageEvents(MAIN_LOOP_WAIT_FOR_PLAYER_CLICK, UNTIL_MOUSE_BUTTON_UP_DOWN_UP);
waitForPlayerClick = 0;
}
@@ -424,9 +571,6 @@ void CineEngine::mainLoop(int bootScriptIdx) {
menuCommandLen = 0;
}
}
-
- manageEvents();
-
} while (!shouldQuit() && !_restartRequested);
hideMouse();
diff --git a/engines/cine/main_loop.h b/engines/cine/main_loop.h
index 98e8f8878a..f357df2f1c 100644
--- a/engines/cine/main_loop.h
+++ b/engines/cine/main_loop.h
@@ -25,8 +25,31 @@
namespace Cine {
+enum CallSource {
+ EXECUTE_PLAYER_INPUT, ///< Called from executePlayerInput function.
+ MAIN_LOOP_WAIT_FOR_PLAYER_CLICK, ///< Called from mainLoop function's waiting for player click section.
+ MAKE_MENU_CHOICE, ///< Called from makeMenuChoice function.
+ MAKE_SYSTEM_MENU, ///< Called from makeSystemMenu function.
+ MAKE_TEXT_ENTRY_MENU, ///< Called from makeTextEntryMenu function.
+ PROCESS_INVENTORY, ///< Called from processInventory function.
+ WAIT_PLAYER_INPUT ///< Called from waitPlayerInput function.
+};
+
+enum EventTarget {
+ UNTIL_MOUSE_BUTTON_UP_DOWN_UP, ///< Wait until first mouse buttons all up, then at least one down, finally all up.
+ UNTIL_MOUSE_BUTTON_DOWN_UP, ///< Wait until first at least one mouse button down, finally all up.
+ UNTIL_MOUSE_BUTTON_UP, ///< Wait until all mouse buttons up.
+ UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED, ///< Wait until all mouse buttons up and wait period (getTimerDelay()) ended.
+ UNTIL_MOUSE_BUTTON_UP_DOWN, ///< Wait until first all mouse buttons up, finally at least one down.
+ UNTIL_MOUSE_BUTTON_DOWN, ///< Wait until at least one mouse button down.
+ UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS, ///< Wait until at least one mouse button down, up key pressed, down key pressed or mouse position in at least one of the given rectangles.
+ UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT, ///< Wait until at least one mouse button down or a key pressed.
+ UNTIL_WAIT_ENDED, ///< Wait until wait period (getTimerDelay()) ended.
+ EMPTY_EVENT_QUEUE ///< Empty the event queue.
+};
+
void mainLoop(int bootScriptIdx);
-void manageEvents();
+void manageEvents(CallSource callSource, EventTarget eventTarget, bool useMaxMouseButtonState = false, Common::Array<Common::Rect> rects = Common::Array<Common::Rect>());
} // End of namespace Cine
diff --git a/engines/cine/msg.cpp b/engines/cine/msg.cpp
index 5366da4a05..78587f5771 100644
--- a/engines/cine/msg.cpp
+++ b/engines/cine/msg.cpp
@@ -29,12 +29,19 @@
namespace Cine {
-void loadMsg(char *pMsgName) {
+int16 loadMsg(char *pMsgName) {
uint32 sourceSize;
checkDataDisk(-1);
g_cine->_messageTable.clear();
- byte *dataPtr = readBundleFile(findFileInBundle(pMsgName), &sourceSize);
+
+ int16 foundFileIdx = findFileInBundle(pMsgName);
+ if (foundFileIdx < 0) {
+ warning("loadMsg(\"%s\"): Could not find file in bundle.", pMsgName);
+ return -1;
+ }
+
+ byte *dataPtr = readBundleFile(foundFileIdx, &sourceSize);
setMouseCursor(MOUSE_CURSOR_DISK);
@@ -67,6 +74,7 @@ void loadMsg(char *pMsgName) {
}
free(dataPtr);
+ return 0;
}
} // End of namespace Cine
diff --git a/engines/cine/msg.h b/engines/cine/msg.h
index dce70c4295..b86f1f7296 100644
--- a/engines/cine/msg.h
+++ b/engines/cine/msg.h
@@ -29,7 +29,7 @@ namespace Cine {
#define NUM_MAX_MESSAGE 255
-void loadMsg(char *pMsgName);
+int16 loadMsg(char *pMsgName);
} // End of namespace Cine
diff --git a/engines/cine/object.cpp b/engines/cine/object.cpp
index 5eabc79e8b..e538e202bb 100644
--- a/engines/cine/object.cpp
+++ b/engines/cine/object.cpp
@@ -39,7 +39,7 @@ void resetObjectTable() {
}
}
-void loadObject(char *pObjectName) {
+int16 loadObject(char *pObjectName) {
debug(5, "loadObject(\"%s\")", pObjectName);
uint16 numEntry;
uint16 entrySize;
@@ -48,7 +48,12 @@ void loadObject(char *pObjectName) {
checkDataDisk(-1);
- ptr = dataPtr = readBundleFile(findFileInBundle(pObjectName));
+ int16 foundFileIdx = findFileInBundle(pObjectName);
+ if (foundFileIdx < 0) {
+ return -1;
+ }
+
+ ptr = dataPtr = readBundleFile(foundFileIdx);
setMouseCursor(MOUSE_CURSOR_DISK);
@@ -59,7 +64,19 @@ void loadObject(char *pObjectName) {
assert(numEntry <= NUM_MAX_OBJECT);
for (i = 0; i < numEntry; i++) {
- if (g_cine->_objectTable[i].costume != -2 && g_cine->_objectTable[i].costume != -3) { // flag is keep?
+ bool overwrite =
+ (g_cine->getGameType() == Cine::GType_FW && g_cine->_objectTable[i].costume != -2) ||
+ (g_cine->getGameType() == Cine::GType_OS && g_cine->_objectTable[i].costume != -3);
+
+ // HACK: Fix handling of electric razor and cable in Amiga version of Operation Stealth
+ // when entering the Dr. Why's control room.
+ if (hacksEnabled && g_cine->getPlatform() == Common::kPlatformAmiga &&
+ g_cine->getGameType() == Cine::GType_OS && (i == 231 || i == 232) &&
+ scumm_stricmp(pObjectName, "SALLE59.REL") == 0) {
+ overwrite = false;
+ }
+
+ if (overwrite) {
Common::MemoryReadStream readS(ptr, entrySize);
g_cine->_objectTable[i].x = readS.readSint16BE();
@@ -80,6 +97,7 @@ void loadObject(char *pObjectName) {
}
free(dataPtr);
+ return 0;
}
/**
diff --git a/engines/cine/object.h b/engines/cine/object.h
index 9c44162d28..ae959e67f9 100644
--- a/engines/cine/object.h
+++ b/engines/cine/object.h
@@ -61,7 +61,7 @@ struct overlay {
#define NUM_MAX_VAR 255
void resetObjectTable();
-void loadObject(char *pObjectName);
+int16 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 18f260cab7..21cdc068e8 100644
--- a/engines/cine/pal.cpp
+++ b/engines/cine/pal.cpp
@@ -140,19 +140,29 @@ int power(int base, int power) {
}
} // end of anonymous namespace
-// a.k.a. palRotate
-Palette &Palette::rotateRight(byte firstIndex, byte lastIndex, signed rotationAmount) {
- debug(1, "Palette::rotateRight(firstIndex: %d, lastIndex: %d, rotationAmount:%d)", firstIndex, lastIndex, rotationAmount);
- assert(rotationAmount >= 0);
+Palette &Palette::rotateRight(byte firstIndex, byte lastIndex) {
+ debug(1, "Palette::rotateRight(firstIndex: %d, lastIndex: %d)", firstIndex, lastIndex);
- for (int j = 0; j < rotationAmount; j++) {
- const Color lastColor = _colors[lastIndex];
+ const Color lastColor = _colors[lastIndex];
- for (int i = lastIndex; i > firstIndex; i--)
- _colors[i] = _colors[i - 1];
+ for (int i = lastIndex; i > firstIndex; i--)
+ _colors[i] = _colors[i - 1];
+
+ _colors[firstIndex] = lastColor;
+
+ return *this;
+}
+
+Palette &Palette::rotateLeft(byte firstIndex, byte lastIndex) {
+ debug(1, "Palette::rotateLeft(firstIndex: %d, lastIndex: %d)", firstIndex, lastIndex);
+
+ const Color firstColor = _colors[firstIndex];
+
+ for (int i = firstIndex; i < lastIndex; i++)
+ _colors[i] = _colors[i + 1];
+
+ _colors[lastIndex] = firstColor;
- _colors[firstIndex] = lastColor;
- }
return *this;
}
@@ -164,6 +174,47 @@ uint Palette::colorCount() const {
return _colors.size();
}
+byte Palette::brightness(byte colorIndex) {
+ return (byte) ((_colors[colorIndex].r*19 +
+ _colors[colorIndex].g*38 +
+ _colors[colorIndex].b*7) / 64);
+}
+
+bool Palette::isEqual(byte index1, byte index2) {
+ return _colors[index1].r == _colors[index2].r &&
+ _colors[index1].g == _colors[index2].g &&
+ _colors[index1].b == _colors[index2].b;
+}
+
+int Palette::findMinBrightnessColorIndex(uint minColorIndex) {
+ int minFoundBrightness = 999;
+ int foundColorIndex = 0;
+ for (uint i = minColorIndex; i < colorCount(); i++) {
+ byte currColorBrightness = brightness(i);
+ if (currColorBrightness < minFoundBrightness) {
+ minFoundBrightness = currColorBrightness;
+ foundColorIndex = i;
+ }
+ }
+
+ return (_colors.size() >= 3 && isEqual(2, foundColorIndex)) ? 0 : foundColorIndex;
+}
+
+bool Palette::ensureContrast(byte &minBrightnessColorIndex) {
+ minBrightnessColorIndex = findMinBrightnessColorIndex();
+ if (_colors.size() >= 3 && isEqual(2, minBrightnessColorIndex)) {
+ Color black{ 0, 0, 0 };
+ Color white{_format.rMax(), _format.gMax(), _format.bMax() };
+
+ _colors[2] = white;
+ if (isEqual(2, minBrightnessColorIndex)) {
+ _colors[minBrightnessColorIndex] = black;
+ }
+ return true;
+ }
+ return false;
+}
+
Palette &Palette::fillWithBlack() {
for (uint i = 0; i < _colors.size(); i++) {
_colors[i].r = 0;
@@ -187,10 +238,15 @@ const Graphics::PixelFormat &Palette::colorFormat() const {
void Palette::setGlobalOSystemPalette() const {
byte buf[256 * 3]; // Allocate space for the largest possible palette
+
+ if (g_cine->mayHave256Colors()) {
+ memset(buf, 0, sizeof(buf)); // Clear whole palette
+ }
+
// The color format used by OSystem's setPalette-function:
save(buf, sizeof(buf), Graphics::PixelFormat(3, 8, 8, 8, 0, 0, 8, 16, 0), CINE_LITTLE_ENDIAN);
- if (g_cine->getPlatform() == Common::kPlatformAmiga && colorCount() == 16) {
+ if (renderer->useTransparentDialogBoxes() && colorCount() == 16) {
// The Amiga version of Future Wars does use the upper 16 colors for a darkened
// game palette to allow transparent dialog boxes. To support that in our code
// we do calculate that palette over here and append it to the screen palette.
@@ -198,6 +254,13 @@ void Palette::setGlobalOSystemPalette() const {
buf[16 * 3 + i] = buf[i] >> 1;
g_system->getPaletteManager()->setPalette(buf, 0, colorCount() * 2);
+ } else if (g_cine->mayHave256Colors()) {
+ // If 256 colors are possible then always set 256 colors
+ // because resources may be a combination of 16 colors and 256 colors
+ // and going from a 16 color screen to a 256 color screen (e.g. when leaving
+ // the rat maze on Dr. Why's island in Operation Stealth) may leave
+ // the upper 240 colors not faded out.
+ g_system->getPaletteManager()->setPalette(buf, 0, 256);
} else {
g_system->getPaletteManager()->setPalette(buf, 0, colorCount());
}
@@ -265,6 +328,19 @@ Palette::Palette(const Graphics::PixelFormat format, const uint numColors) : _fo
fillWithBlack();
}
+Palette::Palette(const Palette& other) :
+ _format(other._format),
+ _colors(other._colors) {
+}
+
+Palette& Palette::operator=(const Palette& other) {
+ if (this != &other) {
+ _format = other._format;
+ _colors = other._colors;
+ }
+ return *this;
+}
+
Palette &Palette::clear() {
_format = Graphics::PixelFormat();
_colors.clear();
diff --git a/engines/cine/pal.h b/engines/cine/pal.h
index 6dc56d8c19..482715acee 100644
--- a/engines/cine/pal.h
+++ b/engines/cine/pal.h
@@ -67,6 +67,8 @@ public:
* @note For the default constructed object (i.e. no parameters given) this will hold: empty() && !isValid()
*/
Palette(const Graphics::PixelFormat format = Graphics::PixelFormat(), const uint numColors = 0);
+ Palette(const Palette& other);
+ Palette& operator=(const Palette& other);
/**
* Clear the palette (Set color count to zero, release memory, overwrite color format with default value).
@@ -113,10 +115,10 @@ public:
byte *save(byte *buf, const uint size, const Graphics::PixelFormat format, const uint numColors, const EndianType endian, const byte firstIndex = 0) const;
/**
- * Rotate the palette in color range [firstIndex, lastIndex] to the right by the specified rotation amount.
- * @param rotationAmount Amount to rotate the sub-palette to the right. Only values 0 and 1 are currently supported!
+ * Rotate the palette in color range [firstIndex, lastIndex] to the right by one.
*/
- Palette &rotateRight(byte firstIndex, byte lastIndex, signed rotationAmount = 1);
+ Palette &rotateRight(byte firstIndex, byte lastIndex);
+ Palette &rotateLeft(byte firstIndex, byte lastIndex);
Palette &saturatedAddColor(Palette &output, byte firstIndex, byte lastIndex, signed r, signed g, signed b) const;
/**
@@ -146,8 +148,7 @@ public:
Palette &saturatedAddNormalizedGray(Palette &output, byte firstIndex, byte lastIndex, signed grayDividend, signed grayDenominator) const;
bool empty() const;
- uint colorCount() const;
-
+ uint colorCount() const;
Palette &fillWithBlack();
/** Is the palette valid? (Mostly just checks the color format for correctness) */
@@ -171,7 +172,12 @@ public:
/** Get the blue color component of the color at the given palette index. */
uint8 getB(byte index) const;
+ bool ensureContrast(byte &minBrightnessColorIndex);
+ bool isEqual(byte index1, byte index2);
+
private:
+ int findMinBrightnessColorIndex(uint minColorIndex = 1);
+ byte brightness(byte colorIndex);
void setColorFormat(const Graphics::PixelFormat format);
// WORKAROUND: Using a reference to a result here instead of returning an Color object.
diff --git a/engines/cine/part.cpp b/engines/cine/part.cpp
index 30d9461a6a..e9161c309e 100644
--- a/engines/cine/part.cpp
+++ b/engines/cine/part.cpp
@@ -22,6 +22,7 @@
#include "common/debug.h"
#include "common/endian.h"
+#include "common/memstream.h"
#include "common/textconsole.h"
#include "cine/cine.h"
@@ -71,6 +72,7 @@ static Common::String fixVolCnfFileName(const uint8 *src, uint len) {
char tmp[14];
memcpy(tmp, src, len);
tmp[len] = 0;
+ Common::String result;
if (len == 11) {
// Filenames of length 11 have no separation of the extension and the basename
@@ -93,14 +95,15 @@ static Common::String fixVolCnfFileName(const uint8 *src, uint len) {
tmp[8] = 0; // Force separation of extension and basename
Common::String basename(tmp);
if (extension.empty()) {
- return basename;
+ result = basename;
} else {
- return basename + "." + extension;
+ result = basename + "." + extension;
}
} else {
// Filenames of length 13 are okay as they are, no need for conversion
- return Common::String(tmp);
+ result = Common::String(tmp);
}
+ return result;
}
void CineEngine::readVolCnf() {
@@ -127,14 +130,19 @@ void CineEngine::readVolCnf() {
error("Error while unpacking 'vol.cnf' data");
}
delete[] packedBuf;
+ Common::Array<VolumeResource> volumeResourceFiles;
uint8 *p = buf;
- int resourceFilesCount = READ_BE_UINT16(p); p += 2;
- int entrySize = READ_BE_UINT16(p); p += 2;
+ int resourceFilesCount = READ_BE_INT16(p); p += 2;
+ int entrySize = READ_BE_INT16(p); p += 2;
for (int i = 0; i < resourceFilesCount; ++i) {
- char volumeResourceFile[9];
- memcpy(volumeResourceFile, p, 8);
- volumeResourceFile[8] = 0;
- _volumeResourceFiles.push_back(volumeResourceFile);
+ Common::MemoryReadStream readS(p, 0x14);
+ VolumeResource res;
+ readS.read(res.name, sizeof(res.name));
+ res.name[sizeof(res.name) - 1] = 0;
+ res.pNamesList = readS.readUint32BE();
+ res.diskNum = readS.readSint16BE();
+ res.sizeOfNamesList = readS.readUint32BE();
+ volumeResourceFiles.push_back(res);
p += entrySize;
}
@@ -170,11 +178,15 @@ void CineEngine::readVolCnf() {
p = buf + 4 + resourceFilesCount * entrySize;
for (int i = 0; i < resourceFilesCount; ++i) {
+ const VolumeResource& volRes = volumeResourceFiles[i];
int count = READ_BE_UINT32(p) / fileNameLength; p += 4;
while (count--) {
Common::String volumeEntryName = fixVolCnfFileName(p, fileNameLength);
- _volumeEntriesMap.setVal(volumeEntryName, _volumeResourceFiles[i].c_str());
- debugC(5, kCineDebugPart, "Added volume entry name '%s' resource file '%s'", volumeEntryName.c_str(), _volumeResourceFiles[i].c_str());
+ if (!_volumeEntriesMap.contains(volumeEntryName)) {
+ _volumeEntriesMap.setVal(volumeEntryName, Common::Array<VolumeResource>());
+ }
+ _volumeEntriesMap.find(volumeEntryName)->_value.push_back(volRes);
+ debugC(5, kCineDebugPart, "Added volume entry name '%s', disk number %d, resource file '%s'", volumeEntryName.c_str(), volRes.diskNum, volRes.name);
p += fileNameLength;
}
}
@@ -183,21 +195,45 @@ void CineEngine::readVolCnf() {
}
int16 findFileInBundle(const char *fileName) {
+ // HACK: Fix underwater background palette by reading it from correct file
+ if (hacksEnabled && g_cine->getGameType() == Cine::GType_OS &&
+ scumm_stricmp(currentPrcName, "SOUSMAR2.PRC") == 0) {
+ Common::Array<VolumeResource> volRes = g_cine->_volumeEntriesMap.find(fileName)->_value;
+ if (volRes.size() == 2 && scumm_stricmp(volRes[0].name, "rsc12") == 0 &&
+ scumm_stricmp(volRes[1].name, "rsc08") == 0 &&
+ (scumm_stricmp(fileName, "39.PI1") == 0 ||
+ scumm_stricmp(fileName, "SP39_11.SET") == 0 ||
+ scumm_stricmp(fileName, "SP39_12.SET") == 0)) {
+ debugC(5, kCineDebugPart, "Reading underwater background and fish from file rsc12 for the original (broken) palette.");
+ loadPart("rsc08");
+ }
+ }
+
if (g_cine->getGameType() == Cine::GType_OS) {
- // look first in currently loaded resource file
for (uint i = 0; i < g_cine->_partBuffer.size(); i++) {
if (!scumm_stricmp(fileName, g_cine->_partBuffer[i].partName)) {
return i;
}
}
- // not found, open the required resource file
- StringPtrHashMap::const_iterator it = g_cine->_volumeEntriesMap.find(fileName);
+
+ StringToVolumeResourceArrayHashMap::const_iterator it = g_cine->_volumeEntriesMap.find(fileName);
if (it == g_cine->_volumeEntriesMap.end()) {
warning("Unable to find part file for filename '%s'", fileName);
return -1;
}
- const char *part = (*it)._value;
- loadPart(part);
+
+ // Prefer current disk's resource file
+ Common::Array<VolumeResource> volRes = it->_value;
+ VolumeResource match = volRes[0];
+ for (int i = 0; i < volRes.size(); i++) {
+ if (volRes[i].diskNum == currentDisk) {
+ match = volRes[i];
+ break;
+ }
+ }
+
+ checkDataDisk(match.diskNum);
+ loadPart(match.name);
}
for (uint i = 0; i < g_cine->_partBuffer.size(); i++) {
if (!scumm_stricmp(fileName, g_cine->_partBuffer[i].partName)) {
@@ -238,7 +274,16 @@ byte *readBundleFile(int16 foundFileIdx, uint32 *size) {
return dataPtr;
}
-byte *readBundleSoundFile(const char *entryName, uint32 *size) {
+byte *readBundleSoundFileOS(const char *entryName, uint32 *size) {
+ int16 index = findFileInBundle(entryName);
+ if (index == -1) {
+ return NULL;
+ }
+
+ return readBundleFile(index, size);
+}
+
+byte *readBundleSoundFileFW(const char *entryName, uint32 *size) {
int16 index;
byte *data = 0;
char previousPartName[15] = "";
@@ -260,6 +305,14 @@ byte *readBundleSoundFile(const char *entryName, uint32 *size) {
return data;
}
+byte *readBundleSoundFile(const char *entryName, uint32 *size) {
+ if (g_cine->getGameType() == Cine::GType_FW) {
+ return readBundleSoundFileFW(entryName, size);
+ } else {
+ return readBundleSoundFileOS(entryName, size);
+ }
+}
+
/** Rotate byte value to the left by n bits */
byte rolByte(byte value, uint n) {
n %= 8;
@@ -292,7 +345,10 @@ byte *readFile(const char *filename, bool crypted) {
return dataPtr;
}
-void checkDataDisk(int16 param) {
+void checkDataDisk(int16 diskNum) {
+ if (diskNum != -1) {
+ currentDisk = diskNum;
+ }
}
void dumpBundle(const char *fileName) {
diff --git a/engines/cine/prc.cpp b/engines/cine/prc.cpp
index 55d3504b7e..553f2f80ac 100644
--- a/engines/cine/prc.cpp
+++ b/engines/cine/prc.cpp
@@ -62,6 +62,7 @@ bool loadPrc(const char *pPrcName) {
(!scumm_stricmp(pPrcName, BOOT_PRC_NAME) || !scumm_stricmp(pPrcName, "demo.prc"))) {
scriptPtr = dataPtr = readFile(pPrcName, (g_cine->getFeatures() & GF_CRYPTED_BOOT_PRC) != 0);
} else {
+ checkDataDisk(-1);
scriptPtr = dataPtr = readBundleFile(findFileInBundle(pPrcName));
}
diff --git a/engines/cine/saveload.cpp b/engines/cine/saveload.cpp
index dfd3a1f4bc..8d8fcb43fc 100644
--- a/engines/cine/saveload.cpp
+++ b/engines/cine/saveload.cpp
@@ -23,6 +23,7 @@
#include "common/debug.h"
#include "common/savefile.h"
#include "common/textconsole.h"
+#include "common/translation.h"
#include "cine/cine.h"
#include "cine/bg_list.h"
@@ -30,6 +31,10 @@
#include "cine/sound.h"
#include "cine/various.h"
+#include "engines/metaengine.h"
+
+#include "gui/message.h"
+
namespace Cine {
int16 currentDisk;
@@ -69,13 +74,20 @@ enum CineSaveGameFormat detectSaveGameFormat(Common::SeekableReadStream &fHandle
// First check for the temporary Operation Stealth savegame format.
fHandle.seek(0);
ChunkHeader hdr;
- loadChunkHeader(fHandle, hdr);
+ bool loadedHeader = loadChunkHeader(fHandle, hdr);
fHandle.seek(prevStreamPos);
- if (hdr.id == TEMP_OS_FORMAT_ID) {
+
+ if (!loadedHeader) {
+ return ANIMSIZE_UNKNOWN;
+ } else if (hdr.id == TEMP_OS_FORMAT_ID) {
return TEMP_OS_FORMAT;
+ } else if (hdr.id == VERSIONED_FW_FORMAT_ID) {
+ return VERSIONED_FW_FORMAT;
+ } else if (hdr.id == VERSIONED_OS_FORMAT_ID) {
+ return VERSIONED_OS_FORMAT;
}
- // Ok, so the savegame isn't using the temporary Operation Stealth savegame format.
+ // Ok, so the savegame isn't using the newer savegame formats.
// Let's check for the plain Future Wars savegame format and its different versions then.
// The animDataTable begins at savefile position 0x2315.
// Each animDataTable entry takes 23 bytes in older saves (Revisions 21772-31443)
@@ -246,7 +258,7 @@ bool loadObjectTable(Common::SeekableReadStream &in) {
bool loadZoneData(Common::SeekableReadStream &in) {
for (int i = 0; i < 16; i++) {
- g_cine->_zoneData[i] = in.readUint16BE();
+ g_cine->_zoneData[i] = in.readSint16BE();
}
return !(in.eos() || in.err());
}
@@ -340,7 +352,7 @@ void saveObjectTable(Common::OutSaveFile &out) {
void saveZoneData(Common::OutSaveFile &out) {
for (int i = 0; i < 16; i++) {
- out.writeUint16BE(g_cine->_zoneData[i]);
+ out.writeSint16BE(g_cine->_zoneData[i]);
}
}
@@ -427,6 +439,10 @@ void saveBgIncrustList(Common::OutSaveFile &out) {
out.writeUint16BE(it->y);
out.writeUint16BE(it->frame);
out.writeUint16BE(it->part);
+
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ out.writeUint16BE(it->bgIdx);
+ }
}
}
@@ -471,7 +487,7 @@ bool CineEngine::loadSaveDirectory() {
// then the missing ones are correctly set to empty strings.
memset(currentSaveName, 0, sizeof(currentSaveName));
- fHandle->read(currentSaveName, 10 * 20);
+ fHandle->read(currentSaveName, sizeof(currentSaveName));
delete fHandle;
// Make sure all savegames' descriptions end with a trailing zero.
@@ -481,32 +497,53 @@ bool CineEngine::loadSaveDirectory() {
return true;
}
-bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
- char musicName[13];
- char bgNames[8][13];
+bool CineEngine::checkSaveHeaderData(const ChunkHeader& hdr) {
+ if (hdr.version > CURRENT_SAVE_VER) {
+ warning("checkSaveHeader: Detected newer format version. Not loading savegame");
+ return false;
+ } else if (hdr.version < CURRENT_SAVE_VER) {
+ debug(3, "checkSaveHeader: Loading older format version (%d < %d).", hdr.version, CURRENT_SAVE_VER);
+ } else {
+ debug(3, "checkSaveHeader: Found correct header (Both the identifier and version number match).");
+ }
- // First check the temporary Operation Stealth savegame format header.
+ // There shouldn't be any data in the header's chunk currently so it's an error if there is.
+ if (hdr.size > 0) {
+ warning("checkSaveHeader: Format header's chunk seems to contain data so format is incorrect. Not loading savegame");
+ return false;
+ }
+
+ return true;
+}
+
+bool CineEngine::loadVersionedSaveFW(Common::SeekableReadStream &in) {
ChunkHeader hdr;
loadChunkHeader(in, hdr);
- if (hdr.id != TEMP_OS_FORMAT_ID) {
- warning("loadTempSaveOS: File has incorrect identifier. Not loading savegame");
+ if (hdr.id != VERSIONED_FW_FORMAT_ID) {
+ warning("loadVersionedSaveFW: File has incorrect identifier. Not loading savegame");
return false;
- } else if (hdr.version > CURRENT_OS_SAVE_VER) {
- warning("loadTempSaveOS: Detected newer format version. Not loading savegame");
+ } else if (!checkSaveHeaderData(hdr)) {
+ warning("loadVersionedSaveFW: Detected incompatible savegame. Not loading savegame");
return false;
- } else if ((int)hdr.version < (int)CURRENT_OS_SAVE_VER) {
- warning("loadTempSaveOS: Detected older format version. Trying to load nonetheless. Things may break");
- } else { // hdr.id == TEMP_OS_FORMAT_ID && hdr.version == CURRENT_OS_SAVE_VER
- debug(3, "loadTempSaveOS: Found correct header (Both the identifier and version number match).");
}
- // There shouldn't be any data in the header's chunk currently so it's an error if there is.
- if (hdr.size > 0) {
- warning("loadTempSaveOS: Format header's chunk seems to contain data so format is incorrect. Not loading savegame");
+ return loadPlainSaveFW(in, ANIMSIZE_30_PTRS_INTACT, hdr.version);
+}
+
+bool CineEngine::loadVersionedSaveOS(Common::SeekableReadStream &in) {
+ char bgNames[8][13];
+
+ ChunkHeader hdr;
+ loadChunkHeader(in, hdr);
+ if (hdr.id != VERSIONED_OS_FORMAT_ID && hdr.id != TEMP_OS_FORMAT_ID) {
+ warning("loadVersionedSaveOS: File has incorrect identifier. Not loading savegame");
+ return false;
+ } else if (!checkSaveHeaderData(hdr)) {
+ warning("loadVersionedSaveOS: Detected incompatible savegame. Not loading savegame");
return false;
}
- // Ok, so we've got a correct header for a temporary Operation Stealth savegame.
+ // Ok, so we've got a correct header for an Operation Stealth savegame.
// Let's start loading the plain savegame data then.
currentDisk = in.readUint16BE();
in.read(currentPartName, 13);
@@ -532,9 +569,6 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
// semantic differences result in having to do things
// in a different order).
{
- // Not sure if this is needed with Operation Stealth...
- checkDataDisk(currentDisk);
-
if (strlen(currentPrcName)) {
loadPrc(currentPrcName);
}
@@ -543,15 +577,6 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
loadRel(currentRelName);
}
- // Reset background music in CD version of Future Wars
- if (getGameType() == GType_FW && (getFeatures() & GF_CD)) {
- if (strlen(bgNames[0])) {
- char buffer[20];
- removeExtention(buffer, bgNames[0]);
- g_sound->setBgMusic(atoi(buffer + 1));
- }
- }
-
// Load first background (Uses loadBg)
if (strlen(bgNames[0])) {
loadBg(bgNames[0]);
@@ -560,7 +585,7 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
// Add backgrounds 1-7 (Uses addBackground)
for (int i = 1; i < 8; i++) {
if (strlen(bgNames[i])) {
- addBackground(bgNames[i], i);
+ renderer->addBackground(bgNames[i], i);
}
}
@@ -580,44 +605,39 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
renderer->setCommand(g_cine->_commandBuffer);
loadZoneQuery(in);
- // TODO: Use the loaded string (Current music name (String, 13 bytes)).
- in.read(musicName, 13);
+ // Current music name (String, 13 bytes).
+ in.read(currentDatName, 13);
// TODO: Use the loaded value (Is music loaded? (Uint16BE, Boolean)).
in.readUint16BE();
- // TODO: Use the loaded value (Is music playing? (Uint16BE, Boolean)).
- in.readUint16BE();
+ // Is music playing? (Uint16BE, Boolean).
+ musicIsPlaying = in.readUint16BE();
renderer->_cmdY = in.readUint16BE();
- in.readUint16BE(); // Some unknown variable that seems to always be zero
+ bgVar0 = in.readUint16BE();
allowPlayerInput = in.readUint16BE();
playerCommand = in.readUint16BE();
commandVar1 = in.readUint16BE();
isDrawCommandEnabled = in.readUint16BE();
- var5 = in.readUint16BE();
+ lastType20OverlayBgIdx = in.readUint16BE();
var4 = in.readUint16BE();
var3 = in.readUint16BE();
var2 = in.readUint16BE();
commandVar2 = in.readUint16BE();
renderer->_messageBg = in.readUint16BE();
- // TODO: Use the loaded value (adBgVar1 (Uint16BE)).
- in.readUint16BE();
-
- currentAdditionalBgIdx = in.readSint16BE();
- currentAdditionalBgIdx2 = in.readSint16BE();
+ reloadBgPalOnNextFlip = in.readUint16BE(); // From Operation Stealth's disassembly
- // TODO: Check whether the scroll value really gets used correctly after this.
- // Note that the backgrounds are loaded only later than this value is set.
+ renderer->selectBg(in.readSint16BE());
+ renderer->selectScrollBg(in.readSint16BE());
renderer->setScroll(in.readUint16BE());
- // TODO: Use the loaded value (adBgVar0 (Uint16BE). Maybe this means bgVar0?).
- in.readUint16BE();
+ forbidBgPalReload = in.readUint16BE();
disableSystemMenu = in.readUint16BE();
- // TODO: adBgVar1 = 1 here
+ reloadBgPalOnNextFlip = 1; // From Operation Stealth's disassembly
// Load the animDataTable entries
in.readUint16BE(); // Entry count (255 in the PC version of Operation Stealth).
@@ -629,7 +649,7 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
loadObjectScripts(in);
loadSeqList(in);
loadOverlayList(in);
- loadBgIncrustFromSave(in);
+ loadBgIncrustFromSave(in, (int)hdr.version >= 2);
// Left this here instead of moving it earlier in this function with
// the other current value loadings (e.g. loading of current procedure,
@@ -639,19 +659,17 @@ bool CineEngine::loadTempSaveOS(Common::SeekableReadStream &in) {
loadMsg(currentMsgName);
}
- // TODO: Add current music loading and playing here
- // TODO: Palette handling?
-
- if (in.pos() == in.size()) {
- debug(3, "loadTempSaveOS: Loaded the whole savefile.");
- } else {
- warning("loadTempSaveOS: Loaded the savefile but didn't exhaust it completely. Something was left over");
+ if (strlen(currentDatName)) {
+ g_sound->loadMusic(currentDatName);
+ if (musicIsPlaying) {
+ g_sound->playMusic();
+ }
}
return !(in.eos() || in.err());
}
-bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat) {
+bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFormat saveGameFormat, uint32 version) {
char bgName[13];
// At savefile position 0x0000:
@@ -707,7 +725,7 @@ bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFor
loadObjectTable(in);
// At 0x2043 (i.e. 0x005F + 2 * 2 + 255 * 32):
- renderer->restorePalette(in, 0);
+ renderer->restorePalette(in, version);
// At 0x2083 (i.e. 0x2043 + 16 * 2 * 2):
g_cine->_globalVars.load(in, NUM_MAX_VAR);
@@ -738,7 +756,7 @@ bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFor
// At 0x2303:
isDrawCommandEnabled = in.readUint16BE();
// At 0x2305:
- var5 = in.readUint16BE();
+ lastType20OverlayBgIdx = in.readUint16BE();
// At 0x2307:
var4 = in.readUint16BE();
// At 0x2309:
@@ -765,6 +783,8 @@ bool CineEngine::loadPlainSaveFW(Common::SeekableReadStream &in, CineSaveGameFor
loadOverlayList(in);
loadBgIncrustFromSave(in);
+ disableSystemMenu = ((version >= 4) ? in.readUint16BE() : 0);
+
if (strlen(currentMsgName)) {
loadMsg(currentMsgName);
}
@@ -831,18 +851,30 @@ bool CineEngine::makeLoad(const Common::String &saveName) {
// then let's try the default format and hope for the best.
warning("Couldn't detect the used savegame format, trying default savegame format. Things may break");
saveGameFormat = ANIMSIZE_30_PTRS_INTACT;
+ } else if (saveGameFormat == TEMP_OS_FORMAT) {
+ GUI::MessageDialog alert(_("WARNING: The savegame you are loading is using "
+ "a temporary broken format. Things will be broken. Please consider starting "
+ "Operation Stealth from beginning using new savegames."),
+ _("Load anyway"), _("Cancel"));
+ load = (alert.runModal() == GUI::kMessageOK);
}
if (load) {
// Reset the engine's state
resetEngine();
- if (saveGameFormat == TEMP_OS_FORMAT) {
- // Load the temporary Operation Stealth savegame format
- result = loadTempSaveOS(*in);
+ if (saveGameFormat == VERSIONED_FW_FORMAT) {
+ result = loadVersionedSaveFW(*in);
+ } else if (saveGameFormat == VERSIONED_OS_FORMAT || saveGameFormat == TEMP_OS_FORMAT) {
+ result = loadVersionedSaveOS(*in);
} else {
- // Load the plain Future Wars savegame format
- result = loadPlainSaveFW(*in, saveGameFormat);
+ // Load the plain Future Wars savegame format using version number 0
+ result = loadPlainSaveFW(*in, saveGameFormat, 0);
+ }
+
+ ExtendedSavegameHeader header;
+ if (MetaEngine::readSavegameHeader(saveFile.get(), &header)) {
+ setTotalPlayTime(header.playtime);
}
}
@@ -851,7 +883,19 @@ bool CineEngine::makeLoad(const Common::String &saveName) {
return result;
}
+void CineEngine::writeSaveHeader(Common::OutSaveFile &out, uint32 headerId) {
+ ChunkHeader header;
+ header.id = headerId;
+ header.version = CURRENT_SAVE_VER;
+ header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it.
+ writeChunkHeader(out, header);
+}
+
void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
+ // Make a Future Wars savegame format chunk header and save it.
+ writeSaveHeader(out, VERSIONED_FW_FORMAT_ID);
+
+ // Start outputting the plain savegame data right after the chunk header.
out.writeUint16BE(currentDisk);
out.write(currentPartName, 13);
out.write(currentDatName, 13);
@@ -875,7 +919,7 @@ void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
out.writeUint16BE(playerCommand);
out.writeUint16BE(commandVar1);
out.writeUint16BE(isDrawCommandEnabled);
- out.writeUint16BE(var5);
+ out.writeUint16BE(lastType20OverlayBgIdx);
out.writeUint16BE(var4);
out.writeUint16BE(var3);
out.writeUint16BE(var2);
@@ -892,23 +936,11 @@ void CineEngine::makeSaveFW(Common::OutSaveFile &out) {
}
/**
- * Save an Operation Stealth type savegame. WIP!
- *
- * NOTE: This is going to be very much a work in progress so the Operation Stealth's
- * savegame formats that are going to be tried are extremely probably not going
- * to be supported at all after Operation Stealth becomes officially supported.
- * This means that the savegame format will hopefully change to something nicer
- * when official support for Operation Stealth begins.
+ * Save an Operation Stealth type savegame.
*/
void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
- int i;
-
- // Make a temporary Operation Stealth savegame format chunk header and save it.
- ChunkHeader header;
- header.id = TEMP_OS_FORMAT_ID;
- header.version = CURRENT_OS_SAVE_VER;
- header.size = 0; // No data is currently put inside the chunk, all the plain data comes right after it.
- writeChunkHeader(out, header);
+ // Make an Operation Stealth savegame format chunk header and save it.
+ writeSaveHeader(out, VERSIONED_OS_FORMAT_ID);
// Start outputting the plain savegame data right after the chunk header.
out.writeUint16BE(currentDisk);
@@ -927,42 +959,36 @@ void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
saveCommandBuffer(out);
saveZoneQuery(out);
- // FIXME: Save a proper name here, saving an empty string currently.
// 0x2925: Current music name (String, 13 bytes).
- for (i = 0; i < 13; i++) {
- out.writeByte(0);
- }
+ out.write(currentDatName, 13);
+
// FIXME: Save proper value for this variable, currently writing zero
// 0x2932: Is music loaded? (Uint16BE, Boolean).
out.writeUint16BE(0);
- // FIXME: Save proper value for this variable, currently writing zero
+
// 0x2934: Is music playing? (Uint16BE, Boolean).
- out.writeUint16BE(0);
+ out.writeUint16BE(musicIsPlaying);
out.writeUint16BE(renderer->_cmdY);
- out.writeUint16BE(0); // Some unknown variable that seems to always be zero
+ out.writeUint16BE(bgVar0);
out.writeUint16BE(allowPlayerInput);
out.writeUint16BE(playerCommand);
out.writeUint16BE(commandVar1);
out.writeUint16BE(isDrawCommandEnabled);
- out.writeUint16BE(var5);
+ out.writeUint16BE(lastType20OverlayBgIdx);
out.writeUint16BE(var4);
out.writeUint16BE(var3);
out.writeUint16BE(var2);
out.writeUint16BE(commandVar2);
out.writeUint16BE(renderer->_messageBg);
- // FIXME: Save proper value for this variable, currently writing zero.
- // An unknown variable at 0x295E: adBgVar1 (Uint16BE).
- out.writeUint16BE(0);
- out.writeSint16BE(currentAdditionalBgIdx);
- out.writeSint16BE(currentAdditionalBgIdx2);
- // FIXME: Save proper value for this variable, currently writing zero.
+ out.writeUint16BE(reloadBgPalOnNextFlip);
+ out.writeSint16BE(renderer->currentBg());
+ out.writeSint16BE(renderer->scrollBg());
+
// 0x2954: additionalBgVScroll (Uint16BE). This probably means renderer->_bgShift.
- out.writeUint16BE(0);
- // FIXME: Save proper value for this variable, currently writing zero.
- // An unknown variable at 0x2956: adBgVar0 (Uint16BE). Maybe this means bgVar0?
- out.writeUint16BE(0);
+ out.writeUint16BE(renderer->getScroll());
+ out.writeUint16BE(forbidBgPalReload);
out.writeUint16BE(disableSystemMenu);
saveAnimDataTable(out);
@@ -974,7 +1000,8 @@ void CineEngine::makeSaveOS(Common::OutSaveFile &out) {
saveBgIncrustList(out);
}
-void CineEngine::makeSave(const Common::String &saveFileName) {
+void CineEngine::makeSave(const Common::String &saveFileName, uint32 playtime,
+ Common::String desc, bool isAutosave) {
Common::SharedPtr<Common::OutSaveFile> fHandle(_saveFileMan->openForSaving(saveFileName));
setMouseCursor(MOUSE_CURSOR_DISK);
@@ -992,6 +1019,15 @@ void CineEngine::makeSave(const Common::String &saveFileName) {
}
}
+ renderer->saveBackBuffer(BEFORE_TAKING_THUMBNAIL);
+ if (renderer->hasSavedBackBuffer(BEFORE_OPENING_MENU)) {
+ renderer->popSavedBackBuffer(BEFORE_OPENING_MENU);
+ }
+
+ MetaEngine::appendExtendedSave(fHandle.get(), playtime, desc, isAutosave);
+
+ renderer->restoreSavedBackBuffer(BEFORE_TAKING_THUMBNAIL);
+
setMouseCursor(MOUSE_CURSOR_NORMAL);
}
diff --git a/engines/cine/saveload.h b/engines/cine/saveload.h
index 91c9452d60..821a5105e4 100644
--- a/engines/cine/saveload.h
+++ b/engines/cine/saveload.h
@@ -27,6 +27,8 @@
namespace Cine {
+extern int16 currentDisk;
+
/**
* Cine engine's save game formats.
* Enumeration entries (Excluding the one used as an error)
@@ -64,14 +66,31 @@ enum CineSaveGameFormat {
ANIMSIZE_23,
ANIMSIZE_30_PTRS_BROKEN,
ANIMSIZE_30_PTRS_INTACT,
- TEMP_OS_FORMAT
+ TEMP_OS_FORMAT,
+ VERSIONED_FW_FORMAT,
+ VERSIONED_OS_FORMAT
};
/** Identifier for the temporary Operation Stealth savegame format. */
static const uint32 TEMP_OS_FORMAT_ID = MKTAG('T', 'E', 'M', 'P');
-/** The current version number of Operation Stealth's savegame format. */
-static const uint32 CURRENT_OS_SAVE_VER = 1;
+/** Identifiers for versioned Future Wars and Operation Stealth savegame formats. */
+static const uint32 VERSIONED_FW_FORMAT_ID = MKTAG('C', '1', 'F', 'W');
+static const uint32 VERSIONED_OS_FORMAT_ID = MKTAG('C', '2', 'O', 'S');
+
+/** The current version number of versioned Future Wars and Operation Stealth savegame formats.
+Version 4: First version used. Added disableSystemMenu to Future Wars savegame format.
+*/
+static const uint32 CURRENT_SAVE_VER = 4;
+
+/** The last version number of temporary Operation Stealth's savegame format.
+Version 0: Color count was not saved, was assumed to be 256. BGIncrust.bgIdx does not exist, _currentBg was used.
+Version 1: Saving of real color count was added but still 256 colors were always saved.
+Version 2: BGIncrust.bgIdx was added.
+Version 3: Saving real values for current music name, music playing status, current background index,
+ scroll background index and background scrolling was added.
+*/
+static const uint32 LAST_TEMP_OS_SAVE_VER = 3;
/** Chunk header used by the temporary Operation Stealth savegame format. */
struct ChunkHeader {
diff --git a/engines/cine/script.h b/engines/cine/script.h
index 1dc9ab2823..c088544a42 100644
--- a/engines/cine/script.h
+++ b/engines/cine/script.h
@@ -177,7 +177,7 @@ protected:
int o1_loadMask4();
int o1_unloadMask4();
int o1_addSpriteFilledToBgList();
- int o1_op1B();
+ int o1_clearBgIncrustList();
int o1_label();
int o1_goto();
int o1_gotoIfSup();
@@ -239,7 +239,7 @@ protected:
int o2_removeSeq();
int o2_playSample();
int o2_playSampleAlt();
- int o2_op81();
+ int o2_clearSeqList();
int o2_modifySeqListElement();
int o2_isSeqRunning();
int o2_gotoIfSupNearest();
diff --git a/engines/cine/script_fw.cpp b/engines/cine/script_fw.cpp
index 4383c11d81..2ed3e96ebd 100644
--- a/engines/cine/script_fw.cpp
+++ b/engines/cine/script_fw.cpp
@@ -79,7 +79,7 @@ void FWScript::setupTable() {
{ &FWScript::o1_loadMask4, "b" },
{ &FWScript::o1_unloadMask4, "b" },
{ &FWScript::o1_addSpriteFilledToBgList, "b" },
- { &FWScript::o1_op1B, "" },
+ { &FWScript::o1_clearBgIncrustList, "" },
/* 1C */
{ 0, 0 },
{ &FWScript::o1_label, "l" },
@@ -947,7 +947,7 @@ int FWScript::o1_loadVar() {
_localVars[varIdx] = _localVars[dataIdx];
break;
case 2:
- debugC(5, kCineDebugScript, "Line: %d: var[%d] = globalVars[%d]", _line, varIdx, dataIdx);
+ debugC(5, kCineDebugScript, "Line: %d: var[%d] = globalVars[%d] (= %d)", _line, varIdx, dataIdx, _globalVars[dataIdx]);
_localVars[varIdx] = _globalVars[dataIdx];
break;
case 3:
@@ -1196,7 +1196,7 @@ int FWScript::o1_addSpriteFilledToBgList() {
return 0;
}
-int FWScript::o1_op1B() {
+int FWScript::o1_clearBgIncrustList() {
debugC(5, kCineDebugScript, "Line: %d: freeBgIncrustList", _line);
g_cine->_bgIncrustList.clear();
return 0;
@@ -1349,6 +1349,7 @@ int FWScript::o1_startGlobalScript() {
}
addScriptToGlobalScripts(param);
+
return 0;
}
@@ -1389,7 +1390,11 @@ int FWScript::o1_loadBg() {
}
loadBg(param);
- g_cine->_bgIncrustList.clear();
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ removeBgIncrustsWithBgIdx(0);
+ } else {
+ g_cine->_bgIncrustList.clear();
+ }
bgVar0 = 0;
return 0;
}
@@ -1455,11 +1460,18 @@ int FWScript::o1_requestCheckPendingDataLoad() {
int FWScript::o1_blitAndFade() {
debugC(5, kCineDebugScript, "Line: %d: request fadein", _line);
- // TODO: use real code
-// fadeFromBlack();
+ // HACK: This section is not present in disassembly
+ // but this is an attempt to prevent flashing a
+ // normally illuminated screen and then fading it in by
+ // setting the palette initially to black.
+ if (hacksEnabled) {
+ renderer->setBlackPalette(false); // Does not update _changePal
+ renderer->setPalette();
+ g_system->updateScreen();
+ }
- renderer->reloadPalette();
+ gfxFadeInRequested = 1;
return 0;
}
@@ -1543,6 +1555,11 @@ int FWScript::o1_break() {
return 0;
}
+ // Jump over breaks when running only until copy protection check
+ if (runOnlyUntilCopyProtectionCheck) {
+ return 0;
+ }
+
return 1;
}
@@ -1623,6 +1640,16 @@ int FWScript::o1_compareGlobalVar() {
_compare = kCmpEQ;
} else {
_compare = compareVars(_globalVars[varIdx], value);
+
+ // Used for bailing out early from AUTO00.PRC before loading a savegame.
+ // Used for making sound effects work using Roland MT-32 and AdLib in
+ // Operation Stealth after loading a savegame. The sound effects are loaded
+ // in AUTO00.PRC using a combination of o2_loadAbs and o2_playSample(1, ...)
+ // before checking if _globalVars[255] == 0.
+ if (varIdx == 255 && value == 0 && runOnlyUntilCopyProtectionCheck) {
+ runOnlyUntilCopyProtectionCheck = false;
+ return o1_endScript();
+ }
}
}
@@ -1685,10 +1712,18 @@ int FWScript::o1_initializeZoneData() {
int FWScript::o1_setZoneDataEntry() {
byte zoneIdx = getNextByte();
- uint16 var = getNextWord();
+ int16 var = getNextWord();
+ // HACK: Fix storage room's door animation on Dr. Why's island.
+ if (hacksEnabled && g_cine->getGameType() == Cine::GType_OS && zoneIdx == 2 && var == 8 &&
+ _script._size >= 10 && _script.getByte(9) == 0 &&
+ scumm_stricmp(_script.getString(0), "Z012_INIT") == 0 && _line == 34) {
+ return 0;
+ }
debugC(5, kCineDebugScript, "Line: %d: setZone[%d] = %d", _line, zoneIdx, var);
- g_cine->_zoneData[zoneIdx] = var;
+ if (zoneIdx < NUM_MAX_ZONE) {
+ g_cine->_zoneData[zoneIdx] = var;
+ }
return 0;
}
@@ -1697,6 +1732,7 @@ int FWScript::o1_getZoneDataEntry() {
byte var = getNextByte();
_localVars[var] = g_cine->_zoneData[zoneIdx];
+ debugC(5, kCineDebugScript, "Line: %d: SET localVars[%d] = zoneData[%d] (= %d)", _line, var, zoneIdx, g_cine->_zoneData[zoneIdx]);
return 0;
}
@@ -1966,7 +2002,7 @@ int16 getZoneFromPosition(byte *page, int16 x, int16 y, int16 width) {
return zoneVar;
}
-int16 getZoneFromPositionRaw(byte *page, int16 x, int16 y, int16 width) {
+byte getZoneFromPositionRaw(byte *page, int16 x, int16 y, int16 width) {
// WORKAROUND for bug #2848940 ("ScummVM crashes with Future wars"):
// Vertical positions outside the 320x200 screen (e.g. in range 200-232)
// are accessed after teleporting Lo'Ann to the future using the pendant
@@ -1989,46 +2025,62 @@ int16 getZoneFromPositionRaw(byte *page, int16 x, int16 y, int16 width) {
return zoneVar;
}
-int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
- debugC(1, kCineDebugCollision, "checkCollision(objIdx: %d x: %d y:%d numZones:%d zoneIdx: %d)", objIdx, x, y, numZones, zoneIdx);
+int16 checkCollisionFW(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
+ int16 lx = g_cine->_objectTable[objIdx].x + x;
+ int16 ly = g_cine->_objectTable[objIdx].y + y;
+
+ for (int16 i = 0; i < numZones; i++, lx++) {
+ int16 idx = getZoneFromPositionRaw(collisionPage, lx, ly, 320);
+
+ if (idx < NUM_MAX_ZONE && g_cine->_zoneData[idx] == zoneIdx) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+int16 checkCollisionOS(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
int16 lx = g_cine->_objectTable[objIdx].x + x;
int16 ly = g_cine->_objectTable[objIdx].y + y;
- int16 idx;
int16 result = 0;
- for (int16 i = 0; i < numZones; i++) {
- // 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;
- }
+ if (ly < 0 || ly > 199) {
+ return result;
+ }
+
+ for (int16 i = 0; i < numZones; i++, lx++) {
+ if (lx < 0 || lx > 319) {
+ continue;
}
- idx = getZoneFromPositionRaw(collisionPage, lx + i, ly, 320);
+ int16 idx = getZoneFromPositionRaw(collisionPage, lx, ly, 320);
- assert(idx >= 0 && idx < NUM_MAX_ZONE);
+ if (idx < NUM_MAX_ZONE) {
+ idx = g_cine->_zoneData[idx];
+ }
- // The zoneQuery table is updated here only in Operation Stealth
- if (g_cine->getGameType() == Cine::GType_OS) {
- if (g_cine->_zoneData[idx] < NUM_MAX_ZONE) {
- g_cine->_zoneQuery[g_cine->_zoneData[idx]]++;
- }
+ if (idx >= 0 && idx < NUM_MAX_ZONE) {
+ g_cine->_zoneQuery[idx]++;
}
- if (g_cine->_zoneData[idx] == zoneIdx) {
+ if (idx == zoneIdx) {
result = 1;
- // Future Wars breaks out early on the first match, but
- // Operation Stealth doesn't because it needs to update
- // the zoneQuery table for the whole loop's period.
- if (g_cine->getGameType() == Cine::GType_FW) {
- break;
- }
}
}
return result;
}
+int16 checkCollision(int16 objIdx, int16 x, int16 y, int16 numZones, int16 zoneIdx) {
+ debugC(1, kCineDebugCollision, "checkCollision(objIdx: %d x: %d y:%d numZones:%d zoneIdx: %d)", objIdx, x, y, numZones, zoneIdx);
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ return checkCollisionOS(objIdx, x, y, numZones, zoneIdx);
+ } else {
+ return checkCollisionFW(objIdx, x, y, numZones, zoneIdx);
+ }
+}
+
uint16 compareVars(int16 a, int16 b) {
uint16 flag = 0;
@@ -2589,6 +2641,23 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
break;
}
+
+ case 0x4A: {
+ byte param1, param2, param3;
+
+ param1 = *(localScriptPtr + position);
+ position++;
+
+ param2 = *(localScriptPtr + position);
+ position++;
+
+ param3 = *(localScriptPtr + position);
+ position++;
+
+ sprintf(lineBuffer, "palRotate(%d,%d,%d)\n", param1, param2, param3);
+ break;
+ }
+
case 0x4F: {
sprintf(lineBuffer, "break()\n");
exitScript = 1;
@@ -3066,33 +3135,59 @@ void decompileScript(const byte *scriptPtr, uint16 scriptSize, uint16 scriptIdx)
break;
}
case 0xA0: { // OS only
- byte param1;
- byte param2;
+ uint16 param1;
+ uint16 param2;
- param1 = *(localScriptPtr + position);
- position++;
+ param1 = READ_BE_UINT16(localScriptPtr + position);
+ position += 2;
- param2 = *(localScriptPtr + position);
- position++;
+ param2 = READ_BE_UINT16(localScriptPtr + position);
+ position += 2;
sprintf(lineBuffer, "OP_A0(%d,%d)\n", param1, param2);
break;
}
case 0xA1: { // OS only
- byte param1;
- byte param2;
+ uint16 param1;
+ uint16 param2;
- param1 = *(localScriptPtr + position);
- position++;
+ param1 = READ_BE_UINT16(localScriptPtr + position);
+ position += 2;
- param2 = *(localScriptPtr + position);
- position++;
+ param2 = READ_BE_UINT16(localScriptPtr + position);
+ position += 2;
sprintf(lineBuffer, "OP_A1(%d,%d)\n", param1, param2);
break;
}
+ case 0xA2: { // OS only
+ uint16 param1;
+ uint16 param2;
+
+ param1 = READ_BE_UINT16(localScriptPtr + position);
+ position += 2;
+
+ param2 = READ_BE_UINT16(localScriptPtr + position);
+ position += 2;
+
+ sprintf(lineBuffer, "OP_A2(%d,%d)\n", param1, param2);
+
+ break;
+ }
+
+ case 0x9A: { // OS only
+ byte param1;
+
+ param1 = *(localScriptPtr + position);
+ position++;
+
+ sprintf(lineBuffer, "o2_wasZoneChecked(%d)\n", param1);
+
+ break;
+ }
+
default: {
sprintf(lineBuffer, "Unsupported opcode %X in decompileScript\n\n", opcode - 1);
position = scriptSize;
diff --git a/engines/cine/script_os.cpp b/engines/cine/script_os.cpp
index 07ed295eab..09516d62c4 100644
--- a/engines/cine/script_os.cpp
+++ b/engines/cine/script_os.cpp
@@ -75,7 +75,7 @@ void OSScript::setupTable() {
{ &FWScript::o1_loadMask4, "b" },
{ &FWScript::o1_unloadMask4, "b" },
{ &FWScript::o1_addSpriteFilledToBgList, "b" },
- { &FWScript::o1_op1B, "" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o1_clearBgIncrustList, "" },
/* 1C */
{ 0, 0 },
{ &FWScript::o1_label, "l" },
@@ -203,7 +203,7 @@ void OSScript::setupTable() {
{ &FWScript::o2_addSeqListElement, "bbbbwww" },
/* 80 */
{ &FWScript::o2_removeSeq, "bb" },
- { &FWScript::o2_op81, "" }, /* TODO: Name this opcode properly. */
+ { &FWScript::o2_clearSeqList, "" },
{ &FWScript::o2_modifySeqListElement, "bbwwb" },
{ &FWScript::o2_isSeqRunning, "bb" },
/* 84 */
@@ -385,6 +385,7 @@ int FWScript::o2_loadCt() {
debugC(5, kCineDebugScript, "Line: %d: loadCt(\"%s\")", _line, param);
loadCtOS(param);
+ removeBgIncrustsWithBgIdx(kCollisionPageBgIdxAlias);
return 0;
}
@@ -406,15 +407,42 @@ int FWScript::o2_playSample() {
getNextWord();
return 0;
}
- return o1_playSample();
+
+ debugC(5, kCineDebugScript, "Line: %d: o2_playSample()", _line);
+
+ byte mode = getNextByte();
+ byte channel = getNextByte();
+
+ int16 param3 = getNextWord();
+ int16 param4 = getNextByte();
+
+ int16 param5 = getNextWord();
+ uint16 size = getNextWord();
+
+ if (mode == 2) {
+ switch (param4) {
+ case 0:
+ param4 = param5;
+ break;
+ case 1:
+ param4 = _localVars[param5];
+ break;
+ case 2:
+ param4 = _globalVars[param5];
+ break;
+ }
+ }
+
+ g_sound->playSound(mode, channel, param3, param4, param5, size);
+ return 0;
}
int FWScript::o2_playSampleAlt() {
byte num = getNextByte();
byte channel = getNextByte();
uint16 frequency = getNextWord();
- getNextByte();
- getNextWord();
+ byte param4 = getNextByte();
+ uint16 param5 = getNextWord();
uint16 size = getNextWord();
if (size == 0xFFFF) {
@@ -451,18 +479,17 @@ int FWScript::o2_removeSeq() {
byte a = getNextByte();
byte b = getNextByte();
- debugC(5, kCineDebugScript, "Line: %d: removeSeq(%d,%d) -> TODO", _line, a, b);
+ debugC(5, kCineDebugScript, "Line: %d: removeSeq(%d,%d)", _line, a, b);
removeSeq(a, 0, b);
return 0;
}
/**
- * @todo Implement this instruction
* @note According to the scripts' opcode usage comparison this opcode isn't used at all.
*/
-int FWScript::o2_op81() {
- warning("STUB: o2_op81()");
- // freeUnkList();
+int FWScript::o2_clearSeqList() {
+ debugC(5, kCineDebugScript, "Line: %d: clearSeqList()", _line);
+ g_cine->_seqList.clear();
return 0;
}
@@ -639,7 +666,8 @@ int FWScript::o2_addBackground() {
const char *param2 = getNextString();
debugC(5, kCineDebugScript, "Line: %d: addBackground(%s,%d)", _line, param2, param1);
- addBackground(param2, param1);
+ renderer->addBackground(param2, param1);
+ removeBgIncrustsWithBgIdx(param1);
return 0;
}
@@ -651,6 +679,7 @@ int FWScript::o2_removeBackground() {
debugC(5, kCineDebugScript, "Line: %d: removeBackground(%d)", _line, param);
renderer->removeBg(param);
+ removeBgIncrustsWithBgIdx(param);
return 0;
}
@@ -687,11 +716,11 @@ int FWScript::o2_loadAbs() {
int FWScript::o2_loadBg() {
byte param = getNextByte();
- assert(param < 9);
-
debugC(5, kCineDebugScript, "Line: %d: useBg(%d)", _line, param);
- renderer->selectBg(param);
+ if (param <= 8) {
+ renderer->selectBg(param);
+ }
return 0;
}
@@ -705,6 +734,7 @@ int FWScript::o2_wasZoneChecked() {
/**
* @todo Implement this instruction
* @note According to the scripts' opcode usage comparison this opcode isn't used at all.
+ * @note In Operation Stealth 16 color DOS version this calculates temporary values and discards them.
*/
int FWScript::o2_op9B() {
uint16 a = getNextWord();
@@ -722,6 +752,7 @@ int FWScript::o2_op9B() {
/**
* @todo Implement this instruction
* @note According to the scripts' opcode usage comparison this opcode isn't used at all.
+ * @note In Operation Stealth 16 color DOS version this calculates temporary values and discards them.
*/
int FWScript::o2_op9C() {
uint16 a = getNextWord();
@@ -739,35 +770,64 @@ int FWScript::o2_useBgScroll() {
debugC(5, kCineDebugScript, "Line: %d: useBgScroll(%d)", _line, param);
- renderer->selectScrollBg(param);
+ if (param <= 8) {
+ renderer->selectScrollBg(param);
+ }
return 0;
}
int FWScript::o2_setAdditionalBgVScroll() {
+ uint16 mouseX, mouseY;
+ unsigned int scroll = renderer->getScroll();
byte param1 = getNextByte();
if (param1) {
byte param2 = getNextByte();
- debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = var[%d]", _line, param2);
- renderer->setScroll(_localVars[param2]);
+ switch (param1) {
+ case 1:
+ debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = var[%d]", _line, param2);
+ scroll = _localVars[param2];
+ break;
+ case 2:
+ debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = globalVar[%d]", _line, param2);
+ scroll = _globalVars[param2];
+ break;
+ case 3:
+ debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = mouseX", _line);
+ getMouseData(mouseUpdateStatus, &dummyU16, &mouseX, &mouseY);
+ scroll = mouseX;
+ break;
+ case 4:
+ debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = mouseY", _line);
+ getMouseData(mouseUpdateStatus, &dummyU16, &mouseX, &mouseY);
+ scroll = mouseY;
+ break;
+ case 5:
+ debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = rand() %% %d", _line, param2);
+ scroll = ((param2 == 0) ? 0 : g_cine->_rnd.getRandomNumber(param2 - 1));
+ break;
+ }
} else {
uint16 param2 = getNextWord();
debugC(5, kCineDebugScript, "Line: %d: additionalBgVScroll = %d", _line, param2);
- renderer->setScroll(param2);
+ scroll = param2;
}
+
+ renderer->setScroll(scroll);
return 0;
}
/**
* @todo Implement this instruction
* @note According to the scripts' opcode usage comparison this opcode isn't used at all.
+ * @note In Operation Stealth 16 color DOS version this calculates temporary values and discards them.
*/
int FWScript::o2_op9F() {
warning("o2_op9F()");
- getNextWord();
- getNextWord();
+ uint16 param1 = getNextWord();
+ uint16 param2 = getNextWord();
return 0;
}
diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp
index cfd6ef8089..0bd590d3ac 100644
--- a/engines/cine/sound.cpp
+++ b/engines/cine/sound.cpp
@@ -48,9 +48,11 @@ public:
virtual ~PCSoundDriver() {}
+ virtual MusicType musicType() const = 0;
virtual void setupChannel(int channel, const byte *data, int instrument, int volume) = 0;
virtual void setChannelFrequency(int channel, int frequency) = 0;
virtual void stopChannel(int channel) = 0;
+ virtual void playSample(int mode, int channel, int param3, int param4, int param5, int size) = 0;
virtual void playSample(const byte *data, int size, int channel, int volume) = 0;
virtual void stopAll() = 0;
virtual const char *getInstrumentExtension() const { return ""; }
@@ -66,19 +68,16 @@ protected:
static const int _noteTableCount;
};
+// 8 octaves, 12 notes per octave
const int PCSoundDriver::_noteTable[] = {
- 0xEEE, 0xE17, 0xD4D, 0xC8C, 0xBD9, 0xB2F, 0xA8E, 0x9F7,
- 0x967, 0x8E0, 0x861, 0x7E8, 0x777, 0x70B, 0x6A6, 0x647,
- 0x5EC, 0x597, 0x547, 0x4FB, 0x4B3, 0x470, 0x430, 0x3F4,
- 0x3BB, 0x385, 0x353, 0x323, 0x2F6, 0x2CB, 0x2A3, 0x27D,
- 0x259, 0x238, 0x218, 0x1FA, 0x1DD, 0x1C2, 0x1A9, 0x191,
- 0x17B, 0x165, 0x151, 0x13E, 0x12C, 0x11C, 0x10C, 0x0FD,
- 0x0EE, 0x0E1, 0x0D4, 0x0C8, 0x0BD, 0x0B2, 0x0A8, 0x09F,
- 0x096, 0x08E, 0x086, 0x07E, 0x077, 0x070, 0x06A, 0x064,
- 0x05E, 0x059, 0x054, 0x04F, 0x04B, 0x047, 0x043, 0x03F,
- 0x03B, 0x038, 0x035, 0x032, 0x02F, 0x02C, 0x02A, 0x027,
- 0x025, 0x023, 0x021, 0x01F, 0x01D, 0x01C, 0x01A, 0x019,
- 0x017, 0x016, 0x015, 0x013, 0x012, 0x011, 0x010, 0x00F
+ 0xEEE, 0xE17, 0xD4D, 0xC8C, 0xBD9, 0xB2F, 0xA8E, 0x9F7, 0x967, 0x8E0, 0x861, 0x7E8,
+ 0x777, 0x70B, 0x6A6, 0x647, 0x5EC, 0x597, 0x547, 0x4FB, 0x4B3, 0x470, 0x430, 0x3F4,
+ 0x3BB, 0x385, 0x353, 0x323, 0x2F6, 0x2CB, 0x2A3, 0x27D, 0x259, 0x238, 0x218, 0x1FA,
+ 0x1DD, 0x1C2, 0x1A9, 0x191, 0x17B, 0x165, 0x151, 0x13E, 0x12C, 0x11C, 0x10C, 0x0FD,
+ 0x0EE, 0x0E1, 0x0D4, 0x0C8, 0x0BD, 0x0B2, 0x0A8, 0x09F, 0x096, 0x08E, 0x086, 0x07E,
+ 0x077, 0x070, 0x06A, 0x064, 0x05E, 0x059, 0x054, 0x04F, 0x04B, 0x047, 0x043, 0x03F,
+ 0x03B, 0x038, 0x035, 0x032, 0x02F, 0x02C, 0x02A, 0x027, 0x025, 0x023, 0x021, 0x01F,
+ 0x01D, 0x01C, 0x01A, 0x019, 0x017, 0x016, 0x015, 0x013, 0x012, 0x011, 0x010, 0x00F
};
const int PCSoundDriver::_noteTableCount = ARRAYSIZE(_noteTable);
@@ -108,18 +107,23 @@ public:
AdLibSoundDriver(Audio::Mixer *mixer);
~AdLibSoundDriver() override;
+ MusicType musicType() const override { return MT_ADLIB; }
// PCSoundDriver interface
void setUpdateCallback(UpdateCallback upCb, void *ref) override;
- void setupChannel(int channel, const byte *data, int instrument, int volume) override;
- void stopChannel(int channel) override;
+ void setupChannel(int channel, const byte *data, int instrument, int volume) override;
void stopAll() override;
void initCard();
void onTimer();
- void setupInstrument(const byte *data, int channel);
+ void setupPreloadedInstrument(int channel);
+ void setupInstrument(const byte *data, int channel, bool loadData = true);
void loadRegisterInstrument(const byte *data, AdLibRegisterSoundInstrument *reg);
virtual void loadInstrument(const byte *data, AdLibSoundInstrument *asi) = 0;
+ enum {
+ MAX_ADLIB_CHANNELS = 8
+ };
+
protected:
UpdateCallback _upCb;
void *_upRef;
@@ -128,8 +132,8 @@ protected:
Audio::Mixer *_mixer;
byte _vibrato;
- int _channelsVolumeTable[4];
- AdLibSoundInstrument _instrumentsTable[4];
+ int _channelsVolumeTable[MAX_ADLIB_CHANNELS];
+ AdLibSoundInstrument _instrumentsTable[MAX_ADLIB_CHANNELS];
static const int _freqTable[];
static const int _freqTableCount;
@@ -164,18 +168,25 @@ public:
AdLibSoundDriverINS(Audio::Mixer *mixer) : AdLibSoundDriver(mixer) {}
const char *getInstrumentExtension() const override { return ".INS"; }
void loadInstrument(const byte *data, AdLibSoundInstrument *asi) override;
+ void stopChannel(int channel) override;
void setChannelFrequency(int channel, int frequency) override;
+ void playSample(int mode, int channel, int param3, int param4, int param5, int size) override;
void playSample(const byte *data, int size, int channel, int volume) override;
};
// Operation Stealth AdLib driver
class AdLibSoundDriverADL : public AdLibSoundDriver {
public:
- AdLibSoundDriverADL(Audio::Mixer *mixer) : AdLibSoundDriver(mixer) {}
+ AdLibSoundDriverADL(Audio::Mixer *mixer);
const char *getInstrumentExtension() const override { return ".ADL"; }
void loadInstrument(const byte *data, AdLibSoundInstrument *asi) override;
+ void stopChannel(int channel) override;
void setChannelFrequency(int channel, int frequency) override;
+ void playSample(int mode, int channel, int param3, int param4, int param5, int size) override;
void playSample(const byte *data, int size, int channel, int volume) override;
+
+protected:
+ AdLibSoundInstrument _samples[49];
};
// (Future Wars) MIDI driver
@@ -184,15 +195,18 @@ public:
MidiSoundDriverH32(MidiDriver *output);
~MidiSoundDriverH32() override;
+ MusicType musicType() const override { return MT_MT32; }
void setUpdateCallback(UpdateCallback upCb, void *ref) override;
void setupChannel(int channel, const byte *data, int instrument, int volume) override;
void setChannelFrequency(int channel, int frequency) override;
void stopChannel(int channel) override;
+ void playSample(int mode, int channel, int param3, int param4, int param5, int size) override;
void playSample(const byte *data, int size, int channel, int volume) override;
void stopAll() override {}
const char *getInstrumentExtension() const override { return ".H32"; }
void notifyInstrumentLoad(const byte *data, int size, int channel) override;
+
private:
MidiDriver *_output;
UpdateCallback _callback;
@@ -200,6 +214,10 @@ private:
void writeInstrument(int offset, const byte *data, int size);
void selectInstrument(int channel, int timbreGroup, int timbreNumber, int volume);
+ void selectInstrument2(int channel, int timbreGroup, int timbreNumber);
+ void selectInstrument3(int channel, int offsetMode, int timbreGroup);
+ void selectInstrument4(int offsetMode, int timbreGroup, int timbreNumber, int keyShift);
+ void selectInstrument5(int messageNum);
};
class PCSoundFxPlayer {
@@ -284,7 +302,8 @@ AdLibSoundDriver::AdLibSoundDriver(Audio::Mixer *mixer)
memset(_channelsVolumeTable, 0, sizeof(_channelsVolumeTable));
memset(_instrumentsTable, 0, sizeof(_instrumentsTable));
initCard();
- _opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 50);
+ // 1000/(10923000 ms / 1193180) ~= 109 Hz
+ _opl->start(new Common::Functor0Mem<void, AdLibSoundDriver>(this, &AdLibSoundDriver::onTimer), 109);
}
AdLibSoundDriver::~AdLibSoundDriver() {
@@ -297,7 +316,7 @@ void AdLibSoundDriver::setUpdateCallback(UpdateCallback upCb, void *ref) {
}
void AdLibSoundDriver::setupChannel(int channel, const byte *data, int instrument, int volume) {
- assert(channel < 4);
+ assert(channel < MAX_ADLIB_CHANNELS);
if (data) {
volume = CLIP(volume, 0, 80);
volume += volume / 4;
@@ -307,8 +326,8 @@ void AdLibSoundDriver::setupChannel(int channel, const byte *data, int instrumen
}
}
-void AdLibSoundDriver::stopChannel(int channel) {
- assert(channel < 4);
+void AdLibSoundDriverINS::stopChannel(int channel) {
+ assert(channel < MAX_ADLIB_CHANNELS);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0 && ins->channel == 6) {
channel = 6;
@@ -322,6 +341,18 @@ void AdLibSoundDriver::stopChannel(int channel) {
}
}
+void AdLibSoundDriverADL::stopChannel(int channel) {
+ assert(channel < MAX_ADLIB_CHANNELS);
+ AdLibSoundInstrument *ins = &_instrumentsTable[channel];
+ if (ins->mode == 0 || ins->channel == 6) {
+ _opl->writeReg(0xB0 | channel, 0);
+ }
+ if (ins->mode != 0) {
+ _vibrato &= ~(1 << (10 - ins->channel));
+ _opl->writeReg(0xBD, _vibrato);
+ }
+}
+
void AdLibSoundDriver::stopAll() {
int i;
for (i = 0; i < 18; ++i) {
@@ -330,7 +361,12 @@ void AdLibSoundDriver::stopAll() {
for (i = 0; i < 9; ++i) {
_opl->writeReg(0xB0 | i, 0);
}
- _opl->writeReg(0xBD, 0);
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ _vibrato &= (1 << 5);
+ _opl->writeReg(0xBD, _vibrato);
+ } else {
+ _opl->writeReg(0xBD, 0);
+ }
}
void AdLibSoundDriver::initCard() {
@@ -363,10 +399,16 @@ void AdLibSoundDriver::onTimer() {
}
}
-void AdLibSoundDriver::setupInstrument(const byte *data, int channel) {
- assert(channel < 4);
+void AdLibSoundDriver::setupPreloadedInstrument(int channel) {
+ setupInstrument(NULL, channel, false);
+}
+
+void AdLibSoundDriver::setupInstrument(const byte *data, int channel, bool loadData) {
+ assert(channel < MAX_ADLIB_CHANNELS);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
- loadInstrument(data, ins);
+ if (loadData && data) {
+ loadInstrument(data, ins);
+ }
int mod, car, tmp;
const AdLibRegisterSoundInstrument *reg;
@@ -452,7 +494,7 @@ void AdLibSoundDriverINS::loadInstrument(const byte *data, AdLibSoundInstrument
}
void AdLibSoundDriverINS::setChannelFrequency(int channel, int frequency) {
- assert(channel < 4);
+ assert(channel < MAX_ADLIB_CHANNELS);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
if (ins->mode != 0 && ins->channel == 6) {
channel = 6;
@@ -476,8 +518,11 @@ void AdLibSoundDriverINS::setChannelFrequency(int channel, int frequency) {
}
}
+void AdLibSoundDriverINS::playSample(int mode, int channel, int param3, int param4, int param5, int size) {
+}
+
void AdLibSoundDriverINS::playSample(const byte *data, int size, int channel, int volume) {
- assert(channel < 4);
+ assert(channel < MAX_ADLIB_CHANNELS);
_channelsVolumeTable[channel] = 127;
resetChannel(channel);
setupInstrument(data + 257, channel);
@@ -501,6 +546,12 @@ void AdLibSoundDriverINS::playSample(const byte *data, int size, int channel, in
}
}
+AdLibSoundDriverADL::AdLibSoundDriverADL(Audio::Mixer *mixer)
+ : AdLibSoundDriver(mixer), _samples() {
+
+ memset(_samples, 0, sizeof(_samples));
+}
+
void AdLibSoundDriverADL::loadInstrument(const byte *data, AdLibSoundInstrument *asi) {
asi->mode = *data++;
asi->channel = *data++;
@@ -513,8 +564,9 @@ void AdLibSoundDriverADL::loadInstrument(const byte *data, AdLibSoundInstrument
}
void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) {
- assert(channel < 4);
+ assert(channel < MAX_ADLIB_CHANNELS);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
+ setupPreloadedInstrument(channel);
if (ins->mode != 0) {
channel = ins->channel;
if (channel == 9) {
@@ -547,8 +599,30 @@ void AdLibSoundDriverADL::setChannelFrequency(int channel, int frequency) {
}
}
+void AdLibSoundDriverADL::playSample(int mode, int channel, int param3, int param4, int param5, int size) {
+ switch (mode) {
+ case 0:
+ _instrumentsTable[(channel & 1) + 4] = _samples[param3];
+ _channelsVolumeTable[(channel & 1) + 4] = 0x7F;
+ stopChannel((channel & 1) + 4);
+ if (param5 >= 0x0C && param5 <= 0x6C) {
+ setChannelFrequency((channel & 1) + 4, param5);
+ }
+ break;
+ case 1:
+ if (channel <= 0x30) {
+ AnimData& animData = g_cine->_animDataTable[param3];
+ const byte *data = animData.data();
+ if (data && animData.size() >= 58) {
+ loadInstrument(data, &_samples[channel]);
+ }
+ }
+ break;
+ }
+}
+
void AdLibSoundDriverADL::playSample(const byte *data, int size, int channel, int volume) {
- assert(channel < 4);
+ assert(channel < MAX_ADLIB_CHANNELS);
_channelsVolumeTable[channel] = 127;
setupInstrument(data, channel);
AdLibSoundInstrument *ins = &_instrumentsTable[channel];
@@ -606,8 +680,8 @@ void MidiSoundDriverH32::setUpdateCallback(UpdateCallback upCb, void *ref) {
timer->removeTimerProc(_callback);
_callback = upCb;
- if (_callback)
- timer->installTimerProc(_callback, 1000000 / 50, ref, "MidiSoundDriverH32");
+ if (_callback) // 10923000 ms / 1193180 ~= 9155 microseconds
+ timer->installTimerProc(_callback, 9155, ref, "MidiSoundDriverH32");
}
void MidiSoundDriverH32::setupChannel(int channel, const byte *data, int instrument, int volume) {
@@ -644,6 +718,46 @@ void MidiSoundDriverH32::stopChannel(int channel) {
_output->send(0xB1 + channel, 0x7B, 0x00);
}
+void MidiSoundDriverH32::playSample(int mode, int channel, int param3, int param4, int param5, int size) {
+ Common::StackLock lock(_mutex);
+
+ switch (mode) {
+ case 0: // play instrument
+ if (param5 >= 0x0C && param5 <= 0x6C) {
+ selectInstrument2(channel + 4, 2, param3 + 0xF);
+ selectInstrument3(channel + 4, 1, param4);
+ }
+
+ stopChannel(channel + 4);
+
+ if (param5 >= 0x0C && param5 <= 0x6C) {
+ _output->send(0x91 + channel + 4, param5, 0x7F);
+ }
+ break;
+ case 1: // load instrument
+ if (channel <= 0x30) {
+ AnimData& animData = g_cine->_animDataTable[param3];
+ const byte *data = animData.data();
+ if (data && data[0] >= 0x80 && data[0] < 0xC0) {
+ writeInstrument((channel + 15) * 512 + 0x80000, data + 1, animData.size() - 1);
+ }
+ }
+ break;
+ case 2:
+ selectInstrument3(channel + 4, param3, param4);
+ break;
+ case 3:
+ selectInstrument4(channel, param3, param4, param5);
+ break;
+ case 4: // show text in Roland MT-32 LCD display
+ // Don't display text in Roland MT-32 LCD display when loading a savegame
+ if (!runOnlyUntilCopyProtectionCheck) {
+ selectInstrument5(channel);
+ }
+ break;
+ }
+}
+
void MidiSoundDriverH32::playSample(const byte *data, int size, int channel, int volume) {
Common::StackLock lock(_mutex);
@@ -654,7 +768,7 @@ void MidiSoundDriverH32::playSample(const byte *data, int size, int channel, int
if (data[0] < 0x80) {
selectInstrument(channel, data[0] / 0x40, data[0] % 0x40, volume);
} else {
- writeInstrument(channel * 512 + 0x80000, data + 1, 256);
+ writeInstrument(channel * 512 + 0x80000, data + 1, size - 1);
selectInstrument(channel, 2, channel, volume);
}
@@ -667,7 +781,7 @@ void MidiSoundDriverH32::notifyInstrumentLoad(const byte *data, int size, int ch
// In case we specify a standard instrument or standard rhythm instrument
// do not do anything here. It might be noteworthy that the instrument
// selection client code does not support rhythm instruments!
- if (data[0] < 0x80 || data[0] > 0xC0)
+ if (data[0] < 0x80 || data[0] >= 0xC0)
return;
writeInstrument(channel * 512 + 0x80000, data + 1, size - 1);
@@ -694,6 +808,151 @@ void MidiSoundDriverH32::writeInstrument(int offset, const byte *data, int size)
_output->sysEx(sysEx, copySize + 8);
}
+void MidiSoundDriverH32::selectInstrument2(int channel, int timbreGroup, int timbreNumber) {
+ const int offset = channel * 16 + 0x30000; // 0x30000 is the start of the patch temp area
+
+ byte sysEx[14] = {
+ 0x41, 0x10, 0x16, 0x12,
+ 0x00, 0x00, 0x00, // offset
+ 0x00, // Timbre group _ timbreGroup * 64 + timbreNumber should be the
+ 0x00, // Timbre number / MT-32 instrument in case timbreGroup is 0 or 1.
+ 0x18, // Key shift (= 0)
+ 0x32, // Fine tune (= 0)
+ 0x0C, // Bender Range
+ 0x03, // Assign Mode
+ 0x00 // Checksum
+ };
+
+
+ sysEx[4] = (offset >> 16) & 0xFF;
+ sysEx[5] = (offset >> 8) & 0xFF;
+ sysEx[6] = (offset >> 0) & 0xFF;
+
+ sysEx[7] = timbreGroup;
+ sysEx[8] = timbreNumber;
+
+ byte checkSum = 0;
+
+ for (int i = 4; i < sizeof(sysEx) - 1; ++i)
+ checkSum += sysEx[i];
+
+ sysEx[sizeof(sysEx) - 1] = 0x80 - (checkSum & 0x7F);
+
+ _output->sysEx(sysEx, sizeof(sysEx));
+}
+
+void MidiSoundDriverH32::selectInstrument3(int channel, int offsetMode, int timbreGroup) {
+ int offset = channel * 16 + 0x30000; // 0x30000 is the start of the patch temp area
+
+ switch (offsetMode) {
+ case 1:
+ offset += 8;
+ break;
+ case 2:
+ offset += 6;
+ break;
+ case 3:
+ offset += 9;
+ break;
+ }
+
+ byte sysEx[9] = {
+ 0x41, 0x10, 0x16, 0x12,
+ 0x00, 0x00, 0x00, // offset
+ 0x00, // Timbre group
+ 0x00 // Checksum
+ };
+
+
+ sysEx[4] = (offset >> 16) & 0xFF;
+ sysEx[5] = (offset >> 8) & 0xFF;
+ sysEx[6] = (offset >> 0) & 0xFF;
+
+ sysEx[7] = timbreGroup;
+
+ byte checkSum = 0;
+
+ for (int i = 4; i < sizeof(sysEx) - 1; ++i)
+ checkSum += sysEx[i];
+
+ sysEx[sizeof(sysEx) - 1] = 0x80 - (checkSum & 0x7F);
+
+ _output->sysEx(sysEx, sizeof(sysEx));
+}
+
+void MidiSoundDriverH32::selectInstrument4(int offsetMode, int timbreGroup, int timbreNumber, int keyShift) {
+ int offset = 0x100000;
+ const int len = (offsetMode == 2) ? 11 : 9;
+
+ switch (offsetMode) {
+ case 1:
+ offset += 0x16;
+ break;
+ case 2:
+ offset += 1;
+ break;
+ }
+
+ byte sysEx[11] = {
+ 0x41, 0x10, 0x16, 0x12,
+ 0x00, 0x00, 0x00, // offset
+ 0x00, // Timbre group
+ 0x00, // Timbre number / MT-32 instrument in case timbreGroup is 0 or 1.
+ 0x18, // Key shift (= 0)
+ 0x00 // Checksum
+ };
+
+
+ sysEx[4] = (offset >> 16) & 0xFF;
+ sysEx[5] = (offset >> 8) & 0xFF;
+ sysEx[6] = (offset >> 0) & 0xFF;
+
+ sysEx[7] = timbreGroup;
+
+ if (offsetMode == 2) {
+ sysEx[8] = timbreNumber;
+ sysEx[9] = keyShift;
+ }
+
+ byte checkSum = 0;
+
+ for (int i = 4; i < len - 1; ++i)
+ checkSum += sysEx[i];
+
+ sysEx[len - 1] = 0x80 - (checkSum & 0x7F);
+
+ _output->sysEx(sysEx, len);
+}
+
+void MidiSoundDriverH32::selectInstrument5(int messageNum) {
+ int offset = 0x200000;
+
+ byte sysEx[28] = {
+ 0x41, 0x10, 0x16, 0x12,
+ 0x00, 0x00, 0x00 // offset
+ };
+
+ memset(sysEx + 7, 0x20, 20);
+
+ if (messageNum >= 0 && messageNum < g_cine->_messageTable.size()) {
+ Common::String msg = g_cine->_messageTable[messageNum];
+ memcpy(sysEx + 7, msg.c_str(), MIN<int>(20, msg.size()));
+ }
+
+ sysEx[4] = (offset >> 16) & 0xFF;
+ sysEx[5] = (offset >> 8) & 0xFF;
+ sysEx[6] = (offset >> 0) & 0xFF;
+
+ byte checkSum = 0;
+
+ for (int i = 4; i < sizeof(sysEx) - 1; ++i)
+ checkSum += sysEx[i];
+
+ sysEx[sizeof(sysEx) - 1] = 0x80 - (checkSum & 0x7F);
+
+ _output->sysEx(sysEx, sizeof(sysEx));
+}
+
void MidiSoundDriverH32::selectInstrument(int channel, int timbreGroup, int timbreNumber, int volume) {
const int offset = channel * 16 + 0x30000; // 0x30000 is the start of the patch temp area
@@ -775,7 +1034,6 @@ bool PCSoundFxPlayer::load(const char *song) {
for (int i = 0; i < NUM_INSTRUMENTS; ++i) {
_instrumentsData[i] = NULL;
-
char instrument[64];
memset(instrument, 0, 64); // Clear the data first
memcpy(instrument, _sfxData + 20 + i * 30, 12);
@@ -788,7 +1046,16 @@ bool PCSoundFxPlayer::load(const char *song) {
}
Common::strlcat(instrument, _driver->getInstrumentExtension(), sizeof(instrument));
uint32 instrumentSize;
- _instrumentsData[i] = readBundleSoundFile(instrument, &instrumentSize);
+ byte *data = readBundleSoundFile(instrument, &instrumentSize);
+ if (g_cine->getGameType() == Cine::GType_OS && _driver->musicType() == MT_MT32 &&
+ data && instrumentSize > 0x16) {
+ instrumentSize -= 0x16;
+ byte *tmp = (byte *)calloc(instrumentSize, 1);
+ memcpy(tmp, data + 0x16, instrumentSize);
+ free(data);
+ data = tmp;
+ }
+ _instrumentsData[i] = data;
if (!_instrumentsData[i]) {
warning("Unable to load soundfx instrument '%s'", instrument);
} else {
@@ -809,7 +1076,12 @@ void PCSoundFxPlayer::play() {
_currentPos = 0;
_currentOrder = 0;
_numOrders = _sfxData[470];
- _eventsDelay = (252 - _sfxData[471]) * 50 / 1060;
+ int timerIntsPerMusicUpdate = (g_cine->getGameType() == Cine::GType_OS) ? 1 : 2;
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ _eventsDelay = (244 - _sfxData[471]) * 109 * timerIntsPerMusicUpdate / 1060;
+ } else {
+ _eventsDelay = (252 - _sfxData[471]) * 55 * timerIntsPerMusicUpdate / 1060;
+ }
_updateTicksCounter = 0;
_playing = true;
}
@@ -820,7 +1092,8 @@ void PCSoundFxPlayer::stop() {
if (_playing || _fadeOutCounter != 0) {
_fadeOutCounter = 0;
_playing = false;
- for (int i = 0; i < NUM_CHANNELS; ++i) {
+ int numChannels = (g_cine->getGameType() == Cine::GType_OS) ? 8 : 4;
+ for (int i = 0; i < numChannels; ++i) {
_driver->stopChannel(i);
}
_driver->stopAll();
@@ -852,7 +1125,7 @@ void PCSoundFxPlayer::update() {
}
void PCSoundFxPlayer::handleEvents() {
- const byte *patternData = _sfxData + 600;
+ const byte *patternData = _sfxData + ((g_cine->getGameType() == Cine::GType_OS) ? 2400 : 600);
const byte *orderTable = _sfxData + 472;
uint16 patternNum = orderTable[_currentOrder] * 1024;
@@ -901,6 +1174,9 @@ void PCSoundFxPlayer::unload() {
_sfxData = NULL;
}
+MusicType Sound::musicType() {
+ return _musicType;
+}
PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
: Sound(mixer, vm), _soundDriver(0) {
@@ -908,6 +1184,7 @@ PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
_currentMusic = 0;
_currentMusicStatus = 0;
_currentBgSlot = 0;
+ _musicType = MT_INVALID;
const MidiDriver::DeviceHandle dev = MidiDriver::detectDevice(MDT_MIDI | MDT_ADLIB);
const MusicType musicType = MidiDriver::getMusicType(dev);
@@ -918,6 +1195,7 @@ PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
if (driver && driver->open() == 0) {
driver->sendMT32Reset();
_soundDriver = new MidiSoundDriverH32(driver);
+ _musicType = MT_MT32;
} else {
warning("Could not create MIDI output, falling back to AdLib");
}
@@ -932,6 +1210,7 @@ PCSound::PCSound(Audio::Mixer *mixer, CineEngine *vm)
} else {
_soundDriver = new AdLibSoundDriverADL(_mixer);
}
+ _musicType = MT_ADLIB;
}
_player = new PCSoundFxPlayer(_soundDriver);
@@ -966,6 +1245,7 @@ static uint8 musicCDTracks[11] = {
void PCSound::loadMusic(const char *name) {
debugC(5, kCineDebugSound, "PCSound::loadMusic('%s')", name);
+
if (_vm->getGameType() == GType_FW && (_vm->getFeatures() & GF_CD)) {
_currentMusic = 0;
_currentMusicStatus = 0;
@@ -1041,6 +1321,11 @@ void PCSound::fadeOutMusic() {
_player->fadeOut();
}
+void PCSound::playSound(int mode, int channel, int param3, int param4, int param5, int size) {
+ debugC(5, kCineDebugSound, "PCSound::playSound() channel %d size %d, new", channel, size);
+ _soundDriver->playSample(mode, channel, param3, param4, param5, size);
+}
+
void PCSound::playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) {
debugC(5, kCineDebugSound, "PCSound::playSound() channel %d size %d", channel, size);
_soundDriver->playSample(data, size, channel, volume);
@@ -1053,6 +1338,7 @@ void PCSound::stopSound(int channel) {
PaulaSound::PaulaSound(Audio::Mixer *mixer, CineEngine *vm)
: Sound(mixer, vm), _sfxTimer(0), _musicTimer(0), _musicFadeTimer(0) {
+ _musicType = MT_AMIGA;
_moduleStream = 0;
// The original is using the following timer frequency:
// 0.709379Mhz / 8000 = 88.672375Hz
@@ -1147,6 +1433,10 @@ void PaulaSound::fadeOutMusic() {
_musicFadeTimer = 1;
}
+void PaulaSound::playSound(int mode, int channel, int param3, int param4, int param5, int size) {
+ // Newer playSound from Operation Stealth might not do anything on Amiga
+}
+
void PaulaSound::playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) {
debugC(5, kCineDebugSound, "PaulaSound::playSound() channel %d size %d", channel, size);
Common::StackLock lock(_sfxMutex);
diff --git a/engines/cine/sound.h b/engines/cine/sound.h
index 928671488b..0a6b8efefe 100644
--- a/engines/cine/sound.h
+++ b/engines/cine/sound.h
@@ -26,6 +26,7 @@
#include "common/util.h"
#include "common/mutex.h"
#include "audio/mixer.h"
+#include "audio/mididrv.h"
namespace Audio {
class AudioStream;
@@ -38,14 +39,16 @@ class CineEngine;
class Sound {
public:
- Sound(Audio::Mixer *mixer, CineEngine *vm) : _mixer(mixer), _vm(vm) {}
+ Sound(Audio::Mixer *mixer, CineEngine *vm) : _mixer(mixer), _vm(vm), _musicType(MT_INVALID) {}
virtual ~Sound() {}
+ virtual MusicType musicType();
virtual void loadMusic(const char *name) = 0;
virtual void playMusic() = 0;
virtual void stopMusic() = 0;
virtual void fadeOutMusic() = 0;
+ virtual void playSound(int mode, int channel, int param3, int param4, int param5, int size) = 0;
virtual void playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) = 0;
virtual void stopSound(int channel) = 0;
virtual void setBgMusic(int num) = 0;
@@ -54,6 +57,7 @@ protected:
Audio::Mixer *_mixer;
CineEngine *_vm;
+ MusicType _musicType;
};
class PCSoundDriver;
@@ -70,6 +74,7 @@ public:
void stopMusic() override;
void fadeOutMusic() override;
+ void playSound(int mode, int channel, int param3, int param4, int param5, int size) override;
void playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) override;
void stopSound(int channel) override;
void setBgMusic(int num) override;
@@ -93,6 +98,7 @@ public:
void stopMusic() override;
void fadeOutMusic() override;
+ void playSound(int mode, int channel, int param3, int param4, int param5, int size) override;
void playSound(int channel, int frequency, const uint8 *data, int size, int volumeStep, int stepCount, int volume, int repeat) override;
void stopSound(int channel) override;
void setBgMusic(int num) override;
diff --git a/engines/cine/texte.cpp b/engines/cine/texte.cpp
index 8d904cb1f2..2df51ddb08 100644
--- a/engines/cine/texte.cpp
+++ b/engines/cine/texte.cpp
@@ -28,6 +28,7 @@
namespace Cine {
+bool allocatedFailureMessages = false;
const char *const *failureMessages;
const CommandeType *defaultActionCommand;
const CommandeType *systemMenu;
@@ -539,7 +540,7 @@ void initLanguage(Common::Language lang) {
switch (lang) {
case Common::FR_FRA:
- failureMessages = failureMessages_FR;
+ setFailureMessages(failureMessages_FR, false);
defaultActionCommand = defaultActionCommand_FR;
systemMenu = systemMenu_FR;
confirmMenu = confirmMenu_FR;
@@ -549,7 +550,7 @@ void initLanguage(Common::Language lang) {
break;
case Common::ES_ESP:
- failureMessages = failureMessages_ES;
+ setFailureMessages(failureMessages_ES, false);
defaultActionCommand = defaultActionCommand_ES;
systemMenu = systemMenu_ES;
confirmMenu = confirmMenu_ES;
@@ -559,7 +560,7 @@ void initLanguage(Common::Language lang) {
break;
case Common::DE_DEU:
- failureMessages = failureMessages_DE;
+ setFailureMessages(failureMessages_DE, false);
defaultActionCommand = defaultActionCommand_DE;
systemMenu = systemMenu_DE;
confirmMenu = confirmMenu_DE;
@@ -569,7 +570,7 @@ void initLanguage(Common::Language lang) {
break;
case Common::IT_ITA:
- failureMessages = failureMessages_IT;
+ setFailureMessages(failureMessages_IT, false);
defaultActionCommand = defaultActionCommand_IT;
systemMenu = systemMenu_IT;
confirmMenu = confirmMenu_IT;
@@ -579,7 +580,7 @@ void initLanguage(Common::Language lang) {
break;
default:
- failureMessages = failureMessages_EN;
+ setFailureMessages(failureMessages_EN, false);
defaultActionCommand = defaultActionCommand_EN;
systemMenu = systemMenu_EN;
confirmMenu = confirmMenu_EN;
@@ -604,15 +605,17 @@ void loadErrmessDat(const char *fname) {
in.open(fname);
if (in.isOpen()) {
- // FIXME - This can leak in some situations in Operation Stealth
- // Engine Restart - multiple allocations with no free?
+ if (allocatedFailureMessages) {
+ freeErrmessDat();
+ }
+
char **ptr = (char **)malloc(sizeof(char *) * 6 * 4 + 60 * 6 * 4);
for (int i = 0; i < 6 * 4; i++) {
ptr[i] = (char *)ptr + (sizeof(char *) * 6 * 4) + 60 * i;
in.read(ptr[i], 60);
}
- failureMessages = const_cast<const char *const *>(ptr);
+ setFailureMessages(const_cast<const char *const *>(ptr), true);
in.close();
} else {
@@ -620,9 +623,20 @@ void loadErrmessDat(const char *fname) {
}
}
+void setFailureMessages(const char *const *messages, bool allocated) {
+ if (allocatedFailureMessages) {
+ freeErrmessDat();
+ }
+ failureMessages = messages;
+ allocatedFailureMessages = allocated;
+}
+
void freeErrmessDat() {
- free(const_cast<const char **>(failureMessages));
+ if (allocatedFailureMessages) {
+ free(const_cast<const char **>(failureMessages));
+ }
failureMessages = 0;
+ allocatedFailureMessages = false;
}
void loadPoldatDat(const char *fname) {
diff --git a/engines/cine/texte.h b/engines/cine/texte.h
index 1e0944c312..e073ab9dd3 100644
--- a/engines/cine/texte.h
+++ b/engines/cine/texte.h
@@ -60,6 +60,7 @@ extern const char *const *commandPrepositionTable;
void loadTextData(const char *filename);
void loadErrmessDat(const char *fname);
+void setFailureMessages(const char *const *messages, bool allocated);
void freeErrmessDat();
void loadPoldatDat(const char *fname);
diff --git a/engines/cine/various.cpp b/engines/cine/various.cpp
index b571000962..3ac82d4e22 100644
--- a/engines/cine/various.cpp
+++ b/engines/cine/various.cpp
@@ -42,6 +42,7 @@ namespace Cine {
int16 disableSystemMenu = 0;
bool inMenu;
+bool runOnlyUntilCopyProtectionCheck = false;
int16 commandVar3[4];
int16 commandVar1;
@@ -52,18 +53,17 @@ int16 commandVar2;
uint16 var2;
uint16 var3;
uint16 var4;
-uint16 var5;
+uint16 lastType20OverlayBgIdx; // Was var5
+uint16 reloadBgPalOnNextFlip;
+uint16 forbidBgPalReload;
+uint16 gfxFadeOutCompleted;
+uint16 gfxFadeInRequested;
int16 buildObjectListCommand(int16 param);
int16 canUseOnObject = 0;
void waitPlayerInput() {
- uint16 button;
-
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
- } while (!button && !g_cine->shouldQuit());
+ manageEvents(WAIT_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_DOWN_UP);
}
void setTextWindow(uint16 param1, uint16 param2, uint16 param3, uint16 param4) {
@@ -72,7 +72,6 @@ void setTextWindow(uint16 param1, uint16 param2, uint16 param3, uint16 param4) {
uint16 errorVar;
byte menuVar;
-bool fadeRequired;
uint16 allowPlayerInput;
uint16 checkForPendingDataLoadSwitch;
uint16 isDrawCommandEnabled;
@@ -128,7 +127,7 @@ uint16 yMoveKeyb = kKeybMoveCenterY;
SelectedObjStruct currentSelectedObject;
-CommandeType currentSaveName[10];
+CommandeType currentSaveName[kMaxSavegames];
static const int16 choiceResultTable[] = { 1, 1, 1, 2, 1, 1, 1 };
static const int16 subObjectUseTable[] = { 3, 3, 3, 3, 3, 0, 0 };
@@ -304,7 +303,8 @@ void CineEngine::resetEngine() {
g_cine->_globalVars.reset();
- var2 = var3 = var4 = var5 = 0;
+ bgVar0 = 0;
+ var2 = var3 = var4 = lastType20OverlayBgIdx = 0;
strcpy(newPrcName, "");
strcpy(newRelName, "");
@@ -322,21 +322,16 @@ void CineEngine::resetEngine() {
g_cine->_globalVars[VAR_MOUSE_X_POS] = 0;
g_cine->_globalVars[VAR_MOUSE_Y_POS] = 0;
- fadeRequired = false;
-
renderer->clear();
+ currentDisk = 1;
checkForPendingDataLoadSwitch = 0;
- if (g_cine->getGameType() == Cine::GType_OS) {
- g_cine->_seqList.clear();
- currentAdditionalBgIdx = 0;
- currentAdditionalBgIdx2 = 0;
- // TODO: Add resetting of the following variables
- // adBgVar1 = 0;
- // adBgVar0 = 0;
- // gfxFadeOutCompleted = 0;
- }
+ g_cine->_seqList.clear();
+ reloadBgPalOnNextFlip = 0;
+ forbidBgPalReload = 0;
+ gfxFadeOutCompleted = 0;
+ gfxFadeInRequested = 0;
}
int CineEngine::scummVMSaveLoadDialog(bool isSave) {
@@ -365,7 +360,7 @@ int CineEngine::scummVMSaveLoadDialog(bool isSave) {
if (slot < 0)
return true;
- Common::String saveFileName(Common::String::format("%s.%1d", _targetName.c_str(), slot));
+ Common::String saveFileName(getSaveStateName(slot));
if (isSave) {
Common::String tmp = Common::String::format("%s.dir", _targetName.c_str());
@@ -376,12 +371,13 @@ int CineEngine::scummVMSaveLoadDialog(bool isSave) {
return false;
}
- Common::strlcpy(currentSaveName[slot], desc.c_str(), 20);
+ Common::strlcpy(currentSaveName[slot], desc.c_str(), sizeof(CommandeType));
- fHandle->write(currentSaveName, 200);
+ fHandle->write(currentSaveName, sizeof(currentSaveName));
delete fHandle;
- makeSave(saveFileName);
+ makeSave(saveFileName, getTotalPlayTime() / 1000, desc, false);
+
return true;
} else {
return makeLoad(saveFileName);
@@ -396,10 +392,8 @@ void CineEngine::makeSystemMenu() {
if (disableSystemMenu != 1) {
inMenu = true;
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
- } while (mouseButton);
+ manageEvents(MAKE_SYSTEM_MENU, UNTIL_MOUSE_BUTTON_UP);
+ getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
numEntry = 6;
@@ -407,11 +401,14 @@ void CineEngine::makeSystemMenu() {
numEntry--;
}
+ renderer->saveBackBuffer(BEFORE_OPENING_MENU);
+
systemCommand = makeMenuChoice(systemMenu, numEntry, mouseX, mouseY, 140);
switch (systemCommand) {
case 0: { // Pause
renderer->drawString(otherMessages[2], 0);
+ PauseToken pauseToken = g_cine->pauseEngine();
waitPlayerInput();
break;
}
@@ -441,12 +438,9 @@ void CineEngine::makeSystemMenu() {
}
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
- selectedSave = makeMenuChoice(currentSaveName, 10, mouseX, mouseY + 8, 180);
+ selectedSave = makeMenuChoice(currentSaveName, kMaxSavegames, mouseX, mouseY + 8, 180);
if (selectedSave >= 0) {
- char saveNameBuffer[256];
- sprintf(saveNameBuffer, "%s.%1d", _targetName.c_str(), selectedSave);
-
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
char loadString[256];
@@ -454,7 +448,7 @@ void CineEngine::makeSystemMenu() {
sprintf(loadString, otherMessages[3], currentSaveName[selectedSave]);
renderer->drawString(loadString, 0);
- makeLoad(saveNameBuffer);
+ loadGameState(selectedSave);
} else {
renderer->drawString(otherMessages[4], 0);
waitPlayerInput();
@@ -481,19 +475,18 @@ void CineEngine::makeSystemMenu() {
return;
}
- selectedSave = makeMenuChoice(currentSaveName, 10, mouseX, mouseY + 8, 180);
+ selectedSave = makeMenuChoice(currentSaveName, kMaxSavegames, mouseX, mouseY + 8, 180, g_cine->getAutosaveSlot() + 1);
if (selectedSave >= 0) {
- char saveFileName[256];
- char saveName[20];
+ CommandeType saveName;
saveName[0] = 0;
- if (!makeTextEntryMenu(otherMessages[6], saveName, 20, 120))
+ if (!makeTextEntryMenu(otherMessages[6], saveName, sizeof(CommandeType), 120))
break;
- Common::strlcpy(currentSaveName[selectedSave], saveName, 20);
+ Common::strlcpy(currentSaveName[selectedSave], saveName, sizeof(CommandeType));
- sprintf(saveFileName, "%s.%1d", _targetName.c_str(), selectedSave);
+ Common::String saveFileName = getSaveStateName(selectedSave);
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
@@ -506,13 +499,13 @@ void CineEngine::makeSystemMenu() {
break;
}
- fHandle->write(currentSaveName, 200);
+ fHandle->write(currentSaveName, sizeof(currentSaveName));
delete fHandle;
sprintf(saveString, otherMessages[3], currentSaveName[selectedSave]);
renderer->drawString(saveString, 0);
- makeSave(saveFileName);
+ makeSave(saveFileName, getTotalPlayTime() / 1000, Common::String((const char *)currentSaveName), false);
checkDataDisk(-1);
} else {
@@ -544,7 +537,6 @@ void drawDoubleMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 c
}
void processInventory(int16 x, int16 y) {
- uint16 button;
int menuWidth;
int listSize;
int commandParam;
@@ -572,15 +564,7 @@ void processInventory(int16 x, int16 y) {
delete menu;
menu = 0;
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
- } while (!button);
-
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
- } while (button);
+ manageEvents(PROCESS_INVENTORY, UNTIL_MOUSE_BUTTON_DOWN_UP);
}
int16 buildObjectListCommand(int16 param) {
@@ -611,7 +595,7 @@ int16 selectSubObject(int16 x, int16 y, int16 param) {
}
if (disableSystemMenu == 0) {
- selectedObject = makeMenuChoice(objectListCommand, listSize, x, y, 140, osExtras);
+ selectedObject = makeMenuChoice(objectListCommand, listSize, x, y, 140, 0, osExtras, osExtras);
}
if (selectedObject == -1)
@@ -635,7 +619,6 @@ void makeCommandLine() {
makeOSCommandLine();
}
-// TODO: Add support for using the different prepositions for different verbs (Doesn't work currently)
void makeOSCommandLine() {
uint16 x, y;
@@ -644,6 +627,13 @@ void makeOSCommandLine() {
if (playerCommand != -1) {
g_cine->_commandBuffer = defaultActionCommand[playerCommand];
+
+ // This is not present in disassembly but here to implement
+ // prepositions for other than USE command.
+ if (choiceResultTable[playerCommand] != 2 && commandPrepositionTable[playerCommand][0]) {
+ g_cine->_commandBuffer += " ";
+ g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
+ }
} else {
g_cine->_commandBuffer = "";
}
@@ -713,7 +703,9 @@ void makeOSCommandLine() {
if (di != -1) {
runObjectScript(di);
- } // TODO: else addFailureMessage(playerCommand)
+ } else {
+ addPlayerCommandMessage(playerCommand);
+ }
playerCommand = -1;
commandVar1 = 0;
@@ -725,7 +717,6 @@ void makeOSCommandLine() {
renderer->setCommand(g_cine->_commandBuffer);
}
-// TODO: Add support for using the different prepositions for different verbs (Doesn't work currently)
void makeFWCommandLine() {
uint16 x, y;
@@ -734,6 +725,13 @@ void makeFWCommandLine() {
if (playerCommand != -1) {
g_cine->_commandBuffer = defaultActionCommand[playerCommand];
+
+ // This is not present in disassembly but here to implement
+ // prepositions for other than USE command.
+ if (choiceResultTable[playerCommand] != 2 && commandPrepositionTable[playerCommand][0]) {
+ g_cine->_commandBuffer += " ";
+ g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
+ }
} else {
g_cine->_commandBuffer = "";
}
@@ -780,7 +778,7 @@ uint16 needMouseSave = 0;
uint16 menuVar4 = 0;
uint16 menuVar5 = 0;
-int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, bool recheckValue) {
+int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, int minY, bool recheckValue, bool allowEmpty) {
int16 paramY;
uint16 button;
int16 var_A;
@@ -790,6 +788,11 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
int16 var_4;
SelectionMenu *menu;
+ Common::StringArray list;
+ for (uint16 i = minY; i < height; ++i)
+ list.push_back(commandList[i]);
+ height -= minY; // Remove values before minY
+
paramY = (height * 9) + 10;
if (X + width > 319) {
@@ -800,32 +803,49 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
Y = 199 - paramY;
}
- Common::StringArray list;
- for (uint16 i = 0; i < height; ++i)
- list.push_back(commandList[i]);
menu = new SelectionMenu(Common::Point(X, Y), width, list);
renderer->pushMenu(menu);
renderer->drawFrame();
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
- } while (button && !g_cine->shouldQuit());
+ manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_UP);
+ getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
var_A = 0;
- currentSelection = 0;
+ currentSelection = allowEmpty ? -1 : 0;
menu->setSelection(currentSelection);
renderer->drawFrame();
- manageEvents();
+ manageEvents(MAKE_MENU_CHOICE, EMPTY_EVENT_QUEUE);
getMouseData(mouseUpdateStatus, &button, (uint16 *)&mouseX, (uint16 *)&mouseY);
menuVar = 0;
do {
- manageEvents();
+ Common::Rect all(X + 1, Y + 1, X + width, Y + paramY + 1);
+ Common::Array<Common::Rect> rects;
+
+ if (currentSelection == -1) {
+ rects.push_back(all);
+ } else if (allowEmpty) {
+ rects.push_back(Common::Rect(0, 0, 320, all.top)); // All above menu (left, top, right)
+ rects.push_back(Common::Rect(0, all.bottom, 320, 200)); // All below menu (left, bottom, right)
+ rects.push_back(Common::Rect(0, all.top, all.left, all.bottom)); // To the left of menu
+ rects.push_back(Common::Rect(all.right, all.top, 320, all.bottom)); // To the right of menu
+ }
+
+ if (currentSelection > 0) {
+ Common::Rect aboveCurr(X + 1, Y + 1, X + width, currentSelection * 9 + Y + 4);
+ rects.push_back(aboveCurr);
+ }
+
+ if (currentSelection != -1 && currentSelection < height - 1) {
+ Common::Rect belowCurr(X + 1, currentSelection * 9 + 13 + Y, X + width, Y + paramY + 1);
+ rects.push_back(belowCurr);
+ }
+
+ manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS, false, rects);
getMouseData(mouseUpdateStatus, &button, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (button) {
@@ -849,7 +869,47 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
}
}
} else {
- if (mouseX > X && mouseX < X + width && mouseY > Y && mouseY < Y + height * 9) {
+ int selectionValueDiff = 0;
+ while (!g_cine->_keyInputList.empty()) {
+ switch (g_cine->_keyInputList.back().keycode) {
+ case Common::KEYCODE_UP:
+ selectionValueDiff--;
+ break;
+ case Common::KEYCODE_DOWN:
+ selectionValueDiff++;
+ break;
+ }
+ g_cine->_keyInputList.pop_back();
+ }
+
+ if (selectionValueDiff != 0) {
+ if (currentSelection == -1) {
+ if (selectionValueDiff > 0) {
+ // Start going down from the top
+ currentSelection = 0;
+ selectionValueDiff--;
+ } else if (selectionValueDiff < 0) {
+ // Start going up from the bottom
+ currentSelection = height - 1;
+ selectionValueDiff++;
+ }
+ }
+
+ currentSelection = CLIP<int16>(currentSelection + selectionValueDiff, 0, height - 1);
+
+ if (currentSelection != oldSelection) {
+ Common::Point currentSelectionCenter(X + width / 2, (currentSelection * 9) + Y + 8);
+ g_system->warpMouse(currentSelectionCenter.x, currentSelectionCenter.y);
+ }
+ } else if (mouseX > X && mouseX < X + width && mouseY > Y && mouseY < Y + paramY + 1) {
+ // Y value range for selection s:
+ // (mouseY - (Y + 4)) / 9 >= s
+ // mouseY - (Y + 4) >= s * 9 <=>
+ // mouseY >= s * 9 + Y + 4
+ //
+ // (mouseY - (Y + 4)) / 9 < (s + 1) <=>
+ // mouseY - (Y + 4) < (s + 1) * 9 <=>
+ // mouseY < s * 9 + 13 + Y
currentSelection = (mouseY - (Y + 4)) / 9;
if (currentSelection < 0)
@@ -857,6 +917,8 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
if (currentSelection >= height)
currentSelection = height - 1;
+ } else if (allowEmpty) {
+ currentSelection = -1;
}
}
@@ -881,15 +943,17 @@ int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X,
menuVar = 0;
- do {
- manageEvents();
- getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
- } while (button && !g_cine->shouldQuit());
+ manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_UP);
+ getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
+
+ if (currentSelection != -1) {
+ currentSelection += minY;
+ }
if (var_4 == 2) { // recheck
if (!recheckValue)
return -1;
- else
+ else if (currentSelection != -1)
return currentSelection + 8000;
}
@@ -907,7 +971,7 @@ void makeActionMenu() {
if (g_cine->getGameType() == Cine::GType_OS) {
if (disableSystemMenu == 0) {
- playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70, true);
+ playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70, 0, true, true);
}
if (playerCommand >= 8000) {
@@ -923,18 +987,138 @@ void makeActionMenu() {
inMenu = false;
}
+void mouseLeftRightDown() {
+ // Left and right mouse buttons are down
+ g_cine->makeSystemMenu();
+}
+
+void allowPlayerInputMouseRightDown() {
+ // Player input is allowed, left mouse button is up, right mouse button is down
+ makeActionMenu();
+ makeCommandLine();
+}
+
+void playerCommandMouseLeft(uint16 &mouseButton, uint16 &mouseX, uint16 &mouseY) {
+ // A player command is given, left mouse button is down, right mouse button is up
+ int16 si;
+ manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_UP);
+ getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
+
+ si = getObjectUnderCursor(mouseX, mouseY);
+
+ if (si != -1) {
+ commandVar3[commandVar1] = si;
+ commandVar1++;
+
+ g_cine->_commandBuffer += " ";
+ g_cine->_commandBuffer += g_cine->_objectTable[si].name;
+
+ isDrawCommandEnabled = 1;
+
+ if (choiceResultTable[playerCommand] == commandVar1) {
+ int16 relEntry;
+
+ SelectedObjStruct obj;
+ obj.idx = commandVar3[0];
+ obj.param = commandVar3[1];
+
+ relEntry = getRelEntryForObject(playerCommand, commandVar1, &obj);
+
+ if (relEntry != -1) {
+ runObjectScript(relEntry);
+ } else {
+ addPlayerCommandMessage(playerCommand);
+ }
+
+ playerCommand = -1;
+
+ commandVar1 = 0;
+ g_cine->_commandBuffer = "";
+ } else if (g_cine->getGameType() == Cine::GType_OS) {
+ isDrawCommandEnabled = 1;
+ g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
+ }
+
+ renderer->setCommand(g_cine->_commandBuffer);
+ } else {
+ g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
+ if (!mouseX) {
+ g_cine->_globalVars[VAR_MOUSE_X_POS]++;
+ }
+
+ g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
+
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ if (!mouseY) {
+ g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
+ }
+ g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
+ g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
+ }
+ }
+}
+
+void playerCommandMouseLeftRightUp(uint16 mouseX, uint16 mouseY) {
+ // A player command is given, left and right mouse buttons are up
+ int16 objIdx;
+
+ objIdx = getObjectUnderCursor(mouseX, mouseY);
+
+ if (g_cine->getGameType() == Cine::GType_OS || commandVar2 != objIdx) {
+ if (objIdx != -1) {
+ renderer->setCommand(g_cine->_commandBuffer + " " + g_cine->_objectTable[objIdx].name);
+ } else {
+ isDrawCommandEnabled = 1;
+ }
+ }
+
+ commandVar2 = objIdx;
+}
+
+void noPlayerCommandMouseLeft(uint16 &mouseX, uint16 &mouseY) {
+ // No player command is given, left mouse button is down, right mouse button is up
+ int16 objIdx;
+ int16 relEntry;
+
+ g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
+ if (!mouseX) {
+ g_cine->_globalVars[VAR_MOUSE_X_POS]++;
+ }
+
+ g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
+
+ if (g_cine->getGameType() == Cine::GType_OS) {
+ if (!mouseY) {
+ g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
+ }
+ g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
+ g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
+ }
+
+ objIdx = getObjectUnderCursor(mouseX, mouseY);
+
+ if (objIdx != -1) {
+ currentSelectedObject.idx = objIdx;
+ currentSelectedObject.param = -1;
+
+ relEntry = getRelEntryForObject(6, 1, ¤tSelectedObject);
+
+ if (relEntry != -1) {
+ runObjectScript(relEntry);
+ }
+ }
+}
+
uint16 executePlayerInput() {
uint16 var_5E;
uint16 var_2;
uint16 mouseX, mouseY, mouseButton;
- uint16 currentEntry = 0;
- uint16 di = 0;
- bool limitMouseCheckCount = false;
canUseOnObject = 0;
if (isInPause) {
renderer->drawString(otherMessages[2], 0);
+ PauseToken pauseToken = g_cine->pauseEngine();
waitPlayerInput();
isInPause = 0;
}
@@ -944,148 +1128,44 @@ uint16 executePlayerInput() {
renderer->setCommand(g_cine->_commandBuffer);
}
isDrawCommandEnabled = 0;
- limitMouseCheckCount = true;
+
+ // DIFFERENCE FROM DISASSEMBLY:
+ // See renderer's drawFrame function for comments related to how the waiting
+ // period from the original engine has been moved to here. In the original
+ // engine a maximum of 200 pollings of the mouse state were done here and
+ // the maximum state of the mouse buttons were read. Now we wait for
+ // g_cine->getTimerDelay() here while reading the mouse state and updating
+ // the command line.
+ manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_WAIT_ENDED, true);
+ } else {
+ manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED);
}
// Get mouse position and button states
- di = 0;
- currentEntry = 0;
getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY);
- while (mouseButton && (!limitMouseCheckCount || currentEntry < 200) && !g_cine->shouldQuit()) {
- di |= (mouseButton & (kLeftMouseButton | kRightMouseButton));
- if (!limitMouseCheckCount) {
- manageEvents();
- }
- getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY);
- currentEntry++;
- }
-
- if (di) {
- mouseButton = di;
- }
-
if ((mouseButton & kLeftMouseButton) && (mouseButton & kRightMouseButton)) {
// Left and right mouse buttons are down
- g_cine->makeSystemMenu();
+ mouseLeftRightDown();
} else if (allowPlayerInput) { // Player input is allowed
if (!(mouseButton & kLeftMouseButton) && (mouseButton & kRightMouseButton)) {
// Player input is allowed, left mouse button is up, right mouse button is down
- makeActionMenu();
- makeCommandLine();
+ allowPlayerInputMouseRightDown();
} else if (playerCommand != -1) { // A player command is given
if (mouseButton & kLeftMouseButton) { // Left mouse button is down
if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
// A player command is given, left mouse button is down, right mouse button is up
- int16 si;
- while (mouseButton && !g_cine->shouldQuit()) {
- manageEvents();
- getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
- }
-
- si = getObjectUnderCursor(mouseX, mouseY);
-
- if (si != -1) {
- commandVar3[commandVar1] = si;
- commandVar1++;
-
- g_cine->_commandBuffer += " ";
- g_cine->_commandBuffer += g_cine->_objectTable[si].name;
-
- isDrawCommandEnabled = 1;
-
- if (choiceResultTable[playerCommand] == commandVar1) {
- int16 relEntry;
-
- SelectedObjStruct obj;
- obj.idx = commandVar3[0];
- obj.param = commandVar3[1];
-
- relEntry = getRelEntryForObject(playerCommand, commandVar1, &obj);
-
- if (relEntry != -1) {
- runObjectScript(relEntry);
- } else {
- addPlayerCommandMessage(playerCommand);
- }
-
- playerCommand = -1;
-
- commandVar1 = 0;
- g_cine->_commandBuffer = "";
- } else if (g_cine->getGameType() == Cine::GType_OS) {
- isDrawCommandEnabled = 1;
- g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
- }
-
- renderer->setCommand(g_cine->_commandBuffer);
- } else {
- g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
- if (!mouseX) {
- g_cine->_globalVars[VAR_MOUSE_X_POS]++;
- }
-
- g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
-
- if (g_cine->getGameType() == Cine::GType_OS) {
- if (!mouseY) {
- g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
- }
- g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
- g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
- }
- }
+ playerCommandMouseLeft(mouseButton, mouseX, mouseY);
}
} else if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
// A player command is given, left and right mouse buttons are up
- int16 objIdx;
-
- objIdx = getObjectUnderCursor(mouseX, mouseY);
-
- if (g_cine->getGameType() == Cine::GType_OS || commandVar2 != objIdx) {
- if (objIdx != -1) {
- renderer->setCommand(g_cine->_commandBuffer + " " + g_cine->_objectTable[objIdx].name);
- } else {
- isDrawCommandEnabled = 1;
- }
- }
-
- commandVar2 = objIdx;
+ playerCommandMouseLeftRightUp(mouseX, mouseY);
}
} else { // No player command is given
if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
if (mouseButton & kLeftMouseButton) { // Left mouse button is down
// No player command is given, left mouse button is down, right mouse button is up
- int16 objIdx;
- int16 relEntry;
-
- g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
- if (!mouseX) {
- g_cine->_globalVars[VAR_MOUSE_X_POS]++;
- }
-
- g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
-
- if (g_cine->getGameType() == Cine::GType_OS) {
- if (!mouseY) {
- g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
- }
- g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
- g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
- }
-
- objIdx = getObjectUnderCursor(mouseX, mouseY);
-
- if (objIdx != -1) {
- currentSelectedObject.idx = objIdx;
- currentSelectedObject.param = -1;
-
- relEntry = getRelEntryForObject(6, 1, ¤tSelectedObject);
-
- if (relEntry != -1) {
- runObjectScript(relEntry);
- }
- }
+ noPlayerCommandMouseLeft(mouseX, mouseY);
}
}
}
@@ -1290,7 +1370,7 @@ void removeMessages() {
// 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));
+ remove = it->type == 3 || (it->type == 2 && (it->color >= 0 || ++(it->color) >= 0));
} else { // Future Wars
remove = it->type == 2 || it->type == 3;
}
@@ -1308,6 +1388,8 @@ uint16 processKeyboard(uint16 param) {
}
void mainLoopSub6() {
+ // (10923000 ms * 3) / 1193180 ~= 27 ms.
+ //g_system->delayMillis(27);
}
void checkForPendingDataLoad() {
@@ -1391,6 +1473,7 @@ void removeSeq(uint16 param1, uint16 param2, uint16 param3) {
}
}
+// Checked against Operation Stealth 16 color DOS disassembly, should be correct.
bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {
Common::List<SeqListElement>::iterator it;
@@ -1497,7 +1580,9 @@ uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &ele
param1, objIdx, (const void *)ptr, element.var8, element.var14, param3);
// In the original an error string is set and 0 is returned if the following doesn't hold
- assert(ptr);
+ if (!ptr) {
+ return 0;
+ }
// We probably could just use a local variable here instead of the dummyU16 but
// haven't checked if this has any side-effects so keeping it this way still.
@@ -1505,7 +1590,9 @@ uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &ele
ptrData = ptr + dummyU16;
// In the original an error string is set and 0 is returned if the following doesn't hold
- assert(*ptrData);
+ if (!*ptrData) {
+ return 0;
+ }
di = (g_cine->_objectTable[objIdx].costume + 1) % (*ptrData);
++ptrData; // Jump over the just read byte
@@ -1696,7 +1783,7 @@ void processSeqListElement(SeqListElement &element) {
}
}
- if (element.var16 + element.var14 == 0) {
+ if (((element.var16 + element.var14) & 0xFFFF) == 0) {
if (element.var1C) {
if (element.var1E) {
g_cine->_objectTable[element.objIdx].costume = 0;
@@ -1732,7 +1819,7 @@ bool makeTextEntryMenu(const char *messagePtr, char *inputString, int stringMaxL
int16 x = (320 - width) / 2;
- getKeyData(); // clear input key
+ g_cine->_keyInputList.clear();
int quit = 0;
bool redraw = true;
@@ -1750,12 +1837,23 @@ bool makeTextEntryMenu(const char *messagePtr, char *inputString, int stringMaxL
redraw = false;
}
+ if (g_cine->_keyInputList.empty()) {
+ manageEvents(MAKE_TEXT_ENTRY_MENU, UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT);
+ }
+
char ch[2];
memset(tempString, 0, stringMaxLength);
ch[1] = 0;
- manageEvents();
- int keycode = getKeyData();
+ Common::KeyState keyState = Common::KeyState();
+
+ if (!g_cine->_keyInputList.empty()) {
+ keyState = g_cine->_keyInputList.back();
+ g_cine->_keyInputList.pop_back();
+ }
+
+ int keycode = keyState.keycode;
+ uint16 ascii = keyState.ascii;
uint16 mouseButton, mouseX, mouseY;
getMouseData(0, &mouseButton, &mouseX, &mouseY);
@@ -1799,12 +1897,12 @@ bool makeTextEntryMenu(const char *messagePtr, char *inputString, int stringMaxL
}
break;
default:
- if (((keycode >= 'a') && (keycode <= 'z')) ||
- ((keycode >= '0') && (keycode <= '9')) ||
- ((keycode >= 'A') && (keycode <= 'Z')) ||
- (keycode == ' ')) {
+ if (((ascii >= 'a') && (ascii <= 'z')) ||
+ ((ascii >= '0') && (ascii <= '9')) ||
+ ((ascii >= 'A') && (ascii <= 'Z')) ||
+ (ascii == ' ')) {
if (inputLength < stringMaxLength - 1) {
- ch[0] = keycode;
+ ch[0] = ascii;
if (inputPos != 1) {
strncpy(tempString, inputString, inputPos - 1);
strcat(tempString, ch);
diff --git a/engines/cine/various.h b/engines/cine/various.h
index eb6312997d..45f091998e 100644
--- a/engines/cine/various.h
+++ b/engines/cine/various.h
@@ -25,17 +25,20 @@
#include "common/file.h"
+#include "common/keyboard.h"
#include "cine/cine.h"
namespace Cine {
+#define kMaxSavegames 20 // 20 fit on screen using original save/load interface
+
// 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);
+int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, int minY = 0, bool recheckValue = false, bool allowEmpty = false);
void makeCommandLine();
void makeFWCommandLine();
void makeOSCommandLine();
@@ -45,8 +48,9 @@ void setTextWindow(uint16 param1, uint16 param2, uint16 param3, uint16 param4);
extern int16 disableSystemMenu;
extern bool inMenu;
+extern bool runOnlyUntilCopyProtectionCheck;
-extern CommandeType currentSaveName[10];
+extern CommandeType currentSaveName[kMaxSavegames];
struct SeqListElement {
int16 var4;
@@ -68,7 +72,11 @@ struct SeqListElement {
extern uint16 var2;
extern uint16 var3;
extern uint16 var4;
-extern uint16 var5;
+extern uint16 lastType20OverlayBgIdx;
+extern uint16 reloadBgPalOnNextFlip;
+extern uint16 forbidBgPalReload;
+extern uint16 gfxFadeOutCompleted;
+extern uint16 gfxFadeInRequested;
extern int16 commandVar1;
extern int16 commandVar2;
extern int16 commandVar3[4];
@@ -83,7 +91,6 @@ extern uint16 allowPlayerInput;
extern uint16 checkForPendingDataLoadSwitch;
-extern bool fadeRequired;
extern uint16 isDrawCommandEnabled;
extern uint16 waitForPlayerClick;
extern uint16 menuCommandLen;
@@ -106,6 +113,7 @@ extern char currentPartName[15];
void stopSample();
void stopMusicAfterFadeOut();
+void playerCommandMouseLeftRightUp(uint16 mouseX, uint16 mouseY);
uint16 executePlayerInput();
void drawOverlays();
@@ -114,7 +122,6 @@ extern uint16 mouseUpdateStatus;
extern uint16 dummyU16;
void getMouseData(uint16 param, uint16 *pButton, uint16 *pX, uint16 *pY);
-int getKeyData();
uint16 processKeyboard(uint16 param);
@@ -147,6 +154,7 @@ void resetGfxEntityEntry(uint16 objIdx);
bool makeTextEntryMenu(const char *caption, char *string, int strLen, int y);
void moveUsingKeyboard(int x, int y);
+int16 getObjectUnderCursor(uint16 x, uint16 y);
} // End of namespace Cine
Commit: 9c68b62eb9d9b48d67506ef3d843cac5bd2fdea2
https://github.com/scummvm/scummvm/commit/9c68b62eb9d9b48d67506ef3d843cac5bd2fdea2
Author: Kari Salminen (kari.salminen at gmail.com)
Date: 2020-07-25T00:33:42+02:00
Commit Message:
CINE: Fix clang compilation and DeepCode warning.
Change structure initialization from C++11 style to older style to fix
clang compilation. Add null check to fix DeepCode warning about
possible undefined behaviour.
Changed paths:
engines/cine/pal.cpp
engines/cine/sound.cpp
diff --git a/engines/cine/pal.cpp b/engines/cine/pal.cpp
index 21cdc068e8..ed0fd3f620 100644
--- a/engines/cine/pal.cpp
+++ b/engines/cine/pal.cpp
@@ -203,8 +203,8 @@ int Palette::findMinBrightnessColorIndex(uint minColorIndex) {
bool Palette::ensureContrast(byte &minBrightnessColorIndex) {
minBrightnessColorIndex = findMinBrightnessColorIndex();
if (_colors.size() >= 3 && isEqual(2, minBrightnessColorIndex)) {
- Color black{ 0, 0, 0 };
- Color white{_format.rMax(), _format.gMax(), _format.bMax() };
+ Color black = {0, 0, 0};
+ Color white = {_format.rMax(), _format.gMax(), _format.bMax()};
_colors[2] = white;
if (isEqual(2, minBrightnessColorIndex)) {
diff --git a/engines/cine/sound.cpp b/engines/cine/sound.cpp
index 0bd590d3ac..970690f79c 100644
--- a/engines/cine/sound.cpp
+++ b/engines/cine/sound.cpp
@@ -1051,6 +1051,9 @@ bool PCSoundFxPlayer::load(const char *song) {
data && instrumentSize > 0x16) {
instrumentSize -= 0x16;
byte *tmp = (byte *)calloc(instrumentSize, 1);
+ if (tmp == NULL) {
+ error("PCSoundFxPlayer::load('%s'): Out of memory (%d bytes)", song, instrumentSize);
+ }
memcpy(tmp, data + 0x16, instrumentSize);
free(data);
data = tmp;
Commit: 580a7e6568839a6505b0d8a10244a814dd16c03b
https://github.com/scummvm/scummvm/commit/580a7e6568839a6505b0d8a10244a814dd16c03b
Author: Kari Salminen (kari.salminen at gmail.com)
Date: 2020-07-25T00:33:42+02:00
Commit Message:
CINE: Fix formatting, string copy and signedness.
Fix code formatting, string copying (strncpy -> strlcpy) and signedness (int -> uint).
Changed paths:
engines/cine/anim.cpp
engines/cine/detection.cpp
engines/cine/pal.cpp
diff --git a/engines/cine/anim.cpp b/engines/cine/anim.cpp
index bcef2ddc0d..ee6a519c0d 100644
--- a/engines/cine/anim.cpp
+++ b/engines/cine/anim.cpp
@@ -820,19 +820,18 @@ int loadResource(const char *resourceName, int16 idx, int16 frameIndex) {
if (g_cine->getGameType() == Cine::GType_OS &&
g_cine->getPlatform() == Common::kPlatformDOS &&
g_sound->musicType() != MT_MT32 &&
- (strstr(resourceName, ".SPL") || strstr(resourceName, ".H32")))
- {
+ (strstr(resourceName, ".SPL") || strstr(resourceName, ".H32"))) {
char base[20];
removeExtention(base, resourceName);
- for (int i = 0; i < ARRAYSIZE(resNameMapping); i++) {
+ for (uint i = 0; i < ARRAYSIZE(resNameMapping); i++) {
if (scumm_stricmp(base, resNameMapping[i].from) == 0) {
- strncpy(base, resNameMapping[i].to, sizeof(base));
+ Common::strlcpy(base, resNameMapping[i].to, sizeof(base));
break;
}
}
- const char* ext = (g_sound->musicType() == MT_ADLIB) ? ".ADL" : ".HP";
+ const char *ext = (g_sound->musicType() == MT_ADLIB) ? ".ADL" : ".HP";
strcat(base, ext);
return loadResource(base, idx, frameIndex);
}
diff --git a/engines/cine/detection.cpp b/engines/cine/detection.cpp
index 79f797527f..18620e956b 100644
--- a/engines/cine/detection.cpp
+++ b/engines/cine/detection.cpp
@@ -331,7 +331,7 @@ void CineMetaEngine::removeSaveState(const char *target, int slot) const {
delete out;
// Delete save file
- const char * saveFileName = getSavegameFile(slot, target);
+ const char *saveFileName = getSavegameFile(slot, target);
g_system->getSavefileManager()->removeSavefile(saveFileName);
}
diff --git a/engines/cine/pal.cpp b/engines/cine/pal.cpp
index ed0fd3f620..9d18548b1c 100644
--- a/engines/cine/pal.cpp
+++ b/engines/cine/pal.cpp
@@ -145,7 +145,7 @@ Palette &Palette::rotateRight(byte firstIndex, byte lastIndex) {
const Color lastColor = _colors[lastIndex];
- for (int i = lastIndex; i > firstIndex; i--)
+ for (uint i = lastIndex; i > firstIndex; i--)
_colors[i] = _colors[i - 1];
_colors[firstIndex] = lastColor;
@@ -158,7 +158,7 @@ Palette &Palette::rotateLeft(byte firstIndex, byte lastIndex) {
const Color firstColor = _colors[firstIndex];
- for (int i = firstIndex; i < lastIndex; i++)
+ for (uint i = firstIndex; i < lastIndex; i++)
_colors[i] = _colors[i + 1];
_colors[lastIndex] = firstColor;
More information about the Scummvm-git-logs
mailing list