Logo Search packages:      
Sourcecode: scummvm version File versions  Download package

script.cpp

/* 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.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-0-11-1/engines/saga/script.cpp $
 * $Id: script.cpp 30944 2008-02-23 22:50:18Z sev $
 *
 */

// Scripting module: Script resource handling functions
#include "saga/saga.h"

#include "saga/gfx.h"
#include "saga/console.h"

#include "saga/script.h"
#include "saga/interface.h"
#include "saga/itedata.h"
#include "saga/scene.h"
#include "saga/events.h"
#include "saga/actor.h"
#include "saga/objectmap.h"
#include "saga/isomap.h"
#include "saga/rscfile.h"

namespace Saga {



// Initializes the scripting module.
// Loads script resource look-up table, initializes script data system
Script::Script(SagaEngine *vm) : _vm(vm) {
      ResourceContext *resourceContext;
      byte *resourcePointer;
      size_t resourceLength;
      int prevTell;
      int i, j;
      byte *stringsPointer;
      size_t stringsLength;

      //initialize member variables
      _abortEnabled = true;
      _skipSpeeches = false;
      _conversingThread = NULL;

      _firstObjectSet = false;
      _secondObjectNeeded = false;
      _pendingVerb = getVerbType(kVerbNone);
      _currentVerb = getVerbType(kVerbNone);
      _stickyVerb = getVerbType(kVerbWalkTo);
      _leftButtonVerb = getVerbType(kVerbNone);
      _rightButtonVerb = getVerbType(kVerbNone);
      _pointerObject = ID_NOTHING;

      _staticSize = 0;
      _commonBufferSize = COMMON_BUFFER_SIZE;
      _commonBuffer = (byte*)malloc(_commonBufferSize);
      memset(_commonBuffer, 0, _commonBufferSize);

      debug(8, "Initializing scripting subsystem");
      // Load script resource file context
      _scriptContext = _vm->_resource->getContext(GAME_SCRIPTFILE);
      if (_scriptContext == NULL) {
            error("Script::Script() script context not found");
      }

      resourceContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
      if (resourceContext == NULL) {
            error("Script::Script() resource context not found");
      }

      debug(3, "Loading module LUT from resource %i", _vm->getResourceDescription()->moduleLUTResourceId);
      _vm->_resource->loadResource(resourceContext, _vm->getResourceDescription()->moduleLUTResourceId, resourcePointer, resourceLength);


      // Create logical script LUT from resource
      if (resourceLength % S_LUT_ENTRYLEN_ITECD == 0) {
            _modulesLUTEntryLen = S_LUT_ENTRYLEN_ITECD;
      } else if (resourceLength % S_LUT_ENTRYLEN_ITEDISK == 0) {
            _modulesLUTEntryLen = S_LUT_ENTRYLEN_ITEDISK;
      } else {
            error("Script::Script() Invalid script lookup table length (%i)", (int)resourceLength);
      }

      // Calculate number of entries
      _modulesCount = resourceLength / _modulesLUTEntryLen;

      debug(3, "LUT has %i entries", _modulesCount);

      // Allocate space for logical LUT
      _modules = (ModuleData *)malloc(_modulesCount * sizeof(*_modules));
      if (_modules == NULL) {
            memoryError("Script::Script()");
      }

      // Convert LUT resource to logical LUT
      MemoryReadStreamEndian scriptS(resourcePointer, resourceLength, resourceContext->isBigEndian);
      for (i = 0; i < _modulesCount; i++) {
            memset(&_modules[i], 0, sizeof(ModuleData));

            prevTell = scriptS.pos();
            _modules[i].scriptResourceId = scriptS.readUint16();
            _modules[i].stringsResourceId = scriptS.readUint16();
            _modules[i].voicesResourceId = scriptS.readUint16();

            // Skip the unused portion of the structure
            for (j = scriptS.pos(); j < prevTell + _modulesLUTEntryLen; j++) {
                  if (scriptS.readByte() != 0)
                        warning("Unused scriptLUT part isn't really unused for LUT %d (pos: %d)", i, j);
            }
      }

      free(resourcePointer);

      // TODO
      //
      // In ITE, the "main strings" resource contains both the verb strings
      // and the object names.
      //
      // In IHNM, the "main strings" contains the verb strings, but not the
      // object names. At least, I think that's the case.

      _vm->_resource->loadResource(resourceContext, _vm->getResourceDescription()->mainStringsResourceId, stringsPointer, stringsLength);

      _vm->loadStrings(_mainStrings, stringsPointer, stringsLength);
      free(stringsPointer);

      setupScriptFuncList();
}

// Shut down script module gracefully; free all allocated module resources
Script::~Script() {

      debug(8, "Shutting down scripting subsystem.");

      _mainStrings.freeMem();

      freeModules();
      free(_modules);

      free(_commonBuffer);
}

void Script::loadModule(int scriptModuleNumber) {
      byte *resourcePointer;
      size_t resourceLength;

      // Validate script number
      if ((scriptModuleNumber < 0) || (scriptModuleNumber >= _modulesCount)) {
            error("Script::loadScript() Invalid script module number");
      }

      if (_modules[scriptModuleNumber].loaded) {
            return;
      }

      // Initialize script data structure
      debug(3, "Loading script module #%d", scriptModuleNumber);

      _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].scriptResourceId, resourcePointer, resourceLength);

      loadModuleBase(_modules[scriptModuleNumber], resourcePointer, resourceLength);
      free(resourcePointer);

      _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].stringsResourceId, resourcePointer, resourceLength);

      _vm->loadStrings(_modules[scriptModuleNumber].strings, resourcePointer, resourceLength);
      free(resourcePointer);

      if (_modules[scriptModuleNumber].voicesResourceId > 0) {
            _vm->_resource->loadResource(_scriptContext, _modules[scriptModuleNumber].voicesResourceId, resourcePointer, resourceLength);

            loadVoiceLUT(_modules[scriptModuleNumber].voiceLUT, resourcePointer, resourceLength);
            free(resourcePointer);
      }

      _modules[scriptModuleNumber].staticOffset = _staticSize;
      _staticSize += _modules[scriptModuleNumber].staticSize;
      if (_staticSize > _commonBufferSize) {
            error("Script::loadModule() _staticSize > _commonBufferSize");
      }
      _modules[scriptModuleNumber].loaded = true;
}

