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  * aint32 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  * Based on the original sources
23  *   Faery Tale II -- The Halls of the Dead
24  *   (c) 1993-1996 The Wyrmkeep Entertainment Co.
25  */
26 
27 #include "saga2/saga2.h"
28 #include "saga2/objects.h"
29 #include "saga2/tile.h"
30 #include "saga2/motion.h"
31 #include "saga2/contain.h"
32 #include "saga2/setup.h"
33 #include "saga2/script.h"
34 #include "saga2/target.h"
35 #include "saga2/uimetrcs.h"
36 #include "saga2/magic.h"
37 #include "saga2/intrface.h"
38 #include "saga2/sensor.h"
39 #include "saga2/timers.h"
40 #include "saga2/grabinfo.h"
41 #include "saga2/localize.h"
42 #include "saga2/spellbuk.h"
43 #include "saga2/tilevect.h"
44 #include "saga2/dispnode.h"
45 #include "saga2/saveload.h"
46 
47 #include "saga2/methods.r"                    // generated by SAGA
48 #include "saga2/pclass.r"
49 namespace Saga2 {
50 
51 APPFUNC(cmdControl);
52 
53 /* ===================================================================== *
54    Resource ID constants
55  * ===================================================================== */
56 
57 const uint32        nameListID  = MKTAG('N', 'A', 'M', 'E'),
58                     objListID   = MKTAG('O', 'B', 'J', 'E'),
59                     objProtoID  = MKTAG('P', 'R', 'O',  0),
60                     actorProtoID = MKTAG('P', 'R', 'O',  1);
61 
62 /* ===================================================================== *
63    Locals
64  * ===================================================================== */
65 
66 uint32              nameListCount;
67 
68 uint16              *tempActorCount = nullptr; // array of temporary actor counts
69 
70 int16               objectProtoCount,       // object prototype count
71                     actorProtoCount;        // actor prototype count
72 
73 GameObject          *objectList = nullptr;     // list of all objects
74 const int16         objectCount = 4971;        // count of objects
75 
76 GameWorld           *worldList = nullptr;      // list of all worlds
77 int16               worldCount;             // number of worlds
78 
79 int32               objectListSize,
80                     actorListSize,
81                     worldListSize;
82 
83 GameWorld           *currentWorld;          // pointer to the current world
84 
85 ObjectID            viewCenterObject;       // ID of object that view tracks
86 
87 hResContext         *listRes;               // object list resource handle
88 extern hResContext  *tileRes;
89 
90 uint8               *ProtoObj::nextAvailObj;
91 
92 
93 // trio ready container consts
94 const ContainerInfo trioReadyContInfo[kNumViews] = { { 476, 105 + 0, 1, 3 },
95 	{ 476, 105 + 150, 1, 3 },
96 	{ 476, 105 + 300, 1, 3 }
97 };
98 // indiv ready container consts
99 const ContainerInfo indivReadyContInfoTop = { 476, 105 + 0, 1, 3 };
100 const ContainerInfo indivReadyContInfoBot = { 476, 105 + 57, 2, 3 };
101 
102 // view controls
103 ReadyContainerView      *TrioCviews[kNumViews] = { nullptr, nullptr, nullptr };
104 ReadyContainerView      *indivCviewTop = nullptr,
105                          *indivCviewBot = nullptr;
106 ContainerNode           *indivReadyNode;
107 
108 // array of image pointers for ready container backgrounds
109 void                    **backImages;
110 
111 // number of images resources to load for the back images
112 int8                    numReadyContRes = 4;
113 
114 int16               objectLimboCount,       // the number of objects in object limbo
115                     actorLimboCount,        // the number of actors in actor limbo
116                     importantLimboCount;    // the number of objects in important limbo
117 
118 //  Indicates wether object states should be paused
119 bool                objectStatesPaused;
120 
121 ObjectSoundFXs      *objectSoundFXTable;    // the global object sound effects table
122 
123 #if DEBUG
124 bool                massAndBulkCount;
125 #endif
126 
127 /* ===================================================================== *
128    Imports
129  * ===================================================================== */
130 
131 extern BackWindow   *mainWindow;
132 extern StaticPoint16 fineScroll;             // current scroll pos
133 extern hResContext  *imageRes;              // image resource handle
134 extern SpellStuff   *spellBook;
135 extern ObjectID     pickedObject;
136 
137 const uint32    imageGroupID = MKTAG('I', 'M', 'A', 'G');
138 
139 bool unstickObject(GameObject *obj);
140 
141 /* ===================================================================== *
142    Functions
143  * ===================================================================== */
144 
145 void **LoadImageRes(hResContext *con, int16 resID, int16 numRes, char a, char b, char c);
146 void UnloadImageRes(void **images, int16 numRes);
147 void drown(GameObject *obj);
148 
149 /* ===================================================================== *
150    class Location member functions
151  * ===================================================================== */
152 
153 /*
154 void Location::screenPos( Point16 &screenCoords );
155 int16 Location::screenDepth( void );
156 bool Location::visible( void );
157 */
158 
159 /* ======================================================================= *
160    Member functions for class GameObject
161  * ======================================================================= */
162 
163 struct GameObjectArchive {
164 	int16           protoIndex;
165 	TilePoint       location;
166 	uint16          nameIndex;
167 	ObjectID        parentID,
168 	                siblingID,
169 	                childID;
170 	uint16          script;
171 	uint16          objectFlags;
172 	uint8           hitPoints,
173 	                bParam;
174 	uint16          misc;
175 	uint8           missileFacing;
176 	ActiveItemID    currentTAG;
177 	uint8           sightCtr;
178 };
179 
180 //-----------------------------------------------------------------------
181 //	Default constructor
182 
GameObject(void)183 GameObject::GameObject(void) {
184 	prototype   = nullptr;
185 	_data.projectDummy = 0;
186 	_data.location    = Nowhere;
187 	_data.nameIndex   = 0;
188 	_data.parentID    = Nothing;
189 	_data.siblingID   = Nothing;
190 	_data.childID     = Nothing;
191 	_data.script      = 0;
192 	_data.objectFlags = 0;
193 	_data.hitPoints   = 0;
194 	_data.bParam      = 0;
195 	_data.massCount   = 0;
196 	_data.missileFacing = missileRt;
197 	_data.currentTAG  = NoActiveItem;
198 	_data.sightCtr    = 0;
199 	memset(&_data.reserved, 0, sizeof(_data.reserved));
200 
201 	_data.obj = this;
202 	_index = 0;
203 
204 	_godmode = false;
205 }
206 
207 //-----------------------------------------------------------------------
208 //	Constructor -- initial object construction
209 
GameObject(const ResourceGameObject & res)210 GameObject::GameObject(const ResourceGameObject &res) {
211 	prototype           = g_vm->_objectProtos[res.protoIndex];
212 	_data.projectDummy = 0;
213 	_data.location            = res.location;
214 	_data.nameIndex           = res.nameIndex;
215 	_data.parentID            = res.parentID;
216 	_data.siblingID           = Nothing;
217 	_data.childID             = Nothing;
218 	_data.script              = res.script;
219 	_data.objectFlags         = res.objectFlags;
220 	_data.hitPoints           = res.hitPoints;
221 	_data.bParam              = prototype->getChargeType() ? prototype->maxCharges : 0;
222 	_data.massCount           = res.misc; //prototype->getInitialItemCount();
223 	_data.missileFacing       = missileRt;
224 	_data.currentTAG          = NoActiveItem;
225 	_data.sightCtr            = 0;
226 	memset(&_data.reserved, 0, sizeof(_data.reserved));
227 
228 	_data.obj = this;
229 	_index = 0;
230 
231 	_godmode = false;
232 }
233 
GameObject(Common::InSaveFile * in)234 GameObject::GameObject(Common::InSaveFile *in) {
235 	read(in, false);
236 	_index = 0;
237 	_godmode = false;
238 }
239 
read(Common::InSaveFile * in,bool expandProto)240 void GameObject::read(Common::InSaveFile *in, bool expandProto) {
241 	int16 pInd = in->readSint16LE();
242 	if (expandProto)
243 		in->readSint16LE();
244 	//  Convert the protoype index into an object proto pointer
245 	prototype = pInd != -1
246 	            ?   g_vm->_objectProtos[pInd]
247 	            :   nullptr;
248 
249 	_data.projectDummy = 0;
250 	_data.location.load(in);
251 	_data.nameIndex = in->readUint16LE();
252 	_data.parentID = in->readUint16LE();
253 	_data.siblingID = in->readUint16LE();
254 	_data.childID = in->readUint16LE();
255 	_data.script = in->readUint16LE();
256 	_data.objectFlags = in->readUint16LE();
257 	_data.hitPoints = in->readByte();
258 	_data.bParam = in->readByte();
259 	_data.massCount = in->readUint16LE();
260 	_data.missileFacing = in->readByte();
261 	_data.currentTAG.val = in->readSint16LE();
262 	_data.sightCtr = in->readByte();
263 	memset(&_data.reserved, 0, sizeof(_data.reserved));
264 
265 	_data.obj = this;
266 
267 	debugC(4, kDebugSaveload, "... protoIndex = %d", pInd);
268 	debugC(4, kDebugSaveload, "... _data.location = (%d, %d, %d)",
269 	       _data.location.u, _data.location.v, _data.location.z);
270 	debugC(4, kDebugSaveload, "... _data.nameIndex = %d", _data.nameIndex);
271 	debugC(4, kDebugSaveload, "... _data.parentID = %d", _data.parentID);
272 	debugC(4, kDebugSaveload, "... _data.siblingID = %d", _data.siblingID);
273 	debugC(4, kDebugSaveload, "... _data.childID = %d", _data.childID);
274 	debugC(4, kDebugSaveload, "... _data.script = %d", _data.script);
275 	debugC(4, kDebugSaveload, "... _data.objectFlags = %d", _data.objectFlags);
276 	debugC(4, kDebugSaveload, "... _data.hitPoints = %d", _data.hitPoints);
277 	debugC(4, kDebugSaveload, "... _data.bParam = %d", _data.bParam);
278 	debugC(4, kDebugSaveload, "... _data.massCount = %d", _data.massCount);
279 	debugC(4, kDebugSaveload, "... _data.missileFacing = %d", _data.missileFacing);
280 	debugC(4, kDebugSaveload, "... _data.currentTAG.val = %d", _data.currentTAG.val);
281 	debugC(4, kDebugSaveload, "... _data.sightCtr = %d", _data.sightCtr);
282 }
283 
284 //-----------------------------------------------------------------------
285 //	Return the number of bytes need to archive this object in an archive
286 //	buffer.
287 
archiveSize(void)288 int32 GameObject::archiveSize(void) {
289 	return sizeof(GameObjectArchive);
290 }
291 
write(Common::MemoryWriteStreamDynamic * out,bool expandProto)292 void GameObject::write(Common::MemoryWriteStreamDynamic *out, bool expandProto) {
293 	debugC(2, kDebugSaveload, "Saving object %d", thisID());
294 
295 	int16 pInd = prototype != nullptr ? getProtoNum() : -1;
296 	out->writeSint16LE(pInd);
297 	if (expandProto)
298 		out->writeSint16LE(0);
299 	_data.location.write(out);
300 	out->writeUint16LE(_data.nameIndex);
301 	out->writeUint16LE(_data.parentID);
302 	out->writeUint16LE(_data.siblingID);
303 	out->writeUint16LE(_data.childID);
304 	out->writeUint16LE(_data.script);
305 	out->writeUint16LE(_data.objectFlags);
306 	out->writeByte(_data.hitPoints);
307 	out->writeByte(_data.bParam);
308 	out->writeUint16LE(_data.massCount);
309 	out->writeByte(_data.missileFacing);
310 	out->writeSint16LE(_data.currentTAG);
311 	out->writeByte(_data.sightCtr);
312 
313 	debugC(4, kDebugSaveload, "... protoIndex = %d", pInd);
314 	debugC(4, kDebugSaveload, "... _data.location = (%d, %d, %d)",
315 	       _data.location.u, _data.location.v, _data.location.z);
316 	debugC(4, kDebugSaveload, "... _data.nameIndex = %d", _data.nameIndex);
317 	debugC(4, kDebugSaveload, "... _data.parentID = %d", _data.parentID);
318 	debugC(4, kDebugSaveload, "... _data.siblingID = %d", _data.siblingID);
319 	debugC(4, kDebugSaveload, "... _data.childID = %d", _data.childID);
320 	debugC(4, kDebugSaveload, "... _data.script = %d", _data.script);
321 	debugC(4, kDebugSaveload, "... _data.objectFlags = %d", _data.objectFlags);
322 	debugC(4, kDebugSaveload, "... _data.hitPoints = %d", _data.hitPoints);
323 	debugC(4, kDebugSaveload, "... _data.bParam = %d", _data.bParam);
324 	debugC(4, kDebugSaveload, "... _data.massCount = %d", _data.massCount);
325 	debugC(4, kDebugSaveload, "... _data.missileFacing = %d", _data.missileFacing);
326 	debugC(4, kDebugSaveload, "... _data.currentTAG.val = %d", _data.currentTAG.val);
327 	debugC(4, kDebugSaveload, "... _data.sightCtr = %d", _data.sightCtr);
328 }
329 
330 //  Same as above but use object addresses instead of ID's
331 
isObject(GameObject * obj)332 bool isObject(GameObject *obj) {
333 	if (obj == nullptr)
334 		return false;
335 
336 	if (obj->_index >= objectCount)
337 		return false;
338 
339 	return (&objectList[obj->_index] == obj);
340 }
341 
isActor(GameObject * obj)342 bool isActor(GameObject *obj) {
343 	if (obj == nullptr)
344 		return false;
345 
346 	if (obj->_index >= kActorCount + ActorBaseID || obj->_index < ActorBaseID)
347 		return false;
348 
349 	return (g_vm->_act->_actorList[obj->_index - ActorBaseID] == obj);
350 }
351 
isWorld(GameObject * obj)352 bool isWorld(GameObject *obj) {
353 	if (obj == nullptr)
354 		return false;
355 
356 	if (obj->_index >= (uint)worldCount + WorldBaseID || obj->_index < WorldBaseID)
357 		return false;
358 
359 	return (&worldList[obj->_index - WorldBaseID] == obj);
360 }
361 
362 //  returns the address of the object based on the ID, and this
363 //  includes accounting for actors and worlds.
364 
objectAddress(ObjectID id)365 GameObject *GameObject::objectAddress(ObjectID id) {
366 	if (isObject(id)) {
367 		if (id >= objectCount)
368 			error("Invalid object ID: %d", id);
369 
370 		return objectList != nullptr ? &objectList[id] : nullptr;
371 	}
372 
373 	if (isWorld(id)) {
374 		if (id - WorldBaseID >= worldCount)
375 			error("Invalid object ID: %d", id);
376 
377 		return worldList != nullptr ? &worldList[id - WorldBaseID] : nullptr;
378 	}
379 
380 	if (id - ActorBaseID >= kActorCount)
381 		error("Invalid object ID: %d!", id);
382 
383 	return (int)g_vm->_act->_actorList.size() > id - ActorBaseID ? g_vm->_act->_actorList[id - ActorBaseID] : nullptr;
384 }
385 
protoAddress(ObjectID id)386 ProtoObj *GameObject::protoAddress(ObjectID id) {
387 	GameObject      *obj = objectAddress(id);
388 
389 	return obj ? obj->prototype : nullptr ;
390 }
391 
nameIndexToID(uint16 ind)392 int32 GameObject::nameIndexToID(uint16 ind) {
393 	for (int i = 0; i < objectCount; ++i) {
394 		if (objectList[i]._data.nameIndex == ind)
395 			return objectList[i].thisID();
396 
397 		if (objectList[i].prototype && objectList[i].prototype->nameIndex == ind)
398 			return objectList[i].thisID();
399 	}
400 
401 	for (int i = 0; i < kActorCount; ++i) {
402 		if (g_vm->_act->_actorList[i]->_data.nameIndex == ind)
403 			return g_vm->_act->_actorList[i]->thisID();
404 
405 		if (g_vm->_act->_actorList[i]->prototype && g_vm->_act->_actorList[i]->prototype->nameIndex == ind)
406 			return g_vm->_act->_actorList[i]->thisID();
407 	}
408 
409 	for (int i = 0; i < worldCount; ++i) {
410 		if (worldList[i]._data.nameIndex == ind)
411 			return worldList[i].thisID();
412 
413 		if (worldList[i].prototype && worldList[i].prototype->nameIndex == ind)
414 			return worldList[i].thisID();
415 	}
416 
417 	return -1;
418 }
419 
nameToID(Common::String name)420 Common::Array<ObjectID> GameObject::nameToID(Common::String name) {
421 	Common::Array<ObjectID> array;
422 	name.toLowercase();
423 
424 	for (int i = 0; i < objectCount; ++i) {
425 		Common::String objName = objectList[i].objName();
426 		objName.toLowercase();
427 		if (objName.contains(name))
428 			array.push_back(objectList[i].thisID());
429 	}
430 
431 	for (int i = 0; i < kActorCount; ++i) {
432 		Common::String objName = g_vm->_act->_actorList[i]->objName();
433 		objName.toLowercase();
434 		if (objName.contains(name))
435 			array.push_back(g_vm->_act->_actorList[i]->thisID());
436 	}
437 
438 	for (int i = 0; i < worldCount; ++i) {
439 		Common::String objName = worldList[i].objName();
440 		objName.toLowercase();
441 		if (objName.contains(name))
442 			array.push_back(worldList[i].thisID());
443 	}
444 
445 	return array;
446 }
447 
448 
containmentSet(void)449 uint16 GameObject::containmentSet(void) {
450 	return  prototype->containmentSet();
451 }
452 
453 //  Calculates the ID of an object, given it's (implicit) address
454 
thisID(void)455 ObjectID GameObject::thisID(void) {         // calculate our own id
456 	return _index;
457 }
458 
459 //  Since Worlds have more than one object chain, we need a function
460 //  to calculate which object chain to use, based on a _data.location.
461 //  This function returns the address of the appropriate "_data.childID"
462 //  pointer (i.e. the ObjectID of the first object in the chain
463 //  of child objects) for any type of object.
464 
getHeadPtr(ObjectID parentID,TilePoint & l)465 ObjectID *GameObject::getHeadPtr(ObjectID parentID, TilePoint &l) {
466 	GameObject      *parentObj = objectAddress(parentID);
467 
468 	if (isWorld(parentID)) {
469 		GameWorld   *world = (GameWorld *)parentObj;
470 		TilePoint   sectors = world->sectorSize();
471 
472 		int16       u = clamp(0, l.u / kSectorSize, sectors.u - 1),
473 		            v = clamp(0, l.v / kSectorSize, sectors.v - 1);
474 
475 		return  &(world->sectorArray)[
476 		     v * world->sectorArraySize + u].childID;
477 	} else return &parentObj->_data.childID;
478 }
479 
480 //  Removes an object from it's chain.
481 
remove(void)482 void GameObject::remove(void) {             // removes from old list
483 	ObjectID        id = thisID(),
484 	                *headPtr;
485 
486 	//  If object has not parent, then it's not on a list
487 	if (_data.parentID == Nothing) return;
488 	if (id <= ImportantLimbo) return;
489 
490 	//  Get the head of the object chain. Worlds have more than
491 	//  one, so we need to get the right one.
492 	headPtr = getHeadPtr(_data.parentID, _data.location);
493 
494 	//  Search the chain until we find ourself.
495 	while (*headPtr != id) {
496 		GameObject  *obj;
497 
498 		if (*headPtr == Nothing)
499 			error("Inconsistant Object Chain! ('%s#%d' not on parent %s#%d chain)",
500 			             objName(), id, objectAddress(_data.parentID)->objName(), _data.parentID);
501 
502 		obj = objectAddress(*headPtr);
503 		headPtr = &obj->_data.siblingID;
504 	}
505 
506 	//  Remove us from the chain
507 	*headPtr = _data.siblingID;
508 	_data.parentID = Nothing;
509 }
510 
511 //  Add an object to a new chain.
512 
append(ObjectID newParent)513 void GameObject::append(ObjectID newParent) {
514 	ObjectID        *headPtr;
515 
516 	//  If object has not parent, then it's not on a list
517 	if (newParent == Nothing) return;
518 
519 	//  Get the head of the object chain. Worlds have more than
520 	//  one, so we need to get the right one.
521 	headPtr = getHeadPtr(newParent, _data.location);
522 
523 	//  Link us in to the parent's chain
524 
525 	_data.parentID = newParent;
526 	_data.siblingID = *headPtr;
527 	*headPtr = thisID();
528 
529 }
530 
531 //  Inserts an object in a chain immediately after another one
532 //  This is the fastest method for inserting an object into a
533 //  chain since the parent's address does not need to be
534 //  computed.
535 
insert(ObjectID newPrev)536 void GameObject::insert(ObjectID newPrev) {
537 	GameObject      *obj = objectAddress(newPrev);
538 
539 	//  If object has not parent, then it's not on a list
540 	if (newPrev == Nothing) return;
541 
542 	//  Link us in to the parent's chain
543 	_data.siblingID = obj->_data.siblingID;
544 	obj->_data.siblingID = thisID();
545 	_data.parentID = obj->_data.parentID;
546 }
547 
548 //  Returns the identity of the actor possessing the object
549 
possessor(void)550 ObjectID GameObject::possessor(void) {
551 	GameObject      *obj;
552 	ObjectID        id = _data.parentID;
553 
554 	while (id != Nothing && isObject(id)) {
555 		obj = objectAddress(id);
556 		id = obj->_data.parentID;
557 	}
558 
559 	return isActor(id) ? id : Nothing ;
560 }
561 
562 //  A different version of getWorldLocation that fills in a _data.location
563 //  structure.
564 
getWorldLocation(Location & loc)565 bool GameObject::getWorldLocation(Location &loc) {
566 	GameObject      *obj = this;
567 	ObjectID        id;
568 	uint8           objHeight = prototype->height;
569 
570 	for (;;) {
571 		id = obj->_data.parentID;
572 		if (isWorld(id)) {
573 			loc = obj->_data.location;
574 			loc.z += (obj->prototype->height - objHeight) / 2;
575 			loc.context = id;
576 			return true;
577 		} else if (id == Nothing) {
578 			loc = Nowhere;
579 			loc.context = Nothing;
580 			return false;
581 		}
582 
583 		obj = objectAddress(id);
584 	}
585 }
586 
notGetLocation(void)587 Location GameObject::notGetLocation(void) {
588 	return Location(getLocation(), IDParent());
589 }
590 
notGetWorldLocation(void)591 Location GameObject::notGetWorldLocation(void) {
592 	GameObject      *obj = this;
593 	ObjectID        id;
594 	uint8           objHeight = prototype->height;
595 
596 	for (;;) {
597 		id = obj->_data.parentID;
598 		if (isWorld(id)) {
599 			TilePoint       loc = obj->_data.location;
600 
601 			loc.z += (obj->prototype->height - objHeight) / 2;
602 			return Location(loc, obj->_data.parentID);
603 		} else if (id == Nothing) return Location(Nowhere, Nothing);
604 
605 		obj = objectAddress(id);
606 	}
607 }
608 
609 
objCursorText(char nameBuf[],const int8 size,int16 count)610 void GameObject::objCursorText(char nameBuf[], const int8 size, int16 count) {
611 	const int addTextSize   = 10;
612 
613 	// put the object name into the buffer as a default value
614 	Common::strlcpy(nameBuf, objName(), size);
615 
616 	assert(strlen(objName()) < (uint)(size - addTextSize));
617 
618 	// check to see if this item is a physical object
619 	// if so, then give the count of the item ( if stacked )
620 	if (prototype->containmentSet() & ProtoObj::isTangible) {
621 		// display charges if item is a chargeable item
622 		if (prototype->chargeType != 0
623 		        &&  prototype->maxCharges != Permanent
624 		        &&  _data.bParam != Permanent) {
625 			uint16 charges = _data.bParam;
626 
627 			if (charges == 1) {
628 				sprintf(nameBuf, SINGLE_CHARGE, objName(), charges);
629 			} else {
630 				sprintf(nameBuf, MULTI_CHARGE, objName(), charges);      // get the count
631 			}
632 		}
633 
634 		if (prototype->flags & ResourceObjectPrototype::objPropMergeable) {
635 			// make a buffer that contains the name of
636 			// the object and it's count
637 			// add only if a mergable item
638 			if (_data.massCount != 1) {
639 				if (count != -1) {
640 					if (count != 1) {
641 						sprintf(nameBuf, PLURAL_DESC, count, objName());     // get the count
642 					}
643 				} else {
644 					sprintf(nameBuf, PLURAL_DESC, _data.massCount, objName());     // get the count
645 				}
646 			}
647 		}
648 	} else {
649 		int16 manaColor = -1;
650 		int16 manaCost = 0;
651 
652 		// figure out if it's a skill or spell
653 		if (prototype->containmentSet() & (ProtoObj::isSkill | ProtoObj::isSpell)) {
654 			// get skill proto for this spell or skill
655 			SkillProto *sProto = skillProtoFromID(thisID());
656 
657 			// determine if this is a skill icon
658 			manaColor = spellBook[sProto->getSpellID()].getManaType();
659 			manaCost  = spellBook[sProto->getSpellID()].getManaAmt();
660 		}
661 
662 		if (manaColor == sManaIDSkill) {     //  It's a skill
663 			// get the level of the skill for the brother in question
664 			uint16  brotherID = getCenterActor()->thisID();
665 			uint16  level;
666 
667 			// get skill prototype for this spell or skill
668 			SkillProto *sProto = skillProtoFromID(thisID());
669 
670 			// check to make sure this is a brother
671 			if (brotherID == ActorBaseID + FTA_JULIAN  ||
672 			        brotherID == ActorBaseID + FTA_PHILIP  ||
673 			        brotherID == ActorBaseID + FTA_KEVIN) {
674 				// get base 0 level
675 				level = g_vm->_playerList[brotherID - ActorBaseID]->getSkillLevel(sProto);
676 
677 				// normalize and output
678 				sprintf(nameBuf, "%s-%d", objName(), ++level);
679 			}
680 		} else if (manaColor >= sManaIDRed
681 		           &&  manaColor <= sManaIDViolet  //  A spell
682 		           &&  manaCost > 0) {
683 			ObjectID        aID = possessor();      //  Who owns the spell
684 			PlayerActorID   pID;
685 
686 			if (actorIDToPlayerID(aID, pID)) {
687 				PlayerActor *player = getPlayerActorAddress(pID);
688 				assert(player);
689 
690 				int16           manaAmount;
691 
692 				manaAmount      = player->getEffStats()->mana(manaColor);
693 
694 				sprintf(nameBuf, "%s [x%d]", objName(), manaAmount / manaCost);
695 			}
696 		}
697 	}
698 }
699 
isTrueSkill(void)700 bool GameObject::isTrueSkill(void) {
701 	// figure out if it's a skill or spell
702 	if (prototype->containmentSet() & (ProtoObj::isSkill | ProtoObj::isSpell)) {
703 		// get skill proto for this spell or skill
704 		SkillProto *sProto = skillProtoFromID(thisID());
705 
706 		// determine if this is a skill icon
707 		if (spellBook[sProto->getSpellID()].getManaType() == sManaIDSkill) {
708 			return true;
709 		}
710 	}
711 
712 	return false;
713 }
714 
715 //  Returns the _data.location of an object within the world
getWorldLocation(void)716 TilePoint GameObject::getWorldLocation(void) {
717 	GameObject      *obj = this;
718 	ObjectID        id;
719 	uint8           objHeight = prototype->height;
720 
721 	for (;;) {
722 		id = obj->_data.parentID;
723 		if (isWorld(id)) {
724 			TilePoint       loc = obj->_data.location;
725 
726 			loc.z += (obj->prototype->height - objHeight) / 2;
727 			return loc;
728 		} else if (id == Nothing) return Nowhere;
729 
730 		obj = objectAddress(id);
731 	}
732 }
733 
734 //  Return a pointer to the world on which this object resides
world(void)735 GameWorld *GameObject::world(void) {
736 	if (isWorld(this)) return (GameWorld *)this;
737 
738 	GameObject      *obj = this;
739 	ObjectID        id;
740 
741 	for (;;) {
742 		id = obj->_data.parentID;
743 		if (isWorld(id)) return &worldList[id - WorldBaseID];
744 		else if (id == Nothing) return nullptr;
745 
746 		obj = objectAddress(id);
747 	}
748 }
749 
750 
getSprOffset(int16 num)751 int32 GameObject::getSprOffset(int16 num) {
752 	// sprite offset delimiters for mergeable objects
753 	enum spriteDelimiters {
754 		spriteNumFew    = 2,
755 		spriteNumSome   = 10,
756 		spriteNumMany   = 25
757 	};
758 
759 	// default return offset is zero ( no change )
760 	int32 value = 0;
761 	int32 units;
762 
763 	if (num != -1) {
764 		units = (int32)num;
765 	} else {
766 		units = (int32)_data.massCount;
767 	}
768 
769 	// if this is a mergeable object
770 	if (prototype->flags & ResourceObjectPrototype::objPropMergeable) {
771 		if (units >= spriteNumFew) {
772 			value = 1;
773 		}
774 
775 		if (units >= spriteNumSome) {
776 			value = 2;
777 		}
778 
779 		if (units >= spriteNumMany) {
780 			value = 3;
781 		}
782 	}
783 
784 	return value;
785 }
786 
787 //  Remove an object from a stack of objects
unstack(void)788 bool GameObject::unstack(void) {
789 	GameObject  *item = nullptr,
790 	            *base = nullptr,
791 	             *zero = nullptr;
792 	int16       count = 0;
793 
794 	//  If this is a world, or it's parent object is a world, or it is
795 	//  an intangible object, then it cannot be stacked so ignore. If it's
796 	//  Z-coord == 1 then it is not stacked now.
797 	if (isWorld(this)
798 	        ||  isWorld(parent())
799 	        ||  IDParent() == Nothing
800 	        ||  _data.location.z == 1
801 	        ||  prototype == nullptr
802 	        || (prototype->containmentSet() & ProtoObj::isIntangible)) return false;
803 
804 	ContainerIterator   iter(parent());
805 
806 	//  Iterate through all the objects in the container.
807 	//  Count how many objects are in this stack
808 	//  Also, check to find the base item, and any non-base item
809 	while (iter.next(&item) != Nothing) {
810 		if (item->_data.location.u == _data.location.u
811 		        &&  item->_data.location.v == _data.location.v
812 		        &&  item->prototype  == prototype) {
813 			count++;
814 			if (item->_data.location.z != 0) base = item;
815 			else zero = item;
816 		}
817 	}
818 
819 	//  If this object is the base item, and there is another item which
820 	//  can become the base item, then make this other item the new base
821 	//  item by transferring all but one of the count to the new base.
822 	//
823 	//  Else if this item is not the base item, then decrement the base
824 	//  item's count by setting it to all but one of the count of items.
825 	if (this == base && zero != nullptr)
826 		zero->_data.location.z = count - 1;
827 	else if (base != nullptr) base->_data.location.z = count - 1;
828 
829 	//  Set this item's count to 1
830 	_data.location.z = 1;
831 	return true;
832 }
833 
834 //  Move the object to a new _data.location, and change context if needed.
setLocation(const Location & l)835 void GameObject::setLocation(const Location &l) {
836 	if (l.context != _data.parentID) {
837 		unstack();                          // if it's in a stack, unstack it.
838 		remove();                           // remove from old list
839 		_data.location = (TilePoint)l;            // change _data.location
840 		append(l.context);                  // append to new list
841 	} else if (isWorld(l.context)) {
842 		GameWorld   *world = (GameWorld *)objectAddress(l.context);
843 		TilePoint   sectors = world->sectorSize();
844 
845 		int16       u0 = clamp(0, _data.location.u / kSectorSize, sectors.u - 1),
846 		            v0 = clamp(0, _data.location.v / kSectorSize, sectors.v - 1),
847 		            u1 = clamp(0, l.u / kSectorSize, sectors.u - 1),
848 		            v1 = clamp(0, l.v / kSectorSize, sectors.v - 1);
849 
850 		if (u0 != u1 || v0 != v1) {         // If sector changed
851 			remove();                       //  Remove from old list
852 			_data.location = (TilePoint)l;        //  Set object coords
853 			append(l.context);              //  append to appropriate list
854 		} else {
855 			_data.location = (TilePoint)l;        //  Set object coords
856 		}
857 	} else {
858 		unstack();                          // if it's in a stack, unstack it.
859 		_data.location = (TilePoint)l;            //  Set object coords
860 	}
861 }
862 
863 //  Move the object without changing worlds...
setLocation(const TilePoint & tp)864 void GameObject::setLocation(const TilePoint &tp) {
865 	if (isWorld(_data.parentID)) {
866 		GameWorld   *world = (GameWorld *)objectAddress(_data.parentID);
867 		TilePoint   sectors = world->sectorSize();
868 
869 		int16       u0 = clamp(0, _data.location.u / kSectorSize, sectors.u - 1),
870 		            v0 = clamp(0, _data.location.v / kSectorSize, sectors.v - 1),
871 		            u1 = clamp(0, tp.u / kSectorSize, sectors.u - 1),
872 		            v1 = clamp(0, tp.v / kSectorSize, sectors.v - 1);
873 
874 		if (u0 != u1 || v0 != v1) {          // If sector changed
875 			ObjectID saveParent = _data.parentID;
876 
877 			remove();                       //  Remove from old list
878 			_data.location = tp;                  //  Set object coords
879 			_data.parentID = saveParent;          //  restore parent (cleared by remove())
880 			append(_data.parentID);               //  append to appropriate list
881 		} else {
882 			_data.location = tp;                  //  Set object coords
883 		}
884 	} else {
885 		_data.location = tp;                      //  Set object coords
886 	}
887 }
888 
move(const Location & location)889 void GameObject::move(const Location &location) {
890 	// move as usual
891 	ObjectID    oldParentID = _data.parentID;
892 
893 	setLocation(location);
894 
895 	updateImage(oldParentID);
896 }
897 
move(const Location & location,int16 num)898 void GameObject::move(const Location &location, int16 num) {
899 	// if this object is merged or stacked
900 	// with others, check for their
901 	// moves ( or copies ) first.
902 	if (!moveMerged(location, num)) {
903 		// do a normal move
904 		move(location);
905 	} else {
906 		ObjectID    oldParentID = _data.parentID;
907 
908 		// update panel after move
909 		updateImage(oldParentID);
910 
911 		// update the ready containers
912 		updateReadyContainers();
913 
914 		return;
915 	}
916 }
917 
918 
getChargeType(void)919 int16 GameObject::getChargeType(void) {
920 	assert(prototype);
921 
922 	return prototype->getChargeType();
923 }
924 
925 // this function recharges an object
recharge(void)926 void GameObject::recharge(void) {
927 	// if this object has a charge type
928 	// other then none, then reset
929 	// it's charges to maximum
930 	if (getChargeType()) {
931 		ProtoObj *po = GameObject::protoAddress(thisID());
932 		assert(po);
933 		_data.bParam = po->maxCharges;
934 	}
935 }
936 
937 // take a charge
deductCharge(ActorManaID manaID,uint16 manaCost)938 bool GameObject::deductCharge(ActorManaID manaID, uint16 manaCost) {
939 	ProtoObj *po = GameObject::protoAddress(thisID());
940 	assert(po);
941 
942 	// if this is not a chargeable item, then return false
943 	if (!getChargeType()) {
944 		return false;
945 	}
946 
947 	if (po->maxCharges == Permanent || _data.bParam == Permanent) {
948 		return true;
949 	}
950 
951 	if (po->maxCharges == 0) {
952 		GameObject *parentObj = parent();
953 
954 		if (isActor(parentObj)) {
955 			return ((Actor *)parentObj)->takeMana(manaID, manaCost);
956 		}
957 	}
958 
959 	if (_data.bParam == 0) {
960 		// not enough mana to use item
961 		return false;
962 	}
963 
964 	if (_data.bParam > 0 && _data.bParam < Permanent) {
965 		_data.bParam--;
966 	}
967 
968 	return true;
969 }
970 
hasCharge(ActorManaID manaID,uint16 manaCost)971 bool GameObject::hasCharge(ActorManaID manaID, uint16 manaCost) {
972 	ProtoObj *po = GameObject::protoAddress(thisID());
973 	assert(po);
974 
975 	// if this is not a chargeable item, then return false
976 	if (!getChargeType()) {
977 		return false;
978 	}
979 
980 	if (_data.bParam == Permanent) {
981 		return true;
982 	}
983 
984 	if (po->maxCharges == 0) {
985 		GameObject *parentObj = parent();
986 
987 		if (isActor(parentObj)) {
988 			return ((Actor *)parentObj)->hasMana(manaID, manaCost);
989 		}
990 	}
991 
992 	if (_data.bParam == 0) {
993 		// not enough mana to use item
994 		return false;
995 	}
996 	return true;
997 }
998 
999 
1000 
move(const TilePoint & tilePoint)1001 void GameObject::move(const TilePoint &tilePoint) {
1002 	// I'm making the assumption that only objects in containers
1003 	// can be merged or stacked
1004 	// move as usual
1005 	ObjectID    oldParentID = _data.parentID;
1006 
1007 	setLocation(tilePoint);
1008 
1009 	updateImage(oldParentID);
1010 }
1011 
updateImage(ObjectID oldParentID)1012 void GameObject::updateImage(ObjectID oldParentID) {
1013 	GameObject  *parent,
1014 	            *oldParent;
1015 
1016 	parent = objectAddress(_data.parentID);
1017 	oldParent = objectAddress(oldParentID);
1018 
1019 	if ((isActor(oldParentID)
1020 	        &&  isPlayerActor((Actor *)oldParent))
1021 	        || (isObject(oldParentID)
1022 	            &&  oldParent->isOpen())) {
1023 		g_vm->_cnm->setUpdate(oldParentID);
1024 	}
1025 
1026 	if (_data.parentID != oldParentID && isActor(oldParentID)) {
1027 		ObjectID        id = thisID();
1028 		Actor           *a = (Actor *)oldParent;
1029 		int             i;
1030 
1031 		if (a->_leftHandObject == id)
1032 			a->_leftHandObject = Nothing;
1033 		else if (a->_rightHandObject == id)
1034 			a->_rightHandObject = Nothing;
1035 
1036 		for (i = 0; i < ARMOR_COUNT; i++) {
1037 			if (a->_armorObjects[i] == id) {
1038 				a->wear(Nothing, i);
1039 				break;
1040 			}
1041 		}
1042 	}
1043 
1044 	if (isWorld(_data.parentID)) {
1045 		GameWorld   *w = world();
1046 		Sector *sect;
1047 
1048 		if (!isMoving()) {
1049 			if (objObscured(this)) {
1050 				_data.objectFlags |= objectObscured;
1051 			} else {
1052 				_data.objectFlags &= ~objectObscured;
1053 			}
1054 		}
1055 		int u = _data.location.u >> kSectorShift;
1056 		int v = _data.location.v >> kSectorShift;
1057 
1058 		sect = w->getSector(u, v);
1059 		if (sect) {
1060 			if (sect->isActivated())
1061 				activate();
1062 		}
1063 		else
1064 			warning("GameObject::updateImage: Invalid Sector (%d, %d))", u, v);
1065 	} else {
1066 		_data.objectFlags &= ~objectObscured;
1067 
1068 		if ((isActor(_data.parentID)
1069 		        &&  isPlayerActor((Actor *)parent))
1070 		        || (isObject(_data.parentID) && parent->isOpen())
1071 		   ) {
1072 			g_vm->_cnm->setUpdate(_data.parentID);
1073 		}
1074 	}
1075 }
1076 
moveMerged(const Location & loc,int16 num)1077 bool GameObject::moveMerged(const Location &loc, int16 num) {
1078 	if (num < _data.massCount
1079 	        &&  !extractMerged(Location(_data.location, _data.parentID), _data.massCount - num))
1080 		return false;
1081 	move(loc);
1082 	return true;
1083 }
1084 
1085 
1086 //  Extract a merged object with specified merge number from another
1087 //  merged object and return its ID
extractMerged(const Location & loc,int16 num)1088 ObjectID GameObject::extractMerged(const Location &loc, int16 num) {
1089 	ObjectID        extractedID;
1090 
1091 	// determine whether this object can be merged
1092 	// with duplicates of it's kind
1093 	if (prototype->flags & ResourceObjectPrototype::objPropMergeable) {
1094 		// get the number requested or all that's there...
1095 		int16 moveCount = MIN<uint16>(num, _data.massCount);
1096 
1097 		// make a new pile with that many items in it.
1098 		if ((extractedID = copy(loc, moveCount)) != Nothing) {
1099 			// and subtract that amount from the currect pile
1100 			_data.massCount -= moveCount;
1101 
1102 			// delete object if count goes to zero
1103 			if (_data.massCount == 0) {
1104 				this->deleteObject();
1105 			}
1106 		} else
1107 			return Nothing;
1108 	} else {
1109 		// not a mergable object return unsuccess
1110 		return Nothing;
1111 	}
1112 
1113 	return extractedID;
1114 }
1115 
extractMerged(int16 num)1116 GameObject *GameObject::extractMerged(int16 num) {
1117 	ObjectID        extractedID;
1118 
1119 	// determine whether this object can be merged
1120 	// with duplicates of it's kind
1121 	if (prototype->flags & ResourceObjectPrototype::objPropMergeable) {
1122 		Location    loc(0, 0, 0, 0);
1123 
1124 		// get the number requested or all that's there...
1125 		int16 moveCount = MIN<uint16>(num, _data.massCount);
1126 
1127 		// make a new pile with that many items in it.
1128 		if ((extractedID = copy(loc, moveCount)) != Nothing) {
1129 			// and subtract that amount from the currect pile
1130 			_data.massCount -= moveCount;
1131 
1132 			// delete object if count goes to zero
1133 			if (_data.massCount == 0) {
1134 				this->deleteObject();
1135 			}
1136 		} else
1137 			return nullptr;
1138 	} else {
1139 		// not a mergable object return unsuccess
1140 		return nullptr;
1141 	}
1142 
1143 	return GameObject::objectAddress(extractedID);
1144 }
1145 
1146 //  Move the object to a new random _data.location
moveRandom(const TilePoint & minLoc,const TilePoint & maxLoc)1147 void GameObject::moveRandom(const TilePoint &minLoc, const TilePoint &maxLoc) {
1148 	//We Should Also Send Flags For Conditional Movements
1149 	//One Consideration Is Whether We Should Get One Random Location
1150 	//And Poke Around That Location If We Cant Go There Or Try Another
1151 	//Random Location ???
1152 
1153 	TilePoint newLoc;
1154 	const int maxMoveAttempts = 1;//This Is Max Tries If Conditional Move
1155 
1156 	for (int i = 0; i < maxMoveAttempts; i++) {
1157 		newLoc.u = GetRandomBetween(minLoc.u, maxLoc.u);
1158 		newLoc.v = GetRandomBetween(minLoc.v, maxLoc.v);
1159 		newLoc.z = _data.location.z; //For Now Keep Z Coord Same
1160 		//If Flags == Collision Check
1161 		if (objectCollision(this, world(), newLoc) == nullptr) { //If No Collision
1162 			move(newLoc);//Move It Else Try Again
1163 			break;
1164 		}
1165 	}
1166 }
1167 // this will need another method to let it know about multiple
1168 // object moves.
1169 //  Copy the object to a new _data.location.
copy(const Location & l)1170 ObjectID GameObject::copy(const Location &l) {
1171 	GameObject      *newObj;
1172 //	ObjectID        id = thisID();
1173 
1174 	if (isWorld(this))
1175 		error("World copying not allowed.\n");
1176 
1177 	if (isActor(this)) {
1178 //      newObj = newActor();
1179 //      newObj->move( l );
1180 		// REM: Call actor copy function...
1181 
1182 		error("Actor copying not yet implemented.\n");
1183 	} else {
1184 		if ((newObj = newObject()) == nullptr) return Nothing;
1185 
1186 		newObj->prototype   = prototype;
1187 		newObj->_data.nameIndex   = _data.nameIndex;
1188 		newObj->_data.script      = _data.script;
1189 		newObj->_data.objectFlags = _data.objectFlags;
1190 		newObj->_data.hitPoints   = _data.hitPoints;
1191 		newObj->_data.massCount   = _data.massCount;
1192 		newObj->_data.bParam      = _data.bParam;
1193 		newObj->_data.missileFacing = _data.missileFacing;
1194 		newObj->_data.currentTAG  = _data.currentTAG;
1195 
1196 		newObj->move(l);     //>>> could this cause the same problem as below?
1197 	}
1198 
1199 	return newObj->thisID();
1200 }
1201 
copy(const Location & l,int16 num)1202 ObjectID GameObject::copy(const Location &l, int16 num) {
1203 	GameObject      *newObj;
1204 
1205 	if (isWorld(this))
1206 		error("World copying not allowed.");
1207 
1208 	if (isActor(this)) {
1209 		error("Actor copying not yet implemented.");
1210 	} else {
1211 		if ((newObj = newObject()) == nullptr) return Nothing;
1212 
1213 
1214 		newObj->prototype   = prototype;
1215 		newObj->_data.nameIndex   = _data.nameIndex;
1216 		newObj->_data.script      = _data.script;
1217 		newObj->_data.objectFlags = _data.objectFlags;
1218 		newObj->_data.hitPoints   = _data.hitPoints;
1219 		newObj->_data.massCount   = num;
1220 
1221 		// this did occur before any of the assignments
1222 		// but that caused a crash when the it tried to update
1223 		// the image during the move and tried to access the prototype
1224 		// pointer field, which is set to nullptr after a newObject()
1225 		newObj->move(l);
1226 	}
1227 
1228 	return newObj->thisID();
1229 }
1230 
1231 //  Create an alias of this object
makeAlias(const Location & l)1232 ObjectID GameObject::makeAlias(const Location &l) {
1233 	ObjectID        newObjID = copy(l);
1234 
1235 	if (newObjID != Nothing) {
1236 		GameObject  *newObject = objectAddress(newObjID);
1237 
1238 		newObject->_data.objectFlags |= objectAlias;
1239 	}
1240 
1241 	return newObjID;
1242 }
1243 
1244 //  Creates a new object (if one is available), and
1245 //  return it's address
newObject(void)1246 GameObject *GameObject::newObject(void) {   // get a newly created object
1247 	GameObject      *limbo = objectAddress(ObjectLimbo),
1248 	                *obj = nullptr;
1249 
1250 	if (limbo->_data.childID == Nothing) {
1251 		int16       i;
1252 
1253 		//  Search object list for the first scavengable object we can find
1254 		for (i = ImportantLimbo + 1; i < objectCount; i++) {
1255 			obj = &objectList[i];
1256 
1257 			if (obj->isScavengable()
1258 			        &&  !obj->isActivated()
1259 			        &&  isWorld(obj->IDParent()))
1260 				break;
1261 		}
1262 
1263 		//  REM: If things start getting really tight, we can
1264 		//  start recycling common objects...
1265 
1266 		if (i >= objectCount)
1267 			return nullptr;
1268 	} else {
1269 		objectLimboCount--;
1270 		obj = limbo->child();
1271 	}
1272 
1273 	obj->remove();
1274 	obj->prototype      = nullptr;
1275 	obj->_data.nameIndex      = 0;
1276 	obj->_data.script         = 0;
1277 	obj->_data.objectFlags    = 0;
1278 	obj->_data.hitPoints      = 0;
1279 	obj->_data.massCount      = 0;
1280 	obj->_data.bParam         = 0;
1281 	obj->_data.missileFacing  = 0;
1282 	obj->_data.currentTAG     = NoActiveItem;
1283 
1284 	return obj;
1285 }
1286 
1287 //  Deletes an object by adding it to either the actor limbo list
1288 //  or the object limbo list.
1289 
deleteObject(void)1290 void GameObject::deleteObject(void) {
1291 	ObjectID        dObj = thisID();
1292 	scriptCallFrame scf;
1293 	ContainerNode   *cn;
1294 
1295 	scf.invokedObject   = dObj;
1296 	scf.enactor         = dObj;
1297 	scf.directObject    = dObj;
1298 	scf.indirectObject  = Nothing;
1299 	scf.value           = 0;
1300 
1301 	runObjectMethod(dObj, Method_GameObject_onDeletion, scf);
1302 
1303 	//  Move objects to appropriate junkyards.
1304 	//  Actors --> actor limbo
1305 	//  Important objects --> important limbo
1306 	//  Normal Objects --> object limbo
1307 
1308 	//  Remove all timers and sensors
1309 	removeAllTimers();
1310 	removeAllSensors();
1311 
1312 	//  Delete any container nodes for this object
1313 	while ((cn = g_vm->_cnm->find(dObj)) != nullptr)
1314 		delete cn;
1315 
1316 	if (isActor(_data.parentID)) {
1317 		ObjectID    id = thisID();
1318 		Actor       *a = (Actor *)objectAddress(_data.parentID);
1319 		int         i;
1320 
1321 		if (a->_leftHandObject == id) a->_leftHandObject = Nothing;
1322 		if (a->_rightHandObject == id) a->_rightHandObject = Nothing;
1323 
1324 		for (i = 0; i < ARRAYSIZE(a->_armorObjects); i++)
1325 			if (a->_armorObjects[i] == id)
1326 				a->wear(Nothing, i);
1327 	}
1328 
1329 	unstack();
1330 
1331 	if (g_vm->_mouseInfo->getObject() == this)
1332 		g_vm->_mouseInfo->replaceObject();
1333 	if (pickedObject == thisID())
1334 		pickedObject = Nothing;
1335 
1336 	remove();
1337 
1338 	if (isActor(this))
1339 		((Actor *)this)->deleteActor();
1340 	else if (_data.objectFlags & objectImportant) {
1341 		append(ImportantLimbo);
1342 		_data.parentID = ImportantLimbo;
1343 		importantLimboCount++;
1344 	} else if (!(_data.objectFlags & objectNoRecycle)) {
1345 		append(ObjectLimbo);
1346 		_data.parentID = ObjectLimbo;
1347 		objectLimboCount++;
1348 	} else
1349 		_data.parentID = Nothing;
1350 }
1351 
1352 //  Delete this object and every object it contains
1353 
deleteObjectRecursive(void)1354 void GameObject::deleteObjectRecursive(void) {
1355 	//  If this is an important object let's not delete it but try to drop
1356 	//  it on the ground instead.
1357 	if (isImportant()) {
1358 		assert((prototype->containmentSet() & ProtoObj::isTangible) != 0);
1359 
1360 		//  If the object is already in a world there's nothing to do.
1361 		if (isWorld(_data.parentID))
1362 			return;
1363 		else {
1364 			ObjectID        ancestorID = _data.parentID;
1365 
1366 			//  Search up the parent chain
1367 			while (ancestorID > ImportantLimbo) {
1368 				GameObject      *ancestor = objectAddress(ancestorID);
1369 
1370 				//  If this ancestor is in a world, drop the object and
1371 				//  we're done.
1372 				if (isWorld(ancestor->_data.parentID)) {
1373 					ancestor->dropInventoryObject(
1374 					    this,
1375 					    isMergeable()
1376 					    ?   _data.massCount
1377 					    :   1);
1378 					return;
1379 				}
1380 
1381 				ancestorID = ancestor->_data.parentID;
1382 			}
1383 		}
1384 	}
1385 	//  The object is not important so recursively call this function
1386 	//  for all of its children.
1387 	else {
1388 		if (_data.childID != Nothing) {
1389 			GameObject          *childObj,
1390 			                    *nextChildObj;
1391 
1392 			for (childObj = objectAddress(_data.childID);
1393 			        childObj != nullptr;
1394 			        childObj = nextChildObj) {
1395 				nextChildObj =  childObj->_data.siblingID != Nothing
1396 				                ?   objectAddress(childObj->_data.siblingID)
1397 				                :   nullptr;
1398 				childObj->deleteObjectRecursive();
1399 			}
1400 		}
1401 	}
1402 
1403 	//  Do the dirty deed.
1404 	deleteObject();
1405 }
1406 
1407 //-----------------------------------------------------------------------
1408 //	Activate this object
1409 
activate(void)1410 void GameObject::activate(void) {
1411 	if (_data.objectFlags & objectActivated)
1412 		return;
1413 
1414 	debugC(1, kDebugActors, "GameObject::activate %d (%s)", thisID(), objName());
1415 
1416 	ObjectID        dObj = thisID();
1417 	scriptCallFrame scf;
1418 
1419 	_data.objectFlags |= objectActivated;
1420 
1421 	scf.invokedObject   = dObj;
1422 	scf.enactor         = dObj;
1423 	scf.directObject    = dObj;
1424 	scf.indirectObject  = Nothing;
1425 	scf.value           = 0;
1426 
1427 	runObjectMethod(dObj, Method_GameObject_onActivate, scf);
1428 
1429 
1430 
1431 	if (isActor(this)) {
1432 		((Actor *)this)->activateActor();
1433 	}
1434 }
1435 
1436 //-----------------------------------------------------------------------
1437 //	Deactivate this object
1438 
deactivate(void)1439 void GameObject::deactivate(void) {
1440 	if (!(_data.objectFlags & objectActivated))
1441 		return;
1442 
1443 	debugC(1, kDebugActors, "GameObject::deactivate %d (%s)", thisID(), objName());
1444 
1445 	ObjectID        dObj = thisID();
1446 	scriptCallFrame scf;
1447 
1448 	//  Clear activated flag
1449 	_data.objectFlags &= ~objectActivated;
1450 
1451 	scf.invokedObject   = dObj;
1452 	scf.enactor         = dObj;
1453 	scf.directObject    = dObj;
1454 	scf.indirectObject  = Nothing;
1455 	scf.value           = 0;
1456 
1457 	runObjectMethod(dObj, Method_GameObject_onDeactivate, scf);
1458 
1459 	//  Remove all timers and sensors
1460 	removeAllTimers();
1461 	removeAllSensors();
1462 
1463 	if (isActor(this))
1464 		((Actor *)this)->deactivateActor();
1465 }
1466 
1467 //  Determine if an object is contained in this object
isContaining(GameObject * item)1468 bool GameObject::isContaining(GameObject *item) {
1469 	ContainerIterator   iter(this);
1470 	GameObject          *containedObj = nullptr;
1471 
1472 	while (iter.next(&containedObj) != Nothing) {
1473 		if (containedObj == item) return true;
1474 
1475 		if (containedObj->_data.childID != Nothing)
1476 			if (containedObj->isContaining(item)) return true;
1477 	}
1478 
1479 	return false;
1480 }
1481 
1482 //  Determine if an instance of the specified target is contained in this
1483 //  object
isContaining(ObjectTarget * objTarget)1484 bool GameObject::isContaining(ObjectTarget *objTarget) {
1485 	ContainerIterator   iter(this);
1486 	GameObject          *containedObj;
1487 
1488 	while (iter.next(&containedObj) != Nothing) {
1489 		if (objTarget->isTarget(containedObj)) return true;
1490 
1491 		if (containedObj->_data.childID != Nothing)
1492 			if (containedObj->isContaining(objTarget)) return true;
1493 	}
1494 
1495 	return false;
1496 }
1497 
1498 const int32 harmfulTerrain = terrainHot | terrainCold | terrainIce | terrainSlash | terrainBash;
1499 
updateState(void)1500 void GameObject::updateState(void) {
1501 	int16            tHeight;
1502 	static TilePoint nullVelocity(0, 0, 0);
1503 	StandingTileInfo sti;
1504 
1505 	tHeight = tileSlopeHeight(_data.location, this, &sti);
1506 
1507 	if (!(_data.location.z >= 0 || prototype->height > 8 - _data.location.z))
1508 		drown(this);
1509 
1510 	TilePoint subTile((_data.location.u >> kSubTileShift) & kSubTileMask,
1511 	                  (_data.location.v >> kSubTileShift) & kSubTileMask,
1512 	                  0);
1513 
1514 
1515 	int32 subTileTerrain =
1516 	    sti.surfaceTile != nullptr
1517 	    ?   sti.surfaceTile->attrs.testTerrain(calcSubTileMask(subTile.u,
1518 	            subTile.v))
1519 	    :   0;
1520 
1521 	if (isActor(this) && 0 != (subTileTerrain & harmfulTerrain)) {
1522 		if (subTileTerrain & terrainHot)
1523 			lavaDamage(this);
1524 		if (subTileTerrain & (terrainCold | terrainIce))
1525 			coldDamage(this);
1526 		if (subTileTerrain & terrainSlash)
1527 			terrainDamageSlash(this);
1528 		if (subTileTerrain & terrainBash)
1529 			terrainDamageBash(this);
1530 	}
1531 	//  If terrain is HIGHER (or even sligtly lower) than we are
1532 	//  currently at, then raise us up a bit.
1533 	if (isMoving()) return;
1534 
1535 	if (_data.objectFlags & objectFloating) return;
1536 
1537 	if (tHeight > _data.location.z + kMaxStepHeight) {
1538 		unstickObject(this);
1539 		tHeight = tileSlopeHeight(_data.location, this, &sti);
1540 	}
1541 	if (tHeight >= _data.location.z - gravity * 4) {
1542 		setObjectSurface(this, sti);
1543 		_data.location.z = tHeight;
1544 		return;
1545 	}
1546 
1547 	//  We're due for a fall, I think...
1548 	MotionTask::throwObject(*this, nullVelocity);
1549 }
1550 
1551 /* ======================================================================= *
1552    Object Names
1553  * ======================================================================= */
1554 
nameText(uint16 index)1555 const char *GameObject::nameText(uint16 index) {
1556 	if (index >= nameListCount)
1557 		return "Bad Name Index";
1558 
1559 	return g_vm->_nameList[index];
1560 }
1561 
1562 #define INTANGIBLE_MASK (ProtoObj::isEnchantment|ProtoObj::isSpell|ProtoObj::isSkill)
1563 
getFirstEmptySlot(GameObject * obj)1564 TilePoint GameObject::getFirstEmptySlot(GameObject *obj) {
1565 	ObjectID        objID;
1566 	GameObject      *item = nullptr;
1567 	TilePoint       newLoc, temp;
1568 	uint16          numRows = prototype->getMaxRows(),
1569 	                numCols = prototype->getMaxCols();
1570 	ProtoObj        *mObjProto = obj->proto();
1571 	bool            objIsEnchantment = (mObjProto->containmentSet() & INTANGIBLE_MASK);
1572 	bool            isReadyCont = isActor(this);
1573 
1574 	//  Enchantments don't follow the normal rules for row count, since container type
1575 	//  is also used for ready containers which are 3x3
1576 	if (objIsEnchantment) numRows = 20;
1577 
1578 	ContainerIterator   iter(this);
1579 
1580 	//This Is The Largest The Row Column Can Be
1581 	static bool     slotTable[maxRow][maxCol];
1582 
1583 	memset(&slotTable, '\0', sizeof(slotTable));    //Initialize Table To false
1584 
1585 	//  Iterate through all the objects in the container.
1586 	//  Set The Filled Spots To True In Table
1587 	while ((objID = iter.next(&item)) != Nothing) {
1588 		ProtoObj *cObjProto = item->proto();
1589 
1590 		//  If we are dropping an enchantment then don't consider non-enchantments
1591 		//  and vice-versa. Extra '!' are to force boolean-ness.
1592 
1593 		if (!isReadyCont &&
1594 		        !((cObjProto->containmentSet() & INTANGIBLE_MASK) != !objIsEnchantment))
1595 			continue;
1596 
1597 		temp = item->getLocation();
1598 
1599 		//Verify Not Writing Outside Array
1600 		if (temp.u >= 0 && temp.v >= 0 && temp.u < numRows && temp.v < numCols) {
1601 			slotTable[temp.u][temp.v] = true;
1602 		}
1603 	}
1604 
1605 	//Go Through Table Until Find A false and Return That Value
1606 	for (int16 u = 0; u < numRows; u++) {
1607 		for (int16 v = 0; v < numCols; v++) {
1608 			if (!slotTable[u][v]) {
1609 				newLoc.v = v;
1610 				newLoc.u = u;
1611 				newLoc.z = 1;
1612 				return (newLoc);
1613 			}
1614 		}
1615 	}
1616 
1617 	return Nowhere;
1618 }
1619 
1620 //-----------------------------------------------------------------------
1621 //	Return the _data.location of the first available slot within this object
1622 //	in which to place the specified object
1623 
getAvailableSlot(GameObject * obj,TilePoint * tp,bool canMerge,GameObject ** mergeObj)1624 bool GameObject::getAvailableSlot(
1625     GameObject      *obj,
1626     TilePoint       *tp,
1627     bool            canMerge,
1628     GameObject      **mergeObj) {
1629 	assert(isObject(obj));
1630 	assert(tp != nullptr);
1631 	assert(!canMerge || mergeObj != nullptr);
1632 
1633 	if (prototype == nullptr) return false;
1634 
1635 	ProtoObj        *objProto = obj->proto();
1636 
1637 	if (canMerge) *mergeObj = nullptr;
1638 
1639 	//  Determine if the specified object is an intagible container
1640 	if ((objProto->containmentSet()
1641 	        & (ProtoObj::isContainer | ProtoObj::isIntangible))
1642 	        == (ProtoObj::isContainer | ProtoObj::isIntangible)) {
1643 //		assert( isActor( obj ) );
1644 
1645 		//  Set intangible container _data.locations to -1, -1.
1646 		tp->u = -1;
1647 		tp->v = -1;
1648 		return true;
1649 	}
1650 
1651 	//  Only actors or containers may contain other objects
1652 	if (isActor(this)
1653 	        || (prototype->containmentSet() & ProtoObj::isContainer)) {
1654 		TilePoint       firstEmptySlot;
1655 
1656 		if (canMerge) {
1657 			GameObject          *inventoryObj = nullptr;
1658 			ContainerIterator   iter(this);
1659 
1660 			//  Iterate through the objects in this container
1661 			while (iter.next(&inventoryObj) != Nothing) {
1662 				if (canStackOrMerge(obj, inventoryObj)
1663 				        !=  cannotStackOrMerge) {
1664 					*tp = inventoryObj->getLocation();
1665 					*mergeObj = inventoryObj;
1666 					return true;
1667 				}
1668 			}
1669 		}
1670 
1671 		//  Nothing to merge with, so get an empty slot
1672 		if ((firstEmptySlot = getFirstEmptySlot(obj)) != Nowhere) {
1673 			*tp = firstEmptySlot;
1674 			return true;
1675 		}
1676 	}
1677 
1678 	return false;
1679 }
1680 
1681 //-----------------------------------------------------------------------
1682 //	Find a slot to place the specified object within this object and
1683 //	drop it in that slot
1684 //	If merge count == 0, then no auto-merging allowed
1685 
placeObject(ObjectID enactor,ObjectID objID,bool canMerge,int16 num)1686 bool GameObject::placeObject(
1687     ObjectID    enactor,
1688     ObjectID    objID,
1689     bool        canMerge,
1690     int16       num) {
1691 	assert(isActor(enactor));
1692 	assert(isObject(objID));
1693 
1694 	TilePoint       slot;
1695 	GameObject      *obj = GameObject::objectAddress(objID),
1696 	                 *mergeObj;
1697 
1698 	if (getAvailableSlot(obj, &slot, canMerge, &mergeObj)) {
1699 		if (canMerge && mergeObj != nullptr)
1700 			return obj->dropOn(enactor, mergeObj->thisID(), num);
1701 		else
1702 			return obj->drop(enactor, Location(slot, thisID()), num);
1703 	}
1704 
1705 	return false;
1706 }
1707 
1708 //-----------------------------------------------------------------------
1709 //	Drop the specified object on the ground in a semi-random _data.location
1710 
dropInventoryObject(GameObject * obj,int16 count)1711 void GameObject::dropInventoryObject(GameObject *obj, int16 count) {
1712 	assert(isWorld(_data.parentID));
1713 
1714 	int16           dist;
1715 	int16           mapNum = getMapNum();
1716 
1717 	dist = prototype->crossSection + obj->proto()->crossSection;
1718 
1719 	//  Iterate until the object is placed
1720 	for (;;) {
1721 		Direction   startDir,
1722 		            dir;
1723 
1724 		startDir = dir = g_vm->_rnd->getRandomNumber(7);
1725 
1726 		do {
1727 			TilePoint           probeLoc;
1728 			StandingTileInfo    sti;
1729 
1730 			//  Compute a _data.location to place the object
1731 			probeLoc = _data.location + incDirTable[dir] * dist;
1732 			probeLoc.u += g_vm->_rnd->getRandomNumber(3) - 2;
1733 			probeLoc.v += g_vm->_rnd->getRandomNumber(3) - 2;
1734 			probeLoc.z = tileSlopeHeight(probeLoc, mapNum, obj, &sti);
1735 
1736 			//  If _data.location is not blocked, drop the object
1737 			if (checkBlocked(obj, mapNum, probeLoc) == blockageNone) {
1738 				//  If we're dropping the object on a TAI, make sure
1739 				//  we call the correct drop function
1740 				if (sti.surfaceTAG == nullptr) {
1741 					obj->drop(
1742 					    thisID(),
1743 					    Location(probeLoc, _data.parentID),
1744 					    count);
1745 				} else {
1746 					obj->dropOn(
1747 					    thisID(),
1748 					    sti.surfaceTAG,
1749 					    Location(probeLoc, _data.parentID),
1750 					    count);
1751 				}
1752 
1753 				return;
1754 			}
1755 
1756 			dir = (dir + 1) & 0x7;
1757 		} while (dir != startDir);
1758 
1759 		dist += 4;
1760 	}
1761 }
1762 
getIntangibleContainer(int containerType)1763 GameObject *GameObject::getIntangibleContainer(int containerType) {
1764 
1765 	ObjectID        objID;
1766 	GameObject      *item;
1767 
1768 	ContainerIterator   iter(this);
1769 	while ((objID = iter.next(&item)) != Nothing) {
1770 		ProtoObj *proto = item->proto();
1771 		if (proto->classType == containerType)
1772 			return (item);
1773 
1774 	}
1775 
1776 	return nullptr;
1777 }
1778 
1779 //-----------------------------------------------------------------------
1780 //	Generic range checking function
1781 
inRange(const TilePoint & tp,uint16 range)1782 bool GameObject::inRange(const TilePoint &tp, uint16 range) {
1783 	uint8       crossSection = prototype->crossSection;
1784 	TilePoint   loc = getLocation();
1785 
1786 	loc =   TilePoint(
1787 	            clamp(loc.u - crossSection, tp.u, loc.u + crossSection),
1788 	            clamp(loc.v - crossSection, tp.v, loc.v + crossSection),
1789 	            clamp(loc.z, tp.z, loc.z + prototype->height));
1790 
1791 	TilePoint   vector = tp - loc;
1792 
1793 	return      vector.quickHDistance() <= range
1794 	            &&  ABS(vector.z) <= range;
1795 }
1796 
1797 //-----------------------------------------------------------------------
1798 //	A timer for this object has ticked
1799 
timerTick(TimerID timer)1800 void GameObject::timerTick(TimerID timer) {
1801 	scriptCallFrame scf;
1802 
1803 	scf.invokedObject   = thisID();
1804 	scf.enactor         = scf.invokedObject;
1805 	scf.idNum           = timer;
1806 
1807 	runObjectMethod(scf.invokedObject, Method_GameObject_onTimerTick, scf);
1808 }
1809 
1810 //-----------------------------------------------------------------------
1811 //	A sensor for this object has sensed an object
1812 
senseObject(SensorID sensor,ObjectID sensedObj)1813 void GameObject::senseObject(SensorID sensor, ObjectID sensedObj) {
1814 	scriptCallFrame scf;
1815 
1816 	scf.invokedObject   = thisID();
1817 	scf.enactor         = scf.invokedObject;
1818 	scf.directObject    = sensedObj;
1819 	scf.idNum           = sensor;
1820 
1821 	runObjectMethod(scf.invokedObject, Method_GameObject_onSenseObject, scf);
1822 }
1823 
1824 //-----------------------------------------------------------------------
1825 //	A sensor for this object has sensed an event
1826 
senseEvent(SensorID sensor,int16 type,ObjectID directObject,ObjectID indirectObject)1827 void GameObject::senseEvent(
1828     SensorID        sensor,
1829     int16           type,
1830     ObjectID        directObject,
1831     ObjectID        indirectObject) {
1832 	scriptCallFrame scf;
1833 
1834 	scf.invokedObject   = thisID();
1835 	scf.enactor         = scf.invokedObject;
1836 	scf.directObject    = directObject;
1837 	scf.indirectObject  = indirectObject;
1838 	scf.idNum           = sensor;
1839 	scf.value           = type;
1840 
1841 	runObjectMethod(scf.invokedObject, Method_GameObject_onSenseEvent, scf);
1842 }
1843 
1844 //	Timer related member functions
1845 
1846 //-----------------------------------------------------------------------
1847 //	Add a new timer to this objects's timer list
1848 
addTimer(TimerID id)1849 bool GameObject::addTimer(TimerID id) {
1850 	return addTimer(id, sensorCheckRate);
1851 }
1852 
1853 //-----------------------------------------------------------------------
1854 //	Add a new timer to this objects's timer list
1855 
addTimer(TimerID id,int16 frameInterval)1856 bool GameObject::addTimer(TimerID id, int16 frameInterval) {
1857 	TimerList   *timerList;
1858 	Timer       *newTimer;
1859 
1860 	//  Create the new timer
1861 	if ((newTimer = new Timer(this, id, frameInterval)) == nullptr)
1862 		return false;
1863 
1864 	//  Fetch the existing timer list for this object or create a
1865 	//  new one
1866 	if ((timerList = fetchTimerList(this)) == nullptr && (timerList = new TimerList(this)) == nullptr) {
1867 		delete newTimer;
1868 		return false;
1869 	}
1870 
1871 	assert(timerList->getObject() == this);
1872 
1873 	//  Search the list to see if there is already a timer with same
1874 	//  ID as the new timer.  If so, remove it and delete it.
1875 	for (Common::List<Timer *>::iterator it = timerList->_timers.begin(); it != timerList->_timers.end(); ++it) {
1876 		assert((*it)->getObject() == this);
1877 
1878 		if (newTimer->thisID() == (*it)->thisID()) {
1879 			deleteTimer(*it);
1880 			delete *it;
1881 			timerList->_timers.erase(it);
1882 
1883 			break;
1884 		}
1885 	}
1886 
1887 	//  Put the new timer into the list
1888 	timerList->_timers.push_back(newTimer);
1889 
1890 	return true;
1891 }
1892 
1893 //-----------------------------------------------------------------------
1894 //	Remove a specified timer from this object's timer list
1895 
removeTimer(TimerID id)1896 void GameObject::removeTimer(TimerID id) {
1897 	TimerList       *timerList;
1898 
1899 	//  Get this object's timer list
1900 	if ((timerList = fetchTimerList(this)) != nullptr) {
1901 		for (Common::List<Timer *>::iterator it = timerList->_timers.begin(); it != timerList->_timers.end(); ++it) {
1902 			if ((*it)->thisID() == id) {
1903 				(*it)->_active = false;
1904 				timerList->_timers.erase(it);
1905 
1906 				if (timerList->_timers.empty())
1907 					delete timerList;
1908 
1909 				break;
1910 			}
1911 		}
1912 	}
1913 }
1914 
1915 //-----------------------------------------------------------------------
1916 //	Remove all timer's from this objects's timer list
1917 
removeAllTimers(void)1918 void GameObject::removeAllTimers(void) {
1919 	TimerList       *timerList;
1920 
1921 	//  Get this object's timer list
1922 	if ((timerList = fetchTimerList(this)) != nullptr) {
1923 		for (Common::List<Timer *>::iterator it = timerList->_timers.begin(); it != timerList->_timers.end(); ++it) {
1924 			deleteTimer(*it);
1925 			delete *it;
1926 		}
1927 
1928 		timerList->_timers.clear();
1929 
1930 		delete timerList;
1931 	}
1932 }
1933 
1934 //	Sensor related member functions
1935 
1936 //-----------------------------------------------------------------------
1937 //	Add the specified sensor to this object's sensor list
1938 
addSensor(Sensor * newSensor)1939 bool GameObject::addSensor(Sensor *newSensor) {
1940 	SensorList          *sensorList;
1941 
1942 	//  Fetch the existing sensor list for this object or allocate a
1943 	//  new one
1944 	if ((sensorList = fetchSensorList(this)) == nullptr
1945 	        && (sensorList = new SensorList(this)) == nullptr)
1946 		return false;
1947 
1948 	assert(sensorList->getObject() == this);
1949 
1950 	//  Search the list to see if there is already a sensor with same
1951 	//  ID as the new sensor.  If so, remove it and delete it.
1952 	for (Common::List<Sensor *>::iterator it = sensorList->_list.begin(); it != sensorList->_list.end(); ++it) {
1953 		assert((*it)->getObject() == this);
1954 
1955 		if (newSensor->thisID() == (*it)->thisID()) {
1956 			delete *it;
1957 			it = sensorList->_list.erase(it);
1958 
1959 			break;
1960 		}
1961 	}
1962 
1963 	//  Put the new sensor into the list
1964 	sensorList->_list.push_back(newSensor);
1965 
1966 	return true;
1967 }
1968 
1969 //-----------------------------------------------------------------------
1970 //	Add a protaganist sensor to this object's sensor list
1971 
addProtaganistSensor(SensorID id,int16 range)1972 bool GameObject::addProtaganistSensor(SensorID id, int16 range) {
1973 	ProtaganistSensor   *newSensor;
1974 	bool                sensorAdded;
1975 
1976 	newSensor = new ProtaganistSensor(this, id, range);
1977 	if (newSensor == nullptr) return false;
1978 
1979 	sensorAdded = addSensor(newSensor);
1980 	if (!sensorAdded) delete newSensor;
1981 
1982 	return sensorAdded;
1983 }
1984 
1985 //-----------------------------------------------------------------------
1986 //	Add a specific actor sensor to this object's sensor list
1987 
addSpecificActorSensor(SensorID id,int16 range,Actor * a)1988 bool GameObject::addSpecificActorSensor(SensorID id, int16 range, Actor *a) {
1989 	SpecificActorSensor *newSensor;
1990 	bool                sensorAdded;
1991 
1992 	newSensor = new SpecificActorSensor(this, id, range, a);
1993 	if (newSensor == nullptr) return false;
1994 
1995 	sensorAdded = addSensor(newSensor);
1996 	if (!sensorAdded) delete newSensor;
1997 
1998 	return sensorAdded;
1999 }
2000 
2001 //-----------------------------------------------------------------------
2002 //	Add a specific object sensor to this object's sensor list
2003 
addSpecificObjectSensor(SensorID id,int16 range,ObjectID obj)2004 bool GameObject::addSpecificObjectSensor(
2005     SensorID    id,
2006     int16       range,
2007     ObjectID    obj) {
2008 	SpecificObjectSensor    *newSensor;
2009 	bool                    sensorAdded;
2010 
2011 	newSensor = new SpecificObjectSensor(this, id, range, obj);
2012 	if (newSensor == nullptr) return false;
2013 
2014 	sensorAdded = addSensor(newSensor);
2015 	if (!sensorAdded) delete newSensor;
2016 
2017 	return sensorAdded;
2018 }
2019 
2020 //-----------------------------------------------------------------------
2021 //	Add an actor property sensor to this object's sensor list
2022 
addActorPropertySensor(SensorID id,int16 range,ActorPropertyID prop)2023 bool GameObject::addActorPropertySensor(
2024     SensorID        id,
2025     int16           range,
2026     ActorPropertyID prop) {
2027 	ActorPropertySensor *newSensor;
2028 	bool                sensorAdded;
2029 
2030 	newSensor = new ActorPropertySensor(this, id, range, prop);
2031 	if (newSensor == nullptr) return false;
2032 
2033 	sensorAdded = addSensor(newSensor);
2034 	if (!sensorAdded) delete newSensor;
2035 
2036 	return sensorAdded;
2037 }
2038 
2039 //-----------------------------------------------------------------------
2040 //	Add an object property sensor to this object's sensor list
2041 
addObjectPropertySensor(SensorID id,int16 range,ObjectPropertyID prop)2042 bool GameObject::addObjectPropertySensor(
2043     SensorID            id,
2044     int16               range,
2045     ObjectPropertyID    prop) {
2046 	ObjectPropertySensor    *newSensor;
2047 	bool                    sensorAdded;
2048 
2049 	newSensor = new ObjectPropertySensor(this, id, range, prop);
2050 	if (newSensor == nullptr) return false;
2051 
2052 	sensorAdded = addSensor(newSensor);
2053 	if (!sensorAdded) delete newSensor;
2054 
2055 	return sensorAdded;
2056 }
2057 
2058 //-----------------------------------------------------------------------
2059 //	Add an event sensor to this object's sensor list
2060 
addEventSensor(SensorID id,int16 range,int16 eventType)2061 bool GameObject::addEventSensor(
2062     SensorID        id,
2063     int16           range,
2064     int16           eventType) {
2065 	EventSensor     *newSensor;
2066 	bool            sensorAdded;
2067 
2068 	newSensor = new EventSensor(this, id, range, eventType);
2069 	if (newSensor == nullptr) return false;
2070 
2071 	sensorAdded = addSensor(newSensor);
2072 	if (!sensorAdded) delete newSensor;
2073 
2074 	return sensorAdded;
2075 }
2076 
2077 //-----------------------------------------------------------------------
2078 //	Remove a specified sensor from this object's sensor list
2079 
removeSensor(SensorID id)2080 void GameObject::removeSensor(SensorID id) {
2081 	SensorList      *sensorList;
2082 
2083 	//  Get this object's sensor list
2084 	if ((sensorList = fetchSensorList(this)) != nullptr) {
2085 		//  Search the sensor list for a sensor with the specified ID
2086 		for (Common::List<Sensor *>::iterator it = sensorList->_list.begin(); it != sensorList->_list.end(); ++it) {
2087 			if ((*it)->thisID() == id) {
2088 				//  Remove the sensor, then delete it
2089 				(*it)->_active = false;
2090 				sensorList->_list.erase(it);
2091 
2092 				//  If the list is now empty, delete it
2093 				if (sensorList->_list.empty()) {
2094 					delete sensorList;
2095 				}
2096 
2097 				break;
2098 			}
2099 		}
2100 	}
2101 }
2102 
2103 //-----------------------------------------------------------------------
2104 //	Remove all sensors from this object's sensor list
2105 
removeAllSensors(void)2106 void GameObject::removeAllSensors(void) {
2107 	SensorList      *sensorList;
2108 
2109 	//  Get this object's sensor list
2110 	if ((sensorList = fetchSensorList(this)) != nullptr) {
2111 		//  Iterate through the sensors
2112 		for (Common::List<Sensor *>::iterator it = sensorList->_list.begin(); it != sensorList->_list.end(); ++it)
2113 			delete *it;
2114 
2115 		deleteSensorList(sensorList);
2116 		delete sensorList;
2117 	}
2118 }
2119 
2120 //-----------------------------------------------------------------------
2121 //	Polling function to determine if this object can sense a protaganist
2122 //	within a specified range
2123 
canSenseProtaganist(SenseInfo & info,int16 range)2124 bool GameObject::canSenseProtaganist(SenseInfo &info, int16 range) {
2125 	ProtaganistSensor   sensor(this, 0, range);
2126 
2127 	if (isActor(this)) {
2128 		Actor *a = (Actor *) this;
2129 		return sensor.check(info, a->_enchantmentFlags);
2130 	}
2131 	return sensor.check(info, nonActorSenseFlags);
2132 }
2133 
2134 //-----------------------------------------------------------------------
2135 //	Polling function to determine if this object can sense a specific
2136 //	actor within a specified range
2137 
canSenseSpecificActor(SenseInfo & info,int16 range,Actor * a)2138 bool GameObject::canSenseSpecificActor(
2139     SenseInfo   &info,
2140     int16       range,
2141     Actor       *a) {
2142 	SpecificActorSensor sensor(this, 0, range, a);
2143 
2144 	if (isActor(this)) {
2145 		Actor *ac = (Actor *)this;
2146 		return sensor.check(info, ac->_enchantmentFlags);
2147 	}
2148 	return sensor.check(info, nonActorSenseFlags);
2149 }
2150 
2151 //-----------------------------------------------------------------------
2152 //	Polling function to determine if this object can sense a specific
2153 //	object within a specified range
2154 
canSenseSpecificObject(SenseInfo & info,int16 range,ObjectID obj)2155 bool GameObject::canSenseSpecificObject(
2156     SenseInfo   &info,
2157     int16       range,
2158     ObjectID    obj) {
2159 	SpecificObjectSensor    sensor(this, 0, range, obj);
2160 
2161 	if (isActor(this)) {
2162 		Actor *a = (Actor *) this;
2163 		return sensor.check(info, a->_enchantmentFlags);
2164 	}
2165 	return sensor.check(info, nonActorSenseFlags);
2166 }
2167 
2168 //-----------------------------------------------------------------------
2169 //	Polling function to determine if this object can sense an actor with
2170 //	a specified property within a specified range
2171 
canSenseActorProperty(SenseInfo & info,int16 range,ActorPropertyID prop)2172 bool GameObject::canSenseActorProperty(
2173     SenseInfo           &info,
2174     int16               range,
2175     ActorPropertyID     prop) {
2176 	ActorPropertySensor     sensor(this, 0, range, prop);
2177 
2178 	if (isActor(this)) {
2179 		Actor *a = (Actor *) this;
2180 		return sensor.check(info, a->_enchantmentFlags);
2181 	}
2182 	return sensor.check(info, nonActorSenseFlags);
2183 }
2184 
2185 //-----------------------------------------------------------------------
2186 //	Polling function to determine if this object can sense an object with
2187 //	a specified property within a specified range
2188 
canSenseObjectProperty(SenseInfo & info,int16 range,ObjectPropertyID prop)2189 bool GameObject::canSenseObjectProperty(
2190     SenseInfo           &info,
2191     int16               range,
2192     ObjectPropertyID    prop) {
2193 	ObjectPropertySensor    sensor(this, 0, range, prop);
2194 
2195 	if (isActor(this)) {
2196 		Actor *a = (Actor *) this;
2197 		return sensor.check(info, a->_enchantmentFlags);
2198 	}
2199 	return sensor.check(info, nonActorSenseFlags);
2200 }
2201 
2202 //-------------------------------------------------------------------
2203 //  Given an object, returns the prototype number
2204 
getProtoNum(void)2205 int32 GameObject::getProtoNum(void) {
2206 	for (uint i = 0; i < g_vm->_actorProtos.size(); ++i) {
2207 		if (prototype == g_vm->_actorProtos[i])
2208 			return i;
2209 	}
2210 
2211 	for (uint i = 0; i < g_vm->_objectProtos.size(); ++i) {
2212 		if (prototype == g_vm->_objectProtos[i])
2213 			return i;
2214 	}
2215 
2216 	return -1;
2217 }
2218 
2219 //-------------------------------------------------------------------
2220 //  Set the prototype of an object to a given prototype number
2221 
setProtoNum(int32 nProto)2222 void GameObject::setProtoNum(int32 nProto) {
2223 	if (isActor(this))
2224 		prototype = g_vm->_actorProtos[nProto];
2225 	else {
2226 		ObjectID    oldParentID = _data.parentID;
2227 		bool        wasStacked = unstack(); //  Unstack if it was in a stack
2228 
2229 		prototype = g_vm->_objectProtos[nProto];
2230 
2231 		if (wasStacked) {
2232 			ObjectID    pos = possessor();
2233 
2234 			move(Location(0, 0, 0, ImportantLimbo));
2235 
2236 			//  Attempt to replace stacked object in inventory
2237 			if (!dropOn((pos != Nothing ? pos : getCenterActorID()), oldParentID, 1))
2238 				deleteObjectRecursive();
2239 		}
2240 
2241 		//  If this object is in a container, then redraw the container window
2242 		if (!isWorld(oldParentID))
2243 			g_vm->_cnm->setUpdate(oldParentID);
2244 	}
2245 }
2246 
2247 //-------------------------------------------------------------------
2248 //	Evaluate the effects of enchantments upon an object
2249 
evalEnchantments(void)2250 void GameObject::evalEnchantments(void) {
2251 	if (isActor(this)) {
2252 		evalActorEnchantments((Actor *)this);
2253 	} else if (isObject(this)) {
2254 		evalObjectEnchantments(this);
2255 	}
2256 }
2257 
2258 #define noMergeFlags    (objectImportant|\
2259                          objectGhosted|\
2260                          objectInvisible|\
2261                          objectFloating|\
2262                          objectNoRecycle)
2263 
canStackOrMerge(GameObject * dropObj,GameObject * target)2264 int32 GameObject::canStackOrMerge(GameObject *dropObj, GameObject *target) {
2265 	int32       cSet = dropObj->proto()->containmentSet();
2266 
2267 	//  If the names are the same, and the prototype is the same, and the object
2268 	//  being dropped is neither an intangible object nor a container,
2269 	//  then stacking / merging may be possible.
2270 
2271 	if (dropObj->getNameIndex() == target->getNameIndex()
2272 	        &&  dropObj->proto() == target->proto()
2273 	        &&  !(cSet & (ProtoObj::isIntangible | ProtoObj::isContainer))) {
2274 		//  If it is a mergeable object
2275 		if (dropObj->proto()->flags & ResourceObjectPrototype::objPropMergeable) {
2276 			//  If the flags are the same, and neither object has children,
2277 			//  then we can merge
2278 			if (((dropObj->_data.objectFlags & noMergeFlags) == (target->_data.objectFlags & noMergeFlags))
2279 			        &&  dropObj->IDChild() == Nothing
2280 			        &&  target->IDChild() == Nothing) {
2281 				return canMerge;
2282 			}
2283 		} else if (!(cSet & (ProtoObj::isWearable | ProtoObj::isWeapon | ProtoObj::isArmor))
2284 		           ||  !isActor(target->IDParent())) {
2285 			//  We can stack if the pile we are stacking on is in a container.
2286 			if (!isWorld(target->IDParent())
2287 			        &&  target->getLocation().z != 0)
2288 				return canStack;
2289 		}
2290 	}
2291 	return cannotStackOrMerge;
2292 }
2293 
mergeWith(GameObject * dropObj,GameObject * target,int16 count)2294 void GameObject::mergeWith(GameObject *dropObj, GameObject *target, int16 count) {
2295 	// get the smaller of the two as a move variable
2296 	int16 moveCount = MIN<uint16>(count, dropObj->getExtra());
2297 
2298 	//  REM: We need to check and see if the container can hold this....
2299 
2300 	//  Inc the masscount on the target object
2301 	//  If we overflow a short, then simply truncate the amount (stuff is lost,
2302 	//  but that's OK for mergeables!)
2303 	target->setExtra(MIN<long>((long)target->getExtra() + moveCount, 0x7fff));
2304 
2305 	// decrement the count on the other pile
2306 	dropObj->setExtra(dropObj->getExtra() - moveCount);
2307 
2308 	// if that was the last object,
2309 	// then delete it from the container
2310 	if (dropObj->getExtra() <= 0) {
2311 		dropObj->deleteObject();
2312 	}
2313 
2314 	g_vm->_cnm->setUpdate(target->IDParent());
2315 }
2316 
2317 
merge(ObjectID enactor,ObjectID objToMergeID,int16 count)2318 bool GameObject::merge(ObjectID enactor, ObjectID objToMergeID, int16 count) {
2319 	GameObject      *objToMerge = objectAddress(objToMergeID);
2320 	Location        loc(getLocation(), IDParent());
2321 
2322 	if (objToMerge->drop(enactor, loc, count)) {
2323 		if (!objToMerge->isMoving())
2324 			mergeWith(objToMerge, this, count);
2325 		return true;
2326 	}
2327 
2328 	return false;
2329 }
2330 
stack(ObjectID enactor,ObjectID objToStackID)2331 bool GameObject::stack(ObjectID enactor, ObjectID objToStackID) {
2332 	GameObject      *objToStack = objectAddress(objToStackID);
2333 
2334 	//  Stack the object
2335 	Location    loc(getLocation(), IDParent());
2336 
2337 	loc.z = 0;
2338 
2339 	if (objToStack->drop(enactor, loc)) {
2340 		if (!objToStack->isMoving()) {
2341 			//  Increase the stack count
2342 			_data.location.z++;
2343 			g_vm->_cnm->setUpdate(IDParent());
2344 		}
2345 
2346 		return true;
2347 	}
2348 
2349 	return false;
2350 }
2351 
2352 //-------------------------------------------------------------------
2353 //	Return the total mass of all objects contained within this object
2354 
totalContainedMass(void)2355 uint16 GameObject::totalContainedMass(void) {
2356 	uint16              total = 0;
2357 	GameObject          *childObj;
2358 	ContainerIterator   iter(this);
2359 
2360 	while (iter.next(&childObj) != Nothing) {
2361 		uint16          objMass;
2362 
2363 		if (!(childObj->containmentSet() & ProtoObj::isTangible))
2364 			continue;
2365 
2366 		objMass = childObj->prototype->mass;
2367 		if (childObj->isMergeable())
2368 			objMass *= childObj->getExtra();
2369 		total += objMass;
2370 
2371 		if (childObj->_data.childID != Nothing)
2372 			total += childObj->totalContainedMass();
2373 	}
2374 
2375 	return total;
2376 }
2377 
2378 //-------------------------------------------------------------------
2379 //	Return the total bulk of all objects contained within this object
2380 
totalContainedBulk(void)2381 uint16 GameObject::totalContainedBulk(void) {
2382 	uint16              total = 0;
2383 	GameObject          *childObj;
2384 	ContainerIterator   iter(this);
2385 
2386 	while (iter.next(&childObj) != Nothing) {
2387 		uint16          objBulk;
2388 
2389 		if (!(childObj->containmentSet() & ProtoObj::isTangible))
2390 			continue;
2391 
2392 		objBulk = childObj->prototype->bulk;
2393 		if (childObj->isMergeable())
2394 			objBulk *= childObj->getExtra();
2395 		total += objBulk;
2396 	}
2397 
2398 	return total;
2399 }
2400 
2401 /* ======================================================================= *
2402    GameWorld member functions
2403  * ======================================================================= */
2404 
2405 //-------------------------------------------------------------------
2406 //	Initial constructor
2407 
GameWorld(int16 map)2408 GameWorld::GameWorld(int16 map) {
2409 	Common::SeekableReadStream *stream;
2410 	if ((stream = loadResourceToStream(tileRes, MKTAG('M', 'A', 'P', (char)map), "game map"))) {
2411 		int16   mapSize;    //  Size of map in MetaTiles
2412 
2413 		mapSize = stream->readSint16LE();
2414 		size.u = (mapSize << kPlatShift) << kTileUVShift;
2415 		size.v = size.u;
2416 
2417 		sectorArraySize = size.u / kSectorSize;
2418 		sectorArray = new Sector[sectorArraySize * sectorArraySize]();
2419 
2420 		if (sectorArray == nullptr)
2421 			error("Unable to allocate world %d sector array", map);
2422 
2423 		mapNum = map;
2424 		delete stream;
2425 	} else {
2426 		size.u = size.v = 0;
2427 		sectorArraySize = 0;
2428 		sectorArray = nullptr;
2429 
2430 		mapNum = -1;
2431 	}
2432 }
2433 
GameWorld(Common::SeekableReadStream * stream)2434 GameWorld::GameWorld(Common::SeekableReadStream *stream) {
2435 	size.u = size.v = stream->readSint16LE();
2436 	mapNum = stream->readSint16LE();
2437 
2438 	debugC(3, kDebugSaveload, "... size.u = size.v = %d", size.u);
2439 	debugC(3, kDebugSaveload, "... mapNum = %d", mapNum);
2440 
2441 	if (size.u != 0) {
2442 		int32 sectorArrayCount;
2443 
2444 		sectorArraySize = size.u / kSectorSize;
2445 		sectorArrayCount = sectorArraySize * sectorArraySize;
2446 		sectorArray = new Sector[sectorArrayCount]();
2447 
2448 		if (sectorArray == nullptr)
2449 			error("Unable to allocate world %d sector array", mapNum);
2450 
2451 		for (int i = 0; i < sectorArrayCount; ++i) {
2452 			sectorArray[i].read(stream);
2453 			debugC(4, kDebugSaveload, "...... sectArray[%d].activationCount = %d", i, sectorArray[i].activationCount);
2454 			debugC(4, kDebugSaveload, "...... sectArray[%d].childID = %d", i, sectorArray[i].childID);
2455 		}
2456 	} else {
2457 		sectorArraySize = 0;
2458 		sectorArray = nullptr;
2459 	}
2460 }
2461 
~GameWorld()2462 GameWorld::~GameWorld() {
2463 	if (sectorArray)
2464 		delete[] sectorArray;
2465 }
2466 
2467 //-------------------------------------------------------------------
2468 //	Return the number of bytes need to make an archive of this world
2469 
archiveSize(void)2470 int32 GameWorld::archiveSize(void) {
2471 	int32   bytes = 0;
2472 
2473 	bytes +=    sizeof(size.u)
2474 	            +   sizeof(mapNum)
2475 	            +   sectorArraySize * sectorArraySize * sizeof(Sector);
2476 
2477 	return bytes;
2478 }
2479 
2480 //-------------------------------------------------------------------
2481 //	Cleanup
2482 
cleanup(void)2483 void GameWorld::cleanup(void) {
2484 	if (sectorArray != nullptr) {
2485 		delete[] sectorArray;
2486 		sectorArray = nullptr;
2487 	}
2488 }
2489 
2490 /* ======================================================================= *
2491    World management
2492  * ======================================================================= */
2493 
2494 extern int enchantmentProto;
2495 
2496 //-------------------------------------------------------------------
2497 //	Load and construct object and actor prototype arrays
2498 
initPrototypes(void)2499 void initPrototypes(void) {
2500 	const int resourceObjProtoSize = 52;
2501 	const int resourceActProtoSize = 86;
2502 	uint count = 0;
2503 	Common::SeekableReadStream *stream;
2504 	Common::String s;
2505 
2506 	debugC(1, kDebugLoading, "Initializing Prototypes");
2507 
2508 	stream = loadResourceToStream(listRes, nameListID, "name list");
2509 	for (uint16 offset = 0; offset < stream->size(); ++count) {
2510 		stream->seek(2 * count);
2511 		offset = stream->readUint16LE();
2512 
2513 		stream->seek(offset);
2514 		s = stream->readString();
2515 		debugC(5, kDebugLoading, "Read string (size %d): %s", s.size(), s.c_str());
2516 
2517 		char *name = new char[s.size() + 1];
2518 		Common::strlcpy(name, s.c_str(), s.size() + 1);
2519 		g_vm->_nameList.push_back(name);
2520 	}
2521 	nameListCount = count;
2522 
2523 	delete stream;
2524 
2525 	//  Load the Object prototype table
2526 
2527 	objectProtoCount = listRes->size(objProtoID)
2528 	                   / resourceObjProtoSize;
2529 
2530 	if (objectProtoCount < 1)
2531 		error("Unable to load Object Prototypes");
2532 
2533 	if ((stream = loadResourceToStream(listRes, objProtoID, "object prototypes")) == nullptr)
2534 		error("Unable to load Object Prototypes");
2535 
2536 	//  Load each individual prototype. Read in everything except
2537 	//  the virtual function pointer.
2538 
2539 	for (int i = 0; i < objectProtoCount; i++) {
2540 		ResourceObjectPrototype ro;
2541 		ProtoObj *pr;
2542 
2543 		ro.load(stream);
2544 
2545 		switch (ro.classType) {
2546 		case protoClassInventory:
2547 			pr = new InventoryProto(ro);
2548 			break;
2549 		case protoClassPhysContainer:
2550 			pr = new PhysicalContainerProto(ro);
2551 			break;
2552 		case protoClassKey:
2553 			pr = new KeyProto(ro);
2554 			break;
2555 
2556 		case protoClassBottle:
2557 			pr = new BottleProto(ro);
2558 			break;
2559 
2560 		case protoClassFood:                        // Food ProtoType
2561 			pr = new FoodProto(ro);
2562 			break;
2563 
2564 		case protoClassBludgeoningWeapon:
2565 			pr = new BludgeoningWeaponProto(ro);
2566 			break;
2567 
2568 		case protoClassSlashingWeapon:
2569 			pr = new SlashingWeaponProto(ro);
2570 			break;
2571 
2572 		case protoClassBow:
2573 			pr = new BowProto(ro);
2574 			break;
2575 
2576 		case protoClassWeaponWand:
2577 			pr = new WeaponWandProto(ro);
2578 			break;
2579 
2580 		case protoClassArrow:
2581 			pr = new ArrowProto(ro);
2582 			break;
2583 
2584 		case protoClassShield:
2585 			pr = new ShieldProto(ro);
2586 			break;
2587 
2588 		case protoClassArmor:
2589 			pr = new ArmorProto(ro);
2590 			break;
2591 
2592 		case protoClassTool:
2593 			pr = new ToolProto(ro);
2594 			break;
2595 
2596 		case protoClassBookDoc:
2597 			pr = new BookProto(ro);
2598 			break;
2599 
2600 		case protoClassScrollDoc:
2601 			pr = new ScrollProto(ro);
2602 			break;
2603 
2604 		case protoClassMap:
2605 			pr = new AutoMapProto(ro);
2606 			break;
2607 
2608 		case protoClassIdea:
2609 			pr = new IdeaProto(ro);
2610 			break;
2611 
2612 		case protoClassMemory:
2613 			pr = new MemoryProto(ro);
2614 			break;
2615 
2616 		case protoClassPsych:
2617 			pr = new PsychProto(ro);
2618 			break;
2619 
2620 		case protoClassSkill:
2621 			pr = new SkillProto(ro);
2622 			initializeSkill((SkillProto *) pr, ((SkillProto *) pr)->getSpellID());
2623 			//initializeSkill(i,((SkillProto *) pr)->getSpellID());
2624 			break;
2625 
2626 		case protoClassIdeaContainer:
2627 			pr = new IdeaContainerProto(ro);
2628 			break;
2629 
2630 		case protoClassMemoryContainer:
2631 			pr = new MemoryContainerProto(ro);
2632 			break;
2633 
2634 		case protoClassPsychContainer:
2635 			pr = new PsychContainerProto(ro);
2636 			break;
2637 
2638 		case protoClassSkillContainer:
2639 			pr = new SkillContainerProto(ro);
2640 			break;
2641 
2642 		case protoClassEnchantment:
2643 			pr = new EnchantmentProto(ro);
2644 			enchantmentProto = i;
2645 			break;
2646 
2647 		case protoClassMonsterGenerator:
2648 			pr = new MonsterGeneratorProto(ro);
2649 			break;
2650 
2651 		//  REM: add this in when we change the database.
2652 		case protoClassEncounterGenerator:
2653 			pr = new EncounterGeneratorProto(ro);
2654 			break;
2655 
2656 		case protoClassMissionGenerator:
2657 			pr = new MissionGeneratorProto(ro);
2658 			break;
2659 
2660 		default:
2661 			//  Unrecognized prototypes now default to
2662 			//  an inventory item.
2663 			pr = new InventoryProto(ro);
2664 			break;
2665 		}
2666 
2667 		g_vm->_objectProtos.push_back(pr);
2668 	}
2669 
2670 	listRes->rest();
2671 	delete stream;
2672 
2673 	//  Load the Actor prototype table
2674 
2675 	actorProtoCount = listRes->size(actorProtoID)
2676 	                  / resourceActProtoSize;
2677 
2678 	if (actorProtoCount < 1)
2679 		error("Unable to load Actor Prototypes");
2680 
2681 	if ((stream = loadResourceToStream(listRes, actorProtoID, "actor prototypes")) == nullptr)
2682 		error("Unable to load Actor Prototypes");
2683 
2684 	for (int i = 0; i < actorProtoCount; i++) {
2685 		ResourceActorPrototype  ra;
2686 		ra.load(stream);
2687 
2688 		ActorProto *pr = new ActorProto(ra);
2689 		g_vm->_actorProtos.push_back(pr);
2690 	}
2691 
2692 	listRes->rest();
2693 	delete stream;
2694 }
2695 
2696 //-------------------------------------------------------------------
2697 //	Cleanup the prototype lists
2698 
cleanupPrototypes(void)2699 void cleanupPrototypes(void) {
2700 	for (uint i = 0; i < nameListCount; ++i) {
2701 		if (g_vm->_nameList[i])
2702 			delete[] g_vm->_nameList[i];
2703 	}
2704 
2705 	g_vm->_nameList.clear();
2706 
2707 	for (uint i = 0; i < g_vm->_actorProtos.size(); ++i) {
2708 		if (g_vm->_actorProtos[i])
2709 			delete g_vm->_actorProtos[i];
2710 	}
2711 
2712 	g_vm->_actorProtos.clear();
2713 
2714 	for (uint i = 0; i < g_vm->_objectProtos.size(); ++i) {
2715 		if (g_vm->_objectProtos[i])
2716 			delete g_vm->_objectProtos[i];
2717 	}
2718 
2719 	g_vm->_objectProtos.clear();
2720 }
2721 
2722 //-------------------------------------------------------------------
2723 //	Load the sound effects table
2724 
initObjectSoundFXTable(void)2725 void initObjectSoundFXTable(void) {
2726 	hResContext     *itemRes;
2727 
2728 	itemRes =   auxResFile->newContext(
2729 	                MKTAG('I', 'T', 'E', 'M'),
2730 	                "item resources");
2731 	if (itemRes == nullptr || !itemRes->_valid)
2732 		error("Error accessing item resource group.\n");
2733 
2734 	objectSoundFXTable =
2735 	    (ObjectSoundFXs *)LoadResource(
2736 	        itemRes,
2737 	        MKTAG('S', 'N', 'D', 'T'),
2738 	        "object sound effect table");
2739 
2740 	if (objectSoundFXTable == nullptr)
2741 		error("Unable to load object sound effects table");
2742 
2743 	auxResFile->disposeContext(itemRes);
2744 }
2745 
2746 //-------------------------------------------------------------------
2747 //	Cleanup the sound effects table
2748 
cleanupObjectSoundFXTable(void)2749 void cleanupObjectSoundFXTable(void) {
2750 	if (objectSoundFXTable != nullptr) {
2751 		free(objectSoundFXTable);
2752 		objectSoundFXTable = nullptr;
2753 	}
2754 }
2755 
2756 //-------------------------------------------------------------------
2757 //	Allocate array to hold the counts of the temp actors
2758 
initTempActorCount(void)2759 void initTempActorCount(void) {
2760 	uint16          i;
2761 
2762 	//  Allocate and initialize the temp actor count array
2763 	tempActorCount = new uint16[actorProtoCount];
2764 	for (i = 0; i < actorProtoCount; i++)
2765 		tempActorCount[i] = 0;
2766 }
2767 
saveTempActorCount(Common::OutSaveFile * outS)2768 void saveTempActorCount(Common::OutSaveFile *outS) {
2769 	debugC(2, kDebugSaveload, "Saving TempActorCount");
2770 
2771 	outS->write("ACNT", 4);
2772 	CHUNK_BEGIN;
2773 	for (int i = 0; i < actorProtoCount; ++i)
2774 		out->writeUint16LE(tempActorCount[i]);
2775 	CHUNK_END;
2776 }
2777 
loadTempActorCount(Common::InSaveFile * in,int32 chunkSize)2778 void loadTempActorCount(Common::InSaveFile *in, int32 chunkSize) {
2779 	debugC(2, kDebugSaveload, "Loading TempActorCount");
2780 
2781 	int count = chunkSize / sizeof(uint16);
2782 	tempActorCount = new uint16[count];
2783 
2784 	for (int i = 0; i < count; ++i)
2785 		tempActorCount[i] = in->readUint16LE();
2786 }
2787 
2788 //-------------------------------------------------------------------
2789 //	Cleanup the array to temp actor counts
2790 
cleanupTempActorCount(void)2791 void cleanupTempActorCount(void) {
2792 	if (tempActorCount != nullptr) {
2793 		delete[] tempActorCount;
2794 		tempActorCount = nullptr;
2795 	}
2796 }
2797 
2798 //-------------------------------------------------------------------
2799 //	Increment the temporary actor count for the specified prototype
2800 
incTempActorCount(uint16 protoNum)2801 void incTempActorCount(uint16 protoNum) {
2802 	tempActorCount[protoNum]++;
2803 }
2804 
2805 //-------------------------------------------------------------------
2806 //	Decrement the temporary actor count for the specified prototype
2807 
decTempActorCount(uint16 protoNum)2808 void decTempActorCount(uint16 protoNum) {
2809 	tempActorCount[protoNum]--;
2810 }
2811 
2812 //-------------------------------------------------------------------
2813 //	Return the number of temporary actors for the specified prototype
2814 
getTempActorCount(uint16 protoNum)2815 uint16 getTempActorCount(uint16 protoNum) {
2816 	return tempActorCount[protoNum];
2817 }
2818 
2819 //-------------------------------------------------------------------
2820 //	Initialize the worlds
2821 
initWorlds(void)2822 void initWorlds(void) {
2823 	int             i;
2824 
2825 	//  worldCount must be set by the map data initialization
2826 	worldListSize = worldCount * sizeof(GameWorld);
2827 
2828 	worldList = new GameWorld[worldListSize]();
2829 	if (worldList == nullptr)
2830 		error("Unable to allocate world list");
2831 
2832 	for (i = 0; i < worldCount; i++) {
2833 		GameWorld   *gw = &worldList[i];
2834 
2835 		new (gw) GameWorld(i);
2836 
2837 		worldList[i]._index = i + WorldBaseID;
2838 	}
2839 
2840 	currentWorld = &worldList[0];
2841 	setCurrentMap(currentWorld->mapNum);
2842 }
2843 
saveWorlds(Common::OutSaveFile * outS)2844 void saveWorlds(Common::OutSaveFile *outS) {
2845 	debugC(2, kDebugSaveload, "Saving worlds");
2846 
2847 	outS->write("WRLD", 4);
2848 	CHUNK_BEGIN;
2849 	out->writeUint16LE(currentWorld->thisID());
2850 
2851 	debugC(3, kDebugSaveload, "... currentWorld->thisID() = %d", currentWorld->thisID());
2852 
2853 	for (int i = 0; i < worldCount; ++i) {
2854 		Sector *sectArray = worldList[i].sectorArray;
2855 		int32 sectorArrayCount = worldList[i].sectorArraySize *
2856 		                         worldList[i].sectorArraySize;
2857 
2858 		out->writeSint16LE(worldList[i].size.u);
2859 		out->writeSint16LE(worldList[i].mapNum);
2860 
2861 		debugC(3, kDebugSaveload, "... worldList[%d].size.u = %d", i, worldList[i].size.u);
2862 		debugC(3, kDebugSaveload, "... worldList[%d].mapNum = %d", i, worldList[i].mapNum);
2863 
2864 		for (int j = 0; j < sectorArrayCount; ++j) {
2865 			sectArray[j].write(out);
2866 			debugC(4, kDebugSaveload, "...... sectArray[%d].activationCount = %d", j, sectArray[j].activationCount);
2867 			debugC(4, kDebugSaveload, "...... sectArray[%d].childID = %d", j, sectArray[j].childID);
2868 		}
2869 	}
2870 	CHUNK_END;
2871 }
2872 
loadWorlds(Common::InSaveFile * in)2873 void loadWorlds(Common::InSaveFile *in) {
2874 	debugC(2, kDebugSaveload, "Loading worlds");
2875 
2876 	ObjectID    currentWorldID;
2877 
2878 	worldList = new GameWorld[worldListSize]();
2879 	if (worldList == nullptr)
2880 		error("Unable to allocate world list");
2881 
2882 	currentWorldID = in->readUint16LE();
2883 
2884 	debugC(3, kDebugSaveload, "... currentWorldID = %d", currentWorldID);
2885 
2886 	for (int i = 0; i < worldCount; ++i) {
2887 		debugC(3, kDebugSaveload, "Loading World %d", i);
2888 
2889 		new (&worldList[i]) GameWorld(in);
2890 
2891 		worldList[i]._index = i + WorldBaseID;
2892 	}
2893 
2894 	//  Reset the current world
2895 	currentWorld = (GameWorld *)GameObject::objectAddress(currentWorldID);
2896 	setCurrentMap(currentWorld->mapNum);
2897 }
2898 
2899 //-------------------------------------------------------------------
2900 //	Cleanup the GameWorld list
2901 
cleanupWorlds(void)2902 void cleanupWorlds(void) {
2903 	for (int i = 0; i < worldCount; i++) {
2904 		GameWorld   *gw = &worldList[i];
2905 
2906 		gw->cleanup();
2907 	}
2908 
2909 	if (worldList != nullptr) {
2910 		delete[] worldList;
2911 		worldList = nullptr;
2912 	}
2913 }
2914 
2915 //-------------------------------------------------------------------
2916 //	Initialize the Objects list
2917 
ResourceGameObject(Common::SeekableReadStream * stream)2918 ResourceGameObject::ResourceGameObject(Common::SeekableReadStream *stream) {
2919 	protoIndex = stream->readSint16LE();
2920 	location.u = stream->readSint16LE();
2921 	location.v = stream->readSint16LE();
2922 	location.z = stream->readSint16LE();
2923 	nameIndex = stream->readUint16LE();
2924 	parentID = stream->readUint16LE();
2925 	script = stream->readUint16LE();
2926 	objectFlags = stream->readUint16LE();
2927 	hitPoints = stream->readByte();
2928 	misc = stream->readUint16LE();
2929 }
2930 
initObjects(void)2931 void initObjects(void) {
2932 	int16 i, resourceObjectCount;
2933 	Common::Array<ResourceGameObject> resourceObjectList;
2934 	Common::SeekableReadStream *stream;
2935 	const int resourceGameObjSize = 19;
2936 
2937 	//  Initialize the limbo counts
2938 	objectLimboCount = 0;
2939 	actorLimboCount = 0;
2940 	importantLimboCount = 0;
2941 
2942 	resourceObjectCount = listRes->size(objListID)
2943 	                      / resourceGameObjSize;
2944 
2945 	if (resourceObjectCount < 4)
2946 		error("Unable to load Objects");
2947 
2948 	//  Allocate memory for the object list
2949 	objectListSize = objectCount * sizeof(GameObject);
2950 	objectList = new GameObject[objectCount]();
2951 
2952 	if (objectList == nullptr)
2953 		error("Unable to load Objects");
2954 
2955 	if ((stream = loadResourceToStream(listRes, objListID, "res object list")) == nullptr)
2956 		error("Unable to load Objects");
2957 
2958 	//  Read the resource Objects
2959 	for (int k = 0; k < resourceObjectCount; ++k) {
2960 		ResourceGameObject res(stream);
2961 		resourceObjectList.push_back(res);
2962 	}
2963 
2964 	delete stream;
2965 
2966 	for (i = 0; i < resourceObjectCount; i++) {
2967 		GameObject  *obj = &objectList[i];
2968 
2969 		if (i < 4)
2970 			//  First four object are limbos, so use the default
2971 			//  constructor
2972 			new (obj) GameObject;
2973 		else
2974 			//  Initialize the objects with the resource data
2975 			new (obj) GameObject(resourceObjectList[i]);
2976 
2977 		objectList[i]._index = i;
2978 	}
2979 
2980 	for (; i < objectCount; i++) {
2981 		GameObject  *obj = &objectList[i];
2982 
2983 		//  Use the default constructor for the extra actors
2984 		new (obj) GameObject;
2985 
2986 		objectList[i]._index = i;
2987 	}
2988 
2989 	//  Go through the object list and initialize all objects.
2990 
2991 	//Add Object To World
2992 	for (i = 0; i < resourceObjectCount; i++) {
2993 		GameObject  *obj = &objectList[i],
2994 		             *parent;
2995 		TilePoint   slot;
2996 
2997 		//skip linking the first four into chain since they are in limbo
2998 
2999 		if (i < 4)
3000 			continue;
3001 
3002 		//  Objects which are inside other objects need to have their
3003 		//  Z-coords initially forced to be 1 so that stacking works OK.
3004 		if (!isWorld(obj->_data.parentID)) obj->_data.location.z = 1;
3005 
3006 		parent = GameObject::objectAddress(obj->_data.parentID);
3007 		if (parent->getAvailableSlot(obj, &slot))
3008 			obj->move(Location(slot, obj->_data.parentID));
3009 
3010 		//  Add object to world.
3011 		if (obj->_data.parentID == Nothing) {
3012 			obj->append(ObjectLimbo);
3013 			obj->_data.parentID = ObjectLimbo;
3014 			objectLimboCount++;
3015 		} else
3016 			obj->append(obj->_data.parentID);
3017 	}
3018 
3019 	for (; i < objectCount; i++) {
3020 		GameObject  *obj = &objectList[i];
3021 
3022 		obj->_data.siblingID = obj->_data.childID = Nothing;
3023 		obj->append(ObjectLimbo);
3024 		obj->_data.parentID = ObjectLimbo;
3025 		objectLimboCount++;
3026 	}
3027 
3028 	//  Make a pass over the actor list appending each actor to their
3029 	//  parent's child list
3030 	for (i = 0; i < kActorCount; i++) {
3031 		Actor       *a = g_vm->_act->_actorList[i];
3032 
3033 		if (a->_data.parentID == Nothing) {
3034 			a->append(ActorLimbo);
3035 			actorLimboCount++;
3036 		} else
3037 			a->append(a->_data.parentID);
3038 	}
3039 
3040 #if DEBUG
3041 	massAndBulkCount = GetPrivateProfileInt("Debug", "MassAndBulkCount", 1, iniFile);
3042 #endif
3043 }
3044 
saveObjects(Common::OutSaveFile * outS)3045 void saveObjects(Common::OutSaveFile *outS) {
3046 	outS->write("OBJS", 4);
3047 	CHUNK_BEGIN;
3048 	//  Store the limbo counts
3049 	out->writeSint16LE(objectLimboCount);
3050 	out->writeSint16LE(actorLimboCount);
3051 	out->writeSint16LE(importantLimboCount);
3052 
3053 	//  Store the object list
3054 	for (int i = 0; i < objectCount; i++) {
3055 		objectList[i].write(out, true);
3056 		out->writeUint16LE(0); // reserved bits
3057 	}
3058 	CHUNK_END;
3059 }
3060 
loadObjects(Common::InSaveFile * in)3061 void loadObjects(Common::InSaveFile *in) {
3062 	//  Restore the limbo counts
3063 	objectLimboCount = in->readSint16LE();
3064 	actorLimboCount = in->readSint16LE();
3065 	importantLimboCount = in->readSint16LE();
3066 
3067 	objectList = new GameObject[objectCount]();
3068 	if (objectList == nullptr)
3069 		error("Unable to load Objects");
3070 
3071 	for (int i = 0; i < objectCount; i++) {
3072 		debugC(3, kDebugSaveload, "Loading object %d", i);
3073 
3074 		objectList[i].read(in, true);
3075 		in->readUint16LE();
3076 		objectList[i]._index = i;
3077 	}
3078 }
3079 
3080 //-------------------------------------------------------------------
3081 //	Cleanup object list
3082 
cleanupObjects(void)3083 void cleanupObjects(void) {
3084 	if (objectList != nullptr)
3085 		delete[] objectList;
3086 	g_vm->_mainDisplayList->reset();
3087 }
3088 
setCurrentWorld(ObjectID worldID)3089 void setCurrentWorld(ObjectID worldID) {
3090 	if (!isWorld(worldID)) {
3091 		error("Cannot set current world to non-world object.\n");
3092 	}
3093 
3094 	currentWorld = (GameWorld *)GameObject::objectAddress(worldID);
3095 }
3096 
3097 //-------------------------------------------------------------------
3098 //	Return the coordinates of the currently viewed object
3099 
getViewTrackPos(TilePoint & tp)3100 void getViewTrackPos(TilePoint &tp) {
3101 	if (viewCenterObject != Nothing) {
3102 		GameObject  *obj = GameObject::objectAddress(viewCenterObject);
3103 
3104 		tp = obj->getLocation();
3105 	}
3106 }
3107 
3108 //-------------------------------------------------------------------
3109 //	Return a pointer to the currently viewed object
3110 
getViewCenterObject(void)3111 GameObject *getViewCenterObject(void) {
3112 	return  viewCenterObject != Nothing
3113 	        ?   GameObject::objectAddress(viewCenterObject)
3114 	        :   nullptr;
3115 }
3116 
3117 /* ======================================================================= *
3118    Sector member functions
3119  * ======================================================================= */
3120 
3121 //-------------------------------------------------------------------
3122 //	Activate all actors in sector if sector is not alreay active
3123 
activate(void)3124 void Sector::activate(void) {
3125 	if (activationCount++ == 0) {
3126 		ObjectID        id = childID;
3127 
3128 		while (id != Nothing) {
3129 			GameObject      *obj = GameObject::objectAddress(id);
3130 
3131 			obj->activate();
3132 
3133 			id = obj->IDNext();
3134 		}
3135 	}
3136 }
3137 
3138 //-------------------------------------------------------------------
3139 //	Decrement the activation count of the sector and deactivate all
3140 //	actors in sector if activation count has reached zero.
3141 
deactivate(void)3142 void Sector::deactivate(void) {
3143 	assert(activationCount != 0);
3144 
3145 	activationCount--;
3146 }
3147 
read(Common::InSaveFile * in)3148 void Sector::read(Common::InSaveFile *in) {
3149 	activationCount = in->readUint16LE();
3150 	childID = in->readUint16LE();
3151 }
3152 
write(Common::MemoryWriteStreamDynamic * out)3153 void Sector::write(Common::MemoryWriteStreamDynamic *out) {
3154 	out->writeUint16LE(activationCount);
3155 	out->writeUint16LE(childID);
3156 }
3157 
3158 
3159 /* ======================================================================= *
3160    ActiveRegion member functions
3161  * ======================================================================= */
3162 
3163 //-------------------------------------------------------------------
3164 //	Update this active region
3165 
read(Common::InSaveFile * in)3166 void ActiveRegion::read(Common::InSaveFile *in) {
3167 	anchor = in->readUint16LE();
3168 	anchorLoc.load(in);
3169 	worldID = in->readUint16LE();
3170 	region.read(in);
3171 
3172 	debugC(4, kDebugSaveload, "... anchor = %d", anchor);
3173 	debugC(4, kDebugSaveload, "... anchorLoc = (%d, %d, %d)", anchorLoc.u, anchorLoc.v, anchorLoc.z);
3174 	debugC(4, kDebugSaveload, "... worldID = %d", worldID);
3175 	debugC(4, kDebugSaveload, "... region = (min: (%d, %d, %d), max: (%d, %d, %d))",
3176 	       region.min.u, region.min.v, region.min.z, region.max.u, region.max.v, region.max.z);
3177 }
3178 
write(Common::MemoryWriteStreamDynamic * out)3179 void ActiveRegion::write(Common::MemoryWriteStreamDynamic *out) {
3180 	out->writeUint16LE(anchor);
3181 	anchorLoc.write(out);
3182 	out->writeUint16LE(worldID);
3183 	region.write(out);
3184 
3185 	debugC(4, kDebugSaveload, "... anchor = %d", anchor);
3186 	debugC(4, kDebugSaveload, "... anchorLoc = (%d, %d, %d)", anchorLoc.u, anchorLoc.v, anchorLoc.z);
3187 	debugC(4, kDebugSaveload, "... worldID = %d", worldID);
3188 	debugC(4, kDebugSaveload, "... region = (min: (%d, %d, %d), max: (%d, %d, %d))",
3189 	       region.min.u, region.min.v, region.min.z, region.max.u, region.max.v, region.max.z);
3190 }
3191 
update(void)3192 void ActiveRegion::update(void) {
3193 	GameObject  *obj = GameObject::objectAddress(anchor);
3194 	GameWorld   *world = (GameWorld *)GameObject::objectAddress(worldID);
3195 	ObjectID    objWorldID = obj->world()->thisID();
3196 
3197 	//  Determine if the world for this active region has changed
3198 	if (worldID != objWorldID) {
3199 		int16   u, v;
3200 
3201 		//  Deactivate all of the old sectors
3202 		for (u = region.min.u; u < region.max.u; u++) {
3203 			for (v = region.min.v; v < region.max.v; v++) {
3204 				world->getSector(u, v)->deactivate();
3205 			}
3206 		}
3207 
3208 		//  Initialize active region for new world
3209 		worldID = objWorldID;
3210 		world = (GameWorld *)GameObject::objectAddress(worldID);
3211 		anchorLoc = Nowhere;
3212 		region.min = Nowhere;
3213 		region.max = Nowhere;
3214 	}
3215 
3216 	TilePoint   loc = obj->getLocation();
3217 
3218 	//  Determine if anchor has moved since the last time
3219 	if (loc != anchorLoc) {
3220 		TileRegion  ptRegion,
3221 		            newRegion;
3222 
3223 		//  Update the anchor _data.location
3224 		anchorLoc = loc;
3225 
3226 		//  Determine the active region in points
3227 		ptRegion.min.u = loc.u - kSectorSize / 2;
3228 		ptRegion.min.v = loc.v - kSectorSize / 2;
3229 		ptRegion.max.u = ptRegion.min.u + kSectorSize;
3230 		ptRegion.max.v = ptRegion.min.v + kSectorSize;
3231 
3232 		//  Convert to sector coordinates
3233 		newRegion.min.u = ptRegion.min.u >> kSectorShift;
3234 		newRegion.min.v = ptRegion.min.v >> kSectorShift;
3235 		newRegion.max.u = (ptRegion.max.u + kSectorMask) >> kSectorShift;
3236 		newRegion.max.v = (ptRegion.max.v + kSectorMask) >> kSectorShift;
3237 
3238 		if (region.min.u != newRegion.min.u
3239 		        ||  region.min.v != newRegion.min.v
3240 		        ||  region.max.u != newRegion.max.u
3241 		        ||  region.max.v != newRegion.max.v) {
3242 			int16   u, v;
3243 
3244 			//  Deactivate all sectors from the old region which are
3245 			//  not in the new region
3246 			for (u = region.min.u; u < region.max.u; u++) {
3247 				bool    uOutOfRange;
3248 
3249 				uOutOfRange = u < newRegion.min.u || u >= newRegion.max.u;
3250 
3251 				for (v = region.min.v; v < region.max.v; v++) {
3252 					if (uOutOfRange
3253 					        ||  v < newRegion.min.v
3254 							||  v >= newRegion.max.v) {
3255 
3256 						if(Sector *sect = world->getSector(u, v))
3257 							sect->deactivate();
3258 						else
3259 							warning("ActiveRegion::update: Invalid Sector (%d, %d)", u, v);
3260 					}
3261 				}
3262 			}
3263 
3264 			//  Activate all sectors in the new region which were not
3265 			//  in the old region
3266 			for (u = newRegion.min.u; u < newRegion.max.u; u++) {
3267 				bool    uOutOfRange;
3268 
3269 				uOutOfRange = u < region.min.u || u >= region.max.u;
3270 
3271 				for (v = newRegion.min.v; v < newRegion.max.v; v++) {
3272 					if (uOutOfRange
3273 					        ||  v < region.min.v
3274 							||  v >= region.max.v) {
3275 
3276 						if(Sector *sect = world->getSector(u, v))
3277 							sect->activate();
3278 						else
3279 							warning("ActiveRegion::update: Invalid Sector (%d, %d)", u, v);
3280 					}
3281 				}
3282 			}
3283 
3284 			//  Update the region coordinates
3285 			region.min.u = newRegion.min.u;
3286 			region.min.v = newRegion.min.v;
3287 			region.max.u = newRegion.max.u;
3288 			region.max.v = newRegion.max.v;
3289 		}
3290 	}
3291 }
3292 
3293 /* ======================================================================= *
3294    ActiveRegion array
3295  * ======================================================================= */
3296 
3297 //-------------------------------------------------------------------
3298 //	Iterate through the active regions, updating each
3299 
updateActiveRegions(void)3300 void updateActiveRegions(void) {
3301 	int16   i;
3302 
3303 	for (i = 0; i < kPlayerActors; i++)
3304 		g_vm->_activeRegionList[i].update();
3305 }
3306 
3307 //-------------------------------------------------------------------
3308 //	Return a pointer to an active region given its PlayerActor's ID
3309 
getActiveRegion(PlayerActorID id)3310 ActiveRegion *getActiveRegion(PlayerActorID id) {
3311 	return &g_vm->_activeRegionList[id];
3312 }
3313 
3314 //-------------------------------------------------------------------
3315 //	Initialize the state of the active regions
3316 
initActiveRegions(void)3317 void initActiveRegions(void) {
3318 	static PlayerActorID    playerIDArray[kPlayerActors] =
3319 	{ FTA_JULIAN, FTA_PHILIP, FTA_KEVIN };
3320 
3321 	int16   i;
3322 
3323 	for (i = 0; i < kPlayerActors; i++) {
3324 		ActiveRegion    *reg = &g_vm->_activeRegionList[i];
3325 		ObjectID        actorID = getPlayerActorAddress(playerIDArray[i])->getActorID();
3326 
3327 		reg->anchor = actorID;
3328 		reg->anchorLoc = Nowhere;
3329 		reg->worldID = Nothing;
3330 		reg->region.min = Nowhere;
3331 		reg->region.max = Nowhere;
3332 	}
3333 }
3334 
saveActiveRegions(Common::OutSaveFile * outS)3335 void saveActiveRegions(Common::OutSaveFile *outS) {
3336 	debugC(2, kDebugSaveload, "Saving ActiveRegions");
3337 
3338 	outS->write("AREG", 4);
3339 	CHUNK_BEGIN;
3340 	for (int i = 0; i < kPlayerActors; ++i) {
3341 		debugC(3, kDebugSaveload, "Saving Active Region %d", i);
3342 		g_vm->_activeRegionList[i].write(out);
3343 	}
3344 	CHUNK_END;
3345 }
3346 
loadActiveRegions(Common::InSaveFile * in)3347 void loadActiveRegions(Common::InSaveFile *in) {
3348 	debugC(2, kDebugSaveload, "Loading ActiveRegions");
3349 
3350 	for (int i = 0; i < kPlayerActors; ++i) {
3351 		debugC(3, kDebugSaveload, "Loading Active Region %d", i);
3352 		g_vm->_activeRegionList[i].read(in);
3353 	}
3354 }
3355 
3356 /* ======================================================================= *
3357    SectorRegionObjectIterator member functions
3358  * ======================================================================= */
3359 
3360 //------------------------------------------------------------------------
3361 //	Constructor
3362 
SectorRegionObjectIterator(GameWorld * world)3363 SectorRegionObjectIterator::SectorRegionObjectIterator(GameWorld *world) :
3364 	searchWorld(world), _currentObject(nullptr) {
3365 	assert(searchWorld != nullptr);
3366 	assert(isWorld(searchWorld));
3367 
3368 	minSector = TilePoint(0, 0, 0);
3369 	maxSector = searchWorld->sectorSize();
3370 }
3371 
3372 //------------------------------------------------------------------------
3373 //	Initialize the object iterator and return the first object found
3374 
first(GameObject ** obj)3375 ObjectID SectorRegionObjectIterator::first(GameObject **obj) {
3376 	Sector      *currentSector;
3377 
3378 	_currentObject = nullptr;
3379 
3380 	sectorCoords = minSector;
3381 	currentSector = searchWorld->getSector(sectorCoords.u, sectorCoords.v);
3382 
3383 	if (currentSector == nullptr)
3384 		return Nothing;
3385 
3386 	while (currentSector->childID == Nothing) {
3387 		if (++sectorCoords.v >= maxSector.v) {
3388 			sectorCoords.v = minSector.v;
3389 			if (++sectorCoords.u >= maxSector.u) {
3390 				if (obj != nullptr) *obj = nullptr;
3391 				return Nothing;
3392 			}
3393 		}
3394 
3395 		currentSector = searchWorld->getSector(
3396 		                    sectorCoords.u,
3397 		                    sectorCoords.v);
3398 	}
3399 
3400 	_currentObject = GameObject::objectAddress(currentSector->childID);
3401 
3402 	if (obj != nullptr) *obj = _currentObject;
3403 	return currentSector->childID;
3404 }
3405 
3406 //------------------------------------------------------------------------
3407 //	Return the next object found
3408 
next(GameObject ** obj)3409 ObjectID SectorRegionObjectIterator::next(GameObject **obj) {
3410 	assert(sectorCoords.u >= minSector.u);
3411 	assert(sectorCoords.v >= minSector.v);
3412 	assert(sectorCoords.u < maxSector.u);
3413 	assert(sectorCoords.v < maxSector.v);
3414 
3415 	ObjectID        currentObjectID;
3416 
3417 	currentObjectID = _currentObject->IDNext();
3418 
3419 	while (currentObjectID == Nothing) {
3420 		Sector      *currentSector;
3421 
3422 		do {
3423 			if (++sectorCoords.v >= maxSector.v) {
3424 				sectorCoords.v = minSector.v;
3425 				if (++sectorCoords.u >= maxSector.u) {
3426 					if (obj != nullptr) *obj = nullptr;
3427 					return Nothing;
3428 				}
3429 			}
3430 
3431 			currentSector = searchWorld->getSector(
3432 			                    sectorCoords.u,
3433 			                    sectorCoords.v);
3434 
3435 		} while (currentSector->childID == Nothing);
3436 
3437 		currentObjectID = currentSector->childID;
3438 	}
3439 
3440 	_currentObject = GameObject::objectAddress(currentObjectID);
3441 
3442 	if (obj != nullptr) *obj = _currentObject;
3443 	return currentObjectID;
3444 }
3445 
3446 /* ======================================================================= *
3447    RadialObjectIterator member functions
3448  * ======================================================================= */
3449 
3450 //------------------------------------------------------------------------
3451 //	Compute the sector region overlaying the specified radial region
3452 
computeSectorRegion(const TilePoint & sectors,const TilePoint & center,int16 radius)3453 TileRegion RadialObjectIterator::computeSectorRegion(
3454     const TilePoint &sectors,
3455     const TilePoint &center,
3456     int16           radius) {
3457 	TileRegion      sectorRegion;
3458 
3459 	sectorRegion.min.u =    clamp(
3460 	                            0,
3461 	                            (center.u - radius) >> kSectorShift,
3462 	                            sectors.u);
3463 	sectorRegion.min.v =    clamp(
3464 	                            0,
3465 	                            (center.v - radius) >> kSectorShift,
3466 	                            sectors.v);
3467 	sectorRegion.max.u =    clamp(
3468 	                            0,
3469 	                            (center.u + radius + kSectorMask) >> kSectorShift,
3470 	                            sectors.u);
3471 	sectorRegion.max.v =    clamp(
3472 	                            0,
3473 	                            (center.v + radius + kSectorMask) >> kSectorShift,
3474 	                            sectors.v);
3475 	sectorRegion.min.z = sectorRegion.max.z = 0;
3476 
3477 	return sectorRegion;
3478 }
3479 
3480 //------------------------------------------------------------------------
3481 //	Return the first object within the specified region
3482 
first(GameObject ** obj,int16 * dist)3483 ObjectID RadialObjectIterator::first(GameObject **obj, int16 *dist) {
3484 	GameObject      *currentObject = nullptr;
3485 	ObjectID        currentObjectID;
3486 	int16           currentDist = 0;
3487 
3488 	currentObjectID = SectorRegionObjectIterator::first(&currentObject);
3489 	while (currentObjectID != Nothing
3490 	        && (currentDist =
3491 	                computeDist(currentObject->getLocation()))
3492 	        >   radius) {
3493 		currentObjectID = SectorRegionObjectIterator::next(&currentObject);
3494 	}
3495 
3496 	if (dist != nullptr) *dist = currentDist;
3497 	if (obj != nullptr) *obj = currentObject;
3498 	return currentObjectID;
3499 }
3500 
3501 //------------------------------------------------------------------------
3502 //	Return the next object within the specified region
3503 
next(GameObject ** obj,int16 * dist)3504 ObjectID RadialObjectIterator::next(GameObject **obj, int16 *dist) {
3505 	GameObject      *currentObject = nullptr;
3506 	ObjectID        currentObjectID;
3507 	int16           currentDist = 0;
3508 
3509 	do {
3510 		currentObjectID = SectorRegionObjectIterator::next(&currentObject);
3511 	} while (currentObjectID != Nothing
3512 	         && (currentDist = computeDist(currentObject->getLocation())) > radius);
3513 
3514 	if (dist != nullptr)
3515 		*dist = currentDist;
3516 	if (obj != nullptr)
3517 		*obj = currentObject;
3518 	return currentObjectID;
3519 }
3520 
3521 /* ======================================================================= *
3522    CircularObjectIterator member functions
3523  * ======================================================================= */
3524 
3525 //------------------------------------------------------------------------
3526 //	Compute the distance to the specified point from the center coordinates
3527 
computeDist(const TilePoint & tp)3528 int16 CircularObjectIterator::computeDist(const TilePoint &tp) {
3529 	//  Simply use quickHDistance()
3530 	return (tp - getCenter()).quickHDistance();
3531 }
3532 
3533 /* ======================================================================= *
3534    RingObjectIterator member functions
3535  * ======================================================================= */
3536 
first(GameObject ** obj)3537 ObjectID RingObjectIterator::first(GameObject **obj) {
3538 	GameObject      *currentObject;
3539 	ObjectID        currentObjectID;
3540 
3541 	currentObjectID = CircularObjectIterator::first(&currentObject);
3542 	while (currentObjectID != Nothing
3543 	        &&  computeDist(currentObject->getLocation()) < innerDist) {
3544 		currentObjectID = CircularObjectIterator::next(&currentObject);
3545 	}
3546 
3547 	if (obj != nullptr) *obj = currentObject;
3548 	return currentObjectID;
3549 
3550 }
3551 
next(GameObject ** obj)3552 ObjectID RingObjectIterator::next(GameObject **obj) {
3553 	GameObject      *currentObject;
3554 	ObjectID        currentObjectID;
3555 
3556 	do {
3557 		currentObjectID = CircularObjectIterator::next(&currentObject);
3558 	} while (currentObjectID != Nothing
3559 	         &&  computeDist(currentObject->getLocation()) < innerDist);
3560 
3561 	if (obj != nullptr) *obj = currentObject;
3562 	return currentObjectID;
3563 }
3564 
3565 
3566 /* ======================================================================= *
3567    DispRegionObjectIterator member functions
3568  * ======================================================================= */
3569 
3570 //------------------------------------------------------------------------
3571 //	Compute the distance to the specified point from the center coordinates
3572 
computeDist(const TilePoint & tp)3573 int16 DispRegionObjectIterator::computeDist(const TilePoint &tp) {
3574 	//  Compute distance from object to screen center.
3575 	//  REM: remember to add in Z there somewhere.
3576 	return  ABS(getCenter().u - tp.u)
3577 	        +  ABS(getCenter().v - tp.v);
3578 }
3579 
3580 /* ======================================================================= *
3581    RegionalObjectIterator member functions
3582  * ======================================================================= */
3583 
3584 //------------------------------------------------------------------------
3585 //	Compute the sector region overlaying the specified tilepoint region
3586 
computeSectorRegion(const TilePoint & sectors,const TilePoint & min,const TilePoint & max)3587 TileRegion RegionalObjectIterator::computeSectorRegion(
3588     const TilePoint &sectors,
3589     const TilePoint &min,
3590     const TilePoint &max) {
3591 	TileRegion  sectorRegion;
3592 
3593 	sectorRegion.min.u =    clamp(
3594 	                            0,
3595 	                            min.u >> kSectorShift,
3596 	                            sectors.u);
3597 	sectorRegion.min.v =    clamp(
3598 	                            0,
3599 	                            min.v >> kSectorShift,
3600 	                            sectors.v);
3601 	sectorRegion.max.u =    clamp(
3602 	                            0,
3603 	                            (max.u + kSectorMask) >> kSectorShift,
3604 	                            sectors.u);
3605 	sectorRegion.max.v =    clamp(
3606 	                            0,
3607 	                            (max.v + kSectorMask) >> kSectorShift,
3608 	                            sectors.v);
3609 	sectorRegion.min.z = sectorRegion.max.z = 0;
3610 
3611 	return sectorRegion;
3612 }
3613 
3614 //------------------------------------------------------------------------
3615 //	Determine if the specified point is within the region
3616 
inRegion(const TilePoint & tp)3617 inline bool RegionalObjectIterator::inRegion(const TilePoint &tp) {
3618 	return      tp.u >= minCoords.u
3619 	            &&  tp.v >= minCoords.v
3620 	            &&  tp.u < maxCoords.u
3621 	            &&  tp.v < maxCoords.v;
3622 }
3623 
3624 //------------------------------------------------------------------------
3625 //	Return the first object within the specified region
3626 
first(GameObject ** obj)3627 ObjectID RegionalObjectIterator::first(GameObject **obj) {
3628 	GameObject      *currentObject;
3629 	ObjectID        currentObjectID;
3630 
3631 	currentObjectID = SectorRegionObjectIterator::first(&currentObject);
3632 
3633 	if (currentObjectID == Nothing)
3634 		return Nothing;
3635 
3636 	while (currentObjectID != Nothing
3637 	        &&  !inRegion(currentObject->getLocation())) {
3638 		currentObjectID = SectorRegionObjectIterator::next(&currentObject);
3639 	}
3640 
3641 	if (obj != nullptr)
3642 		*obj = currentObject;
3643 
3644 	return currentObjectID;
3645 }
3646 
3647 //------------------------------------------------------------------------
3648 //	Return the next object within the specified region
3649 
next(GameObject ** obj)3650 ObjectID RegionalObjectIterator::next(GameObject **obj) {
3651 	GameObject      *currentObject;
3652 	ObjectID        currentObjectID;
3653 
3654 	do {
3655 		currentObjectID = SectorRegionObjectIterator::next(&currentObject);
3656 	} while (currentObjectID != Nothing
3657 	         &&  !inRegion(currentObject->getLocation()));
3658 
3659 	if (obj != nullptr) *obj = currentObject;
3660 	return currentObjectID;
3661 }
3662 
3663 /* ======================================================================= *
3664    RectangularObjectIterator member functions
3665  * ======================================================================= */
3666 
3667 //  Constructor
RectangularObjectIterator(GameWorld * world,const TilePoint & c,const TilePoint & cdelta1,const TilePoint & cdelta2)3668 RectangularObjectIterator::RectangularObjectIterator(
3669 	GameWorld *world,
3670 	const TilePoint &c,
3671 	const TilePoint &cdelta1,
3672 	const TilePoint &cdelta2) :
3673 	RegionalObjectIterator(
3674 		world,
3675 		MinTilePoint(c, c + cdelta1, c + cdelta2, c + cdelta1 + cdelta2),
3676 		MaxTilePoint(c, c + cdelta1, c + cdelta2, c + cdelta1 + cdelta2)),
3677 	coords1(c),
3678 	coords2(c + cdelta1),
3679 	coords3(c + cdelta1 + cdelta2),
3680 	coords4(c + cdelta2),
3681 	center((c + (cdelta1 + cdelta2) / 2)) {
3682 }
3683 
3684 //------------------------------------------------------------------------
3685 //	Determine if the specified point is within the region
3686 
inRegion(const TilePoint & tp)3687 inline bool RectangularObjectIterator::inRegion(const TilePoint &tp) {
3688 	return  sameSide(coords1, coords2, center, tp) &&
3689 	        sameSide(coords2, coords3, center, tp) &&
3690 	        sameSide(coords3, coords4, center, tp) &&
3691 	        sameSide(coords4, coords1, center, tp);
3692 }
3693 
3694 //------------------------------------------------------------------------
3695 //	Return the first object within the specified region
3696 
first(GameObject ** obj)3697 ObjectID RectangularObjectIterator::first(GameObject **obj) {
3698 	GameObject      *currentObject;
3699 	ObjectID        currentObjectID;
3700 
3701 	currentObjectID = RegionalObjectIterator::first(&currentObject);
3702 	while (currentObjectID != Nothing
3703 	        &&  !inRegion(currentObject->getLocation())) {
3704 		currentObjectID = RegionalObjectIterator::next(&currentObject);
3705 	}
3706 
3707 	if (obj != nullptr) *obj = currentObject;
3708 	return currentObjectID;
3709 }
3710 
3711 //------------------------------------------------------------------------
3712 //	Return the next object within the specified region
3713 
next(GameObject ** obj)3714 ObjectID RectangularObjectIterator::next(GameObject **obj) {
3715 	GameObject      *currentObject;
3716 	ObjectID        currentObjectID;
3717 
3718 	do {
3719 		currentObjectID = RegionalObjectIterator::next(&currentObject);
3720 	} while (currentObjectID != Nothing
3721 	         &&  !inRegion(currentObject->getLocation()));
3722 
3723 	if (obj != nullptr) *obj = currentObject;
3724 	return currentObjectID;
3725 }
3726 
3727 
3728 
3729 /* ======================================================================= *
3730    TriangularObjectIterator member functions
3731  * ======================================================================= */
3732 
3733 //  Constructor
TriangularObjectIterator(GameWorld * world,const TilePoint & c1,const TilePoint & c2,const TilePoint & c3)3734 TriangularObjectIterator::TriangularObjectIterator(
3735 	GameWorld *world,
3736 	const TilePoint &c1,
3737 	const TilePoint &c2,
3738 	const TilePoint &c3) :
3739 	RegionalObjectIterator(
3740 		world,
3741 		MinTilePoint(c1, c2, c3),
3742 		MaxTilePoint(c1, c2, c3)),
3743 	coords1(c1),
3744 	coords2(c2),
3745 	coords3(c3) {
3746 }
3747 
3748 //------------------------------------------------------------------------
3749 //	Determine if the specified point is within the region
3750 
inRegion(const TilePoint & tp)3751 inline bool TriangularObjectIterator::inRegion(const TilePoint &tp) {
3752 	return  sameSide(coords1, coords2, coords3, tp) &&
3753 	        sameSide(coords1, coords3, coords2, tp) &&
3754 	        sameSide(coords2, coords3, coords1, tp) ;
3755 }
3756 
3757 //------------------------------------------------------------------------
3758 //	Return the first object within the specified region
3759 
first(GameObject ** obj)3760 ObjectID TriangularObjectIterator::first(GameObject **obj) {
3761 	GameObject      *currentObject;
3762 	ObjectID        currentObjectID;
3763 
3764 	currentObjectID = RegionalObjectIterator::first(&currentObject);
3765 	while (currentObjectID != Nothing
3766 	        &&  !inRegion(currentObject->getLocation())) {
3767 		currentObjectID = RegionalObjectIterator::next(&currentObject);
3768 	}
3769 
3770 	if (obj != nullptr) *obj = currentObject;
3771 	return currentObjectID;
3772 }
3773 
3774 //------------------------------------------------------------------------
3775 //	Return the next object within the specified region
3776 
next(GameObject ** obj)3777 ObjectID TriangularObjectIterator::next(GameObject **obj) {
3778 	GameObject      *currentObject;
3779 	ObjectID        currentObjectID;
3780 
3781 	do {
3782 		currentObjectID = RegionalObjectIterator::next(&currentObject);
3783 	} while (currentObjectID != Nothing
3784 	         &&  !inRegion(currentObject->getLocation()));
3785 
3786 	if (obj != nullptr) *obj = currentObject;
3787 	return currentObjectID;
3788 }
3789 
3790 /* ======================================================================= *
3791    CenterRegionObjectIterator member functions
3792  * ======================================================================= */
3793 
3794 //------------------------------------------------------------------------
3795 
CenterWorld(void)3796 GameWorld *CenterRegionObjectIterator::CenterWorld(void) {
3797 	ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID());
3798 	return ar->getWorld();
3799 }
3800 
MinCenterRegion(void)3801 TilePoint CenterRegionObjectIterator::MinCenterRegion(void) {
3802 	ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID());
3803 	return ar->getRegion().min;
3804 }
3805 
MaxCenterRegion(void)3806 TilePoint CenterRegionObjectIterator::MaxCenterRegion(void) {
3807 	ActiveRegion *ar = getActiveRegion(getCenterActorPlayerID());
3808 	return ar->getRegion().max;
3809 }
3810 
3811 
3812 /* ======================================================================= *
3813    ActiveRegionObjectIterator member functions
3814  * ======================================================================= */
3815 
3816 //------------------------------------------------------------------------
3817 
firstActiveRegion(void)3818 bool ActiveRegionObjectIterator::firstActiveRegion(void) {
3819 	activeRegionIndex = -1;
3820 
3821 	return nextActiveRegion();
3822 }
3823 
3824 //------------------------------------------------------------------------
3825 
nextActiveRegion(void)3826 bool ActiveRegionObjectIterator::nextActiveRegion(void) {
3827 	int16               currentRegionSectors;
3828 	ActiveRegion        *currentRegion;
3829 	TilePoint           currentRegionSize;
3830 
3831 	do {
3832 		if (++activeRegionIndex >= kPlayerActors)
3833 			return false;
3834 
3835 		int16               prevRegionIndex;
3836 
3837 		currentRegion = &g_vm->_activeRegionList[activeRegionIndex];
3838 
3839 		sectorBitMask = 0;
3840 		currentRegionSize.u =       currentRegion->region.max.u
3841 		                            -   currentRegion->region.min.u;
3842 		currentRegionSize.v =       currentRegion->region.max.v
3843 		                            -   currentRegion->region.min.v;
3844 		currentRegionSectors = currentRegionSize.u * currentRegionSize.v;
3845 
3846 		for (prevRegionIndex = 0;
3847 		        prevRegionIndex < activeRegionIndex;
3848 		        prevRegionIndex++) {
3849 			ActiveRegion    *prevRegion;
3850 
3851 			prevRegion = &g_vm->_activeRegionList[prevRegionIndex];
3852 
3853 			//  Determine if the current region and the previous region
3854 			//  overlap.
3855 			if (currentRegion->worldID != prevRegion->worldID
3856 			        ||  prevRegion->region.min.u >= currentRegion->region.max.u
3857 			        ||  currentRegion->region.min.u >= prevRegion->region.max.u
3858 			        ||  prevRegion->region.min.v >= currentRegion->region.max.v
3859 			        ||  currentRegion->region.min.v >= prevRegion->region.max.v)
3860 				continue;
3861 
3862 			TileRegion      intersection;
3863 			int16           u, v;
3864 
3865 			intersection.min.u =    MAX(
3866 			                            currentRegion->region.min.u,
3867 			                            prevRegion->region.min.u)
3868 			                        -   currentRegion->region.min.u;
3869 			intersection.max.u =    MIN(
3870 			                            currentRegion->region.max.u,
3871 			                            prevRegion->region.max.u)
3872 			                        -   currentRegion->region.min.u;
3873 			intersection.min.v =    MAX(
3874 			                            currentRegion->region.min.v,
3875 			                            prevRegion->region.min.v)
3876 			                        -   currentRegion->region.min.v;
3877 			intersection.max.v =    MIN(
3878 			                            currentRegion->region.max.v,
3879 			                            prevRegion->region.max.v)
3880 			                        -   currentRegion->region.min.v;
3881 
3882 			for (u = intersection.min.u;
3883 			        u < intersection.max.u;
3884 			        u++) {
3885 				for (v = intersection.min.v;
3886 				        v < intersection.max.v;
3887 				        v++) {
3888 					uint8       sectorBit;
3889 
3890 					sectorBit = 1 << (u * currentRegionSize.v + v);
3891 
3892 					if (!(sectorBitMask & sectorBit)) {
3893 						currentRegionSectors--;
3894 						assert(currentRegionSectors >= 0);
3895 
3896 						//  Set the bit in the bit mask indicating that this
3897 						//  sector overlaps with a previouse active region
3898 						sectorBitMask |= sectorBit;
3899 					}
3900 				}
3901 			}
3902 
3903 			//  If all of the current regions sectors are intersecting
3904 			//  with previous active regions there is no need to check
3905 			//  any further
3906 			if (currentRegionSectors == 0) break;
3907 		}
3908 
3909 	} while (currentRegionSectors == 0);
3910 
3911 	baseSectorCoords.u = currentRegion->region.min.u;
3912 	baseSectorCoords.v = currentRegion->region.min.v;
3913 	size.u = currentRegionSize.u;
3914 	size.v = currentRegionSize.v;
3915 	currentWorld = (GameWorld *)GameObject::objectAddress(
3916 	                   currentRegion->worldID);
3917 
3918 	return true;
3919 }
3920 
3921 //------------------------------------------------------------------------
3922 
firstSector(void)3923 bool ActiveRegionObjectIterator::firstSector(void) {
3924 	if (!firstActiveRegion())
3925 		return false;
3926 
3927 	sectorCoords.u = baseSectorCoords.u;
3928 	sectorCoords.v = baseSectorCoords.v;
3929 
3930 	if (sectorBitMask & 1) {
3931 		if (!nextSector())
3932 			return false;
3933 	}
3934 
3935 	return true;
3936 }
3937 
3938 //------------------------------------------------------------------------
3939 
nextSector(void)3940 bool ActiveRegionObjectIterator::nextSector(void) {
3941 	int16       u, v;
3942 
3943 	do {
3944 		sectorCoords.v++;
3945 
3946 		if (sectorCoords.v >= baseSectorCoords.v + size.v) {
3947 			sectorCoords.v = baseSectorCoords.v;
3948 			sectorCoords.u++;
3949 
3950 			if (sectorCoords.u >= baseSectorCoords.u + size.u) {
3951 				if (!nextActiveRegion()) return false;
3952 
3953 				sectorCoords.u = baseSectorCoords.u;
3954 				sectorCoords.v = baseSectorCoords.v;
3955 			}
3956 		}
3957 
3958 		u = sectorCoords.u - baseSectorCoords.u;
3959 		v = sectorCoords.v - baseSectorCoords.v;
3960 	} while (sectorBitMask & (1 << (u * size.v + v)));
3961 
3962 	return true;
3963 }
3964 
3965 //------------------------------------------------------------------------
3966 //	Return the first object within the specified region
3967 
first(GameObject ** obj)3968 ObjectID ActiveRegionObjectIterator::first(GameObject **obj) {
3969 	ObjectID        currentObjectID = Nothing;
3970 
3971 	_currentObject = nullptr;
3972 
3973 	if (firstSector()) {
3974 		Sector      *currentSector;
3975 
3976 		currentSector = currentWorld->getSector(
3977 		                    sectorCoords.u,
3978 		                    sectorCoords.v);
3979 
3980 		assert(currentSector != nullptr);
3981 
3982 		currentObjectID = currentSector->childID;
3983 		_currentObject = currentObjectID != Nothing
3984 		                ?   GameObject::objectAddress(currentObjectID)
3985 		                :   nullptr;
3986 
3987 		while (currentObjectID == Nothing) {
3988 			if (!nextSector()) break;
3989 
3990 			currentSector = currentWorld->getSector(
3991 			                    sectorCoords.u,
3992 			                    sectorCoords.v);
3993 
3994 			assert(currentSector != nullptr);
3995 
3996 			currentObjectID = currentSector->childID;
3997 			_currentObject = currentObjectID != Nothing
3998 			                ?   GameObject::objectAddress(currentObjectID)
3999 			                :   nullptr;
4000 		}
4001 	}
4002 
4003 	if (obj != nullptr) *obj = _currentObject;
4004 	return currentObjectID;
4005 }
4006 
4007 //------------------------------------------------------------------------
4008 //	Return the next object within the specified region
4009 
next(GameObject ** obj)4010 ObjectID ActiveRegionObjectIterator::next(GameObject **obj) {
4011 	assert(activeRegionIndex >= 0);
4012 	assert(activeRegionIndex < kPlayerActors);
4013 
4014 	ObjectID        currentObjectID;
4015 
4016 	currentObjectID = _currentObject->IDNext();
4017 	_currentObject = currentObjectID != Nothing
4018 	                ?   GameObject::objectAddress(currentObjectID)
4019 	                :   nullptr;
4020 
4021 	while (currentObjectID == Nothing) {
4022 		Sector      *currentSector;
4023 
4024 		if (!nextSector()) break;
4025 
4026 		currentSector = currentWorld->getSector(
4027 		                    sectorCoords.u,
4028 		                    sectorCoords.v);
4029 
4030 		assert(currentSector != nullptr);
4031 
4032 		currentObjectID = currentSector->childID;
4033 		_currentObject = currentObjectID != Nothing
4034 		                ?   GameObject::objectAddress(currentObjectID)
4035 		                :   nullptr;
4036 	}
4037 
4038 	if (obj != nullptr) *obj = _currentObject;
4039 	return currentObjectID;
4040 }
4041 
4042 /* ======================================================================= *
4043    ContainerIterator member functions
4044  * ======================================================================= */
4045 
4046 //  This class iterates through every object within a container
4047 
ContainerIterator(GameObject * container)4048 ContainerIterator::ContainerIterator(GameObject *container) {
4049 	//  Get the ID of the 1st object in the sector list
4050 	nextID = container->_data.childID;
4051 	object = nullptr;
4052 }
4053 
next(GameObject ** obj)4054 ObjectID ContainerIterator::next(GameObject **obj) {
4055 	ObjectID        id = nextID;
4056 
4057 	if (id == Nothing) return Nothing;
4058 
4059 	object = GameObject::objectAddress(id);
4060 	nextID = object->_data.siblingID;
4061 
4062 	if (obj) *obj = object;
4063 	return id;
4064 }
4065 
4066 #if 0
4067 /* ======================================================================= *
4068    RecursiveContainerIterator member functions
4069  * ======================================================================= */
4070 
4071 //  This class iterates through every object within a container
4072 
4073 RecursiveContainerIterator::~RecursiveContainerIterator(void) {
4074 	if (subIter != nullptr) delete subIter;
4075 }
4076 
4077 ObjectID RecursiveContainerIterator::first(GameObject **obj) {
4078 	if (subIter != nullptr) delete subIter;
4079 
4080 	id(container->IDChild()),
4081 
4082 
4083 	if (obj != nullptr)
4084 		*obj = id != Nothing ? GameObject::objectAddress(id) : nullptr;
4085 
4086 	return id;
4087 }
4088 
4089 ObjectID RecursiveContainerIterator::next(GameObject **obj) {
4090 	GameObject  *currentObj;
4091 
4092 	if (subIter) {
4093 		ObjectID    currentID = subIter->next(&currentObj);
4094 
4095 		if (currentID != Nothing) {
4096 			if (obj) *obj = currentObj;
4097 			return currentID;
4098 		}
4099 
4100 		delete subIter;
4101 		subIter = nullptr;
4102 		currentObj = GameObject::objectAddress(id);
4103 	} else {
4104 		currentObj = GameObject::objectAddress(id);
4105 
4106 		if (currentObj->IDChild()) {
4107 			subIter = NEW_ITER RecursiveContainerIterator(currentObj);
4108 			assert(subIter);
4109 			return subIter->first(obj);
4110 		}
4111 	}
4112 	id = currentObj->IDNext();
4113 
4114 	if (obj != nullptr)
4115 		*obj = id != Nothing ? GameObject::objectAddress(id) : nullptr;
4116 	return id;
4117 }
4118 #endif
4119 
4120 /* ======================================================================= *
4121    RecursiveContainerIterator member functions
4122  * ======================================================================= */
4123 
4124 //  This class iterates through every object within a container
4125 
first(GameObject ** obj)4126 ObjectID RecursiveContainerIterator::first(GameObject **obj) {
4127 	GameObject      *rootObj = GameObject::objectAddress(root);
4128 
4129 	id = rootObj->IDChild();
4130 
4131 	if (obj != nullptr)
4132 		*obj = id != Nothing ? GameObject::objectAddress(id) : nullptr;
4133 
4134 	return id;
4135 }
4136 
next(GameObject ** obj)4137 ObjectID RecursiveContainerIterator::next(GameObject **obj) {
4138 	GameObject  *currentObj = GameObject::objectAddress(id);
4139 
4140 	//  If this object has a child, then the next object (id) is the child.
4141 	//  If it has no child, then check for sibling.
4142 	if ((id = currentObj->IDChild()) == 0) {
4143 		//  If this object has a sibling, then the next object (id) is the sibling.
4144 		//  If it has no sibling, then check for parent.
4145 		while ((id = currentObj->IDNext()) == 0) {
4146 			//  If this object has a parent, then the get the parent.
4147 			if ((id = currentObj->IDParent()) != 0) {
4148 				//  If the parent is the root, then we're done.
4149 				if (id == Nothing || id == root) return 0;
4150 
4151 				//  Set the current object to the parent, and then
4152 				//  Go around the loop once again and get the sibling of the parent.
4153 
4154 				//  The loop will keep going up until we either find an object that
4155 				//  has a sibling, or we hit the original root object.
4156 				currentObj = GameObject::objectAddress(id);
4157 			}
4158 		}
4159 	}
4160 
4161 	if (obj != nullptr)
4162 		*obj = id != Nothing ? GameObject::objectAddress(id) : nullptr;
4163 
4164 	return id;
4165 }
4166 
4167 /* ======================================================================= *
4168    Test for object collision
4169  * ======================================================================= */
4170 
objectCollision(GameObject * obj,GameWorld * world,const TilePoint & loc)4171 GameObject *objectCollision(GameObject *obj, GameWorld *world, const TilePoint &loc) {
4172 	ProtoObj        *proto = obj->proto();
4173 	TileRegion      volume;
4174 	GameObject      *obstacle;
4175 
4176 	volume.min.u = loc.u - proto->crossSection;
4177 	volume.min.v = loc.v - proto->crossSection;
4178 	volume.max.u = loc.u + proto->crossSection;
4179 	volume.max.v = loc.v + proto->crossSection;
4180 	volume.min.z = loc.z;
4181 	volume.max.z = loc.z + proto->height;
4182 
4183 	//  Adjust MIN Z for the fact that they can step over obstacles.
4184 	if (isActor(obj)) volume.min.z += kMaxStepHeight / 2;
4185 
4186 	//  Constructor
4187 	CircularObjectIterator  iter(world, loc, proto->crossSection + 32);
4188 
4189 	for (iter.first(&obstacle);
4190 	        obstacle != nullptr;
4191 	        iter.next(&obstacle)) {
4192 		TilePoint   tp = obstacle->getLocation();
4193 		ProtoObj    *obstacleProto = obstacle->proto();
4194 
4195 		if (obstacle == obj) continue;
4196 
4197 		if (tp.z < volume.max.z
4198 		        &&  tp.z + obstacleProto->height > volume.min.z
4199 		        &&  tp.u - obstacleProto->crossSection < volume.max.u
4200 		        &&  tp.u + obstacleProto->crossSection > volume.min.u
4201 		        &&  tp.v - obstacleProto->crossSection < volume.max.v
4202 		        &&  tp.v + obstacleProto->crossSection > volume.min.v) {
4203 			//  If the actor is dead, then it is not an obstacle.
4204 			if (isActor(obstacle) && ((Actor *)obstacle)->isDead()) continue;
4205 
4206 			return obstacle;
4207 		}
4208 	}
4209 	return nullptr;
4210 }
4211 
4212 /* ======================================================================= *
4213    Test for line of sight between two objects
4214  * ======================================================================= */
4215 
lineOfSight(GameObject * obj1,GameObject * obj2,uint32 terrainMask)4216 bool lineOfSight(GameObject *obj1, GameObject *obj2, uint32 terrainMask) {
4217 	GameWorld   *world;
4218 
4219 	//  If the two objects are not in the same world, there is no line
4220 	//  of sight
4221 	if ((world = obj1->world()) != obj2->world()) return false;
4222 #if 0
4223 	if (isActor(obj1)) {
4224 		Actor *a1 = (Actor *) obj1;
4225 		if (!a1->hasEffect(actorSeeInvis)) {
4226 			if (!isActor(obj2) && obj2->isInvisible())
4227 				return false;
4228 			else if (isActor(obj2)) {
4229 				Actor *a2 = (Actor *) obj2;
4230 				if (a2->hasEffect(actorInvisible))
4231 					return false;
4232 			}
4233 		}
4234 	}
4235 #endif
4236 
4237 	uint32 opaqueTerrain = ~terrainMask;
4238 
4239 	ProtoObj  *obj1proto = obj1->proto(),
4240 	           *obj2proto = obj2->proto();
4241 
4242 	TilePoint obj1Loc = obj1->getWorldLocation(),
4243 	          obj2Loc = obj2->getWorldLocation();
4244 
4245 	obj1Loc.z += obj1proto->height * 7 / 8;
4246 	obj2Loc.z += obj2proto->height * 7 / 8;
4247 
4248 	return (lineTerrain(
4249 	            world->mapNum,
4250 	            obj1Loc,
4251 	            obj2Loc,
4252 	            opaqueTerrain)
4253 	        &   opaqueTerrain)
4254 	       ==  0;
4255 }
4256 
4257 /* ======================================================================= *
4258    Test for line of sight between object and _data.location
4259  * ======================================================================= */
4260 
lineOfSight(GameObject * obj,const TilePoint & loc,uint32 terrainMask)4261 bool lineOfSight(GameObject *obj, const TilePoint &loc, uint32 terrainMask) {
4262 	GameWorld   *world = obj->world();
4263 	uint32      opaqueTerrain = ~terrainMask;
4264 	ProtoObj    *proto = obj->proto();
4265 	TilePoint   objLoc = obj->getWorldLocation();
4266 
4267 	objLoc.z += proto->height * 7 / 8;
4268 
4269 	return (lineTerrain(
4270 	            world->mapNum,
4271 	            objLoc,
4272 	            loc,
4273 	            opaqueTerrain)
4274 	        &   opaqueTerrain)
4275 	       ==  0;
4276 }
4277 
4278 
4279 /* ======================================================================= *
4280    Test for line of sight between two _data.locations
4281  * ======================================================================= */
4282 
lineOfSight(GameWorld * world,const TilePoint & loc1,const TilePoint & loc2,uint32 terrainMask)4283 bool lineOfSight(
4284     GameWorld       *world,
4285     const TilePoint &loc1,
4286     const TilePoint &loc2,
4287     uint32          terrainMask) {
4288 	uint32      opaqueTerrain = ~terrainMask;
4289 
4290 	return (lineTerrain(
4291 	            world->mapNum,
4292 	            loc1,
4293 	            loc2,
4294 	            opaqueTerrain)
4295 	        &   opaqueTerrain)
4296 	       ==  0;
4297 }
4298 
4299 
4300 /* ======================================================================= *
4301    Test if object is obscured by terrain
4302  * ======================================================================= */
4303 
objObscured(GameObject * testObj)4304 bool objObscured(GameObject *testObj) {
4305 
4306 	bool        obscured = false;
4307 
4308 	if (isObject(testObj)) {
4309 		Point16             drawPos,
4310 		                    org;
4311 		ObjectSpriteInfo    objSprInfo;
4312 		ColorTable          objColors;
4313 		ProtoObj            *proto = testObj->proto();
4314 
4315 		//  Calculate X, Y coordinates of the sprite
4316 		TileToScreenCoords(testObj->getLocation(), drawPos);
4317 		drawPos.x += fineScroll.x;
4318 		drawPos.y += fineScroll.y;
4319 
4320 		objSprInfo = proto->getSprite(testObj, ProtoObj::objOnGround);
4321 
4322 		testObj->getColorTranslation(objColors);
4323 
4324 		if (visiblePixelsInSprite(objSprInfo.sp,
4325 		                          objSprInfo.flipped,
4326 		                          objColors,
4327 		                          drawPos,
4328 		                          testObj->getLocation(),
4329 		                          objRoofID(testObj)) <= 5)
4330 			obscured = true;
4331 	}
4332 
4333 	return obscured;
4334 }
4335 
4336 /* ======================================================================= *
4337    Setup object interaction test by placing two objects inside another and
4338    displaying a container
4339  * ======================================================================= */
4340 
4341 extern gPanelList   *playControls;
4342 extern gPanelList   *trioControls;
4343 extern gPanelList   *indivControls;
4344 
4345 //  Kludge!!!
4346 int16 openMindType;
4347 
4348 #ifdef hasReadyContainers
4349 
APPFUNC(cmdBrain)4350 APPFUNC(cmdBrain) {
4351 	int16       part = clamp(0, ev.mouse.x * 3 / ev.panel->getExtent().width, 2);
4352 
4353 	//assert( indivControls->getEnabled() );
4354 	if (!indivControls->getEnabled())
4355 		return;
4356 
4357 	if (ev.eventType == gEventNewValue) {
4358 		//WriteStatusF( 4, "Brain Attempt " );
4359 
4360 		GameObject          *container = indivCviewTop->containerObject;
4361 		ContainerIterator   iter(container);
4362 		GameObject          *item;
4363 
4364 		openMindType = part;
4365 
4366 		assert(container == indivCviewBot->containerObject);
4367 
4368 		//  Get the actor's mind container
4369 		while (iter.next(&item) != Nothing) {
4370 			ProtoObj        *proto = item->proto();
4371 
4372 			if (proto->classType == protoClassIdeaContainer) {
4373 				item->use(item->IDParent());
4374 				break;
4375 			}
4376 		}
4377 	} else if (ev.eventType == gEventMouseMove) {
4378 		if (ev.value == GfxCompImage::leave) {
4379 			g_vm->_mouseInfo->setText(nullptr);
4380 		} else { //if (ev.value == gCompImage::enter)
4381 			// set the text in the cursor
4382 			if (part == 0)      g_vm->_mouseInfo->setText(IDEAS_INVENT);
4383 			else if (part == 1) g_vm->_mouseInfo->setText(SPELL_INVENT);
4384 			else                g_vm->_mouseInfo->setText(SKILL_INVENT);
4385 		}
4386 	}
4387 }
4388 
4389 //  Move to playerActor structure!!!
readyContainerSetup(void)4390 void readyContainerSetup(void) {
4391 	int8                    i;
4392 	int8                    resStart            = 28;
4393 
4394 	// init the resource handle with the image group context
4395 	imageRes = resFile->newContext(imageGroupID, "image resources");
4396 
4397 	backImages = loadImageRes(imageRes, resStart, numReadyContRes, 'B', 'T', 'N');
4398 
4399 	indivReadyNode = CreateReadyContainerNode(0);
4400 
4401 	for (i = 0; i < kNumViews && i < kPlayerActors ; i++) {
4402 		g_vm->_playerList[i]->readyNode = CreateReadyContainerNode(i);
4403 
4404 		TrioCviews[i] = new ReadyContainerView(
4405 		                      *trioControls,
4406 		                      Rect16(trioReadyContInfo[i].xPos,
4407 		                             trioReadyContInfo[i].yPos + 8,
4408 		                             iconOriginX * 2 + iconWidth * trioReadyContInfo[i].cols + iconSpacingY * (trioReadyContInfo[i].cols - 1),
4409 		                             iconOriginY + (iconOriginY * trioReadyContInfo[i].rows) + (trioReadyContInfo[i].rows * iconHeight) - 23),
4410 		                      *g_vm->_playerList[i]->readyNode,
4411 		                      backImages,
4412 		                      numReadyContRes,
4413 		                      trioReadyContInfo[i].rows,
4414 		                      trioReadyContInfo[i].cols,
4415 		                      trioReadyContInfo[i].rows,
4416 		                      0);
4417 
4418 		TrioCviews[i]->draw();
4419 	}
4420 
4421 	indivCviewTop   = new ReadyContainerView(*indivControls,
4422 	                  Rect16(indivReadyContInfoTop.xPos,
4423 	                         indivReadyContInfoTop.yPos + 8,
4424 	                         iconOriginX * 2 + iconWidth * indivReadyContInfoTop.cols + iconSpacingY * (indivReadyContInfoTop.cols - 1),
4425 	                         iconOriginY + (iconOriginY * indivReadyContInfoTop.rows) + (indivReadyContInfoTop.rows * iconHeight) - 23),
4426 	                  *indivReadyNode,
4427 	                  backImages,
4428 	                  numReadyContRes,
4429 	                  indivReadyContInfoTop.rows,
4430 	                  indivReadyContInfoTop.cols,
4431 	                  indivReadyContInfoTop.rows,
4432 	                  0);
4433 
4434 	indivCviewTop->draw();
4435 
4436 	indivCviewBot   = new ReadyContainerView(*indivControls,
4437 	                  Rect16(indivReadyContInfoBot.xPos,
4438 	                         indivReadyContInfoBot.yPos + 8,
4439 	                         iconOriginX * 2 + iconWidth * indivReadyContInfoBot.cols + iconSpacingY * (indivReadyContInfoBot.cols - 1),
4440 	                         iconOriginY + (iconOriginY * indivReadyContInfoBot.rows) + (indivReadyContInfoBot.rows * iconHeight) - 24),
4441 	                  *indivReadyNode,
4442 	                  backImages,
4443 	                  numReadyContRes,
4444 	                  indivReadyContInfoBot.rows,
4445 	                  indivReadyContInfoBot.cols,
4446 	                  indivReadyContInfoBot.rows,
4447 	                  0);
4448 	indivCviewBot->setScrollOffset(1);   // set the object draw up by one
4449 	indivCviewBot->draw();
4450 
4451 	// >>>
4452 	//new gGenericControl(*indivControls,Rect16(488,265,40,40),0,cmdBrain);
4453 }
4454 
cleanupReadyContainers(void)4455 void cleanupReadyContainers(void) {
4456 	if (backImages) {
4457 		// unload the images in the array and the array itself and nulls
4458 		// the appropriate pointers
4459 		unloadImageRes(backImages, numReadyContRes);
4460 	}
4461 
4462 	for (int16 i = 0; i < kNumViews && i < kPlayerActors ; i++) {
4463 		delete TrioCviews[i];
4464 		TrioCviews[i] = nullptr;
4465 
4466 		delete g_vm->_playerList[i]->readyNode;
4467 		g_vm->_playerList[i]->readyNode = nullptr;
4468 	}
4469 	delete indivReadyNode;
4470 
4471 	if (indivCviewTop) {
4472 		delete indivCviewTop;
4473 		indivCviewTop = nullptr;
4474 	}
4475 
4476 	if (indivCviewBot) {
4477 		delete indivCviewBot;
4478 		indivCviewBot = nullptr;
4479 	}
4480 
4481 	//
4482 	if (imageRes) resFile->disposeContext(imageRes);
4483 	imageRes = nullptr;
4484 }
4485 
4486 #endif
4487 
objectTest(void)4488 void objectTest(void) {
4489 }
4490 
APPFUNC(cmdControl)4491 APPFUNC(cmdControl) {
4492 	int newContainer = protoClassIdeaContainer;
4493 
4494 	if (ev.eventType == gEventMouseUp) {
4495 
4496 		GameObject *object = (GameObject *)getCenterActor();
4497 		ContainerIterator   iter(object);
4498 		GameObject *item;
4499 		ObjectID    id;
4500 
4501 		//Get Center Actors Mind Container
4502 		while ((id = iter.next(&item)) != Nothing) {
4503 			ProtoObj        *proto = item->proto();
4504 			//Default First Time To Idea Container
4505 			if (proto->classType == newContainer)
4506 				break;
4507 		}
4508 	}
4509 }
4510 
4511 /* ======================================================================= *
4512    Background simulation code
4513  * ======================================================================= */
4514 
4515 //  This is the time, in game ticks, that we want each
4516 //  actor or object to be visited
4517 //  Let's assume that we want each object and/or actor
4518 //  to be updated once every 10 seconds.
4519 const int32         objectCycleTime = (10 * frameRate),
4520                     actorCycleTime = (5 * frameRate);
4521 
4522 //  Indexes into the array of actors and objects
4523 int32               objectIndex,
4524                     actorIndex;
4525 
4526 //  Indicates paused state of background simulation
4527 bool                backgroundSimulationPaused;
4528 
4529 // ------------------------------------------------------------------------
4530 //	Main background simulation function
4531 //	This function does background processing on a few actors, objects
4532 
doBackgroundSimulation(void)4533 void doBackgroundSimulation(void) {
4534 	if (backgroundSimulationPaused) return;
4535 
4536 	//  Debug code to verify the validity of the limbo counts
4537 #if DEBUG
4538 	int16       count;
4539 	ObjectID    _data.childID;
4540 
4541 	count = 0;
4542 	for (_data.childID = GameObject::objectAddress(ObjectLimbo)->IDChild();
4543 	        _data.childID != Nothing;
4544 	        _data.childID = GameObject::objectAddress(_data.childID)->IDNext())
4545 		count++;
4546 	assert(objectLimboCount == count);
4547 
4548 	count = 0;
4549 	for (_data.childID = GameObject::objectAddress(ActorLimbo)->IDChild();
4550 	        _data.childID != Nothing;
4551 	        _data.childID = GameObject::objectAddress(_data.childID)->IDNext())
4552 		count++;
4553 	assert(actorLimboCount == count);
4554 
4555 	count = 0;
4556 	for (_data.childID = GameObject::objectAddress(ImportantLimbo)->IDChild();
4557 	        _data.childID != Nothing;
4558 	        _data.childID = GameObject::objectAddress(_data.childID)->IDNext())
4559 		count++;
4560 	assert(importantLimboCount == count);
4561 #endif
4562 
4563 	int32           objectUpdateCount,
4564 	                actorUpdateCount;
4565 
4566 	//  Calculate how many actors and/or objects we want to
4567 	//  update in this cycle
4568 	objectUpdateCount = objectCount / objectCycleTime;
4569 	actorUpdateCount = kActorCount / actorCycleTime;
4570 
4571 	//  do background processing on a few objects, based on clock time.
4572 	while (objectUpdateCount--) {
4573 		GameObject      *obj;
4574 
4575 		obj = &objectList[objectIndex++];
4576 
4577 		//  Wrap the counter around to the beginning if needed
4578 		if (objectIndex >= objectCount) objectIndex = 0;
4579 
4580 		//  If object is not deleted, then tell that object to do
4581 		//  a background update
4582 		if (obj->IDParent() > ImportantLimbo) {
4583 			assert(obj->proto());
4584 
4585 			//  If an object has been abandoned by the player,
4586 			//  and is not sitting inside a container,
4587 			//  then the object will be scavenged after an average
4588 			//  of 600 seconds (10 minutes).
4589 
4590 			if (obj->isScavengable()
4591 			        &&  !obj->isActivated()
4592 			        &&  isWorld(obj->IDParent())
4593 			        &&  g_vm->_rnd->getRandomNumber(MIN(objectLimboCount / 2, 60) - 1) == 0) {
4594 				obj->deleteObjectRecursive();
4595 			}
4596 
4597 			//  REM: might want to check for object abandonment here
4598 			obj->proto()->doBackgroundUpdate(obj);
4599 		}
4600 	}
4601 
4602 	//  do background processing on a few actors, based on clock time
4603 	while (actorUpdateCount--) {
4604 		Actor           *a;
4605 
4606 		a = g_vm->_act->_actorList[actorIndex++];
4607 
4608 		//  Wrap the counter around to the beginning if needed
4609 		if (actorIndex >= kActorCount) actorIndex = 0;
4610 
4611 		//  If actor is not deleted, then tell that actor to do
4612 		//  a background update
4613 		if (a->IDParent() > ImportantLimbo) {
4614 			assert(a->proto());
4615 
4616 			a->proto()->doBackgroundUpdate(a);
4617 		}
4618 	}
4619 }
4620 
4621 // ------------------------------------------------------------------------
4622 
pauseBackgroundSimulation(void)4623 void pauseBackgroundSimulation(void) {
4624 	backgroundSimulationPaused = true;
4625 }
4626 
4627 // ------------------------------------------------------------------------
4628 
resumeBackgroundSimulation(void)4629 void resumeBackgroundSimulation(void) {
4630 	backgroundSimulationPaused = false;
4631 }
4632 
4633 //-------------------------------------------------------------------
4634 //	This function simply calls the GameObject::updateState() method
4635 //	for all active objects directly within a world.
4636 
updateObjectStates(void)4637 void updateObjectStates(void) {
4638 	if (objectStatesPaused) return;
4639 
4640 	GameObject          *obj,
4641 	                    *last = &objectList[objectCount];
4642 
4643 	static int16        baseIndex = 0;
4644 
4645 //	baseIndex = (baseIndex + 1) & ~3;
4646 	baseIndex = 0;
4647 
4648 //	for (    obj = &objectList[baseIndex]; obj < last; obj += 4 )
4649 	for (obj = &objectList[baseIndex]; obj < last; obj++) {
4650 		if (isWorld(obj->IDParent()) && obj->isActivated())
4651 			obj->updateState();
4652 	}
4653 }
4654 
4655 //-------------------------------------------------------------------
4656 
pauseObjectStates(void)4657 void pauseObjectStates(void) {
4658 	objectStatesPaused = true;
4659 }
4660 
4661 //-------------------------------------------------------------------
4662 
resumeObjectStates(void)4663 void resumeObjectStates(void) {
4664 	objectStatesPaused = false;
4665 }
4666 
4667 } // end of namespace Saga2
4668