/* $Id: map.cpp,v 1.60.4.1 2006/01/20 11:23:30 chfreund Exp $ */ #include #include // only needed for debugging in Map::saveFlags #include "map.hpp" #include "string.hpp" #include "random.hpp" #include "world.hpp" #include "wopsettings.hpp" #include "objectpaths.hpp" /**********************************************************/ #define BORDER_WIDTH ((Uint32)2) /**********************************************************/ void Map::initializeFlags() { for( Uint32 y = 0; y < m_sizeY; y++ ) { for( Uint32 x = 0; x < m_sizeX; x++ ) { m_flags.setValueAt( x, y, 0 ); } } } /**********************************************************/ void Map::displayFlags() { Uint32 color; Uint8 flags; for( Uint32 y = 0; y < m_sizeY; y++ ) { for( Uint32 x = 0; x < m_sizeX; x++ ) { color = 0; flags = m_flags.getValueAt( x, y ); if( flags & Flags::INDESTRUCTIBLE ) color |= 0x800000; if( flags & Flags::UNDIGGABLE ) color |= 0x400000; if( flags & Flags::UNSMOOTHABLE ) color |= 0x200000; if( flags & Flags::OBSTACLE_EARTH ) color |= 0x008000; if( flags & Flags::OBSTACLE_OBJECT ) color |= 0x004000; if( flags & Flags::OBSTACLE_PARTICLE ) color |= 0x002000; setColorAt( x, y, color ); } } } /**********************************************************/ bool Map::saveFlags(const char* comment ) { static int lastTime = 0; const int currentTime = time( NULL ); if( currentTime - lastTime < 10 ) { lastTime = currentTime; // only save flags if not called for 10s return false; } static int counter = 0; String filename; filename.format( "wop_flags_%04i.ppm", counter++ ); FILE* file = fopen( filename, "w" ); if( ! file ) return false; fprintf( file, "P6\n# %s\n%i %i\n255\n", comment, m_sizeX, m_sizeY ); Uint32 color; Uint8 flags; for( Uint32 y = 0; y < m_sizeY; y++ ) { for( Uint32 x = 0; x < m_sizeX; x++ ) { color = 0; flags = m_flags.getValueAt( x, y ); if( flags & Flags::INDESTRUCTIBLE ) color |= 0x800000; if( flags & Flags::UNDIGGABLE ) color |= 0x400000; if( flags & Flags::UNSMOOTHABLE ) color |= 0x200000; if( flags & Flags::OBSTACLE_EARTH ) color |= 0x008000; if( flags & Flags::OBSTACLE_OBJECT ) color |= 0x004000; if( flags & Flags::OBSTACLE_PARTICLE ) color |= 0x002000; fwrite( &color, 1, 3, file ); } } fclose( file ); lastTime = time( NULL ); return true; } /**********************************************************/ void Map::createFromTheme( Theme& theme, World& world ) { const char fn[] = "ServerMap::createFromTheme:"; const Uint32 DENSITY_REFERENCE_AEREA = 100 * 100; INFO( "%s building map from theme \"%s\"\n", fn, theme.getName().getString() ); // if the loaded theme defines its own map size, check it again if( theme.definesOwnSize() ) { // adapt the global settings ASSERT( getSizeX() == (Uint32)theme.getWidth() && getSizeY() == (Uint32)theme.getHeight(), "%s the theme configuration expects a map of size " "%dx%d at this point, but the current map has size " "%dx%d setting\n", fn, theme.getWidth(), theme.getHeight(), getSizeX(), getSizeY() ); } ////////////////////////////////////////////// // Build the basic map. Since the structure maps should be drawn // and applied on diggable earth only, we first draw the earth, // followed by the structure maps, finished by the oszillating // ground. ////////////////////////////////////////////// // fill map with earth if( theme.isEarthFilled() ) { for( Uint32 y = 0; y < m_sizeY; y++ ) { for( Uint32 x = 0; x < m_sizeX; x++ ) { setSpecialEarth( x, y, Flags::OBSTACLE_EARTH ); } } } // use some structure maps, if present if( theme.hasEarthStructure() ) { Sint32 unstructuredArea = (Sint32)( theme.getStructureDensity() * m_sizeX * m_sizeY ); while( unstructuredArea > 0 ) { const Sprite* item = NULL; if( MapStuffSet::UNDEFINED != theme.getEarthStructure().getRandomItem(&item) ) { // draw the structure map placeMapStuffItem( item, localRnd.getUint32Between(0, m_sizeX), localRnd.getUint32Between(0, m_sizeY - 50), MapStuffSet::DIGGABLE ); unstructuredArea -= (item->getWidth() * item->getHeight()); } } } // set oszillating indestructible ground if( theme.hasOszillatingGround() ) { // build base background (earth) and oscillating ground const real phase1 = 180.0 * localRnd.getNormedReal(), phase2 = 180.0 * localRnd.getNormedReal(), amp1 = 20.0 + 40.0 * localRnd.getNormedReal(), amp2 = 5.0 + 10.0 * localRnd.getNormedReal(), freq1 = 0.2 + 0.8 * localRnd.getNormedReal(), freq2 = 1.5 + 1.5 * localRnd.getNormedReal(); for( Uint32 y = 0; y < m_sizeY; y++ ) { for( Uint32 x = 0; x < m_sizeX; x++ ) { if( y >= m_sizeY - (BORDER_WIDTH + amp1*(1+SIN_REAL( (phase1+x*freq1)*M_PI/180 )) + amp2*(1+SIN_REAL( (phase2+x*freq2)*M_PI/180 ))) ) { // indestructible oscillating ground setSpecialEarth( x, y, Flags::OBSTACLE_EARTH | Flags::UNDIGGABLE | Flags::INDESTRUCTIBLE ); } } } } ////////////////////////////////////////////// // Place initial objects ////////////////////////////////////////////// if( theme.hasObjectSet() ) { // build path for configuration file String path( WopSettings::getInstance()->getData() ); path += '/'; path += SPRITE_CONFIG_PATH; // load path list for additional object graphics ObjectPaths objpaths; ASSERT( objpaths.load(path.getString(), SPRITE_CONFIG, "OBJECT_PATHS_THEME_GRAPHICS"), "%s could not load additional object " "graphics path\n", fn ); // load additional graphics PointerVector > gfx( NUMBER_OF_OBJECT_IDs ); for( int i = 0; i < NUMBER_OF_OBJECT_IDs; i++ ) { // try to get path for object ID "i" const Setting* const pathSetting = objpaths.getSetting( Object::getIDString(i) ); // if there is a path for objects with id "i" if( pathSetting ) { SpriteSet* set = NEW SpriteSet; // load graphics (NOTE: no progress object passed) String fullBasePath( path ); fullBasePath += '/'; fullBasePath += pathSetting->getString(0); ASSERT( set->load(fullBasePath, pathSetting->getString(1)), "%s\n could not load map graphics for object " "\"%s\" from file\n \"%s/%s\"\n", fn, pathSetting->getIDString(), pathSetting->getString(0), pathSetting->getString(1) ); // add sprite set in gfx object gfx.set( i, set ); } } // reference to list of initial objects const ObjectSet &set = theme.getObjectSet(); for( int i = 0; i < set.getNumItems(); i++ ) { const ObjectItem &item = set.getItem( i ); // create object in the world and initialize it Object &newObj = *world.newObject( item.getObjectID() ); // IMORTANT: The initialisation of an object from the passed // item does not mean, that the object simply copies the // coordinates from the item (it might, for example, search // a suitable position, just STARTING from the one in the // passed item. Therefore do not use the item coordinates // any more below this line, but refere directly to the object. ASSERT( newObj.initFromItem(item), "%s could not fully initialize object \"%s\"" "from passed item\n", fn, newObj.getIDString() ); // let the object initialize itself in the maps ASSERT( newObj.initInMaps(gfx[newObj.getID()]), "%s object \"%s\" could not initialize map\n", fn, newObj.getIDString() ); // eventually add locked area to the theme's list if( newObj.definesLockedArea() ) { // NOTE that we have to create a new area object from // the one provided by the object, since the appended // object is administrated and deleted by the item set // on the one hand, and must be shifted on the other // hand. AreaItem *area = NEW AreaItem( *newObj.getLockedArea() ); area->shift( newObj.getIntPosX(), newObj.getIntPosY() ); theme.getLockedAreaSet().append( area ); } } } ////////////////////////////////////////////// // Place the map stuff items. // First we place them. Then we bound the whole map with // an indestructible frame. NOTE that the oszillating ground // has already been drawn. ////////////////////////////////////////////// // calculate the number of random map stuff items // from their specified density // -> first get the total locked area. NOTE that we // do not care about intersections, so that they are // counted multible times. real lockedArea = 0; AreaSet &lockedAreas = theme.getLockedAreaSet(); for( int i = 0; i < theme.getLockedAreaSet().getNumItems(); i++ ) { lockedArea += lockedAreas.getItem(i).getContent(); } // calculate the number of random map struff items const int nItems = (int)( (theme.getDensity() * (m_sizeX*m_sizeY - lockedArea) ) / DENSITY_REFERENCE_AEREA ); // make some holes for( int i = 0; i < nItems/4; i++ ) { int radius = localRnd.getUint32Between( 5, 35 ); makeHole( localRnd.getUint32Between( radius + BORDER_WIDTH, m_sizeX - radius - BORDER_WIDTH ), localRnd.getUint32Between( radius + BORDER_WIDTH, m_sizeY - radius - BORDER_WIDTH ), radius, Flags::UNDIGGABLE ); } // fill the map with mapstuff if( theme.getName() == (String)("randcircles") ) { fillWithRandomCircles(); // use sprite mapstuff } else if( theme.hasMapStuff() ) { // first place the random items for( int i = 0; i < nItems; i++ ) { const Sprite* item = NULL; int material = MapStuffSet::UNDEFINED; if( MapStuffSet::UNDEFINED != (material = theme.getMapStuff().getRandomItem(&item)) ) { // search an appropriate place for this map stuff item Uint32 X = 0, Y = 0; bool isLocked = true; while( isLocked ) { // get random coordinates for the item X = localRnd.getUint32Between( 0, m_sizeX ), Y = localRnd.getUint32Between( 0, m_sizeY - 50 ); // check, if this item would intersect with any locked area isLocked = false; for( int ilocked = 0; ilocked < lockedAreas.getNumItems(); ilocked++ ) { const AreaItem& area = lockedAreas.getItem( ilocked ); if( area.touchesRect( X - item->getHotSpotX(), Y - item->getHotSpotY() + item->getHeight(), item->getWidth(), item->getHeight() ) ) { isLocked = true; break; } } } // place item in map placeMapStuffItem( item, X, Y, material ); } } // then place the fixed items, // because we do not want fixed items to be overdrawn by // random map stuff items for( int i = 0; i < theme.getMapStuff().getNumFixedItems(); i++ ) { const Sprite* item = NULL; Sint32 posX, posY; int material = theme.getMapStuff().getFixedItem( i, m_sizeX, m_sizeY, &item, posX, posY ); if( MapStuffSet::UNDEFINED != material ) { // place item in map placeMapStuffItem( item, posX, posY, material ); } } } // set the indestructible border for( Uint32 y = 0; y < m_sizeY; y++ ) { for( Uint32 x = 0; x < m_sizeX; x++ ) { if( x < BORDER_WIDTH || x >= m_sizeX - BORDER_WIDTH || y < BORDER_WIDTH || y >= m_sizeY - BORDER_WIDTH ) { setSpecialEarth( x, y, Flags::OBSTACLE_EARTH | Flags::UNABLE_ANY ); } } } // smooth away isolated unsmoothable pixels bool smoothedSomething; do { smoothedSomething = false; for( int y = 2; y < int( m_sizeY )-2; y++ ) { for( int x = 2; x < int( m_sizeX )-2; x++ ) { if( (m_flags.getValueAt( x, y ) & (Flags::UNDIGGABLE | Flags::INDESTRUCTIBLE)) && ! (m_flags.getValueAt( x, y ) & Flags::UNSMOOTHABLE) ) { int nObstacleNeighbors = 0, nObstacleNextNeighbors = 0; for( int dy = -2; dy <= 2; dy++ ) { for( int dx = -2; dx <= 2; dx++ ) { if( ! (dx || dy) ) continue; if( m_flags.getValueAt( x+dx, y+dy ) & Flags::UNABLE_ANY ) { if( abs( dx ) == 2 || abs( dy ) == 2 ) nObstacleNextNeighbors++; else nObstacleNeighbors++; } } } if( nObstacleNextNeighbors < 7 || nObstacleNeighbors < 4 ) { m_flags.getReferenceTo( x, y ) &= ~Flags::UNABLE_ANY; //setColorAt( x, y, 255, 0, 0 ); // for debugging only smoothedSomething = true; } } } } } while( smoothedSomething ); } /**********************************************************/ void Map::fillWithRandomCircles() { Random myRnd; for( int i = 0; i < 100; i++ ) { int radius = localRnd.getUint32Between( 5, 35); makeStone( localRnd.getUint32Between( radius + BORDER_WIDTH, m_sizeX - radius - BORDER_WIDTH ), localRnd.getUint32Between( radius + BORDER_WIDTH, m_sizeY - radius - BORDER_WIDTH ) , radius, Flags::OBSTACLE_EARTH | Flags::UNDIGGABLE | Flags::INDESTRUCTIBLE, 0 ); } for( int i = 0; i < 100; i++ ) { int radius = localRnd.getUint32Between( 5, 35 ); makeStone( localRnd.getUint32Between( radius + BORDER_WIDTH, m_sizeX - radius - BORDER_WIDTH ), localRnd.getUint32Between( radius + BORDER_WIDTH, m_sizeY - radius - BORDER_WIDTH ) , radius, Flags::OBSTACLE_EARTH | Flags::UNDIGGABLE, 0 ); } } /**********************************************************/ void Map::placeMapStuffItem( const Sprite* const item, const Sprite* const flags, const Uint32 x, const Uint32 y, const int material, const bool smoothable ) { if( item == NULL ) return; DBG(5) INFO( "ServerMap::placeMapStuffItem: item at (%u,%u)," " mat: %d\n", x, y, material ); // map the material constant from class MapStuffSet to a map flag Flag materialflag = Flags::OBSTACLE_EARTH; switch( material ) { // NOTE: no 'break's in switch construct case MapStuffSet::INDESTRUCTIBLE : materialflag |= Flags::INDESTRUCTIBLE; case MapStuffSet::UNDIGGABLE : materialflag |= Flags::UNDIGGABLE; case MapStuffSet::DIGGABLE : default : break; } // reinsert the UNSMOOTHABLE flag, if necessary // NOTE: At this moment (June, 13th 2005, 21:15h) the regular // mapstuff does not support this flag, but it is introduced // nevertheless, to make this method usable for object // initialisations on the map (Gismo). if( ! smoothable ) materialflag |= Flags::UNSMOOTHABLE; ////////////////////////////////////////////// // draw the visible sprite on the color map ////////////////////////////////////////////// if( item ) { const Sint32 top_left_X = x - item->getHotSpotX(), top_left_Y = y - item->getHotSpotY(); for( Sint32 iy = 0; iy < item->getHeight(); iy++ ) { for( Sint32 ix = 0; ix < item->getWidth(); ix++ ) { const Sint32 i = top_left_X + ix, j = top_left_Y + iy; Uint8 r, g, b, a; if( i >= 0 && i < (Sint32)m_sizeX && j >= 0 && j < (Sint32)m_sizeY && item->getRGBA(ix, iy, r, g, b, a) ) { if( a > 0 ) { if( a == 255 ) { // pixel map setColorAt( i, j, r, g, b ); } else { Uint8 mapR, mapG, mapB; const real itemWeight = a / 255.0, mapWeight = 1.0 - itemWeight; getColorAt( i, j, mapR, mapG, mapB ); real newR = mapWeight*mapR + itemWeight*r, newG = mapWeight*mapG + itemWeight*g, newB = mapWeight*mapB + itemWeight*b; if( newR > 255 ) newR = 255; if( newG > 255 ) newG = 255; if( newB > 255 ) newB = 255; setColorAt( i, j, (Uint8)(newR), (Uint8)(newG), (Uint8)(newB) ); } } } } } } ////////////////////////////////////////////// // draw the flag pattern on the flag map ////////////////////////////////////////////// if( flags ) { // draw the item on the pixel map and set the // corresponding flag in the flag map const Sint32 top_left_X = x - flags->getHotSpotX(), top_left_Y = y - flags->getHotSpotY(); for( Sint32 iy = 0; iy < flags->getHeight(); iy++ ) { for( Sint32 ix = 0; ix < flags->getWidth(); ix++ ) { const Sint32 i = top_left_X + ix, j = top_left_Y + iy; Uint8 r, g, b, a; if( i >= 0 && i < (Sint32)m_sizeX && j >= 0 && j < (Sint32)m_sizeY && flags->getRGBA(ix, iy, r, g, b, a) ) { // We set the material pixel in the map for each // pixel that is not completely invisible. This was // changed, since the previous decision if( a == 255 ) // caused persistent particles to rest a little bit // inside the clouds in theme "sky". // (08.Jan 2006, Gismo) if( a != 0 ) { m_flags.getReferenceTo( i, j ) = materialflag; } } } } } } /**********************************************************/ void ServerMap::serialize( Uint8*& bufferPointer ) const { START_SERIALIZED_SIZE_CHECK( bufferPointer ); // serialize m_SizeX Serialize::serialize( m_sizeX, bufferPointer ); // serialize m_SizeY Serialize::serialize( m_sizeY, bufferPointer ); // serialize m_pixels m_pixels.serialize( bufferPointer ); // serialize m_flags m_flags.serialize( bufferPointer ); END_SERIALIZED_SIZE_CHECK( bufferPointer, ServerMap ); } /**********************************************************/ ServerMap* ServerMap::createAndDeserialize( Uint8*& bufferPointer ) { // deserialize m_sizeX Uint32 sizeX; Serialize::deserialize( bufferPointer, sizeX ); // deserialize m_sizeY Uint32 sizeY; Serialize::deserialize( bufferPointer, sizeY ); // create object ServerMap* newObject = new ServerMap( sizeX, sizeY ); // deserialize m_pixels newObject->getPixels()->deserialize( bufferPointer ); // deserialize m_flags newObject->getFlags()->deserialize( bufferPointer ); return newObject; } /**********************************************************/ void ServerMap::deserialize( Uint8*& bufferPointer ) { // deserialize m_sizeX Serialize::deserialize( bufferPointer, m_sizeX ); // deserialize m_sizeY Serialize::deserialize( bufferPointer, m_sizeY ); // deserialize m_pixels m_pixels.deserialize( bufferPointer ); // deserialize m_flags m_flags.deserialize( bufferPointer ); } /**********************************************************/ void ClientMap::serialize( Uint8*& bufferPointer ) const { START_SERIALIZED_SIZE_CHECK( bufferPointer ); // serialize m_SizeX Serialize::serialize( m_sizeX, bufferPointer ); // serialize m_SizeY Serialize::serialize( m_sizeY, bufferPointer ); // serialize m_pixels // TODO: this is a quick hack! It will fail if size or // pixel format differ! memcpy( bufferPointer, m_pixels->pixels, m_pixels->h * m_pixels->pitch ); bufferPointer += m_pixels->h * m_pixels->pitch; // serialize m_flags m_flags.serialize( bufferPointer ); END_SERIALIZED_SIZE_CHECK( bufferPointer, ClientMap ); } /**********************************************************/ void ClientMap::deserialize( Uint8*& bufferPointer ) { // deserialize m_sizeX Serialize::deserialize( bufferPointer, m_sizeX ); // deserialize m_sizeY Serialize::deserialize( bufferPointer, m_sizeY ); // deserialize m_pixels // TODO: this is a quick hack! It will fail if size or // pixel format differ! memcpy( m_pixels->pixels, bufferPointer, m_pixels->h * m_pixels->pitch ); bufferPointer += m_pixels->h * m_pixels->pitch; // deserialize m_flags m_flags.deserialize( bufferPointer ); } /**********************************************************/