void Script::freeModules() {
      int i;
      for (i = 0; i < _modulesCount; i++) {
            if (_modules[i].loaded) {
                  _modules[i].freeMem();
                  _modules[i].loaded = false;
            }
      }
      _staticSize = 0;
}

void Script::loadModuleBase(ModuleData &module, const byte *resourcePointer, size_t resourceLength) {
      int i;

      debug(3, "Loading module base...");

      module.moduleBase = (byte*)malloc(resourceLength);
      module.moduleBaseSize = resourceLength;

      memcpy(module.moduleBase, resourcePointer, resourceLength);

      MemoryReadStreamEndian scriptS(module.moduleBase, module.moduleBaseSize, _scriptContext->isBigEndian);

      module.entryPointsCount = scriptS.readUint16();
      scriptS.readUint16(); //skip
      module.entryPointsTableOffset = scriptS.readUint16();
      scriptS.readUint16(); //skip

      if ((module.moduleBaseSize - module.entryPointsTableOffset) < (module.entryPointsCount * SCRIPT_TBLENTRY_LEN)) {
            error("Script::loadModuleBase() Invalid table offset");
      }

      if (module.entryPointsCount > SCRIPT_MAX) {
            error("Script::loadModuleBase()Script limit exceeded");
      }

      module.entryPoints = (EntryPoint *)malloc(module.entryPointsCount * sizeof(*module.entryPoints));
      if (module.entryPoints == NULL) {
            memoryError("Script::loadModuleBase");
      }

      // Read in the entrypoint table

      module.staticSize = scriptS.readUint16();
      while (scriptS.pos() < module.entryPointsTableOffset)
            scriptS.readByte();

      for (i = 0; i < module.entryPointsCount; i++) {
            // First uint16 is the offset of the entrypoint name from the start
            // of the bytecode resource, second uint16 is the offset of the
            // bytecode itself for said entrypoint
            module.entryPoints[i].nameOffset = scriptS.readUint16();
            module.entryPoints[i].offset = scriptS.readUint16();

            // Perform a simple range check on offset values
            if ((module.entryPoints[i].nameOffset >= module.moduleBaseSize) || (module.entryPoints[i].offset >= module.moduleBaseSize)) {
                  error("Script::loadModuleBase() Invalid offset encountered in script entrypoint table");
            }
      }
}

void Script::loadVoiceLUT(VoiceLUT &voiceLUT, const byte *resourcePointer, size_t resourceLength) {
      uint16 i;

      voiceLUT.voicesCount = resourceLength / 2;

      voiceLUT.voices = (uint16 *)malloc(voiceLUT.voicesCount * sizeof(*voiceLUT.voices));
      if (voiceLUT.voices == NULL) {
            error("Script::loadVoiceLUT() not enough memory");
      }

      MemoryReadStreamEndian scriptS(resourcePointer, resourceLength, _scriptContext->isBigEndian);

      for (i = 0; i < voiceLUT.voicesCount; i++) {
            voiceLUT.voices[i] = scriptS.readUint16();
      }
}

