SleepIsDeath/gameSource/StateObject.cpp

815 lines
21 KiB
C++

#include "StateObject.h"
#include "resourceManager.h"
#include "usageDatabase.h"
#include "uniqueID.h"
#include "SpriteResource.h"
#include "imageCache.h"
#include "packSaver.h"
#include "minorGems/util/stringUtils.h"
#include "minorGems/util/log/AppLog.h"
static unsigned char objectVersionNumber = 3;
// static inits
StateObject *StateObject::sBlankObject = NULL;
void StateObject::staticInit() {
sBlankObject = new StateObject();
// save once
sBlankObject->finishEdit();
}
void StateObject::staticFree() {
delete sBlankObject;
}
void StateObject::setupDefault() {
const char *defaultName = "default";
memcpy( mName, defaultName, strlen( defaultName ) + 1 );
makeUniqueID();
}
StateObject::StateObject() {
setupDefault();
}
void StateObject::initFromData( unsigned char *inData, int inLength,
char inFromNetwork ) {
readFromBytes( inData, inLength );
if( inFromNetwork ) {
// sprites might not be present locally, either
// more efficient to load them in a chunk instead of with separate
// messages
int numSprites = mSpriteLayers.size();
if( numSprites > 0 ) {
uniqueID *ids = mSpriteLayers.getElementArray();
int *chunkLengths = new int[ numSprites ];
char *fromNetworkFlags = new char[ numSprites ];
unsigned char **chunks = loadResourceData( "sprite",
numSprites,
ids,
chunkLengths,
fromNetworkFlags );
for( int i=0; i<numSprites; i++ ) {
if( chunks[i] != NULL ) {
if( fromNetworkFlags[i] ) {
// make a sprite
SpriteResource r( ids[i], chunks[i], chunkLengths[i] );
// force into disk cache, don't update ID
r.finishEdit( false );
}
delete [] chunks[i];
}
}
delete [] ids;
delete [] fromNetworkFlags;
delete [] chunkLengths;
delete [] chunks;
}
// save Object to disk using the ID that we fetched it with
finishEdit( false );
}
}
StateObject::StateObject( uniqueID inID, unsigned char *inData, int inLength,
char inFromNetwork )
: mID( inID ) {
initFromData( inData, inLength, inFromNetwork );
}
// room loaded from file
StateObject::StateObject( uniqueID inID )
: mID( inID ) {
int length;
char fromNetwork;
unsigned char *data = loadResourceData( "object", mID, &length,
&fromNetwork );
if( data != NULL ) {
initFromData( data, length, fromNetwork );
delete [] data;
}
else {
setupDefault();
}
}
int StateObject::getNumLayers() {
return mSpriteLayers.size();
}
char StateObject::canAdd( int inNumToAdd ) {
if( mSpriteLayers.size() + inNumToAdd <= 255 ) {
return true;
}
return false;
}
char StateObject::addLayer( uniqueID inSpriteID, int inLayerBelow ) {
if( mSpriteLayers.size() < 255 ) {
int thisIndex = inLayerBelow + 1;
SimpleVector<uniqueID> displacedIDs;
SimpleVector<intPair> displacedOffsets;
SimpleVector<unsigned char> displacedTrans;
SimpleVector<unsigned char> displacedGlow;
int oldSize = mSpriteLayers.size();
for( int i=thisIndex; i<oldSize; i++ ) {
displacedIDs.push_back( *( mSpriteLayers.getElement( i ) ) );
displacedOffsets.push_back( *( mLayerOffsets.getElement( i ) ) );
displacedTrans.push_back( *( mLayerTrans.getElement( i ) ) );
displacedGlow.push_back( *( mLayerGlow.getElement( i ) ) );
}
for( int i=oldSize-1; i>=thisIndex; i--) {
mSpriteLayers.deleteElement( i );
mLayerOffsets.deleteElement( i );
mLayerTrans.deleteElement( i );
mLayerGlow.deleteElement( i );
}
// displaced layers removed
// add new layer to end
mSpriteLayers.push_back( inSpriteID );
intPair p = { 0, 0 };
mLayerOffsets.push_back( p );
mLayerTrans.push_back( 255 );
mLayerGlow.push_back( 0 );
// add displaced after that.
int numDisplaced = displacedIDs.size();
for( int i=0; i<numDisplaced; i++ ) {
mSpriteLayers.push_back( *( displacedIDs.getElement( i ) ) );
mLayerOffsets.push_back( *( displacedOffsets.getElement( i ) ) );
mLayerTrans.push_back( *( displacedTrans.getElement( i ) ) );
mLayerGlow.push_back( *( displacedGlow.getElement( i ) ) );
}
return true;
}
else {
AppLog::error( "Error: 256th layer added to full StateObject\n" );
return false;
}
}
void StateObject::deleteLayer( int inLayer ) {
mSpriteLayers.deleteElement( inLayer );
mLayerOffsets.deleteElement( inLayer );
mLayerTrans.deleteElement( inLayer );
mLayerGlow.deleteElement( inLayer );
}
void StateObject::editLayerSprite( int inLayer, uniqueID inSpriteID ) {
*( mSpriteLayers.getElement( inLayer ) ) = inSpriteID;
}
void StateObject::editLayerOffset( int inLayer, intPair inOffset ) {
*( mLayerOffsets.getElement( inLayer ) ) = inOffset;
}
void StateObject::editLayerTrans( int inLayer, unsigned char inTrans ) {
*( mLayerTrans.getElement( inLayer ) ) = inTrans;
}
void StateObject::editLayerGlow( int inLayer, char inGlow ) {
*( mLayerGlow.getElement( inLayer ) ) = (unsigned char)inGlow;
}
uniqueID StateObject::getLayerSprite( int inLayer ) {
return *( mSpriteLayers.getElement( inLayer ) );
}
intPair StateObject::getLayerOffset( int inLayer ) {
return *( mLayerOffsets.getElement( inLayer ) );
}
unsigned char StateObject::getLayerTrans( int inLayer ) {
return *( mLayerTrans.getElement( inLayer ) );
}
char StateObject::getLayerGlow( int inLayer ) {
return (char)( *( mLayerGlow.getElement( inLayer ) ) );
}
void StateObject::moveLayerUp( int inLayer ) {
uniqueID oldID = getLayerSprite( inLayer );
intPair oldOffset = getLayerOffset( inLayer );
unsigned char oldTrans = getLayerTrans( inLayer );
char oldGlow = getLayerGlow( inLayer );
deleteLayer( inLayer );
addLayer( oldID, inLayer );
editLayerOffset( inLayer + 1, oldOffset );
editLayerTrans( inLayer + 1, oldTrans );
editLayerGlow( inLayer + 1, oldGlow );
}
void StateObject::moveLayerDown( int inLayer ) {
uniqueID oldID = getLayerSprite( inLayer );
intPair oldOffset = getLayerOffset( inLayer );
unsigned char oldTrans = getLayerTrans( inLayer );
char oldGlow = getLayerGlow( inLayer );
deleteLayer( inLayer );
addLayer( oldID, inLayer - 2 );
editLayerOffset( inLayer - 1, oldOffset );
editLayerTrans( inLayer - 1, oldTrans );
editLayerGlow( inLayer - 1, oldGlow );
}
void StateObject::editStateObjectName( const char *inName ) {
int newLength = strlen( inName );
if( newLength > 10 ) {
printf( "Error: StateObject name %s is too long (10 max)\n", inName );
}
else {
memcpy( mName, inName, newLength + 1 );
}
}
char *StateObject::getStateObjectName() {
return stringDuplicate( mName );
}
// finishes the edit, generates a new unique ID, saves result
void StateObject::finishEdit( char inGenerateNewID ) {
uniqueID oldID = mID;
if( inGenerateNewID ) {
makeUniqueID();
}
if( !equal( oldID, mID ) ||
! resourceExists( "object", mID ) ) {
// change
int numBytes;
unsigned char *bytes = makeBytes( &numBytes );
saveResourceData( "object", mID, mName, bytes, numBytes );
delete [] bytes;
// track usages
int numSprites = mSpriteLayers.size();
for( int j=0; j<numSprites; j++ ) {
addUsage( mID, *( mSpriteLayers.getElementFast( j ) ) );
}
}
}
void StateObject::saveToPack() {
for( int i=0; i<mSpriteLayers.size(); i++ ) {
SpriteResource s( *( mSpriteLayers.getElement(i) ) );
s.saveToPack();
}
int numBytes;
unsigned char *bytes = makeBytes( &numBytes );
addToPack( "object", mID, mName, bytes, numBytes );
delete [] bytes;
}
uniqueID StateObject::getUniqueID() {
return mID;
}
Sprite *StateObject::getSprite( char inUseTrans, char inCacheOK ) {
Image *cachedImage = NULL;
if( inCacheOK ) {
cachedImage = getCachedImage( mID, inUseTrans );
}
if( cachedImage == NULL ) {
int imageW = P*G;
// zero image
Image fullImage( imageW, imageW, 4, true );
int imageCenter = (P*G)/2;
// track largest rectangle area that is not transparent
int lowX = imageW;
int hiX = 0;
int lowY = imageW;
int hiY = 0;
int numLayers = mSpriteLayers.size();
for( int i=0; i<numLayers; i++ ) {
SpriteResource r( *( mSpriteLayers.getElement(i) ) );
intPair layerOffset = *( mLayerOffsets.getElement(i) );
float layerTrans = *( mLayerTrans.getElement(i) ) / 255.0f;
char layerGlow = *( mLayerGlow.getElement(i) );
// force trans for sub-sprites, or else weird bg color overlap
// results
Image *spriteImage = r.getImage( true );
int spriteCenter = P/2;
for( int y=0; y<P; y++ ) {
for( int x=0; x<P; x++ ) {
int fullY = y - spriteCenter - layerOffset.y;
fullY += imageCenter;
int fullX = x - spriteCenter + layerOffset.x;
fullX += imageCenter;
if( fullY < imageW && fullY >= 0
&&
fullX < imageW && fullX >= 0 ) {
int index = y * P + x;
Color c = spriteImage->getColor( index );
float colorTrans = c.a * layerTrans;
if( colorTrans > 0 ) {
int fullIndex = fullY * imageW + fullX;
Color oldColor = fullImage.getColor( fullIndex );
Color *blend;
if( oldColor.a > 0 ) {
if( ! layerGlow ) {
blend = Color::linearSum(
&c, &( oldColor ),
colorTrans );
}
else {
// additive
blend = oldColor.copy();
blend->r += c.r * colorTrans;
blend->g += c.g * colorTrans;
blend->b += c.b * colorTrans;
// cap
if( blend->r > 1 ) {
blend->r = 1;
}
if( blend->g > 1 ) {
blend->g = 1;
}
if( blend->b > 1 ) {
blend->b = 1;
}
}
}
else {
blend = c.copy();
blend->a = colorTrans;
}
// overwrite with blend of ink from higher layer
fullImage.setColor( fullIndex, *blend );
delete blend;
if( fullX < lowX ) {
lowX = fullX;
}
if( fullX > hiX ) {
hiX = fullX;
}
if( fullY < lowY ) {
lowY = fullY;
}
if( fullY > hiY ) {
hiY = fullY;
}
}
}
}
}
delete spriteImage;
}
int subW = hiX - lowX + 1;
int subH = hiY - lowY + 1;
Image *subImage = fullImage.getSubImage( lowX, lowY,
subW, subH );
//printf( "subimage = x,y=(%d,%d), w,h=(%d,%d)\n",
// lowX, lowY, subW, subH );
// build up sums here
Image *shrunkImage = new Image( P, P, 4, true );
double *subChannels[4];
double *shrunkChannels[4];
int hitCount[P*P];
memset( hitCount, 0, P*P*sizeof(int) );
for( int c=0; c<4; c++ ) {
subChannels[c] = subImage->getChannel( c );
shrunkChannels[c] = shrunkImage->getChannel( c );
}
double scaleFactor;
if( subW > subH ) {
scaleFactor = P / (double)subW;
}
else {
scaleFactor = P / (double)subH;
}
if( scaleFactor > 1 ) {
// P big enough to contain whole object
scaleFactor = 1;
}
// center in shrunk image
int xExtra = (int)( P - ( scaleFactor * subW ) );
int yExtra = (int)( P - ( scaleFactor * subH ) );
int xOffset = xExtra / 2;
int yOffset = yExtra / 2;
for( int y=0; y<subH; y++ ) {
int shrunkY = (int)( y * scaleFactor ) + yOffset;
for( int x=0; x<subW; x++ ) {
int shrunkX = (int)( x * scaleFactor ) + xOffset;
int shrunkIndex = shrunkY * P + shrunkX;
int subIndex = y * subW + x;
// skip transparent pixels
if( subChannels[3][subIndex] > 0 ) {
hitCount[shrunkIndex] ++;
for( int c=0; c<4; c++ ) {
shrunkChannels[c][shrunkIndex] +=
subChannels[c][subIndex];
}
// anything hit by non-trans color is opaque
//shrunkChannels[3][shrunkIndex] = 1;
}
}
}
for( int c=0; c<4; c++ ) {
for( int i=0; i<P*P; i++ ) {
// transparent pixels hit 0 times
if( hitCount[i] > 0 ) {
shrunkChannels[c][i] /= hitCount[i];
}
}
}
// sharp transparency already computed above
// (skipped trans pixels in sums)
delete subImage;
cachedImage = shrunkImage;
if( inCacheOK ) {
addCachedImage( mID, inUseTrans, cachedImage );
}
}
Sprite *sprite = new Sprite( cachedImage );
if( !inCacheOK ) {
// image not in cache... we must destroy it
delete cachedImage;
}
return sprite;
}
const char *StateObject::getResourceType() {
return "object";
}
StateObject StateObject::getDefaultResource() {
return *sBlankObject;
}
#include "minorGems/util/SimpleVector.h"
unsigned char *StateObject::makeBytes( int *outLength ) {
SimpleVector<unsigned char> buffer;
buffer.push_back( (unsigned char *)SID_MAGIC_CODE,
strlen( SID_MAGIC_CODE ) );
buffer.push_back( objectVersionNumber );
unsigned char numLayers = (unsigned char)mSpriteLayers.size();
// one byte for num layers (max 255 layers)
buffer.push_back( numLayers );
for( int i=0; i<numLayers; i++ ) {
buffer.push_back( mSpriteLayers.getElement(i)->bytes, U );
intPair *p = mLayerOffsets.getElement( i );
// one byte each coordinate
buffer.push_back( (unsigned char)p->x );
buffer.push_back( (unsigned char)p->y );
buffer.push_back( *( mLayerTrans.getElement( i ) ) );
buffer.push_back( *( mLayerGlow.getElement( i ) ) );
}
buffer.push_back( (unsigned char *)mName, strlen( mName ) + 1 );
*outLength = buffer.size();
return buffer.getElementArray();
}
void StateObject::readFromBytes( unsigned char *inBytes, int inLength ) {
unsigned char version = 1;
// check for magic code before version number
const char *code = SID_MAGIC_CODE;
int codeLength = strlen( code );
if( inLength >= codeLength ) {
char codeFound = true;
for( int i=0; i<codeLength && codeFound; i++ ) {
if( inBytes[i] != code[i] ) {
codeFound = false;
}
}
if( codeFound ) {
// next byte is file version number
version = inBytes[codeLength];
// skip them
inBytes = &( inBytes[ codeLength + 1 ] );
inLength -= ( codeLength + 1 );
}
// else no version number, data starts right at beginning
}
if( inLength < 0 ) {
return;
}
int numLayers = inBytes[0];
inBytes = &( inBytes[ 1 ] );
inLength --;
int layerDataLength = U+2+1;
if( version < 2 ) {
layerDataLength = U+2;
}
if( inLength < layerDataLength * numLayers ) {
AppLog::error(
"Error: Data stream too short to contain state object.\n" );
return;
}
for( int i=0; i<numLayers; i++ ) {
int numUsed;
uniqueID id = readUniqueID( inBytes, inLength, &numUsed );
if( numUsed < 0 ) {
AppLog::error( "Error: reading StateObject's sprite uniqueID failed.\n" );
return;
}
mSpriteLayers.push_back( id );
inBytes = &( inBytes[ numUsed ] );
inLength -= numUsed;
// convert to signed to unpack potentially-negative values
intPair p = { (char)( inBytes[0] ), (char)( inBytes[1] ) };
mLayerOffsets.push_back( p );
inBytes = &( inBytes[ 2 ] );
inLength -= 2;
if( version >= 2 ) {
mLayerTrans.push_back( inBytes[0] );
inBytes = &( inBytes[ 1 ] );
inLength -= 1;
if( version >= 3 ) {
mLayerGlow.push_back( inBytes[0] );
inBytes = &( inBytes[ 1 ] );
inLength -= 1;
}
else {
// fill in with dummy data
mLayerGlow.push_back( 0 );
}
}
else {
// fill in with dummy data
mLayerTrans.push_back( 255 );
}
}
// remainder is name
if( inLength <= 11 ) {
memcpy( mName, inBytes, inLength );
}
}
void StateObject::makeUniqueID() {
int numBytes;
unsigned char *bytes = makeBytes( &numBytes );
mID = ::makeUniqueID( bytes, numBytes );
delete [] bytes;
}
void StateObject::print() {
char *idString = getHumanReadableString( mID );
printf( "StateObject %s:\n", idString );
delete [] idString;
for( int i=0; i<mSpriteLayers.size(); i++ ) {
idString = getHumanReadableString( *( mSpriteLayers.getElement(i) ) );
intPair *p = mLayerOffsets.getElement( i );
printf( " Sprite: %s, (%d,%d) [%d] [Glow=%d]\n",
idString, p->x, p->y, *(mLayerTrans.getElement( i )),
*(mLayerGlow.getElement( i )) );
delete [] idString;
}
printf( "\n" );
}