scummvm/engines/mads/action.cpp
2021-12-26 18:48:43 +01:00

706 lines
20 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "mads/mads.h"
#include "mads/action.h"
#include "mads/inventory.h"
#include "mads/resources.h"
#include "mads/scene.h"
#include "mads/staticres.h"
namespace MADS {
void ActionDetails::synchronize(Common::Serializer &s) {
s.syncAsUint16LE(_verbId);
s.syncAsUint16LE(_objectNameId);
s.syncAsUint16LE(_indirectObjectId);
}
void ActionSavedFields::synchronize(Common::Serializer &s) {
s.syncAsByte(_commandError);
s.syncAsSint16LE(_commandSource);
s.syncAsSint16LE(_command);
s.syncAsSint16LE(_mainObject);
s.syncAsSint16LE(_secondObject);
s.syncAsSint16LE(_mainObjectSource);
s.syncAsSint16LE(_secondObjectSource);
s.syncAsSint16LE(_articleNumber);
s.syncAsSint16LE(_lookFlag);
}
/*------------------------------------------------------------------------*/
MADSAction::MADSAction(MADSEngine *vm) : _vm(vm) {
clear();
_statusTextIndex = -1;
_selectedAction = 0;
_inProgress = false;
_pickedWord = -1;
_savedFields._commandSource = CAT_NONE;
_savedFields._mainObjectSource = CAT_NONE;
_savedFields._command = -1;
_savedFields._mainObject = 0;
_savedFields._secondObject = 0;
_savedFields._secondObjectSource = CAT_NONE;
_savedFields._articleNumber = PREP_NONE;
_savedFields._lookFlag = false;
_activeAction._verbId = VERB_NONE;
_activeAction._objectNameId = -1;
_activeAction._indirectObjectId = -1;
_savedFields._commandError = false;
_verbType = VERB_INIT;
_prepType = PREP_NONE;
}
void MADSAction::clear() {
_interAwaiting = AWAITING_COMMAND;
_commandSource = CAT_NONE;
_mainObjectSource = CAT_NONE;
_secondObjectSource = CAT_NONE;
_recentCommandSource = CAT_NONE;
_articleNumber = 0;
_lookFlag = false;
_pointEstablished = 0;
_statusText.clear();
_selectedRow = -1;
_hotspotId = -1;
_secondObject = -1;
_recentCommand = -1;
_action._verbId = VERB_NONE;
_action._objectNameId = -1;
_action._indirectObjectId = -1;
_textChanged = true;
}
void MADSAction::appendVocab(int vocabId, bool capitalize) {
Common::String vocabStr = _vm->_game->_scene.getVocab(vocabId);
if (capitalize)
vocabStr.setChar(toupper(vocabStr[0]), 0);
_statusText += vocabStr;
_statusText += " ";
}
void MADSAction::startWalkingDirectly(int walkType) {
Scene &scene = _vm->_game->_scene;
Player &player = _vm->_game->_player;
if (_pointEstablished && (walkType == -3 || _savedFields._command < 0)) {
player._needToWalk = true;
player._prepareWalkPos = scene._customDest;
}
}
void MADSAction::set() {
Scene &scene = _vm->_game->_scene;
UserInterface &userInterface = scene._userInterface;
_statusText = "";
_action._verbId = VERB_NONE;
_action._objectNameId = -1;
_action._indirectObjectId = -1;
if (_commandSource == CAT_TALK_ENTRY) {
// Handle showing the conversation selection. Rex at least doesn't actually seem to use this
if (_selectedRow >= 0) {
_action._verbId = userInterface._talkIds[_selectedRow];
Common::String desc = userInterface._talkStrings[_selectedRow];
if (!desc.empty())
_statusText = desc;
}
} else if (_lookFlag && (_selectedRow == 0)) {
// Two 'look' actions in succession, so the action becomes 'Look around'
_statusText = kLookAroundStr;
} else {
bool flag = false;
if ((_commandSource == CAT_INV_VOCAB) && (_selectedRow >= 0)
&& (_verbType == VERB_THAT) && (_prepType == PREP_NONE)) {
// Use/to action
int invIndex = userInterface._selectedInvIndex;
InventoryObject &objEntry = _vm->_game->_objects.getItem(invIndex);
_action._objectNameId = objEntry._descId;
_action._verbId = objEntry._vocabList[_selectedRow]._vocabId;
// Set up the status text string
_statusText = kUseStr;
appendVocab(_action._objectNameId);
_statusText += kToStr;
appendVocab(_action._verbId);
} else {
// Handling for if an action has been selected
if (_selectedRow >= 0) {
if (_commandSource == CAT_COMMAND) {
// Standard verb action
_action._verbId = scene._verbList[_selectedRow]._id;
} else {
// Selected action on an inventory object
int invIndex = userInterface._selectedInvIndex;
InventoryObject &objEntry = _vm->_game->_objects.getItem(invIndex);
_action._verbId = objEntry._vocabList[_selectedRow]._vocabId;
}
appendVocab(_action._verbId, true);
if (_action._verbId == VERB_LOOK) {
// Add in the word 'add'
_statusText += kArticleList[PREP_AT];
_statusText += " ";
}
}
// Add in any necessary article if necessary
if ((_hotspotId >= 0) && (_selectedRow >= 0) && (_articleNumber > 0) && (_verbType == VERB_THAT)) {
flag = true;
_statusText += kArticleList[_articleNumber];
_statusText += " ";
}
// Handling for hotspot
if (_hotspotId >= 0) {
if (_selectedRow < 0) {
int verbId;
if (_hotspotId < (int)scene._hotspots.size()) {
// Get the verb Id from the hotspot
verbId = scene._hotspots[_hotspotId]._verbId;
} else {
// Get the verb Id from the scene object
verbId = scene._dynamicHotspots.get(_hotspotId - scene._hotspots.size())._verbId;
}
if (verbId > 0) {
// Set the specified action
_action._verbId = verbId;
appendVocab(_action._verbId, true);
} else {
// Default to a standard 'walk to'
_action._verbId = VERB_WALKTO;
_statusText += kWalkToStr;
}
}
if ((_mainObjectSource == CAT_INV_LIST) || (_mainObjectSource == CAT_INV_ANIM)) {
// Get name from given inventory object
InventoryObject &invObject = _vm->_game->_objects.getItem(_hotspotId);
_action._objectNameId = invObject._descId;
} else if (_hotspotId < (int)scene._hotspots.size()) {
// Get name from scene hotspot
_action._objectNameId = scene._hotspots[_hotspotId]._vocabId;
} else {
// Get name from temporary scene hotspot
_action._objectNameId = scene._dynamicHotspots.get(_hotspotId - scene._hotspots.size())._descId;
}
appendVocab(_action._objectNameId);
}
}
if (_secondObject >= 0) {
if (_secondObjectSource == CAT_INV_LIST || _secondObjectSource == CAT_INV_ANIM) {
InventoryObject &invObject = _vm->_game->_objects.getItem(_secondObject);
_action._indirectObjectId = invObject._descId;
} else if (_secondObject < (int)scene._hotspots.size()) {
_action._indirectObjectId = scene._hotspots[_secondObject]._vocabId;
} else {
_action._indirectObjectId = scene._dynamicHotspots.get(_secondObject - scene._hotspots.size())._descId;
}
}
if ((_hotspotId >= 0) && (_articleNumber > 0) && !flag) {
if (_articleNumber == PREP_RELATIONAL) {
if (_secondObject >= 0) {
int articleNum = 0;
if ((_secondObjectSource == 2) || (_secondObjectSource == 5)) {
InventoryObject &invObject = _vm->_game->_objects.getItem(_secondObject);
articleNum = invObject._article;
} else if (_secondObject < (int)scene._hotspots.size()) {
articleNum = scene._hotspots[_secondObject]._articleNumber;
} else {
articleNum = scene._dynamicHotspots.get(_secondObject - scene._hotspots.size())._articleNumber;
}
_statusText += kArticleList[articleNum];
}
} else if ((_articleNumber != VERB_LOOK) || (_vm->getGameID() != GType_RexNebular) ||
(_action._indirectObjectId >= 0 && scene.getVocab(_action._indirectObjectId) != kFenceStr)) {
// Write out the article
_statusText += kArticleList[_articleNumber];
} else {
// Special case for a 'fence' entry in Rex Nebular
_statusText += kOverStr;
}
_statusText += " ";
}
// Append object description if necessary
if (_secondObject >= 0)
appendVocab(_action._indirectObjectId);
// Remove any trailing space character
if (_statusText.hasSuffix(" "))
_statusText.deleteLastChar();
}
_textChanged = true;
}
void MADSAction::refresh() {
Scene &scene = _vm->_game->_scene;
// Exit immediately if nothing has changed
if (!_textChanged)
return;
// Remove any old copy of the status text
if (_statusTextIndex >= 0) {
scene._textDisplay.expire(_statusTextIndex);
_statusTextIndex = -1;
}
if (!_statusText.empty()) {
if ((_vm->_game->_screenObjects._inputMode == kInputBuildingSentences) ||
(_vm->_game->_screenObjects._inputMode == kInputLimitedSentences)) {
Font *font = _vm->_font->getFont(FONT_MAIN);
int textSpacing = -1;
int strWidth = font->getWidth(_statusText);
if (strWidth > MADS_SCREEN_WIDTH) {
// Too large to fit, so fall back on interface font
font = _vm->_font->getFont(FONT_INTERFACE);
strWidth = font->getWidth(_statusText, 0);
textSpacing = 0;
}
// Add a new text display entry to display the status text at the bottom of the screen area
_statusTextIndex = scene._textDisplay.add(160 - (strWidth / 2),
MADS_SCENE_HEIGHT + scene._posAdjust.y - 13, 3, textSpacing, _statusText, font);
}
}
_textChanged = false;
}
void MADSAction::startAction() {
Game &game = *_vm->_game;
Player &player = game._player;
Scene &scene = _vm->_game->_scene;
DynamicHotspots &dynHotspots = scene._dynamicHotspots;
Hotspots &hotspots = scene._hotspots;
player.cancelCommand();
_inProgress = true;
_savedFields._commandError = false;
_savedFields._command = _selectedRow;
_savedFields._mainObject = _hotspotId;
_savedFields._secondObject = _secondObject;
_savedFields._articleNumber = _articleNumber;
_savedFields._commandSource = _commandSource;
_savedFields._mainObjectSource = _mainObjectSource;
_savedFields._secondObjectSource = _secondObjectSource;
_savedFields._lookFlag = _lookFlag;
_activeAction = _action;
// Copy the action to be active
_activeAction = _action;
_sentence = _statusText;
if ((_mainObjectSource == CAT_HOTSPOT) && (_secondObjectSource == 4))
_savedFields._commandError = true;
player._needToWalk = false;
int hotspotId = -1;
if (!_savedFields._lookFlag && (_vm->_game->_screenObjects._inputMode != kInputConversation)) {
if (_savedFields._mainObjectSource == CAT_HOTSPOT)
hotspotId = _savedFields._mainObject;
else if (_secondObjectSource == 4)
hotspotId = _savedFields._secondObject;
if (hotspotId >= (int)hotspots.size()) {
DynamicHotspot &hs = dynHotspots.get(hotspotId - hotspots.size());
if ((hs._feetPos.x == -1) || (hs._feetPos.x == -3)) {
startWalkingDirectly(hs._feetPos.x);
} else if (hs._feetPos.x < 0) {
player._prepareWalkFacing = hs._facing;
} else if (_savedFields._commandSource == CAT_NONE || hs._cursor < CURSOR_WAIT) {
player._needToWalk = true;
player._prepareWalkPos = hs._feetPos;
}
player._prepareWalkFacing = hs._facing;
hotspotId = -1;
}
}
if (hotspotId >= 0) {
Hotspot &hs = hotspots[hotspotId];
if (hs._feetPos.x == -1 || hs._feetPos.x == -3) {
startWalkingDirectly(hs._feetPos.x);
} else if (hs._feetPos.x >= 0) {
if (_savedFields._commandSource == CAT_NONE || hs._cursor < CURSOR_WAIT) {
player._needToWalk = true;
player._prepareWalkPos = hs._feetPos;
}
}
player._prepareWalkFacing = hs._facing;
}
player._readyToWalk = player._needToWalk;
}
void MADSAction::checkAction() {
if (isAction(VERB_LOOK) || isAction(VERB_THROW))
_vm->_game->_player._needToWalk = false;
}
bool MADSAction::isAction(int verbId, int objectNameId, int indirectObjectId) {
if (_activeAction._verbId != verbId)
return false;
if ((objectNameId != 0) && (_activeAction._objectNameId != objectNameId))
return false;
if ((indirectObjectId != 0) && (_activeAction._indirectObjectId != indirectObjectId))
return false;
return true;
}
bool MADSAction::isObject(int objectNameId) {
return _activeAction._objectNameId == objectNameId;
}
bool MADSAction::isTarget(int objectNameId) {
return _activeAction._indirectObjectId == objectNameId;
}
void MADSAction::checkActionAtMousePos() {
Scene &scene = _vm->_game->_scene;
UserInterface &userInterface = scene._userInterface;
if ((userInterface._category == CAT_COMMAND || userInterface._category == CAT_INV_VOCAB) &&
_interAwaiting != AWAITING_COMMAND && _pickedWord >= 0) {
if (_recentCommandSource == userInterface._category || _recentCommand != _pickedWord ||
(_interAwaiting != AWAITING_THIS && _interAwaiting != 3))
clear();
else if (_selectedRow != 0 || userInterface._category != CAT_COMMAND)
scene._lookFlag = false;
else
scene._lookFlag = true;
}
if (_vm->_events->_rightMousePressed && _vm->_events->_mouseButtons) {
switch (userInterface._category) {
case CAT_COMMAND:
case CAT_INV_VOCAB:
return;
case CAT_INV_LIST:
case CAT_HOTSPOT:
case CAT_INV_ANIM:
if (_interAwaiting != AWAITING_THAT) {
if (userInterface._selectedActionIndex >= 0) {
_commandSource = CAT_COMMAND;
_selectedRow = userInterface._selectedActionIndex;
_verbType = scene._verbList[_selectedRow]._verbType;
_prepType = scene._verbList[_selectedRow]._prepType;
_interAwaiting = AWAITING_THIS;
} else if (userInterface._selectedItemVocabIdx >= 0) {
_commandSource = CAT_INV_VOCAB;
_selectedRow = userInterface._selectedItemVocabIdx;
int objectId = _vm->_game->_objects._inventoryList[_selectedRow];
InventoryObject &invObject = _vm->_game->_objects[objectId];
_verbType = invObject._vocabList[_selectedRow - 1]._verbType;
_prepType = invObject._vocabList[_selectedRow - 1]._prepType;
_mainObjectSource = CAT_INV_LIST;
_hotspotId = userInterface._selectedInvIndex;
_articleNumber = _prepType;
if ((_verbType == VERB_THIS && _prepType == PREP_NONE) ||
(_verbType == VERB_THAT && _prepType != PREP_NONE))
_interAwaiting = AWAITING_RIGHT_MOUSE;
else
_interAwaiting = AWAITING_THAT;
}
}
break;
default:
break;
}
}
switch (_interAwaiting) {
case AWAITING_COMMAND:
_articleNumber = 0;
switch (userInterface._category) {
case CAT_COMMAND:
_commandSource = CAT_COMMAND;
_selectedRow = _pickedWord;
if (_selectedRow >= 0) {
_verbType = scene._verbList[_selectedRow]._verbType;
_prepType = scene._verbList[_selectedRow]._prepType;
}
break;
case CAT_INV_VOCAB:
_commandSource = CAT_INV_VOCAB;
_selectedRow = _pickedWord;
if (_selectedRow < 0) {
_hotspotId = -1;
_mainObjectSource = CAT_NONE;
} else {
InventoryObject &invObject = _vm->_game->_objects.getItem(userInterface._selectedInvIndex);
_verbType = invObject._vocabList[_selectedRow]._verbType;
_prepType = invObject._vocabList[_selectedRow]._prepType;
_hotspotId = userInterface._selectedInvIndex;
_mainObjectSource = CAT_INV_LIST;
if (_verbType == VERB_THAT)
_articleNumber = _prepType;
}
break;
case CAT_HOTSPOT:
_selectedRow = -1;
_commandSource = CAT_NONE;
_mainObjectSource = CAT_HOTSPOT;
_hotspotId = _pickedWord;
break;
case CAT_TALK_ENTRY:
_commandSource = CAT_TALK_ENTRY;
_selectedRow = _pickedWord;
break;
default:
break;
}
break;
case AWAITING_THIS:
_articleNumber = 0;
switch (userInterface._category) {
case CAT_INV_LIST:
case CAT_HOTSPOT:
case CAT_INV_ANIM:
_mainObjectSource = userInterface._category;
_hotspotId = _pickedWord;
break;
default:
break;
}
break;
case AWAITING_THAT:
switch (userInterface._category) {
case CAT_INV_LIST:
case CAT_HOTSPOT:
case CAT_INV_ANIM:
_secondObjectSource = userInterface._category;
_secondObject = _pickedWord;
break;
default:
break;
}
break;
default:
break;
}
}
void MADSAction::leftClick() {
Scene &scene = _vm->_game->_scene;
UserInterface &userInterface = scene._userInterface;
bool abortFlag = false;
if ((userInterface._category == CAT_COMMAND || userInterface._category == CAT_INV_VOCAB) &&
_interAwaiting != 1 && _pickedWord >= 0 &&
_recentCommandSource == userInterface._category && _recentCommand == _pickedWord &&
(_interAwaiting == 2 || userInterface._category == CAT_INV_VOCAB)) {
abortFlag = true;
if (_selectedRow == 0 && userInterface._category == CAT_COMMAND) {
_selectedAction = CAT_COMMAND;
scene._lookFlag = true;
} else {
_selectedAction = CAT_NONE;
scene._lookFlag = false;
clear();
}
}
if (abortFlag || (_vm->_events->_rightMousePressed && (userInterface._category == CAT_COMMAND ||
userInterface._category == CAT_INV_VOCAB)))
return;
switch (_interAwaiting) {
case AWAITING_COMMAND:
switch (userInterface._category) {
case CAT_COMMAND:
if (_selectedRow >= 0) {
if (_verbType == VERB_ONLY)
_selectedAction = -1;
else {
_recentCommand = _selectedRow;
_recentCommandSource = _commandSource;
_interAwaiting = AWAITING_THIS;
}
}
break;
case CAT_INV_LIST:
if (_pickedWord >= 0) {
userInterface.selectObject(_pickedWord);
}
break;
case CAT_INV_VOCAB:
if (_selectedRow >= 0) {
if (_verbType != VERB_THIS || _prepType != PREP_NONE) {
if (_verbType != VERB_THAT || _prepType == PREP_NONE) {
_interAwaiting = AWAITING_THAT;
_articleNumber = _prepType;
} else {
_articleNumber = _prepType;
_selectedAction = -1;
}
} else {
_selectedAction = -1;
}
_recentCommand = _selectedRow;
_recentCommandSource = _commandSource;
}
break;
case CAT_HOTSPOT:
_recentCommand = -1;
_recentCommandSource = CAT_NONE;
if (_vm->_events->currentPos().y < MADS_SCENE_HEIGHT) {
scene._customDest = _vm->_events->currentPos() + scene._posAdjust;
_selectedAction = -1;
_pointEstablished = true;
}
break;
case CAT_TALK_ENTRY:
if (_selectedRow >= 0)
_selectedAction = -1;
break;
default:
break;
}
break;
case AWAITING_THIS:
switch (userInterface._category) {
case CAT_INV_LIST:
case CAT_HOTSPOT:
case CAT_INV_ANIM:
if (_hotspotId >= 0) {
if (_prepType) {
_articleNumber = _prepType;
_interAwaiting = AWAITING_THAT;
} else {
_selectedAction = -1;
}
if (userInterface._category == CAT_HOTSPOT) {
scene._customDest = _vm->_events->mousePos() + scene._posAdjust;
_pointEstablished = true;
}
}
break;
default:
break;
}
break;
case AWAITING_THAT:
switch (userInterface._category) {
case CAT_INV_LIST:
case CAT_HOTSPOT:
case CAT_INV_ANIM:
if (_secondObject >= 0) {
_selectedAction = -1;
if (userInterface._category == CAT_HOTSPOT) {
if (!_pointEstablished) {
scene._customDest = _vm->_events->mousePos() + scene._posAdjust;
_pointEstablished = true;
}
}
}
break;
default:
break;
}
break;
default:
break;
}
}
void MADSAction::synchronize(Common::Serializer &s) {
_action.synchronize(s);
_activeAction.synchronize(s);
s.syncAsSint16LE(_articleNumber);
s.syncAsByte(_lookFlag);
s.syncAsByte(_textChanged);
s.syncAsSint16LE(_selectedRow);
s.syncAsSint16LE(_selectedAction);
s.syncAsSint16LE(_statusTextIndex);
s.syncAsSint16LE(_hotspotId);
_savedFields.synchronize(s);
// TODO: When saving in Rex Village Hut, _senetence size() doesn't match
// string length. Find out why not
_sentence = Common::String(_sentence.c_str());
s.syncString(_sentence);
s.syncAsSint16LE(_verbType);
s.syncAsSint16LE(_prepType);
s.syncAsSint16LE(_commandSource);
s.syncAsSint16LE(_mainObjectSource);
s.syncAsSint16LE(_secondObject);
s.syncAsSint16LE(_secondObjectSource);
s.syncAsSint16LE(_recentCommandSource);
s.syncAsSint16LE(_recentCommand);
s.syncAsSint16LE(_interAwaiting);
s.syncAsSint16LE(_pickedWord);
s.syncAsByte(_pointEstablished);
s.syncAsByte(_inProgress);
}
} // End of namespace MADS