// verb
void Script::showVerb(int statusColor) {
      const char *verbName;
      const char *object1Name;
      const char *object2Name;
      char statusString[STATUS_TEXT_LEN];

      if (_leftButtonVerb == getVerbType(kVerbNone)) {
            _vm->_interface->setStatusText("");
            return;
      }

      if (_vm->getGameType() == GType_ITE)
            verbName = _mainStrings.getString(_leftButtonVerb - 1);
      else
            verbName = _mainStrings.getString(_leftButtonVerb + 1);

      if (objectTypeId(_currentObject[0]) == kGameObjectNone) {
            _vm->_interface->setStatusText(verbName, statusColor);
            return;
      }

      object1Name = _vm->getObjectName(_currentObject[0]);

      if (!_secondObjectNeeded) {
            snprintf(statusString, STATUS_TEXT_LEN, "%s %s", verbName, object1Name);
            _vm->_interface->setStatusText(statusString, statusColor);
            return;
      }


      if (objectTypeId(_currentObject[1]) != kGameObjectNone) {
            object2Name = _vm->getObjectName(_currentObject[1]);
      } else {
            object2Name = "";
      }

      if (_leftButtonVerb == getVerbType(kVerbGive)) {
            snprintf(statusString, STATUS_TEXT_LEN, _vm->getTextString(kTextGiveTo), object1Name, object2Name);
            _vm->_interface->setStatusText(statusString, statusColor);
      } else {
            if (_leftButtonVerb == getVerbType(kVerbUse)) {
                  snprintf(statusString, STATUS_TEXT_LEN, _vm->getTextString(kTextUseWidth), object1Name, object2Name);
                  _vm->_interface->setStatusText(statusString, statusColor);
            } else {
                  snprintf(statusString, STATUS_TEXT_LEN, "%s %s", verbName, object1Name);
                  _vm->_interface->setStatusText(statusString, statusColor);
            }
      }
}

int Script::getVerbType(VerbTypes verbType) {
      if (_vm->getGameType() == GType_ITE) {
            switch (verbType) {
            case kVerbNone:
                  return kVerbITENone;
            case kVerbWalkTo:
                  return kVerbITEWalkTo;
            case kVerbGive:
                  return kVerbITEGive;
            case kVerbUse:
                  return kVerbITEUse;
            case kVerbEnter:
                  return kVerbITEEnter;
            case kVerbLookAt:
                  return kVerbITELookAt;
            case kVerbPickUp:
                  return kVerbITEPickUp;
            case kVerbOpen:
                  return kVerbITEOpen;
            case kVerbClose:
                  return kVerbITEClose;
            case kVerbTalkTo:
                  return kVerbITETalkTo;
            case kVerbWalkOnly:
                  return kVerbITEWalkOnly;
            case kVerbLookOnly:
                  return kVerbITELookOnly;
            case kVerbOptions:
                  return kVerbITEOptions;
            }
      }
      else {
            switch (verbType) {
            case kVerbNone:
                  return kVerbIHNMNone;
            case kVerbWalkTo:
                  return kVerbIHNMWalk;
            case kVerbLookAt:
                  return kVerbIHNMLookAt;
            case kVerbPickUp:
                  return kVerbIHNMTake;
            case kVerbUse:
                  return kVerbIHNMUse;
            case kVerbTalkTo:
                  return kVerbIHNMTalkTo;
            case kVerbOpen:
                  return kVerbIHNMSwallow;
            case kVerbGive:
                  return kVerbIHNMGive;
            case kVerbClose:
                  return kVerbIHNMPush;
            case kVerbEnter:
                  return kVerbIHNMEnter;
            case kVerbWalkOnly:
                  return kVerbIHNMWalkOnly;
            case kVerbLookOnly:
                  return kVerbIHNMLookOnly;
            case kVerbOptions:
                  return kVerbIHNMOptions;
            }
      }
      error("Script::getVerbType() unknown verb type %d", verbType);
}

