scummvm/engines/queen/command.cpp
Torbjörn Andersson 0b07029274 QUEEN: Silence GCC 7 warnings about potential buffer overflow
Use snprintf() instead of sprintf() to limit how much is written
to the buffer. Note that there are other places where it looks
like it could overflow, but they did not trigger warnings and I'm
guessing that it doesn't overflow in reality.
2017-08-06 20:06:18 +02:00

1323 lines
37 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 "queen/command.h"
#include "queen/display.h"
#include "queen/input.h"
#include "queen/graphics.h"
#include "queen/grid.h"
#include "queen/logic.h"
#include "queen/queen.h"
#include "queen/resource.h"
#include "queen/sound.h"
#include "queen/state.h"
#include "queen/walk.h"
namespace Queen {
CmdText::CmdText(uint8 y, QueenEngine *vm)
: _y(y), _vm(vm) {
clear();
}
void CmdText::clear() {
memset(_command, 0, sizeof(_command));
}
void CmdText::display(InkColor color, const char *command, bool outlined) {
_vm->display()->textCurrentColor(_vm->display()->getInkColor(color));
if (!command) {
command = _command;
}
_vm->display()->setTextCentered(_y, command, outlined);
}
void CmdText::displayTemp(InkColor color, Verb v) {
char temp[MAX_COMMAND_LEN];
strcpy(temp, _vm->logic()->verbName(v));
display(color, temp, false);
}
void CmdText::displayTemp(InkColor color, const char *name, bool outlined) {
char temp[MAX_COMMAND_LEN];
snprintf(temp, MAX_COMMAND_LEN, "%s %s", _command, name);
display(color, temp, outlined);
}
void CmdText::setVerb(Verb v) {
strcpy(_command, _vm->logic()->verbName(v));
}
void CmdText::addLinkWord(Verb v) {
strcat(_command, " ");
strcat(_command, _vm->logic()->verbName(v));
}
void CmdText::addObject(const char *objName) {
strcat(_command, " ");
strcat(_command, objName);
}
class CmdTextHebrew : public CmdText {
public:
CmdTextHebrew(uint8 y, QueenEngine *vm) : CmdText(y, vm) {}
virtual void displayTemp(InkColor color, const char *name, bool outlined) {
char temp[MAX_COMMAND_LEN];
snprintf(temp, MAX_COMMAND_LEN, "%s %s", name, _command);
display(color, temp, outlined);
}
virtual void addLinkWord(Verb v) {
char temp[MAX_COMMAND_LEN];
strcpy(temp, _command);
strcpy(_command, _vm->logic()->verbName(v));
strcat(_command, " ");
strcat(_command, temp);
}
virtual void addObject(const char *objName) {
char temp[MAX_COMMAND_LEN];
strcpy(temp, _command);
strcpy(_command, objName);
strcat(_command, " ");
strcat(_command, temp);
}
};
class CmdTextGreek : public CmdText {
public:
CmdTextGreek(uint8 y, QueenEngine *vm) : CmdText(y, vm) {}
virtual void displayTemp(InkColor color, const char *name, bool outlined) {
char temp[MAX_COMMAND_LEN];
// don't show a space after the goto and give commands in the Greek version
if (_command[1] != (char)-34 && !(_command[1] == (char)-2 && strlen(_command) > 5))
snprintf(temp, MAX_COMMAND_LEN, "%s %s", _command, name);
else
snprintf(temp, MAX_COMMAND_LEN, "%s%s", _command, name);
display(color, temp, outlined);
}
virtual void addObject(const char *objName) {
// don't show a space after the goto and give commands in the Greek version
if (_command[1] != (char)-34 && !(_command[1] == (char)-2 && strlen(_command) > 5))
strcat(_command, " ");
strcat(_command, objName);
}
};
CmdText *CmdText::makeCmdTextInstance(uint8 y, QueenEngine *vm) {
switch (vm->resource()->getLanguage()) {
case Common::HE_ISR:
return new CmdTextHebrew(y, vm);
case Common::GR_GRE:
return new CmdTextGreek(y, vm);
default:
return new CmdText(y, vm);
}
}
void CmdState::init() {
commandLevel = 1;
oldVerb = verb = action = VERB_NONE;
oldNoun = noun = subject[0] = subject[1] = 0;
selAction = VERB_NONE;
selNoun = 0;
}
Command::Command(QueenEngine *vm)
: _cmdList(NULL), _cmdArea(NULL), _cmdObject(NULL), _cmdInventory(NULL), _cmdGameState(NULL), _vm(vm) {
_cmdText = CmdText::makeCmdTextInstance(CmdText::COMMAND_Y_POS, vm);
}
Command::~Command() {
delete _cmdText;
delete[] _cmdList;
delete[] _cmdArea;
delete[] _cmdObject;
delete[] _cmdInventory;
delete[] _cmdGameState;
}
void Command::clear(bool clearTexts) {
debug(6, "Command::clear(%d)", clearTexts);
_cmdText->clear();
if (clearTexts) {
_vm->display()->clearTexts(CmdText::COMMAND_Y_POS, CmdText::COMMAND_Y_POS);
}
_parse = false;
_state.init();
}
void Command::executeCurrentAction() {
_vm->logic()->entryObj(0);
if (_mouseKey == Input::MOUSE_RBUTTON && _state.subject[0] > 0) {
ObjectData *od = _vm->logic()->objectData(_state.subject[0]);
if (od == NULL || od->name <= 0) {
cleanupCurrentAction();
return;
}
_state.verb = State::findDefaultVerb(od->state);
_state.selAction = (_state.verb == VERB_NONE) ? VERB_WALK_TO : _state.verb;
_cmdText->setVerb(_state.selAction);
_cmdText->addObject(_vm->logic()->objectName(od->name));
}
// always highlight the current command when actioned
_cmdText->display(INK_CMD_SELECT);
_state.selNoun = _state.noun;
_state.commandLevel = 1;
if (handleWrongAction()) {
cleanupCurrentAction();
return;
}
// get the commands associated with this object/item
uint16 comMax = 0;
uint16 matchingCmds[MAX_MATCHING_CMDS];
CmdListData *cmdList = &_cmdList[1];
uint16 i;
for (i = 1; i <= _numCmdList; ++i, ++cmdList) {
if (cmdList->match(_state.selAction, _state.subject[0], _state.subject[1])) {
assert(comMax < MAX_MATCHING_CMDS);
matchingCmds[comMax] = i;
++comMax;
}
}
debug(6, "Command::executeCurrentAction() - comMax=%d subj1=%X subj2=%X", comMax, _state.subject[0], _state.subject[1]);
if (comMax == 0) {
sayInvalidAction(_state.selAction, _state.subject[0], _state.subject[1]);
clear(true);
cleanupCurrentAction();
return;
}
// process each associated command for the Object, until all done
// or one of the Gamestate tests fails...
int16 cond = 0;
CmdListData *com = &_cmdList[0];
uint16 comId = 0;
for (i = 1; i <= comMax; ++i) {
comId = matchingCmds[i - 1];
// WORKAROUND bug #1497280: This command is triggered in room 56 (the
// room with two waterfalls in the maze part of the game) if the user
// tries to walk through the left waterfall (object 423).
//
// Normally, this would move Joe to room 101 on the upper level and
// start a cutscene. Joe would notice that Yan has been trapped (on
// the lower level of the same room). The problem would then appear :
// Joe is stuck behind the waterfall due to a walkbox issue. We could
// fix the walkbox issue, but then Joe would walk through the waterfall
// which wouldn't look that nice, graphically.
//
// Since this command isn't necessary to complete the game and doesn't
// really makes sense here, we just skip it for now. The same cutscene
// is already played in command 648, so the user don't miss anything
// from the story/experience pov.
//
// Note: this happens with the original engine, too.
if (comId == 649) {
continue;
}
com = &_cmdList[comId];
// check the Gamestates and set them if necessary
cond = 0;
if (com->setConditions) {
cond = setConditions(comId, (i == comMax));
}
if (cond == -1 && i == comMax) {
// only exit on a condition fail if at last command
// Joe hasnt spoken, so do normal LOOK command
break;
} else if (cond == -2 && i == comMax) {
// only exit on a condition fail if at last command
// Joe has spoken, so skip LOOK command
cleanupCurrentAction();
return;
} else if (cond >= 0) {
// we've had a successful Gamestate check, so we must now exit
cond = executeCommand(comId, cond);
break;
}
}
if (_state.selAction == VERB_USE_JOURNAL) {
clear(true);
} else {
if (cond <= 0 && _state.selAction == VERB_LOOK_AT) {
lookAtSelectedObject();
} else {
// only play song if it's a PLAY AFTER type
if (com->song < 0) {
_vm->sound()->playSong(-com->song);
}
clear(true);
}
cleanupCurrentAction();
}
}
void Command::updatePlayer() {
if (_vm->logic()->joeWalk() != JWM_MOVE) {
Common::Point mouse = _vm->input()->getMousePos();
lookForCurrentObject(mouse.x, mouse.y);
lookForCurrentIcon(mouse.x, mouse.y);
}
if (_vm->input()->keyVerb() != VERB_NONE) {
if (_vm->input()->keyVerb() == VERB_USE_JOURNAL) {
_vm->logic()->useJournal();
} else if (_vm->input()->keyVerb() != VERB_SKIP_TEXT) {
_state.verb = _vm->input()->keyVerb();
if (isVerbInv(_state.verb)) {
_state.noun = _state.selNoun = 0;
_state.oldNoun = 0;
_state.oldVerb = VERB_NONE;
grabSelectedItem();
} else {
grabSelectedVerb();
}
}
_vm->input()->clearKeyVerb();
}
_mouseKey = _vm->input()->mouseButton();
_vm->input()->clearMouseButton();
if (_mouseKey > 0) {
grabCurrentSelection();
}
}
void Command::readCommandsFrom(byte *&ptr) {
uint16 i;
_numCmdList = READ_BE_UINT16(ptr); ptr += 2;
_cmdList = new CmdListData[_numCmdList + 1];
if (_numCmdList == 0) {
_cmdList[0].readFromBE(ptr);
} else {
memset(&_cmdList[0], 0, sizeof(CmdListData));
for (i = 1; i <= _numCmdList; i++) {
_cmdList[i].readFromBE(ptr);
}
}
_numCmdArea = READ_BE_UINT16(ptr); ptr += 2;
_cmdArea = new CmdArea[_numCmdArea + 1];
if (_numCmdArea == 0) {
_cmdArea[0].readFromBE(ptr);
} else {
memset(&_cmdArea[0], 0, sizeof(CmdArea));
for (i = 1; i <= _numCmdArea; i++) {
_cmdArea[i].readFromBE(ptr);
}
}
_numCmdObject = READ_BE_UINT16(ptr); ptr += 2;
_cmdObject = new CmdObject[_numCmdObject + 1];
if (_numCmdObject == 0) {
_cmdObject[0].readFromBE(ptr);
} else {
memset(&_cmdObject[0], 0, sizeof(CmdObject));
for (i = 1; i <= _numCmdObject; i++) {
_cmdObject[i].readFromBE(ptr);
// WORKAROUND bug #1858081: Fix an off by one error in the object
// command 175. Object 309 should be copied to 308 (disabled).
//
// _objectData[307].name = -195
// _objectData[308].name = 50
// _objectData[309].name = -50
if (i == 175 && _cmdObject[i].id == 320 && _cmdObject[i].dstObj == 307 && _cmdObject[i].srcObj == 309) {
_cmdObject[i].dstObj = 308;
}
}
}
_numCmdInventory = READ_BE_UINT16(ptr); ptr += 2;
_cmdInventory = new CmdInventory[_numCmdInventory + 1];
if (_numCmdInventory == 0) {
_cmdInventory[0].readFromBE(ptr);
} else {
memset(&_cmdInventory[0], 0, sizeof(CmdInventory));
for (i = 1; i <= _numCmdInventory; i++) {
_cmdInventory[i].readFromBE(ptr);
}
}
_numCmdGameState = READ_BE_UINT16(ptr); ptr += 2;
_cmdGameState = new CmdGameState[_numCmdGameState + 1];
if (_numCmdGameState == 0) {
_cmdGameState[0].readFromBE(ptr);
} else {
memset(&_cmdGameState[0], 0, sizeof(CmdGameState));
for (i = 1; i <= _numCmdGameState; i++) {
_cmdGameState[i].readFromBE(ptr);
}
}
}
ObjectData *Command::findObjectData(uint16 objRoomNum) const {
ObjectData *od = NULL;
if (objRoomNum != 0) {
objRoomNum += _vm->logic()->currentRoomData();
od = _vm->logic()->objectData(objRoomNum);
}
return od;
}
ItemData *Command::findItemData(Verb invNum) const {
ItemData *id = NULL;
uint16 itNum = _vm->logic()->findInventoryItem(invNum - VERB_INV_FIRST);
if (itNum != 0) {
id = _vm->logic()->itemData(itNum);
}
return id;
}
int16 Command::executeCommand(uint16 comId, int16 condResult) {
// execute.c l.313-452
debug(6, "Command::executeCommand() - cond = %X, com = %X", condResult, comId);
CmdListData *com = &_cmdList[comId];
if (com->setAreas) {
setAreas(comId);
}
// don't try to grab if action is TALK or WALK
if (_state.selAction != VERB_TALK_TO && _state.selAction != VERB_WALK_TO) {
int i;
for (i = 0; i < 2; ++i) {
int16 obj = _state.subject[i];
if (obj > 0) {
_vm->logic()->joeGrab(State::findGrab(_vm->logic()->objectData(obj)->state));
}
}
}
bool cutDone = false;
if (condResult > 0) {
// check for cutaway/dialogs before updating Objects
const char *desc = _vm->logic()->objectTextualDescription(condResult);
if (executeIfCutaway(desc)) {
condResult = 0;
cutDone = true;
} else if (executeIfDialog(desc)) {
condResult = 0;
}
}
int16 oldImage = 0;
if (_state.subject[0] > 0) {
// an object (not an item)
oldImage = _vm->logic()->objectData(_state.subject[0])->image;
}
if (com->setObjects) {
setObjects(comId);
}
if (com->setItems) {
setItems(comId);
}
if (com->imageOrder != 0 && _state.subject[0] > 0) {
ObjectData *od = _vm->logic()->objectData(_state.subject[0]);
// we must update the graphic image of the object
if (com->imageOrder < 0) {
// instead of setting to -1 or -2, flag as negative
if (od->image > 0) {
// make sure that object is not already updated
od->image = -(od->image + 10);
}
} else {
od->image = com->imageOrder;
}
_vm->graphics()->refreshObject(_state.subject[0]);
} else {
// this object is not being updated by command list, see if
// it has another image copied to it
if (_state.subject[0] > 0) {
// an object (not an item)
if (_vm->logic()->objectData(_state.subject[0])->image != oldImage) {
_vm->graphics()->refreshObject(_state.subject[0]);
}
}
}
// don't play music on an OPEN/CLOSE command - in case the command fails
if (_state.selAction != VERB_NONE &&
_state.selAction != VERB_OPEN &&
_state.selAction != VERB_CLOSE) {
// only play song if it's a PLAY BEFORE type
if (com->song > 0) {
_vm->sound()->playSong(com->song);
}
}
// do a special hardcoded section
// l.419-452 execute.c
switch (com->specialSection) {
case 1:
_vm->logic()->useJournal();
_state.selAction = VERB_USE_JOURNAL;
return condResult;
case 2:
_vm->logic()->joeUseDress(true);
break;
case 3:
_vm->logic()->joeUseClothes(true);
break;
case 4:
_vm->logic()->joeUseUnderwear();
break;
}
if (_state.subject[0] > 0)
changeObjectState(_state.selAction, _state.subject[0], com->song, cutDone);
if (condResult > 0) {
_vm->logic()->makeJoeSpeak(condResult, true);
}
return condResult;
}
int16 Command::makeJoeWalkTo(int16 x, int16 y, int16 objNum, Verb v, bool mustWalk) {
// Check to see if object is actually an exit to another
// room. If so, then set up new room
ObjectData *objData = _vm->logic()->objectData(objNum);
if (objData->x != 0 || objData->y != 0) {
x = objData->x;
y = objData->y;
}
if (v == VERB_WALK_TO) {
_vm->logic()->entryObj(objData->entryObj);
if (objData->entryObj > 0) {
_vm->logic()->newRoom(_vm->logic()->objectData(objData->entryObj)->room);
// because this is an exit object, see if there is
// a walk off point and set (x,y) accordingly
WalkOffData *wod = _vm->logic()->walkOffPointForObject(objNum);
if (wod != NULL) {
x = wod->x;
y = wod->y;
}
}
} else {
_vm->logic()->entryObj(0);
_vm->logic()->newRoom(0);
}
debug(6, "Command::makeJoeWalkTo() - x=%d y=%d newRoom=%d", x, y, _vm->logic()->newRoom());
int16 p = 0;
if (mustWalk) {
// determine which way for Joe to face Object
uint16 facing = State::findDirection(objData->state);
BobSlot *bobJoe = _vm->graphics()->bob(0);
if (x == bobJoe->x && y == bobJoe->y) {
_vm->logic()->joeFacing(facing);
_vm->logic()->joeFace();
} else {
p = _vm->walk()->moveJoe(facing, x, y, false);
if (p != 0) {
_vm->logic()->newRoom(0); // cancel makeJoeWalkTo, that should be equivalent to cr10 fix
}
}
}
return p;
}
void Command::grabCurrentSelection() {
Common::Point mouse = _vm->input()->getMousePos();
_selPosX = mouse.x;
_selPosY = mouse.y;
uint16 zone = _vm->grid()->findObjectUnderCursor(_selPosX, _selPosY);
_state.noun = _vm->grid()->findObjectNumber(zone);
_state.verb = _vm->grid()->findVerbUnderCursor(_selPosX, _selPosY);
_selPosX += _vm->display()->horizontalScroll();
if (isVerbAction(_state.verb) || isVerbInvScroll(_state.verb)) {
grabSelectedVerb();
} else if (isVerbInv(_state.verb)) {
grabSelectedItem();
} else if (_state.noun != 0) {
grabSelectedNoun();
} else if (_selPosY < ROOM_ZONE_HEIGHT && _state.verb == VERB_NONE) {
// select without a command, do a WALK
clear(true);
_vm->logic()->joeWalk(JWM_EXECUTE);
}
}
void Command::grabSelectedObject(int16 objNum, uint16 objState, uint16 objName) {
if (_state.action != VERB_NONE) {
_cmdText->addObject(_vm->logic()->objectName(objName));
}
_state.subject[_state.commandLevel - 1] = objNum;
// if first noun and it's a 2 level command then set up action word
if (_state.action == VERB_USE && _state.commandLevel == 1) {
if (State::findUse(objState) == STATE_USE_ON) {
// object supports 2 levels, command not fully constructed
_state.commandLevel = 2;
_cmdText->addLinkWord(VERB_PREP_WITH);
_cmdText->display(INK_CMD_NORMAL);
_parse = false;
} else {
_parse = true;
}
} else if (_state.action == VERB_GIVE && _state.commandLevel == 1) {
// command not fully constructed
_state.commandLevel = 2;
_cmdText->addLinkWord(VERB_PREP_TO);
_cmdText->display(INK_CMD_NORMAL);
_parse = false;
} else {
_parse = true;
}
if (_parse) {
_state.verb = VERB_NONE;
_vm->logic()->joeWalk(JWM_EXECUTE);
_state.selAction = _state.action;
_state.action = VERB_NONE;
}
}
void Command::grabSelectedItem() {
ItemData *id = findItemData(_state.verb);
if (id == NULL || id->name <= 0) {
return;
}
int16 item = _vm->logic()->findInventoryItem(_state.verb - VERB_INV_FIRST);
// If we've selected via keyboard, and there is no VERB then do
// the ITEMs default, otherwise keep constructing!
if (_mouseKey == Input::MOUSE_LBUTTON ||
(_vm->input()->keyVerb() != VERB_NONE && _state.verb != VERB_NONE)) {
if (_state.action == VERB_NONE) {
if (_vm->input()->keyVerb() != VERB_NONE) {
// We've selected via the keyboard, no command is being
// constructed, so we shall find the item's default
_state.verb = State::findDefaultVerb(id->state);
if (_state.verb == VERB_NONE) {
// set to Look At
_state.verb = VERB_LOOK_AT;
_cmdText->setVerb(VERB_LOOK_AT);
}
_state.action = _state.verb;
} else {
// Action>0 ONLY if command has been constructed
// Left Mouse Button pressed just do Look At
_state.action = VERB_LOOK_AT;
_cmdText->setVerb(VERB_LOOK_AT);
}
}
_state.verb = VERB_NONE;
} else {
if (_cmdText->isEmpty()) {
_state.verb = VERB_LOOK_AT;
_state.action = VERB_LOOK_AT;
_cmdText->setVerb(VERB_LOOK_AT);
} else {
if (_state.commandLevel == 2 && _parse)
_state.verb = _state.action;
else
_state.verb = State::findDefaultVerb(id->state);
if (_state.verb == VERB_NONE) {
// No match made, so command not yet completed. Redefine as LOOK AT
_state.action = VERB_LOOK_AT;
_cmdText->setVerb(VERB_LOOK_AT);
} else {
_state.action = _state.verb;
}
_state.verb = VERB_NONE;
}
}
grabSelectedObject(-item, id->state, id->name);
}
void Command::grabSelectedNoun() {
ObjectData *od = findObjectData(_state.noun);
if (od == NULL || od->name <= 0) {
// selected a turned off object, so just walk
clear(true);
_state.noun = 0;
_vm->logic()->joeWalk(JWM_EXECUTE);
return;
}
if (_state.verb == VERB_NONE) {
if (_mouseKey == Input::MOUSE_LBUTTON) {
if ((_state.commandLevel != 2 && _state.action == VERB_NONE) ||
(_state.commandLevel == 2 && _parse)) {
_state.verb = VERB_WALK_TO;
_state.action = VERB_WALK_TO;
_cmdText->setVerb(VERB_WALK_TO);
}
} else if (_mouseKey == Input::MOUSE_RBUTTON) {
if (_cmdText->isEmpty()) {
_state.verb = State::findDefaultVerb(od->state);
_state.selAction = (_state.verb == VERB_NONE) ? VERB_WALK_TO : _state.verb;
_cmdText->setVerb(_state.selAction);
_cmdText->addObject(_vm->logic()->objectName(od->name));
} else {
if ((_state.commandLevel == 2 && !_parse) || _state.action != VERB_NONE) {
_state.verb = _state.action;
} else {
_state.verb = State::findDefaultVerb(od->state);
}
_state.action = (_state.verb == VERB_NONE) ? VERB_WALK_TO : _state.verb;
_state.verb = VERB_NONE;
}
}
}
_state.selNoun = 0;
int16 objNum = _vm->logic()->currentRoomData() + _state.noun;
grabSelectedObject(objNum, od->state, od->name);
}
void Command::grabSelectedVerb() {
if (isVerbInvScroll(_state.verb)) {
// move through inventory (by four if right mouse button)
uint16 scroll = (_mouseKey == Input::MOUSE_RBUTTON) ? 4 : 1;
_vm->logic()->inventoryScroll(scroll, _state.verb == VERB_SCROLL_UP);
} else {
_state.action = _state.verb;
_state.subject[0] = 0;
_state.subject[1] = 0;
if (_vm->logic()->joeWalk() == JWM_MOVE && _state.verb != VERB_NONE) {
_vm->logic()->joeWalk(JWM_NORMAL);
}
_state.commandLevel = 1;
_state.oldVerb = VERB_NONE;
_state.oldNoun = 0;
_cmdText->setVerb(_state.verb);
_cmdText->display(INK_CMD_NORMAL);
}
}
bool Command::executeIfCutaway(const char *description) {
if (strlen(description) > 4 &&
scumm_stricmp(description + strlen(description) - 4, ".CUT") == 0) {
_vm->display()->clearTexts(CmdText::COMMAND_Y_POS, CmdText::COMMAND_Y_POS);
char nextCutaway[20];
memset(nextCutaway, 0, sizeof(nextCutaway));
_vm->logic()->playCutaway(description, nextCutaway);
while (nextCutaway[0] != '\0') {
_vm->logic()->playCutaway(nextCutaway, nextCutaway);
}
return true;
}
return false;
}
bool Command::executeIfDialog(const char *description) {
if (strlen(description) > 4 &&
scumm_stricmp(description + strlen(description) - 4, ".DOG") == 0) {
_vm->display()->clearTexts(CmdText::COMMAND_Y_POS, CmdText::COMMAND_Y_POS);
char cutaway[20];
memset(cutaway, 0, sizeof(cutaway));
_vm->logic()->startDialogue(description, _state.selNoun, cutaway);
while (cutaway[0] != '\0') {
char currentCutaway[20];
strcpy(currentCutaway, cutaway);
_vm->logic()->playCutaway(currentCutaway, cutaway);
}
return true;
}
return false;
}
bool Command::handleWrongAction() {
// l.96-141 execute.c
uint16 objMax = _vm->grid()->objMax(_vm->logic()->currentRoom());
uint16 roomData = _vm->logic()->currentRoomData();
// select without a command or WALK TO ; do a WALK
if ((_state.selAction == VERB_WALK_TO || _state.selAction == VERB_NONE) &&
(_state.selNoun > objMax || _state.selNoun == 0)) {
if (_state.selAction == VERB_NONE) {
_vm->display()->clearTexts(CmdText::COMMAND_Y_POS, CmdText::COMMAND_Y_POS);
}
_vm->walk()->moveJoe(0, _selPosX, _selPosY, false);
return true;
}
// check to see if one of the objects is hidden
int i;
for (i = 0; i < 2; ++i) {
int16 obj = _state.subject[i];
if (obj > 0 && _vm->logic()->objectData(obj)->name <= 0) {
return true;
}
}
// check for USE command on exists
if (_state.selAction == VERB_USE &&
_state.subject[0] > 0 && _vm->logic()->objectData(_state.subject[0])->entryObj > 0) {
_state.selAction = VERB_WALK_TO;
}
if (_state.selNoun > 0 && _state.selNoun <= objMax) {
uint16 objNum = roomData + _state.selNoun;
if (makeJoeWalkTo(_selPosX, _selPosY, objNum, _state.selAction, true) != 0) {
return true;
}
if (_state.selAction == VERB_WALK_TO && _vm->logic()->objectData(objNum)->entryObj < 0) {
return true;
}
}
return false;
}
void Command::sayInvalidAction(Verb action, int16 subj1, int16 subj2) {
// l.158-272 execute.c
switch (action) {
case VERB_LOOK_AT:
lookAtSelectedObject();
break;
case VERB_OPEN:
// 'it doesn't seem to open'
_vm->logic()->makeJoeSpeak(1);
break;
case VERB_USE:
if (subj1 < 0) {
uint16 k = _vm->logic()->itemData(-subj1)->sfxDescription;
if (k > 0) {
_vm->logic()->makeJoeSpeak(k, true);
} else {
_vm->logic()->makeJoeSpeak(2);
}
} else {
_vm->logic()->makeJoeSpeak(2);
}
break;
case VERB_TALK_TO:
_vm->logic()->makeJoeSpeak(24 + _vm->randomizer.getRandomNumber(2));
break;
case VERB_CLOSE:
_vm->logic()->makeJoeSpeak(2);
break;
case VERB_MOVE:
// 'I can't move it'
if (subj1 > 0) {
int16 img = _vm->logic()->objectData(subj1)->image;
if (img == -4 || img == -3) {
_vm->logic()->makeJoeSpeak(18);
} else {
_vm->logic()->makeJoeSpeak(3);
}
} else {
_vm->logic()->makeJoeSpeak(3);
}
break;
case VERB_GIVE:
// 'I can't give the subj1 to subj2'
if (subj1 < 0) {
if (subj2 > 0) {
int16 img = _vm->logic()->objectData(subj2)->image;
if (img == -4 || img == -3) {
_vm->logic()->makeJoeSpeak(27 + _vm->randomizer.getRandomNumber(2));
}
} else {
_vm->logic()->makeJoeSpeak(11);
}
} else {
_vm->logic()->makeJoeSpeak(12);
}
break;
case VERB_PICK_UP:
if (subj1 < 0) {
_vm->logic()->makeJoeSpeak(14);
} else {
int16 img = _vm->logic()->objectData(subj1)->image;
if (img == -4 || img == -3) {
// Trying to get a person
_vm->logic()->makeJoeSpeak(20);
} else {
// 5 : 'I can't pick that up'
// 6 : 'I don't think I need that'
// 7 : 'I'd rather leave it here'
// 8 : 'I don't think I'd have any use for that'
_vm->logic()->makeJoeSpeak(5 + _vm->randomizer.getRandomNumber(3));
}
}
break;
default:
break;
}
}
void Command::changeObjectState(Verb action, int16 obj, int16 song, bool cutDone) {
// l.456-533 execute.c
ObjectData *objData = _vm->logic()->objectData(obj);
if (action == VERB_OPEN && !cutDone) {
if (State::findOn(objData->state) == STATE_ON_ON) {
State::alterOn(&objData->state, STATE_ON_OFF);
State::alterDefaultVerb(&objData->state, VERB_NONE);
// play music if it exists... (or SFX for open/close door)
if (song != 0) {
_vm->sound()->playSong(ABS(song));
}
if (objData->entryObj != 0) {
// if it's a door, then update door that it links to
openOrCloseAssociatedObject(action, ABS(objData->entryObj));
objData->entryObj = ABS(objData->entryObj);
}
} else {
// 'it's already open !'
_vm->logic()->makeJoeSpeak(9);
}
} else if (action == VERB_CLOSE && !cutDone) {
if (State::findOn(objData->state) == STATE_ON_OFF) {
State::alterOn(&objData->state, STATE_ON_ON);
State::alterDefaultVerb(&objData->state, VERB_OPEN);
// play music if it exists... (or SFX for open/close door)
if (song != 0) {
_vm->sound()->playSong(ABS(song));
}
if (objData->entryObj != 0) {
// if it's a door, then update door that it links to
openOrCloseAssociatedObject(action, ABS(objData->entryObj));
objData->entryObj = -ABS(objData->entryObj);
}
} else {
// 'it's already closed !'
_vm->logic()->makeJoeSpeak(10);
}
} else if (action == VERB_MOVE) {
State::alterOn(&objData->state, STATE_ON_OFF);
}
}
void Command::cleanupCurrentAction() {
// l.595-597 execute.c
_vm->logic()->joeFace();
_state.oldNoun = 0;
_state.oldVerb = VERB_NONE;
}
void Command::openOrCloseAssociatedObject(Verb action, int16 otherObj) {
CmdListData *cmdList = &_cmdList[1];
uint16 com = 0;
uint16 i;
for (i = 1; i <= _numCmdList && com == 0; ++i, ++cmdList) {
if (cmdList->match(action, otherObj, 0)) {
if (cmdList->setConditions) {
CmdGameState *cmdGs = _cmdGameState;
uint16 j;
for (j = 1; j <= _numCmdGameState; ++j) {
if (cmdGs[j].id == i && cmdGs[j].gameStateSlot > 0) {
if (_vm->logic()->gameState(cmdGs[j].gameStateSlot) == cmdGs[j].gameStateValue) {
com = i;
break;
}
}
}
} else {
com = i;
break;
}
}
}
if (com != 0) {
debug(6, "Command::openOrCloseAssociatedObject() com=%X", com);
cmdList = &_cmdList[com];
ObjectData *objData = _vm->logic()->objectData(otherObj);
if (cmdList->imageOrder != 0) {
objData->image = cmdList->imageOrder;
}
if (action == VERB_OPEN) {
if (State::findOn(objData->state) == STATE_ON_ON) {
State::alterOn(&objData->state, STATE_ON_OFF);
State::alterDefaultVerb(&objData->state, VERB_NONE);
objData->entryObj = ABS(objData->entryObj);
}
} else if (action == VERB_CLOSE) {
if (State::findOn(objData->state) == STATE_ON_OFF) {
State::alterOn(&objData->state, STATE_ON_ON);
State::alterDefaultVerb(&objData->state, VERB_OPEN);
objData->entryObj = -ABS(objData->entryObj);
}
}
}
}
int16 Command::setConditions(uint16 command, bool lastCmd) {
debug(9, "Command::setConditions(%d, %d)", command, lastCmd);
int16 ret = 0;
uint16 cmdState[21];
memset(cmdState, 0, sizeof(cmdState));
uint16 cmdStateCount = 0;
uint16 i;
CmdGameState *cmdGs = &_cmdGameState[1];
for (i = 1; i <= _numCmdGameState; ++i, ++cmdGs) {
if (cmdGs->id == command) {
if (cmdGs->gameStateSlot > 0) {
if (_vm->logic()->gameState(cmdGs->gameStateSlot) != cmdGs->gameStateValue) {
debug(6, "Command::setConditions() - GS[%d] == %d (should be %d)", cmdGs->gameStateSlot, _vm->logic()->gameState(cmdGs->gameStateSlot), cmdGs->gameStateValue);
// failed test
ret = i;
break;
}
} else {
cmdState[cmdStateCount] = i;
++cmdStateCount;
}
}
}
if (ret > 0) {
// we've failed, so see if we need to make Joe speak
cmdGs = &_cmdGameState[ret];
if (cmdGs->speakValue > 0 && lastCmd) {
// check to see if fail state is in fact a cutaway
const char *objDesc = _vm->logic()->objectTextualDescription(cmdGs->speakValue);
if (!executeIfCutaway(objDesc) && !executeIfDialog(objDesc)) {
_vm->logic()->makeJoeSpeak(cmdGs->speakValue, true);
}
ret = -2;
} else {
// return -1 so Joe will be able to speak a normal description
ret = -1;
}
} else {
ret = 0;
// all tests were okay, now set gamestates
for (i = 0; i < cmdStateCount; ++i) {
cmdGs = &_cmdGameState[cmdState[i]];
_vm->logic()->gameState(ABS(cmdGs->gameStateSlot), cmdGs->gameStateValue);
// set return value for Joe to say something
ret = cmdGs->speakValue;
}
}
return ret;
}
void Command::setAreas(uint16 command) {
debug(9, "Command::setAreas(%d)", command);
CmdArea *cmdArea = &_cmdArea[1];
for (uint16 i = 1; i <= _numCmdArea; ++i, ++cmdArea) {
if (cmdArea->id == command) {
uint16 areaNum = ABS(cmdArea->area);
Area *area = _vm->grid()->area(cmdArea->room, areaNum);
if (cmdArea->area > 0) {
// turn on area
area->mapNeighbors = ABS(area->mapNeighbors);
} else {
// turn off area
area->mapNeighbors = -ABS(area->mapNeighbors);
}
}
}
}
void Command::setObjects(uint16 command) {
debug(9, "Command::setObjects(%d)", command);
CmdObject *cmdObj = &_cmdObject[1];
for (uint16 i = 1; i <= _numCmdObject; ++i, ++cmdObj) {
if (cmdObj->id == command) {
// found an object
uint16 dstObj = ABS(cmdObj->dstObj);
ObjectData *objData = _vm->logic()->objectData(dstObj);
debug(6, "Command::setObjects() - dstObj=%X srcObj=%X _state.subject[0]=%X", cmdObj->dstObj, cmdObj->srcObj, _state.subject[0]);
if (cmdObj->dstObj > 0) {
// show the object
objData->name = ABS(objData->name);
// test that the object has not already been deleted
// by checking if it is not equal to zero
if (cmdObj->srcObj == -1 && objData->name != 0) {
// delete object by setting its name to 0 and
// turning off graphic image
objData->name = 0;
if (objData->room == _vm->logic()->currentRoom()) {
if (dstObj != _state.subject[0]) {
// if the new object we have updated is on screen and is not the
// current object, then we can update. This is because we turn
// current object off ourselves by COM_LIST(com, 8)
if (objData->image != -3 && objData->image != -4) {
// it is a normal object (not a person)
// turn the graphic image off for the object
objData->image = -(objData->image + 10);
}
}
// invalidate object area
uint16 objZone = dstObj - _vm->logic()->currentRoomData();
_vm->grid()->setZone(GS_ROOM, objZone, 0, 0, 1, 1);
}
}
if (cmdObj->srcObj > 0) {
// copy data from dummy object to object
int16 image1 = objData->image;
int16 image2 = _vm->logic()->objectData(cmdObj->srcObj)->image;
_vm->logic()->objectCopy(cmdObj->srcObj, dstObj);
if (image1 != 0 && image2 == 0 && objData->room == _vm->logic()->currentRoom()) {
uint16 bobNum = _vm->logic()->findBob(dstObj);
if (bobNum != 0) {
_vm->graphics()->clearBob(bobNum);
}
}
}
if (dstObj != _state.subject[0]) {
// if the new object we have updated is on screen and
// is not current object then update it
_vm->graphics()->refreshObject(dstObj);
}
} else {
// hide the object
if (objData->name > 0) {
objData->name = -objData->name;
// may need to turn BOBs off for objects to be hidden on current
// screen ! if the new object we have updated is on screen and
// is not current object then update it
_vm->graphics()->refreshObject(dstObj);
}
}
}
}
}
void Command::setItems(uint16 command) {
debug(9, "Command::setItems(%d)", command);
ItemData *items = _vm->logic()->itemData(0);
CmdInventory *cmdInv = &_cmdInventory[1];
for (uint16 i = 1; i <= _numCmdInventory; ++i, ++cmdInv) {
if (cmdInv->id == command) {
uint16 dstItem = ABS(cmdInv->dstItem);
// found an item
if (cmdInv->dstItem > 0) {
// add item to inventory
if (cmdInv->srcItem > 0) {
// copy data from source item to item, then enable it
items[dstItem] = items[cmdInv->srcItem];
items[dstItem].name = ABS(items[dstItem].name);
}
_vm->logic()->inventoryInsertItem(cmdInv->dstItem);
} else {
// delete item
if (items[dstItem].name > 0) {
_vm->logic()->inventoryDeleteItem(dstItem);
}
if (cmdInv->srcItem > 0) {
// copy data from source item to item, then disable it
items[dstItem] = items[cmdInv->srcItem];
items[dstItem].name = -ABS(items[dstItem].name);
}
}
}
}
}
uint16 Command::nextObjectDescription(ObjectDescription* objDesc, uint16 firstDesc) {
// l.69-103 select.c
uint16 i;
uint16 diff = objDesc->lastDescription - firstDesc;
debug(6, "Command::nextObjectDescription() - diff = %d, type = %d", diff, objDesc->type);
switch (objDesc->type) {
case 0:
// random type, start with first description
if (objDesc->lastSeenNumber == 0) {
// first time look at called
objDesc->lastSeenNumber = firstDesc;
break;
}
// already displayed first, do a random
// fall through
case 1:
i = objDesc->lastSeenNumber;
while (i == objDesc->lastSeenNumber) {
i = firstDesc + _vm->randomizer.getRandomNumber(diff);
}
objDesc->lastSeenNumber = i;
break;
case 2:
// sequential, but loop
++objDesc->lastSeenNumber;
if (objDesc->lastSeenNumber > objDesc->lastDescription) {
objDesc->lastSeenNumber = firstDesc;
}
break;
case 3:
// sequential without looping
if (objDesc->lastSeenNumber != objDesc->lastDescription) {
++objDesc->lastSeenNumber;
}
break;
}
return objDesc->lastSeenNumber;
}
void Command::lookAtSelectedObject() {
uint16 desc;
if (_state.subject[0] < 0) {
desc = _vm->logic()->itemData(-_state.subject[0])->description;
} else {
ObjectData *objData = _vm->logic()->objectData(_state.subject[0]);
if (objData->name <= 0) {
return;
}
desc = objData->description;
}
debug(6, "Command::lookAtSelectedObject() - desc = %X, _state.subject[0] = %X", desc, _state.subject[0]);
// check to see if the object/item has a series of description
ObjectDescription *objDesc = _vm->logic()->objectDescription(1);
uint16 i;
for (i = 1; i <= _vm->logic()->objectDescriptionCount(); ++i, ++objDesc) {
if (objDesc->object == _state.subject[0]) {
desc = nextObjectDescription(objDesc, desc);
break;
}
}
if (desc != 0) {
_vm->logic()->makeJoeSpeak(desc, true);
}
_vm->logic()->joeFace();
}
void Command::lookForCurrentObject(int16 cx, int16 cy) {
uint16 obj = _vm->grid()->findObjectUnderCursor(cx, cy);
_state.noun = _vm->grid()->findObjectNumber(obj);
if (_state.oldNoun == _state.noun) {
return;
}
ObjectData *od = findObjectData(_state.noun);
if (od == NULL || od->name <= 0) {
_state.oldNoun = _state.noun;
_vm->display()->clearTexts(CmdText::COMMAND_Y_POS, CmdText::COMMAND_Y_POS);
if (_state.action != VERB_NONE) {
_cmdText->display(INK_CMD_NORMAL);
}
return;
}
// if no command yet selected, then use DEFAULT command, if any
if (_state.action == VERB_NONE) {
Verb v = State::findDefaultVerb(od->state);
_cmdText->setVerb((v == VERB_NONE) ? VERB_WALK_TO : v);
if (_state.noun == 0) {
_cmdText->clear();
}
}
const char *name = _vm->logic()->objectName(od->name);
_cmdText->displayTemp(INK_CMD_NORMAL, name, false);
_state.oldNoun = _state.noun;
}
void Command::lookForCurrentIcon(int16 cx, int16 cy) {
_state.verb = _vm->grid()->findVerbUnderCursor(cx, cy);
if (_state.oldVerb != _state.verb) {
if (_state.action == VERB_NONE) {
_cmdText->clear();
}
_vm->display()->clearTexts(CmdText::COMMAND_Y_POS, CmdText::COMMAND_Y_POS);
if (isVerbInv(_state.verb)) {
ItemData *id = findItemData(_state.verb);
if (id != NULL && id->name > 0) {
if (_state.action == VERB_NONE) {
Verb v = State::findDefaultVerb(id->state);
_cmdText->setVerb((v == VERB_NONE) ? VERB_LOOK_AT : v);
}
const char *name = _vm->logic()->objectName(id->name);
_cmdText->displayTemp(INK_CMD_NORMAL, name, false);
}
} else if (isVerbAction(_state.verb)) {
_cmdText->displayTemp(INK_CMD_NORMAL, _state.verb);
} else if (_state.verb == VERB_NONE) {
_cmdText->display(INK_CMD_NORMAL);
}
_state.oldVerb = _state.verb;
}
}
} // End of namespace Queen