diff options
Diffstat (limited to 'engines/hugo')
-rw-r--r-- | engines/hugo/hugo.cpp | 7 | ||||
-rw-r--r-- | engines/hugo/hugo.h | 2 | ||||
-rw-r--r-- | engines/hugo/parser.cpp | 764 | ||||
-rw-r--r-- | engines/hugo/parser.h | 54 |
4 files changed, 625 insertions, 202 deletions
diff --git a/engines/hugo/hugo.cpp b/engines/hugo/hugo.cpp index 58629b5189..acc047b68d 100644 --- a/engines/hugo/hugo.cpp +++ b/engines/hugo/hugo.cpp @@ -154,7 +154,6 @@ Common::Error HugoEngine::run() { _mouseHandler = new MouseHandler(*this); _inventoryHandler = new InventoryHandler(*this); - _parser = new Parser(*this); _route = new Route(*this); _soundHandler = new SoundHandler(*this); @@ -164,36 +163,42 @@ Common::Error HugoEngine::run() { _scheduler = new Scheduler_v3d(*this); _introHandler = new intro_v1w(*this); _screen = new Screen_v1w(*this); + _parser = new Parser_v1w(*this); break; case 1: _fileManager = new FileManager_v2d(*this); _scheduler = new Scheduler_v3d(*this); _introHandler = new intro_v2w(*this); _screen = new Screen_v1w(*this); + _parser = new Parser_v1w(*this); break; case 2: _fileManager = new FileManager_v2d(*this); _scheduler = new Scheduler_v3d(*this); _introHandler = new intro_v3w(*this); _screen = new Screen_v1w(*this); + _parser = new Parser_v1w(*this); break; case 3: // H1 DOS _fileManager = new FileManager_v1d(*this); _scheduler = new Scheduler_v1d(*this); _introHandler = new intro_v1d(*this); _screen = new Screen_v1d(*this); + _parser = new Parser_v1d(*this); break; case 4: _fileManager = new FileManager_v2d(*this); _scheduler = new Scheduler_v1d(*this); _introHandler = new intro_v2d(*this); _screen = new Screen_v1d(*this); + _parser = new Parser_v2d(*this); break; case 5: _fileManager = new FileManager_v3d(*this); _scheduler = new Scheduler_v3d(*this); _introHandler = new intro_v3d(*this); _screen = new Screen_v1d(*this); + _parser = new Parser_v1w(*this); break; } diff --git a/engines/hugo/hugo.h b/engines/hugo/hugo.h index 9ba7b47b22..9801546f34 100644 --- a/engines/hugo/hugo.h +++ b/engines/hugo/hugo.h @@ -33,7 +33,7 @@ #include "hugo/game.h" #define HUGO_DAT_VER_MAJ 0 // 1 byte -#define HUGO_DAT_VER_MIN 23 // 1 byte +#define HUGO_DAT_VER_MIN 24 // 1 byte #define DATAALIGNMENT 4 namespace Common { diff --git a/engines/hugo/parser.cpp b/engines/hugo/parser.cpp index 59fe023788..a232e9df96 100644 --- a/engines/hugo/parser.cpp +++ b/engines/hugo/parser.cpp @@ -56,6 +56,9 @@ Parser::Parser(HugoEngine &vm) : _vm(vm), _putIndex(0), _getIndex(0), _checkDoubleF1Fl(false) { } +Parser::~Parser() { +} + void Parser::keyHandler(uint16 nChar, uint16 nFlags) { debugC(1, kDebugParser, "keyHandler(%d, %d)", nChar, nFlags); @@ -202,8 +205,218 @@ void Parser::command(const char *format, ...) { lineHandler(); } +Parser_v1w::Parser_v1w(HugoEngine &vm) : Parser(vm) { +} + +Parser_v1w::~Parser_v1w() { +} + +// Test whether command line contains a verb allowed by this object. +// If it does, and the object is near and passes the tests in the command +// list then carry out the actions in the action list and return TRUE +bool Parser_v1w::isObjectVerb(object_t *obj, char *comment) { + debugC(1, kDebugParser, "isObjectVerb(object_t *obj, %s)", comment); + + // First, find matching verb in cmd list + uint16 cmdIndex = obj->cmdIndex; // ptr to list of commands + if (cmdIndex == 0) // No commands for this obj + return false; + + int i; + for (i = 0; _vm._cmdList[cmdIndex][i].verbIndex != 0; i++) { // For each cmd + if (isWordPresent(_vm._arrayVerbs[_vm._cmdList[cmdIndex][i].verbIndex])) // Was this verb used? + break; + } + + if (_vm._cmdList[cmdIndex][i].verbIndex == 0) // No verbs used. + return false; + + // Verb match found. Check if object is Near + char *verb = *_vm._arrayVerbs[_vm._cmdList[cmdIndex][i].verbIndex]; + if (!isNear(obj, verb, comment)) + return false; + + // Check all required objects are being carried + cmd *cmnd = &_vm._cmdList[cmdIndex][i]; // ptr to struct cmd + if (cmnd->reqIndex) { // At least 1 thing in list + uint16 *reqs = _vm._arrayReqs[cmnd->reqIndex]; // ptr to list of required objects + for (i = 0; reqs[i]; i++) { // for each obj + if (!isCarrying(reqs[i])) { + Utils::Box(BOX_ANY, "%s", _vm._textData[cmnd->textDataNoCarryIndex]); + return true; + } + } + } + + // Required objects are present, now check state is correct + if ((obj->state != cmnd->reqState) && (cmnd->reqState != DONT_CARE)) { + Utils::Box(BOX_ANY, "%s", _vm._textData[cmnd->textDataWrongIndex]); + return true; + } + + // Everything checked. Change the state and carry out any actions + if (cmnd->reqState != DONT_CARE) // Don't change new state if required state didn't care + obj->state = cmnd->newState; + Utils::Box(BOX_ANY, "%s", _vm._textData[cmnd->textDataDoneIndex]); + _vm.scheduler().insertActionList(cmnd->actIndex); + + // See if any additional generic actions + if ((verb == _vm._arrayVerbs[_vm._look][0]) || (verb == _vm._arrayVerbs[_vm._take][0]) || (verb == _vm._arrayVerbs[_vm._drop][0])) + isGenericVerb(obj, comment); + return true; +} + +// Test whether command line contains one of the generic actions +bool Parser_v1w::isGenericVerb(object_t *obj, char *comment) { + debugC(1, kDebugParser, "isGenericVerb(object_t *obj, %s)", comment); + + if (!obj->genericCmd) + return false; + + // Following is equivalent to switch, but couldn't do one + if (isWordPresent(_vm._arrayVerbs[_vm._look]) && isNear(obj, _vm._arrayVerbs[_vm._look][0], comment)) { + // Test state-dependent look before general look + if ((obj->genericCmd & LOOK_S) == LOOK_S) { + Utils::Box(BOX_ANY, "%s", _vm._textData[obj->stateDataIndex[obj->state]]); + warning("isGenericVerb: use of state dependant look - To be validated"); + } else { + if ((LOOK & obj->genericCmd) == LOOK) { + if (_vm._textData[obj->dataIndex]) + Utils::Box(BOX_ANY, "%s", _vm._textData[obj->dataIndex]); + else + return false; + } else { + Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBUnusual]); + } + } + } else if (isWordPresent(_vm._arrayVerbs[_vm._take]) && isNear(obj, _vm._arrayVerbs[_vm._take][0], comment)) { + if (obj->carriedFl) + Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBHave]); + else if ((TAKE & obj->genericCmd) == TAKE) + takeObject(obj); + else if (obj->cmdIndex != 0) // No comment if possible commands + return false; + else if (!obj->verbOnlyFl && (TAKE & obj->genericCmd) == TAKE) // Make sure not taking object in context! + Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBNoUse]); + else + return false; + } else if (isWordPresent(_vm._arrayVerbs[_vm._drop])) { + if (!obj->carriedFl && ((DROP & obj->genericCmd) == DROP)) + Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBDontHave]); + else if (obj->carriedFl && ((DROP & obj->genericCmd) == DROP)) + dropObject(obj); + else if (obj->cmdIndex == 0) + Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBNeed]); + else + return false; + } else { // It was not a generic cmd + return false; + } + + return true; +} + +// Test whether hero is close to object. Return TRUE or FALSE +// If object not near, return suitable comment; may be another object close +// If radius is -1, treat radius as infinity +// Verb is included to determine correct comment if not near +bool Parser_v1w::isNear(object_t *obj, char *verb, char *comment) { + debugC(1, kDebugParser, "isNear(object_t *obj, %s, %s)", verb, comment); + + if (obj->carriedFl) // Object is being carried + return true; + + if (obj->screenIndex != *_vm._screen_p) { + // Not in same screen + if (obj->objValue) + strcpy(comment, _vm._textParser[kCmtAny1]); + else + strcpy(comment, _vm._textParser[kCmtAny2]); + return false; + } + + if (obj->cycling == INVISIBLE) { + if (obj->seqNumb) { + // There is an image + strcpy(comment, _vm._textParser[kCmtAny3]); + return false; + } else { + // No image, assume visible + if ((obj->radius < 0) || + ((abs(obj->x - _vm._hero->x) <= obj->radius) && + (abs(obj->y - _vm._hero->y - _vm._hero->currImagePtr->y2) <= obj->radius))) { + return true; + } else { + // User is not close enough + if (obj->objValue && (verb != _vm._arrayVerbs[_vm._take][0])) + strcpy(comment, _vm._textParser[kCmtAny1]); + else + strcpy(comment, _vm._textParser[kCmtClose]); + return false; + } + } + } + + if ((obj->radius < 0) || + ((abs(obj->x - _vm._hero->x) <= obj->radius) && + (abs(obj->y + obj->currImagePtr->y2 - _vm._hero->y - _vm._hero->currImagePtr->y2) <= obj->radius))) { + return true; + } else { + // User is not close enough + if (obj->objValue && (verb != _vm._arrayVerbs[_vm._take][0])) + strcpy(comment, _vm._textParser[kCmtAny1]); + else + strcpy(comment, _vm._textParser[kCmtClose]); + return false; + } + return true; +} + +// Search for matching verbs in background command list. +// Noun is not required. Return TRUE if match found +// Note that if the background command list has match set TRUE then do not +// print text if there are any recognizable nouns in the command line +bool Parser_v1w::isCatchallVerb(objectList_t obj) { + debugC(1, kDebugParser, "isCatchallVerb(object_list_t obj)"); + + for (int i = 0; obj[i].verbIndex != 0; i++) { + if (isWordPresent(_vm._arrayVerbs[obj[i].verbIndex]) && obj[i].nounIndex == 0 && + (!obj[i].matchFl || !findNoun()) && + ((obj[i].roomState == DONT_CARE) || + (obj[i].roomState == _vm._screenStates[*_vm._screen_p]))) { + Utils::Box(BOX_ANY, "%s", _vm.file().fetchString(obj[i].commentIndex)); + _vm.scheduler().processBonus(obj[i].bonusIndex); + + // If this is LOOK (without a noun), show any takeable objects + if (*(_vm._arrayVerbs[obj[i].verbIndex]) == _vm._arrayVerbs[_vm._look][0]) + showTakeables(); + + return true; + } + } + return false; +} + +// Search for matching verb/noun pairs in background command list +// Print text for possible background object. Return TRUE if match found +bool Parser_v1w::isBackgroundWord(objectList_t obj) { + debugC(1, kDebugParser, "isBackgroundWord(object_list_t obj)"); + + for (int i = 0; obj[i].verbIndex != 0; i++) { + if (isWordPresent(_vm._arrayVerbs[obj[i].verbIndex]) && + isWordPresent(_vm._arrayNouns[obj[i].nounIndex]) && + ((obj[i].roomState == DONT_CARE) || + (obj[i].roomState == _vm._screenStates[*_vm._screen_p]))) { + Utils::Box(BOX_ANY, "%s", _vm.file().fetchString(obj[i].commentIndex)); + _vm.scheduler().processBonus(obj[i].bonusIndex); + return true; + } + } + return false; +} + // Parse the user's line of text input. Generate events as necessary -void Parser::lineHandler() { +void Parser_v1w::lineHandler() { debugC(1, kDebugParser, "lineHandler"); status_t &gameStatus = _vm.getGameStatus(); @@ -349,105 +562,6 @@ void Parser::lineHandler() { } } -// Search for matching verb/noun pairs in background command list -// Print text for possible background object. Return TRUE if match found -bool Parser::isBackgroundWord(objectList_t obj) { - debugC(1, kDebugParser, "isBackgroundWord(object_list_t obj)"); - - for (int i = 0; obj[i].verbIndex != 0; i++) { - if (isWordPresent(_vm._arrayVerbs[obj[i].verbIndex]) && - isWordPresent(_vm._arrayNouns[obj[i].nounIndex]) && - ((obj[i].roomState == DONT_CARE) || - (obj[i].roomState == _vm._screenStates[*_vm._screen_p]))) { - Utils::Box(BOX_ANY, "%s", _vm.file().fetchString(obj[i].commentIndex)); - _vm.scheduler().processBonus(obj[i].bonusIndex); - return true; - } - } - return false; -} - -// Search for matching verbs in background command list. -// Noun is not required. Return TRUE if match found -// Note that if the background command list has match set TRUE then do not -// print text if there are any recognizable nouns in the command line -bool Parser::isCatchallVerb(objectList_t obj) { - debugC(1, kDebugParser, "isCatchallVerb(object_list_t obj)"); - - for (int i = 0; obj[i].verbIndex != 0; i++) { - if (isWordPresent(_vm._arrayVerbs[obj[i].verbIndex]) && obj[i].nounIndex == 0 && - (!obj[i].matchFl || !findNoun()) && - ((obj[i].roomState == DONT_CARE) || - (obj[i].roomState == _vm._screenStates[*_vm._screen_p]))) { - Utils::Box(BOX_ANY, "%s", _vm.file().fetchString(obj[i].commentIndex)); - _vm.scheduler().processBonus(obj[i].bonusIndex); - - // If this is LOOK (without a noun), show any takeable objects - if (*(_vm._arrayVerbs[obj[i].verbIndex]) == _vm._arrayVerbs[_vm._look][0]) - showTakeables(); - - return true; - } - } - return false; -} - -// Test whether hero is close to object. Return TRUE or FALSE -// If object not near, return suitable comment; may be another object close -// If radius is -1, treat radius as infinity -// Verb is included to determine correct comment if not near -bool Parser::isNear(object_t *obj, char *verb, char *comment) { - debugC(1, kDebugParser, "isNear(object_t *obj, %s, %s)", verb, comment); - - if (obj->carriedFl) // Object is being carried - return true; - - if (obj->screenIndex != *_vm._screen_p) { - // Not in same screen - if (obj->objValue) - strcpy(comment, _vm._textParser[kCmtAny1]); - else - strcpy(comment, _vm._textParser[kCmtAny2]); - return false; - } - - if (obj->cycling == INVISIBLE) { - if (obj->seqNumb) { - // There is an image - strcpy(comment, _vm._textParser[kCmtAny3]); - return false; - } else { - // No image, assume visible - if ((obj->radius < 0) || - ((abs(obj->x - _vm._hero->x) <= obj->radius) && - (abs(obj->y - _vm._hero->y - _vm._hero->currImagePtr->y2) <= obj->radius))) { - return true; - } else { - // User is not close enough - if (obj->objValue && (verb != _vm._arrayVerbs[_vm._take][0])) - strcpy(comment, _vm._textParser[kCmtAny1]); - else - strcpy(comment, _vm._textParser[kCmtClose]); - return false; - } - } - } - - if ((obj->radius < 0) || - ((abs(obj->x - _vm._hero->x) <= obj->radius) && - (abs(obj->y + obj->currImagePtr->y2 - _vm._hero->y - _vm._hero->currImagePtr->y2) <= obj->radius))) { - return true; - } else { - // User is not close enough - if (obj->objValue && (verb != _vm._arrayVerbs[_vm._take][0])) - strcpy(comment, _vm._textParser[kCmtAny1]); - else - strcpy(comment, _vm._textParser[kCmtClose]); - return false; - } - return true; -} - // Locate any member of object name list appearing in command line bool Parser::isWordPresent(char **wordArr) { debugC(1, kDebugParser, "isWordPresent(%s)", wordArr[0]); @@ -494,8 +608,8 @@ void Parser::showTakeables() { for (int j = 0; j < _vm._numObj; j++) { object_t *obj = &_vm._objects[j]; if ((obj->cycling != INVISIBLE) && - (obj->screenIndex == *_vm._screen_p) && - (((TAKE & obj->genericCmd) == TAKE) || obj->objValue)) { + (obj->screenIndex == *_vm._screen_p) && + (((TAKE & obj->genericCmd) == TAKE) || obj->objValue)) { Utils::Box(BOX_ANY, "You can also see:\n%s.", _vm._arrayNouns[obj->nounIndex][LOOK_NAME]); } } @@ -535,49 +649,168 @@ void Parser::dropObject(object_t *obj) { Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBOk]); } -// Test whether command line contains one of the generic actions -bool Parser::isGenericVerb(object_t *obj, char *comment) { - debugC(1, kDebugParser, "isGenericVerb(object_t *obj, %s)", comment); +// Return TRUE if object being carried by hero +bool Parser::isCarrying(uint16 wordIndex) { + debugC(1, kDebugParser, "isCarrying(%d)", wordIndex); - if (!obj->genericCmd) + for (int i = 0; i < _vm._numObj; i++) { + if ((wordIndex == _vm._objects[i].nounIndex) && _vm._objects[i].carriedFl) + return true; + } + return false; +} + +void Parser::showDosInventory() { +// Show user all objects being carried in a variable width 2 column format + static const char *blanks = " "; + uint16 index = 0, len1 = 0, len2 = 0; + + for (int i = 0; i < _vm._numObj; i++) { // Find widths of 2 columns + if (_vm._objects[i].carriedFl) { + uint16 len = strlen(_vm._arrayNouns[_vm._objects[i].nounIndex][1]); + if (index++ & 1) // Right hand column + len2 = (len > len2) ? len : len2; + else + len1 = (len > len1) ? len : len1; + } + } + len1 += 1; // For gap between columns + + if (len1 + len2 < (uint16)strlen(_vm._textParser[kTBOutro])) + len1 = strlen(_vm._textParser[kTBOutro]); + + char buffer[XBYTES *NUM_ROWS] = "\0"; + strncat(buffer, blanks, (len1 + len2 - strlen(_vm._textParser[kTBIntro])) / 2); + strcat(strcat(buffer, _vm._textParser[kTBIntro]), "\n"); + index = 0; + for (int i = 0; i < _vm._numObj; i++) { // Assign strings + if (_vm._objects[i].carriedFl) { + if (index++ & 1) + strcat(strcat(buffer, _vm._arrayNouns[_vm._objects[i].nounIndex][1]), "\n"); + else + strncat(strcat(buffer, _vm._arrayNouns[_vm._objects[i].nounIndex][1]), blanks, len1 - strlen(_vm._arrayNouns[_vm._objects[i].nounIndex][1])); + } + } + if (index & 1) + strcat(buffer, "\n"); + strcat(buffer, _vm._textParser[kTBOutro]); + + Utils::Box(BOX_ANY, "%s", buffer); +} + +Parser_v1d::Parser_v1d(HugoEngine &vm) : Parser(vm) { +} + +Parser_v1d::~Parser_v1d() { +} + +// Locate word in list of nouns and return ptr to string in noun list +// If n is NULL, start at beginning of list, else with n +char *Parser_v1d::findNextNoun(char *n) { + int k = -1; + if (n) { // If n not NULL, find index + for (k = 0; _vm._arrayNouns[k]; k++) { + if (n == _vm._arrayNouns[k][0]) + break; + } + } + for (int i = k + 1; _vm._arrayNouns[i]; i++) { + for (int j = 0; strlen(_vm._arrayNouns[i][j]); j++) { + if (strstr(_line, _vm._arrayNouns[i][j])) + return _vm._arrayNouns[i][0]; + } + } + return 0; +} + +// Test whether hero is close to object. Return TRUE or FALSE +// If no noun specified, check context flag in object before other tests. +// If object not near, return suitable string; may be similar object closer +// If radius is -1, treat radius as infinity +bool Parser_v1d::isNear(char *verb, char *noun, object_t *obj, char *comment) { + if (!noun && !obj->verbOnlyFl) { // No noun specified & object not context senesitive + return false; + } else if (noun && (noun != _vm._arrayNouns[obj->nounIndex][0])) { // Noun specified & not same as object + return false; + } else if (obj->carriedFl) { // Object is being carried + return true; + } else if (obj->screenIndex != *_vm._screen_p) { // Not in same screen + if (obj->objValue) + strcpy (comment, "You don't have it!"); return false; + } - // Following is equivalent to switch, but couldn't do one - if (isWordPresent(_vm._arrayVerbs[_vm._look]) && isNear(obj, _vm._arrayVerbs[_vm._look][0], comment)) { - // Test state-dependent look before general look - if ((obj->genericCmd & LOOK_S) == LOOK_S) { - Utils::Box(BOX_ANY, "%s", _vm._textData[obj->stateDataIndex[obj->state]]); - warning("isGenericVerb: use of state dependant look - To be validated"); - } else { - if ((LOOK & obj->genericCmd) == LOOK) { - if (_vm._textData[obj->dataIndex]) - Utils::Box(BOX_ANY, "%s", _vm._textData[obj->dataIndex]); - else - return false; + if (obj->cycling == INVISIBLE) { + if (obj->seqNumb) { // There is an image + strcpy(comment, "I don't see it anywhere"); + return false; + } else { // No image, assume visible + if ((obj->radius < 0) || + ((abs(obj->x - _vm._hero->x) <= obj->radius) && + (abs(obj->y - _vm._hero->y - _vm._hero->currImagePtr->y2) <= obj->radius))) { + return true; } else { - Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBUnusual]); + // User is either not close enough (stationary, valueless objects) + // or is not carrying it (small, portable objects of value) + if (noun) { // Don't say unless object specified + if (obj->objValue && (verb != _vm._arrayVerbs[_vm._take][0])) + strcpy(comment, "You don't have it!"); + else + strcpy(comment, "You're not close enough!"); + } + return false; } } - } else if (isWordPresent(_vm._arrayVerbs[_vm._take]) && isNear(obj, _vm._arrayVerbs[_vm._take][0], comment)) { + } + + if ((obj->radius < 0) || + ((abs(obj->x - _vm._hero->x) <= obj->radius) && + (abs(obj->y + obj->currImagePtr->y2 - _vm._hero->y - _vm._hero->currImagePtr->y2) <= obj->radius))) { + return true; + } else { + // User is either not close enough (stationary, valueless objects) + // or is not carrying it (small, portable objects of value) + if (noun) { // Don't say unless object specified + if (obj->objValue && (verb != _vm._arrayVerbs[_vm._take][0])) + strcpy(comment, "You don't have it!"); + else + strcpy(comment, "You're not close enough!"); + } + return false; + } + + return true; +} + +// Test whether supplied verb is one of the common variety for this object +// say_ok needed for special case of take/drop which may be handled not only +// here but also in a cmd_list with a donestr string simultaneously +bool Parser_v1d::isGenericVerb(char *word, object_t *obj) { + if (!obj->genericCmd) + return false; + + // Following is equivalent to switch, but couldn't do one + if (word == _vm._arrayVerbs[_vm._look][0]) { + if ((LOOK & obj->genericCmd) == LOOK) + Utils::Box(BOX_ANY, "%s", _vm._textData[obj->dataIndex]); + else + Utils::Box(BOX_ANY, "I see nothing special about it"); + } else if (word == _vm._arrayVerbs[_vm._take][0]) { if (obj->carriedFl) - Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBHave]); + Utils::Box(BOX_ANY, "You already have it"); else if ((TAKE & obj->genericCmd) == TAKE) takeObject(obj); - else if (obj->cmdIndex != 0) // No comment if possible commands - return false; - else if (!obj->verbOnlyFl && (TAKE & obj->genericCmd) == TAKE) // Make sure not taking object in context! - Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBNoUse]); + else if (!obj->verbOnlyFl) // Make sure not taking object in context! + Utils::Box(BOX_ANY, "It is of no use to you"); else return false; - } else if (isWordPresent(_vm._arrayVerbs[_vm._drop])) { - if (!obj->carriedFl && ((DROP & obj->genericCmd) == DROP)) - Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBDontHave]); - else if (obj->carriedFl && ((DROP & obj->genericCmd) == DROP)) + } else if (word == _vm._arrayVerbs[_vm._drop][0]) { + if (!obj->carriedFl) + Utils::Box(BOX_ANY, "You don't have it"); + else if ((DROP & obj->genericCmd) == DROP) dropObject(obj); - else if (obj->cmdIndex == 0) - Utils::Box(BOX_ANY, "%s", _vm._textParser[kTBNeed]); else - return false; + Utils::Box(BOX_ANY, "No! You'll be needing it"); } else { // It was not a generic cmd return false; } @@ -585,46 +818,31 @@ bool Parser::isGenericVerb(object_t *obj, char *comment) { return true; } -// Return TRUE if object being carried by hero -bool Parser::isCarrying(uint16 wordIndex) { - debugC(1, kDebugParser, "isCarrying(%d)", wordIndex); - - for (int i = 0; i < _vm._numObj; i++) { - if ((wordIndex == _vm._objects[i].nounIndex) && _vm._objects[i].carriedFl) - return true; - } - return false; -} - -// Test whether command line contains a verb allowed by this object. -// If it does, and the object is near and passes the tests in the command -// list then carry out the actions in the action list and return TRUE -bool Parser::isObjectVerb(object_t *obj, char *comment) { - debugC(1, kDebugParser, "isObjectVerb(object_t *obj, %s)", comment); +// Test whether supplied verb is included in the list of allowed verbs for +// this object. If it is, then perform the tests on it from the cmd list +// and if it passes, perform the actions in the action list. If the verb +// is catered for, return TRUE +bool Parser_v1d::isObjectVerb(char *word, object_t *obj) { +//actlist *actions; // First, find matching verb in cmd list uint16 cmdIndex = obj->cmdIndex; // ptr to list of commands - if (cmdIndex == 0) // No commands for this obj + if (!cmdIndex) // No commands for this obj return false; int i; - for (i = 0; _vm._cmdList[cmdIndex][i].verbIndex != 0; i++) { // For each cmd - if (isWordPresent(_vm._arrayVerbs[_vm._cmdList[cmdIndex][i].verbIndex])) // Was this verb used? + for (i = 0; _vm._cmdList[cmdIndex][i].verbIndex != 0; i++) { // For each cmd + if (!strcmp(word, _vm._arrayVerbs[_vm._cmdList[cmdIndex][i].verbIndex][0])) // Is this verb catered for? break; } - if (_vm._cmdList[cmdIndex][i].verbIndex == 0) // No verbs used. - return false; - - // Verb match found. Check if object is Near - char *verb = *_vm._arrayVerbs[_vm._cmdList[cmdIndex][i].verbIndex]; - if (!isNear(obj, verb, comment)) + if (_vm._cmdList[cmdIndex][i].verbIndex == 0) // No return false; - // Check all required objects are being carried + // Verb match found, check all required objects are being carried cmd *cmnd = &_vm._cmdList[cmdIndex][i]; // ptr to struct cmd if (cmnd->reqIndex) { // At least 1 thing in list - uint16 *reqs = _vm._arrayReqs[cmnd->reqIndex]; // ptr to list of required objects + uint16 *reqs = _vm._arrayReqs[cmnd->reqIndex]; // ptr to list of required objects for (i = 0; reqs[i]; i++) { // for each obj if (!isCarrying(reqs[i])) { Utils::Box(BOX_ANY, "%s", _vm._textData[cmnd->textDataNoCarryIndex]); @@ -634,7 +852,7 @@ bool Parser::isObjectVerb(object_t *obj, char *comment) { } // Required objects are present, now check state is correct - if ((obj->state != cmnd->reqState) && (cmnd->reqState != DONT_CARE)) { + if ((obj->state != cmnd->reqState) && (cmnd->reqState != DONT_CARE)){ Utils::Box(BOX_ANY, "%s", _vm._textData[cmnd->textDataWrongIndex]); return true; } @@ -644,49 +862,211 @@ bool Parser::isObjectVerb(object_t *obj, char *comment) { obj->state = cmnd->newState; Utils::Box(BOX_ANY, "%s", _vm._textData[cmnd->textDataDoneIndex]); _vm.scheduler().insertActionList(cmnd->actIndex); - - // See if any additional generic actions - if ((verb == _vm._arrayVerbs[_vm._look][0]) || (verb == _vm._arrayVerbs[_vm._take][0]) || (verb == _vm._arrayVerbs[_vm._drop][0])) - isGenericVerb(obj, comment); + // Special case if verb is Take or Drop. Assume additional generic actions + if ((word == _vm._arrayVerbs[_vm._take][0]) || (word == _vm._arrayVerbs[_vm._drop][0])) + isGenericVerb(word, obj); return true; } -void Parser::showDosInventory() { -// Show user all objects being carried in a variable width 2 column format - static const char *blanks = " "; - uint16 index = 0, len1 = 0, len2 = 0; +// Print text for possible background object. Return TRUE if match found +// Only match if both verb and noun found. Test_ca will match verb-only +bool Parser_v1d::isBackgroundWord(char *noun, char *verb, objectList_t obj) { + if (!noun) + return false; - for (int i = 0; i < _vm._numObj; i++) { // Find widths of 2 columns - if (_vm._objects[i].carriedFl) { - uint16 len = strlen(_vm._arrayNouns[_vm._objects[i].nounIndex][1]); - if (index++ & 1) // Right hand column - len2 = (len > len2) ? len : len2; - else - len1 = (len > len1) ? len : len1; + for (int i = 0; obj[i].verbIndex; i++) { + if ((verb == _vm._arrayVerbs[obj[i].verbIndex][0]) && (noun == _vm._arrayNouns[obj[i].nounIndex][0])) { + Utils::Box(BOX_ANY, "%s", _vm.file().fetchString(obj[i].commentIndex)); + return true; } } - len1 += 1; // For gap between columns + return false; +} - if (len1 + len2 < (uint16)strlen(_vm._textParser[kTBOutro])) - len1 = strlen(_vm._textParser[kTBOutro]); +// Print text for possible background object. Return TRUE if match found +// If test_noun TRUE, must have a noun given +bool Parser_v1d::isCatchallVerb(bool test_noun, char *noun, char *verb, objectList_t obj) { + if (test_noun && !noun) + return false; - char buffer[XBYTES *NUM_ROWS] = "\0"; - strncat(buffer, blanks, (len1 + len2 - strlen(_vm._textParser[kTBIntro])) / 2); - strcat(strcat(buffer, _vm._textParser[kTBIntro]), "\n"); - index = 0; - for (int i = 0; i < _vm._numObj; i++) { // Assign strings - if (_vm._objects[i].carriedFl) { - if (index++ & 1) - strcat(strcat(buffer, _vm._arrayNouns[_vm._objects[i].nounIndex][1]), "\n"); - else - strncat(strcat(buffer, _vm._arrayNouns[_vm._objects[i].nounIndex][1]), blanks, len1 - strlen(_vm._arrayNouns[_vm._objects[i].nounIndex][1])); + for (int i = 0; obj[i].verbIndex; i++) { + if ((verb == _vm._arrayVerbs[obj[i].verbIndex][0]) && ((noun == _vm._arrayNouns[obj[i].nounIndex][0]) || (obj[i].nounIndex == 0))) { + Utils::Box(BOX_ANY, "%s", _vm.file().fetchString(obj[i].commentIndex)); + return true; } } - if (index & 1) - strcat(buffer, "\n"); - strcat(buffer, _vm._textParser[kTBOutro]); + return false; +} - Utils::Box(BOX_ANY, "%s", buffer); +// Parse the user's line of text input. Generate events as necessary +void Parser_v1d::lineHandler() { + object_t *obj; + status_t &gameStatus = _vm.getGameStatus(); + char farComment[XBYTES * 5] = ""; // hold 5 line comment if object not nearby + +// Reset_prompt_line (); + Utils::strlwr(_line); // Convert to lower case + + if (!strcmp("exit", _line) || strstr(_line, "quit")) { + if (Utils::Box(BOX_YESNO, "Are you sure you want to QUIT?") != 0) + _vm.endGame(); + else + return; + } + + // SAVE/RESTORE + if (!strcmp("save", _line)) { + _config.soundFl = false; + if (gameStatus.gameOverFl) + Utils::gameOverMsg(); + else +// _vm.file().saveOrRestore(true); + warning("STUB: saveOrRestore()"); + return; + } + + if (!strcmp("restore", _line)) { + _config.soundFl = false; +// _vm.file().saveOrRestore(false); + warning("STUB: saveOrRestore()"); + return; + } + + if (*_line == '\0') // Empty line + return; + + if (strspn(_line, " ") == strlen(_line)) // Nothing but spaces! + return; + + if (gameStatus.gameOverFl) { // No commands allowed! + Utils::gameOverMsg(); + return; + } + + // Find the first verb in the line + char *verb = findVerb(); + char *noun = 0; // Noun not found yet + + if (verb) { // OK, verb found. Try to match with object + do { + noun = findNextNoun(noun); // Find a noun in the line + // Must try at least once for objects allowing verb-context + for (int i = 0; i < _vm._numObj; i++) { + obj = &_vm._objects[i]; + if (isNear(verb, noun, obj, farComment)) { + if (isObjectVerb(verb, obj) // Foreground object + || isGenericVerb(verb, obj)) // Common action type + return; + } + } + if ((*farComment == '\0') && isBackgroundWord(noun, verb, _vm._backgroundObjects[*_vm._screen_p])) + return; + } while (noun); + } + noun = findNextNoun(noun); + if (*farComment != '\0') // An object matched but not near enough + Utils::Box(BOX_ANY, "%s", farComment); + else if (!isCatchallVerb(true, noun, verb, _vm._catchallList) && + !isCatchallVerb(false, noun, verb, _vm._backgroundObjects[*_vm._screen_p]) && + !isCatchallVerb(false, noun, verb, _vm._catchallList)) + Utils::Box(BOX_ANY, "Apparently our hero either doesn't\n" + "understand what you mean or doesn't\n" + "think that would be very useful!"); +} + +Parser_v2d::Parser_v2d(HugoEngine &vm) : Parser_v1d(vm) { +} + +Parser_v2d::~Parser_v2d() { +} + +// Parse the user's line of text input. Generate events as necessary +void Parser_v2d::lineHandler() { + object_t *obj; + status_t &gameStatus = _vm.getGameStatus(); + char farComment[XBYTES * 5] = ""; // hold 5 line comment if object not nearby + +// Reset_prompt_line (); + Utils::strlwr(_line); // Convert to lower case + + if (!strcmp("exit", _line) || strstr(_line, "quit")) { + if (Utils::Box(BOX_YESNO, "Are you sure you want to QUIT?") != 0) + _vm.endGame(); + else + return; + } + + // SAVE/RESTORE + if (!strcmp("save", _line)) { + _config.soundFl = false; + if (gameStatus.gameOverFl) + Utils::gameOverMsg(); + else +// _vm.file().saveOrRestore(true); + warning("STUB: saveOrRestore()"); + return; + } + + if (!strcmp("restore", _line)) { + _config.soundFl = false; +// _vm.file().saveOrRestore(false); + warning("STUB: saveOrRestore()"); + return; + } + + if (!strlen(_line)) // Empty line + return; + + if (strspn(_line, " ") == strlen(_line)) // Nothing but spaces! + return; + + if (gameStatus.gameOverFl) { // No commands allowed! + Utils::gameOverMsg(); + return; + } + + // Find the first verb in the line + char *verb = findVerb(); + char *noun = 0; // Noun not found yet + + if (verb) { // OK, verb found. Try to match with object + do { + noun = findNextNoun(noun); // Find a noun in the line + // Must try at least once for objects allowing verb-context + for (int i = 0; i < _vm._numObj; i++) { + obj = &_vm._objects[i]; + if (isNear(verb, noun, obj, farComment)) { + if (isObjectVerb(verb, obj) // Foreground object + || isGenericVerb(verb, obj)) // Common action type + return; + } + } + if ((*farComment != '\0') && isBackgroundWord(noun, verb, _vm._backgroundObjects[*_vm._screen_p])) + return; + } while (noun); + } + + noun = findNextNoun(noun); + if ( !isCatchallVerb(true, noun, verb, _vm._backgroundObjects[*_vm._screen_p]) + && !isCatchallVerb(true, noun, verb, _vm._catchallList) + && !isCatchallVerb(false, noun, verb, _vm._backgroundObjects[*_vm._screen_p]) + && !isCatchallVerb(false, noun, verb, _vm._catchallList)) { + if (*farComment != '\0') { // An object matched but not near enough + Utils::Box(BOX_ANY, "%s", farComment); + } else if (_maze.enabledFl && (verb == _vm._arrayVerbs[_vm._look][0])) { + Utils::Box(BOX_ANY, "You are in a maze of\n" + "twisty little paths,\n" + "which are all alike!"); + showTakeables(); + } else if (verb && noun) { // A combination I didn't think of + Utils::Box(BOX_ANY, "I don't think that would\n" + "accomplish much, somehow!"); + } else if (verb || noun) { + Utils::Box(BOX_ANY, "I don't fully understand!"); + } else { + Utils::Box(BOX_ANY, "I find that befuddling!"); + } + } } } // End of namespace Hugo diff --git a/engines/hugo/parser.h b/engines/hugo/parser.h index 8d2b326d5d..96e5085050 100644 --- a/engines/hugo/parser.h +++ b/engines/hugo/parser.h @@ -44,39 +44,77 @@ enum seqTextParser { class Parser { public: Parser(HugoEngine &vm); + virtual ~Parser(); bool isWordPresent(char **wordArr); void charHandler(); void command(const char *format, ...); void keyHandler(uint16 nChar, uint16 nFlags); - void lineHandler(); + virtual void lineHandler() = 0; protected: HugoEngine &_vm; +protected: + bool isCarrying(uint16 wordIndex); + + char *findNoun(); + char *findVerb(); + + void dropObject(object_t *obj); + void takeObject(object_t *obj); + void showTakeables(); + private: char _ringBuffer[32]; // Ring buffer uint16 _putIndex; uint16 _getIndex; // Index into ring buffer bool _checkDoubleF1Fl; // Flag used to display user help or instructions + void showDosInventory(); +}; + +class Parser_v1w : public Parser { +public: + Parser_v1w(HugoEngine &vm); + ~Parser_v1w(); + + virtual void lineHandler(); + +private: bool isBackgroundWord(objectList_t obj); - bool isCarrying(uint16 wordIndex); bool isCatchallVerb(objectList_t obj); bool isGenericVerb(object_t *obj, char *comment); bool isNear(object_t *obj, char *verb, char *comment); bool isObjectVerb(object_t *obj, char *comment); +}; - char *findNoun(); - char *findVerb(); +class Parser_v1d : public Parser { +public: + Parser_v1d(HugoEngine &vm); + ~Parser_v1d(); + + void lineHandler(); + +protected: + char *findNextNoun(char *noun); + bool isNear(char *verb, char *noun, object_t *obj, char *comment); + bool isGenericVerb(char *word, object_t *obj); + bool isObjectVerb(char *word, object_t *obj); + bool isBackgroundWord(char *noun, char *verb, objectList_t obj); + bool isCatchallVerb(bool test_noun, char *noun, char *verb, objectList_t obj); - void dropObject(object_t *obj); - void showDosInventory(); - void showTakeables(); - void takeObject(object_t *obj); }; +class Parser_v2d : public Parser_v1d { +public: + Parser_v2d(HugoEngine &vm); + ~Parser_v2d(); + + void lineHandler(); +}; + } // End of namespace Hugo #endif //HUGO_PARSER_H |