void Script::setVerb(int verb) {
      _pendingObject[0] = ID_NOTHING;
      _currentObject[0] = ID_NOTHING;
      _pendingObject[1] = ID_NOTHING;
      _currentObject[1] = ID_NOTHING;
      _firstObjectSet = false;
      _secondObjectNeeded = false;

      // The pointer object will be updated again immediately. This way the
      // new verb will be applied to it. It's not exactly how the original
      // engine did it, but it appears to work.
      _pointerObject = ID_NOTHING;

      setLeftButtonVerb( verb );
      showVerb();
}

void Script::setLeftButtonVerb(int verb) {
      int         oldVerb = _currentVerb;

      _currentVerb = _leftButtonVerb = verb;

      if ((_currentVerb != oldVerb) && (_vm->_interface->getMode() == kPanelMain)){
                  if (oldVerb > getVerbType(kVerbNone))
                        _vm->_interface->setVerbState(oldVerb, 2);

                  if (_currentVerb > getVerbType(kVerbNone))
                        _vm->_interface->setVerbState(_currentVerb, 2);
      }
}

void Script::setRightButtonVerb(int verb) {
      int         oldVerb = _rightButtonVerb;

      _rightButtonVerb = verb;

      if ((_rightButtonVerb != oldVerb) && (_vm->_interface->getMode() == kPanelMain)){
            if (oldVerb > getVerbType(kVerbNone))
                  _vm->_interface->setVerbState(oldVerb, 2);

            if (_rightButtonVerb > getVerbType(kVerbNone))
                  _vm->_interface->setVerbState(_rightButtonVerb, 2);
      }
}

void Script::doVerb() {
      int scriptEntrypointNumber = 0;
      int scriptModuleNumber = 0;
      int objectType;
      Event event;
      const char *excuseText;
      int excuseSampleResourceId;
      const HitZone *hitZone;

      objectType = objectTypeId(_pendingObject[0]);

      if (_pendingVerb == getVerbType(kVerbGive)) {
            scriptEntrypointNumber = _vm->_actor->getObjectScriptEntrypointNumber(_pendingObject[1]);
            if (_vm->_actor->getObjectFlags(_pendingObject[1]) & (kFollower|kProtagonist|kExtended)) {
                  scriptModuleNumber = 0;
            } else {
                  scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
            }
            // IHNM never sets scriptModuleNumber to 0
            if (_vm->getGameType() == GType_IHNM)
                  scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
      } else {
            if (_pendingVerb == getVerbType(kVerbUse)) {
                  if ((objectTypeId(_pendingObject[1]) > kGameObjectNone) && (objectType < objectTypeId(_pendingObject[1]))) {
                        SWAP(_pendingObject[0], _pendingObject[1]);
                        objectType = objectTypeId(_pendingObject[0]);
                  }
            }

            if (objectType == 0)
                  return;
            else if (objectType == kGameObjectHitZone) {
                  scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
                  hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[0]));

                  if (hitZone == NULL)
                        return;

                  if ((hitZone->getFlags() & kHitZoneExit) == 0) {
                        scriptEntrypointNumber = hitZone->getScriptNumber();
                  }
            } else {
                  if (objectType & (kGameObjectActor | kGameObjectObject)) {
                        scriptEntrypointNumber = _vm->_actor->getObjectScriptEntrypointNumber(_pendingObject[0]);

                        if ((objectType == kGameObjectActor) && !(_vm->_actor->getObjectFlags(_pendingObject[0]) & (kFollower|kProtagonist|kExtended))) {
                              scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
                        } else {
                              scriptModuleNumber = 0;
                        }
                        // IHNM never sets scriptModuleNumber to 0
                        if (_vm->getGameType() == GType_IHNM)
                              scriptModuleNumber = _vm->_scene->getScriptModuleNumber();
                  }
            }
      }

      // WORKAROUND for a bug in the original game scripts of IHNM. Edna's script (actor 8197) is problematic, so
      // when the knife (object 16385) is used with her, the expected result is not correct. The first time that
      // the knife is used, Edna's heart is cut out (which is correct). But on subsequent use, the object's script
      // is buggy, therefore it's possible to talk to a dead Edna by using the knife on her, or to incorrectly get her
      // heart again, which remove's Gorrister's heart from the inventory. The solution is to disable the "use knife with
      // Edna" action altogether, because if the player wants to kill Edna, he can do that by talking to her and
      // choosing "[Cut out Edna's heart]", which works correctly. To disable this action, if the knife is used on Edna, we
      // change the action here to "use knife with the knife", which yields a better reply ("I'd just dull my knife").
      // Fixes bug #1826871 - "IHNM: Edna's got two hearts but loves to be on the hook"
      if (_vm->getGameType() == GType_IHNM && _pendingObject[0] == 16385 && _pendingObject[1] == 8197 && _pendingVerb == 4)
            _pendingObject[1] = 16385;

      // WORKAROUND for a bug in the original game scripts of IHNM. Gorrister's heart is not supposed to have a
      // "use" phrase attached to it (it's not used anywhere, it's only given), but when "used", an incorrect
      // reply is given to the player ("It's too narrow for me to pass", said when Gorrister tries to pick up the
      // heart without a rope). Therefore, for object number 16397 (Gorrister's heart), when the active verb is
      // "Use", set it to "Push", which gives a more appropriate reply ("What good will that do me?")
      if (_vm->getGameType() == GType_IHNM && _pendingObject[0] == 16397 && _pendingVerb == 4)
            _pendingVerb = 8;

      if (scriptEntrypointNumber > 0) {

            event.type = kEvTOneshot;
            event.code = kScriptEvent;
            event.op = kEventExecNonBlocking;
            event.time = 0;
            event.param = scriptModuleNumber;
            event.param2 = scriptEntrypointNumber;
            event.param3 = _pendingVerb;        // Action
            event.param4 = _pendingObject[0];   // Object
            event.param5 = _pendingObject[1];   // With Object
            event.param6 = (objectType == kGameObjectActor) ? _pendingObject[0] : ID_PROTAG;          // Actor
            _vm->_events->queue(&event);

      } else {
            // Show excuse text in ITE CD Versions
            if (_vm->getGameType() == GType_ITE) {
                  _vm->getExcuseInfo(_pendingVerb, excuseText, excuseSampleResourceId);
                  if (excuseText) {
                        // In Floppy versions we don't have excuse texts
                        if (!(_vm->getFeatures() & GF_CD_FX))
                              excuseSampleResourceId = -1;

                        _vm->_actor->actorSpeech(ID_PROTAG, &excuseText, 1, excuseSampleResourceId, 0);
                  }
            }
      }

      if ((_currentVerb == getVerbType(kVerbWalkTo)) || (_currentVerb == getVerbType(kVerbLookAt))) {
            _stickyVerb = _currentVerb;
      }

      _pendingVerb = getVerbType(kVerbNone);
      _currentObject[0] = _currentObject[1] = ID_NOTHING;
      setLeftButtonVerb(_stickyVerb);

      setPointerVerb();
}

