SleepIsDeath/gameSource/GameStateDisplay.cpp

1538 lines
43 KiB
C++

#include "GameStateDisplay.h"
#include "common.h"
#include "SpriteResource.h"
#include "GameStateEditor.h"
#include "minorGems/graphics/openGL/gui/TextGL.h"
#include "minorGems/util/stringUtils.h"
extern TextGL *largeText;
GameStateDisplay::GameStateDisplay( int inAnchorX, int inAnchorY,
char inShowObjectToolTips )
: GUIComponentGL( inAnchorX, inAnchorY,
G * P, G * P ),
mHasAnySpeechBeenAutoFlipped( false ),
mManualBubblePositioningLive( false ),
mLastPixelClickX( 0 ), mLastPixelClickY( 0 ),
mLastGridClickX( 0 ), mLastGridClickY( 0 ),
mLastActionRelease( false ),
mLastActionPress( false ),
mLastActionKeyPress( false ),
mLastMouseoverObjectIndex( -1 ),
mMouseHover( false ),
mLastHoverGridX( 0 ), mLastHoverGridY( 0 ),
mEditingSpeech( false ),
mEditingAction( false ),
mHighlightEditedBubble( false ),
mShowObjectToolTips( inShowObjectToolTips ),
mEditor( NULL ),
mFocused( false ),
mShowGrid( true ),
mHintMode( false ),
mState( NULL ),
mLastSelected( -1 ),
mBlinkCycle( 0 ) {
for( int y=0; y<G; y++ ) {
for( int x=0; x<G; x++ ) {
mSprites[y][x] = NULL;
}
}
mBubbleTopSprite = new Sprite( "bubbleTop.tga", true );
mBubbleMiddleSprite = new Sprite( "bubbleMiddle.tga", true );
mBubbleMiddleExtraSprite = new Sprite( "bubbleMiddleExtra.tga", true );
mBubbleMiddleExtraThinSprite = new Sprite( "bubbleMiddleExtraThin.tga",
true );
mBubbleBottomSprite = new Sprite( "bubbleBottom.tga", true );
mBubbleBottomSpriteFlip = new Sprite( "bubbleBottomFlip.tga", true );
mBubbleBottomNoTailSprite = new Sprite( "bubbleBottomNoTail.tga", true );
mBubbleMiddleTailSprite = new Sprite( "bubbleMiddleTail.tga", true );
mBubbleMiddleTailFlipSprite =
new Sprite( "bubbleMiddleTailFlip.tga", true );
mSpeechBoxTopSprite = new Sprite( "speechBoxTop.tga", true );
mSpeechBoxBottomSprite = new Sprite( "speechBoxBottom.tga", true );
mActionBoxStartSprite = new Sprite( "actionBoxStart.tga", true );
mActionBoxMiddleSprite = new Sprite( "actionBoxMiddle.tga", true );
mActionBoxMiddleExtraSprite =
new Sprite( "actionBoxMiddleExtra.tga", true );
mActionBoxEndSprite = new Sprite( "actionBoxEnd.tga", true );
mActionBoxEndFlipSprite = new Sprite( "actionBoxEndFlip.tga", true );
mActionBoxStartTallSprite = new Sprite( "actionBoxStartTall.tga", true );
mActionBoxMiddleTallSprite = new Sprite( "actionBoxMiddleTall.tga", true );
mActionBoxMiddleExtraTallSprite =
new Sprite( "actionBoxMiddleExtraTall.tga", true );
mActionBoxEndTallSprite = new Sprite( "actionBoxEndTall.tga", true );
mActionBoxEndFlipTallSprite = new Sprite( "actionBoxEndFlipTall.tga", true );
}
GameStateDisplay::~GameStateDisplay() {
for( int y=0; y<G; y++ ) {
for( int x=0; x<G; x++ ) {
if( mSprites[y][x] != NULL ) {
delete mSprites[y][x];
}
}
}
int numSprites = mObjectSprites.size();
for( int s=0; s<numSprites; s++ ) {
delete *( mObjectSprites.getElement( s ) );
}
if( mState != NULL ) {
delete mState;
}
delete mBubbleTopSprite;
delete mBubbleMiddleSprite;
delete mBubbleMiddleExtraSprite;
delete mBubbleMiddleExtraThinSprite;
delete mBubbleBottomSprite;
delete mBubbleBottomSpriteFlip;
delete mBubbleBottomNoTailSprite;
delete mBubbleMiddleTailSprite;
delete mBubbleMiddleTailFlipSprite;
delete mSpeechBoxTopSprite;
delete mSpeechBoxBottomSprite;
delete mActionBoxStartSprite;
delete mActionBoxMiddleSprite;
delete mActionBoxMiddleExtraSprite;
delete mActionBoxEndSprite;
delete mActionBoxEndFlipSprite;
delete mActionBoxStartTallSprite;
delete mActionBoxMiddleTallSprite;
delete mActionBoxMiddleExtraTallSprite;
delete mActionBoxEndTallSprite;
delete mActionBoxEndFlipTallSprite;
}
void GameStateDisplay::setEditor( void *inEditor ) {
mEditor = inEditor;
}
void GameStateDisplay::setState( GameState *inState ) {
//printf( "Before updating state, %d sprites\n", mObjectSprites.size() );
/*
char newRoom = false;
if( !equal( inState.mRoom.getID(), mState.mRoom.getID() ) {
newRoom = true;
}
*/
if( mState != NULL ) {
delete mState;
}
mState = inState;
for( int y=0; y<G; y++ ) {
for( int x=0; x<G; x++ ) {
if( mSprites[y][x] != NULL ) {
delete mSprites[y][x];
}
Tile t( mState->mRoom.getTile( x, y ) );
mSprites[y][x] = t.getSprite();
}
}
// clear old
int numSprites = mObjectSprites.size();
int i;
for( i=0; i<numSprites; i++ ) {
delete *( mObjectSprites.getElement( i ) );
}
mObjectSprites.deleteAll();
mObjectSpritePositions.deleteAll();
mObjectSpriteOwners.deleteAll();
mObjectSpriteTrans.deleteAll();
mObjectSpriteGlows.deleteAll();
mObjectSpriteDepth.deleteAll();
mObjectSpriteLocked.deleteAll();
mSpeechOffTop.deleteAll();
// add new
int numObjects = mState->mObjects.size();
// flag for 0
mSpeechOffTop.push_back( false );
// add 0 (player object) last so it's always on top
for( i=1; i<numObjects; i++ ) {
mSpeechOffTop.push_back( false );
addSpritesFromStateInstance( i );
}
// now do 0
addSpritesFromStateInstance( 0 );
//printf( "After updating state, %d sprites\n", mObjectSprites.size() );
recomputeDrawOrderMap();
}
void GameStateDisplay::addSpritesFromStateInstance( int inIndex ) {
int i = inIndex;
StateObjectInstance *o = *( mState->mObjects.getElement( i ) );
int numSprites = o->mObject.getNumLayers();
intPair anchorPos = o->mAnchorPosition;
int depth = G - (anchorPos.y / P) - 1;
for( int s=0; s<numSprites; s++ ) {
SpriteResource resource( o->mObject.getLayerSprite( s ) );
mObjectSprites.push_back( resource.getSprite() );
intPair p = o->mObject.getLayerOffset( s );
p = ::add( p, anchorPos );
mObjectSpritePositions.push_back( p );
// no owner flags for non-anchors
mObjectSpriteOwners.push_back( -1 );
float trans =
( o->mObjectTrans / 255.0f ) *
( o->mObject.getLayerTrans( s ) / 255.0f );
mObjectSpriteTrans.push_back( trans );
mObjectSpriteGlows.push_back( o->mObject.getLayerGlow( s ) );
mObjectSpriteDepth.push_back( depth );
mObjectSpriteLocked.push_back( o->mLocked );
}
// last, add anchor sprite for this object, on top
if( i==0 ) {
// special anchor for player
mObjectSprites.push_back( new Sprite( "playerAnchor.tga", true ) );
}
else {
mObjectSprites.push_back( new Sprite( "anchor.tga", true ) );
}
mObjectSpritePositions.push_back( o->mAnchorPosition );
// only "own" the anchors
mObjectSpriteOwners.push_back( i );
// anchors ignore trans and glow
mObjectSpriteTrans.push_back( 1.0 );
mObjectSpriteGlows.push_back( false );
// anchors are all on top
mObjectSpriteDepth.push_back( G );
mObjectSpriteLocked.push_back( o->mLocked );
}
int GameStateDisplay::updatePositionFromStateInstance( int inIndex,
int inSpriteIndex ) {
int i = inIndex;
StateObjectInstance *o = *( mState->mObjects.getElement( i ) );
int numSprites = o->mObject.getNumLayers();
intPair anchorPos = o->mAnchorPosition;
int depth = G - (anchorPos.y / P) - 1;
for( int s=0; s<numSprites; s++ ) {
intPair p = o->mObject.getLayerOffset( s );
p = ::add( p, anchorPos );
*( mObjectSpritePositions.getElement( inSpriteIndex ) ) = p;
float trans =
( o->mObjectTrans / 255.0f ) *
( o->mObject.getLayerTrans( s ) / 255.0f );
*( mObjectSpriteTrans.getElement( inSpriteIndex ) ) = trans;
*( mObjectSpriteGlows.getElement( inSpriteIndex ) ) =
o->mObject.getLayerGlow( s );
*( mObjectSpriteDepth.getElement( inSpriteIndex ) ) = depth;
*( mObjectSpriteLocked.getElement( inSpriteIndex ) ) = o->mLocked;
inSpriteIndex++;
}
*( mObjectSpritePositions.getElement( inSpriteIndex ) ) =
o->mAnchorPosition;
*( mObjectSpriteTrans.getElement( inSpriteIndex ) ) = 1.0;
*( mObjectSpriteGlows.getElement( inSpriteIndex ) ) = false;
*( mObjectSpriteLocked.getElement( inSpriteIndex ) ) = o->mLocked;
inSpriteIndex++;
return inSpriteIndex;
}
void GameStateDisplay::updateSpritePositions( GameState *inState ) {
if( mState != NULL ) {
delete mState;
}
mState = inState;
int numObjects = mState->mObjects.size();
int spriteIndex = 0;
// 0 last (player object on top)
for( int i=1; i<numObjects; i++ ) {
spriteIndex = updatePositionFromStateInstance( i, spriteIndex );
}
// now 0
spriteIndex = updatePositionFromStateInstance( 0, spriteIndex );
recomputeDrawOrderMap();
}
void GameStateDisplay::recomputeDrawOrderMap() {
mDrawOrderMap.deleteAll();
// G+1 bins
SimpleVector<int> depthBins[ G + 1 ];
int numSprites = mObjectSprites.size();
// put locked ones in first (behind) for each depth
for( int i=0; i<numSprites; i++ ) {
if( *( mObjectSpriteLocked.getElement( i ) ) ) {
int depth = *( mObjectSpriteDepth.getElement( i ) );
depthBins[depth].push_back( i );
}
}
// now unlocked ones in front at each depth
for( int i=0; i<numSprites; i++ ) {
if( ! *( mObjectSpriteLocked.getElement( i ) ) ) {
int depth = *( mObjectSpriteDepth.getElement( i ) );
depthBins[depth].push_back( i );
}
}
// dump bins in, in order
for( int b=0; b<=G; b++ ) {
int binSize = depthBins[b].size();
for( int i=0; i<binSize; i++ ) {
mDrawOrderMap.push_back( *( depthBins[b].getElement( i ) ) );
}
}
}
void GameStateDisplay::showGrid( char inShow ) {
mShowGrid = inShow;
}
void GameStateDisplay::setHintMode( char inHintsOnly ) {
mHintMode = inHintsOnly;
}
void GameStateDisplay::copySpeechCoordinates( GameState *inDestinationState ) {
int numObjects = mState->mObjects.size();
for( int i=0; i<numObjects; i++ ) {
StateObjectInstance *o = *( mState->mObjects.getElement( i ) );
StateObjectInstance *oDest =
*( inDestinationState->mObjects.getElement( i ) );
oDest->mSpeechFlip = o->mSpeechFlip;
oDest->mSpeechOffset = o->mSpeechOffset;
}
}
char GameStateDisplay::getSpeechOffTop( int inObject ) {
return *( mSpeechOffTop.getElement( inObject ) );
}
void GameStateDisplay::drawSpeech( int inIndex ) {
int i = inIndex;
int selected = mState->getSelectedObject();
Color *bubbleColor = NULL;
StateObjectInstance *o = *( mState->mObjects.getElement( i ) );
// skip any blank messages
// UNLESS we are editing text of selected
if( strlen( o->mSpokenMessage ) > 0
||
( i == selected && mEditingSpeech ) ) {
if( i == selected && isFocused() && mHighlightEditedBubble ) {
bubbleColor = new Color( 1.0f, 1.0f, 0.5f, 1.0f );
}
intPair pos = ::add( o->mSpeechOffset, o->mAnchorPosition );
double height = 8;
double width = strlen( o->mSpokenMessage ) * height;
double trueStringHeight = height *
largeText->measureTextHeight( o->mSpokenMessage );
char bubbleHasHighLines = false;
char bubbleHasReallyHighLines = false;
if( trueStringHeight > 8 ) {
// make room for high characters
bubbleHasHighLines = true;
}
if( trueStringHeight > 9 ) {
bubbleHasReallyHighLines = true;
}
int bubbleWidth = 64 - 8;
SimpleVector<char*> lines;
// new code: split into words first, ingore player's spacing.
// 1 space between words, two spaces after a period
SimpleVector<char*> *words = tokenizeString( o->mSpokenMessage );
int wordIndex = 0;
int numWords = words->size();
while( wordIndex < numWords ) {
// a word left
char *nextWord = *( words->getElement( wordIndex ) );
double nextWordWidth =
largeText->measureTextWidth( nextWord ) * height;
char *currentLine = NULL;
double currentWidth = 0;
int numLines = lines.size();
if( numLines > 0 ) {
currentLine = *( lines.getElement( numLines - 1 ) );
currentWidth =
largeText->measureTextWidth( currentLine ) * height;
}
char *spaceNeeded = (char *)" ";
int lastCharIndex = -1;
if( currentLine != NULL ) {
lastCharIndex = strlen( currentLine ) - 1;
}
if( currentLine != NULL &&
( currentLine[ lastCharIndex ] == '.' ||
currentLine[ lastCharIndex ] == '?' ||
currentLine[ lastCharIndex ] == '!' ) ) {
// line so far ends with period (or other sentence ender),
// extra space before next word in line
spaceNeeded = (char *)" ";
}
double spaceWidth =
largeText->measureTextWidth( spaceNeeded ) * height;
if( currentLine != NULL &&
currentWidth + spaceWidth + nextWordWidth
< bubbleWidth ) {
// good! we can add it
char *newLine = autoSprintf( "%s%s%s", currentLine,
spaceNeeded, nextWord );
delete [] currentLine;
*( lines.getElement( numLines - 1 ) ) = newLine;
delete [] nextWord;
}
else {
// not enough room on this line, or no line yet
// start a new one with just this word
if( nextWordWidth < bubbleWidth ) {
lines.push_back( nextWord );
}
else {
// problem! really long word
// split it into parts... make sure second part has at
// least 3 chars
char *partialWord = stringDuplicate( nextWord );
double hypenWidth = largeText->measureTextWidth( "-" );
while( ( hypenWidth +
largeText->measureTextWidth( partialWord ) )
* height
>= bubbleWidth ) {
// truncate
partialWord[ strlen( partialWord ) - 1 ] = '\0';
}
int partialLength = strlen( partialWord );
int nextWordLength = strlen( nextWord );
int extraLength = nextWordLength - partialLength;
while( extraLength < 3 ) {
// trim more off partial
partialWord[ partialLength - 1 ] = '\0';
partialLength -= 1;
extraLength ++;
}
// add hyphen
partialWord[ partialLength ] = '-';
partialWord[ partialLength + 1 ] = '\0';
lines.push_back( partialWord );
char *extraChars = stringDuplicate(
&( nextWord[ partialLength ] ) );
*( words->getElement( wordIndex ) ) = extraChars;
delete [] nextWord;
// part of the word still there, back up and look
// at it again
wordIndex --;
}
}
wordIndex ++;
}
delete words;
// start at bottom line and draw up
double bubbleFade = 0.63;
int flipSign = 1;
if( o->mSpeechFlip ) {
flipSign = -1;
}
// draw bottom w/ tip at anchor
Vector3D bubblePos( mAnchorX + pos.x + flipSign * 32,
mAnchorY + pos.y + 7,
0 );
if( o->mSpeechBox ) {
bubblePos.mY -= 8;
}
int numLines = lines.size();
int bubbleHeight = 7 + 9 * numLines + 7;
if( bubbleHasHighLines ) {
if( bubbleHasReallyHighLines ) {
// use extended mode
bubbleHeight = 7 + 12 * numLines + 7;
}
else {
// thin extended mode
bubbleHeight = 7 + 10 * numLines + 7;
}
}
char offTop = false;
if( bubblePos.mY + bubbleHeight > mHeight + mAnchorY ) {
offTop = true;
}
char oldOffTop = *( mSpeechOffTop.getElement( i ) );
*( mSpeechOffTop.getElement( i ) ) = offTop;
if( offTop != oldOffTop ) {
// a change in off-top status. Tell editor about it
mHasAnySpeechBeenAutoFlipped = true;
}
// auto-flip if off the side of screen
char flipThisBubble = o->mSpeechFlip;
int bubbleHalfWidth = 32;
int bubbleFullWidth = 64;
// off-top boxes are no wider
if( ! o->mSpeechBox &&
offTop && numLines > 0 ) {
bubbleHalfWidth += 10;
}
// apply flipping heuristic positioning
int autoFlipExtraOffset = mState->getAutoOffsetOnFlip( i );
if( mManualBubblePositioningLive ) {
// don't mess with the mouse, drop heuristic and go with
// mouse position
autoFlipExtraOffset = 0;
}
if( o->mSpeechFlip && bubblePos.mX - bubbleHalfWidth < mAnchorX ) {
// unflip
flipThisBubble = false;
bubblePos.mX += bubbleFullWidth;
bubblePos.mX += autoFlipExtraOffset;
// auto-flip this bubble for the future, too, so that mouse-based
// positioning
// works correctly, etc
o->mSpeechFlip = false;
o->mSpeechOffset.x += autoFlipExtraOffset;
// thus, we WON'T have to auto-flip next time we draw this same
// frame
mHasAnySpeechBeenAutoFlipped = true;
}
else if( ! o->mSpeechFlip && bubblePos.mX + bubbleHalfWidth >
mWidth + mAnchorX ) {
// force a flip
flipThisBubble = true;
bubblePos.mX -= bubbleFullWidth;
bubblePos.mX += autoFlipExtraOffset;
// again, permanently re-flip
o->mSpeechFlip = true;
o->mSpeechOffset.x += autoFlipExtraOffset;
mHasAnySpeechBeenAutoFlipped = true;
}
if( o->mSpeechFlip ) {
bubblePos.mX += 1;
}
Sprite *bottomSprite = mBubbleBottomSprite;
Sprite *middleTailSprite = mBubbleMiddleTailSprite;
if( flipThisBubble ) {
bottomSprite = mBubbleBottomSpriteFlip;
middleTailSprite = mBubbleMiddleTailFlipSprite;
}
if( o->mSpeechBox ) {
bottomSprite = mSpeechBoxBottomSprite;
// no middle tails
middleTailSprite = mBubbleMiddleSprite;
}
int lineWithTail = 0;
if( offTop ) {
if( !o->mSpeechBox ) {
if( numLines > 0 ) {
bottomSprite = mBubbleBottomNoTailSprite;
// scoot over to make room for horizontal tail
if( flipThisBubble ) {
bubblePos.mX -= 10;
}
else {
bubblePos.mX += 10;
}
// scoot the whole thing down by some number of lines
int linesToScoot = numLines;
//lineWithTail = numLines - 1;
while( bubblePos.mY + bubbleHeight > mHeight + mAnchorY
&&
linesToScoot > 0 ) {
bubblePos.mY -= 9;
if( linesToScoot == numLines ) {
// anchor pos for bubble is above bottom tail, now
// up aligned with a line instead.
bubblePos.mY -= 10;
}
linesToScoot --;
}
//bubblePos.mY -= 10;
// how many lines left?
lineWithTail = linesToScoot;
}
}
else {
// simply flush boxes with top... no tail to align!
bubblePos.mY -=
(bubblePos.mY + bubbleHeight)
- (mHeight + mAnchorY);
}
}
bottomSprite->draw( 0, 0, &bubblePos, 1,
bubbleFade,
bubbleColor );
bubblePos.mY += 4;
// draw lines of text last, to ensure they're on top of bubble parts
double *lineY = new double[numLines];
for( int i=numLines-1; i>=0; i-- ) {
bubblePos.mY += 8;
if( i < numLines - 1 ) {
// extra space for all but bottom line
bubblePos.mY += 1;
}
if( bubbleHasHighLines ) {
// extended line height mode
if( i < numLines - 1 ) {
// extra space for all but bottom line
bubblePos.mY += 1;
if( bubbleHasReallyHighLines ) {
bubblePos.mY += 2;
}
}
// padding above all but top line
if( i > 0 ) {
if( bubbleHasReallyHighLines ) {
mBubbleMiddleExtraSprite->draw( 0, 0, &bubblePos, 1,
bubbleFade,
bubbleColor );
}
else {
mBubbleMiddleExtraThinSprite->draw(
0, 0, &bubblePos, 1,
bubbleFade,
bubbleColor );
}
}
}
if( offTop && lineWithTail == i ) {
// normal line
middleTailSprite->draw( 0, 0, &bubblePos, 1,
bubbleFade,
bubbleColor );
}
else {
// normal line
mBubbleMiddleSprite->draw( 0, 0, &bubblePos, 1,
bubbleFade,
bubbleColor );
}
lineY[i] = bubblePos.mY - 4;
/*
char *line = *( lines.getElement( i ) );
width = strlen( line ) * height;
glColor4f( 0, 0, 0, 1 );
largeText->drawText( line,
bubblePos.mX - 32 + 4,
bubblePos.mY - 4,
width, height );
delete [] line;
*/
}
bubblePos.mY += 7;
if( numLines > 0 ) {
// extra space before top
bubblePos.mY += 1;
}
if( o->mSpeechBox ) {
// flip, because this one is upside down in file (so lower
// corner can have transparency)
mSpeechBoxTopSprite->draw( 0, 0, &bubblePos, -1, bubbleFade,
bubbleColor );
}
else {
mBubbleTopSprite->draw( 0, 0, &bubblePos, 1, bubbleFade,
bubbleColor );
}
// lines last
for( int i=numLines-1; i>=0; i-- ) {
char *line = *( lines.getElement( i ) );
width = strlen( line ) * height;
glColor4f( 0, 0, 0, 1 );
largeText->drawText( line,
bubblePos.mX - 32 + 4,
lineY[i],
width, height );
delete [] line;
}
delete []lineY;
}
if( bubbleColor != NULL ) {
delete bubbleColor;
}
}
// defined in ScreenGL_SDL.cpp
// we draw object hint mode differently depending on this
extern char screenGLStencilBufferSupported;
void GameStateDisplay::fireRedraw() {
// invisible if disabled
if( !mEnabled ) {
return;
}
if( ! mHintMode ) {
double roomTrans = mState->mRoomTrans / 255.0;
for( int y=0; y<G; y++ ) {
for( int x=0; x<G; x++ ) {
if( mSprites[y][x] != NULL ) {
Vector3D pos( mAnchorX + x * P + P/2,
mAnchorY + mHeight - (y * P + P/2), 0 );
mSprites[y][x]->draw( 0, 0, &pos, 1, roomTrans );
}
}
}
}
if( mHintMode && screenGLStencilBufferSupported ) {
// use stenciling to draw *shadows* of sprites only
// don't update color
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
// skip fully-transparent areas
glEnable( GL_ALPHA_TEST );
glAlphaFunc( GL_GREATER, 0 );
// Draw 1 into the stencil buffer wherever a sprite is
glEnable( GL_STENCIL_TEST );
glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
glStencilFunc( GL_ALWAYS, 1, 0xffffffff );
}
// now draw all sprites
int numSprites = mObjectSprites.size();
int selected = mState->getSelectedObject();
if( mLastSelected != selected || ! mShowGrid ) {
// reset
mBlinkCycle = 0;
mLastSelected = selected;
}
else {
mBlinkCycle ++;
}
double blinkFadeFactor = 0.35 * sin( mBlinkCycle / 3.0 ) + 0.65;
//printf( "Drawing %d sprites, %d selected\n", numSprites, selected );
for( int i=0; i<numSprites; i++ ) {
// map index to sort by depth
int s = *( mDrawOrderMap.getElement( i ) );
Sprite *sprite = *( mObjectSprites.getElement( s ) );
intPair pixelPos = *( mObjectSpritePositions.getElement( s ) );
int owner = *( mObjectSpriteOwners.getElement( s ) );
Vector3D pos( mAnchorX + pixelPos.x + P/2,
mAnchorY + pixelPos.y + P/2, 0 );
char drawIt = true;
Color c( 1, 1, 1, 1 );
double fadeFactor = *( mObjectSpriteTrans.getElement( s ) );
if( owner != -1 ) {
// an anchor, override fade
fadeFactor = 0.5;
if( owner == selected ) {
fadeFactor *= blinkFadeFactor;
if( owner != 0 ) {
// green
c.r = 0;
c.b = 0;
}
else if( owner == 0 ) {
// player's object selected in red
c.g = 0;
c.b = 0;
}
}
if( !mShowGrid || mHintMode ) {
// hide anchors
drawIt = false;
}
else if( mState->mLocksOn ) {
// anchors on... but for this object?
StateObjectInstance *o =
*( mState->mObjects.getElement( owner ) );
if( o->mLocked ) {
drawIt = false;
}
}
}
if( drawIt ) {
char glow = *( mObjectSpriteGlows.getElement( s ) );
if( glow ) {
// brighten only
glBlendFunc( GL_SRC_ALPHA, GL_ONE );
}
if( mHintMode ) {
// override fade
fadeFactor = 0.25;
// black color
c.r = 0;
c.g = 0;
c.b = 0;
}
sprite->draw( 0, 0, &pos, 1, fadeFactor, &c );
if( glow ) {
// back to normal blend
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
}
}
}
if( mHintMode && screenGLStencilBufferSupported ) {
// stencil buffer has 1 wherever sprite is
// Re-enable update of color
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
glDisable( GL_ALPHA_TEST );
// Now, only render where stencil is set to 1.
glStencilFunc( GL_EQUAL, 1, 0xffffffff ); // draw if == 1
glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
// draw a big dark rectangle, cut out by stenciled areas
glColor4f( 0, 0, 0, 0.25f );
glBegin( GL_QUADS ); {
glVertex2d( mAnchorX, mAnchorY );
glVertex2d( mAnchorX, mAnchorY + mHeight );
glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight );
glVertex2d( mAnchorX + mWidth, mAnchorY );
}
glEnd();
// draw diagonal yellow hash lines cut out by stenciled areas
glColor4f( 1, 1, 1, 0.25f );
glBegin( GL_LINES ); {
for( int x=0; x<mWidth; x+=3 ) {
glVertex2d( mAnchorX + x, mAnchorY );
glVertex2d( mAnchorX, mAnchorY + x );
glVertex2d( mAnchorX + x + 2, mAnchorY + mHeight );
glVertex2d( mAnchorX + mWidth + 2, mAnchorY + x );
}
}
glEnd();
// back to unstenciled drawing
glDisable( GL_STENCIL_TEST );
glClear( GL_STENCIL_BUFFER_BIT );
}
Color *oldFontColor = largeText->getFontColor()->copy();
largeText->setFontColor( new Color( 0, 0, 0, 1 ) );
if( ! mHintMode ) {
// draw speech for each object
int numObjects = mState->mObjects.size();
// put 0's speech on top
for( int i=1; i<numObjects; i++ ) {
drawSpeech( i );
}
drawSpeech( 0 );
}
// draw any action text for object 0
StateObjectInstance *o = *( mState->mObjects.getElement( 0 ) );
if( ! mHintMode &&
( strlen( o->mAction ) > 0
||
( 0 == selected && mEditingAction ) ) ) {
intPair pos = ::add( o->mActionOffset, o->mAnchorPosition );
double height = 8;
double width = strlen( o->mAction ) * height;
double trueLength = largeText->measureTextWidth( o->mAction ) * height;
int totalArrowLength = (int)trueLength + 10;
double trueStringHeight = height *
largeText->measureTextHeight( o->mAction );
char actionTall = false;
if( trueStringHeight > 9 ) {
// make room for high characters
actionTall = true;
}
// flip anchor if on left side of player
int flip = 1;
Sprite *endSprite = mActionBoxEndSprite;
if( o->mActionOffset.x < P/2 ) {
flip = -1;
endSprite = mActionBoxEndFlipSprite;
}
if( flip == 1 && pos.x - totalArrowLength < 0 ) {
// off left, force flip
flip = -1;
endSprite = mActionBoxEndFlipSprite;
}
else if( flip == -1 && pos.x + totalArrowLength > P * G - 1 ) {
// off right, force flip
flip = 1;
endSprite = mActionBoxEndSprite;
}
if( actionTall ) {
if( endSprite == mActionBoxEndSprite ) {
endSprite = mActionBoxEndTallSprite;
}
else {
endSprite = mActionBoxEndFlipTallSprite;
}
}
Vector3D boxPos( mAnchorX + pos.x - flip * 4,
mAnchorY + pos.y + 1,
0 );
if( flip == -1 ) {
// nudge
boxPos.mX += 1;
}
double boxFade = 0.63;
Vector3D boxStartPos = boxPos;
endSprite->draw( 0, 0, &boxPos, 1,
boxFade );
int trueLengthLeft = (int)trueLength;
// start can contain... 5 pixels?
//trueLengthLeft -= 7;
// go a slight bit over to leave extra space at end if it's tight
// end cap adds to it as well
while( trueLengthLeft > 14 ) {
boxPos.mX -= flip * 8;
if( actionTall ) {
mActionBoxMiddleTallSprite->draw( 0, 0, &boxPos, 1,
boxFade );
}
else {
mActionBoxMiddleSprite->draw( 0, 0, &boxPos, 1,
boxFade );
}
trueLengthLeft -= 8;
}
// now fill in rest, which is less that 8 pixels, with
// smaller lines
// sprite is 2 columns wide, but one column is transparent
// (drawing 1-column-wide sprites, using centered-based drawing,
// causes round-off errors)
// adjust a bit, since tall versions are lopsided
if( actionTall && flip < 0 ) {
boxPos.mY += 2;
}
char firstExtraLine = true;
while( trueLengthLeft > 6 ) {
boxPos.mX -= flip * 1;
if( firstExtraLine ) {
// extra offset from center of last full middle segment
boxPos.mX -= flip * 4;
firstExtraLine = false;
}
if( actionTall ) {
mActionBoxMiddleExtraTallSprite->draw( 0, 0, &boxPos, flip,
boxFade );
}
else {
mActionBoxMiddleExtraSprite->draw( 0, 0, &boxPos, flip,
boxFade );
}
trueLengthLeft -= 1;
}
// cap with end
if( !firstExtraLine ) {
// used at least one extra line
boxPos.mX -= flip * 4;
}
else {
// used no extra lines, offst from last full middle segment
boxPos.mX -= flip * 8;
}
if( actionTall ) {
mActionBoxStartTallSprite->draw( 0, 0, &boxPos, flip,
boxFade );
}
else {
mActionBoxStartSprite->draw( 0, 0, &boxPos, flip,
boxFade );
}
if( flip == 1 ) {
boxStartPos = boxPos;
}
else {
// use real start pos, but adjust
boxStartPos.mX += 7;
}
// make lower to increase chances of it fitting in box
char *lowerAction = stringToLowerCase( o->mAction );
largeText->drawText( lowerAction,
boxStartPos.mX - 4 + 1,
boxStartPos.mY - 4,
width, height );
delete [] lowerAction;
}
// restore
largeText->setFontColor( oldFontColor );
if( mShowGrid && ! mHintMode ) {
//grid lines
glColor4f( 1, 1, 1, 0.25f );
glBegin( GL_LINES ); {
for( int x=0; x<G+1; x++ ) {
glVertex2d( mAnchorX + x * P, mAnchorY );
glVertex2d( mAnchorX + x * P, mAnchorY + mHeight );
}
for( int y=0; y<G+1; y++ ) {
glVertex2d( mAnchorX, mAnchorY + y * P );
glVertex2d( mAnchorX + mWidth, mAnchorY + y * P );
}
}
glEnd();
}
/*
// thin white border, partly transparent
glColor4f( 1, 1, 1, 0.25f );
glBegin( GL_LINE_LOOP ); {
glVertex2d( mAnchorX, mAnchorY );
glVertex2d( mAnchorX + mWidth, mAnchorY );
glVertex2d( mAnchorX + mWidth, mAnchorY + mHeight );
glVertex2d( mAnchorX, mAnchorY + mHeight );
}
glEnd();
*/
if( mEditor != NULL ) {
GameStateEditor *editor = (GameStateEditor *)mEditor;
editor->displayRedrawed();
}
}
void GameStateDisplay::setLastMouse( double inX, double inY ) {
mLastPixelClickX = (int)( inX - mAnchorX );
mLastPixelClickY = (int)( inY - mAnchorY );
mLastGridClickX = (int)( mLastPixelClickX / P );
mLastGridClickY = (int)( mLastPixelClickY / P );
mLastHoverGridX = mLastGridClickX;
mLastHoverGridY = mLastGridClickY;
}
void GameStateDisplay::mouseDragged( double inX, double inY ) {
if( mHintMode ) return;
mLastActionRelease = false;
mLastActionPress = false;
mLastActionKeyPress = false;
if( isEnabled() && isInside( inX, inY ) ) {
setLastMouse( inX, inY );
fireActionPerformed( this );
}
}
void GameStateDisplay::mousePressed( double inX, double inY ) {
if( mHintMode ) return;
mLastActionRelease = false;
mLastActionPress = true;
mLastActionKeyPress = false;
if( isEnabled() && isInside( inX, inY ) ) {
setLastMouse( inX, inY );
fireActionPerformed( this );
// selected object may have changed... update tool tip
mLastMouseoverObjectIndex = -1;
if( mShowObjectToolTips ) {
redoToolTip( mLastGridClickX, mLastGridClickY );
}
}
}
void GameStateDisplay::mouseReleased( double inX, double inY ) {
if( mHintMode ) return;
mLastActionRelease = true;
mLastActionPress = false;
mLastActionKeyPress = false;
// done dragging with mouse
mManualBubblePositioningLive = false;
if( isEnabled() && isInside( inX, inY ) ) {
setLastMouse( inX, inY );
fireActionPerformed( this );
}
}
#include "ToolTipManager.h"
#include "minorGems/util/TranslationManager.h"
void GameStateDisplay::redoToolTip( int inGridX, int inGridY ) {
int objIndex = mState->getHitObject( inGridX, inGridY );
if( objIndex != -1 ) {
if( objIndex != mLastMouseoverObjectIndex ) {
StateObjectInstance *objInstance =
*( mState->mObjects.getElement( objIndex ) );
char *objName =
objInstance->mObject.getStateObjectName();
char *tipString;
int selected = mState->getSelectedObject();
char *selectedString = (char *)"";
if( selected == objIndex ) {
selectedString =
(char*)
TranslationManager::translate( "tip_anchor_selected" );
}
char *transKey;
if( objIndex == 0 ) {
transKey = (char*)"tip_player_anchor";
}
else {
transKey = (char*)"tip_object_anchor";
}
tipString = autoSprintf(
"%s (%s) %s",
TranslationManager::translate( transKey ),
objName,
selectedString );
delete [] objName;
ToolTipManager::setTip( tipString );
delete [] tipString;
}
}
else {
ToolTipManager::setTip( NULL );
}
mLastMouseoverObjectIndex = objIndex;
}
void GameStateDisplay::mouseMoved( double inX, double inY ) {
if( mHintMode ) return;
if( isEnabled() && isInside( inX, inY ) ) {
int pixelX = (int)( inX - mAnchorX );
int pixelY = (int)( inY - mAnchorY );
int gridX = (int)( pixelX / P );
int gridY = (int)( pixelY / P );
if( mShowObjectToolTips ) {
redoToolTip( gridX, gridY );
}
mMouseHover = true;
setLastMouse( inX, inY );
}
else {
mMouseHover = false;
}
}
void GameStateDisplay::setFocus( char inFocus ) {
mFocused = inFocus;
}
char GameStateDisplay::isFocused() {
return mFocused;
}
void GameStateDisplay::keyPressed( unsigned char inKey,
double inX, double inY ) {
if( mHintMode ) return;
mLastActionKeyPress = true;
mLastKeyPressed = inKey;
if( isEnabled() ) {
fireActionPerformed( this );
}
}