forked from CleverRaven/Cataclysm-DDA
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cata_tiles.cpp
5553 lines (5121 loc) · 229 KB
/
cata_tiles.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#if defined(TILES)
#include "cata_tiles.h"
#include <algorithm>
#include <array>
#include <bitset>
#include <cmath>
#include <cstdint>
#include <iterator>
#include <optional>
#include <set>
#include <stdexcept>
#include <tuple>
#include <unordered_set>
#include "action.h"
#include "avatar.h"
#include "cached_options.h"
#include "calendar.h"
#include "cata_assert.h"
#include "cata_utility.h"
#include "catacharset.h"
#include "character.h"
#include "character_id.h"
#include "clzones.h"
#include "color.h"
#include "creature_tracker.h"
#include "cursesdef.h"
#include "cursesport.h"
#include "debug.h"
#include "field.h"
#include "field_type.h"
#include "filesystem.h"
#include "game.h"
#include "game_constants.h"
#include "input.h"
#include "int_id.h"
#include "item.h"
#include "item_factory.h"
#include "itype.h"
#include "json.h"
#include "json_loader.h"
#include "map.h"
#include "map_extras.h"
#include "map_memory.h"
#include "mapdata.h"
#include "mod_tileset.h"
#include "monster.h"
#include "monstergenerator.h"
#include "mtype.h"
#include "mutation.h"
#include "npc.h"
#include "output.h"
#include "overlay_ordering.h"
#include "overmap.h"
#include "path_info.h"
#include "pixel_minimap.h"
#include "rect_range.h"
#include "scent_map.h"
#include "sdl_utils.h"
#include "sdl_wrappers.h"
#include "sdltiles.h"
#include "sounds.h"
#include "string_formatter.h"
#include "string_id.h"
#include "submap.h"
#include "tileray.h"
#include "translations.h"
#include "trap.h"
#include "type_id.h"
#include "units_utility.h"
#include "veh_type.h"
#include "vehicle.h"
#include "vpart_position.h"
#include "weather.h"
#include "weighted_list.h"
#define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": "
static const efftype_id effect_ridden( "ridden" );
static const itype_id itype_corpse( "corpse" );
static const trait_id trait_INATTENTIVE( "INATTENTIVE" );
static const trap_str_id tr_unfinished_construction( "tr_unfinished_construction" );
static const std::string ITEM_HIGHLIGHT( "highlight_item" );
static const std::string ZOMBIE_REVIVAL_INDICATOR( "zombie_revival_indicator" );
static const std::array<std::string, 8> multitile_keys = {{
"center",
"corner",
"edge",
"t_connection",
"end_piece",
"unconnected",
"open",
"broken"
}
};
static const std::string empty_string;
static const std::array<std::string, 17> TILE_CATEGORY_IDS = {{
"", // TILE_CATEGORY::NONE,
"vehicle_part", // TILE_CATEGORY::VEHICLE_PART,
"terrain", // TILE_CATEGORY::TERRAIN,
"item", // TILE_CATEGORY::ITEM,
"furniture", // TILE_CATEGORY::FURNITURE,
"trap", // TILE_CATEGORY::TRAP,
"field", // TILE_CATEGORY::FIELD,
"lighting", // TILE_CATEGORY::LIGHTING,
"monster", // TILE_CATEGORY::MONSTER,
"bullet", // TILE_CATEGORY::BULLET,
"hit_entity", // TILE_CATEGORY::HIT_ENTITY,
"weather", // TILE_CATEGORY::WEATHER,
"overmap_terrain", // TILE_CATEGORY::OVERMAP_TERRAIN
"overmap_vision_level", // TILE_CATEGORY::OVERMAP_VISION_LEVEL
"overmap_weather", // TILE_CATEGORY::OVERMAP_WEATHER
"map_extra", // TILE_CATEGORY::MAP_EXTRA
"overmap_note", // TILE_CATEGORY::OVERMAP_NOTE
}
};
static_assert( TILE_CATEGORY_IDS.size() == static_cast<size_t>( TILE_CATEGORY::last ),
"TILE_CATEGORY_IDS must match list of TILE_CATEGORY values" );
namespace
{
std::string get_ascii_tile_id( const uint32_t sym, const int FG, const int BG )
{
return std::string( { 'A', 'S', 'C', 'I', 'I', '_', static_cast<char>( sym ),
static_cast<char>( FG ), static_cast<char>( BG )
} );
}
pixel_minimap_mode pixel_minimap_mode_from_string( const std::string &mode )
{
if( mode == "solid" ) {
return pixel_minimap_mode::solid;
} else if( mode == "squares" ) {
return pixel_minimap_mode::squares;
} else if( mode == "dots" ) {
return pixel_minimap_mode::dots;
}
debugmsg( "Unsupported pixel minimap mode \"" + mode + "\"." );
return pixel_minimap_mode::solid;
}
auto simple_point_hash = []( const auto &p )
{
return p.x + p.y * 65536;
};
} // namespace
static int msgtype_to_tilecolor( const game_message_type type, const bool bOldMsg )
{
const int iBold = bOldMsg ? 0 : 8;
switch( type ) {
case m_good:
return iBold + catacurses::green;
case m_bad:
return iBold + catacurses::red;
case m_mixed:
case m_headshot:
return iBold + catacurses::magenta;
case m_neutral:
return iBold + catacurses::white;
case m_warning:
case m_critical:
return iBold + catacurses::yellow;
case m_info:
case m_grazing:
return iBold + catacurses::blue;
default:
break;
}
return -1;
}
formatted_text::formatted_text( const std::string &text, const int color,
const direction text_direction )
: text( text ), color( color )
{
switch( text_direction ) {
case direction::NORTHWEST:
case direction::WEST:
case direction::SOUTHWEST:
alignment = text_alignment::right;
break;
case direction::NORTH:
case direction::CENTER:
case direction::SOUTH:
alignment = text_alignment::center;
break;
default:
alignment = text_alignment::left;
break;
}
}
cata_tiles::cata_tiles( const SDL_Renderer_Ptr &renderer, const GeometryRenderer_Ptr &geometry,
tileset_cache &cache ) :
renderer( renderer ),
geometry( geometry ),
cache( cache ),
minimap( renderer, geometry )
{
cata_assert( renderer );
tile_height = 0;
tile_width = 0;
in_animation = false;
do_draw_explosion = false;
do_draw_custom_explosion = false;
do_draw_bullet = false;
do_draw_hit = false;
do_draw_line = false;
do_draw_cursor = false;
do_draw_highlight = false;
do_draw_weather = false;
do_draw_sct = false;
do_draw_zones = false;
nv_goggles_activated = false;
on_options_changed();
}
cata_tiles::~cata_tiles() = default;
void cata_tiles::on_options_changed()
{
memory_map_mode = get_option <std::string>( "MEMORY_MAP_MODE" );
pixel_minimap_settings settings;
settings.mode =
pixel_minimap_mode_from_string( get_option<std::string>( "PIXEL_MINIMAP_MODE" ) );
settings.brightness = get_option<int>( "PIXEL_MINIMAP_BRIGHTNESS" );
settings.beacon_size = get_option<int>( "PIXEL_MINIMAP_BEACON_SIZE" );
settings.beacon_blink_interval = get_option<int>( "PIXEL_MINIMAP_BLINK" );
settings.square_pixels = get_option<bool>( "PIXEL_MINIMAP_RATIO" );
settings.scale_to_fit = get_option<bool>( "PIXEL_MINIMAP_SCALE_TO_FIT" );
minimap->set_settings( settings );
}
void tileset::clear()
{
tile_values.clear();
shadow_tile_values.clear();
night_tile_values.clear();
overexposed_tile_values.clear();
memory_tile_values.clear();
duplicate_ids.clear();
tile_ids.clear();
for( std::unordered_map<std::string, season_tile_value> &m : tile_ids_by_season ) {
m.clear();
}
item_layer_data.clear();
field_layer_data.clear();
}
const tile_type *tileset::find_tile_type( const std::string &id ) const
{
const auto iter = tile_ids.find( id );
return iter != tile_ids.end() ? &iter->second : nullptr;
}
std::optional<tile_lookup_res>
tileset::find_tile_type_by_season( const std::string &id, season_type season ) const
{
cata_assert( season < season_type::NUM_SEASONS );
const auto iter = tile_ids_by_season[season].find( id );
if( iter == tile_ids_by_season[season].end() ) {
return std::nullopt;
}
const tileset::season_tile_value &res = iter->second;
if( res.season_tile ) {
return *res.season_tile;
} else if( res.default_tile ) { // can skip this check, but just in case
return tile_lookup_res( iter->first, *res.default_tile );
}
debugmsg( "empty record found in `tile_ids_by_season` for key: %s", id );
return std::nullopt;
}
tile_type &tileset::create_tile_type( const std::string &id, tile_type &&new_tile_type )
{
// Must overwrite existing tile
// TODO: c++17 - replace [] + find() with insert_or_assign()
tile_ids[id] = std::move( new_tile_type );
auto inserted = tile_ids.find( id );
const std::string &inserted_id = inserted->first;
tile_type &inserted_tile = inserted->second;
// populate cache by season
constexpr size_t suffix_len = 15;
// NOLINTNEXTLINE(cata-use-mdarray,modernize-avoid-c-arrays)
constexpr char season_suffix[NUM_SEASONS][suffix_len] = {
"_season_spring", "_season_summer", "_season_autumn", "_season_winter"
};
bool has_season_suffix = false;
for( int i = 0; i < NUM_SEASONS; i++ ) {
if( string_ends_with( id, season_suffix[i] ) ) {
has_season_suffix = true;
// key is id without _season suffix
season_tile_value &value = tile_ids_by_season[i][id.substr( 0,
id.size() - strlen( season_suffix[i] ) )];
// value stores reference to string id with _season suffix
value.season_tile = tile_lookup_res( inserted_id, inserted_tile );
break;
}
}
// tile doesn't have _season suffix, add it as "default" into all four seasons
if( !has_season_suffix ) {
for( auto &by_season_map : tile_ids_by_season ) {
by_season_map[id].default_tile = &inserted_tile;
}
}
return inserted_tile;
}
void cata_tiles::load_tileset( const std::string &tileset_id, const bool precheck,
const bool force, const bool pump_events )
{
if( tileset_ptr && tileset_ptr->get_tileset_id() == tileset_id && !force ) {
return;
}
// TODO: move into clear or somewhere else.
// reset the overlay ordering from the previous loaded tileset
tileset_mutation_overlay_ordering.clear();
tileset_ptr = cache.load_tileset( tileset_id, renderer, precheck, force, pump_events );
set_draw_scale( 16 );
// Precalculate fog transparency
// On isometric tilesets, fog intensity scales with zlevel_height in tile_config.json
fog_alpha = is_isometric() ? std::min( std::max( int( 255.0f - 255.0f * pow( 155.0f / 255.0f,
zlevel_height / 100.0f ) ), 40 ), 150 ) : 100;
}
void cata_tiles::reinit()
{
set_draw_scale( 16 );
RenderClear( renderer );
}
static void get_tile_information( const cata_path &config_path, std::string &json_path,
std::string &tileset_path, std::string &layering_path )
{
const std::string default_json = PATH_INFO::defaulttilejson();
const std::string default_tileset = PATH_INFO::defaulttilepng();
const std::string default_layering = PATH_INFO::defaultlayeringjson();
// Get JSON and TILESET vars from config
const auto reader = [&]( std::istream & fin ) {
while( !fin.eof() ) {
std::string sOption;
fin >> sOption;
if( string_starts_with( sOption, "JSON" ) ) {
fin >> json_path;
dbg( D_INFO ) << "JSON path set to [" << json_path << "].";
} else if( string_starts_with( sOption, "TILESET" ) ) {
fin >> tileset_path;
dbg( D_INFO ) << "TILESET path set to [" << tileset_path << "].";
} else if( string_starts_with( sOption, "LAYERING" ) ) {
fin >> layering_path;
dbg( D_INFO ) << "LAYERING path set to [" << layering_path << "].";
} else {
getline( fin, sOption );
}
}
};
if( !read_from_file( config_path, reader ) ) {
json_path = default_json;
tileset_path = default_tileset;
layering_path = default_layering;
}
if( json_path.empty() ) {
json_path = default_json;
dbg( D_INFO ) << "JSON set to default [" << json_path << "].";
}
if( tileset_path.empty() ) {
tileset_path = default_tileset;
dbg( D_INFO ) << "TILESET set to default [" << tileset_path << "].";
}
if( layering_path.empty() ) {
layering_path = default_layering;
dbg( D_INFO ) << "TILESET set to default [" << layering_path << "].";
}
}
template<typename PixelConverter>
static SDL_Surface_Ptr apply_color_filter( const SDL_Surface_Ptr &original,
PixelConverter pixel_converter )
{
cata_assert( original );
SDL_Surface_Ptr surf = create_surface_32( original->w, original->h );
cata_assert( surf );
throwErrorIf( SDL_BlitSurface( original.get(), nullptr, surf.get(), nullptr ) != 0,
"SDL_BlitSurface failed" );
SDL_Color *pix = static_cast<SDL_Color *>( surf->pixels );
for( int y = 0, ey = surf->h; y < ey; ++y ) {
for( int x = 0, ex = surf->w; x < ex; ++x, ++pix ) {
if( pix->a == 0x00 ) {
// This check significantly improves the performance since
// vast majority of pixels in the tilesets are completely transparent.
continue;
}
*pix = pixel_converter( *pix );
}
}
return surf;
}
static bool is_contained( const SDL_Rect &smaller, const SDL_Rect &larger )
{
return smaller.x >= larger.x &&
smaller.y >= larger.y &&
smaller.x + smaller.w <= larger.x + larger.w &&
smaller.y + smaller.h <= larger.y + larger.h;
}
void tileset_cache::loader::copy_surface_to_texture( const SDL_Surface_Ptr &surf,
const point &offset, std::vector<texture> &target )
{
cata_assert( surf );
const rect_range<SDL_Rect> input_range( sprite_width, sprite_height,
point( surf->w / sprite_width,
surf->h / sprite_height ) );
const std::shared_ptr<SDL_Texture> texture_ptr = CreateTextureFromSurface( renderer, surf );
cata_assert( texture_ptr );
for( const SDL_Rect rect : input_range ) {
cata_assert( offset.x % sprite_width == 0 );
cata_assert( offset.y % sprite_height == 0 );
const point pos( offset + point( rect.x, rect.y ) );
cata_assert( pos.x % sprite_width == 0 );
cata_assert( pos.y % sprite_height == 0 );
const size_t index = this->offset + ( pos.x / sprite_width ) + ( pos.y / sprite_height ) *
( tile_atlas_width / sprite_width );
cata_assert( index < target.size() );
cata_assert( target[index].dimension() == std::make_pair( 0, 0 ) );
target[index] = texture( texture_ptr, rect );
}
}
void tileset_cache::loader::create_textures_from_tile_atlas( const SDL_Surface_Ptr &tile_atlas,
const point &offset )
{
cata_assert( tile_atlas );
/** perform color filter conversion here */
using tiles_pixel_color_entry = std::tuple<std::vector<texture>*, std::string>;
std::array<tiles_pixel_color_entry, 5> tile_values_data = {{
{ std::make_tuple( &ts.tile_values, "color_pixel_none" ) },
{ std::make_tuple( &ts.shadow_tile_values, "color_pixel_grayscale" ) },
{ std::make_tuple( &ts.night_tile_values, "color_pixel_nightvision" ) },
{ std::make_tuple( &ts.overexposed_tile_values, "color_pixel_overexposed" ) },
{ std::make_tuple( &ts.memory_tile_values, tilecontext->memory_map_mode ) }
}
};
for( tiles_pixel_color_entry &entry : tile_values_data ) {
std::vector<texture> *tile_values = std::get<0>( entry );
color_pixel_function_pointer color_pixel_function = get_color_pixel_function( std::get<1>
( entry ) );
if( !color_pixel_function ) {
// TODO: Move it inside apply_color_filter.
copy_surface_to_texture( tile_atlas, offset, *tile_values );
} else {
copy_surface_to_texture( apply_color_filter( tile_atlas, color_pixel_function ), offset,
*tile_values );
}
}
}
template<typename T>
static void extend_vector_by( std::vector<T> &vec, const size_t additional_size )
{
vec.resize( vec.size() + additional_size );
}
void tileset_cache::loader::load_tileset( const cata_path &img_path, const bool pump_events )
{
cata_assert( sprite_width > 0 );
cata_assert( sprite_height > 0 );
const SDL_Surface_Ptr tile_atlas = load_image( img_path.get_unrelative_path().u8string().c_str() );
cata_assert( tile_atlas );
tile_atlas_width = tile_atlas->w;
if( R >= 0 && R <= 255 && G >= 0 && G <= 255 && B >= 0 && B <= 255 ) {
const Uint32 key = SDL_MapRGB( tile_atlas->format, 0, 0, 0 );
throwErrorIf( SDL_SetColorKey( tile_atlas.get(), SDL_TRUE, key ) != 0,
"SDL_SetColorKey failed" );
throwErrorIf( SDL_SetSurfaceRLE( tile_atlas.get(), 1 ), "SDL_SetSurfaceRLE failed" );
}
SDL_RendererInfo info;
throwErrorIf( SDL_GetRendererInfo( renderer.get(), &info ) != 0, "SDL_GetRendererInfo failed" );
// Software rendering stores textures as surfaces with run-length encoding, which makes
// extracting a part in the middle of the texture slow. Therefore this "simulates" that the
// renderer only supports one tile
// per texture. Each tile will go on its own texture object.
if( info.flags & SDL_RENDERER_SOFTWARE ) {
info.max_texture_width = sprite_width;
info.max_texture_height = sprite_height;
}
// for debugging only: force a very small maximal texture size, as to trigger
// splitting the tile atlas.
#if 0
// +1 to check correct rounding
info.max_texture_width = sprite_width * 10 + 1;
info.max_texture_height = sprite_height * 20 + 1;
#endif
const int min_tile_xcount = 128;
const int min_tile_ycount = min_tile_xcount * 2;
if( info.max_texture_width == 0 ) {
info.max_texture_width = sprite_width * min_tile_xcount;
DebugLog( D_INFO, DC_ALL ) << "SDL_RendererInfo max_texture_width was set to 0. " <<
" Changing it to " << info.max_texture_width;
} else {
throwErrorIf( info.max_texture_width < sprite_width,
"Maximal texture width is smaller than tile width" );
}
if( info.max_texture_height == 0 ) {
info.max_texture_height = sprite_height * min_tile_ycount;
DebugLog( D_INFO, DC_ALL ) << "SDL_RendererInfo max_texture_height was set to 0. " <<
" Changing it to " << info.max_texture_height;
} else {
throwErrorIf( info.max_texture_height < sprite_height,
"Maximal texture height is smaller than tile height" );
}
// Number of tiles in each dimension that fits into a (maximal) SDL texture.
// If the tile atlas contains more than that, we have to split it.
const int max_tile_xcount = info.max_texture_width / sprite_width;
const int max_tile_ycount = info.max_texture_height / sprite_height;
// Range over the tile atlas, wherein each rectangle fits into the maximal
// SDL texture size. In other words: a range over the parts into which the
// tile atlas needs to be split.
const rect_range<SDL_Rect> output_range(
max_tile_xcount * sprite_width,
max_tile_ycount * sprite_height,
point( divide_round_up( tile_atlas->w, info.max_texture_width ),
divide_round_up( tile_atlas->h, info.max_texture_height ) ) );
const int expected_tilecount = ( tile_atlas->w / sprite_width ) *
( tile_atlas->h / sprite_height );
extend_vector_by( ts.tile_values, expected_tilecount );
extend_vector_by( ts.shadow_tile_values, expected_tilecount );
extend_vector_by( ts.night_tile_values, expected_tilecount );
extend_vector_by( ts.overexposed_tile_values, expected_tilecount );
extend_vector_by( ts.memory_tile_values, expected_tilecount );
for( const SDL_Rect sub_rect : output_range ) {
cata_assert( sub_rect.x % sprite_width == 0 );
cata_assert( sub_rect.y % sprite_height == 0 );
cata_assert( sub_rect.w % sprite_width == 0 );
cata_assert( sub_rect.h % sprite_height == 0 );
SDL_Surface_Ptr smaller_surf;
if( is_contained( SDL_Rect{ 0, 0, tile_atlas->w, tile_atlas->h }, sub_rect ) ) {
// can use tile_atlas directly, it is completely contained in the output rectangle
} else {
// Need a temporary surface that contains the parts of the tile atlas that fit
// into sub_rect. But doesn't always need to be as large as sub_rect.
const int w = std::min( tile_atlas->w - sub_rect.x, sub_rect.w );
const int h = std::min( tile_atlas->h - sub_rect.y, sub_rect.h );
smaller_surf = ::create_surface_32( w, h );
cata_assert( smaller_surf );
const SDL_Rect inp{ sub_rect.x, sub_rect.y, w, h };
throwErrorIf( SDL_BlitSurface( tile_atlas.get(), &inp, smaller_surf.get(),
nullptr ) != 0, "SDL_BlitSurface failed" );
}
const SDL_Surface_Ptr &surf_to_use = smaller_surf ? smaller_surf : tile_atlas;
cata_assert( surf_to_use );
create_textures_from_tile_atlas( surf_to_use, point( sub_rect.x, sub_rect.y ) );
if( pump_events ) {
inp_mngr.pump_events();
}
}
size = expected_tilecount;
}
void cata_tiles::set_draw_scale( int scale )
{
cata_assert( tileset_ptr );
const int mult = tileset_ptr->get_tile_pixelscale() * scale;
const int div = 16;
tile_width = tileset_ptr->get_tile_width() * mult / div;
tile_height = tileset_ptr->get_tile_height() * mult / div;
max_tile_extent = tileset_ptr->get_max_tile_extent();
// Rounding down because the extent may be negative
max_tile_extent.p_min.x = divide_round_down( max_tile_extent.p_min.x * mult, div );
max_tile_extent.p_min.y = divide_round_down( max_tile_extent.p_min.y * mult, div );
max_tile_extent.p_max.x = divide_round_down( max_tile_extent.p_max.x * mult, div );
max_tile_extent.p_max.y = divide_round_down( max_tile_extent.p_max.y * mult, div );
zlevel_height = tileset_ptr->get_zlevel_height();
}
void tileset_cache::loader::load( const std::string &tileset_id, const bool precheck,
const bool pump_events )
{
std::string json_conf;
std::string layering;
std::string tileset_path;
cata_path tileset_root;
bool has_layering = true;
const auto tset_iter = TILESETS.find( tileset_id );
if( tset_iter != TILESETS.end() ) {
tileset_root = tset_iter->second;
dbg( D_INFO ) << '"' << tileset_id << '"' << " tileset: found config file path: " <<
tileset_root;
get_tile_information( tileset_root / PATH_INFO::tileset_conf(),
json_conf, tileset_path, layering );
dbg( D_INFO ) << "Current tileset is: " << tileset_id;
} else {
dbg( D_ERROR ) << "Tileset \"" << tileset_id << "\" from options is invalid";
json_conf = PATH_INFO::defaulttilejson();
tileset_path = PATH_INFO::defaulttilepng();
layering = PATH_INFO::defaultlayeringjson();
}
cata_path json_path = tileset_root / fs::u8path( json_conf );
cata_path img_path = tileset_root / fs::u8path( tileset_path );
cata_path layering_path = tileset_root / fs::u8path( layering );
dbg( D_INFO ) << "Attempting to Load LAYERING file " << layering_path;
std::ifstream layering_file( layering_path.get_unrelative_path(),
std::ifstream::in | std::ifstream::binary );
if( !layering_file.good() ) {
has_layering = false;
//throw std::runtime_error(std::string("Failed to open layering info json: ") + layering_path);
}
dbg( D_INFO ) << "Attempting to Load JSON file " << json_path;
std::optional<JsonValue> config_json = json_loader::from_path_opt( json_path );
if( !config_json.has_value() ) {
throw std::runtime_error( std::string( "Failed to open tile info json: " ) +
json_path.generic_u8string() );
}
JsonObject config = ( *config_json ).get_object();
config.allow_omitted_members();
// "tile_info" section must exist.
if( !config.has_member( "tile_info" ) ) {
config.throw_error( "\"tile_info\" missing" );
}
for( const JsonObject curr_info : config.get_array( "tile_info" ) ) {
ts.tile_height = curr_info.get_int( "height" );
ts.tile_width = curr_info.get_int( "width" );
ts.max_tile_extent = half_open_rectangle<point>( point_zero, { ts.tile_width, ts.tile_height } );
ts.zlevel_height = curr_info.get_int( "zlevel_height", 0 );
ts.tile_isometric = curr_info.get_bool( "iso", false );
ts.tile_pixelscale = curr_info.get_float( "pixelscale", 1.0f );
ts.prevent_occlusion_min_dist = curr_info.get_float( "retract_dist_min", -1.0f );
ts.prevent_occlusion_max_dist = curr_info.get_float( "retract_dist_max", 0.0f );
}
if( precheck ) {
config.allow_omitted_members();
return;
}
ts.clear();
// Load tile information if available.
offset = 0;
load_internal( config, tileset_root, img_path, pump_events );
// Load mod tilesets if available
for( const mod_tileset &mts : all_mod_tilesets ) {
// Set sprite_id offset to separate from other tilesets.
sprite_id_offset = offset;
tileset_root = mts.get_base_path();
json_path = mts.get_full_path();
if( !mts.is_compatible( tileset_id ) ) {
dbg( D_ERROR ) << "Mod tileset in \"" << json_path << "\" is not compatible with \""
<< tileset_id << "\".";
continue;
}
dbg( D_INFO ) << "Attempting to Load JSON file " << json_path;
std::optional<JsonValue> mod_config_json_opt = json_loader::from_path_opt( json_path );
if( !mod_config_json_opt.has_value() ) {
throw std::runtime_error( std::string( "Failed to open tile info json: " ) +
json_path.generic_u8string() );
}
JsonValue &mod_config_json = *mod_config_json_opt;
const auto mark_visited = []( const JsonObject & jobj ) {
// These fields have been visited in load_mod_tileset
jobj.get_string_array( "compatibility" );
};
int num_in_file = 1;
if( mod_config_json.test_array() ) {
for( const JsonObject mod_config : mod_config_json.get_array() ) {
if( mod_config.get_string( "type" ) == "mod_tileset" ) {
mark_visited( mod_config );
if( num_in_file == mts.num_in_file() ) {
// visit this if it exists, it's used elsewhere
if( mod_config.has_member( "compatibility" ) ) {
mod_config.get_member( "compatibility" );
}
load_internal( mod_config, tileset_root, img_path, pump_events );
break;
}
num_in_file++;
}
}
} else {
JsonObject mod_config = mod_config_json.get_object();
mark_visited( mod_config );
load_internal( mod_config, tileset_root, img_path, pump_events );
}
}
// loop through all tile ids and eliminate empty/invalid things
for( auto it = ts.tile_ids.begin(); it != ts.tile_ids.end(); ) {
// second is the tile_type describing that id
tile_type &td = it->second;
process_variations_after_loading( td.fg );
process_variations_after_loading( td.bg );
// All tiles need at least foreground or background data, otherwise they are useless.
if( td.bg.empty() && td.fg.empty() ) {
dbg( D_ERROR ) << "tile " << it->first << " has no (valid) foreground nor background";
ts.tile_ids.erase( it++ );
} else {
++it;
}
}
if( !ts.find_tile_type( "unknown" ) ) {
dbg( D_ERROR ) << "The tileset you're using has no 'unknown' tile defined!";
}
ensure_default_item_highlight();
ts.tileset_id = tileset_id;
// set up layering data
if( has_layering ) {
JsonValue layering_json = json_loader::from_path( layering_path );
JsonObject layer_config = layering_json.get_object();
layer_config.allow_omitted_members();
// "variants" section must exist.
if( !layer_config.has_member( "variants" ) ) {
layer_config.throw_error( "\"variants\" missing" );
}
load_layers( layer_config );
}
}
void tileset_cache::loader::load_internal( const JsonObject &config,
const cata_path &tileset_root,
const cata_path &img_path, const bool pump_events )
{
if( config.has_array( "tiles-new" ) ) {
// new system, several entries
// When loading multiple tileset images this defines where
// the tiles from the most recently loaded image start from.
for( const JsonObject tile_part_def : config.get_array( "tiles-new" ) ) {
const cata_path tileset_image_path = tileset_root / tile_part_def.get_string( "file" );
R = -1;
G = -1;
B = -1;
if( tile_part_def.has_object( "transparency" ) ) {
JsonObject tra = tile_part_def.get_object( "transparency" );
R = tra.get_int( "R" );
G = tra.get_int( "G" );
B = tra.get_int( "B" );
}
sprite_width = tile_part_def.get_int( "sprite_width", ts.tile_width );
sprite_height = tile_part_def.get_int( "sprite_height", ts.tile_height );
// Now load the tile definitions for the loaded tileset image.
sprite_offset.x = tile_part_def.get_int( "sprite_offset_x", 0 );
sprite_offset.y = tile_part_def.get_int( "sprite_offset_y", 0 );
sprite_offset_retracted.x = tile_part_def.get_int( "sprite_offset_x_retracted", sprite_offset.x );
sprite_offset_retracted.y = tile_part_def.get_int( "sprite_offset_y_retracted", sprite_offset.y );
sprite_pixelscale = tile_part_def.get_float( "pixelscale", 1.0 );
// Update maximum tile extent
ts.max_tile_extent = half_open_rectangle<point> {
{
std::min( ts.max_tile_extent.p_min.x,
std::min( sprite_offset.x, sprite_offset_retracted.x ) ),
std::min( ts.max_tile_extent.p_min.y,
std::min( sprite_offset.y, sprite_offset_retracted.y ) ),
}, {
std::max( ts.max_tile_extent.p_max.x,
sprite_width + std::max( sprite_offset.x, sprite_offset_retracted.x ) ),
std::max( ts.max_tile_extent.p_max.y,
sprite_height + std::max( sprite_offset.y, sprite_offset_retracted.y ) ),
}
};
// First load the tileset image to get the number of available tiles.
dbg( D_INFO ) << "Attempting to Load Tileset file " << tileset_image_path;
load_tileset( tileset_image_path, pump_events );
load_tilejson_from_file( tile_part_def );
if( tile_part_def.has_member( "ascii" ) ) {
load_ascii( tile_part_def );
}
// Make sure the tile definitions of the next tileset image don't
// override the current ones.
offset += size;
if( pump_events ) {
inp_mngr.pump_events();
}
}
} else {
sprite_width = ts.tile_width;
sprite_height = ts.tile_height;
sprite_offset = point_zero;
sprite_offset_retracted = point_zero;
sprite_pixelscale = 1.0;
R = -1;
G = -1;
B = -1;
// old system, no tile file path entry, only one array of tiles
dbg( D_INFO ) << "Attempting to Load Tileset file " << img_path;
load_tileset( img_path, pump_events );
load_tilejson_from_file( config );
offset = size;
}
// allows a tileset to override the order of mutation images being applied to a character
if( config.has_array( "overlay_ordering" ) ) {
load_overlay_ordering_into_array( config, tileset_mutation_overlay_ordering );
}
// offset should be the total number of sprites loaded from every tileset image
// eliminate any sprite references that are too high to exist
// also eliminate negative sprite references
}
void tileset_cache::loader::load_layers( const JsonObject &config )
{
for( const JsonObject item : config.get_array( "variants" ) ) {
if( item.has_member( "context" ) && ( item.has_array( "item_variants" ) ||
item.has_array( "field_variants" ) ) ) {
std::string context;
context = item.get_string( "context" );
std::vector<layer_variant> item_variants;
std::vector<layer_variant> field_variants;
if( item.has_array( "item_variants" ) ) {
for( const JsonObject vars : item.get_array( "item_variants" ) ) {
if( vars.has_member( "item" ) && vars.has_array( "sprite" ) && vars.has_member( "layer" ) ) {
layer_variant v;
v.id = vars.get_string( "item" );
v.layer = vars.get_int( "layer" );
v.offset = point( vars.get_int( "offset_x", 0 ), vars.get_int( "offset_y", 0 ) );
int total_weight = 0;
for( const JsonObject sprites : vars.get_array( "sprite" ) ) {
std::string id = sprites.get_string( "id" );
int weight = sprites.get_int( "weight", 1 );
v.sprite.emplace( id, weight );
total_weight += weight;
}
v.total_weight = total_weight;
item_variants.push_back( v );
} else {
config.throw_error( "item_variants configured incorrectly" );
}
}
// sort them based on layering so we can draw them correctly
std::sort( item_variants.begin(), item_variants.end(), []( const layer_variant & a,
const layer_variant & b ) {
return a.layer < b.layer;
} );
ts.item_layer_data.emplace( context, item_variants );
}
if( item.has_array( "field_variants" ) ) {
for( const JsonObject vars : item.get_array( "field_variants" ) ) {
if( vars.has_member( "field" ) && vars.has_array( "sprite" ) ) {
layer_variant v;
v.id = vars.get_string( "field" );
v.offset = point( vars.get_int( "offset_x", 0 ), vars.get_int( "offset_y", 0 ) );
int total_weight = 0;
for( const JsonObject sprites : vars.get_array( "sprite" ) ) {
std::string id = sprites.get_string( "id" );
int weight = sprites.get_int( "weight", 1 );
v.sprite.emplace( id, weight );
total_weight += weight;
}
v.total_weight = total_weight;
field_variants.push_back( v );
} else {
config.throw_error( "field_variants configured incorrectly" );
}
}
ts.field_layer_data.emplace( context, field_variants );
}
} else {
config.throw_error( "layering configured incorrectly" );
}
}
}
void tileset_cache::loader::process_variations_after_loading(
weighted_int_list<std::vector<int>> &vs ) const
{
// loop through all of the variations
for( auto &v : vs ) {
// in a given variation, erase any invalid sprite ids
v.obj.erase(
std::remove_if(
v.obj.begin(),
v.obj.end(),
[&]( int id ) {
return id >= offset || id < 0;
} ),
v.obj.end()
);
}
// erase any variations with no valid sprite ids left
vs.erase(
std::remove_if(
vs.begin(),
vs.end(),
[&]( const weighted_object<int, std::vector<int>> &o ) {
return o.obj.empty();
}
),
vs.end()
);
// populate the bookkeeping table used for selecting sprite variations
vs.precalc();
}
void tileset_cache::loader::add_ascii_subtile( tile_type &curr_tile, const std::string &t_id,
int sprite_id, const std::string &s_id )
{
const std::string m_id = t_id + "_" + s_id;
tile_type curr_subtile;
curr_subtile.fg.add( std::vector<int>( {sprite_id} ), 1 );
curr_subtile.rotates = true;
curr_tile.available_subtiles.push_back( s_id );
ts.create_tile_type( m_id, std::move( curr_subtile ) );
}
void tileset_cache::loader::load_ascii( const JsonObject &config )
{
if( !config.has_member( "ascii" ) ) {
config.throw_error( "\"ascii\" section missing" );
}
for( const JsonObject entry : config.get_array( "ascii" ) ) {
load_ascii_set( entry );
}
}
void tileset_cache::loader::load_ascii_set( const JsonObject &entry )
{
// tile for ASCII char 0 is at `in_image_offset`,
// the other ASCII chars follow from there.
const int in_image_offset = entry.get_int( "offset" );
if( in_image_offset >= size ) {
entry.throw_error_at( "offset", "invalid offset (out of range)" );
}