1 #include "World.h"
2 
3 
4 #include "common.h"
5 #include "map.h"
6 
7 
8 #include "minorGems/graphics/Image.h"
9 #include "minorGems/util/SimpleVector.h"
10 
11 
12 
13 
14 
15 class GraphicContainer {
16 
17     public:
18 
GraphicContainer(const char * inTGAFileName)19         GraphicContainer( const char *inTGAFileName ) {
20 
21             Image *image = readTGA( inTGAFileName );
22 
23             mW = image->getWidth();
24             mH = image->getHeight();
25             int imagePixelCount = mW * mH;
26 
27             mRed =  new double[ imagePixelCount ];
28             mGreen =  new double[ imagePixelCount ];
29             mBlue =  new double[ imagePixelCount ];
30 
31             for( int i=0; i<imagePixelCount; i++ ) {
32                 mRed[i] = 255 * image->getChannel(0)[ i ];
33                 mGreen[i] = 255 * image->getChannel(1)[ i ];
34                 mBlue[i] = 255 * image->getChannel(2)[ i ];
35                 }
36             delete image;
37             }
38 
39 
~GraphicContainer()40         ~GraphicContainer() {
41             delete [] mRed;
42             delete [] mGreen;
43             delete [] mBlue;
44             }
45 
46 
47         double *mRed;
48         double *mGreen;
49         double *mBlue;
50 
51         int mW;
52         int mH;
53 
54 
55     };
56 
57 
58 
59 
60 
61 
62 
63 
64 GraphicContainer *tileContainer;
65 GraphicContainer *chestContainer;
66 
67 GraphicContainer *spriteContainer;
68 GraphicContainer *spriteSadContainer;
69 GraphicContainer *spouseContainer;
70 
71 GraphicContainer *prizeAnimationContainer;
72 GraphicContainer *dustAnimationContainer;
73 GraphicContainer *heartAnimationContainer;
74 
75 
76 // dimensions of one tile.  TileImage contains 13 tiles, stacked vertically,
77 // with blank lines between tiles
78 int tileW = 8;
79 int tileH = 8;
80 
81 int tileImageW;
82 int tileImageH;
83 
84 
85 int numTileSets;
86 
87 // how wide the swath of a world is that uses a given tile set
88 int tileSetWorldSpan = 200;
89 // overlap during tile set transition
90 int tileSetWorldOverlap = 50;
91 
92 // for testing
93 int tileSetSkip = 0;
94 
95 int tileSetOrder[18] = { 0, 2, 10, 1, 8, 3, 5, 6, 4, 7, 13, 9, 15,
96                          14, 16, 12, 11, 17 };
97 
98 
99 
mapTileSet(int inSetNumber)100 int mapTileSet( int inSetNumber ) {
101     // stay in bounds of tileSetOrder
102     inSetNumber = inSetNumber % 18;
103 
104     int mappedSetNumber = tileSetOrder[ inSetNumber ];
105 
106     // stay in bounds of tile set collection
107     return mappedSetNumber % numTileSets;
108     }
109 
110 
111 
112 
113 
114 int chestW = 8;
115 int chestH = 8;
116 
117 int numGems = 6;
118 int gemLocations[6] = { 41, 42, 43, 44, 45, 46 };
119 //int gemLocations[4] = { 10, 11, 12, 13 };
120 double gemColors[6][3] = { { 255, 0, 0 },
121                            { 0, 255, 0 },
122                            { 255, 160, 0 },
123                            { 0, 0, 255 },
124                            { 255, 255, 0 },
125                            { 255, 0, 255 } };
126 
127 
128 
129 
130 class Animation {
131     public:
132 
Animation(int inX,int inY,int inFrameW,int inFrameH,char inAutoStep,char inRemoveAtEnd,GraphicContainer * inGraphics)133         Animation( int inX, int inY, int inFrameW, int inFrameH,
134                    char inAutoStep,
135                    char inRemoveAtEnd, GraphicContainer *inGraphics )
136                 : mX( inX ), mY( inY ),
137                   mFrameW( inFrameW ), mFrameH( inFrameH ),
138                   mPageNumber( 0 ),
139                   mFrameNumber( 0 ),
140                   mAutoStep( inAutoStep ),
141                   mRemoveAtEnd( inRemoveAtEnd ),
142                   mGraphics( inGraphics ) {
143 
144             mImageW = mGraphics->mW;
145 
146             mNumFrames = ( mGraphics->mH - mFrameH ) / mFrameH + 1;
147             mNumPages = ( mGraphics->mW - mFrameW ) / mFrameW + 1;
148             }
149 
150         // default constructor so that we can build a vector
151         // of Animations
Animation()152         Animation() {
153             }
154 
155 
156         // replaces graphics of animation
swapGraphics(GraphicContainer * inNewGraphics)157         void swapGraphics( GraphicContainer *inNewGraphics ) {
158 
159             mGraphics = inNewGraphics;
160 
161             mImageW = mGraphics->mW;
162 
163             mNumFrames = ( mGraphics->mH - mFrameH ) / mFrameH + 1;
164             mNumPages = ( mGraphics->mW - mFrameW ) / mFrameW + 1;
165 
166             if( mPageNumber >= mNumPages ) {
167                 mPageNumber = mNumPages - 1;
168                 }
169             if( mFrameNumber >= mNumFrames ) {
170                 mFrameNumber = mNumFrames - 1;
171                 }
172             }
173 
174 
175 
176         int mX, mY;
177 
178 
179         int mFrameW, mFrameH;
180         int mImageW;
181 
182         // can blend between pages
183         double mPageNumber;
184 
185         int mNumPages;
186 
187         int mFrameNumber;
188         int mNumFrames;
189         char mAutoStep;
190         char mRemoveAtEnd;
191 
192 
193         GraphicContainer *mGraphics;
194 
195     };
196 
197 
198 
199 SimpleVector<Animation> animationList;
200 
201 
202 
203 
204 // pointers into vectors are unsafe
205 //Animation *spriteAnimation;
206 //Animation *spouseAnimation;
207 
208 int spriteAnimationIndex;
209 int spouseAnimationIndex;
210 
211 
212 
213 char playerDead = false;
214 char spouseDead = false;
215 char metSpouse = false;
216 
217 
218 
219 void resetSampleHashTable();
220 
221 
222 
initWorld()223 void initWorld() {
224     resetMap();
225     resetSampleHashTable();
226 
227     playerDead = false;
228     spouseDead = false;
229     metSpouse = false;
230 
231     animationList.deleteAll();
232 
233 
234     tileImageW = tileContainer->mW;
235     tileImageH = tileContainer->mH;
236 
237     numTileSets = (tileImageW + 1) / (tileW + 1);
238 
239     Animation character(  0, 0, 8, 8, false, false, spriteContainer );
240 
241 
242     animationList.push_back( character );
243 
244     // unsafe
245     // get pointer to animation in vector
246     //spriteAnimation = animationList.getElement( animationList.size() - 1 );
247     spriteAnimationIndex = animationList.size() - 1;
248 
249     Animation spouse(  100, 7, 8, 8, false, false, spouseContainer );
250     spouse.mFrameNumber = 7;
251 
252 
253     animationList.push_back( spouse );
254 
255     // unsafe!
256     // get pointer to animation in vector
257     //spouseAnimation = animationList.getElement( animationList.size() - 1 );
258     spouseAnimationIndex = animationList.size() - 1;
259     }
260 
261 
262 
263 
264 
265 
266 
267 
isSpriteTransparent(GraphicContainer * inContainer,int inSpriteIndex)268 char isSpriteTransparent( GraphicContainer *inContainer, int inSpriteIndex ) {
269 
270     // take transparent color from corner
271     return
272         inContainer->mRed[ inSpriteIndex ] ==
273         inContainer->mRed[ 0 ]
274         &&
275         inContainer->mGreen[ inSpriteIndex ] ==
276         inContainer->mGreen[ 0 ]
277         &&
278         inContainer->mBlue[ inSpriteIndex ] ==
279         inContainer->mBlue[ 0 ];
280     }
281 
282 
283 
284 struct rgbColorStruct {
285     double r;
286     double g;
287     double b;
288     };
289 typedef struct rgbColorStruct rgbColor;
290 
291 
292 
293 // outTransient set to true if sample returned is part of a transient
294 // world feature (character sprite, chest, etc.)
295 rgbColor sampleFromWorldNoWeight( int inX, int inY, char *outTransient );
296 
297 // same, but wrapped in a hash table to store non-transient results
298 rgbColor sampleFromWorldNoWeightHash( int inX, int inY );
299 
300 
301 
sampleFromWorld(int inX,int inY,double inWeight)302 Uint32 sampleFromWorld( int inX, int inY, double inWeight ) {
303     rgbColor c = sampleFromWorldNoWeightHash( inX, inY );
304 
305     unsigned char r = (unsigned char)( inWeight * c.r );
306     unsigned char g = (unsigned char)( inWeight * c.g );
307     unsigned char b = (unsigned char)( inWeight * c.b );
308 
309 
310     return r << 16 | g << 8 | b;
311     }
312 
313 
314 
isSpritePresent(int inX,int inY)315 char isSpritePresent( int inX, int inY ) {
316     // look at animations
317     for( int i=0; i<animationList.size(); i++ ) {
318         Animation a = *( animationList.getElement( i ) );
319 
320         int animW = a.mFrameW;
321         int animH = a.mFrameH;
322 
323 
324         // pixel position relative to animation center
325         // player position centered on sprint left-to-right
326         int animX = (int)( inX - a.mX + a.mFrameW / 2 );
327         int animY = (int)( inY - a.mY + a.mFrameH / 2 );
328 
329         if( animX >= 0 && animX < animW
330             &&
331             animY >= 0 && animY < animH ) {
332             return true;
333             }
334 
335         }
336 
337 
338     return false;
339     }
340 
341 
342 
343 #include "HashTable.h"
344 
345 HashTable<rgbColor> worldSampleHashTable( 30000 );
346 
347 
resetSampleHashTable()348 void resetSampleHashTable() {
349     worldSampleHashTable.clear();
350     }
351 
352 
353 
sampleFromWorldNoWeightHash(int inX,int inY)354 rgbColor sampleFromWorldNoWeightHash( int inX, int inY ) {
355 
356     char found;
357 
358     rgbColor sample = worldSampleHashTable.lookup( inX, inY, &found );
359 
360     if( isSpritePresent( inX, inY ) ) {
361         // don't consider cached result if a sprite is present
362         found = false;
363         }
364 
365 
366     if( found ) {
367         return sample;
368         }
369 
370     // else not found
371 
372     // call real function to get result
373     char transient;
374     sample = sampleFromWorldNoWeight( inX, inY, &transient );
375 
376     // insert, but only if not transient
377     if( !transient ) {
378         worldSampleHashTable.insert( inX, inY, sample );
379         }
380 
381     return sample;
382     }
383 
384 
385 
sampleFromWorldNoWeight(int inX,int inY,char * outTransient)386 rgbColor sampleFromWorldNoWeight( int inX, int inY, char *outTransient ) {
387     *outTransient = false;
388 
389     rgbColor returnColor;
390 
391     char isSampleAnim = false;
392 
393     // consider sampling from an animation
394     // draw them in reverse order so that oldest sprites are drawn on top
395     for( int i=animationList.size() - 1; i>=0; i-- ) {
396 
397         Animation a = *( animationList.getElement( i ) );
398 
399         int animW = a.mFrameW;
400         int animH = a.mFrameH;
401 
402 
403         // pixel position relative to animation center
404         // player position centered on sprint left-to-right
405         int animX = (int)( inX - a.mX + a.mFrameW / 2 );
406         int animY = (int)( inY - a.mY + a.mFrameH / 2 );
407 
408         if( animX >= 0 && animX < animW
409             &&
410             animY >= 0 && animY < animH ) {
411 
412             // pixel is in animation frame
413 
414             int animIndex = animY * a.mImageW + animX;
415 
416             // skip to appropriate anim page
417             animIndex += (int)a.mPageNumber * (animW + 1);
418 
419 
420             // skip to appropriate anim frame
421             animIndex +=
422                 a.mFrameNumber *
423                 a.mImageW *
424                 ( animH + 1 );
425 
426             // page to blend with
427             int animBlendIndex = animIndex + animW + 1;
428 
429             double blendWeight = a.mPageNumber - (int)a.mPageNumber;
430 
431 
432             if( !isSpriteTransparent( a.mGraphics, animIndex ) ) {
433 
434                 // in non-transparent part
435 
436                 if( blendWeight != 0 ) {
437                     returnColor.r =
438                         ( 1 - blendWeight ) * a.mGraphics->mRed[ animIndex ]
439                         +
440                         blendWeight * a.mGraphics->mRed[ animBlendIndex ];
441                     returnColor.g =
442                         ( 1 - blendWeight ) * a.mGraphics->mGreen[ animIndex ]
443                         +
444                         blendWeight * a.mGraphics->mGreen[ animBlendIndex ];
445                     returnColor.b =
446                         ( 1 - blendWeight ) * a.mGraphics->mBlue[ animIndex ]
447                         +
448                         blendWeight * a.mGraphics->mBlue[ animBlendIndex ];
449                     }
450                 else {
451                     // no blend
452                     returnColor.r = a.mGraphics->mRed[ animIndex ];
453                     returnColor.g = a.mGraphics->mGreen[ animIndex ];
454                     returnColor.b = a.mGraphics->mBlue[ animIndex ];
455                     }
456 
457                 *outTransient = true;
458                 // don't return here, because there might be other
459                 // animations and sprites that are on top of
460                 // this one
461                 isSampleAnim = true;
462                 }
463             }
464         }
465 
466     if( isSampleAnim ) {
467         // we have already found an animation here that is on top
468         // of whatever map graphics we might sample below
469 
470         // thus, we can return now
471         return returnColor;
472         }
473 
474 
475 
476     int tileIndex;
477 
478     char blocked = isBlocked( inX, inY );
479 
480     if( !blocked ) {
481         // empty tile
482         tileIndex = 0;
483         }
484     else {
485 
486         int neighborsBlockedBinary = 0;
487 
488         if( isBlocked( inX, inY - tileH ) ) {
489             // top
490             neighborsBlockedBinary = neighborsBlockedBinary | 1;
491             }
492         if( isBlocked( inX + tileW, inY ) ) {
493             // right
494             neighborsBlockedBinary = neighborsBlockedBinary | 1 << 1;
495             }
496         if( isBlocked( inX, inY + tileH ) ) {
497             // bottom
498             neighborsBlockedBinary = neighborsBlockedBinary | 1 << 2;
499             }
500         if( isBlocked( inX - tileW, inY ) ) {
501             // left
502             neighborsBlockedBinary = neighborsBlockedBinary | 1 << 3;
503             }
504 
505         // skip empty tile, treat as tile index
506         neighborsBlockedBinary += 1;
507 
508         tileIndex = neighborsBlockedBinary;
509         }
510 
511 
512 
513     // pick a tile set
514     int netWorldSpan = tileSetWorldSpan + tileSetWorldOverlap;
515 
516     int tileSet = inX / netWorldSpan + tileSetSkip;
517     int overhang = inX % netWorldSpan;
518     if( inX < 0 ) {
519         // fix to a constant tile set below 0
520         overhang = 0;
521         tileSet = 0;
522         }
523 
524     // is there blending with next tile set?
525     int blendTileSet = tileSet + 1;
526     double blendWeight = 0;
527 
528     if( overhang > tileSetWorldSpan ) {
529         blendWeight = ( overhang - tileSetWorldSpan ) /
530             (double) tileSetWorldOverlap;
531         }
532     // else 100% blend of our first tile set
533 
534 
535     tileSet = tileSet % numTileSets;
536     blendTileSet = blendTileSet % numTileSets;
537 
538     // make sure not negative
539     if( tileSet < 0 ) {
540         tileSet += numTileSets;
541         }
542     if( blendTileSet < 0 ) {
543         blendTileSet += numTileSets;
544         }
545 
546 
547     // apply mapping
548     tileSet = mapTileSet( tileSet );
549     blendTileSet = mapTileSet( blendTileSet );
550 
551 
552     // sample from tile image
553     int imageY = inY % tileH;
554     int imageX = inX % tileW;
555 
556 
557     if( imageX < 0 ) {
558         imageX += tileW;
559         }
560     if( imageY < 0 ) {
561         imageY += tileH;
562         }
563 
564     // offset to top left corner of tile
565     int tileImageOffset = tileIndex * ( tileH + 1 ) * tileImageW
566         + tileSet * (tileW + 1);
567     int blendTileImageOffset = tileIndex * ( tileH + 1 ) * tileImageW
568         + blendTileSet * (tileW + 1);
569 
570 
571     int imageIndex =  tileImageOffset + imageY * tileImageW + imageX;
572     int blendImageIndex =  blendTileImageOffset + imageY * tileImageW + imageX;
573 
574     returnColor.r =
575         (1-blendWeight) * tileContainer->mRed[ imageIndex ] +
576         blendWeight * tileContainer->mRed[ blendImageIndex ];
577 
578     returnColor.g =
579         (1-blendWeight) * tileContainer->mGreen[ imageIndex ] +
580         blendWeight * tileContainer->mGreen[ blendImageIndex ];
581 
582     returnColor.b =
583         (1-blendWeight) * tileContainer->mBlue[ imageIndex ] +
584         blendWeight * tileContainer->mBlue[ blendImageIndex ];
585 
586 
587     // only consider drawing chests in empty spots
588     if( !blocked && isChest( inX, inY ) ) {
589         // draw chest here
590 
591         char chestType = isChest( inX, inY );
592 
593 
594         // sample from chest image
595         int imageY = inY % chestH;
596         int imageX = inX % chestW;
597 
598 
599         if( imageX < 0 ) {
600             imageX += chestW;
601             }
602         if( imageY < 0 ) {
603             imageY += chestH;
604             }
605 
606         int spriteIndex = 0;
607 
608         if( chestType == CHEST_OPEN ) {
609             spriteIndex = 1;
610             }
611 
612         // skip to sub-sprite
613         int spriteOffset =
614             spriteIndex *
615             chestW *
616             ( chestH + 1 );
617 
618         int subSpriteIndex = imageY * chestW + imageX;
619 
620         int imageIndex = spriteOffset + subSpriteIndex;
621 
622         if( !isSpriteTransparent( chestContainer, imageIndex ) ) {
623 
624             if( chestType == CHEST_CLOSED ) {
625                 *outTransient = true;
626                 }
627 
628             // check if this is one of the gem locations
629             char isGem = false;
630             int gemNumber = 0;
631 
632             for( int i=0; i<numGems && !isGem; i++ ) {
633                 if( subSpriteIndex == gemLocations[ i ] ) {
634                     isGem = true;
635                     gemNumber = i;
636                     }
637                 }
638 
639             char gemColorUsed = false;
640 
641             if( isGem ) {
642                 // check if our gem is turned on for this chest
643                 unsigned char code = getChestCode( inX, inY );
644 
645                 if( code & 0x01 << gemNumber ) {
646 
647                     gemColorUsed = true;
648 
649                     returnColor.r = gemColors[ gemNumber ][ 0 ];
650                     returnColor.g = gemColors[ gemNumber ][ 1 ];
651                     returnColor.b = gemColors[ gemNumber ][ 2 ];
652 
653                     }
654 
655                 }
656 
657 
658             if( !gemColorUsed ) {
659                 // use underlying chest color
660                 returnColor.r = chestContainer->mRed[ imageIndex ];
661                 returnColor.g = chestContainer->mGreen[ imageIndex ];
662                 returnColor.b = chestContainer->mBlue[ imageIndex ];
663                 }
664 
665             }
666 
667         }
668 
669 
670     return returnColor;
671     }
672 
673 
674 
675 
676 
677 
getTileWidth()678 int getTileWidth() {
679     return tileW;
680     }
681 
682 
683 
getTileHeight()684 int getTileHeight() {
685     return tileH;
686     }
687 
688 
689 
destroyWorld()690 void destroyWorld() {
691     /*
692     printf( "%d hits, %d misses, %f hit ratio\n",
693             hitCount, missCount, hitCount / (double)( hitCount + missCount ) );
694     */
695     }
696 
697 
698 
stepAnimations()699 void stepAnimations() {
700 
701 
702     for( int i=0; i<animationList.size(); i++ ) {
703         Animation *a = animationList.getElement( i );
704         if( a->mAutoStep ) {
705             if( a->mFrameNumber < a->mNumFrames - 1 ) {
706                 a->mFrameNumber ++;
707                 }
708             else if( a->mRemoveAtEnd ) {
709                 // remove it
710                 animationList.deleteElement( i );
711                 // back up in list for next loop iteration
712                 i--;
713                 }
714             }
715 
716         }
717     }
718 
719 
720 
startPrizeAnimation(int inX,int inY)721 void startPrizeAnimation( int inX, int inY ) {
722 
723     Animation a( inX, inY, 16, 16, true, true, prizeAnimationContainer );
724 
725     animationList.push_back( a );
726     }
727 
728 
729 
startDustAnimation(int inX,int inY)730 void startDustAnimation( int inX, int inY ) {
731 
732     Animation a( inX, inY, 16, 16, true, true, dustAnimationContainer );
733 
734     animationList.push_back( a );
735     }
736 
737 
738 
startHeartAnimation(int inX,int inY)739 void startHeartAnimation( int inX, int inY ) {
740 
741     Animation a( inX, inY, 16, 16, true, true, heartAnimationContainer );
742 
743     animationList.push_back( a );
744     }
745 
746 
747 
748 
749 
750 #include <math.h>
751 
752 
setPlayerPosition(int inX,int inY)753 void setPlayerPosition( int inX, int inY ) {
754 
755     Animation *spriteAnimation =
756         animationList.getElement( spriteAnimationIndex );
757 
758     char moving = false;
759 
760     if( inX != spriteAnimation->mX ) {
761         moving = true;
762         }
763 
764 
765     spriteAnimation->mX = inX;
766     // player position centered at sprite's feet
767     int newSpriteY = inY - spriteAnimation->mFrameH / 2 + 1;
768 
769 
770     if( newSpriteY != spriteAnimation->mY ) {
771         moving = true;
772         }
773 
774     spriteAnimation->mY = newSpriteY;
775 
776     if( metSpouse && ! spouseDead ) {
777         Animation *spouseAnimation =
778             animationList.getElement( spouseAnimationIndex );
779 
780         // spouse stands immediately in front of player
781         int desiredSpouseX = inX + spouseAnimation->mFrameH;
782         int desiredSpouseY = spriteAnimation->mY;
783 
784         // gravitates there gradually one pixel at a time in each x and y
785         int dX = desiredSpouseX - spouseAnimation->mX;
786         int dY = desiredSpouseY - spouseAnimation->mY;
787 
788         // convert to -1, 0, or +1
789         if( dX != 0 ) {
790             dX = (int)( dX / fabs( dX ) );
791             }
792         if( dY != 0 ) {
793             dY = (int)( dY / fabs( dY ) );
794             }
795 
796         if( moving ) {
797             // only execute this transition when player is moving
798             spouseAnimation->mX += dX;
799             spouseAnimation->mY += dY;
800             }
801 
802 
803         // check for heart animation and have it track moving couple
804         for( int i=0; i<animationList.size(); i++ ) {
805             Animation *a = animationList.getElement( i );
806 
807             if( a->mGraphics == heartAnimationContainer ) {
808 
809                 // move it halfway between player and spouse
810                 a->mX = ( spouseAnimation->mX - spriteAnimation->mX ) / 2 +
811                     spriteAnimation->mX;
812                 a->mY = ( spouseAnimation->mY - spriteAnimation->mY ) / 2 +
813                     spriteAnimation->mY + 1;
814                 }
815             }
816 
817 
818         }
819 
820     }
821 
822 
823 
setPlayerSpriteFrame(int inFrame)824 void setPlayerSpriteFrame( int inFrame ) {
825     Animation *spriteAnimation =
826         animationList.getElement( spriteAnimationIndex );
827 
828     spriteAnimation->mFrameNumber = inFrame;
829 
830     if( metSpouse && ! spouseDead ) {
831         Animation *spouseAnimation =
832             animationList.getElement( spouseAnimationIndex );
833 
834         // spouse follows player
835         spouseAnimation->mFrameNumber = inFrame;
836         }
837     }
838 
839 
840 
setCharacterAges(double inAge)841 void setCharacterAges( double inAge ) {
842     Animation *spriteAnimation =
843         animationList.getElement( spriteAnimationIndex );
844     Animation *spouseAnimation =
845         animationList.getElement( spouseAnimationIndex );
846 
847     // 0 -> 0.25, constant page 0
848     if( inAge <= 0.25 ) {
849         spriteAnimation->mPageNumber = 0;
850         spouseAnimation->mPageNumber = 0;
851         }
852     // 0.75 - 1.0, constant last page
853     else if( inAge >= 0.75 ) {
854         spriteAnimation->mPageNumber = spriteAnimation->mNumPages - 1;
855         spouseAnimation->mPageNumber = spouseAnimation->mNumPages - 1;
856         }
857     else {
858         // blend of pages in between
859         double blendingAge = ( inAge - 0.25 ) / 0.5;
860 
861         spriteAnimation->mPageNumber =
862             blendingAge * ( spriteAnimation->mNumPages - 1 );
863 
864         spouseAnimation->mPageNumber =
865             blendingAge * ( spouseAnimation->mNumPages - 1 );
866         }
867     }
868 
869 
870 
getSpousePosition(int * outX,int * outY)871 void getSpousePosition( int *outX, int *outY ) {
872     Animation *spouseAnimation =
873         animationList.getElement( spouseAnimationIndex );
874 
875     *outX = spouseAnimation->mX;
876     *outY = spouseAnimation->mY + spouseAnimation->mFrameH / 2 - 1;
877     }
878 
879 
880 
haveMetSpouse()881 char haveMetSpouse() {
882     return metSpouse && ! spouseDead;
883     }
884 
885 
886 
meetSpouse()887 void meetSpouse() {
888     metSpouse = true;
889     }
890 
891 
892 
diePlayer()893 void diePlayer() {
894     Animation *spriteAnimation =
895         animationList.getElement( spriteAnimationIndex );
896 
897     playerDead = true;
898 
899     // tombstone
900     spriteAnimation->mFrameNumber = 8;
901     }
902 
903 
904 
dieSpouse()905 void dieSpouse() {
906     Animation *spriteAnimation =
907         animationList.getElement( spriteAnimationIndex );
908     Animation *spouseAnimation =
909         animationList.getElement( spouseAnimationIndex );
910 
911     spouseDead = true;
912 
913     // tombstone
914     spouseAnimation->mFrameNumber = 8;
915 
916     if( metSpouse ) {
917         // swap player sprite with sad sprite
918         spriteAnimation->swapGraphics( spriteSadContainer );
919         }
920     }
921 
922 
923 
isPlayerDead()924 char isPlayerDead() {
925     return playerDead;
926     }
927 
928 
929 
isSpouseDead()930 char isSpouseDead() {
931     return spouseDead;
932     }
933 
934 
935 
loadWorldGraphics()936 void loadWorldGraphics() {
937     tileContainer = new GraphicContainer( "tileSet.tga" );
938     chestContainer = new GraphicContainer( "chest.tga" );
939 
940     spriteContainer = new GraphicContainer( "characterSprite.tga" );
941     spriteSadContainer = new GraphicContainer( "characterSpriteSad.tga" );
942     spouseContainer = new GraphicContainer( "spouseSprite.tga" );
943 
944     prizeAnimationContainer = new GraphicContainer( "chestPrize.tga" );
945     dustAnimationContainer = new GraphicContainer( "chestDust.tga" );
946     heartAnimationContainer = new GraphicContainer( "heart.tga" );
947     }
948 
949 
950 
destroyWorldGraphics()951 void destroyWorldGraphics() {
952     delete tileContainer;
953     delete chestContainer;
954 
955     delete spriteContainer;
956     delete spriteSadContainer;
957     delete spouseContainer;
958 
959     delete prizeAnimationContainer;
960     delete dustAnimationContainer;
961     delete heartAnimationContainer;
962     }
963 
964