SleepIsDeath/gameSource/Scene.cpp

877 lines
23 KiB
C++

#include "Scene.h"
#include "resourceManager.h"
#include "usageDatabase.h"
#include "Room.h"
#include "StateObject.h"
#include "SpriteResource.h"
#include "imageCache.h"
#include "packSaver.h"
#include "minorGems/util/stringUtils.h"
#include "minorGems/util/log/AppLog.h"
static unsigned char sceneVersionNumber = 2;
// static inits
Scene *Scene::sBlankScene = NULL;
void Scene::staticInit() {
sBlankScene = new Scene();
// save once
sBlankScene->finishEdit();
}
void Scene::staticFree() {
delete sBlankScene;
}
void Scene::setupDefault() {
const char *defaultName = "default";
memcpy( mName, defaultName, strlen( defaultName ) + 1 );
mRoom = Room::sBlankRoom->getUniqueID();
mRoomTrans = 255;
mObjectZeroFrozen = false;
// okay that there are ZERO objects in the default,
// because when loaded against a blank game state, that game state
// will contain the default object position for Obj-Zero anyway,
// and we simply won't change it (because we have zero objects!)
// No need to duplicate default object position and other attributes here.
makeUniqueID();
}
Scene::Scene() {
setupDefault();
}
// room loaded from file
Scene::Scene( uniqueID inID )
: mID( inID ) {
int length;
char fromNetwork;
unsigned char *data = loadResourceData( "scene", mID, &length,
&fromNetwork );
if( data != NULL ) {
readFromBytes( data, length );
delete [] data;
if( fromNetwork ) {
// room might not be present locally
// force it to load
Room loadedRoom( mRoom );
// objects might not be present locally, either
// more efficient to load them in a chunk instead of with separate
// messages
int numObjects = mObjects.size();
if( numObjects > 0 ) {
uniqueID *ids = mObjects.getElementArray();
int *chunkLengths = new int[ numObjects ];
char *fromNetworkFlags = new char[ numObjects ];
unsigned char **chunks = loadResourceData( "object",
numObjects,
ids,
chunkLengths,
fromNetworkFlags );
for( int i=0; i<numObjects; i++ ) {
if( chunks[i] != NULL ) {
if( fromNetworkFlags[i] ) {
// make an object
StateObject o( ids[i],
chunks[i], chunkLengths[i], true );
// saves itself to disk, since fromNetwork flag
// passed to constructor
}
delete [] chunks[i];
}
}
delete [] ids;
delete [] fromNetworkFlags;
delete [] chunkLengths;
delete [] chunks;
}
// save Scene to disk using the ID that we fetched it with
finishEdit( false );
}
}
else {
setupDefault();
}
}
void Scene::saveToPack() {
Room room( mRoom );
room.saveToPack();
for( int i=0; i<mObjects.size(); i++ ) {
StateObject o( *( mObjects.getElement(i) ) );
o.saveToPack();
}
int numBytes;
unsigned char *bytes = makeBytes( &numBytes );
addToPack( "scene", mID, mName, bytes, numBytes );
delete [] bytes;
}
void Scene::editSceneName( const char *inName ) {
int newLength = strlen( inName );
if( newLength > 10 ) {
AppLog::getLog()->logPrintf(
Log::ERROR_LEVEL,
"Error: Scene name %s is too long (10 max)\n", inName );
}
else {
memcpy( mName, inName, newLength + 1 );
}
}
char *Scene::getSceneName() {
return stringDuplicate( mName );
}
// finishes the edit, generates a new unique ID, saves result
void Scene::finishEdit( char inGenerateNewID ) {
uniqueID oldID = mID;
if( inGenerateNewID ) {
makeUniqueID();
}
if( !equal( oldID, mID ) ||
! resourceExists( "scene", mID ) ) {
// change
int numBytes;
unsigned char *bytes = makeBytes( &numBytes );
saveResourceData( "scene", mID, mName, bytes, numBytes );
delete [] bytes;
// track usages
addUsage( mID, mRoom );
int numObjects = mObjects.size();
for( int j=0; j<numObjects; j++ ) {
addUsage( mID, *( mObjects.getElementFast( j ) ) );
}
}
}
uniqueID Scene::getUniqueID() {
return mID;
}
#include "minorGems/graphics/converters/PNGImageConverter.h"
#include "minorGems/io/file/File.h"
#include "minorGems/io/file/FileOutputStream.h"
Sprite *Scene::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;
// first, lay room in
Room room( mRoom );
float roomTrans = mRoomTrans / 255.0f;
for( int ty=0; ty<G; ty++ ) {
for( int tx=0; tx<G; tx++ ) {
uniqueID tID = room.getTile( tx, ty );
// skip blanks
if( !equal( tID, Room::sBlankTile->getUniqueID() ) ) {
Tile t( tID );
Image *tImage = t.getImage();
for( int py=0; py<P; py++ ) {
int fullY = py + ty * P;
for( int px=0; px<P; px++ ) {
int fullX = px + tx * P;
Color c = tImage->getColor( py * P + px );
// darken based on trans
c.r *= roomTrans;
c.g *= roomTrans;
c.b *= roomTrans;
fullImage.setColor( fullY * imageW + fullX,
c );
if( fullX < lowX ) {
lowX = fullX;
}
if( fullX > hiX ) {
hiX = fullX;
}
if( fullY < lowY ) {
lowY = fullY;
}
if( fullY > hiY ) {
hiY = fullY;
}
}
}
delete tImage;
}
}
}
/*
// FIXME lay tile sprites in... actually, should be okay to lay in
// big 16x16 blocks of color (taken from room thumbnail sprite)
// because that is what would happen with area mapping reduction anyway
Image *roomImage = room.getImage();
double *roomChannels[3] =
{ roomImage->getChannel( 0 ),
roomImage->getChannel( 1 ),
roomImage->getChannel( 2 ) };
double *fullChannels[4] =
{ fullImage.getChannel( 0 ),
fullImage.getChannel( 1 ),
fullImage.getChannel( 2 ),
fullImage.getChannel( 3 ) };
double roomTrans = mRoomTrans / 255.0f;
for( int y=0; y<imageW; y++ ) {
int gy = y / P;
for( int x=0; x<imageW; x++ ) {
int gx = x / P;
// image is PxP, even though room is only GxG
// padded with black
int gi = gy * P + gx;
int fullI = y * imageW + x;
// ignore black areas
if( roomChannels[0][gi] > 0 ||
roomChannels[1][gi] > 0 ||
roomChannels[2][gi] > 0 ) {
// track areas hit with color
if( x < lowX ) {
lowX = x;
}
if( x > hiX ) {
hiX = x;
}
if( y < lowY ) {
lowY = y;
}
if( y > hiY ) {
hiY = y;
}
for( int c=0; c<3; c++ ) {
// darken based on trans
fullChannels[c][fullI] =
roomTrans * roomChannels[c][gi];
}
fullChannels[3][fullI] = 1.0f;
}
}
}
delete roomImage;
*/
int numObjects = mObjects.size();
for( int j=0; j<numObjects; j++ ) {
StateObject obj( *( mObjects.getElement( j ) ) );
intPair objOffset = *( mObjectOffsets.getElement( j ) );
float objTrans = *( mObjectTrans.getElement( j ) ) / 255.0f;
int numLayers = obj.getNumLayers();
for( int i=0; i<numLayers; i++ ) {
SpriteResource r( obj.getLayerSprite( i ) );
intPair layerOffset = obj.getLayerOffset( i );
float layerTrans = obj.getLayerTrans( i ) / 255.0f;
layerTrans *= objTrans;
// 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 -
objOffset.y;
fullY += imageW - spriteCenter;
//fullY += imageCenter;
int fullX = x - spriteCenter + layerOffset.x +
objOffset.x;
fullX += spriteCenter;
//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 ) {
blend = Color::linearSum(
&c, &( oldColor ),
colorTrans );
}
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;
}
}
/*
PNGImageConverter png;
File pngFile( NULL, "fullTest.png" );
FileOutputStream out( &pngFile );
png.formatImage( &fullImage, &out );
*/
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 scene
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<3; c++ ) {
shrunkChannels[c][shrunkIndex] +=
subChannels[c][subIndex];
}
// anything hit by non-trans color is opaque
shrunkChannels[3][shrunkIndex] = 1;
}
}
}
for( int c=0; c<3; 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 *Scene::getResourceType() {
return "scene";
}
Scene Scene::getDefaultResource() {
return *sBlankScene;
}
#include "minorGems/util/SimpleVector.h"
unsigned char *Scene::makeBytes( int *outLength ) {
SimpleVector<unsigned char> buffer;
buffer.push_back( (unsigned char *)SID_MAGIC_CODE,
strlen( SID_MAGIC_CODE ) );
buffer.push_back( sceneVersionNumber );
buffer.push_back( mRoom.bytes, U );
buffer.push_back( mRoomTrans );
buffer.push_back( (unsigned char)mObjectZeroFrozen );
unsigned char numObjects = (unsigned char)mObjects.size();
// one byte for num objects (max 255 objects)
buffer.push_back( numObjects );
for( int i=0; i<numObjects; i++ ) {
buffer.push_back( mObjects.getElement(i)->bytes, U );
intPair *p = mObjectOffsets.getElement( i );
// full precision
buffer.push_back( getChars( *p ), 8 );
p = mSpeechOffsets.getElement( i );
buffer.push_back( getChars( *p ), 8 );
buffer.push_back( *( mSpeechFlipFlags.getElement( i ) ) );
buffer.push_back( *( mSpeechBoxFlags.getElement( i ) ) );
buffer.push_back( *( mLockedFlags.getElement( i ) ) );
buffer.push_back( *( mObjectTrans.getElement( i ) ) );
}
buffer.push_back( (unsigned char *)mName, strlen( mName ) + 1 );
*outLength = buffer.size();
return buffer.getElementArray();
}
void Scene::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
}
int numUsed;
mRoom = readUniqueID( inBytes, inLength, &numUsed );
if( numUsed < 0 ) {
AppLog::error( "Error: reading Scene's room uniqueID failed.\n" );
return;
}
inBytes = &( inBytes[ numUsed ] );
inLength -= numUsed;
if( inLength < 0 ) {
return;
}
mRoomTrans = inBytes[0];
inBytes = &( inBytes[ 1 ] );
inLength --;
if( inLength < 0 ) {
return;
}
mObjectZeroFrozen = inBytes[0];
inBytes = &( inBytes[ 1 ] );
inLength --;
if( inLength < 0 ) {
return;
}
int numObjects = inBytes[0];
inBytes = &( inBytes[ 1 ] );
inLength --;
int objDataLength = U+8+8+1+1+1+1;
if( version < 2 ) {
objDataLength = U+8+8+1+1;
}
if( inLength < objDataLength * numObjects ) {
AppLog::error( "Error: Data stream too short to contain scene.\n" );
return;
}
for( int i=0; i<numObjects; i++ ) {
uniqueID id = readUniqueID( inBytes, inLength, &numUsed );
if( numUsed < 0 ) {
AppLog::error( "Error: reading Scene's object uniqueID failed.\n" );
return;
}
mObjects.push_back( id );
inBytes = &( inBytes[ numUsed ] );
inLength -= numUsed;
// full precision
intPair p = readIntPair( inBytes, inLength,
&numUsed );
if( numUsed == -1 ) {
AppLog::error( "Failed to parse Scene from message\n" );
return;
}
inLength -= numUsed;
inBytes = &( inBytes[ numUsed ] );
mObjectOffsets.push_back( p );
// full precision
p = readIntPair( inBytes, inLength,
&numUsed );
if( numUsed == -1 ) {
AppLog::error( "Failed to parse Scene from message\n" );
return;
}
inLength -= numUsed;
inBytes = &( inBytes[ numUsed ] );
mSpeechOffsets.push_back( p );
mSpeechFlipFlags.push_back( inBytes[0] );
inBytes = &( inBytes[ 1 ] );
inLength -= 1;
if( version >= 2 ) {
mSpeechBoxFlags.push_back( inBytes[0] );
inBytes = &( inBytes[ 1 ] );
inLength -= 1;
mLockedFlags.push_back( inBytes[0] );
inBytes = &( inBytes[ 1 ] );
inLength -= 1;
}
else {
// fill in with dummy data
mSpeechBoxFlags.push_back( 0 );
mLockedFlags.push_back( 0 );
}
mObjectTrans.push_back( inBytes[0] );
inBytes = &( inBytes[ 1 ] );
inLength -= 1;
}
// remainder is name
if( inLength <= 11 ) {
memcpy( mName, inBytes, inLength );
}
}
void Scene::makeUniqueID() {
int numBytes;
unsigned char *bytes = makeBytes( &numBytes );
mID = ::makeUniqueID( bytes, numBytes );
delete [] bytes;
}
void Scene::print() {
char *idString = getHumanReadableString( mID );
printf( "Scene %s:\n", idString );
delete [] idString;
for( int i=0; i<mObjects.size(); i++ ) {
idString = getHumanReadableString( *( mObjects.getElement(i) ) );
intPair *p = mObjectOffsets.getElement( i );
printf( " Object: %s, (%d,%d)\n",
idString, p->x, p->y );
delete [] idString;
}
printf( "\n" );
}