1 /* 2 * TBIN 3 * Copyright 2017, Chase Warrington <spacechase0.and.cat@gmail.com> 4 * 5 * MIT License 6 * 7 * Permission is hereby granted, free of charge, to any person obtaining a copy 8 * of this software and associated documentation files (the "Software"), to deal 9 * in the Software without restriction, including without limitation the rights 10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 * copies of the Software, and to permit persons to whom the Software is 12 * furnished to do so, subject to the following conditions: 13 * 14 * The above copyright notice and this permission notice shall be included in all 15 * copies or substantial portions of the Software. 16 * 17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 * SOFTWARE. 24 */ 25 26 #include "Map.hpp" 27 28 #include <fstream> 29 #include <stdexcept> 30 #include <QDebug> 31 32 #include "FakeSfml.hpp" 33 34 namespace tbin 35 { 36 template< typename T > read(std::istream & in)37 T read( std::istream& in ) 38 { 39 T t; 40 in.read( reinterpret_cast< char* >( &t ), sizeof( T ) ); 41 return t; 42 } 43 44 template<> read(std::istream & in)45 sf::Vector2i read< sf::Vector2i >( std::istream& in ) 46 { 47 sf::Int32 x = read< sf::Int32 >( in ); 48 sf::Int32 y = read< sf::Int32 >( in ); 49 return sf::Vector2i( x, y ); 50 } 51 52 template<> read(std::istream & in)53 std::string read< std::string >( std::istream& in ) 54 { 55 auto len = read< sf::Int32 >( in ); 56 std::string str( len, 0 ); 57 in.read( &str[ 0 ], len ); 58 return str; 59 } 60 61 template< typename T > write(std::ostream & out,const T & t)62 void write( std::ostream& out, const T& t ) 63 { 64 out.write( reinterpret_cast< const char* >( &t ), sizeof( T ) ); 65 } 66 67 template<> write(std::ostream & out,const sf::Vector2i & vec)68 void write< sf::Vector2i >( std::ostream& out, const sf::Vector2i& vec ) 69 { 70 write< sf::Int32 >( out, vec.x ); 71 write< sf::Int32 >( out, vec.y ); 72 } 73 74 template<> write(std::ostream & out,const std::string & str)75 void write< std::string >( std::ostream& out, const std::string& str ) 76 { 77 write< sf::Int32 >( out, str.length() ); 78 out.write( &str[ 0 ], str.length() ); 79 } 80 readProperties(std::istream & in)81 Properties readProperties( std::istream& in ) 82 { 83 Properties ret; 84 85 int count = read< sf::Int32 >( in ); 86 for ( int i = 0; i < count; ++i ) 87 { 88 std::string key; 89 PropertyValue value; 90 91 key = read< std::string >( in ); 92 value.type = static_cast< PropertyValue::Type >( read< sf::Uint8 >( in ) ); 93 switch ( value.type ) 94 { 95 case PropertyValue::Bool: value.data.b = read< sf::Uint8 >( in ) > 0; break; 96 case PropertyValue::Integer: value.data.i = read< sf::Int32 >( in ); break; 97 case PropertyValue::Float: value.data.f = read< float >( in ); break; 98 case PropertyValue::String: value.dataStr = read< std::string >( in ); break; 99 default: throw std::invalid_argument( QT_TRANSLATE_NOOP("TbinMapFormat", "Bad property type") ); 100 } 101 102 ret[ key ] = value; 103 } 104 105 106 return ret; 107 } 108 writeProperties(std::ostream & out,const Properties & props)109 void writeProperties( std::ostream& out, const Properties& props ) 110 { 111 write< sf::Int32 >( out, props.size() ); 112 for ( const auto& prop : props ) 113 { 114 write( out, prop.first ); 115 write< sf::Uint8 >( out, prop.second.type ); 116 switch ( prop.second.type ) 117 { 118 case PropertyValue::Bool: write< sf::Uint8 >( out, prop.second.data.b ? 1 : 0 ); break; 119 case PropertyValue::Integer: write( out, prop.second.data.i ); break; 120 case PropertyValue::Float: write( out, prop.second.data.f ); break; 121 case PropertyValue::String: write( out, prop.second.dataStr ); break; 122 default: throw std::invalid_argument( QT_TRANSLATE_NOOP("TbinMapFormat", "Bad property type") ); 123 } 124 } 125 } 126 readTilesheet(std::istream & in)127 TileSheet readTilesheet( std::istream& in ) 128 { 129 TileSheet ret; 130 ret.id = read< std::string >( in ); 131 ret.desc = read< std::string >( in ); 132 ret.image = read< std::string >( in ); 133 ret.sheetSize = read< sf::Vector2i >( in ); 134 ret.tileSize = read< sf::Vector2i >( in ); 135 ret.margin = read< sf::Vector2i >( in ); 136 ret.spacing = read< sf::Vector2i >( in ); 137 ret.props = readProperties( in ); 138 return ret; 139 } 140 writeTilesheet(std::ostream & out,const TileSheet & ts)141 void writeTilesheet( std::ostream& out, const TileSheet& ts ) 142 { 143 write( out, ts.id ); 144 write( out, ts.desc ); 145 write( out, ts.image ); 146 write( out, ts.sheetSize ); 147 write( out, ts.tileSize ); 148 write( out, ts.margin ); 149 write( out, ts.spacing ); 150 writeProperties( out, ts.props ); 151 } 152 readStaticTile(std::istream & in,const std::string & currTilesheet)153 Tile readStaticTile( std::istream& in, const std::string& currTilesheet ) 154 { 155 Tile ret; 156 ret.tilesheet = currTilesheet; 157 ret.staticData.tileIndex = read< sf::Int32 >( in ); 158 ret.staticData.blendMode = read< sf::Uint8 >( in ); 159 ret.props = readProperties( in ); 160 return ret; 161 } 162 writeStaticTile(std::ostream & out,const Tile & tile)163 void writeStaticTile( std::ostream& out, const Tile& tile ) 164 { 165 write( out, tile.staticData.tileIndex ); 166 write( out, tile.staticData.blendMode ); 167 writeProperties( out, tile.props ); 168 } 169 readAnimatedTile(std::istream & in)170 Tile readAnimatedTile( std::istream& in ) 171 { 172 Tile ret; 173 ret.animatedData.frameInterval = read< sf::Int32 >( in ); 174 175 int frameCount = read< sf::Int32 >( in ); 176 ret.animatedData.frames.reserve( frameCount ); 177 std::string currTilesheet; 178 for ( int i = 0; i < frameCount; ) 179 { 180 char c = in.get(); 181 switch ( c ) 182 { 183 case 'T': 184 currTilesheet = read< std::string >( in ); 185 break; 186 case 'S': 187 ret.animatedData.frames.push_back( readStaticTile( in, currTilesheet ) ); 188 ++i; 189 break; 190 default: 191 throw std::invalid_argument( QT_TRANSLATE_NOOP("TbinMapFormat", "Bad layer tile data") ); 192 } 193 } 194 195 ret.props = readProperties( in ); 196 197 return ret; 198 } 199 writeAnimatedTile(std::ostream & out,const Tile & tile)200 void writeAnimatedTile( std::ostream& out, const Tile& tile ) 201 { 202 write( out, tile.animatedData.frameInterval ); 203 write< sf::Int32 >( out, tile.animatedData.frames.size() ); 204 205 std::string currTilesheet; 206 for ( const Tile& frame : tile.animatedData.frames ) 207 { 208 if ( frame.tilesheet != currTilesheet ) 209 { 210 write< sf::Uint8 >( out, 'T' ); 211 write( out, frame.tilesheet ); 212 currTilesheet = frame.tilesheet; 213 } 214 215 write< sf::Uint8 >( out, 'S' ); 216 writeStaticTile( out, frame ); 217 } 218 219 writeProperties( out, tile.props ); 220 } 221 readLayer(std::istream & in)222 Layer readLayer( std::istream& in ) 223 { 224 Layer ret; 225 ret.id = read< std::string >( in ); 226 ret.visible = read< sf::Uint8 >( in ) > 0; 227 ret.desc = read< std::string >( in ); 228 ret.layerSize = read< sf::Vector2i >( in ); 229 ret.tileSize = read< sf::Vector2i >( in ); 230 ret.props = readProperties( in ); 231 232 Tile nullTile; nullTile.staticData.tileIndex = -1; 233 ret.tiles.resize( ret.layerSize.x * ret.layerSize.y, nullTile ); 234 235 std::string currTilesheet = ""; 236 for ( int iy = 0; iy < ret.layerSize.y; ++iy ) 237 { 238 int ix = 0; 239 while ( ix < ret.layerSize.x ) 240 { 241 sf::Uint8 c = read< sf::Uint8 >( in ); 242 switch ( c ) 243 { 244 case 'N': 245 ix += read< sf::Int32 >( in ); 246 break; 247 case 'S': 248 ret.tiles[ ix + iy * ret.layerSize.x ] = readStaticTile( in, currTilesheet ); 249 ++ix; 250 break; 251 case 'A': 252 ret.tiles[ ix + iy * ret.layerSize.x ] = readAnimatedTile( in ); 253 ++ix; 254 break; 255 case 'T': 256 currTilesheet = read< std::string >( in ); 257 break; 258 default: 259 throw std::invalid_argument( QT_TRANSLATE_NOOP("TbinMapFormat", "Bad layer tile data") ); 260 } 261 } 262 } 263 264 return ret; 265 } 266 writeLayer(std::ostream & out,const Layer & layer)267 void writeLayer( std::ostream& out, const Layer& layer ) 268 { 269 write( out, layer.id ); 270 write< sf::Uint8 >( out, layer.visible ? 1 : 0 ); 271 write( out, layer.desc ); 272 write( out, layer.layerSize ); 273 write( out, layer.tileSize ); 274 writeProperties( out, layer.props ); 275 276 std::string currTilesheet = ""; 277 for ( int iy = 0; iy < layer.layerSize.y; ++iy ) 278 { 279 sf::Int32 nulls = 0; 280 for ( int ix = 0; ix < layer.layerSize.x; ++ix ) 281 { 282 const Tile& tile = layer.tiles[ ix + iy * layer.layerSize.x ]; 283 284 if ( tile.isNullTile() ) 285 { 286 ++nulls; 287 continue; 288 } 289 290 if ( nulls > 0 ) 291 { 292 write< sf::Uint8 >( out, 'N' ); 293 write( out, nulls ); 294 nulls = 0; 295 } 296 297 if ( tile.tilesheet != currTilesheet ) 298 { 299 write< sf::Uint8 >( out, 'T' ); 300 write( out, tile.tilesheet ); 301 currTilesheet = tile.tilesheet; 302 } 303 304 if ( tile.animatedData.frames.size() == 0 ) 305 { 306 write< sf::Uint8 >( out, 'S' ); 307 writeStaticTile( out, tile ); 308 } 309 else 310 { 311 write< sf::Uint8 >( out, 'A' ); 312 writeAnimatedTile( out, tile ); 313 } 314 } 315 316 if ( nulls > 0 ) 317 { 318 write< sf::Uint8 >( out, 'N' ); 319 write( out, nulls ); 320 } 321 } 322 } 323 324 Q_DECL_CONSTEXPR const char* MAGIC_1_0 = "tBIN10"; 325 loadFromFile(const std::string & path)326 bool Map::loadFromFile( const std::string& path ) 327 { 328 std::ifstream file( path, std::ifstream::binary ); 329 if ( !file ) 330 { 331 throw std::runtime_error( QT_TRANSLATE_NOOP("TbinMapFormat", "Failed to open file.") ); 332 } 333 334 return loadFromStream( file ); 335 } 336 loadFromStream(std::istream & in)337 bool Map::loadFromStream( std::istream& in ) 338 { 339 in.exceptions( std::ifstream::failbit ); 340 341 std::string magic( 6, '\0' ); 342 in.read( &magic[ 0 ], 6 ); 343 if ( magic != MAGIC_1_0 ) 344 { 345 throw std::runtime_error( QT_TRANSLATE_NOOP("TbinMapFormat", "File is not a tbin file.") ); 346 } 347 348 std::string id = read< std::string >( in ); 349 std::string desc = read< std::string >( in ); 350 Properties props = readProperties( in ); 351 352 std::vector< TileSheet > tilesheets; 353 int tilesheetCount = read< sf::Int32 >( in ); 354 for ( int i = 0; i < tilesheetCount; ++i ) 355 { 356 tilesheets.push_back( readTilesheet( in ) ); 357 } 358 359 std::vector< Layer > layers; 360 int layerCount = read< sf::Int32 >( in ); 361 for ( int i = 0; i < layerCount; ++i ) 362 { 363 layers.push_back( readLayer( in ) ); 364 } 365 366 std::swap( this->id, id ); 367 std::swap( this->desc, desc ); 368 std::swap( this->props, props ); 369 std::swap( this->tilesheets, tilesheets ); 370 std::swap( this->layers, layers ); 371 372 return true; 373 } 374 saveToFile(const std::string & path) const375 bool Map::saveToFile( const std::string& path ) const 376 { 377 std::ofstream file( path, std::ofstream::binary | std::ofstream::trunc ); 378 if ( !file ) 379 { 380 throw std::runtime_error( QT_TRANSLATE_NOOP("TbinMapFormat", "Failed to open file") ); 381 } 382 383 return saveToStream( file ); 384 } 385 saveToStream(std::ostream & out) const386 bool Map::saveToStream( std::ostream& out ) const 387 { 388 out.exceptions( std::ifstream::failbit ); 389 390 out.write( MAGIC_1_0, 6 ); 391 392 write( out, id ); 393 write( out, desc ); 394 writeProperties( out, props ); 395 396 write< sf::Int32 >( out, tilesheets.size() ); 397 for ( const TileSheet& ts : tilesheets ) 398 writeTilesheet( out, ts ); 399 400 write< sf::Int32 >( out, layers.size() ); 401 for ( const Layer& layer : layers ) 402 writeLayer( out, layer ); 403 404 return true; 405 } 406 } 407