mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-05 00:36:57 +00:00
5dcdfd2600
The major contributors provided their consent: DrMcCoy, Strangerke, sdelamarre, sev. The goal is to allow the re-release of this code under Switch, which is incompatible with pure GPL
480 lines
12 KiB
C++
480 lines
12 KiB
C++
/* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*
|
|
* This file is dual-licensed.
|
|
* In addition to the GPLv3 license mentioned above, this code is also
|
|
* licensed under LGPL 2.1. See LICENSES/COPYING.LGPL file for the
|
|
* full text of the license.
|
|
*
|
|
*/
|
|
|
|
#include "common/endian.h"
|
|
|
|
#include "gob/gob.h"
|
|
#include "gob/inter.h"
|
|
#include "gob/global.h"
|
|
#include "gob/util.h"
|
|
#include "gob/draw.h"
|
|
#include "gob/game.h"
|
|
#include "gob/expression.h"
|
|
#include "gob/script.h"
|
|
#include "gob/hotspots.h"
|
|
#include "gob/scenery.h"
|
|
#include "gob/sound/sound.h"
|
|
|
|
namespace Gob {
|
|
|
|
Inter::Inter(GobEngine *vm) : _vm(vm), _varStack(1000) {
|
|
_terminate = 0;
|
|
_break = false;
|
|
|
|
for (int i = 0; i < 8; i++) {
|
|
_animPalLowIndex[i] = 0;
|
|
_animPalHighIndex[i] = 0;
|
|
_animPalDir[i] = 0;
|
|
}
|
|
|
|
_breakFromLevel = nullptr;
|
|
_nestLevel = nullptr;
|
|
|
|
_soundEndTimeKey = 0;
|
|
_soundStopVal = 0;
|
|
|
|
_lastBusyWait = 0;
|
|
_noBusyWait = false;
|
|
|
|
_variables = nullptr;
|
|
}
|
|
|
|
Inter::~Inter() {
|
|
delocateVars();
|
|
}
|
|
|
|
void Inter::setupOpcodes() {
|
|
setupOpcodesDraw();
|
|
setupOpcodesFunc();
|
|
setupOpcodesGob();
|
|
}
|
|
|
|
void Inter::executeOpcodeDraw(byte i) {
|
|
debugC(1, kDebugDrawOp, "opcodeDraw %d [0x%X] (%s)", i, i, getDescOpcodeDraw(i));
|
|
|
|
if (_opcodesDraw[i].proc && _opcodesDraw[i].proc->isValid())
|
|
(*_opcodesDraw[i].proc)();
|
|
else
|
|
warning("unimplemented opcodeDraw: %d [0x%X]", i, i);
|
|
}
|
|
|
|
void Inter::executeOpcodeFunc(byte i, byte j, OpFuncParams ¶ms) {
|
|
debugC(1, kDebugFuncOp, "%s:%08d: opcodeFunc %d.%d [0x%X.0x%X] (%s)",
|
|
_vm->_game->_curTotFile.c_str(), _vm->_game->_script->pos(), i, j, i, j, getDescOpcodeFunc(i, j));
|
|
|
|
int n = i * 16 + j;
|
|
if ((i <= 4) && (j <= 15) && _opcodesFunc[n].proc && _opcodesFunc[n].proc->isValid())
|
|
(*_opcodesFunc[n].proc)(params);
|
|
else
|
|
warning("unimplemented opcodeFunc: %d.%d [0x%X.0x%X]", i, j, i, j);
|
|
}
|
|
|
|
void Inter::executeOpcodeGob(int i, OpGobParams ¶ms) {
|
|
debugC(1, kDebugGobOp, "opcodeGoblin %d [0x%X] (%s)",
|
|
i, i, getDescOpcodeGob(i));
|
|
|
|
OpcodeEntry<OpcodeGob> *op = nullptr;
|
|
|
|
if (_opcodesGob.contains(i))
|
|
op = &_opcodesGob.getVal(i);
|
|
|
|
if (op && op->proc && op->proc->isValid()) {
|
|
(*op->proc)(params);
|
|
return;
|
|
}
|
|
|
|
_vm->_game->_script->skip(params.paramCount << 1);
|
|
warning("unimplemented opcodeGob: %d [0x%X]", i, i);
|
|
}
|
|
|
|
const char *Inter::getDescOpcodeDraw(byte i) {
|
|
const char *desc = _opcodesDraw[i].desc;
|
|
|
|
return ((desc) ? desc : "");
|
|
}
|
|
|
|
const char *Inter::getDescOpcodeFunc(byte i, byte j) {
|
|
if ((i > 4) || (j > 15))
|
|
return "";
|
|
|
|
const char *desc = _opcodesFunc[i * 16 + j].desc;
|
|
|
|
return ((desc) ? desc : "");
|
|
}
|
|
|
|
const char *Inter::getDescOpcodeGob(int i) {
|
|
if (_opcodesGob.contains(i))
|
|
return _opcodesGob.getVal(i).desc;
|
|
|
|
return "";
|
|
}
|
|
|
|
void Inter::initControlVars(char full) {
|
|
*_nestLevel = 0;
|
|
*_breakFromLevel = -1;
|
|
|
|
*_vm->_scenery->_pCaptureCounter = 0;
|
|
|
|
_break = false;
|
|
_terminate = 0;
|
|
|
|
if (full == 1) {
|
|
for (int i = 0; i < 8; i++)
|
|
_animPalDir[i] = 0;
|
|
_soundEndTimeKey = 0;
|
|
}
|
|
}
|
|
|
|
void Inter::renewTimeInVars() {
|
|
TimeDate t;
|
|
_vm->_system->getTimeAndDate(t);
|
|
|
|
WRITE_VAR(5, 1900 + t.tm_year);
|
|
WRITE_VAR(6, t.tm_mon + 1);
|
|
WRITE_VAR(7, 0);
|
|
WRITE_VAR(8, t.tm_mday);
|
|
WRITE_VAR(9, t.tm_hour);
|
|
WRITE_VAR(10, t.tm_min);
|
|
WRITE_VAR(11, t.tm_sec);
|
|
}
|
|
|
|
void Inter::storeMouse() {
|
|
int16 x;
|
|
int16 y;
|
|
|
|
x = _vm->_global->_inter_mouseX;
|
|
y = _vm->_global->_inter_mouseY;
|
|
_vm->_draw->adjustCoords(1, &x, &y);
|
|
|
|
WRITE_VAR(2, x);
|
|
WRITE_VAR(3, y);
|
|
WRITE_VAR(4, (uint32) _vm->_game->_mouseButtons);
|
|
}
|
|
|
|
void Inter::storeKey(int16 key) {
|
|
WRITE_VAR(12, _vm->_util->getTimeKey() - _vm->_game->_startTimeKey);
|
|
|
|
storeMouse();
|
|
bool isSoundPlaying = _vm->_sound->blasterPlayingSound() ||
|
|
(_vm->getGameType() == kGameTypeAdibou1 && // NOTE: may be needed by other games as well
|
|
_vm->_vidPlayer->isSoundPlaying());
|
|
|
|
WRITE_VAR(1, isSoundPlaying);
|
|
|
|
if (key == kKeyUp)
|
|
key = kShortKeyUp;
|
|
else if (key == kKeyDown)
|
|
key = kShortKeyDown;
|
|
else if (key == kKeyRight)
|
|
key = kShortKeyRight;
|
|
else if (key == kKeyLeft)
|
|
key = kShortKeyLeft;
|
|
else if (key == kKeyEscape)
|
|
key = kShortKeyEscape;
|
|
else if (key == kKeyBackspace)
|
|
key = kShortKeyBackspace;
|
|
else if (key == kKeyDelete)
|
|
key = kShortKeyDelete;
|
|
else if ((key & 0xFF) != 0)
|
|
key &= 0xFF;
|
|
|
|
WRITE_VAR(0, key);
|
|
|
|
if (key != 0)
|
|
_vm->_util->clearKeyBuf();
|
|
}
|
|
|
|
void Inter::writeVar(uint32 offset, uint16 type, uint32 value) {
|
|
switch (type) {
|
|
case TYPE_VAR_INT8:
|
|
case TYPE_ARRAY_INT8:
|
|
WRITE_VARO_UINT8(offset, value);
|
|
break;
|
|
|
|
case TYPE_VAR_INT16:
|
|
case TYPE_VAR_INT32_AS_INT16:
|
|
case TYPE_ARRAY_INT16:
|
|
WRITE_VARO_UINT16(offset, value);
|
|
break;
|
|
|
|
default:
|
|
WRITE_VAR_OFFSET(offset, value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Inter::funcBlock(int16 retFlag) {
|
|
OpFuncParams params;
|
|
byte cmd;
|
|
byte cmd2;
|
|
|
|
params.retFlag = retFlag;
|
|
|
|
if (_vm->_game->_script->isFinished())
|
|
return;
|
|
|
|
_break = false;
|
|
_vm->_game->_script->skip(1);
|
|
params.cmdCount = _vm->_game->_script->readByte();
|
|
_vm->_game->_script->skip(2);
|
|
|
|
if (params.cmdCount == 0) {
|
|
_vm->_game->_script->setFinished(true);
|
|
return;
|
|
}
|
|
|
|
int startaddr = _vm->_game->_script->pos();
|
|
|
|
params.counter = 0;
|
|
do {
|
|
if (_terminate)
|
|
break;
|
|
|
|
// WORKAROUND:
|
|
// The EGA, Mac and Windows versions of gob1 doesn't add a delay after
|
|
// showing images between levels. We manually add it here.
|
|
if ((_vm->getGameType() == kGameTypeGob1) &&
|
|
( _vm->isEGA() ||
|
|
(_vm->getPlatform() == Common::kPlatformMacintosh) ||
|
|
(_vm->getPlatform() == Common::kPlatformWindows))) {
|
|
|
|
int addr = _vm->_game->_script->pos();
|
|
|
|
if ((startaddr == 0x18B4 && addr == 0x1A7F && _vm->isCurrentTot("avt005.tot")) || // Zombie, EGA
|
|
(startaddr == 0x188D && addr == 0x1A58 && _vm->isCurrentTot("avt005.tot")) || // Zombie, Mac
|
|
(startaddr == 0x1299 && addr == 0x139A && _vm->isCurrentTot("avt006.tot")) || // Dungeon
|
|
(startaddr == 0x11C0 && addr == 0x12C9 && _vm->isCurrentTot("avt012.tot")) || // Cauldron, EGA
|
|
(startaddr == 0x11C8 && addr == 0x1341 && _vm->isCurrentTot("avt012.tot")) || // Cauldron, Mac
|
|
(startaddr == 0x09F2 && addr == 0x0AF3 && _vm->isCurrentTot("avt016.tot")) || // Statue
|
|
(startaddr == 0x0B92 && addr == 0x0C93 && _vm->isCurrentTot("avt019.tot")) || // Castle
|
|
(startaddr == 0x17D9 && addr == 0x18DA && _vm->isCurrentTot("avt022.tot")) || // Finale, EGA
|
|
(startaddr == 0x17E9 && addr == 0x19A8 && _vm->isCurrentTot("avt022.tot"))) { // Finale, Mac
|
|
|
|
_vm->_util->longDelay(5000);
|
|
}
|
|
} // End of workaround
|
|
|
|
// WORKAROUND:
|
|
// Apart the CD version which is playing a speech in this room, all the versions
|
|
// of Fascination have a too short delay between the storage room and the lab.
|
|
// We manually add it here.
|
|
if ((_vm->getGameType() == kGameTypeFascination) && _vm->isCurrentTot("PLANQUE.tot")) {
|
|
int addr = _vm->_game->_script->pos();
|
|
if ((startaddr == 0x0202 && addr == 0x0330) || // Before Lab, Amiga & Atari, English
|
|
(startaddr == 0x023D && addr == 0x032D) || // Before Lab, PC floppy, German
|
|
(startaddr == 0x02C2 && addr == 0x03C2)) { // Before Lab, PC floppy, Hebrew
|
|
warning("Fascination - Adding delay");
|
|
_vm->_util->longDelay(3000);
|
|
}
|
|
} // End of workaround
|
|
|
|
cmd = _vm->_game->_script->readByte();
|
|
|
|
// WORKAROUND:
|
|
// A VGA version has some broken code in its scripts, this workaround skips the corrupted parts.
|
|
if (_vm->getGameType() == kGameTypeFascination) {
|
|
int addr = _vm->_game->_script->pos();
|
|
if ((startaddr == 0x212D) && (addr == 0x290E) && (cmd == 0x90) && _vm->isCurrentTot("INTRO1.tot")) {
|
|
_vm->_game->_script->skip(2);
|
|
cmd = _vm->_game->_script->readByte();
|
|
}
|
|
if ((startaddr == 0x207D) && (addr == 0x22CE) && (cmd == 0x90) && _vm->isCurrentTot("INTRO2.tot")) {
|
|
_vm->_game->_script->skip(2);
|
|
cmd = _vm->_game->_script->readByte();
|
|
}
|
|
}
|
|
|
|
if ((cmd >> 4) >= 12) {
|
|
cmd2 = 16 - (cmd >> 4);
|
|
cmd &= 0xF;
|
|
} else
|
|
cmd2 = 0;
|
|
|
|
params.counter++;
|
|
|
|
if (cmd2 == 0)
|
|
cmd >>= 4;
|
|
|
|
params.doReturn = false;
|
|
executeOpcodeFunc(cmd2, cmd, params);
|
|
|
|
if (params.doReturn)
|
|
return;
|
|
|
|
if (_vm->shouldQuit())
|
|
break;
|
|
|
|
if (_break) {
|
|
if (params.retFlag != 2)
|
|
break;
|
|
|
|
if (*_breakFromLevel == -1)
|
|
_break = false;
|
|
break;
|
|
}
|
|
} while (params.counter != params.cmdCount);
|
|
|
|
_vm->_game->_script->setFinished(true);
|
|
}
|
|
|
|
void Inter::callSub(int16 retFlag) {
|
|
byte block;
|
|
|
|
while (!_vm->shouldQuit() && !_vm->_game->_script->isFinished() &&
|
|
(_vm->_game->_script->pos() != 0)) {
|
|
|
|
block = _vm->_game->_script->peekByte();
|
|
if (block == 1)
|
|
funcBlock(retFlag);
|
|
else if (block == 2)
|
|
_vm->_game->_hotspots->evaluate();
|
|
else
|
|
error("Unknown block type %d in Inter::callSub()", block);
|
|
}
|
|
|
|
if (!_vm->_game->_script->isFinished() && (_vm->_game->_script->pos() == 0))
|
|
_terminate = 1;
|
|
}
|
|
|
|
void Inter::allocateVars(uint32 count) {
|
|
if (_vm->getEndianness() == kEndiannessBE)
|
|
_variables = new VariablesBE(count * 4);
|
|
else
|
|
_variables = new VariablesLE(count * 4);
|
|
}
|
|
|
|
void Inter::delocateVars() {
|
|
if (_vm->_game)
|
|
_vm->_game->deletedVars(_variables);
|
|
|
|
delete _variables;
|
|
_variables = nullptr;
|
|
}
|
|
|
|
void Inter::storeValue(uint16 index, uint16 type, uint32 value) {
|
|
switch (type) {
|
|
case OP_ARRAY_INT8:
|
|
case TYPE_VAR_INT8:
|
|
WRITE_VARO_UINT8(index, value);
|
|
break;
|
|
|
|
case TYPE_VAR_INT16:
|
|
case TYPE_VAR_INT32_AS_INT16:
|
|
case TYPE_ARRAY_INT16:
|
|
WRITE_VARO_UINT16(index, value);
|
|
break;
|
|
|
|
default:
|
|
WRITE_VARO_UINT32(index, value);
|
|
}
|
|
}
|
|
|
|
void Inter::storeValue(uint32 value) {
|
|
uint16 type;
|
|
uint16 index = _vm->_game->_script->readVarIndex(nullptr, &type);
|
|
|
|
storeValue(index, type, value);
|
|
}
|
|
|
|
void Inter::storeString(uint16 index, uint16 type, const char *value) {
|
|
uint32 maxLength = _vm->_global->_inter_animDataSize * 4 - 1;
|
|
char *str = GET_VARO_STR(index);
|
|
|
|
switch (type) {
|
|
case TYPE_VAR_STR:
|
|
if (strlen(value) > maxLength)
|
|
warning("Inter_v7::storeString(): String too long");
|
|
|
|
Common::strlcpy(str, value, maxLength);
|
|
break;
|
|
|
|
case TYPE_IMM_INT8:
|
|
case TYPE_VAR_INT8:
|
|
Common::strcpy_s(str, maxLength, value);
|
|
break;
|
|
|
|
case TYPE_ARRAY_INT8:
|
|
WRITE_VARO_UINT8(index, atoi(value));
|
|
break;
|
|
|
|
case TYPE_VAR_INT16:
|
|
case TYPE_VAR_INT32_AS_INT16:
|
|
case TYPE_ARRAY_INT16:
|
|
WRITE_VARO_UINT16(index, atoi(value));
|
|
break;
|
|
|
|
case TYPE_VAR_INT32:
|
|
case TYPE_ARRAY_INT32:
|
|
WRITE_VARO_UINT32(index, atoi(value));
|
|
break;
|
|
|
|
default:
|
|
warning("Inter_v7::storeString(): Requested to store a string into type %d", type);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Inter::storeString(const char *value) {
|
|
uint16 type;
|
|
uint16 varIndex = _vm->_game->_script->readVarIndex(nullptr, &type);
|
|
|
|
storeString(varIndex, type, value);
|
|
}
|
|
|
|
uint32 Inter::readValue(uint16 index, uint16 type) {
|
|
switch (type) {
|
|
case TYPE_IMM_INT8:
|
|
case TYPE_VAR_INT8:
|
|
case TYPE_ARRAY_INT8:
|
|
return (uint32)(((int32)((int8)READ_VARO_UINT8(index))));
|
|
break;
|
|
|
|
case TYPE_VAR_INT16:
|
|
case TYPE_VAR_INT32_AS_INT16:
|
|
case TYPE_ARRAY_INT16:
|
|
return (uint32)(((int32)((int16)READ_VARO_UINT16(index))));
|
|
|
|
default:
|
|
return READ_VARO_UINT32(index);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void Inter::handleBusyWait() {
|
|
uint32 now = _vm->_util->getTimeKey();
|
|
|
|
if (!_noBusyWait)
|
|
if ((now - _lastBusyWait) <= 20)
|
|
_vm->_util->longDelay(1);
|
|
|
|
_lastBusyWait = now;
|
|
_noBusyWait = false;
|
|
}
|
|
|
|
} // End of namespace Gob
|