/* 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. * */ /* * This code is based on original Tony Tough source code * * Copyright (c) 1997-2003 Nayma Software */ #include "common/memstream.h" #include "common/scummsys.h" #include "tony/mpal/mpalutils.h" #include "tony/game.h" #include "tony/loc.h" #include "tony/tony.h" namespace Tony { using namespace ::Tony::MPAL; /****************************************************************************\ * RMPalette Methods \****************************************************************************/ void RMPalette::readFromStream(Common::ReadStream &ds) { ds.read(_data, 1024); } /****************************************************************************\ * RMSlot Methods \****************************************************************************/ void RMPattern::RMSlot::readFromStream(Common::ReadStream &ds, bool bLOX) { // Type byte type = ds.readByte(); _type = (RMPattern::RMSlotType)type; // Data _data = ds.readSint32LE(); // Position _pos.readFromStream(ds); // Generic flag _flag = ds.readByte(); } /****************************************************************************\ * RMPattern Methods \****************************************************************************/ void RMPattern::readFromStream(Common::ReadStream &ds, bool bLOX) { // Pattern name if (!bLOX) _name = readString(ds); // Velocity _speed = ds.readSint32LE(); // Position _pos.readFromStream(ds); // Flag for pattern looping _bLoop = ds.readSint32LE(); // Number of slots _nSlots = ds.readSint32LE(); // Create and read the slots _slots = new RMSlot[_nSlots]; for (int i = 0; i < _nSlots && !ds.err(); i++) { if (bLOX) _slots[i].readFromStream(ds, true); else _slots[i].readFromStream(ds, false); } } void RMPattern::updateCoord() { _curPos = _pos + _slots[_nCurSlot].pos(); } void RMPattern::stopSfx(RMSfx *sfx) { for (int i = 0; i < _nSlots; i++) { if (_slots[i]._type == SOUND) { if (!sfx[_slots[i]._data]._name.empty() && sfx[_slots[i]._data]._name[0] == '_') sfx[_slots[i]._data].stop(); else if (GLOBALS._bSkipSfxNoLoop) sfx[_slots[i]._data].stop(); } } } int RMPattern::init(RMSfx *sfx, bool bPlayP0, byte *bFlag) { // Read the current time _nStartTime = g_vm->getTime(); _nCurSlot = 0; // Find the first frame of the pattern int i = 0; while (_slots[i]._type != SPRITE) { assert(i + 1 < _nSlots); i++; } _nCurSlot = i; _nCurSprite = _slots[i]._data; if (bFlag) *bFlag = _slots[i]._flag; // Calculate the current coordinates updateCoord(); // Check for sound: // If the slot is 0, play // If speed == 0, must play unless it goes into loop '_', or if specified by the parameter // If speed != 0, play only the loop for (i = 0; i < _nSlots; i++) { if (_slots[i]._type == SOUND) { if (i == 0) { if (!sfx[_slots[i]._data]._name.empty() && sfx[_slots[i]._data]._name[0] == '_') { sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); sfx[_slots[i]._data].play(true); } else { sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); sfx[_slots[i]._data].play(); } } else if (_speed == 0) { if (bPlayP0) { sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); sfx[_slots[i]._data].play(); } else if (!sfx[_slots[i]._data]._name.empty() && sfx[_slots[i]._data]._name[0] == '_') { sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); sfx[_slots[i]._data].play(true); } } else { if (_bLoop && !sfx[_slots[i]._data]._name.empty() && sfx[_slots[i]._data]._name[0] == '_') { sfx[_slots[i]._data].setVolume(_slots[i].pos()._x); sfx[_slots[i]._data].play(true); } } } } return _nCurSprite; } int RMPattern::update(uint32 hEndPattern, byte &bFlag, RMSfx *sfx) { int CurTime = g_vm->getTime(); // If the speed is 0, then the pattern never advances if (_speed == 0) { CoroScheduler.pulseEvent(hEndPattern); bFlag = _slots[_nCurSlot]._flag; return _nCurSprite; } // Is it time to change the slots? while (_nStartTime + _speed <= (uint32)CurTime) { _nStartTime += _speed; if (_slots[_nCurSlot]._type == SPRITE) _nCurSlot++; if (_nCurSlot == _nSlots) { _nCurSlot = 0; bFlag = _slots[_nCurSlot]._flag; CoroScheduler.pulseEvent(hEndPattern); // @@@ If there is no loop pattern, and there's a warning that it's the final // frame, then remain on the last frame if (!_bLoop) { _nCurSlot = _nSlots - 1; bFlag = _slots[_nCurSlot]._flag; return _nCurSprite; } } for (;;) { switch (_slots[_nCurSlot]._type) { case SPRITE: // Read the next sprite _nCurSprite = _slots[_nCurSlot]._data; // Update the parent & child coordinates updateCoord(); break; case SOUND: if (sfx != NULL) { sfx[_slots[_nCurSlot]._data].setVolume(_slots[_nCurSlot].pos()._x); if (sfx[_slots[_nCurSlot]._data]._name.empty() || sfx[_slots[_nCurSlot]._data]._name[0] != '_') sfx[_slots[_nCurSlot]._data].play(false); else sfx[_slots[_nCurSlot]._data].play(true); } break; case COMMAND: assert(0); break; default: assert(0); break; } if (_slots[_nCurSlot]._type == SPRITE) break; _nCurSlot++; } } // Return the current sprite bFlag = _slots[_nCurSlot]._flag; return _nCurSprite; } RMPattern::RMPattern() { _slots = NULL; _speed = 0; _bLoop = 0; _nSlots = 0; _nCurSlot = 0; _nCurSprite = 0; _nStartTime = 0; _slots = NULL; } /** * Reads the position of the pattern */ RMPoint RMPattern::pos() { return _curPos; } RMPattern::~RMPattern() { if (_slots != NULL) { delete[] _slots; _slots = NULL; } } /****************************************************************************\ * RMSprite Methods \****************************************************************************/ void RMSprite::init(RMGfxSourceBuffer *buf) { _buf = buf; } void RMSprite::LOXGetSizeFromStream(Common::SeekableReadStream &ds, int *dimx, int *dimy) { uint32 pos = ds.pos(); *dimx = ds.readSint32LE(); *dimy = ds.readSint32LE(); ds.seek(pos); } void RMSprite::getSizeFromStream(Common::SeekableReadStream &ds, int *dimx, int *dimy) { uint32 pos = ds.pos(); _name = readString(ds); *dimx = ds.readSint32LE(); *dimy = ds.readSint32LE(); ds.seek(pos); } void RMSprite::readFromStream(Common::SeekableReadStream &ds, bool bLOX) { // Sprite name if (!bLOX) _name = readString(ds); // Dimensions int dimx = ds.readSint32LE(); int dimy = ds.readSint32LE(); // Bounding box _rcBox.readFromStream(ds); // Unused space if (!bLOX) ds.skip(32); // Create buffer and read _buf->init(ds, dimx, dimy); } void RMSprite::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { _buf->draw(coroParam, bigBuf, prim); } void RMSprite::setPalette(byte *buf) { ((RMGfxSourceBufferPal *)_buf)->loadPalette(buf); } RMSprite::RMSprite() { _buf = NULL; } RMSprite::~RMSprite() { if (_buf) { delete _buf; _buf = NULL; } } /****************************************************************************\ * RMSfx Methods \****************************************************************************/ void RMSfx::readFromStream(Common::ReadStream &ds, bool bLOX) { // sfx name _name = readString(ds); int size = ds.readSint32LE(); // Read the entire buffer into a MemoryReadStream byte *buffer = (byte *)malloc(size); ds.read(buffer, size); Common::SeekableReadStream *stream = new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES); // Create the sound effect _fx = g_vm->createSFX(stream); _fx->setLoop(false); } RMSfx::RMSfx() { _fx = NULL; _bPlayingLoop = false; } RMSfx::~RMSfx() { if (_fx) { _fx->release(); _fx = NULL; } } void RMSfx::play(bool bLoop) { if (_fx && !_bPlayingLoop) { _fx->setLoop(bLoop); _fx->play(); if (bLoop) _bPlayingLoop = true; } } void RMSfx::setVolume(int vol) { if (_fx) { _fx->setVolume(vol); } } void RMSfx::pause(bool bPause) { if (_fx) { _fx->setPause(bPause); } } void RMSfx::stop() { if (_fx) { _fx->stop(); _bPlayingLoop = false; } } /****************************************************************************\ * RMItem Methods \****************************************************************************/ int RMItem::getCurPattern() { return _nCurPattern; } RMGfxSourceBuffer *RMItem::newItemSpriteBuffer(int dimx, int dimy, bool bPreRLE) { if (_cm == CM_256) { RMGfxSourceBuffer8RLE *spr; if (_FX == 2) { // AB spr = new RMGfxSourceBuffer8RLEWordAB; } else if (_FX == 1) { // OMBRA+AA if (dimx == -1 || dimx > 255) spr = new RMGfxSourceBuffer8RLEWordAA; else spr = new RMGfxSourceBuffer8RLEByteAA; spr->setAlphaBlendColor(_FXparm); if (bPreRLE) spr->setAlreadyCompressed(); } else { if (dimx == -1 || dimx > 255) spr = new RMGfxSourceBuffer8RLEWord; else spr = new RMGfxSourceBuffer8RLEByte; if (bPreRLE) spr->setAlreadyCompressed(); } return spr; } else return new RMGfxSourceBuffer16; } bool RMItem::isIn(const RMPoint &pt, int *size) { RMRect rc; if (!_bIsActive) return false; // Search for the right bounding box to use - use the sprite's if it has one, otherwise use the generic one if (_nCurPattern != 0 && !_sprites[_nCurSprite]._rcBox.isEmpty()) rc = _sprites[_nCurSprite]._rcBox + calculatePos(); else if (!_rcBox.isEmpty()) rc = _rcBox; // If no box, return immediately else return false; if (size != NULL) *size = rc.size(); return rc.ptInRect(pt + _curScroll); } void RMItem::readFromStream(Common::SeekableReadStream &ds, bool bLOX) { // MPAL code _mpalCode = ds.readSint32LE(); // Object name _name = readString(ds); // Z (signed) _z = ds.readSint32LE(); // Parent position _pos.readFromStream(ds); // Hotspot _hot.readFromStream(ds); // Bounding box _rcBox.readFromStream(ds); // Number of sprites, sound effects, and patterns _nSprites = ds.readSint32LE(); _nSfx = ds.readSint32LE(); _nPatterns = ds.readSint32LE(); // Color mode byte cm = ds.readByte(); _cm = (RMColorMode)cm; // Flag for the presence of custom palette differences _bPal = ds.readByte(); if (_cm == CM_256) { // If there is a palette, read it in if (_bPal) _pal.readFromStream(ds); } // MPAL data if (!bLOX) ds.skip(20); _FX = ds.readByte(); _FXparm = ds.readByte(); if (!bLOX) ds.skip(106); // Create sub-classes if (_nSprites > 0) _sprites = new RMSprite[_nSprites]; if (_nSfx > 0) _sfx = new RMSfx[_nSfx]; _patterns = new RMPattern[_nPatterns + 1]; int dimx, dimy; // Read in class data if (!ds.err()) { for (int i = 0; i < _nSprites && !ds.err(); i++) { // Download the sprites if (bLOX) _sprites[i].LOXGetSizeFromStream(ds, &dimx, &dimy); else _sprites[i].getSizeFromStream(ds, &dimx, &dimy); _sprites[i].init(newItemSpriteBuffer(dimx, dimy, bLOX)); _sprites[i].readFromStream(ds, bLOX); if (_cm == CM_256 && _bPal) _sprites[i].setPalette(_pal._data); } } if (!ds.err()) { for (int i = 0; i < _nSfx && !ds.err(); i++) { _sfx[i].readFromStream(ds, bLOX); } } // Read the pattern from pattern 1 if (!ds.err()) { for (int i = 1; i <= _nPatterns && !ds.err(); i++) _patterns[i].readFromStream(ds, bLOX); } // Initialize the current pattern if (_bInitCurPattern) setPattern(mpalQueryItemPattern(_mpalCode)); // Initialize the current activation state _bIsActive = mpalQueryItemIsActive(_mpalCode); } RMGfxPrimitive *RMItem::newItemPrimitive() { return new RMGfxPrimitive(this); } void RMItem::setScrollPosition(const RMPoint &scroll) { _curScroll = scroll; } bool RMItem::doFrame(RMGfxTargetBuffer *bigBuf, bool bAddToList) { int oldSprite = _nCurSprite; // Pattern 0 = Do not draw anything! if (_nCurPattern == 0) return false; // We do an update of the pattern, which also returns the current frame if (_nCurPattern != 0) { _nCurSprite = _patterns[_nCurPattern].update(_hEndPattern, _bCurFlag, _sfx); // WORKAROUND: Currently, m_nCurSprite = -1 is used to flag that an item should be removed. // However, this seems to be done inside a process waiting on an event pulsed inside the pattern // Update method. So the value of m_nCurSprite = -1 is being destroyed with the return value // replacing it. It may be that the current coroutine PulseEvent implementation is wrong somehow. // In any case, a special check here is done for items that have ended if (_nCurPattern == 0) _nCurSprite = -1; } // If the function returned -1, it means that the pattern has finished if (_nCurSprite == -1) { // We have pattern 0, so leave. The class will self de-register from the OT list _nCurPattern = 0; return false; } // If we are not in the OT list, add ourselves if (!_nInList && bAddToList) bigBuf->addPrim(newItemPrimitive()); return oldSprite != _nCurSprite; } RMPoint RMItem::calculatePos() { return _pos + _patterns[_nCurPattern].pos(); } void RMItem::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); // If CurSprite == -1, then the pattern is finished if (_nCurSprite == -1) return; // Set the flag prim->setFlag(_bCurFlag); // Offset direction for scrolling prim->getDst().offset(-_curScroll); // We must offset the coordinates of the item inside the primitive // It is estimated as nonno + (babbo + figlio) prim->getDst().offset(calculatePos()); // No stretching, please prim->setStretch(false); // Now we turn to the generic surface drawing routines CORO_INVOKE_2(_sprites[_nCurSprite].draw, bigBuf, prim); CORO_END_CODE; } /** * Overloaded priority: it's based on Z ordering */ int RMItem::priority() { return _z; } /** * Pattern number */ int RMItem::numPattern() { return _nPatterns; } void RMItem::removeThis(CORO_PARAM, bool &result) { // Remove from the OT list if the current frame is -1 (pattern over) result = (_nCurSprite == -1); } void RMItem::setStatus(int nStatus) { _bIsActive = (nStatus > 0); } RMPoint RMItem::getHotspot() { return _hot; } int RMItem::mpalCode() { return _mpalCode; } void RMItem::setPattern(int nPattern, bool bPlayP0) { assert(nPattern >= 0 && nPattern <= _nPatterns); if (_sfx) { if (_nCurPattern > 0) _patterns[_nCurPattern].stopSfx(_sfx); } // Remember the current pattern _nCurPattern = nPattern; // Start the pattern to start the animation if (_nCurPattern != 0) _nCurSprite = _patterns[_nCurPattern].init(_sfx, bPlayP0, &_bCurFlag); else { _nCurSprite = -1; // Look for the sound effect for pattern 0 if (bPlayP0) { for (int i = 0; i < _nSfx; i++) { if (_sfx[i]._name == "p0") _sfx[i].play(); } } } } bool RMItem::getName(Common::String &name) { char buf[256]; mpalQueryItemName(_mpalCode, buf); name = buf; if (buf[0] == '\0') return false; return true; } void RMItem::unload() { if (_patterns != NULL) { delete[] _patterns; _patterns = NULL; } if (_sprites != NULL) { delete[] _sprites; _sprites = NULL; } if (_sfx != NULL) { delete[] _sfx; _sfx = NULL; } } RMItem::RMItem() { _bCurFlag = 0; _patterns = NULL; _sprites = NULL; _sfx = NULL; _curScroll.set(0, 0); _bInitCurPattern = true; _nCurPattern = 0; _z = 0; _cm = CM_256; _FX = 0; _FXparm = 0; _mpalCode = 0; _nSprites = 0; _nSfx = 0; _nPatterns = 0; _bPal = 0; _nCurSprite = 0; _bIsActive = false; memset(_pal._data, 0, sizeof(_pal._data)); _hEndPattern = CoroScheduler.createEvent(false, false); } RMItem::~RMItem() { unload(); CoroScheduler.closeEvent(_hEndPattern); } void RMItem::waitForEndPattern(CORO_PARAM, uint32 hCustomSkip) { CORO_BEGIN_CONTEXT; uint32 h[2]; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); if (_nCurPattern != 0) { if (hCustomSkip == CORO_INVALID_PID_VALUE) CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _hEndPattern, CORO_INFINITE); else { _ctx->h[0] = hCustomSkip; _ctx->h[1] = _hEndPattern; CORO_INVOKE_4(CoroScheduler.waitForMultipleObjects, 2, &_ctx->h[0], false, CORO_INFINITE); } } CORO_END_CODE; } void RMItem::changeHotspot(const RMPoint &pt) { _hot = pt; } void RMItem::setInitCurPattern(bool status) { _bInitCurPattern = status; } void RMItem::playSfx(int nSfx) { if (nSfx < _nSfx) _sfx[nSfx].play(); } void RMItem::pauseSound(bool bPause) { for (int i = 0; i < _nSfx; i++) _sfx[i].pause(bPause); } /****************************************************************************\ * RMWipe Methods \****************************************************************************/ RMWipe::RMWipe() { _hUnregistered = CoroScheduler.createEvent(false, false); _hEndOfFade = CoroScheduler.createEvent(false, false); _bMustRegister = false; _bUnregister = false; _bEndFade = false; _bFading = false; _nFadeStep = 0; } RMWipe::~RMWipe() { CoroScheduler.closeEvent(_hUnregistered); CoroScheduler.closeEvent(_hEndOfFade); } int RMWipe::priority() { return 200; } void RMWipe::unregister() { RMGfxTask::unregister(); assert(_nInList == 0); CoroScheduler.setEvent(_hUnregistered); } void RMWipe::removeThis(CORO_PARAM, bool &result) { result = _bUnregister; } void RMWipe::waitForFadeEnd(CORO_PARAM) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _hEndOfFade, CORO_INFINITE); _bEndFade = true; _bFading = false; CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); CORO_INVOKE_2(CoroScheduler.waitForSingleObject, g_vm->_hEndOfFrame, CORO_INFINITE); CORO_END_CODE; } void RMWipe::closeFade() { _wip0r.unload(); } void RMWipe::initFade(int type) { // Activate the fade _bUnregister = false; _bEndFade = false; _nFadeStep = 0; _bMustRegister = true; RMRes res(RES_W_CIRCLE); Common::SeekableReadStream *ds = res.getReadStream(); _wip0r.readFromStream(*ds); delete ds; _wip0r.setPattern(1); _bFading = true; } void RMWipe::doFrame(RMGfxTargetBuffer &bigBuf) { if (_bMustRegister) { bigBuf.addPrim(new RMGfxPrimitive(this)); _bMustRegister = false; } if (_bFading) { _wip0r.doFrame(&bigBuf, false); _nFadeStep++; if (_nFadeStep == 10) { CoroScheduler.setEvent(_hEndOfFade); } } } void RMWipe::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); if (_bFading) { CORO_INVOKE_2(_wip0r.draw, bigBuf, prim); } if (_bEndFade) Common::fill((byte *)bigBuf, (byte *)bigBuf + bigBuf.getDimx() * bigBuf.getDimy() * 2, 0x0); CORO_END_CODE; } /****************************************************************************\ * RMCharacter Methods \****************************************************************************/ /****************************************************************************/ /* Find the shortest path between two nodes of the graph connecting the BOX */ /* Returns path along the vector path path[] */ /****************************************************************************/ bool RMCharacter::findPath(short source, short destination) { static RMBox box[MAXBOXES]; // Matrix of adjacent boxes static short nodeCost[MAXBOXES]; // Cost per node static short valid[MAXBOXES]; // 0:Invalid 1:Valid 2:Saturated static short nextNode[MAXBOXES]; // Next node bool error = false; RMBoxLoc *cur; g_system->lockMutex(_csMove); if (source == -1 || destination == -1) { g_system->unlockMutex(_csMove); return 0; } // Get the boxes cur = _theBoxes->getBoxes(_curLocation); // Make a backup copy to work on for (int i = 0; i < cur->_numbBox; i++) memcpy(&box[i], &cur->_boxes[i], sizeof(RMBox)); // Invalidate all nodes for (int i = 0; i < cur->_numbBox; i++) valid[i] = 0; // Prepare source and variables for the procedure nodeCost[source] = 0; valid[source] = 1; bool finish = false; // Find the shortest path while (!finish) { short minCost = 32000; // Reset the minimum cost error = true; // Possible error // 1st cycle: explore possible new nodes for (int i = 0; i < cur->_numbBox; i++) { if (valid[i] == 1) { error = false; // Failure de-bunked int j = 0; while (((box[i]._adj[j]) != 1) && (j < cur->_numbBox)) j++; if (j >= cur->_numbBox) valid[i] = 2; // nodo saturated? else { nextNode[i] = j; if (nodeCost[i] + 1 < minCost) minCost = nodeCost[i] + 1; } } } if (error) finish = true; // All nodes saturated // 2nd cycle: adding new nodes that were found, saturate old nodes for (int i = 0; i < cur->_numbBox; i++) { if ((valid[i] == 1) && ((nodeCost[i] + 1) == minCost)) { box[i]._adj[nextNode[i]] = 2; nodeCost[nextNode[i]] = minCost; valid[nextNode[i]] = 1; for (int j = 0; j < cur->_numbBox; j++) if (box[j]._adj[nextNode[i]] == 1) box[j]._adj[nextNode[i]] = 0; if (nextNode[i] == destination) finish = true; } } } // Remove the path from the adjacent modified matrixes if (!error) { _pathLength = nodeCost[destination]; short k = _pathLength; _path[k] = destination; while (_path[k] != source) { int i = 0; while (box[i]._adj[_path[k]] != 2) i++; k--; _path[k] = i; } _pathLength++; } g_system->unlockMutex(_csMove); return !error; } void RMCharacter::goTo(CORO_PARAM, RMPoint destcoord, bool bReversed) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); if (_pos == destcoord) { if (_minPath == 0) { CORO_INVOKE_0(stop); CoroScheduler.pulseEvent(_hEndOfPath); return; } } _status = WALK; _lineStart = _pos; _lineEnd = destcoord; _dx = _lineStart._x - _lineEnd._x; _dy = _lineStart._y - _lineEnd._y; _fx = _dx; _fy = _dy; _dx = ABS(_dx); _dy = ABS(_dy); _walkSpeed = _curSpeed; _walkCount = 0; if (bReversed) { while (0); } int nPatt = getCurPattern(); if (_dx > _dy) { _slope = _fy / _fx; if (_lineEnd._x < _lineStart._x) _walkSpeed = -_walkSpeed; _walkStatus = 1; // Change the pattern for the new direction _bNeedToStop = true; if ((_walkSpeed < 0 && !bReversed) || (_walkSpeed >= 0 && bReversed)) { if (nPatt != PAT_WALKLEFT) setPattern(PAT_WALKLEFT); } else { if (nPatt != PAT_WALKRIGHT) setPattern(PAT_WALKRIGHT); } } else { _slope = _fx / _fy; if (_lineEnd._y < _lineStart._y) _walkSpeed = -_walkSpeed; _walkStatus = 0; _bNeedToStop = true; if ((_walkSpeed < 0 && !bReversed) || (_walkSpeed >= 0 && bReversed)) { if (nPatt != PAT_WALKUP) setPattern(PAT_WALKUP); } else { if (nPatt != PAT_WALKDOWN) setPattern(PAT_WALKDOWN); } } _olddx = _dx; _olddy = _dy; CORO_END_CODE; } RMPoint RMCharacter::searching(char UP, char DOWN, char RIGHT, char LEFT, RMPoint point) { short steps; RMPoint newPt, foundPt; short minStep = 32000; if (UP) { newPt = point; steps = 0; while ((inWhichBox(newPt) == -1) && (newPt._y >= 0)) { newPt._y--; steps++; } if ((inWhichBox(newPt) != -1) && (steps < minStep) && findPath(inWhichBox(_pos), inWhichBox(newPt))) { minStep = steps; newPt._y--; // to avoid error? foundPt = newPt; } } if (DOWN) { newPt = point; steps = 0; while ((inWhichBox(newPt) == -1) && (newPt._y < 480)) { newPt._y++; steps++; } if ((inWhichBox(newPt) != -1) && (steps < minStep) && findPath(inWhichBox(_pos), inWhichBox(newPt))) { minStep = steps; newPt._y++; // to avoid error? foundPt = newPt; } } if (RIGHT) { newPt = point; steps = 0; while ((inWhichBox(newPt) == -1) && (newPt._x < 640)) { newPt._x++; steps++; } if ((inWhichBox(newPt) != -1) && (steps < minStep) && findPath(inWhichBox(_pos), inWhichBox(newPt))) { minStep = steps; newPt._x++; // to avoid error? foundPt = newPt; } } if (LEFT) { newPt = point; steps = 0; while ((inWhichBox(newPt) == -1) && (newPt._x >= 0)) { newPt._x--; steps++; } if ((inWhichBox(newPt) != -1) && (steps < minStep) && findPath(inWhichBox(_pos), inWhichBox(newPt))) { minStep = steps; newPt._x--; // to avoid error? foundPt = newPt; } } if (minStep == 32000) foundPt = point; return foundPt; } RMPoint RMCharacter::nearestPoint(const RMPoint &point) { return searching(1, 1, 1, 1, point); } short RMCharacter::scanLine(const RMPoint &point) { int Ldx, Ldy, Lcount; float Lfx, Lfy, Lslope; RMPoint Lstart, Lend, Lscan; signed char Lspeed, Lstatus; Lstart = _pos; Lend = point; Ldx = Lstart._x - Lend._x; Ldy = Lstart._y - Lend._y; Lfx = Ldx; Lfy = Ldy; Ldx = ABS(Ldx); Ldy = ABS(Ldy); Lspeed = 1; Lcount = 0; if (Ldx > Ldy) { Lslope = Lfy / Lfx; if (Lend._x < Lstart._x) Lspeed = -Lspeed; Lstatus = 1; } else { Lslope = Lfx / Lfy; if (Lend._y < Lstart._y) Lspeed = - Lspeed; Lstatus = 0; } Lscan = Lstart; // Start scanning while (inWhichBox(Lscan) != -1) { Lcount++; if (Lstatus) { Ldx = Lspeed * Lcount; Ldy = (int)(Lslope * Ldx); } else { Ldy = Lspeed * Lcount; Ldx = (int)(Lslope * Ldy); } Lscan._x = Lstart._x + Ldx; Lscan._y = Lstart._y + Ldy; if ((ABS(Lscan._x - Lend._x) <= 1) && (ABS(Lscan._y - Lend._y) <= 1)) return 1; } return 0; } /** * Calculates intersections between the straight line and the closest BBOX */ RMPoint RMCharacter::invScanLine(const RMPoint &point) { RMPoint lStart = point; // Exchange! RMPoint lEnd = _pos; // :-) int lDx = lStart._x - lEnd._x; int lDy = lStart._y - lEnd._y; float lFx = lDx; float lFy = lDy; lDx = ABS(lDx); lDy = ABS(lDy); signed char lSpeed = 1; int lCount = 0; signed char lStatus; float lSlope; if (lDx > lDy) { lSlope = lFy / lFx; if (lEnd._x < lStart._x) lSpeed = -lSpeed; lStatus = 1; } else { lSlope = lFx / lFy; if (lEnd._y < lStart._y) lSpeed = -lSpeed; lStatus = 0; } RMPoint lScan = lStart; signed char lBox = -1; for (;;) { if (inWhichBox(lScan) != -1) { if (inWhichBox(lScan) != lBox) { if (inWhichBox(_pos) == inWhichBox(lScan) || findPath(inWhichBox(_pos), inWhichBox(lScan))) return lScan; else lBox = inWhichBox(lScan); } } lCount++; if (lStatus) { lDx = lSpeed * lCount; lDy = (int)(lSlope * lDx); } else { lDy = lSpeed * lCount; lDx = (int)(lSlope * lDy); } lScan._x = lStart._x + lDx; lScan._y = lStart._y + lDy; // WORKAROUND: Handles cases where the points never fall inside a bounding box if (lScan._x < -100 || lScan._y < -100 || lScan._x >= 1000 || lScan._y >= 1000) return point; } } /** * Returns the HotSpot coordinate closest to the player */ RMPoint RMCharacter::nearestHotSpot(int sourcebox, int destbox) { RMPoint hotspot; int x, y; int minDist = 10000000; RMBoxLoc *cur = _theBoxes->getBoxes(_curLocation); for (short cc = 0; cc < cur->_boxes[sourcebox]._numHotspot; cc++) if ((cur->_boxes[sourcebox]._hotspot[cc]._destination) == destbox) { x = ABS(cur->_boxes[sourcebox]._hotspot[cc]._hotx - _pos._x); y = ABS(cur->_boxes[sourcebox]._hotspot[cc]._hoty - _pos._y); if ((x * x + y * y) < minDist) { minDist = x * x + y * y; hotspot._x = cur->_boxes[sourcebox]._hotspot[cc]._hotx; hotspot._y = cur->_boxes[sourcebox]._hotspot[cc]._hoty; } } return hotspot; } void RMCharacter::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); if (_bDrawNow) { prim->getDst() += _fixedScroll; CORO_INVOKE_2(RMItem::draw, bigBuf, prim); } CORO_END_CODE; } void RMCharacter::newBoxEntered(int nBox) { RMBoxLoc *cur; // Recall on ExitBox mpalQueryDoAction(3, _curLocation, _curBox); cur = _theBoxes->getBoxes(_curLocation); bool bOldReverse = cur->_boxes[_curBox]._bReversed; _curBox = nBox; // If Z is changed, we must remove it from the OT if (cur->_boxes[_curBox]._destZ != _z) { _bRemoveFromOT = true; _z = cur->_boxes[_curBox]._destZ; } // Movement management is reversed, only if we are not in the shortest path. If we are in the shortest // path, directly do the DoFrame if (_bMovingWithoutMinpath) { if ((cur->_boxes[_curBox]._bReversed && !bOldReverse) || (!cur->_boxes[_curBox]._bReversed && bOldReverse)) { switch (getCurPattern()) { case PAT_WALKUP: setPattern(PAT_WALKDOWN); break; case PAT_WALKDOWN: setPattern(PAT_WALKUP); break; case PAT_WALKRIGHT: setPattern(PAT_WALKLEFT); break; case PAT_WALKLEFT: setPattern(PAT_WALKRIGHT); break; default: break; } } } // Recall On EnterBox mpalQueryDoAction(2, _curLocation, _curBox); } void RMCharacter::doFrame(CORO_PARAM, RMGfxTargetBuffer *bigBuf, int loc) { CORO_BEGIN_CONTEXT; bool bEndNow; RMBoxLoc *cur; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); _ctx->bEndNow = false; _bEndOfPath = false; _bDrawNow = (_curLocation == loc); g_system->lockMutex(_csMove); // If we're walking.. if (_status != STAND) { // If we are going horizontally if (_walkStatus == 1) { _dx = _walkSpeed * _walkCount; _dy = (int)(_slope * _dx); _pos._x = _lineStart._x + _dx; _pos._y = _lineStart._y + _dy; // Right if (((_walkSpeed > 0) && (_pos._x > _lineEnd._x)) || ((_walkSpeed < 0) && (_pos._x < _lineEnd._x))) { _pos = _lineEnd; _status = STAND; _ctx->bEndNow = true; } } // If we are going vertical if (_walkStatus == 0) { _dy = _walkSpeed * _walkCount; _dx = (int)(_slope * _dy); _pos._x = _lineStart._x + _dx; _pos._y = _lineStart._y + _dy; // Down if (((_walkSpeed > 0) && (_pos._y > _lineEnd._y)) || ((_walkSpeed < 0) && (_pos._y < _lineEnd._y))) { _pos = _lineEnd; _status = STAND; _ctx->bEndNow = true; } } // Check if the character came out of the BOX in error, in which case he returns immediately if (inWhichBox(_pos) == -1) { _pos._x = _lineStart._x + _olddx; _pos._y = _lineStart._y + _olddy; } // If we have just moved to a temporary location, and is over the shortest path, we stop permanently if (_ctx->bEndNow && _minPath == 0) { if (!_bEndOfPath) CORO_INVOKE_0(stop); _bEndOfPath = true; CoroScheduler.pulseEvent(_hEndOfPath); } _walkCount++; // Update the character Z. @@@ Should remove only if the Z was changed // Check if the box was changed if (!_theBoxes->isInBox(_curLocation, _curBox, _pos)) newBoxEntered(inWhichBox(_pos)); // Update the old coordinates _olddx = _dx; _olddy = _dy; } // If we stop if (_status == STAND) { // Check if there is still the shortest path to calculate if (_minPath == 1) { _ctx->cur = _theBoxes->getBoxes(_curLocation); // If we still have to go through a box if (_pathCount < _pathLength) { // Check if the box we're going into is active if (_ctx->cur->_boxes[_path[_pathCount - 1]]._bActive) { // Move in a straight line towards the nearest hotspot, taking into account the reversing // NEWBOX = path[pathcount-1] CORO_INVOKE_2(goTo, nearestHotSpot(_path[_pathCount - 1], _path[_pathCount]), _ctx->cur->_boxes[_path[_pathCount - 1]]._bReversed); _pathCount++; } else { // If the box is off, we can only block all // @@@ Whilst this should not happen, because have improved // the search for the minimum path _minPath = 0; if (!_bEndOfPath) CORO_INVOKE_0(stop); _bEndOfPath = true; CoroScheduler.pulseEvent(_hEndOfPath); } } else { // If we have already entered the last box, we just have to move in a straight line towards the // point of arrival // NEWBOX = InWhichBox(pathend) _minPath = 0; CORO_INVOKE_2(goTo, _pathEnd, _ctx->cur->_boxes[inWhichBox(_pathEnd)]._bReversed); } } } g_system->unlockMutex(_csMove); // Invoke the DoFrame of the item RMItem::doFrame(bigBuf); CORO_END_CODE; } bool RMCharacter::endOfPath() { return _bEndOfPath; } void RMCharacter::stop(CORO_PARAM) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); _bMoving = false; // You never know.. _status = STAND; _minPath = 0; if (!_bNeedToStop) return; _bNeedToStop = false; switch (getCurPattern()) { case PAT_WALKUP: setPattern(PAT_STANDUP); break; case PAT_WALKDOWN: setPattern(PAT_STANDDOWN); break; case PAT_WALKLEFT: setPattern(PAT_STANDLEFT); break; case PAT_WALKRIGHT: setPattern(PAT_STANDRIGHT); break; default: setPattern(PAT_STANDDOWN); break; } CORO_END_CODE; } /** * Check if the character is moving */ bool RMCharacter::isMoving() { return _bMoving; } inline int RMCharacter::inWhichBox(const RMPoint &pt) { return _theBoxes->whichBox(_curLocation, pt); } void RMCharacter::move(CORO_PARAM, RMPoint pt, bool *result) { CORO_BEGIN_CONTEXT; RMPoint dest; int numbox; RMBoxLoc *cur; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); _bMoving = true; // 0, 0 does not do anything, just stops the character if (pt._x == 0 && pt._y == 0) { _minPath = 0; _status = STAND; CORO_INVOKE_0(stop); if (result) *result = true; return; } // If clicked outside the box _ctx->numbox = inWhichBox(pt); if (_ctx->numbox == -1) { // Find neareste point inside the box _ctx->dest = nearestPoint(pt); // ???!?? if (_ctx->dest == pt) _ctx->dest = invScanLine(pt); pt = _ctx->dest; _ctx->numbox = inWhichBox(pt); } _ctx->cur = _theBoxes->getBoxes(_curLocation); _minPath = 0; _status = STAND; _bMovingWithoutMinpath = true; if (scanLine(pt)) CORO_INVOKE_2(goTo, pt, _ctx->cur->_boxes[_ctx->numbox]._bReversed); else if (findPath(inWhichBox(_pos), inWhichBox(pt))) { _bMovingWithoutMinpath = false; _minPath = 1; _pathCount = 1; _pathEnd = pt; } else { // @@@ This case is whether a hotspot is inside a box, but there is // a path to get there. We use the InvScanLine to search around a point _ctx->dest = invScanLine(pt); pt = _ctx->dest; if (scanLine(pt)) CORO_INVOKE_2(goTo, pt, _ctx->cur->_boxes[_ctx->numbox]._bReversed); else if (findPath(inWhichBox(_pos), inWhichBox(pt))) { _bMovingWithoutMinpath = false; _minPath = 1; _pathCount = 1; _pathEnd = pt; if (result) *result = true; } else { if (result) *result = false; } return; } if (result) *result = true; CORO_END_CODE; } void RMCharacter::setPosition(const RMPoint &pt, int newloc) { RMBoxLoc *box; _minPath = 0; _status = STAND; _pos = pt; if (newloc != -1) _curLocation = newloc; // Update the character's Z value box = _theBoxes->getBoxes(_curLocation); _curBox = inWhichBox(_pos); assert(_curBox != -1); _z = box->_boxes[_curBox]._destZ; _bRemoveFromOT = true; } void RMCharacter::waitForEndMovement(CORO_PARAM) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); if (_bMoving) CORO_INVOKE_2(CoroScheduler.waitForSingleObject, _hEndOfPath, CORO_INFINITE); CORO_END_CODE; } void RMCharacter::setFixedScroll(const RMPoint &fix) { _fixedScroll = fix; } void RMCharacter::setSpeed(int speed) { _curSpeed = speed; } void RMCharacter::removeThis(CORO_PARAM, bool &result) { CORO_BEGIN_CONTEXT; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); if (_bRemoveFromOT) result = true; else CORO_INVOKE_1(RMItem::removeThis, result); CORO_END_CODE; } RMCharacter::RMCharacter() { _csMove = g_system->createMutex(); _hEndOfPath = CoroScheduler.createEvent(false, false); _minPath = 0; _curSpeed = 3; _bRemoveFromOT = false; _bMoving = false; _curLocation = 0; _curBox = 0; _dx = _dy = 0; _olddx = _olddy = 0; _fx = _fy = _slope = 0; _walkSpeed = _walkStatus = 0; _nextBox = 0; _pathLength = _pathCount = 0; _status = STAND; _theBoxes = NULL; _walkCount = 0; _bEndOfPath = false; _bMovingWithoutMinpath = false; _bDrawNow = false; _bNeedToStop = false; memset(_path, 0, sizeof(_path)); _pos.set(0, 0); } RMCharacter::~RMCharacter() { g_system->deleteMutex(_csMove); CoroScheduler.closeEvent(_hEndOfPath); } void RMCharacter::linkToBoxes(RMGameBoxes *boxes) { _theBoxes = boxes; } /****************************************************************************\ * RMBox Methods \****************************************************************************/ void RMBox::readFromStream(Common::ReadStream &ds) { // Bbox _left = ds.readSint32LE(); _top = ds.readSint32LE(); _right = ds.readSint32LE(); _bottom = ds.readSint32LE(); // Adjacency for (int i = 0; i < MAXBOXES; i++) { _adj[i] = ds.readSint32LE(); } // Misc _numHotspot = ds.readSint32LE(); _destZ = ds.readByte(); byte b = ds.readByte(); _bActive = b; b = ds.readByte(); _bReversed = b; // Reversed expansion space for (int i = 0; i < 30; i++) ds.readByte(); uint16 w; // Hotspots for (int i = 0; i < _numHotspot; i++) { w = ds.readUint16LE(); _hotspot[i]._hotx = w; w = ds.readUint16LE(); _hotspot[i]._hoty = w; w = ds.readUint16LE(); _hotspot[i]._destination = w; } } /****************************************************************************\ * RMBoxLoc Methods \****************************************************************************/ RMBoxLoc::RMBoxLoc() { _boxes = NULL; _numbBox = 0; } RMBoxLoc::~RMBoxLoc() { delete[] _boxes; } void RMBoxLoc::readFromStream(Common::ReadStream &ds) { char buf[2]; // ID and version buf[0] = ds.readByte(); buf[1] = ds.readByte(); byte ver = ds.readByte(); assert(buf[0] == 'B' && buf[1] == 'X'); assert(ver == 3); // Number of boxes _numbBox = ds.readSint32LE(); // Allocate memory for the boxes _boxes = new RMBox[_numbBox]; // Read in boxes for (int i = 0; i < _numbBox; i++) _boxes[i].readFromStream(ds); } void RMBoxLoc::recalcAllAdj() { for (int i = 0; i < _numbBox; i++) { Common::fill(_boxes[i]._adj, _boxes[i]._adj + MAXBOXES, 0); for (int j = 0; j < _boxes[i]._numHotspot; j++) { if (_boxes[_boxes[i]._hotspot[j]._destination]._bActive) _boxes[i]._adj[_boxes[i]._hotspot[j]._destination] = 1; } } } /****************************************************************************\ * RMGameBoxes methods \****************************************************************************/ RMGameBoxes::RMGameBoxes() { _nLocBoxes = 0; Common::fill(_allBoxes, _allBoxes + GAME_BOXES_SIZE, (RMBoxLoc *)NULL); } RMGameBoxes::~RMGameBoxes() { for (int i = 1; i <= _nLocBoxes; ++i) delete _allBoxes[i]; } void RMGameBoxes::init() { // Load boxes from disk _nLocBoxes = 130; for (int i = 1; i <= _nLocBoxes; i++) { RMRes res(10000 + i); Common::SeekableReadStream *ds = res.getReadStream(); _allBoxes[i] = new RMBoxLoc(); _allBoxes[i]->readFromStream(*ds); _allBoxes[i]->recalcAllAdj(); delete ds; } } void RMGameBoxes::close() { } RMBoxLoc *RMGameBoxes::getBoxes(int nLoc) { return _allBoxes[nLoc]; } int RMGameBoxes::getLocBoxesCount() const { return _nLocBoxes; } bool RMGameBoxes::isInBox(int nLoc, int nBox, const RMPoint &pt) { RMBoxLoc *cur = getBoxes(nLoc); if ((pt._x >= cur->_boxes[nBox]._left) && (pt._x <= cur->_boxes[nBox]._right) && (pt._y >= cur->_boxes[nBox]._top) && (pt._y <= cur->_boxes[nBox]._bottom)) return true; else return false; } int RMGameBoxes::whichBox(int nLoc, const RMPoint &punto) { RMBoxLoc *cur = getBoxes(nLoc); if (!cur) return -1; for (int i = 0; i < cur->_numbBox; i++) { if (cur->_boxes[i]._bActive) { if ((punto._x >= cur->_boxes[i]._left) && (punto._x <= cur->_boxes[i]._right) && (punto._y >= cur->_boxes[i]._top) && (punto._y <= cur->_boxes[i]._bottom)) return i; } } return -1; } void RMGameBoxes::changeBoxStatus(int nLoc, int nBox, int status) { _allBoxes[nLoc]->_boxes[nBox]._bActive = status; _allBoxes[nLoc]->recalcAllAdj(); } int RMGameBoxes::getSaveStateSize() { int size = 4; for (int i = 1; i <= _nLocBoxes; i++) { size += 4; size += _allBoxes[i]->_numbBox; } return size; } void RMGameBoxes::saveState(byte *state) { // Save the number of locations with boxes WRITE_LE_UINT32(state, _nLocBoxes); state += 4; // For each location, write out the number of boxes and their status for (int i = 1; i <= _nLocBoxes; i++) { WRITE_LE_UINT32(state, _allBoxes[i]->_numbBox); state += 4; for (int j = 0; j < _allBoxes[i]->_numbBox; j++) *state++ = _allBoxes[i]->_boxes[j]._bActive; } } void RMGameBoxes::loadState(byte *state) { // Load number of items int nloc = READ_LE_UINT32(state); state += 4; assert(nloc <= _nLocBoxes); // For each location, read the number of boxes and their status for (int i = 1; i <= nloc; i++) { int nbox = READ_LE_UINT32(state); state += 4; for (int j = 0; j < nbox ; j++) { if (j < _allBoxes[i]->_numbBox) _allBoxes[i]->_boxes[j]._bActive = *state; state++; } _allBoxes[i]->recalcAllAdj(); } } /****************************************************************************\ * RMLocation Methods \****************************************************************************/ /** * Standard constructor */ RMLocation::RMLocation() { _nItems = 0; _items = NULL; _buf = NULL; TEMPNumLoc = 0; _cmode = CM_256; } RMPoint RMLocation::TEMPGetTonyStart() { return TEMPTonyStart; } int RMLocation::TEMPGetNumLoc() { return TEMPNumLoc; } /** * Load a location (.LOC) from a given data stream * * @param ds Data stream * @returns True if succeeded OK, false in case of error. */ bool RMLocation::load(Common::SeekableReadStream &ds) { char id[3]; // Reset dirty rectangling _prevScroll.set(-1, -1); _prevFixedScroll.set(-1, -1); // Check the ID ds.read(id, 3); // Check if we are in a LOX if (id[0] == 'L' && id[1] == 'O' && id[2] == 'X') return loadLOX(ds); // Otherwise, check that it is a normal LOC if (id[0] != 'L' || id[1] != 'O' || id[2] != 'C') return false; // Version byte ver = ds.readByte(); assert(ver == 6); // Location name _name = readString(ds); // Skip the MPAL bailouts (64 bytes) TEMPNumLoc = ds.readSint32LE(); TEMPTonyStart._x = ds.readSint32LE(); TEMPTonyStart._y = ds.readSint32LE(); ds.skip(64 - 4 * 3); // Skip flag associated with the background (?) ds.skip(1); // Location dimensions int dimx = ds.readSint32LE(); int dimy = ds.readSint32LE(); _curScroll.set(0, 0); // Read the color mode byte cm = ds.readByte(); _cmode = (RMColorMode)cm; // Initialize the source buffer and read the location switch (_cmode) { case CM_256: _buf = new RMGfxSourceBuffer8; break; case CM_65K: _buf = new RMGfxSourceBuffer16; break; default: assert(0); break; }; // Initialize the surface, loading the palette if necessary _buf->init(ds, dimx, dimy, true); // Check the size of the location //assert(dimy!=512); // Number of objects _nItems = ds.readSint32LE(); // Create and read in the objects if (_nItems > 0) _items = new RMItem[_nItems]; g_vm->freezeTime(); for (int i = 0; i < _nItems && !ds.err(); i++) _items[i].readFromStream(ds); g_vm->unfreezeTime(); return ds.err(); } bool RMLocation::loadLOX(Common::SeekableReadStream &ds) { // Version byte ver = ds.readByte(); assert(ver == 1); // Location name _name = readString(ds); // Location number TEMPNumLoc = ds.readSint32LE(); TEMPTonyStart._x = ds.readSint32LE(); TEMPTonyStart._y = ds.readSint32LE(); // Dimensions int dimx = ds.readSint32LE(); int dimy = ds.readSint32LE(); _curScroll.set(0, 0); // It's always 65K (16-bit) mode _cmode = CM_65K; _buf = new RMGfxSourceBuffer16; // Initialize the surface, loading in the palette if necessary _buf->init(ds, dimx, dimy, true); // Number of items _nItems = ds.readSint32LE(); // Create and read objects if (_nItems > 0) _items = new RMItem[_nItems]; for (int i = 0; i < _nItems && !ds.err(); i++) _items[i].readFromStream(ds, true); return ds.err(); } /** * Draw method overloaded from RMGfxSourceBUffer8 */ void RMLocation::draw(CORO_PARAM, RMGfxTargetBuffer &bigBuf, RMGfxPrimitive *prim) { CORO_BEGIN_CONTEXT; bool priorTracking; bool hasChanges; CORO_END_CONTEXT(_ctx); CORO_BEGIN_CODE(_ctx); // Set the position of the source scrolling if (_buf->getDimy() > RM_SY || _buf->getDimx() > RM_SX) { prim->setSrc(RMRect(_curScroll, _curScroll + RMPoint(640, 480))); } prim->setDst(_fixedScroll); // Check whether dirty rects are being tracked, and if there are changes, leave tracking // turned on so a dirty rect will be added for the entire background _ctx->priorTracking = bigBuf.getTrackDirtyRects(); _ctx->hasChanges = (_prevScroll != _curScroll) || (_prevFixedScroll != _fixedScroll); bigBuf.setTrackDirtyRects(_ctx->priorTracking && _ctx->hasChanges); // Invoke the drawing method fo the image class, which will draw the location background CORO_INVOKE_2(_buf->draw, bigBuf, prim); if (_ctx->hasChanges) { _prevScroll = _curScroll; _prevFixedScroll = _fixedScroll; } bigBuf.setTrackDirtyRects(_ctx->priorTracking); CORO_END_CODE; } /** * Prepare a frame, adding the location to the OT list, and all the items that have changed animation frame. */ void RMLocation::doFrame(RMGfxTargetBuffer *bigBuf) { // If the location is not in the OT list, add it in if (!_nInList) bigBuf->addPrim(new RMGfxPrimitive(this)); // Process all the location items for (int i = 0; i < _nItems; i++) _items[i].doFrame(bigBuf); } RMItem *RMLocation::getItemFromCode(uint32 dwCode) { for (int i = 0; i < _nItems; i++) { if (_items[i].mpalCode() == (int)dwCode) return &_items[i]; } return NULL; } RMItem *RMLocation::whichItemIsIn(const RMPoint &pt) { int found = -1; int foundSize = 0; int size; for (int i = 0; i < _nItems; i++) { size = 0; if (_items[i].isIn(pt, &size)) { if (found == -1 || size < foundSize) { foundSize = size; found = i; } } } if (found == -1) return NULL; else return &_items[found]; } RMLocation::~RMLocation() { unload(); } void RMLocation::unload() { // Clear memory if (_items) { delete[] _items; _items = NULL; } // Destroy the buffer if (_buf) { delete _buf; _buf = NULL; } } void RMLocation::updateScrolling(const RMPoint &ptShowThis) { RMPoint oldScroll = _curScroll; if (_curScroll._x + 250 > ptShowThis._x) { _curScroll._x = ptShowThis._x - 250; } else if (_curScroll._x + RM_SX - 250 < ptShowThis._x) { _curScroll._x = ptShowThis._x + 250 - RM_SX; } else if (ABS(_curScroll._x + RM_SX / 2 - ptShowThis._x) > 32 && _buf->getDimx() > RM_SX) { if (_curScroll._x + RM_SX / 2 < ptShowThis._x) _curScroll._x++; else _curScroll._x--; } if (_curScroll._y + 180 > ptShowThis._y) { _curScroll._y = ptShowThis._y - 180; } else if (_curScroll._y + RM_SY - 180 < ptShowThis._y) { _curScroll._y = ptShowThis._y + 180 - RM_SY; } else if (ABS(_curScroll._y + RM_SY / 2 - ptShowThis._y) > 16 && _buf->getDimy() > RM_SY) { if (_curScroll._y + RM_SY / 2 < ptShowThis._y) _curScroll._y++; else _curScroll._y--; } if (_curScroll._x < 0) _curScroll._x = 0; if (_curScroll._y < 0) _curScroll._y = 0; if (_curScroll._x + RM_SX > _buf->getDimx()) _curScroll._x = _buf->getDimx() - RM_SX; if (_curScroll._y + RM_SY > _buf->getDimy()) _curScroll._y = _buf->getDimy() - RM_SY; if (oldScroll != _curScroll) { for (int i = 0; i < _nItems; i++) _items[i].setScrollPosition(_curScroll); } } void RMLocation::setFixedScroll(const RMPoint &scroll) { _fixedScroll = scroll; for (int i = 0; i < _nItems; i++) _items[i].setScrollPosition(_curScroll - _fixedScroll); } void RMLocation::setScrollPosition(const RMPoint &scroll) { RMPoint pt = scroll; if (pt._x < 0) pt._x = 0; if (pt._y < 0) pt._y = 0; if (pt._x + RM_SX > _buf->getDimx()) pt._x = _buf->getDimx() - RM_SX; if (pt._y + RM_SY > _buf->getDimy()) pt._y = _buf->getDimy() - RM_SY; _curScroll = pt; for (int i = 0; i < _nItems; i++) _items[i].setScrollPosition(_curScroll); } void RMLocation::pauseSound(bool bPause) { for (int i = 0; i < _nItems; i++) _items[i].pauseSound(bPause); } /** * Read the current scroll position */ RMPoint RMLocation::scrollPosition() { return _curScroll; } /****************************************************************************\ * RMMessage Methods \****************************************************************************/ RMMessage::RMMessage(uint32 dwId) { load(dwId); } RMMessage::RMMessage() { _lpMessage = NULL; _nPeriods = 0; for (int i = 0; i < 256; i++) _lpPeriods[i] = 0; } RMMessage::~RMMessage() { if (_lpMessage) globalDestroy(_lpMessage); } void RMMessage::load(uint32 dwId) { _lpMessage = mpalQueryMessage(dwId); assert(_lpMessage != NULL); if (_lpMessage) parseMessage(); } void RMMessage::parseMessage() { char *p; assert(_lpMessage != NULL); _nPeriods = 1; p = _lpPeriods[0] = _lpMessage; for (;;) { // Find the end of the current period while (*p != '\0') p++; // If there is another '0' at the end of the string, the end has been found p++; if (*p == '\0') break; // Otherwise there is another line, and remember it's start _lpPeriods[_nPeriods++] = p; } } bool RMMessage::isValid() { return _lpMessage != NULL; } int RMMessage::numPeriods() { return _nPeriods; } char *RMMessage::period(int num) { return _lpPeriods[num]; } char *RMMessage::operator[](int num) { return _lpPeriods[num]; } } // End of namespace Tony