#include "GameState.h" #include "Music.h" #include "Song.h" #include "Scene.h" #include "SongPicker.h" #include "SongEditor.h" #include "speechHints.h" #include "minorGems/util/stringUtils.h" #include "minorGems/util/log/AppLog.h" #include extern SongPicker *mainSongPicker; extern SongEditor *mainSongEditor; char GameState::sNoteToggleBuffer[N][N]; GameState::GameState() { mRoomTrans = 255; mObjectZeroFrozen = false; mLocksOn = true; // must have object 0 always StateObject obj; newObject( G / 2, G / 2, obj ); mSelectedObject = 0; } GameState::~GameState() { for( int i=0; imRoom = mRoom; s->mRoomTrans = mRoomTrans; s->mObjectZeroFrozen = mObjectZeroFrozen; s->mLocksOn = mLocksOn; // default s has one object in it // clear it int i; for( i=0; imObjects.size(); i++ ) { delete *( s->mObjects.getElement( i ) ); } s->mObjects.deleteAll(); for( i=0; imObjects.push_back( otherObject->copy() ); } s->mSelectedObject = mSelectedObject; return s; } GameState::GameState( unsigned char *inMessage, int inLength ) { int numUsed; if( inLength < N * N ) { AppLog::error( "Failed to parse GameState from message\n" ); return; } int i=0; for( int y=0; ylogPrintf( Log::DETAIL_LEVEL, "Parsing message for state with %d objects\n", numObjects ); for( int i=0; imObjectTrans = (char)inMessage[0]; inLength -= 1; inMessage = &( inMessage[ 1 ] ); o->mAnchorPosition = readIntPair( inMessage, inLength, &numUsed ); if( numUsed == -1 ) { AppLog::error( "Failed to parse GameState from message\n" ); return; } inLength -= numUsed; inMessage = &( inMessage[ numUsed ] ); o->mSpeechOffset = readIntPair( inMessage, inLength, &numUsed ); if( numUsed == -1 ) { AppLog::error( "Failed to parse GameState from message\n" ); return; } inLength -= numUsed; inMessage = &( inMessage[ numUsed ] ); o->mActionOffset = readIntPair( inMessage, inLength, &numUsed ); if( numUsed == -1 ) { AppLog::error( "Failed to parse GameState from message\n" ); return; } inLength -= numUsed; inMessage = &( inMessage[ numUsed ] ); if( inLength < 4 ) { AppLog::error( "Failed to parse GameState from message\n" ); return; } o->mSpeechFlip = (char)( inMessage[0] ); o->mSpeechBox = (char)( inMessage[1] ); o->mLocked = (char)( inMessage[2] ); int speechLength = inMessage[3]; inLength -= 4; inMessage = &( inMessage[4] ); setSpeechHint( id, o->mSpeechOffset, o->mSpeechFlip ); if( inLength < speechLength ) { AppLog::error( "Failed to parse GameState from message\n" ); return; } delete [] o->mSpokenMessage; o->mSpokenMessage = new char[ speechLength + 1 ]; memcpy( o->mSpokenMessage, inMessage, speechLength ); o->mSpokenMessage[speechLength] = '\0'; inLength -= speechLength; inMessage = &( inMessage[ speechLength ] ); if( inLength < 11 ) { AppLog::error( "Failed to parse GameState from message\n" ); return; } memcpy( o->mAction, inMessage, 11 ); inLength -= 11; inMessage = &( inMessage[ 11 ] ); //printf( " " ); //o->print(); } mHasSong = false; // ignore the possibility of this being present if we are Controller // song should never be sent by Player, but is sometimes due to a v14 // bug. if( inLength >= U && mainSongEditor == NULL ) { // song ID present? uniqueID id = readUniqueID( inMessage, inLength, &numUsed ); if( numUsed == -1 ) { AppLog::error( "Failed to parse GameState from message\n" ); return; } inLength -= numUsed; inMessage = &( inMessage[ numUsed ] ); // load this Song, to force it to be fetched from network Song s( id ); // from network, but don't save it, because it was live on Controller // side (repeat same ID, even though it doesn't match---this // live song has no ID, because it's never been saved) Song liveSong( id, inMessage, inLength, true, false ); mLiveSongReceived = liveSong; mHasSong = true; } /* AppLog::getLog()->logPrintf( Log::DETAIL_LEVEL, "After parse, %d bytes left over\n", inLength ); */ mSelectedObject = 0; } char GameState::hasSong() { // for now return mHasSong; } // from musicPlayer.cpp extern char noteToggles[PARTS][S][N][N]; unsigned char *GameState::getStateAsMessage( int *outLength ) { // NOTE: music is a little inelegant, but that's because // it is *separate* from the game state (not undone, not bookmarked, // etc, along with the game state) // format: // NxN note toggles (actual, live music state, maybe not yet saved) // Music ID (saved music selected in music picker) // Room ID // num objects // each object: // StateObject ID // object trans (0..255, 1 char) // anchor pos (as 8-byte int pair) // speech offset (as 8-byte int pair) // speech flip (1 char) // speech length (1 char) // speech chars // -- v14 stuff at end (so v13 can ignore it): -- // NOTE: these are ONLY present in messages sent FROM Controller // Song ID of last saved song // Inline version of live (perhaps not saved) Song object SimpleVector messageAccum; // for v13 backwards compat // send across first bar of first instrument for( int y=0; ylogPrintf( Log::DETAIL_LEVEL, "Generating message for state with %d objects\n", numObjects ); for( int i=0; iprint(); id = o->mObject.getUniqueID(); messageAccum.push_back( id.bytes, U ); messageAccum.push_back( o->mObjectTrans ); messageAccum.push_back( getChars( o->mAnchorPosition ), 8 ); messageAccum.push_back( getChars( o->mSpeechOffset ), 8 ); messageAccum.push_back( getChars( o->mActionOffset ), 8 ); messageAccum.push_back( (unsigned char)( o->mSpeechFlip ) ); messageAccum.push_back( (unsigned char)( o->mSpeechBox ) ); messageAccum.push_back( (unsigned char)( o->mLocked ) ); int speechLength = strlen( o->mSpokenMessage ); messageAccum.push_back( (unsigned char)speechLength ); messageAccum.push_back( (unsigned char *)( o->mSpokenMessage ), speechLength ); messageAccum.push_back( (unsigned char *)( o->mAction ), 11 ); } if( mainSongEditor != NULL ) { // ID of selected, saved song uniqueID songID = mainSongPicker->getSelectedResourceID(); messageAccum.push_back( songID.bytes, U ); int numBytes; unsigned char *liveSongBytes = mainSongEditor->getLiveSong().makeBytes( &numBytes ); messageAccum.push_back( liveSongBytes, numBytes ); delete [] liveSongBytes; } *outLength = messageAccum.size(); return messageAccum.getElementArray(); } int GameState::getHitObject( int inGridX, int inGridY ) { // top objects first int numObjects = mObjects.size(); // but consider 0 first StateObjectInstance *o = *( mObjects.getElement( 0 ) ); if( o->mAnchorPosition.x / P == inGridX && o->mAnchorPosition.y / P == inGridY ) { return 0; } // always consider unlocked before locked for( int i=numObjects-1; i>=0; i-- ) { StateObjectInstance *o = *( mObjects.getElement( i ) ); if( ! o->mLocked ) { if( o->mAnchorPosition.x / P == inGridX && o->mAnchorPosition.y / P == inGridY ) { return i; } } } if( !mLocksOn ) { // now consider locked, since no unlocked were hit for( int i=numObjects-1; i>=0; i-- ) { StateObjectInstance *o = *( mObjects.getElement( i ) ); if( o->mLocked ) { if( o->mAnchorPosition.x / P == inGridX && o->mAnchorPosition.y / P == inGridY ) { return i; } } } } return -1; } int GameState::getSelectedObject() { return mSelectedObject; } void GameState::setSelectedObject( int inSelected ) { mSelectedObject = inSelected; } // to right a bit, centered vertically static intPair defaultAnchorOffset = { P/2 + P, P/2 }; void GameState::moveSelectedObject( int inGridX, int inGridY ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); intPair oldPos = o->mAnchorPosition; intPair newPos = { inGridX * P, inGridY * P }; intPair deltaPos = subtract( newPos, oldPos ); // subtract this delta from action offset so that it does NOT move // along with object (only if action has text in it AND anchor is not // at starting pos [if at starting pos, it moves with object]) if( strlen( o->mAction ) > 0 && ! equals( o->mActionOffset, defaultAnchorOffset ) ) { o->mActionOffset = subtract( o->mActionOffset, deltaPos ); } o->mAnchorPosition = newPos; } void GameState::moveSelectedSpeechAnchor( int inPixelX, int inPixelY ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); intPair absPos = { inPixelX, inPixelY }; o->mSpeechOffset = subtract( absPos, o->mAnchorPosition ); setSpeechHint( o->mObject.getUniqueID(), o->mSpeechOffset, o->mSpeechFlip ); } void GameState::moveSelectedActionAnchor( int inPixelX, int inPixelY ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); intPair absPos = { inPixelX, inPixelY }; o->mActionOffset = subtract( absPos, o->mAnchorPosition ); } void GameState::resetActionAnchor( int inObjectIndex ) { StateObjectInstance *o = *( mObjects.getElement( inObjectIndex ) ); // to right a bit, centered vertically o->mActionOffset = defaultAnchorOffset; } char GameState::canAdd() { return( mObjects.size() < 255 ); } void GameState::newObject( int inGridX, int inGridY, StateObject inObject ) { if( mObjects.size() >= 255 ) { AppLog::error( "Error: adding another object to a full state!\n" ); } else { StateObjectInstance *o = new StateObjectInstance( inObject ); // centered o->mAnchorPosition.x = P * inGridX; o->mAnchorPosition.y = P * inGridY; o->mSpeechOffset = getSpeechHint( inObject.getUniqueID(), &( o->mSpeechFlip ) ); //o->mSpeechOffset.y = P/2; // no sprites mObjects.push_back( o ); mSelectedObject = mObjects.size() - 1; resetActionAnchor( mSelectedObject ); } } void GameState::deleteSelectedObject() { // never delete obj 0 if( mSelectedObject > 0 ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); delete o; mObjects.deleteElement( mSelectedObject ); setSelectedObject( mSelectedObject - 1 ); if( mLocksOn ) { // look for an unlocked object to make selected while( getSelectedLocked() ) { setSelectedObject( mSelectedObject - 1 ); } } } } void GameState::changeSelectedObject( StateObject inObject ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); o->mObject = inObject; } void GameState::adjustSelectedTrans( unsigned char inTrans ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); o->mObjectTrans = inTrans; } unsigned char GameState::getSelectedTrans() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); return o->mObjectTrans; } void GameState::addCharToSelectedSpeech( char inChar ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); if( strlen( o->mSpokenMessage ) < 255 ) { char *newString = autoSprintf( "%s%c", o->mSpokenMessage, inChar ); delete [] o->mSpokenMessage; o->mSpokenMessage = newString; o->mSpeechStale = false; //printf( "Adding %d char\n", strlen( o->mSpokenMessage ) ); } else { AppLog::error( "Error: adding 256th character to speech\n" ); } } void GameState::deleteCharFromSelectedSpeech() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); int oldLength = strlen( o->mSpokenMessage ); if( oldLength > 0 ) { // terminate one char sooner o->mSpokenMessage[ oldLength - 1 ] = '\0'; o->mSpeechStale = false; } } int GameState::getSelectedSpeechLength() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); return strlen( o->mSpokenMessage ); } void GameState::deleteAllCharsFromSelectedSpeech() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); delete [] o->mSpokenMessage; o->mSpokenMessage = stringDuplicate( "" ); o->mSpeechStale = false; } void GameState::flipSelectedSpeech() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); o->mSpeechFlip = ! o->mSpeechFlip; setSpeechHint( o->mObject.getUniqueID(), o->mSpeechOffset, o->mSpeechFlip ); } void GameState::setSelectedSpeechBox( char inBox ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); o->mSpeechBox = inBox; } void GameState::setSelectedLocked( char inLocked ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); o->mLocked = inLocked; } char GameState::getSelectedLocked() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); return o->mLocked; } void GameState::setObjectHeld( int inObject, char inHeld ) { StateObjectInstance *o = *( mObjects.getElement( inObject ) ); o->mHeld = inHeld; } char GameState::getObjectHeld( int inObject ) { StateObjectInstance *o = *( mObjects.getElement( inObject ) ); return o->mHeld; } int GameState::getAutoOffsetOnFlip( int inObject ) { // heuristic: // if offset is less that one gridspace away from object center, // in direction of manual flip, reverse the offset on the other side // when we auto-flip StateObjectInstance *o = *( mObjects.getElement( inObject ) ); int offset = o->mSpeechOffset.x; int autoFlipExtraOffset = 0; if( !o->mSpeechFlip && offset >=P/2 && offset <= P + P/2 ) { autoFlipExtraOffset = -2 * ( offset - P/2) + 1 - 2; } else if( o->mSpeechFlip && offset <=P/2 && offset >= P/2 - P ) { autoFlipExtraOffset = -2 * ( offset - P/2) + 1 - 2; } return autoFlipExtraOffset; } void GameState::deleteAllNonPlayerSpeech() { int numObjects = mObjects.size(); for( int i=1; imSpeechStale ) { delete [] o->mSpokenMessage; o->mSpokenMessage = stringDuplicate( "" ); o->mSpeechStale = false; } } } void GameState::markNonPlayerSpeechStale() { int numObjects = mObjects.size(); for( int i=1; imSpeechStale = true; } } void GameState::addCharToSelectedAction( char inChar ) { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); int oldLen = strlen( o->mAction ); if( oldLen < 10 ) { o->mAction[ oldLen ] = inChar; o->mAction[ oldLen + 1 ] = '\0'; } else { //printf( "Error: adding 11th character to action\n" ); } } void GameState::deleteCharFromSelectedAction() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); int oldLength = strlen( o->mAction ); if( oldLength > 0 ) { // terminate one char sooner o->mAction[ oldLength - 1 ] = '\0'; } } int GameState::getSelectedActionLength() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); return strlen( o->mAction ); } void GameState::deleteAllCharsFromSelectedAction() { StateObjectInstance *o = *( mObjects.getElement( mSelectedObject ) ); o->mAction[0] = '\0'; } void GameState::freezeObjectZero( char inFrozen ) { mObjectZeroFrozen = inFrozen; } char GameState::isObjectZeroFrozen() { return mObjectZeroFrozen; } static intPair zeroPair = {0,0}; StateObjectInstance::StateObjectInstance( StateObject inObject ) : mObject( inObject ), mObjectTrans( 255 ), mAnchorPosition( zeroPair ), mSpeechOffset( zeroPair ), mActionOffset( zeroPair ), mSpeechFlip( 0 ), mSpeechBox( 0 ), mLocked( 0 ), mHeld( 0 ), mSpokenMessage( stringDuplicate( "" ) ), mSpeechStale( false ) { mAction[0] = '\0'; } StateObjectInstance::~StateObjectInstance() { delete [] mSpokenMessage; } StateObjectInstance *StateObjectInstance::copy() { StateObjectInstance *o = new StateObjectInstance( mObject ); delete [] o->mSpokenMessage; o->mObjectTrans = mObjectTrans; o->mAnchorPosition = mAnchorPosition; o->mSpeechOffset = mSpeechOffset; o->mActionOffset = mActionOffset; o->mSpeechFlip = mSpeechFlip; o->mSpeechBox = mSpeechBox; o->mLocked = mLocked; o->mHeld = mHeld; o->mSpokenMessage = stringDuplicate( mSpokenMessage ); o->mSpeechStale = mSpeechStale; // init mAction first so that we don't leave uninitialized values in // end of buffer memset( (void *)( o->mAction ), '\0', 11 ); memcpy( o->mAction, mAction, strlen( mAction ) + 1 ); return o; } void StateObjectInstance::print() { printf( "State object at " ); ::print( mAnchorPosition ); printf( "\n" ); }