963 lines
23 KiB
C++
963 lines
23 KiB
C++
#include "World.h"
|
|
|
|
|
|
#include "common.h"
|
|
#include "map.h"
|
|
|
|
|
|
#include "minorGems/graphics/Image.h"
|
|
#include "minorGems/util/SimpleVector.h"
|
|
|
|
|
|
|
|
|
|
|
|
class GraphicContainer {
|
|
|
|
public:
|
|
|
|
GraphicContainer( const char *inTGAFileName ) {
|
|
|
|
Image *image = readTGA( inTGAFileName );
|
|
|
|
mW = image->getWidth();
|
|
mH = image->getHeight();
|
|
int imagePixelCount = mW * mH;
|
|
|
|
mRed = new double[ imagePixelCount ];
|
|
mGreen = new double[ imagePixelCount ];
|
|
mBlue = new double[ imagePixelCount ];
|
|
|
|
for( int i=0; i<imagePixelCount; i++ ) {
|
|
mRed[i] = 255 * image->getChannel(0)[ i ];
|
|
mGreen[i] = 255 * image->getChannel(1)[ i ];
|
|
mBlue[i] = 255 * image->getChannel(2)[ i ];
|
|
}
|
|
delete image;
|
|
}
|
|
|
|
|
|
~GraphicContainer() {
|
|
delete [] mRed;
|
|
delete [] mGreen;
|
|
delete [] mBlue;
|
|
}
|
|
|
|
|
|
double *mRed;
|
|
double *mGreen;
|
|
double *mBlue;
|
|
|
|
int mW;
|
|
int mH;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GraphicContainer *tileContainer;
|
|
GraphicContainer *chestContainer;
|
|
|
|
GraphicContainer *spriteContainer;
|
|
GraphicContainer *spriteSadContainer;
|
|
GraphicContainer *spouseContainer;
|
|
|
|
GraphicContainer *prizeAnimationContainer;
|
|
GraphicContainer *dustAnimationContainer;
|
|
GraphicContainer *heartAnimationContainer;
|
|
|
|
|
|
// dimensions of one tile. TileImage contains 13 tiles, stacked vertically,
|
|
// with blank lines between tiles
|
|
int tileW = 8;
|
|
int tileH = 8;
|
|
|
|
int tileImageW;
|
|
int tileImageH;
|
|
|
|
|
|
int numTileSets;
|
|
|
|
// how wide the swath of a world is that uses a given tile set
|
|
int tileSetWorldSpan = 200;
|
|
// overlap during tile set transition
|
|
int tileSetWorldOverlap = 50;
|
|
|
|
// for testing
|
|
int tileSetSkip = 0;
|
|
|
|
int tileSetOrder[18] = { 0, 2, 10, 1, 8, 3, 5, 6, 4, 7, 13, 9, 15,
|
|
14, 16, 12, 11, 17 };
|
|
|
|
|
|
|
|
int mapTileSet( int inSetNumber ) {
|
|
// stay in bounds of tileSetOrder
|
|
inSetNumber = inSetNumber % 18;
|
|
|
|
int mappedSetNumber = tileSetOrder[ inSetNumber ];
|
|
|
|
// stay in bounds of tile set collection
|
|
return mappedSetNumber % numTileSets;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int chestW = 8;
|
|
int chestH = 8;
|
|
|
|
int numGems = 6;
|
|
int gemLocations[6] = { 41, 42, 43, 44, 45, 46 };
|
|
//int gemLocations[4] = { 10, 11, 12, 13 };
|
|
double gemColors[6][3] = { { 255, 0, 0 },
|
|
{ 0, 255, 0 },
|
|
{ 255, 160, 0 },
|
|
{ 0, 0, 255 },
|
|
{ 255, 255, 0 },
|
|
{ 255, 0, 255 } };
|
|
|
|
|
|
|
|
|
|
class Animation {
|
|
public:
|
|
|
|
Animation( int inX, int inY, int inFrameW, int inFrameH,
|
|
char inAutoStep,
|
|
char inRemoveAtEnd, GraphicContainer *inGraphics )
|
|
: mX( inX ), mY( inY ),
|
|
mFrameW( inFrameW ), mFrameH( inFrameH ),
|
|
mPageNumber( 0 ),
|
|
mFrameNumber( 0 ),
|
|
mAutoStep( inAutoStep ),
|
|
mRemoveAtEnd( inRemoveAtEnd ),
|
|
mGraphics( inGraphics ) {
|
|
|
|
mImageW = mGraphics->mW;
|
|
|
|
mNumFrames = ( mGraphics->mH - mFrameH ) / mFrameH + 1;
|
|
mNumPages = ( mGraphics->mW - mFrameW ) / mFrameW + 1;
|
|
}
|
|
|
|
// default constructor so that we can build a vector
|
|
// of Animations
|
|
Animation() {
|
|
}
|
|
|
|
|
|
// replaces graphics of animation
|
|
void swapGraphics( GraphicContainer *inNewGraphics ) {
|
|
|
|
mGraphics = inNewGraphics;
|
|
|
|
mImageW = mGraphics->mW;
|
|
|
|
mNumFrames = ( mGraphics->mH - mFrameH ) / mFrameH + 1;
|
|
mNumPages = ( mGraphics->mW - mFrameW ) / mFrameW + 1;
|
|
|
|
if( mPageNumber >= mNumPages ) {
|
|
mPageNumber = mNumPages - 1;
|
|
}
|
|
if( mFrameNumber >= mNumFrames ) {
|
|
mFrameNumber = mNumFrames - 1;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
int mX, mY;
|
|
|
|
|
|
int mFrameW, mFrameH;
|
|
int mImageW;
|
|
|
|
// can blend between pages
|
|
double mPageNumber;
|
|
|
|
int mNumPages;
|
|
|
|
int mFrameNumber;
|
|
int mNumFrames;
|
|
char mAutoStep;
|
|
char mRemoveAtEnd;
|
|
|
|
|
|
GraphicContainer *mGraphics;
|
|
|
|
};
|
|
|
|
|
|
|
|
SimpleVector<Animation> animationList;
|
|
|
|
|
|
|
|
|
|
// pointers into vectors are unsafe
|
|
//Animation *spriteAnimation;
|
|
//Animation *spouseAnimation;
|
|
|
|
int spriteAnimationIndex;
|
|
int spouseAnimationIndex;
|
|
|
|
|
|
|
|
char playerDead = false;
|
|
char spouseDead = false;
|
|
char metSpouse = false;
|
|
|
|
|
|
|
|
void resetSampleHashTable();
|
|
|
|
|
|
|
|
void initWorld() {
|
|
resetMap();
|
|
resetSampleHashTable();
|
|
|
|
playerDead = false;
|
|
spouseDead = false;
|
|
metSpouse = false;
|
|
|
|
animationList.deleteAll();
|
|
|
|
|
|
tileImageW = tileContainer->mW;
|
|
tileImageH = tileContainer->mH;
|
|
|
|
numTileSets = (tileImageW + 1) / (tileW + 1);
|
|
|
|
Animation character( 0, 0, 8, 8, false, false, spriteContainer );
|
|
|
|
|
|
animationList.push_back( character );
|
|
|
|
// unsafe
|
|
// get pointer to animation in vector
|
|
//spriteAnimation = animationList.getElement( animationList.size() - 1 );
|
|
spriteAnimationIndex = animationList.size() - 1;
|
|
|
|
Animation spouse( 100, 7, 8, 8, false, false, spouseContainer );
|
|
spouse.mFrameNumber = 7;
|
|
|
|
|
|
animationList.push_back( spouse );
|
|
|
|
// unsafe!
|
|
// get pointer to animation in vector
|
|
//spouseAnimation = animationList.getElement( animationList.size() - 1 );
|
|
spouseAnimationIndex = animationList.size() - 1;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
char isSpriteTransparent( GraphicContainer *inContainer, int inSpriteIndex ) {
|
|
|
|
// take transparent color from corner
|
|
return
|
|
inContainer->mRed[ inSpriteIndex ] ==
|
|
inContainer->mRed[ 0 ]
|
|
&&
|
|
inContainer->mGreen[ inSpriteIndex ] ==
|
|
inContainer->mGreen[ 0 ]
|
|
&&
|
|
inContainer->mBlue[ inSpriteIndex ] ==
|
|
inContainer->mBlue[ 0 ];
|
|
}
|
|
|
|
|
|
|
|
struct rgbColorStruct {
|
|
double r;
|
|
double g;
|
|
double b;
|
|
};
|
|
typedef struct rgbColorStruct rgbColor;
|
|
|
|
|
|
|
|
// outTransient set to true if sample returned is part of a transient
|
|
// world feature (character sprite, chest, etc.)
|
|
rgbColor sampleFromWorldNoWeight( int inX, int inY, char *outTransient );
|
|
|
|
// same, but wrapped in a hash table to store non-transient results
|
|
rgbColor sampleFromWorldNoWeightHash( int inX, int inY );
|
|
|
|
|
|
|
|
Uint32 sampleFromWorld( int inX, int inY, double inWeight ) {
|
|
rgbColor c = sampleFromWorldNoWeightHash( inX, inY );
|
|
|
|
unsigned char r = (unsigned char)( inWeight * c.r );
|
|
unsigned char g = (unsigned char)( inWeight * c.g );
|
|
unsigned char b = (unsigned char)( inWeight * c.b );
|
|
|
|
|
|
return r << 16 | g << 8 | b;
|
|
}
|
|
|
|
|
|
|
|
char isSpritePresent( int inX, int inY ) {
|
|
// look at animations
|
|
for( int i=0; i<animationList.size(); i++ ) {
|
|
Animation a = *( animationList.getElement( i ) );
|
|
|
|
int animW = a.mFrameW;
|
|
int animH = a.mFrameH;
|
|
|
|
|
|
// pixel position relative to animation center
|
|
// player position centered on sprint left-to-right
|
|
int animX = (int)( inX - a.mX + a.mFrameW / 2 );
|
|
int animY = (int)( inY - a.mY + a.mFrameH / 2 );
|
|
|
|
if( animX >= 0 && animX < animW
|
|
&&
|
|
animY >= 0 && animY < animH ) {
|
|
return true;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
|
|
#include "HashTable.h"
|
|
|
|
HashTable<rgbColor> worldSampleHashTable( 30000 );
|
|
|
|
|
|
void resetSampleHashTable() {
|
|
worldSampleHashTable.clear();
|
|
}
|
|
|
|
|
|
|
|
rgbColor sampleFromWorldNoWeightHash( int inX, int inY ) {
|
|
|
|
char found;
|
|
|
|
rgbColor sample = worldSampleHashTable.lookup( inX, inY, &found );
|
|
|
|
if( isSpritePresent( inX, inY ) ) {
|
|
// don't consider cached result if a sprite is present
|
|
found = false;
|
|
}
|
|
|
|
|
|
if( found ) {
|
|
return sample;
|
|
}
|
|
|
|
// else not found
|
|
|
|
// call real function to get result
|
|
char transient;
|
|
sample = sampleFromWorldNoWeight( inX, inY, &transient );
|
|
|
|
// insert, but only if not transient
|
|
if( !transient ) {
|
|
worldSampleHashTable.insert( inX, inY, sample );
|
|
}
|
|
|
|
return sample;
|
|
}
|
|
|
|
|
|
|
|
rgbColor sampleFromWorldNoWeight( int inX, int inY, char *outTransient ) {
|
|
*outTransient = false;
|
|
|
|
rgbColor returnColor;
|
|
|
|
char isSampleAnim = false;
|
|
|
|
// consider sampling from an animation
|
|
// draw them in reverse order so that oldest sprites are drawn on top
|
|
for( int i=animationList.size() - 1; i>=0; i-- ) {
|
|
|
|
Animation a = *( animationList.getElement( i ) );
|
|
|
|
int animW = a.mFrameW;
|
|
int animH = a.mFrameH;
|
|
|
|
|
|
// pixel position relative to animation center
|
|
// player position centered on sprint left-to-right
|
|
int animX = (int)( inX - a.mX + a.mFrameW / 2 );
|
|
int animY = (int)( inY - a.mY + a.mFrameH / 2 );
|
|
|
|
if( animX >= 0 && animX < animW
|
|
&&
|
|
animY >= 0 && animY < animH ) {
|
|
|
|
// pixel is in animation frame
|
|
|
|
int animIndex = animY * a.mImageW + animX;
|
|
|
|
// skip to appropriate anim page
|
|
animIndex += (int)a.mPageNumber * (animW + 1);
|
|
|
|
|
|
// skip to appropriate anim frame
|
|
animIndex +=
|
|
a.mFrameNumber *
|
|
a.mImageW *
|
|
( animH + 1 );
|
|
|
|
// page to blend with
|
|
int animBlendIndex = animIndex + animW + 1;
|
|
|
|
double blendWeight = a.mPageNumber - (int)a.mPageNumber;
|
|
|
|
|
|
if( !isSpriteTransparent( a.mGraphics, animIndex ) ) {
|
|
|
|
// in non-transparent part
|
|
|
|
if( blendWeight != 0 ) {
|
|
returnColor.r =
|
|
( 1 - blendWeight ) * a.mGraphics->mRed[ animIndex ]
|
|
+
|
|
blendWeight * a.mGraphics->mRed[ animBlendIndex ];
|
|
returnColor.g =
|
|
( 1 - blendWeight ) * a.mGraphics->mGreen[ animIndex ]
|
|
+
|
|
blendWeight * a.mGraphics->mGreen[ animBlendIndex ];
|
|
returnColor.b =
|
|
( 1 - blendWeight ) * a.mGraphics->mBlue[ animIndex ]
|
|
+
|
|
blendWeight * a.mGraphics->mBlue[ animBlendIndex ];
|
|
}
|
|
else {
|
|
// no blend
|
|
returnColor.r = a.mGraphics->mRed[ animIndex ];
|
|
returnColor.g = a.mGraphics->mGreen[ animIndex ];
|
|
returnColor.b = a.mGraphics->mBlue[ animIndex ];
|
|
}
|
|
|
|
*outTransient = true;
|
|
// don't return here, because there might be other
|
|
// animations and sprites that are on top of
|
|
// this one
|
|
isSampleAnim = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( isSampleAnim ) {
|
|
// we have already found an animation here that is on top
|
|
// of whatever map graphics we might sample below
|
|
|
|
// thus, we can return now
|
|
return returnColor;
|
|
}
|
|
|
|
|
|
|
|
int tileIndex;
|
|
|
|
char blocked = isBlocked( inX, inY );
|
|
|
|
if( !blocked ) {
|
|
// empty tile
|
|
tileIndex = 0;
|
|
}
|
|
else {
|
|
|
|
int neighborsBlockedBinary = 0;
|
|
|
|
if( isBlocked( inX, inY - tileH ) ) {
|
|
// top
|
|
neighborsBlockedBinary = neighborsBlockedBinary | 1;
|
|
}
|
|
if( isBlocked( inX + tileW, inY ) ) {
|
|
// right
|
|
neighborsBlockedBinary = neighborsBlockedBinary | 1 << 1;
|
|
}
|
|
if( isBlocked( inX, inY + tileH ) ) {
|
|
// bottom
|
|
neighborsBlockedBinary = neighborsBlockedBinary | 1 << 2;
|
|
}
|
|
if( isBlocked( inX - tileW, inY ) ) {
|
|
// left
|
|
neighborsBlockedBinary = neighborsBlockedBinary | 1 << 3;
|
|
}
|
|
|
|
// skip empty tile, treat as tile index
|
|
neighborsBlockedBinary += 1;
|
|
|
|
tileIndex = neighborsBlockedBinary;
|
|
}
|
|
|
|
|
|
|
|
// pick a tile set
|
|
int netWorldSpan = tileSetWorldSpan + tileSetWorldOverlap;
|
|
|
|
int tileSet = inX / netWorldSpan + tileSetSkip;
|
|
int overhang = inX % netWorldSpan;
|
|
if( inX < 0 ) {
|
|
// fix to a constant tile set below 0
|
|
overhang = 0;
|
|
tileSet = 0;
|
|
}
|
|
|
|
// is there blending with next tile set?
|
|
int blendTileSet = tileSet + 1;
|
|
double blendWeight = 0;
|
|
|
|
if( overhang > tileSetWorldSpan ) {
|
|
blendWeight = ( overhang - tileSetWorldSpan ) /
|
|
(double) tileSetWorldOverlap;
|
|
}
|
|
// else 100% blend of our first tile set
|
|
|
|
|
|
tileSet = tileSet % numTileSets;
|
|
blendTileSet = blendTileSet % numTileSets;
|
|
|
|
// make sure not negative
|
|
if( tileSet < 0 ) {
|
|
tileSet += numTileSets;
|
|
}
|
|
if( blendTileSet < 0 ) {
|
|
blendTileSet += numTileSets;
|
|
}
|
|
|
|
|
|
// apply mapping
|
|
tileSet = mapTileSet( tileSet );
|
|
blendTileSet = mapTileSet( blendTileSet );
|
|
|
|
|
|
// sample from tile image
|
|
int imageY = inY % tileH;
|
|
int imageX = inX % tileW;
|
|
|
|
|
|
if( imageX < 0 ) {
|
|
imageX += tileW;
|
|
}
|
|
if( imageY < 0 ) {
|
|
imageY += tileH;
|
|
}
|
|
|
|
// offset to top left corner of tile
|
|
int tileImageOffset = tileIndex * ( tileH + 1 ) * tileImageW
|
|
+ tileSet * (tileW + 1);
|
|
int blendTileImageOffset = tileIndex * ( tileH + 1 ) * tileImageW
|
|
+ blendTileSet * (tileW + 1);
|
|
|
|
|
|
int imageIndex = tileImageOffset + imageY * tileImageW + imageX;
|
|
int blendImageIndex = blendTileImageOffset + imageY * tileImageW + imageX;
|
|
|
|
returnColor.r =
|
|
(1-blendWeight) * tileContainer->mRed[ imageIndex ] +
|
|
blendWeight * tileContainer->mRed[ blendImageIndex ];
|
|
|
|
returnColor.g =
|
|
(1-blendWeight) * tileContainer->mGreen[ imageIndex ] +
|
|
blendWeight * tileContainer->mGreen[ blendImageIndex ];
|
|
|
|
returnColor.b =
|
|
(1-blendWeight) * tileContainer->mBlue[ imageIndex ] +
|
|
blendWeight * tileContainer->mBlue[ blendImageIndex ];
|
|
|
|
|
|
// only consider drawing chests in empty spots
|
|
if( !blocked && isChest( inX, inY ) ) {
|
|
// draw chest here
|
|
|
|
char chestType = isChest( inX, inY );
|
|
|
|
|
|
// sample from chest image
|
|
int imageY = inY % chestH;
|
|
int imageX = inX % chestW;
|
|
|
|
|
|
if( imageX < 0 ) {
|
|
imageX += chestW;
|
|
}
|
|
if( imageY < 0 ) {
|
|
imageY += chestH;
|
|
}
|
|
|
|
int spriteIndex = 0;
|
|
|
|
if( chestType == CHEST_OPEN ) {
|
|
spriteIndex = 1;
|
|
}
|
|
|
|
// skip to sub-sprite
|
|
int spriteOffset =
|
|
spriteIndex *
|
|
chestW *
|
|
( chestH + 1 );
|
|
|
|
int subSpriteIndex = imageY * chestW + imageX;
|
|
|
|
int imageIndex = spriteOffset + subSpriteIndex;
|
|
|
|
if( !isSpriteTransparent( chestContainer, imageIndex ) ) {
|
|
|
|
if( chestType == CHEST_CLOSED ) {
|
|
*outTransient = true;
|
|
}
|
|
|
|
// check if this is one of the gem locations
|
|
char isGem = false;
|
|
int gemNumber = 0;
|
|
|
|
for( int i=0; i<numGems && !isGem; i++ ) {
|
|
if( subSpriteIndex == gemLocations[ i ] ) {
|
|
isGem = true;
|
|
gemNumber = i;
|
|
}
|
|
}
|
|
|
|
char gemColorUsed = false;
|
|
|
|
if( isGem ) {
|
|
// check if our gem is turned on for this chest
|
|
unsigned char code = getChestCode( inX, inY );
|
|
|
|
if( code & 0x01 << gemNumber ) {
|
|
|
|
gemColorUsed = true;
|
|
|
|
returnColor.r = gemColors[ gemNumber ][ 0 ];
|
|
returnColor.g = gemColors[ gemNumber ][ 1 ];
|
|
returnColor.b = gemColors[ gemNumber ][ 2 ];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
if( !gemColorUsed ) {
|
|
// use underlying chest color
|
|
returnColor.r = chestContainer->mRed[ imageIndex ];
|
|
returnColor.g = chestContainer->mGreen[ imageIndex ];
|
|
returnColor.b = chestContainer->mBlue[ imageIndex ];
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
return returnColor;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int getTileWidth() {
|
|
return tileW;
|
|
}
|
|
|
|
|
|
|
|
int getTileHeight() {
|
|
return tileH;
|
|
}
|
|
|
|
|
|
|
|
void destroyWorld() {
|
|
/*
|
|
printf( "%d hits, %d misses, %f hit ratio\n",
|
|
hitCount, missCount, hitCount / (double)( hitCount + missCount ) );
|
|
*/
|
|
}
|
|
|
|
|
|
|
|
void stepAnimations() {
|
|
|
|
|
|
for( int i=0; i<animationList.size(); i++ ) {
|
|
Animation *a = animationList.getElement( i );
|
|
if( a->mAutoStep ) {
|
|
if( a->mFrameNumber < a->mNumFrames - 1 ) {
|
|
a->mFrameNumber ++;
|
|
}
|
|
else if( a->mRemoveAtEnd ) {
|
|
// remove it
|
|
animationList.deleteElement( i );
|
|
// back up in list for next loop iteration
|
|
i--;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void startPrizeAnimation( int inX, int inY ) {
|
|
|
|
Animation a( inX, inY, 16, 16, true, true, prizeAnimationContainer );
|
|
|
|
animationList.push_back( a );
|
|
}
|
|
|
|
|
|
|
|
void startDustAnimation( int inX, int inY ) {
|
|
|
|
Animation a( inX, inY, 16, 16, true, true, dustAnimationContainer );
|
|
|
|
animationList.push_back( a );
|
|
}
|
|
|
|
|
|
|
|
void startHeartAnimation( int inX, int inY ) {
|
|
|
|
Animation a( inX, inY, 16, 16, true, true, heartAnimationContainer );
|
|
|
|
animationList.push_back( a );
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#include <math.h>
|
|
|
|
|
|
void setPlayerPosition( int inX, int inY ) {
|
|
|
|
Animation *spriteAnimation =
|
|
animationList.getElement( spriteAnimationIndex );
|
|
|
|
char moving = false;
|
|
|
|
if( inX != spriteAnimation->mX ) {
|
|
moving = true;
|
|
}
|
|
|
|
|
|
spriteAnimation->mX = inX;
|
|
// player position centered at sprite's feet
|
|
int newSpriteY = inY - spriteAnimation->mFrameH / 2 + 1;
|
|
|
|
|
|
if( newSpriteY != spriteAnimation->mY ) {
|
|
moving = true;
|
|
}
|
|
|
|
spriteAnimation->mY = newSpriteY;
|
|
|
|
if( metSpouse && ! spouseDead ) {
|
|
Animation *spouseAnimation =
|
|
animationList.getElement( spouseAnimationIndex );
|
|
|
|
// spouse stands immediately in front of player
|
|
int desiredSpouseX = inX + spouseAnimation->mFrameH;
|
|
int desiredSpouseY = spriteAnimation->mY;
|
|
|
|
// gravitates there gradually one pixel at a time in each x and y
|
|
int dX = desiredSpouseX - spouseAnimation->mX;
|
|
int dY = desiredSpouseY - spouseAnimation->mY;
|
|
|
|
// convert to -1, 0, or +1
|
|
if( dX != 0 ) {
|
|
dX = (int)( dX / fabs( dX ) );
|
|
}
|
|
if( dY != 0 ) {
|
|
dY = (int)( dY / fabs( dY ) );
|
|
}
|
|
|
|
if( moving ) {
|
|
// only execute this transition when player is moving
|
|
spouseAnimation->mX += dX;
|
|
spouseAnimation->mY += dY;
|
|
}
|
|
|
|
|
|
// check for heart animation and have it track moving couple
|
|
for( int i=0; i<animationList.size(); i++ ) {
|
|
Animation *a = animationList.getElement( i );
|
|
|
|
if( a->mGraphics == heartAnimationContainer ) {
|
|
|
|
// move it halfway between player and spouse
|
|
a->mX = ( spouseAnimation->mX - spriteAnimation->mX ) / 2 +
|
|
spriteAnimation->mX;
|
|
a->mY = ( spouseAnimation->mY - spriteAnimation->mY ) / 2 +
|
|
spriteAnimation->mY + 1;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void setPlayerSpriteFrame( int inFrame ) {
|
|
Animation *spriteAnimation =
|
|
animationList.getElement( spriteAnimationIndex );
|
|
|
|
spriteAnimation->mFrameNumber = inFrame;
|
|
|
|
if( metSpouse && ! spouseDead ) {
|
|
Animation *spouseAnimation =
|
|
animationList.getElement( spouseAnimationIndex );
|
|
|
|
// spouse follows player
|
|
spouseAnimation->mFrameNumber = inFrame;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void setCharacterAges( double inAge ) {
|
|
Animation *spriteAnimation =
|
|
animationList.getElement( spriteAnimationIndex );
|
|
Animation *spouseAnimation =
|
|
animationList.getElement( spouseAnimationIndex );
|
|
|
|
// 0 -> 0.25, constant page 0
|
|
if( inAge <= 0.25 ) {
|
|
spriteAnimation->mPageNumber = 0;
|
|
spouseAnimation->mPageNumber = 0;
|
|
}
|
|
// 0.75 - 1.0, constant last page
|
|
else if( inAge >= 0.75 ) {
|
|
spriteAnimation->mPageNumber = spriteAnimation->mNumPages - 1;
|
|
spouseAnimation->mPageNumber = spouseAnimation->mNumPages - 1;
|
|
}
|
|
else {
|
|
// blend of pages in between
|
|
double blendingAge = ( inAge - 0.25 ) / 0.5;
|
|
|
|
spriteAnimation->mPageNumber =
|
|
blendingAge * ( spriteAnimation->mNumPages - 1 );
|
|
|
|
spouseAnimation->mPageNumber =
|
|
blendingAge * ( spouseAnimation->mNumPages - 1 );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
void getSpousePosition( int *outX, int *outY ) {
|
|
Animation *spouseAnimation =
|
|
animationList.getElement( spouseAnimationIndex );
|
|
|
|
*outX = spouseAnimation->mX;
|
|
*outY = spouseAnimation->mY + spouseAnimation->mFrameH / 2 - 1;
|
|
}
|
|
|
|
|
|
|
|
char haveMetSpouse() {
|
|
return metSpouse && ! spouseDead;
|
|
}
|
|
|
|
|
|
|
|
void meetSpouse() {
|
|
metSpouse = true;
|
|
}
|
|
|
|
|
|
|
|
void diePlayer() {
|
|
Animation *spriteAnimation =
|
|
animationList.getElement( spriteAnimationIndex );
|
|
|
|
playerDead = true;
|
|
|
|
// tombstone
|
|
spriteAnimation->mFrameNumber = 8;
|
|
}
|
|
|
|
|
|
|
|
void dieSpouse() {
|
|
Animation *spriteAnimation =
|
|
animationList.getElement( spriteAnimationIndex );
|
|
Animation *spouseAnimation =
|
|
animationList.getElement( spouseAnimationIndex );
|
|
|
|
spouseDead = true;
|
|
|
|
// tombstone
|
|
spouseAnimation->mFrameNumber = 8;
|
|
|
|
if( metSpouse ) {
|
|
// swap player sprite with sad sprite
|
|
spriteAnimation->swapGraphics( spriteSadContainer );
|
|
}
|
|
}
|
|
|
|
|
|
|
|
char isPlayerDead() {
|
|
return playerDead;
|
|
}
|
|
|
|
|
|
|
|
char isSpouseDead() {
|
|
return spouseDead;
|
|
}
|
|
|
|
|
|
|
|
void loadWorldGraphics() {
|
|
tileContainer = new GraphicContainer( "tileSet.tga" );
|
|
chestContainer = new GraphicContainer( "chest.tga" );
|
|
|
|
spriteContainer = new GraphicContainer( "characterSprite.tga" );
|
|
spriteSadContainer = new GraphicContainer( "characterSpriteSad.tga" );
|
|
spouseContainer = new GraphicContainer( "spouseSprite.tga" );
|
|
|
|
prizeAnimationContainer = new GraphicContainer( "chestPrize.tga" );
|
|
dustAnimationContainer = new GraphicContainer( "chestDust.tga" );
|
|
heartAnimationContainer = new GraphicContainer( "heart.tga" );
|
|
}
|
|
|
|
|
|
|
|
void destroyWorldGraphics() {
|
|
delete tileContainer;
|
|
delete chestContainer;
|
|
|
|
delete spriteContainer;
|
|
delete spriteSadContainer;
|
|
delete spouseContainer;
|
|
|
|
delete prizeAnimationContainer;
|
|
delete dustAnimationContainer;
|
|
delete heartAnimationContainer;
|
|
}
|
|
|