diff options
98 files changed, 5863 insertions, 1394 deletions
diff --git a/common/debug.cpp b/common/debug.cpp index ce34a00445..c61fc63dea 100644 --- a/common/debug.cpp +++ b/common/debug.cpp @@ -120,6 +120,18 @@ bool DebugManager::isDebugChannelEnabled(uint32 channel) { } // End of namespace Common +bool debugLevelSet(int level) { + return level <= gDebugLevel; +} + +bool debugChannelSet(int level, uint32 debugChannels) { + if (gDebugLevel != 11) + if (level > gDebugLevel || !(DebugMan.isDebugChannelEnabled(debugChannels))) + return false; + + return true; +} + #ifndef DISABLE_TEXT_CONSOLE diff --git a/common/debug.h b/common/debug.h index 00bad81fa6..883a0bf29d 100644 --- a/common/debug.h +++ b/common/debug.h @@ -31,11 +31,10 @@ inline void debug(const char *s, ...) {} inline void debug(int level, const char *s, ...) {} inline void debugN(const char *s, ...) {} inline void debugN(int level, const char *s, ...) {} -inline void debugC(int level, uint32 engineChannel, const char *s, ...) {} -inline void debugC(uint32 engineChannel, const char *s, ...) {} -inline void debugCN(int level, uint32 engineChannel, const char *s, ...) {} -inline void debugCN(uint32 engineChannel, const char *s, ...) {} - +inline void debugC(int level, uint32 debugChannels, const char *s, ...) {} +inline void debugC(uint32 debugChannels, const char *s, ...) {} +inline void debugCN(int level, uint32 debugChannels, const char *s, ...) {} +inline void debugCN(uint32 debugChannels, const char *s, ...) {} #else @@ -111,6 +110,18 @@ void debugCN(uint32 debugChannels, const char *s, ...) GCC_PRINTF(2, 3); #endif /** + * Returns true if the debug level is set to the specified level + */ +bool debugLevelSet(int level); + +/** + * Returns true if the debug level and channel are active + * + * @see enableDebugChannel + */ +bool debugChannelSet(int level, uint32 debugChannels); + +/** * The debug level. Initially set to -1, indicating that no debug output * should be shown. Positive values usually imply an increasing number of * debug output shall be generated, the higher the value, the more verbose the diff --git a/engines/director/lingo/lingo-builtins.cpp b/engines/director/lingo/lingo-builtins.cpp index 798ab0824b..6738d4b707 100644 --- a/engines/director/lingo/lingo-builtins.cpp +++ b/engines/director/lingo/lingo-builtins.cpp @@ -545,7 +545,7 @@ void Lingo::b_alert(int nargs) { d.toString(); - warning("STUB: b_alert"); + warning("STUB: b_alert(%s)", d.u.s->c_str()); delete d.u.s; } diff --git a/engines/director/lingo/lingo-code.cpp b/engines/director/lingo/lingo-code.cpp index 150e242e88..8712f0990c 100644 --- a/engines/director/lingo/lingo-code.cpp +++ b/engines/director/lingo/lingo-code.cpp @@ -65,7 +65,7 @@ static struct FuncDescr { { Lingo::c_stringpush, "c_stringpush", "s" }, { Lingo::c_varpush, "c_varpush", "s" }, { Lingo::c_assign, "c_assign", "" }, - { Lingo::c_eval, "c_eval", "" }, + { Lingo::c_eval, "c_eval", "s" }, { Lingo::c_theentitypush,"c_theentitypush","ii" }, // entity, field { Lingo::c_theentityassign,"c_theentityassign","ii" }, { Lingo::c_swap, "c_swap", "" }, @@ -92,6 +92,7 @@ static struct FuncDescr { { Lingo::c_repeatwhilecode,"c_repeatwhilecode","oo" }, { Lingo::c_repeatwithcode,"c_repeatwithcode","ooooos" }, { Lingo::c_ifcode, "c_ifcode", "oooi" }, + { Lingo::c_whencode, "c_whencode", "os" }, { Lingo::c_goto, "c_goto", "" }, { Lingo::c_gotoloop, "c_gotoloop", "" }, { Lingo::c_gotonext, "c_gotonext", "" }, @@ -170,6 +171,9 @@ void Lingo::c_printtop(void) { case SYMBOL: warning("%s", d.type2str(true)); break; + case OBJECT: + warning("#%s", d.u.s->c_str()); + break; default: warning("--unknown--"); } @@ -263,8 +267,11 @@ void Lingo::c_assign() { delete d2.u.arr; } else if (d2.type == SYMBOL) { d1.u.sym->u.i = d2.u.i; + } else if (d2.type == OBJECT) { + d1.u.sym->u.s = d2.u.s; } else { warning("c_assign: unhandled type: %s", d2.type2str()); + d1.u.sym->u.s = d2.u.s; } d1.u.sym->type = d2.type; @@ -695,24 +702,38 @@ void Lingo::c_ifcode() { int end = READ_UINT32(&(*g_lingo->_currentScript)[savepc + 2]); int skipEnd = READ_UINT32(&(*g_lingo->_currentScript)[savepc + 3]); - debug(8, "executing cond (have to %s end)", skipEnd ? "skip" : "execute"); + debugC(8, kDebugLingoExec, "executing cond (have to %s end)", skipEnd ? "skip" : "execute"); g_lingo->execute(savepc + 4); /* condition */ d = g_lingo->pop(); if (d.toInt()) { - debug(8, "executing then"); + debugC(8, kDebugLingoExec, "executing then"); g_lingo->execute(then); } else if (elsep) { /* else part? */ - debug(8, "executing else"); + debugC(8, kDebugLingoExec, "executing else"); g_lingo->execute(elsep); } if (!g_lingo->_returning && !skipEnd) { g_lingo->_pc = end; /* next stmt */ - debug(8, "executing end"); - } else - debug(8, "Skipped end"); + debugC(8, kDebugLingoExec, "executing end"); + } else { + debugC(8, kDebugLingoExec, "Skipped end"); + } +} + +void Lingo::c_whencode() { + Datum d; + int start = g_lingo->_pc; + int end = READ_UINT32(&(*g_lingo->_currentScript)[start]); + Common::String eventname((char *)&(*g_lingo->_currentScript)[start]); + + start += g_lingo->calcStringAlignment(eventname.c_str()); + + warning("STUB: c_whencode([%5d][%5d], %s)", start, end, eventname.c_str()); + + g_lingo->_pc = end; } //************************ @@ -789,7 +810,7 @@ void Lingo::call(Common::String &name, int nargs) { if (!g_lingo->_handlers.contains(name)) { Symbol *s = g_lingo->lookupVar(name.c_str(), false); if (s && s->type == OBJECT) { - debug(3, "Dereferencing object reference: %s to %s", name.c_str(), s->u.s->c_str()); + debugC(3, kDebugLingoExec, "Dereferencing object reference: %s to %s", name.c_str(), s->u.s->c_str()); name = *s->u.s; } } @@ -843,6 +864,7 @@ void Lingo::call(Common::String &name, int nargs) { g_lingo->push(d); } + debugC(5, kDebugLingoExec, "Pushing frame %d", g_lingo->_callstack.size() + 1); CFrame *fp = new CFrame; fp->sp = sym; @@ -868,7 +890,10 @@ void Lingo::c_procret() { return; } + debugC(5, kDebugLingoExec, "Popping frame %d", g_lingo->_callstack.size() + 1); + CFrame *fp = g_lingo->_callstack.back(); + g_lingo->_callstack.pop_back(); g_lingo->_currentScript = fp->retscript; g_lingo->_pc = fp->retpc; diff --git a/engines/director/lingo/lingo-codegen.cpp b/engines/director/lingo/lingo-codegen.cpp index 9390a8b7f9..32ddea47ac 100644 --- a/engines/director/lingo/lingo-codegen.cpp +++ b/engines/director/lingo/lingo-codegen.cpp @@ -57,17 +57,21 @@ void Lingo::execute(int pc) { debugC(1, kDebugLingoExec, "[%3d]: %s", _pc, instr.c_str()); + Common::String stack("Stack: "); + for (uint i = 0; i < _stack.size(); i++) { - debugCN(5, kDebugLingoExec, "%d ", _stack[i].u.i); + Datum d = _stack[i]; + d.toString(); + stack += Common::String::format("<%s> ", d.u.s->c_str()); } - debugCN(5, kDebugLingoExec, "%s", ""); + debugC(5, kDebugLingoExec, "%s", stack.c_str()); _pc++; (*((*_currentScript)[_pc - 1]))(); } } -Common::String Lingo::decodeInstruction(int pc) { +Common::String Lingo::decodeInstruction(int pc, int *newPc) { Symbol sym; Common::String res; @@ -114,6 +118,9 @@ Common::String Lingo::decodeInstruction(int pc) { res = "<unknown>"; } + if (newPc) + *newPc = pc; + return res; } @@ -182,7 +189,7 @@ void Lingo::define(Common::String &name, int start, int nargs, Common::String *p if (prefix) name = *prefix + "-" + name; - debug(3, "define(\"%s\", %d, %d, %d)", name.c_str(), start, _currentScript->size() - 1, nargs); + debugC(3, kDebugLingoCompile, "define(\"%s\", %d, %d, %d)", name.c_str(), start, _currentScript->size() - 1, nargs); if (!_handlers.contains(name)) { // Create variable if it was not defined sym = new Symbol; @@ -274,7 +281,7 @@ int Lingo::codeFunc(Common::String *s, int numpar) { if (s->equalsIgnoreCase("me")) { if (!g_lingo->_currentFactory.empty()) { g_lingo->codeString(g_lingo->_currentFactory.c_str()); - debug(2, "Repaced 'me' with %s", g_lingo->_currentFactory.c_str()); + debugC(2, kDebugLingoCompile, "Replaced 'me' with %s", g_lingo->_currentFactory.c_str()); } else { warning("'me' out of factory method"); g_lingo->codeString(s->c_str()); diff --git a/engines/director/lingo/lingo-gr.cpp b/engines/director/lingo/lingo-gr.cpp index 63128058ed..be5b3eb571 100644 --- a/engines/director/lingo/lingo-gr.cpp +++ b/engines/director/lingo/lingo-gr.cpp @@ -486,18 +486,18 @@ union yyalloc #endif /* YYFINAL -- State number of the termination state. */ -#define YYFINAL 87 +#define YYFINAL 88 /* YYLAST -- Last index in YYTABLE. */ -#define YYLAST 921 +#define YYLAST 931 /* YYNTOKENS -- Number of terminals. */ #define YYNTOKENS 83 /* YYNNTS -- Number of nonterminals. */ -#define YYNNTS 35 +#define YYNNTS 36 /* YYNRULES -- Number of rules. */ -#define YYNRULES 124 +#define YYNRULES 125 /* YYNRULES -- Number of states. */ -#define YYNSTATES 259 +#define YYNSTATES 261 /* YYTRANSLATE(YYLEX) -- Bison symbol number corresponding to YYLEX. */ #define YYUNDEFTOK 2 @@ -551,69 +551,69 @@ static const yytype_uint16 yyprhs[] = { 0, 0, 3, 7, 9, 12, 14, 15, 17, 19, 21, 23, 25, 30, 35, 40, 46, 51, 56, 62, - 64, 66, 68, 70, 79, 91, 104, 109, 118, 130, - 142, 149, 160, 171, 172, 176, 179, 181, 184, 186, - 193, 195, 201, 203, 207, 211, 214, 218, 220, 222, - 223, 224, 225, 228, 231, 233, 235, 237, 239, 244, - 246, 248, 251, 253, 257, 261, 265, 269, 273, 277, - 281, 285, 289, 293, 297, 300, 304, 308, 312, 316, - 319, 322, 326, 331, 336, 339, 341, 343, 345, 348, - 351, 354, 356, 359, 364, 367, 369, 373, 376, 379, - 382, 385, 389, 392, 395, 397, 401, 404, 407, 410, - 414, 417, 418, 427, 430, 431, 440, 441, 443, 447, - 452, 453, 457, 458, 460 + 64, 66, 68, 70, 79, 91, 104, 108, 117, 129, + 141, 148, 159, 170, 171, 175, 178, 180, 183, 185, + 192, 194, 200, 202, 206, 210, 213, 217, 219, 221, + 222, 223, 224, 227, 230, 234, 236, 238, 240, 242, + 247, 249, 251, 254, 256, 260, 264, 268, 272, 276, + 280, 284, 288, 292, 296, 300, 303, 307, 311, 315, + 319, 322, 325, 329, 334, 339, 342, 344, 346, 348, + 351, 354, 357, 359, 362, 367, 370, 372, 376, 379, + 382, 385, 388, 392, 395, 398, 400, 404, 407, 410, + 413, 417, 420, 421, 430, 433, 434, 443, 444, 446, + 450, 455, 456, 460, 461, 463 }; /* YYRHS -- A `-1'-separated list of the rules' RHS. */ static const yytype_int8 yyrhs[] = { 84, 0, -1, 84, 85, 86, -1, 86, -1, 1, - 85, -1, 76, -1, -1, 111, -1, 105, -1, 116, - -1, 87, -1, 89, -1, 40, 104, 33, 21, -1, - 42, 21, 70, 104, -1, 42, 13, 70, 104, -1, - 42, 14, 104, 70, 104, -1, 42, 21, 44, 104, - -1, 42, 13, 44, 104, -1, 42, 14, 104, 44, - 104, -1, 104, -1, 105, -1, 88, -1, 90, -1, + 85, -1, 76, -1, -1, 112, -1, 106, -1, 117, + -1, 87, -1, 89, -1, 40, 105, 33, 21, -1, + 42, 21, 70, 105, -1, 42, 13, 70, 105, -1, + 42, 14, 105, 70, 105, -1, 42, 21, 44, 105, + -1, 42, 13, 44, 105, -1, 42, 14, 105, 44, + 105, -1, 105, -1, 106, -1, 88, -1, 90, -1, 97, 77, 96, 78, 103, 102, 27, 41, -1, 98, - 70, 104, 102, 44, 104, 102, 103, 102, 27, 41, - -1, 98, 70, 104, 102, 24, 44, 104, 102, 103, - 102, 27, 41, -1, 45, 21, 43, 104, -1, 99, - 96, 43, 85, 103, 102, 27, 32, -1, 99, 96, - 43, 85, 103, 102, 48, 103, 102, 27, 32, -1, - 99, 96, 43, 85, 103, 102, 101, 92, 102, 27, - 32, -1, 99, 96, 43, 101, 88, 102, -1, 99, - 96, 43, 101, 88, 102, 48, 101, 88, 102, -1, - 99, 96, 43, 101, 88, 102, 93, 102, 91, 102, - -1, -1, 48, 101, 88, -1, 92, 95, -1, 95, - -1, 93, 94, -1, 94, -1, 100, 96, 43, 101, - 89, 102, -1, 93, -1, 100, 96, 43, 103, 102, - -1, 104, -1, 104, 70, 104, -1, 77, 96, 78, - -1, 41, 47, -1, 41, 46, 21, -1, 32, -1, - 26, -1, -1, -1, -1, 103, 85, -1, 103, 89, - -1, 12, -1, 15, -1, 22, -1, 17, -1, 21, - 77, 117, 78, -1, 21, -1, 13, -1, 14, 104, - -1, 87, -1, 104, 71, 104, -1, 104, 72, 104, - -1, 104, 73, 104, -1, 104, 74, 104, -1, 104, - 79, 104, -1, 104, 80, 104, -1, 104, 60, 104, - -1, 104, 55, 104, -1, 104, 56, 104, -1, 104, - 61, 104, -1, 104, 62, 104, -1, 63, 104, -1, - 104, 81, 104, -1, 104, 64, 104, -1, 104, 65, - 104, -1, 104, 66, 104, -1, 71, 104, -1, 72, - 104, -1, 77, 104, 78, -1, 67, 104, 68, 104, - -1, 67, 104, 69, 104, -1, 40, 104, -1, 107, - -1, 110, -1, 28, -1, 30, 106, -1, 19, 104, - -1, 18, 104, -1, 18, -1, 20, 117, -1, 51, - 104, 46, 104, -1, 51, 104, -1, 21, -1, 106, - 82, 21, -1, 31, 34, -1, 31, 37, -1, 31, - 39, -1, 31, 108, -1, 31, 108, 109, -1, 31, - 109, -1, 29, 104, -1, 104, -1, 38, 36, 104, - -1, 36, 104, -1, 52, 53, -1, 52, 108, -1, - 52, 108, 109, -1, 52, 109, -1, -1, 35, 21, - 112, 101, 114, 85, 115, 103, -1, 49, 21, -1, - -1, 50, 21, 113, 101, 114, 85, 115, 103, -1, - -1, 21, -1, 114, 82, 21, -1, 114, 85, 82, - 21, -1, -1, 21, 101, 117, -1, -1, 104, -1, - 117, 82, 104, -1 + 70, 105, 102, 44, 105, 102, 103, 102, 27, 41, + -1, 98, 70, 105, 102, 24, 44, 105, 102, 103, + 102, 27, 41, -1, 104, 105, 102, -1, 99, 96, + 43, 85, 103, 102, 27, 32, -1, 99, 96, 43, + 85, 103, 102, 48, 103, 102, 27, 32, -1, 99, + 96, 43, 85, 103, 102, 101, 92, 102, 27, 32, + -1, 99, 96, 43, 101, 88, 102, -1, 99, 96, + 43, 101, 88, 102, 48, 101, 88, 102, -1, 99, + 96, 43, 101, 88, 102, 93, 102, 91, 102, -1, + -1, 48, 101, 88, -1, 92, 95, -1, 95, -1, + 93, 94, -1, 94, -1, 100, 96, 43, 101, 89, + 102, -1, 93, -1, 100, 96, 43, 103, 102, -1, + 105, -1, 105, 70, 105, -1, 77, 96, 78, -1, + 41, 47, -1, 41, 46, 21, -1, 32, -1, 26, + -1, -1, -1, -1, 103, 85, -1, 103, 89, -1, + 45, 21, 43, -1, 12, -1, 15, -1, 22, -1, + 17, -1, 21, 77, 118, 78, -1, 21, -1, 13, + -1, 14, 105, -1, 87, -1, 105, 71, 105, -1, + 105, 72, 105, -1, 105, 73, 105, -1, 105, 74, + 105, -1, 105, 79, 105, -1, 105, 80, 105, -1, + 105, 60, 105, -1, 105, 55, 105, -1, 105, 56, + 105, -1, 105, 61, 105, -1, 105, 62, 105, -1, + 63, 105, -1, 105, 81, 105, -1, 105, 64, 105, + -1, 105, 65, 105, -1, 105, 66, 105, -1, 71, + 105, -1, 72, 105, -1, 77, 105, 78, -1, 67, + 105, 68, 105, -1, 67, 105, 69, 105, -1, 40, + 105, -1, 108, -1, 111, -1, 28, -1, 30, 107, + -1, 19, 105, -1, 18, 105, -1, 18, -1, 20, + 118, -1, 51, 105, 46, 105, -1, 51, 105, -1, + 21, -1, 107, 82, 21, -1, 31, 34, -1, 31, + 37, -1, 31, 39, -1, 31, 109, -1, 31, 109, + 110, -1, 31, 110, -1, 29, 105, -1, 105, -1, + 38, 36, 105, -1, 36, 105, -1, 52, 53, -1, + 52, 109, -1, 52, 109, 110, -1, 52, 110, -1, + -1, 35, 21, 113, 101, 115, 85, 116, 103, -1, + 49, 21, -1, -1, 50, 21, 114, 101, 115, 85, + 116, 103, -1, -1, 21, -1, 115, 82, 21, -1, + 115, 85, 82, 21, -1, -1, 21, 101, 118, -1, + -1, 105, -1, 118, 82, 105, -1 }; /* YYRLINE[YYN] -- source line where rule number YYN was defined. */ @@ -621,17 +621,17 @@ static const yytype_uint16 yyrline[] = { 0, 103, 103, 104, 105, 108, 113, 114, 115, 116, 117, 118, 121, 127, 133, 141, 149, 155, 163, 172, - 173, 175, 176, 181, 192, 208, 220, 225, 232, 241, - 250, 260, 270, 281, 282, 285, 286, 289, 290, 293, - 301, 302, 310, 311, 312, 314, 316, 322, 328, 335, - 337, 339, 340, 341, 344, 345, 348, 351, 355, 358, - 362, 369, 375, 376, 377, 378, 379, 380, 381, 382, - 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, - 393, 394, 395, 396, 399, 400, 401, 402, 404, 405, - 408, 411, 414, 415, 416, 419, 420, 431, 432, 433, - 434, 437, 440, 445, 446, 449, 450, 453, 454, 457, - 460, 490, 490, 496, 499, 499, 504, 505, 506, 507, - 509, 513, 521, 522, 523 + 173, 175, 176, 181, 192, 208, 220, 228, 235, 244, + 253, 263, 273, 284, 285, 288, 289, 292, 293, 296, + 304, 305, 313, 314, 315, 317, 319, 325, 331, 338, + 340, 342, 343, 344, 347, 353, 354, 357, 360, 364, + 367, 371, 378, 384, 385, 386, 387, 388, 389, 390, + 391, 392, 393, 394, 395, 396, 397, 398, 399, 400, + 401, 402, 403, 404, 405, 408, 409, 410, 411, 413, + 414, 417, 420, 423, 424, 425, 428, 429, 440, 441, + 442, 443, 446, 449, 454, 455, 458, 459, 462, 463, + 466, 469, 499, 499, 505, 508, 508, 513, 514, 515, + 516, 518, 522, 530, 531, 532 }; #endif @@ -655,9 +655,9 @@ static const char *const yytname[] = "programline", "asgn", "stmtoneliner", "stmt", "ifstmt", "elsestmtoneliner", "elseifstmt", "elseifstmtoneliner", "elseifstmtoneliner1", "elseifstmt1", "cond", "repeatwhile", - "repeatwith", "if", "elseif", "begin", "end", "stmtlist", "expr", "func", - "globallist", "gotofunc", "gotoframe", "gotomovie", "playfunc", "defn", - "@1", "@2", "argdef", "argstore", "macro", "arglist", 0 + "repeatwith", "if", "elseif", "begin", "end", "stmtlist", "when", "expr", + "func", "globallist", "gotofunc", "gotoframe", "gotomovie", "playfunc", + "defn", "@1", "@2", "argdef", "argstore", "macro", "arglist", 0 }; #endif @@ -686,14 +686,14 @@ static const yytype_uint8 yyr1[] = 88, 89, 89, 89, 89, 89, 89, 90, 90, 90, 90, 90, 90, 91, 91, 92, 92, 93, 93, 94, 95, 95, 96, 96, 96, 97, 98, 99, 100, 101, - 102, 103, 103, 103, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 105, 105, 105, 105, 105, 105, - 105, 105, 105, 105, 105, 106, 106, 107, 107, 107, - 107, 107, 107, 108, 108, 109, 109, 110, 110, 110, - 110, 112, 111, 111, 113, 111, 114, 114, 114, 114, - 115, 116, 117, 117, 117 + 102, 103, 103, 103, 104, 105, 105, 105, 105, 105, + 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, + 105, 105, 105, 105, 105, 105, 105, 105, 105, 105, + 105, 105, 105, 105, 105, 106, 106, 106, 106, 106, + 106, 106, 106, 106, 106, 106, 107, 107, 108, 108, + 108, 108, 108, 108, 109, 109, 110, 110, 111, 111, + 111, 111, 113, 112, 112, 114, 112, 115, 115, 115, + 115, 116, 117, 118, 118, 118 }; /* YYR2[YYN] -- Number of symbols composing right hand side of rule YYN. */ @@ -701,17 +701,17 @@ static const yytype_uint8 yyr2[] = { 0, 2, 3, 1, 2, 1, 0, 1, 1, 1, 1, 1, 4, 4, 4, 5, 4, 4, 5, 1, - 1, 1, 1, 8, 11, 12, 4, 8, 11, 11, + 1, 1, 1, 8, 11, 12, 3, 8, 11, 11, 6, 10, 10, 0, 3, 2, 1, 2, 1, 6, 1, 5, 1, 3, 3, 2, 3, 1, 1, 0, - 0, 0, 2, 2, 1, 1, 1, 1, 4, 1, - 1, 2, 1, 3, 3, 3, 3, 3, 3, 3, - 3, 3, 3, 3, 2, 3, 3, 3, 3, 2, - 2, 3, 4, 4, 2, 1, 1, 1, 2, 2, - 2, 1, 2, 4, 2, 1, 3, 2, 2, 2, - 2, 3, 2, 2, 1, 3, 2, 2, 2, 3, - 2, 0, 8, 2, 0, 8, 0, 1, 3, 4, - 0, 3, 0, 1, 3 + 0, 0, 2, 2, 3, 1, 1, 1, 1, 4, + 1, 1, 2, 1, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 2, 3, 3, 3, 3, + 2, 2, 3, 4, 4, 2, 1, 1, 1, 2, + 2, 2, 1, 2, 4, 2, 1, 3, 2, 2, + 2, 2, 3, 2, 2, 1, 3, 2, 2, 2, + 3, 2, 0, 8, 2, 0, 8, 0, 1, 3, + 4, 0, 3, 0, 1, 3 }; /* YYDEFACT[STATE-NAME] -- Default rule to reduce with in state @@ -719,259 +719,263 @@ static const yytype_uint8 yyr2[] = means the default is an error. */ static const yytype_uint8 yydefact[] = { - 0, 0, 54, 60, 0, 55, 57, 91, 0, 122, - 49, 56, 87, 0, 0, 47, 0, 0, 0, 0, + 0, 0, 55, 61, 0, 56, 58, 92, 0, 123, + 49, 57, 88, 0, 0, 47, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 3, 62, 21, 11, 22, 0, 0, 0, 19, - 8, 85, 86, 7, 9, 5, 4, 59, 0, 62, - 61, 90, 89, 123, 92, 122, 122, 95, 88, 0, - 97, 0, 98, 0, 99, 104, 100, 102, 111, 84, - 0, 45, 0, 0, 0, 0, 113, 114, 94, 107, - 108, 110, 74, 0, 79, 80, 0, 1, 6, 0, - 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, + 0, 3, 63, 21, 11, 22, 0, 0, 0, 0, + 19, 8, 86, 87, 7, 9, 5, 4, 60, 0, + 63, 62, 91, 90, 124, 93, 123, 123, 96, 89, + 0, 98, 0, 99, 0, 100, 105, 101, 103, 112, + 85, 0, 45, 0, 0, 0, 0, 114, 115, 95, + 108, 109, 111, 75, 0, 80, 81, 0, 1, 6, + 0, 0, 0, 0, 42, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 121, 0, 103, 106, 0, 101, 49, 0, - 46, 0, 0, 0, 0, 0, 0, 49, 0, 109, - 0, 0, 81, 2, 0, 50, 0, 0, 49, 0, - 70, 71, 69, 72, 73, 76, 77, 78, 63, 64, - 65, 66, 67, 68, 75, 124, 58, 96, 105, 116, - 12, 17, 14, 0, 0, 16, 13, 26, 116, 93, - 82, 83, 51, 0, 44, 51, 0, 43, 117, 0, - 18, 15, 0, 50, 0, 0, 50, 50, 20, 0, - 120, 120, 52, 53, 0, 0, 50, 49, 30, 118, - 0, 51, 51, 0, 50, 51, 0, 51, 0, 48, - 49, 50, 38, 0, 119, 112, 115, 23, 51, 50, - 27, 50, 50, 40, 36, 0, 0, 37, 33, 0, - 50, 0, 0, 35, 0, 0, 50, 49, 50, 49, - 0, 0, 0, 0, 49, 31, 0, 32, 0, 0, - 24, 28, 29, 50, 34, 50, 25, 41, 39 + 0, 0, 0, 0, 122, 0, 104, 107, 0, 102, + 49, 0, 46, 0, 0, 0, 0, 0, 54, 49, + 0, 110, 0, 0, 82, 2, 0, 50, 0, 0, + 49, 0, 26, 71, 72, 70, 73, 74, 77, 78, + 79, 64, 65, 66, 67, 68, 69, 76, 125, 59, + 97, 106, 117, 12, 17, 14, 0, 0, 16, 13, + 117, 94, 83, 84, 51, 0, 44, 51, 0, 43, + 118, 0, 18, 15, 0, 50, 0, 0, 50, 50, + 20, 0, 121, 121, 52, 53, 0, 0, 50, 49, + 30, 119, 0, 51, 51, 0, 50, 51, 0, 51, + 0, 48, 49, 50, 38, 0, 120, 113, 116, 23, + 51, 50, 27, 50, 50, 40, 36, 0, 0, 37, + 33, 0, 50, 0, 0, 35, 0, 0, 50, 49, + 50, 49, 0, 0, 0, 0, 49, 31, 0, 32, + 0, 0, 24, 28, 29, 50, 34, 50, 25, 41, + 39 }; /* YYDEFGOTO[NTERM-NUM]. */ static const yytype_int16 yydefgoto[] = { - -1, 30, 192, 31, 49, 33, 193, 35, 238, 222, - 223, 212, 224, 92, 36, 37, 38, 213, 248, 173, - 183, 39, 188, 58, 41, 66, 67, 42, 43, 118, - 127, 179, 201, 44, 54 + -1, 30, 194, 31, 50, 33, 195, 35, 240, 224, + 225, 214, 226, 93, 36, 37, 38, 215, 250, 142, + 185, 39, 40, 190, 59, 42, 67, 68, 43, 44, + 120, 129, 181, 203, 45, 55 }; /* YYPACT[STATE-NUM] -- Index in YYTABLE of the portion describing STATE-NUM. */ -#define YYPACT_NINF -198 +#define YYPACT_NINF -195 static const yytype_int16 yypact[] = { - 243, -53, -198, -198, 385, -198, -198, 385, 385, 385, - 791, -198, -198, 18, 573, -198, 22, 385, 7, 6, - 26, 31, 36, 385, 613, 385, 385, 385, 385, 385, - 4, -198, 5, -198, -198, -198, -48, -8, 512, 769, - -198, -198, -198, -198, -198, -198, -198, -14, 385, -198, - 769, 769, 769, 769, -21, 385, 385, -198, -4, 385, - -198, 385, -198, 38, -198, 769, 8, -198, -198, 151, - 54, -198, -37, 385, -29, 39, -198, -198, 649, -198, - 8, -198, 840, 671, 840, 840, 720, -198, 341, 512, - 385, 512, 40, 747, 385, 385, 385, 385, 385, 385, - 385, 385, 385, 385, 385, 385, 385, 385, 385, 151, - 385, -57, -21, 63, 769, 769, 385, -198, -198, 64, - -198, 385, 385, 627, 385, 385, 385, -198, 385, -198, - 385, 385, -198, -198, 9, 769, 14, 693, -53, 385, - 769, 769, 769, 769, 769, 769, 769, 769, 818, 818, - 840, 840, 769, 769, 769, 769, -198, -198, 769, 65, - -198, 769, 769, 385, 385, 769, 769, 769, 65, 769, - 769, 769, -198, -12, -198, -198, 529, 769, -198, -58, - 769, 769, -58, 402, 49, 385, 402, -198, -198, 73, - 13, 13, -198, -198, 71, 385, 769, -11, -17, -198, - 78, -198, -198, 60, 769, -198, 72, -198, 77, -198, - -198, 77, -198, 512, -198, 402, 402, -198, -198, 402, - -198, 402, 77, 77, -198, 512, 529, -198, 57, 67, - 402, 79, 80, -198, 81, 68, -198, -198, -198, -198, - 85, 74, 84, 87, -16, -198, 529, -198, 468, 76, - -198, -198, -198, 402, -198, -198, -198, -198, -198 + 254, -59, -195, -195, 364, -195, -195, 364, 364, 364, + 801, -195, -195, 14, 552, -195, 23, 364, 10, 6, + 30, 32, 39, 364, 592, 364, 364, 364, 364, 364, + 4, -195, 5, -195, -195, -195, -51, 1, 491, 364, + 779, -195, -195, -195, -195, -195, -195, -195, -5, 364, + -195, 779, 779, 779, 779, -8, 364, 364, -195, -3, + 364, -195, 364, -195, 37, -195, 779, 12, -195, -195, + 606, 61, -195, -31, 364, -30, 40, -195, -195, 659, + -195, 12, -195, 850, 681, 850, 850, 730, -195, 320, + 491, 364, 491, 41, 757, 779, 364, 364, 364, 364, + 364, 364, 364, 364, 364, 364, 364, 364, 364, 364, + 364, 606, 364, -57, -8, 64, 779, 779, 364, -195, + -195, 65, -195, 364, 364, 637, 364, 364, -195, -195, + 364, -195, 364, 364, -195, -195, 15, 779, 18, 703, + -59, 364, -195, 779, 779, 779, 779, 779, 779, 779, + 779, 828, 828, 850, 850, 779, 779, 779, 779, -195, + -195, 779, 67, -195, 779, 779, 364, 364, 779, 779, + 67, 779, 779, 779, -195, -1, -195, -195, 508, 779, + -195, -58, 779, 779, -58, 381, 46, 364, 381, -195, + -195, 76, 17, 17, -195, -195, 74, 364, 779, -20, + -11, -195, 81, -195, -195, 62, 779, -195, 72, -195, + 79, -195, -195, 79, -195, 491, -195, 381, 381, -195, + -195, 381, -195, 381, 79, 79, -195, 491, 508, -195, + 58, 66, 381, 80, 83, -195, 86, 71, -195, -195, + -195, -195, 88, 75, 85, 87, -17, -195, 508, -195, + 447, 77, -195, -195, -195, 381, -195, -195, -195, -195, + -195 }; /* YYPGOTO[NTERM-NUM]. */ static const yytype_int16 yypgoto[] = { - -198, -198, 12, 25, 2, -170, 0, -198, -198, -198, - -78, -197, -101, -61, -198, -198, -198, -186, -9, 113, - -167, 41, 3, -198, -198, 98, -7, -198, -198, -198, - -198, -45, -67, -198, 16 + -195, -195, 11, 19, 2, -170, 0, -195, -195, -195, + -79, -191, -102, -61, -195, -195, -195, -194, -9, -12, + -171, -195, 38, 3, -195, -195, 99, -13, -195, -195, + -195, -195, -46, -67, -195, 13 }; /* YYTABLE[YYPACT[STATE-NUM]]. What to do in state STATE-NUM. If positive, shift that token. If negative, reduce the rule which number is the opposite. If zero, do what YYDEFACT says. If YYTABLE_NINF, syntax error. */ -#define YYTABLE_NINF -60 +#define YYTABLE_NINF -61 static const yytype_int16 yytable[] = { - 34, 56, 32, 40, 87, -10, 187, 121, 186, 209, - -51, -51, 184, 46, 227, 124, 206, 81, 45, 72, - 73, 156, 225, 45, 189, 110, 227, 74, 134, 89, - 136, 210, 185, 122, 215, 216, 225, 207, 219, 57, - 221, 125, 88, 68, 61, 50, 63, 75, 51, 52, - 53, 230, 76, 70, 71, 65, 236, 77, 69, 117, - -51, 110, 90, 55, 78, 65, 82, 83, 84, 85, - 86, 111, 112, 129, 116, 120, 254, 253, 113, 93, - 45, -10, 126, 138, 157, 160, 178, 172, 34, 109, - 32, 40, 174, 195, 199, 200, 53, 53, 203, 214, - 114, 217, 115, 209, 220, 237, 241, 242, 243, 159, - 239, 244, 249, 133, 123, 250, 251, 256, 168, 252, - 211, 233, 80, 182, 202, 0, 0, 0, 0, 176, - 93, 135, 137, 0, 0, 140, 141, 142, 143, 144, - 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, - 175, 155, 229, 0, 0, 0, 0, 158, 0, 0, - 0, 0, 161, 162, 235, 165, 166, 167, 0, 169, - 0, 170, 171, 0, 0, 0, 0, 0, 0, 0, - 177, 0, 0, 0, 119, 0, 0, 0, 208, 0, - 0, 190, 0, 0, 191, 0, 0, 0, 0, 0, - 0, 226, 0, 0, 180, 181, 94, 95, 0, 0, - 0, 96, 97, 98, 0, 99, 100, 101, 0, 0, - 0, 0, 102, 103, 104, 105, 196, 0, 246, 0, - 106, 107, 108, 0, 0, 0, 204, 0, 0, 0, - 0, 0, 0, -6, 1, 0, 0, 0, 255, 0, - 0, 0, 0, 0, 93, 2, 3, 4, 5, 0, - 6, 7, 8, 9, 10, 11, 93, 0, 0, 0, - 0, 12, 0, 13, 14, 15, 0, 0, 16, 0, - 0, 0, 0, 17, 18, 19, 0, 0, 20, 0, - 0, 0, 21, 22, 23, 24, 194, 0, 0, 197, - 198, 0, 0, 0, 0, 0, 25, 0, 0, 205, - 26, 0, 0, 0, 27, 28, 0, 218, 0, -6, - 29, 0, 0, 0, 228, 0, 0, 0, 0, 0, - 0, 0, 231, 0, 232, 234, 0, 0, 0, 0, - 0, 0, 0, 240, 0, 0, 0, 0, 0, 245, - 0, 247, 0, 2, 3, 4, 5, 0, 6, 7, - 8, 9, 10, 11, 0, 0, 257, 0, 258, 12, - 0, 13, 14, 15, 0, 0, 16, 0, 0, 0, - 0, 17, 18, 19, 0, 0, 20, 0, 0, 0, - 21, 22, 23, 24, 0, 0, 0, 2, 3, 4, - 5, 0, 6, 0, 25, 0, 47, 11, 26, 0, - 0, 0, 27, 28, 2, 3, 4, 5, 29, 6, - 7, 8, 9, 47, 11, 48, 0, 19, 0, 0, - 12, 0, 13, 14, 15, 0, 0, 0, 0, 0, - 0, 0, 17, 18, 19, 0, 0, 20, 25, 0, - 0, 0, 26, 23, 24, 0, 27, 28, 0, 0, - 0, 0, 29, 0, 0, 25, 0, 0, 0, 26, - 0, 0, 0, 27, 28, 0, 0, 0, 45, 29, - 2, 3, 4, 5, 0, 6, 7, 8, 9, 47, - 11, 0, 0, 0, 0, 0, 12, 0, 13, 14, - 15, 0, 0, 0, 0, 0, 0, 0, 17, 18, - 19, 0, 0, 20, 0, 0, 0, 0, 0, 23, - 24, 0, 0, 0, 2, 3, 4, 5, 0, 6, - 0, 25, 0, 47, 11, 26, 0, 0, 0, 27, - 28, 2, 3, 4, 5, 29, 6, 7, 8, 9, - 47, 11, 48, 0, 19, 0, 0, 12, 0, 13, - 14, 0, 0, 0, 0, 0, 0, 0, 0, 17, - 0, 19, 0, 0, 0, 25, 0, 0, 0, 26, - 23, 24, 0, 27, 28, 2, 3, 4, 5, 91, - 6, 0, 25, 0, 47, 11, 26, 0, 0, 0, - 27, 28, 59, 0, 0, 0, 29, 60, 0, 61, - 62, 63, 64, 48, 0, 19, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 2, 3, 4, 5, 0, - 6, 0, 0, 0, 47, 11, 25, 0, 0, 0, - 26, 0, 59, 0, 27, 28, 0, 0, 0, 61, - 29, 63, 0, 48, 0, 19, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 79, 0, 0, 0, - 0, 163, 0, 0, 0, 0, 25, 0, 0, 0, - 26, 0, 94, 95, 27, 28, 0, 96, 97, 98, - 29, 99, 100, 101, 0, 128, 0, 164, 102, 103, - 104, 105, 0, 0, 94, 95, 106, 107, 108, 96, - 97, 98, 0, 99, 100, 101, 0, 0, 0, 0, - 102, 103, 104, 105, 0, 0, 94, 95, 106, 107, - 108, 96, 97, 98, 0, 99, 100, 101, 0, 130, - 131, 0, 102, 103, 104, 105, 0, 0, 94, 95, - 106, 107, 108, 96, 97, 98, 0, 99, 100, 101, - 0, 0, 0, 139, 102, 103, 104, 105, 0, 0, - 0, 132, 106, 107, 108, 94, 95, 0, 0, 0, - 96, 97, 98, 0, 99, 100, 101, 0, 0, 0, - 0, 102, 103, 104, 105, 0, 0, 0, 132, 106, - 107, 108, 94, 95, 0, 0, 0, 96, 97, 98, - 0, 99, 100, 101, 0, 0, 0, 139, 102, 103, - 104, 105, 0, 0, 94, 95, 106, 107, 108, 96, - 97, 98, 0, 99, 100, 101, 0, 0, 0, 0, - 102, 103, 104, 105, 0, 0, -59, -59, 106, 107, - 108, -59, -59, -59, 0, -59, -59, -59, 0, 0, - 0, 0, 0, 0, -59, -59, 0, 0, 55, 0, - -59, -59, -59, 94, 95, 0, 0, 0, 96, 97, - 98, 0, 99, 100, 101, 0, 0, 0, 0, 0, - 0, 104, 105, 0, 0, 94, 95, 106, 107, 108, - 96, 97, 98, 0, 99, 100, 101, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 106, - 107, 108 + 34, 57, 32, 41, 88, -10, 188, 208, 189, -51, + -51, 82, 47, 123, 126, 211, 227, 46, 46, 73, + 74, 159, 229, 186, 191, 112, 90, 75, 209, 136, + 227, 138, 217, 218, 229, 58, 221, 212, 223, 124, + 127, 89, 51, 187, 69, 52, 53, 54, 62, 232, + 64, 76, 66, 77, 119, 70, 71, 72, 238, -51, + 78, 79, 66, 83, 84, 85, 86, 87, 131, 113, + 114, 91, 56, 118, 112, 255, 94, 95, 256, 115, + 46, -10, 122, 128, 140, 160, 163, 111, 180, 34, + 197, 32, 41, 174, 54, 54, 176, 201, 116, 202, + 117, 205, 216, 219, 222, 211, 239, 243, 135, 241, + 244, 162, 125, 245, 246, 251, 252, 253, 258, 254, + 170, 213, 235, 81, 184, 175, 204, 0, 94, 137, + 139, 178, 0, 0, 143, 144, 145, 146, 147, 148, + 149, 150, 151, 152, 153, 154, 155, 156, 157, 0, + 158, 177, 0, 0, 231, 0, 161, 0, 0, 0, + 0, 164, 165, 0, 168, 169, 237, 0, 171, 0, + 172, 173, 0, 196, 0, 0, 199, 200, 0, 179, + 0, 0, 0, 0, 0, 0, 207, 0, 0, 0, + 210, 0, 192, 0, 220, 193, 0, 0, 0, 0, + 0, 230, 0, 228, 182, 183, 0, 0, 0, 233, + 0, 234, 236, 0, 0, 0, 0, 0, 0, 0, + 242, 0, 0, 0, 0, 198, 247, 0, 249, 0, + 248, 0, 0, 0, 0, 206, 0, 0, 0, 0, + 0, 0, 0, 259, 0, 260, 0, 0, 0, 0, + 257, 0, 0, 94, -6, 1, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 94, 2, 3, 4, 5, + 0, 6, 7, 8, 9, 10, 11, 0, 0, 0, + 0, 0, 12, 0, 13, 14, 15, 0, 0, 16, + 0, 0, 0, 0, 17, 18, 19, 0, 0, 20, + 0, 0, 0, 21, 22, 23, 24, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 25, 0, 0, + 0, 26, 0, 0, 0, 27, 28, 0, 0, 0, + -6, 29, 2, 3, 4, 5, 0, 6, 7, 8, + 9, 10, 11, 0, 0, 0, 0, 0, 12, 0, + 13, 14, 15, 0, 0, 16, 0, 0, 0, 0, + 17, 18, 19, 0, 0, 20, 0, 0, 0, 21, + 22, 23, 24, 0, 0, 0, 2, 3, 4, 5, + 0, 6, 0, 25, 0, 48, 11, 26, 0, 0, + 0, 27, 28, 2, 3, 4, 5, 29, 6, 7, + 8, 9, 48, 11, 49, 0, 19, 0, 0, 12, + 0, 13, 14, 15, 0, 0, 0, 0, 0, 0, + 0, 17, 18, 19, 0, 0, 20, 25, 0, 0, + 0, 26, 23, 24, 0, 27, 28, 0, 0, 0, + 0, 29, 0, 0, 25, 0, 0, 0, 26, 0, + 0, 0, 27, 28, 0, 0, 0, 46, 29, 2, + 3, 4, 5, 0, 6, 7, 8, 9, 48, 11, + 0, 0, 0, 0, 0, 12, 0, 13, 14, 15, + 0, 0, 0, 0, 0, 0, 0, 17, 18, 19, + 0, 0, 20, 0, 0, 0, 0, 0, 23, 24, + 0, 0, 0, 2, 3, 4, 5, 0, 6, 0, + 25, 0, 48, 11, 26, 0, 0, 0, 27, 28, + 2, 3, 4, 5, 29, 6, 7, 8, 9, 48, + 11, 49, 0, 19, 0, 0, 12, 0, 13, 14, + 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, + 19, 0, 0, 0, 25, 0, 0, 0, 26, 23, + 24, 0, 27, 28, 2, 3, 4, 5, 92, 6, + 0, 25, 0, 48, 11, 26, 0, 0, 0, 27, + 28, 60, 0, 0, 0, 29, 61, 0, 62, 63, + 64, 65, 49, 0, 19, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 2, 3, 4, 5, 0, 6, + 0, 0, 0, 48, 11, 25, 0, 0, 0, 26, + 0, 60, 0, 27, 28, 0, 0, 0, 62, 29, + 64, 0, 49, 0, 19, 0, 0, 0, 0, 121, + 0, 0, 0, 0, 0, 80, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 25, 0, 0, 0, 26, + 0, 96, 97, 27, 28, 0, 98, 99, 100, 29, + 101, 102, 103, 0, 0, 0, 0, 104, 105, 106, + 107, 166, 0, 0, 0, 108, 109, 110, 0, 0, + 0, 0, 96, 97, 0, 0, 0, 98, 99, 100, + 0, 101, 102, 103, 0, 130, 0, 167, 104, 105, + 106, 107, 0, 0, 96, 97, 108, 109, 110, 98, + 99, 100, 0, 101, 102, 103, 0, 0, 0, 0, + 104, 105, 106, 107, 0, 0, 96, 97, 108, 109, + 110, 98, 99, 100, 0, 101, 102, 103, 0, 132, + 133, 0, 104, 105, 106, 107, 0, 0, 96, 97, + 108, 109, 110, 98, 99, 100, 0, 101, 102, 103, + 0, 0, 0, 141, 104, 105, 106, 107, 0, 0, + 0, 134, 108, 109, 110, 96, 97, 0, 0, 0, + 98, 99, 100, 0, 101, 102, 103, 0, 0, 0, + 0, 104, 105, 106, 107, 0, 0, 0, 134, 108, + 109, 110, 96, 97, 0, 0, 0, 98, 99, 100, + 0, 101, 102, 103, 0, 0, 0, 141, 104, 105, + 106, 107, 0, 0, 96, 97, 108, 109, 110, 98, + 99, 100, 0, 101, 102, 103, 0, 0, 0, 0, + 104, 105, 106, 107, 0, 0, -60, -60, 108, 109, + 110, -60, -60, -60, 0, -60, -60, -60, 0, 0, + 0, 0, 0, 0, -60, -60, 0, 0, 56, 0, + -60, -60, -60, 96, 97, 0, 0, 0, 98, 99, + 100, 0, 101, 102, 103, 0, 0, 0, 0, 0, + 0, 106, 107, 0, 0, 96, 97, 108, 109, 110, + 98, 99, 100, 0, 101, 102, 103, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 108, + 109, 110 }; static const yytype_int16 yycheck[] = { - 0, 10, 0, 0, 0, 0, 176, 44, 175, 26, - 26, 27, 24, 1, 211, 44, 27, 24, 76, 13, - 14, 78, 208, 76, 82, 82, 223, 21, 89, 77, - 91, 48, 44, 70, 201, 202, 222, 48, 205, 21, - 207, 70, 30, 21, 36, 4, 38, 21, 7, 8, - 9, 218, 21, 46, 47, 14, 226, 21, 17, 66, - 76, 82, 70, 77, 23, 24, 25, 26, 27, 28, - 29, 55, 56, 80, 36, 21, 246, 244, 82, 38, - 76, 76, 43, 43, 21, 21, 21, 78, 88, 48, - 88, 88, 78, 44, 21, 82, 55, 56, 27, 21, - 59, 41, 61, 26, 32, 48, 27, 27, 27, 118, - 43, 43, 27, 88, 73, 41, 32, 41, 127, 32, - 198, 222, 24, 168, 191, -1, -1, -1, -1, 138, - 89, 90, 91, -1, -1, 94, 95, 96, 97, 98, - 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, - 138, 110, 213, -1, -1, -1, -1, 116, -1, -1, - -1, -1, 121, 122, 225, 124, 125, 126, -1, 128, - -1, 130, 131, -1, -1, -1, -1, -1, -1, -1, - 139, -1, -1, -1, 33, -1, -1, -1, 197, -1, - -1, 179, -1, -1, 182, -1, -1, -1, -1, -1, - -1, 210, -1, -1, 163, 164, 55, 56, -1, -1, - -1, 60, 61, 62, -1, 64, 65, 66, -1, -1, - -1, -1, 71, 72, 73, 74, 185, -1, 237, -1, - 79, 80, 81, -1, -1, -1, 195, -1, -1, -1, - -1, -1, -1, 0, 1, -1, -1, -1, 248, -1, - -1, -1, -1, -1, 213, 12, 13, 14, 15, -1, - 17, 18, 19, 20, 21, 22, 225, -1, -1, -1, - -1, 28, -1, 30, 31, 32, -1, -1, 35, -1, - -1, -1, -1, 40, 41, 42, -1, -1, 45, -1, - -1, -1, 49, 50, 51, 52, 183, -1, -1, 186, - 187, -1, -1, -1, -1, -1, 63, -1, -1, 196, - 67, -1, -1, -1, 71, 72, -1, 204, -1, 76, - 77, -1, -1, -1, 211, -1, -1, -1, -1, -1, - -1, -1, 219, -1, 221, 222, -1, -1, -1, -1, - -1, -1, -1, 230, -1, -1, -1, -1, -1, 236, - -1, 238, -1, 12, 13, 14, 15, -1, 17, 18, - 19, 20, 21, 22, -1, -1, 253, -1, 255, 28, - -1, 30, 31, 32, -1, -1, 35, -1, -1, -1, - -1, 40, 41, 42, -1, -1, 45, -1, -1, -1, - 49, 50, 51, 52, -1, -1, -1, 12, 13, 14, - 15, -1, 17, -1, 63, -1, 21, 22, 67, -1, - -1, -1, 71, 72, 12, 13, 14, 15, 77, 17, - 18, 19, 20, 21, 22, 40, -1, 42, -1, -1, - 28, -1, 30, 31, 32, -1, -1, -1, -1, -1, - -1, -1, 40, 41, 42, -1, -1, 45, 63, -1, - -1, -1, 67, 51, 52, -1, 71, 72, -1, -1, - -1, -1, 77, -1, -1, 63, -1, -1, -1, 67, - -1, -1, -1, 71, 72, -1, -1, -1, 76, 77, - 12, 13, 14, 15, -1, 17, 18, 19, 20, 21, - 22, -1, -1, -1, -1, -1, 28, -1, 30, 31, - 32, -1, -1, -1, -1, -1, -1, -1, 40, 41, - 42, -1, -1, 45, -1, -1, -1, -1, -1, 51, - 52, -1, -1, -1, 12, 13, 14, 15, -1, 17, + 0, 10, 0, 0, 0, 0, 177, 27, 178, 26, + 27, 24, 1, 44, 44, 26, 210, 76, 76, 13, + 14, 78, 213, 24, 82, 82, 77, 21, 48, 90, + 224, 92, 203, 204, 225, 21, 207, 48, 209, 70, + 70, 30, 4, 44, 21, 7, 8, 9, 36, 220, + 38, 21, 14, 21, 67, 17, 46, 47, 228, 76, + 21, 23, 24, 25, 26, 27, 28, 29, 81, 56, + 57, 70, 77, 36, 82, 246, 38, 39, 248, 82, + 76, 76, 21, 43, 43, 21, 21, 49, 21, 89, + 44, 89, 89, 78, 56, 57, 78, 21, 60, 82, + 62, 27, 21, 41, 32, 26, 48, 27, 89, 43, + 27, 120, 74, 27, 43, 27, 41, 32, 41, 32, + 129, 200, 224, 24, 170, 137, 193, -1, 90, 91, + 92, 140, -1, -1, 96, 97, 98, 99, 100, 101, + 102, 103, 104, 105, 106, 107, 108, 109, 110, -1, + 112, 140, -1, -1, 215, -1, 118, -1, -1, -1, + -1, 123, 124, -1, 126, 127, 227, -1, 130, -1, + 132, 133, -1, 185, -1, -1, 188, 189, -1, 141, + -1, -1, -1, -1, -1, -1, 198, -1, -1, -1, + 199, -1, 181, -1, 206, 184, -1, -1, -1, -1, + -1, 213, -1, 212, 166, 167, -1, -1, -1, 221, + -1, 223, 224, -1, -1, -1, -1, -1, -1, -1, + 232, -1, -1, -1, -1, 187, 238, -1, 240, -1, + 239, -1, -1, -1, -1, 197, -1, -1, -1, -1, + -1, -1, -1, 255, -1, 257, -1, -1, -1, -1, + 250, -1, -1, 215, 0, 1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 227, 12, 13, 14, 15, + -1, 17, 18, 19, 20, 21, 22, -1, -1, -1, + -1, -1, 28, -1, 30, 31, 32, -1, -1, 35, + -1, -1, -1, -1, 40, 41, 42, -1, -1, 45, + -1, -1, -1, 49, 50, 51, 52, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, 63, -1, -1, + -1, 67, -1, -1, -1, 71, 72, -1, -1, -1, + 76, 77, 12, 13, 14, 15, -1, 17, 18, 19, + 20, 21, 22, -1, -1, -1, -1, -1, 28, -1, + 30, 31, 32, -1, -1, 35, -1, -1, -1, -1, + 40, 41, 42, -1, -1, 45, -1, -1, -1, 49, + 50, 51, 52, -1, -1, -1, 12, 13, 14, 15, + -1, 17, -1, 63, -1, 21, 22, 67, -1, -1, + -1, 71, 72, 12, 13, 14, 15, 77, 17, 18, + 19, 20, 21, 22, 40, -1, 42, -1, -1, 28, + -1, 30, 31, 32, -1, -1, -1, -1, -1, -1, + -1, 40, 41, 42, -1, -1, 45, 63, -1, -1, + -1, 67, 51, 52, -1, 71, 72, -1, -1, -1, + -1, 77, -1, -1, 63, -1, -1, -1, 67, -1, + -1, -1, 71, 72, -1, -1, -1, 76, 77, 12, + 13, 14, 15, -1, 17, 18, 19, 20, 21, 22, + -1, -1, -1, -1, -1, 28, -1, 30, 31, 32, + -1, -1, -1, -1, -1, -1, -1, 40, 41, 42, + -1, -1, 45, -1, -1, -1, -1, -1, 51, 52, + -1, -1, -1, 12, 13, 14, 15, -1, 17, -1, + 63, -1, 21, 22, 67, -1, -1, -1, 71, 72, + 12, 13, 14, 15, 77, 17, 18, 19, 20, 21, + 22, 40, -1, 42, -1, -1, 28, -1, 30, 31, + -1, -1, -1, -1, -1, -1, -1, -1, 40, -1, + 42, -1, -1, -1, 63, -1, -1, -1, 67, 51, + 52, -1, 71, 72, 12, 13, 14, 15, 77, 17, -1, 63, -1, 21, 22, 67, -1, -1, -1, 71, - 72, 12, 13, 14, 15, 77, 17, 18, 19, 20, - 21, 22, 40, -1, 42, -1, -1, 28, -1, 30, - 31, -1, -1, -1, -1, -1, -1, -1, -1, 40, - -1, 42, -1, -1, -1, 63, -1, -1, -1, 67, - 51, 52, -1, 71, 72, 12, 13, 14, 15, 77, - 17, -1, 63, -1, 21, 22, 67, -1, -1, -1, - 71, 72, 29, -1, -1, -1, 77, 34, -1, 36, - 37, 38, 39, 40, -1, 42, -1, -1, -1, -1, - -1, -1, -1, -1, -1, 12, 13, 14, 15, -1, - 17, -1, -1, -1, 21, 22, 63, -1, -1, -1, - 67, -1, 29, -1, 71, 72, -1, -1, -1, 36, - 77, 38, -1, 40, -1, 42, -1, -1, -1, -1, - -1, -1, -1, -1, -1, -1, 53, -1, -1, -1, - -1, 44, -1, -1, -1, -1, 63, -1, -1, -1, - 67, -1, 55, 56, 71, 72, -1, 60, 61, 62, - 77, 64, 65, 66, -1, 46, -1, 70, 71, 72, + 72, 29, -1, -1, -1, 77, 34, -1, 36, 37, + 38, 39, 40, -1, 42, -1, -1, -1, -1, -1, + -1, -1, -1, -1, 12, 13, 14, 15, -1, 17, + -1, -1, -1, 21, 22, 63, -1, -1, -1, 67, + -1, 29, -1, 71, 72, -1, -1, -1, 36, 77, + 38, -1, 40, -1, 42, -1, -1, -1, -1, 33, + -1, -1, -1, -1, -1, 53, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 63, -1, -1, -1, 67, + -1, 55, 56, 71, 72, -1, 60, 61, 62, 77, + 64, 65, 66, -1, -1, -1, -1, 71, 72, 73, + 74, 44, -1, -1, -1, 79, 80, 81, -1, -1, + -1, -1, 55, 56, -1, -1, -1, 60, 61, 62, + -1, 64, 65, 66, -1, 46, -1, 70, 71, 72, 73, 74, -1, -1, 55, 56, 79, 80, 81, 60, 61, 62, -1, 64, 65, 66, -1, -1, -1, -1, 71, 72, 73, 74, -1, -1, 55, 56, 79, 80, @@ -1005,28 +1009,29 @@ static const yytype_uint8 yystos[] = 21, 22, 28, 30, 31, 32, 35, 40, 41, 42, 45, 49, 50, 51, 52, 63, 67, 71, 72, 77, 84, 86, 87, 88, 89, 90, 97, 98, 99, 104, - 105, 107, 110, 111, 116, 76, 85, 21, 40, 87, - 104, 104, 104, 104, 117, 77, 101, 21, 106, 29, - 34, 36, 37, 38, 39, 104, 108, 109, 21, 104, - 46, 47, 13, 14, 21, 21, 21, 21, 104, 53, - 108, 109, 104, 104, 104, 104, 104, 0, 85, 77, - 70, 77, 96, 104, 55, 56, 60, 61, 62, 64, - 65, 66, 71, 72, 73, 74, 79, 80, 81, 104, - 82, 117, 117, 82, 104, 104, 36, 109, 112, 33, - 21, 44, 70, 104, 44, 70, 43, 113, 46, 109, - 68, 69, 78, 86, 96, 104, 96, 104, 43, 70, - 104, 104, 104, 104, 104, 104, 104, 104, 104, 104, - 104, 104, 104, 104, 104, 104, 78, 21, 104, 101, - 21, 104, 104, 44, 70, 104, 104, 104, 101, 104, - 104, 104, 78, 102, 78, 85, 101, 104, 21, 114, - 104, 104, 114, 103, 24, 44, 103, 88, 105, 82, - 85, 85, 85, 89, 102, 44, 104, 102, 102, 21, - 82, 115, 115, 27, 104, 102, 27, 48, 101, 26, - 48, 93, 94, 100, 21, 103, 103, 41, 102, 103, - 32, 103, 92, 93, 95, 100, 101, 94, 102, 96, - 103, 102, 102, 95, 102, 96, 88, 48, 91, 43, - 102, 27, 27, 27, 43, 102, 101, 102, 101, 27, - 41, 32, 32, 103, 88, 89, 41, 102, 102 + 105, 106, 108, 111, 112, 117, 76, 85, 21, 40, + 87, 105, 105, 105, 105, 118, 77, 101, 21, 107, + 29, 34, 36, 37, 38, 39, 105, 109, 110, 21, + 105, 46, 47, 13, 14, 21, 21, 21, 21, 105, + 53, 109, 110, 105, 105, 105, 105, 105, 0, 85, + 77, 70, 77, 96, 105, 105, 55, 56, 60, 61, + 62, 64, 65, 66, 71, 72, 73, 74, 79, 80, + 81, 105, 82, 118, 118, 82, 105, 105, 36, 110, + 113, 33, 21, 44, 70, 105, 44, 70, 43, 114, + 46, 110, 68, 69, 78, 86, 96, 105, 96, 105, + 43, 70, 102, 105, 105, 105, 105, 105, 105, 105, + 105, 105, 105, 105, 105, 105, 105, 105, 105, 78, + 21, 105, 101, 21, 105, 105, 44, 70, 105, 105, + 101, 105, 105, 105, 78, 102, 78, 85, 101, 105, + 21, 115, 105, 105, 115, 103, 24, 44, 103, 88, + 106, 82, 85, 85, 85, 89, 102, 44, 105, 102, + 102, 21, 82, 116, 116, 27, 105, 102, 27, 48, + 101, 26, 48, 93, 94, 100, 21, 103, 103, 41, + 102, 103, 32, 103, 92, 93, 95, 100, 101, 94, + 102, 96, 103, 102, 102, 95, 102, 96, 88, 48, + 91, 43, 102, 27, 27, 27, 43, 102, 101, 102, + 101, 27, 41, 32, 32, 103, 88, 89, 41, 102, + 102 }; #define yyerrok (yyerrstatus = 0) @@ -1986,12 +1991,15 @@ yyreduce: case 26: #line 220 "engines/director/lingo/lingo-gr.y" { - g_lingo->code1(g_lingo->c_ifcode); + inst end = 0; + WRITE_UINT32(&end, (yyvsp[(3) - (3)].code)); + g_lingo->code1(STOP); + (*g_lingo->_currentScript)[(yyvsp[(1) - (3)].code) + 1] = end; ;} break; case 27: -#line 225 "engines/director/lingo/lingo-gr.y" +#line 228 "engines/director/lingo/lingo-gr.y" { inst then = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(5) - (8)].code)); @@ -2002,7 +2010,7 @@ yyreduce: break; case 28: -#line 232 "engines/director/lingo/lingo-gr.y" +#line 235 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(5) - (11)].code)); @@ -2015,7 +2023,7 @@ yyreduce: break; case 29: -#line 241 "engines/director/lingo/lingo-gr.y" +#line 244 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(5) - (11)].code)); @@ -2028,7 +2036,7 @@ yyreduce: break; case 30: -#line 250 "engines/director/lingo/lingo-gr.y" +#line 253 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(4) - (6)].code)); @@ -2042,7 +2050,7 @@ yyreduce: break; case 31: -#line 260 "engines/director/lingo/lingo-gr.y" +#line 263 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(4) - (10)].code)); @@ -2056,7 +2064,7 @@ yyreduce: break; case 32: -#line 270 "engines/director/lingo/lingo-gr.y" +#line 273 "engines/director/lingo/lingo-gr.y" { inst then = 0, else1 = 0, end = 0; WRITE_UINT32(&then, (yyvsp[(4) - (10)].code)); @@ -2070,17 +2078,17 @@ yyreduce: break; case 33: -#line 281 "engines/director/lingo/lingo-gr.y" +#line 284 "engines/director/lingo/lingo-gr.y" { (yyval.code) = 0; ;} break; case 34: -#line 282 "engines/director/lingo/lingo-gr.y" +#line 285 "engines/director/lingo/lingo-gr.y" { (yyval.code) = (yyvsp[(2) - (3)].code); ;} break; case 39: -#line 293 "engines/director/lingo/lingo-gr.y" +#line 296 "engines/director/lingo/lingo-gr.y" { inst then = 0; WRITE_UINT32(&then, (yyvsp[(4) - (6)].code)); @@ -2090,7 +2098,7 @@ yyreduce: break; case 41: -#line 302 "engines/director/lingo/lingo-gr.y" +#line 305 "engines/director/lingo/lingo-gr.y" { inst then = 0; WRITE_UINT32(&then, (yyvsp[(4) - (5)].code)); @@ -2100,22 +2108,22 @@ yyreduce: break; case 42: -#line 310 "engines/director/lingo/lingo-gr.y" +#line 313 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(STOP); ;} break; case 43: -#line 311 "engines/director/lingo/lingo-gr.y" +#line 314 "engines/director/lingo/lingo-gr.y" { g_lingo->code2(g_lingo->c_eq, STOP); ;} break; case 45: -#line 314 "engines/director/lingo/lingo-gr.y" +#line 317 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code3(g_lingo->c_repeatwhilecode, STOP, STOP); ;} break; case 46: -#line 316 "engines/director/lingo/lingo-gr.y" +#line 319 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code3(g_lingo->c_repeatwithcode, STOP, STOP); g_lingo->code3(STOP, STOP, STOP); @@ -2124,7 +2132,7 @@ yyreduce: break; case 47: -#line 322 "engines/director/lingo/lingo-gr.y" +#line 325 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_ifcode); g_lingo->code3(STOP, STOP, STOP); @@ -2133,7 +2141,7 @@ yyreduce: break; case 48: -#line 328 "engines/director/lingo/lingo-gr.y" +#line 331 "engines/director/lingo/lingo-gr.y" { inst skipEnd; WRITE_UINT32(&skipEnd, 1); // We have to skip end to avoid multiple executions @@ -2143,64 +2151,73 @@ yyreduce: break; case 49: -#line 335 "engines/director/lingo/lingo-gr.y" +#line 338 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->_currentScript->size(); ;} break; case 50: -#line 337 "engines/director/lingo/lingo-gr.y" +#line 340 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(STOP); (yyval.code) = g_lingo->_currentScript->size(); ;} break; case 51: -#line 339 "engines/director/lingo/lingo-gr.y" +#line 342 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->_currentScript->size(); ;} break; case 54: -#line 344 "engines/director/lingo/lingo-gr.y" - { (yyval.code) = g_lingo->codeConst((yyvsp[(1) - (1)].i)); ;} +#line 347 "engines/director/lingo/lingo-gr.y" + { + (yyval.code) = g_lingo->code1(g_lingo->c_whencode); + g_lingo->code1(STOP); + g_lingo->codeString((yyvsp[(2) - (3)].s)->c_str()); + delete (yyvsp[(2) - (3)].s); ;} break; case 55: -#line 345 "engines/director/lingo/lingo-gr.y" +#line 353 "engines/director/lingo/lingo-gr.y" + { (yyval.code) = g_lingo->codeConst((yyvsp[(1) - (1)].i)); ;} + break; + + case 56: +#line 354 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_fconstpush); g_lingo->codeFloat((yyvsp[(1) - (1)].f)); ;} break; - case 56: -#line 348 "engines/director/lingo/lingo-gr.y" + case 57: +#line 357 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_stringpush); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); ;} break; - case 57: -#line 351 "engines/director/lingo/lingo-gr.y" + case 58: +#line 360 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (1)].s)]->u.func); g_lingo->codeConst(0); // Put dummy value delete (yyvsp[(1) - (1)].s); ;} break; - case 58: -#line 355 "engines/director/lingo/lingo-gr.y" + case 59: +#line 364 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->codeFunc((yyvsp[(1) - (4)].s), (yyvsp[(3) - (4)].narg)); delete (yyvsp[(1) - (4)].s); ;} break; - case 59: -#line 358 "engines/director/lingo/lingo-gr.y" + case 60: +#line 367 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_eval); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); delete (yyvsp[(1) - (1)].s); ;} break; - case 60: -#line 362 "engines/director/lingo/lingo-gr.y" + case 61: +#line 371 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->codeConst(0); // Put dummy id g_lingo->code1(g_lingo->c_theentitypush); @@ -2210,8 +2227,8 @@ yyreduce: g_lingo->code2(e, f); ;} break; - case 61: -#line 369 "engines/director/lingo/lingo-gr.y" + case 62: +#line 378 "engines/director/lingo/lingo-gr.y" { (yyval.code) = g_lingo->code1(g_lingo->c_theentitypush); inst e = 0, f = 0; @@ -2220,237 +2237,237 @@ yyreduce: g_lingo->code2(e, f); ;} break; - case 63: -#line 376 "engines/director/lingo/lingo-gr.y" + case 64: +#line 385 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_add); ;} break; - case 64: -#line 377 "engines/director/lingo/lingo-gr.y" + case 65: +#line 386 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_sub); ;} break; - case 65: -#line 378 "engines/director/lingo/lingo-gr.y" + case 66: +#line 387 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_mul); ;} break; - case 66: -#line 379 "engines/director/lingo/lingo-gr.y" + case 67: +#line 388 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_div); ;} break; - case 67: -#line 380 "engines/director/lingo/lingo-gr.y" + case 68: +#line 389 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_gt); ;} break; - case 68: -#line 381 "engines/director/lingo/lingo-gr.y" + case 69: +#line 390 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_lt); ;} break; - case 69: -#line 382 "engines/director/lingo/lingo-gr.y" + case 70: +#line 391 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_neq); ;} break; - case 70: -#line 383 "engines/director/lingo/lingo-gr.y" + case 71: +#line 392 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_ge); ;} break; - case 71: -#line 384 "engines/director/lingo/lingo-gr.y" + case 72: +#line 393 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_le); ;} break; - case 72: -#line 385 "engines/director/lingo/lingo-gr.y" + case 73: +#line 394 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_and); ;} break; - case 73: -#line 386 "engines/director/lingo/lingo-gr.y" + case 74: +#line 395 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_or); ;} break; - case 74: -#line 387 "engines/director/lingo/lingo-gr.y" + case 75: +#line 396 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_not); ;} break; - case 75: -#line 388 "engines/director/lingo/lingo-gr.y" + case 76: +#line 397 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_ampersand); ;} break; - case 76: -#line 389 "engines/director/lingo/lingo-gr.y" + case 77: +#line 398 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_concat); ;} break; - case 77: -#line 390 "engines/director/lingo/lingo-gr.y" + case 78: +#line 399 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_contains); ;} break; - case 78: -#line 391 "engines/director/lingo/lingo-gr.y" + case 79: +#line 400 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_starts); ;} break; - case 79: -#line 392 "engines/director/lingo/lingo-gr.y" + case 80: +#line 401 "engines/director/lingo/lingo-gr.y" { (yyval.code) = (yyvsp[(2) - (2)].code); ;} break; - case 80: -#line 393 "engines/director/lingo/lingo-gr.y" + case 81: +#line 402 "engines/director/lingo/lingo-gr.y" { (yyval.code) = (yyvsp[(2) - (2)].code); g_lingo->code1(g_lingo->c_negate); ;} break; - case 81: -#line 394 "engines/director/lingo/lingo-gr.y" + case 82: +#line 403 "engines/director/lingo/lingo-gr.y" { (yyval.code) = (yyvsp[(2) - (3)].code); ;} break; - case 82: -#line 395 "engines/director/lingo/lingo-gr.y" + case 83: +#line 404 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_intersects); ;} break; - case 83: -#line 396 "engines/director/lingo/lingo-gr.y" + case 84: +#line 405 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_within); ;} break; - case 84: -#line 399 "engines/director/lingo/lingo-gr.y" + case 85: +#line 408 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_printtop); ;} break; - case 87: -#line 402 "engines/director/lingo/lingo-gr.y" + case 88: +#line 411 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(0); // Push fake value on stack g_lingo->code1(g_lingo->c_procret); ;} break; - case 89: -#line 405 "engines/director/lingo/lingo-gr.y" + case 90: +#line 414 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (2)].s)]->u.func); delete (yyvsp[(1) - (2)].s); ;} break; - case 90: -#line 408 "engines/director/lingo/lingo-gr.y" + case 91: +#line 417 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->_handlers[*(yyvsp[(1) - (2)].s)]->u.func); delete (yyvsp[(1) - (2)].s); ;} break; - case 91: -#line 411 "engines/director/lingo/lingo-gr.y" + case 92: +#line 420 "engines/director/lingo/lingo-gr.y" { g_lingo->code2(g_lingo->c_voidpush, g_lingo->_handlers[*(yyvsp[(1) - (1)].s)]->u.func); delete (yyvsp[(1) - (1)].s); ;} break; - case 92: -#line 414 "engines/director/lingo/lingo-gr.y" + case 93: +#line 423 "engines/director/lingo/lingo-gr.y" { g_lingo->codeFunc((yyvsp[(1) - (2)].s), (yyvsp[(2) - (2)].narg)); ;} break; - case 93: -#line 415 "engines/director/lingo/lingo-gr.y" + case 94: +#line 424 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_open); ;} break; - case 94: -#line 416 "engines/director/lingo/lingo-gr.y" + case 95: +#line 425 "engines/director/lingo/lingo-gr.y" { g_lingo->code2(g_lingo->c_voidpush, g_lingo->c_open); ;} break; - case 95: -#line 419 "engines/director/lingo/lingo-gr.y" + case 96: +#line 428 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_global); g_lingo->codeString((yyvsp[(1) - (1)].s)->c_str()); delete (yyvsp[(1) - (1)].s); ;} break; - case 96: -#line 420 "engines/director/lingo/lingo-gr.y" + case 97: +#line 429 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_global); g_lingo->codeString((yyvsp[(3) - (3)].s)->c_str()); delete (yyvsp[(3) - (3)].s); ;} break; - case 97: -#line 431 "engines/director/lingo/lingo-gr.y" + case 98: +#line 440 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_gotoloop); ;} break; - case 98: -#line 432 "engines/director/lingo/lingo-gr.y" + case 99: +#line 441 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_gotonext); ;} break; - case 99: -#line 433 "engines/director/lingo/lingo-gr.y" + case 100: +#line 442 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_gotoprevious); ;} break; - case 100: -#line 434 "engines/director/lingo/lingo-gr.y" + case 101: +#line 443 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(1); g_lingo->code1(g_lingo->c_goto); ;} break; - case 101: -#line 437 "engines/director/lingo/lingo-gr.y" + case 102: +#line 446 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(3); g_lingo->code1(g_lingo->c_goto); ;} break; - case 102: -#line 440 "engines/director/lingo/lingo-gr.y" + case 103: +#line 449 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(2); g_lingo->code1(g_lingo->c_goto); ;} break; - case 107: -#line 453 "engines/director/lingo/lingo-gr.y" + case 108: +#line 462 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_playdone); ;} break; - case 108: -#line 454 "engines/director/lingo/lingo-gr.y" + case 109: +#line 463 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(1); g_lingo->code1(g_lingo->c_play); ;} break; - case 109: -#line 457 "engines/director/lingo/lingo-gr.y" + case 110: +#line 466 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(3); g_lingo->code1(g_lingo->c_play); ;} break; - case 110: -#line 460 "engines/director/lingo/lingo-gr.y" + case 111: +#line 469 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(2); g_lingo->code1(g_lingo->c_play); ;} break; - case 111: -#line 490 "engines/director/lingo/lingo-gr.y" + case 112: +#line 499 "engines/director/lingo/lingo-gr.y" { g_lingo->_indef = true; g_lingo->_currentFactory.clear(); ;} break; - case 112: -#line 491 "engines/director/lingo/lingo-gr.y" + case 113: +#line 500 "engines/director/lingo/lingo-gr.y" { g_lingo->codeConst(0); // Push fake value on stack g_lingo->code1(g_lingo->c_procret); @@ -2458,53 +2475,53 @@ yyreduce: g_lingo->_indef = false; ;} break; - case 113: -#line 496 "engines/director/lingo/lingo-gr.y" + case 114: +#line 505 "engines/director/lingo/lingo-gr.y" { g_lingo->codeFactory(*(yyvsp[(2) - (2)].s)); ;} break; - case 114: -#line 499 "engines/director/lingo/lingo-gr.y" + case 115: +#line 508 "engines/director/lingo/lingo-gr.y" { g_lingo->_indef = true; ;} break; - case 115: -#line 500 "engines/director/lingo/lingo-gr.y" + case 116: +#line 509 "engines/director/lingo/lingo-gr.y" { - g_lingo->code1(STOP); + g_lingo->code1(g_lingo->c_procret); g_lingo->define(*(yyvsp[(2) - (8)].s), (yyvsp[(4) - (8)].code), (yyvsp[(5) - (8)].narg) + 1, &g_lingo->_currentFactory); g_lingo->_indef = false; ;} break; - case 116: -#line 504 "engines/director/lingo/lingo-gr.y" + case 117: +#line 513 "engines/director/lingo/lingo-gr.y" { (yyval.narg) = 0; ;} break; - case 117: -#line 505 "engines/director/lingo/lingo-gr.y" + case 118: +#line 514 "engines/director/lingo/lingo-gr.y" { g_lingo->codeArg((yyvsp[(1) - (1)].s)); (yyval.narg) = 1; ;} break; - case 118: -#line 506 "engines/director/lingo/lingo-gr.y" + case 119: +#line 515 "engines/director/lingo/lingo-gr.y" { g_lingo->codeArg((yyvsp[(3) - (3)].s)); (yyval.narg) = (yyvsp[(1) - (3)].narg) + 1; ;} break; - case 119: -#line 507 "engines/director/lingo/lingo-gr.y" + case 120: +#line 516 "engines/director/lingo/lingo-gr.y" { g_lingo->codeArg((yyvsp[(4) - (4)].s)); (yyval.narg) = (yyvsp[(1) - (4)].narg) + 1; ;} break; - case 120: -#line 509 "engines/director/lingo/lingo-gr.y" + case 121: +#line 518 "engines/director/lingo/lingo-gr.y" { g_lingo->codeArgStore(); ;} break; - case 121: -#line 513 "engines/director/lingo/lingo-gr.y" + case 122: +#line 522 "engines/director/lingo/lingo-gr.y" { g_lingo->code1(g_lingo->c_call); g_lingo->codeString((yyvsp[(1) - (3)].s)->c_str()); @@ -2513,24 +2530,24 @@ yyreduce: g_lingo->code1(numpar); ;} break; - case 122: -#line 521 "engines/director/lingo/lingo-gr.y" + case 123: +#line 530 "engines/director/lingo/lingo-gr.y" { (yyval.narg) = 0; ;} break; - case 123: -#line 522 "engines/director/lingo/lingo-gr.y" + case 124: +#line 531 "engines/director/lingo/lingo-gr.y" { (yyval.narg) = 1; ;} break; - case 124: -#line 523 "engines/director/lingo/lingo-gr.y" + case 125: +#line 532 "engines/director/lingo/lingo-gr.y" { (yyval.narg) = (yyvsp[(1) - (3)].narg) + 1; ;} break; /* Line 1267 of yacc.c. */ -#line 2534 "engines/director/lingo/lingo-gr.cpp" +#line 2551 "engines/director/lingo/lingo-gr.cpp" default: break; } YY_SYMBOL_PRINT ("-> $$ =", yyr1[yyn], &yyval, &yyloc); @@ -2744,6 +2761,6 @@ yyreturn: } -#line 526 "engines/director/lingo/lingo-gr.y" +#line 535 "engines/director/lingo/lingo-gr.y" diff --git a/engines/director/lingo/lingo-gr.y b/engines/director/lingo/lingo-gr.y index ea66bc6fd9..f74244e5fd 100644 --- a/engines/director/lingo/lingo-gr.y +++ b/engines/director/lingo/lingo-gr.y @@ -90,7 +90,7 @@ void yyerror(char *s) { %token tCONCAT tCONTAINS tSTARTS %token tSPRITE tINTERSECTS tWITHIN -%type<code> asgn begin elseif elsestmtoneliner end expr if repeatwhile repeatwith stmtlist +%type<code> asgn begin elseif elsestmtoneliner end expr if when repeatwhile repeatwith stmtlist %type<narg> argdef arglist %right '=' @@ -217,8 +217,11 @@ stmt: stmtoneliner (*g_lingo->_currentScript)[$1 + 3] = body; /* body of loop */ (*g_lingo->_currentScript)[$1 + 4] = inc; /* increment */ (*g_lingo->_currentScript)[$1 + 5] = end; } /* end, if cond fails */ - | tWHEN ID tTHEN expr { - g_lingo->code1(g_lingo->c_ifcode); + | when expr end { + inst end = 0; + WRITE_UINT32(&end, $3); + g_lingo->code1(STOP); + (*g_lingo->_currentScript)[$1 + 1] = end; } ; @@ -341,6 +344,12 @@ stmtlist: /* nothing */ { $$ = g_lingo->_currentScript->size(); } | stmtlist stmt ; +when: tWHEN ID tTHEN { + $$ = g_lingo->code1(g_lingo->c_whencode); + g_lingo->code1(STOP); + g_lingo->codeString($2->c_str()); + delete $2; } + expr: INT { $$ = g_lingo->codeConst($1); } | FLOAT { $$ = g_lingo->code1(g_lingo->c_fconstpush); @@ -498,7 +507,7 @@ defn: tMACRO ID { g_lingo->_indef = true; g_lingo->_currentFactory.clear(); } } | tMETHOD ID { g_lingo->_indef = true; } begin argdef nl argstore stmtlist { - g_lingo->code1(STOP); + g_lingo->code1(g_lingo->c_procret); g_lingo->define(*$2, $4, $5 + 1, &g_lingo->_currentFactory); g_lingo->_indef = false; } ; argdef: /* nothing */ { $$ = 0; } diff --git a/engines/director/lingo/lingo.cpp b/engines/director/lingo/lingo.cpp index 9aea9593e9..d37da42670 100644 --- a/engines/director/lingo/lingo.cpp +++ b/engines/director/lingo/lingo.cpp @@ -121,17 +121,17 @@ const char *Lingo::findNextDefinition(const char *s) { return NULL; if (!strncmp(res, "macro ", 6)) { - warning("See macro"); + debugC(3, kDebugLingoCompile, "See macro"); return res; } if (!strncmp(res, "factory ", 8)) { - warning("See factory"); + debugC(3, kDebugLingoCompile, "See factory"); return res; } if (!strncmp(res, "method ", 7)) { - warning("See method"); + debugC(3, kDebugLingoCompile, "See method"); return res; } @@ -181,6 +181,14 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) { parse(chunk.c_str()); + if (debugChannelSet(3, kDebugLingoCompile)) { + int pc = 0; + while (pc < _currentScript->size()) { + Common::String instr = decodeInstruction(pc, &pc); + debugC(3, kDebugLingoCompile, "[%5d] %s", pc, instr.c_str()); + } + } + _currentScript->clear(); begin = end; @@ -198,8 +206,16 @@ void Lingo::addCode(const char *code, ScriptType type, uint16 id) { _inFactory = false; - if (_currentScript->size() && !_hadError) - Common::hexdump((byte *)&_currentScript->front(), _currentScript->size() * sizeof(inst)); + if (debugChannelSet(3, kDebugLingoCompile)) { + if (_currentScript->size() && !_hadError) + Common::hexdump((byte *)&_currentScript->front(), _currentScript->size() * sizeof(inst)); + + int pc = 0; + while (pc < _currentScript->size()) { + Common::String instr = decodeInstruction(pc, &pc); + debugC(3, kDebugLingoCompile, "[%5d] %s", pc, instr.c_str()); + } + } } void Lingo::executeScript(ScriptType type, uint16 id) { @@ -225,7 +241,7 @@ void Lingo::processEvent(LEvent event, int entityId) { if (!_eventHandlerTypes.contains(event)) error("processEvent: Unknown event %d for entity %d", event, entityId); - debug(2, "processEvent(%s) for %d", _eventHandlerTypes[event], entityId); + debug(2, "STUB: processEvent(%s) for %d", _eventHandlerTypes[event], entityId); } int Lingo::alignTypes(Datum &d1, Datum &d2) { @@ -285,6 +301,15 @@ Common::String *Datum::toString() { delete s; s = u.s; break; + case OBJECT: + *s = Common::String::format("#%s", u.s->c_str()); + break; + case VOID: + *s = "#void"; + break; + case VAR: + *s = Common::String::format("var: #%s", u.sym->name); + break; default: warning("Incorrect operation toString() for type: %s", type2str()); } @@ -313,6 +338,10 @@ const char *Datum::type2str(bool isk) { return isk ? "#point" : "POINT"; case SYMBOL: return isk ? "#symbol" : "SYMBOL"; + case OBJECT: + return isk ? "#object" : "OBJECT"; + case VAR: + return isk ? "#var" : "VAR"; default: snprintf(res, 20, "-- (%d) --", type); return res; @@ -374,7 +403,7 @@ void Lingo::runTests() { Common::sort(fileList.begin(), fileList.end()); - for (int i = 0; i < fileList.size(); i++) { + for (uint i = 0; i < fileList.size(); i++) { Common::SeekableReadStream *const stream = SearchMan.createReadStreamForMember(fileList[i]); if (stream) { uint size = stream->size(); @@ -383,7 +412,7 @@ void Lingo::runTests() { stream->read(script, size); - warning("Compiling file %s of size %d, id: %d", fileList[i].c_str(), size, counter); + debugC(2, kDebugLingoCompile, "Compiling file %s of size %d, id: %d", fileList[i].c_str(), size, counter); _hadError = false; addCode(script, kMovieScript, counter); @@ -391,7 +420,7 @@ void Lingo::runTests() { if (!_hadError) executeScript(kMovieScript, counter); else - warning("Skipping execution"); + debugC(2, kDebugLingoCompile, "Skipping execution"); free(script); diff --git a/engines/director/lingo/lingo.h b/engines/director/lingo/lingo.h index 254030e9b7..71708cf910 100644 --- a/engines/director/lingo/lingo.h +++ b/engines/director/lingo/lingo.h @@ -180,7 +180,7 @@ public: void addCode(const char *code, ScriptType type, uint16 id); void executeScript(ScriptType type, uint16 id); - Common::String decodeInstruction(int pc); + Common::String decodeInstruction(int pc, int *newPC = NULL); void processEvent(LEvent event, int entityId); @@ -267,6 +267,7 @@ public: static void c_repeatwhilecode(); static void c_repeatwithcode(); static void c_ifcode(); + static void c_whencode(); static void c_eq(); static void c_neq(); static void c_gt(); diff --git a/engines/fullpipe/gfx.cpp b/engines/fullpipe/gfx.cpp index dd8d8b246d..b357bbd587 100644 --- a/engines/fullpipe/gfx.cpp +++ b/engines/fullpipe/gfx.cpp @@ -1122,18 +1122,24 @@ void Bitmap::copier(uint32 *dest, byte *src, int len, int32 *palette, bool cb05_ } Bitmap *Bitmap::reverseImage(bool flip) { - if (flip) - _flipping = Graphics::FLIP_H; - else - _flipping = Graphics::FLIP_NONE; + Bitmap *b = new Bitmap(this); + + if (flip) { + if (b->_flipping == Graphics::FLIP_NONE) + b->_flipping = Graphics::FLIP_H; + else + b->_flipping = Graphics::FLIP_NONE; + } - return this; + return b; } Bitmap *Bitmap::flipVertical() { - _flipping = Graphics::FLIP_V; + Bitmap *b = new Bitmap(this); + + b->_flipping = Graphics::FLIP_V; - return this; + return b; } void Bitmap::drawShaded(int type, int x, int y, byte *palette, int alpha) { diff --git a/engines/sci/console.cpp b/engines/sci/console.cpp index 3aaf13efdb..b20ed3f8be 100644 --- a/engines/sci/console.cpp +++ b/engines/sci/console.cpp @@ -54,7 +54,6 @@ #include "sci/graphics/frameout.h" #include "sci/graphics/paint32.h" #include "video/coktel_decoder.h" -#include "sci/video/robot_decoder.h" #endif #include "common/file.h" @@ -266,8 +265,6 @@ void Console::postEnter() { #ifdef ENABLE_SCI32 } else if (_videoFile.hasSuffix(".vmd")) { videoDecoder = new Video::AdvancedVMDDecoder(); - } else if (_videoFile.hasSuffix(".rbt")) { - videoDecoder = new RobotDecoder(_engine->getPlatform() == Common::kPlatformMacintosh); } else if (_videoFile.hasSuffix(".duk")) { duckMode = true; videoDecoder = new Video::AVIDecoder(); diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index f5797dc106..ad2b0f31a5 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -565,8 +565,8 @@ const ADGameDescription *SciMetaEngine::fallbackDetect(const FileMap &allFiles, // the file should be over 10MB, as it contains all the game speech and is usually // around 450MB+. The size check is for some floppy game versions like KQ6 floppy, which // also have a small resource.aud file - if (allFiles.contains("resource.aud") || allFiles.contains("audio001.002")) { - Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : allFiles["audio001.002"]; + if (allFiles.contains("resource.aud") || allFiles.contains("resaud.001") || allFiles.contains("audio001.002")) { + Common::FSNode file = allFiles.contains("resource.aud") ? allFiles["resource.aud"] : (allFiles.contains("resaud.001") ? allFiles["resaud.001"] : allFiles["audio001.002"]); Common::SeekableReadStream *tmpStream = file.createReadStream(); if (tmpStream->size() > 10 * 1024 * 1024) { // We got a CD version, so set the CD flag accordingly diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index b02a7e545a..90f582c291 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -421,6 +421,14 @@ reg_t kStubNull(EngineState *s, int argc, reg_t *argv); #ifdef ENABLE_SCI32 // SCI2 Kernel Functions +reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv); +reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv); +reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv); +reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv); +reg_t kPlatform32(EngineState *s, int argc, reg_t *argv); +reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv); +reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv); + reg_t kDoAudio32(EngineState *s, int argc, reg_t *argv); reg_t kDoAudioInit(EngineState *s, int argc, reg_t *argv); reg_t kDoAudioWaitForPlay(EngineState *s, int argc, reg_t *argv); @@ -441,10 +449,24 @@ reg_t kDoAudioFade(EngineState *s, int argc, reg_t *argv); reg_t kDoAudioHasSignal(EngineState *s, int argc, reg_t *argv); reg_t kDoAudioSetLoop(EngineState *s, int argc, reg_t *argv); +reg_t kRobot(EngineState *s, int argc, reg_t *argv); +reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv); +reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv); +reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv); +reg_t kRobotClose(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv); +reg_t kRobotPause(EngineState *s, int argc, reg_t *argv); +reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv); +reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv); + reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDOpen(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDInit(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv); +reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDShowCursor(EngineState *s, int argc, reg_t *argv); reg_t kPlayVMDSetBlackoutArea(EngineState *s, int argc, reg_t *argv); @@ -587,9 +609,9 @@ reg_t kTextWidth(EngineState *s, int argc, reg_t *argv); reg_t kSave(EngineState *s, int argc, reg_t *argv); reg_t kAutoSave(EngineState *s, int argc, reg_t *argv); reg_t kList(EngineState *s, int argc, reg_t *argv); -reg_t kRobot(EngineState *s, int argc, reg_t *argv); -reg_t kPlayVMD(EngineState *s, int argc, reg_t *argv); reg_t kCD(EngineState *s, int argc, reg_t *argv); +reg_t kCheckCD(EngineState *s, int argc, reg_t *argv); +reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv); reg_t kAddPicAt(EngineState *s, int argc, reg_t *argv); reg_t kAddBefore(EngineState *s, int argc, reg_t *argv); reg_t kMoveToFront(EngineState *s, int argc, reg_t *argv); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 76c24b09e3..68611b0dee 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -396,6 +396,13 @@ static const SciKernelMapSubEntry kBitmap_subops[] = { }; // version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kCD_subops[] = { + { SIG_SINCE_SCI21MID, 0, MAP_CALL(CheckCD), "(i)", NULL }, + { SIG_SINCE_SCI21MID, 1, MAP_CALL(GetSavedCD), "", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kList_subops[] = { { SIG_SINCE_SCI21, 0, MAP_CALL(NewList), "", NULL }, { SIG_SINCE_SCI21, 1, MAP_CALL(DisposeList), "l", NULL }, @@ -406,8 +413,8 @@ static const SciKernelMapSubEntry kList_subops[] = { { SIG_SINCE_SCI21, 6, MAP_CALL(NextNode), "n", NULL }, { SIG_SINCE_SCI21, 7, MAP_CALL(PrevNode), "n", NULL }, { SIG_SINCE_SCI21, 8, MAP_CALL(NodeValue), "[n0]", NULL }, - { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn.", NULL }, - { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln.", NULL }, + { SIG_SINCE_SCI21, 9, MAP_CALL(AddAfter), "lnn(.)", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(AddToFront), "ln(.)", NULL }, { SIG_SINCE_SCI21, 11, MAP_CALL(AddToEnd), "ln(.)", NULL }, { SIG_SINCE_SCI21, 12, MAP_CALL(AddBefore), "ln.", NULL }, { SIG_SINCE_SCI21, 13, MAP_CALL(MoveToFront), "ln", NULL }, @@ -451,6 +458,7 @@ static const SciKernelMapSubEntry kPlayVMD_subops[] = { { SIG_SINCE_SCI21, 0, MAP_CALL(PlayVMDOpen), "r(i)(i)", NULL }, { SIG_SINCE_SCI21, 1, MAP_CALL(PlayVMDInit), "ii(i)(i)(ii)", NULL }, { SIG_SINCE_SCI21, 6, MAP_CALL(PlayVMDClose), "", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(PlayVMDGetStatus), "", NULL }, { SIG_SINCE_SCI21, 14, MAP_CALL(PlayVMDPlayUntilEvent), "i(i)(i)", NULL }, { SIG_SINCE_SCI21, 16, MAP_CALL(PlayVMDShowCursor), "i", NULL }, { SIG_SINCE_SCI21, 17, MAP_DUMMY(PlayVMDStartBlob), "", NULL }, @@ -461,6 +469,22 @@ static const SciKernelMapSubEntry kPlayVMD_subops[] = { }; // version, subId, function-mapping, signature, workarounds +static const SciKernelMapSubEntry kRobot_subops[] = { + { SIG_SINCE_SCI21, 0, MAP_CALL(RobotOpen), "ioiii(i)", NULL }, + { SIG_SINCE_SCI21, 1, MAP_CALL(RobotShowFrame), "i(ii)", NULL }, + { SIG_SINCE_SCI21, 2, MAP_CALL(RobotGetFrameSize), "r", NULL }, + { SIG_SINCE_SCI21, 4, MAP_CALL(RobotPlay), "", NULL }, + { SIG_SINCE_SCI21, 5, MAP_CALL(RobotGetIsFinished), "", NULL }, + { SIG_SINCE_SCI21, 6, MAP_CALL(RobotGetIsPlaying), "", NULL }, + { SIG_SINCE_SCI21, 7, MAP_CALL(RobotClose), "", NULL }, + { SIG_SINCE_SCI21, 8, MAP_CALL(RobotGetCue), "o", NULL }, + { SIG_SINCE_SCI21, 10, MAP_CALL(RobotPause), "", NULL }, + { SIG_SINCE_SCI21, 11, MAP_CALL(RobotGetFrameNo), "", NULL }, + { SIG_SINCE_SCI21, 12, MAP_CALL(RobotSetPriority), "i", NULL }, + SCI_SUBOPENTRY_TERMINATOR +}; + +// version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kRemapColors_subops[] = { { SIG_SCI32, 0, MAP_CALL(RemapColorsOff), "(i)", NULL }, { SIG_SCI32, 1, MAP_CALL(RemapColorsByRange), "iiii(i)", NULL }, @@ -569,7 +593,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Animate), SIG_EVERYWHERE, "(l0)(i)", NULL, NULL }, { MAP_CALL(AssertPalette), SIG_EVERYWHERE, "i", NULL, NULL }, { MAP_CALL(AvoidPath), SIG_EVERYWHERE, "ii(.*)", NULL, NULL }, - { MAP_CALL(BaseSetter), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(BaseSetter), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "BaseSetter", kBaseSetter32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, +#endif { MAP_CALL(CanBeHere), SIG_EVERYWHERE, "o(l)", NULL, NULL }, { MAP_CALL(CantBeHere), SIG_SCI16, SIGFOR_ALL, "o(l)", NULL, NULL }, #ifdef ENABLE_SCI32 @@ -638,8 +665,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL }, { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL }, { MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(GlobalToLocal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, - { MAP_CALL(GlobalToLocal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(GlobalToLocal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "GlobalToLocal", kGlobalToLocal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, +#endif { MAP_CALL(Graph), SIG_EVERYWHERE, NULL, kGraph_subops, NULL }, { MAP_CALL(HaveMouse), SIG_EVERYWHERE, "", NULL, NULL }, { MAP_CALL(HiliteControl), SIG_EVERYWHERE, "o", NULL, NULL }, @@ -650,8 +679,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Joystick), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop { MAP_CALL(LastNode), SIG_EVERYWHERE, "l", NULL, NULL }, { MAP_CALL(Load), SIG_EVERYWHERE, "ii(i*)", NULL, NULL }, - { MAP_CALL(LocalToGlobal), SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, - { MAP_CALL(LocalToGlobal), SIG_EVERYWHERE, "o", NULL, NULL }, + { MAP_CALL(LocalToGlobal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "LocalToGlobal", kLocalToGlobal32, SIG_SCI32, SIGFOR_ALL, "oo", NULL, NULL }, +#endif { MAP_CALL(Lock), SIG_EVERYWHERE, "ii(i)", NULL, NULL }, { MAP_CALL(MapKeyToDir), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(Memory), SIG_EVERYWHERE, "i(.*)", NULL, kMemory_workarounds }, // subop @@ -676,7 +707,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Palette), SIG_EVERYWHERE, "i(.*)", kPalette_subops, NULL }, { MAP_CALL(Parse), SIG_EVERYWHERE, "ro", NULL, NULL }, { MAP_CALL(PicNotValid), SIG_EVERYWHERE, "(i)", NULL, NULL }, - { MAP_CALL(Platform), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Platform), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "Platform", kPlatform32, SIG_SCI32, SIGFOR_ALL, "(i)", NULL, NULL }, +#endif { MAP_CALL(Portrait), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, // subop { MAP_CALL(PrevNode), SIG_EVERYWHERE, "n", NULL, NULL }, { MAP_CALL(PriCoord), SIG_EVERYWHERE, "i", NULL, NULL }, @@ -693,22 +727,26 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL }, { MAP_CALL(SaveGame), SIG_EVERYWHERE, "[r0]i[r0](r0)", NULL, NULL }, { MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL }, - { MAP_CALL(SetCursor), SIG_SINCE_SCI21, SIGFOR_ALL, "i(i)([io])(i*)", NULL, NULL }, - // TODO: SCI2.1 may supply an object optionally (mother goose sci21 right on startup) - find out why { MAP_CALL(SetCursor), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(iiiiii)", NULL, NULL }, - { MAP_CALL(SetCursor), SIG_EVERYWHERE, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds }, + { MAP_CALL(SetCursor), SIG_SCI16, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds }, +#ifdef ENABLE_SCI32 + { "SetCursor", kSetCursor32, SIG_SCI32, SIGFOR_ALL, "i(i)(i)(i)", NULL, kSetCursor_workarounds }, +#endif { MAP_CALL(SetDebug), SIG_EVERYWHERE, "(i*)", NULL, NULL }, { MAP_CALL(SetJump), SIG_EVERYWHERE, "oiii", NULL, NULL }, { MAP_CALL(SetMenu), SIG_EVERYWHERE, "i(.*)", NULL, NULL }, { MAP_CALL(SetNowSeen), SIG_SCI16, SIGFOR_ALL, "o(i)", NULL, NULL }, #ifdef ENABLE_SCI32 - { MAP_CALL(SetNowSeen), SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, + { "SetNowSeen", kSetNowSeen32, SIG_SCI32, SIGFOR_ALL, "o", NULL, NULL }, #endif { MAP_CALL(SetPort), SIG_EVERYWHERE, "i(iiiii)(i)", NULL, kSetPort_workarounds }, { MAP_CALL(SetQuitStr), SIG_EVERYWHERE, "r", NULL, NULL }, { MAP_CALL(SetSynonyms), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(SetVideoMode), SIG_EVERYWHERE, "i", NULL, NULL }, - { MAP_CALL(ShakeScreen), SIG_EVERYWHERE, "(i)(i)", NULL, NULL }, + { MAP_CALL(ShakeScreen), SIG_SCI16, SIGFOR_ALL, "(i)(i)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "ShakeScreen", kShakeScreen32, SIG_SCI32, SIGFOR_ALL, "i(i)", NULL, NULL }, +#endif { MAP_CALL(ShowMovie), SIG_SCI16, SIGFOR_ALL, "(.*)", NULL, NULL }, #ifdef ENABLE_SCI32 { "ShowMovie", kShowMovie32, SIG_SCI32, SIGFOR_DOS, "ri(i)(i)", NULL, NULL }, @@ -840,12 +878,12 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_DUMMY(PointSize), SIG_EVERYWHERE, "(.*)", NULL, NULL }, // SCI2.1 Kernel Functions - { MAP_CALL(CD), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(CD), SIG_SINCE_SCI21MID, SIGFOR_ALL, "(.*)", kCD_subops, NULL }, { MAP_CALL(IsOnMe), SIG_EVERYWHERE, "iioi", NULL, NULL }, { MAP_CALL(List), SIG_SINCE_SCI21, SIGFOR_ALL, "(.*)", kList_subops, NULL }, { MAP_CALL(MulDiv), SIG_EVERYWHERE, "iii", NULL, NULL }, { MAP_CALL(PlayVMD), SIG_EVERYWHERE, "(.*)", kPlayVMD_subops, NULL }, - { MAP_EMPTY(Robot), SIG_EVERYWHERE, "(.*)", NULL, NULL }, + { MAP_CALL(Robot), SIG_EVERYWHERE, "(.*)", kRobot_subops, NULL }, { MAP_CALL(Save), SIG_EVERYWHERE, "i(.*)", kSave_subops, NULL }, { MAP_CALL(Text), SIG_SINCE_SCI21MID, SIGFOR_ALL, "i(.*)", kText_subops, NULL }, { MAP_CALL(AddPicAt), SIG_EVERYWHERE, "oiii(i)(i)", NULL, NULL }, diff --git a/engines/sci/engine/kevent.cpp b/engines/sci/engine/kevent.cpp index d7a716a504..9250e0fc13 100644 --- a/engines/sci/engine/kevent.cpp +++ b/engines/sci/engine/kevent.cpp @@ -34,6 +34,9 @@ #include "sci/graphics/coordadjuster.h" #include "sci/graphics/cursor.h" #include "sci/graphics/maciconbar.h" +#ifdef ENABLE_SCI32 +#include "sci/graphics/frameout.h" +#endif namespace Sci { @@ -58,10 +61,7 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { if (g_debug_simulated_key && (mask & SCI_EVENT_KEYBOARD)) { // In case we use a simulated event we query the current mouse position mousePos = g_sci->_gfxCursor->getPosition(); -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2_1_EARLY) - g_sci->_gfxCoordAdjuster->fromDisplayToScript(mousePos.y, mousePos.x); -#endif + // Limit the mouse cursor position, if necessary g_sci->_gfxCursor->refreshPosition(); @@ -86,11 +86,14 @@ reg_t kGetEvent(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) mousePos = curEvent.mousePosSci; - else + else { #endif mousePos = curEvent.mousePos; - // Limit the mouse cursor position, if necessary - g_sci->_gfxCursor->refreshPosition(); + // Limit the mouse cursor position, if necessary + g_sci->_gfxCursor->refreshPosition(); +#ifdef ENABLE_SCI32 + } +#endif if (g_sci->getVocabulary()) g_sci->getVocabulary()->parser_event = NULL_REG; // Invalidate parser event @@ -281,14 +284,13 @@ reg_t kMapKeyToDir(EngineState *s, int argc, reg_t *argv) { reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; - reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; if (obj.getSegment()) { int16 x = readSelectorValue(segMan, obj, SELECTOR(x)); int16 y = readSelectorValue(segMan, obj, SELECTOR(y)); - g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y, planeObject); + g_sci->_gfxCoordAdjuster->kernelGlobalToLocal(x, y); writeSelectorValue(segMan, obj, SELECTOR(x), x); writeSelectorValue(segMan, obj, SELECTOR(y), y); @@ -300,14 +302,13 @@ reg_t kGlobalToLocal(EngineState *s, int argc, reg_t *argv) { reg_t kLocalToGlobal(EngineState *s, int argc, reg_t *argv) { reg_t obj = argv[0]; - reg_t planeObject = argc > 1 ? argv[1] : NULL_REG; // SCI32 SegManager *segMan = s->_segMan; if (obj.getSegment()) { int16 x = readSelectorValue(segMan, obj, SELECTOR(x)); int16 y = readSelectorValue(segMan, obj, SELECTOR(y)); - g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y, planeObject); + g_sci->_gfxCoordAdjuster->kernelLocalToGlobal(x, y); writeSelectorValue(segMan, obj, SELECTOR(x), x); writeSelectorValue(segMan, obj, SELECTOR(y), y); @@ -322,4 +323,52 @@ reg_t kJoystick(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } +#ifdef ENABLE_SCI32 +reg_t kGlobalToLocal32(EngineState *s, int argc, reg_t *argv) { + const reg_t result = argv[0]; + const reg_t planeObj = argv[1]; + + bool visible = true; + Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); + if (plane == nullptr) { + plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); + visible = false; + } + if (plane == nullptr) { + error("kGlobalToLocal: Plane %04x:%04x not found", PRINT_REG(planeObj)); + } + + const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) - plane->_gameRect.left; + const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) - plane->_gameRect.top; + + writeSelectorValue(s->_segMan, result, SELECTOR(x), x); + writeSelectorValue(s->_segMan, result, SELECTOR(y), y); + + return make_reg(0, visible); +} + +reg_t kLocalToGlobal32(EngineState *s, int argc, reg_t *argv) { + const reg_t result = argv[0]; + const reg_t planeObj = argv[1]; + + bool visible = true; + Plane *plane = g_sci->_gfxFrameout->getVisiblePlanes().findByObject(planeObj); + if (plane == nullptr) { + plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); + visible = false; + } + if (plane == nullptr) { + error("kLocalToGlobal: Plane %04x:%04x not found", PRINT_REG(planeObj)); + } + + const int16 x = readSelectorValue(s->_segMan, result, SELECTOR(x)) + plane->_gameRect.left; + const int16 y = readSelectorValue(s->_segMan, result, SELECTOR(y)) + plane->_gameRect.top; + + writeSelectorValue(s->_segMan, result, SELECTOR(x), x); + writeSelectorValue(s->_segMan, result, SELECTOR(y), y); + + return make_reg(0, visible); +} +#endif + } // End of namespace Sci diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 3bcadd143c..a3a97eb7ee 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -40,6 +40,9 @@ #include "sci/engine/savegame.h" #include "sci/sound/audio.h" #include "sci/console.h" +#ifdef ENABLE_SCI32 +#include "sci/resource.h" +#endif namespace Sci { @@ -196,26 +199,25 @@ reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { #ifdef ENABLE_SCI32 reg_t kCD(EngineState *s, int argc, reg_t *argv) { - // TODO: Stub - switch (argv[0].toUint16()) { - case 0: - if (argc == 1) { - // Check if a disc is in the drive - return TRUE_REG; - } else { - // Check if the specified disc is in the drive - // and return the current disc number. We just - // return the requested disc number. - return argv[1]; - } - case 1: - // Return the current CD number - return make_reg(0, 1); - default: - warning("CD(%d)", argv[0].toUint16()); + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kCheckCD(EngineState *s, int argc, reg_t *argv) { + const int16 cdNo = argc > 0 ? argv[0].toSint16() : 0; + + if (cdNo) { + g_sci->getResMan()->findDisc(cdNo); } - return NULL_REG; + return make_reg(0, g_sci->getResMan()->getCurrentDiscNo()); +} + +reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) { + // TODO: This is wrong, CD number needs to be available prior to + // the save game being loaded + return make_reg(0, g_sci->getResMan()->getCurrentDiscNo()); } #endif diff --git a/engines/sci/engine/kgraphics.cpp b/engines/sci/engine/kgraphics.cpp index cae5a09789..d375a27954 100644 --- a/engines/sci/engine/kgraphics.cpp +++ b/engines/sci/engine/kgraphics.cpp @@ -579,17 +579,8 @@ reg_t kBaseSetter(EngineState *s, int argc, reg_t *argv) { } reg_t kSetNowSeen(EngineState *s, int argc, reg_t *argv) { -#ifdef ENABLE_SCI32 - if (getSciVersion() >= SCI_VERSION_2) { - g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]); - return NULL_REG; - } else { -#endif - g_sci->_gfxCompare->kernelSetNowSeen(argv[0]); - return s->r_acc; -#ifdef ENABLE_SCI32 - } -#endif + g_sci->_gfxCompare->kernelSetNowSeen(argv[0]); + return s->r_acc; } reg_t kPalette(EngineState *s, int argc, reg_t *argv) { diff --git a/engines/sci/engine/kgraphics32.cpp b/engines/sci/engine/kgraphics32.cpp index e458109cc2..13a209ea55 100644 --- a/engines/sci/engine/kgraphics32.cpp +++ b/engines/sci/engine/kgraphics32.cpp @@ -37,8 +37,6 @@ #include "sci/graphics/cache.h" #include "sci/graphics/compare.h" #include "sci/graphics/controls16.h" -#include "sci/graphics/coordadjuster.h" -#include "sci/graphics/cursor.h" #include "sci/graphics/palette.h" #include "sci/graphics/paint16.h" #include "sci/graphics/picture.h" @@ -48,6 +46,7 @@ #include "sci/graphics/text16.h" #include "sci/graphics/view.h" #ifdef ENABLE_SCI32 +#include "sci/graphics/cursor32.h" #include "sci/graphics/celobj32.h" #include "sci/graphics/controls32.h" #include "sci/graphics/font.h" // TODO: remove once kBitmap is moved in a separate class @@ -64,6 +63,107 @@ namespace Sci { extern void showScummVMDialog(const Common::String &message); +reg_t kBaseSetter32(EngineState *s, int argc, reg_t *argv) { + reg_t object = argv[0]; + + const GuiResourceId viewId = readSelectorValue(s->_segMan, object, SELECTOR(view)); + const int16 loopNo = readSelectorValue(s->_segMan, object, SELECTOR(loop)); + const int16 celNo = readSelectorValue(s->_segMan, object, SELECTOR(cel)); + const int16 x = readSelectorValue(s->_segMan, object, SELECTOR(x)); + const int16 y = readSelectorValue(s->_segMan, object, SELECTOR(y)); + + CelObjView celObj(viewId, loopNo, celNo); + + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + const Ratio scaleX(scriptWidth, celObj._scaledWidth); + const Ratio scaleY(scriptHeight, celObj._scaledHeight); + + int16 brLeft; + + if (celObj._mirrorX) { + brLeft = x - ((celObj._width - celObj._displace.x) * scaleX).toInt(); + } else { + brLeft = x - (celObj._displace.x * scaleX).toInt(); + } + + const int16 brRight = brLeft + (celObj._width * scaleX).toInt() - 1; + + writeSelectorValue(s->_segMan, object, SELECTOR(brLeft), brLeft); + writeSelectorValue(s->_segMan, object, SELECTOR(brRight), brRight); + writeSelectorValue(s->_segMan, object, SELECTOR(brBottom), y + 1); + writeSelectorValue(s->_segMan, object, SELECTOR(brTop), y + 1 - readSelectorValue(s->_segMan, object, SELECTOR(yStep))); + + return s->r_acc; +} + +reg_t kSetNowSeen32(EngineState *s, int argc, reg_t *argv) { + const bool found = g_sci->_gfxFrameout->kernelSetNowSeen(argv[0]); + + // NOTE: MGDX is assumed to use the older kSetNowSeen since it was + // released before SQ6, but this has not been verified since it cannot be + // disassembled at the moment (Phar Lap Windows-only release) + if (getSciVersion() <= SCI_VERSION_2_1_EARLY || + g_sci->getGameId() == GID_SQ6 || + g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + + if (!found) { + error("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0])); + } + return s->r_acc; + } + + if (!found) { + warning("kSetNowSeen: Unable to find screen item %04x:%04x", PRINT_REG(argv[0])); + } + + return make_reg(0, found); +} + +reg_t kSetCursor32(EngineState *s, int argc, reg_t *argv) { + switch (argc) { + case 1: { + if (argv[0].toSint16() == -2) { + g_sci->_gfxCursor32->clearRestrictedArea(); + } else { + if (argv[0].isNull()) { + g_sci->_gfxCursor32->hide(); + } else { + g_sci->_gfxCursor32->show(); + } + } + break; + } + case 2: { + const Common::Point position(argv[0].toSint16(), argv[1].toSint16()); + g_sci->_gfxCursor32->setPosition(position); + break; + } + case 3: { + g_sci->_gfxCursor32->setView(argv[0].toUint16(), argv[1].toSint16(), argv[2].toSint16()); + break; + } + case 4: { + const Common::Rect restrictRect(argv[0].toSint16(), + argv[1].toSint16(), + argv[2].toSint16() + 1, + argv[3].toSint16() + 1); + g_sci->_gfxCursor32->setRestrictedArea(restrictRect); + break; + } + default: + error("kSetCursor: Invalid number of arguments (%d)", argc); + } + + return s->r_acc; +} + +reg_t kShakeScreen32(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->shakeScreen(argv[0].toSint16(), (ShakeDirection)argv[1].toSint16()); + return s->r_acc; +} + reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv) { const Buffer &buffer = g_sci->_gfxFrameout->getCurrentBuffer(); if (buffer.screenWidth < 640 || buffer.screenHeight < 400) @@ -266,7 +366,7 @@ reg_t kMessageBox(EngineState *s, int argc, reg_t *argv) { * effect */ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { - ShowStyleType type = (ShowStyleType)argv[0].toUint16(); + const uint16 type = argv[0].toUint16(); reg_t planeObj = argv[1]; int16 seconds = argv[2].toSint16(); // NOTE: This value seems to indicate whether the transition is an @@ -301,6 +401,10 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { divisions = argc > 9 ? argv[9].toSint16() : -1; } + if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() != GID_KQ7 && type == 15) || type > 15) { + error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj)); + } + // TODO: Reuse later for SCI2 and SCI3 implementation and then discard // warning("kSetShowStyle: effect %d, plane: %04x:%04x (%s), sec: %d, " // "dir: %d, prio: %d, animate: %d, ref frame: %d, black screen: %d, " @@ -312,7 +416,7 @@ reg_t kSetShowStyle(EngineState *s, int argc, reg_t *argv) { // NOTE: The order of planeObj and showStyle are reversed // because this is how SCI3 called the corresponding method // on the KernelMgr - g_sci->_gfxTransitions32->kernelSetShowStyle(argc, planeObj, type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen); + g_sci->_gfxTransitions32->kernelSetShowStyle(argc, planeObj, (ShowStyleType)type, seconds, back, priority, animate, refFrame, pFadeArray, divisions, blackScreen); return s->r_acc; } diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index c99540967c..448d7bb8a3 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -540,57 +540,46 @@ enum kSciPlatforms { kSciPlatformWindows = 2 }; -enum kPlatformOps { - kPlatformUnk0 = 0, - kPlatformCDSpeed = 1, - kPlatformColorDepth = 2, - kPlatformCDCheck = 3, - kPlatformGetPlatform = 4, - kPlatformUnk5 = 5, - kPlatformIsHiRes = 6, - kPlatformIsItWindows = 7 -}; - reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { + enum Operation { + kPlatformUnknown = 0, + kPlatformGetPlatform = 4, + kPlatformUnknown5 = 5, + kPlatformIsHiRes = 6, + kPlatformWin311OrHigher = 7 + }; + bool isWindows = g_sci->getPlatform() == Common::kPlatformWindows; - if (argc == 0 && getSciVersion() < SCI_VERSION_2) { + if (argc == 0) { // This is called in KQ5CD with no parameters, where it seems to do some // graphics driver check. This kernel function didn't have subfunctions // then. If 0 is returned, the game functions normally, otherwise all // the animations show up like a slideshow (e.g. in the intro). So we - // return 0. However, the behavior changed for kPlatform with no - // parameters in SCI32. + // return 0. return NULL_REG; } + if (g_sci->forceHiresGraphics()) { + // force Windows platform, so that hires-graphics are enabled + isWindows = true; + } + uint16 operation = (argc == 0) ? 0 : argv[0].toUint16(); switch (operation) { - case kPlatformCDSpeed: - // TODO: Returns CD Speed? - warning("STUB: kPlatform(CDSpeed)"); - break; - case kPlatformColorDepth: - // Always returns 2 - return make_reg(0, /* 256-color */ 2); - case kPlatformCDCheck: - // TODO: Some sort of CD check? - warning("STUB: kPlatform(CDCheck)"); - break; - case kPlatformUnk0: + case kPlatformUnknown: // For Mac versions, kPlatform(0) with other args has more functionality if (g_sci->getPlatform() == Common::kPlatformMacintosh && argc > 1) return kMacPlatform(s, argc - 1, argv + 1); // Otherwise, fall through case kPlatformGetPlatform: return make_reg(0, (isWindows) ? kSciPlatformWindows : kSciPlatformDOS); - case kPlatformUnk5: + case kPlatformUnknown5: // This case needs to return the opposite of case 6 to get hires graphics - return make_reg(0, !ConfMan.getBool("enable_high_resolution_graphics")); + return make_reg(0, !isWindows); case kPlatformIsHiRes: - return make_reg(0, ConfMan.getBool("enable_high_resolution_graphics")); - case kPlatformIsItWindows: + case kPlatformWin311OrHigher: return make_reg(0, isWindows); default: error("Unsupported kPlatform operation %d", operation); @@ -599,6 +588,37 @@ reg_t kPlatform(EngineState *s, int argc, reg_t *argv) { return NULL_REG; } +#ifdef ENABLE_SCI32 +reg_t kPlatform32(EngineState *s, int argc, reg_t *argv) { + enum Operation { + kGetPlatform = 0, + kGetCDSpeed = 1, + kGetColorDepth = 2, + kGetCDDrive = 3 + }; + + const Operation operation = argc > 0 ? (Operation)argv[0].toSint16() : kGetPlatform; + + switch (operation) { + case kGetPlatform: + switch (g_sci->getPlatform()) { + case Common::kPlatformDOS: + return make_reg(0, kSciPlatformDOS); + case Common::kPlatformWindows: + return make_reg(0, kSciPlatformWindows); + default: + error("Unknown platform %d", g_sci->getPlatform()); + } + case kGetColorDepth: + return make_reg(0, /* 256 color */ 2); + case kGetCDSpeed: + case kGetCDDrive: + default: + return make_reg(0, 0); + } +} +#endif + reg_t kEmpty(EngineState *s, int argc, reg_t *argv) { // Placeholder for empty kernel functions which are still called from the // engine scripts (like the empty kSetSynonyms function in SCI1.1). This diff --git a/engines/sci/engine/kvideo.cpp b/engines/sci/engine/kvideo.cpp index 86d8a4b817..b539c84f5d 100644 --- a/engines/sci/engine/kvideo.cpp +++ b/engines/sci/engine/kvideo.cpp @@ -72,11 +72,10 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { uint16 y = (screenHeight - height) / 2; bool skipVideo = false; - EngineState *s = g_sci->getEngineState(); if (videoDecoder->hasDirtyPalette()) { - const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3; - g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart); + const byte *palette = videoDecoder->getPalette(); + g_system->getPaletteManager()->setPalette(palette, 0, 255); } while (!g_engine->shouldQuit() && !videoDecoder->endOfVideo() && !skipVideo) { @@ -85,7 +84,7 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { if (frame) { if (scaleBuffer) { - // TODO: Probably should do aspect ratio correction in e.g. GK1 Windows + // TODO: Probably should do aspect ratio correction in KQ6 g_sci->_gfxScreen->scale2x((const byte *)frame->getPixels(), scaleBuffer, videoDecoder->getWidth(), videoDecoder->getHeight(), bytesPerPixel); g_system->copyRectToScreen(scaleBuffer, pitch, x, y, width, height); } else { @@ -93,8 +92,8 @@ void playVideo(Video::VideoDecoder *videoDecoder, VideoState videoState) { } if (videoDecoder->hasDirtyPalette()) { - const byte *palette = videoDecoder->getPalette() + s->_vmdPalStart * 3; - g_system->getPaletteManager()->setPalette(palette, s->_vmdPalStart, s->_vmdPalEnd - s->_vmdPalStart); + const byte *palette = videoDecoder->getPalette(); + g_system->getPaletteManager()->setPalette(palette, 0, 255); } g_system->updateScreen(); @@ -226,6 +225,80 @@ reg_t kShowMovie32(EngineState *s, int argc, reg_t *argv) { return s->r_acc; } +reg_t kRobot(EngineState *s, int argc, reg_t *argv) { + if (!s) + return make_reg(0, getSciVersion()); + error("not supposed to call this"); +} + +reg_t kRobotOpen(EngineState *s, int argc, reg_t *argv) { + const GuiResourceId robotId = argv[0].toUint16(); + const reg_t plane = argv[1]; + const int16 priority = argv[2].toSint16(); + const int16 x = argv[3].toSint16(); + const int16 y = argv[4].toSint16(); + const int16 scale = argc > 5 ? argv[5].toSint16() : 128; + g_sci->_video32->getRobotPlayer().open(robotId, plane, priority, x, y, scale); + return make_reg(0, 0); +} +reg_t kRobotShowFrame(EngineState *s, int argc, reg_t *argv) { + const uint16 frameNo = argv[0].toUint16(); + const uint16 newX = argc > 1 ? argv[1].toUint16() : (uint16)RobotDecoder::kUnspecified; + const uint16 newY = argc > 1 ? argv[2].toUint16() : (uint16)RobotDecoder::kUnspecified; + g_sci->_video32->getRobotPlayer().showFrame(frameNo, newX, newY, RobotDecoder::kUnspecified); + return s->r_acc; +} + +reg_t kRobotGetFrameSize(EngineState *s, int argc, reg_t *argv) { + Common::Rect frameRect; + const uint16 numFramesTotal = g_sci->_video32->getRobotPlayer().getFrameSize(frameRect); + + reg_t *outRect = s->_segMan->derefRegPtr(argv[0], 4); + outRect[0] = make_reg(0, frameRect.left); + outRect[1] = make_reg(0, frameRect.top); + outRect[2] = make_reg(0, frameRect.right - 1); + outRect[3] = make_reg(0, frameRect.bottom - 1); + + return make_reg(0, numFramesTotal); +} + +reg_t kRobotPlay(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().resume(); + return s->r_acc; +} + +reg_t kRobotGetIsFinished(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusEnd); +} + +reg_t kRobotGetIsPlaying(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getStatus() == RobotDecoder::kRobotStatusPlaying); +} + +reg_t kRobotClose(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().close(); + return s->r_acc; +} + +reg_t kRobotGetCue(EngineState *s, int argc, reg_t *argv) { + writeSelectorValue(s->_segMan, argv[0], SELECTOR(signal), g_sci->_video32->getRobotPlayer().getCue()); + return s->r_acc; +} + +reg_t kRobotPause(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().pause(); + return s->r_acc; +} + +reg_t kRobotGetFrameNo(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getRobotPlayer().getFrameNo()); +} + +reg_t kRobotSetPriority(EngineState *s, int argc, reg_t *argv) { + g_sci->_video32->getRobotPlayer().setPriority(argv[0].toSint16()); + return s->r_acc; +} + reg_t kShowMovieWin(EngineState *s, int argc, reg_t *argv) { if (!s) return make_reg(0, getSciVersion()); @@ -352,6 +425,10 @@ reg_t kPlayVMDClose(EngineState *s, int argc, reg_t *argv) { return make_reg(0, g_sci->_video32->getVMDPlayer().close()); } +reg_t kPlayVMDGetStatus(EngineState *s, int argc, reg_t *argv) { + return make_reg(0, g_sci->_video32->getVMDPlayer().getStatus()); +} + reg_t kPlayVMDPlayUntilEvent(EngineState *s, int argc, reg_t *argv) { const VMDPlayer::EventFlags flags = (VMDPlayer::EventFlags)argv[0].toUint16(); const int16 lastFrameNo = argc > 1 ? argv[1].toSint16() : -1; diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 7804b7892d..eeddda8390 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -48,6 +48,7 @@ #include "sci/sound/music.h" #ifdef ENABLE_SCI32 +#include "sci/graphics/cursor32.h" #include "sci/graphics/frameout.h" #include "sci/graphics/palette32.h" #include "sci/graphics/remap32.h" @@ -889,6 +890,29 @@ void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) { _needsUpdate = true; } } + +void GfxCursor32::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 37) { + return; + } + + s.syncAsSint32LE(_hideCount); + s.syncAsSint16LE(_restrictedArea.left); + s.syncAsSint16LE(_restrictedArea.top); + s.syncAsSint16LE(_restrictedArea.right); + s.syncAsSint16LE(_restrictedArea.bottom); + s.syncAsUint16LE(_cursorInfo.resourceId); + s.syncAsUint16LE(_cursorInfo.loopNo); + s.syncAsUint16LE(_cursorInfo.celNo); + + if (s.isLoading()) { + hide(); + setView(_cursorInfo.resourceId, _cursorInfo.loopNo, _cursorInfo.celNo); + if (!_hideCount) { + show(); + } + } +} #endif void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) { diff --git a/engines/sci/engine/savegame.h b/engines/sci/engine/savegame.h index 1bf66864e8..51dbbedd87 100644 --- a/engines/sci/engine/savegame.h +++ b/engines/sci/engine/savegame.h @@ -37,7 +37,7 @@ struct EngineState; * * Version - new/changed feature * ============================= - * 37 - Segment entry data changed to pointers + * 37 - Segment entry data changed to pointers, SCI32 cursor * 36 - SCI32 bitmap segment * 35 - SCI32 remap * 34 - SCI32 palettes, and store play time in ticks diff --git a/engines/sci/engine/seg_manager.h b/engines/sci/engine/seg_manager.h index acebecea97..8ed1c3a143 100644 --- a/engines/sci/engine/seg_manager.h +++ b/engines/sci/engine/seg_manager.h @@ -30,8 +30,7 @@ #include "sci/engine/vm_types.h" #include "sci/engine/segment.h" #ifdef ENABLE_SCI32 -// TODO: Baaaad? -#include "sci/graphics/celobj32.h" +#include "sci/graphics/celobj32.h" // kLowResX, kLowResY #endif namespace Sci { diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 2c85907628..a338beffc9 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -121,9 +121,6 @@ void EngineState::reset(bool isRestoring) { _videoState.reset(); _syncedAudioOptions = false; - - _vmdPalStart = 0; - _vmdPalEnd = 256; } void EngineState::speedThrottler(uint32 neededSleep) { diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index dd8d76f002..baa912b60e 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -203,7 +203,6 @@ public: // TODO: Excise video code from the state manager VideoState _videoState; - uint16 _vmdPalStart, _vmdPalEnd; bool _syncedAudioOptions; /** diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index 9b3b329418..bc1390864e 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -329,7 +329,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_PEPPER, -1, 894, 0, "Package", "doVerb", NULL, 3, { WORKAROUND_FAKE, 0 } }, // using the hand on the book in the inventory - bug #5154 { GID_PEPPER, 150, 928, 0, "Narrator", "startText", NULL, 0, { WORKAROUND_FAKE, 0 } }, // happens during the non-interactive demo of Pepper { GID_PQ4, -1, 25, 0, "iconToggle", "select", NULL, 1, { WORKAROUND_FAKE, 0 } }, // when toggling the icon bar to auto-hide or not - { GID_PQSWAT, -1, 64950, 0, "View", "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using the menu in the beginning + { GID_PQSWAT, -1, 64950, 0, NULL, "handleEvent", NULL, 0, { WORKAROUND_FAKE, 0 } }, // Using any menus in-game { GID_QFG1, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1/hq1: going to the brigands hideout { GID_QFG1VGA, 16, 16, 0, "lassoFailed", "changeState", NULL, -1, { WORKAROUND_FAKE, 0 } }, // qfg1vga: casting the "fetch" spell in the screen with the flowers, temps 0 and 1 - bug #5309 { GID_QFG1VGA, -1, 210, 0, "Encounter", "init", sig_uninitread_qfg1vga_1, 0, { WORKAROUND_FAKE, 0 } }, // qfg1vga: going to the brigands hideout - bug #5515 @@ -688,6 +688,7 @@ const SciWorkaroundEntry kPaletteUnsetFlag_workarounds[] = { // gameID, room,script,lvl, object-name, method-name, local-call-signature, index, workaround const SciWorkaroundEntry kSetCursor_workarounds[] = { { GID_KQ5, -1, 768, 0, "KQCursor", "init", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // CD: gets called with 4 additional "900d" parameters + { GID_MOTHERGOOSEHIRES,0, 0, -1, "MG", "setCursor", NULL, 0, { WORKAROUND_STILLCALL, 0 } }, // At the start of the game, an object is passed as the cel number SCI_WORKAROUNDENTRY_TERMINATOR }; diff --git a/engines/sci/event.cpp b/engines/sci/event.cpp index 4ad2a0cfa3..b267d2ebc2 100644 --- a/engines/sci/event.cpp +++ b/engines/sci/event.cpp @@ -30,6 +30,7 @@ #include "sci/engine/state.h" #include "sci/engine/kernel.h" #ifdef ENABLE_SCI32 +#include "sci/graphics/cursor32.h" #include "sci/graphics/frameout.h" #endif #include "sci/graphics/screen.h" @@ -168,9 +169,17 @@ SciEvent EventManager::getScummVMEvent() { if (getSciVersion() >= SCI_VERSION_2) { const Buffer &screen = g_sci->_gfxFrameout->getCurrentBuffer(); + if (ev.type == Common::EVENT_MOUSEMOVE) { + // This will clamp `mousePos` according to the restricted zone, + // so any cursor or screen item associated with the mouse position + // does not bounce when it hits the edge (or ignore the edge) + g_sci->_gfxCursor32->deviceMoved(mousePos); + } + Common::Point mousePosSci = mousePos; mulru(mousePosSci, Ratio(screen.scriptWidth, screen.screenWidth), Ratio(screen.scriptHeight, screen.screenHeight)); noEvent.mousePosSci = input.mousePosSci = mousePosSci; + } else { #endif g_sci->_gfxScreen->adjustBackUpscaledCoordinates(mousePos.y, mousePos.x); diff --git a/engines/sci/graphics/cache.cpp b/engines/sci/graphics/cache.cpp index fb1f557ad6..9c77f31a14 100644 --- a/engines/sci/graphics/cache.cpp +++ b/engines/sci/graphics/cache.cpp @@ -95,10 +95,20 @@ int16 GfxCache::kernelViewGetCelHeight(GuiResourceId viewId, int16 loopNo, int16 } int16 GfxCache::kernelViewGetLoopCount(GuiResourceId viewId) { +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + return CelObjView::getNumLoops(viewId); + } +#endif return getView(viewId)->getLoopCount(); } int16 GfxCache::kernelViewGetCelCount(GuiResourceId viewId, int16 loopNo) { +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + return CelObjView::getNumCels(viewId, loopNo); + } +#endif return getView(viewId)->getCelCount(loopNo); } diff --git a/engines/sci/graphics/celobj32.cpp b/engines/sci/graphics/celobj32.cpp index 311684d595..d053fa2eef 100644 --- a/engines/sci/graphics/celobj32.cpp +++ b/engines/sci/graphics/celobj32.cpp @@ -45,7 +45,7 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { } } - int i = 1 - _activeIndex; + const int i = 1 - _activeIndex; _activeIndex = i; CelScalerTable &table = _scaleTables[i]; @@ -65,7 +65,7 @@ void CelScaler::activateScaleTables(const Ratio &scaleX, const Ratio &scaleY) { void CelScaler::buildLookupTable(int *table, const Ratio &ratio, const int size) { int value = 0; int remainder = 0; - int num = ratio.getNumerator(); + const int num = ratio.getNumerator(); for (int i = 0; i < size; ++i) { *table++ = value; remainder += ratio.getDenominator(); @@ -164,8 +164,8 @@ struct SCALER_Scale { const byte *_row; READER _reader; int16 _x; - static int16 _valuesX[1024]; - static int16 _valuesY[1024]; + static int16 _valuesX[4096]; + static int16 _valuesY[4096]; SCALER_Scale(const CelObj &celObj, const Common::Rect &targetRect, const Common::Point &scaledPosition, const Ratio scaleX, const Ratio scaleY) : _row(nullptr), @@ -204,7 +204,7 @@ struct SCALER_Scale { if (g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth == kLowResX) { const int16 unscaledX = (scaledPosition.x / scaleX).toInt(); if (FLIP) { - int lastIndex = celObj._width - 1; + const int lastIndex = celObj._width - 1; for (int16 x = targetRect.left; x < targetRect.right; ++x) { _valuesX[x] = lastIndex - (table->valuesX[x] - unscaledX); } @@ -220,7 +220,7 @@ struct SCALER_Scale { } } else { if (FLIP) { - int lastIndex = celObj._width - 1; + const int lastIndex = celObj._width - 1; for (int16 x = 0; x < targetRect.width(); ++x) { _valuesX[targetRect.left + x] = lastIndex - table->valuesX[x]; } @@ -249,9 +249,9 @@ struct SCALER_Scale { }; template<bool FLIP, typename READER> -int16 SCALER_Scale<FLIP, READER>::_valuesX[1024]; +int16 SCALER_Scale<FLIP, READER>::_valuesX[4096]; template<bool FLIP, typename READER> -int16 SCALER_Scale<FLIP, READER>::_valuesY[1024]; +int16 SCALER_Scale<FLIP, READER>::_valuesY[4096]; #pragma mark - #pragma mark CelObj - Resource readers @@ -261,7 +261,7 @@ private: #ifndef NDEBUG const int16 _sourceHeight; #endif - byte *_pixels; + const byte *_pixels; const int16 _sourceWidth; public: @@ -270,7 +270,7 @@ public: _sourceHeight(celObj._height), #endif _sourceWidth(celObj._width) { - byte *resource = celObj.getResPointer(); + const byte *resource = celObj.getResPointer(); _pixels = resource + READ_SCI11ENDIAN_UINT32(resource + celObj._celHeaderOffset + 24); } @@ -282,8 +282,8 @@ public: struct READER_Compressed { private: - byte *_resource; - byte _buffer[1024]; + const byte *const _resource; + byte _buffer[4096]; uint32 _controlOffset; uint32 _dataOffset; uint32 _uncompressedDataOffset; @@ -301,7 +301,7 @@ public: _maxWidth(maxWidth) { assert(maxWidth <= celObj._width); - byte *celHeader = _resource + celObj._celHeaderOffset; + const byte *const celHeader = _resource + celObj._celHeaderOffset; _dataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 24); _uncompressedDataOffset = READ_SCI11ENDIAN_UINT32(celHeader + 28); _controlOffset = READ_SCI11ENDIAN_UINT32(celHeader + 32); @@ -311,14 +311,14 @@ public: assert(y >= 0 && y < _sourceHeight); if (y != _y) { // compressed data segment for row - byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4); + const byte *row = _resource + _dataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + y * 4); // uncompressed data segment for row - byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4); + const byte *literal = _resource + _uncompressedDataOffset + READ_SCI11ENDIAN_UINT32(_resource + _controlOffset + _sourceHeight * 4 + y * 4); uint8 length; for (int16 i = 0; i < _maxWidth; i += length) { - byte controlByte = *row++; + const byte controlByte = *row++; length = controlByte; // Run-length encoded @@ -581,7 +581,7 @@ void CelObj::submitPalette() const { int CelObj::_nextCacheId = 1; CelCache *CelObj::_cache = nullptr; -int CelObj::searchCache(const CelInfo32 &celInfo, int *nextInsertIndex) const { +int CelObj::searchCache(const CelInfo32 &celInfo, int *const nextInsertIndex) const { *nextInsertIndex = -1; int oldestId = _nextCacheId + 1; int oldestIndex = 0; @@ -791,6 +791,49 @@ void CelObj::scaleDrawUncompNoMD(Buffer &target, const Ratio &scaleX, const Rati #pragma mark - #pragma mark CelObjView +int16 CelObjView::getNumLoops(const GuiResourceId viewId) { + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); + + if (!resource) { + return 0; + } + + assert(resource->size >= 3); + return resource->data[2]; +} + +int16 CelObjView::getNumCels(const GuiResourceId viewId, const int16 loopNo) { + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); + + if (!resource) { + return 0; + } + + const byte *const data = resource->data; + + const uint16 loopCount = data[2]; + if (loopNo >= loopCount || loopNo < 0) { + return 0; + } + + const uint16 viewHeaderSize = READ_SCI11ENDIAN_UINT16(data); + const uint8 loopHeaderSize = data[12]; + const uint8 viewHeaderFieldSize = 2; + +#ifndef NDEBUG + const byte *const dataMax = data + resource->size; +#endif + const byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * loopNo); + assert(loopHeader + 3 <= dataMax); + + if ((int8)loopHeader[0] != -1) { + loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * (int8)loopHeader[0]); + assert(loopHeader >= data && loopHeader + 3 <= dataMax); + } + + return loopHeader[2]; +} + CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) { _info.type = kCelTypeView; _info.resourceId = viewId; @@ -801,7 +844,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int _transparent = true; int cacheInsertIndex; - int cacheIndex = searchCache(_info, &cacheInsertIndex); + const int cacheIndex = searchCache(_info, &cacheInsertIndex); if (cacheIndex != -1) { CelCacheEntry &entry = (*_cache)[cacheIndex]; const CelObjView *const cachedCelObj = dynamic_cast<CelObjView *>(entry.celObj); @@ -817,15 +860,14 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int // generates view resource metadata for both SCI16 and SCI32 // implementations - Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, viewId), false); // NOTE: SCI2.1/SQ6 just silently returns here. if (!resource) { - warning("View resource %d not loaded", viewId); - return; + error("View resource %d not found", viewId); } - byte *data = resource->data; + const byte *const data = resource->data; _scaledWidth = READ_SCI11ENDIAN_UINT16(data + 14); _scaledHeight = READ_SCI11ENDIAN_UINT16(data + 16); @@ -844,7 +886,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int } } - uint16 loopCount = data[2]; + const uint16 loopCount = data[2]; if (_info.loopNo >= loopCount) { _info.loopNo = loopCount - 1; } @@ -859,7 +901,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int const uint8 loopHeaderSize = data[12]; const uint8 viewHeaderFieldSize = 2; - byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo); + const byte *loopHeader = data + viewHeaderFieldSize + viewHeaderSize + (loopHeaderSize * _info.loopNo); if ((int8)loopHeader[0] != -1) { if (loopHeader[1] == 1) { @@ -874,10 +916,14 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int _info.celNo = celCount - 1; } + if (_info.celNo < 0) { + error("Cel is less than 0!"); + } + _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 8); _celHeaderOffset = READ_SCI11ENDIAN_UINT32(loopHeader + 12) + (data[13] * _info.celNo); - byte *celHeader = data + _celHeaderOffset; + const byte *const celHeader = data + _celHeaderOffset; _width = READ_SCI11ENDIAN_UINT16(celHeader); _height = READ_SCI11ENDIAN_UINT16(celHeader + 2); @@ -906,7 +952,7 @@ CelObjView::CelObjView(const GuiResourceId viewId, const int16 loopNo, const int } bool CelObjView::analyzeUncompressedForRemap() const { - byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); + const byte *pixels = getResPointer() + READ_SCI11ENDIAN_UINT32(getResPointer() + _celHeaderOffset + 24); for (int i = 0; i < _width * _height; ++i) { const byte pixel = pixels[i]; if ( @@ -923,7 +969,7 @@ bool CelObjView::analyzeUncompressedForRemap() const { bool CelObjView::analyzeForRemap() const { READER_Compressed reader(*this, _width); for (int y = 0; y < _height; y++) { - const byte *curRow = reader.getRow(y); + const byte *const curRow = reader.getRow(y); for (int x = 0; x < _width; x++) { const byte pixel = curRow[x]; if ( @@ -948,7 +994,7 @@ CelObjView *CelObjView::duplicate() const { } byte *CelObjView::getResPointer() const { - const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false); + Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypeView, _info.resourceId), false); if (resource == nullptr) { error("Failed to load view %d from resource manager", _info.resourceId); } @@ -969,7 +1015,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _remap = false; int cacheInsertIndex; - int cacheIndex = searchCache(_info, &cacheInsertIndex); + const int cacheIndex = searchCache(_info, &cacheInsertIndex); if (cacheIndex != -1) { CelCacheEntry &entry = (*_cache)[cacheIndex]; const CelObjPic *const cachedCelObj = dynamic_cast<CelObjPic *>(entry.celObj); @@ -981,15 +1027,14 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { return; } - Resource *resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false); + const Resource *const resource = g_sci->getResMan()->findResource(ResourceId(kResourceTypePic, picId), false); // NOTE: SCI2.1/SQ6 just silently returns here. if (!resource) { - warning("Pic resource %d not loaded", picId); - return; + error("Pic resource %d not found", picId); } - byte *data = resource->data; + const byte *const data = resource->data; _celCount = data[2]; @@ -1000,7 +1045,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _celHeaderOffset = READ_SCI11ENDIAN_UINT16(data) + (READ_SCI11ENDIAN_UINT16(data + 4) * _info.celNo); _hunkPaletteOffset = READ_SCI11ENDIAN_UINT32(data + 6); - byte *celHeader = data + _celHeaderOffset; + const byte *const celHeader = data + _celHeaderOffset; _width = READ_SCI11ENDIAN_UINT16(celHeader); _height = READ_SCI11ENDIAN_UINT16(celHeader + 2); @@ -1012,8 +1057,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { _relativePosition.x = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 38); _relativePosition.y = (int16)READ_SCI11ENDIAN_UINT16(celHeader + 40); - uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10); - uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12); + const uint16 sizeFlag1 = READ_SCI11ENDIAN_UINT16(data + 10); + const uint16 sizeFlag2 = READ_SCI11ENDIAN_UINT16(data + 12); if (sizeFlag2) { _scaledWidth = sizeFlag1; @@ -1032,7 +1077,7 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { if (celHeader[10] & 128) { // NOTE: This is correct according to SCI2.1/SQ6/DOS; // the engine re-reads the byte value as a word value - uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10); + const uint16 flags = READ_SCI11ENDIAN_UINT16(celHeader + 10); _transparent = flags & 1 ? true : false; _remap = flags & 2 ? true : false; } else { @@ -1047,8 +1092,8 @@ CelObjPic::CelObjPic(const GuiResourceId picId, const int16 celNo) { } bool CelObjPic::analyzeUncompressedForSkip() const { - byte *resource = getResPointer(); - byte *pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24); + const byte *const resource = getResPointer(); + const byte *const pixels = resource + READ_SCI11ENDIAN_UINT32(resource + _celHeaderOffset + 24); for (int i = 0; i < _width * _height; ++i) { uint8 pixel = pixels[i]; if (pixel == _transparentColor) { @@ -1060,7 +1105,7 @@ bool CelObjPic::analyzeUncompressedForSkip() const { } void CelObjPic::draw(Buffer &target, const Common::Rect &targetRect, const Common::Point &scaledPosition, const bool mirrorX) { - Ratio square; + const Ratio square; _drawMirrored = mirrorX; drawTo(target, targetRect, scaledPosition, square, square); } @@ -1088,15 +1133,21 @@ CelObjMem::CelObjMem(const reg_t bitmapObject) { _celHeaderOffset = 0; _transparent = true; - SciBitmap &bitmap = *g_sci->getEngineState()->_segMan->lookupBitmap(bitmapObject); - _width = bitmap.getWidth(); - _height = bitmap.getHeight(); - _displace = bitmap.getDisplace(); - _transparentColor = bitmap.getSkipColor(); - _scaledWidth = bitmap.getScaledWidth(); - _scaledHeight = bitmap.getScaledHeight(); - _hunkPaletteOffset = bitmap.getHunkPaletteOffset(); - _remap = bitmap.getRemap(); + SciBitmap *bitmap = g_sci->getEngineState()->_segMan->lookupBitmap(bitmapObject); + + // NOTE: SSCI did no error checking here at all. + if (!bitmap) { + error("Bitmap %04x:%04x not found", PRINT_REG(bitmapObject)); + } + + _width = bitmap->getWidth(); + _height = bitmap->getHeight(); + _displace = bitmap->getDisplace(); + _transparentColor = bitmap->getSkipColor(); + _scaledWidth = bitmap->getScaledWidth(); + _scaledHeight = bitmap->getScaledHeight(); + _hunkPaletteOffset = bitmap->getHunkPaletteOffset(); + _remap = bitmap->getRemap(); } CelObjMem *CelObjMem::duplicate() const { diff --git a/engines/sci/graphics/celobj32.h b/engines/sci/graphics/celobj32.h index eb6ce3a3c9..21e86d03e0 100644 --- a/engines/sci/graphics/celobj32.h +++ b/engines/sci/graphics/celobj32.h @@ -147,7 +147,7 @@ struct CelScalerTable { * the correct column to read from the source bitmap * when drawing a scaled version of the source bitmap. */ - int valuesX[1024]; + int valuesX[4096]; /** * The ratio used to generate the x-values. @@ -159,7 +159,7 @@ struct CelScalerTable { * the correct row to read from a source bitmap when * drawing a scaled version of the source bitmap. */ - int valuesY[1024]; + int valuesY[4096]; /** * The ratio used to generate the y-values. @@ -400,7 +400,7 @@ public: * Reads the pixel at the given coordinates. This method * is valid only for CelObjView and CelObjPic. */ - virtual uint8 readPixel(uint16 x, uint16 y, bool mirrorX) const; + virtual uint8 readPixel(const uint16 x, const uint16 y, const bool mirrorX) const; /** * Submits the palette from this cel to the palette @@ -505,6 +505,9 @@ public: using CelObj::draw; + static int16 getNumLoops(const GuiResourceId viewId); + static int16 getNumCels(const GuiResourceId viewId, const int16 loopNo); + /** * Draws the cel to the target buffer using the * positioning, mirroring, and scaling information from diff --git a/engines/sci/graphics/compare.cpp b/engines/sci/graphics/compare.cpp index 130416ff60..36026a8134 100644 --- a/engines/sci/graphics/compare.cpp +++ b/engines/sci/graphics/compare.cpp @@ -37,7 +37,7 @@ namespace Sci { -GfxCompare::GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster *coordAdjuster) +GfxCompare::GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster16 *coordAdjuster) : _segMan(segMan), _cache(cache), _screen(screen), _coordAdjuster(coordAdjuster) { } diff --git a/engines/sci/graphics/compare.h b/engines/sci/graphics/compare.h index c7005980d0..dd65b90bea 100644 --- a/engines/sci/graphics/compare.h +++ b/engines/sci/graphics/compare.h @@ -34,7 +34,7 @@ class Screen; */ class GfxCompare { public: - GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster *coordAdjuster); + GfxCompare(SegManager *segMan, GfxCache *cache, GfxScreen *screen, GfxCoordAdjuster16 *coordAdjuster); ~GfxCompare(); uint16 kernelOnControl(byte screenMask, const Common::Rect &rect); @@ -50,7 +50,7 @@ private: SegManager *_segMan; GfxCache *_cache; GfxScreen *_screen; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; uint16 isOnControl(uint16 screenMask, const Common::Rect &rect); diff --git a/engines/sci/graphics/coordadjuster.cpp b/engines/sci/graphics/coordadjuster.cpp index 93dff10382..2f22d191d0 100644 --- a/engines/sci/graphics/coordadjuster.cpp +++ b/engines/sci/graphics/coordadjuster.cpp @@ -32,9 +32,6 @@ namespace Sci { -GfxCoordAdjuster::GfxCoordAdjuster() { -} - GfxCoordAdjuster16::GfxCoordAdjuster16(GfxPorts *ports) : _ports(ports) { } @@ -83,53 +80,4 @@ Common::Rect GfxCoordAdjuster16::pictureGetDisplayArea() { return displayArea; } -#ifdef ENABLE_SCI32 -GfxCoordAdjuster32::GfxCoordAdjuster32(SegManager *segMan) - : _segMan(segMan) { - _scriptsRunningWidth = 0; - _scriptsRunningHeight = 0; -} - -GfxCoordAdjuster32::~GfxCoordAdjuster32() { -} - -void GfxCoordAdjuster32::kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject) { - uint16 planeTop = readSelectorValue(_segMan, planeObject, SELECTOR(top)); - uint16 planeLeft = readSelectorValue(_segMan, planeObject, SELECTOR(left)); - - y -= planeTop; - x -= planeLeft; -} -void GfxCoordAdjuster32::kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject) { - uint16 planeTop = readSelectorValue(_segMan, planeObject, SELECTOR(top)); - uint16 planeLeft = readSelectorValue(_segMan, planeObject, SELECTOR(left)); - - x += planeLeft; - y += planeTop; -} - -void GfxCoordAdjuster32::setScriptsResolution(uint16 width, uint16 height) { - _scriptsRunningWidth = width; - _scriptsRunningHeight = height; -} - -void GfxCoordAdjuster32::fromDisplayToScript(int16 &y, int16 &x) { - y = ((y * _scriptsRunningHeight) / g_sci->_gfxScreen->getHeight()); - x = ((x * _scriptsRunningWidth) / g_sci->_gfxScreen->getWidth()); -} - -void GfxCoordAdjuster32::fromScriptToDisplay(int16 &y, int16 &x) { - y = ((y * g_sci->_gfxScreen->getHeight()) / _scriptsRunningHeight); - x = ((x * g_sci->_gfxScreen->getWidth()) / _scriptsRunningWidth); -} - -void GfxCoordAdjuster32::pictureSetDisplayArea(Common::Rect displayArea) { - _pictureDisplayArea = displayArea; -} - -Common::Rect GfxCoordAdjuster32::pictureGetDisplayArea() { - return _pictureDisplayArea; -} -#endif - } // End of namespace Sci diff --git a/engines/sci/graphics/coordadjuster.h b/engines/sci/graphics/coordadjuster.h index cb0227fbe4..f7ebd3ec75 100644 --- a/engines/sci/graphics/coordadjuster.h +++ b/engines/sci/graphics/coordadjuster.h @@ -35,27 +35,7 @@ class GfxPorts; * most of the time sci32 doesn't do any coordinate adjustment at all * sci16 does a lot of port adjustment on given coordinates */ -class GfxCoordAdjuster { -public: - GfxCoordAdjuster(); - virtual ~GfxCoordAdjuster() { } - - virtual void kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject = NULL_REG) { } - virtual void kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject = NULL_REG) { } - - virtual Common::Rect onControl(Common::Rect rect) { return rect; } - virtual void setCursorPos(Common::Point &pos) { } - virtual void moveCursor(Common::Point &pos) { } - - virtual void setScriptsResolution(uint16 width, uint16 height) { } - virtual void fromScriptToDisplay(int16 &y, int16 &x) { } - virtual void fromDisplayToScript(int16 &y, int16 &x) { } - - virtual Common::Rect pictureGetDisplayArea() { return Common::Rect(0, 0); } -private: -}; - -class GfxCoordAdjuster16 : public GfxCoordAdjuster { +class GfxCoordAdjuster16 { public: GfxCoordAdjuster16(GfxPorts *ports); ~GfxCoordAdjuster16(); @@ -73,32 +53,6 @@ private: GfxPorts *_ports; }; -#ifdef ENABLE_SCI32 -class GfxCoordAdjuster32 : public GfxCoordAdjuster { -public: - GfxCoordAdjuster32(SegManager *segMan); - ~GfxCoordAdjuster32(); - - void kernelGlobalToLocal(int16 &x, int16 &y, reg_t planeObject = NULL_REG); - void kernelLocalToGlobal(int16 &x, int16 &y, reg_t planeObject = NULL_REG); - - void setScriptsResolution(uint16 width, uint16 height); - void fromScriptToDisplay(int16 &y, int16 &x); - void fromDisplayToScript(int16 &y, int16 &x); - - void pictureSetDisplayArea(Common::Rect displayArea); - Common::Rect pictureGetDisplayArea(); - -private: - SegManager *_segMan; - - Common::Rect _pictureDisplayArea; - - uint16 _scriptsRunningWidth; - uint16 _scriptsRunningHeight; -}; -#endif - } // End of namespace Sci #endif diff --git a/engines/sci/graphics/cursor.cpp b/engines/sci/graphics/cursor.cpp index f5dd473959..c3229121c8 100644 --- a/engines/sci/graphics/cursor.cpp +++ b/engines/sci/graphics/cursor.cpp @@ -80,7 +80,7 @@ GfxCursor::~GfxCursor() { kernelClearZoomZone(); } -void GfxCursor::init(GfxCoordAdjuster *coordAdjuster, EventManager *event) { +void GfxCursor::init(GfxCoordAdjuster16 *coordAdjuster, EventManager *event) { _coordAdjuster = coordAdjuster; _event = event; } diff --git a/engines/sci/graphics/cursor.h b/engines/sci/graphics/cursor.h index 5125469cfe..c57d9dab52 100644 --- a/engines/sci/graphics/cursor.h +++ b/engines/sci/graphics/cursor.h @@ -55,7 +55,7 @@ public: GfxCursor(ResourceManager *resMan, GfxPalette *palette, GfxScreen *screen); ~GfxCursor(); - void init(GfxCoordAdjuster *coordAdjuster, EventManager *event); + void init(GfxCoordAdjuster16 *coordAdjuster, EventManager *event); void kernelShow(); void kernelHide(); @@ -103,7 +103,7 @@ private: ResourceManager *_resMan; GfxScreen *_screen; GfxPalette *_palette; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; EventManager *_event; int _upscaledHires; diff --git a/engines/sci/graphics/cursor32.cpp b/engines/sci/graphics/cursor32.cpp new file mode 100644 index 0000000000..014b617c74 --- /dev/null +++ b/engines/sci/graphics/cursor32.cpp @@ -0,0 +1,396 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "common/rational.h" // for Rational, operator* +#include "common/system.h" // for OSystem, g_system +#include "graphics/cursorman.h" // for CursorMan +#include "sci/graphics/celobj32.h" // for CelObjView, CelInfo32, Ratio +#include "sci/graphics/cursor32.h" +#include "sci/graphics/frameout.h" // for GfxFrameout + +namespace Sci { + +GfxCursor32::GfxCursor32() : + _hideCount(0), + _position(0, 0), + _writeToVMAP(false) { + CursorMan.showMouse(false); +} + +void GfxCursor32::init(const Buffer &vmap) { + _vmap = vmap; + _vmapRegion.rect = Common::Rect(_vmap.screenWidth, _vmap.screenHeight); + _vmapRegion.data = (byte *)_vmap.getPixels(); + _restrictedArea = _vmapRegion.rect; +} + +GfxCursor32::~GfxCursor32() { + CursorMan.showMouse(true); + free(_cursor.data); + free(_cursorBack.data); + free(_drawBuff1.data); + free(_drawBuff2.data); + free(_savedVmapRegion.data); +} + +void GfxCursor32::hide() { + if (_hideCount++) { + return; + } + + if (!_cursorBack.rect.isEmpty()) { + drawToHardware(_cursorBack); + } +} + +void GfxCursor32::revealCursor() { + _cursorBack.rect = _cursor.rect; + _cursorBack.rect.clip(_vmapRegion.rect); + if (_cursorBack.rect.isEmpty()) { + return; + } + + readVideo(_cursorBack); + _drawBuff1.rect = _cursor.rect; + copy(_drawBuff1, _cursorBack); + paint(_drawBuff1, _cursor); + drawToHardware(_drawBuff1); +} + +void GfxCursor32::paint(DrawRegion &target, const DrawRegion &source) { + if (source.rect.isEmpty()) { + return; + } + + Common::Rect drawRect(source.rect); + drawRect.clip(target.rect); + if (drawRect.isEmpty()) { + return; + } + + const int16 sourceXOffset = drawRect.left - source.rect.left; + const int16 sourceYOffset = drawRect.top - source.rect.top; + const int16 drawRectWidth = drawRect.width(); + const int16 drawRectHeight = drawRect.height(); + + byte *targetPixel = target.data + ((drawRect.top - target.rect.top) * target.rect.width()) + (drawRect.left - target.rect.left); + const byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset; + const uint8 skipColor = source.skipColor; + + const int16 sourceStride = source.rect.width() - drawRectWidth; + const int16 targetStride = target.rect.width() - drawRectWidth; + + for (int16 y = 0; y < drawRectHeight; ++y) { + for (int16 x = 0; x < drawRectWidth; ++x) { + if (*sourcePixel != skipColor) { + *targetPixel = *sourcePixel; + } + ++targetPixel; + ++sourcePixel; + } + sourcePixel += sourceStride; + targetPixel += targetStride; + } +} + +void GfxCursor32::drawToHardware(const DrawRegion &source) { + Common::Rect drawRect(source.rect); + drawRect.clip(_vmapRegion.rect); + const int16 sourceXOffset = drawRect.left - source.rect.left; + const int16 sourceYOffset = drawRect.top - source.rect.top; + byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset; + + g_system->copyRectToScreen(sourcePixel, source.rect.width(), drawRect.left, drawRect.top, drawRect.width(), drawRect.height()); +} + +void GfxCursor32::unhide() { + if (_hideCount == 0 || --_hideCount) { + return; + } + + _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y); + revealCursor(); +} + +void GfxCursor32::show() { + if (_hideCount) { + _hideCount = 0; + _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y); + revealCursor(); + } +} + +void GfxCursor32::setRestrictedArea(const Common::Rect &rect) { + _restrictedArea = rect; + + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + + mulru(_restrictedArea, Ratio(screenWidth, scriptWidth), Ratio(screenHeight, scriptHeight), 0); + + if (_position.x < rect.left) { + _position.x = rect.left; + } + if (_position.x >= rect.right) { + _position.x = rect.right - 1; + } + if (_position.y < rect.top) { + _position.y = rect.top; + } + if (_position.y >= rect.bottom) { + _position.y = rect.bottom - 1; + } + + g_system->warpMouse(_position.x, _position.y); +} + +void GfxCursor32::clearRestrictedArea() { + _restrictedArea = _vmapRegion.rect; +} + +void GfxCursor32::setView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo) { + hide(); + + _cursorInfo.resourceId = viewId; + _cursorInfo.loopNo = loopNo; + _cursorInfo.celNo = celNo; + + if (viewId != -1) { + CelObjView view(viewId, loopNo, celNo); + + _hotSpot = view._displace; + _width = view._width; + _height = view._height; + + // SSCI never increased the size of cursors, but some of the cursors + // in early SCI32 games were designed for low-resolution display mode + // and so are kind of hard to pick out when running in high-resolution + // mode. + // To address this, we make some slight adjustments to cursor display + // in these early games: + // GK1: All the cursors are increased in size since they all appear to + // be designed for low-res display. + // PQ4: We only make the cursors bigger if they are above a set + // threshold size because inventory items usually have a + // high-resolution cursor representation. + bool pixelDouble = false; + if (g_sci->_gfxFrameout->_isHiRes && + (g_sci->getGameId() == GID_GK1 || + (g_sci->getGameId() == GID_PQ4 && _width <= 22 && _height <= 22))) { + + _width *= 2; + _height *= 2; + _hotSpot.x *= 2; + _hotSpot.y *= 2; + pixelDouble = true; + } + + _cursor.data = (byte *)realloc(_cursor.data, _width * _height); + _cursor.rect = Common::Rect(_width, _height); + memset(_cursor.data, 255, _width * _height); + _cursor.skipColor = 255; + + Buffer target(_width, _height, _cursor.data); + if (pixelDouble) { + view.draw(target, _cursor.rect, Common::Point(0, 0), false, 2, 2); + } else { + view.draw(target, _cursor.rect, Common::Point(0, 0), false); + } + } else { + _hotSpot = Common::Point(0, 0); + _width = _height = 1; + _cursor.data = (byte *)realloc(_cursor.data, _width * _height); + _cursor.rect = Common::Rect(_width, _height); + *_cursor.data = _cursor.skipColor; + _cursorBack.rect = _cursor.rect; + _cursorBack.rect.clip(_vmapRegion.rect); + if (!_cursorBack.rect.isEmpty()) { + readVideo(_cursorBack); + } + } + + _cursorBack.data = (byte *)realloc(_cursorBack.data, _width * _height); + _drawBuff1.data = (byte *)realloc(_drawBuff1.data, _width * _height); + _drawBuff2.data = (byte *)realloc(_drawBuff2.data, _width * _height * 4); + _savedVmapRegion.data = (byte *)realloc(_savedVmapRegion.data, _width * _height); + + unhide(); +} + +void GfxCursor32::readVideo(DrawRegion &target) { + if (g_sci->_gfxFrameout->_frameNowVisible) { + copy(target, _vmapRegion); + } else { + // NOTE: SSCI would read the background for the cursor directly out of + // video memory here, but as far as can be determined, this does not + // seem to actually be necessary for proper cursor rendering + } +} + +void GfxCursor32::copy(DrawRegion &target, const DrawRegion &source) { + if (source.rect.isEmpty()) { + return; + } + + Common::Rect drawRect(source.rect); + drawRect.clip(target.rect); + if (drawRect.isEmpty()) { + return; + } + + const int16 sourceXOffset = drawRect.left - source.rect.left; + const int16 sourceYOffset = drawRect.top - source.rect.top; + const int16 drawWidth = drawRect.width(); + const int16 drawHeight = drawRect.height(); + + byte *targetPixel = target.data + ((drawRect.top - target.rect.top) * target.rect.width()) + (drawRect.left - target.rect.left); + const byte *sourcePixel = source.data + (sourceYOffset * source.rect.width()) + sourceXOffset; + + const int16 sourceStride = source.rect.width(); + const int16 targetStride = target.rect.width(); + + for (int y = 0; y < drawHeight; ++y) { + memcpy(targetPixel, sourcePixel, drawWidth); + targetPixel += targetStride; + sourcePixel += sourceStride; + } +} + +void GfxCursor32::setPosition(const Common::Point &position) { + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + _position.x = (position.x * Ratio(screenWidth, scriptWidth)).toInt(); + _position.y = (position.y * Ratio(screenHeight, scriptHeight)).toInt(); + + g_system->warpMouse(_position.x, _position.y); +} + +void GfxCursor32::gonnaPaint(Common::Rect paintRect) { + if (!_hideCount && !_writeToVMAP && !_cursorBack.rect.isEmpty()) { + paintRect.left &= ~3; + paintRect.right |= 3; + if (_cursorBack.rect.intersects(paintRect)) { + _writeToVMAP = true; + } + } +} + +void GfxCursor32::paintStarting() { + if (_writeToVMAP) { + _savedVmapRegion.rect = _cursor.rect; + copy(_savedVmapRegion, _vmapRegion); + paint(_vmapRegion, _cursor); + } +} + +void GfxCursor32::donePainting() { + if (_writeToVMAP) { + copy(_vmapRegion, _savedVmapRegion); + _savedVmapRegion.rect = Common::Rect(); + _writeToVMAP = false; + } + + if (!_hideCount && !_cursorBack.rect.isEmpty()) { + copy(_cursorBack, _vmapRegion); + } +} + +void GfxCursor32::deviceMoved(Common::Point &position) { + if (position.x < _restrictedArea.left) { + position.x = _restrictedArea.left; + } + if (position.x >= _restrictedArea.right) { + position.x = _restrictedArea.right - 1; + } + if (position.y < _restrictedArea.top) { + position.y = _restrictedArea.top; + } + if (position.y >= _restrictedArea.bottom) { + position.y = _restrictedArea.bottom - 1; + } + + _position = position; + + g_system->warpMouse(position.x, position.y); + move(); +} + +void GfxCursor32::move() { + if (_hideCount) { + return; + } + + // Cursor moved onto the screen after being offscreen + _cursor.rect.moveTo(_position.x - _hotSpot.x, _position.y - _hotSpot.y); + if (_cursorBack.rect.isEmpty()) { + revealCursor(); + return; + } + + // Cursor moved offscreen + if (!_cursor.rect.intersects(_vmapRegion.rect)) { + drawToHardware(_cursorBack); + return; + } + + if (!_cursor.rect.intersects(_cursorBack.rect)) { + // Cursor moved to a completely different part of the screen + _drawBuff1.rect = _cursor.rect; + _drawBuff1.rect.clip(_vmapRegion.rect); + readVideo(_drawBuff1); + + _drawBuff2.rect = _drawBuff1.rect; + copy(_drawBuff2, _drawBuff1); + + paint(_drawBuff1, _cursor); + drawToHardware(_drawBuff1); + + drawToHardware(_cursorBack); + + _cursorBack.rect = _cursor.rect; + _cursorBack.rect.clip(_vmapRegion.rect); + copy(_cursorBack, _drawBuff2); + } else { + // Cursor moved, but still overlaps the previous cursor location + Common::Rect mergedRect(_cursorBack.rect); + mergedRect.extend(_cursor.rect); + mergedRect.clip(_vmapRegion.rect); + + _drawBuff2.rect = mergedRect; + readVideo(_drawBuff2); + + copy(_drawBuff2, _cursorBack); + + _cursorBack.rect = _cursor.rect; + _cursorBack.rect.clip(_vmapRegion.rect); + copy(_cursorBack, _drawBuff2); + + paint(_drawBuff2, _cursor); + drawToHardware(_drawBuff2); + } +} +} // End of namespace Sci diff --git a/engines/sci/graphics/cursor32.h b/engines/sci/graphics/cursor32.h new file mode 100644 index 0000000000..d4745536b1 --- /dev/null +++ b/engines/sci/graphics/cursor32.h @@ -0,0 +1,250 @@ +/* ScummVM - Graphic Adventure Engine + * + * ScummVM is the legal property of its developers, whose names + * are too numerous to list here. Please refer to the COPYRIGHT + * file distributed with this source distribution. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef SCI_GRAPHICS_CURSOR32_H +#define SCI_GRAPHICS_CURSOR32_H + +#include "common/rect.h" // for Point, Rect +#include "common/scummsys.h" // for int16, byte, uint8 +#include "common/serializer.h" // for Serializable, Serializer (ptr only) +#include "sci/graphics/celobj32.h" // for CelInfo32 +#include "sci/graphics/helpers.h" // for GuiResourceId + +namespace Sci { + +class GfxCursor32 : Common::Serializable { +public: + GfxCursor32(); + ~GfxCursor32(); + + /** + * Initialises the cursor system with the given + * buffer to use as the output buffer for + * rendering the cursor. + */ + void init(const Buffer &vmap); + + /** + * Called when the hardware mouse moves. + */ + void deviceMoved(Common::Point &position); + + /** + * Called by GfxFrameout once for each show + * rectangle that is going to be drawn to + * hardware. + */ + void gonnaPaint(Common::Rect paintRect); + + /** + * Called by GfxFrameout when the rendering to + * hardware begins. + */ + void paintStarting(); + + /** + * Called by GfxFrameout when the output buffer + * has finished rendering to hardware. + */ + void donePainting(); + + /** + * Hides the cursor. Each call to `hide` will + * increment a hide counter, which must be + * returned to 0 before the cursor will be + * shown again. + */ + void hide(); + + /** + * Shows the cursor, if the hide counter is + * returned to 0. + */ + void unhide(); + + /** + * Shows the cursor regardless of the state of + * the hide counter. + */ + void show(); + + /** + * Sets the view used to render the cursor. + */ + void setView(const GuiResourceId viewId, const int16 loopNo, const int16 celNo); + + /** + * Explicitly sets the position of the cursor, + * in game script coordinates. + */ + void setPosition(const Common::Point &position); + + /** + * Sets the region that the mouse is allowed + * to move within. + */ + void setRestrictedArea(const Common::Rect &rect); + + /** + * Removes restrictions on mouse movement. + */ + void clearRestrictedArea(); + + virtual void saveLoadWithSerializer(Common::Serializer &ser); + +private: + struct DrawRegion { + Common::Rect rect; + byte *data; + uint8 skipColor; + + DrawRegion() : rect(), data(nullptr) {} + }; + + /** + * Information about the current cursor. + * Used to restore cursor when loading a + * savegame. + */ + CelInfo32 _cursorInfo; + + /** + * Content behind the cursor? TODO + */ + DrawRegion _cursorBack; + + /** + * Scratch buffer. + */ + DrawRegion _drawBuff1; + + /** + * Scratch buffer 2. + */ + DrawRegion _drawBuff2; + + /** + * A draw region representing the current + * output buffer. + */ + DrawRegion _vmapRegion; + + /** + * The content behind the cursor in the + * output buffer. + */ + DrawRegion _savedVmapRegion; + + /** + * The cursor bitmap. + */ + DrawRegion _cursor; + + /** + * The width and height of the cursor, + * in screen coordinates. + */ + int16 _width, _height; + + /** + * The output buffer where the cursor is + * rendered. + */ + Buffer _vmap; + + /** + * The number of times the cursor has been + * hidden. + */ + int _hideCount; + + /** + * The rendered position of the cursor, in + * screen coordinates. + */ + Common::Point _position; + + /** + * The position of the cursor hot spot, relative + * to the cursor origin, in screen pixels. + */ + Common::Point _hotSpot; + + /** + * The area within which the cursor is allowed + * to move, in screen pixels. + */ + Common::Rect _restrictedArea; + + /** + * Indicates whether or not the cursor needs to + * be repainted on the output buffer due to a + * change of graphics in the area underneath the + * cursor. + */ + bool _writeToVMAP; + + /** + * Reads data from the output buffer or hardware + * to the given draw region. + */ + void readVideo(DrawRegion &target); + + /** + * Reads data from the output buffer to the + * given draw region. + */ + void readVideoFromVmap(DrawRegion &target); + + /** + * Copies pixel data from the given source to + * the given target. + */ + void copy(DrawRegion &target, const DrawRegion &source); + + /** + * Draws from the given source onto the given + * target, skipping pixels in the source that + * match the `skipColor` property. + */ + void paint(DrawRegion &target, const DrawRegion &source); + + /** + * Draws the cursor to the position it was + * drawn to prior to moving offscreen or being + * hidden by a call to `hide`. + */ + void revealCursor(); + + /** + * Draws the given source to the output buffer. + */ + void drawToHardware(const DrawRegion &source); + + /** + * Renders the cursor at its new location. + */ + void move(); +}; + +} // End of namespace Sci +#endif diff --git a/engines/sci/graphics/frameout.cpp b/engines/sci/graphics/frameout.cpp index 21ffb5f937..4e0aa22669 100644 --- a/engines/sci/graphics/frameout.cpp +++ b/engines/sci/graphics/frameout.cpp @@ -29,6 +29,7 @@ #include "common/system.h" #include "common/textconsole.h" #include "engines/engine.h" +#include "engines/util.h" #include "graphics/palette.h" #include "graphics/surface.h" @@ -39,47 +40,53 @@ #include "sci/engine/selector.h" #include "sci/engine/vm.h" #include "sci/graphics/cache.h" -#include "sci/graphics/coordadjuster.h" #include "sci/graphics/compare.h" +#include "sci/graphics/cursor32.h" #include "sci/graphics/font.h" -#include "sci/graphics/screen.h" +#include "sci/graphics/frameout.h" #include "sci/graphics/paint32.h" #include "sci/graphics/palette32.h" #include "sci/graphics/plane32.h" #include "sci/graphics/remap32.h" +#include "sci/graphics/screen.h" #include "sci/graphics/screen_item32.h" #include "sci/graphics/text32.h" #include "sci/graphics/frameout.h" -#include "sci/video/robot_decoder.h" #include "sci/graphics/transitions32.h" +#include "sci/graphics/video32.h" namespace Sci { -GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette, GfxTransitions32 *transitions) : +GfxFrameout::GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor) : _isHiRes(ConfMan.getBool("enable_high_resolution_graphics")), _palette(palette), - _resMan(resMan), - _screen(screen), + _cursor(cursor), _segMan(segMan), _transitions(transitions), _benchmarkingFinished(false), _throttleFrameOut(true), _throttleState(0), - // TODO: Stop using _gfxScreen - _currentBuffer(screen->getDisplayWidth(), screen->getDisplayHeight(), nullptr), _remapOccurred(false), _frameNowVisible(false), - _screenRect(screen->getDisplayWidth(), screen->getDisplayHeight()), _overdrawThreshold(0), _palMorphIsOn(false) { - _currentBuffer.setPixels(calloc(1, screen->getDisplayWidth() * screen->getDisplayHeight())); - - // QFG4 is the only SCI32 game that doesn't have a high-resolution toggle + // QFG4 is the only SCI32 game that doesn't have a high-resolution version if (g_sci->getGameId() == GID_QFG4) { _isHiRes = false; } + if (g_sci->getGameId() == GID_PHANTASMAGORIA) { + _currentBuffer = Buffer(630, 450, nullptr); + } else if (_isHiRes) { + _currentBuffer = Buffer(640, 480, nullptr); + } else { + _currentBuffer = Buffer(320, 200, nullptr); + } + _currentBuffer.setPixels(calloc(1, _currentBuffer.screenWidth * _currentBuffer.screenHeight)); + _screenRect = Common::Rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight); + initGraphics(_currentBuffer.screenWidth, _currentBuffer.screenHeight, _isHiRes); + switch (g_sci->getGameId()) { case GID_HOYLE5: case GID_GK2: @@ -96,20 +103,6 @@ GfxFrameout::GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAd // default script width for other games is 320x200 break; } - - // TODO: Nothing in the renderer really uses this. Currently, - // the cursor renderer does, and kLocalToGlobal/kGlobalToLocal - // do, but in the real engine (1) the cursor is handled in - // frameOut, and (2) functions do a very simple lookup of the - // plane and arithmetic with the plane's gameRect. In - // principle, CoordAdjuster could be reused for - // convertGameRectToPlaneRect, but it is not super clear yet - // what the benefit would be to do that. - _coordAdjuster = (GfxCoordAdjuster32 *)coordAdjuster; - - // TODO: Script resolution is hard-coded per game; - // also this must be set or else the engine will crash - _coordAdjuster->setScriptsResolution(_currentBuffer.scriptWidth, _currentBuffer.scriptHeight); } GfxFrameout::~GfxFrameout() { @@ -493,10 +486,12 @@ void GfxFrameout::kernelAddPicAt(const reg_t planeObject, const GuiResourceId pi #pragma mark Rendering void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseRect) { -// TODO: Robot -// if (_robot != nullptr) { -// _robot.doRobot(); -// } + RobotDecoder &robotPlayer = g_sci->_video32->getRobotPlayer(); + const bool robotIsActive = robotPlayer.getStatus() != RobotDecoder::kRobotStatusUninitialized; + + if (robotIsActive) { + robotPlayer.doRobot(); + } // NOTE: The original engine allocated these as static arrays of 100 // pointers to ScreenItemList / RectList @@ -534,10 +529,9 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseR drawScreenItemList(screenItemLists[i]); } -// TODO: Robot -// if (_robot != nullptr) { -// _robot->frameAlmostVisible(); -// } + if (robotIsActive) { + robotPlayer.frameAlmostVisible(); + } _palette->updateHardware(!shouldShowBits); @@ -547,10 +541,9 @@ void GfxFrameout::frameOut(const bool shouldShowBits, const Common::Rect &eraseR _frameNowVisible = true; -// TODO: Robot -// if (_robot != nullptr) { -// robot->frameNowVisible(); -// } + if (robotIsActive) { + robotPlayer.frameNowVisible(); + } } void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *showStyle) { @@ -559,7 +552,7 @@ void GfxFrameout::palMorphFrameOut(const int8 *styleRanges, PlaneShowStyle *show int16 prevRoom = g_sci->getEngineState()->variables[VAR_GLOBAL][12].toSint16(); - Common::Rect rect(_screen->getDisplayWidth(), _screen->getDisplayHeight()); + Common::Rect rect(_currentBuffer.screenWidth, _currentBuffer.screenHeight); _showList.add(rect); showBits(); @@ -1114,6 +1107,10 @@ void GfxFrameout::mergeToShowList(const Common::Rect &drawRect, RectList &showLi } void GfxFrameout::showBits() { + if (!_showList.size()) { + return; + } + for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { Common::Rect rounded(**rect); // NOTE: SCI engine used BR-inclusive rects so used slightly @@ -1121,13 +1118,10 @@ void GfxFrameout::showBits() { // was always even. rounded.left &= ~1; rounded.right = (rounded.right + 1) & ~1; - - // TODO: - // _cursor->GonnaPaint(rounded); + _cursor->gonnaPaint(rounded); } - // TODO: - // _cursor->PaintStarting(); + _cursor->paintStarting(); for (RectList::const_iterator rect = _showList.begin(); rect != _showList.end(); ++rect) { Common::Rect rounded(**rect); @@ -1149,8 +1143,7 @@ void GfxFrameout::showBits() { g_system->copyRectToScreen(sourceBuffer, _currentBuffer.screenWidth, rounded.left, rounded.top, rounded.width(), rounded.height()); } - // TODO: - // _cursor->DonePainting(); + _cursor->donePainting(); _showList.clear(); } @@ -1266,6 +1259,30 @@ void GfxFrameout::showRect(const Common::Rect &rect) { } } +void GfxFrameout::shakeScreen(int16 numShakes, const ShakeDirection direction) { + if (direction & kShakeHorizontal) { + // Used by QFG4 room 750 + warning("TODO: Horizontal shake not implemented"); + return; + } + + while (numShakes--) { + if (direction & kShakeVertical) { + g_system->setShakePos(_isHiRes ? 8 : 4); + } + + g_system->updateScreen(); + g_sci->getEngineState()->wait(3); + + if (direction & kShakeVertical) { + g_system->setShakePos(0); + } + + g_system->updateScreen(); + g_sci->getEngineState()->wait(3); + } +} + #pragma mark - #pragma mark Mouse cursor @@ -1324,7 +1341,7 @@ bool GfxFrameout::isOnMe(const ScreenItem &screenItem, const Plane &plane, const return true; } -void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { +bool GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { const reg_t planeObject = readSelector(_segMan, screenItemObject, SELECTOR(plane)); Plane *plane = _planes.findByObject(planeObject); @@ -1334,7 +1351,7 @@ void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { ScreenItem *screenItem = plane->_screenItemList.findByObject(screenItemObject); if (screenItem == nullptr) { - error("kSetNowSeen: Screen item %04x:%04x not found in plane %04x:%04x", PRINT_REG(screenItemObject), PRINT_REG(planeObject)); + return false; } Common::Rect result = screenItem->getNowSeenRect(*plane); @@ -1342,6 +1359,7 @@ void GfxFrameout::kernelSetNowSeen(const reg_t screenItemObject) const { writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsTop), result.top); writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsRight), result.right - 1); writeSelectorValue(_segMan, screenItemObject, SELECTOR(nsBottom), result.bottom - 1); + return true; } void GfxFrameout::remapMarkRedraw() { diff --git a/engines/sci/graphics/frameout.h b/engines/sci/graphics/frameout.h index 012ecf9e64..e4caffd9e5 100644 --- a/engines/sci/graphics/frameout.h +++ b/engines/sci/graphics/frameout.h @@ -30,8 +30,7 @@ namespace Sci { typedef Common::Array<DrawList> ScreenItemListList; typedef Common::Array<RectList> EraseListList; -class GfxCoordAdjuster32; -class GfxScreen; +class GfxCursor32; class GfxTransitions32; struct PlaneShowStyle; @@ -41,16 +40,16 @@ struct PlaneShowStyle; */ class GfxFrameout { private: - GfxCoordAdjuster32 *_coordAdjuster; + GfxCursor32 *_cursor; GfxPalette32 *_palette; - ResourceManager *_resMan; - GfxScreen *_screen; SegManager *_segMan; public: - GfxFrameout(SegManager *segMan, ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette32 *palette, GfxTransitions32 *transitions); + GfxFrameout(SegManager *segMan, GfxPalette32 *palette, GfxTransitions32 *transitions, GfxCursor32 *cursor); ~GfxFrameout(); + bool _isHiRes; + void clear(); void syncWithScripts(bool addElements); // this is what Game::restore does, only needed when our ScummVM dialogs are patched in void run(); @@ -111,7 +110,7 @@ public: void kernelAddScreenItem(const reg_t object); void kernelUpdateScreenItem(const reg_t object); void kernelDeleteScreenItem(const reg_t object); - void kernelSetNowSeen(const reg_t screenItemObject) const; + bool kernelSetNowSeen(const reg_t screenItemObject) const; #pragma mark - #pragma mark Planes @@ -196,13 +195,6 @@ private: bool _remapOccurred; /** - * Whether or not the data in the current buffer is what - * is visible to the user. During rendering updates, - * this flag is set to false. - */ - bool _frameNowVisible; - - /** * TODO: Document * TODO: Depending upon if the engine ever modifies this * rect, it may be stupid to store it separately instead @@ -308,7 +300,12 @@ private: } public: - bool _isHiRes; + /** + * Whether or not the data in the current buffer is what + * is visible to the user. During rendering updates, + * this flag is set to false. + */ + bool _frameNowVisible; /** * Whether palMorphFrameOut should be used instead of @@ -366,6 +363,11 @@ public: */ void showRect(const Common::Rect &rect); + /** + * Shakes the screen. + */ + void shakeScreen(const int16 numShakes, const ShakeDirection direction); + #pragma mark - #pragma mark Mouse cursor private: diff --git a/engines/sci/graphics/helpers.h b/engines/sci/graphics/helpers.h index 3fcc83c5e2..1da3749c90 100644 --- a/engines/sci/graphics/helpers.h +++ b/engines/sci/graphics/helpers.h @@ -40,8 +40,10 @@ namespace Sci { #define MAX_CACHED_FONTS 20 #define MAX_CACHED_VIEWS 50 -#define SCI_SHAKE_DIRECTION_VERTICAL 1 -#define SCI_SHAKE_DIRECTION_HORIZONTAL 2 +enum ShakeDirection { + kShakeVertical = 1, + kShakeHorizontal = 2 +}; typedef int GuiResourceId; // is a resource-number and -1 means no parameter given diff --git a/engines/sci/graphics/paint16.cpp b/engines/sci/graphics/paint16.cpp index 6004e9ce7a..91817d4060 100644 --- a/engines/sci/graphics/paint16.cpp +++ b/engines/sci/graphics/paint16.cpp @@ -41,7 +41,7 @@ namespace Sci { -GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio) +GfxPaint16::GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster16 *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio) : _resMan(resMan), _segMan(segMan), _cache(cache), _ports(ports), _coordAdjuster(coordAdjuster), _screen(screen), _palette(palette), _transitions(transitions), _audio(audio), _EGAdrawingVisualize(false) { diff --git a/engines/sci/graphics/paint16.h b/engines/sci/graphics/paint16.h index 317388b2df..6fc9cbbdfc 100644 --- a/engines/sci/graphics/paint16.h +++ b/engines/sci/graphics/paint16.h @@ -36,7 +36,7 @@ class GfxView; */ class GfxPaint16 { public: - GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio); + GfxPaint16(ResourceManager *resMan, SegManager *segMan, GfxCache *cache, GfxPorts *ports, GfxCoordAdjuster16 *coordAdjuster, GfxScreen *screen, GfxPalette *palette, GfxTransitions *transitions, AudioPlayer *audio); ~GfxPaint16(); void init(GfxAnimate *animate, GfxText16 *text16); @@ -91,7 +91,7 @@ private: GfxAnimate *_animate; GfxCache *_cache; GfxPorts *_ports; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; GfxScreen *_screen; GfxPalette *_palette; GfxText16 *_text16; diff --git a/engines/sci/graphics/picture.cpp b/engines/sci/graphics/picture.cpp index 2eab391afd..0025b24476 100644 --- a/engines/sci/graphics/picture.cpp +++ b/engines/sci/graphics/picture.cpp @@ -35,7 +35,7 @@ namespace Sci { //#define DEBUG_PICTURE_DRAW -GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize) +GfxPicture::GfxPicture(ResourceManager *resMan, GfxCoordAdjuster16 *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize) : _resMan(resMan), _coordAdjuster(coordAdjuster), _ports(ports), _screen(screen), _palette(palette), _resourceId(resourceId), _EGAdrawingVisualize(EGAdrawingVisualize) { assert(resourceId != -1); initData(resourceId); diff --git a/engines/sci/graphics/picture.h b/engines/sci/graphics/picture.h index 942fa0f107..1be1ae3004 100644 --- a/engines/sci/graphics/picture.h +++ b/engines/sci/graphics/picture.h @@ -38,7 +38,7 @@ enum { class GfxPorts; class GfxScreen; class GfxPalette; -class GfxCoordAdjuster; +class GfxCoordAdjuster16; class ResourceManager; class Resource; @@ -48,7 +48,7 @@ class Resource; */ class GfxPicture { public: - GfxPicture(ResourceManager *resMan, GfxCoordAdjuster *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize = false); + GfxPicture(ResourceManager *resMan, GfxCoordAdjuster16 *coordAdjuster, GfxPorts *ports, GfxScreen *screen, GfxPalette *palette, GuiResourceId resourceId, bool EGAdrawingVisualize = false); ~GfxPicture(); GuiResourceId getResourceId(); @@ -84,7 +84,7 @@ private: void vectorPatternTexturedCircle(Common::Rect box, byte size, byte color, byte prio, byte control, byte texture); ResourceManager *_resMan; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; GfxPorts *_ports; GfxScreen *_screen; GfxPalette *_palette; diff --git a/engines/sci/graphics/screen.cpp b/engines/sci/graphics/screen.cpp index c977a93817..601ab9f09f 100644 --- a/engines/sci/graphics/screen.cpp +++ b/engines/sci/graphics/screen.cpp @@ -53,12 +53,6 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) { if (g_sci->getGameId() == GID_KQ6) _upscaledHires = GFX_SCREEN_UPSCALED_640x440; -#ifdef ENABLE_SCI32 - if (g_sci->getGameId() == GID_GK1) - _upscaledHires = GFX_SCREEN_UPSCALED_640x480; - if (g_sci->getGameId() == GID_PQ4) - _upscaledHires = GFX_SCREEN_UPSCALED_640x480; -#endif } // Japanese versions of games use hi-res font on upscaled version of the game. @@ -90,28 +84,11 @@ GfxScreen::GfxScreen(ResourceManager *resMan) : _resMan(resMan) { } } -#ifdef ENABLE_SCI32 - // GK1 Mac uses a 640x480 resolution too - if (g_sci->getPlatform() == Common::kPlatformMacintosh) { - if (g_sci->getGameId() == GID_GK1) - _upscaledHires = GFX_SCREEN_UPSCALED_640x480; - } -#endif - if (_resMan->detectHires()) { _scriptWidth = 640; _scriptHeight = 480; } -#ifdef ENABLE_SCI32 - // Phantasmagoria 1 effectively outputs 630x450 - // Coordinate translation has to use this resolution as well - if (g_sci->getGameId() == GID_PHANTASMAGORIA) { - _width = 630; - _height = 450; - } -#endif - // if not yet set, set those to script-width/height if (!_width) _width = _scriptWidth; @@ -632,13 +609,13 @@ void GfxScreen::setVerticalShakePos(uint16 shakePos) { void GfxScreen::kernelShakeScreen(uint16 shakeCount, uint16 directions) { while (shakeCount--) { - if (directions & SCI_SHAKE_DIRECTION_VERTICAL) + if (directions & kShakeVertical) setVerticalShakePos(10); // TODO: horizontal shakes g_system->updateScreen(); g_sci->getEngineState()->wait(3); - if (directions & SCI_SHAKE_DIRECTION_VERTICAL) + if (directions & kShakeVertical) setVerticalShakePos(0); g_system->updateScreen(); diff --git a/engines/sci/graphics/screen_item32.h b/engines/sci/graphics/screen_item32.h index 3d9d5ef3d7..4221c0ea52 100644 --- a/engines/sci/graphics/screen_item32.h +++ b/engines/sci/graphics/screen_item32.h @@ -31,6 +31,7 @@ namespace Sci { enum ScaleSignals32 { kScaleSignalNone = 0, + // TODO: rename to 'manual' kScaleSignalDoScaling32 = 1, // enables scaling when drawing that cel (involves scaleX and scaleY) kScaleSignalUseVanishingPoint = 2, // TODO: Is this actually a thing? I have not seen it and diff --git a/engines/sci/graphics/text16.cpp b/engines/sci/graphics/text16.cpp index b0f2c52791..cb6e614657 100644 --- a/engines/sci/graphics/text16.cpp +++ b/engines/sci/graphics/text16.cpp @@ -633,7 +633,7 @@ reg_t GfxText16::allocAndFillReferenceRectArray() { if (rectCount) { reg_t rectArray; byte *rectArrayPtr = g_sci->getEngineState()->_segMan->allocDynmem(4 * 2 * (rectCount + 1), "text code reference rects", &rectArray); - GfxCoordAdjuster *coordAdjuster = g_sci->_gfxCoordAdjuster; + GfxCoordAdjuster16 *coordAdjuster = g_sci->_gfxCoordAdjuster; for (uint curRect = 0; curRect < rectCount; curRect++) { coordAdjuster->kernelLocalToGlobal(_codeRefRects[curRect].left, _codeRefRects[curRect].top); coordAdjuster->kernelLocalToGlobal(_codeRefRects[curRect].right, _codeRefRects[curRect].bottom); diff --git a/engines/sci/graphics/transitions32.cpp b/engines/sci/graphics/transitions32.cpp index bceb0fa84d..37f608da85 100644 --- a/engines/sci/graphics/transitions32.cpp +++ b/engines/sci/graphics/transitions32.cpp @@ -203,10 +203,6 @@ void GfxTransitions32::kernelSetShowStyle(const uint16 argc, const reg_t planeOb color = 0; } - if ((getSciVersion() < SCI_VERSION_2_1_MIDDLE && g_sci->getGameId() != GID_KQ7 && type == 15) || type > 15) { - error("Illegal show style %d for plane %04x:%04x", type, PRINT_REG(planeObj)); - } - Plane *plane = g_sci->_gfxFrameout->getPlanes().findByObject(planeObj); if (plane == nullptr) { error("Plane %04x:%04x is not present in active planes list", PRINT_REG(planeObj)); diff --git a/engines/sci/graphics/video32.cpp b/engines/sci/graphics/video32.cpp index 51be08dac9..8b1d4ef32b 100644 --- a/engines/sci/graphics/video32.cpp +++ b/engines/sci/graphics/video32.cpp @@ -20,6 +20,7 @@ * */ +#include "audio/mixer.h" // for Audio::Mixer::kSFXSoundType #include "common/config-manager.h" // for ConfMan #include "common/textconsole.h" // for warning, error #include "common/util.h" // for ARRAYSIZE @@ -31,7 +32,7 @@ #include "sci/engine/vm_types.h" // for reg_t #include "sci/event.h" // for SciEvent, EventManager, SCI_... #include "sci/graphics/celobj32.h" // for CelInfo32, ::kLowResX, ::kLo... -#include "sci/graphics/cursor.h" // for GfxCursor +#include "sci/graphics/cursor32.h" // for GfxCursor32 #include "sci/graphics/frameout.h" // for GfxFrameout #include "sci/graphics/helpers.h" // for Color, Palette #include "sci/graphics/palette32.h" // for GfxPalette32 @@ -269,6 +270,11 @@ void AVIPlayer::init() { g_sci->_gfxFrameout->addScreenItem(*_screenItem); g_sci->_gfxFrameout->frameOut(true); } else { + // Attempting to draw a palettized cursor into a 24bpp surface will + // cause memory corruption, so hide the cursor in this mode (SCI did not + // have a 24bpp mode but just directed VFW to display videos instead) + g_sci->_gfxCursor32->hide(); + const Buffer ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer(); const Graphics::PixelFormat format = _decoder->getPixelFormat(); initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, g_sci->_gfxFrameout->_isHiRes, &format); @@ -326,6 +332,7 @@ AVIPlayer::IOStatus AVIPlayer::close() { const Buffer ¤tBuffer = g_sci->_gfxFrameout->getCurrentBuffer(); const Graphics::PixelFormat format = Graphics::PixelFormat::createFormatCLUT8(); initGraphics(currentBuffer.screenWidth, currentBuffer.screenHeight, isHiRes, &format); + g_sci->_gfxCursor32->unhide(); } _decoder->close(); @@ -597,7 +604,7 @@ VMDPlayer::IOStatus VMDPlayer::close() { } if (!_showCursor) { - g_sci->_gfxCursor->kernelShow(); + g_sci->_gfxCursor32->unhide(); } _lastYieldedFrameNo = 0; @@ -606,6 +613,22 @@ VMDPlayer::IOStatus VMDPlayer::close() { return kIOSuccess; } +VMDPlayer::VMDStatus VMDPlayer::getStatus() const { + if (!_isOpen) { + return kVMDNotOpen; + } + if (_decoder->isPaused()) { + return kVMDPaused; + } + if (_decoder->isPlaying()) { + return kVMDPlaying; + } + if (_decoder->endOfVideo()) { + return kVMDFinished; + } + return kVMDOpen; +} + VMDPlayer::EventFlags VMDPlayer::kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval) { assert(lastFrameNo >= -1); @@ -658,7 +681,7 @@ VMDPlayer::EventFlags VMDPlayer::playUntilEvent(const EventFlags flags) { _isInitialized = true; if (!_showCursor) { - g_sci->_gfxCursor->kernelHide(); + g_sci->_gfxCursor32->hide(); } Common::Rect vmdRect(_x, diff --git a/engines/sci/graphics/video32.h b/engines/sci/graphics/video32.h index 0496f61d5d..75b8fb2d21 100644 --- a/engines/sci/graphics/video32.h +++ b/engines/sci/graphics/video32.h @@ -27,6 +27,7 @@ #include "common/scummsys.h" // for int16, uint8, uint16, int32 #include "common/str.h" // for String #include "sci/engine/vm_types.h" // for reg_t +#include "sci/video/robot_decoder.h" // for RobotDecoder namespace Video { class AdvancedVMDDecoder; @@ -267,6 +268,15 @@ public: kEventFlagReverse = 0x80 }; + enum VMDStatus { + kVMDNotOpen = 0, + kVMDOpen = 1, + kVMDPlaying = 2, + kVMDPaused = 3, + kVMDStopped = 4, + kVMDFinished = 5 + }; + VMDPlayer(SegManager *segMan, EventManager *eventMan); ~VMDPlayer(); @@ -294,6 +304,11 @@ public: */ IOStatus close(); + /** + * Gets the playback status of the VMD player. + */ + VMDStatus getStatus() const; + // NOTE: Was WaitForEvent in SSCI EventFlags kernelPlayUntilEvent(const EventFlags flags, const int16 lastFrameNo, const int16 yieldInterval); @@ -505,16 +520,19 @@ public: Video32(SegManager *segMan, EventManager *eventMan) : _SEQPlayer(segMan), _AVIPlayer(segMan, eventMan), - _VMDPlayer(segMan, eventMan) {} + _VMDPlayer(segMan, eventMan), + _robotPlayer(segMan) {} SEQPlayer &getSEQPlayer() { return _SEQPlayer; } AVIPlayer &getAVIPlayer() { return _AVIPlayer; } VMDPlayer &getVMDPlayer() { return _VMDPlayer; } + RobotDecoder &getRobotPlayer() { return _robotPlayer; } private: SEQPlayer _SEQPlayer; AVIPlayer _AVIPlayer; VMDPlayer _VMDPlayer; + RobotDecoder _robotPlayer; }; } // End of namespace Sci diff --git a/engines/sci/graphics/view.cpp b/engines/sci/graphics/view.cpp index 1939e66179..0c09fcbb30 100644 --- a/engines/sci/graphics/view.cpp +++ b/engines/sci/graphics/view.cpp @@ -351,18 +351,6 @@ void GfxView::initData(GuiResourceId resourceId) { celData += celSize; } } -#ifdef ENABLE_SCI32 - // adjust width/height returned to scripts - if (_sci2ScaleRes != SCI_VIEW_NATIVERES_NONE) { - for (loopNo = 0; loopNo < _loopCount; loopNo++) - for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++) - _screen->adjustBackUpscaledCoordinates(_loop[loopNo].cel[celNo].scriptWidth, _loop[loopNo].cel[celNo].scriptHeight, _sci2ScaleRes); - } else if ((getSciVersion() >= SCI_VERSION_2_1_EARLY) && (getSciVersion() <= SCI_VERSION_2_1_LATE)) { - for (loopNo = 0; loopNo < _loopCount; loopNo++) - for (celNo = 0; celNo < _loop[loopNo].celCount; celNo++) - _coordAdjuster->fromDisplayToScript(_loop[loopNo].cel[celNo].scriptHeight, _loop[loopNo].cel[celNo].scriptWidth); - } -#endif break; default: diff --git a/engines/sci/graphics/view.h b/engines/sci/graphics/view.h index 96b48c0477..5e422468b5 100644 --- a/engines/sci/graphics/view.h +++ b/engines/sci/graphics/view.h @@ -92,7 +92,7 @@ private: void unditherBitmap(byte *bitmap, int16 width, int16 height, byte clearKey); ResourceManager *_resMan; - GfxCoordAdjuster *_coordAdjuster; + GfxCoordAdjuster16 *_coordAdjuster; GfxScreen *_screen; GfxPalette *_palette; diff --git a/engines/sci/module.mk b/engines/sci/module.mk index 18d97ea57e..eb2c6a148b 100644 --- a/engines/sci/module.mk +++ b/engines/sci/module.mk @@ -93,6 +93,7 @@ MODULE_OBJS += \ graphics/text32.o \ graphics/transitions32.o \ graphics/video32.o \ + graphics/cursor32.o \ sound/audio32.o \ sound/decoders/sol.o \ video/robot_decoder.o diff --git a/engines/sci/resource.cpp b/engines/sci/resource.cpp index 48278e35a7..2bd941b11a 100644 --- a/engines/sci/resource.cpp +++ b/engines/sci/resource.cpp @@ -573,6 +573,9 @@ Resource *ResourceManager::testResource(ResourceId id) { } int ResourceManager::addAppropriateSources() { +#ifdef ENABLE_SCI32 + _multiDiscAudio = false; +#endif if (Common::File::exists("resource.map")) { // SCI0-SCI2 file naming scheme ResourceSource *map = addExternalMap("resource.map"); @@ -615,6 +618,10 @@ int ResourceManager::addAppropriateSources() { if (mapFiles.empty() || files.empty() || mapFiles.size() != files.size()) return 0; + if (Common::File::exists("resaud.001")) { + _multiDiscAudio = true; + } + for (Common::ArchiveMemberList::const_iterator mapIterator = mapFiles.begin(); mapIterator != mapFiles.end(); ++mapIterator) { Common::String mapName = (*mapIterator)->getName(); int mapNumber = atoi(strrchr(mapName.c_str(), '.') + 1); @@ -859,6 +866,13 @@ void ResourceManager::addResourcesFromChunk(uint16 id) { scanNewSources(); } +void ResourceManager::findDisc(const int16 discNo) { + // Since all resources are expected to be copied from the original discs + // into a single game directory, this call just records the number of the CD + // that the game has requested + _currentDiscNo = discNo; +} + #endif void ResourceManager::freeResourceSources() { @@ -878,7 +892,9 @@ void ResourceManager::init() { _LRU.clear(); _resMap.clear(); _audioMapSCI1 = NULL; - +#ifdef ENABLE_SCI32 + _currentDiscNo = 1; +#endif // FIXME: put this in an Init() function, so that we can error out if detection fails completely _mapVersion = detectMapVersion(); @@ -1477,6 +1493,12 @@ void ResourceManager::readResourcePatchesBase36() { for (Common::ArchiveMemberList::const_iterator x = files.begin(); x != files.end(); ++x) { name = (*x)->getName(); + // The S/T prefixes often conflict with non-patch files and generate + // spurious warnings about invalid patches + if (name.hasSuffix(".DLL") || name.hasSuffix(".EXE") || name.hasSuffix(".TXT")) { + continue; + } + ResourceId resource36 = convertPatchNameBase36((ResourceType)i, name); /* @@ -1738,11 +1760,42 @@ int ResourceManager::readResourceMapSCI1(ResourceSource *map) { // if we use the first entries in the resource file, half of the // game will be English and umlauts will also be missing :P if (resource->_source->getSourceType() == kSourceVolume) { + // Maps are read during the scanning process (below), so + // need to be treated as unallocated in order for the new + // data from this volume to be picked up and used + if (resId.getType() == kResourceTypeMap) { + resource->_status = kResStatusNoMalloc; + } resource->_source = source; resource->_fileOffset = fileOffset; resource->size = 0; } } + +#ifdef ENABLE_SCI32 + // Different CDs may have different audio maps on each disc. The + // ResourceManager does not know how to deal with this; it expects + // each resource ID to be unique across an entire game. To work + // around this problem, all audio maps from this disc must be + // processed immediately, since they will be replaced by the audio + // map from the next disc on the next call to readResourceMapSCI1 + if (_multiDiscAudio && resId.getType() == kResourceTypeMap) { + IntMapResourceSource *audioMap = static_cast<IntMapResourceSource *>(addSource(new IntMapResourceSource("MAP", mapVolumeNr, resId.getNumber()))); + Common::String volumeName; + if (resId.getNumber() == 65535) { + volumeName = Common::String::format("RESSFX.%03d", mapVolumeNr); + } else { + volumeName = Common::String::format("RESAUD.%03d", mapVolumeNr); + } + + ResourceSource *audioVolume = addSource(new AudioVolumeResourceSource(this, volumeName, audioMap, mapVolumeNr)); + if (!audioMap->_scanned) { + audioVolume->_scanned = true; + audioMap->_scanned = true; + audioMap->scanSource(this); + } + } +#endif } } diff --git a/engines/sci/resource.h b/engines/sci/resource.h index f70bf48bd4..70db5909b7 100644 --- a/engines/sci/resource.h +++ b/engines/sci/resource.h @@ -296,6 +296,7 @@ protected: typedef Common::HashMap<ResourceId, Resource *, ResourceIdHash> ResourceMap; +class IntMapResourceSource; class ResourceManager { // FIXME: These 'friend' declarations are meant to be a temporary hack to // ease transition to the ResourceSource class system. @@ -397,6 +398,30 @@ public: * resource manager. */ void addResourcesFromChunk(uint16 id); + + /** + * Updates the currently active disc number. + */ + void findDisc(const int16 discNo); + + /** + * Gets the currently active disc number. + */ + int16 getCurrentDiscNo() const { return _currentDiscNo; } + +private: + /** + * The currently active disc number. + */ + int16 _currentDiscNo; + + /** + * If true, the game has multiple audio volumes that contain different + * audio files for each disc. + */ + bool _multiDiscAudio; + +public: #endif bool detectHires(); @@ -520,7 +545,7 @@ protected: * @param map The map * @return 0 on success, an SCI_ERROR_* code otherwise */ - int readAudioMapSCI11(ResourceSource *map); + int readAudioMapSCI11(IntMapResourceSource *map); /** * Reads SCI1 audio map files. diff --git a/engines/sci/resource_audio.cpp b/engines/sci/resource_audio.cpp index 5ab443a16d..cbc4a02739 100644 --- a/engines/sci/resource_audio.cpp +++ b/engines/sci/resource_audio.cpp @@ -277,7 +277,7 @@ void ResourceManager::removeAudioResource(ResourceId resId) { // w syncSize (iff seq has bit 7 set) // w syncAscSize (iff seq has bit 6 set) -int ResourceManager::readAudioMapSCI11(ResourceSource *map) { +int ResourceManager::readAudioMapSCI11(IntMapResourceSource *map) { #ifndef ENABLE_SCI32 // SCI32 support is not built in. Check if this is a SCI32 game // and if it is abort here. @@ -286,17 +286,19 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { #endif uint32 offset = 0; - Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_volumeNumber), false); + Resource *mapRes = findResource(ResourceId(kResourceTypeMap, map->_mapNumber), false); if (!mapRes) { - warning("Failed to open %i.MAP", map->_volumeNumber); + warning("Failed to open %i.MAP", map->_mapNumber); return SCI_ERROR_RESMAP_NOT_FOUND; } - ResourceSource *src = findVolume(map, 0); + ResourceSource *src = findVolume(map, map->_volumeNumber); - if (!src) + if (!src) { + warning("Failed to find volume for %i.MAP", map->_mapNumber); return SCI_ERROR_NO_RESOURCE_FILES_FOUND; + } byte *ptr = mapRes->data; @@ -309,7 +311,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { break; } - if (map->_volumeNumber == 65535) { + if (map->_mapNumber == 65535) { while (ptr < mapRes->data + mapRes->size) { uint16 n = READ_LE_UINT16(ptr); ptr += 2; @@ -327,7 +329,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { addResource(ResourceId(kResourceTypeAudio, n), src, offset); } - } else if (map->_volumeNumber == 0 && entrySize == 10 && ptr[3] == 0) { + } else if (map->_mapNumber == 0 && entrySize == 10 && ptr[3] == 0) { // QFG3 demo format // ptr[3] would be 'seq' in the normal format and cannot possibly be 0 while (ptr < mapRes->data + mapRes->size) { @@ -344,7 +346,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { addResource(ResourceId(kResourceTypeAudio, n), src, offset, size); } - } else if (map->_volumeNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) { + } else if (map->_mapNumber == 0 && entrySize == 8 && READ_LE_UINT16(ptr + 2) == 0xffff) { // LB2 Floppy/Mother Goose SCI1.1 format Common::SeekableReadStream *stream = getVolumeFile(src); @@ -400,7 +402,7 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { // FIXME: The sync36 resource seems to be two bytes too big in KQ6CD // (bytes taken from the RAVE resource right after it) if (syncSize > 0) - addResource(ResourceId(kResourceTypeSync36, map->_volumeNumber, n & 0xffffff3f), src, offset, syncSize); + addResource(ResourceId(kResourceTypeSync36, map->_mapNumber, n & 0xffffff3f), src, offset, syncSize); } if (n & 0x40) { @@ -410,12 +412,12 @@ int ResourceManager::readAudioMapSCI11(ResourceSource *map) { ptr += 2; if (kq6HiresSyncSize > 0) { - addResource(ResourceId(kResourceTypeRave, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize, kq6HiresSyncSize); + addResource(ResourceId(kResourceTypeRave, map->_mapNumber, n & 0xffffff3f), src, offset + syncSize, kq6HiresSyncSize); syncSize += kq6HiresSyncSize; } } - addResource(ResourceId(kResourceTypeAudio36, map->_volumeNumber, n & 0xffffff3f), src, offset + syncSize); + addResource(ResourceId(kResourceTypeAudio36, map->_mapNumber, n & 0xffffff3f), src, offset + syncSize); } } @@ -937,13 +939,21 @@ void AudioVolumeResourceSource::loadResource(ResourceManager *resMan, Resource * } bool ResourceManager::addAudioSources() { +#ifdef ENABLE_SCI32 + // Multi-disc audio is added during addAppropriateSources for those titles + // that require it + if (_multiDiscAudio) { + return true; + } +#endif + Common::List<ResourceId> resources = listResources(kResourceTypeMap); Common::List<ResourceId>::iterator itr; for (itr = resources.begin(); itr != resources.end(); ++itr) { - ResourceSource *src = addSource(new IntMapResourceSource("MAP", itr->getNumber())); + ResourceSource *src = addSource(new IntMapResourceSource("MAP", 0, itr->getNumber())); - if ((itr->getNumber() == 65535) && Common::File::exists("RESOURCE.SFX")) + if (itr->getNumber() == 65535 && Common::File::exists("RESOURCE.SFX")) addSource(new AudioVolumeResourceSource(this, "RESOURCE.SFX", src, 0)); else if (Common::File::exists("RESOURCE.AUD")) addSource(new AudioVolumeResourceSource(this, "RESOURCE.AUD", src, 0)); @@ -991,7 +1001,7 @@ void ResourceManager::changeAudioDirectory(Common::String path) { if ((it->getNumber() == 65535)) continue; - ResourceSource *src = addSource(new IntMapResourceSource(mapName, it->getNumber())); + ResourceSource *src = addSource(new IntMapResourceSource(mapName, 0, it->getNumber())); addSource(new AudioVolumeResourceSource(this, audioResourceName, src, 0)); } diff --git a/engines/sci/resource_intern.h b/engines/sci/resource_intern.h index 461d684005..fe4b0a97f4 100644 --- a/engines/sci/resource_intern.h +++ b/engines/sci/resource_intern.h @@ -134,8 +134,9 @@ public: class IntMapResourceSource : public ResourceSource { public: - IntMapResourceSource(const Common::String &name, int volNum) - : ResourceSource(kSourceIntMap, name, volNum) { + uint16 _mapNumber; + IntMapResourceSource(const Common::String &name, int volNum, int mapNum) + : ResourceSource(kSourceIntMap, name, volNum), _mapNumber(mapNum) { } virtual void scanSource(ResourceManager *resMan); diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 2a098ad975..6c51060296 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -65,6 +65,7 @@ #ifdef ENABLE_SCI32 #include "sci/graphics/controls32.h" +#include "sci/graphics/cursor32.h" #include "sci/graphics/frameout.h" #include "sci/graphics/palette32.h" #include "sci/graphics/remap32.h" @@ -72,17 +73,12 @@ #include "sci/graphics/transitions32.h" #include "sci/graphics/video32.h" #include "sci/sound/audio32.h" -// TODO: Move this to video32 -#include "sci/video/robot_decoder.h" #endif namespace Sci { SciEngine *g_sci = 0; - -class GfxDriver; - SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gameId) : Engine(syst), _gameDescription(desc), _gameId(gameId), _rng("sci") { @@ -96,6 +92,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam #ifdef ENABLE_SCI32 _audio32 = nullptr; _video32 = nullptr; + _gfxCursor32 = nullptr; #endif _features = 0; _resMan = 0; @@ -130,6 +127,7 @@ SciEngine::SciEngine(OSystem *syst, const ADGameDescription *desc, SciGameId gam DebugMan.addDebugChannel(kDebugLevelScripts, "Scripts", "Notifies when scripts are unloaded"); DebugMan.addDebugChannel(kDebugLevelScriptPatcher, "ScriptPatcher", "Notifies when scripts are patched"); DebugMan.addDebugChannel(kDebugLevelWorkarounds, "Workarounds", "Notifies when workarounds are triggered"); + DebugMan.addDebugChannel(kDebugLevelVideo, "Video", "Video (SEQ, VMD, RBT) debugging"); DebugMan.addDebugChannel(kDebugLevelGC, "GC", "Garbage Collector debugging"); DebugMan.addDebugChannel(kDebugLevelResMan, "ResMan", "Resource manager debugging"); DebugMan.addDebugChannel(kDebugLevelOnStartup, "OnStartup", "Enter debugger at start of game"); @@ -171,11 +169,11 @@ SciEngine::~SciEngine() { delete _gfxControls32; delete _gfxPaint32; delete _gfxText32; - delete _robotDecoder; // GfxFrameout and GfxPalette32 must be deleted after Video32 since // destruction of screen items in the Video32 destructor relies on these // components delete _video32; + delete _gfxCursor32; delete _gfxPalette32; delete _gfxTransitions32; delete _gfxFrameout; @@ -262,9 +260,13 @@ Common::Error SciEngine::run() { _forceHiresGraphics = ConfMan.getBool("enable_high_resolution_graphics"); } - // Initialize the game screen - _gfxScreen = new GfxScreen(_resMan); - _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering")); + if (getSciVersion() < SCI_VERSION_2) { + // Initialize the game screen + _gfxScreen = new GfxScreen(_resMan); + _gfxScreen->enableUndithering(ConfMan.getBool("disable_dithering")); + } else { + _gfxScreen = nullptr; + } _kernel = new Kernel(_resMan, segMan); _kernel->init(); @@ -701,12 +703,12 @@ void SciEngine::initGraphics() { #ifdef ENABLE_SCI32 _gfxControls32 = 0; _gfxText32 = 0; - _robotDecoder = 0; _gfxFrameout = 0; _gfxPaint32 = 0; _gfxPalette32 = 0; _gfxRemap32 = 0; _gfxTransitions32 = 0; + _gfxCursor32 = 0; #endif if (hasMacIconBar()) @@ -726,24 +728,23 @@ void SciEngine::initGraphics() { #endif _gfxCache = new GfxCache(_resMan, _gfxScreen, _gfxPalette16); - _gfxCursor = new GfxCursor(_resMan, _gfxPalette16, _gfxScreen); #ifdef ENABLE_SCI32 if (getSciVersion() >= SCI_VERSION_2) { // SCI32 graphic objects creation - _gfxCoordAdjuster = new GfxCoordAdjuster32(_gamestate->_segMan); - _gfxCursor->init(_gfxCoordAdjuster, _eventMan); - _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, _gfxScreen, _gfxCoordAdjuster); + _gfxCursor32 = new GfxCursor32(); + _gfxCompare = new GfxCompare(_gamestate->_segMan, _gfxCache, nullptr, _gfxCoordAdjuster); _gfxPaint32 = new GfxPaint32(_gamestate->_segMan); - _robotDecoder = new RobotDecoder(getPlatform() == Common::kPlatformMacintosh); _gfxTransitions32 = new GfxTransitions32(_gamestate->_segMan); - _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _resMan, _gfxCoordAdjuster, _gfxScreen, _gfxPalette32, _gfxTransitions32); + _gfxFrameout = new GfxFrameout(_gamestate->_segMan, _gfxPalette32, _gfxTransitions32, _gfxCursor32); + _gfxCursor32->init(_gfxFrameout->getCurrentBuffer()); _gfxText32 = new GfxText32(_gamestate->_segMan, _gfxCache); _gfxControls32 = new GfxControls32(_gamestate->_segMan, _gfxCache, _gfxText32); _gfxFrameout->run(); } else { #endif // SCI0-SCI1.1 graphic objects creation + _gfxCursor = new GfxCursor(_resMan, _gfxPalette16, _gfxScreen); _gfxPorts = new GfxPorts(_gamestate->_segMan, _gfxScreen); _gfxCoordAdjuster = new GfxCoordAdjuster16(_gfxPorts); _gfxCursor->init(_gfxCoordAdjuster, _eventMan); diff --git a/engines/sci/sci.h b/engines/sci/sci.h index 0425d21564..b336eb8cce 100644 --- a/engines/sci/sci.h +++ b/engines/sci/sci.h @@ -75,7 +75,7 @@ class GfxCache; class GfxCompare; class GfxControls16; class GfxControls32; -class GfxCoordAdjuster; +class GfxCoordAdjuster16; class GfxCursor; class GfxMacIconBar; class GfxMenu; @@ -92,12 +92,11 @@ class GfxText32; class GfxTransitions; #ifdef ENABLE_SCI32 -// TODO: Move RobotDecoder to Video32 -class RobotDecoder; class GfxFrameout; class Audio32; class Video32; class GfxTransitions32; +class GfxCursor32; #endif // our engine debug levels @@ -125,7 +124,8 @@ enum kDebugLevels { kDebugLevelOnStartup = 1 << 20, kDebugLevelDebugMode = 1 << 21, kDebugLevelScriptPatcher = 1 << 22, - kDebugLevelWorkarounds = 1 << 23 + kDebugLevelWorkarounds = 1 << 23, + kDebugLevelVideo = 1 << 24 }; enum SciGameId { @@ -369,7 +369,7 @@ public: GfxCompare *_gfxCompare; GfxControls16 *_gfxControls16; // Controls for 16-bit gfx GfxControls32 *_gfxControls32; // Controls for 32-bit gfx - GfxCoordAdjuster *_gfxCoordAdjuster; + GfxCoordAdjuster16 *_gfxCoordAdjuster; GfxCursor *_gfxCursor; GfxMenu *_gfxMenu; // Menu for 16-bit gfx GfxPalette *_gfxPalette16; @@ -388,9 +388,9 @@ public: #ifdef ENABLE_SCI32 Audio32 *_audio32; Video32 *_video32; - RobotDecoder *_robotDecoder; GfxFrameout *_gfxFrameout; // kFrameout and the like for 32-bit gfx GfxTransitions32 *_gfxTransitions32; + GfxCursor32 *_gfxCursor32; #endif AudioPlayer *_audio; diff --git a/engines/sci/sound/audio32.cpp b/engines/sci/sound/audio32.cpp index 288b7c00f5..4af474b918 100644 --- a/engines/sci/sound/audio32.cpp +++ b/engines/sci/sound/audio32.cpp @@ -164,7 +164,7 @@ Audio32::~Audio32() { #pragma mark - #pragma mark AudioStream implementation -int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) { +int Audio32::writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop) { int samplesToRead = numSamples; // The parent rate converter will request N * 2 @@ -182,7 +182,8 @@ int Audio32::writeAudioInternal(Audio::RewindableAudioStream *const sourceStream do { if (loop && sourceStream->endOfStream()) { - sourceStream->rewind(); + Audio::RewindableAudioStream *rewindableStream = dynamic_cast<Audio::RewindableAudioStream *>(sourceStream); + rewindableStream->rewind(); } const int loopSamplesWritten = converter->flow(*sourceStream, targetBuffer, samplesToRead, leftVolume, rightVolume); @@ -305,7 +306,14 @@ int Audio32::readBuffer(Audio::st_sample_t *buffer, const int numSamples) { } if (channel.robot) { - // TODO: Robot audio into output buffer + if (channel.stream->endOfStream()) { + stop(channelIndex--); + } else { + const int channelSamplesWritten = writeAudioInternal(channel.stream, channel.converter, buffer, numSamples, kMaxVolume, kMaxVolume, channel.loop); + if (channelSamplesWritten > maxSamplesWritten) { + maxSamplesWritten = channelSamplesWritten; + } + } continue; } @@ -443,9 +451,9 @@ void Audio32::freeUnusedChannels() { Common::StackLock lock(_mutex); for (int channelIndex = 0; channelIndex < _numActiveChannels; ++channelIndex) { const AudioChannel &channel = getChannel(channelIndex); - if (channel.stream->endOfStream()) { + if (!channel.robot && channel.stream->endOfStream()) { if (channel.loop) { - channel.stream->rewind(); + dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->rewind(); } else { stop(channelIndex--); } @@ -466,21 +474,29 @@ void Audio32::freeChannel(const int16 channelIndex) { Common::StackLock lock(_mutex); AudioChannel &channel = getChannel(channelIndex); - // We cannot unlock resources from the audio thread - // because ResourceManager is not thread-safe; instead, - // we just record that the resource needs unlocking and - // unlock it whenever we are on the main thread again - if (_inAudioThread) { - _resourcesToUnlock.push_back(channel.resource); + // Robots have no corresponding resource to free + if (channel.robot) { + delete channel.stream; + channel.stream = nullptr; + channel.robot = false; } else { - _resMan->unlockResource(channel.resource); + // We cannot unlock resources from the audio thread + // because ResourceManager is not thread-safe; instead, + // we just record that the resource needs unlocking and + // unlock it whenever we are on the main thread again + if (_inAudioThread) { + _resourcesToUnlock.push_back(channel.resource); + } else { + _resMan->unlockResource(channel.resource); + } + + channel.resource = nullptr; + delete channel.stream; + channel.stream = nullptr; + delete channel.resourceStream; + channel.resourceStream = nullptr; } - channel.resource = nullptr; - delete channel.stream; - channel.stream = nullptr; - delete channel.resourceStream; - channel.resourceStream = nullptr; delete channel.converter; channel.converter = nullptr; @@ -527,6 +543,111 @@ void Audio32::setNumOutputChannels(int16 numChannels) { } #pragma mark - +#pragma mark Robot + +int16 Audio32::findRobotChannel() const { + Common::StackLock lock(_mutex); + for (int16 i = 0; i < _numActiveChannels; ++i) { + if (_channels[i].robot) { + return i; + } + } + + return kNoExistingChannel; +} + +bool Audio32::playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet) { + // Stop immediately + if (packet.dataSize == 0) { + warning("Stopping robot stream by zero-length packet"); + return stopRobotAudio(); + } + + // Flush and then stop + if (packet.dataSize == -1) { + warning("Stopping robot stream by negative-length packet"); + return finishRobotAudio(); + } + + Common::StackLock lock(_mutex); + int16 channelIndex = findRobotChannel(); + + bool isNewChannel = false; + if (channelIndex == kNoExistingChannel) { + if (_numActiveChannels == _channels.size()) { + return false; + } + + channelIndex = _numActiveChannels++; + isNewChannel = true; + } + + AudioChannel &channel = getChannel(channelIndex); + + if (isNewChannel) { + channel.id = ResourceId(); + channel.resource = nullptr; + channel.loop = false; + channel.robot = true; + channel.fadeStartTick = 0; + channel.pausedAtTick = 0; + channel.soundNode = NULL_REG; + channel.volume = kMaxVolume; + // TODO: SCI3 introduces stereo audio + channel.pan = -1; + channel.converter = Audio::makeRateConverter(RobotAudioStream::kRobotSampleRate, getRate(), false); + // The RobotAudioStream buffer size is + // ((bytesPerSample * channels * sampleRate * 2000ms) / 1000ms) & ~3 + // where bytesPerSample = 2, channels = 1, and sampleRate = 22050 + channel.stream = new RobotAudioStream(88200); + _robotAudioPaused = false; + + if (_numActiveChannels == 1) { + _startedAtTick = g_sci->getTickCount(); + } + } + + return static_cast<RobotAudioStream *>(channel.stream)->addPacket(packet); +} + +bool Audio32::queryRobotAudio(RobotAudioStream::StreamState &status) const { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + status.bytesPlaying = 0; + return false; + } + + status = static_cast<RobotAudioStream *>(getChannel(channelIndex).stream)->getStatus(); + return true; +} + +bool Audio32::finishRobotAudio() { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + return false; + } + + static_cast<RobotAudioStream *>(getChannel(channelIndex).stream)->finish(); + return true; +} + +bool Audio32::stopRobotAudio() { + Common::StackLock lock(_mutex); + + const int16 channelIndex = findRobotChannel(); + if (channelIndex == kNoExistingChannel) { + return false; + } + + stop(channelIndex); + return true; +} + +#pragma mark - #pragma mark Playback uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool autoPlay, const bool loop, const int16 volume, const reg_t soundNode, const bool monitor) { @@ -536,14 +657,15 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool if (channelIndex != kNoExistingChannel) { AudioChannel &channel = getChannel(channelIndex); + Audio::SeekableAudioStream *stream = dynamic_cast<Audio::SeekableAudioStream *>(channel.stream); if (channel.pausedAtTick) { resume(channelIndex); - return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000); + return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000); } warning("Tried to resume channel %s that was not paused", channel.id.toString().c_str()); - return MIN(65534, 1 + channel.stream->getLength().msecs() * 60 / 1000); + return MIN(65534, 1 + stream->getLength().msecs() * 60 / 1000); } if (_numActiveChannels == _channels.size()) { @@ -642,7 +764,7 @@ uint16 Audio32::play(int16 channelIndex, const ResourceId resourceId, const bool // use audio streams, and allocate and fill the monitoring buffer // when reading audio data from the stream. - channel.duration = /* round up */ 1 + (channel.stream->getLength().msecs() * 60 / 1000); + channel.duration = /* round up */ 1 + (dynamic_cast<Audio::SeekableAudioStream *>(channel.stream)->getLength().msecs() * 60 / 1000); const uint32 now = g_sci->getTickCount(); channel.pausedAtTick = autoPlay ? 0 : now; @@ -687,8 +809,6 @@ bool Audio32::resume(const int16 channelIndex) { if (channel.robot) { channel.startedAtTick += now - channel.pausedAtTick; channel.pausedAtTick = 0; - // TODO: Robot - // StartRobot(); return true; } } diff --git a/engines/sci/sound/audio32.h b/engines/sci/sound/audio32.h index ac3176cc5a..a9905ab6bf 100644 --- a/engines/sci/sound/audio32.h +++ b/engines/sci/sound/audio32.h @@ -30,8 +30,10 @@ #include "common/scummsys.h" // for int16, uint8, uint32, uint16 #include "engines/sci/resource.h" // for ResourceId #include "sci/engine/vm_types.h" // for reg_t, NULL_REG +#include "sci/video/robot_decoder.h" // for RobotAudioStream namespace Sci { +#pragma mark AudioChannel /** * An audio channel used by the software SCI mixer. @@ -53,14 +55,11 @@ struct AudioChannel { Common::SeekableReadStream *resourceStream; /** - * The audio stream loaded into this channel. - * `SeekableAudioStream` is used here instead of - * `RewindableAudioStream` because - * `RewindableAudioStream` does not include the - * `getLength` function, which is needed to tell the - * game engine the duration of audio streams. + * The audio stream loaded into this channel. Can cast + * to `SeekableAudioStream` for normal channels and + * `RobotAudioStream` for robot channels. */ - Audio::SeekableAudioStream *stream; + Audio::AudioStream *stream; /** * The converter used to transform and merge the input @@ -188,7 +187,7 @@ private: * Mixes audio from the given source stream into the * target buffer using the given rate converter. */ - int writeAudioInternal(Audio::RewindableAudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop); + int writeAudioInternal(Audio::AudioStream *const sourceStream, Audio::RateConverter *const converter, Audio::st_sample_t *targetBuffer, const int numSamples, const Audio::st_volume_t leftVolume, const Audio::st_volume_t rightVolume, const bool loop); #pragma mark - #pragma mark Channel management @@ -395,9 +394,18 @@ private: #pragma mark - #pragma mark Robot public: + bool playRobotAudio(const RobotAudioStream::RobotAudioPacket &packet); + bool queryRobotAudio(RobotAudioStream::StreamState &outStatus) const; + bool finishRobotAudio(); + bool stopRobotAudio(); private: /** + * Finds a channel that is configured for robot playback. + */ + int16 findRobotChannel() const; + + /** * When true, channels marked as robot audio will not be * played. */ diff --git a/engines/sci/sound/decoders/sol.cpp b/engines/sci/sound/decoders/sol.cpp index e445403120..ee1ba35406 100644 --- a/engines/sci/sound/decoders/sol.cpp +++ b/engines/sci/sound/decoders/sol.cpp @@ -21,6 +21,7 @@ */ #include "audio/audiostream.h" +#include "audio/rate.h" #include "audio/decoders/raw.h" #include "common/substream.h" #include "common/util.h" @@ -52,7 +53,7 @@ static const byte tableDPCM8[8] = { 0, 1, 2, 3, 6, 10, 15, 21 }; * Decompresses 16-bit DPCM compressed audio. Each byte read * outputs one sample into the decompression buffer. */ -static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numBytes, int16 &sample) { +static void deDPCM16(int16 *out, Common::ReadStream &audioStream, const uint32 numBytes, int16 &sample) { for (uint32 i = 0; i < numBytes; ++i) { const uint8 delta = audioStream.readByte(); if (delta & 0x80) { @@ -65,6 +66,19 @@ static void deDPCM16(int16 *out, Common::ReadStream &audioStream, uint32 numByte } } +void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample) { + for (uint32 i = 0; i < numBytes; ++i) { + const uint8 delta = *in++; + if (delta & 0x80) { + sample -= tableDPCM16[delta & 0x7f]; + } else { + sample += tableDPCM16[delta]; + } + sample = CLIP<int16>(sample, -32768, 32767); + *out++ = TO_LE_16(sample); + } +} + /** * Decompresses one half of an 8-bit DPCM compressed audio * byte. @@ -178,7 +192,7 @@ int SOLStream<STEREO, S16BIT>::getRate() const { template <bool STEREO, bool S16BIT> bool SOLStream<STEREO, S16BIT>::endOfData() const { - return _stream->eos() || _stream->pos() >= _dataOffset + _rawDataSize; + return _stream->eos() || _stream->pos() >= _rawDataSize; } template <bool STEREO, bool S16BIT> @@ -269,5 +283,4 @@ Audio::SeekableAudioStream *makeSOLStream(Common::SeekableReadStream *headerStre return Audio::makeRawStream(dataStream, sampleRate, rawFlags, disposeAfterUse); } - } diff --git a/engines/sci/video/robot_decoder.cpp b/engines/sci/video/robot_decoder.cpp index a2795d21f9..f3354f9e44 100644 --- a/engines/sci/video/robot_decoder.cpp +++ b/engines/sci/video/robot_decoder.cpp @@ -20,391 +20,1598 @@ * */ -#include "common/archive.h" -#include "common/stream.h" -#include "common/substream.h" -#include "common/system.h" -#include "common/textconsole.h" -#include "common/util.h" - -#include "graphics/surface.h" -#include "audio/audiostream.h" -#include "audio/decoders/raw.h" - -#include "sci/resource.h" -#include "sci/util.h" -#include "sci/sound/audio.h" #include "sci/video/robot_decoder.h" +#include "common/archive.h" // for SearchMan +#include "common/debug.h" // for debugC +#include "common/endian.h" // for MKTAG +#include "common/memstream.h" // for MemoryReadStream +#include "common/platform.h" // for Platform::kPlatformMacintosh +#include "common/rational.h" // for operator*, Rational +#include "common/str.h" // for String +#include "common/stream.h" // for SeekableReadStream +#include "common/substream.h" // for SeekableSubReadStreamEndian +#include "common/textconsole.h" // for error, warning +#include "common/types.h" // for Flag::NO, Flag::YES +#include "sci/engine/seg_manager.h" // for SegManager +#include "sci/graphics/celobj32.h" // for Ratio, ::kLowResX, ::kLowResY +#include "sci/graphics/text32.h" // for BitmapResource +#include "sci/sound/audio32.h" // for Audio32 +#include "sci/sci.h" // for kDebugLevels::kDebugLevelVideo +#include "sci/util.h" // for READ_SCI11ENDIAN_UINT16, READ_SC... namespace Sci { -// TODO: -// - Positioning -// - Proper handling of frame scaling - scaled frames look squashed -// (probably because both dimensions should be scaled) -// - Transparency support -// - Timing - the arbitrary 100ms delay between each frame is not quite right -// - Proper handling of sound chunks in some cases, so that the frame size -// table can be ignored (it's only used to determine the correct sound chunk -// size at the moment, cause it can be wrong in some cases) -// - Fix audio "hiccups" - probably data that shouldn't be in the audio frames - - -// Some non technical information on robot files, from an interview with -// Greg Tomko-Pavia of Sierra On-Line -// Taken from http://anthonylarme.tripod.com/phantas/phintgtp.html -// -// (...) What we needed was a way of playing video, but have it blend into -// normal room art instead of occupying its own rectangular area. Room art -// consists of a background pic overlaid with various animating cels -// (traditional lingo: sprites). The cels each have a priority that determines -// who is on top and who is behind in the drawing order. Cels are read from -// *.v56 files (another proprietary format). A Robot is video frames with -// transparent background including priority and x,y information. Thus, it is -// like a cel, except it comes from an RBT - not a v56. Because it blends into -// our graphics engine, it looks just like a part of the room. A RBT can move -// around the screen and go behind other objects. (...) - -enum RobotPalTypes { - kRobotPalVariable = 0, - kRobotPalConstant = 1 -}; - -RobotDecoder::RobotDecoder(bool isBigEndian) { - _fileStream = 0; - _pos = Common::Point(0, 0); - _isBigEndian = isBigEndian; - _frameTotalSize = 0; +#pragma mark RobotAudioStream + +extern void deDPCM16(int16 *out, const byte *in, const uint32 numBytes, int16 &sample); + +RobotAudioStream::RobotAudioStream(const int32 bufferSize) : + _loopBuffer((byte *)malloc(bufferSize)), + _loopBufferSize(bufferSize), + _decompressionBuffer(nullptr), + _decompressionBufferSize(0), + _decompressionBufferPosition(-1), + _waiting(true), + _finished(false), + _firstPacketPosition(-1) {} + +RobotAudioStream::~RobotAudioStream() { + free(_loopBuffer); + free(_decompressionBuffer); } -RobotDecoder::~RobotDecoder() { - close(); +static void interpolateChannel(int16 *buffer, int32 numSamples, const int8 bufferIndex) { + if (numSamples <= 0) { + return; + } + + if (bufferIndex) { + int16 lastSample = *buffer; + int sample = lastSample; + int16 *target = buffer + 1; + const int16 *source = buffer + 2; + --numSamples; + + while (numSamples--) { + sample = *source + lastSample; + lastSample = *source; + sample /= 2; + *target = sample; + source += 2; + target += 2; + } + + *target = sample; + } else { + int16 *target = buffer; + const int16 *source = buffer + 1; + int16 lastSample = *source; + + while (numSamples--) { + int sample = *source + lastSample; + lastSample = *source; + sample /= 2; + *target = sample; + source += 2; + target += 2; + } + } } -bool RobotDecoder::loadStream(Common::SeekableReadStream *stream) { - close(); +static void copyEveryOtherSample(int16 *out, const int16 *in, int numSamples) { + while (numSamples--) { + *out = *in++; + out += 2; + } +} + +bool RobotAudioStream::addPacket(const RobotAudioPacket &packet) { + Common::StackLock lock(_mutex); + + if (_finished) { + warning("Packet %d sent to finished robot audio stream", packet.position); + return false; + } + + // `packet.position` is the decompressed (doubled) position of the packet, + // so values of `position` will always be divisible either by 2 (even) or by + // 4 (odd). + const int8 bufferIndex = packet.position % 4 ? 1 : 0; + + // Packet 0 is the first primer, packet 2 is the second primer, + // packet 4+ are regular audio data + if (packet.position <= 2 && _firstPacketPosition == -1) { + _readHead = 0; + _readHeadAbs = 0; + _maxWriteAbs = _loopBufferSize; + _writeHeadAbs = 2; + _jointMin[0] = 0; + _jointMin[1] = 2; + _waiting = true; + _finished = false; + _firstPacketPosition = packet.position; + fillRobotBuffer(packet, bufferIndex); + return true; + } - _fileStream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), _isBigEndian, DisposeAfterUse::YES); + const int32 packetEndByte = packet.position + (packet.dataSize * sizeof(int16) * kEOSExpansion); - readHeaderChunk(); + // Already read all the way past this packet (or already wrote valid samples + // to this channel all the way past this packet), so discard it + if (packetEndByte <= MAX(_readHeadAbs, _jointMin[bufferIndex])) { + debugC(kDebugLevelVideo, "Rejecting packet %d, read past %d / %d", packet.position, _readHeadAbs, _jointMin[bufferIndex]); + return true; + } - // There are several versions of robot files, ranging from 3 to 6. - // v3: no known examples - // v4: PQ:SWAT demo - // v5: SCI2.1 and SCI3 games - // v6: SCI3 games - if (_header.version < 4 || _header.version > 6) - error("Unknown robot version: %d", _header.version); + // The loop buffer is full, so tell the caller to send the packet again + // later + if (_maxWriteAbs <= _jointMin[bufferIndex]) { + debugC(kDebugLevelVideo, "Rejecting packet %d, full buffer", packet.position); + return false; + } - RobotVideoTrack *videoTrack = new RobotVideoTrack(_header.frameCount); - addTrack(videoTrack); + fillRobotBuffer(packet, bufferIndex); - if (_header.hasSound) - addTrack(new RobotAudioTrack()); + // This packet is the second primer, so allow playback to begin + if (_firstPacketPosition != -1 && _firstPacketPosition != packet.position) { + debugC(kDebugLevelVideo, "Done waiting. Robot audio begins"); + _waiting = false; + _firstPacketPosition = -1; + } - videoTrack->readPaletteChunk(_fileStream, _header.paletteDataSize); - readFrameSizesChunk(); - videoTrack->calculateVideoDimensions(_fileStream, _frameTotalSize); + // Only part of the packet could be read into the loop buffer before it was + // full, so tell the caller to send the packet again later + if (packetEndByte > _maxWriteAbs) { + debugC(kDebugLevelVideo, "Partial read of packet %d (%d / %d)", packet.position, packetEndByte - _maxWriteAbs, packetEndByte - packet.position); + return false; + } + + // The entire packet was successfully read into the loop buffer return true; } -bool RobotDecoder::load(GuiResourceId id) { - // TODO: RAMA's robot 1003 cannot be played (shown at the menu screen) - - // its drawn at odd coordinates. SV can't play it either (along with some - // others), so it must be some new functionality added in RAMA's robot - // videos. Skip it for now. - if (g_sci->getGameId() == GID_RAMA && id == 1003) - return false; +void RobotAudioStream::fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex) { + int32 sourceByte = 0; - // Robots for the options in the RAMA menu - if (g_sci->getGameId() == GID_RAMA && (id >= 1004 && id <= 1009)) - return false; + const int32 decompressedSize = packet.dataSize * sizeof(int16); + if (_decompressionBufferPosition != packet.position) { + if (decompressedSize != _decompressionBufferSize) { + _decompressionBuffer = (byte *)realloc(_decompressionBuffer, decompressedSize); + _decompressionBufferSize = decompressedSize; + } - // TODO: The robot video in the Lighthouse demo gets stuck - if (g_sci->getGameId() == GID_LIGHTHOUSE && id == 16) - return false; + int16 carry = 0; + deDPCM16((int16 *)_decompressionBuffer, packet.data, packet.dataSize, carry); + _decompressionBufferPosition = packet.position; + } + + int32 numBytes = decompressedSize; + int32 packetPosition = packet.position; + int32 endByte = packet.position + decompressedSize * kEOSExpansion; + int32 startByte = MAX(_readHeadAbs + bufferIndex * 2, _jointMin[bufferIndex]); + int32 maxWriteByte = _maxWriteAbs + bufferIndex * 2; + if (packetPosition < startByte) { + sourceByte = (startByte - packetPosition) / kEOSExpansion; + numBytes -= sourceByte; + packetPosition = startByte; + } + if (packetPosition > maxWriteByte) { + numBytes += (packetPosition - maxWriteByte) / kEOSExpansion; + packetPosition = maxWriteByte; + } + if (endByte > maxWriteByte) { + numBytes -= (endByte - maxWriteByte) / kEOSExpansion; + endByte = maxWriteByte; + } - Common::String fileName = Common::String::format("%d.rbt", id); + const int32 maxJointMin = MAX(_jointMin[0], _jointMin[1]); + if (endByte > maxJointMin) { + _writeHeadAbs += endByte - maxJointMin; + } + + if (packetPosition > _jointMin[bufferIndex]) { + int32 packetEndByte = packetPosition % _loopBufferSize; + int32 targetBytePosition; + int32 numBytesToEnd; + if ((packetPosition & ~3) > (_jointMin[1 - bufferIndex] & ~3)) { + targetBytePosition = _jointMin[1 - bufferIndex] % _loopBufferSize; + if (targetBytePosition >= packetEndByte) { + numBytesToEnd = _loopBufferSize - targetBytePosition; + memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd); + targetBytePosition = (1 - bufferIndex) ? 2 : 0; + } + numBytesToEnd = packetEndByte - targetBytePosition; + if (numBytesToEnd > 0) { + memset(_loopBuffer + targetBytePosition, 0, numBytesToEnd); + } + } + targetBytePosition = _jointMin[bufferIndex] % _loopBufferSize; + if (targetBytePosition >= packetEndByte) { + numBytesToEnd = _loopBufferSize - targetBytePosition; + interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0); + targetBytePosition = bufferIndex ? 2 : 0; + } + numBytesToEnd = packetEndByte - targetBytePosition; + if (numBytesToEnd > 0) { + interpolateChannel((int16 *)(_loopBuffer + targetBytePosition), numBytesToEnd / sizeof(int16) / kEOSExpansion, 0); + } + } + + if (numBytes > 0) { + int32 targetBytePosition = packetPosition % _loopBufferSize; + int32 packetEndByte = endByte % _loopBufferSize; + int32 numBytesToEnd = 0; + if (targetBytePosition >= packetEndByte) { + numBytesToEnd = (_loopBufferSize - (targetBytePosition & ~3)) / kEOSExpansion; + copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte), numBytesToEnd / kEOSExpansion); + targetBytePosition = bufferIndex ? 2 : 0; + } + copyEveryOtherSample((int16 *)(_loopBuffer + targetBytePosition), (int16 *)(_decompressionBuffer + sourceByte + numBytesToEnd), (packetEndByte - targetBytePosition) / sizeof(int16) / kEOSExpansion); + } + _jointMin[bufferIndex] = endByte; +} + +void RobotAudioStream::interpolateMissingSamples(int32 numSamples) { + int32 numBytes = numSamples * sizeof(int16) * kEOSExpansion; + int32 targetPosition = _readHead; + + if (_readHeadAbs > _jointMin[1]) { + if (_readHeadAbs > _jointMin[0]) { + if (targetPosition + numBytes >= _loopBufferSize) { + const int32 numBytesToEdge = (_loopBufferSize - targetPosition); + memset(_loopBuffer + targetPosition, 0, numBytesToEdge); + numBytes -= numBytesToEdge; + targetPosition = 0; + } + memset(_loopBuffer + targetPosition, 0, numBytes); + _jointMin[0] += numBytes; + _jointMin[1] += numBytes; + } else { + if (targetPosition + numBytes >= _loopBufferSize) { + const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion; + interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 1); + numSamples -= numSamplesToEdge; + targetPosition = 0; + } + interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 1); + _jointMin[1] += numBytes; + } + } else if (_readHeadAbs > _jointMin[0]) { + if (targetPosition + numBytes >= _loopBufferSize) { + const int32 numSamplesToEdge = (_loopBufferSize - targetPosition) / sizeof(int16) / kEOSExpansion; + interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamplesToEdge, 0); + numSamples -= numSamplesToEdge; + targetPosition = 2; + } + interpolateChannel((int16 *)(_loopBuffer + targetPosition), numSamples, 0); + _jointMin[0] += numBytes; + } +} + +void RobotAudioStream::finish() { + Common::StackLock lock(_mutex); + _finished = true; +} + +RobotAudioStream::StreamState RobotAudioStream::getStatus() const { + Common::StackLock lock(_mutex); + StreamState status; + status.bytesPlaying = _readHeadAbs; + status.rate = getRate(); + status.bits = 8 * sizeof(int16); + return status; +} + +int RobotAudioStream::readBuffer(Audio::st_sample_t *outBuffer, int numSamples) { + Common::StackLock lock(_mutex); + + if (_waiting) { + return 0; + } + + assert(!((_writeHeadAbs - _readHeadAbs) & 1)); + const int maxNumSamples = (_writeHeadAbs - _readHeadAbs) / sizeof(Audio::st_sample_t); + numSamples = MIN(numSamples, maxNumSamples); + + if (!numSamples) { + return 0; + } + + interpolateMissingSamples(numSamples); + + Audio::st_sample_t *inBuffer = (Audio::st_sample_t *)(_loopBuffer + _readHead); + + assert(!((_loopBufferSize - _readHead) & 1)); + const int numSamplesToEnd = (_loopBufferSize - _readHead) / sizeof(Audio::st_sample_t); + + int numSamplesToRead = MIN(numSamples, numSamplesToEnd); + Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer); + + if (numSamplesToRead < numSamples) { + inBuffer = (Audio::st_sample_t *)_loopBuffer; + outBuffer += numSamplesToRead; + numSamplesToRead = numSamples - numSamplesToRead; + Common::copy(inBuffer, inBuffer + numSamplesToRead, outBuffer); + } + + const int32 numBytes = numSamples * sizeof(Audio::st_sample_t); + + _readHead += numBytes; + if (_readHead > _loopBufferSize) { + _readHead -= _loopBufferSize; + } + _readHeadAbs += numBytes; + _maxWriteAbs += numBytes; + assert(!(_readHead & 1)); + assert(!(_readHeadAbs & 1)); + + return numSamples; +} + +#pragma mark - +#pragma mark RobotDecoder + +RobotDecoder::RobotDecoder(SegManager *segMan) : + _delayTime(this), + _segMan(segMan), + _status(kRobotStatusUninitialized), + _audioBuffer(nullptr), + _rawPalette((uint8 *)malloc(kRawPaletteSize)) {} + +RobotDecoder::~RobotDecoder() { + close(); + free(_rawPalette); + free(_audioBuffer); +} + +#pragma mark - +#pragma mark RobotDecoder - Initialization + +void RobotDecoder::initStream(const GuiResourceId robotId) { + const Common::String fileName = Common::String::format("%d.rbt", robotId); Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(fileName); + _fileOffset = 0; - if (!stream) { - warning("Unable to open robot file %s", fileName.c_str()); - return false; + if (stream == nullptr) { + error("Unable to open robot file %s", fileName.c_str()); + } + + const uint16 id = stream->readUint16LE(); + if (id != 0x16) { + error("Invalid robot file %s", fileName.c_str()); + } + + // TODO: Mac version not tested, so this could be totally wrong + _stream = new Common::SeekableSubReadStreamEndian(stream, 0, stream->size(), g_sci->getPlatform() == Common::kPlatformMacintosh, DisposeAfterUse::YES); + _stream->seek(2, SEEK_SET); + if (_stream->readUint32BE() != MKTAG('S', 'O', 'L', 0)) { + error("Resource %s is not Robot type!", fileName.c_str()); + } +} + +void RobotDecoder::initPlayback() { + _startFrameNo = 0; + _startTime = -1; + _startingFrameNo = -1; + _cueForceShowFrame = -1; + _previousFrameNo = -1; + _currentFrameNo = 0; + _status = kRobotStatusPaused; +} + +void RobotDecoder::initAudio() { + _syncFrame = true; + + _audioRecordInterval = RobotAudioStream::kRobotSampleRate / _frameRate; + + // TODO: Might actually be for all games newer than Lighthouse; check to + // see which games have this condition. + if (g_sci->getGameId() != GID_LIGHTHOUSE && !(_audioRecordInterval & 1)) { + ++_audioRecordInterval; + } + + _expectedAudioBlockSize = _audioBlockSize - kAudioBlockHeaderSize; + _audioBuffer = (byte *)realloc(_audioBuffer, kRobotZeroCompressSize + _expectedAudioBlockSize); + + if (_primerReservedSize != 0) { + const int32 primerHeaderPosition = _stream->pos(); + _totalPrimerSize = _stream->readSint32(); + const int16 compressionType = _stream->readSint16(); + _evenPrimerSize = _stream->readSint32(); + _oddPrimerSize = _stream->readSint32(); + _primerPosition = _stream->pos(); + + if (compressionType) { + error("Unknown audio header compression type %d", compressionType); + } + + if (_evenPrimerSize + _oddPrimerSize != _primerReservedSize) { + _stream->seek(primerHeaderPosition + _primerReservedSize, SEEK_SET); + } + } else if (_primerZeroCompressFlag) { + _evenPrimerSize = 19922; + _oddPrimerSize = 21024; + } + + _firstAudioRecordPosition = _evenPrimerSize * 2; + + const int usedEachFrame = (RobotAudioStream::kRobotSampleRate / 2) / _frameRate; + _maxSkippablePackets = MAX(0, _audioBlockSize / usedEachFrame - 1); +} + +void RobotDecoder::initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize) { + _position = Common::Point(x, y); + + if (scale != 128) { + _scaleInfo.x = scale; + _scaleInfo.y = scale; + _scaleInfo.signal = kScaleSignalDoScaling32; + } + + _plane = g_sci->_gfxFrameout->getPlanes().findByObject(plane); + if (_plane == nullptr) { + error("Invalid plane %04x:%04x passed to RobotDecoder::open", PRINT_REG(plane)); + } + + _minFrameRate = _frameRate - kMaxFrameRateDrift; + _maxFrameRate = _frameRate + kMaxFrameRateDrift; + + if (_xResolution == 0 || _yResolution == 0) { + // TODO: Default values were taken from RESOURCE.CFG hires property + // if it exists, so need to check games' configuration files for those + _xResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + _yResolution = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; } - return loadStream(stream); + if (hasPalette) { + _stream->read(_rawPalette, paletteSize); + } else { + _stream->seek(paletteSize, SEEK_CUR); + } + + _screenItemList.reserve(kScreenItemListSize); + _maxCelArea.reserve(kFixedCelListSize); + + // Fixed cel buffers are for version 5 and newer + _fixedCels.reserve(MIN(_maxCelsPerFrame, (int16)kFixedCelListSize)); + _celDecompressionBuffer.reserve(_maxCelArea[0] + SciBitmap::getBitmapHeaderSize() + kRawPaletteSize); + _celDecompressionArea = _maxCelArea[0]; +} + +void RobotDecoder::initRecordAndCuePositions() { + PositionList recordSizes; + _videoSizes.reserve(_numFramesTotal); + _recordPositions.reserve(_numFramesTotal); + recordSizes.reserve(_numFramesTotal); + + switch(_version) { + case 5: // 16-bit sizes and positions + for (int i = 0; i < _numFramesTotal; ++i) { + _videoSizes.push_back(_stream->readUint16()); + } + for (int i = 0; i < _numFramesTotal; ++i) { + recordSizes.push_back(_stream->readUint16()); + } + break; + case 6: // 32-bit sizes and positions + for (int i = 0; i < _numFramesTotal; ++i) { + _videoSizes.push_back(_stream->readSint32()); + } + for (int i = 0; i < _numFramesTotal; ++i) { + recordSizes.push_back(_stream->readSint32()); + } + break; + default: + error("Unknown Robot version %d", _version); + } + + for (int i = 0; i < kCueListSize; ++i) { + _cueTimes[i] = _stream->readSint32(); + } + + for (int i = 0; i < kCueListSize; ++i) { + _cueValues[i] = _stream->readUint16(); + } + + Common::copy(_cueTimes, _cueTimes + kCueListSize, _masterCueTimes); + + int bytesRemaining = (_stream->pos() - _fileOffset) % kRobotFrameSize; + if (bytesRemaining != 0) { + _stream->seek(kRobotFrameSize - bytesRemaining, SEEK_CUR); + } + + int position = _stream->pos(); + _recordPositions.push_back(position); + for (int i = 0; i < _numFramesTotal - 1; ++i) { + position += recordSizes[i]; + _recordPositions.push_back(position); + } +} + +#pragma mark - +#pragma mark RobotDecoder - Playback + +void RobotDecoder::open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale) { + if (_status != kRobotStatusUninitialized) { + warning("Last robot was not closed"); + close(); + } + + initStream(robotId); + + _version = _stream->readUint16(); + + // TODO: Version 4 for PQ:SWAT demo? + if (_version < 5 || _version > 6) { + error("Unsupported version %d of Robot resource", _version); + } + + debugC(kDebugLevelVideo, "Opening version %d robot %d", _version, robotId); + + initPlayback(); + + _audioBlockSize = _stream->readUint16(); + _primerZeroCompressFlag = _stream->readSint16(); + _stream->seek(2, SEEK_CUR); // unused + _numFramesTotal = _stream->readUint16(); + const uint16 paletteSize = _stream->readUint16(); + _primerReservedSize = _stream->readUint16(); + _xResolution = _stream->readSint16(); + _yResolution = _stream->readSint16(); + const bool hasPalette = (bool)_stream->readByte(); + _hasAudio = (bool)_stream->readByte(); + _stream->seek(2, SEEK_CUR); // unused + _frameRate = _normalFrameRate = _stream->readSint16(); + _isHiRes = (bool)_stream->readSint16(); + _maxSkippablePackets = _stream->readSint16(); + _maxCelsPerFrame = _stream->readSint16(); + + // used for memory preallocation of fixed cels + _maxCelArea.push_back(_stream->readSint32()); + _maxCelArea.push_back(_stream->readSint32()); + _maxCelArea.push_back(_stream->readSint32()); + _maxCelArea.push_back(_stream->readSint32()); + _stream->seek(8, SEEK_CUR); // reserved + + if (_hasAudio) { + initAudio(); + } else { + _stream->seek(_primerReservedSize, SEEK_CUR); + } + + _priority = priority; + initVideo(x, y, scale, plane, hasPalette, paletteSize); + initRecordAndCuePositions(); } void RobotDecoder::close() { - VideoDecoder::close(); + if (_status == kRobotStatusUninitialized) { + return; + } + + debugC(kDebugLevelVideo, "Closing robot"); - delete _fileStream; - _fileStream = 0; + _status = kRobotStatusUninitialized; + _videoSizes.clear(); + _recordPositions.clear(); + _celDecompressionBuffer.clear(); + _doVersion5Scratch.clear(); + delete _stream; + _stream = nullptr; + + for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) { + if (_celHandles[i].status == CelHandleInfo::kFrameLifetime) { + _segMan->freeBitmap(_celHandles[i].bitmapId); + } + } + _celHandles.clear(); + + for (FixedCelsList::size_type i = 0; i < _fixedCels.size(); ++i) { + _segMan->freeBitmap(_fixedCels[i]); + } + _fixedCels.clear(); + + if (g_sci->_gfxFrameout->getPlanes().findByObject(_plane->_object) != nullptr) { + for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + if (_screenItemList[i] != nullptr) { + g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]); + } + } + } + _screenItemList.clear(); - delete[] _frameTotalSize; - _frameTotalSize = 0; + if (_hasAudio) { + _audioList.reset(); + } } -void RobotDecoder::readNextPacket() { - // Get our track - RobotVideoTrack *videoTrack = (RobotVideoTrack *)getTrack(0); - videoTrack->increaseCurFrame(); - Graphics::Surface *surface = videoTrack->getSurface(); +void RobotDecoder::pause() { + if (_status != kRobotStatusPlaying) { + return; + } + + if (_hasAudio) { + _audioList.stopAudioNow(); + } + + _status = kRobotStatusPaused; + _frameRate = _normalFrameRate; +} - if (videoTrack->endOfTrack()) +void RobotDecoder::resume() { + if (_status != kRobotStatusPaused) { return; + } + + _startingFrameNo = _currentFrameNo; + _status = kRobotStatusPlaying; + if (_hasAudio) { + primeAudio(_currentFrameNo * 60 / _frameRate); + _syncFrame = true; + } + + setRobotTime(_currentFrameNo); + for (int i = 0; i < kCueListSize; ++i) { + if (_masterCueTimes[i] != -1 && _masterCueTimes[i] < _currentFrameNo) { + _cueTimes[i] = -1; + } else { + _cueTimes[i] = _masterCueTimes[i]; + } + } +} - // Read frame image header (24 bytes) - _fileStream->skip(3); - byte frameScale = _fileStream->readByte(); - uint16 frameWidth = _fileStream->readUint16(); - uint16 frameHeight = _fileStream->readUint16(); - _fileStream->skip(4); // unknown, almost always 0 - uint16 frameX = _fileStream->readUint16(); - uint16 frameY = _fileStream->readUint16(); - - // TODO: In v4 robot files, frameX and frameY have a different meaning. - // Set them both to 0 for v4 for now, so that robots in PQ:SWAT show up - // correctly. - if (_header.version == 4) - frameX = frameY = 0; - - uint16 compressedSize = _fileStream->readUint16(); - uint16 frameFragments = _fileStream->readUint16(); - _fileStream->skip(4); // unknown - uint32 decompressedSize = frameWidth * frameHeight * frameScale / 100; - - // FIXME: A frame's height + position can go off limits... why? With the - // following, we cut the contents to fit the frame - uint16 scaledHeight = CLIP<uint16>(decompressedSize / frameWidth, 0, surface->h - frameY); - - // FIXME: Same goes for the frame's width + position. In this case, we - // modify the position to fit the contents on screen. - if (frameWidth + frameX > surface->w) - frameX = surface->w - frameWidth; - - assert(frameWidth + frameX <= surface->w && scaledHeight + frameY <= surface->h); - - DecompressorLZS lzs; - byte *decompressedFrame = new byte[decompressedSize]; - byte *outPtr = decompressedFrame; - - if (_header.version == 4) { - // v4 has just the one fragment, it seems, and ignores the fragment count - Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedSize); - lzs.unpack(&fragmentStream, outPtr, compressedSize, decompressedSize); +void RobotDecoder::showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority) { + debugC(kDebugLevelVideo, "Show frame %d (%d %d %d)", frameNo, newX, newY, newPriority); + + if (newX != kUnspecified) { + _position.x = newX; + } + + if (newY != kUnspecified) { + _position.y = newY; + } + + if (newPriority != kUnspecified) { + _priority = newPriority; + } + + _currentFrameNo = frameNo; + pause(); + + if (frameNo != _previousFrameNo) { + seekToFrame(frameNo); + doVersion5(false); } else { - for (uint16 i = 0; i < frameFragments; ++i) { - uint32 compressedFragmentSize = _fileStream->readUint32(); - uint32 decompressedFragmentSize = _fileStream->readUint32(); - uint16 compressionType = _fileStream->readUint16(); - - if (compressionType == 0) { - Common::SeekableSubReadStream fragmentStream(_fileStream, _fileStream->pos(), _fileStream->pos() + compressedFragmentSize); - lzs.unpack(&fragmentStream, outPtr, compressedFragmentSize, decompressedFragmentSize); - } else if (compressionType == 2) { // untested - _fileStream->read(outPtr, compressedFragmentSize); + for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + if (_isHiRes) { + SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[i].bitmapId); + + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + if (scriptWidth == kLowResX && scriptHeight == kLowResY) { + const Ratio lowResToScreenX(screenWidth, kLowResX); + const Ratio lowResToScreenY(screenHeight, kLowResY); + const Ratio screenToLowResX(kLowResX, screenWidth); + const Ratio screenToLowResY(kLowResY, screenHeight); + + const int16 scaledX = _originalScreenItemX[i] + (_position.x * lowResToScreenX).toInt(); + const int16 scaledY1 = _originalScreenItemY[i] + (_position.y * lowResToScreenY).toInt(); + const int16 scaledY2 = scaledY1 + bitmap.getHeight() - 1; + + const int16 lowResX = (scaledX * screenToLowResX).toInt(); + const int16 lowResY = (scaledY2 * screenToLowResY).toInt(); + + bitmap.setDisplace(Common::Point( + (scaledX - (lowResX * lowResToScreenX).toInt()) * -1, + (lowResY * lowResToScreenY).toInt() - scaledY1 + )); + + _screenItemX[i] = lowResX; + _screenItemY[i] = lowResY; + } else { + const int16 scaledX = _originalScreenItemX[i] + _position.x; + const int16 scaledY = _originalScreenItemY[i] + _position.y + bitmap.getHeight() - 1; + bitmap.setDisplace(Common::Point(0, bitmap.getHeight() - 1)); + _screenItemX[i] = scaledX; + _screenItemY[i] = scaledY; + } + } else { + _screenItemX[i] = _originalScreenItemX[i] + _position.x; + _screenItemY[i] = _originalScreenItemY[i] + _position.y; + } + + if (_screenItemList[i] == nullptr) { + CelInfo32 celInfo; + celInfo.type = kCelTypeMem; + celInfo.bitmap = _celHandles[i].bitmapId; + ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo); + _screenItemList[i] = screenItem; + screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]); + if (_priority == -1) { + screenItem->_fixedPriority = false; + } else { + screenItem->_priority = _priority; + screenItem->_fixedPriority = true; + } + g_sci->_gfxFrameout->addScreenItem(*screenItem); } else { - error("Unknown frame compression found: %d", compressionType); + ScreenItem *screenItem = _screenItemList[i]; + screenItem->_celInfo.bitmap = _celHandles[i].bitmapId; + screenItem->_position = Common::Point(_screenItemX[i], _screenItemY[i]); + if (_priority == -1) { + screenItem->_fixedPriority = false; + } else { + screenItem->_priority = _priority; + screenItem->_fixedPriority = true; + } + g_sci->_gfxFrameout->updateScreenItem(*screenItem); + } + } + } + + _previousFrameNo = frameNo; +} + +int16 RobotDecoder::getCue() const { + if (_status == kRobotStatusUninitialized || + _status == kRobotStatusPaused || + _syncFrame) { + return 0; + } + + if (_status == kRobotStatusEnd) { + return -1; + } + + const uint16 estimatedNextFrameNo = MIN(calculateNextFrameNo(_delayTime.predictedTicks()), _numFramesTotal); + + for (int i = 0; i < kCueListSize; ++i) { + if (_cueTimes[i] != -1 && _cueTimes[i] <= estimatedNextFrameNo) { + if (_cueTimes[i] >= _previousFrameNo) { + _cueForceShowFrame = _cueTimes[i] + 1; } - outPtr += decompressedFragmentSize; + _cueTimes[i] = -1; + return _cueValues[i]; } } - // Copy over the decompressed frame - byte *inFrame = decompressedFrame; - byte *outFrame = (byte *)surface->getPixels(); + return 0; +} - // Black out the surface - memset(outFrame, 0, surface->w * surface->h); +int16 RobotDecoder::getFrameNo() const { + if (_status == kRobotStatusUninitialized) { + return 0; + } - // Move to the correct y coordinate - outFrame += surface->w * frameY; + return _currentFrameNo; +} + +RobotDecoder::RobotStatus RobotDecoder::getStatus() const { + return _status; +} - for (uint16 y = 0; y < scaledHeight; y++) { - memcpy(outFrame + frameX, inFrame, frameWidth); - inFrame += frameWidth; - outFrame += surface->w; +bool RobotDecoder::seekToFrame(const int frameNo) { + return _stream->seek(_recordPositions[frameNo], SEEK_SET); +} + +void RobotDecoder::setRobotTime(const int frameNo) { + _startTime = getTickCount(); + _startFrameNo = frameNo; +} + +#pragma mark - +#pragma mark RobotDecoder - Timing + +RobotDecoder::DelayTime::DelayTime(RobotDecoder *decoder) : + _decoder(decoder) { + for (int i = 0; i < kDelayListSize; ++i) { + _timestamps[i] = i; + _delays[i] = 0; } - delete[] decompressedFrame; + _oldestTimestamp = 0; + _newestTimestamp = kDelayListSize - 1; + _startTime = 0; +} - uint32 audioChunkSize = _frameTotalSize[videoTrack->getCurFrame()] - (24 + compressedSize); +void RobotDecoder::DelayTime::startTiming() { + _startTime = _decoder->getTickCount(); +} -// TODO: The audio chunk size below is usually correct, but there are some -// exceptions (e.g. robot 4902 in Phantasmagoria, towards its end) -#if 0 - // Read frame audio header (14 bytes) - _fileStream->skip(2); // buffer position - _fileStream->skip(2); // unknown (usually 1) - _fileStream->skip(2); /*uint16 audioChunkSize = _fileStream->readUint16() + 8;*/ - _fileStream->skip(2); -#endif +void RobotDecoder::DelayTime::endTiming() { + const int timeDelta = _decoder->getTickCount() - _startTime; + for (uint i = 0; i < kDelayListSize; ++i) { + if (_timestamps[i] == _oldestTimestamp) { + _timestamps[i] = ++_newestTimestamp; + _delays[i] = timeDelta; + break; + } + } + ++_newestTimestamp; + _startTime = 0; + sortList(); +} - // Queue the next audio frame - // FIXME: For some reason, there are audio hiccups/gaps - if (_header.hasSound) { - RobotAudioTrack *audioTrack = (RobotAudioTrack *)getTrack(1); - _fileStream->skip(8); // header - audioChunkSize -= 8; - audioTrack->queueBuffer(g_sci->_audio->getDecodedRobotAudioFrame(_fileStream, audioChunkSize), audioChunkSize * 2); +bool RobotDecoder::DelayTime::timingInProgress() const { + return _startTime != 0; +} + +int RobotDecoder::DelayTime::predictedTicks() const { + return _delays[kDelayListSize / 2]; +} + +void RobotDecoder::DelayTime::sortList() { + for (uint i = 0; i < kDelayListSize - 1; ++i) { + int smallestDelay = _delays[i]; + uint smallestIndex = i; + + for (uint j = i + 1; j < kDelayListSize - 1; ++j) { + if (_delays[j] < smallestDelay) { + smallestDelay = _delays[j]; + smallestIndex = j; + } + } + + if (smallestIndex != i) { + SWAP(_delays[i], _delays[smallestIndex]); + SWAP(_timestamps[i], _timestamps[smallestIndex]); + } + } +} + +uint16 RobotDecoder::calculateNextFrameNo(const uint32 extraTicks) const { + return ticksToFrames(getTickCount() + extraTicks - _startTime) + _startFrameNo; +} + +uint32 RobotDecoder::ticksToFrames(const uint32 ticks) const { + return (ticks * _frameRate) / 60; +} + +uint32 RobotDecoder::getTickCount() const { + return g_sci->getTickCount(); +} + +#pragma mark - +#pragma mark RobotDecoder - Audio + +RobotDecoder::AudioList::AudioList() : + _blocks(), + _blocksSize(0), + _oldestBlockIndex(0), + _newestBlockIndex(0), + _startOffset(0), + _status(kRobotAudioReady) {} + +void RobotDecoder::AudioList::startAudioNow() { + submitDriverMax(); + g_sci->_audio32->resume(kRobotChannel); + _status = kRobotAudioPlaying; +} + +void RobotDecoder::AudioList::stopAudio() { + g_sci->_audio32->finishRobotAudio(); + freeAudioBlocks(); + _status = kRobotAudioStopping; +} + +void RobotDecoder::AudioList::stopAudioNow() { + if (_status == kRobotAudioPlaying || _status == kRobotAudioStopping || _status == kRobotAudioPaused) { + g_sci->_audio32->stopRobotAudio(); + _status = kRobotAudioStopped; + } + + freeAudioBlocks(); +} + +void RobotDecoder::AudioList::submitDriverMax() { + while (_blocksSize != 0) { + if (!_blocks[_oldestBlockIndex]->submit(_startOffset)) { + return; + } + + delete _blocks[_oldestBlockIndex]; + _blocks[_oldestBlockIndex] = nullptr; + ++_oldestBlockIndex; + if (_oldestBlockIndex == kAudioListSize) { + _oldestBlockIndex = 0; + } + + --_blocksSize; + } +} + +void RobotDecoder::AudioList::addBlock(const int position, const int size, const byte *data) { + assert(data != nullptr); + assert(size >= 0); + assert(position >= -1); + + if (_blocksSize == kAudioListSize) { + delete _blocks[_oldestBlockIndex]; + _blocks[_oldestBlockIndex] = nullptr; + ++_oldestBlockIndex; + if (_oldestBlockIndex == kAudioListSize) { + _oldestBlockIndex = 0; + } + --_blocksSize; + } + + if (_blocksSize == 0) { + _oldestBlockIndex = _newestBlockIndex = 0; } else { - _fileStream->skip(audioChunkSize); - } -} - -void RobotDecoder::readHeaderChunk() { - // Header (60 bytes) - _fileStream->skip(6); - _header.version = _fileStream->readUint16(); - _header.audioChunkSize = _fileStream->readUint16(); - _header.audioSilenceSize = _fileStream->readUint16(); - _fileStream->skip(2); - _header.frameCount = _fileStream->readUint16(); - _header.paletteDataSize = _fileStream->readUint16(); - _header.unkChunkDataSize = _fileStream->readUint16(); - _fileStream->skip(5); - _header.hasSound = _fileStream->readByte(); - _fileStream->skip(34); - - // Some videos (e.g. robot 1305 in Phantasmagoria and - // robot 184 in Lighthouse) have an unknown chunk before - // the palette chunk (probably used for sound preloading). - // Skip it here. - if (_header.unkChunkDataSize) - _fileStream->skip(_header.unkChunkDataSize); -} - -void RobotDecoder::readFrameSizesChunk() { - // The robot video file contains 2 tables, with one entry for each frame: - // - A table containing the size of the image in each video frame - // - A table containing the total size of each video frame. - // In v5 robots, the tables contain 16-bit integers, whereas in v6 robots, - // they contain 32-bit integers. - - _frameTotalSize = new uint32[_header.frameCount]; - - // TODO: The table reading code can probably be removed once the - // audio chunk size is figured out (check the TODO inside processNextFrame()) -#if 0 - // We don't need any of the two tables to play the video, so we ignore - // both of them. - uint16 wordSize = _header.version == 6 ? 4 : 2; - _fileStream->skip(_header.frameCount * wordSize * 2); -#else - switch (_header.version) { - case 4: - case 5: // sizes are 16-bit integers - // Skip table with frame image sizes, as we don't need it - _fileStream->skip(_header.frameCount * 2); - for (int i = 0; i < _header.frameCount; ++i) - _frameTotalSize[i] = _fileStream->readUint16(); - break; - case 6: // sizes are 32-bit integers - // Skip table with frame image sizes, as we don't need it - _fileStream->skip(_header.frameCount * 4); - for (int i = 0; i < _header.frameCount; ++i) - _frameTotalSize[i] = _fileStream->readUint32(); - break; - default: - error("Can't yet handle index table for robot version %d", _header.version); + ++_newestBlockIndex; + if (_newestBlockIndex == kAudioListSize) { + _newestBlockIndex = 0; + } } -#endif - // 2 more unknown tables - _fileStream->skip(1024 + 512); + _blocks[_newestBlockIndex] = new AudioBlock(position, size, data); + ++_blocksSize; +} - // Pad to nearest 2 kilobytes - uint32 curPos = _fileStream->pos(); - if (curPos & 0x7ff) - _fileStream->seek((curPos & ~0x7ff) + 2048); +void RobotDecoder::AudioList::reset() { + stopAudioNow(); + _startOffset = 0; + _status = kRobotAudioReady; } -RobotDecoder::RobotVideoTrack::RobotVideoTrack(int frameCount) : _frameCount(frameCount) { - _surface = new Graphics::Surface(); - _curFrame = -1; - _dirtyPalette = false; +void RobotDecoder::AudioList::prepareForPrimer() { + g_sci->_audio32->pause(kRobotChannel); + _status = kRobotAudioPaused; } -RobotDecoder::RobotVideoTrack::~RobotVideoTrack() { - _surface->free(); - delete _surface; +void RobotDecoder::AudioList::setAudioOffset(const int offset) { + _startOffset = offset; } -uint16 RobotDecoder::RobotVideoTrack::getWidth() const { - return _surface->w; +RobotDecoder::AudioList::AudioBlock::AudioBlock(const int position, const int size, const byte* const data) : + _position(position), + _size(size) { + _data = (byte *)malloc(size); + memcpy(_data, data, size); } -uint16 RobotDecoder::RobotVideoTrack::getHeight() const { - return _surface->h; +RobotDecoder::AudioList::AudioBlock::~AudioBlock() { + free(_data); } -Graphics::PixelFormat RobotDecoder::RobotVideoTrack::getPixelFormat() const { - return _surface->format; +bool RobotDecoder::AudioList::AudioBlock::submit(const int startOffset) { + assert(_data != nullptr); + RobotAudioStream::RobotAudioPacket packet(_data, _size, (_position - startOffset) * 2); + return g_sci->_audio32->playRobotAudio(packet); } -void RobotDecoder::RobotVideoTrack::readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize) { - byte *paletteData = new byte[chunkSize]; - stream->read(paletteData, chunkSize); +void RobotDecoder::AudioList::freeAudioBlocks() { + while (_blocksSize != 0) { + delete _blocks[_oldestBlockIndex]; + _blocks[_oldestBlockIndex] = nullptr; + ++_oldestBlockIndex; + if (_oldestBlockIndex == kAudioListSize) { + _oldestBlockIndex = 0; + } + + --_blocksSize; + } +} - // SCI1.1 palette - byte palFormat = paletteData[32]; - uint16 palColorStart = paletteData[25]; - uint16 palColorCount = READ_SCI11ENDIAN_UINT16(paletteData + 29); +bool RobotDecoder::primeAudio(const uint32 startTick) { + bool success = true; + _audioList.reset(); + + if (startTick == 0) { + _audioList.prepareForPrimer(); + byte *evenPrimerBuff = new byte[_evenPrimerSize]; + byte *oddPrimerBuff = new byte[_oddPrimerSize]; + + success = readPrimerData(evenPrimerBuff, oddPrimerBuff); + if (success) { + if (_evenPrimerSize != 0) { + _audioList.addBlock(0, _evenPrimerSize, evenPrimerBuff); + } + if (_oddPrimerSize != 0) { + _audioList.addBlock(1, _oddPrimerSize, oddPrimerBuff); + } + } + + delete[] evenPrimerBuff; + delete[] oddPrimerBuff; + } else { + assert(_evenPrimerSize * 2 >= _audioRecordInterval || _oddPrimerSize * 2 >= _audioRecordInterval); + + int audioStartFrame = 0; + int videoStartFrame = startTick * _frameRate / 60; + assert(videoStartFrame < _numFramesTotal); + + int audioStartPosition = (startTick * RobotAudioStream::kRobotSampleRate) / 60; + if (audioStartPosition & 1) { + audioStartPosition--; + } + _audioList.setAudioOffset(audioStartPosition); + _audioList.prepareForPrimer(); + + if (audioStartPosition < _evenPrimerSize * 2 || + audioStartPosition + 1 < _oddPrimerSize * 2) { + + byte *evenPrimerBuffer = new byte[_evenPrimerSize]; + byte *oddPrimerBuffer = new byte[_oddPrimerSize]; + success = readPrimerData(evenPrimerBuffer, oddPrimerBuffer); + if (success) { + int halfAudioStartPosition = audioStartPosition / 2; + if (audioStartPosition < _evenPrimerSize * 2) { + _audioList.addBlock(audioStartPosition, _evenPrimerSize - halfAudioStartPosition, &evenPrimerBuffer[halfAudioStartPosition]); + } + + if (audioStartPosition + 1 < _oddPrimerSize * 2) { + _audioList.addBlock(audioStartPosition + 1, _oddPrimerSize - halfAudioStartPosition, &oddPrimerBuffer[halfAudioStartPosition]); + } + } + + delete[] evenPrimerBuffer; + delete[] oddPrimerBuffer; + } - int palOffset = 37; - memset(_palette, 0, 256 * 3); + if (audioStartPosition >= _firstAudioRecordPosition) { + int audioRecordSize = _expectedAudioBlockSize; + assert(audioRecordSize > 0); + assert(_audioRecordInterval > 0); + assert(_firstAudioRecordPosition >= 0); - for (uint16 colorNo = palColorStart; colorNo < palColorStart + palColorCount; colorNo++) { - if (palFormat == kRobotPalVariable) - palOffset++; - _palette[colorNo * 3 + 0] = paletteData[palOffset++]; - _palette[colorNo * 3 + 1] = paletteData[palOffset++]; - _palette[colorNo * 3 + 2] = paletteData[palOffset++]; + audioStartFrame = (audioStartPosition - _firstAudioRecordPosition) / _audioRecordInterval; + assert(audioStartFrame < videoStartFrame); + + if (audioStartFrame > 0) { + int lastAudioFrame = audioStartFrame - 1; + int oddRemainder = lastAudioFrame & 1; + int audioRecordStart = (lastAudioFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition; + int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition; + + if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) { + --audioStartFrame; + } + } + + assert(!(audioStartPosition & 1)); + if (audioStartFrame & 1) { + ++audioStartPosition; + } + + if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition)) { + return false; + } + + ++audioStartFrame; + assert(audioStartFrame < videoStartFrame); + + int oddRemainder = audioStartFrame & 1; + int audioRecordStart = (audioStartFrame * _audioRecordInterval) + oddRemainder + _firstAudioRecordPosition; + int audioRecordEnd = (audioRecordStart + ((audioRecordSize - 1) * 2)) + oddRemainder + _firstAudioRecordPosition; + + if (audioStartPosition >= audioRecordStart && audioStartPosition <= audioRecordEnd) { + if (!readPartialAudioRecordAndSubmit(audioStartFrame, audioStartPosition + 1)) { + return false; + } + + ++audioStartFrame; + } + } + + int audioPosition, audioSize; + for (int i = audioStartFrame; i < videoStartFrame; i++) { + if (!readAudioDataFromRecord(i, _audioBuffer, audioPosition, audioSize)) { + break; + } + + _audioList.addBlock(audioPosition, audioSize, _audioBuffer); + } } - _dirtyPalette = true; - delete[] paletteData; + return success; } -void RobotDecoder::RobotVideoTrack::calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes) { - // This is an O(n) operation, as each frame has a different size. - // We need to know the actual frame size to have a constant video size. - uint32 pos = stream->pos(); +bool RobotDecoder::readPrimerData(byte *outEvenBuffer, byte *outOddBuffer) { + if (_primerReservedSize != 0) { + if (_totalPrimerSize != 0) { + _stream->seek(_primerPosition, SEEK_SET); + if (_evenPrimerSize > 0) { + _stream->read(outEvenBuffer, _evenPrimerSize); + } + + if (_oddPrimerSize > 0) { + _stream->read(outOddBuffer, _oddPrimerSize); + } + } + } else if (_primerZeroCompressFlag) { + memset(outEvenBuffer, 0, _evenPrimerSize); + memset(outOddBuffer, 0, _oddPrimerSize); + } else { + error("ReadPrimerData - Flags corrupt"); + } + + return !_stream->err(); +} + +bool RobotDecoder::readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize) { + _stream->seek(_recordPositions[frameNo] + _videoSizes[frameNo], SEEK_SET); + _audioList.submitDriverMax(); + + // Compressed absolute position of the audio block in the audio stream + const int position = _stream->readSint32(); - uint16 width = 0, height = 0; + // Size of the block of audio, excluding the audio block header + int size = _stream->readSint32(); - for (int curFrame = 0; curFrame < _frameCount; curFrame++) { - stream->skip(4); - uint16 frameWidth = stream->readUint16(); - uint16 frameHeight = stream->readUint16(); - if (frameWidth > width) - width = frameWidth; - if (frameHeight > height) - height = frameHeight; - stream->skip(frameSizes[curFrame] - 8); + assert(size <= _expectedAudioBlockSize); + + if (position == 0) { + return false; } - stream->seek(pos); + if (size != _expectedAudioBlockSize) { + memset(outBuffer, 0, kRobotZeroCompressSize); + _stream->read(outBuffer + kRobotZeroCompressSize, size); + size += kRobotZeroCompressSize; + } else { + _stream->read(outBuffer, size); + } - _surface->create(width, height, Graphics::PixelFormat::createFormatCLUT8()); + outAudioPosition = position; + outAudioSize = size; + return !_stream->err(); } -RobotDecoder::RobotAudioTrack::RobotAudioTrack() { - _audioStream = Audio::makeQueuingAudioStream(11025, false); +bool RobotDecoder::readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition) { + int audioPosition, audioSize; + bool success = readAudioDataFromRecord(startFrame, _audioBuffer, audioPosition, audioSize); + if (success) { + const int relativeStartOffset = (startPosition - audioPosition) / 2; + _audioList.addBlock(startPosition, audioSize - relativeStartOffset, _audioBuffer + relativeStartOffset); + } + + return success; } -RobotDecoder::RobotAudioTrack::~RobotAudioTrack() { - delete _audioStream; +#pragma mark - +#pragma mark RobotDecoder - Rendering + +uint16 RobotDecoder::getFrameSize(Common::Rect &outRect) const { + outRect.clip(0, 0); + for (RobotScreenItemList::size_type i = 0; i < _screenItemList.size(); ++i) { + ScreenItem &screenItem = *_screenItemList[i]; + outRect.extend(screenItem.getNowSeenRect(*_plane)); + } + + return _numFramesTotal; } -void RobotDecoder::RobotAudioTrack::queueBuffer(byte *buffer, int size) { - _audioStream->queueBuffer(buffer, size, DisposeAfterUse::YES, Audio::FLAG_16BITS | Audio::FLAG_LITTLE_ENDIAN); +void RobotDecoder::doRobot() { + if (_status != kRobotStatusPlaying) { + return; + } + + if (!_syncFrame) { + if (_cueForceShowFrame != -1) { + _currentFrameNo = _cueForceShowFrame; + _cueForceShowFrame = -1; + } else { + const int nextFrameNo = calculateNextFrameNo(_delayTime.predictedTicks()); + if (nextFrameNo < _currentFrameNo) { + return; + } + _currentFrameNo = nextFrameNo; + } + } + + if (_currentFrameNo >= _numFramesTotal) { + const int finalFrameNo = _numFramesTotal - 1; + if (_previousFrameNo == finalFrameNo) { + _status = kRobotStatusEnd; + if (_hasAudio) { + _audioList.stopAudio(); + _frameRate = _normalFrameRate; + _hasAudio = false; + } + return; + } else { + _currentFrameNo = finalFrameNo; + } + } + + if (_currentFrameNo == _previousFrameNo) { + _audioList.submitDriverMax(); + return; + } + + if (_hasAudio) { + for (int candidateFrameNo = _previousFrameNo + _maxSkippablePackets + 1; candidateFrameNo < _currentFrameNo; candidateFrameNo += _maxSkippablePackets + 1) { + + _audioList.submitDriverMax(); + + int audioPosition, audioSize; + if (readAudioDataFromRecord(candidateFrameNo, _audioBuffer, audioPosition, audioSize)) { + _audioList.addBlock(audioPosition, audioSize, _audioBuffer); + } + } + _audioList.submitDriverMax(); + } + + _delayTime.startTiming(); + seekToFrame(_currentFrameNo); + doVersion5(); + if (_hasAudio) { + _audioList.submitDriverMax(); + } +} + +void RobotDecoder::frameAlmostVisible() { + if (_status == kRobotStatusPlaying && !_syncFrame) { + if (_previousFrameNo != _currentFrameNo) { + while (calculateNextFrameNo() < _currentFrameNo) { + _audioList.submitDriverMax(); + } + } + } +} + +void RobotDecoder::frameNowVisible() { + if (_status != kRobotStatusPlaying) { + return; + } + + if (_syncFrame) { + _syncFrame = false; + if (_hasAudio) { + _audioList.startAudioNow(); + _checkAudioSyncTime = _startTime + kAudioSyncCheckInterval; + } + + setRobotTime(_currentFrameNo); + } + + if (_delayTime.timingInProgress()) { + _delayTime.endTiming(); + } + + if (_hasAudio) { + _audioList.submitDriverMax(); + } + + if (_previousFrameNo != _currentFrameNo) { + _previousFrameNo = _currentFrameNo; + } + + if (!_syncFrame && _hasAudio && getTickCount() >= _checkAudioSyncTime) { + RobotAudioStream::StreamState status; + const bool success = g_sci->_audio32->queryRobotAudio(status); + if (!success) { + return; + } + + const int bytesPerFrame = status.rate / _normalFrameRate * (status.bits == 16 ? 2 : 1); + // check again in 1/3rd second + _checkAudioSyncTime = getTickCount() + 60 / 3; + + const int currentVideoFrameNo = calculateNextFrameNo() - _startingFrameNo; + const int currentAudioFrameNo = status.bytesPlaying / bytesPerFrame; + debugC(kDebugLevelVideo, "Video frame %d %s audio frame %d", currentVideoFrameNo, currentVideoFrameNo == currentAudioFrameNo ? "=" : currentVideoFrameNo < currentAudioFrameNo ? "<" : ">", currentAudioFrameNo); + if (currentVideoFrameNo < _numFramesTotal && + currentAudioFrameNo < _numFramesTotal) { + + bool shouldResetRobotTime = false; + + if (currentAudioFrameNo < currentVideoFrameNo - 1 && _frameRate != _minFrameRate) { + debugC(kDebugLevelVideo, "[v] Reducing frame rate"); + _frameRate = _minFrameRate; + shouldResetRobotTime = true; + } else if (currentAudioFrameNo > currentVideoFrameNo + 1 && _frameRate != _maxFrameRate) { + debugC(kDebugLevelVideo, "[^] Increasing frame rate"); + _frameRate = _maxFrameRate; + shouldResetRobotTime = true; + } else if (_frameRate != _normalFrameRate) { + debugC(kDebugLevelVideo, "[=] Setting to normal frame rate"); + _frameRate = _normalFrameRate; + shouldResetRobotTime = true; + } + + if (shouldResetRobotTime) { + if (currentAudioFrameNo < _currentFrameNo) { + setRobotTime(_currentFrameNo); + } else { + setRobotTime(currentAudioFrameNo); + } + } + } + } +} + +void RobotDecoder::expandCel(byte* target, const byte* source, const int16 celWidth, const int16 celHeight) const { + assert(source != nullptr && target != nullptr); + + const int sourceHeight = (celHeight * _verticalScaleFactor) / 100; + assert(sourceHeight > 0); + + const int16 numerator = celHeight; + const int16 denominator = sourceHeight; + int remainder = 0; + for (int16 y = sourceHeight - 1; y >= 0; --y) { + remainder += numerator; + int16 linesToDraw = remainder / denominator; + remainder %= denominator; + + while (linesToDraw--) { + memcpy(target, source, celWidth); + target += celWidth; + } + + source += celWidth; + } +} + +void RobotDecoder::setPriority(const int16 newPriority) { + _priority = newPriority; +} + +void RobotDecoder::doVersion5(const bool shouldSubmitAudio) { + const RobotScreenItemList::size_type oldScreenItemCount = _screenItemList.size(); + const int videoSize = _videoSizes[_currentFrameNo]; + _doVersion5Scratch.resize(videoSize); + + byte *videoFrameData = _doVersion5Scratch.begin(); + + if (!_stream->read(videoFrameData, videoSize)) { + error("RobotDecoder::doVersion5: Read error"); + } + + const RobotScreenItemList::size_type screenItemCount = READ_SCI11ENDIAN_UINT16(videoFrameData); + + if (screenItemCount > kScreenItemListSize) { + return; + } + + if (_hasAudio && + (getSciVersion() < SCI_VERSION_3 || shouldSubmitAudio)) { + int audioPosition, audioSize; + if (readAudioDataFromRecord(_currentFrameNo, _audioBuffer, audioPosition, audioSize)) { + _audioList.addBlock(audioPosition, audioSize, _audioBuffer); + } + } + + if (screenItemCount > oldScreenItemCount) { + _screenItemList.resize(screenItemCount); + _screenItemX.resize(screenItemCount); + _screenItemY.resize(screenItemCount); + _originalScreenItemX.resize(screenItemCount); + _originalScreenItemY.resize(screenItemCount); + } + + createCels5(videoFrameData + 2, screenItemCount, true); + for (RobotScreenItemList::size_type i = 0; i < screenItemCount; ++i) { + Common::Point position(_screenItemX[i], _screenItemY[i]); + +// TODO: Version 6 robot? +// int scaleXRemainder; + if (_scaleInfo.signal == kScaleSignalDoScaling32) { + position.x = (position.x * _scaleInfo.x) / 128; +// TODO: Version 6 robot? +// scaleXRemainder = (position.x * _scaleInfo.x) % 128; + position.y = (position.y * _scaleInfo.y) / 128; + } + + if (_screenItemList[i] == nullptr) { + CelInfo32 celInfo; + celInfo.bitmap = _celHandles[i].bitmapId; + ScreenItem *screenItem = new ScreenItem(_plane->_object, celInfo, position, _scaleInfo); + _screenItemList[i] = screenItem; + // TODO: Version 6 robot? + // screenItem->_field_30 = scaleXRemainder; + + if (_priority == -1) { + screenItem->_fixedPriority = false; + } else { + screenItem->_fixedPriority = true; + screenItem->_priority = _priority; + } + g_sci->_gfxFrameout->addScreenItem(*screenItem); + } else { + ScreenItem *screenItem = _screenItemList[i]; + screenItem->_celInfo.bitmap = _celHandles[i].bitmapId; + screenItem->_position = position; + // TODO: Version 6 robot? + // screenItem->_field_30 = scaleXRemainder; + + if (_priority == -1) { + screenItem->_fixedPriority = false; + } else { + screenItem->_fixedPriority = true; + screenItem->_priority = _priority; + } + g_sci->_gfxFrameout->updateScreenItem(*screenItem); + } + } + + for (RobotScreenItemList::size_type i = screenItemCount; i < oldScreenItemCount; ++i) { + if (_screenItemList[i] != nullptr) { + g_sci->_gfxFrameout->deleteScreenItem(*_screenItemList[i]); + _screenItemList[i] = nullptr; + } + } +} + +void RobotDecoder::createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette) { + preallocateCelMemory(rawVideoData, numCels); + for (int16 i = 0; i < numCels; ++i) { + rawVideoData += createCel5(rawVideoData, i, usePalette); + } +} + +uint32 RobotDecoder::createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette) { + _verticalScaleFactor = rawVideoData[1]; + const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2); + const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4); + const Common::Point celPosition((int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 10), + (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 12)); + const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14); + const int16 numDataChunks = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 16); + + rawVideoData += kCelHeaderSize; + + const int16 scriptWidth = g_sci->_gfxFrameout->getCurrentBuffer().scriptWidth; + const int16 scriptHeight = g_sci->_gfxFrameout->getCurrentBuffer().scriptHeight; + const int16 screenWidth = g_sci->_gfxFrameout->getCurrentBuffer().screenWidth; + const int16 screenHeight = g_sci->_gfxFrameout->getCurrentBuffer().screenHeight; + + Common::Point displace; + if (scriptWidth == kLowResX && scriptHeight == kLowResY) { + const Ratio lowResToScreenX(screenWidth, kLowResX); + const Ratio lowResToScreenY(screenHeight, kLowResY); + const Ratio screenToLowResX(kLowResX, screenWidth); + const Ratio screenToLowResY(kLowResY, screenHeight); + + const int16 scaledX = celPosition.x + (_position.x * lowResToScreenX).toInt(); + const int16 scaledY1 = celPosition.y + (_position.y * lowResToScreenY).toInt(); + const int16 scaledY2 = scaledY1 + celHeight - 1; + + const int16 lowResX = (scaledX * screenToLowResX).toInt(); + const int16 lowResY = (scaledY2 * screenToLowResY).toInt(); + + displace.x = (scaledX - (lowResX * lowResToScreenX).toInt()) * -1; + displace.y = (lowResY * lowResToScreenY).toInt() - scaledY1; + _screenItemX[screenItemIndex] = lowResX; + _screenItemY[screenItemIndex] = lowResY; + + debugC(kDebugLevelVideo, "Low resolution position c: %d %d l: %d/%d %d/%d d: %d %d s: %d/%d %d/%d x: %d y: %d", celPosition.x, celPosition.y, lowResX, scriptWidth, lowResY, scriptHeight, displace.x, displace.y, scaledX, screenWidth, scaledY2, screenHeight, scaledX - displace.x, scaledY2 - displace.y); + } else { + const int16 highResX = celPosition.x + _position.x; + const int16 highResY = celPosition.y + _position.y + celHeight - 1; + + displace.x = 0; + displace.y = celHeight - 1; + _screenItemX[screenItemIndex] = highResX; + _screenItemY[screenItemIndex] = highResY; + + debugC(kDebugLevelVideo, "High resolution position c: %d %d s: %d %d d: %d %d", celPosition.x, celPosition.y, highResX, highResY, displace.x, displace.y); + } + + _originalScreenItemX[screenItemIndex] = celPosition.x; + _originalScreenItemY[screenItemIndex] = celPosition.y; + + assert(_celHandles[screenItemIndex].area >= celWidth * celHeight); + + SciBitmap &bitmap = *_segMan->lookupBitmap(_celHandles[screenItemIndex].bitmapId); + assert(bitmap.getWidth() == celWidth && bitmap.getHeight() == celHeight); + assert(bitmap.getScaledWidth() == _xResolution && bitmap.getScaledHeight() == _yResolution); + assert(bitmap.getHunkPaletteOffset() == (uint32)bitmap.getWidth() * bitmap.getHeight() + SciBitmap::getBitmapHeaderSize()); + bitmap.setDisplace(displace); + + byte *targetBuffer = nullptr; + if (_verticalScaleFactor == 100) { + // direct copy to bitmap + targetBuffer = bitmap.getPixels(); + } else { + // go through squashed cel decompressor + _celDecompressionBuffer.resize(_celDecompressionArea >= celWidth * (celHeight * _verticalScaleFactor / 100)); + targetBuffer = _celDecompressionBuffer.begin(); + } + + for (int i = 0; i < numDataChunks; ++i) { + uint compressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData); + uint decompressedSize = READ_SCI11ENDIAN_UINT32(rawVideoData + 4); + uint16 compressionType = READ_SCI11ENDIAN_UINT16(rawVideoData + 8); + rawVideoData += 10; + + switch (compressionType) { + case kCompressionLZS: { + Common::MemoryReadStream videoDataStream(rawVideoData, compressedSize, DisposeAfterUse::NO); + _decompressor.unpack(&videoDataStream, targetBuffer, compressedSize, decompressedSize); + break; + } + case kCompressionNone: + Common::copy(rawVideoData, rawVideoData + decompressedSize, targetBuffer); + break; + default: + error("Unknown compression type %d!", compressionType); + } + + rawVideoData += compressedSize; + targetBuffer += decompressedSize; + } + + if (_verticalScaleFactor != 100) { + expandCel(bitmap.getPixels(), _celDecompressionBuffer.begin(), celWidth, celHeight); + } + + if (usePalette) { + Common::copy(_rawPalette, _rawPalette + kRawPaletteSize, bitmap.getHunkPalette()); + } + + return kCelHeaderSize + dataSize; } -Audio::AudioStream *RobotDecoder::RobotAudioTrack::getAudioStream() const { - return _audioStream; +void RobotDecoder::preallocateCelMemory(const byte *rawVideoData, const int16 numCels) { + for (CelHandleList::size_type i = 0; i < _celHandles.size(); ++i) { + CelHandleInfo &celHandle = _celHandles[i]; + + if (celHandle.status == CelHandleInfo::kFrameLifetime) { + _segMan->freeBitmap(celHandle.bitmapId); + celHandle.bitmapId = NULL_REG; + celHandle.status = CelHandleInfo::kNoCel; + celHandle.area = 0; + } + } + _celHandles.resize(numCels); + + const int numFixedCels = MIN(numCels, (int16)kFixedCelListSize); + for (int i = 0; i < numFixedCels; ++i) { + CelHandleInfo &celHandle = _celHandles[i]; + + // NOTE: There was a check to see if the cel handle was not allocated + // here, for some reason, which would mean that nothing was ever + // allocated from fixed cels, because the _celHandles array just got + // deleted and recreated... + if (celHandle.bitmapId == NULL_REG) { + break; + } + + celHandle.bitmapId = _fixedCels[i]; + celHandle.status = CelHandleInfo::kRobotLifetime; + celHandle.area = _maxCelArea[i]; + } + + uint maxFrameArea = 0; + for (int i = 0; i < numCels; ++i) { + const int16 celWidth = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 2); + const int16 celHeight = (int16)READ_SCI11ENDIAN_UINT16(rawVideoData + 4); + const uint16 dataSize = READ_SCI11ENDIAN_UINT16(rawVideoData + 14); + const uint area = celWidth * celHeight; + + if (area > maxFrameArea) { + maxFrameArea = area; + } + + CelHandleInfo &celHandle = _celHandles[i]; + if (celHandle.status == CelHandleInfo::kRobotLifetime) { + if (_maxCelArea[i] < area) { + _segMan->freeBitmap(celHandle.bitmapId); + _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false); + celHandle.area = area; + celHandle.status = CelHandleInfo::kFrameLifetime; + } + } else if (celHandle.status == CelHandleInfo::kNoCel) { + _segMan->allocateBitmap(&celHandle.bitmapId, celWidth, celHeight, 255, 0, 0, _xResolution, _yResolution, kRawPaletteSize, false, false); + celHandle.area = area; + celHandle.status = CelHandleInfo::kFrameLifetime; + } else { + error("Cel Handle has bad status"); + } + + rawVideoData += kCelHeaderSize + dataSize; + } + + if (maxFrameArea > _celDecompressionBuffer.size()) { + _celDecompressionBuffer.resize(maxFrameArea); + } } } // End of namespace Sci diff --git a/engines/sci/video/robot_decoder.h b/engines/sci/video/robot_decoder.h index 4faea5008a..5fd6ad49c4 100644 --- a/engines/sci/video/robot_decoder.h +++ b/engines/sci/video/robot_decoder.h @@ -20,109 +20,1407 @@ * */ -#ifndef SCI_VIDEO_ROBOT_DECODER_H -#define SCI_VIDEO_ROBOT_DECODER_H +#ifndef SCI_SOUND_DECODERS_ROBOT_H +#define SCI_SOUND_DECODERS_ROBOT_H -#include "common/rational.h" -#include "common/rect.h" -#include "video/video_decoder.h" +#include "audio/audiostream.h" // for AudioStream +#include "audio/rate.h" // for st_sample_t +#include "common/array.h" // for Array +#include "common/mutex.h" // for StackLock, Mutex +#include "common/rect.h" // for Point, Rect (ptr only) +#include "common/scummsys.h" // for int16, int32, byte, uint16 +#include "sci/engine/vm_types.h" // for NULL_REG, reg_t +#include "sci/graphics/helpers.h" // for GuiResourceId +#include "sci/graphics/screen_item32.h" // for ScaleInfo, ScreenItem (ptr o... -namespace Audio { -class QueuingAudioStream; -} +namespace Common { class SeekableSubReadStreamEndian; } +namespace Sci { +class Plane; +class SegManager; -namespace Common { -class SeekableSubReadStreamEndian; -} +// Notes on Robot v5/v6 format: +// +// Robot is a packetized streaming AV format that encodes multiple bitmaps + +// positioning data, plus synchronised audio, for rendering in the SCI graphics +// system. +// +// Unlike traditional AV formats, Robot videos almost always require playback +// within the game engine because certain information (like the resolution of +// the Robot coordinates and the background for the video) is dependent on data +// that does not exist within the Robot file itself. +// +// The Robot container consists of a file header, an optional primer audio +// section, an optional colour palette, a frame seek index, a set of cuepoints, +// and variable-sized packets of compressed video+audio data. +// +// Integers in Robot files are coded using native endianness (LSB for x86 +// versions, MSB for 68k/PPC versions). +// +// Robot video coding is a relatively simple variable-length compression with no +// interframe compression. Each cel in a frame is constructed from multiple +// contiguous data blocks, each of which can be independently compressed with +// LZS or left uncompressed. An entire cel can also be line decimated, where +// lines are deleted from the source bitmap at compression time and are +// reconstructed by decompression using line doubling. Each cel also includes +// coordinates where it should be placed within the video frame, relative to the +// top-left corner of the frame. +// +// Audio coding is fixed-length, and all audio blocks except for the primer +// audio are the same size. Audio is encoded with Sierra SOL DPCM16 compression, +// and is split into two channels ('even' and 'odd'), each at a 11025Hz sample +// rate. The original signal is restored by interleaving samples from the two +// channels together. Channel packets are 'even' if they have an ''absolute +// position of audio'' that is evenly divisible by 2; otherwise, they are 'odd'. +// Because the channels use DPCM compression, there is an 8-byte runway at the +// start of every audio block that is never written to the output stream, which +// is used to move the signal to the correct location by the 9th sample. +// +// File header (v5/v6): +// +// byte | description +// 0 | signature 0x16 +// 1 | unused +// 2-5 | signature 'SOL\0' +// 6-7 | version (4, 5, and 6 are the only known versions) +// 8-9 | size of audio blocks +// 10-11 | primer is compressed flag +// 12-13 | unused +// 14-15 | total number of video frames +// 16-17 | embedded palette size, in bytes +// 18-19 | primer reserved size +// 20-21 | coordinate X-resolution (if 0, uses game coordinates) +// 22-23 | coordinate Y-resolution (if 0, uses game coordinates) +// 24 | if non-zero, Robot includes a palette +// 25 | if non-zero, Robot includes audio +// 26-27 | unused +// 28-29 | the frame rate, in frames per second +// 30-31 | coordinate conversion flag; if true, screen item coordinates +// | from the robot should be used as-is with NO conversion when +// | explicitly displaying a specific frame +// 32-33 | the maximum number of packets that can be skipped without causing +// | audio drop-out +// 34-35 | the maximum possible number of cels that will be displayed in any +// | frame of the robot +// 36-39 | the maximum possible size, in bytes, of the first fixed cel +// 40-43 | the maximum possible size, in bytes, of the second fixed cel +// 44-47 | the maximum possible size, in bytes, of the third fixed cel +// 48-51 | the maximum possible size, in bytes, of the fourth fixed cel +// 52-59 | unused +// +// If the ''file includes audio'' flag is false, seek ''primer reserved size'' +// bytes from the end of the file header to get past a padding zone. +// +// If the ''file includes audio'' flag is true, and the ''primer reserved size'' +// is not zero, the data immediately after the file header consists of an audio +// primer header plus compressed audio data: +// +// Audio primer header: +// +// byte | description +// 0-3 | the size, in bytes, of the entire primer audio section +// 4-5 | the compression format of the primer audio (must be zero) +// 6-9 | the size, in bytes, of the "even" primer +// 10-13 | the size, in bytes, of the "odd" primer +// +// If the combined sizes of the even and odd primers do not match the ''primer +// reserved size'', the next header block can be found ''primer reserved size'' +// bytes from the *start* of the audio primer header. +// +// Otherwise, if the Robot has audio, and the ''primer reserved size'' is zero, +// and the ''primer is compressed flag'' is set, the "even" primer size is +// 19922, the "odd" primer size is 21024, and the "even" and "odd" buffers +// should be zero-filled. +// +// Any other combination of these flags is an error. +// +// If the Robot has a palette, the next ''palette size'' bytes should be read +// as a SCI HunkPalette. Otherwise, seek ''palette size'' bytes from the current +// position to get to the frame index. +// +// The next section of the Robot is the video frame size index. In version 5 +// robots, read ''total number of frames'' 16-bit integers to get the size of +// the compressed video for each frame. For version 6 robots, use 32-bit +// integers. +// +// The next section of the Robot is the packet size index (combined compressed +// size of video + audio for each frame). In version 5 Robots, read ''total +// number of frames'' 16-bit integers. In version 6 robots, use 32-bit integers. +// +// The next section of the Robot is the cue times index. Read 256 32-bit +// integers, which represent the number of ticks from the start of playback that +// the given cue point falls on. +// +// The next section of the Robot is the cue values index. Read 256 16-bit +// integers, which represent the actual cue values that will be passed back to +// the game engine when a cue is requested. +// +// Finally, to get to the first frame packet, seek from the current position to +// the start of the next 2048-byte-aligned sector. +// +// Frame packet: +// +// byte | description +// 0..n | video data (size is in the ''video frame size index'') +// n+1.. | optional audio data (size is ''size of audio blocks'') +// +// Video data: +// +// byte | description +// 0-2 | number of cels in the frame (max 10) +// 3..n | cels +// +// Cel: +// +// 0-17 | cel header +// 18..n | data chunks +// +// Cel header: +// +// byte | description +// 0 | unused +// 1 | vertical scale factor, in percent decimation (100 = no decimation, +// | 50 = 50% of lines were removed) +// 2-3 | cel width +// 4-5 | cel height +// 6-9 | unused +// 10-11 | cel x-position, in Robot coordinates +// 12-13 | cel y-position, in Robot coordinates +// 14-15 | cel total data chunk size, in bytes +// 16-17 | number of data chunks +// +// Cel data chunk: +// +// 0-9 | cel data chunk header +// 10..n | cel data +// +// Cel data chunk header: +// +// byte | description +// 0-3 | compressed size +// 4-7 | decompressed size +// 8-9 | compression type (0 = LZS, 2 = uncompressed) +// +// Random frame seeking can be done by calculating the address of the frame +// packet by adding up the ''packet size index'' entries up to the current +// frame. This will normally disable audio playback, as audio data in a packet +// does not correspond to the video in the same packet. +// +// Audio data is placed immediately after the end of the video data in a packet, +// and consists of an audio header plus compressed audio data: +// +// Audio data: +// +// byte | description +// 0-7 | audio data header +// 8-15 | DPCM runway +// 16..n | compressed audio data +// +// Audio data header: +// +// byte | description +// 0-3 | absolute position of audio in the audio stream +// 4-7 | the size of the audio block, excluding the header +// +// When a block of audio is processed, first check to ensure that the +// decompressed audio block's `position * 2 + length * 4` runs past the end of +// the last packet of the same evenness/oddness. Discard the audio block +// entirely if data has already been written past the end of this block for this +// channel, or if the read head has already read past the end of this audio +// block. +// +// If the block is not discarded, apply DPCM decompression to the entire block, +// starting from beginning of the DPCM runway, using an initial sample value of +// 0. Then, copy every sample from the decompressed source outside of the DPCM +// runway into every *other* sample of the final audio buffer (1 -> 2, 2 -> 4, +// 3 -> 6, etc.). +// +// Finally, for any skipped samples where the opposing (even/odd) channel did +// not yet write, interpolate the skipped areas by adding together the +// neighbouring samples from this audio block and dividing by two. (This allows +// the audio quality to degrade to 11kHz in case it takes too long to decode all +// the frames in the stream). Interpolated samples must not be written on top of +// true data from the opposing channel. Audio from later packets must also not +// be written on top of data in the same channel that was already written by an +// earlier packet, in particular because the first 8 bytes of the next packet +// are garbage data used to move the waveform to the correct position (due to +// the use of DPCM compression). -namespace Sci { +#pragma mark - +#pragma mark RobotAudioStream + +/** + * A Robot audio stream is a simple loop buffer + * that accepts audio blocks from the Robot engine. + */ +class RobotAudioStream : public Audio::AudioStream { +public: + enum { + /** + * The sample rate used for all robot audio. + */ + kRobotSampleRate = 22050, + + /** + * Multiplier for the size of a packet that + * is being expanded by writing to every other + * byte of the target buffer. + */ + kEOSExpansion = 2 + }; + + /** + * Playback state information. Used for framerate + * calculation. + */ + struct StreamState { + /** + * The current position of the read head of + * the audio stream. + */ + int bytesPlaying; + + /** + * The sample rate of the audio stream. + * Always 22050. + */ + uint16 rate; + + /** + * The bit depth of the audio stream. + * Always 16. + */ + uint8 bits; + }; + + /** + * A single packet of compressed audio from a + * Robot data stream. + */ + struct RobotAudioPacket { + /** + * Raw DPCM-compressed audio data. + */ + byte *data; + + /** + * The size of the compressed audio data, + * in bytes. + */ + int dataSize; + + /** + * The uncompressed, file-relative position + * of this audio packet. + */ + int position; + + RobotAudioPacket(byte *data_, const int dataSize_, const int position_) : + data(data_), dataSize(dataSize_), position(position_) {} + }; + + RobotAudioStream(const int32 bufferSize); + virtual ~RobotAudioStream(); + + /** + * Adds a new audio packet to the stream. + * @returns `true` if the audio packet was fully + * consumed, otherwise `false`. + */ + bool addPacket(const RobotAudioPacket &packet); + + /** + * Prevents any additional audio packets from + * being added to the audio stream. + */ + void finish(); + + /** + * Returns the current status of the audio + * stream. + */ + StreamState getStatus() const; + +private: + Common::Mutex _mutex; + + /** + * Loop buffer for playback. Contains decompressed + * 16-bit PCM samples. + */ + byte *_loopBuffer; + + /** + * The size of the loop buffer, in bytes. + */ + int32 _loopBufferSize; + + /** + * The position of the read head within the loop + * buffer, in bytes. + */ + int32 _readHead; + + /** + * The lowest file position that can be buffered, + * in uncompressed bytes. + */ + int32 _readHeadAbs; + + /** + * The highest file position that can be buffered, + * in uncompressed bytes. + */ + int32 _maxWriteAbs; + + /** + * The highest file position, in uncompressed bytes, + * that has been written to the stream. + * Different from `_maxWriteAbs`, which is the highest + * uncompressed position which *can* be written right + * now. + */ + int32 _writeHeadAbs; + + /** + * The highest file position, in uncompressed bytes, + * that has been written to the even & odd sides of + * the stream. + * + * Index 0 corresponds to the 'even' side; index + * 1 correspond to the 'odd' side. + */ + int32 _jointMin[2]; + + /** + * When `true`, the stream is waiting for all primer + * blocks to be received before allowing playback to + * begin. + */ + bool _waiting; + + /** + * When `true`, the stream will accept no more audio + * blocks. + */ + bool _finished; + + /** + * The uncompressed position of the first packet of + * robot data. Used to decide whether all primer + * blocks have been received and the stream should + * be started. + */ + int32 _firstPacketPosition; + + /** + * Decompression buffer, used to temporarily store + * an uncompressed block of audio data. + */ + byte *_decompressionBuffer; + + /** + * The size of the decompression buffer, in bytes. + */ + int32 _decompressionBufferSize; + + /** + * The position of the packet currently in the + * decompression buffer. Used to avoid + * re-decompressing audio data that has already + * been decompressed during a partial packet read. + */ + int32 _decompressionBufferPosition; + + /** + * Calculates the absolute ranges for new fills + * into the loop buffer. + */ + void fillRobotBuffer(const RobotAudioPacket &packet, const int8 bufferIndex); + + /** + * Interpolates `numSamples` samples from the read + * head, if no true samples were written for one + * (or both) of the joint channels. + */ + void interpolateMissingSamples(const int32 numSamples); + +#pragma mark - +#pragma mark RobotAudioStream - AudioStream implementation +public: + int readBuffer(Audio::st_sample_t *outBuffer, int numSamples) override; + virtual bool isStereo() const override { return false; }; + virtual int getRate() const override { return 22050; }; + virtual bool endOfData() const override { + Common::StackLock lock(_mutex); + return _readHeadAbs >= _writeHeadAbs; + }; + virtual bool endOfStream() const override { + Common::StackLock lock(_mutex); + return _finished && endOfData(); + } +}; + +#pragma mark - +#pragma mark RobotDecoder + +/** + * RobotDecoder implements the logic required + * for Robot animations. + * + * @note A paused or finished RobotDecoder was + * classified as serializable in SCI3, but the + * save/load code would attempt to use uninitialised + * values, so it seems that robots were not ever + * actually able to be saved. + */ +class RobotDecoder { +public: + RobotDecoder(SegManager *segMan); + ~RobotDecoder(); + +private: + SegManager *_segMan; + +#pragma mark Constants +public: + /** + * The playback status of the robot. + */ + enum RobotStatus { + kRobotStatusUninitialized = 0, + kRobotStatusPlaying = 1, + kRobotStatusEnd = 2, + kRobotStatusPaused = 3 + }; + + enum { + // Special high value used to represent + // parameters that should be left unchanged + // when calling `showFrame` + kUnspecified = 50000 + }; + +private: + enum { + /** + * Maximum number of on-screen screen items. + */ + kScreenItemListSize = 10, + + /** + * Maximum number of queued audio blocks. + */ + kAudioListSize = 10, + + /** + * Maximum number of samples used for frame timing. + */ + kDelayListSize = 10, + + /** + * Maximum number of cues. + */ + kCueListSize = 256, + + /** + * Maximum number of 'fixed' cels that never + * change for the duration of a robot. + */ + kFixedCelListSize = 4, + + /** + * The size of a hunk palette in the Robot stream. + */ + kRawPaletteSize = 1200, + + /** + * The size of a frame of Robot data. This + * value was used to align the first block of + * data after the main Robot header to the next + * CD sector. + */ + kRobotFrameSize = 2048, + + /** + * The size of a block of zero-compressed + * audio. Used to fill audio when the size of + * an audio packet does not match the expected + * packet size. + */ + kRobotZeroCompressSize = 2048, -class RobotDecoder : public Video::VideoDecoder { + /** + * The size of the audio block header, in bytes. + * The audio block header consists of the + * compressed size of the audio in the record, + * plus the position of the audio in the + * compressed data stream. + */ + kAudioBlockHeaderSize = 8, + + /** + * The size of a Robot cel header, in bytes. + */ + kCelHeaderSize = 22, + + /** + * The maximum amount that the frame rate is + * allowed to drift from the nominal frame rate + * in order to correct for AV drift or slow + * playback. + */ + kMaxFrameRateDrift = 1 + }; + + /** + * The version number for the currently loaded + * robot. + * + * There are several known versions of robot: + * + * v2: before Nov 1994; no known examples + * v3: before Nov 1994; no known examples + * v4: Jan 1995; PQ:SWAT demo + * v5: Mar 1995; SCI2.1 and SCI3 games + * v6: SCI3 games + */ + uint16 _version; + +#pragma mark - +#pragma mark Initialisation +private: + /** + * Sets up the read stream for the robot. + */ + void initStream(const GuiResourceId robotId); + + /** + * Sets up the initial values for playback control. + */ + void initPlayback(); + + /** + * Sets up the initial values for audio decoding. + */ + void initAudio(); + + /** + * Sets up the initial values for video rendering. + */ + void initVideo(const int16 x, const int16 y, const int16 scale, const reg_t plane, const bool hasPalette, const uint16 paletteSize); + + /** + * Sets up the robot's data record and cue positions. + */ + void initRecordAndCuePositions(); + +#pragma mark - +#pragma mark Playback public: - RobotDecoder(bool isBigEndian); - virtual ~RobotDecoder(); + /** + * Opens a robot file for playback. + * Newly opened robots are paused by default. + */ + void open(const GuiResourceId robotId, const reg_t plane, const int16 priority, const int16 x, const int16 y, const int16 scale); - bool loadStream(Common::SeekableReadStream *stream); - bool load(GuiResourceId id); + /** + * Closes the currently open robot file. + */ void close(); - void setPos(uint16 x, uint16 y) { _pos = Common::Point(x, y); } - Common::Point getPos() const { return _pos; } + /** + * Pauses the robot. Once paused, the audio for a robot + * is disabled until the end of playback. + */ + void pause(); + + /** + * Resumes a paused robot. + */ + void resume(); + + /** + * Moves robot to the specified frame and pauses playback. + * + * @note Called DisplayFrame in SSCI. + */ + void showFrame(const uint16 frameNo, const uint16 newX, const uint16 newY, const uint16 newPriority); + + /** + * Retrieves the value associated with the + * current cue point. + */ + int16 getCue() const; + + /** + * Gets the currently displayed frame. + */ + int16 getFrameNo() const; + + /** + * Gets the playback status of the player. + */ + RobotStatus getStatus() const; + +private: + /** + * The read stream containing raw robot data. + */ + Common::SeekableSubReadStreamEndian *_stream; + + /** + * The current status of the player. + */ + RobotStatus _status; + + typedef Common::Array<int> PositionList; + + /** + * A map of frame numbers to byte offsets within `_stream`. + */ + PositionList _recordPositions; + + /** + * The offset of the Robot file within a + * resource bundle. + */ + int32 _fileOffset; + + /** + * A list of cue times that is updated to + * prevent earlier cue values from being + * given to the game more than once. + */ + mutable int32 _cueTimes[kCueListSize]; + + /** + * The original list of cue times from the + * raw Robot data. + */ + int32 _masterCueTimes[kCueListSize]; + + /** + * The list of values to provide to a game + * when a cue value is requested. + */ + int32 _cueValues[kCueListSize]; + + /** + * The current playback frame rate. + */ + int16 _frameRate; + + /** + * The nominal playback frame rate. + */ + int16 _normalFrameRate; + + /** + * The minimal playback frame rate. Used to + * correct for AV sync drift when the video + * is more than one frame ahead of the audio. + */ + int16 _minFrameRate; + + /** + * The maximum playback frame rate. Used to + * correct for AV sync drift when the video + * is more than one frame behind the audio. + */ + int16 _maxFrameRate; + + /** + * The maximum number of record blocks that + * can be skipped without causing audio to + * drop out. + */ + int16 _maxSkippablePackets; + + /** + * The currently displayed frame number. + */ + int _currentFrameNo; + + /** + * The last displayed frame number. + */ + int _previousFrameNo; + + /** + * The time, in ticks, when the robot was + * last started or resumed. + */ + int32 _startTime; + + /** + * The first frame displayed when the + * robot was resumed. + */ + int32 _startFrameNo; + + /** + * The last frame displayed when the robot + * was resumed. + */ + int32 _startingFrameNo; + + /** + * Seeks the raw data stream to the record for + * the given frame number. + */ + bool seekToFrame(const int frameNo); -protected: - void readNextPacket(); + /** + * Sets the start time and frame of the robot + * when the robot is started or resumed. + */ + void setRobotTime(const int frameNo); +#pragma mark - +#pragma mark Timing private: - class RobotVideoTrack : public FixedRateVideoTrack { + /** + * This class tracks the amount of time it takes for + * a frame of robot animation to be rendered. This + * information is used by the player to speculatively + * skip rendering of future frames to keep the + * animation in sync with the robot audio. + */ + class DelayTime { public: - RobotVideoTrack(int frameCount); - ~RobotVideoTrack(); - - uint16 getWidth() const; - uint16 getHeight() const; - Graphics::PixelFormat getPixelFormat() const; - int getCurFrame() const { return _curFrame; } - int getFrameCount() const { return _frameCount; } - const Graphics::Surface *decodeNextFrame() { return _surface; } - const byte *getPalette() const { _dirtyPalette = false; return _palette; } - bool hasDirtyPalette() const { return _dirtyPalette; } - - void readPaletteChunk(Common::SeekableSubReadStreamEndian *stream, uint16 chunkSize); - void calculateVideoDimensions(Common::SeekableSubReadStreamEndian *stream, uint32 *frameSizes); - Graphics::Surface *getSurface() { return _surface; } - void increaseCurFrame() { _curFrame++; } - - protected: - Common::Rational getFrameRate() const { return Common::Rational(60, 10); } + DelayTime(RobotDecoder *decoder); + + /** + * Starts performance timing. + */ + void startTiming(); + + /** + * Ends performance timing. + */ + void endTiming(); + + /** + * Returns whether or not timing is currently in + * progress. + */ + bool timingInProgress() const; + + /** + * Returns the median time, in ticks, of the + * currently stored timing samples. + */ + int predictedTicks() const; private: - int _frameCount; - int _curFrame; - byte _palette[256 * 3]; - mutable bool _dirtyPalette; - Graphics::Surface *_surface; + RobotDecoder *_decoder; + + /** + * The start time, in ticks, of the current timing + * loop. If no loop is in progress, the value is 0. + * + * @note This is slightly different than SSCI where + * the not-timing value was -1. + */ + uint32 _startTime; + + /** + * A sorted list containing the timing data for + * the last `kDelayListSize` frames, in ticks. + */ + int _delays[kDelayListSize]; + + /** + * A list of monotonically increasing identifiers + * used to identify and replace the oldest sample + * in the `_delays` array when finishing the + * next timing operation. + */ + uint _timestamps[kDelayListSize]; + + /** + * The identifier of the oldest timing. + */ + uint _oldestTimestamp; + + /** + * The identifier of the newest timing. + */ + uint _newestTimestamp; + + /** + * Sorts the list of timings. + */ + void sortList(); }; - class RobotAudioTrack : public AudioTrack { + /** + * Calculates the next frame number that needs + * to be rendered, using the timing data + * collected by DelayTime. + */ + uint16 calculateNextFrameNo(const uint32 extraTicks = 0) const; + + /** + * Calculates and returns the number of frames + * that should be rendered in `ticks` time, + * according to the current target frame rate + * of the robot. + */ + uint32 ticksToFrames(const uint32 ticks) const; + + /** + * Gets the current game time, in ticks. + */ + uint32 getTickCount() const; + + /** + * The performance timer for the robot. + */ + DelayTime _delayTime; + +#pragma mark - +#pragma mark Audio +private: + enum { + /** + * The number of ticks that should elapse + * between each AV sync check. + */ + kAudioSyncCheckInterval = 5 * 60 /* 5 seconds */ + }; + + /** + * The status of the audio track of a Robot + * animation. + */ + enum RobotAudioStatus { + kRobotAudioReady = 1, + kRobotAudioStopped = 2, + kRobotAudioPlaying = 3, + kRobotAudioPaused = 4, + kRobotAudioStopping = 5 + }; + +#pragma mark - +#pragma mark Audio - AudioList +private: + /** + * This class manages packetized audio playback + * for robots. + */ + class AudioList { public: - RobotAudioTrack(); - ~RobotAudioTrack(); + AudioList(); + + /** + * Starts playback of robot audio. + */ + void startAudioNow(); + + /** + * Stops playback of robot audio, allowing + * any queued audio to finish playing back. + */ + void stopAudio(); + + /** + * Stops playback of robot audio immediately. + */ + void stopAudioNow(); + + /** + * Submits as many blocks of audio as possible + * to the audio engine. + */ + void submitDriverMax(); + + /** + * Adds a new AudioBlock to the queue. + * + * @param position The absolute position of the + * audio for the block, in compressed bytes. + * @param size The size of the buffer. + * @param buffer A pointer to compressed audio + * data that will be copied into the new + * AudioBlock. + */ + void addBlock(const int position, const int size, const byte *buffer); - Audio::Mixer::SoundType getSoundType() const { return Audio::Mixer::kMusicSoundType; } + /** + * Immediately stops any active playback and + * purges all audio data in the audio list. + */ + void reset(); - void queueBuffer(byte *buffer, int size); + /** + * Pauses the robot audio channel in + * preparation for the first block of audio + * data to be read. + */ + void prepareForPrimer(); - protected: - Audio::AudioStream *getAudioStream() const; + /** + * Sets the audio offset which is used to + * offset the position of audio packets + * sent to the audio stream. + */ + void setAudioOffset(const int offset); + +#pragma mark - +#pragma mark Audio - AudioList - AudioBlock private: - Audio::QueuingAudioStream *_audioStream; + /** + * AudioBlock represents a block of audio + * from the Robot's audio track. + */ + class AudioBlock { + public: + AudioBlock(const int position, const int size, const byte *const data); + ~AudioBlock(); + + /** + * Submits the block of audio to the + * audio manager. + * @returns true if the block was fully + * read, or false if the block was not + * read or only partially read. + */ + bool submit(const int startOffset); + + private: + /** + * The absolute position, in compressed + * bytes, of this audio block's audio + * data in the audio stream. + */ + int _position; + + /** + * The compressed size, in bytes, of + * this audio block's audio data. + */ + int _size; + + /** + * A buffer containing raw + * SOL-compressed audio data. + */ + byte *_data; + }; + + /** + * The list of compressed audio blocks + * submitted for playback. + */ + AudioBlock *_blocks[kAudioListSize]; + + /** + * The number of blocks in `_blocks` that are + * ready to be submitted. + */ + uint8 _blocksSize; + + /** + * The index of the oldest submitted audio block. + */ + uint8 _oldestBlockIndex; + + /** + * The index of the newest submitted audio block. + */ + uint8 _newestBlockIndex; + + /** + * The offset used when sending packets to the + * audio stream. + */ + int _startOffset; + + /** + * The status of robot audio playback. + */ + RobotAudioStatus _status; + + /** + * Frees all audio blocks in the `_blocks` list. + */ + void freeAudioBlocks(); }; - struct RobotHeader { - // 6 bytes, identifier bytes - uint16 version; - uint16 audioChunkSize; - uint16 audioSilenceSize; - // 2 bytes, unknown - uint16 frameCount; - uint16 paletteDataSize; - uint16 unkChunkDataSize; - // 5 bytes, unknown - byte hasSound; - // 34 bytes, unknown - } _header; - - void readHeaderChunk(); - void readFrameSizesChunk(); - - Common::Point _pos; - bool _isBigEndian; - uint32 *_frameTotalSize; - - Common::SeekableSubReadStreamEndian *_fileStream; -}; + /** + * Whether or not this robot animation has + * an audio track. + */ + bool _hasAudio; + + /** + * The audio list for the current robot. + */ + AudioList _audioList; + + /** + * The size, in bytes, of a block of audio data, + * excluding the audio block header. + */ + uint16 _audioBlockSize; + + /** + * The expected size of a block of audio data, + * in bytes, excluding the audio block header. + */ + int16 _expectedAudioBlockSize; + + /** + * The number of compressed audio bytes that are + * needed per frame to fill the audio buffer + * without causing audio to drop out. + */ + int16 _audioRecordInterval; + + /** + * If true, primer audio buffers should be filled + * with silence instead of trying to read buffers + * from the Robot data. + */ + uint16 _primerZeroCompressFlag; + + /** + * The size, in bytes, of the primer audio in the + * Robot, including any extra alignment padding. + */ + uint16 _primerReservedSize; + + /** + * The combined size, in bytes, of the even and odd + * primer channels. + */ + int32 _totalPrimerSize; + + /** + * The absolute offset of the primer audio data in + * the robot data stream. + */ + int32 _primerPosition; + + /** + * The size, in bytes, of the even primer. + */ + int32 _evenPrimerSize; + + /** + * The size, in bytes, of the odd primer. + */ + int32 _oddPrimerSize; + + /** + * The absolute position in the audio stream of + * the first audio packet. + */ + int32 _firstAudioRecordPosition; -} // End of namespace Sci + /** + * A temporary buffer used to hold one frame of + * raw (DPCM-compressed) audio when reading audio + * records from the robot stream. + */ + byte *_audioBuffer; + /** + * The next tick count when AV sync should be + * checked and framerate adjustments made, if + * necessary. + */ + uint32 _checkAudioSyncTime; + + /** + * Primes the audio buffer with the first frame + * of audio data. + * + * @note `primeAudio` was `InitAudio` in SSCI + */ + bool primeAudio(const uint32 startTick); + + /** + * Reads primer data from the robot data stream + * and puts it into the given buffers. + */ + bool readPrimerData(byte *outEvenBuffer, byte *outOddBuffer); + + /** + * Reads audio data for the given frame number + * into the given buffer. + * + * @param outAudioPosition The position of the + * audio, in compressed bytes, in the data stream. + * @param outAudioSize The size of the audio data, + * in compressed bytes. + */ + bool readAudioDataFromRecord(const int frameNo, byte *outBuffer, int &outAudioPosition, int &outAudioSize); + + /** + * Submits part of the audio packet of the given + * frame to the audio list, starting `startPosition` + * bytes into the audio. + */ + bool readPartialAudioRecordAndSubmit(const int startFrame, const int startPosition); + +#pragma mark - +#pragma mark Rendering +public: + /** + * Puts the current dimensions of the robot, in game script + * coordinates, into the given rect, and returns the total + * number of frames in the robot animation. + */ + uint16 getFrameSize(Common::Rect &outRect) const; + + /** + * Pumps the robot player for the next frame of video. + * This is the main rendering function. + */ + void doRobot(); + + /** + * Submits any outstanding audio blocks that should + * be added to the queue before the robot frame + * becomes visible. + */ + void frameAlmostVisible(); + + /** + * Evaluates frame drift and makes modifications to + * the player in order to ensure that future frames + * will arrive on time. + */ + void frameNowVisible(); + + /** + * Scales a vertically compressed cel to its original + * uncompressed dimensions. + */ + void expandCel(byte *target, const byte* source, const int16 celWidth, const int16 celHeight) const; + + /** + * Sets the visual priority of the robot. + * @see Plane::_priority + */ + void setPriority(const int16 newPriority); + +private: + enum CompressionType { + kCompressionLZS = 0, + kCompressionNone = 2 + }; + + /** + * Describes the state of a Robot video cel. + */ + struct CelHandleInfo { + /** + * The persistence level of Robot cels. + */ + enum CelHandleLifetime { + kNoCel = 0, + kFrameLifetime = 1, + kRobotLifetime = 2 + }; + + /** + * A reg_t pointer to an in-memory + * bitmap containing the cel. + */ + reg_t bitmapId; + + /** + * The lifetime of the cel, either just + * for this frame or for the entire + * duration of the robot playback. + */ + CelHandleLifetime status; + + /** + * The size, in pixels, of the decompressed + * cel. + */ + int area; + + CelHandleInfo() : bitmapId(NULL_REG), status(kNoCel), area(0) {} + }; + + typedef Common::Array<ScreenItem *> RobotScreenItemList; + typedef Common::Array<CelHandleInfo> CelHandleList; + typedef Common::Array<int> VideoSizeList; + typedef Common::Array<uint> MaxCelAreaList; + typedef Common::Array<reg_t> FixedCelsList; + typedef Common::Array<Common::Point> CelPositionsList; + typedef Common::Array<byte> ScratchMemory; + + /** + * Renders a version 5/6 robot frame. + */ + void doVersion5(const bool shouldSubmitAudio = true); + + /** + * Creates screen items for a version 5/6 robot. + */ + void createCels5(const byte *rawVideoData, const int16 numCels, const bool usePalette); + + /** + * Creates a single screen item for a cel in a + * version 5/6 robot. + * + * Returns the size, in bytes, of the raw cel data. + */ + uint32 createCel5(const byte *rawVideoData, const int16 screenItemIndex, const bool usePalette); + + /** + * Preallocates memory for the next `numCels` cels + * in the robot data stream. + */ + void preallocateCelMemory(const byte *rawVideoData, const int16 numCels); + + /** + * The decompressor for LZS-compressed cels. + */ + DecompressorLZS _decompressor; + + /** + * The origin of the robot animation, in screen + * coordinates. + */ + Common::Point _position; + + /** + * Global scaling applied to the robot. + */ + ScaleInfo _scaleInfo; + + /** + * The native resolution of the robot. + */ + int16 _xResolution, _yResolution; + + /** + * Whether or not the coordinates read from robot + * data are high resolution. + */ + bool _isHiRes; + + /** + * The maximum number of cels that will be rendered + * on any given frame in this robot. Used for + * preallocation of cel memory. + */ + int16 _maxCelsPerFrame; + + /** + * The maximum areas, in pixels, for each of + * the fixed cels in the robot. Used for + * preallocation of cel memory. + */ + MaxCelAreaList _maxCelArea; + + /** + * The hunk palette to use when rendering the + * current frame, if the `usePalette` flag was set + * in the robot header. + */ + uint8 *_rawPalette; + + /** + * A list of the raw video data sizes, in bytes, + * for each frame of the robot. + */ + VideoSizeList _videoSizes; + + /** + * A list of cels that will be present for the + * entire duration of the robot animation. + */ + FixedCelsList _fixedCels; + + /** + * A list of handles for each cel in the current + * frame. + */ + CelHandleList _celHandles; + + /** + * Scratch memory used to temporarily store + * decompressed cel data for vertically squashed + * cels. + */ + ScratchMemory _celDecompressionBuffer; + + /** + * The size, in bytes, of the squashed cel + * decompression buffer. + */ + int _celDecompressionArea; + + /** + * If true, the robot just started playing and + * is awaiting output for the first frame. + */ + bool _syncFrame; + + /** + * Scratch memory used to store the compressed robot + * video data for the current frame. + */ + ScratchMemory _doVersion5Scratch; + + /** + * When set to a non-negative value, forces the next + * call to doRobot to render the given frame number + * instead of whatever frame would have normally been + * rendered. + */ + mutable int _cueForceShowFrame; + + /** + * The plane where the robot animation will be drawn. + */ + Plane *_plane; + + /** + * A list of pointers to ScreenItems used by the robot. + */ + RobotScreenItemList _screenItemList; + + /** + * The positions of the various screen items in this + * robot, in screen coordinates. + */ + Common::Array<int16> _screenItemX, _screenItemY; + + /** + * The raw position values from the cel header for + * each screen item currently on-screen. + */ + Common::Array<int16> _originalScreenItemX, _originalScreenItemY; + + /** + * The duration of the current robot, in frames. + */ + uint16 _numFramesTotal; + + /** + * The screen priority of the video. + * @see ScreenItem::_priority + */ + int16 _priority; + + /** + * The amount of visual vertical compression applied + * to the current cel. A value of 100 means no + * compression; a value above 100 indicates how much + * the cel needs to be scaled along the y-axis to + * return to its original dimensions. + */ + uint8 _verticalScaleFactor; +}; +} // end of namespace Sci #endif diff --git a/engines/titanic/carry/chicken.h b/engines/titanic/carry/chicken.h index 65fe30fd81..e64ae458a4 100644 --- a/engines/titanic/carry/chicken.h +++ b/engines/titanic/carry/chicken.h @@ -41,7 +41,7 @@ class CChicken : public CCarry { bool MouseDragEndMsg(CMouseDragEndMsg *msg); bool PETObjectStateMsg(CPETObjectStateMsg *msg); bool PETLostObjectMsg(CPETLostObjectMsg *msg); -private: +public: static int _v1; public: int _field12C; diff --git a/engines/titanic/carry/crushed_tv.cpp b/engines/titanic/carry/crushed_tv.cpp index a265b611a9..486537d28e 100644 --- a/engines/titanic/carry/crushed_tv.cpp +++ b/engines/titanic/carry/crushed_tv.cpp @@ -76,5 +76,4 @@ bool CCrushedTV::MouseDragStartMsg(CMouseDragStartMsg *msg) { return CCarry::MouseDragStartMsg(msg); } - } // End of namespace Titanic diff --git a/engines/titanic/core/click_responder.cpp b/engines/titanic/core/click_responder.cpp index f9694557df..9a0e0de7ab 100644 --- a/engines/titanic/core/click_responder.cpp +++ b/engines/titanic/core/click_responder.cpp @@ -24,20 +24,33 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CClickResponder, CGameObject) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CClickResponder::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string1, indent); - file->writeQuotedLine(_string2, indent); + file->writeQuotedLine(_message, indent); + file->writeQuotedLine(_soundName, indent); CGameObject::save(file, indent); } void CClickResponder::load(SimpleFile *file) { file->readNumber(); - _string1 = file->readString(); - _string2 = file->readString(); + _message = file->readString(); + _soundName = file->readString(); CGameObject::load(file); } +bool CClickResponder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (!_soundName.empty()) + playSound(_soundName); + if (!_message.empty()) + petDisplayMessage(_message); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/core/click_responder.h b/engines/titanic/core/click_responder.h index 78381b9948..40f22d7906 100644 --- a/engines/titanic/core/click_responder.h +++ b/engines/titanic/core/click_responder.h @@ -28,8 +28,10 @@ namespace Titanic { class CClickResponder : public CGameObject { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); protected: - CString _string1, _string2; + CString _message, _soundName; public: CLASSDEF; diff --git a/engines/titanic/game/bridge_view.cpp b/engines/titanic/game/bridge_view.cpp index 9854969494..466480a64c 100644 --- a/engines/titanic/game/bridge_view.cpp +++ b/engines/titanic/game/bridge_view.cpp @@ -24,16 +24,92 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CBridgeView, CBackground) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CBridgeView::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeNumberLine(_fieldE0, indent); + file->writeNumberLine(_mode, indent); CBackground::save(file, indent); } void CBridgeView::load(SimpleFile *file) { file->readNumber(); - _fieldE0 = file->readNumber(); + _mode = file->readNumber(); CBackground::load(file); } +bool CBridgeView::ActMsg(CActMsg *msg) { + CTurnOn onMsg; + CSetVolumeMsg volumeMsg; + volumeMsg._secondsTransition = 1; + + if (msg->_action == "End") { + _mode = 4; + petLockInput(); + petHide(); + setVisible(true); + playMovie(MOVIE_NOTIFY_OBJECT); + } else if (msg->_action == "Go") { + _mode = 1; + setVisible(true); + volumeMsg._volume = 100; + volumeMsg.execute("EngineSounds"); + onMsg.execute("EngineSounds"); + playMovie(MOVIE_NOTIFY_OBJECT); + } else { + volumeMsg._volume = 50; + volumeMsg.execute("EngineSounds"); + onMsg.execute("EngineSounds"); + + if (msg->_action == "Cruise") { + _mode = 2; + setVisible(true); + playMovie(MOVIE_NOTIFY_OBJECT); + } else if (msg->_action == "GoENd") { + _mode = 3; + setVisible(true); + CChangeMusicMsg musicMsg; + musicMsg._flags = 1; + musicMsg.execute("BridgeAutoMusicPlayer"); + playSound("a#42.wav"); + playMovie(MOVIE_NOTIFY_OBJECT); + } + } + + return true; +} + +bool CBridgeView::MovieEndMsg(CMovieEndMsg *msg) { + CTurnOff offMsg; + offMsg.execute("EngineSounds"); + + switch (_mode) { + case 0: + case 1: + setVisible(false); + dec54(); + break; + + case 2: { + setVisible(false); + CActMsg actMsg("End"); + actMsg.execute("HomeSequence"); + break; + } + + case 3: + setVisible(false); + changeView("TheEnd.Node 3.N"); + break; + + default: + break; + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/bridge_view.h b/engines/titanic/game/bridge_view.h index d7c7c35aa9..45cfa3f4c8 100644 --- a/engines/titanic/game/bridge_view.h +++ b/engines/titanic/game/bridge_view.h @@ -28,11 +28,14 @@ namespace Titanic { class CBridgeView : public CBackground { + DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: - int _fieldE0; + int _mode; public: CLASSDEF; - CBridgeView() : CBackground(), _fieldE0(0) {} + CBridgeView() : CBackground(), _mode(0) {} /** * Save the data for the class to file diff --git a/engines/titanic/game/chicken_cooler.cpp b/engines/titanic/game/chicken_cooler.cpp index 29232e10bf..d10405de38 100644 --- a/engines/titanic/game/chicken_cooler.cpp +++ b/engines/titanic/game/chicken_cooler.cpp @@ -21,9 +21,15 @@ */ #include "titanic/game/chicken_cooler.h" +#include "titanic/carry/chicken.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CChickenCooler, CGameObject) + ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(EnterViewMsg) +END_MESSAGE_MAP() + void CChickenCooler::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldBC, indent); @@ -41,7 +47,32 @@ void CChickenCooler::load(SimpleFile *file) { } bool CChickenCooler::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CChickenCoolor::handlEvent"); + if (_fieldC0) { + CGameObject *obj = getMailManFirstObject(); + if (obj) { + // WORKAROUND: Redundant loop for chicken in originalhere + } else { + getNextMail(nullptr); + if (CChicken::_v1 > _fieldBC) + CChicken::_v1 = _fieldBC; + } + } + + return true; +} + +bool CChickenCooler::EnterViewMsg(CEnterViewMsg *msg) { + if (!_fieldC0) { + for (CGameObject *obj = getMailManFirstObject(); obj; + obj = getNextMail(obj)) { + if (obj->isEquals("Chicken")) + return true; + } + + if (CChicken::_v1 > _fieldBC) + CChicken::_v1 = _fieldBC; + } + return true; } diff --git a/engines/titanic/game/chicken_cooler.h b/engines/titanic/game/chicken_cooler.h index 724727b905..54dba90686 100644 --- a/engines/titanic/game/chicken_cooler.h +++ b/engines/titanic/game/chicken_cooler.h @@ -29,7 +29,9 @@ namespace Titanic { class CChickenCooler : public CGameObject { + DECLARE_MESSAGE_MAP; bool EnterRoomMsg(CEnterRoomMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); private: int _fieldBC; int _fieldC0; diff --git a/engines/titanic/game/chicken_dispensor.cpp b/engines/titanic/game/chicken_dispensor.cpp index a9bf576765..7fb8fefcda 100644 --- a/engines/titanic/game/chicken_dispensor.cpp +++ b/engines/titanic/game/chicken_dispensor.cpp @@ -21,9 +21,21 @@ */ #include "titanic/game/chicken_dispensor.h" +#include "titanic/core/project_item.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CChickenDispensor, CBackground) + ON_MESSAGE(StatusChangeMsg) + ON_MESSAGE(MovieEndMsg) + ON_MESSAGE(ActMsg) + ON_MESSAGE(LeaveViewMsg) + ON_MESSAGE(EnterViewMsg) + ON_MESSAGE(MouseDragStartMsg) + ON_MESSAGE(TurnOff) +END_MESSAGE_MAP() + CChickenDispensor::CChickenDispensor() : CBackground(), _fieldE0(0), _fieldE4(0), _fieldE8(0) { } @@ -45,4 +57,133 @@ void CChickenDispensor::load(SimpleFile *file) { CBackground::load(file); } +bool CChickenDispensor::StatusChangeMsg(CStatusChangeMsg *msg) { + msg->execute("SGTRestLeverAnimation"); + int v1 = _fieldE8 ? 0 : _fieldE4; + CPetControl *pet = getPetControl(); + CGameObject *obj; + + for (obj = pet->getFirstObject(); obj; obj = pet->getNextObject(obj)) { + if (obj->isEquals("Chicken")) { + petDisplayMessage(1, "Chickens are allocated on a one-per-customer basis."); + return true; + } + } + + for (obj = getMailManFirstObject(); obj; obj = getNextMail(obj)) { + if (obj->isEquals("Chicken")) { + petDisplayMessage(1, "Chickens are allocated on a one-per-customer basis."); + return true; + } + } + + if (v1 == 1 || v1 == 2) + _fieldE8 = 1; + + switch (v1) { + case 0: + petDisplayMessage(1, "Only one piece of chicken per passenger. Thank you."); + break; + case 1: + setVisible(true); + if (_fieldE0) { + playMovie(0, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("z#400.wav"); + _fieldE4 = 0; + } else { + playMovie(12, 16, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _fieldE8 = 1; + _fieldE4 = 0; + } + break; + + case 2: + setVisible(true); + if (_fieldE0) { + playMovie(0, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("z#400.wav"); + } else { + playMovie(12, 16, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _fieldE8 = 1; + } + break; + + default: + break; + } + + return true; +} + +bool CChickenDispensor::MovieEndMsg(CMovieEndMsg *msg) { + if (getMovieFrame() == 16) { + playSound("b#50.wav", 50); + CActMsg actMsg("Dispense Chicken"); + actMsg.execute("Chicken"); + } else if (_fieldE8) { + _cursorId = CURSOR_ARROW; + loadFrame(0); + setVisible(false); + if (_fieldE4 == 2) + _fieldE8 = 0; + } else { + loadFrame(0); + setVisible(false); + changeView("SgtLobby.Node 1.N"); + } + + return true; +} + +bool CChickenDispensor::ActMsg(CActMsg *msg) { + if (msg->_action == "EnableObject") + _fieldE0 = 0; + else if (msg->_action == "DisableObject") + _fieldE0 = 1; + else if (msg->_action == "IncreaseQuantity") + _fieldE4 = 2; + else if (msg->_action == "DecreaseQuantity") + _fieldE4 = 1; + + return true; +} + +bool CChickenDispensor::LeaveViewMsg(CLeaveViewMsg *msg) { + return true; +} + +bool CChickenDispensor::EnterViewMsg(CEnterViewMsg *msg) { + playSound("b#51.wav"); + _fieldE8 = 0; + _cursorId = CURSOR_ARROW; + return true; +} + +bool CChickenDispensor::MouseDragStartMsg(CMouseDragStartMsg *msg) { + if (getMovieFrame() == 16) { + setVisible(false); + loadFrame(0); + _cursorId = CURSOR_ARROW; + _fieldE8 = 1; + + CVisibleMsg visibleMsg; + visibleMsg.execute("Chicken"); + CPassOnDragStartMsg passMsg(msg->_mousePos, 1); + passMsg.execute("Chicken"); + + msg->_dragItem = getRoot()->findByName("Chicken"); + } + + return true; +} + +bool CChickenDispensor::TurnOff(CTurnOff *msg) { + if (getMovieFrame() == 16) + setVisible(false); + playMovie(16, 12, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + _fieldE8 = 0; + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/chicken_dispensor.h b/engines/titanic/game/chicken_dispensor.h index d86b850871..5e3ba47ee8 100644 --- a/engines/titanic/game/chicken_dispensor.h +++ b/engines/titanic/game/chicken_dispensor.h @@ -28,6 +28,14 @@ namespace Titanic { class CChickenDispensor : public CBackground { + DECLARE_MESSAGE_MAP; + bool StatusChangeMsg(CStatusChangeMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); + bool ActMsg(CActMsg *msg); + bool LeaveViewMsg(CLeaveViewMsg *msg); + bool EnterViewMsg(CEnterViewMsg *msg); + bool MouseDragStartMsg(CMouseDragStartMsg *msg); + bool TurnOff(CTurnOff *msg); public: int _fieldE0; int _fieldE4; diff --git a/engines/titanic/game/close_broken_pel.cpp b/engines/titanic/game/close_broken_pel.cpp index d27441ac96..c234590849 100644 --- a/engines/titanic/game/close_broken_pel.cpp +++ b/engines/titanic/game/close_broken_pel.cpp @@ -24,16 +24,26 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCloseBrokenPel, CBackground) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + void CCloseBrokenPel::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string3, indent); + file->writeQuotedLine(_target, indent); CBackground::save(file, indent); } void CCloseBrokenPel::load(SimpleFile *file) { file->readNumber(); - _string3 = file->readString(); + _target = file->readString(); CBackground::load(file); } +bool CCloseBrokenPel::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + CActMsg actMsg("Close"); + actMsg.execute(_target); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/close_broken_pel.h b/engines/titanic/game/close_broken_pel.h index aacda6c002..4bd66255df 100644 --- a/engines/titanic/game/close_broken_pel.h +++ b/engines/titanic/game/close_broken_pel.h @@ -28,8 +28,10 @@ namespace Titanic { class CCloseBrokenPel : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: - CString _string3; + CString _target; public: CLASSDEF; diff --git a/engines/titanic/game/cookie.cpp b/engines/titanic/game/cookie.cpp index 915bb93b4a..96edca4058 100644 --- a/engines/titanic/game/cookie.cpp +++ b/engines/titanic/game/cookie.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCookie, CGameObject) + ON_MESSAGE(LeaveNodeMsg) + ON_MESSAGE(FreshenCookieMsg) +END_MESSAGE_MAP() + void CCookie::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_value1, indent); @@ -40,4 +45,16 @@ void CCookie::load(SimpleFile *file) { CGameObject::load(file); } +bool CCookie::LeaveNodeMsg(CLeaveNodeMsg *msg) { + if (_value2) + _value1 = 1; + return true; +} + +bool CCookie::FreshenCookieMsg(CFreshenCookieMsg *msg) { + _value1 = msg->_value2; + _value2 = msg->_value1; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/cookie.h b/engines/titanic/game/cookie.h index 7ae04f1144..2018deeb3e 100644 --- a/engines/titanic/game/cookie.h +++ b/engines/titanic/game/cookie.h @@ -28,6 +28,9 @@ namespace Titanic { class CCookie : public CGameObject { + DECLARE_MESSAGE_MAP; + bool LeaveNodeMsg(CLeaveNodeMsg *msg); + bool FreshenCookieMsg(CFreshenCookieMsg *msg); public: int _value1; int _value2; diff --git a/engines/titanic/game/credits.cpp b/engines/titanic/game/credits.cpp index 7078d41a17..d9149f6dd2 100644 --- a/engines/titanic/game/credits.cpp +++ b/engines/titanic/game/credits.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCredits, CGameObject) + ON_MESSAGE(SignalObject) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + CCredits::CCredits() : CGameObject(), _fieldBC(-1), _fieldC0(1) { } @@ -43,4 +48,34 @@ void CCredits::load(SimpleFile *file) { CGameObject::load(file); } +bool CCredits::SignalObject(CSignalObject *msg) { + petHide(); + disableMouse(); + addTimer(50); + return true; +} + +bool CCredits::TimerMsg(CTimerMsg *msg) { + stopGlobalSound(true, -1); + setVisible(true); + loadSound("a#16.wav"); + loadSound("a#24.wav"); + + playCutscene(0, 18); + playGlobalSound("a#16.wav", -1, false, false, 0); + playCutscene(19, 642); + playSound("a#24.wav"); + playCutscene(643, 750); + + COpeningCreditsMsg creditsMsg; + creditsMsg.execute("Service Elevator Entity"); + changeView("EmbLobby.Node 6.S"); + + setVisible(false); + petShow(); + enableMouse(); + stopGlobalSound(true, -1); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/credits.h b/engines/titanic/game/credits.h index fa9794b6de..23fd25584d 100644 --- a/engines/titanic/game/credits.h +++ b/engines/titanic/game/credits.h @@ -28,6 +28,9 @@ namespace Titanic { class CCredits : public CGameObject { + DECLARE_MESSAGE_MAP; + bool SignalObject(CSignalObject *msg); + bool TimerMsg(CTimerMsg *msg); public: int _fieldBC, _fieldC0; public: diff --git a/engines/titanic/game/credits_button.cpp b/engines/titanic/game/credits_button.cpp index 90bb1b5ebe..ee8f7bb329 100644 --- a/engines/titanic/game/credits_button.cpp +++ b/engines/titanic/game/credits_button.cpp @@ -24,6 +24,11 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CCreditsButton, CBackground) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + CCreditsButton::CCreditsButton() : CBackground(), _fieldE0(1) { } @@ -39,4 +44,19 @@ void CCreditsButton::load(SimpleFile *file) { CBackground::load(file); } +bool CCreditsButton::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + return true; +} + +bool CCreditsButton::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + if (_fieldE0) { + playSound("a#20.wav"); + CSignalObject signalMsg; + signalMsg._numValue = 1; + signalMsg.execute("CreditsPlayer"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/credits_button.h b/engines/titanic/game/credits_button.h index 5e0bf96677..4a53083195 100644 --- a/engines/titanic/game/credits_button.h +++ b/engines/titanic/game/credits_button.h @@ -28,6 +28,9 @@ namespace Titanic { class CCreditsButton : public CBackground { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); public: int _fieldE0; public: diff --git a/engines/titanic/game/desk_click_responder.cpp b/engines/titanic/game/desk_click_responder.cpp index d9b2cb64b4..2cd5d1e83b 100644 --- a/engines/titanic/game/desk_click_responder.cpp +++ b/engines/titanic/game/desk_click_responder.cpp @@ -21,13 +21,19 @@ */ #include "titanic/game/desk_click_responder.h" +#include "titanic/titanic.h" namespace Titanic { +BEGIN_MESSAGE_MAP(CDeskClickResponder, CClickResponder) + ON_MESSAGE(MouseButtonDownMsg) + ON_MESSAGE(LoadSuccessMsg) +END_MESSAGE_MAP() + void CDeskClickResponder::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeNumberLine(_fieldD4, indent); - file->writeNumberLine(_fieldD8, indent); + file->writeNumberLine(_ticks, indent); CClickResponder::save(file, indent); } @@ -35,9 +41,28 @@ void CDeskClickResponder::save(SimpleFile *file, int indent) { void CDeskClickResponder::load(SimpleFile *file) { file->readNumber(); _fieldD4 = file->readNumber(); - _fieldD8 = file->readNumber(); + _ticks = file->readNumber(); CClickResponder::load(file); } +bool CDeskClickResponder::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + _fieldD4 = (_fieldD4 + 1) % 3; + if (_fieldD4) + return CClickResponder::MouseButtonDownMsg(msg); + + uint ticks = g_vm->_events->getTicksCount(); + if (!_ticks || ticks > (_ticks + 4000)) { + playSound("a#22.wav"); + _ticks = ticks; + } + + return true; +} + +bool CDeskClickResponder::LoadSuccessMsg(CLoadSuccessMsg *msg) { + _ticks = 0; + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/desk_click_responder.h b/engines/titanic/game/desk_click_responder.h index 12825ba9de..13cf7f4b87 100644 --- a/engines/titanic/game/desk_click_responder.h +++ b/engines/titanic/game/desk_click_responder.h @@ -28,9 +28,12 @@ namespace Titanic { class CDeskClickResponder : public CClickResponder { + DECLARE_MESSAGE_MAP; + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); + bool LoadSuccessMsg(CLoadSuccessMsg *msg); protected: int _fieldD4; - int _fieldD8; + uint _ticks; public: CLASSDEF; diff --git a/engines/titanic/game/null_port_hole.cpp b/engines/titanic/game/null_port_hole.cpp index e651b1b59f..b1514c7cbf 100644 --- a/engines/titanic/game/null_port_hole.cpp +++ b/engines/titanic/game/null_port_hole.cpp @@ -27,22 +27,22 @@ namespace Titanic { EMPTY_MESSAGE_MAP(CNullPortHole, CClickResponder); CNullPortHole::CNullPortHole() : CClickResponder() { - _string1 = "For a better view, why not visit the Promenade Deck?"; - _string2 = "b#48.wav"; + _message = "For a better view, why not visit the Promenade Deck?"; + _soundName = "b#48.wav"; } void CNullPortHole::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string2, indent); - file->writeQuotedLine(_string1, indent); + file->writeQuotedLine(_soundName, indent); + file->writeQuotedLine(_message, indent); CClickResponder::save(file, indent); } void CNullPortHole::load(SimpleFile *file) { file->readNumber(); - _string2 = file->readString(); - _string1 = file->readString(); + _soundName = file->readString(); + _message = file->readString(); CClickResponder::load(file); } diff --git a/engines/titanic/game/sgt/desk.cpp b/engines/titanic/game/sgt/desk.cpp index 4dd0fdab92..51be14adb9 100644 --- a/engines/titanic/game/sgt/desk.cpp +++ b/engines/titanic/game/sgt/desk.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDesk, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CDesk::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +40,44 @@ void CDesk::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CDesk::TurnOn(CTurnOn *msg) { + if (_statics->_v5 == "Closed" && _statics->_v1 == "RestingG" + && _statics->_v1 == "OpenWrong") { + _statics->_v5 = "Open"; + _fieldE0 = false; + _startFrame = 1; + _endFrame = 26; + playMovie(1, 26, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#12.wav"); + } + + return true; +} + +bool CDesk::TurnOff(CTurnOff *msg) { + if (_statics->_v5 == "Open" && _statics->_v6 == "Closed" + && _statics->_v1 == "Open") { + CVisibleMsg visibleMsg(false); + visibleMsg.execute("ChestOfDrawers"); + + _statics->_v5 = "Closed"; + _fieldE0 = true; + _startFrame = 26; + _endFrame = 51; + playMovie(26, 51, MOVIE_GAMESTATE); + playSound("b#9.wav"); + } + + return true; +} + +bool CDesk::MovieEndMsg(CMovieEndMsg *msg) { + if (_statics->_v5 == "Open") { + CVisibleMsg visibleMsg(true); + visibleMsg.execute("ChestOfDrawers"); + } + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/desk.h b/engines/titanic/game/sgt/desk.h index 77b5fa17af..8b9e1fe841 100644 --- a/engines/titanic/game/sgt/desk.h +++ b/engines/titanic/game/sgt/desk.h @@ -28,6 +28,10 @@ namespace Titanic { class CDesk : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/deskchair.cpp b/engines/titanic/game/sgt/deskchair.cpp index a4a2badeb0..7f64c2ee34 100644 --- a/engines/titanic/game/sgt/deskchair.cpp +++ b/engines/titanic/game/sgt/deskchair.cpp @@ -24,6 +24,13 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDeskchair, CSGTStateRoom) + ON_MESSAGE(TurnOn) + ON_MESSAGE(TurnOff) + ON_MESSAGE(ActMsg) + ON_MESSAGE(MovieEndMsg) +END_MESSAGE_MAP() + void CDeskchair::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); CSGTStateRoom::save(file, indent); @@ -34,4 +41,49 @@ void CDeskchair::load(SimpleFile *file) { CSGTStateRoom::load(file); } +bool CDeskchair::TurnOn(CTurnOn *msg) { + if (_statics->_v8 == "Closed" && _statics->_v9 == "Closed") { + setVisible(true); + _statics->_v9 = "Open"; + _fieldE0 = false; + _startFrame = 0; + _endFrame = 16; + playMovie(0, 16, MOVIE_GAMESTATE); + playSound("b#8.wav"); + } + + return true; +} + +bool CDeskchair::TurnOff(CTurnOff *msg) { + if (_statics->_v9 == "Open") { + _statics->_v9 = "Closed"; + _fieldE0 = true; + _startFrame = 16; + _endFrame = 32; + playMovie(16, 32, MOVIE_NOTIFY_OBJECT | MOVIE_GAMESTATE); + playSound("b#2.wav"); + } + + return true; +} + +bool CDeskchair::ActMsg(CActMsg *msg) { + if (msg->_action == "Smash") { + setVisible(false); + _statics->_v9 = "Closed"; + _fieldE0 = true; + loadFrame(0); + return true; + } else { + return CSGTStateRoom::ActMsg(msg); + } +} + +bool CDeskchair::MovieEndMsg(CMovieEndMsg *msg) { + if (_statics->_v9 == "Closed") + setVisible(false); + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/game/sgt/deskchair.h b/engines/titanic/game/sgt/deskchair.h index 5181b650d2..6e7bbe4169 100644 --- a/engines/titanic/game/sgt/deskchair.h +++ b/engines/titanic/game/sgt/deskchair.h @@ -28,6 +28,11 @@ namespace Titanic { class CDeskchair : public CSGTStateRoom { + DECLARE_MESSAGE_MAP; + bool TurnOn(CTurnOn *msg); + bool TurnOff(CTurnOff *msg); + bool ActMsg(CActMsg *msg); + bool MovieEndMsg(CMovieEndMsg *msg); public: CLASSDEF; diff --git a/engines/titanic/game/sgt/sgt_state_room.cpp b/engines/titanic/game/sgt/sgt_state_room.cpp index 55f08de8b4..c089e401b8 100644 --- a/engines/titanic/game/sgt/sgt_state_room.cpp +++ b/engines/titanic/game/sgt/sgt_state_room.cpp @@ -21,17 +21,22 @@ */ #include "titanic/game/sgt/sgt_state_room.h" +#include "titanic/pet_control/pet_control.h" namespace Titanic { BEGIN_MESSAGE_MAP(CSGTStateRoom, CBackground) + ON_MESSAGE(ActMsg) + ON_MESSAGE(VisibleMsg) ON_MESSAGE(EnterRoomMsg) + ON_MESSAGE(LeaveRoomMsg) END_MESSAGE_MAP() CSGTStateRoomStatics *CSGTStateRoom::_statics; void CSGTStateRoom::init() { _statics = new CSGTStateRoomStatics(); + _statics->_v1 = "Closed"; } void CSGTStateRoom::deinit() { @@ -94,8 +99,80 @@ void CSGTStateRoom::load(SimpleFile *file) { CBackground::load(file); } +bool CSGTStateRoom::ActMsg(CActMsg *msg) { + CPetControl *pet = getPetControl(); + uint roomFlags = pet->getRoomFlags(); + uint assignedRoom = pet->getAssignedRoomFlags(); + + if (roomFlags != assignedRoom) { + petDisplayMessage("This is not your assigned room. Please do not enjoy."); + } else if (_fieldE0) { + CTurnOn onMsg; + onMsg.execute(this); + } else { + CTurnOff offMsg; + offMsg.execute(this); + } + + return true; +} + +bool CSGTStateRoom::VisibleMsg(CVisibleMsg *msg) { + setVisible(msg->_visible); + return true; +} + bool CSGTStateRoom::EnterRoomMsg(CEnterRoomMsg *msg) { - warning("CSGTStateRoom::handleEvent"); + CPetControl *pet = getPetControl(); + uint roomFlags = pet->getRoomFlags(); + uint assignedRoom = pet->getAssignedRoomFlags(); + + if (roomFlags == assignedRoom) { + loadFrame(_fieldE8); + _fieldE0 = _fieldEC; + setVisible(_fieldF0); + + if (isEquals("Desk") && _statics->_v5 == "Closed") + loadFrame(1); + } + + if (isEquals("Drawer")) { + petSetArea(PET_REMOTE); + if (roomFlags == assignedRoom && getPassengerClass() == 3 + && _statics->_v13) { + playSound("b#21.wav"); + _statics->_v13 = 0; + } + + _statics->_v7 = "Closed"; + setVisible(false); + _fieldE0 = true; + } else if (roomFlags != assignedRoom) { + loadFrame(0); + if (_fieldE4) { + setVisible(true); + if (isEquals("Desk")) + loadFrame(1); + } else { + setVisible(false); + } + } + + return true; +} + +bool CSGTStateRoom::LeaveRoomMsg(CLeaveRoomMsg *msg) { + CPetControl *pet = getPetControl(); + uint roomFlags = pet->getRoomFlags(); + uint assignedRoom = pet->getAssignedRoomFlags(); + + if (roomFlags == assignedRoom) { + _fieldE8 = getMovieFrame(); + _fieldEC = _fieldE0; + _fieldF0 = _visible; + } + + _statics->_v14 = roomFlags; return true; } diff --git a/engines/titanic/game/sgt/sgt_state_room.h b/engines/titanic/game/sgt/sgt_state_room.h index d9ffdb8e9e..3975f7b59b 100644 --- a/engines/titanic/game/sgt/sgt_state_room.h +++ b/engines/titanic/game/sgt/sgt_state_room.h @@ -47,15 +47,18 @@ struct CSGTStateRoomStatics { class CSGTStateRoom : public CBackground { DECLARE_MESSAGE_MAP; + bool ActMsg(CActMsg *msg); + bool VisibleMsg(CVisibleMsg *msg); bool EnterRoomMsg(CEnterRoomMsg *msg); + bool LeaveRoomMsg(CLeaveRoomMsg *msg); protected: static CSGTStateRoomStatics *_statics; protected: - int _fieldE0; + bool _fieldE0; int _fieldE4; int _fieldE8; - int _fieldEC; - int _fieldF0; + bool _fieldEC; + bool _fieldF0; public: CLASSDEF; CSGTStateRoom(); diff --git a/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp b/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp index ed37b0a5c7..72cd7f9037 100644 --- a/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp +++ b/engines/titanic/game/sgt/sgt_upper_doors_sound.cpp @@ -25,19 +25,19 @@ namespace Titanic { CSGTUpperDoorsSound::CSGTUpperDoorsSound() { - _string2 = "b#53.wav"; + _soundName = "b#53.wav"; } void CSGTUpperDoorsSound::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); - file->writeQuotedLine(_string2, indent); + file->writeQuotedLine(_soundName, indent); CClickResponder::save(file, indent); } void CSGTUpperDoorsSound::load(SimpleFile *file) { file->readNumber(); - _string2 = file->readString(); + _soundName = file->readString(); CClickResponder::load(file); } diff --git a/engines/titanic/gfx/chev_switch.cpp b/engines/titanic/gfx/chev_switch.cpp index a6ce93098c..177f0ada76 100644 --- a/engines/titanic/gfx/chev_switch.cpp +++ b/engines/titanic/gfx/chev_switch.cpp @@ -24,7 +24,13 @@ namespace Titanic { -CChevSwitch::CChevSwitch() : CToggleSwitch() { +BEGIN_MESSAGE_MAP(CChevSwitch, CToggleSwitch) + ON_MESSAGE(MouseButtonUpMsg) + ON_MESSAGE(SetChevButtonImageMsg) + ON_MESSAGE(MouseButtonDownMsg) +END_MESSAGE_MAP() + +CChevSwitch::CChevSwitch() : CToggleSwitch(), _value(0) { } void CChevSwitch::save(SimpleFile *file, int indent) { @@ -37,4 +43,36 @@ void CChevSwitch::load(SimpleFile *file) { CToggleSwitch::load(file); } +bool CChevSwitch::MouseButtonUpMsg(CMouseButtonUpMsg *msg) { + return true; +} + +bool CChevSwitch::SetChevButtonImageMsg(CSetChevButtonImageMsg *msg) { + if (msg->_value2 && getParent()) { + error("TODO: Don't know parent type"); + } + + _fieldBC = msg->_value1; + if (_fieldBC) { + loadImage((_value & 1) ? "on_odd.tga" : "on_even.tga"); + } else { + loadImage((_value & 1) ? "off_odd.tga" : "off_even.tga"); + } + + return true; +} + +bool CChevSwitch::MouseButtonDownMsg(CMouseButtonDownMsg *msg) { + _fieldBC ^= 1; + if (getParent()) { + CSetChevPanelBitMsg bitMsg(_value, _fieldBC); + bitMsg.execute(getParent()); + } + + CSetChevButtonImageMsg chevMsg(_fieldBC, 0); + chevMsg.execute(this); + + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/gfx/chev_switch.h b/engines/titanic/gfx/chev_switch.h index 0305a6ca83..01da53c854 100644 --- a/engines/titanic/gfx/chev_switch.h +++ b/engines/titanic/gfx/chev_switch.h @@ -28,6 +28,12 @@ namespace Titanic { class CChevSwitch : public CToggleSwitch { + DECLARE_MESSAGE_MAP; + bool MouseButtonUpMsg(CMouseButtonUpMsg *msg); + bool SetChevButtonImageMsg(CSetChevButtonImageMsg *msg); + bool MouseButtonDownMsg(CMouseButtonDownMsg *msg); +public: + int _value; public: CLASSDEF; CChevSwitch(); diff --git a/engines/titanic/gfx/toggle_switch.h b/engines/titanic/gfx/toggle_switch.h index ae96c75ebd..8e7d057d8c 100644 --- a/engines/titanic/gfx/toggle_switch.h +++ b/engines/titanic/gfx/toggle_switch.h @@ -28,7 +28,7 @@ namespace Titanic { class CToggleSwitch : public CGameObject { -private: +protected: int _fieldBC; Point _pos1; public: diff --git a/engines/titanic/messages/door_auto_sound_event.cpp b/engines/titanic/messages/door_auto_sound_event.cpp index b9cedae6de..7618577e50 100644 --- a/engines/titanic/messages/door_auto_sound_event.cpp +++ b/engines/titanic/messages/door_auto_sound_event.cpp @@ -24,6 +24,12 @@ namespace Titanic { +BEGIN_MESSAGE_MAP(CDoorAutoSoundEvent, CAutoSoundEvent) + ON_MESSAGE(PreEnterNodeMsg) + ON_MESSAGE(LeaveNodeMsg) + ON_MESSAGE(TimerMsg) +END_MESSAGE_MAP() + void CDoorAutoSoundEvent::save(SimpleFile *file, int indent) { file->writeNumberLine(1, indent); file->writeQuotedLine(_string1, indent); @@ -44,4 +50,16 @@ void CDoorAutoSoundEvent::load(SimpleFile *file) { CAutoSoundEvent::load(file); } +bool CDoorAutoSoundEvent::PreEnterNodeMsg(CPreEnterNodeMsg *msg) { + return true; +} + +bool CDoorAutoSoundEvent::LeaveNodeMsg(CLeaveNodeMsg *msg) { + return true; +} + +bool CDoorAutoSoundEvent::TimerMsg(CTimerMsg *msg) { + return true; +} + } // End of namespace Titanic diff --git a/engines/titanic/messages/door_auto_sound_event.h b/engines/titanic/messages/door_auto_sound_event.h index e6ea1b0f98..8b064a7221 100644 --- a/engines/titanic/messages/door_auto_sound_event.h +++ b/engines/titanic/messages/door_auto_sound_event.h @@ -28,6 +28,10 @@ namespace Titanic { class CDoorAutoSoundEvent : public CAutoSoundEvent { + DECLARE_MESSAGE_MAP; + bool PreEnterNodeMsg(CPreEnterNodeMsg *msg); + bool LeaveNodeMsg(CLeaveNodeMsg *msg); + bool TimerMsg(CTimerMsg *msg); public: CString _string1; CString _string2; |