/* 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. * * This file contains the Object Manager code. */ #include "tinsel/object.h" #include "tinsel/background.h" #include "tinsel/cliprect.h" // object clip rect defs #include "tinsel/graphics.h" // low level interface #include "tinsel/handle.h" #include "tinsel/text.h" #include "tinsel/tinsel.h" #include "common/textconsole.h" #define OID_EFFECTS 0x2000 // generic special effects object id namespace Tinsel { // FIXME: Avoid non-const global vars // list of all objects static OBJECT *objectList = 0; // pointer to free object list static OBJECT *pFreeObjects = 0; #ifdef DEBUG // diagnostic object counters static int numObj = 0; static int maxObj = 0; #endif void FreeObjectList() { free(objectList); objectList = NULL; } /** * Kills all objects and places them on the free list. */ void KillAllObjects() { int i; #ifdef DEBUG // clear number of objects in use numObj = 0; #endif if (objectList == NULL) { // first time - allocate memory for object list objectList = (OBJECT *)calloc(NUM_OBJECTS, sizeof(OBJECT)); // make sure memory allocated if (objectList == NULL) { error("Cannot allocate memory for object data"); } } // place first object on free list pFreeObjects = objectList; // link all other objects after first for (i = 1; i < NUM_OBJECTS; i++) { objectList[i - 1].pNext = objectList + i; } // null the last object objectList[NUM_OBJECTS - 1].pNext = NULL; } #ifdef DEBUG /** * Shows the maximum number of objects used at once. */ void ObjectStats() { debug("%i objects of %i used", maxObj, NUM_OBJECTS); } #endif /** * Allocate a object from the free list. */ OBJECT *AllocObject() { OBJECT *pObj = pFreeObjects; // get a free object // check for no free objects assert(pObj != NULL); // a free object exists // get link to next free object pFreeObjects = pObj->pNext; // clear out object memset(pObj, 0, sizeof(OBJECT)); // set default drawing mode and set changed bit pObj->flags = DMA_WNZ | DMA_CHANGED; #ifdef DEBUG // one more object in use if (++numObj > maxObj) maxObj = numObj; #endif // return new object return pObj; } bool isValidObject(OBJECT *obj) { return (obj >= objectList && obj <= objectList + NUM_OBJECTS - 1); } /** * Copy one object to another. * @param pDest Destination object * @param pSrc Source object */ void CopyObject(OBJECT *pDest, OBJECT *pSrc) { // save previous dimensions etc. Common::Rect rcSave = pDest->rcPrev; // make a copy memcpy(pDest, pSrc, sizeof(OBJECT)); // restore previous dimensions etc. pDest->rcPrev = rcSave; // set changed flag in destination pDest->flags |= DMA_CHANGED; // null the links pDest->pNext = pDest->pSlave = NULL; } /** * Inserts an object onto the specified object list. The object * lists are sorted in Z Y order. * @param pObjList List to insert object onto * @param pInsObj Object to insert */ void InsertObject(OBJECT *pObjList, OBJECT *pInsObj) { OBJECT *pPrev, *pObj; // object list traversal pointers // validate object pointer assert(isValidObject(pInsObj)); for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { // check Z order if (pInsObj->zPos < pObj->zPos) { // object Z is lower than list Z - insert here break; } else if (pInsObj->zPos == pObj->zPos) { // Z values are the same - sort on Y if (fracToDouble(pInsObj->yPos) <= fracToDouble(pObj->yPos)) { // object Y is lower than or same as list Y - insert here break; } } } // insert obj between pPrev and pObj pInsObj->pNext = pObj; pPrev->pNext = pInsObj; } /** * Deletes an object from the specified object list and places it * on the free list. * @param pObjList List to delete object from * @param pDelObj Object to delete */ void DelObject(OBJECT *pObjList, OBJECT *pDelObj) { OBJECT *pPrev, *pObj; // object list traversal pointers const Common::Rect rcScreen(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); // validate object pointer assert(isValidObject(pDelObj)); #ifdef DEBUG // one less object in use --numObj; assert(numObj >= 0); #endif for (pPrev = pObjList, pObj = pObjList->pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { if (pObj == pDelObj) { // found object to delete if (IntersectRectangle(pDelObj->rcPrev, pDelObj->rcPrev, rcScreen)) { // allocate a clipping rect for objects previous pos AddClipRect(pDelObj->rcPrev); } // make PREV next = OBJ next - removes OBJ from list pPrev->pNext = pObj->pNext; // place free list in OBJ next pObj->pNext = pFreeObjects; // add OBJ to top of free list pFreeObjects = pObj; // delete objects palette if (pObj->pPal) FreePalette(pObj->pPal); // quit return; } } // if we get to here - object has not been found on the list // This can be triggered in Act 3 in DW1 while talking to the guard, // so this has been turned to a warning instead of an error warning("DelObject(): formally 'assert(0)!'"); } /** * Sort the specified object list in Z Y order. * @param pObjList List to sort */ void SortObjectList(OBJECT *pObjList) { OBJECT *pPrev, *pObj; // object list traversal pointers OBJECT head; // temporary head of list - because pObjList is not usually a OBJECT // put at head of list head.pNext = pObjList->pNext; // set head of list dummy OBJ Z Y values to lowest possible head.yPos = intToFrac(MIN_INT16); head.zPos = MIN_INT; for (pPrev = &head, pObj = head.pNext; pObj != NULL; pPrev = pObj, pObj = pObj->pNext) { // check Z order if (pObj->zPos < pPrev->zPos) { // object Z is lower than previous Z // remove object from list pPrev->pNext = pObj->pNext; // re-insert object on list InsertObject(pObjList, pObj); // back to beginning of list pPrev = &head; pObj = head.pNext; } else if (pObj->zPos == pPrev->zPos) { // Z values are the same - sort on Y if (fracToDouble(pObj->yPos) < fracToDouble(pPrev->yPos)) { // object Y is lower than previous Y // remove object from list pPrev->pNext = pObj->pNext; // re-insert object on list InsertObject(pObjList, pObj); // back to beginning of list pPrev = &head; pObj = head.pNext; } } } } /** * Returns the animation offsets of a image, dependent on the * images orientation flags. * @param hImg Iimage to get animation offset of * @param flags Images current flags * @param pAniX Gets set to new X animation offset * @param pAniY Gets set to new Y animation offset */ void GetAniOffset(SCNHANDLE hImg, int flags, int *pAniX, int *pAniY) { if (hImg) { const IMAGE *pImg = (const IMAGE *)LockMem(hImg); // set ani X *pAniX = (int16) FROM_LE_16(pImg->anioffX); // set ani Y *pAniY = (int16) FROM_LE_16(pImg->anioffY); if (flags & DMA_FLIPH) { // we are flipped horizontally // set ani X = -ani X + width - 1 *pAniX = -*pAniX + FROM_LE_16(pImg->imgWidth) - 1; } if (flags & DMA_FLIPV) { // we are flipped vertically // set ani Y = -ani Y + height - 1 *pAniY = -*pAniY + (FROM_LE_16(pImg->imgHeight) & ~C16_FLAG_MASK) - 1; } } else // null image *pAniX = *pAniY = 0; } /** * Returns the x,y position of an objects animation point. * @param pObj Pointer to object * @param pPosX Gets set to objects X animation position * @param pPosY Gets set to objects Y animation position */ void GetAniPosition(OBJECT *pObj, int *pPosX, int *pPosY) { // validate object pointer assert(isValidObject(pObj)); // get the animation offset of the object GetAniOffset(pObj->hImg, pObj->flags, pPosX, pPosY); // from animation offset and objects position - determine objects animation point *pPosX += fracToInt(pObj->xPos); *pPosY += fracToInt(pObj->yPos); } /** * Initialise a object using a OBJ_INIT structure to supply parameters. * @param pInitTbl Pointer to object initialisation table */ OBJECT *InitObject(const OBJ_INIT *pInitTbl) { // allocate a new object OBJECT *pObj = AllocObject(); // make sure object created assert(pObj != NULL); // set objects shape pObj->hImg = pInitTbl->hObjImg; // set objects ID pObj->oid = pInitTbl->objID; // set objects flags pObj->flags = DMA_CHANGED | pInitTbl->objFlags; // set objects Z position pObj->zPos = pInitTbl->objZ; // get pointer to image if (pInitTbl->hObjImg) { int aniX, aniY; // objects animation offsets PALQ *pPalQ = NULL; // palette queue pointer const IMAGE *pImg = (const IMAGE *)LockMem(pInitTbl->hObjImg); // handle to image if (pImg->hImgPal) { // allocate a palette for this object pPalQ = AllocPalette(FROM_LE_32(pImg->hImgPal)); // make sure palette allocated assert(pPalQ != NULL); } // assign palette to object pObj->pPal = pPalQ; // set objects size pObj->width = FROM_LE_16(pImg->imgWidth); pObj->height = FROM_LE_16(pImg->imgHeight) & ~C16_FLAG_MASK; pObj->flags &= ~C16_FLAG_MASK; pObj->flags |= FROM_LE_16(pImg->imgHeight) & C16_FLAG_MASK; // set objects bitmap definition pObj->hBits = FROM_LE_32(pImg->hImgBits); // get animation offset of object GetAniOffset(pObj->hImg, pInitTbl->objFlags, &aniX, &aniY); // set objects X position - subtract ani offset pObj->xPos = intToFrac(pInitTbl->objX - aniX); // set objects Y position - subtract ani offset pObj->yPos = intToFrac(pInitTbl->objY - aniY); } else { // no image handle - null image // set objects X position pObj->xPos = intToFrac(pInitTbl->objX); // set objects Y position pObj->yPos = intToFrac(pInitTbl->objY); } // return new object return pObj; } /** * Give a object a new image and new orientation flags. * @param pAniObj Object to be updated * @param newflags Objects new flags * @param hNewImg Objects new image */ void AnimateObjectFlags(OBJECT *pAniObj, int newflags, SCNHANDLE hNewImg) { // validate object pointer assert(isValidObject(pAniObj)); if (pAniObj->hImg != hNewImg || (pAniObj->flags & DMA_HARDFLAGS) != (newflags & DMA_HARDFLAGS)) { // something has changed int oldAniX, oldAniY; // objects old animation offsets int newAniX, newAniY; // objects new animation offsets // get objects old animation offsets GetAniOffset(pAniObj->hImg, pAniObj->flags, &oldAniX, &oldAniY); // get objects new animation offsets GetAniOffset(hNewImg, newflags, &newAniX, &newAniY); if (hNewImg) { // get pointer to image const IMAGE *pNewImg = (IMAGE *)LockMem(hNewImg); // setup new shape pAniObj->width = FROM_LE_16(pNewImg->imgWidth); pAniObj->height = FROM_LE_16(pNewImg->imgHeight) & ~C16_FLAG_MASK; newflags &= ~C16_FLAG_MASK; newflags |= FROM_LE_16(pNewImg->imgHeight) & C16_FLAG_MASK; // set objects bitmap definition pAniObj->hBits = FROM_LE_32(pNewImg->hImgBits); } else { // null image pAniObj->width = 0; pAniObj->height = 0; pAniObj->hBits = 0; } // set objects flags and signal a change pAniObj->flags = newflags | DMA_CHANGED; // set objects image pAniObj->hImg = hNewImg; // adjust objects position - subtract new from old for difference pAniObj->xPos += intToFrac(oldAniX - newAniX); pAniObj->yPos += intToFrac(oldAniY - newAniY); } } /** * Give an object a new image. * @param pAniObj Object to animate * @param hNewImg Objects new image */ void AnimateObject(OBJECT *pAniObj, SCNHANDLE hNewImg) { // dont change the objects flags AnimateObjectFlags(pAniObj, pAniObj->flags, hNewImg); } /** * Creates a rectangle object of the given dimensions and returns * a pointer to the object. * @param hPal Palette for the rectangle object * @param color Which color offset from the above palette * @param width Width of rectangle * @param height Height of rectangle */ OBJECT *RectangleObject(SCNHANDLE hPal, int color, int width, int height) { // template for initialising the rectangle object static const OBJ_INIT rectObj = {0, DMA_CONST, OID_EFFECTS, 0, 0, 0}; PALQ *pPalQ; // palette queue pointer // allocate and init a new object OBJECT *pRect = InitObject(&rectObj); // allocate a palette for this object pPalQ = AllocPalette(hPal); // make sure palette allocated assert(pPalQ != NULL); // assign palette to object pRect->pPal = pPalQ; // set color in the palette pRect->constant = color; // set rectangle width pRect->width = width; // set rectangle height pRect->height = height; // return pointer to rectangle object return pRect; } /** * Creates a translucent rectangle object of the given dimensions * and returns a pointer to the object. * @param width Width of rectangle * @param height Height of rectangle */ OBJECT *TranslucentObject(int width, int height) { // template for initialising the rectangle object static const OBJ_INIT rectObj = {0, DMA_TRANS, OID_EFFECTS, 0, 0, 0}; // allocate and init a new object OBJECT *pRect = InitObject(&rectObj); // set rectangle width pRect->width = width; // set rectangle height pRect->height = height; // return pointer to rectangle object return pRect; } } // End of namespace Tinsel