scummvm/engines/cine/various.cpp
Kari Salminen 70d16a3689 CINE: FW: Fix command line updating
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 update the command line for both Future Wars and
Operation Stealth which seems to fix the command line updating.

I think this probably was a regression caused by adding support for
Operation Stealth (i.e. pull request #2365) and the efforts made in it
to make the user interface responsive.
2020-09-10 00:34:32 +01:00

2012 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(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 += 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