void Script::setPointerVerb() {
      if (_vm->_interface->isActive()) {
            _pointerObject = ID_PROTAG;
            whichObject(_vm->mousePos());
      }
}

void Script::hitObject(bool leftButton) {
      int verb;
      verb = leftButton ? _leftButtonVerb : _rightButtonVerb;

      if (verb > getVerbType(kVerbNone)) {
            if (_firstObjectSet) {
                  if (_secondObjectNeeded) {
                        _pendingObject[0] = _currentObject[0];
                        _pendingObject[1] = _currentObject[1];
                        _pendingVerb = verb;

                        _leftButtonVerb = verb;
                        if (_pendingVerb > getVerbType(kVerbNone))
                              showVerb(kITEColorBrightWhite);
                        else
                              showVerb();

                        _secondObjectNeeded = false;
                        _firstObjectSet = false;
                        return;
                  }
            } else {
                  if (verb == getVerbType(kVerbGive)) {
                        _secondObjectNeeded = true;
                  } else {
                        if (verb == getVerbType(kVerbUse)) {

                              if (_currentObjectFlags[0] & kObjUseWith) {
                                    _secondObjectNeeded = true;
                              }
                        }
                  }

                  if (!_secondObjectNeeded) {
                        _pendingObject[0] = _currentObject[0];
                        _pendingObject[1] = ID_NOTHING;
                        _pendingVerb = verb;

                        _secondObjectNeeded = false;
                        _firstObjectSet = false;
                  } else {
                        _firstObjectSet = true;
                  }
            }

            _leftButtonVerb = verb;
            if (_pendingVerb > getVerbType(kVerbNone))
                  showVerb(kITEColorBrightWhite);
            else
                  showVerb();
      }

}

