scummvm/engines/mutationofjb/commands/endblockcommand.cpp
2021-12-26 18:48:43 +01:00

259 lines
8.1 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 "mutationofjb/commands/endblockcommand.h"
#include "mutationofjb/script.h"
#include "mutationofjb/commands/conditionalcommand.h"
#include "common/str.h"
#include "common/debug.h"
/** @file
* <look> | <walk> | <talk> | <pickup> | <use> | <else> | <macro> | <extra> | <endRandom>
*
* look ::= ("#L " | "-L ") <object>
* walk ::= ("#W " | "-W ") <object>
* talk ::= ("#T " | "-T ") <object>
* pickup ::= ("#P " | "-P ") <object1>
* use ::= ("#U " | "-U ") <object1> [<object2>]
* else ::= ("#ELSE" | "-ELSE") [<tag>]
* macro ::= "#MACRO " <name>
* extra ::= "#EXTRA" <name>
* endRandom ::= "\"
*
* If a line starts with '#', '=', '-', '\' it is treated as the end of a section.
* However, at the same time it can also start a new section depending on what follows.
*
* #L (look), #W (walk), #T (talk), #U (use) sections are executed
* when the user starts corresponding action on the object or in case of "use" up to two objects.
* The difference between '#' and '-' version is whether the player walks towards the object ('#') or not ('-').
*
* #ELSE is used by conditional commands (see comments for IfCommand and others).
*
* #MACRO starts a new macro. Global script can call macros from local script and vice versa.
*
* #EXTRA defines an "extra" section. This is called from dialog responses ("TALK TO HIM" command).
*
* TODO: TIMERPROC.
*/
namespace MutationOfJB {
bool EndBlockCommandParser::parse(const Common::String &line, ScriptParseContext &parseCtx, Command *&command) {
if (line.empty()) {
return false;
}
const char firstChar = line.firstChar();
if (firstChar != '#' && firstChar != '=' && firstChar != '-' && firstChar != '\\') {
return false;
}
// This is the start or end of section/block.
command = new EndBlockCommand();
if (line.size() >= 4 && (line.hasPrefix("#L ") || line.hasPrefix("-L "))) {
ActionInfo ai = {ActionInfo::Look, line.c_str() + 3, "", firstChar == '#', nullptr};
parseCtx._actionInfos.push_back(ai);
_pendingActionInfos.push_back(parseCtx._actionInfos.size() - 1);
} else if (line.size() >= 4 && (line.hasPrefix("#W ") || line.hasPrefix("-W "))) {
ActionInfo ai = {ActionInfo::Walk, line.c_str() + 3, "", firstChar == '#', nullptr};
parseCtx._actionInfos.push_back(ai);
_pendingActionInfos.push_back(parseCtx._actionInfos.size() - 1);
} else if (line.size() >= 4 && (line.hasPrefix("#T ") || line.hasPrefix("-T "))) {
ActionInfo ai = {ActionInfo::Talk, line.c_str() + 3, "", firstChar == '#', nullptr};
parseCtx._actionInfos.push_back(ai);
_pendingActionInfos.push_back(parseCtx._actionInfos.size() - 1);
} else if (line.size() >= 4 && (line.hasPrefix("#P ") || line.hasPrefix("-P "))) {
ActionInfo ai = {ActionInfo::PickUp, line.c_str() + 3, "", firstChar == '#', nullptr};
parseCtx._actionInfos.push_back(ai);
_pendingActionInfos.push_back(parseCtx._actionInfos.size() - 1);
} else if (line.size() >= 4 && (line.hasPrefix("#U ") || line.hasPrefix("-U "))) {
int secondObjPos = -1;
for (uint i = 3; i < line.size(); ++i) {
if (line[i] == ' ') {
secondObjPos = i + 1;
break;
}
}
Common::String obj1;
Common::String obj2;
if (secondObjPos == -1) {
obj1 = line.c_str() + 3;
} else {
obj1 = Common::String(line.c_str() + 3, secondObjPos - 4);
obj2 = line.c_str() + secondObjPos;
}
ActionInfo ai = {
ActionInfo::Use,
obj1,
obj2,
firstChar == '#',
nullptr
};
parseCtx._actionInfos.push_back(ai);
_pendingActionInfos.push_back(parseCtx._actionInfos.size() - 1);
} else if ((line.hasPrefix("#ELSE") || line.hasPrefix("=ELSE"))) {
_elseFound = true;
_ifTag = 0;
if (line.size() >= 6) {
_ifTag = line[5];
}
} else if (line.size() >= 8 && line.hasPrefix("#MACRO")) {
NameAndCommand nc = {line.c_str() + 7, command};
_foundMacros.push_back(nc);
} else if (line.size() >= 10 && line.hasPrefix("#STARTUP")) {
const uint8 startupId = atoi(line.c_str() + 9);
IdAndCommand ic = {startupId, command};
_foundStartups.push_back(ic);
} else if (line.size() >= 7 && line.hasPrefix("#EXTRA")) {
NameAndCommand nc = {line.c_str() + 6, command};
_foundExtras.push_back(nc);
}
if (firstChar == '#') {
_hashFound = true;
}
return true;
}
void EndBlockCommandParser::transition(ScriptParseContext &parseCtx, Command *oldCommand, Command *newCommand, CommandParser *newCommandParser) {
if (_elseFound || _hashFound) {
if (newCommand) {
ScriptParseContext::ConditionalCommandInfos::iterator it = parseCtx._pendingCondCommands.begin();
while (it != parseCtx._pendingCondCommands.end()) {
if ((it->_firstHash && _hashFound) || (!it->_firstHash && it->_tag == _ifTag)) {
it->_command->setFalseCommand(newCommand);
it = parseCtx._pendingCondCommands.erase(it);
} else {
++it;
}
}
}
_elseFound = false;
_hashFound = false;
_ifTag = 0;
}
if (!_foundMacros.empty()) {
if (newCommand) {
for (NameAndCommandArray::iterator it = _foundMacros.begin(); it != _foundMacros.end();) {
if (it->_command != oldCommand) {
it++;
continue;
}
if (!parseCtx._macros.contains(it->_name)) {
parseCtx._macros[it->_name] = newCommand;
} else {
warning("Macro '%s' already exists", it->_name.c_str());
}
it = _foundMacros.erase(it);
}
}
}
if (!_foundStartups.empty()) {
if (newCommand) {
for (IdAndCommandArray::iterator it = _foundStartups.begin(); it != _foundStartups.end();) {
if (it->_command != oldCommand) {
it++;
continue;
}
if (!parseCtx._startups.contains(it->_id)) {
parseCtx._startups[it->_id] = newCommand;
} else {
warning("Startup %u already exists", (unsigned int) it->_id);
}
it = _foundStartups.erase(it);
}
}
}
if (!_foundExtras.empty()) {
if (newCommand) {
for (NameAndCommandArray::iterator it = _foundExtras.begin(); it != _foundExtras.end();) {
if (it->_command != oldCommand) {
it++;
continue;
}
if (!parseCtx._extras.contains(it->_name)) {
parseCtx._extras[it->_name] = newCommand;
} else {
warning("Extra '%s' already exists", it->_name.c_str());
}
it = _foundExtras.erase(it);
}
}
}
if (newCommandParser != this) {
if (!_pendingActionInfos.empty()) {
for (Common::Array<uint>::iterator it = _pendingActionInfos.begin(); it != _pendingActionInfos.end(); ++it) {
parseCtx._actionInfos[*it]._command = newCommand;
}
_pendingActionInfos.clear();
}
}
}
void EndBlockCommandParser::finish(ScriptParseContext &) {
_elseFound = false;
_hashFound = false;
_ifTag = 0;
if (!_pendingActionInfos.empty()) {
debug("Problem: Pending action infos from end block parser is not empty!");
}
if (!_foundMacros.empty()) {
debug("Problem: Found macros from end block parser is not empty!");
}
if (!_foundStartups.empty()) {
debug("Problem: Found startups from end block parser is not empty!");
}
if (!_foundExtras.empty()) {
debug("Problem: Found extras from end block parser is not empty!");
}
_pendingActionInfos.clear();
_foundMacros.clear();
_foundStartups.clear();
_foundExtras.clear();
}
Command::ExecuteResult EndBlockCommand::execute(ScriptExecutionContext &scriptExecCtx) {
_nextCmd = scriptExecCtx.popReturnCommand();
return Finished;
}
Command *EndBlockCommand::next() const {
return _nextCmd;
}
Common::String EndBlockCommand::debugString() const {
return "ENDBLOCK";
}
}