scummvm/engines/saga2/contain.cpp
2021-07-07 19:58:06 +02:00

2030 lines
57 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
*
* Based on the original sources
* Faery Tale II -- The Halls of the Dead
* (c) 1993-1996 The Wyrmkeep Entertainment Co.
*/
#define FORBIDDEN_SYMBOL_ALLOW_ALL // FIXME: Remove
#include "saga2/saga2.h"
#include "saga2/objects.h"
#include "saga2/contain.h"
#include "saga2/grabinfo.h"
#include "saga2/motion.h"
#include "saga2/uimetrcs.h"
#include "saga2/localize.h"
#include "saga2/intrface.h"
#include "saga2/spellbuk.h"
#include "saga2/imagcach.h"
#include "saga2/hresmgr.h"
#include "saga2/fontlib.h"
#include "saga2/savefile.h"
#include "saga2/pclass.r"
namespace Saga2 {
enum {
kMaxOpenDistance = 32
};
// selector image pointer
static void *selImage;
/* ===================================================================== *
Imports
* ===================================================================== */
extern ReadyContainerView *TrioCviews[kNumViews];
extern ReadyContainerView *indivCviewTop, *indivCviewBot;
extern SpellStuff spellBook[];
extern SpriteSet *objectSprites; // object sprites
extern PlayerActor playerList[]; // Master list of all PlayerActors
extern Alarm containerObjTextAlarm;
extern bool gameSetupComplete;
extern hResContext *imageRes; // image resource handle
hResContext *containerRes; // image resource handle
extern APPFUNC(cmdWindowFunc);
// Temporary...
void grabObject(ObjectID pickedObject); // turn object into mouse ptr
void releaseObject(void); // restore mouse pointer
/* Reference Types
ProtoObj::isTangible
ProtoObj::isContainer
ProtoObj::isBottle
ProtoObj::isFood
ProtoObj::isWearable
ProtoObj::isWeapon
ProtoObj::isDocument
ProtoObj::isIntangible
ProtoObj::isConcept
ProtoObj::isMemory
ProtoObj::isPsych
ProtoObj::isSpell
ProtoObj::isEnchantment
*/
// used to ignore doubleClick when doubleClick == singleClick
static bool alreadyDone;
// ID of the last object that the mouse moved over
/* ===================================================================== *
ContainerView member functions
* ===================================================================== */
// static mouse info variables
ObjectID ContainerView::lastPickedObjectID = Nothing;
int32 ContainerView::lastPickedObjectQuantity = - 1;
bool ContainerView::objTextAlarm = false;
char ContainerView::mouseText[ContainerView::bufSize] = { "" };
bool ContainerView::mouseInView = false;
uint16 ContainerView::numPicked = 1;
GameObject *ContainerView::objToGet;
int32 ContainerView::amountAccumulator = 0;
int16 ContainerView::amountIndY = -1;
//-----------------------------------------------------------------------
// Physical container appearance
static ContainerAppearanceDef physicalContainerAppearance = {
{250, 60, 268, 304 + 16},
{17 + 4, 87, 268 - 2, 304 - 87},
{13 + 8, 37, 44, 42},
{13 + 8 + 44, 37, 44, 42},
{13 + 118, 50, 36, 36},
{13 + 139, 37, 88, 43},
{ MKTAG('P', 'C', 'L', 0), MKTAG('P', 'C', 'L', 1) },
{ MKTAG('P', 'S', 'L', 0), MKTAG('P', 'S', 'L', 1) },
{13, 8},
{22, 22},
0, 0,
0
};
static StaticWindow brassDecorations[] = {
{{0, 0, 268, 86}, nullptr, 3},
{{13, 86, 242, 109}, nullptr, 4},
{{13, 195, 242, 121}, nullptr, 5}
};
static StaticWindow clothDecorations[] = {
{{0, 0, 268, 86}, nullptr, 6},
{{13, 86, 242, 109}, nullptr, 7},
{{13, 195, 242, 121}, nullptr, 8}
};
static StaticWindow steelDecorations[] = {
{{0, 0, 268, 86}, nullptr, 9},
{{13, 86, 242, 109}, nullptr, 10},
{{13, 195, 242, 121}, nullptr, 11}
};
static StaticWindow woodDecorations[] = {
{{0, 0, 268, 86}, nullptr, 12},
{{13, 86, 242, 109}, nullptr, 13},
{{13, 195, 242, 121}, nullptr, 14}
};
//-----------------------------------------------------------------------
// Death container appearance
static ContainerAppearanceDef deathContainerAppearance = {
{260, 60, 206, 250},
{2, 87, 206 - 22, 250 - 87 - 32},
{16, 24, 44, 42},
{120 + 18, 24, 44, 42},
{0, 0, 0, 0},
{0, 0, 0, 0},
{ MKTAG('D', 'C', 'L', 0), MKTAG('D', 'C', 'L', 1) },
{ MKTAG('D', 'S', 'L', 0), MKTAG('D', 'S', 'L', 1) },
{27, -4},
{22, 22},
0, 0,
0
};
// physal dialog window decorations
static StaticWindow deathDecorations[] = {
{{0, 0, 206, 250}, nullptr, 15}
};
//-----------------------------------------------------------------------
// ReadyContainer appearance
static ContainerAppearanceDef readyContainerAppearance = {
{0, 0, 0, 0},
{476, 105, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{ 0, 0 },
{ 0, 0 },
{iconOriginX - 1, iconOriginY - 1 - 8},
{iconSpacingX, iconSpacingY},
1, 3,
3
};
//-----------------------------------------------------------------------
// Mental Container appearance
static ContainerAppearanceDef mentalContainerAppearance = {
{478, 168 - 54, 158, 215},
{2, 86 - 18 - 4, 158 - 2, 215 - 66},
{2, 19, 44, 44},
{103, 40 - 18 - 4, 44, 44},
{0, 0, 0, 0},
{0, 0, 0, 0},
{ MKTAG('C', 'L', 'S', 0), MKTAG('C', 'L', 'S', 1) },
{ MKTAG('S', 'E', 'L', 0), MKTAG('S', 'E', 'L', 1) },
{3, 0},
{4, 4},
4, 4,
20
};
static StaticWindow mentalDecorations[] = {
{{0, 0, 158, 215}, nullptr, 0} // Bottom decoration panel
};
//-----------------------------------------------------------------------
// Enchantment container appearance
static ContainerAppearanceDef enchantmentContainerAppearance = {
{262, 92, 116, 202},
{2, 87, 116 - 2, 202 - 87},
{7, 50, 44, 43},
{57, 50, 44, 43},
{38, 7, 32, 32},
{0, 0, 0, 0},
{ MKTAG('A', 'A', 'A', 0), MKTAG('A', 'A', 'A', 0) },
{ MKTAG('A', 'A', 'A', 0), MKTAG('A', 'A', 'A', 0) },
{12, 98},
{16, 13},
2, 2,
2
};
//-----------------------------------------------------------------------
// ContainerView class
// Constructor
/* ContainerView::ContainerView(
gPanelList &list,
const Rect16 &box,
ContainerNode &nd,
Point16 org,
Point16 space,
int16 numRows,
int16 numCols,
int16 totRows,
uint16 ident,
AppFunc *cmd ) */
ContainerView::ContainerView(
gPanelList &list,
const Rect16 &rect,
ContainerNode &nd,
ContainerAppearanceDef &app,
AppFunc *cmd)
: gControl(list, rect, NULL, 0, cmd),
iconOrigin(app.iconOrigin),
iconSpacing(app.iconSpacing),
visibleRows(app.rows),
visibleCols(app.cols),
node(nd) {
containerObject = GameObject::objectAddress(nd.object);
scrollPosition = 0;
totalRows = app.totRows;
setMousePoll(true);
totalMass = 0;
totalBulk = 0;
numObjects = 0;
}
// Destructor
ContainerView::~ContainerView() {
}
/****** contain.cpp/ContainerView::findPane ***********************
*
* NAME
* findPane - find a ContainerView control that is
* viewing the contents of the target object
*
* SYNOPSIS
* If pane is NULL, start search at first ContainerView
* else, start search at pane->next.
*
* Iterate through ContainerView list until a
* ContainerView is found that is viewing the
* target object (obj).
*
* INPUTS
* obj Target Game Object
* pane Previously found pane, or NULL if search start
*
* RESULT
* Returns a pointer to a ContainterPanel if one is found
* or NULL if no more found.
*
********************************************************************
*/
// returns true if the object is visible for this type of
// container.
bool ContainerView::isVisible(GameObject *item) {
ProtoObj *proto = item->proto();
if (proto->containmentSet() & ProtoObj::isEnchantment)
return false;
// If Intangible Container then don't show it.
if ((proto->containmentSet() & (ProtoObj::isContainer | ProtoObj::isIntangible)) == (ProtoObj::isContainer | ProtoObj::isIntangible))
return true;
return true;
}
// total the mass, bulk, and number of all objects in container.
void ContainerView::totalObjects(void) {
ObjectID objID;
GameObject *item;
totalMass = 0;
totalBulk = 0;
numObjects = 0;
if (containerObject == NULL) return;
RecursiveContainerIterator iter(containerObject);
// See if he has enough currency
for (objID = iter.first(&item); objID != Nothing; objID = iter.next(&item)) {
// If the object is visible, then add to total mass and
// bulk.
if (isVisible(item)) {
uint16 numItems;
ProtoObj *proto = item->proto();
numObjects++;
// if it's mergeable calc using the getExtra method of
// quantity calculation
// if not, then use the objLoc.z method
if (proto->flags & ResourceObjectPrototype::objPropMergeable)
numItems = item->getExtra();
else numItems = 1;
totalMass += proto->mass * numItems;
totalBulk += proto->bulk * numItems;
}
}
// ContainerView *viewToUpdate = ContainerView::findPane( containerObject );
// viewToUpdate->getWindow()->update( viewToUpdate->getExtent() );
}
// Return the Nth visible object from this container.
ObjectID ContainerView::getObject(int16 slotNum) {
ObjectID objID;
GameObject *item;
if (containerObject == NULL) return Nothing;
ContainerIterator iter(containerObject);
// Iterate through all the objects in the container.
while ((objID = iter.next(&item)) != Nothing) {
// If the object is visible, then check which # it is,
// and return the object if slotnum has been decremented
// to zero.
if (isVisible(item)) {
if (slotNum == 0) return objID;
slotNum--;
}
}
return Nothing;
}
// REM: We need to handle the case of a NULL container here...
// Draw the contents of the container.
void ContainerView::drawClipped(
gPort &port,
const Point16 &offset,
const Rect16 &r) {
// Coordinates to draw the inventory icon at.
int16 x,
y;
// Coordinates for slot 0,0.
int16 originX = extent.x - offset.x + iconOrigin.x,
originY = extent.y - offset.y + iconOrigin.y;
ObjectID objID;
GameObject *item;
// Iterator class for the container.
ContainerIterator iter(containerObject);
// Color set to draw the object.
ColorTable objColors;
// If there's no overlap between extent and clip, then return.
if (!extent.overlap(r)) return;
// Iterate through each item within the container.
while ((objID = iter.next(&item)) != Nothing) {
TilePoint objLoc;
ProtoObj *objProto = item->proto();
objLoc = item->getLocation();
if (objLoc.z == 0) continue;
// Draw object only if visible and in a visible row & col.
if (objLoc.u >= scrollPosition
&& objLoc.u < scrollPosition + visibleRows
&& objLoc.v < visibleCols
&& isVisible(item)) {
Sprite *spr;
ProtoObj *proto = item->proto();
Point16 sprPos;
y = originY
+ (objLoc.u - scrollPosition)
* (iconSpacing.y + iconHeight);
x = originX
+ objLoc.v
* (iconSpacing.x + iconWidth);
// Get the address of the sprite.
spr = proto->getSprite(item, ProtoObj::objInContainerView).sp;
sprPos.x = x + ((iconWidth - spr->size.x) >> 1)
- spr->offset.x;
sprPos.y = y + ((iconHeight - spr->size.y) >> 1)
- spr->offset.y;
// Build the color table.
item->getColorTranslation(objColors);
// Draw the sprite into the port
DrawColorMappedSprite(
port,
sprPos,
spr,
objColors);
// check to see if selecting amount for this objec
if (objToGet == item) {
Point16 selectorPos = Point16(x + ((iconWidth - selectorX) >> 1),
y + ((iconHeight - selectorY) >> 1));
// draw the selector thingy
drawSelector(port, selectorPos);
// set the position of the inc center
amountIndY = y - (selectorY >> 1) - 12;
} else drawQuantity(port, item, objProto, x, y);
}
}
}
// draws the mereged object multi-item selector
void ContainerView::drawSelector(gPort &port, Point16 &pos) {
char buf[20];
uint8 num;
SAVE_GPORT_STATE(port);
// draw the arrow images
drawCompressedImage(port, pos, selImage);
// draw the number of items selected thus far
num = sprintf(buf, " %d ", numPicked);
port.moveTo(Point16(pos.x - ((3 * (num - 3)) + 1), pos.y + 7));
port.setFont(&Helv11Font);
port.setColor(11); // set color to white
port.setStyle(textStyleThickOutline);
port.setOutlineColor(24); // set outline color to black
port.setMode(drawModeMatte);
port.drawText(buf);
}
void ContainerView::drawQuantity(
gPort &port,
GameObject *item,
ProtoObj *objProto,
int16 x,
int16 y) {
int16 quantity;
quantity = (objProto->flags & ResourceObjectPrototype::objPropMergeable)
? item->getExtra()
: item->getLocation().z;
if (quantity > 1) {
SAVE_GPORT_STATE(port);
char buf[8];
// draw the number of items selected thus far
sprintf(buf, "%d", quantity);
port.moveTo(x - 1, y + 22);
port.setFont(&Helv11Font);
port.setColor(11); // set color to white
port.setStyle(textStyleThickOutline);
port.setOutlineColor(24); // set outline color to black
port.setMode(drawModeMatte);
port.drawText(buf);
}
}
void ContainerView::setContainer(GameObject *containerObj) {
containerObject = containerObj;
totalObjects();
}
// Get the slot that the point is over
TilePoint ContainerView::pickObjectSlot(const Point16 &pickPos) {
TilePoint slot;
Point16 temp;
// Compute the u/v of the slot that they clicked on.
temp = pickPos + iconSpacing / 2 - iconOrigin;
slot.v = clamp(0, temp.x / (iconWidth + iconSpacing.x), visibleCols - 1);
slot.u = clamp(0, temp.y / (iconHeight + iconSpacing.y), visibleRows - 1) + scrollPosition;
slot.z = 1;
return slot;
}
// Get the object that the pointer is over
GameObject *ContainerView::getObject(const TilePoint &slot) {
GameObject *item;
TilePoint loc;
item = containerObject->child();
while (item != NULL) {
// Skip objects that are stacked behind other objects
if (item->getLocation().z != 0) {
ProtoObj *proto = item->proto();
loc = item->getLocation();
if (
(loc.u == slot.u) &&
(loc.v == slot.v) &&
//Skip The Enchantments
(!(proto->containmentSet() & ProtoObj::isEnchantment))
) {
return item;
}
}
item = item->next();
}
return NULL;
}
// Get the object ID that the point is over
ObjectID ContainerView::pickObjectID(const Point16 &pickPos) {
TilePoint slot;
GameObject *item;
slot = pickObjectSlot(pickPos);
item = getObject(slot);
if (item != NULL) {
return item->thisID();
} else {
return 0;
}
}
// Get the object ID that the point is over
GameObject *ContainerView::pickObject(const Point16 &pickPos) {
TilePoint slot;
GameObject *item;
slot = pickObjectSlot(pickPos);
item = getObject(slot);
return item;
}
bool ContainerView::activate(gEventType why) {
gPanel::activate(why);
return true;
}
void ContainerView::deactivate(void) {
}
void ContainerView::pointerMove(gPanelMessage &msg) {
if (msg.pointerLeave) {
lastPickedObjectID = Nothing;
lastPickedObjectQuantity = -1;
g_vm->_mouseInfo->setText(NULL);
mouseText[0] = 0;
// static bool that tells if the mouse cursor
// is in a panel
mouseInView = false;
g_vm->_mouseInfo->setDoable(true);
} else {
// if( msg.pointerEnter )
{
// static bool that tells if the mouse cursor
// is in a panel
mouseInView = true;
GameObject *mouseObject;
mouseObject = g_vm->_mouseInfo->getObject();
if (!node.isAccessable(getCenterActorID())) {
g_vm->_mouseInfo->setDoable(false);
} else if (mouseObject == NULL) {
g_vm->_mouseInfo->setDoable(true);
} else {
g_vm->_mouseInfo->setDoable(containerObject->canContain(mouseObject->thisID()));
}
}
// Determine if mouse is pointing at a new object
updateMouseText(msg.pickPos);
}
}
bool ContainerView::pointerHit(gPanelMessage &msg) {
GameObject *mouseObject;
GameObject *slotObject;
uint16 mouseSet;
slotObject = pickObject(msg.pickPos);
mouseObject = g_vm->_mouseInfo->getObject();
mouseSet = mouseObject ? mouseObject->containmentSet() : 0;
if (!g_vm->_mouseInfo->getDoable()) return false;
if (msg.doubleClick && !alreadyDone) {
dblClick(mouseObject, slotObject, msg);
} else { // single click
if (mouseObject != NULL) {
alreadyDone = true; // if object then no doubleClick
if (g_vm->_mouseInfo->getIntent() == GrabInfo::Drop) {
if (mouseSet & ProtoObj::isTangible) {
dropPhysical(msg, mouseObject, slotObject, g_vm->_mouseInfo->getMoveCount());
}
// intangibles are used by dropping them
else if ((mouseSet & ProtoObj::isConcept) ||
(mouseSet & ProtoObj::isPsych) ||
(mouseSet & ProtoObj::isSpell) ||
(mouseSet & ProtoObj::isSkill)) {
useConcept(msg, mouseObject, slotObject);
} else {
// !!!! bad state, reset cursor
g_vm->_mouseInfo->replaceObject();
}
} else if (g_vm->_mouseInfo->getIntent() == GrabInfo::Use) {
if (mouseSet & ProtoObj::isTangible) {
usePhysical(msg, mouseObject, slotObject);
} else if ((mouseSet & ProtoObj::isSpell) ||
(mouseSet & ProtoObj::isSkill)) {
g_vm->_mouseInfo->replaceObject();
} else {
useConcept(msg, mouseObject, slotObject);
}
} else {
// !!!! bad state, reset cursor
g_vm->_mouseInfo->replaceObject();
}
} else {
// default to doubleClick active
alreadyDone = false;
clickOn(msg, mouseObject, slotObject);
}
}
// total the mass and bulk of all the objects in this container
totalObjects();
window.update(extent);
return activate(gEventMouseDown);
}
void ContainerView::pointerRelease(gPanelMessage &) {
// see if in multi-item get mode
if (objToGet) {
objToGet->take(getCenterActorID(), numPicked);
// reset the flags and pointer dealing with merged object movement
objToGet = NULL;
numPicked = 1;
amountIndY = -1;
}
gPanel::deactivate();
}
void ContainerView::timerTick(gPanelMessage &msg) {
// validate objToGet and make sure that the number selected for move
// is less then or equal to the number of items present in the merged object
if (objToGet && amountIndY != -1) {
int32 rate = (amountIndY - msg.pickAbsPos.y);
rate = rate * ((rate > 0) ? rate : -rate);
// Add to the amount accumulator based on the mouse position
amountAccumulator += rate / 4 /* * accelSpeed */;
// Take the top bits of the amount accumulator and add to
// the mergeable amount.
numPicked = clamp(1,
numPicked + (amountAccumulator >> 8),
objToGet->getExtra());
// Now remove the bits that we added to the grab amount
// keep the remaining bits to accumulate for next time
amountAccumulator &= 0x00ff;
}
}
void ContainerView::dblClick(GameObject *mouseObject, GameObject *slotObject, gPanelMessage &msg) {
alreadyDone = true;
// double click stuff
dblClickOn(msg, mouseObject, slotObject);
}
// activate the click function
void ContainerView::clickOn(
gPanelMessage &,
GameObject *,
GameObject *cObj) {
if (cObj != NULL) {
if (cObj->proto()->flags & ResourceObjectPrototype::objPropMergeable) {
if (!rightButtonState()) {
// just get the object into the cursor
cObj->take(getCenterActorID(), cObj->getExtra());
} else {
// activate multi-object get interface if a mergeable object
getMerged(cObj);
g_vm->_mouseInfo->setText(NULL);
mouseText[0] = 0;
}
} else {
// just get the object into the cursor
cObj->take(getCenterActorID(), numPicked);
}
}
}
void ContainerView::getMerged(GameObject *obj) {
// reset the number picked.
numPicked = 1;
// set the object to be gotten
objToGet = obj;
}
// Activate the double click function
void ContainerView::dblClickOn(
gPanelMessage &,
GameObject *mObj,
GameObject *) {
if (mObj != NULL) {
ObjectID possessor = mObj->possessor();
ProtoObj *proto = mObj->proto();
PlayerActorID pID;
// Only player actors can be possessors as far as the UI is concerned
if (actorIDToPlayerID(possessor, pID) == false) possessor = Nothing;
g_vm->_mouseInfo->replaceObject(); //Put Object Back
if (!(proto->setUseCursor(mObj->thisID()))) {
MotionTask::useObject(
possessor == Nothing ? *getCenterActor() : * (Actor *)GameObject::objectAddress(possessor),
*mObj);
}
}
}
// drop a physical object
void ContainerView::dropPhysical(
gPanelMessage &msg,
GameObject *mObj,
GameObject *cObj,
int16 num) {
assert(g_vm->_mouseInfo->getObject() == mObj);
assert(mObj->containmentSet() & ProtoObj::isTangible);
// Place object back where it came from, temporarily
g_vm->_mouseInfo->replaceObject();
// test to check if item is accepted by container
if (containerObject->canContain(mObj->thisID())) {
Actor *centerActor = getCenterActor();
Location loc(pickObjectSlot(msg.pickPos),
containerObject->thisID());
// check if no object in the current slot
if (cObj == NULL) {
MotionTask::dropObject(*centerActor, *mObj, loc, num);
WriteStatusF(6, "No object state");
} else {
// Try dropping this object on the object in the container
MotionTask::dropObjectOnObject(*centerActor, *mObj, *cObj, num);
}
alreadyDone = true;
}
}
// use a physical object
void ContainerView::usePhysical(
gPanelMessage &msg,
GameObject *mObj,
GameObject *cObj) {
assert(g_vm->_mouseInfo->getObject() == mObj);
assert(mObj->containmentSet() & ProtoObj::isTangible);
if (cObj == NULL) {
dropPhysical(msg, mObj, cObj);
} else {
g_vm->_mouseInfo->replaceObject();
// Use mouse object on container object
MotionTask::useObjectOnObject(*getCenterActor(), *mObj, *cObj);
}
}
// Use Concept or Psycological state
void ContainerView::useConcept(
gPanelMessage &msg,
GameObject *mObj,
GameObject *cObj) {
assert(g_vm->_mouseInfo->getObject() == mObj);
assert(mObj->containmentSet() & ProtoObj::isIntangible);
g_vm->_mouseInfo->replaceObject();
// Determine if this object can go into this container
if (containerObject->canContain(mObj->thisID())) {
ObjectID centerActorID = getCenterActorID();
if (cObj == NULL) {
// If there is no object already in this slot drop the
// mouse object here
Location loc(pickObjectSlot(msg.pickPos),
containerObject->thisID());
mObj->drop(centerActorID, loc);
} else
// If there is an object here drop the mouse object onto it
mObj->dropOn(centerActorID, cObj->thisID());
alreadyDone = true;
}
}
// Use Spell or Skill
void ContainerView::useSpell(
gPanelMessage &msg,
GameObject *mObj,
GameObject *cObj) {
useConcept(msg, mObj, cObj);
}
// Determine if the mouse is pointing at a new object, and if so,
// adjust the mouse text
void ContainerView::updateMouseText(Point16 &pickPos) {
ObjectID slotID = pickObjectID(pickPos);
// set the mouse text to null if there is no object to get hints about
if (slotID == Nothing) {
// clear out the mouse text
g_vm->_mouseInfo->setText(NULL);
mouseText[0] = 0;
// reset the last picked thingy
lastPickedObjectID = Nothing;
lastPickedObjectQuantity = -1;
// set the display alarm to false
objTextAlarm = false;
return;
}
// get handles to the object in question
GameObject *slotObject = GameObject::objectAddress(slotID);
if (slotID == lastPickedObjectID && slotObject->getExtra() == lastPickedObjectQuantity) {
return; // same object, bug out
} else {
// was not same, but is now.
lastPickedObjectID = slotID;
lastPickedObjectQuantity = slotObject->getExtra();
// clear out the mouse text
g_vm->_mouseInfo->setText(NULL);
mouseText[0] = 0;
// reset the alarm flag
objTextAlarm = false;
// set the hint alarm
containerObjTextAlarm.set(ticksPerSecond / 2);
// put the normalized text into mouseText
slotObject->objCursorText(mouseText, bufSize);
}
}
#if 0
// Functions do not appear to be called
void ContainerView::setCursorText(GameObject *obj) {
assert(obj);
const bufSize = 40;
char cursorText[bufSize];
// put the normalized text into cursorText
obj->objCursorText(cursorText, bufSize);
g_vm->_mouseInfo->setText(cursorText);
}
void ContainerView::setDelayedCursorText(GameObject *obj) {
// clear out the mouse text
g_vm->_mouseInfo->setText(NULL);
mouseText[0] = 0;
// reset the alarm flag
objTextAlarm = false;
// set the hint alarm
containerObjTextAlarm.set(ticksPerSecond / 2);
// put the normalized text into mouseText
obj->objCursorText(mouseText, bufSize);
}
#endif
/* ===================================================================== *
EnchantmentContainerView member functions
* ===================================================================== */
EnchantmentContainerView::EnchantmentContainerView(
gPanelList &list,
ContainerNode &nd,
ContainerAppearanceDef &app,
AppFunc *cmd)
: ContainerView(list, app.viewRect, nd, app, cmd) {
}
// Check If Sprite Should Be Shown
bool EnchantmentContainerView::pointerHit(gPanelMessage &) {
return true;
}
void EnchantmentContainerView::pointerMove(gPanelMessage &) {}
/* ===================================================================== *
ReadyContainerView member functions
* ===================================================================== */
ReadyContainerView::ReadyContainerView(
gPanelList &list,
const Rect16 &box,
ContainerNode &nd,
void **backgrounds,
int16 numRes,
int16 numRows,
int16 numCols,
int16 totRows,
AppFunc *cmd)
: ContainerView(list, box, nd, readyContainerAppearance, cmd) {
// Over-ride row and column info in appearance record.
visibleRows = numRows;
visibleCols = numCols;
totalRows = totRows;
if (backgrounds) {
backImages = backgrounds;
numIm = numRes;
} else {
backImages = NULL;
numIm = 0;
}
}
void ReadyContainerView::setScrollOffset(int8 num) {
if (num > 0) {
scrollPosition = num;
}
}
void ReadyContainerView::timerTick(gPanelMessage &msg) {
// validate objToGet and make sure that the number selected for move
// is less then or equal to the number of items present in the merged object
if (objToGet && amountIndY != -1) {
ContainerView::timerTick(msg);
// redraw the container to draw the amount indicator
draw();
}
}
void ReadyContainerView::drawClipped(
gPort &port,
const Point16 &offset,
const Rect16 &r) {
// Coordinates to draw the inventory icon at.
int16 x,
y;
// Coordinates for slot 0,0.
int16 originX = extent.x - offset.x + iconOrigin.x,
originY = extent.y - offset.y + iconOrigin.y;
// Row an column number of the inventory slot.
int16 col,
row;
ObjectID objID;
GameObject *item;
// Iterator class for the container.
ContainerIterator iter(containerObject);
// Color set to draw the object.
ColorTable objColors;
// If there's no overlap between extent and clip, then return.
if (!extent.overlap(r)) return;
// Draw the boxes for visible rows and cols.
if (backImages) {
int16 i;
Point16 drawOrg(extent.x - offset.x + backOriginX,
extent.y - offset.y + backOriginY);
for (y = drawOrg.y, row = 0;
row < visibleRows;
row++, y += iconSpacing.y + iconHeight) {
// reset y for background image stuff
//y = drawOrg.y;
for (i = 0, x = drawOrg.x, col = 0;
col < visibleCols;
i++, col++, x += iconSpacing.x + iconWidth) {
Point16 pos(x, y);
if (isGhosted()) drawCompressedImageGhosted(port, pos, backImages[i % numIm]);
else drawCompressedImage(port, pos, backImages[i % numIm]);
}
}
} else {
for (y = originY, row = 0;
row < visibleRows;
row++, y += iconSpacing.y + iconHeight) {
for (x = originX, col = 0;
col < visibleCols;
col++, x += iconSpacing.x + iconWidth) {
// REM: We need to come up with some way of
// indicating how to render the pattern data which
// is behind the object...
port.setColor(14);
port.fillRect(x, y, iconWidth, iconHeight);
}
}
}
// Iterate through each item within the container.
while ((objID = iter.next(&item)) != Nothing) {
TilePoint objLoc;
ProtoObj *objProto = item->proto();
// If Intangible Container then don't show it.
if ((objProto->containmentSet() & (ProtoObj::isContainer | ProtoObj::isIntangible)) == (ProtoObj::isContainer | ProtoObj::isIntangible))
continue;
objLoc = item->getLocation();
if (objLoc.z == 0) continue;
// Draw object only if visible and in a visible row & col.
if (objLoc.u >= scrollPosition &&
objLoc.u < scrollPosition + visibleRows &&
objLoc.v < visibleCols &&
isVisible(item)) {
Sprite *spr;
ProtoObj *proto = item->proto();
Point16 sprPos;
y = originY + (objLoc.u - scrollPosition) * (iconSpacing.y + iconHeight);
x = originX + objLoc.v * (iconSpacing.x + iconWidth);
// Get the address of the sprite.
spr = proto->getSprite(item, ProtoObj::objInContainerView).sp;
sprPos.x = x + ((iconWidth - spr->size.x) >> 1)
- spr->offset.x;
sprPos.y = y + ((iconHeight - spr->size.y) >> 1)
- spr->offset.y;
if (isGhosted()) return;
// Draw the "in use" indicator.
if (backImages && proto->isObjectBeingUsed(item)) {
drawCompressedImage(port,
Point16(x - 4, y - 4), backImages[3]);
}
// Build the color table.
item->getColorTranslation(objColors);
// Draw the sprite into the port
DrawColorMappedSprite(
port,
sprPos,
spr,
objColors);
// check to see if selecting amount for this objec
if (objToGet == item) {
Point16 selectorPos = Point16(x + ((iconWidth - selectorX) >> 1),
y + ((iconHeight - selectorY) >> 1));
// draw the selector thingy
drawSelector(port, selectorPos);
// set the position of the inc center
amountIndY = y - (selectorY >> 1) + 28; // extent.y;
} else drawQuantity(port, item, objProto, x, y);
}
}
}
/* ===================================================================== *
ContainerWindow member functions
* ===================================================================== */
// ContainerWindow class
ContainerWindow::ContainerWindow(ContainerNode &nd,
ContainerAppearanceDef &app,
const char saveas[])
: FloatingWindow(nd.position, 0, saveas, cmdWindowFunc) {
// Initialize view to NULL.
view = NULL;
// create the close button for this window
closeCompButton = new gCompButton(
*this,
app.closeRect, // rect for button
containerRes, // resource context
app.closeResID[0],
app.closeResID[1],
0,
cmdCloseButtonFunc); // mind app func
}
// Virtual destructor (base does nothing)
ContainerWindow::~ContainerWindow(void) {}
ContainerView &ContainerWindow::getView(void) {
return *view;
}
/* ===================================================================== *
ScrollableContainerWindow member functions
* ===================================================================== */
ScrollableContainerWindow::ScrollableContainerWindow(
ContainerNode &nd, ContainerAppearanceDef &app, const char saveas[])
: ContainerWindow(nd, app, saveas) {
view = new ContainerView(*this, app.viewRect, nd, app);
// make the button conected to this window
scrollCompButton = new gCompButton(
*this,
app.scrollRect, // rect for button
containerRes, // resource context
app.scrollResID[0], // resource handle name
app.scrollResID[1],
0,
cmdScrollFunc); // mind app func
assert(view != NULL);
assert(scrollCompButton != NULL);
}
/* ===================================================================== *
TangibleContainerWindow member functions
* ===================================================================== */
TangibleContainerWindow::TangibleContainerWindow(
ContainerNode &nd, ContainerAppearanceDef &app)
: ScrollableContainerWindow(nd, app, "ObjectWindow") {
#if DEBUG
assert(view->containerObject);
assert(view->containerObject->proto());
#endif
const int weightIndicatorType = 2;
objRect = app.iconRect;
deathFlag = nd.getType() == ContainerNode::deadType;
containerSpriteImg = NULL;
// setup the mass and weight indicator
if (deathFlag) {
// set the decorations for this window
setDecorations(deathDecorations,
ARRAYSIZE(deathDecorations),
containerRes, 'F', 'R', 'M');
massWeightIndicator = NULL;
} else {
static StaticWindow *winDecs[] = {
brassDecorations,
clothDecorations,
steelDecorations,
woodDecorations
};
uint16 bgndType = view->containerObject->proto()->appearanceType;
assert(bgndType < 4);
setContainerSprite(); // show at the top of the box
// set the decorations for this window
setDecorations(winDecs[bgndType],
ARRAYSIZE(brassDecorations), // brass was arb, all should have same
containerRes, 'F', 'R', 'M');
// set the userdata such that we can extract the container object later
// through an appfunc.
this->userData = view->containerObject;
massWeightIndicator = new CMassWeightIndicator(
this,
Point16(app.massRect.x, app.massRect.y),
weightIndicatorType,
deathFlag);
}
}
TangibleContainerWindow::~TangibleContainerWindow(void) {
if (massWeightIndicator) delete massWeightIndicator;
if (containerSpriteImg) delete containerSpriteImg;
}
void TangibleContainerWindow::setContainerSprite(void) {
// pointer to sprite data that will be drawn
Sprite *spr;
ProtoObj *proto = view->containerObject->proto();
Point16 sprPos;
char dummy = '\0';
// Get the address of the sprite.
spr = proto->getSprite(view->containerObject, ProtoObj::objInContainerView).sp;
sprPos.x = objRect.x - (spr->size.x >> 1); //objRect.x + ( spr->size.x >> 1 );
sprPos.y = objRect.y - (spr->size.y >> 1);
containerSpriteImg = new gSpriteImage(
*this,
Rect16(sprPos.x,
sprPos.y,
objRect.height,
objRect.width),
view->containerObject,
dummy,
0,
NULL);
}
void TangibleContainerWindow::massBulkUpdate() {
if (massWeightIndicator) { // Death container doesn't have MW indicator
// set the indicators to the correct mass and bulk
massWeightIndicator->setMassPie(view->totalMass);
massWeightIndicator->setBulkPie(view->totalBulk);
}
}
void TangibleContainerWindow::drawClipped(
gPort &port,
const Point16 &offset,
const Rect16 &clip) {
if (!extent.overlap(clip)) return;
// draw the decorations
ScrollableContainerWindow::drawClipped(port, offset, clip);
}
/* ===================================================================== *
IntangibleContainerWindow member functions
* ===================================================================== */
IntangibleContainerWindow::IntangibleContainerWindow(
ContainerNode &nd, ContainerAppearanceDef &app)
: ScrollableContainerWindow(nd, app, "MentalWindow") {
// make the button conected to this window
mindSelectorCompButton = new gMultCompButton(
*this,
Rect16(49, 15 - 13, 52, 67),
containerRes,
'H', 'E', 'D', 1, 3, 1,
0,
cmdMindContainerFunc); // mind app func
assert(mindSelectorCompButton != NULL);
mindSelectorCompButton->setResponse(false);
// set the decorations for this window
setDecorations(mentalDecorations,
ARRAYSIZE(mentalDecorations),
containerRes, 'F', 'R', 'M');
setMindContainer(nd.mindType, *this);
}
/* ===================================================================== *
EnchantmentContainerWindow member functions
* ===================================================================== */
EnchantmentContainerWindow::EnchantmentContainerWindow(
ContainerNode &nd, ContainerAppearanceDef &app)
: ContainerWindow(nd, app, "EnchantmentWindow") {
view = new EnchantmentContainerView(*this, nd, app);
// make the button conected to this window
scrollCompButton = new gCompButton(
*this,
app.scrollRect, // rect for button
containerRes, // resource context
app.scrollResID[0], // resource handle name
app.scrollResID[1],
0,
cmdScrollFunc); // mind app func
assert(view != NULL);
assert(scrollCompButton != NULL);
}
/* ===================================================================== *
ContainerNode functions
* ===================================================================== */
ContainerNode::ContainerNode(ContainerList &cl, ObjectID id, int typ) {
GameObject *obj = GameObject::objectAddress(id);
PlayerActorID ownerID;
// Convert the possessor() of the object to a player actor ID,
// if it is indeed a player actor; Else set to "nobody".
if (isActor(id)) {
if (actorIDToPlayerID(id, ownerID) == false)
ownerID = ContainerNode::nobody;
} else {
ObjectID possessor = obj->possessor();
if (possessor == Nothing || actorIDToPlayerID(possessor, ownerID) == false)
ownerID = ContainerNode::nobody;
}
// Compute the initial position of the container window
switch (typ) {
case readyType:
break;
case deadType:
position = deathContainerAppearance.defaultWindowPos;
break;
case mentalType:
mindType = 0; //protoClassIdeaContainer;
position = mentalContainerAppearance.defaultWindowPos;
break;
case physicalType:
position = physicalContainerAppearance.defaultWindowPos;
break;
case enchantType:
position = enchantmentContainerAppearance.defaultWindowPos;
break;
}
// Fill in the initial values.
window = NULL;
type = typ;
object = id;
owner = ownerID;
action = 0;
mindType = 0;
// Add to container list.
cl.add(this);
}
// Return the container window for a container node, if it is visible
ContainerWindow *ContainerNode::getWindow(void) {
return window;
}
// Return the container view for a container node, if it is visible
ContainerView *ContainerNode::getView(void) {
return window ? &window->getView() : NULL;
}
// Destructor
ContainerNode::~ContainerNode() {
// Close the container window.
hide();
// Remove from container list
g_vm->_containerList->remove(this);
}
// Restore the state of this ContainerNode from archive buffer
void *ContainerNode::restore(void *buf) {
Archive *a = (Archive *)buf;
// Restore fields
object = a->object;
type = a->type;
owner = a->owner;
position = a->position;
mindType = a->mindType;
window = NULL;
action = 0;
// If this container was shown, re-show it
if (a->shown) markForShow();
return &a[1];
}
// Store the state of this ContainerNode into archive buffer
void *ContainerNode::archive(void *buf) {
Archive *a = (Archive *)buf;
// Store fields
a->object = object;
a->type = type;
a->owner = owner;
a->position = position;
a->mindType = mindType;
a->shown = window != NULL;
return &a[1];
}
// Close the container window, but leave the node.
void ContainerNode::hide(void) {
// close the window, but don't close the object.
if (type != readyType && window != NULL) {
position = window->getExtent(); // Save old window position
window->close();
delete window;
window = NULL;
}
}
// Open the cotainer window, given the node info.
void ContainerNode::show(void) {
ProtoObj *proto = GameObject::protoAddress(object);
assert(proto);
// open the window; Object should already be "open"
if (window == NULL) {
switch (type) {
case physicalType:
physicalContainerAppearance.rows = proto->getViewableRows();
physicalContainerAppearance.cols = proto->getViewableCols();
physicalContainerAppearance.totRows = proto->getMaxRows();
window = new TangibleContainerWindow(*this, physicalContainerAppearance);
break;
case deadType:
deathContainerAppearance.rows = proto->getViewableRows();
deathContainerAppearance.cols = proto->getViewableCols();
deathContainerAppearance.totRows = proto->getMaxRows();
window = new TangibleContainerWindow(*this, deathContainerAppearance);
break;
case mentalType:
window = new IntangibleContainerWindow(*this, mentalContainerAppearance);
break;
case enchantType:
window = new EnchantmentContainerWindow(*this, enchantmentContainerAppearance);
break;
case readyType:
default:
return;
}
}
window->open();
}
void ContainerNode::update(void) {
if (type == readyType) {
// Update ready containers if they are enabled
if (TrioCviews[owner]->getEnabled()) TrioCviews[owner]->invalidate();
if (indivCviewTop->getEnabled()) indivCviewTop->invalidate();
if (indivCviewBot->getEnabled()) indivCviewBot->invalidate();
// If the container to update is the center brother's ready container.
if (isIndivMode() && owner == getCenterActorPlayerID()) {
// Update player's mass & weight indicator...
MassWeightIndicator->update();
}
} else if (window) {
getView()->invalidate();
window->massBulkUpdate();
}
}
// Find a container node, given a specific object
ContainerNode *ContainerList::find(ObjectID id) {
for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it)
if ((*it)->object == id)
return *it;
return NULL;
}
ContainerNode *ContainerList::find(ObjectID id, int16 type) {
for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it)
if ((*it)->object == id && (*it)->type == type)
return *it;
return NULL;
}
// returns true if the object represented by the container can be
// accessed by the player.
bool ContainerNode::isAccessable(ObjectID enactor) {
Actor *a = (Actor *)GameObject::objectAddress(enactor);
ObjectID holder;
GameObject *obj = GameObject::objectAddress(object);
int32 dist;
// REM: We really ought to do a line-of-sight test here.
// Calculate distance between actor and container.
dist = (a->getLocation() - obj->getWorldLocation()).quickHDistance();
// If the container object is too far away we can't access any containers.
// Note: Actors are not considered to be in possession of themselves...
holder = obj->possessor();
if (holder != Nothing || isActor(object)) {
// "Reach" for other players is further than for other objects
if (holder != a->thisID() && dist > 96)
return false;
} else if (dist > kMaxOpenDistance)
return false;
return true;
}
// Change the owner of a ready container (for indiv mode)
void ContainerNode::changeOwner(int16 newOwner) {
owner = newOwner;
object = getPlayerActorAddress(newOwner)->getActorID();
}
/* ===================================================================== *
ContainerList functions
* ===================================================================== */
void ContainerList::setPlayerNum(PlayerActorID playerNum) {
// Close all containers which are not on the ground and not owned
// by the current protagonist.
for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it) {
ContainerNode *n = *it;
if (n->owner != ContainerNode::nobody && n->owner != playerNum)
n->hide();
}
// Open any containers which belong to the new protagonist.
for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it) {
ContainerNode *n = *it;
if (n->owner == playerNum)
n->markForShow();
}
}
void ContainerList::doDeferredActions(void) {
Common::List<ContainerNode *>::iterator nextIt;
Actor *a = getCenterActor();
TilePoint tp = a->getLocation();
GameObject *world = a->parent();
// Close all containers which are not on the ground and not owned
// by the current protagonist.
for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); it = nextIt) {
nextIt = it;
nextIt++;
ContainerNode *n = *it;
// If the object is not in a player inventory (i.e. on the ground)
if (n->owner == ContainerNode::nobody) {
// If the object is in a different world, or too far away
// from the protagonist, then quietly close the object.
GameObject *obj = GameObject::objectAddress(n->object);
if (obj->world() != world
|| (obj->getWorldLocation() - tp).quickHDistance() > kMaxOpenDistance) {
// Close object image and window (silently)
obj->setFlags(0, objectOpen);
delete n;
continue;
}
}
if (n->action & ContainerNode::actionDelete) {
delete n;
continue;
}
if (n->action & ContainerNode::actionHide) {
n->hide();
} else {
if (n->action & ContainerNode::actionShow) n->show();
if (n->action & ContainerNode::actionUpdate) n->update();
}
n->action = 0;
}
}
void ContainerList::setUpdate(ObjectID id) {
// Close all containers which are not on the ground and not owned
// by the current protagonist.
for (Common::List<ContainerNode *>::iterator it = _list.begin(); it != _list.end(); ++it) {
ContainerNode *n = *it;
if (n->object == id)
n->update();
else if (n->type == ContainerNode::mentalType // Special case for mind containers
&& n->object == GameObject::objectAddress(id)->IDParent())
n->update();
}
}
extern int16 openMindType;
// General function to create a container "node". This determines what
// kind of container is appropriate, and also if a container of that
// type is already open.
ContainerNode *CreateContainerNode(ObjectID id, bool open, int16) {
ContainerNode *cn = NULL;
GameObject *obj = GameObject::objectAddress(id);
PlayerActorID owner;
if (isActor(id)) {
if (actorIDToPlayerID(id, owner) == false)
owner = ContainerNode::nobody;
if (((Actor *)obj)->isDead()) {
// Open dead container for dead actor
if (!(cn = g_vm->_containerList->find(owner, ContainerNode::deadType)))
cn = new ContainerNode(*g_vm->_containerList, id, ContainerNode::deadType);
} else if (owner != ContainerNode::nobody) {
return OpenMindContainer(owner, open, /*mType*/ openMindType);
}
#if DEBUG
else fatal("Attempt to open non-dead actor as a container.\n");
#endif
} else {
if (actorIDToPlayerID(obj->possessor(), owner) == false)
owner = ContainerNode::nobody;
if (!(cn = g_vm->_containerList->find(id, ContainerNode::physicalType)))
cn = new ContainerNode(*g_vm->_containerList, id, ContainerNode::physicalType);
}
// If node was successfull created, and we wanted it open, and the owner
// is the center actor or no-actor then make the container window visible.
if (cn != NULL
&& open
&& (owner == getCenterActorID() || owner == ContainerNode::nobody)) {
cn->show();
}
return cn;
}
ContainerNode *CreateReadyContainerNode(PlayerActorID player) {
return new ContainerNode(*g_vm->_containerList,
getPlayerActorAddress(player)->getActorID(),
ContainerNode::readyType);
}
ContainerNode *OpenMindContainer(PlayerActorID player, int16 open, int16 type) {
ContainerNode *cn;
ObjectID id = getPlayerActorAddress(player)->getActorID();
if (!(cn = g_vm->_containerList->find(id, ContainerNode::mentalType))) {
cn = new ContainerNode(*g_vm->_containerList, id, ContainerNode::mentalType);
cn->mindType = type;
// If node was successfull created, and we wanted it open, and the owner
// is the center actor or no-actor then make the container window visible.
if (open && id == getCenterActorID()) {
cn->show();
}
} else {
IntangibleContainerWindow *cw = (IntangibleContainerWindow *)cn->getWindow();
if (cw && (type != cn->mindType)) {
cn->mindType = type;
setMindContainer(cn->mindType, *cw);
cw->update(cw->getView().getExtent());
}
}
return cn;
}
/* ===================================================================== *
Misc. functions
* ===================================================================== */
void initContainers(void) {
if (containerRes == NULL)
containerRes = resFile->newContext(MKTAG('C', 'O', 'N', 'T'), "cont.resources");
selImage = g_vm->_imageCache->requestImage(imageRes, MKTAG('A', 'M', 'N', 'T'));
}
void cleanupContainers(void) {
if (selImage)
g_vm->_imageCache->releaseImage(selImage);
if (containerRes)
resFile->disposeContext(containerRes);
selImage = NULL;
containerRes = NULL;
delete g_vm->_containerList;
}
void initContainerNodes(void) {
#if DEBUG
// Verify the globalContainerList only has ready ContainerNodes
ContainerNode *node;
bool onlyReady = true;
for (node = g_vm->_containerList->first(); node != NULL; node = node->next()) {
if (node->getType() != ContainerNode::readyType) {
onlyReady = false;
break;
}
}
assert(onlyReady);
#endif
}
void saveContainerNodes(SaveFileConstructor &saveGame) {
int16 numNodes = 0;
void *archiveBuffer,
*bufferPtr;
int32 archiveBufSize;
// Make sure there are no pending container view actions
g_vm->_containerList->doDeferredActions();
archiveBufSize = sizeof(numNodes);
// Count the number of nodes to save
for (Common::List<ContainerNode *>::iterator it = g_vm->_containerList->_list.begin(); it != g_vm->_containerList->_list.end(); ++it) {
ContainerNode *n = *it;
if (n->getType() != ContainerNode::readyType)
numNodes++;
}
// Compute size of archive buffer
archiveBufSize += numNodes * ContainerNode::archiveSize();
// Allocate the archive buffer
archiveBuffer = malloc(archiveBufSize);
if (archiveBuffer == NULL)
error("Unable to allocate ContainerNode archive buffer\n");
bufferPtr = archiveBuffer;
// Store the number of nodes to save
*((int16 *)bufferPtr) = numNodes;
bufferPtr = (int16 *)bufferPtr + 1;
// Store the nodes
for (Common::List<ContainerNode *>::iterator it = g_vm->_containerList->_list.begin(); it != g_vm->_containerList->_list.end(); ++it) {
ContainerNode *n = *it;
if (n->getType() != ContainerNode::readyType)
bufferPtr = n->archive(bufferPtr);
}
// Write the archive data to the save file
saveGame.writeChunk(
MakeID('C', 'O', 'N', 'T'),
archiveBuffer,
archiveBufSize);
// Free the archive buffer
free(archiveBuffer);
}
void loadContainerNodes(SaveFileReader &saveGame) {
ContainerNode *node;
Common::List<ContainerNode *> tempList;
int16 i,
numNodes;
void *archiveBuffer,
*bufferPtr;
int32 archiveBufSize;
// Read in the number of container nodes to restore
saveGame.read(&numNodes, sizeof(numNodes));
// Allocate archive buffer
archiveBufSize = saveGame.bytesLeftInChunk();
archiveBuffer = malloc(archiveBufSize);
if (archiveBuffer == NULL)
error("Unable to allocate ContainerNode archive buffer\n");
// Read in node data
saveGame.read(archiveBuffer, archiveBufSize);
bufferPtr = archiveBuffer;
for (i = 0; i < numNodes; i++) {
node = new ContainerNode;
// Restore the state of the node
bufferPtr = node->restore(bufferPtr);
// Add it back to the container list
g_vm->_containerList->add(node);
}
assert(tempList.empty());
// Free the archive buffer
free(archiveBuffer);
}
void cleanupContainerNodes(void) {
for (Common::List<ContainerNode *>::iterator it = g_vm->_containerList->_list.begin(); it != g_vm->_containerList->_list.end(); ++it) {
ContainerNode *n = *it;
if (n->getType() != ContainerNode::readyType)
delete n;
}
}
void updateContainerWindows(void) {
g_vm->_containerList->doDeferredActions();
}
void setMindContainer(int index, IntangibleContainerWindow &cw) {
static int classTable[] = { protoClassIdeaContainer,
protoClassSkillContainer,
protoClassMemoryContainer,
protoClassPsychContainer // Not used anymore
};
ObjectID ownerID = cw.getView().node.getObject();
GameObject *object = GameObject::objectAddress(ownerID);
ContainerIterator iter(object);
GameObject *item;
ObjectID id;
assert(index >= 0);
assert(index < ARRAYSIZE(classTable));
int containerClass = classTable[index];
cw.mindSelectorCompButton->setCurrent(index);
cw.mindSelectorCompButton->invalidate();
while ((id = iter.next(&item)) != Nothing) {
if (item->proto()->classType == containerClass) {
cw.view->setContainer(item);
return;
}
}
}
APPFUNC(cmdMindContainerFunc) {
if (ev.panel && ev.eventType == gEventNewValue /* && ev.value */) {
IntangibleContainerWindow *cw = (IntangibleContainerWindow *)ev.window;
ContainerNode &nd = cw->getView().node;
int newMindType = nd.mindType;
static Rect16 idea(0, 0, 22, 67), // idea button click area
skill(22, 0, 11, 67), // skill area
memory(33, 0, 9, 67), // memory area
psych(42, 0, 10, 67); // psych(ic?) area
if (idea.ptInside(ev.mouse)) newMindType = 0; //protoClassIdeaContainer;
if (skill.ptInside(ev.mouse)) newMindType = 1; //protoClassSkillContainer;
if (memory.ptInside(ev.mouse)) newMindType = 2; //protoClassMemoryContainer;
// if (psych.ptInside(ev.mouse)) newMindType = protoClassPsychContainer;
if (newMindType != nd.mindType) {
nd.mindType = newMindType;
setMindContainer(nd.mindType, *cw);
cw->update(cw->getView().getExtent());
}
} else if (ev.eventType == gEventMouseMove) {
//if (ev.value == gCompImage::enter)
{
static Rect16 idea(0, 0, 22, 67), // idea button click area
skill(22, 0, 11, 67), // skill area
memory(33, 0, 9, 67); // memory area
const int BUF_SIZE = 64;
char textBuffer[BUF_SIZE];
int mindType = -1;
if (idea.ptInside(ev.mouse)) mindType = 0; //protoClassIdeaContainer;
if (skill.ptInside(ev.mouse)) mindType = 1; //protoClassSkillContainer;
if (memory.ptInside(ev.mouse)) mindType = 2; //protoClassMemoryContainer;
switch (mindType) {
case 0:
sprintf(textBuffer, IDEAS_MENTAL);
break;
case 1:
sprintf(textBuffer, SPELL_MENTAL);
break;
case 2:
sprintf(textBuffer, SKILL_MENTAL);
break;
case -1:
textBuffer[0] = 0;
break;
default:
assert(false); // should never get here
break;
}
// set the text in the cursor
g_vm->_mouseInfo->setText(textBuffer);
}
if (ev.value == gCompImage::leave) {
g_vm->_mouseInfo->setText(NULL);
}
}
}
APPFUNC(cmdCloseButtonFunc) {
if (ev.eventType == gEventNewValue && ev.value == 1) {
ContainerWindow *win = (ContainerWindow *)ev.window;
if (win->getView().node.getType() == ContainerNode::mentalType) {
win->getView().node.markForDelete();
} else {
win->containerObject()->close(getCenterActorID());
}
updateContainerWindows();
// make sure the hint text goes away
if (g_vm->_mouseInfo->getObject() == NULL) {
g_vm->_mouseInfo->setText(NULL);
}
} else if (ev.eventType == gEventMouseMove) {
if (ev.value == gCompImage::enter) {
g_vm->_mouseInfo->setText(CLOSE_MOUSE);
} else if (ev.value == gCompImage::leave) {
g_vm->_mouseInfo->setText(NULL);
}
}
}
APPFUNC(cmdScrollFunc) {
if (ev.panel && ev.eventType == gEventNewValue && ev.value) {
ScrollableContainerWindow *cw;
static Rect16 upArea(0, 0, 44, 22);
cw = (ScrollableContainerWindow *)ev.window;
if (upArea.ptInside(ev.mouse))
cw->scrollUp();
else
cw->scrollDown();
ev.window->update(cw->getView().getExtent());
} else if (ev.eventType == gEventMouseMove) {
if (ev.value == gCompImage::enter) {
g_vm->_mouseInfo->setText(SCROLL_MOUSE);
} else if (ev.value == gCompImage::leave) {
g_vm->_mouseInfo->setText(NULL);
}
}
}
} // end of namespace Saga2