void Script::playfieldClick(const Point& mousePoint, bool leftButton) {
      Location pickLocation;
      const HitZone *hitZone;
      Point specialPoint;

      _vm->incrementMouseClickCount();
      _vm->_actor->abortSpeech();

      if ((_vm->_actor->_protagonist->_currentAction != kActionWait) &&
            (_vm->_actor->_protagonist->_currentAction != kActionFreeze) &&
            (_vm->_actor->_protagonist->_currentAction != kActionWalkToLink) &&
            (_vm->_actor->_protagonist->_currentAction != kActionWalkToPoint)) {
            return;
      }
      if (_pendingVerb > getVerbType(kVerbNone)) {
            setLeftButtonVerb(getVerbType(kVerbWalkTo));
      }

      if (_pointerObject != ID_NOTHING) {
            hitObject( leftButton );
      } else {
            _pendingObject[0] = ID_NOTHING;
            _pendingObject[1] = ID_NOTHING;
            _pendingVerb = getVerbType(kVerbWalkTo);
      }


      // tiled stuff
      if (_vm->_scene->getFlags() & kSceneFlagISO) {
            _vm->_isoMap->screenPointToTileCoords(mousePoint, pickLocation);
      } else {
            pickLocation.fromScreenPoint(mousePoint);
      }


      hitZone = NULL;

      if (objectTypeId(_pendingObject[0]) == kGameObjectHitZone) {
             hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[0]));
      } else {
            if ((_pendingVerb == getVerbType(kVerbUse)) && (objectTypeId(_pendingObject[1]) == kGameObjectHitZone)) {
                  hitZone = _vm->_scene->_objectMap->getHitZone(objectIdToIndex(_pendingObject[1]));
            }
      }

      if (hitZone != NULL) {
            if (_vm->getGameType() == GType_ITE) {
                  if (hitZone->getFlags() & kHitZoneNoWalk) {
                        _vm->_actor->actorFaceTowardsPoint(ID_PROTAG, pickLocation);
                        doVerb();
                        return;
                  }
            } else { 
                  if (_vm->getGameType() == GType_IHNM) {
                        if ((hitZone->getFlags() & kHitZoneNoWalk) && (_pendingVerb != getVerbType(kVerbWalkTo))) {
                              doVerb();
                              return;
                        }
                  }
            }

            if (hitZone->getFlags() & kHitZoneProject) {
                  if (!hitZone->getSpecialPoint(specialPoint)) {
                        // Original behaved this way and this prevents from crash
                        // at ruins. See bug #1257459
                        specialPoint.x = specialPoint.y = 0;
                  }

                  // tiled stuff
                  if (_vm->_scene->getFlags() & kSceneFlagISO) {
                        pickLocation.u() = specialPoint.x;
                        pickLocation.v() = specialPoint.y;
                        pickLocation.z = _vm->_actor->_protagonist->_location.z;
                  } else {
                        pickLocation.fromScreenPoint(specialPoint);
                  }
            }
      }

      if (_vm->getGameType() == GType_ITE) {
            if ((_pendingVerb == getVerbType(kVerbWalkTo)) ||
                  (_pendingVerb == getVerbType(kVerbPickUp)) ||
                  (_pendingVerb == getVerbType(kVerbOpen)) ||
                  (_pendingVerb == getVerbType(kVerbClose)) ||
                  (_pendingVerb == getVerbType(kVerbUse))) {
                        _vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);
            } else {
                  if (_pendingVerb == getVerbType(kVerbLookAt)) {
                        if (objectTypeId(_pendingObject[0]) != kGameObjectActor ) {
                              _vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);
                        } else {
                              doVerb();
                        }
                  } else {
                        if ((_pendingVerb == getVerbType(kVerbTalkTo)) ||
                              (_pendingVerb == getVerbType(kVerbGive))) {
                                    doVerb();
                        }
                  }
            }
      }

      if (_vm->getGameType() == GType_IHNM) {

            if ((_pendingVerb == getVerbType(kVerbWalkTo)) ||
                  (_pendingVerb == getVerbType(kVerbPickUp)) ||
                  (_pendingVerb == getVerbType(kVerbOpen)) ||
                  (_pendingVerb == getVerbType(kVerbClose)) ||
                  (_pendingVerb == getVerbType(kVerbUse))) {
                        _vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);

                        // Auto-use no-walk hitzones in IHNM, needed for Benny's chapter
                        if (_pendingVerb == getVerbType(kVerbWalkTo) &&
                              hitZone != NULL && (hitZone->getFlags() & kHitZoneNoWalk)) {
                              _pendingVerb = getVerbType(kVerbUse);
                              if (objectTypeId(_pendingObject[0]) == kGameObjectActor) {
                                    _vm->_actor->actorFaceTowardsObject(ID_PROTAG, _pendingObject[0]);
                                    doVerb();
                              }
                        }

                        // Auto-use hitzone with id 24576 (the exit to the left) in screens 16 - 19
                        // (screens with Gorrister's heart) in IHNM. For some reason, this zone does
                        // not have a corresponding action zone, so we auto-use it here, like the exits
                        // in Benny's chapter
                        if (_vm->_scene->currentChapterNumber() == 1 && 
                              _vm->_scene->currentSceneNumber() >= 16 && 
                              _vm->_scene->currentSceneNumber() <= 19 &&
                              _pendingVerb == getVerbType(kVerbWalkTo) &&
                              hitZone != NULL && hitZone->getHitZoneId() == 24576) {
                              _pendingVerb = getVerbType(kVerbUse);
                              if (objectTypeId(_pendingObject[0]) == kGameObjectActor) {
                                    _vm->_actor->actorFaceTowardsObject(ID_PROTAG, _pendingObject[0]);
                                    doVerb();
                              }
                        }

            } else {
                  if (_pendingVerb == getVerbType(kVerbLookAt)) {
                        if (objectTypeId(_pendingObject[0]) != kGameObjectActor) {
                              _vm->_actor->actorWalkTo(ID_PROTAG, pickLocation);
                        } else {
                              _vm->_actor->actorFaceTowardsObject(ID_PROTAG, _pendingObject[0]);
                              doVerb();
                        }
                  } else {
                        if ((_pendingVerb == getVerbType(kVerbTalkTo)) ||
                              (_pendingVerb == getVerbType(kVerbGive))) {
                                    doVerb();
                        }
                  }
            }
      }

}

