/* 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 "scumm/actor.h" #include "scumm/bomp.h" #include "scumm/he/intern_he.h" #include "scumm/object.h" #include "scumm/resource.h" #include "scumm/scumm_v0.h" #include "scumm/scumm_v8.h" #include "scumm/usage_bits.h" #include "scumm/util.h" namespace Scumm { void ScummEngine::addObjectToInventory(uint obj, uint room) { int idx, slot; uint32 size; const byte *ptr; byte *dst; FindObjectInRoom foir; debug(1, "Adding object %d from room %d into inventory", obj, room); if (whereIsObject(obj) == WIO_FLOBJECT) { idx = getObjectIndex(obj); assert(idx >= 0); ptr = getResourceAddress(rtFlObject, _objs[idx].fl_object_index) + 8; assert(ptr); size = READ_BE_UINT32(ptr + 4); } else { findObjectInRoom(&foir, foCodeHeader, obj, room); if (_game.features & GF_OLD_BUNDLE) size = READ_LE_UINT16(foir.obcd); else if (_game.features & GF_SMALL_HEADER) size = READ_LE_UINT32(foir.obcd); else size = READ_BE_UINT32(foir.obcd + 4); ptr = foir.obcd; } slot = getInventorySlot(); _inventory[slot] = obj; dst = _res->createResource(rtInventory, slot, size); assert(dst); memcpy(dst, ptr, size); } int ScummEngine::getInventorySlot() { int i; for (i = 0; i < _numInventory; i++) { if (_inventory[i] == 0) return i; } error("Inventory full, %d max items", _numInventory); return -1; } int ScummEngine::findInventory(int owner, int idx) { int count = 1, i, obj; for (i = 0; i < _numInventory; i++) { obj = _inventory[i]; if (obj && getOwner(obj) == owner && count++ == idx) return obj; } return 0; } int ScummEngine::getInventoryCount(int owner) { int i, obj; int count = 0; for (i = 0; i < _numInventory; i++) { obj = _inventory[i]; if (obj && getOwner(obj) == owner) count++; } return count; } void ScummEngine::setOwnerOf(int obj, int owner) { ScriptSlot *ss; // In Sam & Max this is necessary, or you won't get your stuff back // from the Lost and Found tent after riding the Cone of Tragedy. But // it probably applies to all V6+ games. See bugs #493153 and #907113. // FT disassembly is checked, behavior is correct. [sev] int arg = (_game.version >= 6) ? obj : 0; // WORKAROUND for bug #1917981: Game crash when finishing Indy3 demo. // Script 94 tries to empty the inventory but does so in a bogus way. // This causes it to try to remove object 0 from the inventory. if (_game.id == GID_PASS && obj == 0 && vm.slot[_currentScript].number == 94) return; // WORKAROUND for bug #6802: assert() was triggered in freddi2. // Bug is in room 39. Problem is script 10, in the localvar2==78 case; // this only sets the obj id if var198 is non-zero, but in the asserting // case, it is obj 0. That means two setOwnerOf calls are made with obj 0. // The correct setOwnerOf calls are made afterwards, so just ignoring this // seems to work just fine. if (_game.id == GID_HEGAME && obj == 0 && _currentRoom == 39 && vm.slot[_currentScript].number == 10) return; assert(obj > 0); if (owner == 0) { clearOwnerOf(obj); // FIXME: See bug #1535358 and many others. Essentially, the following // code, while matching disasm of various versions of the SCUMM engine, // is total bullocks, and leads to odd crashes due to out-of-bounds // array (read) access. Three "famous" crashes were caused by this: // Monkey Island 1: Using meat with flower // FOA: Using ribcage with another item // DOTT: Using stamp with contract // // The bad code: // if (ss->where == WIO_INVENTORY && _inventory[ss->number] == obj) { // That check makes no sense at all: _inventory only contains 80 items, // which are in the order the player picked up items. We can only // guess that the SCUMM coders meant to write // if (ss->where == WIO_INVENTORY && ss->number == obj) { // which would ensure that an object script that nukes itself gets // stopped. Alas, we can't just make that change, since it could // lead to new regressions. // Another fix would be to completely remove this check, which should // not cause much problems, since it'll only succeed by pure chance. // // For now we follow a more defensive route: We perform the check // if ss->number is small enough. ss = &vm.slot[_currentScript]; if (ss->where == WIO_INVENTORY) { if (ss->number < _numInventory && _inventory[ss->number] == obj) { error("Odd setOwnerOf case #1: Please report to Fingolfin where you encountered this"); putOwner(obj, 0); runInventoryScript(arg); stopObjectCode(); return; } if (ss->number == obj) error("Odd setOwnerOf case #2: Please report to Fingolfin where you encountered this"); } } putOwner(obj, owner); runInventoryScript(arg); } void ScummEngine::clearOwnerOf(int obj) { int i; // Stop the associated object script code (else crashes might occurs) stopObjectScript(obj); // If the object is "owned" by a the current room, we scan the // object list and (only if it's a floating object) nuke it. if (getOwner(obj) == OF_OWNER_ROOM) { for (i = 0; i < _numLocalObjects; i++) { if (_objs[i].obj_nr == obj && _objs[i].fl_object_index) { // Removing an flObject from a room means we can nuke it _res->nukeResource(rtFlObject, _objs[i].fl_object_index); _objs[i].obj_nr = 0; _objs[i].fl_object_index = 0; } } } else { // Alternatively, scan the inventory to see if the object is in there... for (i = 0; i < _numInventory; i++) { if (_inventory[i] == obj) { assert(WIO_INVENTORY == whereIsObject(obj)); // Found the object! Nuke it from the inventory. _res->nukeResource(rtInventory, i); _inventory[i] = 0; // Now fill up the gap removing the object from the inventory created. for (i = 0; i < _numInventory - 1; i++) { if (!_inventory[i] && _inventory[i+1]) { _inventory[i] = _inventory[i+1]; _inventory[i+1] = 0; // FIXME FIXME FIXME: This is incomplete, as we do not touch flags, status... BUG _res->_types[rtInventory][i]._address = _res->_types[rtInventory][i + 1]._address; _res->_types[rtInventory][i]._size = _res->_types[rtInventory][i + 1]._size; _res->_types[rtInventory][i + 1]._address = NULL; _res->_types[rtInventory][i + 1]._size = 0; } } break; } } } } bool ScummEngine::getClass(int obj, int cls) const { if (_game.version == 0) return false; assertRange(0, obj, _numGlobalObjects - 1, "object"); cls &= 0x7F; assertRange(1, cls, 32, "class"); if (_game.features & GF_SMALL_HEADER) { // Translate the new (V5) object classes to the old classes // (for those which differ). switch (cls) { case kObjectClassUntouchable: cls = 24; break; case kObjectClassPlayer: cls = 23; break; case kObjectClassXFlip: cls = 19; break; case kObjectClassYFlip: cls = 18; break; default: break; } } return (_classData[obj] & (1 << (cls - 1))) != 0; } void ScummEngine::putClass(int obj, int cls, bool set) { if (_game.version == 0) return; assertRange(0, obj, _numGlobalObjects - 1, "object"); cls &= 0x7F; assertRange(1, cls, 32, "class"); if (_game.features & GF_SMALL_HEADER) { // Translate the new (V5) object classes to the old classes // (for those which differ). switch (cls) { case kObjectClassUntouchable: cls = 24; break; case kObjectClassPlayer: cls = 23; break; case kObjectClassXFlip: cls = 19; break; case kObjectClassYFlip: cls = 18; break; default: break; } } if (set) _classData[obj] |= (1 << (cls - 1)); else _classData[obj] &= ~(1 << (cls - 1)); if (_game.version <= 4 && obj >= 1 && obj < _numActors) { _actors[obj]->classChanged(cls, set); } } int ScummEngine::getOwner(int obj) const { assertRange(0, obj, _numGlobalObjects - 1, "object"); return _objectOwnerTable[obj]; } void ScummEngine::putOwner(int obj, int owner) { assertRange(0, obj, _numGlobalObjects - 1, "object"); assertRange(0, owner, 0xFF, "owner"); _objectOwnerTable[obj] = owner; } int ScummEngine::getState(int obj) { assertRange(0, obj, _numGlobalObjects - 1, "object"); if (!_copyProtection) { // I knew LucasArts sold cracked copies of the original Maniac Mansion, // at least as part of Day of the Tentacle. Apparently they also sold // cracked versions of the enhanced version. At least in Germany. // // This will keep the security door open at all times. I can only // assume that 182 and 193 each correspond to one particular side of // it. Fortunately this does not prevent frustrated players from // blowing up the mansion, should they feel the urge to. if (_game.id == GID_MANIAC && _game.version != 0 && (obj == 182 || obj == 193)) _objectStateTable[obj] |= kObjectState_08; } return _objectStateTable[obj]; } void ScummEngine::putState(int obj, int state) { assertRange(0, obj, _numGlobalObjects - 1, "object"); assertRange(0, state, 0xFF, "state"); _objectStateTable[obj] = state; } int ScummEngine::getObjectRoom(int obj) const { assertRange(0, obj, _numGlobalObjects - 1, "object"); return _objectRoomTable[obj]; } int ScummEngine::getObjectIndex(int object) const { int i; if (object < 1) return -1; for (i = (_numLocalObjects-1); i > 0; i--) { if (_objs[i].obj_nr == object) return i; } return -1; } int ScummEngine::whereIsObject(int object) const { int i; // Note: in MM v0 bg objects are greater _numGlobalObjects if (_game.version != 0 && object >= _numGlobalObjects) return WIO_NOT_FOUND; if (object < 1) return WIO_NOT_FOUND; if ((_game.version != 0 || OBJECT_V0_TYPE(object) == 0) && _objectOwnerTable[object] != OF_OWNER_ROOM) { for (i = 0; i < _numInventory; i++) if (_inventory[i] == object) return WIO_INVENTORY; return WIO_NOT_FOUND; } for (i = (_numLocalObjects-1); i > 0; i--) { if (_objs[i].obj_nr == object) { if (_objs[i].fl_object_index) return WIO_FLOBJECT; return WIO_ROOM; } } return WIO_NOT_FOUND; } int ScummEngine::getObjectOrActorXY(int object, int &x, int &y) { Actor *act; if (objIsActor(object)) { act = derefActorSafe(objToActor(object), "getObjectOrActorXY"); if (act && act->isInCurrentRoom()) { x = act->getRealPos().x; y = act->getRealPos().y; return 0; } else return -1; } switch (whereIsObject(object)) { case WIO_NOT_FOUND: return -1; case WIO_INVENTORY: if (objIsActor(_objectOwnerTable[object])) { act = derefActor(_objectOwnerTable[object], "getObjectOrActorXY(2)"); if (act && act->isInCurrentRoom()) { x = act->getRealPos().x; y = act->getRealPos().y; return 0; } } return -1; default: break; } getObjectXYPos(object, x, y); return 0; } /** * Return the position of an object. * Returns X, Y and direction in angles */ void ScummEngine::getObjectXYPos(int object, int &x, int &y, int &dir) { int idx = getObjectIndex(object); assert(idx >= 0); ObjectData &od = _objs[idx]; int state; const byte *ptr; const ImageHeader *imhd; if (_game.version >= 6) { state = getState(object) - 1; if (state < 0) state = 0; ptr = getOBIMFromObjectData(od); if (!ptr) { // FIXME: We used to assert here, but it seems that in the nexus // in The Dig, this can happen, at least with old savegames, and // it's safe to continue... debug(0, "getObjectXYPos: Can't find object %d", object); return; } imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), ptr); assert(imhd); if (_game.version == 8) { switch (FROM_LE_32(imhd->v8.version)) { case 800: x = od.x_pos + (int32)READ_LE_UINT32((const byte *)imhd + 8 * state + 0x44); y = od.y_pos + (int32)READ_LE_UINT32((const byte *)imhd + 8 * state + 0x48); break; case 801: x = od.x_pos + (int32)READ_LE_UINT32(&imhd->v8.hotspot[state].x); y = od.y_pos + (int32)READ_LE_UINT32(&imhd->v8.hotspot[state].y); break; default: error("Unsupported image header version %d", FROM_LE_32(imhd->v8.version)); } } else if (_game.version == 7) { x = od.x_pos + (int16)READ_LE_UINT16(&imhd->v7.hotspot[state].x); y = od.y_pos + (int16)READ_LE_UINT16(&imhd->v7.hotspot[state].y); } else { x = od.x_pos + (int16)READ_LE_UINT16(&imhd->old.hotspot[state].x); y = od.y_pos + (int16)READ_LE_UINT16(&imhd->old.hotspot[state].y); } } else if (_game.version <= 2) { x = od.walk_x; y = od.walk_y; // Adjust x, y when no actor direction is set, but only perform this // adjustment for V0 games (e.g. MM C64), otherwise certain scenes in // newer games are affected as well (e.g. the interior of the Shuttle // Bus scene in Zak V2, where no actor is present). Refer to bug #3526089. if (!od.actordir && _game.version == 0) { x = od.x_pos + od.width / 2; y = od.y_pos + od.height / 2; } x = x >> V12_X_SHIFT; y = y >> V12_Y_SHIFT; } else { x = od.walk_x; y = od.walk_y; } if (_game.version == 8) dir = fromSimpleDir(1, od.actordir); else dir = oldDirToNewDir(od.actordir & 3); } int ScummEngine::getDist(int x, int y, int x2, int y2) { int a = ABS(y - y2); int b = ABS(x - x2); return MAX(a, b); } int ScummEngine::getObjActToObjActDist(int a, int b) { int x, y, x2, y2; Actor *acta = NULL; Actor *actb = NULL; if (objIsActor(a)) acta = derefActorSafe(objToActor(a), "getObjActToObjActDist"); if (objIsActor(b)) actb = derefActorSafe(objToActor(b), "getObjActToObjActDist(2)"); if (acta && actb && acta->getRoom() == actb->getRoom() && acta->getRoom() && !acta->isInCurrentRoom()) return 0; if (getObjectOrActorXY(a, x, y) == -1) return 0xFF; if (getObjectOrActorXY(b, x2, y2) == -1) return 0xFF; // Perform adjustXYToBeInBox() *only* if the first item is an // actor and the second is an object. This used to not check // whether the second item is a non-actor, which caused bug // #853874). if (acta && !actb) { AdjustBoxResult r = acta->adjustXYToBeInBox(x2, y2); x2 = r.x; y2 = r.y; } // Now compute the distance between the two points return getDist(x, y, x2, y2); } int ScummEngine::findObject(int x, int y) { int i, b; byte a; const int mask = (_game.version <= 2) ? kObjectState_08 : 0xF; for (i = 1; i < _numLocalObjects; i++) { if ((_objs[i].obj_nr < 1) || getClass(_objs[i].obj_nr, kObjectClassUntouchable)) continue; if ((_game.version == 0 && OBJECT_V0_TYPE(_objs[i].obj_nr) == kObjectV0TypeFG) || (_game.version > 0 && _game.version <= 2)) { if (_objs[i].state & kObjectStateUntouchable) continue; } b = i; do { a = _objs[b].parentstate; b = _objs[b].parent; if (b == 0) { #ifdef ENABLE_HE if (_game.heversion >= 71) { if (((ScummEngine_v71he *)this)->_wiz->polygonHit(_objs[i].obj_nr, x, y)) return _objs[i].obj_nr; } #endif if (_objs[i].x_pos <= x && _objs[i].width + _objs[i].x_pos > x && _objs[i].y_pos <= y && _objs[i].height + _objs[i].y_pos > y) return _objs[i].obj_nr; break; } } while ((_objs[b].state & mask) == a); } return 0; } void ScummEngine::drawRoomObject(int i, int arg) { ObjectData *od; byte a; const int mask = (_game.version <= 2) ? kObjectState_08 : 0xF; od = &_objs[i]; if ((i < 1) || (od->obj_nr < 1) || !od->state) return; do { a = od->parentstate; if (!od->parent) { if (_game.version <= 6 || od->fl_object_index == 0) drawObject(i, arg); break; } od = &_objs[od->parent]; } while ((od->state & mask) == a); } void ScummEngine::drawRoomObjects(int arg) { int i; const int mask = (_game.version <= 2) ? kObjectState_08 : 0xF; if (_game.heversion >= 60) { // In HE games, normal objects are drawn, followed by FlObjects. for (i = (_numLocalObjects-1); i > 0; i--) { if (_objs[i].obj_nr > 0 && (_objs[i].state & mask) && _objs[i].fl_object_index == 0) drawRoomObject(i, arg); } for (i = (_numLocalObjects-1); i > 0; i--) { if (_objs[i].obj_nr > 0 && (_objs[i].state & mask) && _objs[i].fl_object_index != 0) drawRoomObject(i, arg); } } else if (_game.id == GID_SAMNMAX) { // In Sam & Max, objects are drawn in reverse order. for (i = 1; i < _numLocalObjects; i++) if (_objs[i].obj_nr > 0) drawRoomObject(i, arg); } else { for (i = (_numLocalObjects-1); i > 0; i--) if (_objs[i].obj_nr > 0 && (_objs[i].state & mask)) { drawRoomObject(i, arg); } } } void ScummEngine::drawObject(int obj, int arg) { if (_skipDrawObject) return; ObjectData &od = _objs[obj]; int height, width; const byte *ptr; int x, a, numstrip; int tmp; if (_bgNeedsRedraw) arg = 0; if (od.obj_nr == 0) return; assertRange(0, od.obj_nr, _numGlobalObjects - 1, "object"); const int xpos = od.x_pos / 8; const int ypos = od.y_pos; width = od.width / 8; height = od.height &= 0xFFFFFFF8; // Mask out last 3 bits // Short circuit for objects which aren't visible at all. if (width == 0 || xpos > _screenEndStrip || xpos + width < _screenStartStrip) return; // For objects without image in Apple II & Commodore 64 versions of Maniac Mansion if (_game.version == 0 && od.OBIMoffset == 0) return; ptr = getObjectImage(getOBIMFromObjectData(od), getState(od.obj_nr)); if (!ptr) return; x = 0xFFFF; for (a = numstrip = 0; a < width; a++) { tmp = xpos + a; if (tmp < _screenStartStrip || _screenEndStrip < tmp) continue; if (arg > 0 && _screenStartStrip + arg <= tmp) continue; if (arg < 0 && tmp <= _screenEndStrip + arg) continue; setGfxUsageBit(tmp, USAGE_BIT_DIRTY); if (tmp < x) x = tmp; numstrip++; } if (numstrip != 0) { byte flags = od.flags | Gdi::dbObjectMode; // Sam & Max needs this to fix object-layering problems with // the inventory and conversation icons. if ((_game.id == GID_SAMNMAX && getClass(od.obj_nr, kObjectClassIgnoreBoxes)) || (_game.id == GID_FT && getClass(od.obj_nr, kObjectClassPlayer))) flags |= Gdi::dbDrawMaskOnAll; #ifdef ENABLE_HE if (_game.heversion >= 70 && findResource(MKTAG('S','M','A','P'), ptr) == NULL) _gdi->drawBMAPObject(ptr, &_virtscr[kMainVirtScreen], obj, od.x_pos, od.y_pos, od.width, od.height); else #endif _gdi->drawBitmap(ptr, &_virtscr[kMainVirtScreen], x, ypos, width * 8, height, x - xpos, numstrip, flags); } } void ScummEngine::clearRoomObjects() { int i; if (_game.features & GF_SMALL_HEADER) { for (i = 0; i < _numLocalObjects; i++) { _objs[i].obj_nr = 0; } } else { for (i = 0; i < _numLocalObjects; i++) { if (_objs[i].obj_nr < 1) // Optimise codepath continue; // Nuke all non-flObjects (flObjects are nuked in script.cpp) if (_objs[i].fl_object_index == 0) { _objs[i].obj_nr = 0; } else { // Nuke all unlocked flObjects if (!_res->isLocked(rtFlObject, _objs[i].fl_object_index)) { _res->nukeResource(rtFlObject, _objs[i].fl_object_index); _objs[i].obj_nr = 0; _objs[i].fl_object_index = 0; } } } } } void ScummEngine_v70he::resetRoomObjects() { ScummEngine_v60he::resetRoomObjects(); restoreFlObjects(); } void ScummEngine_v70he::clearRoomObjects() { _numStoredFlObjects = 0; for (int i = 0; i < _numLocalObjects; i++) { if (_objs[i].obj_nr < 1) // Optimise codepath continue; if (_objs[i].fl_object_index != 0) { if (!_res->isLocked(rtFlObject, _objs[i].fl_object_index)) { _res->nukeResource(rtFlObject, _objs[i].fl_object_index); } else { storeFlObject(i); } } _objs[i].fl_object_index = 0; _objs[i].obj_nr = 0; } if (_currentRoom == 0) restoreFlObjects(); } void ScummEngine_v70he::storeFlObject(int slot) { memcpy(&_storedFlObjects[_numStoredFlObjects], &_objs[slot], sizeof(_objs[slot])); _numStoredFlObjects++; if (_numStoredFlObjects > 100) error("Too many flobjects saved on room transition"); } void ScummEngine_v70he::restoreFlObjects() { int i, slot; for (i = 0; i < _numStoredFlObjects; i++) { slot = findLocalObjectSlot(); memcpy(&_objs[slot], &_storedFlObjects[i], sizeof(_objs[slot])); } _numStoredFlObjects = 0; } void ScummEngine::resetRoomObjects() { int i, j; ObjectData *od; const byte *ptr; uint16 obim_id; const byte *room, *searchptr, *rootptr; const CodeHeader *cdhd; room = getResourceAddress(rtRoom, _roomResource); assert(room); if (_numObjectsInRoom == 0) return; if (_numObjectsInRoom > _numLocalObjects) error("More than %d objects in room %d", _numLocalObjects, _roomResource); if (_game.version == 8) searchptr = rootptr = getResourceAddress(rtRoomScripts, _roomResource); else searchptr = rootptr = room; assert(searchptr); // Load in new room objects ResourceIterator obcds(searchptr, false); for (i = 0; i < _numObjectsInRoom; i++) { od = &_objs[findLocalObjectSlot()]; ptr = obcds.findNext(MKTAG('O','B','C','D')); if (ptr == NULL) error("Room %d missing object code block(s)", _roomResource); od->OBCDoffset = ptr - rootptr; cdhd = (const CodeHeader *)findResourceData(MKTAG('C','D','H','D'), ptr); if (_game.version >= 7) od->obj_nr = READ_LE_UINT16(&(cdhd->v7.obj_id)); else if (_game.version == 6) od->obj_nr = READ_LE_UINT16(&(cdhd->v6.obj_id)); else od->obj_nr = READ_LE_UINT16(&(cdhd->v5.obj_id)); if (_dumpScripts) { char buf[32]; sprintf(buf, "roomobj-%d-", _roomResource); ptr = findResource(MKTAG('V','E','R','B'), ptr); dumpResource(buf, od->obj_nr, ptr); } } searchptr = room; ResourceIterator obims(room, false); for (i = 0; i < _numObjectsInRoom; i++) { ptr = obims.findNext(MKTAG('O','B','I','M')); if (ptr == NULL) error("Room %d missing image blocks(s)", _roomResource); obim_id = getObjectIdFromOBIM(ptr); for (j = 1; j < _numLocalObjects; j++) { if (_objs[j].obj_nr == obim_id) _objs[j].OBIMoffset = ptr - room; } } for (i = 1; i < _numLocalObjects; i++) { if (_objs[i].obj_nr && !_objs[i].fl_object_index) resetRoomObject(&_objs[i], room); } } void ScummEngine_v3old::resetRoomObjects() { int i; ObjectData *od; const byte *room, *ptr; room = getResourceAddress(rtRoom, _roomResource); assert(room); if (_numObjectsInRoom == 0) return; if (_numObjectsInRoom > _numLocalObjects) error("More than %d objects in room %d", _numLocalObjects, _roomResource); if (_game.version <= 2) ptr = room + 28; else ptr = room + 29; // Default pointer of objects without image, in v0 version of Maniac Mansion int defaultPtr = READ_LE_UINT16(ptr + 2 * _numObjectsInRoom); for (i = 0; i < _numObjectsInRoom; i++) { od = &_objs[findLocalObjectSlot()]; if (_game.version == 0 && READ_LE_UINT16(ptr) == defaultPtr) od->OBIMoffset = 0; else od->OBIMoffset = READ_LE_UINT16(ptr); od->OBCDoffset = READ_LE_UINT16(ptr + 2 * _numObjectsInRoom); resetRoomObject(od, room); ptr += 2; if (_dumpScripts) { char buf[32]; sprintf(buf, "roomobj-%d-", _roomResource); dumpResource(buf, od->obj_nr, room + od->OBCDoffset); } } } void ScummEngine_v4::resetRoomObjects() { int i, j; ObjectData *od; const byte *ptr; uint16 obim_id; const byte *room; room = getResourceAddress(rtRoom, _roomResource); assert(room); if (_numObjectsInRoom == 0) return; if (_numObjectsInRoom > _numLocalObjects) error("More than %d objects in room %d", _numLocalObjects, _roomResource); ResourceIterator obcds(room, true); for (i = 0; i < _numObjectsInRoom; i++) { od = &_objs[findLocalObjectSlot()]; ptr = obcds.findNext(MKTAG('O','B','C','D')); if (ptr == NULL) error("Room %d missing object code block(s)", _roomResource); od->OBCDoffset = ptr - room; od->obj_nr = READ_LE_UINT16(ptr + 6); if (_dumpScripts) { char buf[32]; sprintf(buf, "roomobj-%d-", _roomResource); dumpResource(buf, od->obj_nr, ptr); } } ResourceIterator obims(room, true); for (i = 0; i < _numObjectsInRoom; i++) { // In the PC Engine version of Loom, there aren't image blocks // for all objects. ptr = obims.findNext(MKTAG('O','B','I','M')); if (ptr == NULL) break; obim_id = READ_LE_UINT16(ptr + 6); for (j = 1; j < _numLocalObjects; j++) { if (_objs[j].obj_nr == obim_id) _objs[j].OBIMoffset = ptr - room; } } for (i = 1; i < _numLocalObjects; i++) { if (_objs[i].obj_nr && !_objs[i].fl_object_index) { resetRoomObject(&_objs[i], room); } } } void ScummEngine_v0::resetRoomObject(ObjectData *od, const byte *room, const byte *searchptr) { assert(room); const byte *ptr = room + od->OBCDoffset; ptr -= 2; od->obj_nr = OBJECT_V0(*(ptr + 6), *(ptr + 7)); od->x_pos = *(ptr + 8) * 8; od->y_pos = ((*(ptr + 9)) & 0x7F) * 8; od->parentstate = (*(ptr + 9) & 0x80) ? 1 : 0; od->parentstate *= 8; od->width = *(ptr + 10) * 8; od->parent = *(ptr + 11); od->walk_x = *(ptr + 12) * 8; od->walk_y = (*(ptr + 13) & 0x1f) * 8; od->actordir = (*(ptr + 14)) & 7; od->height = *(ptr + 14) & 0xf8; } void ScummEngine_v4::resetRoomObject(ObjectData *od, const byte *room, const byte *searchptr) { assert(room); const byte *ptr = room + od->OBCDoffset; if (_game.features & GF_OLD_BUNDLE) ptr -= 2; od->obj_nr = READ_LE_UINT16(ptr + 6); if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine) { od->x_pos = *(ptr + 8) * 8; od->y_pos = ((*(ptr + 9)) & 0x7F) * 8; od->parentstate = (*(ptr + 9) & 0x80) ? 1 : 0; od->width = *(ptr + 10) * 8; // TODO: Where is parent data? od->parent = 0; od->walk_x = READ_LE_UINT16(ptr + 11); od->walk_y = READ_LE_UINT16(ptr + 13); od->actordir = (*(ptr + 15)) & 7; od->height = *(ptr + 15) & 0xf8; } else { od->x_pos = *(ptr + 9) * 8; od->y_pos = ((*(ptr + 10)) & 0x7F) * 8; od->parentstate = (*(ptr + 10) & 0x80) ? 1 : 0; if (_game.version <= 2) od->parentstate *= 8; od->width = *(ptr + 11) * 8; od->parent = *(ptr + 12); if (_game.version <= 2) { od->walk_x = *(ptr + 13) * 8; od->walk_y = (*(ptr + 14) & 0x1f) * 8; od->actordir = (*(ptr + 15)) & 7; od->height = *(ptr + 15) & 0xf8; } else { od->walk_x = READ_LE_UINT16(ptr + 13); od->walk_y = READ_LE_UINT16(ptr + 15); od->actordir = (*(ptr + 17)) & 7; od->height = *(ptr + 17) & 0xf8; } } } void ScummEngine::resetRoomObject(ObjectData *od, const byte *room, const byte *searchptr) { const CodeHeader *cdhd = NULL; const ImageHeader *imhd = NULL; assert(room); if (searchptr == NULL) { if (_game.version == 8) { searchptr = getResourceAddress(rtRoomScripts, _roomResource); assert(searchptr); } else { searchptr = room; } } cdhd = (const CodeHeader *)findResourceData(MKTAG('C','D','H','D'), searchptr + od->OBCDoffset); if (cdhd == NULL) error("Room %d missing CDHD blocks(s)", _roomResource); if (od->OBIMoffset) imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), room + od->OBIMoffset); od->flags = Gdi::dbAllowMaskOr; if (_game.version == 8) { assert(imhd); od->obj_nr = READ_LE_UINT16(&(cdhd->v7.obj_id)); od->parent = cdhd->v7.parent; od->parentstate = cdhd->v7.parentstate; od->x_pos = (int)READ_LE_UINT32(&imhd->v8.x_pos); od->y_pos = (int)READ_LE_UINT32(&imhd->v8.y_pos); od->width = (uint)READ_LE_UINT32(&imhd->v8.width); od->height = (uint)READ_LE_UINT32(&imhd->v8.height); // HACK: This is done since an angle doesn't fit into a byte (360 > 256) od->actordir = toSimpleDir(1, READ_LE_UINT32(&imhd->v8.actordir)); if (FROM_LE_32(imhd->v8.version) == 801) od->flags = ((((byte)READ_LE_UINT32(&imhd->v8.flags)) & 16) == 0) ? Gdi::dbAllowMaskOr : 0; } else if (_game.version == 7) { assert(imhd); od->obj_nr = READ_LE_UINT16(&(cdhd->v7.obj_id)); od->parent = cdhd->v7.parent; od->parentstate = cdhd->v7.parentstate; od->x_pos = READ_LE_UINT16(&imhd->v7.x_pos); od->y_pos = READ_LE_UINT16(&imhd->v7.y_pos); od->width = READ_LE_UINT16(&imhd->v7.width); od->height = READ_LE_UINT16(&imhd->v7.height); od->actordir = (byte)READ_LE_UINT16(&imhd->v7.actordir); } else if (_game.version == 6) { od->obj_nr = READ_LE_UINT16(&(cdhd->v6.obj_id)); od->width = READ_LE_UINT16(&cdhd->v6.w); od->height = READ_LE_UINT16(&cdhd->v6.h); od->x_pos = ((int16)READ_LE_UINT16(&cdhd->v6.x)); od->y_pos = ((int16)READ_LE_UINT16(&cdhd->v6.y)); if (cdhd->v6.flags == 0x80) { od->parentstate = 1; } else { od->parentstate = (cdhd->v6.flags & 0xF); } od->parent = cdhd->v6.parent; od->actordir = cdhd->v6.actordir; if (_game.heversion >= 60 && imhd) od->flags = ((imhd->old.flags & 1) != 0) ? Gdi::dbAllowMaskOr : 0; } else { od->obj_nr = READ_LE_UINT16(&(cdhd->v5.obj_id)); od->width = cdhd->v5.w * 8; od->height = cdhd->v5.h * 8; od->x_pos = cdhd->v5.x * 8; od->y_pos = cdhd->v5.y * 8; if (cdhd->v5.flags == 0x80) { od->parentstate = 1; } else { od->parentstate = (cdhd->v5.flags & 0xF); } od->parent = cdhd->v5.parent; od->walk_x = READ_LE_UINT16(&cdhd->v5.walk_x); od->walk_y = READ_LE_UINT16(&cdhd->v5.walk_y); od->actordir = cdhd->v5.actordir; } od->fl_object_index = 0; } void ScummEngine::updateObjectStates() { int i; ObjectData *od = &_objs[1]; for (i = 1; i < _numLocalObjects; i++, od++) { // V0 MM, objects with type == 1 are room objects (room specific objects, non-pickup) if (_game.version == 0 && OBJECT_V0_TYPE(od->obj_nr) == kObjectV0TypeBG) continue; if (od->obj_nr > 0) od->state = getState(od->obj_nr); } } void ScummEngine::processDrawQue() { int i, j; for (i = 0; i < _drawObjectQueNr; i++) { j = _drawObjectQue[i]; if (j) drawObject(j, 0); } _drawObjectQueNr = 0; } void ScummEngine::addObjectToDrawQue(int object) { if ((unsigned int)_drawObjectQueNr >= ARRAYSIZE(_drawObjectQue)) error("Draw Object Que overflow"); _drawObjectQue[_drawObjectQueNr++] = object; } void ScummEngine::removeObjectFromDrawQue(int object) { if (_drawObjectQueNr <= 0) return; int i; for (i = 0; i < _drawObjectQueNr; i++) { if (_drawObjectQue[i] == object) _drawObjectQue[i] = 0; } } void ScummEngine::clearDrawObjectQueue() { _drawObjectQueNr = 0; } void ScummEngine::clearDrawQueues() { clearDrawObjectQueue(); } void ScummEngine_v6::clearDrawQueues() { ScummEngine::clearDrawQueues(); _blastObjectQueuePos = 0; } #ifdef ENABLE_HE void ScummEngine_v71he::clearDrawQueues() { ScummEngine_v6::clearDrawQueues(); _wiz->polygonClear(); } void ScummEngine_v80he::clearDrawQueues() { ScummEngine_v71he::clearDrawQueues(); _wiz->clearWizBuffer(); } #endif /** * Mark the rectangle covered by the given object as dirty, thus eventually * ensuring a redraw of that area. This function is typically invoked when an * object gets removed from the current room, or when its state changed. */ void ScummEngine::markObjectRectAsDirty(int obj) { int i, strip; ++_V0Delay._objectRedrawCount; for (i = 1; i < _numLocalObjects; i++) { if (_objs[i].obj_nr == (uint16)obj) { if (_objs[i].width != 0) { const int minStrip = MAX(_screenStartStrip, _objs[i].x_pos / 8); const int maxStrip = MIN(_screenEndStrip+1, _objs[i].x_pos / 8 + _objs[i].width / 8); for (strip = minStrip; strip < maxStrip; strip++) { ++_V0Delay._objectStripRedrawCount; setGfxUsageBit(strip, USAGE_BIT_DIRTY); } } _bgNeedsRedraw = true; return; } } } const byte *ScummEngine::getObjOrActorName(int obj) { byte *objptr; int i; if (objIsActor(obj)) return derefActor(objToActor(obj), "getObjOrActorName")->getActorName(); for (i = 0; i < _numNewNames; i++) { if (_newNames[i] == obj) { debug(5, "Found new name for object %d at _newNames[%d]", obj, i); return getResourceAddress(rtObjectName, i); } } objptr = getOBCDFromObject(obj, true); if (objptr == NULL) return NULL; if (_game.features & GF_SMALL_HEADER) { byte offset = 0; if (_game.version == 0) offset = *(objptr + 13); else if (_game.version <= 2) offset = *(objptr + 14); else if (_game.features & GF_OLD_BUNDLE) offset = *(objptr + 16); else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine) offset = *(objptr + 16) + 17; else offset = *(objptr + 18); return (objptr + offset); } return findResourceData(MKTAG('O','B','N','A'), objptr); } void ScummEngine::setObjectName(int obj) { int i; if (objIsActor(obj)) error("Can't set actor %d name with new-name-of", obj); for (i = 0; i < _numNewNames; i++) { if (_newNames[i] == obj) { _res->nukeResource(rtObjectName, i); _newNames[i] = 0; break; } } for (i = 0; i < _numNewNames; i++) { if (_newNames[i] == 0) { loadPtrToResource(rtObjectName, i, NULL); _newNames[i] = obj; runInventoryScript(0); return; } } error("New name of %d overflows name table (max = %d)", obj, _numNewNames); } uint32 ScummEngine::getOBCDOffs(int object) const { int i; if ((_game.version != 0 || OBJECT_V0_TYPE(object) == 0) && _objectOwnerTable[object] != OF_OWNER_ROOM) return 0; for (i = (_numLocalObjects-1); i > 0; i--) { if (_objs[i].obj_nr == object) { if (_objs[i].fl_object_index != 0) return 8; return _objs[i].OBCDoffset; } } return 0; } byte *ScummEngine::getOBCDFromObject(int obj, bool v0CheckInventory) { int i; byte *ptr; if ((_game.version != 0 || OBJECT_V0_TYPE(obj) == 0) && _objectOwnerTable[obj] != OF_OWNER_ROOM) { if (_game.version == 0 && !v0CheckInventory) return 0; for (i = 0; i < _numInventory; i++) { if (_inventory[i] == obj) return getResourceAddress(rtInventory, i); } } else { for (i = (_numLocalObjects-1); i > 0; --i) { if (_objs[i].obj_nr == obj) { if (_objs[i].fl_object_index) { assert(_objs[i].OBCDoffset == 8); ptr = getResourceAddress(rtFlObject, _objs[i].fl_object_index); } else if (_game.version == 8) ptr = getResourceAddress(rtRoomScripts, _roomResource); else ptr = getResourceAddress(rtRoom, _roomResource); assert(ptr); return ptr + _objs[i].OBCDoffset; } } } return 0; } const byte *ScummEngine::getOBIMFromObjectData(const ObjectData &od) { const byte *ptr; if (od.fl_object_index) { ptr = getResourceAddress(rtFlObject, od.fl_object_index); ptr = findResource(MKTAG('O','B','I','M'), ptr); } else { ptr = getResourceAddress(rtRoom, _roomResource); if (ptr) ptr += od.OBIMoffset; } return ptr; } static const uint32 IMxx_tags[] = { MKTAG('I','M','0','0'), MKTAG('I','M','0','1'), MKTAG('I','M','0','2'), MKTAG('I','M','0','3'), MKTAG('I','M','0','4'), MKTAG('I','M','0','5'), MKTAG('I','M','0','6'), MKTAG('I','M','0','7'), MKTAG('I','M','0','8'), MKTAG('I','M','0','9'), MKTAG('I','M','0','A'), MKTAG('I','M','0','B'), MKTAG('I','M','0','C'), MKTAG('I','M','0','D'), MKTAG('I','M','0','E'), MKTAG('I','M','0','F'), MKTAG('I','M','1','0') }; const byte *ScummEngine::getObjectImage(const byte *ptr, int state) { assert(ptr); if (_game.features & GF_OLD_BUNDLE) ptr += 0; else if (_game.features & GF_SMALL_HEADER) { ptr += 8; } else if (_game.version == 8) { // The OBIM contains an IMAG, which in turn contains a WRAP, which contains // an OFFS chunk and multiple BOMP/SMAP chunks. To find the right BOMP/SMAP, // we use the offsets in the OFFS chunk, ptr = findResource(MKTAG('I','M','A','G'), ptr); if (!ptr) return 0; ptr = findResource(MKTAG('W','R','A','P'), ptr); if (!ptr) return 0; ptr = findResource(MKTAG('O','F','F','S'), ptr); if (!ptr) return 0; // Get the address of the specified SMAP (corresponding to IMxx) ptr += READ_LE_UINT32(ptr + 4 + 4*state); } else { ptr = findResource(IMxx_tags[state], ptr); } return ptr; } int ScummEngine::getObjectImageCount(int object) { const byte *ptr; const ImageHeader *imhd; int objnum; objnum = getObjectIndex(object); if (objnum == -1) return 0; ptr = getOBIMFromObjectData(_objs[objnum]); imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), ptr); if (!imhd) return 0; if (_game.version == 8) { return (READ_LE_UINT32(&imhd->v8.image_count)); } else if (_game.version == 7) { return(READ_LE_UINT16(&imhd->v7.image_count)); } else { return (READ_LE_UINT16(&imhd->old.image_count)); } } #ifdef ENABLE_SCUMM_7_8 int ScummEngine_v8::getObjectIdFromOBIM(const byte *obim) { // In V8, IMHD has no obj_id, but rather a name string. We map the name // back to an object id using a table derived from the DOBJ resource. const ImageHeader *imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), obim); ObjectNameId *found = (ObjectNameId *)bsearch(imhd->v8.name, _objectIDMap, _objectIDMapSize, sizeof(ObjectNameId), (int (*)(const void*, const void*))strcmp); assert(found); return found->id; } int ScummEngine_v7::getObjectIdFromOBIM(const byte *obim) { const ImageHeader *imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), obim); return READ_LE_UINT16(&imhd->v7.obj_id); } #endif int ScummEngine::getObjectIdFromOBIM(const byte *obim) { if (_game.features & GF_SMALL_HEADER) return READ_LE_UINT16(obim + 6); const ImageHeader *imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), obim); return READ_LE_UINT16(&imhd->old.obj_id); } void ScummEngine::findObjectInRoom(FindObjectInRoom *fo, byte findWhat, uint id, uint room) { const CodeHeader *cdhd; int i, numobj; const byte *roomptr, *obcdptr, *obimptr, *searchptr; int id2; int obim_id; id2 = getObjectIndex(id); if (findWhat & foCheckAlreadyLoaded && id2 != -1) { assert(_game.version >= 6); if (findWhat & foCodeHeader) { fo->obcd = obcdptr = getOBCDFromObject(id); assert(obcdptr); fo->cdhd = (const CodeHeader *)findResourceData(MKTAG('C','D','H','D'), obcdptr); } if (findWhat & foImageHeader) { fo->obim = obimptr = getOBIMFromObjectData(_objs[id2]); assert(obimptr); } return; } fo->roomptr = roomptr = getResourceAddress(rtRoom, room); if (!roomptr) error("findObjectInRoom: failed getting roomptr to %d", room); if (_game.features & GF_OLD_BUNDLE) { numobj = roomptr[20]; } else { const RoomHeader *roomhdr = (const RoomHeader *)findResourceData(MKTAG('R','M','H','D'), roomptr); if (_game.version == 8) numobj = READ_LE_UINT32(&(roomhdr->v8.numObjects)); else if (_game.version == 7) numobj = READ_LE_UINT16(&(roomhdr->v7.numObjects)); else numobj = READ_LE_UINT16(&(roomhdr->old.numObjects)); } if (numobj == 0) error("findObjectInRoom: No object found in room %d", room); if (numobj > _numLocalObjects) error("findObjectInRoom: More (%d) than %d objects in room %d", numobj, _numLocalObjects, room); if (_game.features & GF_OLD_BUNDLE) { if (_game.version <= 2) searchptr = roomptr + 28; else searchptr = roomptr + 29; for (i = 0; i < numobj; i++) { obimptr = roomptr + READ_LE_UINT16(searchptr); obcdptr = roomptr + READ_LE_UINT16(searchptr + 2 * numobj); id2 = READ_LE_UINT16(obcdptr + 4); if (id2 == (uint16)id) { if (findWhat & foCodeHeader) { fo->obcd = obcdptr; // We assume that the code header starts at a fixed offset. // A bit hackish, but works reasonably well. fo->cdhd = (const CodeHeader *)(obcdptr + 10); } if (findWhat & foImageHeader) { fo->obim = obimptr; } break; } searchptr += 2; } return; } if (findWhat & foCodeHeader) { if (_game.version == 8) searchptr = getResourceAddress(rtRoomScripts, room); else searchptr = roomptr; assert(searchptr); ResourceIterator obcds(searchptr, (_game.features & GF_SMALL_HEADER) != 0); for (i = 0; i < numobj; i++) { obcdptr = obcds.findNext(MKTAG('O','B','C','D')); if (obcdptr == NULL) error("findObjectInRoom: Not enough code blocks in room %d", room); cdhd = (const CodeHeader *)findResourceData(MKTAG('C','D','H','D'), obcdptr); if (_game.features & GF_SMALL_HEADER) id2 = READ_LE_UINT16(obcdptr + 6); else if (_game.version >= 7) id2 = READ_LE_UINT16(&(cdhd->v7.obj_id)); else if (_game.version == 6) id2 = READ_LE_UINT16(&(cdhd->v6.obj_id)); else id2 = READ_LE_UINT16(&(cdhd->v5.obj_id)); if (id2 == (uint16)id) { fo->obcd = obcdptr; fo->cdhd = cdhd; break; } } if (i == numobj) error("findObjectInRoom: Object %d not found in room %d", id, room); } roomptr = fo->roomptr; if (findWhat & foImageHeader) { ResourceIterator obims(roomptr, (_game.features & GF_SMALL_HEADER) != 0); for (i = 0; i < numobj; i++) { obimptr = obims.findNext(MKTAG('O','B','I','M')); if (obimptr == NULL) error("findObjectInRoom: Not enough image blocks in room %d", room); obim_id = getObjectIdFromOBIM(obimptr); if (obim_id == (uint16)id) { fo->obim = obimptr; break; } } if (i == numobj) error("findObjectInRoom: Object %d image not found in room %d", id, room); } } bool ScummEngine_v0::objIsActor(int obj) { // object IDs < _numActors are used in v0 for objects too (e.g. hamster) return OBJECT_V0_TYPE(obj) == kObjectV0TypeActor; } int ScummEngine_v0::objToActor(int obj) { return OBJECT_V0_ID(obj); } int ScummEngine_v0::actorToObj(int actor) { return OBJECT_V0(actor, kObjectV0TypeActor); } bool ScummEngine::objIsActor(int obj) { return obj < _numActors; } int ScummEngine::objToActor(int obj) { return obj; } int ScummEngine::actorToObj(int actor) { return actor; } int ScummEngine::getObjX(int obj) { if (obj < 1) return 0; /* fix for indy4's map */ if (objIsActor(obj)) { return derefActor(objToActor(obj), "getObjX")->getRealPos().x; } else { if (whereIsObject(obj) == WIO_NOT_FOUND) return -1; int x, y; if (getObjectOrActorXY(obj, x, y) == -1) return -1; return x; } } int ScummEngine::getObjY(int obj) { if (obj < 1) return 0; /* fix for indy4's map */ if (objIsActor(obj)) { return derefActor(objToActor(obj), "getObjY")->getRealPos().y; } else { if (whereIsObject(obj) == WIO_NOT_FOUND) return -1; int x, y; if (getObjectOrActorXY(obj, x, y) == -1) return -1; return y; } } int ScummEngine::getObjOldDir(int obj) { return newDirToOldDir(getObjNewDir(obj)); } int ScummEngine::getObjNewDir(int obj) { int dir; if (objIsActor(obj)) { dir = derefActor(objToActor(obj), "getObjNewDir")->getFacing(); } else { int x, y; getObjectXYPos(obj, x, y, dir); } return dir; } void ScummEngine::setObjectState(int obj, int state, int x, int y) { int i; i = getObjectIndex(obj); if (i == -1) { debug(0, "setObjectState: no such object %d", obj); return; } if (x != -1 && x != 0x7FFFFFFF) { _objs[i].x_pos = x * 8; _objs[i].y_pos = y * 8; } addObjectToDrawQue(i); if (_game.version >= 7) { int imagecount; if (state == 0xFF) { state = getState(obj); imagecount = getObjectImageCount(obj); if (state < imagecount) state++; else state = 1; } if (state == 0xFE) state = _rnd.getRandomNumber(getObjectImageCount(obj)); } putState(obj, state); } int ScummEngine_v6::getDistanceBetween(bool is_obj_1, int b, int c, bool is_obj_2, int e, int f) { int i, j; int x, y; int x2, y2; j = i = 0xFF; if (is_obj_1) { if (getObjectOrActorXY(b, x, y) == -1) return -1; if (b < _numActors) i = derefActor(b, "getDistanceBetween_is_obj_1")->_scalex; } else { x = b; y = c; } if (is_obj_2) { if (getObjectOrActorXY(e, x2, y2) == -1) return -1; if (e < _numActors) j = derefActor(e, "getDistanceBetween_is_obj_2")->_scalex; } else { x2 = e; y2 = f; } return getDist(x, y, x2, y2) * 0xFF / ((i + j) / 2); } void ScummEngine::nukeFlObjects(int min, int max) { ObjectData *od; int i; debug(0, "nukeFlObjects(%d,%d)", min, max); for (i = (_numLocalObjects-1), od = _objs; --i >= 0; od++) if (od->fl_object_index && od->obj_nr >= min && od->obj_nr <= max) { _res->nukeResource(rtFlObject, od->fl_object_index); od->obj_nr = 0; od->fl_object_index = 0; } } void ScummEngine_v6::enqueueObject(int objectNumber, int objectX, int objectY, int objectWidth, int objectHeight, int scaleX, int scaleY, int image, int mode) { BlastObject *eo; if (_blastObjectQueuePos >= (int)ARRAYSIZE(_blastObjectQueue)) { error("enqueueObject: overflow"); } int idx = getObjectIndex(objectNumber); assert(idx >= 0); eo = &_blastObjectQueue[_blastObjectQueuePos++]; eo->number = objectNumber; eo->rect.left = objectX; eo->rect.top = objectY + _screenTop; if (objectWidth == 0) { eo->rect.right = eo->rect.left + _objs[idx].width; } else { eo->rect.right = eo->rect.left + objectWidth; } if (objectHeight == 0) { eo->rect.bottom = eo->rect.top + _objs[idx].height; } else { eo->rect.bottom = eo->rect.top + objectHeight; } eo->scaleX = scaleX; eo->scaleY = scaleY; eo->image = image; eo->mode = mode; } void ScummEngine_v6::drawBlastObjects() { BlastObject *eo; int i; eo = _blastObjectQueue; for (i = 0; i < _blastObjectQueuePos; i++, eo++) { drawBlastObject(eo); } } void ScummEngine_v6::drawBlastObject(BlastObject *eo) { VirtScreen *vs; const byte *bomp, *ptr; int objnum; BompDrawData bdd; vs = &_virtscr[kMainVirtScreen]; assertRange(30, eo->number, _numGlobalObjects - 1, "blast object"); objnum = getObjectIndex(eo->number); if (objnum == -1) error("drawBlastObject: getObjectIndex on BlastObject %d failed", eo->number); ptr = getOBIMFromObjectData(_objs[objnum]); if (!ptr) error("BlastObject object %d image not found", eo->number); const byte *img = getObjectImage(ptr, eo->image); if (_game.version == 8) { assert(img); bomp = img + 8; } else { if (!img) img = getObjectImage(ptr, 1); // Backward compatibility with samnmax blast objects assert(img); bomp = findResourceData(MKTAG('B','O','M','P'), img); } if (!bomp) error("object %d is not a blast object", eo->number); bdd.dst = *vs; bdd.dst.setPixels(vs->getPixels(0, 0)); bdd.x = eo->rect.left; bdd.y = eo->rect.top; // Skip the bomp header if (_game.version == 8) { bdd.src = bomp + 8; } else { bdd.src = bomp + 10; } if (_game.version == 8) { bdd.srcwidth = READ_LE_UINT32(bomp); bdd.srcheight = READ_LE_UINT32(bomp+4); } else { bdd.srcwidth = READ_LE_UINT16(bomp+2); bdd.srcheight = READ_LE_UINT16(bomp+4); } bdd.scale_x = (byte)eo->scaleX; bdd.scale_y = (byte)eo->scaleY; bdd.maskPtr = NULL; bdd.numStrips = _gdi->_numStrips; if ((bdd.scale_x != 255) || (bdd.scale_y != 255)) { bdd.shadowMode = 0; } else { bdd.shadowMode = eo->mode; } bdd.shadowPalette = _shadowPalette; bdd.actorPalette = 0; bdd.mirror = false; drawBomp(bdd); markRectAsDirty(vs->number, bdd.x, bdd.x + bdd.srcwidth, bdd.y, bdd.y + bdd.srcheight); } void ScummEngine_v6::removeBlastObjects() { BlastObject *eo; int i; eo = _blastObjectQueue; for (i = 0; i < _blastObjectQueuePos; i++, eo++) { removeBlastObject(eo); } _blastObjectQueuePos = 0; } void ScummEngine_v6::removeBlastObject(BlastObject *eo) { VirtScreen *vs = &_virtscr[kMainVirtScreen]; Common::Rect r; int left_strip, right_strip; int i; r = eo->rect; r.clip(Common::Rect(vs->w, vs->h)); if (r.width() <= 0 || r.height() <= 0) return; left_strip = r.left / 8; right_strip = (r.right + (vs->xstart % 8)) / 8; if (left_strip < 0) left_strip = 0; if (right_strip > _gdi->_numStrips - 1) right_strip = _gdi->_numStrips - 1; for (i = left_strip; i <= right_strip; i++) _gdi->resetBackground(r.top, r.bottom, i); markRectAsDirty(kMainVirtScreen, r, USAGE_BIT_RESTORED); } int ScummEngine::findLocalObjectSlot() { int i; for (i = 1; i < _numLocalObjects; i++) { if (!_objs[i].obj_nr) { memset(&_objs[i], 0, sizeof(_objs[i])); return i; } } return -1; } int ScummEngine::findFlObjectSlot() { int i; for (i = 1; i < _numFlObject; i++) { if (_res->_types[rtFlObject][i]._address == NULL) return i; } error("findFlObjectSlot: Out of FLObject slots"); return -1; } void ScummEngine_v70he::loadFlObject(uint object, uint room) { // Don't load an already stored object for (int i = 0; i < _numStoredFlObjects; i++) { if (_storedFlObjects[i].obj_nr == object) return; } ScummEngine_v60he::loadFlObject(object, room); } void ScummEngine::loadFlObject(uint object, uint room) { FindObjectInRoom foir; int slot, objslot; ObjectData *od; byte *flob; uint32 obcd_size, obim_size, flob_size; bool isRoomLocked, isRoomScriptsLocked; // Don't load an already loaded object if (getObjectIndex(object) != -1) return; // Locate the object in the room resource findObjectInRoom(&foir, foImageHeader | foCodeHeader, object, room); // Add an entry for the new floating object in the local object table objslot = findLocalObjectSlot(); if (objslot == -1) error("loadFlObject: Local Object Table overflow"); od = &_objs[objslot]; // Dump object script if (_dumpScripts) { char buf[32]; const byte *ptr = foir.obcd; sprintf(buf, "roomobj-%u-", room); ptr = findResource(MKTAG('V','E','R','B'), ptr); dumpResource(buf, object, ptr); } // Setup sizes obcd_size = READ_BE_UINT32(foir.obcd + 4); od->OBCDoffset = 8; od->OBIMoffset = obcd_size + 8; obim_size = READ_BE_UINT32(foir.obim + 4); flob_size = obcd_size + obim_size + 8; // Lock room/roomScripts for the given room. They contains the OBCD/OBIM // data, and a call to createResource might expire them, hence we lock them. isRoomLocked = _res->isLocked(rtRoom, room); isRoomScriptsLocked = _res->isLocked(rtRoomScripts, room); if (!isRoomLocked) _res->lock(rtRoom, room); if (_game.version == 8 && !isRoomScriptsLocked) _res->lock(rtRoomScripts, room); // Allocate slot & memory for floating object slot = findFlObjectSlot(); flob = _res->createResource(rtFlObject, slot, flob_size); assert(flob); // Copy object code + object image to floating object WRITE_UINT32(flob, MKTAG('F','L','O','B')); WRITE_BE_UINT32(flob + 4, flob_size); memcpy(flob + 8, foir.obcd, obcd_size); memcpy(flob + 8 + obcd_size, foir.obim, obim_size); // Unlock room/roomScripts if (!isRoomLocked) _res->unlock(rtRoom, room); if (_game.version == 8 && !isRoomScriptsLocked) _res->unlock(rtRoomScripts, room); // Setup local object flags resetRoomObject(od, flob, flob); od->fl_object_index = slot; } } // End of namespace Scumm