SleepIsDeath/gameSource/GameState.cpp

957 lines
23 KiB
C++

#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 <stdio.h>
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; i<mObjects.size(); i++ ) {
delete *( mObjects.getElement( i ) );
}
}
GameState *GameState::copy() {
GameState *s = new GameState();
s->mRoom = mRoom;
s->mRoomTrans = mRoomTrans;
s->mObjectZeroFrozen = mObjectZeroFrozen;
s->mLocksOn = mLocksOn;
// default s has one object in it
// clear it
int i;
for( i=0; i<s->mObjects.size(); i++ ) {
delete *( s->mObjects.getElement( i ) );
}
s->mObjects.deleteAll();
for( i=0; i<mObjects.size(); i++ ) {
StateObjectInstance *otherObject = *( mObjects.getElement( i ) );
s->mObjects.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; y<N; y++ ) {
for( int x=0; x<N; x++ ) {
sNoteToggleBuffer[y][x] = inMessage[i];
i++;
}
}
numUsed = N * N;
inLength -= numUsed;
inMessage = &( inMessage[ numUsed ] );
// now music unique id:
// do this *just* to fetch a copy of any saved remote music into the
// local database. Don't actually load it into our game state (because
// live music grid loaded above)
mMusicID = readUniqueID( inMessage, inLength, &numUsed );
if( numUsed == -1 ) {
AppLog::error( "Failed to parse GameState from message\n" );
return;
}
inLength -= numUsed;
inMessage = &( inMessage[ numUsed ] );
Music m( mMusicID );
// now scene unique id:
// do this *just* to fetch a copy of any saved remote scene into the
// local database. Don't actually load it into our game state (because
// we're loading the live state here, which may differ)
mSceneID = readUniqueID( inMessage, inLength, &numUsed );
if( numUsed == -1 ) {
AppLog::error( "Failed to parse GameState from message\n" );
return;
}
inLength -= numUsed;
inMessage = &( inMessage[ numUsed ] );
Scene s( mSceneID );
uniqueID id = readUniqueID( inMessage, inLength, &numUsed );
if( numUsed == -1 ) {
AppLog::error( "Failed to parse GameState from message\n" );
return;
}
inLength -= numUsed;
inMessage = &( inMessage[ numUsed ] );
Room r( id );
mRoom = r;
if( inLength <= 0 ) {
AppLog::error( "Failed to parse GameState from message\n" );
return;
}
mRoomTrans = (char)inMessage[0];
inLength -= 1;
inMessage = &( inMessage[ 1 ] );
if( inLength <= 0 ) {
AppLog::error( "Failed to parse GameState from message\n" );
return;
}
mObjectZeroFrozen = (char)inMessage[0];
inLength -= 1;
inMessage = &( inMessage[ 1 ] );
if( inLength <= 0 ) {
AppLog::error( "Failed to parse GameState from message\n" );
return;
}
int numObjects = inMessage[0];
inLength -= 1;
inMessage = &( inMessage[ 1 ] );
AppLog::getLog()->logPrintf(
Log::DETAIL_LEVEL,
"Parsing message for state with %d objects\n", numObjects );
for( int i=0; i<numObjects; i++ ) {
id = readUniqueID( inMessage, inLength, &numUsed );
if( numUsed == -1 ) {
AppLog::error( "Failed to parse GameState from message\n" );
return;
}
inLength -= numUsed;
inMessage = &( inMessage[ numUsed ] );
StateObject obj( id );
StateObjectInstance *o = new StateObjectInstance( obj );
mObjects.push_back( o );
if( inLength <= 0 ) {
AppLog::error( "Failed to parse GameState from message\n" );
return;
}
o->mObjectTrans = (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<unsigned char> messageAccum;
// for v13 backwards compat
// send across first bar of first instrument
for( int y=0; y<N; y++ ) {
for( int x=0; x<N; x++ ) {
messageAccum.push_back(
(unsigned char)( noteToggles[0][0][y][x] ) );
}
}
messageAccum.push_back( mMusicID.bytes, U );
messageAccum.push_back( mSceneID.bytes, U );
uniqueID id = mRoom.getUniqueID();
messageAccum.push_back( id.bytes, U );
messageAccum.push_back( mRoomTrans );
messageAccum.push_back( (unsigned char)mObjectZeroFrozen );
int numObjects = mObjects.size();
messageAccum.push_back( (unsigned char)( numObjects ) );
AppLog::getLog()->logPrintf(
Log::DETAIL_LEVEL,
"Generating message for state with %d objects\n",
numObjects );
for( int i=0; i<numObjects; i++ ) {
StateObjectInstance *o = *( mObjects.getElement( i ) );
//printf( " " );
//o->print();
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; i<numObjects; i++ ) {
StateObjectInstance *o = *( mObjects.getElement( i ) );
if( o->mSpeechStale ) {
delete [] o->mSpokenMessage;
o->mSpokenMessage = stringDuplicate( "" );
o->mSpeechStale = false;
}
}
}
void GameState::markNonPlayerSpeechStale() {
int numObjects = mObjects.size();
for( int i=1; i<numObjects; i++ ) {
StateObjectInstance *o = *( mObjects.getElement( i ) );
o->mSpeechStale = 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" );
}