#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; igetChannel(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 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= 0 && animX < animW && animY >= 0 && animY < animH ) { return true; } } return false; } #include "HashTable.h" HashTable 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; imRed[ 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; imAutoStep ) { 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 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; imGraphics == 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; }