scummvm/engines/cine/various.cpp
2021-09-04 23:14:18 +02:00

2013 lines
55 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 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/config-manager.h"
#include "common/endian.h"
#include "common/events.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "graphics/cursorman.h"
#include "gui/saveload.h"
#include "cine/cine.h"
#include "cine/main_loop.h"
#include "cine/object.h"
#include "cine/sound.h"
#include "cine/bg_list.h"
#include "cine/various.h"
namespace Cine {
int16 disableSystemMenu = 0;
bool inMenu;
bool runOnlyUntilFreePartRangeFirst200 = false;
int16 commandVar3[4];
int16 commandVar1;
int16 commandVar2;
//Message messageTable[NUM_MAX_MESSAGE];
uint16 var2;
uint16 var3;
uint16 var4;
uint16 lastType20OverlayBgIdx; // Was var5
uint16 reloadBgPalOnNextFlip;
uint16 forbidBgPalReload;
uint16 gfxFadeOutCompleted;
uint16 gfxFadeInRequested;
uint32 safeControlsLastAccessedMs;
int16 lastSafeControlObjIdx;
int16 buildObjectListCommand(int16 param);
int16 canUseOnObject = 0;
void waitPlayerInput() {
manageEvents(WAIT_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_DOWN_UP);
}
void setTextWindow(uint16 param1, uint16 param2, uint16 param3, uint16 param4) {
}
uint16 errorVar;
byte menuVar;
uint16 allowPlayerInput;
uint16 checkForPendingDataLoadSwitch;
uint16 isDrawCommandEnabled;
uint16 waitForPlayerClick;
uint16 menuCommandLen;
bool _paletteNeedUpdate;
uint16 _messageLen;
int16 playerCommand;
char currentPrcName[20];
char currentRelName[20];
char currentObjectName[20];
char currentMsgName[20];
char newPrcName[20];
char newRelName[20];
char newObjectName[20];
char newMsgName[20];
char currentCtName[15];
char currentPartName[15];
char currentDatName[30];
uint16 musicIsPlaying;
byte isInPause = 0;
/**
* Bit masks for mouse buttons' states
* Bit on = mouse button down
* Bit off = mouse button up
*/
enum MouseButtonState {
kLeftMouseButton = (1 << 0),
kRightMouseButton = (1 << 1)
};
/** Values used by the xMoveKeyb variable */
enum xMoveKeybEnums {
kKeybMoveCenterX = 0,
kKeybMoveRight = 1,
kKeybMoveLeft = 2
};
/** Values used by the yMoveKeyb variable */
enum yMoveKeybEnums {
kKeybMoveCenterY = 0,
kKeybMoveDown = 1,
kKeybMoveUp = 2
};
uint16 xMoveKeyb = kKeybMoveCenterX;
bool egoMovedWithKeyboard = false;
uint16 yMoveKeyb = kKeybMoveCenterY;
SelectedObjStruct currentSelectedObject;
CommandeType currentSaveName[kMaxSavegames];
static const CommandeType saveChoices[] = {"0-19", "20-39", "40-59", "60-79", "80-99"};
static const int16 choiceResultTable[] = { 1, 1, 1, 2, 1, 1, 1 };
static const int16 subObjectUseTable[] = { 3, 3, 3, 3, 3, 0, 0 };
static const int16 canUseOnItemTable[] = { 1, 0, 0, 1, 1, 0, 0 };
CommandeType objectListCommand[20];
int16 objListTab[20];
/**
* Move the player character using the keyboard
* @param x Negative values move left, positive right, zero not at all
* @param y Negative values move down, positive up, zero not at all
* NOTE: If both x and y are zero then the character stops
* FIXME: This seems to only work in Operation Stealth. May need code changes somewhere else...
*/
void moveUsingKeyboard(int x, int y) {
if (x > 0) {
xMoveKeyb = kKeybMoveRight;
} else if (x < 0) {
xMoveKeyb = kKeybMoveLeft;
} else {
xMoveKeyb = kKeybMoveCenterX;
}
if (y > 0) {
yMoveKeyb = kKeybMoveUp;
} else if (y < 0) {
yMoveKeyb = kKeybMoveDown;
} else {
yMoveKeyb = kKeybMoveCenterY;
}
egoMovedWithKeyboard = x || y;
}
void stopMusicAfterFadeOut() {
// if (g_sfxPlayer->_fadeOutCounter != 0 && g_sfxPlayer->_fadeOutCounter < 100) {
// g_sfxPlayer->stop();
// }
}
uint safeControlAccessMinMs() {
return 250;
}
void runObjectScript(int16 entryIdx) {
ScriptPtr tmp(g_cine->_scriptInfo->create(*g_cine->_relTable[entryIdx], entryIdx));
assert(tmp);
g_cine->_objectScripts.push_back(tmp);
}
/**
* Add action result message to overlay list
* @param cmd Message description
* @todo Why are x, y, width and color left uninitialized?
*/
void addPlayerCommandMessage(int16 cmd) {
overlay tmp;
memset(&tmp, 0, sizeof(tmp));
tmp.objIdx = cmd;
tmp.type = 3;
g_cine->_overlayList.push_back(tmp);
}
int16 getRelEntryForObject(uint16 param1, uint16 param2, SelectedObjStruct *pSelectedObject) {
int16 i;
int16 found = -1;
for (i = 0; i < (int16)g_cine->_relTable.size(); i++) {
if (g_cine->_relTable[i]->_param1 == param1 && g_cine->_relTable[i]->_param2 == pSelectedObject->idx) {
if (param2 == 1) {
found = i;
} else if (param2 == 2) {
if (g_cine->_relTable[i]->_param3 == pSelectedObject->param) {
found = i;
}
}
}
if (found != -1)
break;
}
return found;
}
/**
* Find index of the object under cursor
* @param x Mouse cursor coordinate
* @param y Mouse cursor coordinate
* @todo Fix displaced type 1 objects
*/
int16 getObjectUnderCursor(uint16 x, uint16 y) {
Common::List<overlay>::iterator it;
int16 objX, objY, frame, part, threshold, height, xdif, ydif;
int width;
// reverse_iterator would be nice
for (it = g_cine->_overlayList.reverse_begin(); it != g_cine->_overlayList.end(); --it) {
if (it->type >= 2 || !g_cine->_objectTable[it->objIdx].name[0]) {
continue;
}
objX = g_cine->_objectTable[it->objIdx].x;
objY = g_cine->_objectTable[it->objIdx].y;
frame = ABS((int16)(g_cine->_objectTable[it->objIdx].frame));
part = g_cine->_objectTable[it->objIdx].part;
// Additional case for negative frame values in Operation Stealth
if (g_cine->getGameType() == Cine::GType_OS && g_cine->_objectTable[it->objIdx].frame < 0) {
if ((it->type == 1) && (x >= objX) && (objX + frame >= x) && (y >= objY) && (objY + part >= y)) {
return it->objIdx;
} else {
continue;
}
}
if (it->type == 0) {
threshold = g_cine->_animDataTable[frame]._var1;
} else {
threshold = g_cine->_animDataTable[frame]._width / 2;
}
height = g_cine->_animDataTable[frame]._height;
width = g_cine->_animDataTable[frame]._realWidth;
xdif = x - objX;
ydif = y - objY;
if ((xdif < 0) || ((threshold << 4) <= xdif) || (ydif <= 0) || (ydif >= height) || !g_cine->_animDataTable[frame].data()) {
continue;
}
if (g_cine->getGameType() == Cine::GType_OS) {
// This test isn't present in Operation Stealth's PC version's disassembly
// but removing it makes things crash sometimes (e.g. when selecting a verb
// and moving the mouse cursor around the floor in the airport's bathroom).
if (xdif >= width) {
continue;
}
if (it->type == 0 && g_cine->_animDataTable[frame].getColor(xdif, ydif) != (part & 0x0F)) {
return it->objIdx;
} else if (it->type == 1 && gfxGetBit(xdif, ydif, g_cine->_animDataTable[frame].data(), g_cine->_animDataTable[frame]._width * 4)) {
return it->objIdx;
}
} else if (it->type == 0) { // use generated mask
if (gfxGetBit(xdif, ydif, g_cine->_animDataTable[frame].mask(), g_cine->_animDataTable[frame]._width)) {
return it->objIdx;
}
} else if (it->type == 1) { // is mask
if (gfxGetBit(xdif, ydif, g_cine->_animDataTable[frame].data(), g_cine->_animDataTable[frame]._width * 4)) {
return it->objIdx;
}
}
}
return -1;
}
void CineEngine::resetEngine() {
g_sound->stopMusic();
freeAnimDataTable();
g_cine->_overlayList.clear();
g_cine->_bgIncrustList.clear();
closePart();
g_cine->_objectScripts.clear();
g_cine->_globalScripts.clear();
g_cine->_relTable.clear();
g_cine->_scriptTable.clear();
g_cine->_messageTable.clear();
resetObjectTable();
g_cine->_globalVars.reset();
bgVar0 = 0;
var2 = var3 = var4 = lastType20OverlayBgIdx = 0;
strcpy(newPrcName, "");
strcpy(newRelName, "");
strcpy(newObjectName, "");
strcpy(newMsgName, "");
strcpy(currentCtName, "");
allowPlayerInput = 0;
waitForPlayerClick = 0;
playerCommand = -1;
isDrawCommandEnabled = 0;
g_cine->_commandBuffer = "";
g_cine->_globalVars[VAR_MOUSE_X_POS] = 0;
g_cine->_globalVars[VAR_MOUSE_Y_POS] = 0;
renderer->clear();
currentDisk = 1;
checkForPendingDataLoadSwitch = 0;
g_cine->_seqList.clear();
reloadBgPalOnNextFlip = 0;
forbidBgPalReload = 0;
gfxFadeOutCompleted = 0;
gfxFadeInRequested = 0;
}
int CineEngine::scummVMSaveLoadDialog(bool isSave) {
GUI::SaveLoadChooser *dialog;
Common::String desc;
int slot;
if (isSave) {
dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
slot = dialog->runModalWithCurrentTarget();
desc = dialog->getResultString();
if (desc.empty()) {
// create our own description for the saved game, the user didnt enter it
desc = dialog->createDefaultSaveDescription(slot);
}
}
else {
dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
slot = dialog->runModalWithCurrentTarget();
}
delete dialog;
if (slot < 0)
return true;
Common::String saveFileName(getSaveStateName(slot));
if (isSave) {
Common::String tmp = Common::String::format("%s.dir", _targetName.c_str());
Common::OutSaveFile *fHandle = _saveFileMan->openForSaving(tmp);
if (!fHandle) {
warning("Unable to open file %s for saving", tmp.c_str());
return false;
}
Common::strlcpy(currentSaveName[slot], desc.c_str(), sizeof(CommandeType));
fHandle->write(currentSaveName, sizeof(currentSaveName));
delete fHandle;
makeSave(saveFileName, getTotalPlayTime() / 1000, desc, false);
return true;
} else {
return makeLoad(saveFileName);
}
}
void CineEngine::makeSystemMenu() {
int16 numEntry, systemCommand;
int16 mouseX, mouseY, mouseButton;
int16 selectedSave;
if (disableSystemMenu != 1) {
inMenu = true;
manageEvents(MAKE_SYSTEM_MENU, UNTIL_MOUSE_BUTTON_UP);
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
numEntry = 6;
if (!allowPlayerInput) {
numEntry--;
}
renderer->saveBackBuffer(BEFORE_OPENING_MENU);
systemCommand = makeMenuChoice(systemMenu, numEntry, mouseX, mouseY, 140);
switch (systemCommand) {
case 0: { // Pause
renderer->drawString(otherMessages[2], 0);
PauseToken pauseToken = g_cine->pauseEngine();
waitPlayerInput();
break;
}
case 1: { // Restart Game
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
_restartRequested = true;
}
break;
}
case 2: { // Quit
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
quitGame();
}
break;
}
case 3: { // Select save drive... change ?
break;
}
case 4: { // load game
if (loadSaveDirectory()) {
if (!ConfMan.getBool("originalsaveload")) {
scummVMSaveLoadDialog(false);
inMenu = false;
return;
}
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
// Choose savegame range first (0-19, 20-39, 40-59, 60-79, 80-99) and then save slot
int start = makeMenuChoice(saveChoices, ARRAYSIZE(saveChoices), mouseX, mouseY + 8, 9 * 5);
if (start >= 0) {
start *= kMaxOrigUiSavegames;
selectedSave = makeMenuChoice(&currentSaveName[start], kMaxOrigUiSavegames, mouseX, mouseY + 8, 180);
if (selectedSave >= 0) {
selectedSave += start;
}
} else {
selectedSave = -1;
}
if (selectedSave >= 0) {
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
char loadString[256];
sprintf(loadString, otherMessages[3], currentSaveName[selectedSave]);
renderer->drawString(loadString, 0);
loadGameState(selectedSave);
} else {
renderer->drawString(otherMessages[4], 0);
waitPlayerInput();
checkDataDisk(-1);
}
} else {
renderer->drawString(otherMessages[4], 0);
waitPlayerInput();
checkDataDisk(-1);
}
} else {
renderer->drawString(otherMessages[5], 0);
waitPlayerInput();
checkDataDisk(-1);
}
break;
}
case 5: { // Save game
loadSaveDirectory();
if (!ConfMan.getBool("originalsaveload")) {
scummVMSaveLoadDialog(true);
inMenu = false;
return;
}
// Choose savegame range first (0-19, 20-39, 40-59, 60-79, 80-99) and then save slot
int start = makeMenuChoice(saveChoices, ARRAYSIZE(saveChoices), mouseX, mouseY + 8, 9 * 5);
if (start >= 0) {
start *= kMaxOrigUiSavegames;
int minY = ((start == 0) ? g_cine->getAutosaveSlot() + 1 : 0); // Don't allow saving over autosave slot
selectedSave = makeMenuChoice(&currentSaveName[start], kMaxOrigUiSavegames, mouseX, mouseY + 8, 180, minY);
if (selectedSave >= 0) {
selectedSave += start;
}
} else {
selectedSave = -1;
}
if (selectedSave >= 0) {
CommandeType saveName;
saveName[0] = 0;
if (!makeTextEntryMenu(otherMessages[6], saveName, sizeof(CommandeType), 120))
break;
Common::strlcpy(currentSaveName[selectedSave], saveName, sizeof(CommandeType));
Common::String saveFileName = getSaveStateName(selectedSave);
getMouseData(mouseUpdateStatus, (uint16 *)&mouseButton, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (!makeMenuChoice(confirmMenu, 2, mouseX, mouseY + 8, 100)) {
char saveString[256];
Common::String tmp = Common::String::format("%s.dir", _targetName.c_str());
Common::OutSaveFile *fHandle = _saveFileMan->openForSaving(tmp);
if (!fHandle) {
warning("Unable to open file %s for saving", tmp.c_str());
break;
}
fHandle->write(currentSaveName, sizeof(currentSaveName));
delete fHandle;
sprintf(saveString, otherMessages[3], currentSaveName[selectedSave]);
renderer->drawString(saveString, 0);
makeSave(saveFileName, getTotalPlayTime() / 1000, Common::String((const char *)currentSaveName), false);
checkDataDisk(-1);
} else {
renderer->drawString(otherMessages[4], 0);
waitPlayerInput();
checkDataDisk(-1);
}
}
break;
}
default:
break;
}
inMenu = false;
}
}
void drawMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 offset, int16 color, byte *page) {
gfxDrawLine(x + offset, y + offset, x + width - offset, y + offset, color, page); // top
gfxDrawLine(x + offset, currentY + 4 - offset, x + width - offset, currentY + 4 - offset, color, page); // bottom
gfxDrawLine(x + offset, y + offset, x + offset, currentY + 4 - offset, color, page); // left
gfxDrawLine(x + width - offset, y + offset, x + width - offset, currentY + 4 - offset, color, page); // right
}
void drawDoubleMessageBox(int16 x, int16 y, int16 width, int16 currentY, int16 color, byte *page) {
drawMessageBox(x, y, width, currentY, 1, 0, page);
drawMessageBox(x, y, width, currentY, 0, color, page);
}
void processInventory(int16 x, int16 y) {
int menuWidth;
int listSize;
int commandParam;
if (g_cine->getGameType() == Cine::GType_FW) {
menuWidth = 140;
commandParam = -2;
} else { // Operation Stealth
menuWidth = 160;
commandParam = -3;
}
listSize = buildObjectListCommand(commandParam);
if (!listSize)
return;
Common::StringArray list;
for (int i = 0; i < listSize; ++i)
list.push_back(objectListCommand[i]);
SelectionMenu *menu = new SelectionMenu(Common::Point(x, y), menuWidth, list);
inMenu = true;
renderer->pushMenu(menu);
renderer->drawFrame();
renderer->popMenu();
delete menu;
menu = 0;
manageEvents(PROCESS_INVENTORY, UNTIL_MOUSE_BUTTON_DOWN_UP);
inMenu = false;
}
int16 buildObjectListCommand(int16 param) {
int16 i = 0, j = 0;
for (i = 0; i < 20; i++) {
objectListCommand[i][0] = 0;
}
for (i = 0; i < 255; i++) {
if (g_cine->_objectTable[i].name[0] && g_cine->_objectTable[i].costume == param) {
strcpy(objectListCommand[j], g_cine->_objectTable[i].name);
objListTab[j] = i;
j++;
}
}
return j;
}
int16 selectSubObject(int16 x, int16 y, int16 param) {
int16 listSize = buildObjectListCommand(param);
int16 selectedObject = -1;
bool osExtras = g_cine->getGameType() == Cine::GType_OS;
if (!listSize) {
return -2;
}
if (disableSystemMenu == 0) {
inMenu = true;
selectedObject = makeMenuChoice(objectListCommand, listSize, x, y, 140, 0, osExtras, osExtras);
inMenu = false;
}
if (selectedObject == -1)
return -1;
if (osExtras) {
if (selectedObject >= 8000) {
return objListTab[selectedObject - 8000] + 8000;
}
}
if (selectedObject >= 20)
error("Invalid value for selectedObject: %d", selectedObject);
return objListTab[selectedObject];
}
void makeCommandLine() {
if (g_cine->getGameType() == Cine::GType_FW)
makeFWCommandLine();
else
makeOSCommandLine();
}
void makeOSCommandLine() {
uint16 x, y;
commandVar1 = 0;
commandVar2 = -10;
if (playerCommand != -1) {
g_cine->_commandBuffer = defaultActionCommand[playerCommand];
// This is not present in disassembly but here to implement
// prepositions for other than USE command.
if (choiceResultTable[playerCommand] != 2 && commandPrepositionTable[playerCommand][0]) {
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
}
} else {
g_cine->_commandBuffer = "";
}
if ((playerCommand != -1) && (choiceResultTable[playerCommand] == 2)) { // need object selection?
int16 si;
getMouseData(mouseUpdateStatus, &dummyU16, &x, &y);
si = selectSubObject(x, y + 8, -subObjectUseTable[playerCommand]);
if (si < 0) {
canUseOnObject = 0;
} else {
if (si >= 8000) {
si -= 8000;
canUseOnObject = canUseOnItemTable[playerCommand];
} else {
canUseOnObject = 0;
}
commandVar3[0] = si;
commandVar1 = 1;
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += g_cine->_objectTable[commandVar3[0]].name;
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
}
}
if (playerCommand == 2) {
getMouseData(mouseUpdateStatus, &dummyU16, &x, &y);
CursorMan.showMouse(false);
processInventory(x, y + 8);
playerCommand = -1;
commandVar1 = 0;
g_cine->_commandBuffer = "";
CursorMan.showMouse(true);
}
if (playerCommand != 2) {
if (playerCommand != -1 && canUseOnObject != 0) { // call use on sub object
int16 si;
getMouseData(mouseUpdateStatus, &dummyU16, &x, &y);
si = selectSubObject(x, y + 8, -subObjectUseTable[playerCommand]);
if (si >= 0) {
if (si >= 8000) {
si -= 8000;
}
commandVar3[commandVar1] = si;
commandVar1++;
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += g_cine->_objectTable[si].name;
}
}
isDrawCommandEnabled = 1;
if (playerCommand != -1 && choiceResultTable[playerCommand] == commandVar1) {
SelectedObjStruct obj;
obj.idx = commandVar3[0];
obj.param = commandVar3[1];
int16 di = getRelEntryForObject(playerCommand, commandVar1, &obj);
if (di != -1) {
runObjectScript(di);
} else {
addPlayerCommandMessage(playerCommand);
}
playerCommand = -1;
commandVar1 = 0;
g_cine->_commandBuffer = "";
}
}
isDrawCommandEnabled = 1;
renderer->setCommand(g_cine->_commandBuffer);
}
void makeFWCommandLine() {
uint16 x, y;
commandVar1 = 0;
commandVar2 = -10;
if (playerCommand != -1) {
g_cine->_commandBuffer = defaultActionCommand[playerCommand];
// This is not present in disassembly but here to implement
// prepositions for other than USE command.
if (choiceResultTable[playerCommand] != 2 && commandPrepositionTable[playerCommand][0]) {
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
}
} else {
g_cine->_commandBuffer = "";
}
if ((playerCommand != -1) && (choiceResultTable[playerCommand] == 2)) { // need object selection?
int16 si;
getMouseData(mouseUpdateStatus, &dummyU16, &x, &y);
si = selectSubObject(x, y + 8, -2);
if (si < 0) {
playerCommand = -1;
g_cine->_commandBuffer = "";
} else {
commandVar3[0] = si;
commandVar1 = 1;
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += g_cine->_objectTable[commandVar3[0]].name;
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += defaultCommandPreposition;
}
}
if (!(playerCommand != -1 && choiceResultTable[playerCommand] == 2)) {
if (playerCommand == 2) {
getMouseData(mouseUpdateStatus, &dummyU16, &x, &y);
CursorMan.showMouse(false);
processInventory(x, y + 8);
playerCommand = -1;
commandVar1 = 0;
g_cine->_commandBuffer = "";
CursorMan.showMouse(true);
}
}
if (!disableSystemMenu) {
isDrawCommandEnabled = 1;
renderer->setCommand(g_cine->_commandBuffer);
}
}
uint16 needMouseSave = 0;
uint16 menuVar4 = 0;
uint16 menuVar5 = 0;
int16 makeMenuChoice(const CommandeType commandList[], uint16 height, uint16 X, uint16 Y, uint16 width, int minY, bool recheckValue, bool allowEmpty) {
int16 paramY;
uint16 button;
int16 var_A;
uint16 j;
int16 mouseX, mouseY;
int16 currentSelection, oldSelection;
int16 var_4;
SelectionMenu *menu;
Common::StringArray list;
for (uint16 i = minY; i < height; ++i)
list.push_back(commandList[i]);
height -= minY; // Remove values before minY
paramY = (height * 9) + 10;
if (X + width > 319) {
X = 319 - width;
}
if (Y + paramY > 199) {
Y = 199 - paramY;
}
menu = new SelectionMenu(Common::Point(X, Y), width, list);
renderer->pushMenu(menu);
renderer->drawFrame();
manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_UP);
getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
var_A = 0;
currentSelection = allowEmpty ? -1 : 0;
menu->setSelection(currentSelection);
renderer->drawFrame();
manageEvents(MAKE_MENU_CHOICE, EMPTY_EVENT_QUEUE);
getMouseData(mouseUpdateStatus, &button, (uint16 *)&mouseX, (uint16 *)&mouseY);
menuVar = 0;
do {
Common::Rect all(X + 1, Y + 1, X + width, Y + paramY + 1);
Common::Array<Common::Rect> rects;
if (currentSelection == -1) {
rects.push_back(all);
} else if (allowEmpty) {
rects.push_back(Common::Rect(0, 0, 320, all.top)); // All above menu (left, top, right)
rects.push_back(Common::Rect(0, all.bottom, 320, 200)); // All below menu (left, bottom, right)
rects.push_back(Common::Rect(0, all.top, all.left, all.bottom)); // To the left of menu
rects.push_back(Common::Rect(all.right, all.top, 320, all.bottom)); // To the right of menu
}
if (currentSelection > 0) {
Common::Rect aboveCurr(X + 1, Y + 1, X + width, currentSelection * 9 + Y + 4);
rects.push_back(aboveCurr);
}
if (currentSelection != -1 && currentSelection < height - 1) {
Common::Rect belowCurr(X + 1, currentSelection * 9 + 13 + Y, X + width, Y + paramY + 1);
rects.push_back(belowCurr);
}
manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_UP_OR_DOWN_OR_IN_RECTS, false, rects);
getMouseData(mouseUpdateStatus, &button, (uint16 *)&mouseX, (uint16 *)&mouseY);
if (button) {
var_A = 1;
}
oldSelection = currentSelection;
if (needMouseSave) {
for (j = 0; j < 3; j++) {
mainLoopSub6();
}
if (menuVar4 && currentSelection > 0) { // go up
currentSelection--;
}
if (menuVar5) { // go down
if (height - 1 > currentSelection) {
currentSelection++;
}
}
} else {
int selectionValueDiff = 0;
while (!g_cine->_keyInputList.empty()) {
switch (g_cine->_keyInputList.back().keycode) {
case Common::KEYCODE_UP:
selectionValueDiff--;
break;
case Common::KEYCODE_DOWN:
selectionValueDiff++;
break;
default:
break;
}
g_cine->_keyInputList.pop_back();
}
if (selectionValueDiff != 0) {
if (currentSelection == -1) {
if (selectionValueDiff > 0) {
// Start going down from the top
currentSelection = 0;
selectionValueDiff--;
} else if (selectionValueDiff < 0) {
// Start going up from the bottom
currentSelection = height - 1;
selectionValueDiff++;
}
}
currentSelection = CLIP<int16>(currentSelection + selectionValueDiff, 0, height - 1);
if (currentSelection != oldSelection) {
Common::Point currentSelectionCenter(X + width / 2, (currentSelection * 9) + Y + 8);
g_system->warpMouse(currentSelectionCenter.x, currentSelectionCenter.y);
}
} else if (mouseX > X && mouseX < X + width && mouseY > Y && mouseY < Y + paramY + 1) {
// Y value range for selection s:
// (mouseY - (Y + 4)) / 9 >= s
// mouseY - (Y + 4) >= s * 9 <=>
// mouseY >= s * 9 + Y + 4
//
// (mouseY - (Y + 4)) / 9 < (s + 1) <=>
// mouseY - (Y + 4) < (s + 1) * 9 <=>
// mouseY < s * 9 + 13 + Y
currentSelection = (mouseY - (Y + 4)) / 9;
if (currentSelection < 0)
currentSelection = 0;
if (currentSelection >= height)
currentSelection = height - 1;
} else if (allowEmpty) {
currentSelection = -1;
}
}
if (currentSelection != oldSelection) { // old != new
if (needMouseSave) {
hideMouse();
}
menu->setSelection(currentSelection);
renderer->drawFrame();
// if (needMouseSave) {
// gfxRedrawMouseCursor();
// }
}
} while (!var_A && !g_cine->shouldQuit());
assert(!needMouseSave);
var_4 = button;
menuVar = 0;
manageEvents(MAKE_MENU_CHOICE, UNTIL_MOUSE_BUTTON_UP);
getMouseData(mouseUpdateStatus, &button, &dummyU16, &dummyU16);
if (currentSelection != -1) {
currentSelection += minY;
}
if (var_4 == 2) { // recheck
if (!recheckValue)
return -1;
else if (currentSelection != -1)
return currentSelection + 8000;
}
return currentSelection;
}
void makeActionMenu() {
uint16 mouseButton;
uint16 mouseX;
uint16 mouseY;
inMenu = true;
getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY);
if (g_cine->getGameType() == Cine::GType_OS) {
if (disableSystemMenu == 0) {
playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70, 0, true, true);
}
if (playerCommand >= 8000) {
playerCommand -= 8000;
canUseOnObject = canUseOnItemTable[playerCommand];
}
} else {
if (disableSystemMenu == 0) {
playerCommand = makeMenuChoice(defaultActionCommand, 6, mouseX, mouseY, 70);
}
}
inMenu = false;
}
void mouseLeftRightDown() {
// Left and right mouse buttons are down
g_cine->makeSystemMenu();
}
void allowPlayerInputMouseRightDown() {
// Player input is allowed, left mouse button is up, right mouse button is down
makeActionMenu();
makeCommandLine();
}
void playerCommandMouseLeft(uint16 &mouseButton, uint16 &mouseX, uint16 &mouseY) {
// A player command is given, left mouse button is down, right mouse button is up
int16 si;
manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_UP);
getMouseData(mouseUpdateStatus, &mouseButton, &dummyU16, &dummyU16);
si = getObjectUnderCursor(mouseX, mouseY);
if (si != -1) {
commandVar3[commandVar1] = si;
commandVar1++;
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += g_cine->_objectTable[si].name;
isDrawCommandEnabled = 1;
if (choiceResultTable[playerCommand] == commandVar1) {
int16 relEntry;
SelectedObjStruct obj;
obj.idx = commandVar3[0];
obj.param = commandVar3[1];
relEntry = getRelEntryForObject(playerCommand, commandVar1, &obj);
if (relEntry != -1) {
runObjectScript(relEntry);
} else {
addPlayerCommandMessage(playerCommand);
}
playerCommand = -1;
commandVar1 = 0;
g_cine->_commandBuffer = "";
} else if (g_cine->getGameType() == Cine::GType_OS) {
isDrawCommandEnabled = 1;
g_cine->_commandBuffer += " ";
g_cine->_commandBuffer += commandPrepositionTable[playerCommand];
}
renderer->setCommand(g_cine->_commandBuffer);
} else {
g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
if (!mouseX) {
g_cine->_globalVars[VAR_MOUSE_X_POS]++;
}
g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
if (g_cine->getGameType() == Cine::GType_OS) {
if (!mouseY) {
g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
}
g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
}
}
}
void playerCommandMouseLeftRightUp(uint16 mouseX, uint16 mouseY) {
// A player command is given, left and right mouse buttons are up
int16 objIdx;
objIdx = getObjectUnderCursor(mouseX, mouseY);
// Previously in Operation Stealth the following code was always run but in
// Future Wars only if commandVar2 != objIdx (Both cases based on disassembly).
// Trying to update the command line e.g. "EXAMINE" -> "EXAMINE scaffolding"
// in the manageEvents function to make the user interface responsive made a
// regression in Future Wars.
//
// In Future Wars the command line was not always updated and thus failed sometimes
// to be up to date (i.e. showing wrong text, e.g. "EXAMINE" only when it should
// have read "EXAMINE scaffolding" because the mouse cursor was on the scaffolding).
//
// Now we just always run this code for both Future Wars and Operation Stealth
// which seems to fix the command line updating.
const bool update = true; // Previously: g_cine->getGameType() == Cine::GType_OS || commandVar2 != objIdx
if (update) {
if (objIdx != -1) {
renderer->setCommand(g_cine->_commandBuffer + " " + g_cine->_objectTable[objIdx].name);
} else {
isDrawCommandEnabled = 1;
}
}
commandVar2 = objIdx;
}
void noPlayerCommandMouseLeft(uint16 &mouseX, uint16 &mouseY) {
// No player command is given, left mouse button is down, right mouse button is up
int16 objIdx;
int16 relEntry;
g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
if (!mouseX) {
g_cine->_globalVars[VAR_MOUSE_X_POS]++;
}
g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
if (g_cine->getGameType() == Cine::GType_OS) {
if (!mouseY) {
g_cine->_globalVars[VAR_MOUSE_Y_POS]++;
}
g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_X_POS];
g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = g_cine->_globalVars[VAR_MOUSE_Y_POS];
}
objIdx = getObjectUnderCursor(mouseX, mouseY);
if (objIdx != -1) {
currentSelectedObject.idx = objIdx;
currentSelectedObject.param = -1;
relEntry = getRelEntryForObject(6, 1, &currentSelectedObject);
if (relEntry != -1) {
bool skipSafeControlAccess = false;
// HACK: Throttle speed of otherwise overly sensitive safe controls (Bug #11621)
if (hacksEnabled && g_cine->getGameType() == Cine::GType_OS &&
scumm_stricmp(renderer->getBgName(), "COFFRE.PI1") == 0 &&
scumm_stricmp(currentPrcName, "PALAIS1.PRC") == 0) {
uint32 now = g_system->getMillis();
// Throttle access to the same safe control repeatedly in succession.
if (safeControlsLastAccessedMs != 0 &&
(now - safeControlsLastAccessedMs) < safeControlAccessMinMs() &&
objIdx == lastSafeControlObjIdx) {
skipSafeControlAccess = true;
warning("Skipping safe control access (Time since last called = %d ms < throttling value of %d ms)",
now - safeControlsLastAccessedMs, safeControlAccessMinMs());
} else {
safeControlsLastAccessedMs = now;
}
lastSafeControlObjIdx = objIdx;
}
if (!skipSafeControlAccess) {
runObjectScript(relEntry);
}
}
}
}
uint16 executePlayerInput() {
uint16 var_5E;
uint16 var_2;
uint16 mouseX, mouseY, mouseButton;
canUseOnObject = 0;
if (isInPause) {
renderer->drawString(otherMessages[2], 0);
PauseToken pauseToken = g_cine->pauseEngine();
waitPlayerInput();
isInPause = 0;
}
if (allowPlayerInput) { // Player input is allowed
if (isDrawCommandEnabled) {
renderer->setCommand(g_cine->_commandBuffer);
}
isDrawCommandEnabled = 0;
// DIFFERENCE FROM DISASSEMBLY:
// See renderer's drawFrame function for comments related to how the waiting
// period from the original engine has been moved to here. In the original
// engine a maximum of 200 pollings of the mouse state were done here and
// the maximum state of the mouse buttons were read. Now we wait for
// g_cine->getTimerDelay() here while reading the mouse state and updating
// the command line.
manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_WAIT_ENDED, true);
} else {
manageEvents(EXECUTE_PLAYER_INPUT, UNTIL_MOUSE_BUTTON_UP_AND_WAIT_ENDED, true);
}
// Get mouse position and button states
getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY);
if ((mouseButton & kLeftMouseButton) && (mouseButton & kRightMouseButton)) {
// Left and right mouse buttons are down
mouseLeftRightDown();
} else if (allowPlayerInput) { // Player input is allowed
if (!(mouseButton & kLeftMouseButton) && (mouseButton & kRightMouseButton)) {
// Player input is allowed, left mouse button is up, right mouse button is down
allowPlayerInputMouseRightDown();
} else if (playerCommand != -1) { // A player command is given
if (mouseButton & kLeftMouseButton) { // Left mouse button is down
if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
// A player command is given, left mouse button is down, right mouse button is up
playerCommandMouseLeft(mouseButton, mouseX, mouseY);
}
} else if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
// A player command is given, left and right mouse buttons are up
playerCommandMouseLeftRightUp(mouseX, mouseY);
}
} else { // No player command is given
if (!(mouseButton & kRightMouseButton)) { // Right mouse button is up
if (mouseButton & kLeftMouseButton) { // Left mouse button is down
// No player command is given, left mouse button is down, right mouse button is up
noPlayerCommandMouseLeft(mouseX, mouseY);
}
}
}
}
var_2 = menuVar & 0x7F;
var_5E = var_2;
if (menuVar & 0x80) {
var_5E = 0;
var_2 = 0;
}
if (g_cine->getGameType() == Cine::GType_OS) { // OS: Move using keyboard
// Handle possible horizontal movement by keyboard
if (xMoveKeyb != kKeybMoveCenterX && allowPlayerInput) {
if (xMoveKeyb == kKeybMoveRight) { // moving right
const int16 playerFrame = g_cine->_objectTable[1].frame;
const int16 playerX = g_cine->_objectTable[1].x;
// TODO: Check if multiplying _width by two here is correct or not
const int16 newX = g_cine->_animDataTable[playerFrame]._width * 2 + playerX + 8;
g_cine->_globalVars[VAR_MOUSE_X_POS] = g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = newX;
} else { // moving left
const int16 playerX = g_cine->_objectTable[1].x;
const int16 newX = playerX - 8;
g_cine->_globalVars[VAR_MOUSE_X_POS] = g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = newX;
}
// Restrain horizontal position to range 0-319
if (g_cine->_globalVars[VAR_MOUSE_X_POS] < 0) {
g_cine->_globalVars[VAR_MOUSE_X_POS] = g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = 0;
} else if (g_cine->_globalVars[VAR_MOUSE_X_POS] > 319) {
g_cine->_globalVars[VAR_MOUSE_X_POS] = g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = 319;
}
}
// Handle possible vertical movement by keyboard
if (yMoveKeyb != kKeybMoveCenterY && allowPlayerInput) {
if (yMoveKeyb == kKeybMoveDown) { // moving down
const int16 playerFrame = g_cine->_objectTable[1].frame;
const int16 playerY = g_cine->_objectTable[1].y;
// TODO: Check if multiplying _height by two here is correct or not
const int16 newY = g_cine->_animDataTable[playerFrame]._height * 2 + playerY - 1;
g_cine->_globalVars[VAR_MOUSE_Y_POS] = g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = newY;
} else { // moving up
const int16 playerY = g_cine->_objectTable[1].y;
const int16 newY = playerY - 8;
g_cine->_globalVars[VAR_MOUSE_Y_POS] = g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = newY;
}
// Restrain vertical position to range 0-199
if (g_cine->_globalVars[VAR_MOUSE_Y_POS] < 0) {
g_cine->_globalVars[VAR_MOUSE_Y_POS] = g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = 0;
} else if (g_cine->_globalVars[VAR_MOUSE_Y_POS] > 199) {
g_cine->_globalVars[VAR_MOUSE_Y_POS] = g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = 199;
}
}
} else if (egoMovedWithKeyboard && allowPlayerInput) { // FW: Move using keyboard
egoMovedWithKeyboard = false;
switch (g_cine->_globalVars[VAR_MOUSE_X_MODE]) {
case 1:
mouseX = g_cine->_objectTable[1].x + 12;
break;
case 2:
mouseX = g_cine->_objectTable[1].x + 7;
break;
default:
mouseX = g_cine->_globalVars[VAR_MOUSE_X_POS];
break;
}
switch (g_cine->_globalVars[VAR_MOUSE_Y_MODE]) {
case 1:
mouseY = g_cine->_objectTable[1].y + 34;
break;
case 2:
mouseY = g_cine->_objectTable[1].y + 28;
break;
default:
mouseY = g_cine->_globalVars[VAR_MOUSE_Y_POS];
break;
}
if (var_5E == bgVar0) {
var_5E = 0;
g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
} else {
if (xMoveKeyb) {
if (xMoveKeyb == kKeybMoveLeft) {
g_cine->_globalVars[VAR_MOUSE_X_POS] = 1;
} else {
g_cine->_globalVars[VAR_MOUSE_X_POS] = 320;
}
} else {
g_cine->_globalVars[VAR_MOUSE_X_POS] = mouseX;
}
if (yMoveKeyb) {
if (yMoveKeyb == kKeybMoveUp) {
g_cine->_globalVars[VAR_MOUSE_Y_POS] = 1;
} else {
g_cine->_globalVars[VAR_MOUSE_Y_POS] = 200;
}
} else {
g_cine->_globalVars[VAR_MOUSE_Y_POS] = mouseY;
}
}
bgVar0 = var_5E;
}
if (g_cine->getGameType() == Cine::GType_OS || !(egoMovedWithKeyboard && allowPlayerInput)) {
getMouseData(mouseUpdateStatus, &mouseButton, &mouseX, &mouseY);
// TODO: Investigate why some of these buttons don't work (Maybe main_loop.cpp's processEvent has something to do with it?)
switch (var_2 - 59) {
case 0: // F1 = EXAMINE
case 1: // F2 = TAKE
case 2: // F3 = INVENTORY
case 3: // F4 = USE
case 4: // F5 = OPERATE
case 5: // F6 = SPEAK
if (allowPlayerInput) {
playerCommand = var_2 - 59;
// TODO: Operation Stealth uses shift key here for handling canUseOnObject differently... investigate and implement.
makeCommandLine();
}
break;
case 6: // F7
case 7: // F8
case 8: // F9
case 23: // Keypad-0/Ins
// TODO: Restrict this case only to F7 for Operation Stealth
if (allowPlayerInput) {
makeActionMenu();
makeCommandLine();
}
break;
case 9: // F10
case 24: // Keypad-./Del
// TODO: Restrict this case only to F10 for Operation Stealth
g_cine->makeSystemMenu();
break;
default:
break;
}
}
renderer->clearMenuStack();
return var_5E;
}
void drawSprite(Common::List<overlay>::iterator it, const byte *spritePtr, const byte *maskPtr, uint16 width, uint16 height, byte *page, int16 x, int16 y) {
byte *msk = NULL;
int16 maskX, maskY, maskWidth, maskHeight;
uint16 maskSpriteIdx;
msk = (byte *)malloc(width * height);
if (g_cine->getGameType() == Cine::GType_OS) {
generateMask(spritePtr, msk, width * height, g_cine->_objectTable[it->objIdx].part);
} else {
memcpy(msk, maskPtr, width * height);
}
for (++it; it != g_cine->_overlayList.end(); ++it) {
if (it->type != 5) {
continue;
}
maskX = g_cine->_objectTable[it->objIdx].x;
maskY = g_cine->_objectTable[it->objIdx].y;
maskSpriteIdx = ABS((int16)(g_cine->_objectTable[it->objIdx].frame));
maskWidth = g_cine->_animDataTable[maskSpriteIdx]._realWidth;
maskHeight = g_cine->_animDataTable[maskSpriteIdx]._height;
gfxUpdateSpriteMask(msk, x, y, width, height, g_cine->_animDataTable[maskSpriteIdx].data(), maskX, maskY, maskWidth, maskHeight);
#ifdef DEBUG_SPRITE_MASK
gfxFillSprite(g_cine->_animDataTable[maskSpriteIdx].data(), maskWidth, maskHeight, page, maskX, maskY, 1);
#endif
}
gfxDrawMaskedSprite(spritePtr, msk, width, height, page, x, y);
free(msk);
}
void removeMessages() {
Common::List<overlay>::iterator it;
bool remove;
for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end();) {
if (g_cine->getGameType() == Cine::GType_OS) {
// NOTE: These are really removeOverlay calls that have been deferred.
// In Operation Stealth's disassembly elements are removed from the
// overlay list right in the drawOverlays function (And actually in
// some other places too) and that's where incrementing a the overlay's
// last parameter by one if it's negative and testing it for positivity
// comes from too.
remove = it->type == 3 || (it->type == 2 && (it->color >= 0 || ++(it->color) >= 0));
} else { // Future Wars
remove = it->type == 2 || it->type == 3;
}
if (remove) {
it = g_cine->_overlayList.erase(it);
} else {
++it;
}
}
}
uint16 processKeyboard(uint16 param) {
return 0;
}
void mainLoopSub6() {
// (10923000 ms * 3) / 1193180 ~= 27 ms.
//g_system->delayMillis(27);
}
void checkForPendingDataLoad() {
if (newPrcName[0] != 0) {
bool loadPrcOk = loadPrc(newPrcName);
strcpy(currentPrcName, newPrcName);
strcpy(newPrcName, "");
// Check that the loading of the script file was successful before
// trying to add script 1 from it to the global scripts list. This
// fixes a crash when failing copy protection in Amiga or Atari ST
// versions of Future Wars.
if (loadPrcOk) {
addScriptToGlobalScripts(1);
} else if (scumm_stricmp(currentPrcName, COPY_PROT_FAIL_PRC_NAME)) {
// We only show an error here for other files than the file that
// is loaded if copy protection fails (i.e. L201.ANI).
warning("checkForPendingDataLoad: loadPrc(%s) failed", currentPrcName);
}
}
if (newRelName[0] != 0) {
loadRel(newRelName);
strcpy(currentRelName, newRelName);
strcpy(newRelName, "");
}
if (newObjectName[0] != 0) {
g_cine->_overlayList.clear();
loadObject(newObjectName);
strcpy(currentObjectName, newObjectName);
strcpy(newObjectName, "");
}
if (newMsgName[0] != 0) {
loadMsg(newMsgName);
strcpy(currentMsgName, newMsgName);
strcpy(newMsgName, "");
}
}
void hideMouse() {
}
void removeExtention(char *dest, const char *source) {
strcpy(dest, source);
byte *ptr = (byte *) strchr(dest, '.');
if (ptr) {
*ptr = 0;
}
}
void addMessage(byte param1, int16 param2, int16 param3, int16 param4, int16 param5) {
overlay tmp;
tmp.objIdx = param1;
tmp.type = 2;
tmp.x = param2;
tmp.y = param3;
tmp.width = param4;
tmp.color = param5;
g_cine->_overlayList.push_back(tmp);
}
void removeSeq(uint16 param1, uint16 param2, uint16 param3) {
Common::List<SeqListElement>::iterator it;
for (it = g_cine->_seqList.begin(); it != g_cine->_seqList.end(); ++it) {
if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) {
it->var4 = -1;
break;
}
}
}
// Checked against Operation Stealth 16 color DOS disassembly, should be correct.
bool isSeqRunning(uint16 param1, uint16 param2, uint16 param3) {
Common::List<SeqListElement>::iterator it;
for (it = g_cine->_seqList.begin(); it != g_cine->_seqList.end(); ++it) {
if (it->objIdx == param1 && it->var4 == param2 && it->varE == param3) {
// Just to be on the safe side there's a restriction of the
// addition's result to 16-bit arithmetic here like in the
// original. It's possible that it's not strictly needed.
return ((it->var14 + it->var16) & 0xFFFF) == 0;
}
}
return true;
}
void addSeqListElement(uint16 objIdx, int16 param1, int16 param2, int16 frame, int16 param4, int16 param5, int16 param6, int16 param7, int16 param8) {
Common::List<SeqListElement>::iterator it;
SeqListElement tmp;
for (it = g_cine->_seqList.begin(); it != g_cine->_seqList.end() && it->varE < param7; ++it)
;
tmp.objIdx = objIdx;
tmp.var4 = param1;
tmp.var8 = param2;
tmp.frame = frame;
tmp.varC = param4;
tmp.var14 = 0;
tmp.var16 = 0;
tmp.var18 = param5;
tmp.var1A = param6;
tmp.varE = param7;
tmp.var10 = param8;
tmp.var12 = param8;
tmp.var1C = 0;
tmp.var1E = 0;
g_cine->_seqList.insert(it, tmp);
}
void modifySeqListElement(uint16 objIdx, int16 var4Test, int16 param1, int16 param2, int16 param3, int16 param4) {
// Find a suitable list element and modify it
for (Common::List<SeqListElement>::iterator it = g_cine->_seqList.begin(); it != g_cine->_seqList.end(); ++it) {
if (it->objIdx == objIdx && it->var4 == var4Test) {
it->varC = param1;
it->var18 = param2;
it->var1A = param3;
it->var10 = it->var12 = param4;
break;
}
}
}
void computeMove1(SeqListElement &element, int16 x, int16 y, int16 param1,
int16 param2, int16 x2, int16 y2) {
element.var16 = 0;
element.var14 = 0;
if (y2) {
if (y - param2 > y2) {
element.var16 = 2;
}
if (y + param2 < y2) {
element.var16 = 1;
}
}
if (x2) {
if (x - param1 > x2) {
element.var14 = 2;
}
if (x + param1 < x2) {
element.var14 = 1;
}
}
}
uint16 computeMove2(SeqListElement &element) {
int16 returnVar = 0;
if (element.var16 == 1) {
returnVar = 4;
} else if (element.var16 == 2) {
returnVar = 3;
}
if (element.var14 == 1) {
returnVar = 1;
} else if (element.var14 == 2) {
returnVar = 2;
}
return returnVar;
}
uint16 addAni(uint16 param1, uint16 objIdx, const int8 *ptr, SeqListElement &element, uint16 param3, int16 *param4) {
const int8 *ptrData;
const int8 *ptr2;
int16 di;
debug(5, "addAni: param1 = %d, objIdx = %d, ptr = %p, element.var8 = %d, element.var14 = %d param3 = %d",
param1, objIdx, (const void *)ptr, element.var8, element.var14, param3);
// In the original an error string is set and 0 is returned if the following doesn't hold
if (!ptr) {
return 0;
}
// We probably could just use a local variable here instead of the dummyU16 but
// haven't checked if this has any side-effects so keeping it this way still.
dummyU16 = READ_BE_UINT16(ptr + param1 * 2 + 8);
ptrData = ptr + dummyU16;
// In the original an error string is set and 0 is returned if the following doesn't hold
if (!*ptrData) {
return 0;
}
di = (g_cine->_objectTable[objIdx].costume + 1) % (*ptrData);
++ptrData; // Jump over the just read byte
// Here ptr2 seems to be indexing a table of structs (8 bytes per struct):
// struct {
// int8 x; // 0 (Used with checkCollision)
// int8 y; // 1 (Used with checkCollision)
// int8 numZones; // 2 (Used with checkCollision)
// int8 var3; // 3 (Not used in this function)
// int8 xAdd; // 4 (Used with an object)
// int8 yAdd; // 5 (Used with an object)
// int8 maskAdd; // 6 (Used with an object)
// int8 frameAdd; // 7 (Used with an object)
// };
ptr2 = ptrData + di * 8;
// We might probably safely discard the AND by 1 here because
// at least in the original checkCollision returns always 0 or 1.
if ((checkCollision(objIdx, ptr2[0], ptr2[1], ptr2[2], ptr[0]) & 1)) {
return 0;
}
g_cine->_objectTable[objIdx].x += ptr2[4];
g_cine->_objectTable[objIdx].y += ptr2[5];
g_cine->_objectTable[objIdx].mask += ptr2[6];
if (ptr2[6]) {
resetGfxEntityEntry(objIdx);
}
g_cine->_objectTable[objIdx].frame = ptr2[7] + element.var8;
if (param3 || !element.var14) {
g_cine->_objectTable[objIdx].costume = di;
} else {
assert(param4);
*param4 = di;
}
return 1;
}
/**
* Permutates the overlay list into a different order according to some logic.
* @todo Check this function for correctness (Wasn't very easy to reverse engineer so there may be errors)
*/
void resetGfxEntityEntry(uint16 objIdx) {
Common::List<overlay>::iterator it, bObjsCutPoint;
Common::List<overlay> aReverseObjs, bObjs;
bool foundCutPoint = false;
// Go through the overlay list and partition the whole list into two categories (Type A and type B objects)
for (it = g_cine->_overlayList.begin(); it != g_cine->_overlayList.end(); ++it) {
if (it->objIdx == objIdx && it->type != 2 && it->type != 3) { // Type A object
aReverseObjs.push_front(*it);
} else { // Type B object
bObjs.push_back(*it);
uint16 objectMask;
if (it->type == 2 || it->type == 3) {
objectMask = 10000;
} else {
objectMask = g_cine->_objectTable[it->objIdx].mask;
}
if (g_cine->_objectTable[objIdx].mask > objectMask) { // Check for B objects' cut point
bObjsCutPoint = bObjs.reverse_begin();
foundCutPoint = true;
}
}
}
// Recreate the overlay list in a different order.
g_cine->_overlayList.clear();
if (foundCutPoint) {
// If a cut point was found the order is:
// B objects before the cut point, the cut point, A objects in reverse order, B objects after cut point.
++bObjsCutPoint; // Include the cut point in the first list insertion
g_cine->_overlayList.insert(g_cine->_overlayList.end(), bObjs.begin(), bObjsCutPoint);
g_cine->_overlayList.insert(g_cine->_overlayList.end(), aReverseObjs.begin(), aReverseObjs.end());
g_cine->_overlayList.insert(g_cine->_overlayList.end(), bObjsCutPoint, bObjs.end());
} else {
// If no cut point was found the order is:
// A objects in reverse order, B objects.
g_cine->_overlayList.insert(g_cine->_overlayList.end(), aReverseObjs.begin(), aReverseObjs.end());
g_cine->_overlayList.insert(g_cine->_overlayList.end(), bObjs.begin(), bObjs.end());
}
}
void processSeqListElement(SeqListElement &element) {
int16 x = g_cine->_objectTable[element.objIdx].x;
int16 y = g_cine->_objectTable[element.objIdx].y;
const int8 *ptr1 = (const int8 *) g_cine->_animDataTable[element.frame].data();
int16 var_10;
int16 var_4;
int16 var_2;
// Initial interpretations for variables addressed through ptr1 (8-bit addressing):
// These may be inaccurate!
// 0: ?
// 1: xRadius
// 2: yRadius
// 3: ?
// 4: xAdd
// 5: yAdd
// 6: ?
// 7: ?
// After this come (At least at positions 0, 1 and 3 in 16-bit addressing)
// 16-bit big-endian values used for addressing through ptr1.
if (element.var12 < element.var10) {
element.var12++;
return;
}
element.var12 = 0;
if (ptr1) {
int16 param1 = ptr1[1];
int16 param2 = ptr1[2];
if (element.varC != 255) {
int16 x2 = element.var18;
int16 y2 = element.var1A;
if (element.varC) {
x2 += g_cine->_objectTable[element.varC].x;
y2 += g_cine->_objectTable[element.varC].y;
}
computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, x2, y2);
} else {
if (xMoveKeyb && allowPlayerInput) {
int16 adder = param1 + 1;
if (xMoveKeyb != kKeybMoveRight) {
adder = -adder;
}
g_cine->_globalVars[VAR_MOUSE_X_POS] = g_cine->_globalVars[VAR_MOUSE_X_POS_2ND] = ptr1[4] + x + adder;
}
if (yMoveKeyb && allowPlayerInput) {
int16 adder = param2 + 1;
if (yMoveKeyb != kKeybMoveDown) {
adder = -adder;
}
g_cine->_globalVars[VAR_MOUSE_Y_POS] = g_cine->_globalVars[VAR_MOUSE_Y_POS_2ND] = ptr1[5] + y + adder;
}
if (g_cine->_globalVars[VAR_MOUSE_X_POS] || g_cine->_globalVars[VAR_MOUSE_Y_POS]) {
computeMove1(element, ptr1[4] + x, ptr1[5] + y, param1, param2, g_cine->_globalVars[VAR_MOUSE_X_POS], g_cine->_globalVars[VAR_MOUSE_Y_POS]);
} else {
element.var16 = 0;
element.var14 = 0;
}
}
var_10 = computeMove2(element);
if (var_10) {
element.var1C = var_10;
element.var1E = var_10;
}
var_4 = -1;
if ((element.var16 == 1
&& !addAni(3, element.objIdx, ptr1, element, 0, &var_4)) || (element.var16 == 2 && !addAni(2, element.objIdx, ptr1, element, 0, &var_4))) {
if (element.varC == 255) {
g_cine->_globalVars[VAR_MOUSE_Y_POS] = 0;
}
}
if ((element.var14 == 1
&& !addAni(0, element.objIdx, ptr1, element, 1, &var_2))) {
if (element.varC == 255) {
g_cine->_globalVars[VAR_MOUSE_X_POS] = 0;
if (var_4 != -1) {
g_cine->_objectTable[element.objIdx].costume = var_4;
}
}
}
if ((element.var14 == 2 && !addAni(1, element.objIdx, ptr1, element, 1, &var_2))) {
if (element.varC == 255) {
g_cine->_globalVars[VAR_MOUSE_X_POS] = 0;
if (var_4 != -1) {
g_cine->_objectTable[element.objIdx].costume = var_4;
}
}
}
if (((element.var16 + element.var14) & 0xFFFF) == 0) {
if (element.var1C) {
if (element.var1E) {
g_cine->_objectTable[element.objIdx].costume = 0;
element.var1E = 0;
}
addAni(element.var1C + 3, element.objIdx, ptr1, element, 1, &var_2);
}
}
}
}
void processSeqList() {
Common::List<SeqListElement>::iterator it;
for (it = g_cine->_seqList.begin(); it != g_cine->_seqList.end(); ++it) {
if (it->var4 == -1) {
continue;
}
processSeqListElement(*it);
}
}
bool makeTextEntryMenu(const char *messagePtr, char *inputString, int stringMaxLength, int y) {
int len = strlen(messagePtr);
int16 width = 6 * len + 20;
width = CLIP((int)width, 180, 250);
int16 x = (320 - width) / 2;
g_cine->_keyInputList.clear();
int quit = 0;
bool redraw = true;
CommandeType tempString;
int inputLength = strlen(inputString);
int inputPos = inputLength + 1;
TextInputMenu *inputBox = new TextInputMenu(Common::Point(x - 16, y), width + 32, messagePtr);
renderer->pushMenu(inputBox);
while (!quit) {
if (redraw) {
inputBox->setInput(inputString, inputPos);
renderer->drawFrame();
redraw = false;
}
if (g_cine->_keyInputList.empty()) {
manageEvents(MAKE_TEXT_ENTRY_MENU, UNTIL_MOUSE_BUTTON_DOWN_OR_KEY_INPUT);
}
char ch[2];
memset(tempString, 0, stringMaxLength);
ch[1] = 0;
Common::KeyState keyState = Common::KeyState();
if (!g_cine->_keyInputList.empty()) {
keyState = g_cine->_keyInputList.back();
g_cine->_keyInputList.pop_back();
}
int keycode = keyState.keycode;
uint16 ascii = keyState.ascii;
uint16 mouseButton, mouseX, mouseY;
getMouseData(0, &mouseButton, &mouseX, &mouseY);
if ((mouseButton & kRightMouseButton) || g_cine->shouldQuit())
quit = kRightMouseButton;
else if (mouseButton & kLeftMouseButton)
quit = kLeftMouseButton;
switch (keycode) {
case Common::KEYCODE_BACKSPACE:
if (inputPos <= 1) {
break;
}
inputPos--;
redraw = true;
// fall through
case Common::KEYCODE_DELETE:
if (inputPos <= inputLength) {
if (inputPos != 1) {
strncpy(tempString, inputString, inputPos - 1);
}
if (inputPos != inputLength) {
strncat(tempString, &inputString[inputPos], inputLength - inputPos);
}
strcpy(inputString, tempString);
inputLength = strlen(inputString);
redraw = true;
}
break;
case Common::KEYCODE_LEFT:
if (inputPos > 1) {
inputPos--;
redraw = true;
}
break;
case Common::KEYCODE_RIGHT:
if (inputPos <= inputLength) {
inputPos++;
redraw = true;
}
break;
default:
if (((ascii >= 'a') && (ascii <= 'z')) ||
((ascii >= '0') && (ascii <= '9')) ||
((ascii >= 'A') && (ascii <= 'Z')) ||
(ascii == ' ')) {
if (inputLength < stringMaxLength - 1) {
ch[0] = ascii;
if (inputPos != 1) {
strncpy(tempString, inputString, inputPos - 1);
strcat(tempString, ch);
}
if ((inputLength == 0) || (inputPos == 1)) {
strcpy(tempString, ch);
}
if ((inputLength != 0) && (inputPos != inputLength)) {
strncat(tempString, &inputString[inputPos - 1], inputLength - inputPos + 1);
}
strcpy(inputString, tempString);
inputLength = strlen(inputString);
inputPos++;
redraw = true;
}
}
break;
}
}
renderer->popMenu();
delete inputBox;
if (quit == kRightMouseButton)
return false;
return true;
}
} // End of namespace Cine