scummvm/engines/dreamweb/object.cpp
Colin Snover a6659ba9d5 DREAMWEB: Use accurate memory reclamation for Ex transfers
When the Ex memory regions are close to full, it is possible for
the game to fail to purge objects and then crash with an OOM error
even if it isn't actually out of memory. This patch calculates the
amount of free memory truly needed when allocating to Ex memory to
allow exactly the entire frame & text regions to be used, instead
previously where a hard-coded amount of free space to maintain was
used, which guaranteed that the entire memory region could not
actually be used by the game.

This change may be masking some underlying memory leak, or it may
just be that near the end of the game the game naturally comes
close to reaching the maximum memory region size. For the moment,
I am assuming the latter.

This commit also adds some assertion checks to the memory transfer
functions to make sure the regions don't quietly overflow in other
cases, since pickupConts performs transfers in a manner that
doesn't ensure enough free memory exists for them to be successful.

Fixes Trac#6820.
2017-11-12 23:15:05 -06:00

1256 lines
28 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 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.
*
*/
#include "dreamweb/dreamweb.h"
namespace DreamWeb {
void DreamWebEngine::showRyanPage() {
showFrame(_icons1, kInventx + 167, kInventy - 12, 12, 0);
showFrame(_icons1, kInventx + 167 + 18 * _vars._ryanPage, kInventy - 12, 13 + _vars._ryanPage, 0);
}
void DreamWebEngine::findAllRyan() {
memset(_ryanInvList, 0xff, sizeof(_ryanInvList));
for (uint i = 0; i < kNumexobjects; ++i) {
const DynObject *extra = getExAd(i);
if (extra->mapad[0] != kExObjectType)
continue;
if (extra->mapad[1] != 0xff)
continue;
uint8 slot = extra->mapad[2];
assert(slot < 30);
_ryanInvList[slot]._index = i;
_ryanInvList[slot]._type = kExObjectType;
}
}
void DreamWebEngine::fillRyan() {
ObjectRef *inv = &_ryanInvList[_vars._ryanPage * 10];
findAllRyan();
for (uint i = 0; i < 2; ++i) {
for (uint j = 0; j < 5; ++j) {
obToInv(inv->_index, inv->_type, kInventx + j * kItempicsize, kInventy + i * kItempicsize);
++inv;
}
}
showRyanPage();
}
bool DreamWebEngine::isItWorn(const DynObject *object) {
return (object->objId[0] == 'W'-'A') && (object->objId[1] == 'E'-'A');
}
void DreamWebEngine::wornError() {
_commandType = 255;
delPointer();
printMessage(76, 21, 57, 240, false);
workToScreenM();
hangOnP(50);
showPanel();
showMan();
examIcon();
_commandType = 255;
workToScreenM();
}
void DreamWebEngine::makeWorn(DynObject *object) {
object->objId[0] = 'W'-'A';
object->objId[1] = 'E'-'A';
}
void DreamWebEngine::obToInv(uint8 index, uint8 flag, uint16 x, uint16 y) {
showFrame(_icons1, x - 2, y - 1, 10, 0);
if (index == 0xff)
return;
if (flag == kExObjectType)
showFrame(_exFrames, x + 18, y + 19, 3 * index + 1, 128);
else
showFrame(_freeFrames, x + 18, y + 19, 3 * index + 1, 128);
const DynObject *object = (const DynObject *)getAnyAdDir(index, flag);
bool worn = isItWorn(object);
if (worn)
showFrame(_icons1, x - 3, y - 2, 7, 0);
}
void DreamWebEngine::obPicture() {
if (_objectType == kSetObjectType1)
return;
uint8 frame = 3 * _command + 1;
if (_objectType == kExObjectType)
showFrame(_exFrames, 160, 68, frame, 0x80);
else
showFrame(_freeFrames, 160, 68, frame, 0x80);
}
void DreamWebEngine::obIcons() {
uint8 slotSize, slotCount;
getAnyAd(&slotSize, &slotCount);
if (slotSize != 0xff) {
// can open it
showFrame(_icons2, 210, 1, 4, 0);
}
showFrame(_icons2, 260, 1, 1, 0);
}
void DreamWebEngine::examineOb(bool examineAgain) {
_pointerMode = 0;
_timeCount = 0;
while (true) {
if (examineAgain) {
_inMapArea = 0;
_examAgain = 0;
_openedOb = 255;
_openedType = 255;
_invOpen = 0;
_objectType = _commandType;
_itemFrame = 0;
_pointerFrame = 0;
createPanel();
showPanel();
showMan();
showExit();
obIcons();
obPicture();
describeOb();
underTextLine();
_commandType = 255;
readMouse();
showPointer();
workToScreen();
delPointer();
examineAgain = false;
}
readMouse();
showPointer();
waitForVSync();
dumpPointer();
dumpTextLine();
delPointer();
_getBack = 0;
switch (_invOpen) {
case 0: {
RectWithCallback examList[] = {
{ 273,320,157,198,&DreamWebEngine::getBackFromOb },
{ 260,300,0,44,&DreamWebEngine::useObject },
{ 210,254,0,44,&DreamWebEngine::selectOpenOb },
{ 144,176,64,96,&DreamWebEngine::setPickup },
{ 0,50,50,200,&DreamWebEngine::examineInventory },
{ 0,320,0,200,&DreamWebEngine::blank },
{ 0xFFFF,0,0,0,0 }
};
checkCoords(examList);
break;
}
case 1: {
// Note: This table contains the non-constant _openChangeSize!
RectWithCallback invList1[] = {
{ 273,320,157,198,&DreamWebEngine::getBackFromOb },
{ 255,294,0,24,&DreamWebEngine::dropObject },
{ kInventx+167,kInventx+167+(18*3),kInventy-18,kInventy-2,&DreamWebEngine::incRyanPage },
{ kInventx,_openChangeSize,kInventy+100,kInventy+100+kItempicsize,&DreamWebEngine::useOpened },
{ kInventx,kInventx+(5*kItempicsize),kInventy,kInventy+(2*kItempicsize),&DreamWebEngine::inToInv },
{ 0,320,0,200,&DreamWebEngine::blank },
{ 0xFFFF,0,0,0,0 }
};
checkCoords(invList1);
break;
}
default: {
RectWithCallback withList1[] = {
{ 273,320,157,198,&DreamWebEngine::getBackFromOb },
{ kInventx+167,kInventx+167+(18*3),kInventy-18,kInventy-2,&DreamWebEngine::incRyanPage },
{ kInventx,kInventx+(5*kItempicsize), kInventy,kInventy+(2*kItempicsize),&DreamWebEngine::selectOb },
{ 0,320,0,200,&DreamWebEngine::blank },
{ 0xFFFF,0,0,0,0 }
};
checkCoords(withList1);
break;
}
}
if (_quitRequested)
break;
if (_examAgain != 0)
examineAgain = true;
else if (_getBack != 0)
break;
}
_pickUp = 0;
if (_vars._watchingTime != 0 || _newLocation == 255) {
// isWatching
makeMainScreen();
}
_invOpen = 0;
_openedOb = 255;
}
void DreamWebEngine::inventory() {
if (_vars._manDead == 1 || _vars._watchingTime != 0) {
blank();
return;
}
commandOnlyCond(32, 239);
if (_mouseButton == _oldButton)
return;
if (!(_mouseButton & 1)) // only on left mouse button
return;
_timeCount = 0;
_pointerMode = 0;
_inMapArea = 0;
animPointer();
createPanel();
showPanel();
examIcon();
showMan();
showExit();
underTextLine();
_pickUp = 0;
_invOpen = 2;
openInv();
readMouse();
showPointer();
workToScreen();
delPointer();
_openedOb = 255;
examineOb(false);
}
void DreamWebEngine::transferText(uint8 from, uint8 to) {
_exText.setOffset(to, _vars._exTextPos);
const char *src = _freeDesc.getString(from);
char *dst = _exText._text + _vars._exTextPos;
size_t len = strlen(src);
assert(_vars._exTextPos + len + 1 <= kExtextlen);
memcpy(dst, src, len + 1);
_vars._exTextPos += len + 1;
}
void DreamWebEngine::getBackFromOb() {
if (_pickUp != 1)
getBack1();
else
blank();
}
byte DreamWebEngine::getOpenedSlotCount() {
byte obj = _openedOb;
switch (_openedType) {
case kExObjectType:
return getExAd(obj)->slotCount;
case kFreeObjectType:
return getFreeAd(obj)->slotCount;
default:
return getSetAd(obj)->slotCount;
}
}
byte DreamWebEngine::getOpenedSlotSize() {
byte obj = _openedOb;
switch (_openedType) {
case kExObjectType:
return getExAd(obj)->slotSize;
case kFreeObjectType:
return getFreeAd(obj)->slotSize;
default:
return getSetAd(obj)->slotSize;
}
}
void DreamWebEngine::openOb() {
uint8 commandLine[64] = "OBJECT NAME ONE ";
copyName(_openedType, _openedOb, commandLine);
printMessage(kInventx, kInventy+86, 62, 240, false);
printDirect(commandLine, _lastXPos + 5, kInventy+86, 220, false);
fillOpen();
_openChangeSize = getOpenedSlotCount() * kItempicsize + kInventx;
}
void DreamWebEngine::identifyOb() {
if (_vars._watchingTime != 0) {
blank();
return;
}
uint16 initialX = _mouseX - _mapAdX;
uint16 initialY = _mouseY - _mapAdY;
if (initialX >= 22 * 8 || initialY >= 20 * 8) {
blank();
return;
}
byte x = initialX & 0xFF;
byte y = initialY & 0xFF;
_inMapArea = 1;
_pointersPath = findPathOfPoint(x, y);
_pointerFirstPath = findFirstPath(x, y);
if (checkIfEx(x, y) || checkIfFree(x, y) ||
checkIfPerson(x, y) || checkIfSet(x, y))
return; // finishidentify
x = (_mouseX - _mapAdX) & 0xFF;
y = (_mouseY - _mapAdY) & 0xFF;
byte flag, flagEx, type, flagX, flagY;
checkOne(x, y, &flag, &flagEx, &type, &flagX, &flagY);
if (type != 0 && _vars._manDead != 1)
obName(type, 3);
else
blank();
}
ObjectRef DreamWebEngine::findInvPos() {
uint16 x = _mouseX - kInventx;
uint16 y = _mouseY - kInventy;
uint8 pos = (x / kItempicsize) + (y / kItempicsize) * 5;
uint8 invPos = _vars._ryanPage * 10 + pos;
_lastInvPos = invPos;
return _ryanInvList[invPos];
}
void DreamWebEngine::selectOb() {
ObjectRef objectId = findInvPos();
if (objectId._index == 255) {
blank();
return;
}
_withObject = objectId._index;
_withType = objectId._type;
if (objectId != _oldSubject || _commandType != 221) {
if (objectId == _oldSubject)
_commandType = 221;
_oldSubject = objectId;
commandWithOb(0, objectId._type, objectId._index);
}
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return;
delPointer();
_invOpen = 0;
useRoutine();
}
void DreamWebEngine::setPickup() {
if (_objectType != kSetObjectType1 && _objectType != kSetObjectType3) {
// Object types 1 and 3 are excluded, so the resulting object is a DynObject
uint8 dummy;
DynObject *object = (DynObject *)getAnyAd(&dummy, &dummy);
if (object->mapad[0] == 4) {
blank();
return;
}
} else {
blank();
return;
}
if (_commandType != 209) {
_commandType = 209;
commandWithOb(33, _objectType, _command);
}
if (_mouseButton != 1 || _mouseButton == _oldButton)
return;
createPanel();
showPanel();
showMan();
showExit();
examIcon();
_pickUp = 1;
_invOpen = 2;
if (_objectType != kExObjectType) {
assert(_objectType == kFreeObjectType);
_openedOb = 255;
_itemFrame = transferToEx(_command);
_objectType = kExObjectType;
DynObject *object = getExAd(_itemFrame);
object->mapad[0] = 20;
object->mapad[1] = 255;
} else {
_itemFrame = _command;
_openedOb = 255;
}
openInv();
workToScreenM();
}
void DreamWebEngine::deleteExFrame(uint8 frameNum) {
Frame *frame = &_exFrames._frames[frameNum];
uint16 frameSize = frame->width * frame->height;
// Note: the original asm didn't subtract frameSize from remainder
uint16 remainder = kExframeslen - frame->ptr() - frameSize;
uint16 startOff = frame->ptr();
uint16 endOff = startOff + frameSize;
// Shift frame data after this one down
memmove(&_exFrames._data[startOff], &_exFrames._data[endOff], remainder);
// Combined frame data is now frameSize smaller
_vars._exFramePos -= frameSize;
// Adjust all frame pointers pointing into the shifted data
for (unsigned int i = 0; i < kNumexobjects; ++i) {
if (_exData[i].mapad[0] != 0xff) {
frame = &_exFrames._frames[3*i+0];
if (frame->ptr() >= startOff) {
frame->setPtr(frame->ptr() - frameSize);
assert(frame->ptr() + frame->width*frame->height <= _vars._exFramePos);
} else {
assert(frame->ptr() + frame->width*frame->height <= startOff);
}
frame = &_exFrames._frames[3*i+1];
if (frame->ptr() >= startOff) {
frame->setPtr(frame->ptr() - frameSize);
assert(frame->ptr() + frame->width*frame->height <= _vars._exFramePos);
} else {
assert(frame->ptr() + frame->width*frame->height <= startOff);
}
}
}
}
void DreamWebEngine::deleteExText(uint8 textNum) {
uint16 offset = _exText.getOffset(textNum);
uint16 startOff = offset;
uint16 textSize = strlen(_exText.getString(textNum)) + 1;
uint16 endOff = startOff + textSize;
uint16 remainder = kExtextlen - offset - textSize;
// Shift text data after this one down
memmove(&_exText._text[startOff], &_exText._text[endOff], remainder);
// Combined text data is now frameSize smaller
_vars._exTextPos -= textSize;
// Adjust all text pointers pointing into the shifted data
for (unsigned int i = 0; i < kNumexobjects; ++i) {
uint16 t = _exText.getOffset(i);
if (t >= offset + textSize)
_exText.setOffset(i, t - textSize);
}
}
void DreamWebEngine::deleteExObject(uint8 index) {
DynObject *obj = getExAd(index);
memset(obj, 0xFF, sizeof(DynObject));
deleteExFrame(3*index);
deleteExFrame(3*index + 1);
deleteExText(index);
for (uint8 i = 0; i < kNumexobjects; ++i) {
DynObject *t = getExAd(i);
// Is this object contained in the one we've just deleted?
if (t->mapad[0] == 4 && t->mapad[1] == index)
deleteExObject(i);
}
}
void DreamWebEngine::removeObFromInv() {
if (_command == 100)
return; // object doesn't exist
assert(_objectType == kExObjectType);
deleteExObject(_command);
}
void DreamWebEngine::inToInv() {
if (!_pickUp) {
outOfInv();
return;
}
ObjectRef subject = findInvPos();
if (subject._index != 255) {
swapWithInv();
return;
}
subject._type = _objectType;
subject._index = _itemFrame;
if (subject != _oldSubject || _commandType != 220) {
if (subject == _oldSubject)
_commandType = 220;
_oldSubject = subject;
commandWithOb(35, subject._type, subject._index);
}
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return; // notletgo2
delPointer();
DynObject *object = getExAd(_itemFrame);
object->mapad[0] = 4;
object->mapad[1] = 255;
object->mapad[2] = _lastInvPos;
_pickUp = 0;
fillRyan();
readMouse();
showPointer();
outOfInv();
workToScreen();
delPointer();
}
void DreamWebEngine::outOfInv() {
ObjectRef subject = findInvPos();
if (subject._index == 255) {
blank();
return;
}
if (_mouseButton == 2) {
reExFromInv();
return;
}
if (subject != _oldSubject || _commandType != 221) {
if (subject == _oldSubject)
_commandType = 221;
_oldSubject = subject;
commandWithOb(36, subject._type, subject._index);
}
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return;
delPointer();
_pickUp = 1;
subject = findInvPos();
_objectType = subject._type;
_itemFrame = subject._index;
assert(subject._type == kExObjectType);
DynObject *object = getExAd(subject._index);
object->mapad[0] = 20;
object->mapad[1] = 255;
fillRyan();
readMouse();
showPointer();
inToInv();
workToScreen();
delPointer();
}
void DreamWebEngine::purgeALocation(uint8 index) {
// index == al
for (uint8 i = 0; i < kNumexobjects; ++i) {
DynObject *t = getExAd(i);
if (t->currentLocation == index && t->mapad[0] == 0) {
deleteExObject(i);
}
}
}
const uint8 *DreamWebEngine::getObTextStart() {
const uint8 *textBase = 0;
const uint8 *text;
uint16 textOff = 0;
if (_objectType == kFreeObjectType) {
text = (const uint8 *)_freeDesc.getString(_command);
} else if (_objectType == kSetObjectType1) {
textBase = (const uint8 *)_setDesc._text;
textOff = kNumSetTexts * 2;
text = (const uint8 *)_setDesc.getString(_command);
} else {
text = (const uint8 *)_exText.getString(_command);
}
if (_objectType != kSetObjectType1)
return text;
const uint8 *obname = text;
while (true) {
const uint8 *start = text;
findNextColon(&text);
// Not an empty description string?
if (*text != 0 && *text != ':')
return start;
// If the description string (of a SetObjectType1 object) is empty,
// look for an object with the same name.
// Example: Eden's garage door outside has two parts. The right part
// has no description of its own but uses that of the left part.
bool found = false;
do {
text++;
uint8 c = *obname;
// scan for matching first character
while (*text != c) {
text++;
// arbitrary give-up counter
// FIXME: Make this more precise to avoid reading out of bounds
if (text - (textBase - textOff) >= 8000) {
warning("Object description for %d/%d not found", _objectType, _command);
return obname;
}
}
// found matching first character, so match the rest
const uint8 *s1 = obname;
const uint8 *s2 = text;
do {
s1++;
s2++;
} while (*s1 != ':' && *s1 != 0 && *s1 == *s2);
if (*s1 == ':' || *s1 == 0)
found = true; // (prefix) matched the entire object name
} while (!found);
// We found an object with the same name. The next loop iteration
// will check if this one again has an empty description.
}
}
void DreamWebEngine::dropObject() {
if (_commandType != 223) {
_commandType = 223;
if (!_pickUp) {
blank();
return;
}
commandWithOb(37, _objectType, _itemFrame);
}
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return;
if (isItWorn(getEitherAd())) {
wornError();
return;
}
if (_realLocation != 47) {
byte flag, flagEx, type, flagX, flagY;
checkOne(_ryanX + 12, _ryanY + 12, &flag, &flagEx, &type, &flagX, &flagY);
if (flag >= 2) {
dropError();
return;
}
} else {
dropError();
return;
}
if (_mapXSize == 64 && _mapYSize == 64) {
// Inside lift
dropError();
return;
}
if (compare(_itemFrame, kExObjectType, "GUNA") || compare(_itemFrame, kExObjectType, "SHLD")) {
cantDrop();
return;
}
_objectType = kExObjectType;
DynObject *object = getExAd(_itemFrame);
object->mapad[0] = 0;
object->mapad[1] = ((_ryanX + 4) >> 4) + _mapX;
object->mapad[2] = (_ryanX + 4) & 0xF;
object->mapad[3] = ((_ryanY + 8) >> 4) + _mapY;
object->mapad[4] = (_ryanY + 8) & 0xF;
_pickUp = 0;
object->currentLocation = _realLocation;
}
bool DreamWebEngine::checkObjectSize() {
byte containerSize = getOpenedSlotSize();
DynObject *object = getEitherAd();
// If there is no size defined for the object in the editor, set its size
// to 6. This could be a bad idea, according to the original source.
byte objectSize = (object->objectSize != 255) ? object->objectSize : 6;
if (containerSize >= 100) {
// Special type of container: only objects of the same special type fit.
if (containerSize == objectSize)
return true;
errorMessage3();
return false;
}
if (objectSize >= 100) {
// Special type of object, but a regular container.
// Subtract 100 from the size to get its regular size.
objectSize -= 100;
}
if (containerSize >= objectSize)
return true;
errorMessage2();
return false;
}
void DreamWebEngine::selectOpenOb() {
uint8 slotSize, slotCount;
getAnyAd(&slotSize, &slotCount);
if (slotCount == 255) {
// Can't open the object
blank();
return;
}
if (_commandType != 224) {
_commandType = 224;
commandWithOb(38, _objectType, _command);
}
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return;
_openedOb = _command;
_openedType = _objectType;
createPanel();
showPanel();
showMan();
examIcon();
showExit();
openInv();
openOb();
underTextLine();
readMouse();
showPointer();
workToScreen();
delPointer();
}
void DreamWebEngine::reExFromInv() {
ObjectRef objectId = findInvPos();
_commandType = objectId._type;
_command = objectId._index;
_examAgain = 1;
_pointerMode = 0;
}
void DreamWebEngine::swapWithInv() {
ObjectRef subject;
subject._type = _objectType;
subject._index = _itemFrame;
if (subject != _oldSubject || _commandType != 243) {
if (subject == _oldSubject)
_commandType = 243;
_oldSubject = subject;
commandWithOb(34, subject._type, subject._index);
}
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return;
byte prevType = _objectType;
byte prevFrame = _itemFrame;
ObjectRef objectId = findInvPos();
_itemFrame = objectId._index;
_objectType = objectId._type;
DynObject *object = getEitherAd();
object->mapad[0] = 20;
object->mapad[1] = 255;
byte prevType2 = _objectType;
byte prevFrame2 = _itemFrame;
_objectType = prevType;
_itemFrame = prevFrame;
delPointer();
object = getEitherAd();
object->mapad[0] = 4;
object->mapad[1] = 255;
object->mapad[2] = _lastInvPos;
_objectType = prevType2;
_itemFrame = prevFrame2;
fillRyan();
readMouse();
showPointer();
workToScreen();
delPointer();
}
void DreamWebEngine::useOpened() {
if (_openedOb == 255)
return; // cannot use opened object
if (!_pickUp) {
outOfOpen();
return;
}
ObjectRef objectId = findOpenPos();
if (objectId._index != 255) {
swapWithOpen();
return;
}
if (_pickUp != 1) {
blank();
return;
}
objectId._type = _objectType;
objectId._index = _itemFrame;
if (objectId != _oldSubject || _commandType != 227) {
if (objectId == _oldSubject)
_commandType = 227;
_oldSubject = objectId;
commandWithOb(35, objectId._type, objectId._index);
}
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return;
if (isItWorn(getEitherAd())) {
wornError();
return;
}
delPointer();
if (_itemFrame == _openedOb &&
_objectType == _openedType) {
errorMessage1();
return;
}
if (!checkObjectSize())
return;
_pickUp = 0;
DynObject *object = getEitherAd();
object->mapad[0] = _openedType;
object->mapad[1] = _openedOb;
object->mapad[2] = _lastInvPos;
object->mapad[3] = _realLocation;
fillOpen();
underTextLine();
readMouse();
useOpened();
showPointer();
workToScreen();
delPointer();
}
void DreamWebEngine::outOfOpen() {
if (_openedOb == 255)
return; // cannot use opened object
ObjectRef objectId = findOpenPos();
if (objectId._index == 255) {
blank();
return;
}
if (objectId != _oldSubject || _commandType != 228) {
if (objectId == _oldSubject)
_commandType = 228;
_oldSubject = objectId;
commandWithOb(36, objectId._type, objectId._index);
}
if (_mouseButton == _oldButton)
return; // notletgo4
if (_mouseButton != 1)
return;
delPointer();
_pickUp = 1;
objectId = findOpenPos();
_objectType = objectId._type;
_itemFrame = objectId._index;
if (_objectType != kExObjectType) {
assert(objectId._type == kFreeObjectType);
_itemFrame = transferToEx(objectId._index);
_objectType = kExObjectType;
}
DynObject *object = getEitherAd();
object->mapad[0] = 20;
object->mapad[1] = 255;
fillOpen();
underTextLine();
readMouse();
useOpened();
showPointer();
workToScreen();
delPointer();
}
void DreamWebEngine::swapWithOpen() {
ObjectRef subject;
subject._type = _objectType;
subject._index = _itemFrame;
if (subject != _oldSubject || _commandType != 242) {
if (subject == _oldSubject)
_commandType = 242;
_oldSubject = subject;
commandWithOb(34, subject._type, subject._index);
}
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return;
if (isItWorn(getEitherAd())) {
wornError();
return;
}
delPointer();
if (_itemFrame == _openedOb &&
_objectType == _openedType) {
errorMessage1();
return;
}
if (!checkObjectSize())
return;
byte prevType = _objectType;
byte prevFrame = _itemFrame;
ObjectRef objectId = findOpenPos();
_objectType = objectId._type;
_itemFrame = objectId._index;
if (_objectType != kExObjectType) {
assert(objectId._type == kFreeObjectType);
_itemFrame = transferToEx(objectId._index);
_objectType = kExObjectType;
}
DynObject *object = getEitherAd();
object->mapad[0] = 20;
object->mapad[1] = 255;
byte prevType2 = _objectType;
byte prevFrame2 = _itemFrame;
_objectType = prevType;
_itemFrame = prevFrame;
object = getEitherAd();
object->mapad[0] = _openedType;
object->mapad[1] = _openedOb;
object->mapad[2] = _lastInvPos;
object->mapad[3] = _realLocation;
_objectType = prevType2;
_itemFrame = prevFrame2;
fillOpen();
fillRyan();
underTextLine();
readMouse();
useOpened();
showPointer();
workToScreen();
delPointer();
}
ObjectRef DreamWebEngine::findOpenPos() {
uint8 pos = (_mouseX - kInventx) / kItempicsize;
_lastInvPos = pos;
return _openInvList[pos];
}
byte DreamWebEngine::transferToEx(uint8 from) {
emergencyPurge(from);
byte pos = getExPos();
DynObject *exObject = getExAd(pos);
DynObject *freeObject = getFreeAd(from);
memcpy(exObject, freeObject, sizeof(DynObject));
exObject->currentLocation = _realLocation;
exObject->initialLocation = _realLocation;
exObject->index = from;
exObject->mapad[0] = 4;
exObject->mapad[1] = 255;
exObject->mapad[2] = _lastInvPos;
transferFrame(from, pos, 0);
transferFrame(from, pos, 1);
transferText(from, pos);
freeObject->mapad[0] = 254;
pickupConts(from, pos);
return pos;
}
void DreamWebEngine::fillOpen() {
delTextLine();
uint8 size = getOpenedSlotCount();
if (size > 4)
size = 4;
findAllOpen();
for (uint8 i = 0; i < size; ++i) {
uint8 index = _openInvList[i]._index;
uint8 type = _openInvList[i]._type;
obToInv(index, type, kInventx + i * kItempicsize, kInventy + 96);
}
underTextLine();
}
void DreamWebEngine::findAllOpen() {
memset(_openInvList, 0xFF, 32);
for (uint8 i = 0; i < kNumexobjects; ++i) {
const DynObject *obj = getExAd(i);
if (obj->mapad[1] != _openedOb)
continue;
if (obj->mapad[0] != _openedType)
continue;
if (_openedType != kExObjectType && obj->mapad[3] != _realLocation)
continue;
uint8 slot = obj->mapad[2];
assert(slot < 16);
_openInvList[slot]._index = i;
_openInvList[slot]._type = kExObjectType;
}
for (uint8 i = 0; i < 80; ++i) {
const DynObject *obj = getFreeAd(i);
if (obj->mapad[1] != _openedOb)
continue;
if (obj->mapad[0] != _openedType)
continue;
uint8 slot = obj->mapad[2];
_openInvList[slot]._index = i;
_openInvList[slot]._type = kFreeObjectType;
}
}
void DreamWebEngine::pickupConts(uint8 from, uint8 containerEx) {
const DynObject *obj = getFreeAd(from);
if (obj->slotCount == 255)
return; // not openable
for (uint8 index = 0; index < 80; ++index) {
DynObject *freeObj = getFreeAd(index);
if (freeObj->mapad[0] != kFreeObjectType)
continue;
if (freeObj->mapad[1] != from)
continue;
uint8 pos = getExPos();
DynObject *exObj = getExAd(pos);
memcpy(exObj, freeObj, sizeof(DynObject));
exObj->currentLocation = _realLocation;
exObj->initialLocation = _realLocation;
exObj->index = index;
exObj->mapad[0] = 4; // kExObjectType?
exObj->mapad[1] = containerEx;
transferFrame(index, pos, 0);
transferFrame(index, pos, 1);
transferText(index, pos);
freeObj->mapad[0] = 0xFF;
}
}
void DreamWebEngine::incRyanPage() {
commandOnlyCond(31, 222);
if (_mouseButton == _oldButton || !(_mouseButton & 1))
return;
_vars._ryanPage = (_mouseX - (kInventx + 167)) / 18;
delPointer();
fillRyan();
readMouse();
showPointer();
workToScreen();
delPointer();
}
void DreamWebEngine::emergencyPurge(uint8 from) {
debug(2, "Ex memory: frames %d/%d, text %d/%d", _vars._exFramePos, kExframeslen, _vars._exTextPos, kExtextlen);
uint16 frameBytesNeeded = 0;
for (int offset = 0; offset <= 1; ++offset) {
const Frame &freeFrame = _freeFrames._frames[3 * from + offset];
frameBytesNeeded += freeFrame.width * freeFrame.height;
}
const uint16 textBytesNeeded = strlen(_freeDesc.getString(from)) + 1;
while (_vars._exFramePos + frameBytesNeeded > kExframeslen ||
_vars._exTextPos + textBytesNeeded > kExtextlen)
{
purgeAnItem();
debug(2, "Ex memory after purging: frames %d/%d, text %d/%d", _vars._exFramePos, kExframeslen, _vars._exTextPos, kExtextlen);
}
}
void DreamWebEngine::purgeAnItem() {
const DynObject *extraObjects = _exData;
for (uint i = 0; i < kNumexobjects; ++i) {
if (extraObjects[i].mapad[0] == 0 &&
(extraObjects[i].objId[0] == 255 || extraObjects[i].objId[0] == 2) &&
extraObjects[i].initialLocation != _realLocation) {
debug(1, "Purging ex object %d", i);
deleteExObject(i);
return;
}
}
for (uint i = 0; i < kNumexobjects; ++i) {
if (extraObjects[i].mapad[0] == 0 && extraObjects[i].objId[0] == 255) {
debug(1, "Purging ex object %d", i);
deleteExObject(i);
return;
}
}
error("Out of Ex object memory");
}
void DreamWebEngine::dropError() {
_commandType = 255;
delPointer();
printMessage(76, 21, 56, 240, 240 & 1);
workToScreenM();
hangOnP(50);
showPanel();
showMan();
examIcon();
_commandType = 255;
workToScreenM();
}
void DreamWebEngine::cantDrop() {
_commandType = 255;
delPointer();
printMessage(76, 21, 24, 240, 240 & 1);
workToScreenM();
hangOnP(50);
showPanel();
showMan();
examIcon();
_commandType = 255;
workToScreenM();
}
void DreamWebEngine::examineInventory() {
commandOnlyCond(32, 249);
if (!(_mouseButton & 1))
return;
createPanel();
showPanel();
showMan();
showExit();
examIcon();
_pickUp = 0;
_invOpen = 2;
openInv();
workToScreenM();
}
void DreamWebEngine::openInv() {
_invOpen = 1;
printMessage(80, 58 - 10, 61, 240, (240 & 1));
fillRyan();
_commandType = 255;
}
void DreamWebEngine::pickupOb(uint8 command, uint8 pos) {
_lastInvPos = pos;
_objectType = kFreeObjectType;
_itemFrame = command;
_command = command;
//uint8 dummy;
//getAnyAd(&dummy, &dummy); // was in the original source, seems useless here
transferToEx(command);
}
void DreamWebEngine::initialInv() {
if (_realLocation != 24)
return;
pickupOb(11, 5);
pickupOb(12, 6);
pickupOb(13, 7);
pickupOb(14, 8);
pickupOb(18, 0);
pickupOb(19, 1);
pickupOb(20, 9);
pickupOb(16, 2);
_vars._watchMode = 1;
_vars._reelToHold = 0;
_vars._endOfHoldReel = 6;
_vars._watchSpeed = 1;
_vars._speedCount = 1;
switchRyanOff();
}
} // End of namespace DreamWeb