1 /* ScummVM - Graphic Adventure Engine
2 *
3 * ScummVM is the legal property of its developers, whose names
4 * are too numerous to list here. Please refer to the COPYRIGHT
5 * file distributed with this source distribution.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 *
21 *
22 * 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/contain.h"
30 #include "saga2/grabinfo.h"
31 #include "saga2/motion.h"
32 #include "saga2/uimetrcs.h"
33 #include "saga2/localize.h"
34 #include "saga2/intrface.h"
35 #include "saga2/spellbuk.h"
36 #include "saga2/imagcach.h"
37 #include "saga2/hresmgr.h"
38 #include "saga2/fontlib.h"
39
40 #include "saga2/pclass.r"
41
42 namespace Saga2 {
43
44 enum {
45 kMaxOpenDistance = 32
46 };
47
48 // selector image pointer
49 static void *selImage;
50
51 /* ===================================================================== *
52 Imports
53 * ===================================================================== */
54
55 extern ReadyContainerView *TrioCviews[kNumViews];
56 extern ReadyContainerView *indivCviewTop, *indivCviewBot;
57 extern SpriteSet *objectSprites; // object sprites
58 extern Alarm containerObjTextAlarm;
59 extern bool gameSetupComplete;
60
61 extern hResContext *imageRes; // image resource handle
62 hResContext *containerRes; // image resource handle
63
64 extern APPFUNC(cmdWindowFunc);
65
66 // Temporary...
67 void grabObject(ObjectID pickedObject); // turn object into mouse ptr
68 void releaseObject(void); // restore mouse pointer
69
70 /* Reference Types
71 ProtoObj::isTangible
72 ProtoObj::isContainer
73 ProtoObj::isBottle
74 ProtoObj::isFood
75 ProtoObj::isWearable
76 ProtoObj::isWeapon
77 ProtoObj::isDocument
78 ProtoObj::isIntangible
79 ProtoObj::isConcept
80 ProtoObj::isMemory
81 ProtoObj::isPsych
82 ProtoObj::isSpell
83 ProtoObj::isEnchantment
84 */
85
86 // used to ignore doubleClick when doubleClick == singleClick
87 static bool alreadyDone;
88 // ID of the last object that the mouse moved over
89
90 /* ===================================================================== *
91 ContainerView member functions
92 * ===================================================================== */
93
94 // static mouse info variables
95 ObjectID ContainerView::lastPickedObjectID = Nothing;
96 int32 ContainerView::lastPickedObjectQuantity = - 1;
97 bool ContainerView::objTextAlarm = false;
98 char ContainerView::mouseText[ContainerView::bufSize] = { "" };
99 bool ContainerView::mouseInView = false;
100 uint16 ContainerView::numPicked = 1;
101 GameObject *ContainerView::objToGet;
102 int32 ContainerView::amountAccumulator = 0;
103 int16 ContainerView::amountIndY = -1;
104
105 //-----------------------------------------------------------------------
106 // Physical container appearance
107
108 static ContainerAppearanceDef physicalContainerAppearance = {
109 {250, 60, 268, 304 + 16},
110 {17 + 4, 87, 268 - 2, 304 - 87},
111 {13 + 8, 37, 44, 42},
112 {13 + 8 + 44, 37, 44, 42},
113 {13 + 118, 50, 36, 36},
114 {13 + 139, 37, 88, 43},
115 { MKTAG('P', 'C', 'L', 0), MKTAG('P', 'C', 'L', 1) },
116 { MKTAG('P', 'S', 'L', 0), MKTAG('P', 'S', 'L', 1) },
117 {13, 8},
118 {22, 22},
119 0, 0,
120 0
121 };
122
123 static const StaticWindow brassDecorations[] = {
124 {{0, 0, 268, 86}, nullptr, 3},
125 {{13, 86, 242, 109}, nullptr, 4},
126 {{13, 195, 242, 121}, nullptr, 5}
127 };
128
129 static const StaticWindow clothDecorations[] = {
130 {{0, 0, 268, 86}, nullptr, 6},
131 {{13, 86, 242, 109}, nullptr, 7},
132 {{13, 195, 242, 121}, nullptr, 8}
133 };
134
135 static const StaticWindow steelDecorations[] = {
136 {{0, 0, 268, 86}, nullptr, 9},
137 {{13, 86, 242, 109}, nullptr, 10},
138 {{13, 195, 242, 121}, nullptr, 11}
139 };
140
141 static const StaticWindow woodDecorations[] = {
142 {{0, 0, 268, 86}, nullptr, 12},
143 {{13, 86, 242, 109}, nullptr, 13},
144 {{13, 195, 242, 121}, nullptr, 14}
145 };
146
147 //-----------------------------------------------------------------------
148 // Death container appearance
149
150 static ContainerAppearanceDef deathContainerAppearance = {
151 {260, 60, 206, 250},
152 {2, 87, 206 - 22, 250 - 87 - 32},
153 {16, 24, 44, 42},
154 {120 + 18, 24, 44, 42},
155 {0, 0, 0, 0},
156 {0, 0, 0, 0},
157 { MKTAG('D', 'C', 'L', 0), MKTAG('D', 'C', 'L', 1) },
158 { MKTAG('D', 'S', 'L', 0), MKTAG('D', 'S', 'L', 1) },
159 {27, -4},
160 {22, 22},
161 0, 0,
162 0
163 };
164
165 // physal dialog window decorations
166 static const StaticWindow deathDecorations[] = {
167 {{0, 0, 206, 250}, nullptr, 15}
168 };
169
170 //-----------------------------------------------------------------------
171 // ReadyContainer appearance
172
173 static const ContainerAppearanceDef readyContainerAppearance = {
174 {0, 0, 0, 0},
175 {476, 105, 0, 0},
176 {0, 0, 0, 0},
177 {0, 0, 0, 0},
178 {0, 0, 0, 0},
179 {0, 0, 0, 0},
180 { 0, 0 },
181 { 0, 0 },
182 {iconOriginX - 1, iconOriginY - 1 - 8},
183 {iconSpacingX, iconSpacingY},
184 1, 3,
185 3
186 };
187
188 //-----------------------------------------------------------------------
189 // Mental Container appearance
190
191 static const ContainerAppearanceDef mentalContainerAppearance = {
192 {478, 168 - 54, 158, 215},
193 {2, 86 - 18 - 4, 158 - 2, 215 - 66},
194 {2, 19, 44, 44},
195 {103, 40 - 18 - 4, 44, 44},
196 {0, 0, 0, 0},
197 {0, 0, 0, 0},
198 { MKTAG('C', 'L', 'S', 0), MKTAG('C', 'L', 'S', 1) },
199 { MKTAG('S', 'E', 'L', 0), MKTAG('S', 'E', 'L', 1) },
200 {3, 0},
201 {4, 4},
202 4, 4,
203 20
204 };
205
206 static const StaticWindow mentalDecorations[] = {
207 {{0, 0, 158, 215}, nullptr, 0} // Bottom decoration panel
208 };
209 //-----------------------------------------------------------------------
210 // Enchantment container appearance
211
212 static const ContainerAppearanceDef enchantmentContainerAppearance = {
213 {262, 92, 116, 202},
214 {2, 87, 116 - 2, 202 - 87},
215 {7, 50, 44, 43},
216 {57, 50, 44, 43},
217 {38, 7, 32, 32},
218 {0, 0, 0, 0},
219 { MKTAG('A', 'A', 'A', 0), MKTAG('A', 'A', 'A', 0) },
220 { MKTAG('A', 'A', 'A', 0), MKTAG('A', 'A', 'A', 0) },
221 {12, 98},
222 {16, 13},
223 2, 2,
224 2
225 };
226
227 //-----------------------------------------------------------------------
228 // ContainerView class
229
230 // Constructor
231 /* ContainerView::ContainerView(
232 gPanelList &list,
233 const Rect16 &box,
234 ContainerNode &nd,
235 Point16 org,
236 Point16 space,
237 int16 numRows,
238 int16 numCols,
239 int16 totRows,
240 uint16 ident,
241 AppFunc *cmd ) */
242
ContainerView(gPanelList & list,const Rect16 & rect,ContainerNode & nd,const ContainerAppearanceDef & app,AppFunc * cmd)243 ContainerView::ContainerView(
244 gPanelList &list,
245 const Rect16 &rect,
246 ContainerNode &nd,
247 const ContainerAppearanceDef &app,
248 AppFunc *cmd)
249 : gControl(list, rect, NULL, 0, cmd),
250 iconOrigin(app.iconOrigin),
251 iconSpacing(app.iconSpacing),
252 visibleRows(app.rows),
253 visibleCols(app.cols),
254 node(nd) {
255 containerObject = GameObject::objectAddress(nd.object);
256 scrollPosition = 0;
257 totalRows = app.totRows;
258 setMousePoll(true);
259 totalMass = 0;
260 totalBulk = 0;
261 numObjects = 0;
262 }
263
264 // Destructor
~ContainerView()265 ContainerView::~ContainerView() {
266 }
267
268 /****** contain.cpp/ContainerView::findPane ***********************
269 *
270 * NAME
271 * findPane - find a ContainerView control that is
272 * viewing the contents of the target object
273 *
274 * SYNOPSIS
275 * If pane is NULL, start search at first ContainerView
276 * else, start search at pane->next.
277 *
278 * Iterate through ContainerView list until a
279 * ContainerView is found that is viewing the
280 * target object (obj).
281 *
282 * INPUTS
283 * obj Target Game Object
284 * pane Previously found pane, or NULL if search start
285 *
286 * RESULT
287 * Returns a pointer to a ContainterPanel if one is found
288 * or NULL if no more found.
289 *
290 ********************************************************************
291 */
292
293 // returns true if the object is visible for this type of
294 // container.
isVisible(GameObject * item)295 bool ContainerView::isVisible(GameObject *item) {
296 ProtoObj *proto = item->proto();
297
298 if (proto->containmentSet() & ProtoObj::isEnchantment)
299 return false;
300
301 // If Intangible Container then don't show it.
302 if ((proto->containmentSet() & (ProtoObj::isContainer | ProtoObj::isIntangible)) == (ProtoObj::isContainer | ProtoObj::isIntangible))
303 return true;
304
305 return true;
306 }
307
308 // total the mass, bulk, and number of all objects in container.
totalObjects(void)309 void ContainerView::totalObjects(void) {
310 ObjectID objID;
311 GameObject *item = nullptr;
312
313 totalMass = 0;
314 totalBulk = 0;
315 numObjects = 0;
316
317 if (containerObject == NULL) return;
318
319 RecursiveContainerIterator iter(containerObject);
320
321 // See if he has enough currency
322 for (objID = iter.first(&item); objID != Nothing; objID = iter.next(&item)) {
323 // If the object is visible, then add to total mass and
324 // bulk.
325 if (isVisible(item)) {
326 uint16 numItems;
327
328 ProtoObj *proto = item->proto();
329
330 numObjects++;
331
332 // if it's mergeable calc using the getExtra method of
333 // quantity calculation
334 // if not, then use the objLoc.z method
335 if (proto->flags & ResourceObjectPrototype::objPropMergeable)
336 numItems = item->getExtra();
337 else numItems = 1;
338
339 totalMass += proto->mass * numItems;
340 totalBulk += proto->bulk * numItems;
341 }
342 }
343
344 // ContainerView *viewToUpdate = ContainerView::findPane( containerObject );
345
346 // viewToUpdate->getWindow()->update( viewToUpdate->getExtent() );
347 }
348
349 // Return the Nth visible object from this container.
getObject(int16 slotNum)350 ObjectID ContainerView::getObject(int16 slotNum) {
351 ObjectID objID;
352 GameObject *item;
353
354 if (containerObject == NULL) return Nothing;
355
356 ContainerIterator iter(containerObject);
357
358 // Iterate through all the objects in the container.
359 while ((objID = iter.next(&item)) != Nothing) {
360 // If the object is visible, then check which # it is,
361 // and return the object if slotnum has been decremented
362 // to zero.
363 if (isVisible(item)) {
364 if (slotNum == 0) return objID;
365 slotNum--;
366 }
367 }
368 return Nothing;
369 }
370
371 // REM: We need to handle the case of a NULL container here...
372 // Draw the contents of the container.
drawClipped(gPort & port,const Point16 & offset,const Rect16 & r)373 void ContainerView::drawClipped(
374 gPort &port,
375 const Point16 &offset,
376 const Rect16 &r) {
377 // Coordinates to draw the inventory icon at.
378 int16 x,
379 y;
380
381 // Coordinates for slot 0,0.
382 int16 originX = _extent.x - offset.x + iconOrigin.x,
383 originY = _extent.y - offset.y + iconOrigin.y;
384
385 ObjectID objID;
386 GameObject *item;
387
388 // Iterator class for the container.
389 ContainerIterator iter(containerObject);
390
391 // Color set to draw the object.
392 ColorTable objColors;
393
394 // If there's no overlap between extent and clip, then return.
395 if (!_extent.overlap(r)) return;
396
397 // Iterate through each item within the container.
398 while ((objID = iter.next(&item)) != Nothing) {
399 TilePoint objLoc;
400 ProtoObj *objProto = item->proto();
401
402 objLoc = item->getLocation();
403
404 if (objLoc.z == 0) continue;
405
406 // Draw object only if visible and in a visible row & col.
407 if (objLoc.u >= scrollPosition
408 && objLoc.u < scrollPosition + visibleRows
409 && objLoc.v < visibleCols
410 && isVisible(item)) {
411 Sprite *spr;
412 ProtoObj *proto = item->proto();
413 Point16 sprPos;
414
415 y = originY
416 + (objLoc.u - scrollPosition)
417 * (iconSpacing.y + iconHeight);
418 x = originX
419 + objLoc.v
420 * (iconSpacing.x + iconWidth);
421
422 // Get the address of the sprite.
423 spr = proto->getSprite(item, ProtoObj::objInContainerView).sp;
424
425 sprPos.x = x + ((iconWidth - spr->size.x) >> 1)
426 - spr->offset.x;
427 sprPos.y = y + ((iconHeight - spr->size.y) >> 1)
428 - spr->offset.y;
429
430 // Build the color table.
431 item->getColorTranslation(objColors);
432 // Draw the sprite into the port
433
434 DrawColorMappedSprite(
435 port,
436 sprPos,
437 spr,
438 objColors);
439
440 // check to see if selecting amount for this objec
441 if (objToGet == item) {
442 Point16 selectorPos = Point16(x + ((iconWidth - selectorX) >> 1),
443 y + ((iconHeight - selectorY) >> 1));
444
445 // draw the selector thingy
446 drawSelector(port, selectorPos);
447
448 // set the position of the inc center
449 amountIndY = y - (selectorY >> 1) - 12;
450 } else drawQuantity(port, item, objProto, x, y);
451 }
452 }
453 }
454
455 // draws the mereged object multi-item selector
drawSelector(gPort & port,Point16 & pos)456 void ContainerView::drawSelector(gPort &port, Point16 &pos) {
457 char buf[20];
458 uint8 num;
459
460 SAVE_GPORT_STATE(port);
461
462 // draw the arrow images
463 drawCompressedImage(port, pos, selImage);
464
465 // draw the number of items selected thus far
466 num = sprintf(buf, " %d ", numPicked);
467
468 port.moveTo(Point16(pos.x - ((3 * (num - 3)) + 1), pos.y + 7));
469 port.setFont(&Helv11Font);
470 port.setColor(11); // set color to white
471 port.setStyle(textStyleThickOutline);
472 port.setOutlineColor(24); // set outline color to black
473 port.setMode(drawModeMatte);
474
475 port.drawText(buf);
476 }
477
drawQuantity(gPort & port,GameObject * item,ProtoObj * objProto,int16 x,int16 y)478 void ContainerView::drawQuantity(
479 gPort &port,
480 GameObject *item,
481 ProtoObj *objProto,
482 int16 x,
483 int16 y) {
484 int16 quantity;
485
486 quantity = (objProto->flags & ResourceObjectPrototype::objPropMergeable)
487 ? item->getExtra()
488 : item->getLocation().z;
489
490 if (quantity > 1) {
491 SAVE_GPORT_STATE(port);
492 char buf[8];
493
494 // draw the number of items selected thus far
495 sprintf(buf, "%d", quantity);
496
497 port.moveTo(x - 1, y + 22);
498 port.setFont(&Helv11Font);
499 port.setColor(11); // set color to white
500 port.setStyle(textStyleThickOutline);
501 port.setOutlineColor(24); // set outline color to black
502 port.setMode(drawModeMatte);
503
504 port.drawText(buf);
505 }
506 }
507
setContainer(GameObject * containerObj)508 void ContainerView::setContainer(GameObject *containerObj) {
509 containerObject = containerObj;
510 totalObjects();
511 }
512
513
514 // Get the slot that the point is over
pickObjectSlot(const Point16 & pickPos)515 TilePoint ContainerView::pickObjectSlot(const Point16 &pickPos) {
516 TilePoint slot;
517 Point16 temp;
518
519 // Compute the u/v of the slot that they clicked on.
520 temp = pickPos + iconSpacing / 2 - iconOrigin;
521 slot.v = clamp(0, temp.x / (iconWidth + iconSpacing.x), visibleCols - 1);
522 slot.u = clamp(0, temp.y / (iconHeight + iconSpacing.y), visibleRows - 1) + scrollPosition;
523 slot.z = 1;
524 return slot;
525 }
526
527 // Get the object that the pointer is over
getObject(const TilePoint & slot)528 GameObject *ContainerView::getObject(const TilePoint &slot) {
529 GameObject *item;
530 TilePoint loc;
531
532 item = containerObject->child();
533
534 while (item != NULL) {
535 // Skip objects that are stacked behind other objects
536 if (item->getLocation().z != 0) {
537 ProtoObj *proto = item->proto();
538
539 loc = item->getLocation();
540
541 if (
542 (loc.u == slot.u) &&
543 (loc.v == slot.v) &&
544 //Skip The Enchantments
545 (!(proto->containmentSet() & ProtoObj::isEnchantment))
546 ) {
547 return item;
548 }
549 }
550
551 item = item->next();
552 }
553
554 return NULL;
555
556 }
557
558 // Get the object ID that the point is over
pickObjectID(const Point16 & pickPos)559 ObjectID ContainerView::pickObjectID(const Point16 &pickPos) {
560 TilePoint slot;
561 GameObject *item;
562
563 slot = pickObjectSlot(pickPos);
564 item = getObject(slot);
565
566 if (item != NULL) {
567 return item->thisID();
568 } else {
569 return 0;
570 }
571 }
572
573 // Get the object ID that the point is over
pickObject(const Point16 & pickPos)574 GameObject *ContainerView::pickObject(const Point16 &pickPos) {
575 TilePoint slot;
576 GameObject *item;
577
578 slot = pickObjectSlot(pickPos);
579 item = getObject(slot);
580
581 return item;
582 }
583
activate(gEventType why)584 bool ContainerView::activate(gEventType why) {
585 gPanel::activate(why);
586
587 return true;
588 }
589
deactivate(void)590 void ContainerView::deactivate(void) {
591 }
592
pointerMove(gPanelMessage & msg)593 void ContainerView::pointerMove(gPanelMessage &msg) {
594 if (msg.pointerLeave) {
595 lastPickedObjectID = Nothing;
596 lastPickedObjectQuantity = -1;
597 g_vm->_mouseInfo->setText(NULL);
598 mouseText[0] = 0;
599
600 // static bool that tells if the mouse cursor
601 // is in a panel
602 mouseInView = false;
603 g_vm->_mouseInfo->setDoable(true);
604 } else {
605 // if( msg.pointerEnter )
606 {
607 // static bool that tells if the mouse cursor
608 // is in a panel
609 mouseInView = true;
610
611 GameObject *mouseObject;
612 mouseObject = g_vm->_mouseInfo->getObject();
613
614 if (!node.isAccessable(getCenterActorID())) {
615 g_vm->_mouseInfo->setDoable(false);
616 } else if (mouseObject == NULL) {
617 g_vm->_mouseInfo->setDoable(true);
618 } else {
619 g_vm->_mouseInfo->setDoable(containerObject->canContain(mouseObject->thisID()));
620 }
621 }
622
623 // Determine if mouse is pointing at a new object
624 updateMouseText(msg.pickPos);
625 }
626 }
627
pointerHit(gPanelMessage & msg)628 bool ContainerView::pointerHit(gPanelMessage &msg) {
629 GameObject *mouseObject;
630 GameObject *slotObject;
631 uint16 mouseSet;
632
633 slotObject = pickObject(msg.pickPos);
634 mouseObject = g_vm->_mouseInfo->getObject();
635 mouseSet = mouseObject ? mouseObject->containmentSet() : 0;
636
637 if (!g_vm->_mouseInfo->getDoable()) return false;
638
639 if (msg.doubleClick && !alreadyDone) {
640 dblClick(mouseObject, slotObject, msg);
641 } else { // single click
642 if (mouseObject != NULL) {
643 alreadyDone = true; // if object then no doubleClick
644
645 if (g_vm->_mouseInfo->getIntent() == GrabInfo::Drop) {
646 if (mouseSet & ProtoObj::isTangible) {
647 dropPhysical(msg, mouseObject, slotObject, g_vm->_mouseInfo->getMoveCount());
648 }
649
650 // intangibles are used by dropping them
651 else if ((mouseSet & ProtoObj::isConcept) ||
652 (mouseSet & ProtoObj::isPsych) ||
653 (mouseSet & ProtoObj::isSpell) ||
654 (mouseSet & ProtoObj::isSkill)) {
655 useConcept(msg, mouseObject, slotObject);
656 } else {
657 // !!!! bad state, reset cursor
658 g_vm->_mouseInfo->replaceObject();
659 }
660 } else if (g_vm->_mouseInfo->getIntent() == GrabInfo::Use) {
661 if (mouseSet & ProtoObj::isTangible) {
662 usePhysical(msg, mouseObject, slotObject);
663 } else if ((mouseSet & ProtoObj::isSpell) ||
664 (mouseSet & ProtoObj::isSkill)) {
665 g_vm->_mouseInfo->replaceObject();
666 } else {
667 useConcept(msg, mouseObject, slotObject);
668 }
669 } else {
670 // !!!! bad state, reset cursor
671 g_vm->_mouseInfo->replaceObject();
672 }
673 } else {
674 // default to doubleClick active
675 alreadyDone = false;
676 clickOn(msg, mouseObject, slotObject);
677 }
678 }
679
680 // total the mass and bulk of all the objects in this container
681 totalObjects();
682 window.update(_extent);
683
684 return activate(gEventMouseDown);
685 }
686
pointerRelease(gPanelMessage &)687 void ContainerView::pointerRelease(gPanelMessage &) {
688 // see if in multi-item get mode
689 if (objToGet) {
690 objToGet->take(getCenterActorID(), numPicked);
691
692 // reset the flags and pointer dealing with merged object movement
693 objToGet = NULL;
694 numPicked = 1;
695 amountIndY = -1;
696 }
697
698 gPanel::deactivate();
699 }
700
timerTick(gPanelMessage & msg)701 void ContainerView::timerTick(gPanelMessage &msg) {
702 // validate objToGet and make sure that the number selected for move
703 // is less then or equal to the number of items present in the merged object
704 if (objToGet && amountIndY != -1) {
705 int32 rate = (amountIndY - msg.pickAbsPos.y);
706
707 rate = rate * ((rate > 0) ? rate : -rate);
708
709 // Add to the amount accumulator based on the mouse position
710 amountAccumulator += rate / 4 /* * accelSpeed */;
711
712 // Take the top bits of the amount accumulator and add to
713 // the mergeable amount.
714 numPicked = clamp(1,
715 numPicked + (amountAccumulator >> 8),
716 objToGet->getExtra());
717
718 // Now remove the bits that we added to the grab amount
719 // keep the remaining bits to accumulate for next time
720 amountAccumulator &= 0x00ff;
721 }
722 }
723
dblClick(GameObject * mouseObject,GameObject * slotObject,gPanelMessage & msg)724 void ContainerView::dblClick(GameObject *mouseObject, GameObject *slotObject, gPanelMessage &msg) {
725 alreadyDone = true;
726
727 // double click stuff
728 dblClickOn(msg, mouseObject, slotObject);
729 }
730
731 // activate the click function
clickOn(gPanelMessage &,GameObject *,GameObject * cObj)732 void ContainerView::clickOn(
733 gPanelMessage &,
734 GameObject *,
735 GameObject *cObj) {
736 if (cObj != NULL) {
737 if (cObj->proto()->flags & ResourceObjectPrototype::objPropMergeable) {
738 if (!rightButtonState()) {
739 // just get the object into the cursor
740 cObj->take(getCenterActorID(), cObj->getExtra());
741 } else {
742 // activate multi-object get interface if a mergeable object
743 getMerged(cObj);
744 g_vm->_mouseInfo->setText(NULL);
745 mouseText[0] = 0;
746 }
747 } else {
748 // just get the object into the cursor
749 cObj->take(getCenterActorID(), numPicked);
750 }
751 }
752 }
753
getMerged(GameObject * obj)754 void ContainerView::getMerged(GameObject *obj) {
755 // reset the number picked.
756 numPicked = 1;
757
758 // set the object to be gotten
759 objToGet = obj;
760 }
761
762 // Activate the double click function
dblClickOn(gPanelMessage &,GameObject * mObj,GameObject *)763 void ContainerView::dblClickOn(
764 gPanelMessage &,
765 GameObject *mObj,
766 GameObject *) {
767 if (mObj != NULL) {
768 ObjectID possessor = mObj->possessor();
769 ProtoObj *proto = mObj->proto();
770 PlayerActorID pID;
771
772 // Only player actors can be possessors as far as the UI is concerned
773 if (actorIDToPlayerID(possessor, pID) == false) possessor = Nothing;
774
775 g_vm->_mouseInfo->replaceObject(); //Put Object Back
776 if (!(proto->setUseCursor(mObj->thisID()))) {
777 MotionTask::useObject(
778 possessor == Nothing ? *getCenterActor() : * (Actor *)GameObject::objectAddress(possessor),
779 *mObj);
780 }
781 }
782 }
783
784 // drop a physical object
dropPhysical(gPanelMessage & msg,GameObject * mObj,GameObject * cObj,int16 num)785 void ContainerView::dropPhysical(
786 gPanelMessage &msg,
787 GameObject *mObj,
788 GameObject *cObj,
789 int16 num) {
790 assert(g_vm->_mouseInfo->getObject() == mObj);
791 assert(mObj->containmentSet() & ProtoObj::isTangible);
792
793 // Place object back where it came from, temporarily
794 g_vm->_mouseInfo->replaceObject();
795
796 // test to check if item is accepted by container
797 if (containerObject->canContain(mObj->thisID())) {
798 Actor *centerActor = getCenterActor();
799 Location loc(pickObjectSlot(msg.pickPos),
800 containerObject->thisID());
801
802 // check if no object in the current slot
803 if (cObj == NULL) {
804 MotionTask::dropObject(*centerActor, *mObj, loc, num);
805
806 WriteStatusF(6, "No object state");
807 } else {
808 // Try dropping this object on the object in the container
809 MotionTask::dropObjectOnObject(*centerActor, *mObj, *cObj, num);
810 }
811
812 alreadyDone = true;
813 }
814 }
815
816 // use a physical object
usePhysical(gPanelMessage & msg,GameObject * mObj,GameObject * cObj)817 void ContainerView::usePhysical(
818 gPanelMessage &msg,
819 GameObject *mObj,
820 GameObject *cObj) {
821 assert(g_vm->_mouseInfo->getObject() == mObj);
822 assert(mObj->containmentSet() & ProtoObj::isTangible);
823
824 if (cObj == NULL) {
825 dropPhysical(msg, mObj, cObj);
826 } else {
827 g_vm->_mouseInfo->replaceObject();
828 // Use mouse object on container object
829 MotionTask::useObjectOnObject(*getCenterActor(), *mObj, *cObj);
830 }
831 }
832
833 // Use Concept or Psycological state
useConcept(gPanelMessage & msg,GameObject * mObj,GameObject * cObj)834 void ContainerView::useConcept(
835 gPanelMessage &msg,
836 GameObject *mObj,
837 GameObject *cObj) {
838 assert(g_vm->_mouseInfo->getObject() == mObj);
839 assert(mObj->containmentSet() & ProtoObj::isIntangible);
840
841 g_vm->_mouseInfo->replaceObject();
842
843 // Determine if this object can go into this container
844 if (containerObject->canContain(mObj->thisID())) {
845 ObjectID centerActorID = getCenterActorID();
846
847 if (cObj == NULL) {
848 // If there is no object already in this slot drop the
849 // mouse object here
850
851 Location loc(pickObjectSlot(msg.pickPos),
852 containerObject->thisID());
853
854 mObj->drop(centerActorID, loc);
855 } else
856 // If there is an object here drop the mouse object onto it
857 mObj->dropOn(centerActorID, cObj->thisID());
858
859 alreadyDone = true;
860 }
861 }
862
863 // Use Spell or Skill
useSpell(gPanelMessage & msg,GameObject * mObj,GameObject * cObj)864 void ContainerView::useSpell(
865 gPanelMessage &msg,
866 GameObject *mObj,
867 GameObject *cObj) {
868 useConcept(msg, mObj, cObj);
869 }
870
871 // Determine if the mouse is pointing at a new object, and if so,
872 // adjust the mouse text
updateMouseText(Point16 & pickPos)873 void ContainerView::updateMouseText(Point16 &pickPos) {
874 ObjectID slotID = pickObjectID(pickPos);
875
876 // set the mouse text to null if there is no object to get hints about
877 if (slotID == Nothing) {
878 // clear out the mouse text
879 g_vm->_mouseInfo->setText(NULL);
880 mouseText[0] = 0;
881
882 // reset the last picked thingy
883 lastPickedObjectID = Nothing;
884 lastPickedObjectQuantity = -1;
885
886 // set the display alarm to false
887 objTextAlarm = false;
888
889 return;
890 }
891
892 // get handles to the object in question
893 GameObject *slotObject = GameObject::objectAddress(slotID);
894
895 if (slotID == lastPickedObjectID && slotObject->getExtra() == lastPickedObjectQuantity) {
896 return; // same object, bug out
897 } else {
898 // was not same, but is now.
899 lastPickedObjectID = slotID;
900 lastPickedObjectQuantity = slotObject->getExtra();
901
902 // clear out the mouse text
903 g_vm->_mouseInfo->setText(NULL);
904 mouseText[0] = 0;
905
906 // reset the alarm flag
907 objTextAlarm = false;
908
909 // set the hint alarm
910 containerObjTextAlarm.set(ticksPerSecond / 2);
911
912 // put the normalized text into mouseText
913 slotObject->objCursorText(mouseText, bufSize);
914 }
915 }
916
917 #if 0
918 // Functions do not appear to be called
919
920 void ContainerView::setCursorText(GameObject *obj) {
921 assert(obj);
922
923 const bufSize = 40;
924 char cursorText[bufSize];
925
926 // put the normalized text into cursorText
927 obj->objCursorText(cursorText, bufSize);
928
929 g_vm->_mouseInfo->setText(cursorText);
930 }
931
932 void ContainerView::setDelayedCursorText(GameObject *obj) {
933 // clear out the mouse text
934 g_vm->_mouseInfo->setText(NULL);
935 mouseText[0] = 0;
936
937 // reset the alarm flag
938 objTextAlarm = false;
939
940 // set the hint alarm
941 containerObjTextAlarm.set(ticksPerSecond / 2);
942
943 // put the normalized text into mouseText
944 obj->objCursorText(mouseText, bufSize);
945 }
946 #endif
947
948 /* ===================================================================== *
949 EnchantmentContainerView member functions
950 * ===================================================================== */
951
EnchantmentContainerView(gPanelList & list,ContainerNode & nd,const ContainerAppearanceDef & app,AppFunc * cmd)952 EnchantmentContainerView::EnchantmentContainerView(
953 gPanelList &list,
954 ContainerNode &nd,
955 const ContainerAppearanceDef &app,
956 AppFunc *cmd)
957 : ContainerView(list, app.viewRect, nd, app, cmd) {
958 }
959
960 // Check If Sprite Should Be Shown
pointerHit(gPanelMessage &)961 bool EnchantmentContainerView::pointerHit(gPanelMessage &) {
962 return true;
963 }
964
pointerMove(gPanelMessage &)965 void EnchantmentContainerView::pointerMove(gPanelMessage &) {}
966
967 /* ===================================================================== *
968 ReadyContainerView member functions
969 * ===================================================================== */
970
ReadyContainerView(gPanelList & list,const Rect16 & box,ContainerNode & nd,void ** backgrounds,int16 numRes,int16 numRows,int16 numCols,int16 totRows,AppFunc * cmd)971 ReadyContainerView::ReadyContainerView(
972 gPanelList &list,
973 const Rect16 &box,
974 ContainerNode &nd,
975 void **backgrounds,
976 int16 numRes,
977 int16 numRows,
978 int16 numCols,
979 int16 totRows,
980 AppFunc *cmd)
981 : ContainerView(list, box, nd, readyContainerAppearance, cmd) {
982 // Over-ride row and column info in appearance record.
983 visibleRows = numRows;
984 visibleCols = numCols;
985 totalRows = totRows;
986
987 if (backgrounds) {
988 backImages = backgrounds;
989 numIm = numRes;
990 } else {
991 backImages = NULL;
992 numIm = 0;
993 }
994 }
995
setScrollOffset(int8 num)996 void ReadyContainerView::setScrollOffset(int8 num) {
997 if (num > 0) {
998 scrollPosition = num;
999 }
1000 }
1001
timerTick(gPanelMessage & msg)1002 void ReadyContainerView::timerTick(gPanelMessage &msg) {
1003 // validate objToGet and make sure that the number selected for move
1004 // is less then or equal to the number of items present in the merged object
1005 if (objToGet && amountIndY != -1) {
1006 ContainerView::timerTick(msg);
1007
1008 // redraw the container to draw the amount indicator
1009 draw();
1010 }
1011 }
1012
drawClipped(gPort & port,const Point16 & offset,const Rect16 & r)1013 void ReadyContainerView::drawClipped(
1014 gPort &port,
1015 const Point16 &offset,
1016 const Rect16 &r) {
1017 // Coordinates to draw the inventory icon at.
1018 int16 x,
1019 y;
1020
1021 // Coordinates for slot 0,0.
1022 int16 originX = _extent.x - offset.x + iconOrigin.x,
1023 originY = _extent.y - offset.y + iconOrigin.y;
1024
1025 // Row an column number of the inventory slot.
1026 int16 col,
1027 row;
1028
1029 ObjectID objID;
1030 GameObject *item;
1031
1032 // Iterator class for the container.
1033 ContainerIterator iter(containerObject);
1034
1035 // Color set to draw the object.
1036 ColorTable objColors;
1037
1038 // If there's no overlap between extent and clip, then return.
1039 if (!_extent.overlap(r)) return;
1040
1041 // Draw the boxes for visible rows and cols.
1042
1043 if (backImages) {
1044 int16 i;
1045 Point16 drawOrg(_extent.x - offset.x + backOriginX,
1046 _extent.y - offset.y + backOriginY);
1047
1048 for (y = drawOrg.y, row = 0;
1049 row < visibleRows;
1050 row++, y += iconSpacing.y + iconHeight) {
1051 // reset y for background image stuff
1052 //y = drawOrg.y;
1053
1054 for (i = 0, x = drawOrg.x, col = 0;
1055 col < visibleCols;
1056 i++, col++, x += iconSpacing.x + iconWidth) {
1057 Point16 pos(x, y);
1058
1059 if (isGhosted()) drawCompressedImageGhosted(port, pos, backImages[i % numIm]);
1060 else drawCompressedImage(port, pos, backImages[i % numIm]);
1061 }
1062
1063 }
1064 } else {
1065 for (y = originY, row = 0;
1066 row < visibleRows;
1067 row++, y += iconSpacing.y + iconHeight) {
1068
1069 for (x = originX, col = 0;
1070 col < visibleCols;
1071 col++, x += iconSpacing.x + iconWidth) {
1072 // REM: We need to come up with some way of
1073 // indicating how to render the pattern data which
1074 // is behind the object...
1075 port.setColor(14);
1076 port.fillRect(x, y, iconWidth, iconHeight);
1077
1078 }
1079
1080 }
1081
1082 }
1083
1084 // Iterate through each item within the container.
1085 while ((objID = iter.next(&item)) != Nothing) {
1086
1087 TilePoint objLoc;
1088 ProtoObj *objProto = item->proto();
1089
1090 // If Intangible Container then don't show it.
1091 if ((objProto->containmentSet() & (ProtoObj::isContainer | ProtoObj::isIntangible)) == (ProtoObj::isContainer | ProtoObj::isIntangible))
1092 continue;
1093
1094 objLoc = item->getLocation();
1095
1096 if (objLoc.z == 0) continue;
1097
1098 // Draw object only if visible and in a visible row & col.
1099 if (objLoc.u >= scrollPosition &&
1100 objLoc.u < scrollPosition + visibleRows &&
1101 objLoc.v < visibleCols &&
1102 isVisible(item)) {
1103 Sprite *spr;
1104 ProtoObj *proto = item->proto();
1105 Point16 sprPos;
1106
1107 y = originY + (objLoc.u - scrollPosition) * (iconSpacing.y + iconHeight);
1108 x = originX + objLoc.v * (iconSpacing.x + iconWidth);
1109
1110 // Get the address of the sprite.
1111 spr = proto->getSprite(item, ProtoObj::objInContainerView).sp;
1112
1113 sprPos.x = x + ((iconWidth - spr->size.x) >> 1)
1114 - spr->offset.x;
1115 sprPos.y = y + ((iconHeight - spr->size.y) >> 1)
1116 - spr->offset.y;
1117
1118 if (isGhosted()) return;
1119
1120 // Draw the "in use" indicator.
1121 if (backImages && proto->isObjectBeingUsed(item)) {
1122 drawCompressedImage(port,
1123 Point16(x - 4, y - 4), backImages[3]);
1124 }
1125
1126 // Build the color table.
1127 item->getColorTranslation(objColors);
1128
1129 // Draw the sprite into the port
1130 DrawColorMappedSprite(
1131 port,
1132 sprPos,
1133 spr,
1134 objColors);
1135
1136 // check to see if selecting amount for this objec
1137 if (objToGet == item) {
1138 Point16 selectorPos = Point16(x + ((iconWidth - selectorX) >> 1),
1139 y + ((iconHeight - selectorY) >> 1));
1140
1141 // draw the selector thingy
1142 drawSelector(port, selectorPos);
1143
1144 // set the position of the inc center
1145 amountIndY = y - (selectorY >> 1) + 28; // extent.y;
1146 } else drawQuantity(port, item, objProto, x, y);
1147 }
1148 }
1149 }
1150
1151 /* ===================================================================== *
1152 ContainerWindow member functions
1153 * ===================================================================== */
1154
1155 // ContainerWindow class
1156
ContainerWindow(ContainerNode & nd,const ContainerAppearanceDef & app,const char saveas[])1157 ContainerWindow::ContainerWindow(ContainerNode &nd,
1158 const ContainerAppearanceDef &app,
1159 const char saveas[])
1160 : FloatingWindow(nd.position, 0, saveas, cmdWindowFunc) {
1161 // Initialize view to NULL.
1162 view = NULL;
1163
1164 // create the close button for this window
1165 closeCompButton = new GfxCompButton(
1166 *this,
1167 app.closeRect, // rect for button
1168 containerRes, // resource context
1169 app.closeResID[0],
1170 app.closeResID[1],
1171 0,
1172 cmdCloseButtonFunc); // mind app func
1173 }
1174
1175 // Virtual destructor (base does nothing)
~ContainerWindow(void)1176 ContainerWindow::~ContainerWindow(void) {}
1177
getView(void)1178 ContainerView &ContainerWindow::getView(void) {
1179 return *view;
1180 }
1181
1182 /* ===================================================================== *
1183 ScrollableContainerWindow member functions
1184 * ===================================================================== */
1185
ScrollableContainerWindow(ContainerNode & nd,const ContainerAppearanceDef & app,const char saveas[])1186 ScrollableContainerWindow::ScrollableContainerWindow(
1187 ContainerNode &nd, const ContainerAppearanceDef &app, const char saveas[])
1188 : ContainerWindow(nd, app, saveas) {
1189 view = new ContainerView(*this, app.viewRect, nd, app);
1190
1191 // make the button conected to this window
1192 scrollCompButton = new GfxCompButton(
1193 *this,
1194 app.scrollRect, // rect for button
1195 containerRes, // resource context
1196 app.scrollResID[0], // resource handle name
1197 app.scrollResID[1],
1198 0,
1199 cmdScrollFunc); // mind app func
1200
1201 assert(view != NULL);
1202 assert(scrollCompButton != NULL);
1203 }
1204
1205 /* ===================================================================== *
1206 TangibleContainerWindow member functions
1207 * ===================================================================== */
1208
TangibleContainerWindow(ContainerNode & nd,const ContainerAppearanceDef & app)1209 TangibleContainerWindow::TangibleContainerWindow(
1210 ContainerNode &nd, const ContainerAppearanceDef &app)
1211 : ScrollableContainerWindow(nd, app, "ObjectWindow") {
1212 #if DEBUG
1213 assert(view->containerObject);
1214 assert(view->containerObject->proto());
1215 #endif
1216
1217 const int weightIndicatorType = 2;
1218 objRect = app.iconRect;
1219 deathFlag = nd.getType() == ContainerNode::deadType;
1220 containerSpriteImg = NULL;
1221
1222 // setup the mass and weight indicator
1223 if (deathFlag) {
1224 // set the decorations for this window
1225 setDecorations(deathDecorations,
1226 ARRAYSIZE(deathDecorations),
1227 containerRes, 'F', 'R', 'M');
1228 massWeightIndicator = NULL;
1229 } else {
1230 const StaticWindow *winDecs[] = {
1231 brassDecorations,
1232 clothDecorations,
1233 steelDecorations,
1234 woodDecorations
1235 };
1236 uint16 bgndType = view->containerObject->proto()->appearanceType;
1237
1238 assert(bgndType < 4);
1239
1240 setContainerSprite(); // show at the top of the box
1241
1242 // set the decorations for this window
1243 setDecorations(winDecs[bgndType],
1244 ARRAYSIZE(brassDecorations), // brass was arb, all should have same
1245 containerRes, 'F', 'R', 'M');
1246
1247 // set the userdata such that we can extract the container object later
1248 // through an appfunc.
1249 this->userData = view->containerObject;
1250
1251 massWeightIndicator = new CMassWeightIndicator(
1252 this,
1253 Point16(app.massRect.x, app.massRect.y),
1254 weightIndicatorType,
1255 deathFlag);
1256 }
1257 }
1258
~TangibleContainerWindow(void)1259 TangibleContainerWindow::~TangibleContainerWindow(void) {
1260 if (massWeightIndicator) delete massWeightIndicator;
1261 if (containerSpriteImg) delete containerSpriteImg;
1262 }
1263
setContainerSprite(void)1264 void TangibleContainerWindow::setContainerSprite(void) {
1265 // pointer to sprite data that will be drawn
1266 Sprite *spr;
1267 ProtoObj *proto = view->containerObject->proto();
1268 Point16 sprPos;
1269 char dummy = '\0';
1270
1271 // Get the address of the sprite.
1272 spr = proto->getSprite(view->containerObject, ProtoObj::objInContainerView).sp;
1273
1274 sprPos.x = objRect.x - (spr->size.x >> 1); //objRect.x + ( spr->size.x >> 1 );
1275 sprPos.y = objRect.y - (spr->size.y >> 1);
1276
1277 containerSpriteImg = new GfxSpriteImage(
1278 *this,
1279 Rect16(sprPos.x,
1280 sprPos.y,
1281 objRect.height,
1282 objRect.width),
1283 view->containerObject,
1284 dummy,
1285 0,
1286 NULL);
1287 }
1288
massBulkUpdate()1289 void TangibleContainerWindow::massBulkUpdate() {
1290 if (massWeightIndicator) { // Death container doesn't have MW indicator
1291 // set the indicators to the correct mass and bulk
1292 massWeightIndicator->setMassPie(view->totalMass);
1293 massWeightIndicator->setBulkPie(view->totalBulk);
1294 }
1295 }
1296
drawClipped(gPort & port,const Point16 & offset,const Rect16 & clip)1297 void TangibleContainerWindow::drawClipped(
1298 gPort &port,
1299 const Point16 &offset,
1300 const Rect16 &clip) {
1301 if (!_extent.overlap(clip)) return;
1302
1303 // draw the decorations
1304 ScrollableContainerWindow::drawClipped(port, offset, clip);
1305 }
1306
1307 /* ===================================================================== *
1308 IntangibleContainerWindow member functions
1309 * ===================================================================== */
1310
IntangibleContainerWindow(ContainerNode & nd,const ContainerAppearanceDef & app)1311 IntangibleContainerWindow::IntangibleContainerWindow(
1312 ContainerNode &nd, const ContainerAppearanceDef &app)
1313 : ScrollableContainerWindow(nd, app, "MentalWindow") {
1314 // make the button conected to this window
1315 mindSelectorCompButton = new GfxMultCompButton(
1316 *this,
1317 Rect16(49, 15 - 13, 52, 67),
1318 containerRes,
1319 'H', 'E', 'D', 1, 3, 1,
1320 0,
1321 cmdMindContainerFunc); // mind app func
1322
1323 assert(mindSelectorCompButton != NULL);
1324
1325 mindSelectorCompButton->setResponse(false);
1326
1327 // set the decorations for this window
1328 setDecorations(mentalDecorations,
1329 ARRAYSIZE(mentalDecorations),
1330 containerRes, 'F', 'R', 'M');
1331
1332 setMindContainer(nd.mindType, *this);
1333 }
1334
1335 /* ===================================================================== *
1336 EnchantmentContainerWindow member functions
1337 * ===================================================================== */
1338
EnchantmentContainerWindow(ContainerNode & nd,const ContainerAppearanceDef & app)1339 EnchantmentContainerWindow::EnchantmentContainerWindow(
1340 ContainerNode &nd, const ContainerAppearanceDef &app)
1341 : ContainerWindow(nd, app, "EnchantmentWindow") {
1342 view = new EnchantmentContainerView(*this, nd, app);
1343
1344 // make the button conected to this window
1345 scrollCompButton = new GfxCompButton(
1346 *this,
1347 app.scrollRect, // rect for button
1348 containerRes, // resource context
1349 app.scrollResID[0], // resource handle name
1350 app.scrollResID[1],
1351 0,
1352 cmdScrollFunc); // mind app func
1353
1354 assert(view != NULL);
1355 assert(scrollCompButton != NULL);
1356 }
1357
1358 /* ===================================================================== *
1359 ContainerNode functions
1360 * ===================================================================== */
1361
ContainerNode(ContainerManager & cl,ObjectID id,int typ)1362 ContainerNode::ContainerNode(ContainerManager &cl, ObjectID id, int typ) {
1363 GameObject *obj = GameObject::objectAddress(id);
1364 PlayerActorID ownerID;
1365
1366 // Convert the possessor() of the object to a player actor ID,
1367 // if it is indeed a player actor; Else set to "nobody".
1368 if (isActor(id)) {
1369 if (actorIDToPlayerID(id, ownerID) == false)
1370 ownerID = ContainerNode::nobody;
1371 } else {
1372 ObjectID possessor = obj->possessor();
1373
1374 if (possessor == Nothing || actorIDToPlayerID(possessor, ownerID) == false)
1375 ownerID = ContainerNode::nobody;
1376 }
1377
1378 // Compute the initial position of the container window
1379 switch (typ) {
1380 case readyType:
1381 break;
1382
1383 case deadType:
1384 position = deathContainerAppearance.defaultWindowPos;
1385 break;
1386
1387 case mentalType:
1388 mindType = 0; //protoClassIdeaContainer;
1389 position = mentalContainerAppearance.defaultWindowPos;
1390 break;
1391
1392 case physicalType:
1393 position = physicalContainerAppearance.defaultWindowPos;
1394 break;
1395
1396 case enchantType:
1397 position = enchantmentContainerAppearance.defaultWindowPos;
1398 break;
1399 }
1400
1401 // Fill in the initial values.
1402 window = NULL;
1403 type = typ;
1404 object = id;
1405 owner = ownerID;
1406 action = 0;
1407 mindType = 0;
1408
1409 // Add to container list.
1410 cl.add(this);
1411 }
1412
1413 // Return the container window for a container node, if it is visible
getWindow(void)1414 ContainerWindow *ContainerNode::getWindow(void) {
1415 return window;
1416 }
1417
1418 // Return the container view for a container node, if it is visible
getView(void)1419 ContainerView *ContainerNode::getView(void) {
1420 return window ? &window->getView() : NULL;
1421 }
1422
1423 // Destructor
~ContainerNode()1424 ContainerNode::~ContainerNode() {
1425 // Close the container window.
1426 hide();
1427
1428 // Remove from container list
1429 g_vm->_cnm->remove(this);
1430 }
1431
read(Common::InSaveFile * in)1432 void ContainerNode::read(Common::InSaveFile *in) {
1433 // Restore fields
1434 object = in->readUint16LE();
1435 type = in->readByte();
1436 owner = in->readByte();
1437 position.read(in);
1438 mindType = in->readByte();
1439 window = NULL;
1440 action = 0;
1441
1442 bool shown = in->readUint16LE();
1443
1444 // If this container was shown, re-show it
1445 if (shown)
1446 markForShow();
1447
1448 debugC(4, kDebugSaveload, "... object = %d", object);
1449 debugC(4, kDebugSaveload, "... type = %d", type);
1450 debugC(4, kDebugSaveload, "... owner = %d", owner);
1451 debugC(4, kDebugSaveload, "... position = (%d, %d, %d, %d)", position.x, position.y, position.width, position.height);
1452 debugC(4, kDebugSaveload, "... mindType = %d", mindType);
1453 debugC(4, kDebugSaveload, "... shown = %d", shown);
1454 }
1455
write(Common::MemoryWriteStreamDynamic * out)1456 void ContainerNode::write(Common::MemoryWriteStreamDynamic *out) {
1457 // Store fields
1458 out->writeUint16LE(object);
1459 out->writeByte(type);
1460 out->writeByte(owner);
1461 position.write(out);
1462 out->writeByte(mindType);
1463 out->writeUint16LE(window != NULL);
1464
1465 debugC(4, kDebugSaveload, "... object = %d", object);
1466 debugC(4, kDebugSaveload, "... type = %d", type);
1467 debugC(4, kDebugSaveload, "... owner = %d", owner);
1468 debugC(4, kDebugSaveload, "... position = (%d, %d, %d, %d)", position.x, position.y, position.width, position.height);
1469 debugC(4, kDebugSaveload, "... mindType = %d", mindType);
1470 debugC(4, kDebugSaveload, "... shown = %d", window != NULL);
1471 }
1472
1473 // Close the container window, but leave the node.
hide(void)1474 void ContainerNode::hide(void) {
1475 // close the window, but don't close the object.
1476 if (type != readyType && window != NULL) {
1477 position = window->getExtent(); // Save old window position
1478 window->close();
1479 delete window;
1480 window = NULL;
1481 }
1482 }
1483
1484 // Open the cotainer window, given the node info.
show(void)1485 void ContainerNode::show(void) {
1486 ProtoObj *proto = GameObject::protoAddress(object);
1487
1488 assert(proto);
1489
1490 // open the window; Object should already be "open"
1491 if (window == NULL) {
1492 switch (type) {
1493 case physicalType:
1494 physicalContainerAppearance.rows = proto->getViewableRows();
1495 physicalContainerAppearance.cols = proto->getViewableCols();
1496 physicalContainerAppearance.totRows = proto->getMaxRows();
1497 window = new TangibleContainerWindow(*this, physicalContainerAppearance);
1498 break;
1499
1500 case deadType:
1501 deathContainerAppearance.rows = proto->getViewableRows();
1502 deathContainerAppearance.cols = proto->getViewableCols();
1503 deathContainerAppearance.totRows = proto->getMaxRows();
1504 window = new TangibleContainerWindow(*this, deathContainerAppearance);
1505 break;
1506
1507 case mentalType:
1508 window = new IntangibleContainerWindow(*this, mentalContainerAppearance);
1509 break;
1510
1511 case enchantType:
1512 window = new EnchantmentContainerWindow(*this, enchantmentContainerAppearance);
1513 break;
1514
1515 case readyType:
1516 default:
1517 return;
1518 }
1519 }
1520
1521 window->open();
1522 }
1523
update(void)1524 void ContainerNode::update(void) {
1525 if (type == readyType) {
1526 // Update ready containers if they are enabled
1527 if (TrioCviews[owner]->getEnabled()) TrioCviews[owner]->invalidate();
1528 if (indivCviewTop->getEnabled()) indivCviewTop->invalidate();
1529 if (indivCviewBot->getEnabled()) indivCviewBot->invalidate();
1530
1531 // If the container to update is the center brother's ready container.
1532 if (isIndivMode() && owner == getCenterActorPlayerID()) {
1533 // Update player's mass & weight indicator...
1534 MassWeightIndicator->update();
1535 }
1536 } else if (window) {
1537 getView()->invalidate();
1538 window->massBulkUpdate();
1539 }
1540 }
1541
1542 // Find a container node, given a specific object
find(ObjectID id)1543 ContainerNode *ContainerManager::find(ObjectID id) {
1544 for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it)
1545 if ((*it)->object == id)
1546 return *it;
1547
1548 return NULL;
1549 }
1550
find(ObjectID id,int16 type)1551 ContainerNode *ContainerManager::find(ObjectID id, int16 type) {
1552 for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it)
1553 if ((*it)->object == id && (*it)->type == type)
1554 return *it;
1555
1556 return NULL;
1557 }
1558
1559 // returns true if the object represented by the container can be
1560 // accessed by the player.
isAccessable(ObjectID enactor)1561 bool ContainerNode::isAccessable(ObjectID enactor) {
1562 Actor *a = (Actor *)GameObject::objectAddress(enactor);
1563 ObjectID holder;
1564 GameObject *obj = GameObject::objectAddress(object);
1565 int32 dist;
1566
1567 // REM: We really ought to do a line-of-sight test here.
1568
1569 // Calculate distance between actor and container.
1570 dist = (a->getLocation() - obj->getWorldLocation()).quickHDistance();
1571
1572 // If the container object is too far away we can't access any containers.
1573 // Note: Actors are not considered to be in possession of themselves...
1574 holder = obj->possessor();
1575 if (holder != Nothing || isActor(object)) {
1576 // "Reach" for other players is further than for other objects
1577 if (holder != a->thisID() && dist > 96)
1578 return false;
1579 } else if (dist > kMaxOpenDistance)
1580 return false;
1581
1582 return true;
1583 }
1584
1585 // Change the owner of a ready container (for indiv mode)
changeOwner(int16 newOwner)1586 void ContainerNode::changeOwner(int16 newOwner) {
1587 owner = newOwner;
1588 object = getPlayerActorAddress(newOwner)->getActorID();
1589 }
1590
1591 /* ===================================================================== *
1592 ContainerManager functions
1593 * ===================================================================== */
1594
setPlayerNum(PlayerActorID playerNum)1595 void ContainerManager::setPlayerNum(PlayerActorID playerNum) {
1596 // Close all containers which are not on the ground and not owned
1597 // by the current protagonist.
1598 for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it) {
1599 ContainerNode *n = *it;
1600
1601 if (n->owner != ContainerNode::nobody && n->owner != playerNum)
1602 n->hide();
1603 }
1604
1605 // Open any containers which belong to the new protagonist.
1606 for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it) {
1607 ContainerNode *n = *it;
1608
1609 if (n->owner == playerNum)
1610 n->markForShow();
1611 }
1612 }
1613
doDeferredActions(void)1614 void ContainerManager::doDeferredActions(void) {
1615 Common::List<ContainerNode *>::iterator nextIt;
1616 Actor *a = getCenterActor();
1617 TilePoint tp = a->getLocation();
1618 GameObject *world = a->parent();
1619
1620 // Close all containers which are not on the ground and not owned
1621 // by the current protagonist.
1622 for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); it = nextIt) {
1623 nextIt = it;
1624 nextIt++;
1625 ContainerNode *n = *it;
1626
1627 // If the object is not in a player inventory (i.e. on the ground)
1628 if (n->owner == ContainerNode::nobody) {
1629 // If the object is in a different world, or too far away
1630 // from the protagonist, then quietly close the object.
1631 GameObject *obj = GameObject::objectAddress(n->object);
1632 if (obj->world() != world
1633 || (obj->getWorldLocation() - tp).quickHDistance() > kMaxOpenDistance) {
1634 // Close object image and window (silently)
1635 obj->setFlags(0, objectOpen);
1636 delete n;
1637 continue;
1638 }
1639 }
1640
1641 if (n->action & ContainerNode::actionDelete) {
1642 delete n;
1643 continue;
1644 }
1645
1646 if (n->action & ContainerNode::actionHide) {
1647 n->hide();
1648 } else {
1649 if (n->action & ContainerNode::actionShow) n->show();
1650 if (n->action & ContainerNode::actionUpdate) n->update();
1651 }
1652
1653 n->action = 0;
1654 }
1655 }
1656
setUpdate(ObjectID id)1657 void ContainerManager::setUpdate(ObjectID id) {
1658 // Close all containers which are not on the ground and not owned
1659 // by the current protagonist.
1660 for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it) {
1661 ContainerNode *n = *it;
1662
1663 if (n->object == id)
1664 n->update();
1665 else if (n->type == ContainerNode::mentalType // Special case for mind containers
1666 && n->object == GameObject::objectAddress(id)->IDParent())
1667 n->update();
1668 }
1669 }
1670
1671 extern int16 openMindType;
1672
1673 // General function to create a container "node". This determines what
1674 // kind of container is appropriate, and also if a container of that
1675 // type is already open.
CreateContainerNode(ObjectID id,bool open,int16)1676 ContainerNode *CreateContainerNode(ObjectID id, bool open, int16) {
1677 ContainerNode *cn = NULL;
1678 GameObject *obj = GameObject::objectAddress(id);
1679 PlayerActorID owner;
1680
1681 if (isActor(id)) {
1682 if (actorIDToPlayerID(id, owner) == false)
1683 owner = ContainerNode::nobody;
1684
1685 if (((Actor *)obj)->isDead()) {
1686 // Open dead container for dead actor
1687 if (!(cn = g_vm->_cnm->find(owner, ContainerNode::deadType)))
1688 cn = new ContainerNode(*g_vm->_cnm, id, ContainerNode::deadType);
1689 } else if (owner != ContainerNode::nobody) {
1690 return OpenMindContainer(owner, open, /*mType*/ openMindType);
1691 }
1692 #if DEBUG
1693 else fatal("Attempt to open non-dead actor as a container.\n");
1694 #endif
1695 } else {
1696 if (actorIDToPlayerID(obj->possessor(), owner) == false)
1697 owner = ContainerNode::nobody;
1698
1699 if (!(cn = g_vm->_cnm->find(id, ContainerNode::physicalType)))
1700 cn = new ContainerNode(*g_vm->_cnm, id, ContainerNode::physicalType);
1701 }
1702
1703 // If node was successfull created, and we wanted it open, and the owner
1704 // is the center actor or no-actor then make the container window visible.
1705 if (cn != NULL
1706 && open
1707 && (owner == getCenterActorID() || owner == ContainerNode::nobody)) {
1708 cn->show();
1709 }
1710
1711 return cn;
1712 }
1713
CreateReadyContainerNode(PlayerActorID player)1714 ContainerNode *CreateReadyContainerNode(PlayerActorID player) {
1715 return new ContainerNode(*g_vm->_cnm,
1716 getPlayerActorAddress(player)->getActorID(),
1717 ContainerNode::readyType);
1718 }
1719
OpenMindContainer(PlayerActorID player,int16 open,int16 type)1720 ContainerNode *OpenMindContainer(PlayerActorID player, int16 open, int16 type) {
1721 ContainerNode *cn;
1722 ObjectID id = getPlayerActorAddress(player)->getActorID();
1723
1724 if (!(cn = g_vm->_cnm->find(id, ContainerNode::mentalType))) {
1725 cn = new ContainerNode(*g_vm->_cnm, id, ContainerNode::mentalType);
1726 cn->mindType = type;
1727
1728 // If node was successfull created, and we wanted it open, and the owner
1729 // is the center actor or no-actor then make the container window visible.
1730 if (open && id == getCenterActorID()) {
1731 cn->show();
1732 }
1733 } else {
1734 IntangibleContainerWindow *cw = (IntangibleContainerWindow *)cn->getWindow();
1735
1736 if (cw && (type != cn->mindType)) {
1737 cn->mindType = type;
1738 setMindContainer(cn->mindType, *cw);
1739 cw->update(cw->getView().getExtent());
1740 }
1741 }
1742 return cn;
1743 }
1744
1745 /* ===================================================================== *
1746 Misc. functions
1747 * ===================================================================== */
1748
initContainers(void)1749 void initContainers(void) {
1750 if (containerRes == NULL)
1751 containerRes = resFile->newContext(MKTAG('C', 'O', 'N', 'T'), "cont.resources");
1752
1753 selImage = g_vm->_imageCache->requestImage(imageRes, MKTAG('A', 'M', 'N', 'T'));
1754 }
1755
cleanupContainers(void)1756 void cleanupContainers(void) {
1757 if (selImage)
1758 g_vm->_imageCache->releaseImage(selImage);
1759 if (containerRes)
1760 resFile->disposeContext(containerRes);
1761
1762 selImage = NULL;
1763 containerRes = NULL;
1764 }
1765
initContainerNodes(void)1766 void initContainerNodes(void) {
1767 #if DEBUG
1768 // Verify the globalContainerList only has ready ContainerNodes
1769
1770 ContainerNode *node;
1771 bool onlyReady = true;
1772
1773 for (node = g_vm->_cnm->first(); node != NULL; node = node->next()) {
1774 if (node->getType() != ContainerNode::readyType) {
1775 onlyReady = false;
1776 break;
1777 }
1778 }
1779
1780 assert(onlyReady);
1781 #endif
1782 }
1783
saveContainerNodes(Common::OutSaveFile * outS)1784 void saveContainerNodes(Common::OutSaveFile *outS) {
1785 debugC(2, kDebugSaveload, "Saving Container Nodes");
1786
1787 int i = 0;
1788 int16 numNodes = 0;
1789
1790 // Make sure there are no pending container view actions
1791 g_vm->_cnm->doDeferredActions();
1792
1793 // Count the number of nodes to save
1794 for (Common::List<ContainerNode *>::iterator it = g_vm->_cnm->_list.begin(); it != g_vm->_cnm->_list.end(); ++it) {
1795 ContainerNode *n = *it;
1796
1797 if (n->getType() != ContainerNode::readyType)
1798 numNodes++;
1799 }
1800
1801 outS->write("CONT", 4);
1802 CHUNK_BEGIN;
1803 // Store the number of nodes to save
1804 out->writeSint16LE(numNodes);
1805
1806 debugC(3, kDebugSaveload, "... numNodes = %d", numNodes);
1807
1808 // Store the nodes
1809 for (Common::List<ContainerNode *>::iterator it = g_vm->_cnm->_list.begin(); it != g_vm->_cnm->_list.end(); ++it) {
1810 ContainerNode *n = *it;
1811
1812 if (n->getType() != ContainerNode::readyType) {
1813 debugC(3, kDebugSaveload, "Saving ContainerNode %d", i++);
1814 n->write(out);
1815 }
1816 }
1817 CHUNK_END;
1818 }
1819
loadContainerNodes(Common::InSaveFile * in)1820 void loadContainerNodes(Common::InSaveFile *in) {
1821 debugC(2, kDebugSaveload, "Loading Container Nodes");
1822
1823 ContainerNode *node;
1824 Common::List<ContainerNode *> tempList;
1825 int16 numNodes;
1826
1827 // Read in the number of container nodes to restore
1828 numNodes = in->readSint16LE();
1829 debugC(3, kDebugSaveload, "... numNodes = %d", numNodes);
1830
1831 for (int i = 0; i < numNodes; i++) {
1832 debugC(3, kDebugSaveload, "Loading ContainerNode %d", i);
1833
1834 node = new ContainerNode;
1835
1836 // Restore the state of the node
1837 node->read(in);
1838
1839 // Add it back to the container list
1840 g_vm->_cnm->add(node);
1841 }
1842
1843 assert(tempList.empty());
1844 }
1845
cleanupContainerNodes(void)1846 void cleanupContainerNodes(void) {
1847 if (g_vm->_cnm == nullptr)
1848 return;
1849
1850 Common::Array<ContainerNode *> deletionArray;
1851
1852 for (Common::List<ContainerNode *>::iterator it = g_vm->_cnm->_list.begin(); it != g_vm->_cnm->_list.end(); ++it) {
1853 ContainerNode *n = *it;
1854
1855 if (n->getType() != ContainerNode::readyType)
1856 deletionArray.push_back(*it);
1857 }
1858
1859 for (uint i = 0; i < deletionArray.size(); ++i)
1860 delete deletionArray[i];
1861 }
1862
updateContainerWindows(void)1863 void updateContainerWindows(void) {
1864 g_vm->_cnm->doDeferredActions();
1865 }
1866
setMindContainer(int index,IntangibleContainerWindow & cw)1867 void setMindContainer(int index, IntangibleContainerWindow &cw) {
1868 const int classTable[] = {
1869 protoClassIdeaContainer,
1870 protoClassSkillContainer,
1871 protoClassMemoryContainer,
1872 protoClassPsychContainer // Not used anymore
1873 };
1874
1875 ObjectID ownerID = cw.getView().node.getObject();
1876 GameObject *object = GameObject::objectAddress(ownerID);
1877 ContainerIterator iter(object);
1878 GameObject *item;
1879 ObjectID id;
1880
1881 assert(index >= 0);
1882 assert(index < ARRAYSIZE(classTable));
1883
1884 int containerClass = classTable[index];
1885
1886 cw.mindSelectorCompButton->setCurrent(index);
1887 cw.mindSelectorCompButton->invalidate();
1888
1889 while ((id = iter.next(&item)) != Nothing) {
1890 if (item->proto()->classType == containerClass) {
1891 cw.view->setContainer(item);
1892 return;
1893 }
1894 }
1895 }
1896
APPFUNC(cmdMindContainerFunc)1897 APPFUNC(cmdMindContainerFunc) {
1898 if (ev.panel && ev.eventType == gEventNewValue /* && ev.value */) {
1899 IntangibleContainerWindow *cw = (IntangibleContainerWindow *)ev.window;
1900 ContainerNode &nd = cw->getView().node;
1901 int newMindType = nd.mindType;
1902
1903 const Rect16 idea(0, 0, 22, 67), // idea button click area
1904 skill(22, 0, 11, 67), // skill area
1905 memory(33, 0, 9, 67), // memory area
1906 psych(42, 0, 10, 67); // psych(ic?) area
1907
1908 if (idea.ptInside(ev.mouse)) newMindType = 0; //protoClassIdeaContainer;
1909 if (skill.ptInside(ev.mouse)) newMindType = 1; //protoClassSkillContainer;
1910 if (memory.ptInside(ev.mouse)) newMindType = 2; //protoClassMemoryContainer;
1911 // if (psych.ptInside(ev.mouse)) newMindType = protoClassPsychContainer;
1912
1913 if (newMindType != nd.mindType) {
1914 nd.mindType = newMindType;
1915 setMindContainer(nd.mindType, *cw);
1916 cw->update(cw->getView().getExtent());
1917 }
1918 } else if (ev.eventType == gEventMouseMove) {
1919 //if (ev.value == gCompImage::enter)
1920 {
1921 const Rect16 idea(0, 0, 22, 67), // idea button click area
1922 skill(22, 0, 11, 67), // skill area
1923 memory(33, 0, 9, 67); // memory area
1924
1925
1926 const int BUF_SIZE = 64;
1927 char textBuffer[BUF_SIZE];
1928 int mindType = -1;
1929
1930
1931 if (idea.ptInside(ev.mouse)) mindType = 0; //protoClassIdeaContainer;
1932 if (skill.ptInside(ev.mouse)) mindType = 1; //protoClassSkillContainer;
1933 if (memory.ptInside(ev.mouse)) mindType = 2; //protoClassMemoryContainer;
1934
1935 switch (mindType) {
1936 case 0:
1937 sprintf(textBuffer, IDEAS_MENTAL);
1938 break;
1939
1940 case 1:
1941 sprintf(textBuffer, SPELL_MENTAL);
1942 break;
1943
1944 case 2:
1945 sprintf(textBuffer, SKILL_MENTAL);
1946 break;
1947
1948 case -1:
1949 textBuffer[0] = 0;
1950 break;
1951
1952 default:
1953 assert(false); // should never get here
1954 break;
1955 }
1956
1957 // set the text in the cursor
1958 g_vm->_mouseInfo->setText(textBuffer);
1959 }
1960
1961 if (ev.value == GfxCompImage::leave) {
1962 g_vm->_mouseInfo->setText(NULL);
1963 }
1964 }
1965 }
1966
APPFUNC(cmdCloseButtonFunc)1967 APPFUNC(cmdCloseButtonFunc) {
1968 if (ev.eventType == gEventNewValue && ev.value == 1) {
1969 ContainerWindow *win = (ContainerWindow *)ev.window;
1970
1971 if (win->getView().node.getType() == ContainerNode::mentalType) {
1972 win->getView().node.markForDelete();
1973 } else {
1974 win->containerObject()->close(getCenterActorID());
1975 }
1976 updateContainerWindows();
1977
1978 // make sure the hint text goes away
1979 if (g_vm->_mouseInfo->getObject() == NULL) {
1980 g_vm->_mouseInfo->setText(NULL);
1981 }
1982 } else if (ev.eventType == gEventMouseMove) {
1983 if (ev.value == GfxCompImage::enter) {
1984 g_vm->_mouseInfo->setText(CLOSE_MOUSE);
1985 } else if (ev.value == GfxCompImage::leave) {
1986 g_vm->_mouseInfo->setText(NULL);
1987 }
1988 }
1989 }
1990
APPFUNC(cmdScrollFunc)1991 APPFUNC(cmdScrollFunc) {
1992 if (ev.panel && ev.eventType == gEventNewValue && ev.value) {
1993 ScrollableContainerWindow *cw;
1994 const Rect16 upArea(0, 0, 44, 22);
1995
1996 cw = (ScrollableContainerWindow *)ev.window;
1997 if (upArea.ptInside(ev.mouse))
1998 cw->scrollUp();
1999 else
2000 cw->scrollDown();
2001 ev.window->update(cw->getView().getExtent());
2002 } else if (ev.eventType == gEventMouseMove) {
2003 if (ev.value == GfxCompImage::enter) {
2004 g_vm->_mouseInfo->setText(SCROLL_MOUSE);
2005 } else if (ev.value == GfxCompImage::leave) {
2006 g_vm->_mouseInfo->setText(NULL);
2007 }
2008 }
2009 }
2010
2011 } // end of namespace Saga2
2012