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