Files
2026-02-02 04:50:13 +01:00

3183 lines
125 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 3 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, see <http://www.gnu.org/licenses/>.
*
*/
#include "bagel/hodjnpodj/hnplibs/stdafx.h"
#include "bagel/hodjnpodj/hnplibs/sprite.h"
namespace Bagel {
namespace HodjNPodj {
#define SPRITE_DEBUG 0 // set for debugging sprite reconstruction
int CSprite::m_nIndex = 0; // used for generating unique Ids
CSprite *CSprite::m_pSpriteChain = nullptr; // pointer to chain of linked sprites
bool CSprite::m_bTouchedSprite = false; // set if sprite overlapped during painting
CSprite *CSprite::m_pTouchedSprite = nullptr; // pointer to sprite overlapped during painting
bool CSprite::m_bHaveBackdrop = false; // whether we have a bitmap for the background
CDC *CSprite::m_pBackdropDC = nullptr; // backdrop context for background painting
CBitmap *CSprite::m_pBackdrop = nullptr; // backdrop for background painting
CBitmap *CSprite::m_pBackdropOld = nullptr; // previous bitmap mapped to backdrop context
CPalette *CSprite::m_pBackdropPalette = nullptr; // palette mapped to backdrop context
CPalette *CSprite::m_pBackdropPalOld = nullptr; // previous palette mapped to backdrop context
IMPLEMENT_DYNCREATE(CSprite, CObject)
/*************************************************************************
*
* CSprite()
*
* Parameters: none
*
* Return Value: none
*
* Description: Constructor for sprite class. Initialize all fields
* to logical nullptr. Calls should then be made to other
* sprite routines to load palettes and bitmaps.
*
************************************************************************/
CSprite::CSprite() {
m_pImageDC = nullptr; // no device context allocated for sprite image
m_pImage = nullptr; // no initial bitmap image for the sprite
m_pImageOld = nullptr; // ... hence no unmapped bitmap for the context
m_pMaskDC = nullptr; // no device context allocated for sprite mask
m_pMask = nullptr; // no initial image mask
m_pMaskOld = nullptr; // ... hence no unmapped bitmap for the context
m_pBackgroundDC = nullptr; // no device context allocated for background
m_pBackground = nullptr; // no initial background mask
m_pBackgroundOld = nullptr; // ... hence no unmapped bitmap for the context
m_pPalette = nullptr; // no initial palette assigned to this sprite
m_pPalImageOld = nullptr; // ... hence no unmapped palette for the image
m_pPalBackOld = nullptr; // ... and no unmapped palette for the background
m_bSharedPalette = false; // ... and thus it is not currently shared
m_cSize = CSize(0, 0); // there is no size to the sprite image
m_cRect = CRect(0, 0, 0, 0); // rectangular bounds not yet defined
m_cImageRect = m_cRect; // image rectangle starts same as display bounds
m_cPosition = CPoint(0, 0); // default position to upper left corner of display
m_cMovementDelta = CPoint(0, 0); // no initial movement rate
m_bPositioned = false; // not yet positioned
m_cHotspot = CPoint(0, 0); // default the hot spot to upper left corner
m_bVisible = false; // sprite has no initial image
m_bMasked = false; // simple unmasked bitmap
m_bMobile = false; // it is a stationary sprite
m_bOverlaps = false; // does not yet overlap other sprites
m_bIntercepts = true; // can be detected by interception
m_bRetainBackground = true; // retain background image for updates
m_bRetainContexts = false; // do not retain device contexts
m_bDuplicated = false; // not sharing resources with other sprites
m_nId = ++m_nIndex; // set its unique identifirer
m_nType = 0; // no user defined information
m_pData = nullptr; // no user define pointer information
m_nZOrder = SPRITE_TOPMOST; // default to top most in fore/back ground order
m_nZPosition = SPRITE_TOPMOST; // default to top most in fore/back ground order
m_nCelID = -1; // cel identifier not pointing at a cel
m_nCelCount = 0; // no cels to be played
m_bAnimated = false; // not initially animated
m_bLinked = false; // not initially linked into the sprite chain
m_pNext = nullptr; // it is not yet in the sprite chain and
m_pPrev = nullptr; // ... thus has no links to other sprites
m_pZPrev = nullptr;
}
/*************************************************************************
*
* ~CSprite()
*
* Parameters: none
*
* Return Value: none
*
* Description: Destructor for sprite class. It is important to tear
* things down in the order we built them; map out bitmap,
* map out the palette, then discard the device context -
* failure to release key resources will cause application
* or system crashes.
*
************************************************************************/
CSprite::~CSprite() {
ClearImage(); // clear the sprite image bitmap and context
ClearMask(); // clear the sprite mask bitmap
ClearBackground(); // clear the sprite background bitmap and context
ClearPalette(); // clear the palette resource
if (m_bLinked) // unlink from chain if still hooked in
UnlinkSprite();
}
/*************************************************************************
*
* LinkSprite()
*
* Parameters: none
*
* Return Value: none
*
* Description: Link this sprite into the chain by placing it at the
* the head of the chain
*
************************************************************************/
void CSprite::LinkSprite() {
m_bLinked = true; // set for linked into chain
if ((m_pPrev != nullptr) || // punt if already in chain
(m_pNext != nullptr))
return;
m_pNext = m_pSpriteChain; // link sprite into head of chain
m_pPrev = nullptr; // ... by pointing it at the current
if (m_pSpriteChain != nullptr) // ... initial sprite, and pointing it
(*m_pSpriteChain).m_pPrev = this; // ... back at us
m_pSpriteChain = this;
}
/*************************************************************************
*
* UnLinkSprite()
*
* Parameters: none
*
* Return Value: none
*
* Description: Remove this sprite from the sprite chain and point its
* neighbors at each other to fill the gap
*
************************************************************************/
void CSprite::UnlinkSprite() {
m_bLinked = false; // set for not linked into chain
if ((m_pPrev == nullptr) && // punt if not in chain
(m_pNext == nullptr) &&
(m_pSpriteChain != this))
return;
if (m_pPrev != nullptr) // disconnect us from the sprite chain
(*m_pPrev).m_pNext = m_pNext; // ... by pointing the one before us, and
else // ... the one after us, at each other
m_pSpriteChain = m_pNext; // special case the instance where the
// ... sprite to be removed is the first
if (m_pNext != nullptr) // ... in the list - in particular, update
(*m_pNext).m_pPrev = m_pPrev; // ... the head of chain pointer
m_pNext = m_pPrev = nullptr;
}
/*************************************************************************
*
* FlushSpriteChain()
*
* Parameters: none
*
* Return Value: none
*
* Description: Remove all sprites from the chain and delete them via
* the standard destructor
*
************************************************************************/
void CSprite::FlushSpriteChain() {
CSprite *pSprite = nullptr;
while ((pSprite = CSprite::GetSpriteChain()) != nullptr) { // cycle getting head of chain
(*pSprite).UnlinkSprite(); // ... unlinking it
delete pSprite;
} // ... and then deleting it
}
/*************************************************************************
*
* SetBackdrop()
*
* Parameters:
* CDC *pDC screen device context
* CPalette *pPalette palette to use for painting
* CBitmap *pBitmap pointer to screen-size bitmap for background painting
*
* Return Value:
* bool success/failure condition
*
* Description: establish the bitmap used for restoring the background
* as sprite images are moved around the screen.
*
************************************************************************/
bool CSprite::SetBackdrop(CDC *pDC, CPalette *pPalette, CBitmap *pBitmap) {
if (m_pBackdropDC != nullptr) // if the backdrop context is active
ReleaseBackdropDC(); // ... then unmap all of its resources
if (m_pBackdrop != nullptr) // delete the old bitmap
delete m_pBackdrop;
m_pBackdrop = pBitmap; // save the new bitmap
m_pBackdropPalette = pPalette; // ... and palette
m_bHaveBackdrop = true; // indicate we have a backdrop
return true;
}
/*************************************************************************
*
* ClearBackdrop()
*
* Parameters: none
*
* Return Value: none
*
* Description: clear the bitmap and context used for restoring the
* background as sprite images were moved around the screen.
*
************************************************************************/
void CSprite::ClearBackdrop() {
ReleaseBackdropDC(); // unmap any resources
if (m_pBackdrop != nullptr) { // delete the backdrop bitmap
delete m_pBackdrop;
m_pBackdrop = nullptr;
}
m_pBackdropPalette = nullptr;
if (m_pBackdropDC != nullptr) { // delete the device context
delete m_pBackdropDC;
m_pBackdropDC = nullptr;
}
m_bHaveBackdrop = false;
}
/*************************************************************************
*
* RefreshBackdrop()
*
* Parameters:
* CDC *pDC screen device context
* CPalette *pPalette palette to use for painting
*
* Return Value:
* bool success/failure condition
*
* Description: repaint the backdrop.
*
************************************************************************/
bool CSprite::RefreshBackdrop(CDC *pDC, CPalette *pPalette) {
bool bSuccess = false;
CPalette *pPalOld = nullptr;
if (m_bHaveBackdrop == false) // punt if no backdrop art
return false;
if (GetBackdropDC(pDC) == nullptr) // setup backdrop context
return false;
if (pPalette != nullptr) { // map in our palette
pPalOld = (*pDC).SelectPalette(pPalette, false);
(*pDC).RealizePalette();
}
// zap it to the screen
bSuccess = (*pDC).BitBlt(0, 0, GAME_WIDTH, GAME_HEIGHT, m_pBackdropDC, 0, 0, SRCCOPY);
if (pPalOld != nullptr) // map out the palette
(*pDC).SelectPalette(pPalOld, false);
ReleaseBackdropDC(); // release backdrop context resources
return bSuccess;
}
/*************************************************************************
*
* DuplicateSprite()
*
* Parameters:
* CDC *pDC pointer to device context to be used in creating
* the sprite
*
* Return Value:
* CSprite * pointer to new sprite (Success) / nullptr (failure)
*
* Description: Create a sprite based on sharing the resources of a
* master sprite (passed implicitly when this is invoked).
*
* NOTE: Do NOT delete a sprite that has been duplicated
* until ALL of its duplicates are deleted; using a duplicate
* when its master has been deleted will result in a crash.
*
************************************************************************/
CSprite *CSprite::DuplicateSprite(CDC *pDC) {
CSprite *pSprite = nullptr;
pSprite = new CSprite(); // create an object for the sprite
if (DuplicateSprite(pDC, pSprite))
return pSprite;
delete pSprite;
return nullptr; // return failure
}
/*************************************************************************
*
* DuplicateSprite()
*
* Parameters:
* CDC *pDC pointer to device context to be used in creating
* the sprite
* CSprite *pSprite pointer to sprite to be duplicated
*
* Return Value:
* bool success/failure condition
*
* Description: Update a sprite based on sharing the resources of a
* master sprite (passed implicitly when this is invoked).
*
* NOTE: Do NOT delete a sprite that has been duplicated
* until ALL of its duplicates are deleted; using a duplicate
* when its master has been deleted will result in a crash.
*
************************************************************************/
bool CSprite::DuplicateSprite(CDC *pDC, CSprite *pSprite) {
CPalette *pPalOld = nullptr;
if (m_pPalette) {
pPalOld = (*pDC).SelectPalette(m_pPalette, false);
(*pDC).RealizePalette();
}
if (pSprite != nullptr) { // only duplicate visible sprites // ... unable to create that object
if (m_bVisible) {
if (m_bRetainContexts) {
if (!SetupImage(pDC))
return false;
(void)SetupMask(pDC);
(*pSprite).m_pImageDC = m_pImageDC;
(*pSprite).m_pImageOld = m_pImageOld;
(*pSprite).m_pMaskDC = m_pMaskDC;
(*pSprite).m_pMaskOld = m_pMaskOld;
} else {
ReleaseImageContext(); // release existing device contexts.
ReleaseMaskContext();
}
(*pSprite).m_pImage = m_pImage;
}
(*pSprite).m_pPalette = m_pPalette;
if (m_pPalette != nullptr) {
(*pSprite).m_bSharedPalette = true;
(*pSprite).m_pPalImageOld = m_pPalImageOld;
}
(*pSprite).m_cRect = m_cRect;
(*pSprite).m_cImageRect = m_cImageRect;
(*pSprite).m_cSize = m_cSize;
(*pSprite).m_cPosition = m_cPosition;
(*pSprite).m_cMovementDelta = m_cMovementDelta;
(*pSprite).m_cHotspot = m_cHotspot;
(*pSprite).m_nType = m_nType;
(*pSprite).m_pData = m_pData;
(*pSprite).m_nZOrder = m_nZOrder;
(*pSprite).m_nZPosition = m_nZPosition;
(*pSprite).m_nCelID = m_nCelID;
(*pSprite).m_nCelCount = m_nCelCount;
(*pSprite).m_bVisible = m_bVisible;
(*pSprite).m_bMasked = m_bMasked;
(*pSprite).m_bMobile = m_bMobile;
(*pSprite).m_bIntercepts = m_bIntercepts;
(*pSprite).m_bRetainBackground = m_bRetainBackground;
(*pSprite).m_bRetainContexts = m_bRetainContexts;
(*pSprite).m_bAnimated = m_bAnimated;
(*pSprite).m_bDuplicated = true; // mark it as a sprite with shared resources
if (m_bVisible) {
if (!m_bMasked || CreateMask(pDC)) { // create an image mask if needed
(*pSprite).m_pMask = m_pMask;
if (m_pPalette)
(*pDC).SelectPalette(pPalOld, false);
return true;
}
} else
return true;
}
if (m_pPalette)
(*pDC).SelectPalette(pPalOld, false);
return false;
}
/*************************************************************************
*
* LoadSprite()
*
* Parameters:
* CDC *pDC pointer to device context to be used in creating
* a compatible context for the sprite's bitmap
*
* char *pszPathName pointer to text string with the fully qualified file
* specification for the DIB bitmap file
*
* Return Value:
* bool success/failure condition
*
* Description: Read in a DIB file and convert it to a DDB bitmap for use as
* the sprite's image. A device context for the image is retained
* if speed optimization has been specified. The DIB palette is
* retained for use by the sprite if it does not yet have an
* established palette.
*
************************************************************************/
bool CSprite::LoadSprite(CDC *pDC, const char *pszPathName) {
CDibDoc *myDib = nullptr; // pointer to our loaded DIB file
bool bHavePalette = false; // whether or not we have a palette already
CPalette *pPalOld = nullptr; // palette previously mapped to base context
HPALETTE hPalette = nullptr;
ClearImage(); // clear out any/all existing bitmaps, palettes,
ClearMask(); // ... and device contexts
myDib = new CDibDoc(); // create an object for our DIB
m_pImage = new CBitmap(); // create an object for the sprite's image
bHavePalette = (m_pPalette ? true : false); // loading a DIB automatically includes
// ... loading its color palette, which
// ... must be deleted if we already
// ... have one assigned to this sprite
if ((myDib != nullptr) && // verify we have the objects we just created
(m_pImage != nullptr) && // .. and then attempt to open the requested
(*myDib).OpenDocument(pszPathName)) { // .... bitmap file
m_bVisible = true;
if (!bHavePalette) // if we don't have a palette, then
m_pPalette = (*myDib).DetachPalette(); // ... detach the DIB's palette for our use
if (m_pPalette != nullptr) {
pPalOld = (*pDC).SelectPalette(m_pPalette, false);
(*pDC).RealizePalette();
hPalette = (HPALETTE)(*m_pPalette).m_hObject;
}
if (CreateImageContext(pDC)) {
// Create a context for the image
HDIB hDib = (*myDib).GetHDIB();
(*m_pImage).m_hObject = DIBtoBitmap(
(*pDC).m_hDC, // convert the DIB to a DDB
hPalette,
hDib); // ... and store it in the sprite
if ((*m_pImage).m_hObject != nullptr) { // verify the conversion was successful
if (pPalOld != nullptr)
(*pDC).SelectPalette(pPalOld, false);
if (!m_bRetainContexts) // release the context if not optimizing
ReleaseImageContext();
m_cSize = (*myDib).GetDocSize(); // ... size related variables
m_cRect.right = m_cRect.left + m_cSize.cx; // ... update rectangular dimensions
m_cRect.bottom = m_cRect.top + m_cSize.cy;
m_cImageRect.SetRect(0, 0, m_cSize.cx, m_cSize.cy);
m_nCelID = -1;
m_nCelCount = 0;
delete myDib; // discard the DIB
myDib = nullptr;
return true; // return success
}
}
}
if (pPalOld != nullptr)
(*pDC).SelectPalette(pPalOld, false);
ClearImage();
if (!bHavePalette && // delete the palette resource and object
(m_pPalette != nullptr)) { // ... if it came from the DIB
(*m_pPalette).DeleteObject();
delete m_pPalette;
m_pPalette = nullptr;
}
if (myDib != nullptr) // lastly delete the DIB itself
delete myDib;
return false; // return failure
}
/*************************************************************************
*
* LoadSprite()
*
* Parameters:
* CBitmap *pBitmap pointer to bitmap to be used and owned
* CPalette *pPalette pointer to palette to be shared
*
* Return Value:
* bool success/failure condition
*
* Description: use a CBitmap object as the sprite's image. A device context
* for the image is retained if speed optimization has been specified.
* The palette is retained for use by the sprite if it does not yet have an
* established palette.
*
************************************************************************/
bool CSprite::LoadSprite(CBitmap *pBitmap, CPalette *pPalette) {
CSize mySize;
BITMAP cBitmapData;
if (pBitmap == nullptr) // punt if no bitmap
return false;
ClearImage(); // clear out any/all existing bitmaps, palettes,
ClearMask(); // ... and device contexts
m_pImage = pBitmap; // save pointer to bitmap
if (pPalette != nullptr) // ... and the palette, if specified
SharePalette(pPalette);
(*pBitmap).GetObject(sizeof(BITMAP), &cBitmapData); // get the size of the bitmap
m_cSize.cx = cBitmapData.bmWidth; // ... and initialize size variables
m_cSize.cy = cBitmapData.bmHeight;
m_cRect.right = m_cRect.left + m_cSize.cx;
m_cRect.bottom = m_cRect.top + m_cSize.cy;
m_cImageRect.SetRect(0, 0, m_cSize.cx, m_cSize.cy);
m_nCelID = -1;
m_nCelCount = 0;
m_bVisible = true;
return true; // return success
}
/*************************************************************************
*
* LoadResourceSprite()
*
* Parameters:
* CDC *pDC pointer to device context to be used in creating
* a compatible context for the sprite's bitmap
*
* int resId resource number for a bitmap identified in the game's
* .RC resource file
*
* Return Value:
* bool success/failure condition
*
* Description: Read in a DDB bitmap resource and store it in the sprite object.
* Resource bitmaps do not have an accessible palette, so you
* may need to set one directly (SetPalette or SharedPalette);
* if none is specified, then the current system palette is used.
*
************************************************************************/
bool CSprite::LoadResourceSprite(CDC *pDC, const int resId) {
bool bSuccess = false;
char chResID[16];
Common::sprintf_s(chResID, "#%d", resId);
bSuccess = LoadResourceSprite(pDC, chResID);
return bSuccess; // return failure
}
/*************************************************************************
*
* LoadResourceSprite()
*
* Parameters:
* CDC *pDC pointer to device context to be used in creating
* a compatible context for the sprite's bitmap
*
* char *pszPathName resource name for a bitmap identified in the game's
* .RC resource file
*
* Return Value:
* bool success/failure condition
*
* Description: Read in a DDB bitmap resource and store it in the sprite object.
* Resource bitmaps do not have an accessible palette, so you
* may need to set one directly (SetPalette or SharedPalette);
* if none is specified, then the current system palette is used.
*
************************************************************************/
bool CSprite::LoadResourceSprite(CDC *pDC, const char *pszName) {
CDibDoc *myDib = nullptr; // pointer to our loaded DIB file
bool bHavePalette = false; // whether or not we have a palette already
CPalette *pPalOld = nullptr; // palette previously mapped to base context
HPALETTE hPalette = nullptr;
ClearImage(); // clear out any/all existing bitmaps, palettes,
ClearMask(); // ... and device contexts
myDib = new CDibDoc(); // create an object for our DIB
m_pImage = new CBitmap(); // create an object for the sprite's image
bHavePalette = (m_pPalette ? true : false); // loading a DIB automatically includes
// ... loading its color palette, which
// ... must be deleted if we already
// ... have one assigned to this sprite
if ((myDib != nullptr) && // verify we have the objects we just created
(m_pImage != nullptr) && // .. and then attempt to open the requested
(*myDib).OpenResourceDocument(pszName)) { // .... bitmap file
m_bVisible = true;
if (!bHavePalette) // if we don't have a palette, then
m_pPalette = (*myDib).DetachPalette(); // ... detach the DIB's palette for our use
if (m_pPalette != nullptr) {
pPalOld = (*pDC).SelectPalette(m_pPalette, false);
(*pDC).RealizePalette();
hPalette = (HPALETTE)(*m_pPalette).m_hObject;
}
if (CreateImageContext(pDC)) {
// Create a context for the image
HDIB hDib = (*myDib).GetHDIB();
(*m_pImage).m_hObject = DIBtoBitmap((*pDC).m_hDC, // convert the DIB to a DDB
hPalette,
hDib); // ... and store it in the sprite
if ((*m_pImage).m_hObject != nullptr) { // verify the conversion was sucessfull
if (pPalOld != nullptr)
(*pDC).SelectPalette(pPalOld, false);
if (!m_bRetainContexts) // release the context if not optimizing
ReleaseImageContext();
m_cSize = (*myDib).GetDocSize(); // ... size related variables
m_cRect.right = m_cRect.left + m_cSize.cx; // ... update rectangular dimensions
m_cRect.bottom = m_cRect.top + m_cSize.cy;
m_cImageRect.SetRect(0, 0, m_cSize.cx, m_cSize.cy);
m_nCelID = -1;
m_nCelCount = 0;
delete myDib; // discard the DIB
myDib = nullptr;
return true; // return success
}
}
}
if (pPalOld != nullptr)
(*pDC).SelectPalette(pPalOld, false);
ClearImage();
if (!bHavePalette && // delete the palette resource and object
(m_pPalette != nullptr)) { // ... if it came from the DIB
(*m_pPalette).DeleteObject();
delete m_pPalette;
m_pPalette = nullptr;
}
if (myDib != nullptr) // lastly delete the DIB itself
delete myDib;
return false; // return failure
}
/*************************************************************************
*
* LoadCels()
*
* Parameters:
* CDC *pDC pointer to device context to be used in creating
* a compatible context for the sprite's cel bitmap
* char *pszPathName pointer to text string with the fully qualified file
* specification for the DIB bitmap file
* int nCels number of cels in strip
*
* Return Value:
* bool success/failure condition
*
* Description: Read in a DIB file and convert it to a DDB bitmap for use as
* the sprite's cels.
*
************************************************************************/
bool CSprite::LoadCels(CDC *pDC, const char *pszPathName, const int nCels) {
int nOldCelCount;
nOldCelCount = m_nCelCount; // retain previous cel count
if (LoadSprite(pDC, pszPathName) && // load the cel strip as if a normal
SetupCels(nCels)) // ... sprite image, then set specifics
if (nOldCelCount == 0) // if there wasn't a previous cel strip
m_bAnimated = true; // ... then set for initially animated
return true;
return false;
}
/*************************************************************************
*
* LoadCels()
*
* Parameters:
* CBitmap *pBitmap pointer to bitmap to be used and owned
* int nCels number of cels in strip
* CPalette *pPalette pointer to palette to be shared
*
* Return Value:
* bool success/failure condition
*
* Description: Read in a DIB file and convert it to a DDB bitmap for use as
* the sprite's cels.
*
************************************************************************/
bool CSprite::LoadCels(CBitmap *pBitmap, const int nCels, CPalette *pPalette) {
int nOldCelCount;
nOldCelCount = m_nCelCount; // retain previous cel count
if (LoadSprite(pBitmap, pPalette) && // load the cel strip as if a normal
SetupCels(nCels)) // ... sprite image, then set specifics
if (nOldCelCount == 0) // if there wasn't a previous cel strip
m_bAnimated = true; // ... then set for initially animated
return true;
return false;
}
/*************************************************************************
*
* LoadResourceCels()
*
* Parameters:
* CDC *pDC pointer to device context to be used in creating
* a compatible context for the sprite's cel bitmap
* int resId resource number for a bitmap identified in the game's
* int nCels number of cels in strip
* .RC resource file
*
* Return Value:
* bool success/failure condition
*
* Description: Read in a DIB file and convert it to a DDB bitmap for use as
* the sprite's cels.
*
************************************************************************/
bool CSprite::LoadResourceCels(CDC *pDC, const int resId, const int nCels) {
bool bSuccess = false;
char chResID[16];
Common::sprintf_s(chResID, "#%d", resId);
bSuccess = LoadResourceCels(pDC, chResID, nCels);
return bSuccess; // return failure
}
/*************************************************************************
*
* LoadResourceCels()
*
* Parameters:
* CDC *pDC pointer to device context to be used in creating
* a compatible context for the sprite's cel bitmap
* char *pszPathName resource name for a bitmap identified in the game's
* .RC resource file
* int nCels number of cels in strip
*
* Return Value:
* bool success/failure condition
*
* Description: Read in a DIB file and convert it to a DDB bitmap for use as
* the sprite's cels.
*
************************************************************************/
bool CSprite::LoadResourceCels(CDC *pDC, const char *pszName, const int nCels) {
int nOldCelCount;
nOldCelCount = m_nCelCount; // retain previous cel count
if (LoadResourceSprite(pDC, pszName) && // load the cel strip as if a normal
SetupCels(nCels)) // ... sprite image, then set specifics
if (nOldCelCount == 0) // if there wasn't a previous cel strip
m_bAnimated = true; // ... then set for initially animated
return true;
return false;
}
/*************************************************************************
*
* SetupCels()
*
* Parameters: none
*
* Return Value:
* bool success/failure condition
*
* Description: initialize cel animation strip variables and validate
* strip length
*
************************************************************************/
bool CSprite::SetupCels(const int nCels) {
int nStripWidth; // temp place to hold cel strip width
m_nCelID = -1; // no current cel
m_nCelCount = nCels; // set cel count
nStripWidth = m_cSize.cx; // retain cell strip pixel length
m_cSize.cx /= nCels; // calculate width of a cel
if (m_cSize.cx * nCels == nStripWidth) { // verify we have an even multiple
m_cRect.right = m_cRect.left + m_cSize.cx; // reset sprite rectangular bounds
m_cRect.bottom = m_cRect.top + m_cSize.cy; // ... based on cel dimensions
m_cImageRect.SetRect(0, 0, m_cSize.cx, m_cSize.cy); // set bounds for first cel in strip
return true;
}
return false;
}
/*************************************************************************
*
* SetPalette()
*
* Parameters:
* CPalette *pPalette color palette to be associated with the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Set to use the specified palette when dealing with this sprite.
* If a palette has been previously associated with the sprite,
* then map it out of the various contexts associated with the
* sprite, then map in the new one.
*
************************************************************************/
bool CSprite::SetPalette(CPalette *pPalette) {
if (m_pImageDC != nullptr) { // if we already have a palette we must
if (m_pImage != nullptr) // ... first map out any existing image
(*m_pImageDC).SelectObject(m_pImageOld); // ... then map out the old palette
if (m_pPalette != nullptr)
(*m_pImageDC).SelectPalette(m_pPalImageOld, false);
}
if (m_pBackgroundDC != nullptr) { // similarly we need to map out our background
if (m_pBackground != nullptr) // ... bitmap as well
(*m_pBackgroundDC).SelectObject(m_pBackgroundOld);
if (m_pPalette != nullptr) // .. then map out the palette
(*m_pBackgroundDC).SelectPalette(m_pPalBackOld, false);
}
ClearPalette(); // release existing palette
m_pPalette = pPalette; // set the new palette
if (m_pImageDC == nullptr) // done if no context to map it into
return true;
m_pPalImageOld = (*m_pImageDC).SelectPalette(m_pPalette, false); // map in the new palette and then
(*m_pImageDC).RealizePalette(); // ... tell the system to use it
if (m_pImage != nullptr) {
m_pImageOld = (*m_pImageDC).SelectObject(m_pImage); // map in our image bitmap if it exists
if (m_pImageOld == nullptr)
return false;
}
if (m_pBackgroundDC == nullptr) // done if no background context
return true;
m_pPalBackOld = (*m_pBackgroundDC).SelectPalette(m_pPalette, false); // map in the new palette and then
(*m_pBackgroundDC).RealizePalette(); // ... tell the system to use it
if (m_pBackground != nullptr) {
m_pBackgroundOld = (*m_pBackgroundDC).SelectObject(m_pBackground); // map in our background
if (m_pBackgroundOld == nullptr)
return false;
}
return true;
}
/*************************************************************************
*
* SharePalette()
*
* Parameters:
* CPalette *pPalette color palette to be associated with the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Use the specified palette when dealing with this sprite,
* and mark it as a shared resource; hence don't delete it
* when later changing the palette or destroying the sprite.
* If a palette has been previously associated with the sprite,
* then map it (and its bitmap) out of the device context,
* delete it if it is not a shared resource, map the new
* palette into the context, and then map in the bitmap.
*
************************************************************************/
bool CSprite::SharePalette(CPalette *pPalette) {
if (SetPalette(pPalette)) { // load a new palette into our sprite
m_bSharedPalette = true; // ... and mark it as a shared resource
return true;
} // ... whioh means it continues to exist
// ... when this sprite is later destroyed
m_pPalette = nullptr; // not able to do that so ensure the
return false; // ... pointer is nulled
}
/*************************************************************************
*
* PaintSprite()
*
* Parameters:
* CDC *pDC pointer to device context for the destination
* of the painting operation (e.g. display screen)
*
* int x coordinate which identifies where to begin painting
* int y the sprite, and which corresponds to the upper lefthand
* corner location of the sprite's bitmap
*
* Return Value:
* bool success/failure condition
*
* Description: Paint the sprite. If it is a mobile sprite, then save
* the bitmap area it will cover up so that when it moves
* again so the saved bitmap area can be painted back to the
* screen to correctly re-establish the background. Background
* restoration is either straightforward, or complex, depending
* on whether or not the sprite overlaps other sprites.
*
************************************************************************/
bool CSprite::PaintSprite(CDC *pDC, const int x, const int y) {
bool bSuccess = false;
CRect overlapRect, dstRect;
CSprite *pOverlap = nullptr;
m_bTouchedSprite = false; // default to no sprite being overlapped by this
m_pTouchedSprite = nullptr; // ... painting operation
if (!m_bPositioned) // setup the initial location if not
SetPosition(x, y);
if (!m_bVisible) { // just place it if invisible
SetPosition(x, y);
return true;
}
if (pDC == nullptr) // punt if no device context for the sprite
return false;
if (m_bAnimated && m_nCelCount) // advance to the next cel in the strip
UpdateCel();
dstRect.SetRect(x, y, x + m_cSize.cx, y + m_cSize.cy); // calculate destination rectangle
pOverlap = Interception(&dstRect); // see if the sprite will intercept another
if (pOverlap != nullptr) { // ... and if so, record that fact
m_bTouchedSprite = true; // we've touched something
m_pTouchedSprite = pOverlap; // here's what we touched
}
if (!m_bLinked || // how we do the painting depends on whether
(!m_bOverlaps &&
(pOverlap == nullptr))) { // ... we intercept another sprite
if ((m_bHaveBackdrop || (m_pBackground != nullptr)) &&
overlapRect.IntersectRect(&m_cRect, &dstRect)) { // ... optimize painting of localized movement
bSuccess = DoOptimizedPainting(pDC, &dstRect); // ... by doing it all offscreen
SetPosition(x, y); // establish the sprite's new position
} else {
if (m_bHaveBackdrop || (m_pBackground != nullptr)) {
bSuccess = RefreshBackground(pDC); // If it isn't close, just restore its background
if (!bSuccess)
return false;
}
SetPosition(x, y); // formally set the sprites new location
if (!m_bHaveBackdrop && m_bRetainBackground) {
bSuccess = SaveBackground(pDC); // save the background art of its new location
if (!bSuccess)
return false;
}
bSuccess = UpdateSprite(pDC); // paint the sprite in its new location
}
m_bOverlaps = false; // this sprite does not overlap another
m_nZPosition = m_nZOrder; // ... so reset its z ordering
} else { // we have an overlap, so we need to do tear down
bSuccess = DoOverlapPainting(pDC, &dstRect); // ... and rebuild sprite by sprite
SetPosition(x, y); // now establish the sprite's new position
}
return bSuccess;
}
/*************************************************************************
*
* SetCel()
*
* Parameters:
* int nCelID index of the desired cell
*
* Return Value:
* bool success/failure condition
*
* Description: advance the cel image to the specified bitmap in the strip.
*
************************************************************************/
void CSprite::SetCel(const int nCelID) {
m_nCelID = nCelID;
while (m_nCelID >= m_nCelCount) {
m_nCelID -= m_nCelCount;
}
m_cImageRect.left = m_nCelID * m_cSize.cx;
m_cImageRect.right = m_cImageRect.left + m_cSize.cx;
}
/*************************************************************************
*
* UpdateSprite()
*
* Parameters:
* CDC *pDC pointer to device context for the destination
* of the painting operation (e.g. display screen)
*
* Return Value:
* bool success/failure condition
*
* Description: Repaint the sprite in its current location. If it is a
* mobile sprite without a saved background bitmap, then it
* will be created and saved for later use.
*
* NOTE: use PaintSprite() to paint a sprite to new location.
*
************************************************************************/
bool CSprite::UpdateSprite(CDC *pDC) {
CPalette *pPalOld = nullptr;
bool bSuccess = false;
if (!m_bVisible) // punt if not visible
return true;
if (pDC == nullptr) // verify we have the necessary device contexts
return false;
if (!m_bHaveBackdrop && m_bRetainBackground && // if we don't have a bitmap backdrop, and if
(m_pBackgroundDC == nullptr)) { // ... we move and are painting for the first time
if (!SaveBackground(pDC)) // ... and then load it with the proper image
return false;
}
if (m_pPalette != nullptr) {
pPalOld = (*pDC).SelectPalette(m_pPalette, false); // map in a palette if present
(*pDC).RealizePalette();
} // ... and tell the system we did that
if (m_bMasked) // if masked, then paint the sprite, masking out
bSuccess = DoSpritePainting(pDC, m_cPosition); // ... (ignoring) pure white pixels
else {
bSuccess = SetupImage(pDC); // not masked - so just splat the entire bitmap
if (bSuccess) {
bSuccess = (*pDC).BitBlt(m_cPosition.x,
m_cPosition.y,
m_cSize.cx,
m_cSize.cy,
m_pImageDC,
m_cImageRect.left,
m_cImageRect.top,
SRCCOPY);
if (!m_bRetainContexts)
ReleaseImageContext();
}
}
if (m_pPalette != nullptr) // map back the previous palette if needed
(*pDC).SelectPalette(pPalOld, false);
return bSuccess;
}
/*************************************************************************
*
* DoSpritePainting()
*
* Parameters:
* CDC *pDC pointer to device context for the destination
* of the painting operation (e.g. display screen)
*
* Return Value:
* bool success/failure condition
*
* Description: Paint the source bitmap into the destination bitmap.
* White pixels in the source bitmap are masked out (not painted)
* thereby allowing irregular shaped images to be overlayed onto
* a background. Painting is done in an offscreen bitmap and
* then transfered to the destination context; this reduces sprite
* flicker, albeit at the cost of addition time.
*
************************************************************************/
bool CSprite::DoSpritePainting(CDC * pDC, CPoint cPoint) {
CDC *pDC2 = nullptr; // context for manipulating background image
CBitmap *pBitmap2 = nullptr, // bitmap for pDC2
*pBitmap2Old = nullptr; // bitmap previously mapped into pDC2
CPalette *pPal2Old = nullptr; // palette previously mapped into pDC2
bool bSuccess = false; // success/failure
if (!m_bVisible) // punt if not visible
return true;
if (pDC == nullptr) // punt if we don't have contexts
return false;
if (SetupImage(pDC) && // put the image and mask bitmaps into
SetupMask(pDC)) { // ... device contexts
pDC2 = new CDC(); // create our temporary bitmap work areas
pBitmap2 = new CBitmap();
if ((pDC2 != nullptr) &&
(pBitmap2 != nullptr) &&
(*pDC2).CreateCompatibleDC(pDC)) {
if (m_pPalette != nullptr) { // map in a palette if available
pPal2Old = (*pDC2).SelectPalette(m_pPalette, false); // ... and for the background work area
(*pDC2).RealizePalette();
} // ... make it real too
if ((*pBitmap2).CreateCompatibleBitmap(pDC, m_cSize.cx, m_cSize.cy)) {
pBitmap2Old = (*pDC2).SelectObject(pBitmap2);
if (pBitmap2Old != nullptr) {
if (m_bHaveBackdrop) { // get background image, either from
if (GetBackdropDC(pDC) == nullptr) // .. the backdrop or the sprite itselt
goto punt;
(*pDC2).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, m_pBackdropDC, cPoint.x, cPoint.y, SRCCOPY);
ReleaseBackdropDC();
} else
(*pDC2).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, pDC, cPoint.x, cPoint.y, SRCCOPY); // get the sprite's background image
(*pDC2).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, m_pMaskDC, m_cImageRect.left, m_cImageRect.top, SRCAND); // ... mask out where sprite will go
(*pDC2).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, m_pImageDC, m_cImageRect.left, m_cImageRect.top, SRCPAINT); // combine image with background
bSuccess = (*pDC).BitBlt(cPoint.x, cPoint.y, m_cSize.cx, m_cSize.cy, pDC2, 0, 0, SRCCOPY); // paint result to the screen
}
}
}
punt:
if (pBitmap2Old != nullptr) // now release the temporary resources we used
(*pDC2).SelectObject(pBitmap2Old);
if (pBitmap2 != nullptr) {
(*pBitmap2).DeleteObject();
delete pBitmap2;
}
if (pPal2Old != nullptr)
(*pDC2).SelectPalette(pPal2Old, false);
if (pDC2 != nullptr) {
(*pDC2).DeleteDC();
delete pDC2;
}
}
if (!m_bRetainContexts) { // release the sprite contexts if not optimizing
ReleaseImageContext();
ReleaseMaskContext();
}
return bSuccess; // return success or failure
}
/*************************************************************************
*
* DoOptimizedPainting()
*
* Parameters:
* CDC *pDC pointer to device context for the destination
* of the painting operation (e.g. display screen)
*
* CRect *pDstRect rectangle defining the area where the sprite will be
*
* Return Value:
* bool success/failure condition
*
* Description: Determine the area affected by painting the sprite in a
* new location (e.g. old area where the background bitmap
* needs to be restored, as well as the area where the sprite
* is to be newly painted), and then do all of that painting
* in a compatible offscreen bitmap. Once that is accomplished,
* paint the offscreen bitmap onto the destination device context;
* This elimates very noticeable sprite "flicker" that would
* otherwise occur, albeit at the cost of additional time.
*
************************************************************************/
bool CSprite::DoOptimizedPainting(CDC *pDC, CRect *pDstRect) {
bool bSuccess = false; // success/failure return status
CPoint cPoint; // where to paint sprite in the work area
CRect unionRect; // rectangle enclosing old and new sprite locations
CDC workDC; // device context for offscreen work area
CBitmap *pBitmap = nullptr, // bitmap for the work area
*pBitmapOldWork = nullptr; // bitmap previously mapped to work area's context
CPalette *pPalOld = nullptr, // palette previously mapped to destination context
*pPalOldWork = nullptr; // palette prevously mapped to work area's context
int dx, dy; // delta sizes of work area's bitmap
if (pDC == nullptr) // punt if no sprite device context
return false;
if (m_pPalette != nullptr) { // if there is a palette
pPalOld = (*pDC).SelectPalette(m_pPalette, false); // ... select it into the context
(*pDC).RealizePalette();
} // ... and tell the system about it
unionRect = m_cRect; // the work area is defined by our sprite
if (pDstRect != nullptr)
unionRect.UnionRect(unionRect, pDstRect); // calculate the smallest enclosing rectangle that
dx = unionRect.right - unionRect.left; // ... contains the bitmap area where the sprite was
dy = unionRect.bottom - unionRect.top; // ... and the bitmap area where it will be next
pBitmap = new CBitmap(); // create an offscreen bitmap where we do all the
if (pBitmap == nullptr) // ... work; first create a bitmap for the enclosing
return false; // ... rectangle, and if that fails, then punt
if (!(*pBitmap).CreateCompatibleBitmap(pDC, dx, dy)) {
delete pBitmap;
return false;
}
if (workDC.CreateCompatibleDC(pDC)) { // setup the work area context
if (m_pPalette != nullptr) { // if we have a palette, map it into the work context
pPalOldWork = workDC.SelectPalette(m_pPalette, false);
workDC.RealizePalette();
} // ... and tell the system to use it
pBitmapOldWork = workDC.SelectObject(pBitmap); // now map in the bitmap into that context
if (pBitmapOldWork != nullptr) { // next copy the image enclosed by the rectangle
if (m_bHaveBackdrop) { // restore the background where the sprite was
if (GetBackdropDC(pDC) == nullptr)
goto punt;
bSuccess = workDC.BitBlt(0,
0,
dx,
dy,
m_pBackdropDC,
unionRect.left,
unionRect.top,
SRCCOPY);
ReleaseBackdropDC();
if (!bSuccess)
goto punt;
} else {
workDC.BitBlt(0, 0, dx, dy, pDC, unionRect.left, unionRect.top, SRCCOPY); // ... into the bitmap
if (SetupBackground(pDC)) {
workDC.BitBlt(m_cPosition.x - unionRect.left, // restore the background where the sprite was
m_cPosition.y - unionRect.top,
m_cSize.cx,
m_cSize.cy,
m_pBackgroundDC,
0,
0,
SRCCOPY);
(*m_pBackgroundDC).BitBlt(0, // save the background where the sprite will be
0,
m_cSize.cx,
m_cSize.cy,
&workDC,
(*pDstRect).left - unionRect.left,
(*pDstRect).top - unionRect.top,
SRCCOPY);
if (!m_bRetainContexts || m_bDuplicated) // release background context if not optimizing
ReleaseBackgroundContext();
} else
goto punt;
}
cPoint.x = (*pDstRect).left - unionRect.left; // determine where to paint the new sprite image
cPoint.y = (*pDstRect).top - unionRect.top; // ... into the work area
bSuccess = ((!m_bMasked || SetupMask(pDC)) && // setup the contexts we need
SetupImage(pDC));
if (bSuccess) {
if (m_bMasked) { // need to do a masked copy ...
workDC.BitBlt(cPoint.x, // ... mask out where sprite goes
cPoint.y,
m_cSize.cx,
m_cSize.cy,
m_pMaskDC,
m_cImageRect.left,
m_cImageRect.top,
SRCAND);
workDC.BitBlt(cPoint.x, // ... paint in the sprite
cPoint.y,
m_cSize.cx,
m_cSize.cy,
m_pImageDC,
m_cImageRect.left,
m_cImageRect.top,
SRCPAINT);
} else // just paint in the sprite
workDC.BitBlt(cPoint.x,
cPoint.y,
m_cSize.cx,
m_cSize.cy,
m_pImageDC,
m_cImageRect.left,
m_cImageRect.top,
SRCCOPY);
if (!m_bRetainContexts) { // release contexts if not optimizing
ReleaseImageContext();
if (m_bMasked)
ReleaseMaskContext();
}
}
if (bSuccess) {
(*pDC).BitBlt(unionRect.left,
unionRect.top,
dx,
dy,
&workDC,
0,
0,
SRCCOPY);
workDC.SelectObject(pBitmapOldWork); // tear down the work area's bitmap, palette
if (m_pPalette != nullptr) { // ... and device context, as well as the
workDC.SelectPalette(pPalOldWork, false); // ... destination's palette
(*pDC).SelectPalette(pPalOld, false);
}
workDC.DeleteDC();
(*pBitmap).DeleteObject();
delete pBitmap;
return bSuccess;
}
}
}
punt:
if (pBitmapOldWork != nullptr) // we failed, so tear down the resources used
workDC.SelectObject(pBitmapOldWork); // ... map out the bitmap
if (pPalOldWork != nullptr) // ... map out the palette
workDC.SelectPalette(pPalOldWork, false);
if (workDC.m_hDC != nullptr) // ... release the context
workDC.DeleteDC();
(*pBitmap).DeleteObject(); // ... delete the bitmap
delete pBitmap;
if (pPalOld != nullptr) // map out the palette from the output context
(*pDC).SelectPalette(pPalOld, false);
return false;
}
/*************************************************************************
*
* DoOverlapPainting()
*
* Parameters:
* CDC *pDC pointer to device context for the destination
* of the painting operation (e.g. display screen)
* CRect *myRect new area where the sprite will be painted
* CSprite *pSprite pointer to intercepted sprite in chain
*
* Return Value:
* bool success/failure condition
*
* Description: Paint a sprite into its new location, after restoring
* the background where it is currently located. The sprite
* is assumed to be part of a set of overlapping sprites, so
* sprite-by-sprite reconstruction of the background is needed.
*
************************************************************************/
bool CSprite::DoOverlapPainting(CDC *pDC, CRect *myRect) {
CPalette *pPalOld = nullptr;
bool bSuccess = false;
if (pDC == nullptr) // punt if no context
return false;
if (m_pPalette != nullptr) { // if there is a palette
pPalOld = (*pDC).SelectPalette(m_pPalette, false); // ... select it into the context
(*pDC).RealizePalette();
} // ... and tell the system about it
bSuccess = ReconstructBackground(pDC, myRect); // go do the actual painting
if (m_pPalette != nullptr) // map out the palette
(*pDC).SelectPalette(pPalOld, false);
return bSuccess;
}
/*************************************************************************
*
* RefreshBackground()
*
* Parameters:
* CDC *pDC pointer to device context for the destination
* of the painting operation (e.g. display screen)
*
* Return Value:
* bool success/failure condition
*
* Description: Paint a sprite's saved background bitmap into its
* appropriate area.
*
************************************************************************/
bool CSprite::RefreshBackground(CDC *pDC) {
CPalette *pPalOld = nullptr;
bool bSuccess = false;
if (!m_bVisible) // punt if not visible
return true;
if (pDC == nullptr) // punt if no sprite device context
return false;
if (!m_bHaveBackdrop &&
m_pBackground == nullptr) // done if no saved background
return true;
if (m_pPalette != nullptr) { // map a palette to the destination context
pPalOld = (*pDC).SelectPalette(m_pPalette, false); // ... if available
(*pDC).RealizePalette();
}
if (m_bOverlaps) // paint the background bitmap to the device
bSuccess = ReconstructBackground(pDC, nullptr); // ... from all the sprites that overlap it
else if (m_bHaveBackdrop) {
if (GetBackdropDC(pDC) == nullptr)
bSuccess = false;
else {
bSuccess = (*pDC).BitBlt(m_cPosition.x, m_cPosition.y, m_cSize.cx, m_cSize.cy, m_pBackdropDC, m_cPosition.x, m_cPosition.y, SRCCOPY);
ReleaseBackdropDC();
}
} else {
bSuccess = SetupBackground(pDC); // paint the background bitmap to the device
if (bSuccess) {
bSuccess = (*pDC).BitBlt(m_cPosition.x, m_cPosition.y, m_cSize.cx, m_cSize.cy, m_pBackgroundDC, 0, 0, SRCCOPY);
if (!m_bRetainContexts || m_bDuplicated)
ReleaseBackgroundContext();
}
}
if (m_pPalette != nullptr) // map out the palette from the destination
(*pDC).SelectPalette(pPalOld, false);
m_bPositioned = false; // no real position now
m_nZPosition = m_nZOrder; // reset z ordering
return bSuccess;
}
/*************************************************************************
*
* SaveBackground()
*
* Parameters:
* CDC *pDC pointer to device context where the sprite will be painted
*
* Return Value:
* bool success/failure condition
*
* Description: Fetch the area into which the sprite will be painted and
* save it as a bitmap. It can then be repainted there later
* to restore the background.
*
************************************************************************/
bool CSprite::SaveBackground(CDC *pDC) {
CPalette *pPalOld = nullptr;
bool bSuccess = false;
if (m_bHaveBackdrop || !m_bRetainBackground) // not valid to save background with backdrop
return false; // ... or if not retaining same
if (!m_bVisible) // punt if not visible
return true;
if (pDC == nullptr) // punt if no output context
return false;
if (!CreateBackground(pDC)) // create background context
return false;
if (m_pPalette != nullptr) { // map a palette to the destination context
pPalOld = (*pDC).SelectPalette(m_pPalette, false);
(*pDC).RealizePalette();
}
bSuccess = SetupBackground(pDC); // grab the background bitmap
if (bSuccess) {
bSuccess = (*m_pBackgroundDC).BitBlt(0, 0, m_cSize.cx, m_cSize.cy, pDC, m_cPosition.x, m_cPosition.y, SRCCOPY);
if (!m_bRetainContexts || m_bDuplicated) // release context if not optimizing
ReleaseBackgroundContext();
}
if (m_pPalette != nullptr) // map out palette from destination context
(*pDC).SelectPalette(pPalOld, false);
return bSuccess;
}
/*************************************************************************
*
* ReconstructBackground()
*
* Parameters:
* CDC *pDC pointer to device context where the sprite will be painted
* CRect *myRect nullptr or pointer to new area where sprite gets painted
* CSprite *pSprite pointer to intercepted sprite in chain
*
* Return Value:
* bool success/failure condition
*
* Description: Reconstruct the background image by restoring the background
* bitmaps from all overlapping sprites, working from topmost
* down to including our sprite. Then paint all those sprite's
* images (excluding ourself) onto the restore background, thus
* creating a bitmap that no longer contains our sprite's image.
* Lastly, if we have a pointer to a rectangle, then paint our
* sprite image into it, since that is its new location.
*
************************************************************************/
bool CSprite::ReconstructBackground(CDC *pDC, CRect *myRect) {
CDC workDC; // context for our work area
CSprite *pSprite = nullptr, // various sprite pointers
*pTestSprite,
*pZHead;
bool bDoingTopMost; // special case for single sprite that is topmost
bool bSuccess = false; // used to hold success/failure
CRect baseRect, // rectangle spanning the sprite update
unionRect, // rectangle spanning all sprites touched
overlapRect; // temporary rectangle information
int dx, dy; // size of unionRect and work bitmap
CPoint cPoint; // temporary point information
CBitmap *pBitmap = nullptr, // work area bitmap
*pBitmapOld = nullptr; // bitmap previously selected to work context
CPalette *pPalOld = nullptr, // palette previously selected to display context
*pPalOldWork = nullptr; // palette previously selected to work context
#if SPRITE_DEBUG
CRect clipRect; // current clipping area
CPoint viewOrigin; // current viewport origin
int nClipResult;
(*pDC).GetClipBox(&clipRect);
nClipResult = (*pDC).SelectClipRgn(nullptr);
(*pDC).GetClipBox(&clipRect);
viewOrigin = (*pDC).GetViewportOrg();
viewOrigin.x = - viewOrigin.x;
viewOrigin.y = - viewOrigin.y;
#endif
unionRect = m_cRect; // the work area is defined by our sprite
if (myRect != nullptr) // include the area of our new destination
unionRect.UnionRect(unionRect, myRect); // ... if specified, and retain it for later
baseRect = unionRect;
pSprite = this; // start processing from this sprite and make
(*pSprite).m_pZNext = nullptr; // ... it be the first in the z order chain
(*pSprite).m_pZPrev = nullptr;
pTestSprite = m_pSpriteChain; // set all sprites to not having been tested
while (pTestSprite != nullptr) { // ... for this reconstruction cycle
if ((*pTestSprite).m_bVisible && (*pTestSprite).m_bPositioned)
(*pTestSprite).m_bOverlapTest = false;
else
(*pTestSprite).m_bOverlapTest = true;
pTestSprite = (*pTestSprite).m_pNext;
}
pTestSprite = m_pSpriteChain; // get first sprite to test against
m_bOverlapTest = true; // set to not test against the first sprite
m_bOverlaps = true;
if (m_bHaveBackdrop || (m_pBackground != nullptr))
m_bPaintOverlap = true;
else
m_bPaintOverlap = false;
while (pTestSprite != nullptr) { // look for sprites we overlap or which are
if (!(*pTestSprite).m_bOverlapTest && // ... overlapped by sprites that overlap us
overlapRect.IntersectRect(&unionRect, &(*pTestSprite).m_cRect)) {
unionRect.UnionRect(unionRect, (*pTestSprite).m_cRect); // expand rectangle to encompass it
(*pTestSprite).m_bOverlapTest = true; // mark it as having been tested
(*pTestSprite).m_bOverlaps = true; // mark it as involved in an overlap situation
if ((m_bHaveBackdrop || // won't paint sprites without a background
((*pTestSprite).m_pBackground != nullptr)) &&
overlapRect.IntersectRect(&baseRect, &(*pTestSprite).m_cRect))
(*pTestSprite).m_bPaintOverlap = true;
else
(*pTestSprite).m_bPaintOverlap = false;
while (true) { // insert the sprite in the sorted z chain
if ((*pTestSprite).m_nZPosition < (*pSprite).m_nZPosition) { // need to head leftward
if (((*pSprite).m_pZPrev == nullptr) || // put it to the left of us (i.e. overlaps us)
((*(*pSprite).m_pZPrev).m_nZPosition < (*pTestSprite).m_nZPosition)) {
(*pTestSprite).m_pZPrev = (*pSprite).m_pZPrev;
(*pTestSprite).m_pZNext = pSprite;
(*pSprite).m_pZPrev = pTestSprite;
if ((*pTestSprite).m_pZPrev != nullptr)
(*(*pTestSprite).m_pZPrev).m_pZNext = pTestSprite;
break;
} else
pSprite = (*pSprite).m_pZPrev; // shift left in chain and set to test again
} else // need to head rightward
if ((*pTestSprite).m_nZPosition >= (*pSprite).m_nZPosition) {
if (((*pSprite).m_pZNext == nullptr) || // put it to the right of us (we overlap it)
((*(*pSprite).m_pZNext).m_nZPosition >= (*pTestSprite).m_nZPosition)) {
(*pTestSprite).m_pZNext = (*pSprite).m_pZNext;
(*pTestSprite).m_pZPrev = pSprite;
(*pSprite).m_pZNext = pTestSprite;
if ((*pTestSprite).m_pZNext != nullptr)
(*(*pTestSprite).m_pZNext).m_pZPrev = pTestSprite;
break;
} else
pSprite = (*pSprite).m_pZNext; // shift right in chain and set to test again
}
}
pTestSprite = m_pSpriteChain; // need to check all sprites again since the
} // ... the bounding rectangle has expanded and
else // ... may now intercept additional sprites
pTestSprite = (*pTestSprite).m_pNext; // set to test against the next sprite
}
while ((*pSprite).m_pZPrev != nullptr) // find the head of the z order chain so that
pSprite = (*pSprite).m_pZPrev; // ... we can begin background reconstruction
pZHead = pSprite; // save head of z order chain
if (((*pZHead).m_nId == m_nId) && // if we are the head of the z order chain
(m_nZOrder == SPRITE_TOPMOST) && // ... and we are a top-most type of sprite
!m_bHaveBackdrop) { // ... and we're painting direct to the screen
pTestSprite = (*pZHead).m_pZNext; // ... then we only need to handle this sprite
while (pTestSprite != nullptr) { // so set to not paint others in z order chain
(*pTestSprite).m_bPaintOverlap = false;
pTestSprite = (*pTestSprite).m_pZNext;
}
if (m_nCelCount <= 1) // if we're not a cel strip, then set a
bDoingTopMost = true; // ... special flag indicating just our sprite
else
bDoingTopMost = false; // ... otherwise use default methodology
unionRect = baseRect; // minimize the bounding rectangle
} else
bDoingTopMost = false;
dx = unionRect.right - unionRect.left; // get the width and height of the work area
dy = unionRect.bottom - unionRect.top; // ... spanned by all overlapping sprites
pBitmap = new CBitmap(); // create a bitmap to contain our work area
if (pBitmap == nullptr)
return false;
if (!(*pBitmap).CreateCompatibleBitmap(pDC, dx, dy)) {
delete pBitmap;
return false;
}
if (workDC.CreateCompatibleDC(pDC)) { // create a device context to contain our
if (m_pPalette != nullptr) { // ... work area and map in the palette
pPalOldWork = workDC.SelectPalette(m_pPalette, false);
workDC.RealizePalette();
pPalOld = (*pDC).SelectPalette(m_pPalette, false);
(*pDC).RealizePalette();
}
pBitmapOld = workDC.SelectObject(pBitmap); // map our bitmap into the work area
if (pBitmapOld != nullptr) { // now need to create the original background
if (m_bHaveBackdrop) { // ... which is easy if we are maintaining an
if (GetBackdropDC(pDC) == nullptr) // ... offscreen bitmap for the whole screen
bSuccess = false;
else {
bSuccess = workDC.BitBlt(0, // copy the background section into the work area
0,
dx,
dy,
m_pBackdropDC,
unionRect.left,
unionRect.top,
SRCCOPY);
ReleaseBackdropDC();
}
if (!bSuccess)
goto punt;
while (true) {
pTestSprite = (*pSprite).m_pZNext; // now update z order relative positions
if (pTestSprite == nullptr) // ... of every sprite in the z order chain
break; // ... to ensure/maintain increasing ordering
if ((*pSprite).m_nZOrder == (*pTestSprite).m_nZOrder)
(*pTestSprite).m_nZPosition = (*pSprite).m_nZPosition + 1;
pSprite = pTestSprite;
}
} else { // no backdrop, so first grab the screen
bSuccess = workDC.BitBlt(0, 0, dx, dy, pDC, unionRect.left, unionRect.top, SRCCOPY);
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY);
#endif
while (true) { // now restore the saved background from each
if ((*pSprite).m_bPaintOverlap && (*pSprite).m_bRetainBackground) {
bSuccess = (*pSprite).SetupBackground(pDC); // ... sprite in the z order chain
if (bSuccess) {
if ((*pSprite).m_bMasked && // if just doing the single topmost sprite, then
bDoingTopMost && // ... replace its image area with its background
(*pSprite).SetupMask(pDC)) { // .this is extra work but compensated by ignoring all others
bSuccess = workDC.BitBlt((*pSprite).m_cPosition.x - unionRect.left, // mask away where sprite image is located
(*pSprite).m_cPosition.y - unionRect.top,
(*pSprite).m_cSize.cx,
(*pSprite).m_cSize.cy,
(*pSprite).m_pMaskDC,
(*pSprite).m_cImageRect.left,
(*pSprite).m_cImageRect.top,
SRCAND);
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY);
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, 0, 0, SRCCOPY);
#endif
bSuccess = (*(*pSprite).m_pMaskDC).BitBlt( // invert the image mask
(*pSprite).m_cImageRect.left,
(*pSprite).m_cImageRect.top,
(*pSprite).m_cSize.cx,
(*pSprite).m_cSize.cy,
(*pSprite).m_pMaskDC,
(*pSprite).m_cImageRect.left,
(*pSprite).m_cImageRect.top,
NOTSRCCOPY);
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, 0, 0, SRCCOPY);
#endif
bSuccess = (*(*pSprite).m_pBackgroundDC).BitBlt(0, 0, // mask away area of saved background that
(*pSprite).m_cSize.cx, // ... is NOT spanned by the sprite image
(*pSprite).m_cSize.cy,
(*pSprite).m_pMaskDC,
(*pSprite).m_cImageRect.left,
(*pSprite).m_cImageRect.top,
SRCAND);
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pBackgroundDC, 0, 0, SRCCOPY);
#endif
bSuccess = (*(*pSprite).m_pMaskDC).BitBlt(
(*pSprite).m_cImageRect.left, // put the mask back the way it was
(*pSprite).m_cImageRect.top,
(*pSprite).m_cSize.cx,
(*pSprite).m_cSize.cy,
(*pSprite).m_pMaskDC,
(*pSprite).m_cImageRect.left,
(*pSprite).m_cImageRect.top,
NOTSRCCOPY);
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, (*pSprite).m_cSize.cx, (*pSprite).m_cSize.cy, (*pSprite).m_pMaskDC, 0, 0, SRCCOPY);
#endif
bSuccess = workDC.BitBlt((*pSprite).m_cPosition.x - unionRect.left, // paint the remaining background into cleared area
(*pSprite).m_cPosition.y - unionRect.top, // i.e. we only updated the pixels where the image
(*pSprite).m_cSize.cx, // ... was actually located, leaving all else untouched
(*pSprite).m_cSize.cy,
(*pSprite).m_pBackgroundDC,
0,
0,
SRCPAINT);
} else // just splat sprite's background to work area
bSuccess = workDC.BitBlt((*pSprite).m_cPosition.x - unionRect.left,
(*pSprite).m_cPosition.y - unionRect.top,
(*pSprite).m_cSize.cx,
(*pSprite).m_cSize.cy,
(*pSprite).m_pBackgroundDC, 0, 0, SRCCOPY);
if (!(*pSprite).m_bRetainContexts) // release contexts if not optimizing
(*pSprite).ReleaseMaskContext();
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY);
#endif
}
if (!bSuccess)
goto punt;
}
if ((*pSprite).m_pZNext == nullptr) { // once we have restored all backgrounds
if ((m_nZOrder == SPRITE_TOPMOST) && // ... then place us at the head of the
(m_nId != (*pZHead).m_nId)) { // ... z order chain if we are to be topmost
if ((*pSprite).m_nId == m_nId) // maintain pointer to last in z order
pSprite = m_pZPrev; // ... backing up one if it is us
pTestSprite = m_pZPrev; // ... since we're moving to the head
(*pTestSprite).m_pZNext = m_pZNext;
if (m_pZNext != nullptr)
(*m_pZNext).m_pZPrev = pTestSprite;
m_pZNext = pZHead;
m_pZPrev = nullptr;
(*pZHead).m_pZPrev = this;
pZHead = this;
m_nZPosition = m_nZOrder;
}
break;
}
pSprite = (*pSprite).m_pZNext;
}
pTestSprite = pZHead; // update z order relative positions
while ((*pTestSprite).m_pZNext != nullptr) { // ... to ensure increasing ordering
if ((*pTestSprite).m_nZOrder == (*(*pTestSprite).m_pZNext).m_nZOrder)
(*(*pTestSprite).m_pZNext).m_nZPosition = (*pTestSprite).m_nZPosition + 1;
pTestSprite = (*pTestSprite).m_pZNext;
}
}
while (true) { // now repaint the images of the sprites
if (m_nId == (*pSprite).m_nId) { // ... onto the work area
if (myRect == nullptr) // no image, just erasing our sprite
bSuccess = true;
else {
if (!m_bHaveBackdrop) { // but before we paint in the sprite's image
if (m_bRetainBackground) { // ... retain what that area looks like as
if (m_pBackground == nullptr) { // ... the new saved background
if (!CreateBackground(pDC) ||
!SetupBackground(pDC))
goto punt;
}
bSuccess = (*m_pBackgroundDC).BitBlt(0, 0,
m_cSize.cx,
m_cSize.cy,
&workDC,
(*myRect).left - unionRect.left,
(*myRect).top - unionRect.top,
SRCCOPY);
if (!m_bRetainContexts || m_bDuplicated)
ReleaseBackgroundContext();
if (!bSuccess)
goto punt;
}
m_bPaintOverlap = true;
}
cPoint.x = (*myRect).left - unionRect.left; // set the destination for where our
cPoint.y = (*myRect).top - unionRect.top; // ... sprite will be painted
goto paint_sprite;
}
} else { // save what the background looks like
if (!m_bHaveBackdrop && (*pSprite).m_bRetainBackground &&
(*pSprite).m_bPaintOverlap) {
bSuccess = (*(*pSprite).m_pBackgroundDC).BitBlt(0, 0,
(*pSprite).m_cSize.cx,
(*pSprite).m_cSize.cy,
&workDC,
(*pSprite).m_cPosition.x - unionRect.left,
(*pSprite).m_cPosition.y - unionRect.top,
SRCCOPY);
if (!(*pSprite).m_bRetainContexts || (*pSprite).m_bDuplicated)
(*pSprite).ReleaseBackgroundContext();
if (!bSuccess)
goto punt;
}
cPoint.x = (*pSprite).m_cPosition.x - unionRect.left; // set the destination for sprite
cPoint.y = (*pSprite).m_cPosition.y - unionRect.top;
paint_sprite:
if ((*pSprite).m_bPaintOverlap) {
bSuccess = (!(*pSprite).m_bMasked || (*pSprite).SetupMask(pDC)) && // setup image and mask contexts
(*pSprite).SetupImage(pDC);
if (bSuccess) {
if ((*pSprite).m_bMasked) { // need to do a masked transfer ...
workDC.BitBlt(cPoint.x, // ... mask away where image will go
cPoint.y,
(*pSprite).m_cSize.cx,
(*pSprite).m_cSize.cy,
(*pSprite).m_pMaskDC,
(*pSprite).m_cImageRect.left,
(*pSprite).m_cImageRect.top,
SRCAND);
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY);
#endif
workDC.BitBlt(cPoint.x, // ... paint image into cleared area
cPoint.y,
(*pSprite).m_cSize.cx,
(*pSprite).m_cSize.cy,
(*pSprite).m_pImageDC,
(*pSprite).m_cImageRect.left,
(*pSprite).m_cImageRect.top,
SRCPAINT);
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY);
#endif
} else {
workDC.BitBlt(cPoint.x, // just paint the image
cPoint.y,
(*pSprite).m_cSize.cx,
(*pSprite).m_cSize.cy,
(*pSprite).m_pImageDC,
(*pSprite).m_cImageRect.left,
(*pSprite).m_cImageRect.top,
SRCCOPY);
#if SPRITE_DEBUG
bSuccess = (*pDC).BitBlt(viewOrigin.x, viewOrigin.y, dx, dy, &workDC, 0, 0, SRCCOPY);
#endif
}
if (!(*pSprite).m_bRetainContexts) { // release contexts if not optimizing
(*pSprite).ReleaseImageContext();
if ((*pSprite).m_bMasked)
(*pSprite).ReleaseMaskContext();
}
}
}
}
if (!bSuccess)
goto punt;
if ((*pSprite).m_pZPrev == nullptr) // see if done with image painting
break;
pSprite = (*pSprite).m_pZPrev;
}
(*pDC).BitBlt(baseRect.left, // paint only the part of the work area
baseRect.top, // ... spanned by our sprites source
baseRect.right - baseRect.left, // ... and destination locations to
baseRect.bottom - baseRect.top, // ... the screen; i.e. doing the whole
&workDC, // ... work area would be painting stuff
baseRect.left - unionRect.left, // ... that wasn't modified
baseRect.top - unionRect.top,
SRCCOPY);
workDC.SelectObject(pBitmapOld); // tear down the work area's bitmap, palette
if (m_pPalette != nullptr) { // ... and device context, as well as the
workDC.SelectPalette(pPalOldWork, false); // ... destination's palette
(*pDC).SelectPalette(pPalOld, false);
}
workDC.DeleteDC();
(*pBitmap).DeleteObject();
delete pBitmap;
return true;
}
}
punt:
if (pBitmapOld != nullptr) // we failed, so tear down the resources used
workDC.SelectObject(pBitmapOld); // ... map out the bitmap
if (pPalOldWork != nullptr) // ... map out the palette
workDC.SelectPalette(pPalOldWork, false);
if (workDC.m_hDC != nullptr) // ... release the context
workDC.DeleteDC();
(*pBitmap).DeleteObject(); // ... delete the bitmap
delete pBitmap;
if (pPalOld != nullptr) // map out the palette from the output context
(*pDC).SelectPalette(pPalOld, false);
return false;
}
/*************************************************************************
*
* EraseSprites()
*
* Parameters:
* CDC *pDC pointer to device context where the sprites are painted
*
* Return Value:
* bool success/failure condition
*
* Description: For each sprite in the sprite chain, clear the sprite's
* image by repainting its stored background and then clear
* the background bitmap.
*
************************************************************************/
bool CSprite::EraseSprites(CDC *pDC) {
CSprite *pSprite = nullptr;
pSprite = m_pSpriteChain;
while (pSprite != nullptr) {
if (!(*pSprite).EraseSprite(pDC))
return false;
pSprite = (*pSprite).m_pNext;
}
return true;
}
/*************************************************************************
*
* EraseSprite()
*
* Parameters:
* CDC *pDC pointer to device context where the sprite is painted
*
* Return Value:
* bool success/failure condition
*
* Description: Clear the sprite's image by repainting its stored background
* and then clear the background bitmap.
*
************************************************************************/
bool CSprite::EraseSprite(CDC *pDC) {
if (!m_bVisible) // punt if not visible
return true;
if (RefreshBackground(pDC)) { // repaint the background
ClearBackground(); // clear the background art
m_bPositioned = false; // no longer has a real position
return true;
}
return false;
}
/*************************************************************************
*
* TestInterception()
*
* Parameters:
* CDC *pDC pointer to device context where the sprite gets painted
* CSprite * pTestSprite pointer to a lone test sprite for testing
* CPoint *pPoint optional address of place to store estimated pixel overlap
*
* Return Value:
* CSprite * pointer to sprite touched, or nullptr
*
* Description: Determine whether a given sprite touches another given sprite.
* The defining rectangular areas of the sprites are tested first,
* and bit masking techniques are used if both sprites are masked.
*
************************************************************************/
bool CSprite::TestInterception(CDC *pDC, CSprite * pTestSprite, CPoint *pPoint) {
CRect myRect, // rectangle occupied by current sprite
testRect, // sprite retangle to be tested against
overlapRect; // area of overlap between rectangles
if (!m_bIntercepts || !(*pTestSprite).m_bIntercepts) // punt if no interception allowed
return false;
myRect = m_cRect; // acquire the rectangle for base sprite
if (m_nId != (*pTestSprite).m_nId) { // be sure to not test against ourself
testRect = (*pTestSprite).m_cRect; // get bounding rectangle to test against
if (overlapRect.IntersectRect(&myRect, &testRect)) { // use simple rectangle screening first
if (!m_bMasked || // ... and if that succeeds, see if we
!(*pTestSprite).m_bMasked || // ... and if that succeeds, see if we
SpritesOverlap(pDC, pTestSprite, pPoint)) // ... have image masks that overlap
return true; // got a simple or complex overlap
}
}
return false;
}
/*************************************************************************
*
* Interception()
*
* Parameters:
* CRect *newRect rectangle defining the new location of a sprite
* CSprite * pointer to a lone sprite or a sprite chain
*
* Return Value:
* CSprite * pointer to sprite touched, or nullptr
*
* Description: Determine whether a sprite will touch another sprite if
* moved to its new location. Simply determine whether
* the defining rectangular areas of the sprites intersect
* for basic sprites.
*
************************************************************************/
CSprite *CSprite::Interception(CRect *newRect, CSprite * pTestSprite) {
CSprite *pSprite; // pointer to current sprite
CRect myRect, // rectangle occupied by current sprite
testRect, // sprite retangle to be tested against
overlapRect; // area of overlap between rectangles
if (!m_bIntercepts) // punt if interceptions not allowed
return nullptr;
pSprite = pTestSprite; // get first sprite to be tested
myRect = *newRect; // acquire the rectangle for base sprite
while (pSprite != nullptr) { // thumb through the sprite chain
if ((m_nId != (*pSprite).m_nId) && // be sure to not test against ourself
(*pSprite).m_bIntercepts) { // ... and only test against overlapping sprites
testRect = (*pSprite).m_cRect; // sprites touch if their rectangles intersect
if (overlapRect.IntersectRect(&myRect, &testRect)) // does our sprite overlap another?
return (pSprite); // ... if so return a pointer to it
}
pSprite = (*pSprite).m_pNext;
} // fetch next sprite in chain for testing
return nullptr;
}
/*************************************************************************
*
* Interception()
*
* Parameters:
* CDC *pDC pointer to device context where the sprite gets painted
* CSprite * pTestSprite pointer to a lone test sprite or a sprite chain
*
* Return Value:
* CSprite * pointer to sprite touched, or nullptr
*
* Description: Determine whether a sprite will touch another sprite if
* moved to its new location. Simply determine whether
* the defining rectangular areas of the sprites intersect
* for basic sprites; use bit masking techniques for the
* complex sprite images.
*
************************************************************************/
CSprite *CSprite::Interception(CDC *pDC, CSprite * pTestSprite) {
CSprite *pSprite = nullptr; // pointer to current sprite
pSprite = pTestSprite; // get first sprite to be tested
while (pSprite != nullptr) { // thumb through the entire sprite collection
if (TestInterception(pDC, pSprite, nullptr)) // ... testing against each sprite in turn
return (pSprite); // found an interception
pSprite = (*pSprite).m_pNext;
} // fetch next sprite in chain for testing
return nullptr;
}
/*************************************************************************
*
* SpritesOverlap()
*
* Parameters:
* CDC *pDC pointer to device context where the sprite gets painted
* CSprite * pSprite pointer to a lone test sprite
* CPoint *pPoint optional address of place to store estimated pixel overlap
*
* Return Value:
* bool success/failure condition
*
* Description: Determine whether a sprite touches another sprite by
* checking for overlap in their monochrome bitmap masks
*
************************************************************************/
bool CSprite::SpritesOverlap(CDC * pDC, CSprite * pSprite, CPoint *pPoint) {
bool bSuccess = false; // success/failure return status
CRect unionRect; // rectangle enclosing old and new sprite locations
CDC *cDC1 = nullptr, // device context for sprite 1
*cDC2 = nullptr; // device context for sprite 2
CBitmap *pBitmap1 = nullptr, // bitmap for context 1
*pBitmap2 = nullptr, // bitmap for context 2
*pBitmap1Old = nullptr, // bitmap previously mapped to context 1
*pBitmap2Old = nullptr; // bitmap previously mapped to context 2
byte *chPixels = nullptr; // buffer for holding pixel values
int dx, dy, // dimensions of context bitmaps
i, j;
size_t stN;
uint32 dwN; //, dwByte; // delta sizes of work area's bitmap
BITMAP cBitmapData;
int bx, by, bdx, bdy;
if (!m_bVisible || !(*pSprite).m_bVisible) // punt if not visible
return false;
if (pDC == nullptr) // punt if no output context
return false;
unionRect.UnionRect(m_cRect, (*pSprite).m_cRect); // calculate the smallest enclosing rectangle that
dx = unionRect.right - unionRect.left; // ... contains the bitmap area where the sprite was
dy = unionRect.bottom - unionRect.top; // ... and the bitmap area where it will be next
// Allocate a pixel buffer
dwN = stN = dx * dy;
chPixels = (byte *) calloc((size_t) 1, stN);
if (!chPixels)
return false;
cDC1 = new CDC(); // get objects for the offscreen bitmaps
cDC2 = new CDC(); // ... i.e. for the contexts and bitmaps themselves
pBitmap1 = new CBitmap();
pBitmap2 = new CBitmap();
if ((cDC1 != nullptr) && // verify we got the objects we asked for
(cDC2 != nullptr) &&
(*cDC1).CreateCompatibleDC(pDC) && // create the actual device contexts
(*cDC2).CreateCompatibleDC(pDC) &&
(*pBitmap1).CreateBitmap(dx, dy, 1, 1, chPixels) && // create the actual bitmaps
(*pBitmap2).CreateBitmap(dx, dy, 1, 1, chPixels)) {
pBitmap1Old = (*cDC1).SelectObject(pBitmap1); // map the bitmaps into the contexts
pBitmap2Old = (*cDC2).SelectObject(pBitmap2);
if ((pBitmap1Old != nullptr) &&
(pBitmap2Old != nullptr)) {
bx = m_cPosition.x - unionRect.left; // get positioning information for sprite 1
by = m_cPosition.y - unionRect.top;
bdx = m_cSize.cx;
bdy = m_cSize.cy;
bSuccess = SetupMask(pDC);
if (bSuccess) {
(*cDC1).BitBlt(bx, by, bdx, bdy, m_pMaskDC, m_cImageRect.left, m_cImageRect.top, NOTSRCCOPY); // get sprite 1's mask
if (!m_bRetainContexts)
ReleaseMaskContext();
bx = (*pSprite).m_cPosition.x - unionRect.left; // get positioning information for sprite 2
by = (*pSprite).m_cPosition.y - unionRect.top;
bdx = (*pSprite).m_cSize.cx;
bdy = (*pSprite).m_cSize.cy;
bSuccess = (*pSprite).SetupMask(pDC);
if (bSuccess) {
(*cDC2).BitBlt(bx, by, bdx, bdy, (*pSprite).m_pMaskDC, (*pSprite).m_cImageRect.left, (*pSprite).m_cImageRect.top, NOTSRCCOPY); // get sprite 2's mask
if (!(*pSprite).m_bRetainContexts)
(*pSprite).ReleaseMaskContext();
(*cDC1).BitBlt(0, 0, dx, dy, cDC2, 0, 0, SRCAND); // logically AND the masks together
(*cDC1).SelectObject(pBitmap1Old); // ... leaving bits set where they overlap
pBitmap1Old = nullptr;
(*pBitmap1).GetBitmapBits(dwN, chPixels); // fetch the image we created
(*pBitmap1).GetObject(sizeof(BITMAP), &cBitmapData); // .. get the scanline length
bSuccess = false;
for (i = 0; i < cBitmapData.bmHeight; i++) { // ... and look for a byte that is nonzero
for (j = 0; j < cBitmapData.bmWidthBytes; j++) {// ... in which case we have an image overlap
if (chPixels[(i * cBitmapData.bmWidthBytes) + j] != 0) {
bSuccess = true;
if (pPoint != nullptr) { // estimate point of intersection
(*pPoint).x = j - (m_cPosition.x - unionRect.left);
(*pPoint).y = i - (m_cPosition.y - unionRect.top);
if ((*pPoint).x < 0)
(*pPoint).x = 0;
if ((*pPoint).y < 0)
(*pPoint).y = 0;
}
break;
}
}
}
}
}
}
}
free(chPixels); // free up the work area's bitmap
if (pBitmap1Old != nullptr) // map out the bitmaps we used
(*cDC1).SelectObject(pBitmap1Old);
if (pBitmap2Old != nullptr)
(*cDC2).SelectObject(pBitmap2Old);
if (pBitmap1 != nullptr) { // delete the bitmap resources and objects
(*pBitmap1).DeleteObject();
delete pBitmap1;
}
if (pBitmap2 != nullptr) {
(*pBitmap2).DeleteObject();
delete pBitmap2;
}
if (cDC1 != nullptr) { // release the contexts and delete the objects
(*cDC1).DeleteDC();
delete cDC1;
}
if (cDC2 != nullptr) {
(*cDC2).DeleteDC();
delete cDC2;
}
return bSuccess; // return success/failure
}
/*************************************************************************
*
* Touched()
*
* Parameters:
* CPoint testPoint X/Y point against which to test
*
* Return Value:
* bool success/failure condition
*
* Description: Determine whether a sprite is touched by the given point
*
************************************************************************/
bool CSprite::Touching(CPoint myPoint) {
if (m_bIntercepts && // ignoring sprites that don't intercept
m_cRect.PtInRect(myPoint)) // see if the point is in the sprite's rectangle
return true; // ... and if so, return success
return false;
}
/*************************************************************************
*
* Touched()
*
* Parameters:
* CPoint testPoint X/Y point against which to test succeeding sprites
* CSprite * pointer to sprite with which to begin testing
*
* Return Value:
* CSprite * pointer to sprite touched, or nullptr
*
* Description: Determine whether a sprite is touched by the given point,
* if not test against succeeding members in its chain.
*
************************************************************************/
CSprite *CSprite::Touched(CPoint myPoint, CSprite *pSprite) {
CRect testRect; // sprite area to be tested
while (pSprite != nullptr) { // thumb through the entire sprite collection
if ((*pSprite).m_bIntercepts) { // ... ignoring sprites that don't intercept
testRect = (*pSprite).m_cRect;
if (testRect.PtInRect(myPoint)) // See if the point is in the sprite's rectangle
return (pSprite); // ... and if so, return a pointer to it
}
pSprite = (*pSprite).m_pNext;
} // fetch next sprite for testing
return nullptr;
}
/*************************************************************************
*
* SetPosition()
*
* Parameters:
* int x coordinate which identifies where to begin painting
* int y the sprite, and which corresponds to the upper lefthand
* corner location of the sprite's bitmap
*
* Return Value: none
*
* Description: Establish the new location, and encompassing rectangular
* area, for the sprite.
*
************************************************************************/
void CSprite::SetPosition(int x, int y) {
m_bPositioned = true; // now have a real location
m_cPosition.x = x; // establish the new location of the sprite
m_cPosition.y = y; // ... and setup the bitmap's bounding rectangle
m_cRect.SetRect(m_cPosition.x, m_cPosition.y, m_cPosition.x + m_cSize.cx, m_cPosition.y + m_cSize.cy);
}
/*************************************************************************
*
* CropImage()
*
* Parameters:
* CDC *pDC pointer to device context used by the sprite
* CRect *pRect pointer of rectangular area to be cropped;
* position is relative to upper left hand corner
* of sprite image which is the origin (0,0)
*
* bool success/failure condition
*
* Description: Crop a rectangular area from a sprite's image;
* i.e. turn it black so that the background art
* shows through, and make the corresponding area
* in the mask be white.
*
************************************************************************/
bool CSprite::CropImage(CDC *pDC, CRect *pRect) {
CRect myRect;
CBrush myBrush;
bool bSuccess = false;
myRect = *pRect; // offset crop area by image rect
myRect.left += m_cImageRect.left;
myRect.right += m_cImageRect.left;
if (m_pImage != nullptr) {
if (SetupImage(pDC)) { // blacken image section to crop it
myBrush.CreateStockObject(BLACK_BRUSH);
(*m_pImageDC).FillRect(&myRect, &myBrush);
if (!m_bMasked || (m_pMask == nullptr))
bSuccess = true;
else {
bSuccess = SetupMask(pDC); // whiten mask section to crop it
myBrush.CreateStockObject(WHITE_BRUSH);
(*m_pMaskDC).FillRect(&myRect, &myBrush);
}
if (bSuccess && m_bHaveBackdrop) {
if (GetBackdropDC(pDC) == nullptr)
bSuccess = false;
else {
bSuccess = (*pDC).BitBlt(
m_cRect.left + (*pRect).left,
m_cRect.top + (*pRect).top,
(*pRect).right - (*pRect).left,
(*pRect).bottom - (*pRect).top,
m_pBackdropDC,
m_cRect.left + (*pRect).left,
m_cRect.top + (*pRect).top,
SRCCOPY);
ReleaseBackdropDC();
}
} else if (bSuccess && (m_pBackground != nullptr)) {
bSuccess = SetupBackground(pDC); // update background with cropped area
if (bSuccess) {
bSuccess = (*pDC).BitBlt(
m_cRect.left + (*pRect).left,
m_cRect.top + (*pRect).top,
(*pRect).right - (*pRect).left,
(*pRect).bottom - (*pRect).top,
m_pBackgroundDC,
(*pRect).left,
(*pRect).top,
SRCCOPY);
ReleaseBackgroundContext();
}
}
}
}
if (!m_bRetainContexts) {
ReleaseImageContext();
ReleaseMaskContext();
}
return bSuccess;
}
/*************************************************************************
*
* CreateMask()
*
* Parameters:
* CDC *myDC pointer to device context used by the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Create a monochrome bitmap mask for the sprite, with
* white (1s) for the image, and black (0s) for the background.
*
************************************************************************/
bool CSprite::CreateMask(CDC *pDC) {
bool bHaveImageContext = false, // keep track of whether contexts exist
bHaveMaskContext = false;
CSize mySize;
if (!m_bVisible) // punt if not visible
return false;
if (m_pMask != nullptr) // done if already have a mask
return true;
if (!m_bMasked || // fail if not masked or, no image from
(m_pImage == nullptr) || // ... which to obtain a mask, or no
(pDC == nullptr)) // ... output context
return false;
if (m_pImageDC != nullptr) // see if contexts already exist
bHaveImageContext = true;
if (m_pMaskDC != nullptr)
bHaveMaskContext = true;
mySize.cx = m_nCelCount ? m_cSize.cx * m_nCelCount : m_cSize.cx;
mySize.cy = m_cSize.cy;
m_pMask = new CBitmap(); // create object to hold the mask
if ((m_pMask != nullptr) && // verify that worked
SetupImage(pDC) && // setup the image bitmap and context
CreateMaskContext(pDC)) { // create a context for the mask
if ((*m_pMask).CreateBitmap(mySize.cx, mySize.cy, 1, 1, nullptr)) { // create mask bitmap
m_pMaskOld = (*m_pMaskDC).SelectObject(m_pMask); // map bitmap into context
if (m_pMaskOld) {
(*m_pMaskDC).BitBlt(0, // copy in the image, doing an inversion
0, // ... so that we can mask out the sprite's
mySize.cx, // ... transparent area
mySize.cy,
m_pImageDC,
0,
0,
NOTSRCCOPY);
(*m_pImageDC).BitBlt(0, // remove transparent area from sprite image
0,
mySize.cx,
mySize.cy,
m_pMaskDC,
0,
0,
SRCAND);
(*m_pMaskDC).BitBlt(0, // invert mask again so it can be used later
0, // ... to mask away the sprite image area
mySize.cx, // ... from the background for each new
mySize.cy, // ... destination
m_pMaskDC,
0,
0,
DSTINVERT);
if (!bHaveImageContext) // release contexts if not optimizing
ReleaseImageContext();
if (!bHaveMaskContext)
ReleaseMaskContext();
return true;
}
}
}
if (!bHaveImageContext) // release contexts if they didn't
ReleaseImageContext(); // ... already exist
if (!bHaveMaskContext)
ReleaseMaskContext();
if (m_pMask != nullptr) { // delete mask bitmap since we failed
delete m_pMask;
m_pMask = nullptr;
}
return false;
}
/*************************************************************************
*
* CreateBackground()
*
* Parameters:
* CDC *myDC pointer to device context used by the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Create an offscreen bitmap to hold the background image
* that the sprite covers up.
*
************************************************************************/
bool CSprite::CreateBackground(CDC *pDC) {
if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop
return false;
if (m_pBackground != nullptr) // done if already have a background
return true;
if (pDC != nullptr) { // create an object to hold things
m_pBackground = new CBitmap();
if (m_pBackground != nullptr) { // create the background bitmap
if ((*m_pBackground).CreateCompatibleBitmap(pDC, m_cSize.cx, m_cSize.cy))
return true;
delete m_pBackground; // tear things down if we failed
m_pBackground = nullptr;
}
}
return false;
}
/*************************************************************************
*
* SetupImage()
*
* Parameters:
* CDC *myDC pointer to device context used by the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Establish a device context to hold the sprite's image
* and then map in its palette and bitmap.
*
************************************************************************/
bool CSprite::SetupImage(CDC * pDC) {
bool bHaveContext = false; // whether we already have a context
if (!m_bVisible) // punt if not visible
return false;
if ((m_pImage != nullptr) && // punt if no image bitmap
(pDC != nullptr)) { // ... or no output context
if (m_pImageDC != nullptr) // see if already have a context
bHaveContext = true;
if (CreateImageContext(pDC)) { // create a context for the image
if (m_pImageOld == nullptr) // ... then map in the bitmap
m_pImageOld = (*m_pImageDC).SelectObject(m_pImage);
if (m_pImageOld != nullptr)
return true;
}
if (!bHaveContext) // release context if we didn't
ReleaseImageContext(); // ... have it already
}
return false;
}
/*************************************************************************
*
* SetupBackground()
*
* Parameters:
* CDC *myDC pointer to device context used by the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Establish a device context to hold the sprite's Background
* and then map in its palette and bitmap.
*
************************************************************************/
bool CSprite::SetupBackground(CDC * pDC) {
bool bHaveContext = false; // whether there is already a context
if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop
return false;
if ((m_pBackground != nullptr) && // verify there is a background bitmap
(pDC != nullptr)) { // ... and an output context
if (m_pBackgroundDC != nullptr) // see if a context already exists
bHaveContext = true;
if (CreateBackgroundContext(pDC)) { // create a context for
if (m_pBackgroundOld == nullptr) // ... and map in the bitmap
m_pBackgroundOld = (*m_pBackgroundDC).SelectObject(m_pBackground);
if (m_pBackgroundOld != nullptr)
return true;
}
if (!bHaveContext) // release context if we didn't
ReleaseBackgroundContext(); // ... have it already
}
return false;
}
/*************************************************************************
*
* SetupMask()
*
* Parameters:
* CDC *myDC pointer to device context used by the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Establish a device context to hold the sprite's Mask
* and then map in its bitmap.
*
************************************************************************/
bool CSprite::SetupMask(CDC * pDC) {
bool bHaveContext = false; // whether we have a context
if (!m_bVisible) // punt if not visible
return false;
if (m_bMasked && // verify we have a bitmap
(pDC != nullptr)) { // ... and an output context
if (m_pMaskDC != nullptr) // see if we already have a context
bHaveContext = true;
if (CreateMaskContext(pDC) && // create a context for the mask
CreateMask(pDC)) { // create the mask bitmap and content
if (m_pMaskOld == nullptr) // map bitmap into context
m_pMaskOld = (*m_pMaskDC).SelectObject(m_pMask);
if (m_pMaskOld != nullptr)
return true;
}
if (!bHaveContext) // release context if we didn't
ReleaseMaskContext(); // ... already have it
}
return false;
}
/*************************************************************************
*
* CreateImageContext()
*
* Parameters:
* CDC *myDC pointer to device context used by the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Create a device context to hold the sprite's image.
*
************************************************************************/
bool CSprite::CreateImageContext(CDC * pDC) {
if (!m_bVisible) // punt if not visible
return false;
if (pDC != nullptr) { // verify output context
if (m_pImageDC == nullptr) { // if we don't already have
m_pImageDC = new CDC(); // ... a context for the image
if (!(*m_pImageDC).CreateCompatibleDC(pDC)) // ... we create one now
goto Error;
}
if ((m_pPalette != nullptr) && (m_pPalImageOld == nullptr)) { // map in palette if present
m_pPalImageOld = (*m_pImageDC).SelectPalette(m_pPalette, false);
(*m_pImageDC).RealizePalette();
}
return true;
}
Error:
ReleaseImageContext(); // failed, so release the context
return false;
}
/*************************************************************************
*
* CreateBackgroundContext()
*
* Parameters:
* CDC *myDC pointer to device context used by the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Create a device context to hold the sprite's background.
*
************************************************************************/
bool CSprite::CreateBackgroundContext(CDC * pDC) {
if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop
return false;
if (pDC != nullptr) { // verify the output context
if (m_pBackgroundDC == nullptr) { // if we don't have a context
m_pBackgroundDC = new CDC(); // ... then we create one now
if (!(*m_pBackgroundDC).CreateCompatibleDC(pDC))
goto Error;
}
if ((m_pPalette != nullptr) && (m_pPalBackOld == nullptr)) { // map in palette if present
m_pPalBackOld = (*m_pBackgroundDC).SelectPalette(m_pPalette, false);
(*m_pBackgroundDC).RealizePalette();
}
return true;
}
Error:
ReleaseBackgroundContext(); // failed, so release the context
return false;
}
/*************************************************************************
*
* CreateMaskContext()
*
* Parameters:
* CDC *myDC pointer to device context used by the sprite
*
* Return Value:
* bool success/failure condition
*
* Description: Create a device context to hold the sprite's mask.
*
************************************************************************/
bool CSprite::CreateMaskContext(CDC * pDC) {
if (!m_bVisible) // punt if not visible
return false;
if (m_bMasked && // verify it is a masked sprite
(pDC != nullptr)) { // ... and we have an output context
if (m_pMaskDC != nullptr) // done if already have a context
return true;
m_pMaskDC = new CDC(); // create an object for the context
if ((m_pMaskDC != nullptr) && // ... then create the context itself
(*m_pMaskDC).CreateCompatibleDC(pDC))
return true;
ReleaseMaskContext();
} // release the context if we failed
return false;
}
/*************************************************************************
*
* ReleaseImageContext()
*
* Parameters: none
*
* Return Value: none
*
* Description: Release the sprite's image context.
*
************************************************************************/
void CSprite::ReleaseImageContext() {
if (!m_bVisible) // punt if not visible
return;
if (m_pImageDC != nullptr) { // if there is an image device context
if (m_pImageOld != nullptr) // ... map out existing bitmap
(*m_pImageDC).SelectObject(m_pImageOld);
if (m_pPalImageOld != nullptr) // ... map out the palette
(*m_pImageDC).SelectPalette(m_pPalImageOld, false);
(*m_pImageDC).DeleteDC(); // ... release the context
delete m_pImageDC; // ... then delete the device context
m_pImageOld = nullptr;
m_pPalImageOld = nullptr;
m_pImageDC = nullptr;
}
}
/*************************************************************************
*
* ReleaseBackgroundContext()
*
* Parameters: none
*
* Return Value: none
*
* Description: Release the sprite's background context.
*
************************************************************************/
void CSprite::ReleaseBackgroundContext() {
if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop
return;
if (m_pBackgroundDC != nullptr) { // if there is a background device context
if (m_pBackgroundOld != nullptr) { // ... map out existing bitmap
(*m_pBackgroundDC).SelectObject(m_pBackgroundOld);
m_pBackgroundOld = nullptr;
}
if (m_pPalBackOld != nullptr) { // ... map out the palette
(*m_pBackgroundDC).SelectPalette(m_pPalBackOld, false);
m_pPalBackOld = nullptr;
}
(*m_pBackgroundDC).DeleteDC(); // ... release the context
delete m_pBackgroundDC;
m_pBackgroundDC = nullptr;
} // ... then delete the device context
}
/*************************************************************************
*
* ReleaseMaskContext()
*
* Parameters: none
*
* Return Value: none
*
* Description: Release the sprite's mask context.
*
************************************************************************/
void CSprite::ReleaseMaskContext() {
if (!m_bVisible) // punt if not visible
return;
if (m_bMasked &&
(m_pMaskDC != nullptr)) { // if there is a mask device context
if (m_pMaskOld != nullptr) // ... map out existing bitmap
(*m_pMaskDC).SelectObject(m_pMaskOld);
(*m_pMaskDC).DeleteDC(); // ... release the context
delete m_pMaskDC; // ... then delete the device context
m_pMaskOld = nullptr;
m_pMaskDC = nullptr;
}
}
/*************************************************************************
*
* ClearImage()
*
* Parameters: none
*
* Return Value: none
*
* Description: Destroy the sprite's image bitmap and context
*
************************************************************************/
void CSprite::ClearImage() {
if (!m_bVisible) // punt if not visible
return;
if (!m_bDuplicated) {
ReleaseImageContext(); // release the imnage context
if (m_pImage != nullptr) { // destroy the image bitmap object
(*m_pImage).DeleteObject(); // ... if present
delete m_pImage;
}
}
m_pImage = nullptr;
}
/*************************************************************************
*
* ClearBackgrounds()
*
* Parameters:
* CDC *pDC pointer to device context where the sprites are painted
*
* Return Value:
* bool success/failure condition
*
* Description: For each sprite in the sprite chain, clear the sprite's
* background bitmap.
*
************************************************************************/
void CSprite::ClearBackgrounds() {
CSprite *pSprite = nullptr;
pSprite = m_pSpriteChain;
while (pSprite != nullptr) {
(*pSprite).ClearBackground();
pSprite = (*pSprite).m_pNext;
}
}
/*************************************************************************
*
* ClearBackground()
*
* Parameters: none
*
* Return Value: none
*
* Description: Destroy the sprite's background bitmap and context
*
************************************************************************/
void CSprite::ClearBackground() {
m_nZPosition = m_nZOrder; // reset z ordering
m_bOverlaps = false; // no longer overlaps other sprites
m_bPositioned = false; // no longer has a real position
if (!m_bVisible || m_bHaveBackdrop) // punt if not visible or have backdrop
return;
ReleaseBackgroundContext(); // release the background context
if (m_pBackground != nullptr) { // destroy the backgrond bitmap object
(*m_pBackground).DeleteObject(); // ... if present
delete m_pBackground;
m_pBackground = nullptr;
}
}
/*************************************************************************
*
* SetRetainBackground()
*
* Parameters: true / false
*
* Return Value: none
*
* Description: set for retaining or ignoring background images for updates,
* and clear away existing background image if required.
*
************************************************************************/
void CSprite::SetRetainBackground(bool bValue) {
if (!bValue) // if not retaining backgrounds now
ClearBackground(); // ... then clear what we have
m_bRetainBackground = bValue; // set new state
}
/*************************************************************************
*
* ClearMask()
*
* Parameters: none
*
* Return Value: none
*
* Description: Destroy the sprite's mask bitmap and context
*
************************************************************************/
void CSprite::ClearMask() {
if (!m_bVisible) // punt if not visible
return;
if (m_bMasked && !m_bDuplicated) {
ReleaseMaskContext(); // release mask context
if (m_pMask != nullptr) { // destroy the Mask bitmap resource and object
(*m_pMask).DeleteObject(); // ... if present
delete m_pMask;
}
}
m_pMask = nullptr;
}
/*************************************************************************
*
* ClearPalette()
*
* Parameters: none
*
* Return Value: none
*
* Description: Destroy the sprite's palette resource if it is not
* shared by other sprites.
*
************************************************************************/
void CSprite::ClearPalette() {
if ((m_pPalette != nullptr) && !m_bSharedPalette) { // explicitly delete the palette resource
(*m_pPalette).DeleteObject(); // ... if it is not shared by other sprites
delete m_pPalette;
}
m_bSharedPalette = false;
m_pPalette = nullptr;
}
/*************************************************************************
*
* GetBackdropDC()
*
* Parameters: none
*
* Return Value:
* CDC * pointer to device context or nullptr
*
* Description: setup the device context for the backdrop.
*
************************************************************************/
CDC *CSprite::GetBackdropDC(CDC *pDC) {
if (m_pBackdropDC != nullptr) // punt if no context
return (m_pBackdropDC);
m_pBackdropDC = new CDC();
if (!(*m_pBackdropDC).CreateCompatibleDC(pDC)) {
delete m_pBackdropDC;
m_pBackdropDC = nullptr;
return nullptr;
}
if (m_pBackdropPalette != nullptr) { // map in the palette if present
m_pBackdropPalOld = (*m_pBackdropDC).SelectPalette(m_pBackdropPalette, false);
if (m_pBackdropPalOld == nullptr) { // punt if not successful
delete m_pBackdropDC;
m_pBackdropDC = nullptr;
return nullptr;
}
(*m_pBackdropDC).RealizePalette(); // make the system use the palette
}
m_pBackdropOld = (*m_pBackdropDC).SelectObject(m_pBackdrop); // map in our bitmap
if (m_pBackdropOld == nullptr) {
if (m_pBackdropPalOld != nullptr) { // map out palette on failure
(*m_pBackdropDC).SelectPalette(m_pBackdropPalOld, false);
m_pBackdropPalOld = nullptr;
delete m_pBackdropDC;
m_pBackdropDC = nullptr;
}
return nullptr;
}
return (m_pBackdropDC); // send back the context
}
/*************************************************************************
*
* ReleaseBackdropDC()
*
* Parameters: none
*
* Return Value: none
*
* Description: release the backdrop device context resources.
*
************************************************************************/
void CSprite::ReleaseBackdropDC() {
if (m_pBackdropOld != nullptr) { // map back the previous bitmap
(*m_pBackdropDC).SelectObject(m_pBackdropOld);
m_pBackdropOld = nullptr;
}
if (m_pBackdropPalOld != nullptr) { // map back the previous palette
(*m_pBackdropDC).SelectPalette(m_pBackdropPalOld, false);
m_pBackdropPalOld = nullptr;
}
if (m_pBackdropDC != nullptr) {
delete m_pBackdropDC;
m_pBackdropDC = nullptr;
}
}
} // namespace HodjNPodj
} // namespace Bagel