1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 */
22
23 #include "scumm/actor.h"
24 #include "scumm/bomp.h"
25 #include "scumm/he/intern_he.h"
26 #include "scumm/object.h"
27 #include "scumm/resource.h"
28 #include "scumm/scumm_v0.h"
29 #include "scumm/scumm_v8.h"
30 #include "scumm/usage_bits.h"
31 #include "scumm/util.h"
32
33 namespace Scumm {
34
addObjectToInventory(uint obj,uint room)35 void ScummEngine::addObjectToInventory(uint obj, uint room) {
36 int idx, slot;
37 uint32 size;
38 const byte *ptr;
39 byte *dst;
40 FindObjectInRoom foir;
41
42 debug(1, "Adding object %d from room %d into inventory", obj, room);
43
44 if (whereIsObject(obj) == WIO_FLOBJECT) {
45 idx = getObjectIndex(obj);
46 assert(idx >= 0);
47 ptr = getResourceAddress(rtFlObject, _objs[idx].fl_object_index) + 8;
48 assert(ptr);
49 size = READ_BE_UINT32(ptr + 4);
50 } else {
51 findObjectInRoom(&foir, foCodeHeader, obj, room);
52 if (_game.features & GF_OLD_BUNDLE)
53 size = READ_LE_UINT16(foir.obcd);
54 else if (_game.features & GF_SMALL_HEADER)
55 size = READ_LE_UINT32(foir.obcd);
56 else
57 size = READ_BE_UINT32(foir.obcd + 4);
58 ptr = foir.obcd;
59 }
60
61 slot = getInventorySlot();
62 _inventory[slot] = obj;
63 dst = _res->createResource(rtInventory, slot, size);
64 assert(dst);
65 memcpy(dst, ptr, size);
66 }
67
getInventorySlot()68 int ScummEngine::getInventorySlot() {
69 int i;
70 for (i = 0; i < _numInventory; i++) {
71 if (_inventory[i] == 0)
72 return i;
73 }
74 error("Inventory full, %d max items", _numInventory);
75 return -1;
76 }
77
findInventory(int owner,int idx)78 int ScummEngine::findInventory(int owner, int idx) {
79 int count = 1, i, obj;
80 for (i = 0; i < _numInventory; i++) {
81 obj = _inventory[i];
82 if (obj && getOwner(obj) == owner && count++ == idx)
83 return obj;
84 }
85 return 0;
86 }
87
getInventoryCount(int owner)88 int ScummEngine::getInventoryCount(int owner) {
89 int i, obj;
90 int count = 0;
91 for (i = 0; i < _numInventory; i++) {
92 obj = _inventory[i];
93 if (obj && getOwner(obj) == owner)
94 count++;
95 }
96 return count;
97 }
98
setOwnerOf(int obj,int owner)99 void ScummEngine::setOwnerOf(int obj, int owner) {
100 ScriptSlot *ss;
101
102 // In Sam & Max this is necessary, or you won't get your stuff back
103 // from the Lost and Found tent after riding the Cone of Tragedy. But
104 // it probably applies to all V6+ games. See bugs #52 and #1507.
105 // FT disassembly is checked, behavior is correct. [sev]
106
107 int arg = (_game.version >= 6) ? obj : 0;
108
109 // WORKAROUND for bug #3657: Game crash when finishing Indy3 demo.
110 // Script 94 tries to empty the inventory but does so in a bogus way.
111 // This causes it to try to remove object 0 from the inventory.
112 if (_game.id == GID_PASS && obj == 0 && vm.slot[_currentScript].number == 94)
113 return;
114
115 // WORKAROUND for bug #6802: assert() was triggered in freddi2.
116 // Bug is in room 39. Problem is script 10, in the localvar2==78 case;
117 // this only sets the obj id if var198 is non-zero, but in the asserting
118 // case, it is obj 0. That means two setOwnerOf calls are made with obj 0.
119 // The correct setOwnerOf calls are made afterwards, so just ignoring this
120 // seems to work just fine.
121 if (_game.id == GID_HEGAME && obj == 0 && _currentRoom == 39 && vm.slot[_currentScript].number == 10)
122 return;
123
124 assert(obj > 0);
125
126 if (owner == 0) {
127 clearOwnerOf(obj);
128
129 // FIXME: See bug #2771 and many others. Essentially, the following
130 // code, while matching disasm of various versions of the SCUMM engine,
131 // is total bullocks, and leads to odd crashes due to out-of-bounds
132 // array (read) access. Three "famous" crashes were caused by this:
133 // Monkey Island 1: Using meat with flower
134 // FOA: Using ribcage with another item
135 // DOTT: Using stamp with contract
136 //
137 // The bad code:
138 // if (ss->where == WIO_INVENTORY && _inventory[ss->number] == obj) {
139 // That check makes no sense at all: _inventory only contains 80 items,
140 // which are in the order the player picked up items. We can only
141 // guess that the SCUMM coders meant to write
142 // if (ss->where == WIO_INVENTORY && ss->number == obj) {
143 // which would ensure that an object script that nukes itself gets
144 // stopped. Alas, we can't just make that change, since it could
145 // lead to new regressions.
146 // Another fix would be to completely remove this check, which should
147 // not cause much problems, since it'll only succeed by pure chance.
148 //
149 // For now we follow a more defensive route: We perform the check
150 // if ss->number is small enough.
151
152 ss = &vm.slot[_currentScript];
153 if (ss->where == WIO_INVENTORY) {
154 if (ss->number < _numInventory && _inventory[ss->number] == obj) {
155 error("Odd setOwnerOf case #1: Please report to Fingolfin where you encountered this");
156 putOwner(obj, 0);
157 runInventoryScript(arg);
158 stopObjectCode();
159 return;
160 }
161 if (ss->number == obj)
162 error("Odd setOwnerOf case #2: Please report to Fingolfin where you encountered this");
163 }
164 }
165
166 putOwner(obj, owner);
167 runInventoryScript(arg);
168 }
169
clearOwnerOf(int obj)170 void ScummEngine::clearOwnerOf(int obj) {
171 int i;
172
173 // Stop the associated object script code (else crashes might occurs)
174 stopObjectScript(obj);
175
176 // If the object is "owned" by a the current room, we scan the
177 // object list and (only if it's a floating object) nuke it.
178 if (getOwner(obj) == OF_OWNER_ROOM) {
179 for (i = 0; i < _numLocalObjects; i++) {
180 if (_objs[i].obj_nr == obj && _objs[i].fl_object_index) {
181 // Removing an flObject from a room means we can nuke it
182 _res->nukeResource(rtFlObject, _objs[i].fl_object_index);
183 _objs[i].obj_nr = 0;
184 _objs[i].fl_object_index = 0;
185 }
186 }
187 } else {
188
189 // Alternatively, scan the inventory to see if the object is in there...
190 for (i = 0; i < _numInventory; i++) {
191 if (_inventory[i] == obj) {
192 assert(WIO_INVENTORY == whereIsObject(obj));
193
194 // Found the object! Nuke it from the inventory.
195 _res->nukeResource(rtInventory, i);
196 _inventory[i] = 0;
197
198 // Now fill up the gap removing the object from the inventory created.
199 for (i = 0; i < _numInventory - 1; i++) {
200 if (!_inventory[i] && _inventory[i+1]) {
201 _inventory[i] = _inventory[i+1];
202 _inventory[i+1] = 0;
203 // FIXME FIXME FIXME: This is incomplete, as we do not touch flags, status... BUG
204 _res->_types[rtInventory][i]._address = _res->_types[rtInventory][i + 1]._address;
205 _res->_types[rtInventory][i]._size = _res->_types[rtInventory][i + 1]._size;
206 _res->_types[rtInventory][i + 1]._address = NULL;
207 _res->_types[rtInventory][i + 1]._size = 0;
208 }
209 }
210 break;
211 }
212 }
213 }
214 }
215
getClass(int obj,int cls) const216 bool ScummEngine::getClass(int obj, int cls) const {
217 if (_game.version == 0)
218 return false;
219
220 assertRange(0, obj, _numGlobalObjects - 1, "object");
221 cls &= 0x7F;
222 assertRange(1, cls, 32, "class");
223
224 if (_game.features & GF_SMALL_HEADER) {
225 // Translate the new (V5) object classes to the old classes
226 // (for those which differ).
227 switch (cls) {
228 case kObjectClassUntouchable:
229 cls = 24;
230 break;
231 case kObjectClassPlayer:
232 cls = 23;
233 break;
234 case kObjectClassXFlip:
235 cls = 19;
236 break;
237 case kObjectClassYFlip:
238 cls = 18;
239 break;
240 default:
241 break;
242 }
243 }
244
245 return (_classData[obj] & (1 << (cls - 1))) != 0;
246 }
247
putClass(int obj,int cls,bool set)248 void ScummEngine::putClass(int obj, int cls, bool set) {
249 if (_game.version == 0)
250 return;
251
252 assertRange(0, obj, _numGlobalObjects - 1, "object");
253 cls &= 0x7F;
254 assertRange(1, cls, 32, "class");
255
256 if (_game.features & GF_SMALL_HEADER) {
257 // Translate the new (V5) object classes to the old classes
258 // (for those which differ).
259 switch (cls) {
260 case kObjectClassUntouchable:
261 cls = 24;
262 break;
263 case kObjectClassPlayer:
264 cls = 23;
265 break;
266 case kObjectClassXFlip:
267 cls = 19;
268 break;
269 case kObjectClassYFlip:
270 cls = 18;
271 break;
272 default:
273 break;
274 }
275 }
276
277 if (set)
278 _classData[obj] |= (1 << (cls - 1));
279 else
280 _classData[obj] &= ~(1 << (cls - 1));
281
282 if (_game.version <= 4 && obj >= 1 && obj < _numActors) {
283 _actors[obj]->classChanged(cls, set);
284 }
285 }
286
getOwner(int obj) const287 int ScummEngine::getOwner(int obj) const {
288 assertRange(0, obj, _numGlobalObjects - 1, "object");
289 return _objectOwnerTable[obj];
290 }
291
putOwner(int obj,int owner)292 void ScummEngine::putOwner(int obj, int owner) {
293 assertRange(0, obj, _numGlobalObjects - 1, "object");
294 assertRange(0, owner, 0xFF, "owner");
295 _objectOwnerTable[obj] = owner;
296 }
297
getState(int obj)298 int ScummEngine::getState(int obj) {
299 assertRange(0, obj, _numGlobalObjects - 1, "object");
300
301 if (!_copyProtection) {
302 // I knew LucasArts sold cracked copies of the original Maniac Mansion,
303 // at least as part of Day of the Tentacle. Apparently they also sold
304 // cracked versions of the enhanced version. At least in Germany.
305 //
306 // This will keep the security door open at all times. I can only
307 // assume that 182 and 193 each correspond to one particular side of
308 // it. Fortunately this does not prevent frustrated players from
309 // blowing up the mansion, should they feel the urge to.
310
311 if (_game.id == GID_MANIAC && _game.version != 0 && (obj == 182 || obj == 193))
312 _objectStateTable[obj] |= kObjectState_08;
313 }
314
315 return _objectStateTable[obj];
316 }
317
putState(int obj,int state)318 void ScummEngine::putState(int obj, int state) {
319 assertRange(0, obj, _numGlobalObjects - 1, "object");
320 assertRange(0, state, 0xFF, "state");
321 _objectStateTable[obj] = state;
322 }
323
getObjectRoom(int obj) const324 int ScummEngine::getObjectRoom(int obj) const {
325 assertRange(0, obj, _numGlobalObjects - 1, "object");
326 return _objectRoomTable[obj];
327 }
328
getObjectIndex(int object) const329 int ScummEngine::getObjectIndex(int object) const {
330 int i;
331
332 if (object < 1)
333 return -1;
334
335 for (i = (_numLocalObjects-1); i > 0; i--) {
336 if (_objs[i].obj_nr == object)
337 return i;
338 }
339 return -1;
340 }
341
whereIsObject(int object) const342 int ScummEngine::whereIsObject(int object) const {
343 int i;
344
345 // Note: in MM v0 bg objects are greater _numGlobalObjects
346 if (_game.version != 0 && object >= _numGlobalObjects)
347 return WIO_NOT_FOUND;
348
349 if (object < 1)
350 return WIO_NOT_FOUND;
351
352 if ((_game.version != 0 || OBJECT_V0_TYPE(object) == 0) &&
353 _objectOwnerTable[object] != OF_OWNER_ROOM)
354 {
355 for (i = 0; i < _numInventory; i++)
356 if (_inventory[i] == object)
357 return WIO_INVENTORY;
358 return WIO_NOT_FOUND;
359 }
360
361 for (i = (_numLocalObjects-1); i > 0; i--) {
362 if (_objs[i].obj_nr == object) {
363 if (_objs[i].fl_object_index)
364 return WIO_FLOBJECT;
365 return WIO_ROOM;
366 }
367 }
368
369 return WIO_NOT_FOUND;
370 }
371
getObjectOrActorXY(int object,int & x,int & y)372 int ScummEngine::getObjectOrActorXY(int object, int &x, int &y) {
373 Actor *act;
374
375 if (objIsActor(object)) {
376 act = derefActorSafe(objToActor(object), "getObjectOrActorXY");
377 if (act && act->isInCurrentRoom()) {
378 x = act->getRealPos().x;
379 y = act->getRealPos().y;
380 return 0;
381 } else
382 return -1;
383 }
384
385 switch (whereIsObject(object)) {
386 case WIO_NOT_FOUND:
387 return -1;
388 case WIO_INVENTORY:
389 if (objIsActor(_objectOwnerTable[object])) {
390 act = derefActor(_objectOwnerTable[object], "getObjectOrActorXY(2)");
391 if (act && act->isInCurrentRoom()) {
392 x = act->getRealPos().x;
393 y = act->getRealPos().y;
394 return 0;
395 }
396 }
397 return -1;
398 default:
399 break;
400 }
401 getObjectXYPos(object, x, y);
402 return 0;
403 }
404
405 /**
406 * Return the position of an object.
407 * Returns X, Y and direction in angles
408 */
getObjectXYPos(int object,int & x,int & y,int & dir)409 void ScummEngine::getObjectXYPos(int object, int &x, int &y, int &dir) {
410 int idx = getObjectIndex(object);
411 assert(idx >= 0);
412 ObjectData &od = _objs[idx];
413 int state;
414 const byte *ptr;
415 const ImageHeader *imhd;
416
417 if (_game.version >= 6) {
418 state = getState(object) - 1;
419 if (state < 0)
420 state = 0;
421
422 ptr = getOBIMFromObjectData(od);
423 if (!ptr) {
424 // FIXME: We used to assert here, but it seems that in the nexus
425 // in The Dig, this can happen, at least with old savegames, and
426 // it's safe to continue...
427 debug(0, "getObjectXYPos: Can't find object %d", object);
428 return;
429 }
430 imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), ptr);
431 assert(imhd);
432 if (_game.version == 8) {
433 switch (FROM_LE_32(imhd->v8.version)) {
434 case 800:
435 x = od.x_pos + (int32)READ_LE_UINT32((const byte *)imhd + 8 * state + 0x44);
436 y = od.y_pos + (int32)READ_LE_UINT32((const byte *)imhd + 8 * state + 0x48);
437 break;
438 case 801:
439 x = od.x_pos + (int32)READ_LE_UINT32(&imhd->v8.hotspot[state].x);
440 y = od.y_pos + (int32)READ_LE_UINT32(&imhd->v8.hotspot[state].y);
441 break;
442 default:
443 error("Unsupported image header version %d", FROM_LE_32(imhd->v8.version));
444 }
445 } else if (_game.version == 7) {
446 x = od.x_pos + (int16)READ_LE_UINT16(&imhd->v7.hotspot[state].x);
447 y = od.y_pos + (int16)READ_LE_UINT16(&imhd->v7.hotspot[state].y);
448 } else {
449 x = od.x_pos + (int16)READ_LE_UINT16(&imhd->old.hotspot[state].x);
450 y = od.y_pos + (int16)READ_LE_UINT16(&imhd->old.hotspot[state].y);
451 }
452 } else if (_game.version <= 2) {
453 x = od.walk_x;
454 y = od.walk_y;
455
456 // Adjust x, y when no actor direction is set, but only perform this
457 // adjustment for V0 games (e.g. MM C64), otherwise certain scenes in
458 // newer games are affected as well (e.g. the interior of the Shuttle
459 // Bus scene in Zak V2, where no actor is present). Refer to bug #6034.
460 if (!od.actordir && _game.version == 0) {
461 x = od.x_pos + od.width / 2;
462 y = od.y_pos + od.height / 2;
463 }
464 x = x >> V12_X_SHIFT;
465 y = y >> V12_Y_SHIFT;
466 } else {
467 x = od.walk_x;
468 y = od.walk_y;
469 }
470 if (_game.version == 8)
471 dir = fromSimpleDir(1, od.actordir);
472 else
473 dir = oldDirToNewDir(od.actordir & 3);
474 }
475
getDist(int x,int y,int x2,int y2)476 int ScummEngine::getDist(int x, int y, int x2, int y2) {
477 int a = ABS(y - y2);
478 int b = ABS(x - x2);
479 return MAX(a, b);
480 }
481
getObjActToObjActDist(int a,int b)482 int ScummEngine::getObjActToObjActDist(int a, int b) {
483 int x, y, x2, y2;
484 Actor *acta = NULL;
485 Actor *actb = NULL;
486
487 if (objIsActor(a))
488 acta = derefActorSafe(objToActor(a), "getObjActToObjActDist");
489
490 if (objIsActor(b))
491 actb = derefActorSafe(objToActor(b), "getObjActToObjActDist(2)");
492
493 if (acta && actb && acta->getRoom() == actb->getRoom() && acta->getRoom() && !acta->isInCurrentRoom())
494 return 0;
495
496 if (getObjectOrActorXY(a, x, y) == -1)
497 return 0xFF;
498
499 if (getObjectOrActorXY(b, x2, y2) == -1)
500 return 0xFF;
501
502 // Perform adjustXYToBeInBox() *only* if the first item is an
503 // actor and the second is an object. This used to not check
504 // whether the second item is a non-actor, which caused bug
505 // #1320).
506 if (acta && !actb) {
507 AdjustBoxResult r = acta->adjustXYToBeInBox(x2, y2);
508 x2 = r.x;
509 y2 = r.y;
510 }
511
512
513 // Now compute the distance between the two points
514 return getDist(x, y, x2, y2);
515 }
516
findObject(int x,int y)517 int ScummEngine::findObject(int x, int y) {
518 int i, b;
519 byte a;
520 const int mask = (_game.version <= 2) ? kObjectState_08 : 0xF;
521
522 for (i = 1; i < _numLocalObjects; i++) {
523 if ((_objs[i].obj_nr < 1) || getClass(_objs[i].obj_nr, kObjectClassUntouchable))
524 continue;
525
526 if ((_game.version == 0 && OBJECT_V0_TYPE(_objs[i].obj_nr) == kObjectV0TypeFG) ||
527 (_game.version > 0 && _game.version <= 2)) {
528 if (_objs[i].state & kObjectStateUntouchable)
529 continue;
530 }
531
532 b = i;
533 do {
534 a = _objs[b].parentstate;
535 b = _objs[b].parent;
536 if (b == 0) {
537 #ifdef ENABLE_HE
538 if (_game.heversion >= 71) {
539 if (((ScummEngine_v71he *)this)->_wiz->polygonHit(_objs[i].obj_nr, x, y))
540 return _objs[i].obj_nr;
541 }
542 #endif
543 if (_game.id == GID_CMI || _game.id == GID_DIG) {
544 if (_objs[i].x_pos <= x && _objs[i].width + _objs[i].x_pos >= x &&
545 _objs[i].y_pos <= y && _objs[i].height + _objs[i].y_pos >= y)
546 return _objs[i].obj_nr;
547 } else {
548 if (_objs[i].x_pos <= x && _objs[i].width + _objs[i].x_pos > x &&
549 _objs[i].y_pos <= y && _objs[i].height + _objs[i].y_pos > y)
550 return _objs[i].obj_nr;
551 }
552 break;
553 }
554 } while ((_objs[b].state & mask) == a);
555 }
556
557 return 0;
558 }
559
drawRoomObject(int i,int arg)560 void ScummEngine::drawRoomObject(int i, int arg) {
561 ObjectData *od;
562 byte a;
563 const int mask = (_game.version <= 2) ? kObjectState_08 : 0xF;
564
565 od = &_objs[i];
566 if ((i < 1) || (od->obj_nr < 1) || !od->state)
567 return;
568
569 do {
570 a = od->parentstate;
571 if (!od->parent) {
572 if (_game.version <= 6 || od->fl_object_index == 0)
573 drawObject(i, arg);
574 break;
575 }
576 od = &_objs[od->parent];
577 } while ((od->state & mask) == a);
578 }
579
drawRoomObjects(int arg)580 void ScummEngine::drawRoomObjects(int arg) {
581 int i;
582 const int mask = (_game.version <= 2) ? kObjectState_08 : 0xF;
583
584 if (_game.heversion >= 60) {
585 // In HE games, normal objects are drawn, followed by FlObjects.
586 for (i = (_numLocalObjects-1); i > 0; i--) {
587 if (_objs[i].obj_nr > 0 && (_objs[i].state & mask) && _objs[i].fl_object_index == 0)
588 drawRoomObject(i, arg);
589 }
590 for (i = (_numLocalObjects-1); i > 0; i--) {
591 if (_objs[i].obj_nr > 0 && (_objs[i].state & mask) && _objs[i].fl_object_index != 0)
592 drawRoomObject(i, arg);
593 }
594 } else if (_game.id == GID_SAMNMAX) {
595 // In Sam & Max, objects are drawn in reverse order.
596 for (i = 1; i < _numLocalObjects; i++)
597 if (_objs[i].obj_nr > 0)
598 drawRoomObject(i, arg);
599 } else {
600 for (i = (_numLocalObjects-1); i > 0; i--)
601 if (_objs[i].obj_nr > 0 && (_objs[i].state & mask)) {
602 drawRoomObject(i, arg);
603 }
604 }
605 }
606
drawObject(int obj,int arg)607 void ScummEngine::drawObject(int obj, int arg) {
608 if (_skipDrawObject)
609 return;
610
611 ObjectData &od = _objs[obj];
612 int height, width;
613 const byte *ptr;
614 int x, a, numstrip;
615 int tmp;
616
617 if (_bgNeedsRedraw)
618 arg = 0;
619
620 if (od.obj_nr == 0)
621 return;
622
623 assertRange(0, od.obj_nr, _numGlobalObjects - 1, "object");
624
625 const int xpos = od.x_pos / 8;
626 const int ypos = od.y_pos;
627
628 width = od.width / 8;
629 height = od.height &= 0xFFFFFFF8; // Mask out last 3 bits
630
631 // Short circuit for objects which aren't visible at all.
632 if (width == 0 || xpos > _screenEndStrip || xpos + width < _screenStartStrip)
633 return;
634
635 // For objects without image in Apple II & Commodore 64 versions of Maniac Mansion
636 if (_game.version == 0 && od.OBIMoffset == 0)
637 return;
638
639 ptr = getObjectImage(getOBIMFromObjectData(od), getState(od.obj_nr));
640 if (!ptr)
641 return;
642
643 x = 0xFFFF;
644
645 for (a = numstrip = 0; a < width; a++) {
646 tmp = xpos + a;
647 if (tmp < _screenStartStrip || _screenEndStrip < tmp)
648 continue;
649 if (arg > 0 && _screenStartStrip + arg <= tmp)
650 continue;
651 if (arg < 0 && tmp <= _screenEndStrip + arg)
652 continue;
653 setGfxUsageBit(tmp, USAGE_BIT_DIRTY);
654 if (tmp < x)
655 x = tmp;
656 numstrip++;
657 }
658
659 if (numstrip != 0) {
660 byte flags = od.flags | Gdi::dbObjectMode;
661
662 // Sam & Max needs this to fix object-layering problems with
663 // the inventory and conversation icons.
664 if ((_game.id == GID_SAMNMAX && getClass(od.obj_nr, kObjectClassIgnoreBoxes)) ||
665 (_game.id == GID_FT && getClass(od.obj_nr, kObjectClassPlayer)))
666 flags |= Gdi::dbDrawMaskOnAll;
667
668 #ifdef ENABLE_HE
669 if (_game.heversion >= 70 && findResource(MKTAG('S','M','A','P'), ptr) == NULL)
670 _gdi->drawBMAPObject(ptr, &_virtscr[kMainVirtScreen], obj, od.x_pos, od.y_pos, od.width, od.height);
671 else
672 #endif
673 _gdi->drawBitmap(ptr, &_virtscr[kMainVirtScreen], x, ypos, width * 8, height, x - xpos, numstrip, flags);
674 }
675 }
676
clearRoomObjects()677 void ScummEngine::clearRoomObjects() {
678 int i;
679
680 if (_game.features & GF_SMALL_HEADER) {
681 for (i = 0; i < _numLocalObjects; i++) {
682 _objs[i].obj_nr = 0;
683 }
684 } else {
685 for (i = 0; i < _numLocalObjects; i++) {
686 if (_objs[i].obj_nr < 1) // Optimise codepath
687 continue;
688
689 // Nuke all non-flObjects (flObjects are nuked in script.cpp)
690 if (_objs[i].fl_object_index == 0) {
691 _objs[i].obj_nr = 0;
692 } else {
693 // Nuke all unlocked flObjects
694 if (!_res->isLocked(rtFlObject, _objs[i].fl_object_index)) {
695 _res->nukeResource(rtFlObject, _objs[i].fl_object_index);
696 _objs[i].obj_nr = 0;
697 _objs[i].fl_object_index = 0;
698 }
699 }
700 }
701 }
702 }
703
resetRoomObjects()704 void ScummEngine_v70he::resetRoomObjects() {
705 ScummEngine_v60he::resetRoomObjects();
706 restoreFlObjects();
707 }
708
clearRoomObjects()709 void ScummEngine_v70he::clearRoomObjects() {
710 _numStoredFlObjects = 0;
711
712 for (int i = 0; i < _numLocalObjects; i++) {
713 if (_objs[i].obj_nr < 1) // Optimise codepath
714 continue;
715
716 if (_objs[i].fl_object_index != 0) {
717 if (!_res->isLocked(rtFlObject, _objs[i].fl_object_index)) {
718 _res->nukeResource(rtFlObject, _objs[i].fl_object_index);
719 } else {
720 storeFlObject(i);
721 }
722 }
723 _objs[i].fl_object_index = 0;
724 _objs[i].obj_nr = 0;
725 }
726
727 if (_currentRoom == 0)
728 restoreFlObjects();
729 }
730
storeFlObject(int slot)731 void ScummEngine_v70he::storeFlObject(int slot) {
732 memcpy(&_storedFlObjects[_numStoredFlObjects], &_objs[slot], sizeof(_objs[slot]));
733 _numStoredFlObjects++;
734 if (_numStoredFlObjects > 100)
735 error("Too many flobjects saved on room transition");
736 }
737
restoreFlObjects()738 void ScummEngine_v70he::restoreFlObjects() {
739 int i, slot;
740
741 for (i = 0; i < _numStoredFlObjects; i++) {
742 slot = findLocalObjectSlot();
743 memcpy(&_objs[slot], &_storedFlObjects[i], sizeof(_objs[slot]));
744 }
745
746 _numStoredFlObjects = 0;
747 }
748
resetRoomObjects()749 void ScummEngine::resetRoomObjects() {
750 int i, j;
751 ObjectData *od;
752 const byte *ptr;
753 uint16 obim_id;
754 const byte *room, *searchptr, *rootptr;
755 const CodeHeader *cdhd;
756
757 room = getResourceAddress(rtRoom, _roomResource);
758 assert(room);
759
760 if (_numObjectsInRoom == 0)
761 return;
762
763 if (_numObjectsInRoom > _numLocalObjects)
764 error("More than %d objects in room %d", _numLocalObjects, _roomResource);
765
766 if (_game.version == 8)
767 searchptr = rootptr = getResourceAddress(rtRoomScripts, _roomResource);
768 else
769 searchptr = rootptr = room;
770 assert(searchptr);
771
772 // Load in new room objects
773 ResourceIterator obcds(searchptr, false);
774 for (i = 0; i < _numObjectsInRoom; i++) {
775 od = &_objs[findLocalObjectSlot()];
776
777 ptr = obcds.findNext(MKTAG('O','B','C','D'));
778 if (ptr == NULL)
779 error("Room %d missing object code block(s)", _roomResource);
780
781 od->OBCDoffset = ptr - rootptr;
782 cdhd = (const CodeHeader *)findResourceData(MKTAG('C','D','H','D'), ptr);
783
784 if (_game.version >= 7)
785 od->obj_nr = READ_LE_UINT16(&(cdhd->v7.obj_id));
786 else if (_game.version == 6)
787 od->obj_nr = READ_LE_UINT16(&(cdhd->v6.obj_id));
788 else
789 od->obj_nr = READ_LE_UINT16(&(cdhd->v5.obj_id));
790
791 if (_dumpScripts) {
792 char buf[32];
793 sprintf(buf, "roomobj-%d-", _roomResource);
794 ptr = findResource(MKTAG('V','E','R','B'), ptr);
795 dumpResource(buf, od->obj_nr, ptr);
796 }
797
798 }
799
800 searchptr = room;
801 ResourceIterator obims(room, false);
802 for (i = 0; i < _numObjectsInRoom; i++) {
803 ptr = obims.findNext(MKTAG('O','B','I','M'));
804 if (ptr == NULL)
805 error("Room %d missing image blocks(s)", _roomResource);
806
807 obim_id = getObjectIdFromOBIM(ptr);
808
809 for (j = 1; j < _numLocalObjects; j++) {
810 if (_objs[j].obj_nr == obim_id)
811 _objs[j].OBIMoffset = ptr - room;
812 }
813 }
814
815 for (i = 1; i < _numLocalObjects; i++) {
816 if (_objs[i].obj_nr && !_objs[i].fl_object_index)
817 resetRoomObject(&_objs[i], room);
818 }
819 }
820
resetRoomObjects()821 void ScummEngine_v3old::resetRoomObjects() {
822 int i;
823 ObjectData *od;
824 const byte *room, *ptr;
825
826 room = getResourceAddress(rtRoom, _roomResource);
827 assert(room);
828
829 if (_numObjectsInRoom == 0)
830 return;
831
832 if (_numObjectsInRoom > _numLocalObjects)
833 error("More than %d objects in room %d", _numLocalObjects, _roomResource);
834
835 if (_game.version <= 2)
836 ptr = room + 28;
837 else
838 ptr = room + 29;
839
840 // Default pointer of objects without image, in v0 version of Maniac Mansion
841 int defaultPtr = READ_LE_UINT16(ptr + 2 * _numObjectsInRoom);
842
843 for (i = 0; i < _numObjectsInRoom; i++) {
844 od = &_objs[findLocalObjectSlot()];
845
846 if (_game.version == 0 && READ_LE_UINT16(ptr) == defaultPtr)
847 od->OBIMoffset = 0;
848 else
849 od->OBIMoffset = READ_LE_UINT16(ptr);
850
851 od->OBCDoffset = READ_LE_UINT16(ptr + 2 * _numObjectsInRoom);
852 resetRoomObject(od, room);
853
854 ptr += 2;
855
856 if (_dumpScripts) {
857 char buf[32];
858 sprintf(buf, "roomobj-%d-", _roomResource);
859 dumpResource(buf, od->obj_nr, room + od->OBCDoffset);
860 }
861 }
862 }
863
resetRoomObjects()864 void ScummEngine_v4::resetRoomObjects() {
865 int i, j;
866 ObjectData *od;
867 const byte *ptr;
868 uint16 obim_id;
869 const byte *room;
870
871 room = getResourceAddress(rtRoom, _roomResource);
872 assert(room);
873
874 if (_numObjectsInRoom == 0)
875 return;
876
877 if (_numObjectsInRoom > _numLocalObjects)
878 error("More than %d objects in room %d", _numLocalObjects, _roomResource);
879
880 ResourceIterator obcds(room, true);
881 for (i = 0; i < _numObjectsInRoom; i++) {
882 od = &_objs[findLocalObjectSlot()];
883
884 ptr = obcds.findNext(MKTAG('O','B','C','D'));
885 if (ptr == NULL)
886 error("Room %d missing object code block(s)", _roomResource);
887
888 od->OBCDoffset = ptr - room;
889 od->obj_nr = READ_LE_UINT16(ptr + 6);
890 if (_dumpScripts) {
891 char buf[32];
892 sprintf(buf, "roomobj-%d-", _roomResource);
893 dumpResource(buf, od->obj_nr, ptr);
894 }
895 }
896
897 ResourceIterator obims(room, true);
898 for (i = 0; i < _numObjectsInRoom; i++) {
899 // In the PC Engine version of Loom, there aren't image blocks
900 // for all objects.
901 ptr = obims.findNext(MKTAG('O','B','I','M'));
902 if (ptr == NULL)
903 break;
904
905 obim_id = READ_LE_UINT16(ptr + 6);
906
907 for (j = 1; j < _numLocalObjects; j++) {
908 if (_objs[j].obj_nr == obim_id)
909 _objs[j].OBIMoffset = ptr - room;
910 }
911 }
912
913 for (i = 1; i < _numLocalObjects; i++) {
914 if (_objs[i].obj_nr && !_objs[i].fl_object_index) {
915 resetRoomObject(&_objs[i], room);
916 }
917 }
918 }
919
resetRoomObject(ObjectData * od,const byte * room,const byte * searchptr)920 void ScummEngine_v0::resetRoomObject(ObjectData *od, const byte *room, const byte *searchptr) {
921 assert(room);
922 const byte *ptr = room + od->OBCDoffset;
923 ptr -= 2;
924
925 od->obj_nr = OBJECT_V0(*(ptr + 6), *(ptr + 7));
926
927 od->x_pos = *(ptr + 8) * 8;
928 od->y_pos = ((*(ptr + 9)) & 0x7F) * 8;
929
930 od->parentstate = (*(ptr + 9) & 0x80) ? 1 : 0;
931 od->parentstate *= 8;
932
933 od->width = *(ptr + 10) * 8;
934
935 od->parent = *(ptr + 11);
936
937 od->walk_x = *(ptr + 12) * 8;
938 od->walk_y = (*(ptr + 13) & 0x1f) * 8;
939 od->actordir = (*(ptr + 14)) & 7;
940 od->height = *(ptr + 14) & 0xf8;
941 }
942
resetRoomObject(ObjectData * od,const byte * room,const byte * searchptr)943 void ScummEngine_v4::resetRoomObject(ObjectData *od, const byte *room, const byte *searchptr) {
944 assert(room);
945 const byte *ptr = room + od->OBCDoffset;
946
947 if (_game.features & GF_OLD_BUNDLE)
948 ptr -= 2;
949
950 od->obj_nr = READ_LE_UINT16(ptr + 6);
951
952 if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine) {
953 od->x_pos = *(ptr + 8) * 8;
954 od->y_pos = ((*(ptr + 9)) & 0x7F) * 8;
955
956 od->parentstate = (*(ptr + 9) & 0x80) ? 1 : 0;
957 od->width = *(ptr + 10) * 8;
958
959 // TODO: Where is parent data?
960 od->parent = 0;
961 od->walk_x = READ_LE_UINT16(ptr + 11);
962 od->walk_y = READ_LE_UINT16(ptr + 13);
963 od->actordir = (*(ptr + 15)) & 7;
964 od->height = *(ptr + 15) & 0xf8;
965 } else {
966 od->x_pos = *(ptr + 9) * 8;
967 od->y_pos = ((*(ptr + 10)) & 0x7F) * 8;
968
969 od->parentstate = (*(ptr + 10) & 0x80) ? 1 : 0;
970 if (_game.version <= 2)
971 od->parentstate *= 8;
972
973 od->width = *(ptr + 11) * 8;
974
975 od->parent = *(ptr + 12);
976
977 if (_game.version <= 2) {
978 od->walk_x = *(ptr + 13) * 8;
979 od->walk_y = (*(ptr + 14) & 0x1f) * 8;
980 od->actordir = (*(ptr + 15)) & 7;
981 od->height = *(ptr + 15) & 0xf8;
982 } else {
983 od->walk_x = READ_LE_UINT16(ptr + 13);
984 od->walk_y = READ_LE_UINT16(ptr + 15);
985 od->actordir = (*(ptr + 17)) & 7;
986 od->height = *(ptr + 17) & 0xf8;
987 }
988 }
989 }
990
resetRoomObject(ObjectData * od,const byte * room,const byte * searchptr)991 void ScummEngine::resetRoomObject(ObjectData *od, const byte *room, const byte *searchptr) {
992 const CodeHeader *cdhd = NULL;
993 const ImageHeader *imhd = NULL;
994
995 assert(room);
996
997 if (searchptr == NULL) {
998 if (_game.version == 8) {
999 searchptr = getResourceAddress(rtRoomScripts, _roomResource);
1000 assert(searchptr);
1001 } else {
1002 searchptr = room;
1003 }
1004 }
1005
1006 cdhd = (const CodeHeader *)findResourceData(MKTAG('C','D','H','D'), searchptr + od->OBCDoffset);
1007 if (cdhd == NULL)
1008 error("Room %d missing CDHD blocks(s)", _roomResource);
1009 if (od->OBIMoffset)
1010 imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), room + od->OBIMoffset);
1011
1012 od->flags = Gdi::dbAllowMaskOr;
1013
1014 if (_game.version == 8) {
1015 assert(imhd);
1016 od->obj_nr = READ_LE_UINT16(&(cdhd->v7.obj_id));
1017
1018 od->parent = cdhd->v7.parent;
1019 od->parentstate = cdhd->v7.parentstate;
1020
1021 od->x_pos = (int)READ_LE_UINT32(&imhd->v8.x_pos);
1022 od->y_pos = (int)READ_LE_UINT32(&imhd->v8.y_pos);
1023 od->width = (uint)READ_LE_UINT32(&imhd->v8.width);
1024 od->height = (uint)READ_LE_UINT32(&imhd->v8.height);
1025 // HACK: This is done since an angle doesn't fit into a byte (360 > 256)
1026 od->actordir = toSimpleDir(1, READ_LE_UINT32(&imhd->v8.actordir));
1027 if (FROM_LE_32(imhd->v8.version) == 801)
1028 od->flags = ((((byte)READ_LE_UINT32(&imhd->v8.flags)) & 16) == 0) ? Gdi::dbAllowMaskOr : 0;
1029
1030 } else if (_game.version == 7) {
1031 assert(imhd);
1032 od->obj_nr = READ_LE_UINT16(&(cdhd->v7.obj_id));
1033
1034 od->parent = cdhd->v7.parent;
1035 od->parentstate = cdhd->v7.parentstate;
1036
1037 od->x_pos = READ_LE_UINT16(&imhd->v7.x_pos);
1038 od->y_pos = READ_LE_UINT16(&imhd->v7.y_pos);
1039 od->width = READ_LE_UINT16(&imhd->v7.width);
1040 od->height = READ_LE_UINT16(&imhd->v7.height);
1041 od->actordir = (byte)READ_LE_UINT16(&imhd->v7.actordir);
1042
1043 } else if (_game.version == 6) {
1044 od->obj_nr = READ_LE_UINT16(&(cdhd->v6.obj_id));
1045
1046 od->width = READ_LE_UINT16(&cdhd->v6.w);
1047 od->height = READ_LE_UINT16(&cdhd->v6.h);
1048 od->x_pos = ((int16)READ_LE_UINT16(&cdhd->v6.x));
1049 od->y_pos = ((int16)READ_LE_UINT16(&cdhd->v6.y));
1050 if (cdhd->v6.flags == 0x80) {
1051 od->parentstate = 1;
1052 } else {
1053 od->parentstate = (cdhd->v6.flags & 0xF);
1054 }
1055 od->parent = cdhd->v6.parent;
1056 od->actordir = cdhd->v6.actordir;
1057
1058 if (_game.heversion >= 60 && imhd)
1059 od->flags = ((imhd->old.flags & 1) != 0) ? Gdi::dbAllowMaskOr : 0;
1060
1061 } else {
1062 od->obj_nr = READ_LE_UINT16(&(cdhd->v5.obj_id));
1063
1064 od->width = cdhd->v5.w * 8;
1065 od->height = cdhd->v5.h * 8;
1066 od->x_pos = cdhd->v5.x * 8;
1067 od->y_pos = cdhd->v5.y * 8;
1068 if (cdhd->v5.flags == 0x80) {
1069 od->parentstate = 1;
1070 } else {
1071 od->parentstate = (cdhd->v5.flags & 0xF);
1072 }
1073 od->parent = cdhd->v5.parent;
1074 od->walk_x = READ_LE_UINT16(&cdhd->v5.walk_x);
1075 od->walk_y = READ_LE_UINT16(&cdhd->v5.walk_y);
1076 od->actordir = cdhd->v5.actordir;
1077 }
1078
1079 od->fl_object_index = 0;
1080 }
1081
updateObjectStates()1082 void ScummEngine::updateObjectStates() {
1083 int i;
1084 ObjectData *od = &_objs[1];
1085 for (i = 1; i < _numLocalObjects; i++, od++) {
1086 // V0 MM, objects with type == 1 are room objects (room specific objects, non-pickup)
1087 if (_game.version == 0 && OBJECT_V0_TYPE(od->obj_nr) == kObjectV0TypeBG)
1088 continue;
1089
1090 if (od->obj_nr > 0)
1091 od->state = getState(od->obj_nr);
1092 }
1093 }
1094
processDrawQue()1095 void ScummEngine::processDrawQue() {
1096 int i, j;
1097 for (i = 0; i < _drawObjectQueNr; i++) {
1098 j = _drawObjectQue[i];
1099 if (j)
1100 drawObject(j, 0);
1101 }
1102 _drawObjectQueNr = 0;
1103 }
1104
addObjectToDrawQue(int object)1105 void ScummEngine::addObjectToDrawQue(int object) {
1106 if ((unsigned int)_drawObjectQueNr >= ARRAYSIZE(_drawObjectQue))
1107 error("Draw Object Que overflow");
1108 _drawObjectQue[_drawObjectQueNr++] = object;
1109 }
1110
removeObjectFromDrawQue(int object)1111 void ScummEngine::removeObjectFromDrawQue(int object) {
1112 if (_drawObjectQueNr <= 0)
1113 return;
1114
1115 int i;
1116 for (i = 0; i < _drawObjectQueNr; i++) {
1117 if (_drawObjectQue[i] == object)
1118 _drawObjectQue[i] = 0;
1119 }
1120 }
1121
clearDrawObjectQueue()1122 void ScummEngine::clearDrawObjectQueue() {
1123 _drawObjectQueNr = 0;
1124 }
1125
clearDrawQueues()1126 void ScummEngine::clearDrawQueues() {
1127 clearDrawObjectQueue();
1128 }
1129
clearDrawQueues()1130 void ScummEngine_v6::clearDrawQueues() {
1131 ScummEngine::clearDrawQueues();
1132
1133 _blastObjectQueuePos = 0;
1134 }
1135
1136 #ifdef ENABLE_HE
clearDrawQueues()1137 void ScummEngine_v71he::clearDrawQueues() {
1138 ScummEngine_v6::clearDrawQueues();
1139
1140 _wiz->polygonClear();
1141 }
1142
clearDrawQueues()1143 void ScummEngine_v80he::clearDrawQueues() {
1144 ScummEngine_v71he::clearDrawQueues();
1145
1146 _wiz->clearWizBuffer();
1147 }
1148 #endif
1149
1150 /**
1151 * Mark the rectangle covered by the given object as dirty, thus eventually
1152 * ensuring a redraw of that area. This function is typically invoked when an
1153 * object gets removed from the current room, or when its state changed.
1154 */
markObjectRectAsDirty(int obj)1155 void ScummEngine::markObjectRectAsDirty(int obj) {
1156 int i, strip;
1157 ++_V0Delay._objectRedrawCount;
1158
1159 for (i = 1; i < _numLocalObjects; i++) {
1160 if (_objs[i].obj_nr == (uint16)obj) {
1161 if (_objs[i].width != 0) {
1162 const int minStrip = MAX(_screenStartStrip, _objs[i].x_pos / 8);
1163 const int maxStrip = MIN(_screenEndStrip+1, _objs[i].x_pos / 8 + _objs[i].width / 8);
1164 for (strip = minStrip; strip < maxStrip; strip++) {
1165 ++_V0Delay._objectStripRedrawCount;
1166 setGfxUsageBit(strip, USAGE_BIT_DIRTY);
1167 }
1168 }
1169 _bgNeedsRedraw = true;
1170 return;
1171 }
1172 }
1173 }
1174
getObjOrActorName(int obj)1175 const byte *ScummEngine::getObjOrActorName(int obj) {
1176 byte *objptr;
1177 int i;
1178
1179 if (objIsActor(obj))
1180 return derefActor(objToActor(obj), "getObjOrActorName")->getActorName();
1181
1182 for (i = 0; i < _numNewNames; i++) {
1183 if (_newNames[i] == obj) {
1184 debug(5, "Found new name for object %d at _newNames[%d]", obj, i);
1185 return getResourceAddress(rtObjectName, i);
1186 }
1187 }
1188
1189 objptr = getOBCDFromObject(obj, true);
1190 if (objptr == NULL)
1191 return NULL;
1192
1193 if (_game.features & GF_SMALL_HEADER) {
1194 byte offset = 0;
1195
1196 if (_game.version == 0)
1197 offset = *(objptr + 13);
1198 else if (_game.version <= 2)
1199 offset = *(objptr + 14);
1200 else if (_game.features & GF_OLD_BUNDLE)
1201 offset = *(objptr + 16);
1202 else if (_game.id == GID_LOOM && _game.platform == Common::kPlatformPCEngine)
1203 offset = *(objptr + 16) + 17;
1204 else
1205 offset = *(objptr + 18);
1206
1207 return (objptr + offset);
1208 }
1209
1210 return findResourceData(MKTAG('O','B','N','A'), objptr);
1211 }
1212
setObjectName(int obj)1213 void ScummEngine::setObjectName(int obj) {
1214 int i;
1215
1216 if (objIsActor(obj))
1217 error("Can't set actor %d name with new-name-of", obj);
1218
1219 for (i = 0; i < _numNewNames; i++) {
1220 if (_newNames[i] == obj) {
1221 _res->nukeResource(rtObjectName, i);
1222 _newNames[i] = 0;
1223 break;
1224 }
1225 }
1226
1227 for (i = 0; i < _numNewNames; i++) {
1228 if (_newNames[i] == 0) {
1229 loadPtrToResource(rtObjectName, i, NULL);
1230 _newNames[i] = obj;
1231 runInventoryScript(0);
1232 return;
1233 }
1234 }
1235
1236 error("New name of %d overflows name table (max = %d)", obj, _numNewNames);
1237 }
1238
getOBCDOffs(int object) const1239 uint32 ScummEngine::getOBCDOffs(int object) const {
1240 int i;
1241
1242 if ((_game.version != 0 || OBJECT_V0_TYPE(object) == 0) &&
1243 _objectOwnerTable[object] != OF_OWNER_ROOM)
1244 return 0;
1245
1246 for (i = (_numLocalObjects-1); i > 0; i--) {
1247 if (_objs[i].obj_nr == object) {
1248 if (_objs[i].fl_object_index != 0)
1249 return 8;
1250 return _objs[i].OBCDoffset;
1251 }
1252 }
1253 return 0;
1254 }
1255
getOBCDFromObject(int obj,bool v0CheckInventory)1256 byte *ScummEngine::getOBCDFromObject(int obj, bool v0CheckInventory) {
1257 int i;
1258 byte *ptr;
1259
1260 if ((_game.version != 0 || OBJECT_V0_TYPE(obj) == 0) &&
1261 _objectOwnerTable[obj] != OF_OWNER_ROOM)
1262 {
1263 if (_game.version == 0 && !v0CheckInventory)
1264 return 0;
1265 for (i = 0; i < _numInventory; i++) {
1266 if (_inventory[i] == obj)
1267 return getResourceAddress(rtInventory, i);
1268 }
1269 } else {
1270 for (i = (_numLocalObjects-1); i > 0; --i) {
1271 if (_objs[i].obj_nr == obj) {
1272 if (_objs[i].fl_object_index) {
1273 assert(_objs[i].OBCDoffset == 8);
1274 ptr = getResourceAddress(rtFlObject, _objs[i].fl_object_index);
1275 } else if (_game.version == 8)
1276 ptr = getResourceAddress(rtRoomScripts, _roomResource);
1277 else
1278 ptr = getResourceAddress(rtRoom, _roomResource);
1279 assert(ptr);
1280 return ptr + _objs[i].OBCDoffset;
1281 }
1282 }
1283 }
1284 return 0;
1285 }
1286
getOBIMFromObjectData(const ObjectData & od)1287 const byte *ScummEngine::getOBIMFromObjectData(const ObjectData &od) {
1288 const byte *ptr;
1289
1290 if (od.fl_object_index) {
1291 ptr = getResourceAddress(rtFlObject, od.fl_object_index);
1292 ptr = findResource(MKTAG('O','B','I','M'), ptr);
1293 } else {
1294 ptr = getResourceAddress(rtRoom, _roomResource);
1295 if (ptr)
1296 ptr += od.OBIMoffset;
1297 }
1298 return ptr;
1299 }
1300
1301 static const uint32 IMxx_tags[] = {
1302 MKTAG('I','M','0','0'),
1303 MKTAG('I','M','0','1'),
1304 MKTAG('I','M','0','2'),
1305 MKTAG('I','M','0','3'),
1306 MKTAG('I','M','0','4'),
1307 MKTAG('I','M','0','5'),
1308 MKTAG('I','M','0','6'),
1309 MKTAG('I','M','0','7'),
1310 MKTAG('I','M','0','8'),
1311 MKTAG('I','M','0','9'),
1312 MKTAG('I','M','0','A'),
1313 MKTAG('I','M','0','B'),
1314 MKTAG('I','M','0','C'),
1315 MKTAG('I','M','0','D'),
1316 MKTAG('I','M','0','E'),
1317 MKTAG('I','M','0','F'),
1318 MKTAG('I','M','1','0')
1319 };
1320
getObjectImage(const byte * ptr,int state)1321 const byte *ScummEngine::getObjectImage(const byte *ptr, int state) {
1322 assert(ptr);
1323 if (_game.features & GF_OLD_BUNDLE)
1324 ptr += 0;
1325 else if (_game.features & GF_SMALL_HEADER) {
1326 ptr += 8;
1327 } else if (_game.version == 8) {
1328 // The OBIM contains an IMAG, which in turn contains a WRAP, which contains
1329 // an OFFS chunk and multiple BOMP/SMAP chunks. To find the right BOMP/SMAP,
1330 // we use the offsets in the OFFS chunk,
1331 ptr = findResource(MKTAG('I','M','A','G'), ptr);
1332 if (!ptr)
1333 return 0;
1334
1335 ptr = findResource(MKTAG('W','R','A','P'), ptr);
1336 if (!ptr)
1337 return 0;
1338
1339 ptr = findResource(MKTAG('O','F','F','S'), ptr);
1340 if (!ptr)
1341 return 0;
1342
1343 // Get the address of the specified SMAP (corresponding to IMxx)
1344 ptr += READ_LE_UINT32(ptr + 4 + 4*state);
1345 } else {
1346 ptr = findResource(IMxx_tags[state], ptr);
1347 }
1348
1349 return ptr;
1350 }
1351
getObjectImageCount(int object)1352 int ScummEngine::getObjectImageCount(int object) {
1353 const byte *ptr;
1354 const ImageHeader *imhd;
1355 int objnum;
1356
1357 objnum = getObjectIndex(object);
1358 if (objnum == -1)
1359 return 0;
1360
1361 ptr = getOBIMFromObjectData(_objs[objnum]);
1362 imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), ptr);
1363 if (!imhd)
1364 return 0;
1365
1366 if (_game.version == 8) {
1367 return (READ_LE_UINT32(&imhd->v8.image_count));
1368 } else if (_game.version == 7) {
1369 return(READ_LE_UINT16(&imhd->v7.image_count));
1370 } else {
1371 return (READ_LE_UINT16(&imhd->old.image_count));
1372 }
1373 }
1374
1375 #ifdef ENABLE_SCUMM_7_8
getObjectIdFromOBIM(const byte * obim)1376 int ScummEngine_v8::getObjectIdFromOBIM(const byte *obim) {
1377 // In V8, IMHD has no obj_id, but rather a name string. We map the name
1378 // back to an object id using a table derived from the DOBJ resource.
1379 const ImageHeader *imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), obim);
1380 ObjectNameId *found = (ObjectNameId *)bsearch(imhd->v8.name, _objectIDMap, _objectIDMapSize,
1381 sizeof(ObjectNameId), (int (*)(const void*, const void*))strcmp);
1382 assert(found);
1383 return found->id;
1384 }
1385
getObjectIdFromOBIM(const byte * obim)1386 int ScummEngine_v7::getObjectIdFromOBIM(const byte *obim) {
1387 const ImageHeader *imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), obim);
1388 return READ_LE_UINT16(&imhd->v7.obj_id);
1389 }
1390 #endif
1391
getObjectIdFromOBIM(const byte * obim)1392 int ScummEngine::getObjectIdFromOBIM(const byte *obim) {
1393 if (_game.features & GF_SMALL_HEADER)
1394 return READ_LE_UINT16(obim + 6);
1395
1396 const ImageHeader *imhd = (const ImageHeader *)findResourceData(MKTAG('I','M','H','D'), obim);
1397 return READ_LE_UINT16(&imhd->old.obj_id);
1398 }
1399
findObjectInRoom(FindObjectInRoom * fo,byte findWhat,uint id,uint room)1400 void ScummEngine::findObjectInRoom(FindObjectInRoom *fo, byte findWhat, uint id, uint room) {
1401
1402 const CodeHeader *cdhd;
1403 int i, numobj;
1404 const byte *roomptr, *obcdptr, *obimptr, *searchptr;
1405 int id2;
1406 int obim_id;
1407
1408 id2 = getObjectIndex(id);
1409 if (findWhat & foCheckAlreadyLoaded && id2 != -1) {
1410 assert(_game.version >= 6);
1411 if (findWhat & foCodeHeader) {
1412 fo->obcd = obcdptr = getOBCDFromObject(id);
1413 assert(obcdptr);
1414 fo->cdhd = (const CodeHeader *)findResourceData(MKTAG('C','D','H','D'), obcdptr);
1415 }
1416 if (findWhat & foImageHeader) {
1417 fo->obim = obimptr = getOBIMFromObjectData(_objs[id2]);
1418 assert(obimptr);
1419 }
1420 return;
1421 }
1422
1423 fo->roomptr = roomptr = getResourceAddress(rtRoom, room);
1424 if (!roomptr)
1425 error("findObjectInRoom: failed getting roomptr to %d", room);
1426
1427 if (_game.features & GF_OLD_BUNDLE) {
1428 numobj = roomptr[20];
1429 } else {
1430 const RoomHeader *roomhdr = (const RoomHeader *)findResourceData(MKTAG('R','M','H','D'), roomptr);
1431
1432 if (_game.version == 8)
1433 numobj = READ_LE_UINT32(&(roomhdr->v8.numObjects));
1434 else if (_game.version == 7)
1435 numobj = READ_LE_UINT16(&(roomhdr->v7.numObjects));
1436 else
1437 numobj = READ_LE_UINT16(&(roomhdr->old.numObjects));
1438 }
1439
1440 if (numobj == 0)
1441 error("findObjectInRoom: No object found in room %d", room);
1442 if (numobj > _numLocalObjects)
1443 error("findObjectInRoom: More (%d) than %d objects in room %d", numobj, _numLocalObjects, room);
1444
1445 if (_game.features & GF_OLD_BUNDLE) {
1446 if (_game.version <= 2)
1447 searchptr = roomptr + 28;
1448 else
1449 searchptr = roomptr + 29;
1450
1451 for (i = 0; i < numobj; i++) {
1452 obimptr = roomptr + READ_LE_UINT16(searchptr);
1453 obcdptr = roomptr + READ_LE_UINT16(searchptr + 2 * numobj);
1454 id2 = READ_LE_UINT16(obcdptr + 4);
1455
1456 if (id2 == (uint16)id) {
1457 if (findWhat & foCodeHeader) {
1458 fo->obcd = obcdptr;
1459 // We assume that the code header starts at a fixed offset.
1460 // A bit hackish, but works reasonably well.
1461 fo->cdhd = (const CodeHeader *)(obcdptr + 10);
1462 }
1463 if (findWhat & foImageHeader) {
1464 fo->obim = obimptr;
1465 }
1466 break;
1467 }
1468 searchptr += 2;
1469 }
1470 return;
1471 }
1472
1473 if (findWhat & foCodeHeader) {
1474 if (_game.version == 8)
1475 searchptr = getResourceAddress(rtRoomScripts, room);
1476 else
1477 searchptr = roomptr;
1478 assert(searchptr);
1479 ResourceIterator obcds(searchptr, (_game.features & GF_SMALL_HEADER) != 0);
1480 for (i = 0; i < numobj; i++) {
1481 obcdptr = obcds.findNext(MKTAG('O','B','C','D'));
1482 if (obcdptr == NULL)
1483 error("findObjectInRoom: Not enough code blocks in room %d", room);
1484 cdhd = (const CodeHeader *)findResourceData(MKTAG('C','D','H','D'), obcdptr);
1485
1486 if (_game.features & GF_SMALL_HEADER)
1487 id2 = READ_LE_UINT16(obcdptr + 6);
1488 else if (_game.version >= 7)
1489 id2 = READ_LE_UINT16(&(cdhd->v7.obj_id));
1490 else if (_game.version == 6)
1491 id2 = READ_LE_UINT16(&(cdhd->v6.obj_id));
1492 else
1493 id2 = READ_LE_UINT16(&(cdhd->v5.obj_id));
1494
1495 if (id2 == (uint16)id) {
1496 fo->obcd = obcdptr;
1497 fo->cdhd = cdhd;
1498 break;
1499 }
1500 }
1501 if (i == numobj)
1502 error("findObjectInRoom: Object %d not found in room %d", id, room);
1503 }
1504
1505 roomptr = fo->roomptr;
1506 if (findWhat & foImageHeader) {
1507 ResourceIterator obims(roomptr, (_game.features & GF_SMALL_HEADER) != 0);
1508 for (i = 0; i < numobj; i++) {
1509 obimptr = obims.findNext(MKTAG('O','B','I','M'));
1510 if (obimptr == NULL)
1511 error("findObjectInRoom: Not enough image blocks in room %d", room);
1512 obim_id = getObjectIdFromOBIM(obimptr);
1513
1514 if (obim_id == (uint16)id) {
1515 fo->obim = obimptr;
1516 break;
1517 }
1518 }
1519 if (i == numobj)
1520 error("findObjectInRoom: Object %d image not found in room %d", id, room);
1521 }
1522 }
1523
objIsActor(int obj)1524 bool ScummEngine_v0::objIsActor(int obj) {
1525 // object IDs < _numActors are used in v0 for objects too (e.g. hamster)
1526 return OBJECT_V0_TYPE(obj) == kObjectV0TypeActor;
1527 }
1528
objToActor(int obj)1529 int ScummEngine_v0::objToActor(int obj) {
1530 return OBJECT_V0_ID(obj);
1531 }
1532
actorToObj(int actor)1533 int ScummEngine_v0::actorToObj(int actor) {
1534 return OBJECT_V0(actor, kObjectV0TypeActor);
1535 }
1536
objIsActor(int obj)1537 bool ScummEngine::objIsActor(int obj) {
1538 return obj < _numActors;
1539 }
1540
objToActor(int obj)1541 int ScummEngine::objToActor(int obj) {
1542 return obj;
1543 }
1544
actorToObj(int actor)1545 int ScummEngine::actorToObj(int actor) {
1546 return actor;
1547 }
1548
getObjX(int obj)1549 int ScummEngine::getObjX(int obj) {
1550 if (obj < 1)
1551 return 0; /* fix for indy4's map */
1552
1553 if (objIsActor(obj)) {
1554 return derefActor(objToActor(obj), "getObjX")->getRealPos().x;
1555 } else {
1556 if (whereIsObject(obj) == WIO_NOT_FOUND)
1557 return -1;
1558 int x, y;
1559 if (getObjectOrActorXY(obj, x, y) == -1)
1560 return -1;
1561 return x;
1562 }
1563 }
1564
getObjY(int obj)1565 int ScummEngine::getObjY(int obj) {
1566 if (obj < 1)
1567 return 0; /* fix for indy4's map */
1568
1569 if (objIsActor(obj)) {
1570 return derefActor(objToActor(obj), "getObjY")->getRealPos().y;
1571 } else {
1572 if (whereIsObject(obj) == WIO_NOT_FOUND)
1573 return -1;
1574 int x, y;
1575 if (getObjectOrActorXY(obj, x, y) == -1)
1576 return -1;
1577 return y;
1578 }
1579 }
1580
getObjOldDir(int obj)1581 int ScummEngine::getObjOldDir(int obj) {
1582 return newDirToOldDir(getObjNewDir(obj));
1583 }
1584
getObjNewDir(int obj)1585 int ScummEngine::getObjNewDir(int obj) {
1586 int dir;
1587 if (objIsActor(obj)) {
1588 dir = derefActor(objToActor(obj), "getObjNewDir")->getFacing();
1589 } else {
1590 int x, y;
1591 getObjectXYPos(obj, x, y, dir);
1592 }
1593 return dir;
1594 }
1595
setObjectState(int obj,int state,int x,int y)1596 void ScummEngine::setObjectState(int obj, int state, int x, int y) {
1597 int i;
1598
1599 i = getObjectIndex(obj);
1600 if (i == -1) {
1601 debug(0, "setObjectState: no such object %d", obj);
1602 return;
1603 }
1604
1605 if (x != -1 && x != 0x7FFFFFFF) {
1606 _objs[i].x_pos = x * 8;
1607 _objs[i].y_pos = y * 8;
1608 }
1609
1610 addObjectToDrawQue(i);
1611 if (_game.version >= 7) {
1612 int imagecount;
1613 if (state == 0xFF) {
1614 state = getState(obj);
1615 imagecount = getObjectImageCount(obj);
1616
1617 if (state < imagecount)
1618 state++;
1619 else
1620 state = 1;
1621 }
1622
1623 if (state == 0xFE)
1624 state = _rnd.getRandomNumber(getObjectImageCount(obj));
1625 }
1626 putState(obj, state);
1627 }
1628
getDistanceBetween(bool is_obj_1,int b,int c,bool is_obj_2,int e,int f)1629 int ScummEngine_v6::getDistanceBetween(bool is_obj_1, int b, int c, bool is_obj_2, int e, int f) {
1630 int i, j;
1631 int x, y;
1632 int x2, y2;
1633
1634 j = i = 0xFF;
1635
1636 if (is_obj_1) {
1637 if (getObjectOrActorXY(b, x, y) == -1)
1638 return -1;
1639 if (b < _numActors)
1640 i = derefActor(b, "getDistanceBetween_is_obj_1")->_scalex;
1641 } else {
1642 x = b;
1643 y = c;
1644 }
1645
1646 if (is_obj_2) {
1647 if (getObjectOrActorXY(e, x2, y2) == -1)
1648 return -1;
1649 if (e < _numActors)
1650 j = derefActor(e, "getDistanceBetween_is_obj_2")->_scalex;
1651 } else {
1652 x2 = e;
1653 y2 = f;
1654 }
1655
1656 return getDist(x, y, x2, y2) * 0xFF / ((i + j) / 2);
1657 }
1658
nukeFlObjects(int min,int max)1659 void ScummEngine::nukeFlObjects(int min, int max) {
1660 ObjectData *od;
1661 int i;
1662
1663 debug(0, "nukeFlObjects(%d,%d)", min, max);
1664
1665 for (i = (_numLocalObjects-1), od = _objs; --i >= 0; od++)
1666 if (od->fl_object_index && od->obj_nr >= min && od->obj_nr <= max) {
1667 _res->nukeResource(rtFlObject, od->fl_object_index);
1668 od->obj_nr = 0;
1669 od->fl_object_index = 0;
1670 }
1671 }
1672
enqueueObject(int objectNumber,int objectX,int objectY,int objectWidth,int objectHeight,int scaleX,int scaleY,int image,int mode)1673 void ScummEngine_v6::enqueueObject(int objectNumber, int objectX, int objectY, int objectWidth,
1674 int objectHeight, int scaleX, int scaleY, int image, int mode) {
1675 BlastObject *eo;
1676
1677 if (_blastObjectQueuePos >= (int)ARRAYSIZE(_blastObjectQueue)) {
1678 error("enqueueObject: overflow");
1679 }
1680
1681 int idx = getObjectIndex(objectNumber);
1682 assert(idx >= 0);
1683
1684 eo = &_blastObjectQueue[_blastObjectQueuePos++];
1685 eo->number = objectNumber;
1686 eo->rect.left = objectX;
1687 eo->rect.top = objectY + _screenTop;
1688 if (objectWidth == 0) {
1689 eo->rect.right = eo->rect.left + _objs[idx].width;
1690 } else {
1691 eo->rect.right = eo->rect.left + objectWidth;
1692 }
1693 if (objectHeight == 0) {
1694 eo->rect.bottom = eo->rect.top + _objs[idx].height;
1695 } else {
1696 eo->rect.bottom = eo->rect.top + objectHeight;
1697 }
1698
1699 eo->scaleX = scaleX;
1700 eo->scaleY = scaleY;
1701 eo->image = image;
1702
1703 eo->mode = mode;
1704 }
1705
drawBlastObjects()1706 void ScummEngine_v6::drawBlastObjects() {
1707 BlastObject *eo;
1708 int i;
1709
1710 eo = _blastObjectQueue;
1711 for (i = 0; i < _blastObjectQueuePos; i++, eo++) {
1712 drawBlastObject(eo);
1713 }
1714 }
1715
drawBlastObject(BlastObject * eo)1716 void ScummEngine_v6::drawBlastObject(BlastObject *eo) {
1717 VirtScreen *vs;
1718 const byte *bomp, *ptr;
1719 int objnum;
1720 BompDrawData bdd;
1721
1722 vs = &_virtscr[kMainVirtScreen];
1723
1724 assertRange(30, eo->number, _numGlobalObjects - 1, "blast object");
1725
1726 objnum = getObjectIndex(eo->number);
1727 if (objnum == -1)
1728 error("drawBlastObject: getObjectIndex on BlastObject %d failed", eo->number);
1729
1730 ptr = getOBIMFromObjectData(_objs[objnum]);
1731 if (!ptr)
1732 error("BlastObject object %d image not found", eo->number);
1733
1734 const byte *img = getObjectImage(ptr, eo->image);
1735 if (_game.version == 8) {
1736 assert(img);
1737 bomp = img + 8;
1738 } else {
1739 if (!img)
1740 img = getObjectImage(ptr, 1); // Backward compatibility with samnmax blast objects
1741 assert(img);
1742 bomp = findResourceData(MKTAG('B','O','M','P'), img);
1743 }
1744
1745 if (!bomp)
1746 error("object %d is not a blast object", eo->number);
1747
1748 bdd.dst = *vs;
1749 bdd.dst.setPixels(vs->getPixels(0, 0));
1750 bdd.x = eo->rect.left;
1751 bdd.y = eo->rect.top;
1752
1753 // Skip the bomp header
1754 if (_game.version == 8) {
1755 bdd.src = bomp + 8;
1756 } else {
1757 bdd.src = bomp + 10;
1758 }
1759 if (_game.version == 8) {
1760 bdd.srcwidth = READ_LE_UINT32(bomp);
1761 bdd.srcheight = READ_LE_UINT32(bomp+4);
1762 } else {
1763 bdd.srcwidth = READ_LE_UINT16(bomp+2);
1764 bdd.srcheight = READ_LE_UINT16(bomp+4);
1765 }
1766
1767 bdd.scale_x = (byte)eo->scaleX;
1768 bdd.scale_y = (byte)eo->scaleY;
1769
1770 bdd.maskPtr = NULL;
1771 bdd.numStrips = _gdi->_numStrips;
1772
1773 if ((bdd.scale_x != 255) || (bdd.scale_y != 255)) {
1774 bdd.shadowMode = 0;
1775 } else {
1776 bdd.shadowMode = eo->mode;
1777 }
1778 bdd.shadowPalette = _shadowPalette;
1779
1780 bdd.actorPalette = 0;
1781 bdd.mirror = false;
1782
1783 drawBomp(bdd);
1784
1785 markRectAsDirty(vs->number, bdd.x, bdd.x + bdd.srcwidth, bdd.y, bdd.y + bdd.srcheight);
1786 }
1787
removeBlastObjects()1788 void ScummEngine_v6::removeBlastObjects() {
1789 BlastObject *eo;
1790 int i;
1791
1792 eo = _blastObjectQueue;
1793 for (i = 0; i < _blastObjectQueuePos; i++, eo++) {
1794 removeBlastObject(eo);
1795 }
1796 _blastObjectQueuePos = 0;
1797 }
1798
removeBlastObject(BlastObject * eo)1799 void ScummEngine_v6::removeBlastObject(BlastObject *eo) {
1800 VirtScreen *vs = &_virtscr[kMainVirtScreen];
1801
1802 Common::Rect r;
1803 int left_strip, right_strip;
1804 int i;
1805
1806 r = eo->rect;
1807
1808 r.clip(Common::Rect(vs->w, vs->h));
1809
1810 if (r.width() <= 0 || r.height() <= 0)
1811 return;
1812
1813 left_strip = r.left / 8;
1814 right_strip = (r.right + (vs->xstart % 8)) / 8;
1815
1816 if (left_strip < 0)
1817 left_strip = 0;
1818 if (right_strip > _gdi->_numStrips - 1)
1819 right_strip = _gdi->_numStrips - 1;
1820 for (i = left_strip; i <= right_strip; i++)
1821 _gdi->resetBackground(r.top, r.bottom, i);
1822
1823 markRectAsDirty(kMainVirtScreen, r, USAGE_BIT_RESTORED);
1824 }
1825
findLocalObjectSlot()1826 int ScummEngine::findLocalObjectSlot() {
1827 int i;
1828
1829 for (i = 1; i < _numLocalObjects; i++) {
1830 if (!_objs[i].obj_nr) {
1831 memset(&_objs[i], 0, sizeof(_objs[i]));
1832 return i;
1833 }
1834 }
1835
1836 return -1;
1837 }
1838
findFlObjectSlot()1839 int ScummEngine::findFlObjectSlot() {
1840 int i;
1841 for (i = 1; i < _numFlObject; i++) {
1842 if (_res->_types[rtFlObject][i]._address == NULL)
1843 return i;
1844 }
1845 error("findFlObjectSlot: Out of FLObject slots");
1846 return -1;
1847 }
1848
loadFlObject(uint object,uint room)1849 void ScummEngine_v70he::loadFlObject(uint object, uint room) {
1850 // Don't load an already stored object
1851 for (int i = 0; i < _numStoredFlObjects; i++) {
1852 if (_storedFlObjects[i].obj_nr == object)
1853 return;
1854 }
1855
1856 ScummEngine_v60he::loadFlObject(object, room);
1857 }
1858
loadFlObject(uint object,uint room)1859 void ScummEngine::loadFlObject(uint object, uint room) {
1860 FindObjectInRoom foir;
1861 int slot, objslot;
1862 ObjectData *od;
1863 byte *flob;
1864 uint32 obcd_size, obim_size, flob_size;
1865 bool isRoomLocked, isRoomScriptsLocked;
1866
1867 // Don't load an already loaded object
1868 if (getObjectIndex(object) != -1)
1869 return;
1870
1871 // Locate the object in the room resource
1872 findObjectInRoom(&foir, foImageHeader | foCodeHeader, object, room);
1873
1874 // Add an entry for the new floating object in the local object table
1875 objslot = findLocalObjectSlot();
1876 if (objslot == -1)
1877 error("loadFlObject: Local Object Table overflow");
1878
1879 od = &_objs[objslot];
1880
1881 // Dump object script
1882 if (_dumpScripts) {
1883 char buf[32];
1884 const byte *ptr = foir.obcd;
1885 sprintf(buf, "roomobj-%u-", room);
1886 ptr = findResource(MKTAG('V','E','R','B'), ptr);
1887 dumpResource(buf, object, ptr);
1888 }
1889
1890 // Setup sizes
1891 obcd_size = READ_BE_UINT32(foir.obcd + 4);
1892 od->OBCDoffset = 8;
1893 od->OBIMoffset = obcd_size + 8;
1894 obim_size = READ_BE_UINT32(foir.obim + 4);
1895 flob_size = obcd_size + obim_size + 8;
1896
1897 // Lock room/roomScripts for the given room. They contains the OBCD/OBIM
1898 // data, and a call to createResource might expire them, hence we lock them.
1899 isRoomLocked = _res->isLocked(rtRoom, room);
1900 isRoomScriptsLocked = _res->isLocked(rtRoomScripts, room);
1901 if (!isRoomLocked)
1902 _res->lock(rtRoom, room);
1903 if (_game.version == 8 && !isRoomScriptsLocked)
1904 _res->lock(rtRoomScripts, room);
1905
1906 // Allocate slot & memory for floating object
1907 slot = findFlObjectSlot();
1908 flob = _res->createResource(rtFlObject, slot, flob_size);
1909 assert(flob);
1910
1911 // Copy object code + object image to floating object
1912 WRITE_UINT32(flob, MKTAG('F','L','O','B'));
1913 WRITE_BE_UINT32(flob + 4, flob_size);
1914 memcpy(flob + 8, foir.obcd, obcd_size);
1915 memcpy(flob + 8 + obcd_size, foir.obim, obim_size);
1916
1917 // Unlock room/roomScripts
1918 if (!isRoomLocked)
1919 _res->unlock(rtRoom, room);
1920 if (_game.version == 8 && !isRoomScriptsLocked)
1921 _res->unlock(rtRoomScripts, room);
1922
1923 // Setup local object flags
1924 resetRoomObject(od, flob, flob);
1925
1926 od->fl_object_index = slot;
1927 }
1928
1929 } // End of namespace Scumm
1930