[Scummvm-git-logs] scummvm master -> 62712ca02b34478a6b14a34cfe451a95cf23b74c
elasota
noreply at scummvm.org
Thu May 4 03:41:32 UTC 2023
This automated email contains information about 11 new commits which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .
Summary:
1c5898d3fd VCRUISE: Add support for Schizm script dialect.
8f38fbcdc7 VCRUISE: Add function calls and Schizm main menu things
50a505b6a8 VCRUISE: Add animation volume handling and score loader.
cc0dcb1f71 VCRUISE: Fix up some initial Schizm things.
0188660e82 VCRUISE: Fix up volume calculations for Schizm.
3cb2a31280 VCRUISE: Fix some function names being parsed as hex numbers. Add disc type variations.
298011eb43 VCRUISE: Support Schizm's presets section in playlist file
e7f10fe8f8 VCRUISE: Fix Schizm DVD identification
d0aee6d817 VCRUISE: Add menu text labels
9923c44a78 VCRUISE: Fix options
62712ca02b VCRUISE: Add skip main menu option functionality.
Commit: 1c5898d3fdee849e6051c181e7ee97b1ea800928
https://github.com/scummvm/scummvm/commit/1c5898d3fdee849e6051c181e7ee97b1ea800928
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:43-04:00
Commit Message:
VCRUISE: Add support for Schizm script dialect.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
engines/vcruise/script.h
engines/vcruise/textparser.cpp
engines/vcruise/textparser.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index a41a008e982..580cea7d983 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -116,13 +116,9 @@ Common::Point RuntimeMenuInterface::getMouseCoordinate() const {
void RuntimeMenuInterface::restartGame() const {
Common::SharedPtr<SaveGameSnapshot> snapshot(new SaveGameSnapshot());
- if (_runtime->_gameID == GID_REAH) {
- snapshot->roomNumber = 1;
- snapshot->screenNumber = 0xb0;
- snapshot->loadedAnimation = 1;
- } else {
- error("Don't know what screen to start on for this game");
- }
+ snapshot->roomNumber = 1;
+ snapshot->screenNumber = 0xb0;
+ snapshot->loadedAnimation = 1;
_runtime->_saveGame = snapshot;
_runtime->restoreSaveGameSnapshot();
@@ -1043,13 +1039,49 @@ bool Runtime::bootGame(bool newGame) {
_gameState = kGameStateIdle;
- if (newGame) {
- if (_gameID == GID_REAH) {
- changeToScreen(1, 0xb1);
- } else
- error("Couldn't figure out what screen to start on");
+ if (_gameID == GID_SCHIZM) {
+ Common::SharedPtr<IScriptCompilerGlobalState> gs = createScriptCompilerGlobalState();
+
+ // Precompile all scripts. We must do this because global functions are stored in room 3, which is never used.
+ Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
+
+ Common::ArchiveMemberList scriptFiles;
+ SearchMan.listMatchingMembers(scriptFiles, "Log/Room##.log", true);
+
+ Common::Array<bool> scriptExists;
+
+ uint highestScriptIndex = 0;
+ for (const Common::ArchiveMemberPtr &scriptFile : scriptFiles) {
+ Common::String scriptName = scriptFile->getName();
+
+ uint scriptIndex = ((scriptName[4] - '0') * 10) + (scriptName[5] - '0');
+ if (scriptIndex > highestScriptIndex) {
+ highestScriptIndex = scriptIndex;
+ scriptExists.resize(highestScriptIndex + 1);
+ }
+ scriptExists[scriptIndex] = true;
+ }
+
+ for (uint i = 0; i <= highestScriptIndex; i++) {
+ if (!scriptExists[i])
+ continue;
+
+ Common::String logicFileName = Common::String::format("Log/Room%02u.log", i);
+
+ Common::File logicFile;
+ if (logicFile.open(logicFileName)) {
+ debug(1, "Compiling script %s...", logicFileName.c_str());
+ compileSchizmLogicFile(*scriptSet, logicFile, static_cast<uint>(logicFile.size()), logicFileName, gs.get());
+ logicFile.close();
+ }
+ }
+
+ _scriptSet = scriptSet;
}
+ if (newGame)
+ changeToScreen(1, 0xb1);
+
Common::Language lang = Common::parseLanguage(ConfMan.get("language"));
bool foundLang = false;
@@ -1738,6 +1770,8 @@ bool Runtime::runScript() {
DISPATCH_OP(AnimG);
DISPATCH_OP(AnimS);
DISPATCH_OP(Anim);
+ DISPATCH_OP(AnimChange);
+ DISPATCH_OP(AnimVolume);
DISPATCH_OP(Static);
DISPATCH_OP(VarLoad);
@@ -1773,6 +1807,7 @@ bool Runtime::runScript() {
DISPATCH_OP(Music);
DISPATCH_OP(MusicVolRamp);
+ DISPATCH_OP(MusicStop);
DISPATCH_OP(Parm0);
DISPATCH_OP(Parm1);
DISPATCH_OP(Parm2);
@@ -2280,14 +2315,21 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
// This shouldn't happen when running a script
assert(!_activeScript);
- _scriptSet.reset();
+ if (_gameID == GID_SCHIZM) {
+ // Keep script set
+ } else if (_gameID == GID_REAH) {
+ _scriptSet.reset();
+
+ Common::String logicFileName = Common::String::format("Log/Room%02i.log", static_cast<int>(roomNumber));
+ Common::File logicFile;
+ if (logicFile.open(logicFileName)) {
+ _scriptSet = compileReahLogicFile(logicFile, static_cast<uint>(logicFile.size()), logicFileName);
+
+ logicFile.close();
+ }
+ } else
+ error("Don't know how to compile scripts for this game");
- Common::String logicFileName = Common::String::format("Log/Room%02i.log", static_cast<int>(roomNumber));
- Common::File logicFile;
- if (logicFile.open(logicFileName)) {
- _scriptSet = compileLogicFile(logicFile, static_cast<uint>(logicFile.size()), logicFileName);
- logicFile.close();
- }
_map.clear();
@@ -4473,6 +4515,9 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
}
}
+OPCODE_STUB(AnimChange)
+OPCODE_STUB(AnimVolume)
+
void Runtime::scriptOpStatic(ScriptArg_t arg) {
TAKE_STACK_INT(kAnimDefStackArgs);
@@ -4863,6 +4908,7 @@ void Runtime::scriptOpMusicVolRamp(ScriptArg_t arg) {
}
}
+OPCODE_STUB(MusicStop)
void Runtime::scriptOpParm0(ScriptArg_t arg) {
TAKE_STACK_INT(4);
@@ -5433,9 +5479,9 @@ void Runtime::scriptOpAnimName(ScriptArg_t arg) {
error("Can't resolve animation for room, room number was invalid");
- Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(_scriptSet->strings[arg]);
+ Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(_activeScript->strings[arg]);
if (it == roomDef->animations.end())
- error("Can't resolve animation for room, couldn't find animation '%s'", _scriptSet->strings[arg].c_str());
+ error("Can't resolve animation for room, couldn't find animation '%s'", _activeScript->strings[arg].c_str());
pushAnimDef(it->_value);
}
@@ -5448,7 +5494,7 @@ void Runtime::scriptOpValueName(ScriptArg_t arg) {
if (!roomDef)
error("Room def doesn't exist");
- const Common::String &varName = _scriptSet->strings[arg];
+ const Common::String &varName = _activeScript->strings[arg];
Common::HashMap<Common::String, int>::const_iterator it = roomDef->values.find(varName);
if (it == roomDef->values.end())
@@ -5465,7 +5511,7 @@ void Runtime::scriptOpVarName(ScriptArg_t arg) {
if (!roomDef)
error("Room def doesn't exist");
- const Common::String &varName = _scriptSet->strings[arg];
+ const Common::String &varName = _activeScript->strings[arg];
Common::HashMap<Common::String, uint>::const_iterator it = roomDef->vars.find(varName);
if (it == roomDef->vars.end())
@@ -5475,11 +5521,11 @@ void Runtime::scriptOpVarName(ScriptArg_t arg) {
}
void Runtime::scriptOpSoundName(ScriptArg_t arg) {
- _scriptStack.push_back(StackValue(_scriptSet->strings[arg]));
+ _scriptStack.push_back(StackValue(_activeScript->strings[arg]));
}
void Runtime::scriptOpCursorName(ScriptArg_t arg) {
- const Common::String &cursorName = _scriptSet->strings[arg];
+ const Common::String &cursorName = _activeScript->strings[arg];
Common::HashMap<Common::String, StackInt_t>::const_iterator namedCursorIt = _namedCursors.find(cursorName);
if (namedCursorIt == _namedCursors.end()) {
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index b95a50f9288..08dc37247da 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -74,6 +74,7 @@ class RuntimeMenuInterface;
class TextParser;
struct ScriptSet;
struct Script;
+struct IScriptCompilerGlobalState;
struct Instruction;
enum GameState {
@@ -768,6 +769,8 @@ private:
void scriptOpAnimG(ScriptArg_t arg);
void scriptOpAnimS(ScriptArg_t arg);
void scriptOpAnim(ScriptArg_t arg);
+ void scriptOpAnimChange(ScriptArg_t arg);
+ void scriptOpAnimVolume(ScriptArg_t arg);
void scriptOpStatic(ScriptArg_t arg);
void scriptOpVarLoad(ScriptArg_t arg);
@@ -805,6 +808,7 @@ private:
void scriptOpMusic(ScriptArg_t arg);
void scriptOpMusicVolRamp(ScriptArg_t arg);
+ void scriptOpMusicStop(ScriptArg_t arg);
void scriptOpParm0(ScriptArg_t arg);
void scriptOpParm1(ScriptArg_t arg);
void scriptOpParm2(ScriptArg_t arg);
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 4661241a07b..43d57b16621 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -28,6 +28,11 @@
namespace VCruise {
+enum ScriptDialect {
+ kScriptDialectReah,
+ kScriptDialectSchizm,
+};
+
class LogicUnscrambleStream : public Common::ReadStream {
public:
LogicUnscrambleStream(Common::ReadStream *stream, uint streamSize);
@@ -129,11 +134,17 @@ ProtoInstruction::ProtoInstruction(ProtoOp paramProtoOp, ScriptOps::ScriptOp par
struct ProtoScript {
Common::Array<ProtoInstruction> instrs;
+ Common::Array<Common::String> strings;
+ Common::HashMap<Common::String, uint> stringToIndex;
+
void reset();
};
void ProtoScript::reset() {
instrs.clear();
+
+ strings.clear();
+ stringToIndex.clear(true);
}
struct ScriptNamedInstruction {
@@ -144,9 +155,9 @@ struct ScriptNamedInstruction {
class ScriptCompiler {
public:
- ScriptCompiler(TextParser &parser, const Common::String &blamePath);
+ ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, IScriptCompilerGlobalState *gs);
- void compileRoomScriptSet(ScriptSet *ss);
+ void compileScriptSet(ScriptSet *ss);
private:
bool parseNumber(const Common::String &token, uint32 &outNumber) const;
@@ -156,12 +167,14 @@ private:
void expectNumber(uint32 &outNumber);
void compileRoomScriptSet(RoomScriptSet *rss);
- void compileScreenScriptSet(ScreenScriptSet *sss);
+ void compileReahScreenScriptSet(ScreenScriptSet *sss);
+ void compileSchizmScreenScriptSet(ScreenScriptSet *sss);
+ void compileFunction(Script *script);
bool compileInstructionToken(ProtoScript &script, const Common::String &token);
void codeGenScript(ProtoScript &protoScript, Script &script);
- uint indexString(const Common::String &str);
+ static uint indexString(ProtoScript &script, const Common::String &str);
enum NumberParsingMode {
kNumberParsingDec,
@@ -173,32 +186,62 @@ private:
NumberParsingMode _numberParsingMode;
const Common::String _blamePath;
- Common::HashMap<Common::String, uint> _stringToIndex;
- Common::Array<Common::String> _strings;
+ ScriptDialect _dialect;
+
+ const char *_scrToken;
+ const char *_eroomToken;
+
+ IScriptCompilerGlobalState *_gs;
+};
+
+class ScriptCompilerGlobalState : public IScriptCompilerGlobalState {
+public:
+ void define(const Common::String &key, const Common::String &value) override;
+
+ const Common::String *getTokenReplacement(const Common::String &str) const override;
+
+ void addFunction(const Common::String &fnName, const Common::SharedPtr<Script> &fn) override;
+ Common::SharedPtr<Script> getFunction(const Common::String &fnName) const override;
+
+private:
+ Common::HashMap<Common::String, Common::String> _defs;
+ Common::HashMap<Common::String, Common::SharedPtr<Script> > _functions;
};
-ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::String &blamePath) : _numberParsingMode(kNumberParsingHex), _parser(parser), _blamePath(blamePath) {
+ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, IScriptCompilerGlobalState *gs)
+ : _numberParsingMode(kNumberParsingHex), _parser(parser), _blamePath(blamePath), _dialect(dialect), _gs(gs),
+ _scrToken(nullptr), _eroomToken(nullptr) {
}
bool ScriptCompiler::parseNumber(const Common::String &token, uint32 &outNumber) const {
if (token.size() == 0)
return false;
- if (token[0] == 'd')
- return parseDecNumber(token, 1, outNumber);
-
- if (token[0] == '0') {
- switch (_numberParsingMode) {
- case kNumberParsingDec:
- return parseDecNumber(token, 0, outNumber);
- case kNumberParsingHex:
- return parseHexNumber(token, 0, outNumber);
- case kNumberParsingBin:
- return parseBinNumber(token, 0, outNumber);
- default:
- error("Unknown number parsing mode");
- return false;
+ if (_dialect == kScriptDialectReah) {
+ if (token[0] == 'd')
+ return parseDecNumber(token, 1, outNumber);
+
+ if (token[0] == '0') {
+ switch (_numberParsingMode) {
+ case kNumberParsingDec:
+ return parseDecNumber(token, 0, outNumber);
+ case kNumberParsingHex:
+ return parseHexNumber(token, 0, outNumber);
+ case kNumberParsingBin:
+ return parseBinNumber(token, 0, outNumber);
+ default:
+ error("Unknown number parsing mode");
+ return false;
+ }
}
+ } else if (_dialect == kScriptDialectSchizm) {
+ if (token.size() >= 2 && token[0] == '0' && token[1] == 'x')
+ return parseHexNumber(token, 2, outNumber);
+ if (token[token.size() - 1] == 'b')
+ return parseBinNumber(token.substr(0, token.size() - 1), 0, outNumber);
+ if (token[token.size() - 1] == 'h')
+ return parseHexNumber(token.substr(0, token.size() - 1), 0, outNumber);
+ return parseDecNumber(token, 0, outNumber);
}
return false;
@@ -256,56 +299,100 @@ void ScriptCompiler::expectNumber(uint32 &outNumber) {
}
}
-void ScriptCompiler::compileRoomScriptSet(ScriptSet *ss) {
+void ScriptCompiler::compileScriptSet(ScriptSet *ss) {
Common::SharedPtr<RoomScriptSet> roomScript;
+ const char *roomToken = nullptr;
+
+ if (_dialect == kScriptDialectReah) {
+ roomToken = "~ROOM";
+ _eroomToken = "~EROOM";
+ _scrToken = "~SCR";
+ } else if (_dialect == kScriptDialectSchizm) {
+ roomToken = "~Room";
+ _eroomToken = "~ERoom";
+ _scrToken = "~Scr";
+ } else
+ error("Unknown script dialect");
+
TextParserState state;
Common::String token;
while (_parser.parseToken(token, state)) {
- if (token == "~ROOM") {
+ if (token == roomToken) {
if (roomScript)
- error("Error compiling script at line %i col %i: Encountered ~ROOM without ~EROOM", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+ error("Error compiling script at line %i col %i: Encountered %s without %s", static_cast<int>(state._lineNum), static_cast<int>(state._col), roomToken, _eroomToken);
roomScript.reset(new RoomScriptSet());
- uint32 roomNumber = 0;
- expectNumber(roomNumber);
+ {
+ uint32 roomNumber = 0;
- ss->roomScripts[roomNumber] = roomScript;
+ if (_parser.parseToken(token, state)) {
+ // Many Schizm rooms use 0xxh as the room number and are empty. In this case the room is discarded.
+ if (_dialect != kScriptDialectSchizm || token != "0xxh") {
+ if (!parseNumber(token, roomNumber))
+ error("Error compiling script at line %i col %i: Expected number but found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+
+ ss->roomScripts[roomNumber] = roomScript;
+ }
+ } else {
+ error("Error compiling script at line %i col %i: Expected number", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+ }
+ }
compileRoomScriptSet(roomScript.get());
} else {
- error("Error compiling script at line %i col %i: Expected ~ROOM and found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+ error("Error compiling script at line %i col %i: Expected %s and found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), roomToken, token.c_str());
}
}
-
- ss->strings = Common::move(_strings);
}
void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
TextParserState state;
Common::String token;
while (_parser.parseToken(token, state)) {
- if (token == "~EROOM") {
+ if (token == _eroomToken) {
return;
- } else if (token == "~SCR") {
+ } else if (token == _scrToken) {
uint32 screenNumber = 0;
expectNumber(screenNumber);
Common::SharedPtr<ScreenScriptSet> sss(new ScreenScriptSet());
- compileScreenScriptSet(sss.get());
+ if (_dialect == kScriptDialectReah)
+ compileReahScreenScriptSet(sss.get());
+ else if (_dialect == kScriptDialectSchizm)
+ compileSchizmScreenScriptSet(sss.get());
// QUIRK: The tower in Reah (Room 06) has two 0cb screens, the second one is bad and must be ignored
if (rss->screenScripts.find(screenNumber) == rss->screenScripts.end())
rss->screenScripts[screenNumber] = sss;
+ } else if (_dialect == kScriptDialectSchizm && token == "#def") {
+ Common::String key;
+ Common::String value;
+ if (!_parser.parseToken(key, state))
+ error("Error compiling script at line %i col %i: Expected key", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+ if (!_parser.parseToken(value, state))
+ error("Error compiling script at line %i col %i: Expected value", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+ _gs->define(key, value);
+ } else if (_dialect == kScriptDialectSchizm && token == "~Fun") {
+ Common::String fnName;
+ if (!_parser.parseToken(fnName, state))
+ error("Error compiling script at line %i col %i: Expected function name", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+ Common::SharedPtr<Script> func(new Script());
+
+ compileFunction(func.get());
+
+ _gs->addFunction(fnName, func);
} else {
- error("Error compiling script at line %i col %i: Expected ~EROOM or ~SCR and found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+ error("Error compiling script at line %i col %i: Expected %s or %s and found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), _eroomToken, _scrToken, token.c_str());
}
}
error("Error compiling script: Room wasn't terminated");
}
-void ScriptCompiler::compileScreenScriptSet(ScreenScriptSet *sss) {
+void ScriptCompiler::compileReahScreenScriptSet(ScreenScriptSet *sss) {
TextParserState state;
Common::String token;
@@ -339,7 +426,45 @@ void ScriptCompiler::compileScreenScriptSet(ScreenScriptSet *sss) {
} else if (token == "dubbing") {
Common::String dubbingName;
_parser.expectToken(dubbingName, _blamePath);
- protoScript.instrs.push_back(ProtoInstruction(ScriptOps::kDubbing, indexString(dubbingName)));
+ protoScript.instrs.push_back(ProtoInstruction(ScriptOps::kDubbing, indexString(protoScript, dubbingName)));
+ } else if (compileInstructionToken(protoScript, token)) {
+ // Nothing
+ } else {
+ error("Error compiling script at line %i col %i: Expected %s or %s or ~* or instruction but found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), _eroomToken, _scrToken, token.c_str());
+ }
+ }
+}
+
+void ScriptCompiler::compileSchizmScreenScriptSet(ScreenScriptSet *sss) {
+ TextParserState state;
+ Common::String token;
+
+ ProtoScript protoScript;
+ Common::SharedPtr<Script> currentScript(new Script());
+
+ sss->entryScript.reset(currentScript);
+
+ if (!_parser.parseToken(token, state))
+ error("Error compiling script at line %i col %i: Expected screen name", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+ sss->screenName = token;
+
+ while (_parser.parseToken(token, state)) {
+ if (token == "~ERoom" || token == "~Scr" || token == "~Fun") {
+ _parser.requeue(token, state);
+
+ codeGenScript(protoScript, *currentScript);
+ return;
+ } else if (token == "~*") {
+ uint32 interactionNumber = 0;
+ expectNumber(interactionNumber);
+
+ codeGenScript(protoScript, *currentScript);
+
+ currentScript.reset(new Script());
+ protoScript.reset();
+
+ sss->interactionScripts[interactionNumber] = currentScript;
} else if (compileInstructionToken(protoScript, token)) {
// Nothing
} else {
@@ -348,7 +473,27 @@ void ScriptCompiler::compileScreenScriptSet(ScreenScriptSet *sss) {
}
}
-static ScriptNamedInstruction g_namedInstructions[] = {
+void ScriptCompiler::compileFunction(Script *script) {
+ TextParserState state;
+ Common::String token;
+
+ ProtoScript protoScript;
+
+ while (_parser.parseToken(token, state)) {
+ if (token == "~ERoom" || token == "~Scr" || token == "~Fun") {
+ _parser.requeue(token, state);
+
+ codeGenScript(protoScript, *script);
+ return;
+ } else if (compileInstructionToken(protoScript, token)) {
+ // Nothing
+ } else {
+ error("Error compiling script at line %i col %i: Expected ~ERoom or ~Scr or ~Fun but found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), token.c_str());
+ }
+ }
+}
+
+static ScriptNamedInstruction g_reahNamedInstructions[] = {
{"rotate", ProtoOp::kProtoOpScript, ScriptOps::kRotate},
{"angle", ProtoOp::kProtoOpScript, ScriptOps::kAngle},
{"angleG@", ProtoOp::kProtoOpScript, ScriptOps::kAngleGGet},
@@ -356,10 +501,10 @@ static ScriptNamedInstruction g_namedInstructions[] = {
{"sanimL", ProtoOp::kProtoOpScript, ScriptOps::kSAnimL},
{"changeL", ProtoOp::kProtoOpScript, ScriptOps::kChangeL},
{"changeL1", ProtoOp::kProtoOpScript, ScriptOps::kChangeL}, // This seems wrong, but not sure what changeL1 does differently from changeL yet
- {"animR", ProtoOp::kProtoOpScript, ScriptOps::kAnimR},
{"animF", ProtoOp::kProtoOpScript, ScriptOps::kAnimF},
- {"animN", ProtoOp::kProtoOpScript, ScriptOps::kAnimN},
{"animG", ProtoOp::kProtoOpScript, ScriptOps::kAnimG},
+ {"animN", ProtoOp::kProtoOpScript, ScriptOps::kAnimN},
+ {"animR", ProtoOp::kProtoOpScript, ScriptOps::kAnimR},
{"animS", ProtoOp::kProtoOpScript, ScriptOps::kAnimS},
{"anim", ProtoOp::kProtoOpScript, ScriptOps::kAnim},
{"static", ProtoOp::kProtoOpScript, ScriptOps::kStatic},
@@ -465,7 +610,147 @@ static ScriptNamedInstruction g_namedInstructions[] = {
{"allowedSave", ProtoOp::kProtoOpNoop, ScriptOps::kInvalid},
};
-bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::String &token) {
+
+
+static ScriptNamedInstruction g_schizmNamedInstructions[] = {
+ {"StopScore", ProtoOp::kProtoOpScript, ScriptOps::kMusicStop},
+ {"PlayScore", ProtoOp::kProtoOpScript, ScriptOps::kMusicPlayScore},
+ {"ScoreAlways", ProtoOp::kProtoOpScript, ScriptOps::kScoreAlways},
+ {"ScoreNormal", ProtoOp::kProtoOpScript, ScriptOps::kScoreNormal},
+ {"SndAddRandom", ProtoOp::kProtoOpScript, ScriptOps::kSndAddRandom},
+ {"SndClearRandom", ProtoOp::kProtoOpScript, ScriptOps::kSndClearRandom},
+ {"SndPlay", ProtoOp::kProtoOpScript, ScriptOps::kSndPlay},
+ {"SndPlayEx", ProtoOp::kProtoOpScript, ScriptOps::kSndPlayEx},
+ {"SndPlay3D", ProtoOp::kProtoOpScript, ScriptOps::kSndPlay3D},
+ {"SndPlaying", ProtoOp::kProtoOpScript, ScriptOps::kSndPlaying},
+ {"SndHalt", ProtoOp::kProtoOpScript, ScriptOps::kSndHalt},
+ {"SndWait", ProtoOp::kProtoOpScript, ScriptOps::kSndWait},
+ {"SndToBack", ProtoOp::kProtoOpScript, ScriptOps::kSndToBack},
+ {"SndStop", ProtoOp::kProtoOpScript, ScriptOps::kSndStop},
+ {"SndStopAll", ProtoOp::kProtoOpScript, ScriptOps::kSndStopAll},
+ {"VolumeAdd", ProtoOp::kProtoOpScript, ScriptOps::kVolumeAdd},
+ {"VolumeChange", ProtoOp::kProtoOpScript, ScriptOps::kVolumeChange},
+ {"VolumeDown", ProtoOp::kProtoOpScript, ScriptOps::kVolumeDown},
+ {"esc_on", ProtoOp::kProtoOpScript, ScriptOps::kEscOn},
+ {"esc_off", ProtoOp::kProtoOpScript, ScriptOps::kEscOff},
+ {"esc_get@", ProtoOp::kProtoOpScript, ScriptOps::kEscGet},
+ {"room!", ProtoOp::kProtoOpScript, ScriptOps::kSetRoom},
+ {"room@", ProtoOp::kProtoOpScript, ScriptOps::kGetRoom},
+ {"lmb", ProtoOp::kProtoOpScript, ScriptOps::kLMB},
+ {"lmb1", ProtoOp::kProtoOpScript, ScriptOps::kLMB1},
+ {"animVolume", ProtoOp::kProtoOpScript, ScriptOps::kAnimVolume},
+ {"animChange", ProtoOp::kProtoOpScript, ScriptOps::kAnimChange},
+ {"anim", ProtoOp::kProtoOpScript, ScriptOps::kAnim}, // Dialect difference: Accepts room name
+ {"static", ProtoOp::kProtoOpScript, ScriptOps::kStatic},
+ {"animF", ProtoOp::kProtoOpScript, ScriptOps::kAnimF},
+ {"animG", ProtoOp::kProtoOpScript, ScriptOps::kAnimG},
+ {"animN", ProtoOp::kProtoOpScript, ScriptOps::kAnimN},
+ {"animR", ProtoOp::kProtoOpScript, ScriptOps::kAnimR},
+ {"animS", ProtoOp::kProtoOpScript, ScriptOps::kAnimS},
+ {"sanimL", ProtoOp::kProtoOpScript, ScriptOps::kSAnimL},
+ {"sparmX", ProtoOp::kProtoOpScript, ScriptOps::kSParmX},
+ {"sanimX", ProtoOp::kProtoOpScript, ScriptOps::kSAnimX},
+ {"byte@", ProtoOp::kProtoOpScript, ScriptOps::kExtractByte},
+ {"byte!", ProtoOp::kProtoOpScript, ScriptOps::kInsertByte},
+ {"rotate", ProtoOp::kProtoOpScript, ScriptOps::kRotate},
+ {"rotateUpdate", ProtoOp::kProtoOpScript, ScriptOps::kRotateUpdate},
+ {"bit@", ProtoOp::kProtoOpScript, ScriptOps::kBitLoad},
+ {"bit0!", ProtoOp::kProtoOpScript, ScriptOps::kBitSet0},
+ {"bit1!", ProtoOp::kProtoOpScript, ScriptOps::kBitSet1},
+ {"speech", ProtoOp::kProtoOpScript, ScriptOps::kSpeech},
+ {"speechEx", ProtoOp::kProtoOpScript, ScriptOps::kSpeechEx},
+ {"speechTest", ProtoOp::kProtoOpScript, ScriptOps::kSpeechTest},
+ {"say", ProtoOp::kProtoOpScript, ScriptOps::kSay},
+ {"changeL", ProtoOp::kProtoOpScript, ScriptOps::kChangeL}, // Dialect difference: Accepts room name
+ {"range", ProtoOp::kProtoOpScript, ScriptOps::kRange},
+ {"sound3DL2", ProtoOp::kProtoOpScript, ScriptOps::k3DSoundL2}, // Dialect difference: Different name
+ {"random", ProtoOp::kProtoOpScript, ScriptOps::kRandomInclusive},
+ {"heroSetPos", ProtoOp::kProtoOpScript, ScriptOps::kHeroSetPos},
+ {"heroGetPos", ProtoOp::kProtoOpScript, ScriptOps::kHeroGetPos},
+ {"heroOut", ProtoOp::kProtoOpScript, ScriptOps::kHeroOut},
+ {"hero@", ProtoOp::kProtoOpScript, ScriptOps::kHeroGet},
+ {"ret", ProtoOp::kProtoOpScript, ScriptOps::kReturn},
+ {"setTimer", ProtoOp::kProtoOpScript, ScriptOps::kSetTimer},
+ {"getTimer", ProtoOp::kProtoOpScript, ScriptOps::kGetTimer},
+ {"delay", ProtoOp::kProtoOpScript, ScriptOps::kDelay},
+ {"lo!", ProtoOp::kProtoOpScript, ScriptOps::kLoSet},
+ {"lo@", ProtoOp::kProtoOpScript, ScriptOps::kLoGet},
+ {"hi!", ProtoOp::kProtoOpScript, ScriptOps::kHiSet},
+ {"hi@", ProtoOp::kProtoOpScript, ScriptOps::kHiGet},
+ {"angle@", ProtoOp::kProtoOpScript, ScriptOps::kAngleGet},
+ {"angleG@", ProtoOp::kProtoOpScript, ScriptOps::kAngleGGet},
+ {"cd@", ProtoOp::kProtoOpScript, ScriptOps::kCDGet},
+ {"disc", ProtoOp::kProtoOpScript, ScriptOps::kDisc},
+ {"save0", ProtoOp::kProtoOpNoop, ScriptOps::kSave0},
+ {"hidePanel", ProtoOp::kProtoOpNoop, ScriptOps::kHidePanel},
+ {"ItemExist@", ProtoOp::kProtoOpScript, ScriptOps::kItemCheck},
+ {"ItemSelect!", ProtoOp::kProtoOpScript, ScriptOps::kItemHighlightSet},
+ {"ItemPlace@", ProtoOp::kProtoOpScript, ScriptOps::kItemHaveSpace},
+ {"ItemPutInto!", ProtoOp::kProtoOpScript, ScriptOps::kItemAdd},
+ {"ItemRemove!", ProtoOp::kProtoOpScript, ScriptOps::kItemRemove},
+ {"cyfra@", ProtoOp::kProtoOpScript, ScriptOps::kCyfraGet},
+ {"puzzleInit", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleInit},
+ {"puzzleCanPress", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleCanPress},
+ {"puzzleDoMove1", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleDoMove1},
+ {"puzzleDoMove2", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleDoMove2},
+ {"puzzleDone", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleDone},
+ {"puzzleWhoWon", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleWhoWon},
+
+ {"+", ProtoOp::kProtoOpScript, ScriptOps::kAdd},
+ {"-", ProtoOp::kProtoOpScript, ScriptOps::kSub},
+ {"*", ProtoOp::kProtoOpScript, ScriptOps::kMul},
+ {"/", ProtoOp::kProtoOpScript, ScriptOps::kDiv},
+ {"%", ProtoOp::kProtoOpScript, ScriptOps::kMod},
+
+ {"&&", ProtoOp::kProtoOpScript, ScriptOps::kAnd},
+ {"or", ProtoOp::kProtoOpScript, ScriptOps::kOr},
+ {"||", ProtoOp::kProtoOpScript, ScriptOps::kOr},
+ {"+", ProtoOp::kProtoOpScript, ScriptOps::kAdd},
+ {"-", ProtoOp::kProtoOpScript, ScriptOps::kSub},
+ {">", ProtoOp::kProtoOpScript, ScriptOps::kCmpGt},
+ {"<", ProtoOp::kProtoOpScript, ScriptOps::kCmpLt},
+ {"=", ProtoOp::kProtoOpScript, ScriptOps::kCmpEq},
+ {"==", ProtoOp::kProtoOpScript, ScriptOps::kCmpEq},
+ {"!=", ProtoOp::kProtoOpScript, ScriptOps::kCmpNE},
+ {">=", ProtoOp::kProtoOpScript, ScriptOps::kCmpGE},
+ {"<=", ProtoOp::kProtoOpScript, ScriptOps::kCmpLE},
+
+ {"&", ProtoOp::kProtoOpScript, ScriptOps::kBitAnd},
+ {"|", ProtoOp::kProtoOpScript, ScriptOps::kBitOr},
+
+ {"#if", ProtoOp::kProtoOpIf, ScriptOps::kInvalid},
+ {"#eif", ProtoOp::kProtoOpEndIf, ScriptOps::kInvalid},
+ {"#else", ProtoOp::kProtoOpElse, ScriptOps::kInvalid},
+
+ {"#switch:", ProtoOp::kProtoOpSwitch, ScriptOps::kInvalid},
+ {"#eswitch", ProtoOp::kProtoOpEndSwitch, ScriptOps::kInvalid},
+ {"break", ProtoOp::kProtoOpBreak, ScriptOps::kInvalid},
+
+ {"ret", ProtoOp::kProtoOpScript, ScriptOps::kReturn},
+
+ {"backStart", ProtoOp::kProtoOpScript, ScriptOps::kBackStart},
+ {"allowedSave", ProtoOp::kProtoOpNoop, ScriptOps::kInvalid},
+};
+
+bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::String &tokenBase) {
+ const Common::String *tokenPtr = &tokenBase;
+
+ if (_dialect == kScriptDialectSchizm) {
+ const Common::String *ppToken = _gs->getTokenReplacement(tokenBase);
+ if (ppToken)
+ tokenPtr = ppToken;
+ }
+
+ const Common::String &token = *tokenPtr;
+
+ if (_dialect == kScriptDialectSchizm && token.hasPrefix("-")) {
+ uint32 unumber = 0;
+ if (parseNumber(token.substr(1), unumber)) {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kNumber, -static_cast<int32>(unumber)));
+ return true;
+ }
+ }
+
uint32 number = 0;
if (parseNumber(token, number)) {
script.instrs.push_back(ProtoInstruction(ScriptOps::kNumber, number));
@@ -473,29 +758,33 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
}
if (token.size() >= 1 && token[0] == ':') {
- if (token.size() >= 3 && token[2] == ':') {
- if (token[1] == 'Y') {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kVarName, indexString(token.substr(3))));
- return true;
- } else if (token[1] == 'V') {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kValueName, indexString(token.substr(3))));
- return true;
- } else
- return false;
+ if (_dialect == kScriptDialectReah) {
+ if (token.size() >= 3 && token[2] == ':') {
+ if (token[1] == 'Y') {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kVarName, indexString(script, token.substr(3))));
+ return true;
+ } else if (token[1] == 'V') {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kValueName, indexString(script, token.substr(3))));
+ return true;
+ } else
+ return false;
+ }
}
- script.instrs.push_back(ProtoInstruction(ScriptOps::kAnimName, indexString(token.substr(1))));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kAnimName, indexString(script, token.substr(1))));
return true;
}
if (token.size() >= 2 && token[0] == '_') {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kSoundName, indexString(token.substr(1))));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kSoundName, indexString(script, token.substr(1))));
return true;
}
- if (token.hasPrefix("CUR_")) {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(token)));
- return true;
+ if (_dialect == kScriptDialectReah) {
+ if (token.hasPrefix("CUR_")) {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(script, token)));
+ return true;
+ }
}
if (token == "#switch") {
@@ -521,9 +810,87 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
return true;
}
- for (const ScriptNamedInstruction &namedInstr : g_namedInstructions) {
- if (token == namedInstr.str) {
- script.instrs.push_back(ProtoInstruction(namedInstr.protoOp, namedInstr.op, 0));
+ if (_dialect == kScriptDialectReah) {
+ for (const ScriptNamedInstruction &namedInstr : g_reahNamedInstructions) {
+ if (token == namedInstr.str) {
+ script.instrs.push_back(ProtoInstruction(namedInstr.protoOp, namedInstr.op, 0));
+ return true;
+ }
+ }
+ } else if (_dialect == kScriptDialectSchizm) {
+ if (token.hasPrefix("fn")) {
+ uint fnIndex = indexString(script, token);
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kCallFunction, fnIndex));
+ return true;
+ }
+
+ if (token.size() >= 2 && token[0] == '\"' && token[token.size() - 1] == '\"') {
+ // Seems like these are only used for sounds and music?
+ uint fnIndex = indexString(script, token.substr(1, token.size() - 2));
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kString, fnIndex));
+ return true;
+ }
+
+ if (token == "dvd@") {
+ // Always pass disc checks
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kNumber, 1));
+ return true;
+ }
+
+ for (const ScriptNamedInstruction &namedInstr : g_schizmNamedInstructions) {
+ if (token == namedInstr.str) {
+ script.instrs.push_back(ProtoInstruction(namedInstr.protoOp, namedInstr.op, 0));
+ return true;
+ }
+ }
+
+ if (token.size() >= 2 && token.hasSuffix("!")) {
+ if (compileInstructionToken(script, token.substr(0, token.size() - 1))) {
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kVarGlobalStore, 0));
+ return true;
+ } else
+ return false;
+ }
+
+ // HACK: Work around bugged variable name in Room02.log
+ if (token == "dwFirst\x8c@") {
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kNumber, 0));
+ return true;
+ }
+
+ if (token.size() >= 2 && token.hasSuffix("@")) {
+ if (compileInstructionToken(script, token.substr(0, token.size() - 1))) {
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kVarGlobalLoad, 0));
+ return true;
+ } else
+ return false;
+ }
+
+ // Does this look like a screen name?
+ bool couldBeScreenName = true;
+ for (uint i = 0; i < token.size(); i++) {
+ char c = token[i];
+ bool isAlphaNumeric = ((c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9'));
+ if (!isAlphaNumeric) {
+ couldBeScreenName = false;
+ break;
+ }
+ }
+
+ if (couldBeScreenName) {
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kScreenName, indexString(script, token)));
+ return true;
+ }
+
+ if (token.hasPrefix("cur")) {
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(script, token)));
+ return true;
+ }
+
+ // HACK: Work around broken volume variable names in Room02. Some of these appear to have "par"
+ // where it should be "vol" but some are garbage. Figure this out later.
+ if (token.hasPrefix("par")) {
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kGarbage, indexString(script, token)));
return true;
}
}
@@ -734,8 +1101,33 @@ void ScriptCompiler::codeGenScript(ProtoScript &protoScript, Script &script) {
}
}
- if (controlFlowStack.size() > 0)
- error("Error in codegen: Unterminated flow control construct");
+ if (controlFlowStack.size() > 0) {
+ if (_dialect == kScriptDialectSchizm) {
+ // For some reason line 105 in Room36 and line 342 in Room61 have unterminated conditional blocks,
+ // and the comments say that's intentional.
+
+ while (controlFlowStack.size() > 0) {
+ const CodeGenControlFlowBlock &cf = controlFlowStack.back();
+
+ switch (controlFlowStack.back().type) {
+ case kFlowControlIf: {
+ warning("CodeGen encountered unterminated #if statement");
+ instrs.push_back(ProtoInstruction(kProtoOpLabel, ScriptOps::kInvalid, ifs[cf.index].endLabel));
+ controlFlowStack.pop_back();
+ } break;
+ case kFlowControlSwitch: {
+ warning("CodeGen encountered unterminated #switch statement");
+ instrs.push_back(ProtoInstruction(kProtoOpLabel, ScriptOps::kInvalid, switches[cf.index].endLabel));
+ controlFlowStack.pop_back();
+ } break;
+ default:
+ error("Unknown control flow type");
+ }
+ }
+ } else {
+ error("Error in codegen: Unterminated flow control construct");
+ }
+ }
Common::Array<ProtoInstruction> instrs2;
@@ -802,31 +1194,72 @@ void ScriptCompiler::codeGenScript(ProtoScript &protoScript, Script &script) {
break;
}
}
+
+ // Commit strings
+ script.strings = Common::move(protoScript.strings);
}
-uint ScriptCompiler::indexString(const Common::String &str) {
- Common::HashMap<Common::String, uint>::const_iterator it = _stringToIndex.find(str);
- if (it == _stringToIndex.end()) {
- uint index = _strings.size();
- _stringToIndex[str] = index;
- _strings.push_back(str);
+uint ScriptCompiler::indexString(ProtoScript &script, const Common::String &str) {
+ Common::HashMap<Common::String, uint>::const_iterator it = script.stringToIndex.find(str);
+ if (it == script.stringToIndex.end()) {
+ uint index = script.strings.size();
+ script.stringToIndex[str] = index;
+ script.strings.push_back(str);
return index;
}
return it->_value;
}
-Common::SharedPtr<ScriptSet> compileLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath) {
+void ScriptCompilerGlobalState::define(const Common::String &key, const Common::String &value) {
+ _defs.setVal(key, value);
+}
+
+const Common::String *ScriptCompilerGlobalState::getTokenReplacement(const Common::String &str) const {
+ Common::HashMap<Common::String, Common::String>::const_iterator it = _defs.find(str);
+ if (it == _defs.end())
+ return nullptr;
+
+ return &it->_value;
+}
+
+void ScriptCompilerGlobalState::addFunction(const Common::String &fnName, const Common::SharedPtr<Script> &fn) {
+ _functions.setVal(fnName, fn);
+}
+
+Common::SharedPtr<Script> ScriptCompilerGlobalState::getFunction(const Common::String &fnName) const {
+ Common::HashMap<Common::String, Common::SharedPtr<Script> >::const_iterator it = _functions.find(fnName);
+ if (it == _functions.end())
+ return Common::SharedPtr<Script>();
+
+ return it->_value;
+}
+
+IScriptCompilerGlobalState::~IScriptCompilerGlobalState() {
+}
+
+static void compileLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, ScriptDialect dialect, IScriptCompilerGlobalState *gs) {
LogicUnscrambleStream unscrambleStream(&stream, streamSize);
TextParser parser(&unscrambleStream);
- Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
+ ScriptCompiler compiler(parser, blamePath, dialect, gs);
- ScriptCompiler compiler(parser, blamePath);
+ compiler.compileScriptSet(&scriptSet);
+}
+
+Common::SharedPtr<IScriptCompilerGlobalState> createScriptCompilerGlobalState() {
+ return Common::SharedPtr<IScriptCompilerGlobalState>(new ScriptCompilerGlobalState());
+}
- compiler.compileRoomScriptSet(scriptSet.get());
+Common::SharedPtr<ScriptSet> compileReahLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath) {
+ Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
+ compileLogicFile(*scriptSet, stream, streamSize, blamePath, kScriptDialectReah, nullptr);
return scriptSet;
}
+void compileSchizmLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs) {
+ compileLogicFile(scriptSet, stream, streamSize, blamePath, kScriptDialectSchizm, gs);
+}
+
} // namespace VCruise
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index f0086c07a12..6cf002f4144 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -38,6 +38,7 @@ namespace VCruise {
struct ScreenScriptSet;
struct RoomScriptSet;
struct ScriptSet;
+struct ITextPreprocessor;
namespace ScriptOps {
@@ -156,6 +157,65 @@ enum ScriptOp {
kCheckValue, // Check if stack top is equal to arg. If it is, pop the argument, otherwise leave it on the stack and skip the next instruction.
kJump, // Offset instruction index by arg.
+ // Schizm ops
+ kCallFunction,
+ kMusicStop,
+ kMusicPlayScore,
+ kScoreAlways,
+ kScoreNormal,
+ kSndPlay,
+ kSndPlayEx,
+ kSndPlay3D,
+ kSndPlaying,
+ kSndWait,
+ kSndHalt,
+ kSndToBack,
+ kSndStop,
+ kSndStopAll,
+ kSndAddRandom,
+ kSndClearRandom,
+ kVolumeAdd,
+ kVolumeChange,
+ kVolumeDown,
+ kAnimVolume,
+ kAnimChange,
+ kScreenName,
+ kExtractByte,
+ kInsertByte,
+ kString,
+ kCmpNE,
+ kCmpLE,
+ kCmpGE,
+ kReturn,
+ kSpeech,
+ kSpeechEx,
+ kSpeechTest,
+ kSay,
+ kRandomInclusive,
+ kHeroOut,
+ kHeroGetPos,
+ kHeroSetPos,
+ kHeroGet,
+ kGarbage,
+ kGetRoom,
+ kBitAnd,
+ kBitOr,
+ kAngleGet,
+ kCDGet,
+ kDisc,
+ kHidePanel,
+ kRotateUpdate,
+ kMul,
+ kDiv,
+ kMod,
+ kCyfraGet, // Cyfra = digit?
+ kPuzzleInit,
+ kPuzzleCanPress,
+ kPuzzleDoMove1,
+ kPuzzleDoMove2,
+ kPuzzleDone,
+ kPuzzleWhoWon,
+
kNumOps,
};
@@ -172,6 +232,7 @@ struct Instruction {
struct Script {
Common::Array<Instruction> instrs;
+ Common::Array<Common::String> strings;
};
typedef Common::HashMap<uint, Common::SharedPtr<Script> > ScriptMap_t;
@@ -181,6 +242,8 @@ typedef Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> > RoomScriptSetMa
struct ScreenScriptSet {
Common::SharedPtr<Script> entryScript;
ScriptMap_t interactionScripts;
+
+ Common::String screenName; // Only in Schizm
};
struct RoomScriptSet {
@@ -189,10 +252,28 @@ struct RoomScriptSet {
struct ScriptSet {
RoomScriptSetMap_t roomScripts;
- Common::Array<Common::String> strings;
};
-Common::SharedPtr<ScriptSet> compileLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath);
+struct FunctionDef {
+ Common::String fnName;
+ Common::SharedPtr<Script> func;
+};
+
+// Global state is required for Schizm because its preprocessor defines exist across files.
+// For example, volPortWaves is set in Room01 but used in Room03 and Room20
+struct IScriptCompilerGlobalState {
+ virtual ~IScriptCompilerGlobalState();
+
+ virtual void define(const Common::String &key, const Common::String &value) = 0;
+ virtual const Common::String *getTokenReplacement(const Common::String &str) const = 0;
+
+ virtual void addFunction(const Common::String &fnName, const Common::SharedPtr<Script> &fn) = 0;
+ virtual Common::SharedPtr<Script> getFunction(const Common::String &fnName) const = 0;
+};
+
+Common::SharedPtr<IScriptCompilerGlobalState> createScriptCompilerGlobalState();
+Common::SharedPtr<ScriptSet> compileReahLogicFile(Common::ReadStream &stream, uint streamSize, const Common::String &blamePath);
+void compileSchizmLogicFile(ScriptSet &scriptSet, Common::ReadStream &stream, uint streamSize, const Common::String &blamePath, IScriptCompilerGlobalState *gs);
}
diff --git a/engines/vcruise/textparser.cpp b/engines/vcruise/textparser.cpp
index b8ecf16189e..03f3d355b77 100644
--- a/engines/vcruise/textparser.cpp
+++ b/engines/vcruise/textparser.cpp
@@ -29,6 +29,7 @@ namespace VCruise {
TextParserState::TextParserState() : _lineNum(1), _col(1), _prevWasCR(false), _isParsingComment(false) {
}
+
TextParser::TextParser(Common::ReadStream *stream) : _stream(stream), _returnedBufferPos(kReturnedBufferSize) {
memset(_returnedBuffer, 0, kReturnedBufferSize);
}
@@ -102,6 +103,13 @@ bool TextParser::isDelimiter(char c) {
return false;
}
+bool TextParser::isCompoundDelimiter(char c1, char c2) {
+ if (c2 == '=' && (c1 == '=' || c1 == '<' || c1 == '>' || c1 == '!'))
+ return true;
+
+ return false;
+}
+
bool TextParser::isWhitespace(char c) {
return (c == ' ') || ((c & 0xe0) == 0);
}
@@ -246,18 +254,46 @@ bool TextParser::parseToken(Common::String &outString, TextParserState &outState
outString += c;
- if (isDelimiter(c))
+ if (c == '\"') {
+ while (readOneChar(c, state)) {
+ if (c == '\n' || c == '\r') {
+ requeue(&c, 1, state);
+ return true;
+ }
+
+ outString += c;
+ if (c == '\"')
+ return true;
+ }
+ return true;
+ }
+
+ if (isDelimiter(c)) {
+ char firstC = c;
+ if (readOneChar(c, state)) {
+ if (isCompoundDelimiter(firstC, c))
+ outString += c;
+ else
+ requeue(&c, 1, state);
+ }
+
return true;
+ }
while (readOneChar(c, state)) {
if (isWhitespace(c) || _state._isParsingComment) {
requeue(&c, 1, state);
- return true;
+ break;
+ }
+
+ if (outString.size() == 1 && isCompoundDelimiter(outString[0], c)) {
+ outString += c;
+ break;
}
if (isDelimiter(c)) {
requeue(&c, 1, state);
- return true;
+ break;
}
outString += c;
diff --git a/engines/vcruise/textparser.h b/engines/vcruise/textparser.h
index 7bbe73eb115..f62dbaa690d 100644
--- a/engines/vcruise/textparser.h
+++ b/engines/vcruise/textparser.h
@@ -22,6 +22,8 @@
#ifndef VCRUISE_TEXTPARSER_H
#define VCRUISE_TEXTPARSER_H
+#include "common/hashmap.h"
+#include "common/hash-str.h"
#include "common/str.h"
#include "common/memstream.h"
@@ -69,6 +71,7 @@ private:
void expectTokenInternal(Common::String &outToken, const Common::String &blamePath, TextParserState &outState);
static bool isDelimiter(char c);
+ static bool isCompoundDelimiter(char c1, char c2);
static bool isWhitespace(char c);
TextParserState _state;
Commit: 8f38fbcdc7ce979fb9b59eb7ab7b6e090b0b384f
https://github.com/scummvm/scummvm/commit/8f38fbcdc7ce979fb9b59eb7ab7b6e090b0b384f
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:43-04:00
Commit Message:
VCRUISE: Add function calls and Schizm main menu things
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
engines/vcruise/script.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 580cea7d983..3ba53a525f2 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -261,6 +261,9 @@ Runtime::StackValue &Runtime::StackValue::operator=(StackValue &&other) {
return *this;
}
+Runtime::CallStackFrame::CallStackFrame() : _nextInstruction(0) {
+}
+
Runtime::Gyro::Gyro() {
reset();
}
@@ -840,7 +843,7 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
- _havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _scriptNextInstruction(0), _escOn(false), _debugMode(false), _fastAnimationMode(false),
+ _havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _escOn(false), _debugMode(false), _fastAnimationMode(false),
_musicTrack(0), _musicVolume(100), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
_panoramaDirectionFlags(0),
_loadedAnimation(0), _loadedAnimationHasSound(false), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
@@ -982,6 +985,10 @@ bool Runtime::runFrame() {
case kGameStatePanRight:
moreActions = runHorizontalPan(true);
break;
+ case kGameStateScriptReset:
+ _gameState = kGameStateScript;
+ moreActions = runScript();
+ break;
case kGameStateScript:
moreActions = runScript();
break;
@@ -1039,46 +1046,6 @@ bool Runtime::bootGame(bool newGame) {
_gameState = kGameStateIdle;
- if (_gameID == GID_SCHIZM) {
- Common::SharedPtr<IScriptCompilerGlobalState> gs = createScriptCompilerGlobalState();
-
- // Precompile all scripts. We must do this because global functions are stored in room 3, which is never used.
- Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
-
- Common::ArchiveMemberList scriptFiles;
- SearchMan.listMatchingMembers(scriptFiles, "Log/Room##.log", true);
-
- Common::Array<bool> scriptExists;
-
- uint highestScriptIndex = 0;
- for (const Common::ArchiveMemberPtr &scriptFile : scriptFiles) {
- Common::String scriptName = scriptFile->getName();
-
- uint scriptIndex = ((scriptName[4] - '0') * 10) + (scriptName[5] - '0');
- if (scriptIndex > highestScriptIndex) {
- highestScriptIndex = scriptIndex;
- scriptExists.resize(highestScriptIndex + 1);
- }
- scriptExists[scriptIndex] = true;
- }
-
- for (uint i = 0; i <= highestScriptIndex; i++) {
- if (!scriptExists[i])
- continue;
-
- Common::String logicFileName = Common::String::format("Log/Room%02u.log", i);
-
- Common::File logicFile;
- if (logicFile.open(logicFileName)) {
- debug(1, "Compiling script %s...", logicFileName.c_str());
- compileSchizmLogicFile(*scriptSet, logicFile, static_cast<uint>(logicFile.size()), logicFileName, gs.get());
- logicFile.close();
- }
- }
-
- _scriptSet = scriptSet;
- }
-
if (newGame)
changeToScreen(1, 0xb1);
@@ -1743,16 +1710,25 @@ void Runtime::commitSectionToScreen(const RenderSection §ion, const Common::
case ScriptOps::k##op: this->scriptOp##op(arg); break
bool Runtime::runScript() {
+ if (_scriptCallStack.empty()) {
+ terminateScript();
+ return true;
+ }
+
+ CallStackFrame &frame = _scriptCallStack.back();
+ const Common::Array<Instruction> &instrs = frame._script->instrs;
+
while (_gameState == kGameStateScript) {
- uint instrNum = _scriptNextInstruction;
- if (!_activeScript || instrNum >= _activeScript->instrs.size()) {
- terminateScript();
+ uint instrNum = frame._nextInstruction;
+
+ if (instrNum >= instrs.size()) {
+ _scriptCallStack.pop_back();
return true;
}
- _scriptNextInstruction++;
+ frame._nextInstruction = instrNum + 1u;
- const Instruction &instr = _activeScript->instrs[instrNum];
+ const Instruction &instr = instrs[instrNum];
int32 arg = instr.arg;
switch (instr.op) {
@@ -1770,8 +1746,6 @@ bool Runtime::runScript() {
DISPATCH_OP(AnimG);
DISPATCH_OP(AnimS);
DISPATCH_OP(Anim);
- DISPATCH_OP(AnimChange);
- DISPATCH_OP(AnimVolume);
DISPATCH_OP(Static);
DISPATCH_OP(VarLoad);
@@ -1807,7 +1781,6 @@ bool Runtime::runScript() {
DISPATCH_OP(Music);
DISPATCH_OP(MusicVolRamp);
- DISPATCH_OP(MusicStop);
DISPATCH_OP(Parm0);
DISPATCH_OP(Parm1);
DISPATCH_OP(Parm2);
@@ -1874,6 +1847,67 @@ bool Runtime::runScript() {
DISPATCH_OP(CheckValue);
DISPATCH_OP(Jump);
+ // Schizm ops
+ DISPATCH_OP(CallFunction);
+ DISPATCH_OP(Return);
+
+ DISPATCH_OP(MusicStop);
+ DISPATCH_OP(MusicPlayScore);
+ DISPATCH_OP(ScoreAlways);
+ DISPATCH_OP(ScoreNormal);
+ DISPATCH_OP(SndPlay);
+ DISPATCH_OP(SndPlayEx);
+ DISPATCH_OP(SndPlay3D);
+ DISPATCH_OP(SndPlaying);
+ DISPATCH_OP(SndWait);
+ DISPATCH_OP(SndHalt);
+ DISPATCH_OP(SndToBack);
+ DISPATCH_OP(SndStop);
+ DISPATCH_OP(SndStopAll);
+ DISPATCH_OP(SndAddRandom);
+ DISPATCH_OP(SndClearRandom);
+ DISPATCH_OP(VolumeAdd);
+ DISPATCH_OP(VolumeChange);
+ DISPATCH_OP(VolumeDown);
+ DISPATCH_OP(AnimVolume);
+ DISPATCH_OP(AnimChange);
+ DISPATCH_OP(ScreenName);
+ DISPATCH_OP(ExtractByte);
+ DISPATCH_OP(InsertByte);
+ DISPATCH_OP(String);
+ DISPATCH_OP(CmpNE);
+ DISPATCH_OP(CmpLE);
+ DISPATCH_OP(CmpGE);
+ DISPATCH_OP(Speech);
+ DISPATCH_OP(SpeechEx);
+ DISPATCH_OP(SpeechTest);
+ DISPATCH_OP(Say);
+ DISPATCH_OP(RandomInclusive);
+ DISPATCH_OP(HeroOut);
+ DISPATCH_OP(HeroGetPos);
+ DISPATCH_OP(HeroSetPos);
+ DISPATCH_OP(HeroGet);
+ DISPATCH_OP(Garbage);
+ DISPATCH_OP(GetRoom);
+ DISPATCH_OP(BitAnd);
+ DISPATCH_OP(BitOr);
+ DISPATCH_OP(AngleGet);
+ DISPATCH_OP(CDGet);
+ DISPATCH_OP(Disc);
+ DISPATCH_OP(HidePanel);
+ DISPATCH_OP(RotateUpdate);
+ DISPATCH_OP(Mul);
+ DISPATCH_OP(Div);
+ DISPATCH_OP(Mod);
+ DISPATCH_OP(CyfraGet);
+ DISPATCH_OP(PuzzleInit);
+ DISPATCH_OP(PuzzleCanPress);
+ DISPATCH_OP(PuzzleDoMove1);
+ DISPATCH_OP(PuzzleDoMove2);
+ DISPATCH_OP(PuzzleDone);
+ DISPATCH_OP(PuzzleWhoWon);
+ DISPATCH_OP(Fn);
+
default:
error("Unimplemented opcode %i", static_cast<int>(instr.op));
}
@@ -1885,8 +1919,7 @@ bool Runtime::runScript() {
#undef DISPATCH_OP
void Runtime::terminateScript() {
- _activeScript.reset();
- _scriptNextInstruction = 0;
+ _scriptCallStack.clear();
if (_gameState == kGameStateScript)
_gameState = kGameStateIdle;
@@ -2313,10 +2346,16 @@ void Runtime::changeToScreen(uint roomNumber, uint screenNumber) {
if (changedRoom) {
// This shouldn't happen when running a script
- assert(!_activeScript);
+ assert(_scriptCallStack.empty());
if (_gameID == GID_SCHIZM) {
- // Keep script set
+ uint roomsToCompile[3] = {1, 3, 0};
+ uint numRoomsToCompile = 2;
+
+ if (roomNumber != 1 && roomNumber != 3)
+ roomsToCompile[numRoomsToCompile++] = roomNumber;
+
+ compileSchizmLogicSet(roomsToCompile, numRoomsToCompile);
} else if (_gameID == GID_REAH) {
_scriptSet.reset();
@@ -3276,12 +3315,56 @@ void Runtime::activateScript(const Common::SharedPtr<Script> &script, const Scri
if (script->instrs.size() == 0)
return;
+ assert(_gameState != kGameStateScript);
+
_scriptEnv = envVars;
- _activeScript = script;
- _scriptNextInstruction = 0;
+
+ CallStackFrame frame;
+ frame._script = script;
+ frame._nextInstruction = 0;
+
+ _scriptCallStack.resize(1);
+ _scriptCallStack[0] = frame;
+
_gameState = kGameStateScript;
}
+void Runtime::compileSchizmLogicSet(const uint *roomNumbers, uint numRooms) {
+ _scriptSet.reset();
+
+ Common::SharedPtr<IScriptCompilerGlobalState> gs = createScriptCompilerGlobalState();
+
+ Common::SharedPtr<ScriptSet> scriptSet(new ScriptSet());
+
+ for (uint i = 0; i < numRooms; i++) {
+
+ Common::String logicFileName = Common::String::format("Log/Room%02u.log", roomNumbers[i]);
+
+ Common::File logicFile;
+ if (logicFile.open(logicFileName)) {
+ debug(1, "Compiling script %s...", logicFileName.c_str());
+ compileSchizmLogicFile(*scriptSet, logicFile, static_cast<uint>(logicFile.size()), logicFileName, gs.get());
+ logicFile.close();
+ }
+ }
+
+ gs->dumpFunctionNames(scriptSet->functionNames);
+
+ uint numFunctions = gs->getNumFunctions();
+
+ scriptSet->functions.resize(numFunctions);
+
+ for (uint i = 0; i < numFunctions; i++) {
+ Common::SharedPtr<Script> function = gs->getFunction(i);
+ scriptSet->functions[i] = function;
+
+ if (!function)
+ warning("Function '%s' was referenced but not defined", scriptSet->functionNames[i].c_str());
+ }
+
+ _scriptSet = scriptSet;
+}
+
bool Runtime::parseIndexDef(IndexParseType parseType, uint roomNumber, const Common::String &key, const Common::String &value) {
switch (parseType) {
case kIndexParseTypeNameRoom: {
@@ -3317,6 +3400,10 @@ bool Runtime::parseIndexDef(IndexParseType parseType, uint roomNumber, const Com
int numValuesRead = sscanf(value.c_str(), "%i, %i, %i, %i", &left, &top, &width, &height);
+ // Work around bad def in Schizm at line 5899
+ if (numValuesRead != 4)
+ numValuesRead = sscanf(value.c_str(), "%i ,%i, %i, %i", &left, &top, &width, &height);
+
if (numValuesRead == 4) {
AnimationDef &animDef = _roomDefs[roomNumber]->animations[key];
@@ -4515,9 +4602,6 @@ void Runtime::scriptOpAnim(ScriptArg_t arg) {
}
}
-OPCODE_STUB(AnimChange)
-OPCODE_STUB(AnimVolume)
-
void Runtime::scriptOpStatic(ScriptArg_t arg) {
TAKE_STACK_INT(kAnimDefStackArgs);
@@ -4908,8 +4992,6 @@ void Runtime::scriptOpMusicVolRamp(ScriptArg_t arg) {
}
}
-OPCODE_STUB(MusicStop)
-
void Runtime::scriptOpParm0(ScriptArg_t arg) {
TAKE_STACK_INT(4);
@@ -5292,6 +5374,26 @@ void Runtime::scriptOpVerticalPanGet() {
_scriptStack.push_back(StackValue(isInRadius ? 1 : 0));
}
+void Runtime::scriptOpCallFunction(ScriptArg_t arg) {
+ Common::SharedPtr<Script> function = _scriptSet->functions[arg];
+ if (function) {
+ CallStackFrame newFrame;
+ newFrame._script = function;
+ newFrame._nextInstruction = 0;
+
+ _scriptCallStack.push_back(newFrame);
+
+ _gameState = kGameStateScriptReset;
+ } else {
+ error("Unknown function '%s'", _scriptSet->functionNames[arg].c_str());
+ }
+}
+
+void Runtime::scriptOpReturn(ScriptArg_t arg) {
+ _scriptCallStack.pop_back();
+ _gameState = kGameStateScriptReset;
+}
+
void Runtime::scriptOpSaveAs(ScriptArg_t arg) {
TAKE_STACK_INT(4);
@@ -5444,8 +5546,15 @@ void Runtime::scriptOpGoto(ScriptArg_t arg) {
}
if (newScript) {
- _scriptNextInstruction = 0;
- _activeScript = newScript;
+ // This only happens in Reah so we don't have to worry about what to do about frames on the callstack in Schizm
+ _gameState = kGameStateScriptReset;
+
+ CallStackFrame frame;
+ frame._script = newScript;
+ frame._nextInstruction = 0;
+
+ _scriptCallStack.resize(1);
+ _scriptCallStack[0] = frame;
} else {
error("Goto target %u couldn't be resolved", newInteraction);
}
@@ -5479,9 +5588,9 @@ void Runtime::scriptOpAnimName(ScriptArg_t arg) {
error("Can't resolve animation for room, room number was invalid");
- Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(_activeScript->strings[arg]);
+ Common::HashMap<Common::String, AnimationDef>::const_iterator it = roomDef->animations.find(_scriptSet->strings[arg]);
if (it == roomDef->animations.end())
- error("Can't resolve animation for room, couldn't find animation '%s'", _activeScript->strings[arg].c_str());
+ error("Can't resolve animation for room, couldn't find animation '%s'", _scriptSet->strings[arg].c_str());
pushAnimDef(it->_value);
}
@@ -5494,7 +5603,7 @@ void Runtime::scriptOpValueName(ScriptArg_t arg) {
if (!roomDef)
error("Room def doesn't exist");
- const Common::String &varName = _activeScript->strings[arg];
+ const Common::String &varName = _scriptSet->strings[arg];
Common::HashMap<Common::String, int>::const_iterator it = roomDef->values.find(varName);
if (it == roomDef->values.end())
@@ -5511,7 +5620,7 @@ void Runtime::scriptOpVarName(ScriptArg_t arg) {
if (!roomDef)
error("Room def doesn't exist");
- const Common::String &varName = _activeScript->strings[arg];
+ const Common::String &varName = _scriptSet->strings[arg];
Common::HashMap<Common::String, uint>::const_iterator it = roomDef->vars.find(varName);
if (it == roomDef->vars.end())
@@ -5521,11 +5630,11 @@ void Runtime::scriptOpVarName(ScriptArg_t arg) {
}
void Runtime::scriptOpSoundName(ScriptArg_t arg) {
- _scriptStack.push_back(StackValue(_activeScript->strings[arg]));
+ _scriptStack.push_back(StackValue(_scriptSet->strings[arg]));
}
void Runtime::scriptOpCursorName(ScriptArg_t arg) {
- const Common::String &cursorName = _activeScript->strings[arg];
+ const Common::String &cursorName = _scriptSet->strings[arg];
Common::HashMap<Common::String, StackInt_t>::const_iterator namedCursorIt = _namedCursors.find(cursorName);
if (namedCursorIt == _namedCursors.end()) {
@@ -5546,13 +5655,76 @@ void Runtime::scriptOpCheckValue(ScriptArg_t arg) {
if (stackArgs[0].type == StackValue::kNumber && stackArgs[0].value.i == arg)
_scriptStack.pop_back();
else
- _scriptNextInstruction++;
+ _scriptCallStack.back()._nextInstruction++;
}
void Runtime::scriptOpJump(ScriptArg_t arg) {
- _scriptNextInstruction = arg;
+ _scriptCallStack.back()._nextInstruction = arg;
}
+OPCODE_STUB(MusicStop)
+OPCODE_STUB(MusicPlayScore)
+OPCODE_STUB(ScoreAlways)
+OPCODE_STUB(ScoreNormal)
+OPCODE_STUB(SndPlay)
+OPCODE_STUB(SndPlayEx)
+OPCODE_STUB(SndPlay3D)
+OPCODE_STUB(SndPlaying)
+OPCODE_STUB(SndWait)
+OPCODE_STUB(SndHalt)
+OPCODE_STUB(SndToBack)
+OPCODE_STUB(SndStop)
+OPCODE_STUB(SndStopAll)
+OPCODE_STUB(SndAddRandom)
+OPCODE_STUB(SndClearRandom)
+OPCODE_STUB(VolumeAdd)
+OPCODE_STUB(VolumeChange)
+OPCODE_STUB(VolumeDown)
+OPCODE_STUB(AnimVolume)
+OPCODE_STUB(AnimChange)
+OPCODE_STUB(ScreenName)
+
+void Runtime::scriptOpExtractByte(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+
+ _scriptStack.push_back(StackValue(static_cast<StackInt_t>((stackArgs[0] >> (stackArgs[1] * 8) & 0xff))));
+}
+
+OPCODE_STUB(InsertByte)
+OPCODE_STUB(String)
+OPCODE_STUB(CmpNE)
+OPCODE_STUB(CmpLE)
+OPCODE_STUB(CmpGE)
+OPCODE_STUB(Speech)
+OPCODE_STUB(SpeechEx)
+OPCODE_STUB(SpeechTest)
+OPCODE_STUB(Say)
+OPCODE_STUB(RandomInclusive)
+OPCODE_STUB(HeroOut)
+OPCODE_STUB(HeroGetPos)
+OPCODE_STUB(HeroSetPos)
+OPCODE_STUB(HeroGet)
+OPCODE_STUB(Garbage)
+OPCODE_STUB(GetRoom)
+OPCODE_STUB(BitAnd)
+OPCODE_STUB(BitOr)
+OPCODE_STUB(AngleGet)
+OPCODE_STUB(CDGet)
+OPCODE_STUB(Disc)
+OPCODE_STUB(HidePanel)
+OPCODE_STUB(RotateUpdate)
+OPCODE_STUB(Mul)
+OPCODE_STUB(Div)
+OPCODE_STUB(Mod)
+OPCODE_STUB(CyfraGet)
+OPCODE_STUB(PuzzleInit)
+OPCODE_STUB(PuzzleCanPress)
+OPCODE_STUB(PuzzleDoMove1)
+OPCODE_STUB(PuzzleDoMove2)
+OPCODE_STUB(PuzzleDone)
+OPCODE_STUB(PuzzleWhoWon)
+OPCODE_STUB(Fn)
+
#undef TAKE_STACK_STR
#undef TAKE_STACK_STR_NAMED
#undef TAKE_STACK_INT
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 08dc37247da..9680f4aa33f 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -86,6 +86,7 @@ enum GameState {
kGameStateIdle, // Waiting for input events
kGameStateDelay, // Waiting for delay completion time
kGameStateScript, // Running a script
+ kGameStateScriptReset, // Resetting script interpreter into a new script
kGameStateGyroIdle, // Waiting for mouse movement to run a gyro
kGameStateGyroAnimation, // Animating a gyro
@@ -640,6 +641,13 @@ private:
ValueUnion value;
};
+ struct CallStackFrame {
+ CallStackFrame();
+
+ Common::SharedPtr<Script> _script;
+ uint _nextInstruction;
+ };
+
struct SubtitleDef {
SubtitleDef();
@@ -718,6 +726,7 @@ private:
void pushAnimDef(const AnimationDef &animDef);
void activateScript(const Common::SharedPtr<Script> &script, const ScriptEnvironmentVars &envVars);
+ void compileSchizmLogicSet(const uint *roomNumbers, uint numRooms);
bool parseIndexDef(IndexParseType parseType, uint roomNumber, const Common::String &key, const Common::String &value);
void allocateRoomsUpTo(uint roomNumber);
@@ -769,8 +778,6 @@ private:
void scriptOpAnimG(ScriptArg_t arg);
void scriptOpAnimS(ScriptArg_t arg);
void scriptOpAnim(ScriptArg_t arg);
- void scriptOpAnimChange(ScriptArg_t arg);
- void scriptOpAnimVolume(ScriptArg_t arg);
void scriptOpStatic(ScriptArg_t arg);
void scriptOpVarLoad(ScriptArg_t arg);
@@ -808,7 +815,6 @@ private:
void scriptOpMusic(ScriptArg_t arg);
void scriptOpMusicVolRamp(ScriptArg_t arg);
- void scriptOpMusicStop(ScriptArg_t arg);
void scriptOpParm0(ScriptArg_t arg);
void scriptOpParm1(ScriptArg_t arg);
void scriptOpParm2(ScriptArg_t arg);
@@ -878,6 +884,67 @@ private:
void scriptOpVerticalPanSet(bool *flags);
void scriptOpVerticalPanGet();
+ // Schizm ops
+ void scriptOpCallFunction(ScriptArg_t arg);
+
+ void scriptOpMusicStop(ScriptArg_t arg);
+ void scriptOpMusicPlayScore(ScriptArg_t arg);
+ void scriptOpScoreAlways(ScriptArg_t arg);
+ void scriptOpScoreNormal(ScriptArg_t arg);
+ void scriptOpSndPlay(ScriptArg_t arg);
+ void scriptOpSndPlayEx(ScriptArg_t arg);
+ void scriptOpSndPlay3D(ScriptArg_t arg);
+ void scriptOpSndPlaying(ScriptArg_t arg);
+ void scriptOpSndWait(ScriptArg_t arg);
+ void scriptOpSndHalt(ScriptArg_t arg);
+ void scriptOpSndToBack(ScriptArg_t arg);
+ void scriptOpSndStop(ScriptArg_t arg);
+ void scriptOpSndStopAll(ScriptArg_t arg);
+ void scriptOpSndAddRandom(ScriptArg_t arg);
+ void scriptOpSndClearRandom(ScriptArg_t arg);
+ void scriptOpVolumeAdd(ScriptArg_t arg);
+ void scriptOpVolumeChange(ScriptArg_t arg);
+ void scriptOpVolumeDown(ScriptArg_t arg);
+ void scriptOpAnimVolume(ScriptArg_t arg);
+ void scriptOpAnimChange(ScriptArg_t arg);
+ void scriptOpScreenName(ScriptArg_t arg);
+ void scriptOpExtractByte(ScriptArg_t arg);
+ void scriptOpInsertByte(ScriptArg_t arg);
+ void scriptOpString(ScriptArg_t arg);
+ void scriptOpCmpNE(ScriptArg_t arg);
+ void scriptOpCmpLE(ScriptArg_t arg);
+ void scriptOpCmpGE(ScriptArg_t arg);
+ void scriptOpReturn(ScriptArg_t arg);
+ void scriptOpSpeech(ScriptArg_t arg);
+ void scriptOpSpeechEx(ScriptArg_t arg);
+ void scriptOpSpeechTest(ScriptArg_t arg);
+ void scriptOpSay(ScriptArg_t arg);
+ void scriptOpRandomInclusive(ScriptArg_t arg);
+ void scriptOpHeroOut(ScriptArg_t arg);
+ void scriptOpHeroGetPos(ScriptArg_t arg);
+ void scriptOpHeroSetPos(ScriptArg_t arg);
+ void scriptOpHeroGet(ScriptArg_t arg);
+ void scriptOpGarbage(ScriptArg_t arg);
+ void scriptOpGetRoom(ScriptArg_t arg);
+ void scriptOpBitAnd(ScriptArg_t arg);
+ void scriptOpBitOr(ScriptArg_t arg);
+ void scriptOpAngleGet(ScriptArg_t arg);
+ void scriptOpCDGet(ScriptArg_t arg);
+ void scriptOpDisc(ScriptArg_t arg);
+ void scriptOpHidePanel(ScriptArg_t arg);
+ void scriptOpRotateUpdate(ScriptArg_t arg);
+ void scriptOpMul(ScriptArg_t arg);
+ void scriptOpDiv(ScriptArg_t arg);
+ void scriptOpMod(ScriptArg_t arg);
+ void scriptOpCyfraGet(ScriptArg_t arg);
+ void scriptOpPuzzleInit(ScriptArg_t arg);
+ void scriptOpPuzzleCanPress(ScriptArg_t arg);
+ void scriptOpPuzzleDoMove1(ScriptArg_t arg);
+ void scriptOpPuzzleDoMove2(ScriptArg_t arg);
+ void scriptOpPuzzleDone(ScriptArg_t arg);
+ void scriptOpPuzzleWhoWon(ScriptArg_t arg);
+ void scriptOpFn(ScriptArg_t arg);
+
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursors; // Cursors indexed as CURSOR_CUR_##
Common::Array<Common::SharedPtr<Graphics::WinCursorGroup> > _cursorsShort; // Cursors indexed as CURSOR_#
@@ -947,8 +1014,8 @@ private:
Common::Array<Common::SharedPtr<RoomDef> > _roomDefs;
Common::SharedPtr<ScriptSet> _scriptSet;
- Common::SharedPtr<Script> _activeScript;
- uint _scriptNextInstruction;
+ Common::Array<CallStackFrame> _scriptCallStack;
+
Common::Array<StackValue> _scriptStack;
ScriptEnvironmentVars _scriptEnv;
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 43d57b16621..5ef4c330312 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -132,19 +132,13 @@ ProtoInstruction::ProtoInstruction(ProtoOp paramProtoOp, ScriptOps::ScriptOp par
}
struct ProtoScript {
- Common::Array<ProtoInstruction> instrs;
-
- Common::Array<Common::String> strings;
- Common::HashMap<Common::String, uint> stringToIndex;
-
void reset();
+
+ Common::Array<ProtoInstruction> instrs;
};
void ProtoScript::reset() {
instrs.clear();
-
- strings.clear();
- stringToIndex.clear(true);
}
struct ScriptNamedInstruction {
@@ -174,7 +168,7 @@ private:
void codeGenScript(ProtoScript &protoScript, Script &script);
- static uint indexString(ProtoScript &script, const Common::String &str);
+ uint indexString(const Common::String &str);
enum NumberParsingMode {
kNumberParsingDec,
@@ -191,6 +185,8 @@ private:
const char *_scrToken;
const char *_eroomToken;
+ Common::HashMap<Common::String, uint> _stringToIndex;
+
IScriptCompilerGlobalState *_gs;
};
@@ -200,12 +196,18 @@ public:
const Common::String *getTokenReplacement(const Common::String &str) const override;
- void addFunction(const Common::String &fnName, const Common::SharedPtr<Script> &fn) override;
- Common::SharedPtr<Script> getFunction(const Common::String &fnName) const override;
+ uint getFunctionIndex(const Common::String &fnName) override;
+ void setFunction(uint fnIndex, const Common::SharedPtr<Script> &fn) override;
+
+ uint getNumFunctions() const override;
+ void dumpFunctionNames(Common::Array<Common::String> &fnNames) const override;
+ Common::SharedPtr<Script> getFunction(uint fnIndex) const override;
private:
Common::HashMap<Common::String, Common::String> _defs;
- Common::HashMap<Common::String, Common::SharedPtr<Script> > _functions;
+
+ Common::HashMap<Common::String, uint> _functionNameToIndex;
+ Common::Array<Common::SharedPtr<Script> > _functions;
};
ScriptCompiler::ScriptCompiler(TextParser &parser, const Common::String &blamePath, ScriptDialect dialect, IScriptCompilerGlobalState *gs)
@@ -302,6 +304,11 @@ void ScriptCompiler::expectNumber(uint32 &outNumber) {
void ScriptCompiler::compileScriptSet(ScriptSet *ss) {
Common::SharedPtr<RoomScriptSet> roomScript;
+ uint numExistingStrings = ss->strings.size();
+
+ for (uint i = 0; i < numExistingStrings; i++)
+ _stringToIndex[ss->strings[i]] = i;
+
const char *roomToken = nullptr;
if (_dialect == kScriptDialectReah) {
@@ -344,6 +351,15 @@ void ScriptCompiler::compileScriptSet(ScriptSet *ss) {
error("Error compiling script at line %i col %i: Expected %s and found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), roomToken, token.c_str());
}
}
+
+ for (const Common::HashMap<Common::String, uint>::Node &stiNode : _stringToIndex) {
+ if (stiNode._value >= numExistingStrings) {
+ if (stiNode._value >= ss->strings.size())
+ ss->strings.resize(stiNode._value + 1);
+
+ ss->strings[stiNode._value] = stiNode._key;
+ }
+ }
}
void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
@@ -383,7 +399,15 @@ void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
compileFunction(func.get());
- _gs->addFunction(fnName, func);
+ uint fnIndex = _gs->getFunctionIndex(fnName);
+
+ if (_gs->getFunction(fnIndex)) {
+ // This triggers on fnSoundFountain_Start and fnSoundFountain_Stop in Room30.
+ // fnSoundFountain_Start is called in Room31, so might not matter there? But fnSoundFountain_Stop is called in Room30.
+ warning("Function '%s' was defined multiple times", fnName.c_str());
+ }
+
+ _gs->setFunction(_gs->getFunctionIndex(fnName), func);
} else {
error("Error compiling script at line %i col %i: Expected %s or %s and found '%s'", static_cast<int>(state._lineNum), static_cast<int>(state._col), _eroomToken, _scrToken, token.c_str());
}
@@ -426,7 +450,7 @@ void ScriptCompiler::compileReahScreenScriptSet(ScreenScriptSet *sss) {
} else if (token == "dubbing") {
Common::String dubbingName;
_parser.expectToken(dubbingName, _blamePath);
- protoScript.instrs.push_back(ProtoInstruction(ScriptOps::kDubbing, indexString(protoScript, dubbingName)));
+ protoScript.instrs.push_back(ProtoInstruction(ScriptOps::kDubbing, indexString(dubbingName)));
} else if (compileInstructionToken(protoScript, token)) {
// Nothing
} else {
@@ -695,6 +719,7 @@ static ScriptNamedInstruction g_schizmNamedInstructions[] = {
{"puzzleDoMove2", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleDoMove2},
{"puzzleDone", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleDone},
{"puzzleWhoWon", ProtoOp::kProtoOpScript, ScriptOps::kPuzzleWhoWon},
+ {"fn", ProtoOp::kProtoOpScript, ScriptOps::kFn},
{"+", ProtoOp::kProtoOpScript, ScriptOps::kAdd},
{"-", ProtoOp::kProtoOpScript, ScriptOps::kSub},
@@ -761,28 +786,28 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
if (_dialect == kScriptDialectReah) {
if (token.size() >= 3 && token[2] == ':') {
if (token[1] == 'Y') {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kVarName, indexString(script, token.substr(3))));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kVarName, indexString(token.substr(3))));
return true;
} else if (token[1] == 'V') {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kValueName, indexString(script, token.substr(3))));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kValueName, indexString(token.substr(3))));
return true;
} else
return false;
}
}
- script.instrs.push_back(ProtoInstruction(ScriptOps::kAnimName, indexString(script, token.substr(1))));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kAnimName, indexString(token.substr(1))));
return true;
}
if (token.size() >= 2 && token[0] == '_') {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kSoundName, indexString(script, token.substr(1))));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kSoundName, indexString(token.substr(1))));
return true;
}
if (_dialect == kScriptDialectReah) {
if (token.hasPrefix("CUR_")) {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(script, token)));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(token)));
return true;
}
}
@@ -818,15 +843,9 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
}
}
} else if (_dialect == kScriptDialectSchizm) {
- if (token.hasPrefix("fn")) {
- uint fnIndex = indexString(script, token);
- script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kCallFunction, fnIndex));
- return true;
- }
-
if (token.size() >= 2 && token[0] == '\"' && token[token.size() - 1] == '\"') {
// Seems like these are only used for sounds and music?
- uint fnIndex = indexString(script, token.substr(1, token.size() - 2));
+ uint fnIndex = indexString(token.substr(1, token.size() - 2));
script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kString, fnIndex));
return true;
}
@@ -844,6 +863,12 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
}
}
+ if (token.hasPrefix("fn")) {
+ uint fnIndex = _gs->getFunctionIndex(token);
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kCallFunction, fnIndex));
+ return true;
+ }
+
if (token.size() >= 2 && token.hasSuffix("!")) {
if (compileInstructionToken(script, token.substr(0, token.size() - 1))) {
script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kVarGlobalStore, 0));
@@ -878,19 +903,19 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
}
if (couldBeScreenName) {
- script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kScreenName, indexString(script, token)));
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kScreenName, indexString(token)));
return true;
}
if (token.hasPrefix("cur")) {
- script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(script, token)));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(token)));
return true;
}
// HACK: Work around broken volume variable names in Room02. Some of these appear to have "par"
// where it should be "vol" but some are garbage. Figure this out later.
if (token.hasPrefix("par")) {
- script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kGarbage, indexString(script, token)));
+ script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kGarbage, indexString(token)));
return true;
}
}
@@ -1194,17 +1219,13 @@ void ScriptCompiler::codeGenScript(ProtoScript &protoScript, Script &script) {
break;
}
}
-
- // Commit strings
- script.strings = Common::move(protoScript.strings);
}
-uint ScriptCompiler::indexString(ProtoScript &script, const Common::String &str) {
- Common::HashMap<Common::String, uint>::const_iterator it = script.stringToIndex.find(str);
- if (it == script.stringToIndex.end()) {
- uint index = script.strings.size();
- script.stringToIndex[str] = index;
- script.strings.push_back(str);
+uint ScriptCompiler::indexString(const Common::String &str) {
+ Common::HashMap<Common::String, uint>::const_iterator it = _stringToIndex.find(str);
+ if (it == _stringToIndex.end()) {
+ uint index = _stringToIndex.size();
+ _stringToIndex[str] = index;
return index;
}
@@ -1223,16 +1244,39 @@ const Common::String *ScriptCompilerGlobalState::getTokenReplacement(const Commo
return &it->_value;
}
-void ScriptCompilerGlobalState::addFunction(const Common::String &fnName, const Common::SharedPtr<Script> &fn) {
- _functions.setVal(fnName, fn);
+uint ScriptCompilerGlobalState::getFunctionIndex(const Common::String &fnName) {
+ Common::HashMap<Common::String, uint>::const_iterator it = _functionNameToIndex.find(fnName);
+
+ assert(fnName != "fn");
+
+ if (it == _functionNameToIndex.end()) {
+ uint newIndex = _functionNameToIndex.size();
+ _functionNameToIndex.setVal(fnName, newIndex);
+ _functions.push_back(nullptr);
+
+ return newIndex;
+ } else
+ return it->_value;
+}
+
+void ScriptCompilerGlobalState::setFunction(uint fnIndex, const Common::SharedPtr<Script> &fn) {
+ _functions[fnIndex] = fn;
}
-Common::SharedPtr<Script> ScriptCompilerGlobalState::getFunction(const Common::String &fnName) const {
- Common::HashMap<Common::String, Common::SharedPtr<Script> >::const_iterator it = _functions.find(fnName);
- if (it == _functions.end())
- return Common::SharedPtr<Script>();
+uint ScriptCompilerGlobalState::getNumFunctions() const {
+ return _functionNameToIndex.size();
+}
- return it->_value;
+void ScriptCompilerGlobalState::dumpFunctionNames(Common::Array<Common::String> &fnNames) const {
+ fnNames.clear();
+ fnNames.resize(_functionNameToIndex.size());
+
+ for (const Common::HashMap<Common::String, uint>::Node &node : _functionNameToIndex)
+ fnNames[node._value] = node._key;
+}
+
+Common::SharedPtr<Script> ScriptCompilerGlobalState::getFunction(uint fnIndex) const {
+ return _functions[fnIndex];
}
IScriptCompilerGlobalState::~IScriptCompilerGlobalState() {
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index 6cf002f4144..b7b6d5ee859 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -215,6 +215,7 @@ enum ScriptOp {
kPuzzleDoMove2,
kPuzzleDone,
kPuzzleWhoWon,
+ kFn,
kNumOps,
};
@@ -232,7 +233,6 @@ struct Instruction {
struct Script {
Common::Array<Instruction> instrs;
- Common::Array<Common::String> strings;
};
typedef Common::HashMap<uint, Common::SharedPtr<Script> > ScriptMap_t;
@@ -252,6 +252,10 @@ struct RoomScriptSet {
struct ScriptSet {
RoomScriptSetMap_t roomScripts;
+
+ Common::Array<Common::SharedPtr<Script> > functions;
+ Common::Array<Common::String> functionNames;
+ Common::Array<Common::String> strings;
};
struct FunctionDef {
@@ -267,8 +271,12 @@ struct IScriptCompilerGlobalState {
virtual void define(const Common::String &key, const Common::String &value) = 0;
virtual const Common::String *getTokenReplacement(const Common::String &str) const = 0;
- virtual void addFunction(const Common::String &fnName, const Common::SharedPtr<Script> &fn) = 0;
- virtual Common::SharedPtr<Script> getFunction(const Common::String &fnName) const = 0;
+ virtual uint getFunctionIndex(const Common::String &fnName) = 0;
+ virtual void setFunction(uint fnIndex, const Common::SharedPtr<Script> &fn) = 0;
+
+ virtual uint getNumFunctions() const = 0;
+ virtual void dumpFunctionNames(Common::Array<Common::String> &fnNames) const = 0;
+ virtual Common::SharedPtr<Script> getFunction(uint fnIndex) const = 0;
};
Common::SharedPtr<IScriptCompilerGlobalState> createScriptCompilerGlobalState();
Commit: 50a505b6a8c66f786175a2fc62ec6a4cd14c8253
https://github.com/scummvm/scummvm/commit/50a505b6a8c66f786175a2fc62ec6a4cd14c8253
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:43-04:00
Commit Message:
VCRUISE: Add animation volume handling and score loader.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
engines/vcruise/script.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 3ba53a525f2..fae7d37a028 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -573,6 +573,9 @@ void TriggeredOneShot::read(Common::ReadStream *stream) {
uniqueSlot = stream->readUint32BE();
}
+ScoreSectionDef::ScoreSectionDef() : volumeOrDurationInSeconds(0) {
+}
+
StaticAnimParams::StaticAnimParams() : initialDelay(0), repeatDelay(0), lockInteractions(false) {
}
@@ -677,8 +680,8 @@ void SaveGameSnapshot::Sound::read(Common::ReadStream *stream) {
params3D.read(stream);
}
-SaveGameSnapshot::SaveGameSnapshot() : roomNumber(0), screenNumber(0), direction(0), escOn(false), musicTrack(0), musicVolume(100), loadedAnimation(0),
- animDisplayingFrame(0), listenerX(0), listenerY(0), listenerAngle(0) {
+SaveGameSnapshot::SaveGameSnapshot() : roomNumber(0), screenNumber(0), direction(0), escOn(false), musicTrack(0), musicVolume(100), musicActive(true), loadedAnimation(0),
+ animDisplayingFrame(0), animVolume(100), listenerX(0), listenerY(0), listenerAngle(0) {
}
void SaveGameSnapshot::write(Common::WriteStream *stream) const {
@@ -693,8 +696,13 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeSint32BE(musicTrack);
stream->writeUint32BE(musicVolume);
+ writeString(stream, scoreTrack);
+ writeString(stream, scoreSection);
+ stream->writeByte(musicActive ? 1 : 0);
+
stream->writeUint32BE(loadedAnimation);
stream->writeUint32BE(animDisplayingFrame);
+ stream->writeUint32BE(animVolume);
pendingStaticAnimParams.write(stream);
pendingSoundParams3D.write(stream);
@@ -768,9 +776,22 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
else
musicVolume = 100;
+ if (saveVersion >= 6) {
+ scoreTrack = safeReadString(stream);
+ scoreSection = safeReadString(stream);
+ musicActive = (stream->readByte() != 0);
+ } else {
+ musicActive = true;
+ }
+
loadedAnimation = stream->readUint32BE();
animDisplayingFrame = stream->readUint32BE();
+ if (saveVersion >= 6)
+ animVolume = stream->readUint32BE();
+ else
+ animVolume = 100;
+
pendingStaticAnimParams.read(stream);
pendingSoundParams3D.read(stream);
@@ -840,13 +861,26 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
return kLoadGameOutcomeSucceeded;
}
+Common::String SaveGameSnapshot::safeReadString(Common::ReadStream *stream) {
+ uint len = stream->readUint32BE();
+ if (stream->eos() || stream->err())
+ len = 0;
+
+ return stream->readString(0, len);
+}
+
+void SaveGameSnapshot::writeString(Common::WriteStream *stream, const Common::String &str) {
+ stream->writeUint32BE(str.size());
+ stream->writeString(str);
+}
+
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
_havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _escOn(false), _debugMode(false), _fastAnimationMode(false),
- _musicTrack(0), _musicVolume(100), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
+ _musicTrack(0), _musicActive(true), _scoreSectionEndTime(0), _musicVolume(100), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
_panoramaDirectionFlags(0),
- _loadedAnimation(0), _loadedAnimationHasSound(false), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0),
+ _loadedAnimation(0), _loadedAnimationHasSound(false), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0), _animVolume(100),
_animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleLockInteractions(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
_inGameMenuState(kInGameMenuStateInvisible), _inGameMenuActiveElement(0), _inGameMenuButtonActive {false, false, false, false, false},
@@ -1039,6 +1073,11 @@ bool Runtime::bootGame(bool newGame) {
findWaves();
debug(1, "Waves indexed OK");
+ if (_gameID == GID_SCHIZM) {
+ loadScore();
+ debug(1, "Score loaded OK");
+ }
+
_trayBackgroundGraphic = loadGraphic("Pocket", true);
_trayHighlightGraphic = loadGraphic("Select", true);
_trayCompassGraphic = loadGraphic("Select_1", true);
@@ -1046,8 +1085,13 @@ bool Runtime::bootGame(bool newGame) {
_gameState = kGameStateIdle;
- if (newGame)
- changeToScreen(1, 0xb1);
+ if (newGame) {
+ // TODO: Implement menus and go to b1 in Schizm instead
+ if (_gameID == GID_SCHIZM)
+ changeToScreen(1, 0xb0);
+ else
+ changeToScreen(1, 0xb1);
+ }
Common::Language lang = Common::parseLanguage(ConfMan.get("language"));
@@ -2200,6 +2244,52 @@ void Runtime::findWaves() {
}
}
+void Runtime::loadScore() {
+ Common::INIFile scoreINI;
+ if (scoreINI.loadFromFile("Sfx/score.ini")) {
+
+ for (const Common::INIFile::Section §ion : scoreINI.getSections()) {
+ ScoreTrackDef &trackDef = _scoreDefs[section.name];
+
+ for (const Common::INIFile::KeyValue &kv : section.keys) {
+
+ uint32 firstSpacePos = kv.value.find(' ', 0);
+ if (firstSpacePos != Common::String::npos) {
+ uint32 secondSpacePos = kv.value.find(' ', firstSpacePos + 1);
+
+ if (secondSpacePos == Common::String::npos) {
+ // Silent section
+ Common::String durationSlice = kv.value.substr(0, firstSpacePos);
+ Common::String nextSectionSlice = kv.value.substr(firstSpacePos + 1);
+
+ uint duration = 0;
+ if (sscanf(durationSlice.c_str(), "%u", &duration) == 1) {
+ ScoreSectionDef §ionDef = trackDef.sections[kv.key];
+ sectionDef.nextSection = nextSectionSlice;
+ sectionDef.volumeOrDurationInSeconds = duration;
+ } else
+ warning("Couldn't parse score silent section duration");
+ } else {
+ Common::String fileNameSlice = kv.value.substr(0, firstSpacePos);
+ Common::String volumeSlice = kv.value.substr(firstSpacePos + 1, secondSpacePos - firstSpacePos - 1);
+ Common::String nextSectionSlice = kv.value.substr(secondSpacePos + 1);
+
+ int volume = 0;
+ if (sscanf(volumeSlice.c_str(), "%i", &volume) == 1) {
+ ScoreSectionDef §ionDef = trackDef.sections[kv.key];
+ sectionDef.nextSection = nextSectionSlice;
+ sectionDef.volumeOrDurationInSeconds = normalizeSoundVolume(volume);
+ sectionDef.musicFileName = fileNameSlice;
+ } else
+ warning("Couldn't parse score section volume");
+ }
+ }
+ }
+ }
+ } else
+ warning("Couldn't load music score");
+}
+
Common::SharedPtr<SoundInstance> Runtime::loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr) {
for (const Common::SharedPtr<SoundInstance> &activeSound : _activeSounds) {
if (activeSound->name == soundName)
@@ -2312,6 +2402,15 @@ void Runtime::resolveSoundByName(const Common::String &soundName, bool load, Sta
}
}
+SoundInstance *Runtime::resolveSoundByID(uint soundID) {
+ for (const Common::SharedPtr<SoundInstance> &snd : _activeSounds) {
+ if (snd->id == soundID)
+ return snd.get();
+ }
+
+ return nullptr;
+}
+
void Runtime::resolveSoundByNameOrID(const StackValue &stackValue, bool load, StackInt_t &outSoundID, SoundInstance *&outWave) {
outSoundID = 0;
outWave = nullptr;
@@ -2721,6 +2820,9 @@ void Runtime::changeMusicTrack(int track) {
_musicPlayer.reset();
_musicTrack = track;
+ if (!_musicActive)
+ return;
+
Common::String wavFileName = Common::String::format("Sfx/Music-%02i.wav", static_cast<int>(track));
Common::File *wavFile = new Common::File();
if (wavFile->open(wavFileName)) {
@@ -2774,6 +2876,8 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
delete aviFile;
}
+ applyAnimationVolume();
+
Common::String sfxFileName = Common::String::format("Sfx/Anim%04i.sfx", animFile);
Common::File sfxFile;
@@ -2837,6 +2941,15 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
debug(1, "Animation last frame set to %u", animDef.lastFrame);
}
+void Runtime::applyAnimationVolume() {
+ if (_animDecoder) {
+ uint volume = _animVolume * static_cast<uint>(Audio::Mixer::kMaxChannelVolume) / 100u;
+ if (volume > Audio::Mixer::kMaxChannelVolume)
+ volume = Audio::Mixer::kMaxChannelVolume;
+ _animDecoder->setVolume(volume);
+ }
+}
+
void Runtime::setSound3DParameters(SoundInstance &snd, int32 x, int32 y, const SoundParams3D &soundParams3D) {
snd.x = x;
snd.y = y;
@@ -3273,6 +3386,16 @@ void Runtime::triggerAmbientSounds() {
snd.sceneChangesRemaining--;
}
+uint Runtime::normalizeSoundVolume(StackInt_t arg) const {
+ int32 adjustedVol = static_cast<int32>(arg) + 50;
+ if (adjustedVol < 0)
+ return 0;
+ if (adjustedVol > 100)
+ return 100;
+
+ return static_cast<uint>(adjustedVol);
+}
+
AnimationDef Runtime::stackArgsToAnimDef(const StackInt_t *args) const {
AnimationDef def;
def.animNum = args[0];
@@ -4131,6 +4254,7 @@ void Runtime::recordSaveGameSnapshot() {
snapshot->escOn = _escOn;
snapshot->musicTrack = _musicTrack;
+ snapshot->musicActive = _musicActive;
snapshot->musicVolume = _musicVolume;
@@ -4222,6 +4346,8 @@ void Runtime::restoreSaveGameSnapshot() {
_musicVolumeRampRatePerMSec = 0;
_musicVolumeRampEnd = _musicVolume;
+ _musicActive = _saveGame->musicActive;
+
changeMusicTrack(_saveGame->musicTrack);
// Stop all sounds since the player instances are stored in the sound cache.
@@ -5662,11 +5788,22 @@ void Runtime::scriptOpJump(ScriptArg_t arg) {
_scriptCallStack.back()._nextInstruction = arg;
}
-OPCODE_STUB(MusicStop)
-OPCODE_STUB(MusicPlayScore)
+void Runtime::scriptOpMusicStop(ScriptArg_t arg) {
+ _musicPlayer.reset();
+ _musicActive = false;
+}
+
+void Runtime::scriptOpMusicPlayScore(ScriptArg_t arg) {
+ error("MusicPlayScore opcode not implemented");
+}
+
OPCODE_STUB(ScoreAlways)
OPCODE_STUB(ScoreNormal)
-OPCODE_STUB(SndPlay)
+
+void Runtime::scriptOpSndPlay(ScriptArg_t arg) {
+ scriptOpSoundL1(arg);
+}
+
OPCODE_STUB(SndPlayEx)
OPCODE_STUB(SndPlay3D)
OPCODE_STUB(SndPlaying)
@@ -5678,11 +5815,48 @@ OPCODE_STUB(SndStopAll)
OPCODE_STUB(SndAddRandom)
OPCODE_STUB(SndClearRandom)
OPCODE_STUB(VolumeAdd)
-OPCODE_STUB(VolumeChange)
+
+void Runtime::scriptOpVolumeChange(ScriptArg_t arg) {
+ TAKE_STACK_INT(3);
+
+ SoundInstance *cachedSound = resolveSoundByID(static_cast<uint>(stackArgs[0]));
+
+ if (cachedSound)
+ triggerSoundRamp(*cachedSound, stackArgs[1] * 100, stackArgs[2], false);
+}
+
OPCODE_STUB(VolumeDown)
-OPCODE_STUB(AnimVolume)
-OPCODE_STUB(AnimChange)
-OPCODE_STUB(ScreenName)
+
+void Runtime::scriptOpAnimVolume(ScriptArg_t arg) {
+ TAKE_STACK_INT(1);
+
+ _animVolume = normalizeSoundVolume(stackArgs[0]);
+
+ applyAnimationVolume();
+}
+
+void Runtime::scriptOpAnimChange(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+
+ (void)stackArgs;
+
+ warning("animChange opcode isn't implemented yet");
+}
+
+void Runtime::scriptOpScreenName(ScriptArg_t arg) {
+ const Common::String &scrName = _scriptSet->strings[arg];
+ RoomScriptSetMap_t::const_iterator scriptSetIt = _scriptSet->roomScripts.find(_roomNumber);
+ if (scriptSetIt == _scriptSet->roomScripts.end())
+ error("Couldn't resolve room number to find screen name: '%s'", scrName.c_str());
+
+ const RoomScriptSet *rss = scriptSetIt->_value.get();
+
+ ScreenNameMap_t::const_iterator screenNameIt = rss->screenNames.find(scrName);
+ if (screenNameIt == rss->screenNames.end())
+ error("Couldn't resolve screen name '%s'", scrName.c_str());
+
+ _scriptStack.push_back(StackValue(static_cast<StackInt_t>(screenNameIt->_value)));
+}
void Runtime::scriptOpExtractByte(ScriptArg_t arg) {
TAKE_STACK_INT(2);
@@ -5691,7 +5865,11 @@ void Runtime::scriptOpExtractByte(ScriptArg_t arg) {
}
OPCODE_STUB(InsertByte)
-OPCODE_STUB(String)
+
+void Runtime::scriptOpString(ScriptArg_t arg) {
+ _scriptStack.push_back(StackValue(_scriptSet->strings[arg]));
+}
+
OPCODE_STUB(CmpNE)
OPCODE_STUB(CmpLE)
OPCODE_STUB(CmpGE)
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 9680f4aa33f..552f8b80282 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -271,6 +271,20 @@ struct TriggeredOneShot {
void read(Common::ReadStream *stream);
};
+struct ScoreSectionDef {
+ ScoreSectionDef();
+
+ Common::String musicFileName; // If empty, this is silent
+ Common::String nextSection;
+ uint32 volumeOrDurationInSeconds;
+};
+
+struct ScoreTrackDef {
+ typedef Common::HashMap<Common::String, ScoreSectionDef> ScoreSectionMap_t;
+
+ ScoreSectionMap_t sections;
+};
+
struct StaticAnimParams {
StaticAnimParams();
@@ -346,7 +360,7 @@ struct SaveGameSnapshot {
LoadGameOutcome read(Common::ReadStream *stream);
static const uint kSaveGameIdentifier = 0x53566372;
- static const uint kSaveGameCurrentVersion = 5;
+ static const uint kSaveGameCurrentVersion = 6;
static const uint kSaveGameEarliestSupportedVersion = 2;
struct InventoryItem {
@@ -380,6 +394,9 @@ struct SaveGameSnapshot {
void read(Common::ReadStream *stream);
};
+ static Common::String safeReadString(Common::ReadStream *stream);
+ static void writeString(Common::WriteStream *stream, const Common::String &str);
+
uint roomNumber;
uint screenNumber;
uint direction;
@@ -387,10 +404,15 @@ struct SaveGameSnapshot {
bool escOn;
int musicTrack;
+ Common::String scoreTrack;
+ Common::String scoreSection;
+ bool musicActive;
+
uint musicVolume;
uint loadedAnimation;
uint animDisplayingFrame;
+ uint animVolume;
StaticAnimParams pendingStaticAnimParams;
SoundParams3D pendingSoundParams3D;
@@ -689,10 +711,12 @@ private:
void loadIndex();
void findWaves();
+ void loadScore();
Common::SharedPtr<SoundInstance> loadWave(const Common::String &soundName, uint soundID, const Common::ArchiveMemberPtr &archiveMemberPtr);
SoundCache *loadCache(SoundInstance &sound);
- void resolveSoundByName(const Common::String &soundName, bool resolveSoundByName, StackInt_t &outSoundID, SoundInstance *&outWave);
- void resolveSoundByNameOrID(const StackValue &stackValue, bool resolveSoundByName, StackInt_t &outSoundID, SoundInstance *&outWave);
+ void resolveSoundByName(const Common::String &soundName, bool load, StackInt_t &outSoundID, SoundInstance *&outWave);
+ SoundInstance *resolveSoundByID(uint soundID);
+ void resolveSoundByNameOrID(const StackValue &stackValue, bool load, StackInt_t &outSoundID, SoundInstance *&outWave);
void changeToScreen(uint roomNumber, uint screenNumber);
void returnToIdleState();
@@ -708,6 +732,7 @@ private:
void changeAnimation(const AnimationDef &animDef, bool consumeFPSOverride);
void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride);
void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride, const Fraction &defaultFrameRate);
+ void applyAnimationVolume();
void setSound3DParameters(SoundInstance &sound, int32 x, int32 y, const SoundParams3D &soundParams3D);
void triggerSound(bool looping, SoundInstance &sound, uint volume, int32 balance, bool is3D, bool isSpeech);
@@ -718,6 +743,7 @@ private:
void update3DSounds();
bool computeEffectiveVolumeAndBalance(SoundInstance &snd);
void triggerAmbientSounds();
+ uint normalizeSoundVolume(StackInt_t arg) const;
void triggerWaveSubtitles(const SoundInstance &sound, const Common::String &id);
void stopSubtitles();
@@ -1024,6 +1050,12 @@ private:
Common::SharedPtr<AudioPlayer> _musicPlayer;
int _musicTrack;
uint _musicVolume;
+ bool _musicActive;
+
+ Common::String _scoreTrack;
+ Common::String _scoreSection;
+ uint32 _scoreSectionEndTime;
+ Common::HashMap<Common::String, ScoreTrackDef> _scoreDefs;
uint32 _musicVolumeRampStartTime;
uint _musicVolumeRampStartVolume;
@@ -1040,6 +1072,7 @@ private:
uint _animFirstFrame;
uint _animLastFrame;
uint _animStopFrame;
+ uint _animVolume;
Fraction _animFrameRateLock;
Common::Rect _animConstraintRect;
uint32 _animStartTime;
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 5ef4c330312..158c66e46db 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -375,8 +375,15 @@ void ScriptCompiler::compileRoomScriptSet(RoomScriptSet *rss) {
Common::SharedPtr<ScreenScriptSet> sss(new ScreenScriptSet());
if (_dialect == kScriptDialectReah)
compileReahScreenScriptSet(sss.get());
- else if (_dialect == kScriptDialectSchizm)
+ else if (_dialect == kScriptDialectSchizm) {
+
+ if (!_parser.parseToken(token, state))
+ error("Error compiling script at line %i col %i: Expected screen name", static_cast<int>(state._lineNum), static_cast<int>(state._col));
+
+ rss->screenNames[token] = screenNumber;
+
compileSchizmScreenScriptSet(sss.get());
+ }
// QUIRK: The tower in Reah (Room 06) has two 0cb screens, the second one is bad and must be ignored
if (rss->screenScripts.find(screenNumber) == rss->screenScripts.end())
@@ -468,11 +475,6 @@ void ScriptCompiler::compileSchizmScreenScriptSet(ScreenScriptSet *sss) {
sss->entryScript.reset(currentScript);
- if (!_parser.parseToken(token, state))
- error("Error compiling script at line %i col %i: Expected screen name", static_cast<int>(state._lineNum), static_cast<int>(state._col));
-
- sss->screenName = token;
-
while (_parser.parseToken(token, state)) {
if (token == "~ERoom" || token == "~Scr" || token == "~Fun") {
_parser.requeue(token, state);
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index b7b6d5ee859..94b996ebdcd 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -238,16 +238,16 @@ struct Script {
typedef Common::HashMap<uint, Common::SharedPtr<Script> > ScriptMap_t;
typedef Common::HashMap<uint, Common::SharedPtr<ScreenScriptSet> > ScreenScriptSetMap_t;
typedef Common::HashMap<uint, Common::SharedPtr<RoomScriptSet> > RoomScriptSetMap_t;
+typedef Common::HashMap<Common::String, uint> ScreenNameMap_t;
struct ScreenScriptSet {
Common::SharedPtr<Script> entryScript;
ScriptMap_t interactionScripts;
-
- Common::String screenName; // Only in Schizm
};
struct RoomScriptSet {
ScreenScriptSetMap_t screenScripts;
+ ScreenNameMap_t screenNames;
};
struct ScriptSet {
Commit: cc0dcb1f717dd837cd506e131d573c1d0f9a50b2
https://github.com/scummvm/scummvm/commit/cc0dcb1f717dd837cd506e131d573c1d0f9a50b2
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:43-04:00
Commit Message:
VCRUISE: Fix up some initial Schizm things.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index fae7d37a028..53909b516ce 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -41,6 +41,8 @@
#include "image/bmp.h"
#include "audio/decoders/wave.h"
+#include "audio/decoders/vorbis.h"
+
#include "audio/audiostream.h"
#include "video/avi_decoder.h"
@@ -971,19 +973,29 @@ void Runtime::loadCursors(const char *exeName) {
_namedCursors["CUR_PRZOD"] = 1; // Przod = forward
// CUR_ZOSTAW is in the executable memory but appears to be unused
-
- _panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp] = 2;
- _panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableDown] = 3;
- _panCursors[kPanCursorDraggableHoriz] = 4;
- _panCursors[kPanCursorDraggableHoriz | kPanCursorDirectionRight] = 5;
- _panCursors[kPanCursorDraggableHoriz | kPanCursorDirectionLeft] = 6;
- _panCursors[kPanCursorDraggableUp] = 7;
- _panCursors[kPanCursorDraggableDown] = 8;
- _panCursors[kPanCursorDraggableUp | kPanCursorDirectionUp] = 9;
- _panCursors[kPanCursorDraggableDown | kPanCursorDirectionDown] = 10;
- _panCursors[kPanCursorDraggableUp | kPanCursorDraggableDown] = 11;
- _panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp | kPanCursorDraggableDown] = 12;
}
+
+ if (_gameID == GID_SCHIZM) {
+ _namedCursors["curPress"] = 16;
+ _namedCursors["curLookFor"] = 21;
+ _namedCursors["curForward"] = 1;
+ _namedCursors["curBack"] = 13;
+ _namedCursors["curNothing"] = 0;
+ _namedCursors["curPickUp"] = 90;
+ _namedCursors["curDrop"] = 91;
+ }
+
+ _panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp] = 2;
+ _panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableDown] = 3;
+ _panCursors[kPanCursorDraggableHoriz] = 4;
+ _panCursors[kPanCursorDraggableHoriz | kPanCursorDirectionRight] = 5;
+ _panCursors[kPanCursorDraggableHoriz | kPanCursorDirectionLeft] = 6;
+ _panCursors[kPanCursorDraggableUp] = 7;
+ _panCursors[kPanCursorDraggableDown] = 8;
+ _panCursors[kPanCursorDraggableUp | kPanCursorDirectionUp] = 9;
+ _panCursors[kPanCursorDraggableDown | kPanCursorDirectionDown] = 10;
+ _panCursors[kPanCursorDraggableUp | kPanCursorDraggableDown] = 11;
+ _panCursors[kPanCursorDraggableHoriz | kPanCursorDraggableUp | kPanCursorDraggableDown] = 12;
}
void Runtime::setDebugMode(bool debugMode) {
@@ -2838,6 +2850,44 @@ void Runtime::changeMusicTrack(int track) {
}
}
+void Runtime::startScoreSection() {
+ _musicPlayer.reset();
+
+ if (!_musicActive)
+ return;
+
+ Common::HashMap<Common::String, ScoreTrackDef>::const_iterator trackIt = _scoreDefs.find(_scoreTrack);
+ if (trackIt != _scoreDefs.end()) {
+ const ScoreTrackDef::ScoreSectionMap_t §ionMap = trackIt->_value.sections;
+
+ ScoreTrackDef::ScoreSectionMap_t::const_iterator sectionIt = sectionMap.find(_scoreSection);
+ if (sectionIt != sectionMap.end()) {
+ const ScoreSectionDef §ionDef = sectionIt->_value;
+
+ if (sectionDef.musicFileName.empty()) {
+ _scoreSectionEndTime = sectionDef.volumeOrDurationInSeconds * 1000u + g_system->getMillis();
+ } else {
+ Common::String trackFileName = Common::String("Sfx/") + sectionDef.musicFileName;
+
+ Common::File *trackFile = new Common::File();
+ if (trackFile->open(trackFileName)) {
+ if (Audio::SeekableAudioStream *audioStream = Audio::makeVorbisStream(trackFile, DisposeAfterUse::YES)) {
+ _musicPlayer.reset(new AudioPlayer(_mixer, Common::SharedPtr<Audio::AudioStream>(audioStream), Audio::Mixer::kMusicSoundType));
+ _musicPlayer->play(sectionDef.volumeOrDurationInSeconds, 0);
+
+ _scoreSectionEndTime = static_cast<uint32>(audioStream->getLength().msecs()) + g_system->getMillis();
+ } else {
+ warning("Couldn't create Vorbis stream for music file '%s'", trackFileName.c_str());
+ delete trackFile;
+ }
+ } else {
+ warning("Music file '%s' is missing", trackFileName.c_str());
+ }
+ }
+ }
+ }
+}
+
void Runtime::changeAnimation(const AnimationDef &animDef, bool consumeFPSOverride) {
changeAnimation(animDef, animDef.firstFrame, consumeFPSOverride);
}
@@ -5794,7 +5844,13 @@ void Runtime::scriptOpMusicStop(ScriptArg_t arg) {
}
void Runtime::scriptOpMusicPlayScore(ScriptArg_t arg) {
- error("MusicPlayScore opcode not implemented");
+ TAKE_STACK_STR(2);
+
+ _scoreTrack = stackArgs[0];
+ _scoreSection = stackArgs[1];
+ _musicActive = true;
+
+ startScoreSection();
}
OPCODE_STUB(ScoreAlways)
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 552f8b80282..c249a5af6eb 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -729,6 +729,8 @@ private:
void loadFrameData2(Common::SeekableReadStream *stream);
void changeMusicTrack(int musicID);
+ void startScoreSection();
+
void changeAnimation(const AnimationDef &animDef, bool consumeFPSOverride);
void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride);
void changeAnimation(const AnimationDef &animDef, uint initialFrame, bool consumeFPSOverride, const Fraction &defaultFrameRate);
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 158c66e46db..4975a50e854 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -911,6 +911,7 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
if (token.hasPrefix("cur")) {
script.instrs.push_back(ProtoInstruction(ScriptOps::kCursorName, indexString(token)));
+ script.instrs.push_back(ProtoInstruction(ScriptOps::kSetCursor, 0));
return true;
}
Commit: 0188660e82e87e133ed73ff8804ed381dd14855e
https://github.com/scummvm/scummvm/commit/0188660e82e87e133ed73ff8804ed381dd14855e
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:43-04:00
Commit Message:
VCRUISE: Fix up volume calculations for Schizm.
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 53909b516ce..cefe58121b1 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -533,7 +533,7 @@ void RandomAmbientSound::write(Common::WriteStream *stream) const {
stream->writeUint32BE(name.size());
stream->writeString(name);
- stream->writeUint32BE(volume);
+ stream->writeSint32BE(volume);
stream->writeSint32BE(balance);
stream->writeUint32BE(frequency);
@@ -547,7 +547,7 @@ void RandomAmbientSound::read(Common::ReadStream *stream) {
name = stream->readString(0, nameLen);
- volume = stream->readUint32BE();
+ volume = stream->readSint32BE();
balance = stream->readSint32BE();
frequency = stream->readUint32BE();
@@ -647,7 +647,7 @@ void SaveGameSnapshot::Sound::write(Common::WriteStream *stream) const {
stream->writeString(name);
stream->writeUint32BE(id);
- stream->writeUint32BE(volume);
+ stream->writeSint32BE(volume);
stream->writeSint32BE(balance);
stream->writeByte(is3D ? 1 : 0);
@@ -669,7 +669,7 @@ void SaveGameSnapshot::Sound::read(Common::ReadStream *stream) {
name = stream->readString(0, nameLen);
id = stream->readUint32BE();
- volume = stream->readUint32BE();
+ volume = stream->readSint32BE();
balance = stream->readSint32BE();
is3D = (stream->readByte() != 0);
@@ -696,7 +696,7 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeByte(escOn ? 1 : 0);
stream->writeSint32BE(musicTrack);
- stream->writeUint32BE(musicVolume);
+ stream->writeSint32BE(musicVolume);
writeString(stream, scoreTrack);
writeString(stream, scoreSection);
@@ -704,7 +704,7 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeUint32BE(loadedAnimation);
stream->writeUint32BE(animDisplayingFrame);
- stream->writeUint32BE(animVolume);
+ stream->writeSint32BE(animVolume);
pendingStaticAnimParams.write(stream);
pendingSoundParams3D.write(stream);
@@ -774,7 +774,7 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
musicTrack = stream->readSint32BE();
if (saveVersion >= 5)
- musicVolume = stream->readUint32BE();
+ musicVolume = stream->readSint32BE();
else
musicVolume = 100;
@@ -790,7 +790,7 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
animDisplayingFrame = stream->readUint32BE();
if (saveVersion >= 6)
- animVolume = stream->readUint32BE();
+ animVolume = stream->readSint32BE();
else
animVolume = 100;
@@ -880,9 +880,9 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
_havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _escOn(false), _debugMode(false), _fastAnimationMode(false),
- _musicTrack(0), _musicActive(true), _scoreSectionEndTime(0), _musicVolume(100), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
+ _musicTrack(0), _musicActive(true), _scoreSectionEndTime(0), _musicVolume(getDefaultSoundVolume()), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
_panoramaDirectionFlags(0),
- _loadedAnimation(0), _loadedAnimationHasSound(false), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0), _animVolume(100),
+ _loadedAnimation(0), _loadedAnimationHasSound(false), _animPendingDecodeFrame(0), _animDisplayingFrame(0), _animFirstFrame(0), _animLastFrame(0), _animStopFrame(0), _animVolume(getDefaultSoundVolume()),
_animStartTime(0), _animFramesDecoded(0), _animDecoderState(kAnimDecoderStateStopped),
_animPlayWhileIdle(false), _idleLockInteractions(false), _idleIsOnInteraction(false), _idleHaveClickInteraction(false), _idleHaveDragInteraction(false), _idleInteractionID(0), _haveIdleStaticAnimation(false),
_inGameMenuState(kInGameMenuStateInvisible), _inGameMenuActiveElement(0), _inGameMenuButtonActive {false, false, false, false, false},
@@ -916,6 +916,9 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
warning("Couldn't load subtitle font, subtitles will be disabled");
_menuInterface.reset(new RuntimeMenuInterface(this));
+
+ for (int32 i = 0; i < 49; i++)
+ _dbToVolume[i] = decibelsToLinear(i - 49, Audio::Mixer::kMaxChannelVolume, Audio::Mixer::kMaxChannelVolume);
}
Runtime::~Runtime() {
@@ -1099,9 +1102,10 @@ bool Runtime::bootGame(bool newGame) {
if (newGame) {
// TODO: Implement menus and go to b1 in Schizm instead
- if (_gameID == GID_SCHIZM)
+ if (_gameID == GID_SCHIZM) {
changeToScreen(1, 0xb0);
- else
+ _isInGame = true;
+ } else
changeToScreen(1, 0xb1);
}
@@ -1186,6 +1190,8 @@ bool Runtime::bootGame(bool newGame) {
_uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(_languageIndex * 100u + i)), false);
if (_languageIndex != 0 && !_uiGraphics[i])
_uiGraphics[i] = loadGraphic(Common::String::format("Image%03u", static_cast<uint>(i)), false);
+ } else if (_gameID == GID_SCHIZM) {
+ _uiGraphics[i] = loadGraphic(Common::String::format("Data%03u", i), false);
}
}
@@ -2290,7 +2296,7 @@ void Runtime::loadScore() {
if (sscanf(volumeSlice.c_str(), "%i", &volume) == 1) {
ScoreSectionDef §ionDef = trackDef.sections[kv.key];
sectionDef.nextSection = nextSectionSlice;
- sectionDef.volumeOrDurationInSeconds = normalizeSoundVolume(volume);
+ sectionDef.volumeOrDurationInSeconds = volume;
sectionDef.musicFileName = fileNameSlice;
} else
warning("Couldn't parse score section volume");
@@ -2842,7 +2848,7 @@ void Runtime::changeMusicTrack(int track) {
Common::SharedPtr<Audio::AudioStream> loopingStream(Audio::makeLoopingAudioStream(audioStream, 0));
_musicPlayer.reset(new AudioPlayer(_mixer, loopingStream, Audio::Mixer::kMusicSoundType));
- _musicPlayer->play(_musicVolume, 0);
+ _musicPlayer->play(applyVolumeScale(_musicVolume), 0);
}
} else {
warning("Music file '%s' is missing", wavFileName.c_str());
@@ -2873,7 +2879,7 @@ void Runtime::startScoreSection() {
if (trackFile->open(trackFileName)) {
if (Audio::SeekableAudioStream *audioStream = Audio::makeVorbisStream(trackFile, DisposeAfterUse::YES)) {
_musicPlayer.reset(new AudioPlayer(_mixer, Common::SharedPtr<Audio::AudioStream>(audioStream), Audio::Mixer::kMusicSoundType));
- _musicPlayer->play(sectionDef.volumeOrDurationInSeconds, 0);
+ _musicPlayer->play(applyVolumeScale(sectionDef.volumeOrDurationInSeconds), 0);
_scoreSectionEndTime = static_cast<uint32>(audioStream->getLength().msecs()) + g_system->getMillis();
} else {
@@ -2993,10 +2999,7 @@ void Runtime::changeAnimation(const AnimationDef &animDef, uint initialFrame, bo
void Runtime::applyAnimationVolume() {
if (_animDecoder) {
- uint volume = _animVolume * static_cast<uint>(Audio::Mixer::kMaxChannelVolume) / 100u;
- if (volume > Audio::Mixer::kMaxChannelVolume)
- volume = Audio::Mixer::kMaxChannelVolume;
- _animDecoder->setVolume(volume);
+ _animDecoder->setVolume(applyVolumeScale(_animVolume));
}
}
@@ -3006,7 +3009,7 @@ void Runtime::setSound3DParameters(SoundInstance &snd, int32 x, int32 y, const S
snd.params3D = soundParams3D;
}
-void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32 balance, bool is3D, bool isSpeech) {
+void Runtime::triggerSound(bool looping, SoundInstance &snd, int32 volume, int32 balance, bool is3D, bool isSpeech) {
snd.volume = volume;
snd.balance = balance;
snd.is3D = is3D;
@@ -3015,7 +3018,7 @@ void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32
computeEffectiveVolumeAndBalance(snd);
- if (volume == 0 && looping) {
+ if (volume == getSilentSoundVolume() && looping) {
if (snd.cache) {
if (snd.cache->player)
snd.cache->player.reset();
@@ -3071,14 +3074,14 @@ void Runtime::triggerSound(bool looping, SoundInstance &snd, uint volume, int32
snd.endTime = g_system->getMillis(true) + static_cast<uint32>(cache->stream->getLength().msecs()) + 1000u;
}
-void Runtime::triggerSoundRamp(SoundInstance &snd, uint durationMSec, uint newVolume, bool terminateOnCompletion) {
+void Runtime::triggerSoundRamp(SoundInstance &snd, uint durationMSec, int32 newVolume, bool terminateOnCompletion) {
snd.rampStartVolume = snd.volume;
snd.rampEndVolume = newVolume;
snd.rampTerminateOnCompletion = terminateOnCompletion;
snd.rampStartTime = g_system->getMillis();
snd.rampRatePerMSec = 65536;
- if (!snd.isLooping && newVolume == 0)
+ if (!snd.isLooping && newVolume == getSilentSoundVolume())
snd.rampTerminateOnCompletion = true;
if (durationMSec)
@@ -3159,15 +3162,15 @@ void Runtime::updateSounds(uint32 timestamp) {
SoundInstance &snd = *_activeSounds[sndIndex];
if (snd.rampRatePerMSec) {
- uint ramp = snd.rampRatePerMSec * (timestamp - snd.rampStartTime);
- uint newVolume = snd.volume;
+ int32 ramp = snd.rampRatePerMSec * static_cast<int32>(timestamp - snd.rampStartTime);
+ int32 newVolume = snd.volume;
if (ramp >= 65536) {
snd.rampRatePerMSec = 0;
newVolume = snd.rampEndVolume;
if (snd.rampTerminateOnCompletion)
stopSound(snd);
} else {
- uint rampedVolume = (snd.rampStartVolume * (65536u - ramp)) + (snd.rampEndVolume * ramp);
+ int32 rampedVolume = (snd.rampStartVolume * (65536 - ramp)) + (snd.rampEndVolume * ramp);
newVolume = rampedVolume >> 16;
}
@@ -3191,7 +3194,7 @@ void Runtime::updateSounds(uint32 timestamp) {
}
if (snd.isLooping) {
- if (snd.volume == 0) {
+ if (snd.volume == getSilentSoundVolume()) {
if (!snd.isSilencedLoop) {
if (snd.cache) {
snd.cache->player.reset();
@@ -3227,14 +3230,14 @@ void Runtime::updateSounds(uint32 timestamp) {
if (ramp > rampMax)
ramp = rampMax;
- uint32 newVolume = _musicVolumeRampStartVolume;
+ int32 newVolume = _musicVolumeRampStartVolume;
if (negative)
- newVolume -= ramp;
+ newVolume -= static_cast<int32>(ramp);
else
- newVolume += ramp;
+ newVolume += static_cast<int32>(ramp);
if (newVolume != _musicVolume) {
- _musicPlayer->setVolume(static_cast<byte>(newVolume));
+ _musicPlayer->setVolume(applyVolumeScale(newVolume));
_musicVolume = newVolume;
}
@@ -3328,7 +3331,7 @@ void Runtime::update3DSounds() {
}
bool Runtime::computeEffectiveVolumeAndBalance(SoundInstance &snd) {
- uint effectiveVolume = snd.volume;
+ uint effectiveVolume = applyVolumeScale(snd.volume);
int32 effectiveBalance = snd.balance;
double radians = Common::deg2rad<double>(_listenerAngle);
@@ -3436,14 +3439,45 @@ void Runtime::triggerAmbientSounds() {
snd.sceneChangesRemaining--;
}
-uint Runtime::normalizeSoundVolume(StackInt_t arg) const {
- int32 adjustedVol = static_cast<int32>(arg) + 50;
- if (adjustedVol < 0)
+uint Runtime::decibelsToLinear(int db, uint baseVolume, uint maxVol) const {
+ double linearized = floor(pow(1.1220184543019634355910389464779, db) * static_cast<double>(baseVolume) + 0.5);
+
+ if (linearized > static_cast<double>(maxVol))
+ return maxVol;
+
+ return static_cast<uint>(linearized);
+}
+
+int32 Runtime::getSilentSoundVolume() const {
+ if (_gameID == GID_SCHIZM)
+ return -50;
+ else
return 0;
- if (adjustedVol > 100)
+}
+
+int32 Runtime::getDefaultSoundVolume() const {
+ if (_gameID == GID_SCHIZM)
+ return 0;
+ else
return 100;
+}
- return static_cast<uint>(adjustedVol);
+uint Runtime::applyVolumeScale(int32 volume) const {
+ if (_gameID == GID_SCHIZM) {
+ if (volume >= 0)
+ return Audio::Mixer::kMaxChannelVolume;
+ else if (volume < -49)
+ return 0;
+
+ return _dbToVolume[volume + 49];
+ } else {
+ if (volume > 100)
+ return Audio::Mixer::kMaxChannelVolume;
+ else if (volume < 0)
+ return 0;
+
+ return volume * Audio::Mixer::kMaxChannelVolume / 100;
+ }
}
AnimationDef Runtime::stackArgsToAnimDef(const StackInt_t *args) const {
@@ -5025,7 +5059,7 @@ void Runtime::scriptOpSoundL1(ScriptArg_t arg) {
resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
if (cachedSound)
- triggerSound(true, *cachedSound, 100, 0, false, false);
+ triggerSound(true, *cachedSound, getDefaultSoundVolume(), 0, false, false);
}
void Runtime::scriptOpSoundL2(ScriptArg_t arg) {
@@ -5148,7 +5182,7 @@ void Runtime::scriptOpMusicVolRamp(ScriptArg_t arg) {
TAKE_STACK_INT(2);
uint32 duration = static_cast<uint32>(stackArgs[0]) * 100u;
- uint32 newVolume = stackArgs[1];
+ int32 newVolume = stackArgs[1];
_musicVolumeRampRatePerMSec = 0;
@@ -5160,7 +5194,7 @@ void Runtime::scriptOpMusicVolRamp(ScriptArg_t arg) {
if (newVolume != _musicVolume) {
uint32 timestamp = g_system->getMillis();
- _musicVolumeRampRatePerMSec = (static_cast<int32>(newVolume) - static_cast<int32>(_musicVolume)) * 65536 / static_cast<int32>(duration);
+ _musicVolumeRampRatePerMSec = (newVolume - _musicVolume) * 65536 / static_cast<int32>(duration);
_musicVolumeRampStartTime = timestamp;
_musicVolumeRampStartVolume = _musicVolume;
_musicVolumeRampEnd = newVolume;
@@ -5597,7 +5631,7 @@ void Runtime::scriptOpExit(ScriptArg_t arg) {
changeMusicTrack(0);
if (_musicPlayer)
- _musicPlayer->setVolumeAndBalance(100, 0);
+ _musicPlayer->setVolumeAndBalance(applyVolumeScale(getDefaultSoundVolume()), 0);
} else {
error("Don't know what screen to go to on exit");
}
@@ -5877,8 +5911,9 @@ void Runtime::scriptOpVolumeChange(ScriptArg_t arg) {
SoundInstance *cachedSound = resolveSoundByID(static_cast<uint>(stackArgs[0]));
+ // FIXME: Figure out what the duration scale really is
if (cachedSound)
- triggerSoundRamp(*cachedSound, stackArgs[1] * 100, stackArgs[2], false);
+ triggerSoundRamp(*cachedSound, stackArgs[1], stackArgs[2], false);
}
OPCODE_STUB(VolumeDown)
@@ -5886,7 +5921,7 @@ OPCODE_STUB(VolumeDown)
void Runtime::scriptOpAnimVolume(ScriptArg_t arg) {
TAKE_STACK_INT(1);
- _animVolume = normalizeSoundVolume(stackArgs[0]);
+ _animVolume = stackArgs[0];
applyAnimationVolume();
}
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index c249a5af6eb..c22a04ebaa8 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -218,13 +218,13 @@ struct SoundInstance {
uint id;
- uint rampStartVolume;
- uint rampEndVolume;
- uint32 rampRatePerMSec;
+ int32 rampStartVolume;
+ int32 rampEndVolume;
+ int32 rampRatePerMSec;
uint32 rampStartTime;
bool rampTerminateOnCompletion;
- uint volume;
+ int32 volume;
int32 balance;
uint effectiveVolume;
@@ -248,7 +248,7 @@ struct RandomAmbientSound {
Common::String name;
- uint volume;
+ int32 volume;
int32 balance;
uint frequency;
@@ -276,7 +276,7 @@ struct ScoreSectionDef {
Common::String musicFileName; // If empty, this is silent
Common::String nextSection;
- uint32 volumeOrDurationInSeconds;
+ int32 volumeOrDurationInSeconds;
};
struct ScoreTrackDef {
@@ -378,7 +378,7 @@ struct SaveGameSnapshot {
Common::String name;
uint id;
- uint volume;
+ int32 volume;
int32 balance;
bool is3D;
@@ -408,11 +408,11 @@ struct SaveGameSnapshot {
Common::String scoreSection;
bool musicActive;
- uint musicVolume;
+ int32 musicVolume;
uint loadedAnimation;
uint animDisplayingFrame;
- uint animVolume;
+ int32 animVolume;
StaticAnimParams pendingStaticAnimParams;
SoundParams3D pendingSoundParams3D;
@@ -737,15 +737,18 @@ private:
void applyAnimationVolume();
void setSound3DParameters(SoundInstance &sound, int32 x, int32 y, const SoundParams3D &soundParams3D);
- void triggerSound(bool looping, SoundInstance &sound, uint volume, int32 balance, bool is3D, bool isSpeech);
- void triggerSoundRamp(SoundInstance &sound, uint durationMSec, uint newVolume, bool terminateOnCompletion);
+ void triggerSound(bool looping, SoundInstance &sound, int32 volume, int32 balance, bool is3D, bool isSpeech);
+ void triggerSoundRamp(SoundInstance &sound, uint durationMSec, int32 newVolume, bool terminateOnCompletion);
void stopSound(SoundInstance &sound);
void updateSounds(uint32 timestamp);
void updateSubtitles();
void update3DSounds();
bool computeEffectiveVolumeAndBalance(SoundInstance &snd);
void triggerAmbientSounds();
- uint normalizeSoundVolume(StackInt_t arg) const;
+ uint decibelsToLinear(int db, uint baseVolume, uint maxVolume) const;
+ int32 getSilentSoundVolume() const;
+ int32 getDefaultSoundVolume() const;
+ uint applyVolumeScale(int32 volume) const;
void triggerWaveSubtitles(const SoundInstance &sound, const Common::String &id);
void stopSubtitles();
@@ -1051,7 +1054,7 @@ private:
Common::SharedPtr<AudioPlayer> _musicPlayer;
int _musicTrack;
- uint _musicVolume;
+ int32 _musicVolume;
bool _musicActive;
Common::String _scoreTrack;
@@ -1060,9 +1063,9 @@ private:
Common::HashMap<Common::String, ScoreTrackDef> _scoreDefs;
uint32 _musicVolumeRampStartTime;
- uint _musicVolumeRampStartVolume;
+ int32 _musicVolumeRampStartVolume;
int32 _musicVolumeRampRatePerMSec;
- uint _musicVolumeRampEnd;
+ int32 _musicVolumeRampEnd;
SfxData _sfxData;
@@ -1169,6 +1172,8 @@ private:
Common::HashMap<Common::String, SubtitleDef> _waveSubtitles;
Common::Array<SubtitleQueueItem> _subtitleQueue;
bool _isDisplayingSubtitles;
+
+ int32 _dbToVolume[49];
};
} // End of namespace VCruise
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index 509780b3653..41f96302975 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -103,7 +103,7 @@ Common::Error VCruiseEngine::run() {
}
#endif
-
+ syncSoundSettings();
const Graphics::PixelFormat *fmt16_565 = nullptr;
const Graphics::PixelFormat *fmt16_555 = nullptr;
Commit: 3cb2a31280da053872e969f68bd1dc398e63a92b
https://github.com/scummvm/scummvm/commit/3cb2a31280da053872e969f68bd1dc398e63a92b
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:43-04:00
Commit Message:
VCRUISE: Fix some function names being parsed as hex numbers. Add disc type variations.
Changed paths:
engines/vcruise/detection.h
engines/vcruise/detection_tables.h
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
engines/vcruise/script.cpp
engines/vcruise/script.h
engines/vcruise/vcruise.cpp
diff --git a/engines/vcruise/detection.h b/engines/vcruise/detection.h
index 073c0c37d27..6ae8bb0761f 100644
--- a/engines/vcruise/detection.h
+++ b/engines/vcruise/detection.h
@@ -37,6 +37,8 @@ enum VCruiseGameFlag {
VCRUISE_GF_WANT_MP3 = (1 << 0),
VCRUISE_GF_WANT_OGG_VORBIS = (1 << 1),
VCRUISE_GF_NEED_JPEG = (1 << 2),
+ VCRUISE_GF_CD_VARIANT = (1 << 3),
+ VCRUISE_GF_DVD_VARIANT = (1 << 4),
};
struct VCruiseGameDescription {
diff --git a/engines/vcruise/detection_tables.h b/engines/vcruise/detection_tables.h
index ef4756d51c5..a91d38f0ff4 100644
--- a/engines/vcruise/detection_tables.h
+++ b/engines/vcruise/detection_tables.h
@@ -61,7 +61,7 @@ static const VCruiseGameDescription gameDescriptions[] = {
AD_ENTRY1s("Schizm.exe", "296edd26d951c3bdc4d303c4c88b27cd", 364544),
Common::UNK_LANG,
Common::kPlatformWindows,
- ADGF_UNSTABLE | VCRUISE_GF_WANT_OGG_VORBIS | VCRUISE_GF_NEED_JPEG,
+ ADGF_UNSTABLE | VCRUISE_GF_WANT_OGG_VORBIS | VCRUISE_GF_NEED_JPEG | VCRUISE_GF_DVD_VARIANT,
GUIO0()
},
GID_SCHIZM,
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index cefe58121b1..8c825c378f7 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -682,7 +682,7 @@ void SaveGameSnapshot::Sound::read(Common::ReadStream *stream) {
params3D.read(stream);
}
-SaveGameSnapshot::SaveGameSnapshot() : roomNumber(0), screenNumber(0), direction(0), escOn(false), musicTrack(0), musicVolume(100), musicActive(true), loadedAnimation(0),
+SaveGameSnapshot::SaveGameSnapshot() : roomNumber(0), screenNumber(0), direction(0), hero(0), escOn(false), musicTrack(0), musicVolume(100), musicActive(true), loadedAnimation(0),
animDisplayingFrame(0), animVolume(100), listenerX(0), listenerY(0), listenerAngle(0) {
}
@@ -693,6 +693,7 @@ void SaveGameSnapshot::write(Common::WriteStream *stream) const {
stream->writeUint32BE(roomNumber);
stream->writeUint32BE(screenNumber);
stream->writeUint32BE(direction);
+ stream->writeUint32BE(hero);
stream->writeByte(escOn ? 1 : 0);
stream->writeSint32BE(musicTrack);
@@ -770,6 +771,11 @@ LoadGameOutcome SaveGameSnapshot::read(Common::ReadStream *stream) {
screenNumber = stream->readUint32BE();
direction = stream->readUint32BE();
+ if (saveVersion >= 6)
+ hero = stream->readUint32BE();
+ else
+ hero = 0;
+
escOn = (stream->readByte() != 0);
musicTrack = stream->readSint32BE();
@@ -876,8 +882,8 @@ void SaveGameSnapshot::writeString(Common::WriteStream *stream, const Common::St
stream->writeString(str);
}
-Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID)
- : _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
+Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID, bool isCDVariant, bool isDVDVariant)
+ : _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _hero(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
_havePendingPlayAmbientSounds(false), _ambientSoundFinishTime(0), _escOn(false), _debugMode(false), _fastAnimationMode(false),
_musicTrack(0), _musicActive(true), _scoreSectionEndTime(0), _musicVolume(getDefaultSoundVolume()), _musicVolumeRampStartTime(0), _musicVolumeRampStartVolume(0), _musicVolumeRampRatePerMSec(0), _musicVolumeRampEnd(0),
@@ -891,7 +897,8 @@ Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &roo
_panoramaState(kPanoramaStateInactive),
_listenerX(0), _listenerY(0), _listenerAngle(0), _soundCacheIndex(0),
_isInGame(false),
- _subtitleFont(nullptr), _isDisplayingSubtitles(false), _languageIndex(0) {
+ _subtitleFont(nullptr), _isDisplayingSubtitles(false), _languageIndex(0),
+ _isCDVariant(isCDVariant), _isDVDVariant(isDVDVariant) {
for (uint i = 0; i < kNumDirections; i++) {
_haveIdleAnimations[i] = false;
@@ -1091,6 +1098,9 @@ bool Runtime::bootGame(bool newGame) {
if (_gameID == GID_SCHIZM) {
loadScore();
debug(1, "Score loaded OK");
+
+ if (_isCDVariant == _isDVDVariant)
+ error("Detection entry is malformed, Schizm requires either VCRUISE_GF_CD_VARIANT or VCRUISE_GF_DVD_VARIANT");
}
_trayBackgroundGraphic = loadGraphic("Pocket", true);
@@ -1954,7 +1964,8 @@ bool Runtime::runScript() {
DISPATCH_OP(BitAnd);
DISPATCH_OP(BitOr);
DISPATCH_OP(AngleGet);
- DISPATCH_OP(CDGet);
+ DISPATCH_OP(IsCDVersion);
+ DISPATCH_OP(IsDVDVersion);
DISPATCH_OP(Disc);
DISPATCH_OP(HidePanel);
DISPATCH_OP(RotateUpdate);
@@ -4327,6 +4338,7 @@ void Runtime::recordSaveGameSnapshot() {
snapshot->roomNumber = _roomNumber;
snapshot->screenNumber = _screenNumber;
snapshot->direction = _direction;
+ snapshot->hero = _hero;
snapshot->pendingStaticAnimParams = _pendingStaticAnimParams;
@@ -4411,6 +4423,7 @@ void Runtime::restoreSaveGameSnapshot() {
_roomNumber = _saveGame->roomNumber;
_screenNumber = _saveGame->screenNumber;
_direction = _saveGame->direction;
+ _hero = _saveGame->hero;
_pendingStaticAnimParams = _saveGame->pendingStaticAnimParams;
@@ -5679,18 +5692,36 @@ void Runtime::scriptOpCmpEq(ScriptArg_t arg) {
_scriptStack.push_back(StackValue((stackArgs[0] == stackArgs[1]) ? 1 : 0));
}
+void Runtime::scriptOpCmpNE(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+
+ _scriptStack.push_back(StackValue((stackArgs[0] != stackArgs[1]) ? 1 : 0));
+}
+
void Runtime::scriptOpCmpLt(ScriptArg_t arg) {
TAKE_STACK_INT(2);
_scriptStack.push_back(StackValue((stackArgs[0] < stackArgs[1]) ? 1 : 0));
}
+void Runtime::scriptOpCmpLE(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+
+ _scriptStack.push_back(StackValue((stackArgs[0] <= stackArgs[1]) ? 1 : 0));
+}
+
void Runtime::scriptOpCmpGt(ScriptArg_t arg) {
TAKE_STACK_INT(2);
_scriptStack.push_back(StackValue((stackArgs[0] > stackArgs[1]) ? 1 : 0));
}
+void Runtime::scriptOpCmpGE(ScriptArg_t arg) {
+ TAKE_STACK_INT(2);
+
+ _scriptStack.push_back(StackValue((stackArgs[0] >= stackArgs[1]) ? 1 : 0));
+}
+
void Runtime::scriptOpBitLoad(ScriptArg_t arg) {
TAKE_STACK_INT(2);
@@ -5961,24 +5992,42 @@ void Runtime::scriptOpString(ScriptArg_t arg) {
_scriptStack.push_back(StackValue(_scriptSet->strings[arg]));
}
-OPCODE_STUB(CmpNE)
-OPCODE_STUB(CmpLE)
-OPCODE_STUB(CmpGE)
OPCODE_STUB(Speech)
OPCODE_STUB(SpeechEx)
OPCODE_STUB(SpeechTest)
OPCODE_STUB(Say)
-OPCODE_STUB(RandomInclusive)
+
+void Runtime::scriptOpRandomInclusive(ScriptArg_t arg) {
+ TAKE_STACK_INT(1);
+
+ if (stackArgs[0] == 0)
+ _scriptStack.push_back(StackValue(0));
+ else
+ _scriptStack.push_back(StackValue(_rng->getRandomNumber(stackArgs[0])));
+}
+
OPCODE_STUB(HeroOut)
OPCODE_STUB(HeroGetPos)
OPCODE_STUB(HeroSetPos)
-OPCODE_STUB(HeroGet)
+
+void Runtime::scriptOpHeroGet(ScriptArg_t arg) {
+ _scriptStack.push_back(StackValue(_hero));
+}
+
OPCODE_STUB(Garbage)
OPCODE_STUB(GetRoom)
OPCODE_STUB(BitAnd)
OPCODE_STUB(BitOr)
OPCODE_STUB(AngleGet)
-OPCODE_STUB(CDGet)
+
+void Runtime::scriptOpIsDVDVersion(ScriptArg_t arg) {
+ _scriptStack.push_back(StackValue(_isDVDVariant ? 1 : 0));
+}
+
+void Runtime::scriptOpIsCDVersion(ScriptArg_t arg) {
+ _scriptStack.push_back(StackValue(_isCDVariant ? 1 : 0));
+}
+
OPCODE_STUB(Disc)
OPCODE_STUB(HidePanel)
OPCODE_STUB(RotateUpdate)
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index c22a04ebaa8..a8c70cdc8b5 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -400,6 +400,7 @@ struct SaveGameSnapshot {
uint roomNumber;
uint screenNumber;
uint direction;
+ uint hero;
bool escOn;
int musicTrack;
@@ -476,7 +477,7 @@ class Runtime {
public:
friend class RuntimeMenuInterface;
- Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID);
+ Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID, bool isCDVariant, bool isDVDVariant);
virtual ~Runtime();
void initSections(const Common::Rect &gameRect, const Common::Rect &menuRect, const Common::Rect &trayRect, const Common::Rect &fullscreenMenuRect, const Graphics::PixelFormat &pixFmt);
@@ -960,7 +961,8 @@ private:
void scriptOpBitAnd(ScriptArg_t arg);
void scriptOpBitOr(ScriptArg_t arg);
void scriptOpAngleGet(ScriptArg_t arg);
- void scriptOpCDGet(ScriptArg_t arg);
+ void scriptOpIsDVDVersion(ScriptArg_t arg);
+ void scriptOpIsCDVersion(ScriptArg_t arg);
void scriptOpDisc(ScriptArg_t arg);
void scriptOpHidePanel(ScriptArg_t arg);
void scriptOpRotateUpdate(ScriptArg_t arg);
@@ -997,7 +999,7 @@ private:
uint _roomNumber; // Room number can be changed independently of the loaded room, the screen doesn't change until a command changes it
uint _screenNumber;
uint _direction;
- //uint _highPrecisionDirection;
+ uint _hero;
GyroState _gyros;
@@ -1163,6 +1165,8 @@ private:
const Graphics::Font *_subtitleFont;
Common::SharedPtr<Graphics::Font> _subtitleFontKeepalive;
uint _languageIndex;
+ bool _isDVDVariant;
+ bool _isCDVariant;
typedef Common::HashMap<uint, SubtitleDef> FrameToSubtitleMap_t;
typedef Common::HashMap<uint, FrameToSubtitleMap_t> AnimSubtitleMap_t;
diff --git a/engines/vcruise/script.cpp b/engines/vcruise/script.cpp
index 4975a50e854..1340754b53a 100644
--- a/engines/vcruise/script.cpp
+++ b/engines/vcruise/script.cpp
@@ -253,9 +253,17 @@ bool ScriptCompiler::parseDecNumber(const Common::String &token, uint start, uin
if (start == token.size())
return false;
- uint num = 0;
- if (!sscanf(token.c_str() + start, "%u", &num))
- return false;
+ // We don't use sscanf because sscanf accepts partial results and we want to reject this if any character is mismatched
+ uint32 num = 0;
+ for (uint i = start; i < token.size(); i++) {
+ num *= 10u;
+
+ char c = token[i];
+ if (c >= '0' && c <= '9')
+ num += static_cast<uint32>(c - '0');
+ else
+ return false;
+ }
outNumber = num;
return true;
@@ -265,9 +273,21 @@ bool ScriptCompiler::parseHexNumber(const Common::String &token, uint start, uin
if (start == token.size())
return false;
- uint num = 0;
- if (!sscanf(token.c_str() + start, "%x", &num))
- return false;
+ // We don't use sscanf because sscanf accepts partial results and we want to reject this if any character is mismatched
+ uint32 num = 0;
+ for (uint i = start; i < token.size(); i++) {
+ num *= 16u;
+
+ char c = token[i];
+ if (c >= '0' && c <= '9')
+ num += static_cast<uint32>(c - '0');
+ else if (c >= 'a' && c <= 'f')
+ num += static_cast<uint32>(c - 'a' + 0xa);
+ else if (c >= 'A' && c <= 'F')
+ num += static_cast<uint32>(c - 'a' + 0xa);
+ else
+ return false;
+ }
outNumber = num;
return true;
@@ -278,7 +298,9 @@ bool ScriptCompiler::parseBinNumber(const Common::String &token, uint start, uin
return false;
uint num = 0;
- for (char c : token) {
+ for (uint i = start; i < token.size(); i++) {
+ char c = token[i];
+
num <<= 1;
if (c == '1')
num |= 1;
@@ -705,7 +727,8 @@ static ScriptNamedInstruction g_schizmNamedInstructions[] = {
{"hi@", ProtoOp::kProtoOpScript, ScriptOps::kHiGet},
{"angle@", ProtoOp::kProtoOpScript, ScriptOps::kAngleGet},
{"angleG@", ProtoOp::kProtoOpScript, ScriptOps::kAngleGGet},
- {"cd@", ProtoOp::kProtoOpScript, ScriptOps::kCDGet},
+ {"cd@", ProtoOp::kProtoOpScript, ScriptOps::kIsCDVersion},
+ {"dvd@", ProtoOp::kProtoOpScript, ScriptOps::kIsDVDVersion},
{"disc", ProtoOp::kProtoOpScript, ScriptOps::kDisc},
{"save0", ProtoOp::kProtoOpNoop, ScriptOps::kSave0},
{"hidePanel", ProtoOp::kProtoOpNoop, ScriptOps::kHidePanel},
@@ -852,12 +875,6 @@ bool ScriptCompiler::compileInstructionToken(ProtoScript &script, const Common::
return true;
}
- if (token == "dvd@") {
- // Always pass disc checks
- script.instrs.push_back(ProtoInstruction(kProtoOpScript, ScriptOps::kNumber, 1));
- return true;
- }
-
for (const ScriptNamedInstruction &namedInstr : g_schizmNamedInstructions) {
if (token == namedInstr.str) {
script.instrs.push_back(ProtoInstruction(namedInstr.protoOp, namedInstr.op, 0));
diff --git a/engines/vcruise/script.h b/engines/vcruise/script.h
index 94b996ebdcd..00562195dff 100644
--- a/engines/vcruise/script.h
+++ b/engines/vcruise/script.h
@@ -201,7 +201,8 @@ enum ScriptOp {
kBitAnd,
kBitOr,
kAngleGet,
- kCDGet,
+ kIsCDVersion,
+ kIsDVDVersion,
kDisc,
kHidePanel,
kRotateUpdate,
diff --git a/engines/vcruise/vcruise.cpp b/engines/vcruise/vcruise.cpp
index 41f96302975..cbf78a2ee40 100644
--- a/engines/vcruise/vcruise.cpp
+++ b/engines/vcruise/vcruise.cpp
@@ -105,6 +105,9 @@ Common::Error VCruiseEngine::run() {
syncSoundSettings();
+ bool isCDVariant = ((_gameDescription->desc.flags & VCRUISE_GF_CD_VARIANT) != 0);
+ bool isDVDVariant = ((_gameDescription->desc.flags & VCRUISE_GF_DVD_VARIANT) != 0);
+
const Graphics::PixelFormat *fmt16_565 = nullptr;
const Graphics::PixelFormat *fmt16_555 = nullptr;
const Graphics::PixelFormat *fmt32 = nullptr;
@@ -164,7 +167,7 @@ Common::Error VCruiseEngine::run() {
_system->fillScreen(0);
- _runtime.reset(new Runtime(_system, _mixer, _rootFSNode, _gameDescription->gameID));
+ _runtime.reset(new Runtime(_system, _mixer, _rootFSNode, _gameDescription->gameID, isCDVariant, isDVDVariant));
_runtime->initSections(_videoRect, _menuBarRect, _trayRect, Common::Rect(640, 480), _system->getScreenFormat());
const char *exeName = _gameDescription->desc.filesDescriptions[0].fileName;
Commit: 298011eb430953e9990f0b3eaed48153c731e036
https://github.com/scummvm/scummvm/commit/298011eb430953e9990f0b3eaed48153c731e036
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:43-04:00
Commit Message:
VCRUISE: Support Schizm's presets section in playlist file
Changed paths:
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 8c825c378f7..9d0f267f213 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -351,14 +351,24 @@ void SfxData::load(Common::SeekableReadStream &stream, Audio::Mixer *mixer) {
const Common::INIFile::Section *samplesSection = nullptr;
const Common::INIFile::Section *playlistsSection = nullptr;
+ const Common::INIFile::Section *presetsSection = nullptr;
- Common::INIFile::SectionList sections = iniFile.getSections(); // Why does this require a copy??
+ Common::INIFile::SectionList sections = iniFile.getSections(); // Why does this require a copy? Sigh.
for (const Common::INIFile::Section §ion : sections) {
if (section.name == "samples")
samplesSection = §ion;
else if (section.name == "playlists")
playlistsSection = §ion;
+ else if (section.name == "presets")
+ presetsSection = §ion;
+ }
+
+ Common::HashMap<Common::String, Common::String> presets;
+
+ if (presetsSection) {
+ for (const Common::INIFile::KeyValue &keyValue : presetsSection->keys)
+ presets.setVal(keyValue.key, keyValue.value);
}
if (samplesSection) {
@@ -470,11 +480,19 @@ void SfxData::load(Common::SeekableReadStream &stream, Audio::Mixer *mixer) {
continue;
}
+ if (!presets.empty()) {
+ for (Common::String &tokenRef : tokens) {
+ Common::HashMap<Common::String, Common::String>::const_iterator presetIt = presets.find(tokenRef);
+ if (presetIt != presets.end())
+ tokenRef = presetIt->_value;
+ }
+ }
+
unsigned int frameNum = 0;
int balance = 0;
- unsigned int volume = 0;
+ int volume = 0;
- if (!sscanf(tokens[0].c_str(), "%u", &frameNum) || !sscanf(tokens[2].c_str(), "%i", &balance) || !sscanf(tokens[3].c_str(), "%u", &volume)) {
+ if (!sscanf(tokens[0].c_str(), "%u", &frameNum) || !sscanf(tokens[2].c_str(), "%i", &balance) || !sscanf(tokens[3].c_str(), "%i", &volume)) {
warning("Malformed playlist entry: %s", key.c_str());
continue;
}
@@ -1719,11 +1737,11 @@ void Runtime::continuePlayingAnimation(bool loop, bool useStopFrame, bool &outAn
VCruise::AudioPlayer &audioPlayer = *playlistEntry.sample->audioPlayer;
if (playlistEntry.isUpdate) {
- audioPlayer.setVolumeAndBalance(playlistEntry.volume, playlistEntry.balance);
+ audioPlayer.setVolumeAndBalance(applyVolumeScale(playlistEntry.volume), playlistEntry.balance);
} else {
audioPlayer.stop();
playlistEntry.sample->audioStream->seek(0);
- audioPlayer.play(playlistEntry.volume, playlistEntry.balance);
+ audioPlayer.play(applyVolumeScale(playlistEntry.volume), playlistEntry.balance);
}
// No break, it's possible for there to be multiple sounds in the same frame
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index a8c70cdc8b5..54a7765229f 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -165,7 +165,7 @@ struct SfxPlaylistEntry {
uint frame;
Common::SharedPtr<SfxSound> sample;
int8 balance;
- uint8 volume;
+ int32 volume;
bool isUpdate;
};
Commit: e7f10fe8f83faae4108ae1b4f4ac173f8a8111ea
https://github.com/scummvm/scummvm/commit/e7f10fe8f83faae4108ae1b4f4ac173f8a8111ea
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:44-04:00
Commit Message:
VCRUISE: Fix Schizm DVD identification
Changed paths:
engines/vcruise/detection_tables.h
diff --git a/engines/vcruise/detection_tables.h b/engines/vcruise/detection_tables.h
index a91d38f0ff4..b949c0fd94d 100644
--- a/engines/vcruise/detection_tables.h
+++ b/engines/vcruise/detection_tables.h
@@ -54,10 +54,10 @@ static const VCruiseGameDescription gameDescriptions[] = {
},
GID_REAH,
},
- { // Schizm, 5 CD Version
+ { // Schizm, DVD/digital Version
{
"schizm",
- "CD",
+ "DVD",
AD_ENTRY1s("Schizm.exe", "296edd26d951c3bdc4d303c4c88b27cd", 364544),
Common::UNK_LANG,
Common::kPlatformWindows,
Commit: d0aee6d817a05eea165214794e361c96b087d33e
https://github.com/scummvm/scummvm/commit/d0aee6d817a05eea165214794e361c96b087d33e
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:44-04:00
Commit Message:
VCRUISE: Add menu text labels
Changed paths:
engines/vcruise/menu.cpp
engines/vcruise/menu.h
engines/vcruise/runtime.cpp
engines/vcruise/runtime.h
diff --git a/engines/vcruise/menu.cpp b/engines/vcruise/menu.cpp
index accaf524e2e..a029e692df8 100644
--- a/engines/vcruise/menu.cpp
+++ b/engines/vcruise/menu.cpp
@@ -21,6 +21,7 @@
#include "common/config-manager.h"
+#include "graphics/font.h"
#include "graphics/managed_surface.h"
#include "audio/mixer.h"
@@ -30,9 +31,9 @@
namespace VCruise {
-class ReahMenuPage : public MenuPage {
+class ReahSchizmMenuPage : public MenuPage {
public:
- ReahMenuPage();
+ explicit ReahSchizmMenuPage(bool isSchizm);
bool run() override;
void start() override;
@@ -76,12 +77,15 @@ protected:
struct Button {
Button();
Button(Graphics::Surface *graphic, const Common::Rect &graphicRect, const Common::Rect &screenRect, const Common::Point &stateOffset, bool enabled);
+ Button(Graphics::Surface *graphic, const Common::Rect &graphicRect, const Common::Rect &screenRect, const Common::Point &stateOffset, bool enabled, const Common::String (&states)[4]);
Graphics::Surface *_graphic;
Common::Rect _graphicRect;
Common::Rect _screenRect;
Common::Point _stateOffset;
bool _enabled;
+
+ Common::String _buttonStates[4];
};
struct Slider {
@@ -113,11 +117,13 @@ protected:
Common::Point _sliderDragStart;
int _sliderDragValue;
+
+ bool _isSchizm;
};
-class ReahMenuBarPage : public ReahMenuPage {
+class ReahMenuBarPage : public ReahSchizmMenuPage {
public:
- explicit ReahMenuBarPage(uint page);
+ ReahMenuBarPage(uint page, bool isSchizm);
void start() override final;
@@ -140,14 +146,14 @@ protected:
class ReahHelpMenuPage : public ReahMenuBarPage {
public:
- ReahHelpMenuPage();
+ explicit ReahHelpMenuPage(bool isSchizm);
void addPageContents() override;
};
class ReahSoundMenuPage : public ReahMenuBarPage {
public:
- ReahSoundMenuPage();
+ explicit ReahSoundMenuPage(bool isSchizm);
void addPageContents() override;
void onSettingsChanged() override;
@@ -183,7 +189,7 @@ private:
class ReahQuitMenuPage : public ReahMenuBarPage {
public:
- ReahQuitMenuPage();
+ explicit ReahQuitMenuPage(bool isSchizm);
void addPageContents() override;
void onButtonClicked(uint button, bool &outChangedState) override;
@@ -197,13 +203,15 @@ private:
class ReahPauseMenuPage : public ReahMenuBarPage {
public:
- ReahPauseMenuPage();
+ explicit ReahPauseMenuPage(bool isSchizm);
void addPageContents() override;
};
-class ReahMainMenuPage : public ReahMenuPage {
+class ReahSchizmMainMenuPage : public ReahSchizmMenuPage {
public:
+ explicit ReahSchizmMainMenuPage(bool isSchizm);
+
void start() override;
protected:
@@ -220,10 +228,10 @@ private:
};
};
-ReahMenuPage::ReahMenuPage() : _interactionIndex(0), _interactionState(kInteractionStateNotInteracting), _sliderDragValue(0) {
+ReahSchizmMenuPage::ReahSchizmMenuPage(bool isSchizm) : _interactionIndex(0), _interactionState(kInteractionStateNotInteracting), _sliderDragValue(0), _isSchizm(isSchizm) {
}
-bool ReahMenuPage::run() {
+bool ReahSchizmMenuPage::run() {
bool changedState = false;
OSEvent evt;
@@ -252,7 +260,7 @@ bool ReahMenuPage::run() {
return false;
}
-void ReahMenuPage::start() {
+void ReahSchizmMenuPage::start() {
for (uint buttonIndex = 0; buttonIndex < _buttons.size(); buttonIndex++)
drawButtonInState(buttonIndex, _buttons[buttonIndex]._enabled ? kButtonStateIdle : kButtonStateDisabled);
@@ -266,20 +274,20 @@ void ReahMenuPage::start() {
handleMouseMove(mousePoint);
}
-void ReahMenuPage::onButtonClicked(uint button, bool &outChangedState) {
+void ReahSchizmMenuPage::onButtonClicked(uint button, bool &outChangedState) {
outChangedState = false;
}
-void ReahMenuPage::onCheckboxClicked(uint button, bool &outChangedState) {
+void ReahSchizmMenuPage::onCheckboxClicked(uint button, bool &outChangedState) {
}
-void ReahMenuPage::onSliderMoved(uint slider) {
+void ReahSchizmMenuPage::onSliderMoved(uint slider) {
}
-void ReahMenuPage::eraseSlider(uint sliderIndex) const {
+void ReahSchizmMenuPage::eraseSlider(uint sliderIndex) const {
}
-void ReahMenuPage::handleMouseMove(const Common::Point &pt) {
+void ReahSchizmMenuPage::handleMouseMove(const Common::Point &pt) {
switch (_interactionState) {
case kInteractionStateNotInteracting:
for (uint buttonIndex = 0; buttonIndex < _buttons.size(); buttonIndex++) {
@@ -411,7 +419,7 @@ void ReahMenuPage::handleMouseMove(const Common::Point &pt) {
}
}
-void ReahMenuPage::handleMouseDown(const Common::Point &pt, bool &outChangedState) {
+void ReahSchizmMenuPage::handleMouseDown(const Common::Point &pt, bool &outChangedState) {
switch (_interactionState) {
case kInteractionStateNotInteracting:
case kInteractionStateClickingOnButton:
@@ -442,7 +450,7 @@ void ReahMenuPage::handleMouseDown(const Common::Point &pt, bool &outChangedStat
}
}
-void ReahMenuPage::handleMouseUp(const Common::Point &pt, bool &outChangedState) {
+void ReahSchizmMenuPage::handleMouseUp(const Common::Point &pt, bool &outChangedState) {
switch (_interactionState) {
case kInteractionStateNotInteracting:
case kInteractionStateOverButton:
@@ -487,7 +495,7 @@ void ReahMenuPage::handleMouseUp(const Common::Point &pt, bool &outChangedState)
}
}
-ReahMenuBarPage::ReahMenuBarPage(uint page) : _page(page) {
+ReahMenuBarPage::ReahMenuBarPage(uint page, bool isSchizm) : ReahSchizmMenuPage(isSchizm), _page(page) {
}
void ReahMenuBarPage::start() {
@@ -519,13 +527,13 @@ void ReahMenuBarPage::start() {
addPageContents();
- ReahMenuPage::start();
+ ReahSchizmMenuPage::start();
}
void ReahMenuBarPage::onButtonClicked(uint button, bool &outChangedState) {
switch (button) {
case kMenuBarButtonHelp:
- _menuInterface->changeMenu(new ReahHelpMenuPage());
+ _menuInterface->changeMenu(new ReahHelpMenuPage(_isSchizm));
outChangedState = true;
break;
case kMenuBarButtonLoad:
@@ -535,11 +543,11 @@ void ReahMenuBarPage::onButtonClicked(uint button, bool &outChangedState) {
g_engine->saveGameDialog();
break;
case kMenuBarButtonSound:
- _menuInterface->changeMenu(new ReahSoundMenuPage());
+ _menuInterface->changeMenu(new ReahSoundMenuPage(_isSchizm));
outChangedState = true;
break;
case kMenuBarButtonQuit:
- _menuInterface->changeMenu(new ReahQuitMenuPage());
+ _menuInterface->changeMenu(new ReahQuitMenuPage(_isSchizm));
outChangedState = true;
break;
@@ -547,7 +555,7 @@ void ReahMenuBarPage::onButtonClicked(uint button, bool &outChangedState) {
if (_menuInterface->canSave())
outChangedState = _menuInterface->reloadFromCheckpoint();
else {
- _menuInterface->changeMenu(new ReahMainMenuPage());
+ _menuInterface->changeMenu(new ReahSchizmMainMenuPage(_isSchizm));
outChangedState = true;
}
break;
@@ -556,15 +564,15 @@ void ReahMenuBarPage::onButtonClicked(uint button, bool &outChangedState) {
}
}
-void ReahMenuPage::drawButtonInState(uint buttonIndex, ButtonState state) const {
+void ReahSchizmMenuPage::drawButtonInState(uint buttonIndex, ButtonState state) const {
drawButtonFromListInState(_buttons, buttonIndex, state);
}
-void ReahMenuPage::drawCheckboxInState(uint buttonIndex, CheckboxState state) const {
+void ReahSchizmMenuPage::drawCheckboxInState(uint buttonIndex, CheckboxState state) const {
drawButtonFromListInState(_checkboxes, buttonIndex, state);
}
-void ReahMenuPage::drawSlider(uint sliderIndex) const {
+void ReahSchizmMenuPage::drawSlider(uint sliderIndex) const {
const Slider &slider = _sliders[sliderIndex];
Common::Point screenPoint(slider._baseRect.left + slider._value, slider._baseRect.top);
@@ -573,32 +581,65 @@ void ReahMenuPage::drawSlider(uint sliderIndex) const {
_menuInterface->commitRect(Common::Rect(screenPoint.x, screenPoint.y, screenPoint.x + slider._baseRect.width(), screenPoint.y + slider._baseRect.height()));
}
-void ReahMenuPage::drawButtonFromListInState(const Common::Array<Button> &buttonList, uint buttonIndex, int state) const {
+void ReahSchizmMenuPage::drawButtonFromListInState(const Common::Array<Button> &buttonList, uint buttonIndex, int state) const {
const Button &button = buttonList[buttonIndex];
Common::Rect graphicRect = button._graphicRect;
graphicRect.translate(button._stateOffset.x * state, button._stateOffset.y * state);
- _menuInterface->getMenuSurface()->blitFrom(*button._graphic, graphicRect, button._screenRect);
+ Graphics::ManagedSurface *menuSurf = _menuInterface->getMenuSurface();
+ menuSurf->blitFrom(*button._graphic, graphicRect, button._screenRect);
+
+ const Graphics::Font *font = nullptr;
+ const Common::String *labelTextUTF8 = nullptr;
+ uint32 textColor;
+ uint32 shadowColor;
+ _menuInterface->getLabelDef(button._buttonStates[state], font, labelTextUTF8, textColor, shadowColor);
+
+ if (font && labelTextUTF8) {
+ Common::U32String text = labelTextUTF8->decode(Common::kUtf8);
+
+ int strWidth = font->getStringWidth(text);
+ int strHeight = font->getFontHeight();
+
+ Common::Point textPos(button._screenRect.left + (button._screenRect.width() - strWidth) / 2, button._screenRect.top + (button._screenRect.height() - strHeight) / 2);
+
+ if (shadowColor != 0) {
+ Common::Point shadowPos = textPos + Common::Point(1, 1);
+
+ uint32 realShadowColor = menuSurf->format.RGBToColor((shadowColor >> 16) & 0xff, (shadowColor >> 8) & 0xff, shadowColor & 0xff);
+ font->drawString(menuSurf, text, shadowPos.x, shadowPos.y, strWidth, realShadowColor);
+ }
+
+ uint32 realTextColor = menuSurf->format.RGBToColor((textColor >> 16) & 0xff, (textColor >> 8) & 0xff, textColor & 0xff);
+ font->drawString(menuSurf, text, textPos.x, textPos.y, strWidth, realTextColor);
+ }
+
_menuInterface->commitRect(Common::Rect(button._screenRect.left, button._screenRect.top, button._screenRect.left + graphicRect.width(), button._screenRect.top + graphicRect.height()));
}
-ReahMenuPage::Button::Button() : _graphic(nullptr), _enabled(true) {
+ReahSchizmMenuPage::Button::Button() : _graphic(nullptr), _enabled(true) {
+}
+
+ReahSchizmMenuPage::Button::Button(Graphics::Surface *graphic, const Common::Rect &graphicRect, const Common::Rect &screenRect, const Common::Point &stateOffset, bool enabled)
+ : _graphic(graphic), _graphicRect(graphicRect), _screenRect(screenRect), _stateOffset(stateOffset), _enabled(enabled) {
}
-ReahMenuPage::Button::Button(Graphics::Surface *graphic, const Common::Rect &graphicRect, const Common::Rect &screenRect, const Common::Point &stateOffset, bool enabled)
+ReahSchizmMenuPage::Button::Button(Graphics::Surface *graphic, const Common::Rect &graphicRect, const Common::Rect &screenRect, const Common::Point &stateOffset, bool enabled, const Common::String (&states)[4])
: _graphic(graphic), _graphicRect(graphicRect), _screenRect(screenRect), _stateOffset(stateOffset), _enabled(enabled) {
+ for (int i = 0; i < 4; i++)
+ this->_buttonStates[i] = states[i];
}
-ReahMenuPage::Slider::Slider() : _graphic(nullptr), _value(0), _maxValue(1) {
+ReahSchizmMenuPage::Slider::Slider() : _graphic(nullptr), _value(0), _maxValue(1) {
}
-ReahMenuPage::Slider::Slider(Graphics::Surface *graphic, const Common::Rect &baseRect, int value, int maxValue)
+ReahSchizmMenuPage::Slider::Slider(Graphics::Surface *graphic, const Common::Rect &baseRect, int value, int maxValue)
: _graphic(graphic), _baseRect(baseRect), _value(value), _maxValue(maxValue) {
assert(_value >= 0 && _value <= maxValue);
}
-ReahHelpMenuPage::ReahHelpMenuPage() : ReahMenuBarPage(kMenuBarButtonHelp) {
+ReahHelpMenuPage::ReahHelpMenuPage(bool isSchizm) : ReahMenuBarPage(kMenuBarButtonHelp, isSchizm) {
}
void ReahHelpMenuPage::addPageContents() {
@@ -609,7 +650,7 @@ void ReahHelpMenuPage::addPageContents() {
}
}
-ReahSoundMenuPage::ReahSoundMenuPage() : ReahMenuBarPage(kMenuBarButtonSound), _soundChecked(false), _musicChecked(false) {
+ReahSoundMenuPage::ReahSoundMenuPage(bool isSchizm) : ReahMenuBarPage(kMenuBarButtonSound, isSchizm), _soundChecked(false), _musicChecked(false) {
}
void ReahSoundMenuPage::addPageContents() {
@@ -770,7 +811,7 @@ void ReahSoundMenuPage::applyMusicVolume() const {
g_engine->syncSoundSettings();
}
-ReahQuitMenuPage::ReahQuitMenuPage() : ReahMenuBarPage(kMenuBarButtonQuit) {
+ReahQuitMenuPage::ReahQuitMenuPage(bool isSchizm) : ReahMenuBarPage(kMenuBarButtonQuit, isSchizm) {
}
void ReahQuitMenuPage::addPageContents() {
@@ -840,7 +881,7 @@ void ReahQuitMenuPage::onButtonClicked(uint button, bool &outChangedState) {
onButtonClicked(kMenuBarButtonReturn, outChangedState);
}
-ReahPauseMenuPage::ReahPauseMenuPage() : ReahMenuBarPage(static_cast<uint>(-1)) {
+ReahPauseMenuPage::ReahPauseMenuPage(bool isSchizm) : ReahMenuBarPage(static_cast<uint>(-1), isSchizm) {
}
void ReahPauseMenuPage::addPageContents() {
@@ -858,8 +899,10 @@ void ReahPauseMenuPage::addPageContents() {
_menuInterface->commitRect(Common::Rect(0, 44, 640, 392));
}
+ReahSchizmMainMenuPage::ReahSchizmMainMenuPage(bool isSchizm) : ReahSchizmMenuPage(isSchizm) {
+}
-void ReahMainMenuPage::start() {
+void ReahSchizmMainMenuPage::start() {
Graphics::Surface *bgGraphic = _menuInterface->getUIGraphic(0);
Graphics::ManagedSurface *menuSurf = _menuInterface->getMenuSurface();
@@ -872,7 +915,39 @@ void ReahMainMenuPage::start() {
Graphics::Surface *buttonGraphic = _menuInterface->getUIGraphic(1);
- const int buttonTopYs[6] = {66, 119, 171, 224, 277, 330};
+ Common::Point buttonSize;
+
+ Common::Point buttonCoords[6];
+ Common::String buttonStates[6][4];
+
+ if (_isSchizm) {
+ buttonCoords[0] = Common::Point(240, 52);
+ buttonCoords[1] = Common::Point(181, 123);
+ buttonCoords[2] = Common::Point(307, 157);
+ buttonCoords[3] = Common::Point(179, 232);
+ buttonCoords[4] = Common::Point(298, 296);
+ buttonCoords[5] = Common::Point(373, 395);
+
+ buttonSize = Common::Point(150, 40);
+
+ for (int i = 0; i < 6; i++) {
+ int index = i;
+ if (i == 5)
+ index = 6;
+
+ buttonStates[i][0] = Common::String::format("szData001_%02i", static_cast<int>(index + 1));
+ buttonStates[i][1] = Common::String::format("szData001_%02i", static_cast<int>(index + 8));
+ buttonStates[i][2] = Common::String::format("szData001_%02i", static_cast<int>(index + 15));
+ buttonStates[i][3] = Common::String::format("szData001_%02i", static_cast<int>(index + 22));
+ }
+
+ } else {
+ const int buttonTopYs[6] = {66, 119, 171, 224, 277, 330};
+ for (int i = 0; i < 6; i++)
+ buttonCoords[i] = Common::Point(492, buttonTopYs[i]);
+
+ buttonSize = Common::Point(112, 44);
+ }
for (int i = 0; i < 6; i++) {
bool isEnabled = true;
@@ -881,13 +956,22 @@ void ReahMainMenuPage::start() {
else if (i == kButtonLoad)
isEnabled = _menuInterface->hasAnySave();
- _buttons.push_back(Button(buttonGraphic, Common::Rect(0, i * 44, 112, i * 44 + 44), Common::Rect(492, buttonTopYs[i], 492 + 112, buttonTopYs[i] + 44), Common::Point(112, 0), isEnabled));
+ int coordScale = i;
+
+ // Skip uninstall button
+ if (_isSchizm && i == 5)
+ coordScale = 6;
+
+ Common::Rect graphicRect(0, coordScale * buttonSize.y, buttonSize.x, (coordScale + 1) * buttonSize.y);
+ Common::Rect screenRect(buttonCoords[i].x, buttonCoords[i].y, buttonCoords[i].x + buttonSize.x, buttonCoords[i].y + buttonSize.y);
+
+ _buttons.push_back(Button(buttonGraphic, graphicRect, screenRect, Common::Point(buttonSize.x, 0), isEnabled, buttonStates[i]));
}
- ReahMenuPage::start();
+ ReahSchizmMenuPage::start();
}
-void ReahMainMenuPage::onButtonClicked(uint button, bool &outChangedState) {
+void ReahSchizmMainMenuPage::onButtonClicked(uint button, bool &outChangedState) {
switch (button) {
case kButtonContinue: {
Common::Error loadError = g_engine->loadGameState(g_engine->getAutosaveSlot());
@@ -904,7 +988,7 @@ void ReahMainMenuPage::onButtonClicked(uint button, bool &outChangedState) {
break;
case kButtonSound:
- _menuInterface->changeMenu(new ReahSoundMenuPage());
+ _menuInterface->changeMenu(new ReahSoundMenuPage(_isSchizm));
outChangedState = true;
break;
@@ -914,7 +998,7 @@ void ReahMainMenuPage::onButtonClicked(uint button, bool &outChangedState) {
break;
case kButtonQuit:
- _menuInterface->changeMenu(new ReahQuitMenuPage());
+ _menuInterface->changeMenu(new ReahQuitMenuPage(_isSchizm));
outChangedState = true;
break;
}
@@ -943,24 +1027,24 @@ bool MenuPage::run() {
return false;
}
-MenuPage *createMenuReahMain() {
- return new ReahMainMenuPage();
+MenuPage *createMenuMain(bool isSchizm) {
+ return new ReahSchizmMainMenuPage(isSchizm);
}
-MenuPage *createMenuReahQuit() {
- return new ReahQuitMenuPage();
+MenuPage *createMenuQuit(bool isSchizm) {
+ return new ReahQuitMenuPage(isSchizm);
}
-MenuPage *createMenuReahHelp() {
- return new ReahHelpMenuPage();
+MenuPage *createMenuHelp(bool isSchizm) {
+ return new ReahHelpMenuPage(isSchizm);
}
-MenuPage *createMenuReahSound() {
- return new ReahSoundMenuPage();
+MenuPage *createMenuSound(bool isSchizm) {
+ return new ReahSoundMenuPage(isSchizm);
}
-MenuPage *createMenuReahPause() {
- return new ReahPauseMenuPage();
+MenuPage *createMenuPause(bool isSchizm) {
+ return new ReahPauseMenuPage(isSchizm);
}
} // End of namespace VCruise
diff --git a/engines/vcruise/menu.h b/engines/vcruise/menu.h
index 7e88f08d12d..44132582dc6 100644
--- a/engines/vcruise/menu.h
+++ b/engines/vcruise/menu.h
@@ -27,6 +27,7 @@
namespace Graphics {
+class Font;
struct Surface;
class ManagedSurface;
@@ -61,6 +62,8 @@ public:
virtual void quitGame() const = 0;
virtual bool canSave() const = 0;
virtual bool reloadFromCheckpoint() const = 0;
+
+ virtual void getLabelDef(const Common::String &labelID, const Graphics::Font *&outFont, const Common::String *&outTextUTF8, uint32 &outColor, uint32 &outShadowColor) const = 0;
};
class MenuPage {
@@ -78,11 +81,11 @@ protected:
const MenuInterface *_menuInterface;
};
-MenuPage *createMenuReahMain();
-MenuPage *createMenuReahHelp();
-MenuPage *createMenuReahSound();
-MenuPage *createMenuReahQuit();
-MenuPage *createMenuReahPause();
+MenuPage *createMenuMain(bool isSchizm);
+MenuPage *createMenuHelp(bool isSchizm);
+MenuPage *createMenuSound(bool isSchizm);
+MenuPage *createMenuQuit(bool isSchizm);
+MenuPage *createMenuPause(bool isSchizm);
} // End of namespace VCruise
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 9d0f267f213..ba1404d182c 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -77,6 +77,8 @@ public:
bool canSave() const override;
bool reloadFromCheckpoint() const override;
+ void getLabelDef(const Common::String &labelID, const Graphics::Font *&outFont, const Common::String *&outTextUTF8, uint32 &outColor, uint32 &outShadowColor) const override;
+
private:
Runtime *_runtime;
};
@@ -117,10 +119,14 @@ Common::Point RuntimeMenuInterface::getMouseCoordinate() const {
void RuntimeMenuInterface::restartGame() const {
Common::SharedPtr<SaveGameSnapshot> snapshot(new SaveGameSnapshot());
-
+
snapshot->roomNumber = 1;
snapshot->screenNumber = 0xb0;
- snapshot->loadedAnimation = 1;
+
+ if (_runtime->_gameID == GID_SCHIZM)
+ snapshot->loadedAnimation = 200;
+ else
+ snapshot->loadedAnimation = 1;
_runtime->_saveGame = snapshot;
_runtime->restoreSaveGameSnapshot();
@@ -159,6 +165,11 @@ bool RuntimeMenuInterface::reloadFromCheckpoint() const {
return true;
}
+void RuntimeMenuInterface::getLabelDef(const Common::String &labelID, const Graphics::Font *&outFont, const Common::String *&outTextUTF8, uint32 &outColor, uint32 &outShadowColor) const {
+ return _runtime->getLabelDef(labelID, outFont, outTextUTF8, outColor, outShadowColor);
+}
+
+
AnimationDef::AnimationDef() : animNum(0), firstFrame(0), lastFrame(0) {
}
@@ -900,6 +911,10 @@ void SaveGameSnapshot::writeString(Common::WriteStream *stream, const Common::St
stream->writeString(str);
}
+
+FontCacheItem::FontCacheItem() : font(nullptr), size(0) {
+}
+
Runtime::Runtime(OSystem *system, Audio::Mixer *mixer, const Common::FSNode &rootFSNode, VCruiseGameID gameID, bool isCDVariant, bool isDVDVariant)
: _system(system), _mixer(mixer), _roomNumber(1), _screenNumber(0), _direction(0), _hero(0), _haveHorizPanAnimations(false), _loadedRoomNumber(0), _activeScreenNumber(0),
_gameState(kGameStateBoot), _gameID(gameID), _havePendingScreenChange(false), _forceScreenChange(false), _havePendingReturnToIdleState(false), _havePendingCompletionCheck(false),
@@ -1129,11 +1144,9 @@ bool Runtime::bootGame(bool newGame) {
_gameState = kGameStateIdle;
if (newGame) {
- // TODO: Implement menus and go to b1 in Schizm instead
- if (_gameID == GID_SCHIZM) {
- changeToScreen(1, 0xb0);
- _isInGame = true;
- } else
+ if (_gameID == GID_SCHIZM)
+ changeToScreen(1, 0xb1);
+ else
changeToScreen(1, 0xb1);
}
@@ -1226,6 +1239,31 @@ bool Runtime::bootGame(bool newGame) {
return true;
}
+void Runtime::getLabelDef(const Common::String &labelID, const Graphics::Font *&outFont, const Common::String *&outTextUTF8, uint32 &outColor, uint32 &outShadowColor) {
+ outFont = nullptr;
+ outTextUTF8 = nullptr;
+ outColor = 0;
+ outShadowColor = 0;
+
+ Common::HashMap<Common::String, UILabelDef>::const_iterator labelDefIt = _locUILabels.find(labelID);
+ if (labelDefIt != _locUILabels.end()) {
+ const UILabelDef &labelDef = labelDefIt->_value;
+
+ Common::HashMap<Common::String, Common::String>::const_iterator lineIt = _locStrings.find(labelDef.lineID);
+
+ if (lineIt != _locStrings.end()) {
+ Common::HashMap<Common::String, TextStyleDef>::const_iterator styleIt = _locTextStyles.find(labelDef.styleDefID);
+
+ if (styleIt != _locTextStyles.end()) {
+ outFont = resolveFont(styleIt->_value.fontName, styleIt->_value.size);
+ outColor = styleIt->_value.colorRGB;
+ outShadowColor = styleIt->_value.shadowColorRGB;
+ outTextUTF8 = &lineIt->_value;
+ }
+ }
+ }
+}
+
bool Runtime::runIdle() {
if (_havePendingScreenChange) {
_havePendingScreenChange = false;
@@ -1351,7 +1389,7 @@ bool Runtime::runIdle() {
switch (osEvent.keymappedEvent) {
case kKeymappedEventHelp:
if (_gameID == GID_REAH)
- changeToMenuPage(createMenuReahHelp());
+ changeToMenuPage(createMenuHelp(_gameID == GID_SCHIZM));
else
error("Don't have a help menu for this game");
return true;
@@ -1365,13 +1403,13 @@ bool Runtime::runIdle() {
break;
case kKeymappedEventPause:
if (_gameID == GID_REAH)
- changeToMenuPage(createMenuReahPause());
+ changeToMenuPage(createMenuPause(_gameID == GID_SCHIZM));
else
error("Don't have a pause menu for this game");
return true;
case kKeymappedEventQuit:
if (_gameID == GID_REAH)
- changeToMenuPage(createMenuReahQuit());
+ changeToMenuPage(createMenuQuit(_gameID == GID_SCHIZM));
else
error("Don't have a quit menu for this game");
return true;
@@ -1462,6 +1500,7 @@ bool Runtime::runWaitForAnimation() {
_animDecoder->pauseVideo(true);
_animDecoderState = kAnimDecoderStatePaused;
}
+ _scriptEnv.esc = true;
_gameState = kGameStateScript;
return true;
}
@@ -2029,8 +2068,8 @@ void Runtime::terminateScript() {
if (_scriptEnv.exitToMenu && _gameState == kGameStateIdle) {
changeToCursor(_cursors[kCursorArrow]);
- if (_gameID == GID_REAH)
- changeToMenuPage(createMenuReahMain());
+ if (_gameID == GID_REAH || _gameID == GID_SCHIZM)
+ changeToMenuPage(createMenuMain(_gameID == GID_SCHIZM));
else
error("Missing main menu behavior for this game");
}
@@ -3223,7 +3262,7 @@ void Runtime::updateSounds(uint32 timestamp) {
}
if (snd.isLooping) {
- if (snd.volume == getSilentSoundVolume()) {
+ if (snd.volume <= getSilentSoundVolume()) {
if (!snd.isSilencedLoop) {
if (snd.cache) {
snd.cache->player.reset();
@@ -4069,20 +4108,74 @@ void Runtime::loadSubtitles(Common::CodePage codePage) {
frameMap = &_animSubtitles[animID];
}
- if (frameMap != nullptr || isWave) {
- for (const Common::INIFile::KeyValue &kv : section.getKeys()) {
- if (kv.value.size() < 23)
- continue;
+ bool isTextData = (section.name == "szTextData");
+ bool isFontData = (section.name == "szFontData");
+ bool isStringData = (section.name.hasPrefix("szData"));
+
+ for (const Common::INIFile::KeyValue &kv : section.getKeys()) {
+ // Tokenize the line
+ Common::Array<Common::String> tokens;
+
+ {
+ const Common::String &valueStr = kv.value;
+
+ uint currentTokenStart = 0;
+ uint nextCharPos = 0;
+ bool isQuotedString = false;
+
+ while (nextCharPos < valueStr.size()) {
+ char c = valueStr[nextCharPos];
+ nextCharPos++;
+
+ if (isQuotedString) {
+ if (c == '\"')
+ isQuotedString = false;
+ continue;
+ }
+
+ if (c == '\"') {
+ isQuotedString = true;
+ continue;
+ }
+
+ if (c == ',') {
+ while (valueStr[currentTokenStart] == ' ')
+ currentTokenStart++;
+
+ tokens.push_back(valueStr.substr(currentTokenStart, (nextCharPos - currentTokenStart) - 1u));
+
+ currentTokenStart = nextCharPos;
+ }
+
+ if (c == ';') {
+ nextCharPos--;
+ break;
+ }
+ }
+
+ while (currentTokenStart < nextCharPos && valueStr[currentTokenStart] == ' ')
+ currentTokenStart++;
+
+ while (nextCharPos > currentTokenStart && valueStr[nextCharPos - 1] == ' ')
+ nextCharPos--;
+
+ if (currentTokenStart < nextCharPos)
+ tokens.push_back(valueStr.substr(currentTokenStart, (nextCharPos - currentTokenStart)));
+ }
- if (kv.value[21] != '\"' || kv.value[kv.value.size() - 1] != '\"')
+ if (frameMap != nullptr || isWave) {
+ if (tokens.size() != 4)
continue;
- Common::String locLineParamSlice = kv.value.substr(0, 21);
+ const Common::String &textToken = tokens[3];
+
+ if (textToken[0] != '\"' || textToken[textToken.size() - 1] != '\"')
+ continue;
uint colorCode = 0;
uint param1 = 0;
uint param2 = 0;
- if (sscanf(locLineParamSlice.c_str(), "0x%x, 0x%x, %u, ", &colorCode, ¶m1, ¶m2) == 3) {
+ if (sscanf(tokens[0].c_str(), "0x%x", &colorCode) && sscanf(tokens[1].c_str(), "0x%x", ¶m1) && sscanf(tokens[2].c_str(), "%u", ¶m2)) {
SubtitleDef *subDef = nullptr;
if (isWave)
@@ -4099,9 +4192,55 @@ void Runtime::loadSubtitles(Common::CodePage codePage) {
subDef->color[2] = (colorCode & 0xff);
subDef->unknownValue1 = param1;
subDef->durationInDeciseconds = param2;
- subDef->str = kv.value.substr(22, kv.value.size() - 23).decode(codePage).encode(Common::kUtf8);
+ subDef->str = textToken.substr(1, textToken.size() - 2).decode(codePage).encode(Common::kUtf8);
}
}
+ } else if (isTextData) {
+ if (tokens.size() != 1)
+ continue;
+
+ const Common::String &textToken = tokens[0];
+
+ if (textToken[0] != '\"' || textToken[textToken.size() - 1] != '\"')
+ continue;
+
+ _locStrings[kv.key] = textToken.substr(1, textToken.size() - 2);
+ } else if (isFontData) {
+ if (tokens.size() != 9)
+ continue;
+
+ const Common::String &fontToken = tokens[0];
+
+ if (fontToken[0] != '\"' || fontToken[fontToken.size() - 1] != '\"')
+ continue;
+
+ TextStyleDef tsDef;
+ tsDef.fontName = fontToken.substr(1, fontToken.size() - 2);
+
+ if (sscanf(tokens[1].c_str(), "%u", &tsDef.size) &&
+ sscanf(tokens[2].c_str(), "%u", &tsDef.unknown1) &&
+ sscanf(tokens[3].c_str(), "%u", &tsDef.unknown2) &&
+ sscanf(tokens[4].c_str(), "%u", &tsDef.unknown3) &&
+ sscanf(tokens[5].c_str(), "0x%x", &tsDef.colorRGB) &&
+ sscanf(tokens[6].c_str(), "0x%x", &tsDef.shadowColorRGB) &&
+ sscanf(tokens[7].c_str(), "%u", &tsDef.unknown4) &&
+ sscanf(tokens[8].c_str(), "%u", &tsDef.unknown5)) {
+ _locTextStyles[kv.key] = tsDef;
+ }
+ } else if (isStringData) {
+ if (tokens.size() != 6)
+ continue;
+
+ UILabelDef labelDef;
+ labelDef.lineID = tokens[0];
+ labelDef.styleDefID = tokens[1];
+
+ if (sscanf(tokens[2].c_str(), "%u", &labelDef.unknown1) &&
+ sscanf(tokens[3].c_str(), "%u", &labelDef.unknown2) &&
+ sscanf(tokens[4].c_str(), "%u", &labelDef.unknown3) &&
+ sscanf(tokens[5].c_str(), "%u", &labelDef.unknown4)) {
+ _locUILabels[kv.key] = labelDef;
+ }
}
}
}
@@ -4218,7 +4357,7 @@ void Runtime::dischargeInGameMenuMouseUp() {
// Handle click event
switch (_inGameMenuActiveElement) {
case 0:
- changeToMenuPage(createMenuReahHelp());
+ changeToMenuPage(createMenuHelp(_gameID == GID_SCHIZM));
break;
case 1:
g_engine->saveGameDialog();
@@ -4227,10 +4366,10 @@ void Runtime::dischargeInGameMenuMouseUp() {
g_engine->loadGameDialog();
break;
case 3:
- changeToMenuPage(createMenuReahSound());
+ changeToMenuPage(createMenuSound(_gameID == GID_SCHIZM));
break;
case 4:
- changeToMenuPage(createMenuReahQuit());
+ changeToMenuPage(createMenuQuit(_gameID == GID_SCHIZM));
break;
default:
break;
@@ -4281,6 +4420,32 @@ void Runtime::drawInGameMenuButton(uint element) {
commitSectionToScreen(_menuSection, buttonDestRect);
}
+const Graphics::Font *Runtime::resolveFont(const Common::String &textStyle, uint size) {
+ for (const Common::SharedPtr<FontCacheItem> &item : _fontCache) {
+ if (item->fname == textStyle && item->size == size)
+ return item->font;
+ }
+
+ Common::SharedPtr<FontCacheItem> fcItem(new FontCacheItem());
+ fcItem->fname = textStyle;
+ fcItem->size = size;
+
+
+#ifdef USE_FREETYPE2
+ const char *fontFile = "NotoSans-Regular.ttf";
+
+ fcItem->keepAlive.reset(Graphics::loadTTFFontFromArchive(fontFile, size, Graphics::kTTFSizeModeCharacter, 0, Graphics::kTTFRenderModeLight));
+ fcItem->font = fcItem->keepAlive.get();
+#endif
+
+ if (!fcItem->font)
+ fcItem->font = FontMan.getFontByUsage(Graphics::FontManager::kLocalizedFont);
+
+ _fontCache.push_back(fcItem);
+
+ return fcItem->font;
+}
+
void Runtime::onLButtonDown(int16 x, int16 y) {
onMouseMove(x, y);
@@ -5940,7 +6105,14 @@ OPCODE_STUB(ScoreAlways)
OPCODE_STUB(ScoreNormal)
void Runtime::scriptOpSndPlay(ScriptArg_t arg) {
- scriptOpSoundL1(arg);
+ TAKE_STACK_STR_NAMED(1, sndNameArgs);
+
+ StackInt_t soundID = 0;
+ SoundInstance *cachedSound = nullptr;
+ resolveSoundByName(sndNameArgs[0], true, soundID, cachedSound);
+
+ if (cachedSound)
+ triggerSound(true, *cachedSound, getSilentSoundVolume(), 0, false, false);
}
OPCODE_STUB(SndPlayEx)
@@ -5960,9 +6132,8 @@ void Runtime::scriptOpVolumeChange(ScriptArg_t arg) {
SoundInstance *cachedSound = resolveSoundByID(static_cast<uint>(stackArgs[0]));
- // FIXME: Figure out what the duration scale really is
if (cachedSound)
- triggerSoundRamp(*cachedSound, stackArgs[1], stackArgs[2], false);
+ triggerSoundRamp(*cachedSound, stackArgs[1] * 100, stackArgs[2], false);
}
OPCODE_STUB(VolumeDown)
diff --git a/engines/vcruise/runtime.h b/engines/vcruise/runtime.h
index 54a7765229f..ce9a7787244 100644
--- a/engines/vcruise/runtime.h
+++ b/engines/vcruise/runtime.h
@@ -473,6 +473,37 @@ struct OSEvent {
uint32 timestamp;
};
+struct TextStyleDef {
+ Common::String fontName;
+ uint size;
+ uint unknown1;
+ uint unknown2;
+ uint unknown3; // Seems to always be 0 for English, other values for other languages
+ uint colorRGB;
+ uint shadowColorRGB;
+ uint unknown4;
+ uint unknown5; // Possibly drop shadow offset
+};
+
+struct UILabelDef {
+ Common::String lineID;
+ Common::String styleDefID;
+ uint unknown1;
+ uint unknown2;
+ uint unknown3;
+ uint unknown4;
+};
+
+struct FontCacheItem {
+ FontCacheItem();
+
+ Common::String fname;
+ uint size;
+
+ const Graphics::Font *font;
+ Common::SharedPtr<Graphics::Font> keepAlive;
+};
+
class Runtime {
public:
friend class RuntimeMenuInterface;
@@ -506,6 +537,8 @@ public:
bool bootGame(bool newGame);
+ void getLabelDef(const Common::String &labelID, const Graphics::Font *&outFont, const Common::String *&outTextUTF8, uint32 &outColor, uint32 &outShadowColor);
+
private:
enum IndexParseType {
kIndexParseTypeNone,
@@ -795,6 +828,8 @@ private:
void dischargeInGameMenuMouseUp();
void drawInGameMenuButton(uint element);
+ const Graphics::Font *resolveFont(const Common::String &textStyle, uint size);
+
// Script things
void scriptOpNumber(ScriptArg_t arg);
void scriptOpRotate(ScriptArg_t arg);
@@ -1177,6 +1212,12 @@ private:
Common::Array<SubtitleQueueItem> _subtitleQueue;
bool _isDisplayingSubtitles;
+ Common::HashMap<Common::String, Common::String> _locStrings;
+ Common::HashMap<Common::String, TextStyleDef> _locTextStyles;
+ Common::HashMap<Common::String, UILabelDef> _locUILabels;
+
+ Common::Array<Common::SharedPtr<FontCacheItem> > _fontCache;
+
int32 _dbToVolume[49];
};
Commit: 9923c44a78f4d2b7ce0b6536734379fa7072a794
https://github.com/scummvm/scummvm/commit/9923c44a78f4d2b7ce0b6536734379fa7072a794
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:44-04:00
Commit Message:
VCRUISE: Fix options
Changed paths:
engines/vcruise/detection.cpp
engines/vcruise/detection.h
engines/vcruise/metaengine.cpp
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/detection.cpp b/engines/vcruise/detection.cpp
index 096e9010f2e..454560ba017 100644
--- a/engines/vcruise/detection.cpp
+++ b/engines/vcruise/detection.cpp
@@ -39,7 +39,7 @@ static const PlainGameDescriptor vCruiseGames[] = {
class VCruiseMetaEngineDetection : public AdvancedMetaEngineDetection {
public:
VCruiseMetaEngineDetection() : AdvancedMetaEngineDetection(VCruise::gameDescriptions, sizeof(VCruise::VCruiseGameDescription), vCruiseGames) {
- _guiOptions = GUIO1(GAMEOPTION_LAUNCH_DEBUG);
+ _guiOptions = GUIO3(GAMEOPTION_LAUNCH_DEBUG, GAMEOPTION_FAST_ANIMATIONS, GAMEOPTION_SKIP_MENU);
_maxScanDepth = 1;
_directoryGlobs = nullptr;
_flags = kADFlagCanPlayUnknownVariants;
diff --git a/engines/vcruise/detection.h b/engines/vcruise/detection.h
index 6ae8bb0761f..510455e9cfa 100644
--- a/engines/vcruise/detection.h
+++ b/engines/vcruise/detection.h
@@ -50,6 +50,7 @@ struct VCruiseGameDescription {
#define GAMEOPTION_LAUNCH_DEBUG GUIO_GAMEOPTIONS1
#define GAMEOPTION_FAST_ANIMATIONS GUIO_GAMEOPTIONS2
+#define GAMEOPTION_SKIP_MENU GUIO_GAMEOPTIONS3
} // End of namespace VCruise
diff --git a/engines/vcruise/metaengine.cpp b/engines/vcruise/metaengine.cpp
index 57b5d41a1d7..031638c690c 100644
--- a/engines/vcruise/metaengine.cpp
+++ b/engines/vcruise/metaengine.cpp
@@ -48,7 +48,7 @@ static const ADExtraGuiOptionsMap optionsList[] = {
}
},
{
- GAMEOPTION_LAUNCH_DEBUG,
+ GAMEOPTION_FAST_ANIMATIONS,
{
_s("Faster animations"),
_s("Speeds up animations."),
@@ -58,6 +58,17 @@ static const ADExtraGuiOptionsMap optionsList[] = {
0
}
},
+ {
+ GAMEOPTION_SKIP_MENU,
+ {
+ _s("Skip main menu"),
+ _s("Starts a new game upon launching instead of going to the main menu."),
+ "vcruise_skip_menu",
+ false,
+ 0,
+ 0
+ }
+ },
AD_EXTRA_GUI_OPTIONS_TERMINATOR
};
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index ba1404d182c..694403962b4 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -4432,7 +4432,7 @@ const Graphics::Font *Runtime::resolveFont(const Common::String &textStyle, uint
#ifdef USE_FREETYPE2
- const char *fontFile = "NotoSans-Regular.ttf";
+ const char *fontFile = "NotoSans-Bold.ttf";
fcItem->keepAlive.reset(Graphics::loadTTFFontFromArchive(fontFile, size, Graphics::kTTFSizeModeCharacter, 0, Graphics::kTTFRenderModeLight));
fcItem->font = fcItem->keepAlive.get();
Commit: 62712ca02b34478a6b14a34cfe451a95cf23b74c
https://github.com/scummvm/scummvm/commit/62712ca02b34478a6b14a34cfe451a95cf23b74c
Author: elasota (ejlasota at gmail.com)
Date: 2023-05-03T23:40:44-04:00
Commit Message:
VCRUISE: Add skip main menu option functionality.
Changed paths:
engines/vcruise/runtime.cpp
diff --git a/engines/vcruise/runtime.cpp b/engines/vcruise/runtime.cpp
index 694403962b4..fb660888cd1 100644
--- a/engines/vcruise/runtime.cpp
+++ b/engines/vcruise/runtime.cpp
@@ -1144,10 +1144,12 @@ bool Runtime::bootGame(bool newGame) {
_gameState = kGameStateIdle;
if (newGame) {
- if (_gameID == GID_SCHIZM)
- changeToScreen(1, 0xb1);
- else
+ if (ConfMan.hasKey("vcruise_skip_menu") && ConfMan.getBool("vcruise_skip_menu")) {
+ _isInGame = true;
+ changeToScreen(1, 0xb0);
+ } else {
changeToScreen(1, 0xb1);
+ }
}
Common::Language lang = Common::parseLanguage(ConfMan.get("language"));
More information about the Scummvm-git-logs
mailing list