void Script::whichObject(const Point& mousePoint) {
      uint16 objectId;
      int16 objectFlags;
      int newRightButtonVerb;
      uint16 newObjectId;
      ActorData *actor;
      ObjectData *obj;
      Point pickPoint;
      Location pickLocation;
      int hitZoneIndex;
      const HitZone * hitZone;
      PanelButton * panelButton;

      objectId = ID_NOTHING;
      objectFlags = 0;
      _leftButtonVerb = _currentVerb;
      newRightButtonVerb = getVerbType(kVerbNone);

      // _protagonist can be null while loading a game from the command line
      if (_vm->_actor->_protagonist == NULL)
            return;

      if (_vm->_actor->_protagonist->_currentAction != kActionWalkDir) {
            if (_vm->_scene->getHeight() >= mousePoint.y) {
                  newObjectId = _vm->_actor->hitTest(mousePoint, true);

                  if (newObjectId != ID_NOTHING) {
                        if (objectTypeId(newObjectId) == kGameObjectObject) {
                              objectId = newObjectId;
                              objectFlags = 0;
                              newRightButtonVerb = getVerbType(kVerbLookAt);

                              if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
                                    objectId = ID_NOTHING;
                                    newObjectId = ID_NOTHING;
                              }
                        } else {
                              actor = _vm->_actor->getActor(newObjectId);
                              objectId = newObjectId;
                              if (_vm->getGameType() == GType_ITE)
                                    objectFlags = kObjUseWith;
                              // Note: for IHNM, the default right button action is "Look at" for actors,
                              // but "Talk to" makes much more sense
                              newRightButtonVerb = getVerbType(kVerbTalkTo);
                              // Slight hack because of the above change: the jukebox in Gorrister's chapter 
                              // is an actor, so change the right button action to "Look at"
                              if (_vm->getGameType() == GType_IHNM && objectId == 8199)
                                    newRightButtonVerb = getVerbType(kVerbLookAt);

                              if ((_currentVerb == getVerbType(kVerbPickUp)) ||
                                    (_currentVerb == getVerbType(kVerbOpen)) ||
                                    (_currentVerb == getVerbType(kVerbClose)) ||
                                    ((_currentVerb == getVerbType(kVerbGive)) && !_firstObjectSet) ||
                                    ((_currentVerb == getVerbType(kVerbUse)) && !(actor->_flags & kFollower))) {
                                          if (_vm->getGameType() == GType_ITE) {
                                                objectId = ID_NOTHING;
                                                newObjectId = ID_NOTHING;
                                          }
                                    }
                        }
                  }

                  if (newObjectId == ID_NOTHING) {

                        pickPoint = mousePoint;

                        if (_vm->_scene->getFlags() & kSceneFlagISO) {
                              pickPoint.y -= _vm->_actor->_protagonist->_location.z;
                              _vm->_isoMap->screenPointToTileCoords(pickPoint, pickLocation);
                              pickLocation.toScreenPointUV(pickPoint);
                        }

                        hitZoneIndex = _vm->_scene->_objectMap->hitTest(pickPoint);

                        // WORKAROUND for an incorrect hitzone which exists in IHNM
                        // In Gorrister's chapter, in the toilet screen, the hitzone of the exit is
                        // placed over the place where Gorrister sits to examine the graffiti on the wall
                        // to the left, which makes him exit the screen when the graffiti is examined.
                        // We effectively change the left side of the hitzone here so that it starts from
                        // pixel 301 onwards. The same workaround is applied in Actor::handleActions
                        if (_vm->getGameType() == GType_IHNM) {
                              if (_vm->_scene->currentChapterNumber() == 1 && _vm->_scene->currentSceneNumber() == 22)
                                    if (hitZoneIndex == 8 && pickPoint.x <= 300)
                                          hitZoneIndex = -1;
                        }

                        if ((hitZoneIndex != -1)) {
                              hitZone = _vm->_scene->_objectMap->getHitZone(hitZoneIndex);
                              objectId = hitZone->getHitZoneId();
                              objectFlags = 0;
                              newRightButtonVerb = hitZone->getRightButtonVerb() & 0x7f;

                              // WORKAROUND for a problematic object in IHNM
                              // In the freezer room, the key that drops is made of a hitzone which
                              // contains the key object itself. We change the object ID that the
                              // hitzone contains (object ID 24578 - "The key") to the ID of the key
                              // object itself (object ID 16402 - "Edna's key"), as the user can keep
                              // hovering the cursor to both items, but can only pick up one
                              if (_vm->getGameType() == GType_IHNM) {
                                    if (_vm->_scene->currentChapterNumber() == 1 && _vm->_scene->currentSceneNumber() == 24) {
                                          if (objectId == 24578)
                                                objectId = 16402;
                                    }
                              }

                              if (_vm->getGameType() == GType_ITE) {

                                    if (newRightButtonVerb == getVerbType(kVerbWalkOnly)) {
                                          if (_firstObjectSet) {
                                                objectId = ID_NOTHING;
                                          } else {
                                                newRightButtonVerb = _leftButtonVerb = getVerbType(kVerbWalkTo);
                                          }
                                    } else {
                                          if (newRightButtonVerb == getVerbType(kVerbLookOnly)) {
                                                if (_firstObjectSet) {
                                                      objectId = ID_NOTHING;
                                                } else {
                                                      newRightButtonVerb = _leftButtonVerb = getVerbType(kVerbLookAt);
                                                }
                                          }
                                    }

                                    if (newRightButtonVerb >= getVerbType(kVerbOptions)) {
                                          newRightButtonVerb = getVerbType(kVerbNone);
                                    }
                              } else {
                                    if (newRightButtonVerb >= getVerbType(kVerbOptions)) {
                                          newRightButtonVerb = getVerbType(kVerbWalkTo);
                                    }
                              }

                              if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
                                    objectId = ID_NOTHING;
                                    newObjectId = ID_NOTHING;
                              }

                              if ((_leftButtonVerb == getVerbType(kVerbUse)) && (hitZone->getRightButtonVerb() & 0x80)) {
                                    objectFlags = kObjUseWith;
                              }
                        }
                  }
            } else {
                  if ((_currentVerb == getVerbType(kVerbTalkTo)) || ((_currentVerb == getVerbType(kVerbGive)) && _firstObjectSet)) {
                        // no way
                  } else {
                        panelButton = _vm->_interface->inventoryHitTest(mousePoint);
                        if (panelButton) {
                              objectId = _vm->_interface->getInventoryContentByPanelButton(panelButton);
                              if (objectId != 0) {
                                    obj = _vm->_actor->getObj(objectId);
                                    newRightButtonVerb = getVerbType(kVerbLookAt);
                                    if (obj->_interactBits & kObjUseWith) {
                                          objectFlags = kObjUseWith;
                                    }
                              }
                        }
                  }

                  if ((_currentVerb == getVerbType(kVerbPickUp)) || (_currentVerb == getVerbType(kVerbTalkTo)) || (_currentVerb == getVerbType(kVerbWalkTo))) {
                        _leftButtonVerb = getVerbType(kVerbLookAt);
                  }
            }
      }

      if (objectId != _pointerObject) {
            _pointerObject = objectId;
            _currentObject[_firstObjectSet ? 1 : 0] = objectId;
            _currentObjectFlags[_firstObjectSet ? 1 : 0] = objectFlags;
            if (_pendingVerb == getVerbType(kVerbNone)) {
                  showVerb();
            }
      }

      if (newRightButtonVerb != _rightButtonVerb) {
            setRightButtonVerb(newRightButtonVerb);
      }
}

} // End of namespace Saga

Generated by  Doxygen 1.6.0   Back to index