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 §ors,
3455 const TilePoint ¢er,
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(¤tObject);
3489 while (currentObjectID != Nothing
3490 && (currentDist =
3491 computeDist(currentObject->getLocation()))
3492 > radius) {
3493 currentObjectID = SectorRegionObjectIterator::next(¤tObject);
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(¤tObject);
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(¤tObject);
3542 while (currentObjectID != Nothing
3543 && computeDist(currentObject->getLocation()) < innerDist) {
3544 currentObjectID = CircularObjectIterator::next(¤tObject);
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(¤tObject);
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 §ors,
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(¤tObject);
3632
3633 if (currentObjectID == Nothing)
3634 return Nothing;
3635
3636 while (currentObjectID != Nothing
3637 && !inRegion(currentObject->getLocation())) {
3638 currentObjectID = SectorRegionObjectIterator::next(¤tObject);
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(¤tObject);
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(¤tObject);
3702 while (currentObjectID != Nothing
3703 && !inRegion(currentObject->getLocation())) {
3704 currentObjectID = RegionalObjectIterator::next(¤tObject);
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(¤tObject);
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(¤tObject);
3765 while (currentObjectID != Nothing
3766 && !inRegion(currentObject->getLocation())) {
3767 currentObjectID = RegionalObjectIterator::next(¤tObject);
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(¤tObject);
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(¤tObj);
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