#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 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; jgetUniqueID() ) ) { Tile t( tID ); Image *tImage = t.getImage(); for( int py=0; pygetColor( 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 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= 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 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 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 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; ibytes, 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= 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; ix, p->y ); delete [] idString; } printf( "\n" ); }