1 #if defined(TILES)
2 #include "cata_tiles.h"
3 
4 #include <algorithm>
5 #include <array>
6 #include <bitset>
7 #include <cmath>
8 #include <cstdint>
9 #include <fstream>
10 #include <iterator>
11 #include <set>
12 #include <stdexcept>
13 #include <tuple>
14 #include <unordered_set>
15 
16 #include "action.h"
17 #include "avatar.h"
18 #include "cached_options.h"
19 #include "calendar.h"
20 #include "cata_assert.h"
21 #include "cata_utility.h"
22 #include "catacharset.h"
23 #include "character.h"
24 #include "character_id.h"
25 #include "clzones.h"
26 #include "color.h"
27 #include "cursesdef.h"
28 #include "cursesport.h"
29 #include "debug.h"
30 #include "field.h"
31 #include "field_type.h"
32 #include "game.h"
33 #include "game_constants.h"
34 #include "int_id.h"
35 #include "item.h"
36 #include "item_factory.h"
37 #include "itype.h"
38 #include "json.h"
39 #include "map.h"
40 #include "map_memory.h"
41 #include "mapdata.h"
42 #include "mod_tileset.h"
43 #include "monster.h"
44 #include "monstergenerator.h"
45 #include "mtype.h"
46 #include "npc.h"
47 #include "optional.h"
48 #include "output.h"
49 #include "overlay_ordering.h"
50 #include "path_info.h"
51 #include "pixel_minimap.h"
52 #include "player.h"
53 #include "rect_range.h"
54 #include "scent_map.h"
55 #include "sdl_utils.h"
56 #include "sdl_wrappers.h"
57 #include "sdltiles.h"
58 #include "sounds.h"
59 #include "string_formatter.h"
60 #include "string_id.h"
61 #include "submap.h"
62 #include "tileray.h"
63 #include "translations.h"
64 #include "trap.h"
65 #include "type_id.h"
66 #include "veh_type.h"
67 #include "vehicle.h"
68 #include "vpart_position.h"
69 #include "weather.h"
70 #include "weighted_list.h"
71 
72 #define dbg(x) DebugLog((x),D_SDL) << __FILE__ << ":" << __LINE__ << ": "
73 
74 static const efftype_id effect_ridden( "ridden" );
75 
76 static const itype_id itype_corpse( "corpse" );
77 
78 static const std::string ITEM_HIGHLIGHT( "highlight_item" );
79 static const std::string ZOMBIE_REVIVAL_INDICATOR( "zombie_revival_indicator" );
80 
81 static const std::array<std::string, 8> multitile_keys = {{
82         "center",
83         "corner",
84         "edge",
85         "t_connection",
86         "end_piece",
87         "unconnected",
88         "open",
89         "broken"
90     }
91 };
92 
93 static const std::string empty_string;
94 static const std::array<std::string, 12> TILE_CATEGORY_IDS = {{
95         "", // C_NONE,
96         "vehicle_part", // C_VEHICLE_PART,
97         "terrain", // C_TERRAIN,
98         "item", // C_ITEM,
99         "furniture", // C_FURNITURE,
100         "trap", // C_TRAP,
101         "field", // C_FIELD,
102         "lighting", // C_LIGHTING,
103         "monster", // C_MONSTER,
104         "bullet", // C_BULLET,
105         "hit_entity", // C_HIT_ENTITY,
106         "weather", // C_WEATHER,
107     }
108 };
109 
110 namespace
111 {
112 
get_ascii_tile_id(const uint32_t sym,const int FG,const int BG)113 std::string get_ascii_tile_id( const uint32_t sym, const int FG, const int BG )
114 {
115     return std::string( { 'A', 'S', 'C', 'I', 'I', '_', static_cast<char>( sym ),
116                           static_cast<char>( FG ), static_cast<char>( BG )
117                         } );
118 }
119 
pixel_minimap_mode_from_string(const std::string & mode)120 pixel_minimap_mode pixel_minimap_mode_from_string( const std::string &mode )
121 {
122     if( mode == "solid" ) {
123         return pixel_minimap_mode::solid;
124     } else if( mode == "squares" ) {
125         return pixel_minimap_mode::squares;
126     } else if( mode == "dots" ) {
127         return pixel_minimap_mode::dots;
128     }
129 
130     debugmsg( "Unsupported pixel minimap mode \"" + mode + "\"." );
131     return pixel_minimap_mode::solid;
132 }
133 
134 } // namespace
135 
msgtype_to_tilecolor(const game_message_type type,const bool bOldMsg)136 static int msgtype_to_tilecolor( const game_message_type type, const bool bOldMsg )
137 {
138     const int iBold = bOldMsg ? 0 : 8;
139 
140     switch( type ) {
141         case m_good:
142             return iBold + catacurses::green;
143         case m_bad:
144             return iBold + catacurses::red;
145         case m_mixed:
146         case m_headshot:
147             return iBold + catacurses::magenta;
148         case m_neutral:
149             return iBold + catacurses::white;
150         case m_warning:
151         case m_critical:
152             return iBold + catacurses::yellow;
153         case m_info:
154         case m_grazing:
155             return iBold + catacurses::blue;
156         default:
157             break;
158     }
159 
160     return -1;
161 }
162 
formatted_text(const std::string & text,const int color,const direction text_direction)163 formatted_text::formatted_text( const std::string &text, const int color,
164                                 const direction text_direction )
165     : text( text ), color( color )
166 {
167     switch( text_direction ) {
168         case direction::NORTHWEST:
169         case direction::WEST:
170         case direction::SOUTHWEST:
171             alignment = text_alignment::right;
172             break;
173         case direction::NORTH:
174         case direction::CENTER:
175         case direction::SOUTH:
176             alignment = text_alignment::center;
177             break;
178         default:
179             alignment = text_alignment::left;
180             break;
181     }
182 }
183 
cata_tiles(const SDL_Renderer_Ptr & renderer,const GeometryRenderer_Ptr & geometry)184 cata_tiles::cata_tiles( const SDL_Renderer_Ptr &renderer, const GeometryRenderer_Ptr &geometry ) :
185     renderer( renderer ),
186     geometry( geometry ),
187     minimap( renderer, geometry )
188 {
189     cata_assert( renderer );
190 
191     tile_height = 0;
192     tile_width = 0;
193     tile_ratiox = 0;
194     tile_ratioy = 0;
195 
196     in_animation = false;
197     do_draw_explosion = false;
198     do_draw_custom_explosion = false;
199     do_draw_bullet = false;
200     do_draw_hit = false;
201     do_draw_line = false;
202     do_draw_cursor = false;
203     do_draw_highlight = false;
204     do_draw_weather = false;
205     do_draw_sct = false;
206     do_draw_zones = false;
207 
208     nv_goggles_activated = false;
209 
210     on_options_changed();
211 }
212 
213 cata_tiles::~cata_tiles() = default;
214 
on_options_changed()215 void cata_tiles::on_options_changed()
216 {
217     memory_map_mode = get_option <std::string>( "MEMORY_MAP_MODE" );
218 
219     pixel_minimap_settings settings;
220 
221     settings.mode = pixel_minimap_mode_from_string( get_option<std::string>( "PIXEL_MINIMAP_MODE" ) );
222     settings.brightness = get_option<int>( "PIXEL_MINIMAP_BRIGHTNESS" );
223     settings.beacon_size = get_option<int>( "PIXEL_MINIMAP_BEACON_SIZE" );
224     settings.beacon_blink_interval = get_option<int>( "PIXEL_MINIMAP_BLINK" );
225     settings.square_pixels = get_option<bool>( "PIXEL_MINIMAP_RATIO" );
226     settings.scale_to_fit = get_option<bool>( "PIXEL_MINIMAP_SCALE_TO_FIT" );
227 
228     minimap->set_settings( settings );
229 }
230 
find_tile_type(const std::string & id) const231 const tile_type *tileset::find_tile_type( const std::string &id ) const
232 {
233     const auto iter = tile_ids.find( id );
234     return iter != tile_ids.end() ? &iter->second : nullptr;
235 }
236 
237 cata::optional<tile_lookup_res>
find_tile_type_by_season(const std::string & id,season_type season) const238 tileset::find_tile_type_by_season( const std::string &id, season_type season ) const
239 {
240     cata_assert( season < season_type::NUM_SEASONS );
241     const auto iter = tile_ids_by_season[season].find( id );
242 
243     if( iter == tile_ids_by_season[season].end() ) {
244         return cata::nullopt;
245     }
246     auto &res = iter->second;
247     if( res.season_tile ) {
248         return *res.season_tile;
249     } else if( res.default_tile ) { // can skip this check, but just in case
250         return tile_lookup_res( iter->first, *res.default_tile );
251     }
252     debugmsg( "empty record found in `tile_ids_by_season` for key: %s", id );
253     return cata::nullopt;
254 }
255 
create_tile_type(const std::string & id,tile_type && new_tile_type)256 tile_type &tileset::create_tile_type( const std::string &id, tile_type &&new_tile_type )
257 {
258     auto inserted = tile_ids.insert( std::make_pair( id, new_tile_type ) ).first;
259     const std::string &inserted_id = inserted->first;
260     tile_type &inserted_tile = inserted->second;
261 
262     // populate cache by season
263     constexpr size_t suffix_len = 15;
264     constexpr char season_suffix[NUM_SEASONS][suffix_len] = {
265         "_season_spring", "_season_summer", "_season_autumn", "_season_winter"
266     };
267     bool has_season_suffix = false;
268     for( int i = 0; i < NUM_SEASONS; i++ ) {
269         if( string_ends_with( id, season_suffix[i] ) ) {
270             has_season_suffix = true;
271             // key is id without _season suffix
272             season_tile_value &value = tile_ids_by_season[i][id.substr( 0,
273                                        id.size() - strlen( season_suffix[i] ) )];
274             // value stores reference to string id with _season suffix
275             value.season_tile = tile_lookup_res( inserted_id, inserted_tile );
276             break;
277         }
278     }
279     // tile doesn't have _season suffix, add it as "default" into all four seasons
280     if( !has_season_suffix ) {
281         for( auto &by_season_map : tile_ids_by_season ) {
282             by_season_map[id].default_tile = &inserted_tile;
283         }
284     }
285 
286     return inserted_tile;
287 }
288 
load_tileset(const std::string & tileset_id,const bool precheck,const bool force)289 void cata_tiles::load_tileset( const std::string &tileset_id, const bool precheck,
290                                const bool force )
291 {
292     if( tileset_ptr && tileset_ptr->get_tileset_id() == tileset_id && !force ) {
293         return;
294     }
295     // TODO: move into clear or somewhere else.
296     // reset the overlay ordering from the previous loaded tileset
297     tileset_mutation_overlay_ordering.clear();
298 
299     // Load the tileset into a separate instance and only set this->tileset_ptr
300     // when the loading has succeeded.
301     std::unique_ptr<tileset> new_tileset_ptr = std::make_unique<tileset>();
302     tileset_loader loader( *new_tileset_ptr, renderer );
303     loader.load( tileset_id, precheck );
304     tileset_ptr = std::move( new_tileset_ptr );
305 
306     set_draw_scale( 16 );
307 
308     minimap->set_type( tile_iso ? pixel_minimap_type::iso : pixel_minimap_type::ortho );
309 }
310 
reinit()311 void cata_tiles::reinit()
312 {
313     set_draw_scale( 16 );
314     RenderClear( renderer );
315 }
316 
get_tile_information(const std::string & config_path,std::string & json_path,std::string & tileset_path)317 static void get_tile_information( const std::string &config_path, std::string &json_path,
318                                   std::string &tileset_path )
319 {
320     const std::string default_json = PATH_INFO::defaulttilejson();
321     const std::string default_tileset = PATH_INFO::defaulttilepng();
322 
323     // Get JSON and TILESET vars from config
324     const auto reader = [&]( std::istream & fin ) {
325         while( !fin.eof() ) {
326             std::string sOption;
327             fin >> sOption;
328 
329             if( sOption.empty() ) {
330                 getline( fin, sOption );
331             } else if( sOption[0] == '#' ) {
332                 // Skip comment
333                 getline( fin, sOption );
334             } else if( sOption.find( "JSON" ) != std::string::npos ) {
335                 fin >> json_path;
336                 dbg( D_INFO ) << "JSON path set to [" << json_path << "].";
337             } else if( sOption.find( "TILESET" ) != std::string::npos ) {
338                 fin >> tileset_path;
339                 dbg( D_INFO ) << "TILESET path set to [" << tileset_path << "].";
340             } else {
341                 getline( fin, sOption );
342             }
343         }
344     };
345 
346     if( !read_from_file( config_path, reader ) ) {
347         json_path = default_json;
348         tileset_path = default_tileset;
349     }
350 
351     if( json_path.empty() ) {
352         json_path = default_json;
353         dbg( D_INFO ) << "JSON set to default [" << json_path << "].";
354     }
355     if( tileset_path.empty() ) {
356         tileset_path = default_tileset;
357         dbg( D_INFO ) << "TILESET set to default [" << tileset_path << "].";
358     }
359 }
360 
361 template<typename PixelConverter>
apply_color_filter(const SDL_Surface_Ptr & original,PixelConverter pixel_converter)362 static SDL_Surface_Ptr apply_color_filter( const SDL_Surface_Ptr &original,
363         PixelConverter pixel_converter )
364 {
365     cata_assert( original );
366     SDL_Surface_Ptr surf = create_surface_32( original->w, original->h );
367     cata_assert( surf );
368     throwErrorIf( SDL_BlitSurface( original.get(), nullptr, surf.get(), nullptr ) != 0,
369                   "SDL_BlitSurface failed" );
370 
371     SDL_Color *pix = static_cast<SDL_Color *>( surf->pixels );
372 
373     for( int y = 0, ey = surf->h; y < ey; ++y ) {
374         for( int x = 0, ex = surf->w; x < ex; ++x, ++pix ) {
375             if( pix->a == 0x00 ) {
376                 // This check significantly improves the performance since
377                 // vast majority of pixels in the tilesets are completely transparent.
378                 continue;
379             }
380             *pix = pixel_converter( *pix );
381         }
382     }
383 
384     return surf;
385 }
386 
is_contained(const SDL_Rect & smaller,const SDL_Rect & larger)387 static bool is_contained( const SDL_Rect &smaller, const SDL_Rect &larger )
388 {
389     return smaller.x >= larger.x &&
390            smaller.y >= larger.y &&
391            smaller.x + smaller.w <= larger.x + larger.w &&
392            smaller.y + smaller.h <= larger.y + larger.h;
393 }
394 
copy_surface_to_texture(const SDL_Surface_Ptr & surf,const point & offset,std::vector<texture> & target)395 void tileset_loader::copy_surface_to_texture( const SDL_Surface_Ptr &surf, const point &offset,
396         std::vector<texture> &target )
397 {
398     cata_assert( surf );
399     const rect_range<SDL_Rect> input_range( sprite_width, sprite_height,
400                                             point( surf->w / sprite_width,
401                                                     surf->h / sprite_height ) );
402 
403     const std::shared_ptr<SDL_Texture> texture_ptr = CreateTextureFromSurface( renderer, surf );
404     cata_assert( texture_ptr );
405 
406     for( const SDL_Rect rect : input_range ) {
407         cata_assert( offset.x % sprite_width == 0 );
408         cata_assert( offset.y % sprite_height == 0 );
409         const point pos( offset + point( rect.x, rect.y ) );
410         cata_assert( pos.x % sprite_width == 0 );
411         cata_assert( pos.y % sprite_height == 0 );
412         const size_t index = this->offset + ( pos.x / sprite_width ) + ( pos.y / sprite_height ) *
413                              ( tile_atlas_width / sprite_width );
414         cata_assert( index < target.size() );
415         cata_assert( target[index].dimension() == std::make_pair( 0, 0 ) );
416         target[index] = texture( texture_ptr, rect );
417     }
418 }
419 
create_textures_from_tile_atlas(const SDL_Surface_Ptr & tile_atlas,const point & offset)420 void tileset_loader::create_textures_from_tile_atlas( const SDL_Surface_Ptr &tile_atlas,
421         const point &offset )
422 {
423     cata_assert( tile_atlas );
424 
425     /** perform color filter conversion here */
426     using tiles_pixel_color_entry = std::tuple<std::vector<texture>*, std::string>;
427     std::array<tiles_pixel_color_entry, 5> tile_values_data = {{
428             { std::make_tuple( &ts.tile_values, "color_pixel_none" ) },
429             { std::make_tuple( &ts.shadow_tile_values, "color_pixel_grayscale" ) },
430             { std::make_tuple( &ts.night_tile_values, "color_pixel_nightvision" ) },
431             { std::make_tuple( &ts.overexposed_tile_values, "color_pixel_overexposed" ) },
432             { std::make_tuple( &ts.memory_tile_values, tilecontext->memory_map_mode ) }
433         }
434     };
435     for( tiles_pixel_color_entry &entry : tile_values_data ) {
436         std::vector<texture> *tile_values = std::get<0>( entry );
437         color_pixel_function_pointer color_pixel_function = get_color_pixel_function( std::get<1>
438                 ( entry ) );
439         if( !color_pixel_function ) {
440             // TODO: Move it inside apply_color_filter.
441             copy_surface_to_texture( tile_atlas, offset, *tile_values );
442         } else {
443             copy_surface_to_texture( apply_color_filter( tile_atlas, color_pixel_function ), offset,
444                                      *tile_values );
445         }
446     }
447 }
448 
449 template<typename T>
extend_vector_by(std::vector<T> & vec,const size_t additional_size)450 static void extend_vector_by( std::vector<T> &vec, const size_t additional_size )
451 {
452     vec.resize( vec.size() + additional_size );
453 }
454 
load_tileset(const std::string & img_path)455 void tileset_loader::load_tileset( const std::string &img_path )
456 {
457     const SDL_Surface_Ptr tile_atlas = load_image( img_path.c_str() );
458     cata_assert( tile_atlas );
459     tile_atlas_width = tile_atlas->w;
460 
461     if( R >= 0 && R <= 255 && G >= 0 && G <= 255 && B >= 0 && B <= 255 ) {
462         const Uint32 key = SDL_MapRGB( tile_atlas->format, 0, 0, 0 );
463         throwErrorIf( SDL_SetColorKey( tile_atlas.get(), SDL_TRUE, key ) != 0,
464                       "SDL_SetColorKey failed" );
465         throwErrorIf( SDL_SetSurfaceRLE( tile_atlas.get(), 1 ), "SDL_SetSurfaceRLE failed" );
466     }
467 
468     SDL_RendererInfo info;
469     throwErrorIf( SDL_GetRendererInfo( renderer.get(), &info ) != 0, "SDL_GetRendererInfo failed" );
470     // Software rendering stores textures as surfaces with run-length encoding, which makes
471     // extracting a part in the middle of the texture slow. Therefore this "simulates" that the
472     // renderer only supports one tile
473     // per texture. Each tile will go on its own texture object.
474     if( info.flags & SDL_RENDERER_SOFTWARE ) {
475         info.max_texture_width = sprite_width;
476         info.max_texture_height = sprite_height;
477     }
478     // for debugging only: force a very small maximal texture size, as to trigger
479     // splitting the tile atlas.
480 #if 0
481     // +1 to check correct rounding
482     info.max_texture_width = sprite_width * 10 + 1;
483     info.max_texture_height = sprite_height * 20 + 1;
484 #endif
485 
486     const int min_tile_xcount = 128;
487     const int min_tile_ycount = min_tile_xcount * 2;
488 
489     if( info.max_texture_width == 0 ) {
490         info.max_texture_width = sprite_width * min_tile_xcount;
491         DebugLog( D_INFO, DC_ALL ) << "SDL_RendererInfo max_texture_width was set to 0. " <<
492                                    " Changing it to " << info.max_texture_width;
493     } else {
494         throwErrorIf( info.max_texture_width < sprite_width,
495                       "Maximal texture width is smaller than tile width" );
496     }
497 
498     if( info.max_texture_height == 0 ) {
499         info.max_texture_height = sprite_height * min_tile_ycount;
500         DebugLog( D_INFO, DC_ALL ) << "SDL_RendererInfo max_texture_height was set to 0. " <<
501                                    " Changing it to " << info.max_texture_height;
502     } else {
503         throwErrorIf( info.max_texture_height < sprite_height,
504                       "Maximal texture height is smaller than tile height" );
505     }
506 
507     // Number of tiles in each dimension that fits into a (maximal) SDL texture.
508     // If the tile atlas contains more than that, we have to split it.
509     const int max_tile_xcount = info.max_texture_width / sprite_width;
510     const int max_tile_ycount = info.max_texture_height / sprite_height;
511     // Range over the tile atlas, wherein each rectangle fits into the maximal
512     // SDL texture size. In other words: a range over the parts into which the
513     // tile atlas needs to be split.
514     const rect_range<SDL_Rect> output_range(
515         max_tile_xcount * sprite_width,
516         max_tile_ycount * sprite_height,
517         point( divide_round_up( tile_atlas->w, info.max_texture_width ),
518                divide_round_up( tile_atlas->h, info.max_texture_height ) ) );
519 
520     const int expected_tilecount = ( tile_atlas->w / sprite_width ) *
521                                    ( tile_atlas->h / sprite_height );
522     extend_vector_by( ts.tile_values, expected_tilecount );
523     extend_vector_by( ts.shadow_tile_values, expected_tilecount );
524     extend_vector_by( ts.night_tile_values, expected_tilecount );
525     extend_vector_by( ts.overexposed_tile_values, expected_tilecount );
526     extend_vector_by( ts.memory_tile_values, expected_tilecount );
527 
528     for( const SDL_Rect sub_rect : output_range ) {
529         cata_assert( sub_rect.x % sprite_width == 0 );
530         cata_assert( sub_rect.y % sprite_height == 0 );
531         cata_assert( sub_rect.w % sprite_width == 0 );
532         cata_assert( sub_rect.h % sprite_height == 0 );
533         SDL_Surface_Ptr smaller_surf;
534 
535         if( is_contained( SDL_Rect{ 0, 0, tile_atlas->w, tile_atlas->h }, sub_rect ) ) {
536             // can use tile_atlas directly, it is completely contained in the output rectangle
537         } else {
538             // Need a temporary surface that contains the parts of the tile atlas that fit
539             // into sub_rect. But doesn't always need to be as large as sub_rect.
540             const int w = std::min( tile_atlas->w - sub_rect.x, sub_rect.w );
541             const int h = std::min( tile_atlas->h - sub_rect.y, sub_rect.h );
542             smaller_surf = ::create_surface_32( w, h );
543             cata_assert( smaller_surf );
544             const SDL_Rect inp{ sub_rect.x, sub_rect.y, w, h };
545             throwErrorIf( SDL_BlitSurface( tile_atlas.get(), &inp, smaller_surf.get(),
546                                            nullptr ) != 0, "SDL_BlitSurface failed" );
547         }
548         const SDL_Surface_Ptr &surf_to_use = smaller_surf ? smaller_surf : tile_atlas;
549         cata_assert( surf_to_use );
550 
551         create_textures_from_tile_atlas( surf_to_use, point( sub_rect.x, sub_rect.y ) );
552     }
553 
554     size = expected_tilecount;
555 }
556 
set_draw_scale(int scale)557 void cata_tiles::set_draw_scale( int scale )
558 {
559     cata_assert( tileset_ptr );
560     tile_width = tileset_ptr->get_tile_width() * tileset_ptr->get_tile_pixelscale() * scale / 16;
561     tile_height = tileset_ptr->get_tile_height() * tileset_ptr->get_tile_pixelscale() * scale / 16;
562 
563     tile_ratiox = ( static_cast<float>( tile_width ) / static_cast<float>( fontwidth ) );
564     tile_ratioy = ( static_cast<float>( tile_height ) / static_cast<float>( fontheight ) );
565 }
566 
load(const std::string & tileset_id,const bool precheck)567 void tileset_loader::load( const std::string &tileset_id, const bool precheck )
568 {
569     std::string json_conf;
570     std::string tileset_path;
571     std::string tileset_root;
572 
573     const auto tset_iter = TILESETS.find( tileset_id );
574     if( tset_iter != TILESETS.end() ) {
575         tileset_root = tset_iter->second;
576         dbg( D_INFO ) << '"' << tileset_id << '"' << " tileset: found config file path: " <<
577                       tileset_root;
578         get_tile_information( tileset_root + '/' + PATH_INFO::tileset_conf(),
579                               json_conf, tileset_path );
580         dbg( D_INFO ) << "Current tileset is: " << tileset_id;
581     } else {
582         dbg( D_ERROR ) << "Tileset \"" << tileset_id << "\" from options is invalid";
583         json_conf = PATH_INFO::defaulttilejson();
584         tileset_path = PATH_INFO::defaulttilepng();
585     }
586 
587     std::string json_path = tileset_root + '/' + json_conf;
588     std::string img_path = tileset_root + '/' + tileset_path;
589 
590     dbg( D_INFO ) << "Attempting to Load JSON file " << json_path;
591     std::ifstream config_file( json_path.c_str(), std::ifstream::in | std::ifstream::binary );
592 
593     if( !config_file.good() ) {
594         throw std::runtime_error( std::string( "Failed to open tile info json: " ) + json_path );
595     }
596 
597     JsonIn config_json( config_file );
598     JsonObject config = config_json.get_object();
599     config.allow_omitted_members();
600 
601     // "tile_info" section must exist.
602     if( !config.has_member( "tile_info" ) ) {
603         config.throw_error( "\"tile_info\" missing" );
604     }
605 
606     for( const JsonObject curr_info : config.get_array( "tile_info" ) ) {
607         ts.tile_height = curr_info.get_int( "height" );
608         ts.tile_width = curr_info.get_int( "width" );
609         tile_iso = curr_info.get_bool( "iso", false );
610         ts.tile_pixelscale = curr_info.get_float( "pixelscale", 1.0f );
611     }
612 
613     if( precheck ) {
614         config.allow_omitted_members();
615         return;
616     }
617 
618     // Load tile information if available.
619     offset = 0;
620     load_internal( config, tileset_root, img_path );
621 
622     // Load mod tilesets if available
623     for( const mod_tileset &mts : all_mod_tilesets ) {
624         // Set sprite_id offset to separate from other tilesets.
625         sprite_id_offset = offset;
626         tileset_root = mts.get_base_path();
627         json_path = mts.get_full_path();
628 
629         if( !mts.is_compatible( tileset_id ) ) {
630             dbg( D_ERROR ) << "Mod tileset in \"" << json_path << "\" is not compatible.";
631             continue;
632         }
633         dbg( D_INFO ) << "Attempting to Load JSON file " << json_path;
634         std::ifstream mod_config_file( json_path.c_str(), std::ifstream::in |
635                                        std::ifstream::binary );
636 
637         if( !mod_config_file.good() ) {
638             throw std::runtime_error( std::string( "Failed to open tile info json: " ) +
639                                       json_path );
640         }
641 
642         JsonIn mod_config_json( mod_config_file );
643 
644         const auto mark_visited = []( const JsonObject & jobj ) {
645             // These fields have been visited in load_mod_tileset
646             jobj.get_string_array( "compatibility" );
647         };
648 
649         int num_in_file = 1;
650         if( mod_config_json.test_array() ) {
651             for( const JsonObject mod_config : mod_config_json.get_array() ) {
652                 if( mod_config.get_string( "type" ) == "mod_tileset" ) {
653                     mark_visited( mod_config );
654                     if( num_in_file == mts.num_in_file() ) {
655                         // visit this if it exists, it's used elsewhere
656                         if( mod_config.has_member( "compatibility" ) ) {
657                             mod_config.get_member( "compatibility" );
658                         }
659                         load_internal( mod_config, tileset_root, img_path );
660                         break;
661                     }
662                     num_in_file++;
663                 }
664             }
665         } else {
666             JsonObject mod_config = mod_config_json.get_object();
667             mark_visited( mod_config );
668             load_internal( mod_config, tileset_root, img_path );
669         }
670     }
671 
672     // loop through all tile ids and eliminate empty/invalid things
673     for( auto it = ts.tile_ids.begin(); it != ts.tile_ids.end(); ) {
674         // second is the tile_type describing that id
675         auto &td = it->second;
676         process_variations_after_loading( td.fg );
677         process_variations_after_loading( td.bg );
678         // All tiles need at least foreground or background data, otherwise they are useless.
679         if( td.bg.empty() && td.fg.empty() ) {
680             dbg( D_ERROR ) << "tile " << it->first << " has no (valid) foreground nor background";
681             ts.tile_ids.erase( it++ );
682         } else {
683             ++it;
684         }
685     }
686 
687     if( !ts.find_tile_type( "unknown" ) ) {
688         dbg( D_ERROR ) << "The tileset you're using has no 'unknown' tile defined!";
689     }
690     ensure_default_item_highlight();
691 
692     ts.tileset_id = tileset_id;
693 }
694 
load_internal(const JsonObject & config,const std::string & tileset_root,const std::string & img_path)695 void tileset_loader::load_internal( const JsonObject &config, const std::string &tileset_root,
696                                     const std::string &img_path )
697 {
698     if( config.has_array( "tiles-new" ) ) {
699         // new system, several entries
700         // When loading multiple tileset images this defines where
701         // the tiles from the most recently loaded image start from.
702         for( const JsonObject tile_part_def : config.get_array( "tiles-new" ) ) {
703             const std::string tileset_image_path = tileset_root + '/' +
704                                                    tile_part_def.get_string( "file" );
705             R = -1;
706             G = -1;
707             B = -1;
708             if( tile_part_def.has_object( "transparency" ) ) {
709                 JsonObject tra = tile_part_def.get_object( "transparency" );
710                 R = tra.get_int( "R" );
711                 G = tra.get_int( "G" );
712                 B = tra.get_int( "B" );
713             }
714             sprite_width = tile_part_def.get_int( "sprite_width", ts.tile_width );
715             sprite_height = tile_part_def.get_int( "sprite_height", ts.tile_height );
716             // Now load the tile definitions for the loaded tileset image.
717             sprite_offset.x = tile_part_def.get_int( "sprite_offset_x", 0 );
718             sprite_offset.y = tile_part_def.get_int( "sprite_offset_y", 0 );
719             // First load the tileset image to get the number of available tiles.
720             dbg( D_INFO ) << "Attempting to Load Tileset file " << tileset_image_path;
721             load_tileset( tileset_image_path );
722             load_tilejson_from_file( tile_part_def );
723             if( tile_part_def.has_member( "ascii" ) ) {
724                 load_ascii( tile_part_def );
725             }
726             // Make sure the tile definitions of the next tileset image don't
727             // override the current ones.
728             offset += size;
729         }
730     } else {
731         sprite_width = ts.tile_width;
732         sprite_height = ts.tile_height;
733         sprite_offset = point_zero;
734         R = -1;
735         G = -1;
736         B = -1;
737         // old system, no tile file path entry, only one array of tiles
738         dbg( D_INFO ) << "Attempting to Load Tileset file " << img_path;
739         load_tileset( img_path );
740         load_tilejson_from_file( config );
741         offset = size;
742     }
743 
744     // allows a tileset to override the order of mutation images being applied to a character
745     if( config.has_array( "overlay_ordering" ) ) {
746         load_overlay_ordering_into_array( config, tileset_mutation_overlay_ordering );
747     }
748 
749     // offset should be the total number of sprites loaded from every tileset image
750     // eliminate any sprite references that are too high to exist
751     // also eliminate negative sprite references
752 }
753 
process_variations_after_loading(weighted_int_list<std::vector<int>> & vs)754 void tileset_loader::process_variations_after_loading( weighted_int_list<std::vector<int>> &vs )
755 {
756     // loop through all of the variations
757     for( auto &v : vs ) {
758         // in a given variation, erase any invalid sprite ids
759         v.obj.erase(
760             std::remove_if(
761                 v.obj.begin(),
762                 v.obj.end(),
763         [&]( int id ) {
764             return id >= offset || id < 0;
765         } ),
766         v.obj.end()
767         );
768     }
769     // erase any variations with no valid sprite ids left
770     vs.erase(
771         std::remove_if(
772             vs.begin(),
773             vs.end(),
774     [&]( const weighted_object<int, std::vector<int>> &o ) {
775         return o.obj.empty();
776     }
777         ),
778     vs.end()
779     );
780     // populate the bookkeeping table used for selecting sprite variations
781     vs.precalc();
782 }
783 
add_ascii_subtile(tile_type & curr_tile,const std::string & t_id,int sprite_id,const std::string & s_id)784 void tileset_loader::add_ascii_subtile( tile_type &curr_tile, const std::string &t_id,
785                                         int sprite_id, const std::string &s_id )
786 {
787     const std::string m_id = t_id + "_" + s_id;
788     tile_type curr_subtile;
789     curr_subtile.fg.add( std::vector<int>( {sprite_id} ), 1 );
790     curr_subtile.rotates = true;
791     curr_tile.available_subtiles.push_back( s_id );
792     ts.create_tile_type( m_id, std::move( curr_subtile ) );
793 }
794 
load_ascii(const JsonObject & config)795 void tileset_loader::load_ascii( const JsonObject &config )
796 {
797     if( !config.has_member( "ascii" ) ) {
798         config.throw_error( "\"ascii\" section missing" );
799     }
800     for( const JsonObject entry : config.get_array( "ascii" ) ) {
801         load_ascii_set( entry );
802     }
803 }
804 
load_ascii_set(const JsonObject & entry)805 void tileset_loader::load_ascii_set( const JsonObject &entry )
806 {
807     // tile for ASCII char 0 is at `in_image_offset`,
808     // the other ASCII chars follow from there.
809     const int in_image_offset = entry.get_int( "offset" );
810     if( in_image_offset >= size ) {
811         entry.throw_error( "invalid offset (out of range)", "offset" );
812     }
813     // color, of the ASCII char. Can be -1 to indicate all/default colors.
814     int FG = -1;
815     const std::string scolor = entry.get_string( "color", "DEFAULT" );
816     if( scolor == "BLACK" ) {
817         FG = catacurses::black;
818     } else if( scolor == "RED" ) {
819         FG = catacurses::red;
820     } else if( scolor == "GREEN" ) {
821         FG = catacurses::green;
822     } else if( scolor == "YELLOW" ) {
823         FG = catacurses::yellow;
824     } else if( scolor == "BLUE" ) {
825         FG = catacurses::blue;
826     } else if( scolor == "MAGENTA" ) {
827         FG = catacurses::magenta;
828     } else if( scolor == "CYAN" ) {
829         FG = catacurses::cyan;
830     } else if( scolor == "WHITE" ) {
831         FG = catacurses::white;
832     } else if( scolor == "DEFAULT" ) {
833         FG = -1;
834     } else {
835         entry.throw_error( "invalid color for ASCII", "color" );
836     }
837     // Add an offset for bold colors (ncurses has this bold attribute,
838     // this mimics it). bold does not apply to default color.
839     if( FG != -1 && entry.get_bool( "bold", false ) ) {
840         FG += 8;
841     }
842     const int base_offset = offset + in_image_offset;
843     // Finally load all 256 ASCII chars (actually extended ASCII)
844     for( int ascii_char = 0; ascii_char < 256; ascii_char++ ) {
845         const int index_in_image = ascii_char + in_image_offset;
846         if( index_in_image < 0 || index_in_image >= size ) {
847             // Out of range is ignored for now.
848             continue;
849         }
850         const std::string id = get_ascii_tile_id( ascii_char, FG, -1 );
851         tile_type curr_tile;
852         curr_tile.offset = sprite_offset;
853         auto &sprites = *( curr_tile.fg.add( std::vector<int>( {index_in_image + offset} ), 1 ) );
854         switch( ascii_char ) {
855             // box bottom/top side (horizontal line)
856             case LINE_OXOX_C:
857                 sprites[0] = 205 + base_offset;
858                 break;
859             // box left/right side (vertical line)
860             case LINE_XOXO_C:
861                 sprites[0] = 186 + base_offset;
862                 break;
863             // box top left
864             case LINE_OXXO_C:
865                 sprites[0] = 201 + base_offset;
866                 break;
867             // box top right
868             case LINE_OOXX_C:
869                 sprites[0] = 187 + base_offset;
870                 break;
871             // box bottom right
872             case LINE_XOOX_C:
873                 sprites[0] = 188 + base_offset;
874                 break;
875             // box bottom left
876             case LINE_XXOO_C:
877                 sprites[0] = 200 + base_offset;
878                 break;
879             // box bottom north T (left, right, up)
880             case LINE_XXOX_C:
881                 sprites[0] = 202 + base_offset;
882                 break;
883             // box bottom east T (up, right, down)
884             case LINE_XXXO_C:
885                 sprites[0] = 208 + base_offset;
886                 break;
887             // box bottom south T (left, right, down)
888             case LINE_OXXX_C:
889                 sprites[0] = 203 + base_offset;
890                 break;
891             // box X (left down up right)
892             case LINE_XXXX_C:
893                 sprites[0] = 206 + base_offset;
894                 break;
895             // box bottom east T (left, down, up)
896             case LINE_XOXX_C:
897                 sprites[0] = 184 + base_offset;
898                 break;
899         }
900         if( ascii_char == LINE_XOXO_C || ascii_char == LINE_OXOX_C ) {
901             curr_tile.rotates = false;
902             curr_tile.multitile = true;
903             add_ascii_subtile( curr_tile, id, 206 + base_offset, "center" );
904             add_ascii_subtile( curr_tile, id, 201 + base_offset, "corner" );
905             add_ascii_subtile( curr_tile, id, 186 + base_offset, "edge" );
906             add_ascii_subtile( curr_tile, id, 203 + base_offset, "t_connection" );
907             add_ascii_subtile( curr_tile, id, 210 + base_offset, "end_piece" );
908             add_ascii_subtile( curr_tile, id, 219 + base_offset, "unconnected" );
909         }
910         ts.create_tile_type( id, std::move( curr_tile ) );
911     }
912 }
913 
load_tilejson_from_file(const JsonObject & config)914 void tileset_loader::load_tilejson_from_file( const JsonObject &config )
915 {
916     if( !config.has_member( "tiles" ) ) {
917         config.throw_error( "\"tiles\" section missing" );
918     }
919 
920     for( const JsonObject entry : config.get_array( "tiles" ) ) {
921         std::vector<std::string> ids;
922         if( entry.has_string( "id" ) ) {
923             ids.push_back( entry.get_string( "id" ) );
924         } else if( entry.has_array( "id" ) ) {
925             ids = entry.get_string_array( "id" );
926         }
927         for( const std::string &t_id : ids ) {
928             tile_type &curr_tile = load_tile( entry, t_id );
929             curr_tile.offset = sprite_offset;
930             bool t_multi = entry.get_bool( "multitile", false );
931             bool t_rota = entry.get_bool( "rotates", t_multi );
932             int t_h3d = entry.get_int( "height_3d", 0 );
933             if( t_multi ) {
934                 // fetch additional tiles
935                 for( const JsonObject subentry : entry.get_array( "additional_tiles" ) ) {
936                     const std::string s_id = subentry.get_string( "id" );
937                     const std::string m_id = t_id + "_" + s_id;
938                     tile_type &curr_subtile = load_tile( subentry, m_id );
939                     curr_subtile.offset = sprite_offset;
940                     curr_subtile.rotates = true;
941                     curr_subtile.height_3d = t_h3d;
942                     curr_tile.available_subtiles.push_back( s_id );
943                 }
944             } else if( entry.has_array( "additional_tiles" ) ) {
945                 try {
946                     entry.throw_error( "Additional tiles defined, but 'multitile' is not true." );
947                 } catch( const JsonError &err ) {
948                     debugmsg( "(json-error)\n%s", err.what() );
949                 }
950             }
951             // write the information of the base tile to curr_tile
952             curr_tile.multitile = t_multi;
953             curr_tile.rotates = t_rota;
954             curr_tile.height_3d = t_h3d;
955             curr_tile.animated = entry.get_bool( "animated", false );
956         }
957     }
958     dbg( D_INFO ) << "Tile Width: " << ts.tile_width << " Tile Height: " << ts.tile_height <<
959                   " Tile Definitions: " << ts.tile_ids.size();
960 }
961 
962 /**
963  * Load a tile definition and add it to the @ref tileset::tile_ids map.
964  * All loaded tiles go into one vector (@ref tileset::tile_values), their index in it is their id.
965  * The JSON data (loaded here) contains tile ids relative to the associated image.
966  * They are translated into global ids by adding the @p offset, which is the number of
967  * previously loaded tiles (excluding the tiles from the associated image).
968  * @param id The id of the new tile definition (which is the key in @ref tileset::tile_ids).
969  * Any existing definition of the same id is overridden.
970  * @return A reference to the loaded tile inside the @ref tileset::tile_ids map.
971  */
load_tile(const JsonObject & entry,const std::string & id)972 tile_type &tileset_loader::load_tile( const JsonObject &entry, const std::string &id )
973 {
974     tile_type curr_subtile;
975 
976     load_tile_spritelists( entry, curr_subtile.fg, "fg" );
977     load_tile_spritelists( entry, curr_subtile.bg, "bg" );
978 
979     return ts.create_tile_type( id, std::move( curr_subtile ) );
980 }
981 
load_tile_spritelists(const JsonObject & entry,weighted_int_list<std::vector<int>> & vs,const std::string & objname)982 void tileset_loader::load_tile_spritelists( const JsonObject &entry,
983         weighted_int_list<std::vector<int>> &vs,
984         const std::string &objname )
985 {
986     // json array indicates rotations or variations
987     if( entry.has_array( objname ) ) {
988         JsonArray g_array = entry.get_array( objname );
989         // int elements of array indicates rotations
990         // create one variation, populate sprite_ids with list of ints
991         if( g_array.test_int() ) {
992             std::vector<int> v;
993             for( const int entry : g_array ) {
994                 const int sprite_id = entry + sprite_id_offset;
995                 if( sprite_id >= 0 ) {
996                     v.push_back( sprite_id );
997                 }
998             }
999             vs.add( v, 1 );
1000         }
1001         // object elements of array indicates variations
1002         // create one variation per object
1003         else if( g_array.test_object() ) {
1004             for( const JsonObject vo : g_array ) {
1005                 std::vector<int> v;
1006                 int weight = vo.get_int( "weight" );
1007                 // negative weight is invalid
1008                 if( weight < 0 ) {
1009                     vo.throw_error( "Invalid weight for sprite variation (<0)", objname );
1010                 }
1011                 // int sprite means one sprite
1012                 if( vo.has_int( "sprite" ) ) {
1013                     const int sprite_id = vo.get_int( "sprite" ) + sprite_id_offset;
1014                     if( sprite_id >= 0 ) {
1015                         v.push_back( sprite_id );
1016                     }
1017                 }
1018                 // array sprite means rotations
1019                 else if( vo.has_array( "sprite" ) ) {
1020                     for( const int entry : vo.get_array( "sprite" ) ) {
1021                         const int sprite_id = entry + sprite_id_offset;
1022                         if( sprite_id >= 0 && sprite_id < size ) {
1023                             v.push_back( sprite_id );
1024                         } else {
1025                             v.push_back( sprite_id + offset );
1026                         }
1027                     }
1028                 }
1029                 if( v.size() != 1 &&
1030                     v.size() != 2 &&
1031                     v.size() != 4 ) {
1032                     vo.throw_error( "Invalid number of sprites (not 1, 2, or 4)", objname );
1033                 }
1034                 vs.add( v, weight );
1035             }
1036         }
1037     }
1038     // json int indicates a single sprite id
1039     else if( entry.has_int( objname ) && entry.get_int( objname ) >= 0 ) {
1040         vs.add( std::vector<int>( {entry.get_int( objname ) + sprite_id_offset} ), 1 );
1041     }
1042 }
1043 
1044 struct tile_render_info {
1045     const tripoint pos{};
1046     // accumulator for 3d tallness of sprites rendered here so far;
1047     int height_3d = 0;
1048     lit_level ll;
1049     bool invisible[5];
tile_render_infotile_render_info1050     tile_render_info( const tripoint &pos, const int height_3d, const lit_level ll,
1051                       const bool ( &invisible )[5] )
1052         : pos( pos ), height_3d( height_3d ), ll( ll ) {
1053         std::copy_n( invisible, 5, this->invisible );
1054     }
1055 };
1056 
draw(const point & dest,const tripoint & center,int width,int height,std::multimap<point,formatted_text> & overlay_strings,color_block_overlay_container & color_blocks)1057 void cata_tiles::draw( const point &dest, const tripoint &center, int width, int height,
1058                        std::multimap<point, formatted_text> &overlay_strings,
1059                        color_block_overlay_container &color_blocks )
1060 {
1061     if( !g ) {
1062         return;
1063     }
1064 
1065 #if defined(__ANDROID__)
1066     // Attempted bugfix for Google Play crash - prevent divide-by-zero if no tile
1067     // width/height specified
1068     if( tile_width == 0 || tile_height == 0 ) {
1069         return;
1070     }
1071 #endif
1072 
1073     {
1074         //set clipping to prevent drawing over stuff we shouldn't
1075         SDL_Rect clipRect = {dest.x, dest.y, width, height};
1076         printErrorIf( SDL_RenderSetClipRect( renderer.get(), &clipRect ) != 0,
1077                       "SDL_RenderSetClipRect failed" );
1078 
1079         //fill render area with black to prevent artifacts where no new pixels are drawn
1080         geometry->rect( renderer, clipRect, SDL_Color() );
1081     }
1082 
1083     point s;
1084     get_window_tile_counts( width, height, s.x, s.y );
1085 
1086     init_light();
1087     map &here = get_map();
1088     const visibility_variables &cache = here.get_visibility_variables_cache();
1089 
1090     const bool iso_mode = tile_iso;
1091 
1092     o = iso_mode ? center.xy() : center.xy() - point( POSX, POSY );
1093 
1094     op = dest;
1095     // Rounding up to include incomplete tiles at the bottom/right edges
1096     screentile_width = divide_round_up( width, tile_width );
1097     screentile_height = divide_round_up( height, tile_height );
1098 
1099     const int min_col = 0;
1100     const int max_col = s.x;
1101     const int min_row = 0;
1102     const int max_row = s.y;
1103 
1104     avatar &you = get_avatar();
1105     //limit the render area to maximum view range (121x121 square centered on player)
1106     const int min_visible_x = you.posx() % SEEX;
1107     const int min_visible_y = you.posy() % SEEY;
1108     const int max_visible_x = ( you.posx() % SEEX ) + ( MAPSIZE - 1 ) * SEEX;
1109     const int max_visible_y = ( you.posy() % SEEY ) + ( MAPSIZE - 1 ) * SEEY;
1110 
1111     const level_cache &ch = here.access_cache( center.z );
1112 
1113     //set up a default tile for the edges outside the render area
1114     visibility_type offscreen_type = visibility_type::DARK;
1115     if( cache.u_is_boomered ) {
1116         offscreen_type = visibility_type::BOOMER_DARK;
1117     }
1118 
1119     //retrieve night vision goggle status once per draw
1120     auto vision_cache = you.get_vision_modes();
1121     nv_goggles_activated = vision_cache[NV_GOGGLES];
1122 
1123     // check that the creature for which we'll draw the visibility map is still alive at that point
1124     if( g->display_overlay_state( ACTION_DISPLAY_VISIBILITY ) &&
1125         g->displaying_visibility_creature ) {
1126         const Creature *creature = g->displaying_visibility_creature;
1127         const auto is_same_creature_predicate = [&creature]( const Creature & c ) {
1128             return creature == &c;
1129         };
1130         if( g->get_creature_if( is_same_creature_predicate ) == nullptr )  {
1131             g->displaying_visibility_creature = nullptr;
1132         }
1133     }
1134     std::unordered_set<point> collision_checkpoints;
1135     std::unordered_set<point> target_points;
1136     for( const wrapped_vehicle &elem : here.get_vehicles() ) {
1137         if( elem.v->get_autodrive_target() != tripoint_zero ) {
1138             target_points.insert( here.getlocal( elem.v->get_autodrive_target().xy() ) );
1139         }
1140         if( elem.v->collision_check_points.empty() ) {
1141             continue;
1142         } else {
1143             for( const point &pt_elem : elem.v->collision_check_points ) {
1144                 collision_checkpoints.insert( here.getlocal( pt_elem ) );
1145             }
1146         }
1147     }
1148     const point half_tile( tile_width / 2, 0 );
1149     const point quarter_tile( tile_width / 4, tile_height / 4 );
1150     if( g->display_overlay_state( ACTION_DISPLAY_VEHICLE_AI ) ) {
1151         for( const point &pt_elem : collision_checkpoints ) {
1152             overlay_strings.emplace( player_to_screen( pt_elem ) + half_tile,
1153                                      formatted_text( "CHECK", catacurses::yellow,
1154                                              direction::NORTH ) );
1155         }
1156         for( const point &pt_elem : target_points ) {
1157             overlay_strings.emplace( player_to_screen( pt_elem ) + half_tile,
1158                                      formatted_text( "TARGET", catacurses::red,
1159                                              direction::NORTH ) );
1160         }
1161     }
1162     const auto apply_visible = [&]( const tripoint & np, const level_cache & ch, map & here ) {
1163         return np.y < min_visible_y || np.y > max_visible_y ||
1164                np.x < min_visible_x || np.x > max_visible_x ||
1165                would_apply_vision_effects( here.get_visibility( ch.visibility_cache[np.x][np.y],
1166                                            cache ) );
1167     };
1168 
1169     for( int row = min_row; row < max_row; row ++ ) {
1170         std::vector<tile_render_info> draw_points;
1171         draw_points.reserve( max_col );
1172         for( int col = min_col; col < max_col; col ++ ) {
1173             int temp_x;
1174             int temp_y;
1175             if( iso_mode ) {
1176                 // in isometric, rows and columns represent a checkerboard screen space,
1177                 // and we place the appropriate tile in valid squares by getting position
1178                 // relative to the screen center.
1179                 if( modulo( row - s.y / 2, 2 ) != modulo( col - s.x / 2, 2 ) ) {
1180                     continue;
1181                 }
1182                 temp_x = divide_round_down( col - row - s.x / 2 + s.y / 2, 2 ) + o.x;
1183                 temp_y = divide_round_down( row + col - s.y / 2 - s.x / 2, 2 ) + o.y;
1184             } else {
1185                 temp_x = col + o.x;
1186                 temp_y = row + o.y;
1187             }
1188             const tripoint pos( temp_x, temp_y, center.z );
1189             const int &x = pos.x;
1190             const int &y = pos.y;
1191 
1192             lit_level ll;
1193             // invisible to normal eyes
1194             bool invisible[5];
1195             invisible[0] = false;
1196 
1197             if( y < min_visible_y || y > max_visible_y || x < min_visible_x || x > max_visible_x ) {
1198                 if( has_memory_at( pos ) ) {
1199                     ll = lit_level::MEMORIZED;
1200                     invisible[0] = true;
1201                 } else if( has_draw_override( pos ) ) {
1202                     ll = lit_level::DARK;
1203                     invisible[0] = true;
1204                 } else {
1205                     apply_vision_effects( pos, offscreen_type );
1206                     continue;
1207                 }
1208             } else {
1209                 ll = ch.visibility_cache[x][y];
1210             }
1211 
1212             // Add scent value to the overlay_strings list for every visible tile when
1213             // displaying scent
1214             if( g->display_overlay_state( ACTION_DISPLAY_SCENT ) && !invisible[0] ) {
1215                 const int scent_value = get_scent().get( pos );
1216                 if( scent_value > 0 ) {
1217                     overlay_strings.emplace( player_to_screen( point( x, y ) ) + half_tile,
1218                                              formatted_text( std::to_string( scent_value ),
1219                                                      8 + catacurses::yellow, direction::NORTH ) );
1220                 }
1221             }
1222 
1223             // Add scent type to the overlay_strings list for every visible tile when
1224             // displaying scent
1225             if( g->display_overlay_state( ACTION_DISPLAY_SCENT_TYPE ) && !invisible[0] ) {
1226                 const scenttype_id scent_type = get_scent().get_type( pos );
1227                 if( !scent_type.is_empty() ) {
1228                     overlay_strings.emplace( player_to_screen( point( x, y ) ) + half_tile,
1229                                              formatted_text( scent_type.c_str(),
1230                                                      8 + catacurses::yellow, direction::NORTH ) );
1231                 }
1232             }
1233 
1234             if( g->display_overlay_state( ACTION_DISPLAY_RADIATION ) ) {
1235                 const auto rad_override = radiation_override.find( pos );
1236                 const bool rad_overridden = rad_override != radiation_override.end();
1237                 if( rad_overridden || !invisible[0] ) {
1238                     const int rad_value = rad_overridden ? rad_override->second :
1239                                           here.get_radiation( pos );
1240                     catacurses::base_color col;
1241                     if( rad_value > 0 ) {
1242                         col = catacurses::green;
1243                     } else {
1244                         col = catacurses::cyan;
1245                     }
1246                     overlay_strings.emplace( player_to_screen( point( x, y ) ) + half_tile,
1247                                              formatted_text( std::to_string( rad_value ),
1248                                                      8 + col, direction::NORTH ) );
1249                 }
1250             }
1251 
1252             // Add temperature value to the overlay_strings list for every visible tile when
1253             // displaying temperature
1254             if( g->display_overlay_state( ACTION_DISPLAY_TEMPERATURE ) && !invisible[0] ) {
1255                 int temp_value = get_weather().get_temperature( pos );
1256                 int ctemp = temp_to_celsius( temp_value );
1257                 short color;
1258                 const short bold = 8;
1259                 if( ctemp > 40 ) {
1260                     color = catacurses::red;
1261                 } else if( ctemp > 25 ) {
1262                     color = catacurses::yellow + bold;
1263                 } else if( ctemp > 10 ) {
1264                     color = catacurses::green + bold;
1265                 } else if( ctemp > 0 ) {
1266                     color = catacurses::white + bold;
1267                 } else if( ctemp > -10 ) {
1268                     color = catacurses::cyan + bold;
1269                 } else {
1270                     color = catacurses::blue + bold;
1271                 }
1272                 if( get_option<std::string>( "USE_CELSIUS" ) == "celsius" ) {
1273                     temp_value = temp_to_celsius( temp_value );
1274                 } else if( get_option<std::string>( "USE_CELSIUS" ) == "kelvin" ) {
1275                     temp_value = temp_to_kelvin( temp_value );
1276 
1277                 }
1278                 overlay_strings.emplace( player_to_screen( point( x, y ) ) + half_tile,
1279                                          formatted_text( std::to_string( temp_value ), color,
1280                                                  direction::NORTH ) );
1281             }
1282 
1283             if( g->display_overlay_state( ACTION_DISPLAY_VISIBILITY ) &&
1284                 g->displaying_visibility_creature && !invisible[0] ) {
1285                 const bool visibility = g->displaying_visibility_creature->sees( pos );
1286 
1287                 // color overlay.
1288                 SDL_Color block_color = visibility ? windowsPalette[catacurses::green] :
1289                                         SDL_Color{ 192, 192, 192, 255 };
1290                 block_color.a = 100;
1291                 color_blocks.first = SDL_BLENDMODE_BLEND;
1292                 color_blocks.second.emplace( player_to_screen( point( x, y ) ), block_color );
1293 
1294                 // overlay string
1295                 std::string visibility_str = visibility ? "+" : "-";
1296                 overlay_strings.emplace( player_to_screen( point( x, y ) ) + quarter_tile,
1297                                          formatted_text( visibility_str, catacurses::black,
1298                                                  direction::NORTH ) );
1299             }
1300 
1301             static std::vector<SDL_Color> lighting_colors;
1302             // color hue in the range of [0..10], 0 being white,  10 being blue
1303             auto draw_debug_tile = [&]( const int color_hue, const std::string & text ) {
1304                 if( lighting_colors.empty() ) {
1305                     SDL_Color white = { 255, 255, 255, 255 };
1306                     SDL_Color blue = { 0, 0, 255, 255 };
1307                     lighting_colors = color_linear_interpolate( white, blue, 9 );
1308                 }
1309                 point tile_pos = player_to_screen( point( x, y ) );
1310 
1311                 // color overlay
1312                 SDL_Color color = lighting_colors[std::min( std::max( 0, color_hue ), 10 )];
1313                 color.a = 100;
1314                 color_blocks.first = SDL_BLENDMODE_BLEND;
1315                 color_blocks.second.emplace( tile_pos, color );
1316 
1317                 // string overlay
1318                 overlay_strings.emplace( tile_pos + quarter_tile, formatted_text( text, catacurses::black,
1319                                          direction::NORTH ) );
1320             };
1321 
1322             if( g->display_overlay_state( ACTION_DISPLAY_LIGHTING ) ) {
1323                 if( g->displaying_lighting_condition == 0 ) {
1324                     const float light = here.ambient_light_at( {x, y, center.z} );
1325                     // note: lighting will be constrained in the [1.0, 11.0] range.
1326                     int intensity = static_cast<int>( std::max( 1.0, LIGHT_AMBIENT_LIT - light + 1.0 ) ) - 1;
1327                     draw_debug_tile( intensity, string_format( "%.1f", light ) );
1328                 }
1329             }
1330 
1331             if( g->display_overlay_state( ACTION_DISPLAY_TRANSPARENCY ) ) {
1332                 const float tr = here.light_transparency( {x, y, center.z} );
1333                 int intensity =  tr <= LIGHT_TRANSPARENCY_SOLID ? 10 :  static_cast<int>
1334                                  ( ( tr - LIGHT_TRANSPARENCY_OPEN_AIR ) * 8 );
1335                 draw_debug_tile( intensity, string_format( "%.2f", tr ) );
1336             }
1337 
1338             if( g->display_overlay_state( ACTION_DISPLAY_REACHABILITY_ZONES ) ) {
1339                 tripoint tile_pos( x, y, center.z );
1340                 int value = here.reachability_cache_value( tile_pos,
1341                             g->debug_rz_display.r_cache_vertical, g->debug_rz_display.quadrant );
1342                 // use color to denote reachability from you to the target tile according to the cache
1343                 bool reachable = here.has_potential_los( you.pos(), tile_pos );
1344                 draw_debug_tile( reachable ? 0 : 6, std::to_string( value ) );
1345             }
1346 
1347             if( !invisible[0] && apply_vision_effects( pos, here.get_visibility( ll, cache ) ) ) {
1348                 const Creature *critter = g->critter_at( pos, true );
1349                 if( has_draw_override( pos ) || has_memory_at( pos ) ||
1350                     ( critter && ( you.sees_with_infrared( *critter ) ||
1351                                    you.sees_with_specials( *critter ) ) ) ) {
1352 
1353                     invisible[0] = true;
1354                 } else {
1355                     continue;
1356                 }
1357             }
1358             for( int i = 0; i < 4; i++ ) {
1359                 const tripoint np = pos + neighborhood[i];
1360                 invisible[1 + i] = apply_visible( np, ch, here );
1361             }
1362 
1363             int height_3d = 0;
1364 
1365             // light level is now used for choosing between grayscale filter and normal lit tiles.
1366             draw_terrain( pos, ll, height_3d, invisible );
1367 
1368             draw_points.emplace_back( pos, height_3d, ll, invisible );
1369         }
1370         const std::array<decltype( &cata_tiles::draw_furniture ), 11> drawing_layers = {{
1371                 &cata_tiles::draw_furniture, &cata_tiles::draw_graffiti, &cata_tiles::draw_trap,
1372                 &cata_tiles::draw_field_or_item, &cata_tiles::draw_vpart_below,
1373                 &cata_tiles::draw_critter_at_below, &cata_tiles::draw_terrain_below,
1374                 &cata_tiles::draw_vpart, &cata_tiles::draw_critter_at,
1375                 &cata_tiles::draw_zone_mark, &cata_tiles::draw_zombie_revival_indicators
1376             }
1377         };
1378         // for each of the drawing layers in order, back to front ...
1379         for( auto f : drawing_layers ) {
1380             // ... draw all the points we drew terrain for, in the same order
1381             for( auto &p : draw_points ) {
1382                 ( this->*f )( p.pos, p.ll, p.height_3d, p.invisible );
1383             }
1384         }
1385         // display number of monsters to spawn in mapgen preview
1386         for( const auto &p : draw_points ) {
1387             const auto mon_override = monster_override.find( p.pos );
1388             if( mon_override != monster_override.end() ) {
1389                 const int count = std::get<1>( mon_override->second );
1390                 const bool more = std::get<2>( mon_override->second );
1391                 if( count > 1 || more ) {
1392                     std::string text = "x" + std::to_string( count );
1393                     if( more ) {
1394                         text += "+";
1395                     }
1396                     overlay_strings.emplace( player_to_screen( p.pos.xy() ) + half_tile,
1397                                              formatted_text( text, catacurses::red,
1398                                                      direction::NORTH ) );
1399                 }
1400             }
1401             if( !p.invisible[0] ) {
1402                 here.check_and_set_seen_cache( p.pos );
1403             }
1404         }
1405     }
1406     // tile overrides are already drawn in the previous code
1407     void_radiation_override();
1408     void_terrain_override();
1409     void_furniture_override();
1410     void_graffiti_override();
1411     void_trap_override();
1412     void_field_override();
1413     void_item_override();
1414     void_vpart_override();
1415     void_draw_below_override();
1416     void_monster_override();
1417 
1418     //Memorize everything the character just saw even if it wasn't displayed.
1419     for( int mem_y = min_visible_y; mem_y <= max_visible_y; mem_y++ ) {
1420         for( int mem_x = min_visible_x; mem_x <= max_visible_x; mem_x++ ) {
1421             half_open_rectangle<point> already_drawn(
1422                 point( min_col, min_row ), point( max_col, max_row ) );
1423             if( iso_mode ) {
1424                 // calculate the screen position according to the drawing code above
1425                 // (division rounded down):
1426 
1427                 // mem_x = ( col - row - sx / 2 + sy / 2 ) / 2 + o.x;
1428                 // mem_y = ( row + col - sy / 2 - sx / 2 ) / 2 + o.y;
1429                 // ( col - sx / 2 ) % 2 = ( row - sy / 2 ) % 2
1430                 // ||
1431                 // \/
1432                 const int col = mem_y + mem_x + s.x / 2 - o.y - o.x;
1433                 const int row = mem_y - mem_x + s.y / 2 - o.y + o.x;
1434                 if( already_drawn.contains( point( col, row ) ) ) {
1435                     continue;
1436                 }
1437             } else {
1438                 // calculate the screen position according to the drawing code above:
1439 
1440                 // mem_x = col + o.x
1441                 // mem_y = row + o.y
1442                 // ||
1443                 // \/
1444                 // col = mem_x - o.x
1445                 // row = mem_y - o.y
1446                 if( already_drawn.contains( point( mem_x, mem_y ) - o ) ) {
1447                     continue;
1448                 }
1449             }
1450             const tripoint p( mem_x, mem_y, center.z );
1451             lit_level lighting = ch.visibility_cache[p.x][p.y];
1452             if( apply_vision_effects( p, here.get_visibility( lighting, cache ) ) ) {
1453                 continue;
1454             }
1455             int height_3d = 0;
1456             bool invisible[5];
1457             invisible[0] = false;
1458             for( int i = 0; i < 4; i++ ) {
1459                 const tripoint np = p + neighborhood[i];
1460                 invisible[1 + i] = apply_visible( np, ch, here );
1461             }
1462             //calling draw to memorize everything.
1463             //bypass cache check in case we learn something new about the terrain's connections
1464             draw_terrain( p, lighting, height_3d, invisible );
1465             if( here.check_seen_cache( p ) ) {
1466                 draw_furniture( p, lighting, height_3d, invisible );
1467                 draw_trap( p, lighting, height_3d, invisible );
1468                 draw_vpart( p, lighting, height_3d, invisible );
1469                 here.check_and_set_seen_cache( p );
1470             }
1471         }
1472     }
1473 
1474     in_animation = do_draw_explosion || do_draw_custom_explosion ||
1475                    do_draw_bullet || do_draw_hit || do_draw_line ||
1476                    do_draw_cursor || do_draw_highlight || do_draw_weather ||
1477                    do_draw_sct || do_draw_zones;
1478 
1479     draw_footsteps_frame();
1480     if( in_animation ) {
1481         if( do_draw_explosion ) {
1482             draw_explosion_frame();
1483         }
1484         if( do_draw_custom_explosion ) {
1485             draw_custom_explosion_frame();
1486         }
1487         if( do_draw_bullet ) {
1488             draw_bullet_frame();
1489         }
1490         if( do_draw_hit ) {
1491             draw_hit_frame();
1492             void_hit();
1493         }
1494         if( do_draw_line ) {
1495             draw_line();
1496             void_line();
1497         }
1498         if( do_draw_weather ) {
1499             draw_weather_frame();
1500             void_weather();
1501         }
1502         if( do_draw_sct ) {
1503             draw_sct_frame( overlay_strings );
1504             void_sct();
1505         }
1506         if( do_draw_zones ) {
1507             draw_zones_frame();
1508             void_zones();
1509         }
1510         if( do_draw_cursor ) {
1511             draw_cursor();
1512             void_cursor();
1513         }
1514         if( do_draw_highlight ) {
1515             draw_highlight();
1516             void_highlight();
1517         }
1518     } else if( you.view_offset != tripoint_zero && !you.in_vehicle ) {
1519         // check to see if player is located at ter
1520         draw_from_id_string( "cursor", C_NONE, empty_string,
1521                              tripoint( g->ter_view_p.xy(), center.z ), 0, 0, lit_level::LIT,
1522                              false );
1523     }
1524     if( you.controlling_vehicle ) {
1525         cata::optional<tripoint> indicator_offset = g->get_veh_dir_indicator_location( true );
1526         if( indicator_offset ) {
1527             draw_from_id_string( "cursor", C_NONE, empty_string,
1528                                  indicator_offset->xy() +
1529                                  tripoint( you.posx(), you.posy(), center.z ),
1530                                  0, 0, lit_level::LIT, false );
1531         }
1532     }
1533 
1534     printErrorIf( SDL_RenderSetClipRect( renderer.get(), nullptr ) != 0,
1535                   "SDL_RenderSetClipRect failed" );
1536 }
1537 
draw_minimap(const point & dest,const tripoint & center,int width,int height)1538 void cata_tiles::draw_minimap( const point &dest, const tripoint &center, int width, int height )
1539 {
1540     minimap->draw( SDL_Rect{ dest.x, dest.y, width, height }, center );
1541 }
1542 
get_window_tile_counts(const int width,const int height,int & columns,int & rows) const1543 void cata_tiles::get_window_tile_counts( const int width, const int height, int &columns,
1544         int &rows ) const
1545 {
1546     if( tile_iso ) {
1547         columns = std::ceil( static_cast<double>( width ) / tile_width ) * 2 + 4;
1548         rows = std::ceil( static_cast<double>( height ) / ( tile_width / 2.0 - 1 ) ) * 2 + 4;
1549     } else {
1550         columns = std::ceil( static_cast<double>( width ) / tile_width );
1551         rows = std::ceil( static_cast<double>( height ) / tile_height );
1552     }
1553 }
1554 
draw_from_id_string(const std::string & id,const tripoint & pos,int subtile,int rota,lit_level ll,bool apply_night_vision_goggles)1555 bool cata_tiles::draw_from_id_string( const std::string &id, const tripoint &pos, int subtile,
1556                                       int rota,
1557                                       lit_level ll, bool apply_night_vision_goggles )
1558 {
1559     int nullint = 0;
1560     return cata_tiles::draw_from_id_string( id, C_NONE, empty_string, pos, subtile,
1561                                             rota, ll, apply_night_vision_goggles, nullint );
1562 }
1563 
draw_from_id_string(const std::string & id,TILE_CATEGORY category,const std::string & subcategory,const tripoint & pos,int subtile,int rota,lit_level ll,bool apply_night_vision_goggles)1564 bool cata_tiles::draw_from_id_string( const std::string &id, TILE_CATEGORY category,
1565                                       const std::string &subcategory, const tripoint &pos,
1566                                       int subtile, int rota, lit_level ll,
1567                                       bool apply_night_vision_goggles )
1568 {
1569     int nullint = 0;
1570     return cata_tiles::draw_from_id_string( id, category, subcategory, pos, subtile, rota,
1571                                             ll, apply_night_vision_goggles, nullint );
1572 }
1573 
draw_from_id_string(const std::string & id,const tripoint & pos,int subtile,int rota,lit_level ll,bool apply_night_vision_goggles,int & height_3d)1574 bool cata_tiles::draw_from_id_string( const std::string &id, const tripoint &pos, int subtile,
1575                                       int rota,
1576                                       lit_level ll, bool apply_night_vision_goggles,
1577                                       int &height_3d )
1578 {
1579     return cata_tiles::draw_from_id_string( id, C_NONE, empty_string, pos, subtile,
1580                                             rota, ll, apply_night_vision_goggles, height_3d );
1581 }
1582 
1583 cata::optional<tile_lookup_res>
find_tile_with_season(const std::string & id) const1584 cata_tiles::find_tile_with_season( const std::string &id ) const
1585 {
1586     const season_type season = season_of_year( calendar::turn );
1587     return tileset_ptr->find_tile_type_by_season( id, season );
1588 }
1589 
1590 template<typename T>
1591 cata::optional<tile_lookup_res>
find_tile_looks_like_by_string_id(const std::string & id,TILE_CATEGORY category,const int looks_like_jumps_limit) const1592 cata_tiles::find_tile_looks_like_by_string_id( const std::string &id, TILE_CATEGORY category,
1593         const int looks_like_jumps_limit ) const
1594 {
1595     const string_id<T> s_id( id );
1596     if( !s_id.is_valid() ) {
1597         return cata::nullopt;
1598     }
1599     const T &obj = s_id.obj();
1600     return find_tile_looks_like( obj.looks_like, category, looks_like_jumps_limit - 1 );
1601 }
1602 
1603 cata::optional<tile_lookup_res>
find_tile_looks_like(const std::string & id,TILE_CATEGORY category,const int looks_like_jumps_limit) const1604 cata_tiles::find_tile_looks_like( const std::string &id, TILE_CATEGORY category,
1605                                   const int looks_like_jumps_limit ) const
1606 {
1607     if( id.empty() || looks_like_jumps_limit <= 0 ) {
1608         return cata::nullopt;
1609     }
1610 
1611     // Note on memory management:
1612     // This method must returns pointers to the objects (std::string *id  and tile_type * tile)
1613     // that are valid when this metod returns. Ideally they should have the lifetime
1614     // that is equal or exceeds lifetime of `this` or `this::tileset_ptr`.
1615     // For example, `id` argument may have shorter lifetime and thus should not be returned!
1616     // The result of `find_tile_with_season` is OK to be returned, because it's guaranteed to
1617     // return pointers to the keys and values that are stored inside the `tileset_ptr`.
1618     const auto tile_with_season = find_tile_with_season( id );
1619     if( tile_with_season ) {
1620         return tile_with_season;
1621     }
1622 
1623     switch( category ) {
1624         case C_FURNITURE:
1625             return find_tile_looks_like_by_string_id<furn_t>( id, category, looks_like_jumps_limit );
1626         case C_TERRAIN:
1627             return find_tile_looks_like_by_string_id<ter_t>( id, category, looks_like_jumps_limit );
1628         case C_FIELD:
1629             return find_tile_looks_like_by_string_id<field_type>( id, category, looks_like_jumps_limit );
1630         case C_MONSTER:
1631             return find_tile_looks_like_by_string_id<mtype>( id, category, looks_like_jumps_limit );
1632 
1633         case C_VEHICLE_PART: {
1634             cata::optional<tile_lookup_res> ret;
1635             // vehicle parts start with vp_ for their tiles, but not their IDs
1636             const vpart_id new_vpid( id.substr( 3 ) );
1637             // check the base id for a vehicle with variant parts
1638             vpart_id base_vpid;
1639             std::string variant_id;
1640             std::tie( base_vpid, variant_id ) = get_vpart_id_variant( new_vpid );
1641             if( base_vpid.is_valid() ) {
1642                 ret = find_tile_looks_like( "vp_" + base_vpid.str(), category, looks_like_jumps_limit - 1 );
1643             }
1644             if( !ret.has_value() ) {
1645                 if( new_vpid.is_valid() ) {
1646                     const vpart_info &new_vpi = new_vpid.obj();
1647                     ret = find_tile_looks_like( "vp_" + new_vpi.looks_like, category, looks_like_jumps_limit - 1 );
1648                 }
1649             }
1650             return ret;
1651         }
1652 
1653         case C_ITEM: {
1654             if( !item::type_is_defined( itype_id( id ) ) ) {
1655                 if( string_starts_with( id, "corpse_" ) ) {
1656                     return find_tile_looks_like(
1657                                "corpse", category, looks_like_jumps_limit - 1
1658                            );
1659                 }
1660                 return cata::nullopt;
1661             }
1662             const itype *new_it = item::find_type( itype_id( id ) );
1663             return find_tile_looks_like( new_it->looks_like.str(), category, looks_like_jumps_limit - 1 );
1664         }
1665 
1666         default:
1667             return cata::nullopt;
1668     }
1669 }
1670 
find_overlay_looks_like(const bool male,const std::string & overlay,std::string & draw_id)1671 bool cata_tiles::find_overlay_looks_like( const bool male, const std::string &overlay,
1672         std::string &draw_id )
1673 {
1674     bool exists = false;
1675 
1676     std::string looks_like;
1677     std::string over_type;
1678 
1679     if( string_starts_with( overlay, "worn_" ) ) {
1680         looks_like = overlay.substr( 5 );
1681         over_type = "worn_";
1682     } else if( string_starts_with( overlay, "wielded_" ) ) {
1683         looks_like = overlay.substr( 8 );
1684         over_type = "wielded_";
1685     } else {
1686         looks_like = overlay;
1687     }
1688 
1689     for( int cnt = 0; cnt < 10 && !looks_like.empty(); cnt++ ) {
1690         draw_id = ( male ? "overlay_male_" : "overlay_female_" ) + over_type + looks_like;
1691         if( tileset_ptr->find_tile_type( draw_id ) ) {
1692             exists = true;
1693             break;
1694         }
1695         draw_id = "overlay_" + over_type + looks_like;
1696         if( tileset_ptr->find_tile_type( draw_id ) ) {
1697             exists = true;
1698             break;
1699         }
1700         if( string_starts_with( looks_like, "mutation_active_" ) ) {
1701             looks_like = "mutation_" + looks_like.substr( 16 );
1702             continue;
1703         }
1704         if( !item::type_is_defined( itype_id( looks_like ) ) ) {
1705             break;
1706         }
1707         const itype *new_it = item::find_type( itype_id( looks_like ) );
1708         looks_like = new_it->looks_like.str();
1709     }
1710     return exists;
1711 }
1712 
draw_from_id_string(const std::string & id,TILE_CATEGORY category,const std::string & subcategory,const tripoint & pos,int subtile,int rota,lit_level ll,bool apply_night_vision_goggles,int & height_3d)1713 bool cata_tiles::draw_from_id_string( const std::string &id, TILE_CATEGORY category,
1714                                       const std::string &subcategory, const tripoint &pos,
1715                                       int subtile, int rota, lit_level ll,
1716                                       bool apply_night_vision_goggles, int &height_3d )
1717 {
1718     // If the ID string does not produce a drawable tile
1719     // it will revert to the "unknown" tile.
1720     // The "unknown" tile is one that is highly visible so you kinda can't miss it :D
1721 
1722     // check to make sure that we are drawing within a valid area
1723     // [0->width|height / tile_width|height]
1724 
1725     half_open_rectangle<point> screen_bounds( o, o + point( screentile_width, screentile_height ) );
1726     if( !tile_iso &&
1727         !screen_bounds.contains( pos.xy() ) ) {
1728         return false;
1729     }
1730 
1731     cata::optional<tile_lookup_res> res = find_tile_looks_like( id, category );
1732     const tile_type *tt = nullptr;
1733     if( res ) {
1734         tt = &( res -> tile() );
1735     }
1736     const std::string &found_id = res ? ( res->id() ) : id;
1737 
1738     if( !tt ) {
1739         uint32_t sym = UNKNOWN_UNICODE;
1740         nc_color col = c_white;
1741         if( category == C_FURNITURE ) {
1742             const furn_str_id fid( found_id );
1743             if( fid.is_valid() ) {
1744                 const furn_t &f = fid.obj();
1745                 sym = f.symbol();
1746                 col = f.color();
1747             }
1748         } else if( category == C_TERRAIN ) {
1749             const ter_str_id tid( found_id );
1750             if( tid.is_valid() ) {
1751                 const ter_t &t = tid.obj();
1752                 sym = t.symbol();
1753                 col = t.color();
1754             }
1755         } else if( category == C_MONSTER ) {
1756             const mtype_id mid( found_id );
1757             if( mid.is_valid() ) {
1758                 const mtype &mt = mid.obj();
1759                 sym = UTF8_getch( mt.sym );
1760                 col = mt.color;
1761             }
1762         } else if( category == C_VEHICLE_PART ) {
1763             const std::pair<std::string,
1764                   std::string> &vpid_data = get_vpart_str_variant( found_id.substr( 3 ) );
1765             const vpart_id vpid( vpid_data.first );
1766             if( vpid.is_valid() ) {
1767                 const vpart_info &v = vpid.obj();
1768 
1769                 if( subtile == open_ ) {
1770                     sym = '\'';
1771                 } else if( subtile == broken ) {
1772                     sym = v.sym_broken;
1773                 } else {
1774                     sym = v.sym;
1775                     if( !vpid_data.second.empty() ) {
1776                         const auto &var_data = v.symbols.find( vpid_data.second );
1777                         if( var_data != v.symbols.end() ) {
1778                             sym = var_data->second;
1779                         }
1780                     }
1781                 }
1782                 subtile = -1;
1783 
1784                 tileray face = tileray( units::from_degrees( rota ) );
1785                 sym = special_symbol( face.dir_symbol( sym ) );
1786                 rota = 0;
1787 
1788                 col = v.color;
1789             }
1790         } else if( category == C_FIELD ) {
1791             const field_type_id fid = field_type_id( found_id );
1792             sym = fid->get_intensity_level().symbol;
1793             // TODO: field intensity?
1794             col = fid->get_intensity_level().color;
1795         } else if( category == C_TRAP ) {
1796             const trap_str_id tmp( found_id );
1797             if( tmp.is_valid() ) {
1798                 const trap &t = tmp.obj();
1799                 sym = t.sym;
1800                 col = t.color;
1801             }
1802         } else if( category == C_ITEM ) {
1803             item tmp;
1804             if( string_starts_with( found_id, "corpse_" ) ) {
1805                 tmp = item( itype_corpse, calendar::turn_zero );
1806             } else {
1807                 tmp = item( found_id, calendar::turn_zero );
1808             }
1809             sym = tmp.symbol().empty() ? ' ' : tmp.symbol().front();
1810             col = tmp.color();
1811         }
1812         // Special cases for walls
1813         switch( sym ) {
1814             case LINE_XOXO:
1815                 sym = LINE_XOXO_C;
1816                 break;
1817             case LINE_OXOX:
1818                 sym = LINE_OXOX_C;
1819                 break;
1820             case LINE_XXOO:
1821                 sym = LINE_XXOO_C;
1822                 break;
1823             case LINE_OXXO:
1824                 sym = LINE_OXXO_C;
1825                 break;
1826             case LINE_OOXX:
1827                 sym = LINE_OOXX_C;
1828                 break;
1829             case LINE_XOOX:
1830                 sym = LINE_XOOX_C;
1831                 break;
1832             case LINE_XXXO:
1833                 sym = LINE_XXXO_C;
1834                 break;
1835             case LINE_XXOX:
1836                 sym = LINE_XXOX_C;
1837                 break;
1838             case LINE_XOXX:
1839                 sym = LINE_XOXX_C;
1840                 break;
1841             case LINE_OXXX:
1842                 sym = LINE_OXXX_C;
1843                 break;
1844             case LINE_XXXX:
1845                 sym = LINE_XXXX_C;
1846                 break;
1847             default:
1848                 // sym goes unchanged
1849                 break;
1850         }
1851         if( sym != 0 && sym < 256 ) {
1852             // see cursesport.cpp, function wattron
1853             const int pairNumber = col.to_color_pair_index();
1854             const cata_cursesport::pairs &colorpair = cata_cursesport::colorpairs[pairNumber];
1855             // What about isBlink?
1856             const bool isBold = col.is_bold();
1857             const int FG = colorpair.FG + ( isBold ? 8 : 0 );
1858             std::string generic_id = get_ascii_tile_id( sym, FG, -1 );
1859 
1860             // do not rotate fallback tiles!
1861             if( sym != LINE_XOXO_C && sym != LINE_OXOX_C ) {
1862                 rota = 0;
1863             }
1864 
1865             if( tileset_ptr->find_tile_type( generic_id ) ) {
1866                 return draw_from_id_string( generic_id, pos, subtile, rota,
1867                                             ll, apply_night_vision_goggles );
1868             }
1869             // Try again without color this time (using default color).
1870             generic_id = get_ascii_tile_id( sym, -1, -1 );
1871             if( tileset_ptr->find_tile_type( generic_id ) ) {
1872                 return draw_from_id_string( generic_id, pos, subtile, rota,
1873                                             ll, apply_night_vision_goggles );
1874             }
1875         }
1876     }
1877 
1878     // if id is not found, try to find a tile for the category+subcategory combination
1879     if( !tt ) {
1880         const std::string &category_id = TILE_CATEGORY_IDS[category];
1881         if( !category_id.empty() && !subcategory.empty() ) {
1882             tt = tileset_ptr->find_tile_type( "unknown_" + category_id + "_" + subcategory );
1883         }
1884     }
1885 
1886     // if at this point we have no tile, try just the category
1887     if( !tt ) {
1888         const std::string &category_id = TILE_CATEGORY_IDS[category];
1889         if( !category_id.empty() ) {
1890             tt = tileset_ptr->find_tile_type( "unknown_" + category_id );
1891         }
1892     }
1893 
1894     // if we still have no tile, we're out of luck, fall back to unknown
1895     if( !tt ) {
1896         tt = tileset_ptr->find_tile_type( "unknown" );
1897     }
1898 
1899     //  this really shouldn't happen, but the tileset creator might have forgotten to define
1900     // an unknown tile
1901     if( !tt ) {
1902         return false;
1903     }
1904 
1905     const tile_type &display_tile = *tt;
1906     // check to see if the display_tile is multitile, and if so if it has the key related to
1907     // subtile
1908     if( subtile != -1 && display_tile.multitile ) {
1909         const auto &display_subtiles = display_tile.available_subtiles;
1910         const auto end = std::end( display_subtiles );
1911         if( std::find( begin( display_subtiles ), end, multitile_keys[subtile] ) != end ) {
1912             // append subtile name to tile and re-find display_tile
1913             return draw_from_id_string( found_id + "_" + multitile_keys[subtile],
1914                                         category, subcategory, pos, -1, rota, ll, apply_night_vision_goggles,
1915                                         height_3d );
1916         }
1917     }
1918 
1919     // translate from player-relative to screen relative tile position
1920     const point screen_pos = player_to_screen( pos.xy() );
1921 
1922     // seed the PRNG to get a reproducible random int
1923     // TODO: faster solution here
1924     unsigned int seed = 0;
1925     map &here = get_map();
1926     // TODO: determine ways other than category to differentiate more types of sprites
1927     switch( category ) {
1928         case C_TERRAIN:
1929         case C_FIELD:
1930         case C_LIGHTING:
1931             // stationary map tiles, seed based on map coordinates
1932             seed = here.getabs( pos ).x + here.getabs( pos ).y * 65536;
1933             break;
1934         case C_VEHICLE_PART:
1935             // vehicle parts, seed based on coordinates within the vehicle
1936             // TODO: also use some vehicle id, for less predictability
1937         {
1938             // new scope for variable declarations
1939             const auto vp_override = vpart_override.find( pos );
1940             const bool vp_overridden = vp_override != vpart_override.end();
1941             if( vp_overridden ) {
1942                 const vpart_id &vp_id = std::get<0>( vp_override->second );
1943                 if( vp_id ) {
1944                     const point &mount = std::get<4>( vp_override->second );
1945                     seed = mount.x + mount.y * 65536;
1946                 }
1947             } else {
1948                 const optional_vpart_position vp = here.veh_at( pos );
1949                 if( vp ) {
1950                     seed = vp->mount().x + vp->mount().y * 65536;
1951                 }
1952             }
1953 
1954             // convert vehicle 360-degree direction (0=E,45=SE, etc) to 4-way tile
1955             // rotation (0=N,1=W,etc)
1956             tileray face = tileray( units::from_degrees( rota ) );
1957             rota = 3 - face.dir4();
1958 
1959         }
1960         break;
1961         case C_FURNITURE: {
1962             // If the furniture is not movable, we'll allow seeding by the position
1963             // since we won't get the behavior that occurs where the tile constantly
1964             // changes when the player grabs the furniture and drags it, causing the
1965             // seed to change.
1966             const furn_str_id fid( found_id );
1967             if( fid.is_valid() ) {
1968                 const furn_t &f = fid.obj();
1969                 if( !f.is_movable() ) {
1970                     seed = here.getabs( pos ).x + here.getabs( pos ).y * 65536;
1971                 }
1972             }
1973         }
1974         break;
1975         case C_ITEM:
1976         case C_TRAP:
1977         case C_NONE:
1978         case C_BULLET:
1979         case C_HIT_ENTITY:
1980         case C_WEATHER:
1981             // TODO: come up with ways to make random sprites consistent for these types
1982             break;
1983         case C_MONSTER:
1984             // FIXME: add persistent id to Creature type, instead of using monster pointer address
1985             if( monster_override.find( pos ) == monster_override.end() ) {
1986                 seed = reinterpret_cast<uintptr_t>( g->critter_at<monster>( pos ) );
1987             }
1988             break;
1989         default:
1990             // player
1991             if( string_starts_with( found_id, "player_" ) ) {
1992                 seed = get_player_character().name[0];
1993                 break;
1994             }
1995             // NPC
1996             if( string_starts_with( found_id, "npc_" ) ) {
1997                 if( npc *const guy = g->critter_at<npc>( pos ) ) {
1998                     seed = guy->getID().get_value();
1999                     break;
2000                 }
2001             }
2002     }
2003 
2004     // make sure we aren't going to rotate the tile if it shouldn't be rotated
2005     if( !display_tile.rotates && !( category == C_NONE ) && !( category == C_MONSTER ) ) {
2006         rota = 0;
2007     }
2008 
2009     unsigned int loc_rand = 0;
2010     // only bother mixing up a hash/random value if the tile has some sprites to randomly pick
2011     // between
2012     if( display_tile.fg.size() > 1 || display_tile.bg.size() > 1 ) {
2013         static const auto rot32 = []( const unsigned int x, const int k ) {
2014             return ( x << k ) | ( x >> ( 32 - k ) );
2015         };
2016         // use a fair mix function to turn the "random" seed into a random int
2017         // taken from public domain code at http://burtleburtle.net/bob/c/lookup3.c 2015/12/11
2018         unsigned int a = seed, b = -seed, c = seed * seed;
2019         c ^= b;
2020         c -= rot32( b, 14 );
2021         a ^= c;
2022         a -= rot32( c, 11 );
2023         b ^= a;
2024         b -= rot32( a, 25 );
2025         c ^= b;
2026         c -= rot32( b, 16 );
2027         a ^= c;
2028         a -= rot32( c, 4 );
2029         b ^= a;
2030         b -= rot32( a, 14 );
2031         c ^= b;
2032         c -= rot32( b, 24 );
2033         loc_rand = c;
2034 
2035         // idle tile animations:
2036         if( display_tile.animated ) {
2037             // idle animations run during the user's turn, and the animation speed
2038             // needs to be defined by the tileset to look good, so we use system clock:
2039             auto now = std::chrono::system_clock::now();
2040             auto now_ms = std::chrono::time_point_cast<std::chrono::milliseconds>( now );
2041             auto value = now_ms.time_since_epoch();
2042             // aiming roughly at the standard 60 frames per second:
2043             int animation_frame = value.count() / 17;
2044             // offset by log_rand so that everything does not blink at the same time:
2045             animation_frame += loc_rand;
2046             int frames_in_loop = display_tile.fg.get_weight();
2047             // loc_rand is actually the weighed index of the selected tile, and
2048             // for animations the "weight" is the number of frames to show the tile for:
2049             loc_rand = animation_frame % frames_in_loop;
2050         }
2051     }
2052 
2053     //draw it!
2054     draw_tile_at( display_tile, screen_pos, loc_rand, rota, ll,
2055                   apply_night_vision_goggles, height_3d );
2056 
2057     return true;
2058 }
2059 
draw_sprite_at(const tile_type & tile,const weighted_int_list<std::vector<int>> & svlist,const point & p,unsigned int loc_rand,bool rota_fg,int rota,lit_level ll,bool apply_night_vision_goggles)2060 bool cata_tiles::draw_sprite_at(
2061     const tile_type &tile, const weighted_int_list<std::vector<int>> &svlist,
2062     const point &p, unsigned int loc_rand, bool rota_fg, int rota, lit_level ll,
2063     bool apply_night_vision_goggles )
2064 {
2065     int nullint = 0;
2066     return cata_tiles::draw_sprite_at( tile, svlist, p, loc_rand, rota_fg, rota, ll,
2067                                        apply_night_vision_goggles, nullint );
2068 }
2069 
draw_sprite_at(const tile_type & tile,const weighted_int_list<std::vector<int>> & svlist,const point & p,unsigned int loc_rand,bool rota_fg,int rota,lit_level ll,bool apply_night_vision_goggles,int & height_3d)2070 bool cata_tiles::draw_sprite_at(
2071     const tile_type &tile, const weighted_int_list<std::vector<int>> &svlist,
2072     const point &p, unsigned int loc_rand, bool rota_fg, int rota, lit_level ll,
2073     bool apply_night_vision_goggles, int &height_3d )
2074 {
2075     const std::vector<int> *picked = svlist.pick( loc_rand );
2076     if( !picked ) {
2077         return true;
2078     }
2079     const std::vector<int> &spritelist = *picked;
2080     if( spritelist.empty() ) {
2081         return true;
2082     }
2083 
2084     int ret = 0;
2085     // blit foreground based on rotation
2086     bool rotate_sprite = false;
2087     int sprite_num = 0;
2088     if( !rota_fg && spritelist.size() == 1 ) {
2089         // don't rotate, a background tile without manual rotations
2090         rotate_sprite = false;
2091         sprite_num = 0;
2092     } else if( spritelist.size() == 1 ) {
2093         // just one tile, apply SDL sprite rotation if not in isometric mode
2094         rotate_sprite = true;
2095         sprite_num = 0;
2096     } else {
2097         // multiple rotated tiles defined, don't apply sprite rotation after picking one
2098         rotate_sprite = false;
2099         // two tiles, tile 0 is N/S, tile 1 is E/W
2100         // four tiles, 0=N, 1=E, 2=S, 3=W
2101         // extending this to more than 4 rotated tiles will require changing rota to degrees
2102         sprite_num = rota % spritelist.size();
2103     }
2104 
2105     const int sprite_index = spritelist[sprite_num];
2106     const texture *sprite_tex = tileset_ptr->get_tile( sprite_index );
2107 
2108     //use night vision colors when in use
2109     //then use low light tile if available
2110     if( ll == lit_level::MEMORIZED ) {
2111         if( const texture *ptr = tileset_ptr->get_memory_tile( sprite_index ) ) {
2112             sprite_tex = ptr;
2113         }
2114     } else if( apply_night_vision_goggles ) {
2115         if( ll != lit_level::LOW ) {
2116             if( const texture *ptr = tileset_ptr->get_overexposed_tile( sprite_index ) ) {
2117                 sprite_tex = ptr;
2118             }
2119         } else {
2120             if( const texture *ptr = tileset_ptr->get_night_tile( sprite_index ) ) {
2121                 sprite_tex = ptr;
2122             }
2123         }
2124     } else if( ll == lit_level::LOW ) {
2125         if( const texture *ptr = tileset_ptr->get_shadow_tile( sprite_index ) ) {
2126             sprite_tex = ptr;
2127         }
2128     }
2129 
2130     int width = 0;
2131     int height = 0;
2132     std::tie( width, height ) = sprite_tex->dimension();
2133 
2134     SDL_Rect destination;
2135     destination.x = p.x + tile.offset.x * tile_width / tileset_ptr->get_tile_width();
2136     destination.y = p.y + ( tile.offset.y - height_3d ) *
2137                     tile_width / tileset_ptr->get_tile_width();
2138     destination.w = width * tile_width / tileset_ptr->get_tile_width();
2139     destination.h = height * tile_height / tileset_ptr->get_tile_height();
2140 
2141     if( rotate_sprite ) {
2142         switch( rota ) {
2143             default:
2144             case 0:
2145                 // unrotated (and 180, with just two sprites)
2146                 ret = sprite_tex->render_copy_ex( renderer, &destination, 0, nullptr,
2147                                                   SDL_FLIP_NONE );
2148                 break;
2149             case 1:
2150                 // 90 degrees (and 270, with just two sprites)
2151 #if defined(_WIN32) && defined(CROSS_LINUX)
2152                 // For an unknown reason, additional offset is required in direct3d mode
2153                 // for cross-compilation from Linux to Windows
2154                 if( direct3d_mode ) {
2155                     destination.y -= 1;
2156                 }
2157 #endif
2158                 if( !tile_iso ) {
2159                     // never rotate isometric tiles
2160                     ret = sprite_tex->render_copy_ex( renderer, &destination, -90, nullptr,
2161                                                       SDL_FLIP_NONE );
2162                 } else {
2163                     ret = sprite_tex->render_copy_ex( renderer, &destination, 0, nullptr,
2164                                                       SDL_FLIP_NONE );
2165                 }
2166                 break;
2167             case 2:
2168                 // 180 degrees, implemented with flips instead of rotation
2169                 if( !tile_iso ) {
2170                     // never flip isometric tiles vertically
2171                     ret = sprite_tex->render_copy_ex( renderer, &destination, 0, nullptr,
2172                                                       static_cast<SDL_RendererFlip>( SDL_FLIP_HORIZONTAL | SDL_FLIP_VERTICAL ) );
2173                 } else {
2174                     ret = sprite_tex->render_copy_ex( renderer, &destination, 0, nullptr,
2175                                                       SDL_FLIP_NONE );
2176                 }
2177                 break;
2178             case 3:
2179                 // 270 degrees
2180 #if defined(_WIN32) && defined(CROSS_LINUX)
2181                 // For an unknown reason, additional offset is required in direct3d mode
2182                 // for cross-compilation from Linux to Windows
2183                 if( direct3d_mode ) {
2184                     destination.x -= 1;
2185                 }
2186 #endif
2187                 if( !tile_iso ) {
2188                     // never rotate isometric tiles
2189                     ret = sprite_tex->render_copy_ex( renderer, &destination, 90, nullptr,
2190                                                       SDL_FLIP_NONE );
2191                 } else {
2192                     ret = sprite_tex->render_copy_ex( renderer, &destination, 0, nullptr,
2193                                                       SDL_FLIP_NONE );
2194                 }
2195                 break;
2196             case 4:
2197                 // flip horizontally
2198                 ret = sprite_tex->render_copy_ex( renderer, &destination, 0, nullptr,
2199                                                   static_cast<SDL_RendererFlip>( SDL_FLIP_HORIZONTAL ) );
2200         }
2201     } else {
2202         // don't rotate, same as case 0 above
2203         ret = sprite_tex->render_copy_ex( renderer, &destination, 0, nullptr, SDL_FLIP_NONE );
2204     }
2205 
2206     printErrorIf( ret != 0, "SDL_RenderCopyEx() failed" );
2207     // this reference passes all the way back up the call chain back to
2208     // cata_tiles::draw() std::vector<tile_render_info> draw_points[].height_3d
2209     // where we are accumulating the height of every sprite stacked up in a tile
2210     height_3d += tile.height_3d;
2211     return true;
2212 }
2213 
draw_tile_at(const tile_type & tile,const point & p,unsigned int loc_rand,int rota,lit_level ll,bool apply_night_vision_goggles,int & height_3d)2214 bool cata_tiles::draw_tile_at(
2215     const tile_type &tile, const point &p, unsigned int loc_rand, int rota,
2216     lit_level ll, bool apply_night_vision_goggles, int &height_3d )
2217 {
2218     draw_sprite_at( tile, tile.bg, p, loc_rand, /*fg:*/ false, rota, ll,
2219                     apply_night_vision_goggles );
2220     draw_sprite_at( tile, tile.fg, p, loc_rand, /*fg:*/ true, rota, ll,
2221                     apply_night_vision_goggles, height_3d );
2222     return true;
2223 }
2224 
would_apply_vision_effects(const visibility_type visibility) const2225 bool cata_tiles::would_apply_vision_effects( const visibility_type visibility ) const
2226 {
2227     return visibility != visibility_type::CLEAR;
2228 }
2229 
apply_vision_effects(const tripoint & pos,const visibility_type visibility)2230 bool cata_tiles::apply_vision_effects( const tripoint &pos,
2231                                        const visibility_type visibility )
2232 {
2233     if( !would_apply_vision_effects( visibility ) ) {
2234         return false;
2235     }
2236     std::string light_name;
2237     switch( visibility ) {
2238         case visibility_type::HIDDEN:
2239             light_name = "lighting_hidden";
2240             break;
2241         case visibility_type::LIT:
2242             light_name = "lighting_lowlight_light";
2243             break;
2244         case visibility_type::BOOMER:
2245             light_name = "lighting_boomered_light";
2246             break;
2247         case visibility_type::BOOMER_DARK:
2248             light_name = "lighting_boomered_dark";
2249             break;
2250         case visibility_type::DARK:
2251             light_name = "lighting_lowlight_dark";
2252             break;
2253         case visibility_type::CLEAR:
2254             // should never happen
2255             break;
2256     }
2257 
2258     // lighting is never rotated, though, could possibly add in random rotation?
2259     draw_from_id_string( light_name, C_LIGHTING, empty_string, pos, 0, 0, lit_level::LIT, false );
2260 
2261     return true;
2262 }
2263 
draw_terrain_below(const tripoint & p,const lit_level,int &,const bool (& invisible)[5])2264 bool cata_tiles::draw_terrain_below( const tripoint &p, const lit_level, int &,
2265                                      const bool ( &invisible )[5] )
2266 {
2267     map &here = get_map();
2268     const auto low_override = draw_below_override.find( p );
2269     const bool low_overridden = low_override != draw_below_override.end();
2270     if( low_overridden ? !low_override->second :
2271         ( invisible[0] || here.dont_draw_lower_floor( p ) ) ) {
2272         return false;
2273     }
2274 
2275     tripoint pbelow = tripoint( p.xy(), p.z - 1 );
2276     SDL_Color tercol = curses_color_to_SDL( c_dark_gray );
2277 
2278     const ter_t &curr_ter = here.ter( pbelow ).obj();
2279     const furn_t &curr_furn = here.furn( pbelow ).obj();
2280     int part_below;
2281     int sizefactor = 2;
2282     const vehicle *veh;
2283     //        const vehicle *veh;
2284     if( curr_furn.has_flag( TFLAG_SEEN_FROM_ABOVE ) ) {
2285         tercol = curses_color_to_SDL( curr_furn.color() );
2286     } else if( curr_furn.movecost < 0 ) {
2287         tercol = curses_color_to_SDL( curr_furn.color() );
2288     } else if( ( veh = here.veh_at_internal( pbelow, part_below ) ) != nullptr ) {
2289         const int roof = veh->roof_at_part( part_below );
2290         const auto vpobst = vpart_position( const_cast<vehicle &>( *veh ),
2291                                             part_below ).obstacle_at_part();
2292         tercol = curses_color_to_SDL( ( roof >= 0 ||
2293                                         vpobst ) ? c_light_gray : c_magenta );
2294         sizefactor = ( roof >= 0 || vpobst ) ? 4 : 2;
2295     } else if( curr_ter.has_flag( TFLAG_SEEN_FROM_ABOVE ) || curr_ter.movecost == 0 ) {
2296         tercol = curses_color_to_SDL( curr_ter.color() );
2297     } else if( !curr_ter.has_flag( TFLAG_NO_FLOOR ) ) {
2298         sizefactor = 4;
2299         tercol = curses_color_to_SDL( curr_ter.color() );
2300     } else {
2301         tercol = curses_color_to_SDL( curr_ter.color() );
2302     }
2303 
2304     SDL_Rect belowRect;
2305     belowRect.h = tile_width / sizefactor;
2306     belowRect.w = tile_height / sizefactor;
2307     if( tile_iso ) {
2308         belowRect.h = ( belowRect.h * 2 ) / 3;
2309         belowRect.w = ( belowRect.w * 3 ) / 4;
2310     }
2311     // translate from player-relative to screen relative tile position
2312     point screen;
2313     if( tile_iso ) {
2314         screen.x = ( ( pbelow.x - o.x ) - ( o.y - pbelow.y ) + screentile_width - 2 ) *
2315                    tile_width / 2 + op.x;
2316         // y uses tile_width because width is definitive for iso tiles
2317         // tile footprints are half as tall as wide, arbitrarily tall
2318         screen.y = ( ( pbelow.y - o.y ) - ( pbelow.x - o.x ) - 4 ) * tile_width / 4 +
2319                    screentile_height * tile_height / 2 + // TODO: more obvious centering math
2320                    op.y;
2321     } else {
2322         screen.x = ( pbelow.x - o.x ) * tile_width + op.x;
2323         screen.y = ( pbelow.y - o.y ) * tile_height + op.y;
2324     }
2325     belowRect.x = screen.x + ( tile_width - belowRect.w ) / 2;
2326     belowRect.y = screen.y + ( tile_height - belowRect.h ) / 2;
2327     if( tile_iso ) {
2328         belowRect.y += tile_height / 8;
2329     }
2330     geometry->rect( renderer, belowRect, tercol );
2331 
2332     return true;
2333 }
2334 
draw_terrain(const tripoint & p,const lit_level ll,int & height_3d,const bool (& invisible)[5])2335 bool cata_tiles::draw_terrain( const tripoint &p, const lit_level ll, int &height_3d,
2336                                const bool ( &invisible )[5] )
2337 {
2338     map &here = get_map();
2339     const auto override = terrain_override.find( p );
2340     const bool overridden = override != terrain_override.end();
2341     bool neighborhood_overridden = overridden;
2342     if( !neighborhood_overridden ) {
2343         for( const point &dir : neighborhood ) {
2344             if( terrain_override.find( p + dir ) != terrain_override.end() ) {
2345                 neighborhood_overridden = true;
2346                 break;
2347             }
2348         }
2349     }
2350     // first memorize the actual terrain
2351     const ter_id &t = here.ter( p );
2352     if( t && !invisible[0] ) {
2353         int subtile = 0;
2354         int rotation = 0;
2355         int connect_group = 0;
2356         if( t.obj().connects( connect_group ) ) {
2357             get_connect_values( p, subtile, rotation, connect_group, {} );
2358             // re-memorize previously seen terrain in case new connections have been seen
2359             here.set_memory_seen_cache_dirty( p );
2360         } else {
2361             get_terrain_orientation( p, rotation, subtile, {}, invisible );
2362             // do something to get other terrain orientation values
2363         }
2364         const std::string &tname = t.id().str();
2365         if( here.check_seen_cache( p ) ) {
2366             get_avatar().memorize_tile( here.getabs( p ), tname, subtile, rotation );
2367         }
2368         // draw the actual terrain if there's no override
2369         if( !neighborhood_overridden ) {
2370             return draw_from_id_string( tname, C_TERRAIN, empty_string, p, subtile, rotation, ll,
2371                                         nv_goggles_activated, height_3d );
2372         }
2373     }
2374     if( invisible[0] ? overridden : neighborhood_overridden ) {
2375         // and then draw the override terrain
2376         const ter_id &t2 = overridden ? override->second : t;
2377         if( t2 ) {
2378             // both the current and neighboring overrides may change the appearance
2379             // of the tile, so always re-calculate it.
2380             int subtile = 0;
2381             int rotation = 0;
2382             int connect_group = 0;
2383             if( t2.obj().connects( connect_group ) ) {
2384                 get_connect_values( p, subtile, rotation, connect_group, terrain_override );
2385             } else {
2386                 get_terrain_orientation( p, rotation, subtile, terrain_override, invisible );
2387             }
2388             const std::string &tname = t2.id().str();
2389             // tile overrides are never memorized
2390             // tile overrides are always shown with full visibility
2391             const lit_level lit = overridden ? lit_level::LIT : ll;
2392             const bool nv = overridden ? false : nv_goggles_activated;
2393             return draw_from_id_string( tname, C_TERRAIN, empty_string, p, subtile, rotation,
2394                                         lit, nv, height_3d );
2395         }
2396     } else if( invisible[0] && has_terrain_memory_at( p ) ) {
2397         // try drawing memory if invisible and not overridden
2398         const auto &t = get_terrain_memory_at( p );
2399         return draw_from_id_string( t.tile, C_TERRAIN, empty_string, p, t.subtile, t.rotation,
2400                                     lit_level::MEMORIZED, nv_goggles_activated, height_3d );
2401     }
2402     return false;
2403 }
2404 
has_memory_at(const tripoint & p) const2405 bool cata_tiles::has_memory_at( const tripoint &p ) const
2406 {
2407     avatar &you = get_avatar();
2408     if( you.should_show_map_memory() ) {
2409         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2410         return !t.tile.empty();
2411     }
2412     return false;
2413 }
2414 
has_terrain_memory_at(const tripoint & p) const2415 bool cata_tiles::has_terrain_memory_at( const tripoint &p ) const
2416 {
2417     avatar &you = get_avatar();
2418     if( you.should_show_map_memory() ) {
2419         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2420         if( string_starts_with( t.tile, "t_" ) ) {
2421             return true;
2422         }
2423     }
2424     return false;
2425 }
2426 
has_furniture_memory_at(const tripoint & p) const2427 bool cata_tiles::has_furniture_memory_at( const tripoint &p ) const
2428 {
2429     avatar &you = get_avatar();
2430     if( you.should_show_map_memory() ) {
2431         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2432         if( string_starts_with( t.tile, "f_" ) ) {
2433             return true;
2434         }
2435     }
2436     return false;
2437 }
2438 
has_trap_memory_at(const tripoint & p) const2439 bool cata_tiles::has_trap_memory_at( const tripoint &p ) const
2440 {
2441     avatar &you = get_avatar();
2442     if( you.should_show_map_memory() ) {
2443         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2444         if( string_starts_with( t.tile, "tr_" ) ) {
2445             return true;
2446         }
2447     }
2448     return false;
2449 }
2450 
has_vpart_memory_at(const tripoint & p) const2451 bool cata_tiles::has_vpart_memory_at( const tripoint &p ) const
2452 {
2453     avatar &you = get_avatar();
2454     if( you.should_show_map_memory() ) {
2455         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2456         if( string_starts_with( t.tile, "vp_" ) ) {
2457             return true;
2458         }
2459     }
2460     return false;
2461 }
2462 
get_terrain_memory_at(const tripoint & p) const2463 memorized_terrain_tile cata_tiles::get_terrain_memory_at( const tripoint &p ) const
2464 {
2465     avatar &you = get_avatar();
2466     if( you.should_show_map_memory() ) {
2467         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2468         if( string_starts_with( t.tile, "t_" ) ) {
2469             return t;
2470         }
2471     }
2472     return {};
2473 }
2474 
get_furniture_memory_at(const tripoint & p) const2475 memorized_terrain_tile cata_tiles::get_furniture_memory_at( const tripoint &p ) const
2476 {
2477     avatar &you = get_avatar();
2478     if( you.should_show_map_memory() ) {
2479         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2480         if( string_starts_with( t.tile, "f_" ) ) {
2481             return t;
2482         }
2483     }
2484     return {};
2485 }
2486 
get_trap_memory_at(const tripoint & p) const2487 memorized_terrain_tile cata_tiles::get_trap_memory_at( const tripoint &p ) const
2488 {
2489     avatar &you = get_avatar();
2490     if( you.should_show_map_memory() ) {
2491         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2492         if( string_starts_with( t.tile, "tr_" ) ) {
2493             return t;
2494         }
2495     }
2496     return {};
2497 }
2498 
get_vpart_memory_at(const tripoint & p) const2499 memorized_terrain_tile cata_tiles::get_vpart_memory_at( const tripoint &p ) const
2500 {
2501     avatar &you = get_avatar();
2502     if( you.should_show_map_memory() ) {
2503         const memorized_terrain_tile t = you.get_memorized_tile( get_map().getabs( p ) );
2504         if( string_starts_with( t.tile, "vp_" ) ) {
2505             return t;
2506         }
2507     }
2508     return {};
2509 }
2510 
draw_furniture(const tripoint & p,const lit_level ll,int & height_3d,const bool (& invisible)[5])2511 bool cata_tiles::draw_furniture( const tripoint &p, const lit_level ll, int &height_3d,
2512                                  const bool ( &invisible )[5] )
2513 {
2514     avatar &you = get_avatar();
2515     const auto override = furniture_override.find( p );
2516     const bool overridden = override != furniture_override.end();
2517     bool neighborhood_overridden = overridden;
2518     if( !neighborhood_overridden ) {
2519         for( const point &dir : neighborhood ) {
2520             if( furniture_override.find( p + dir ) != furniture_override.end() ) {
2521                 neighborhood_overridden = true;
2522                 break;
2523             }
2524         }
2525     }
2526     map &here = get_map();
2527     // first memorize the actual furniture
2528     const furn_id &f = here.furn( p );
2529     if( f && !invisible[0] ) {
2530         const int neighborhood[4] = {
2531             static_cast<int>( here.furn( p + point_south ) ),
2532             static_cast<int>( here.furn( p + point_east ) ),
2533             static_cast<int>( here.furn( p + point_west ) ),
2534             static_cast<int>( here.furn( p + point_north ) )
2535         };
2536         int subtile = 0;
2537         int rotation = 0;
2538         int connect_group = 0;
2539         if( f.obj().connects( connect_group ) ) {
2540             get_furn_connect_values( p, subtile, rotation, connect_group, {} );
2541         } else {
2542             get_tile_values_with_ter( p, f.to_i(), neighborhood, subtile, rotation );
2543         }
2544         const std::string &fname = f.id().str();
2545         if( !( you.get_grab_type() == object_type::FURNITURE
2546                && p == you.pos() + you.grab_point )
2547             && here.check_seen_cache( p ) ) {
2548             you.memorize_tile( here.getabs( p ), fname, subtile, rotation );
2549         }
2550         // draw the actual furniture if there's no override
2551         if( !neighborhood_overridden ) {
2552             return draw_from_id_string( fname, C_FURNITURE, empty_string, p, subtile, rotation, ll,
2553                                         nv_goggles_activated, height_3d );
2554         }
2555     }
2556     if( invisible[0] ? overridden : neighborhood_overridden ) {
2557         // and then draw the override furniture
2558         const furn_id &f2 = overridden ? override->second : f;
2559         if( f2 ) {
2560             // both the current and neighboring overrides may change the appearance
2561             // of the tile, so always re-calculate it.
2562             const auto furn = [&]( const tripoint & q, const bool invis ) -> furn_id {
2563                 const auto it = furniture_override.find( q );
2564                 return it != furniture_override.end() ? it->second :
2565                 ( !overridden || !invis ) ? here.furn( q ) : f_null;
2566             };
2567             const int neighborhood[4] = {
2568                 static_cast<int>( furn( p + point_south, invisible[1] ) ),
2569                 static_cast<int>( furn( p + point_east, invisible[2] ) ),
2570                 static_cast<int>( furn( p + point_west, invisible[3] ) ),
2571                 static_cast<int>( furn( p + point_north, invisible[4] ) )
2572             };
2573             int subtile = 0;
2574             int rotation = 0;
2575             int connect_group = 0;
2576             if( f.obj().connects( connect_group ) ) {
2577                 get_furn_connect_values( p, subtile, rotation, connect_group, {} );
2578             } else {
2579                 get_tile_values_with_ter( p, f.to_i(), neighborhood, subtile, rotation );
2580             }
2581             get_tile_values_with_ter( p, f2.to_i(), neighborhood, subtile, rotation );
2582             const std::string &fname = f2.id().str();
2583             // tile overrides are never memorized
2584             // tile overrides are always shown with full visibility
2585             const lit_level lit = overridden ? lit_level::LIT : ll;
2586             const bool nv = overridden ? false : nv_goggles_activated;
2587             return draw_from_id_string( fname, C_FURNITURE, empty_string, p, subtile, rotation,
2588                                         lit, nv, height_3d );
2589         }
2590     } else if( invisible[0] && has_furniture_memory_at( p ) ) {
2591         // try drawing memory if invisible and not overridden
2592         const auto &t = get_furniture_memory_at( p );
2593         return draw_from_id_string( t.tile, C_FURNITURE, empty_string, p, t.subtile, t.rotation,
2594                                     lit_level::MEMORIZED, nv_goggles_activated, height_3d );
2595     }
2596     return false;
2597 }
2598 
draw_trap(const tripoint & p,const lit_level ll,int & height_3d,const bool (& invisible)[5])2599 bool cata_tiles::draw_trap( const tripoint &p, const lit_level ll, int &height_3d,
2600                             const bool ( &invisible )[5] )
2601 {
2602     const auto override = trap_override.find( p );
2603     const bool overridden = override != trap_override.end();
2604     bool neighborhood_overridden = overridden;
2605     if( !neighborhood_overridden ) {
2606         for( const point &dir : neighborhood ) {
2607             if( trap_override.find( p + dir ) != trap_override.end() ) {
2608                 neighborhood_overridden = true;
2609                 break;
2610             }
2611         }
2612     }
2613 
2614     avatar &you = get_avatar();
2615     map &here = get_map();
2616     // first memorize the actual trap
2617     const trap &tr = here.tr_at( p );
2618     if( !tr.is_null() && !invisible[0] && tr.can_see( p, you ) ) {
2619         const int neighborhood[4] = {
2620             static_cast<int>( here.tr_at( p + point_south ).loadid ),
2621             static_cast<int>( here.tr_at( p + point_east ).loadid ),
2622             static_cast<int>( here.tr_at( p + point_west ).loadid ),
2623             static_cast<int>( here.tr_at( p + point_north ).loadid )
2624         };
2625         int subtile = 0;
2626         int rotation = 0;
2627         get_tile_values( tr.loadid.to_i(), neighborhood, subtile, rotation );
2628         const std::string trname = tr.loadid.id().str();
2629         if( here.check_seen_cache( p ) ) {
2630             you.memorize_tile( here.getabs( p ), trname, subtile, rotation );
2631         }
2632         // draw the actual trap if there's no override
2633         if( !neighborhood_overridden ) {
2634             return draw_from_id_string( trname, C_TRAP, empty_string, p, subtile, rotation, ll,
2635                                         nv_goggles_activated, height_3d );
2636         }
2637     }
2638     if( overridden || ( !invisible[0] && neighborhood_overridden &&
2639                         tr.can_see( p, you ) ) ) {
2640         // and then draw the override trap
2641         const trap_id &tr2 = overridden ? override->second : tr.loadid;
2642         if( tr2 ) {
2643             // both the current and neighboring overrides may change the appearance
2644             // of the tile, so always re-calculate it.
2645             const auto tr_at = [&]( const tripoint & q, const bool invis ) -> trap_id {
2646                 const auto it = trap_override.find( q );
2647                 return it != trap_override.end() ? it->second :
2648                 ( !overridden || !invis ) ? here.tr_at( q ).loadid : tr_null;
2649             };
2650             const int neighborhood[4] = {
2651                 static_cast<int>( tr_at( p + point_south, invisible[1] ) ),
2652                 static_cast<int>( tr_at( p + point_east, invisible[2] ) ),
2653                 static_cast<int>( tr_at( p + point_west, invisible[3] ) ),
2654                 static_cast<int>( tr_at( p + point_north, invisible[4] ) )
2655             };
2656             int subtile = 0;
2657             int rotation = 0;
2658             get_tile_values( tr2.to_i(), neighborhood, subtile, rotation );
2659             const std::string &trname = tr2.id().str();
2660             // tile overrides are never memorized
2661             // tile overrides are always shown with full visibility
2662             const lit_level lit = overridden ? lit_level::LIT : ll;
2663             const bool nv = overridden ? false : nv_goggles_activated;
2664             return draw_from_id_string( trname, C_TRAP, empty_string, p, subtile, rotation,
2665                                         lit, nv, height_3d );
2666         }
2667     } else if( invisible[0] && has_trap_memory_at( p ) ) {
2668         // try drawing memory if invisible and not overridden
2669         const auto &t = get_trap_memory_at( p );
2670         return draw_from_id_string( t.tile, C_TRAP, empty_string, p, t.subtile, t.rotation,
2671                                     lit_level::MEMORIZED, nv_goggles_activated, height_3d );
2672     }
2673     return false;
2674 }
2675 
draw_graffiti(const tripoint & p,const lit_level ll,int & height_3d,const bool (& invisible)[5])2676 bool cata_tiles::draw_graffiti( const tripoint &p, const lit_level ll, int &height_3d,
2677                                 const bool ( &invisible )[5] )
2678 {
2679     const auto override = graffiti_override.find( p );
2680     const bool overridden = override != graffiti_override.end();
2681     if( overridden ? !override->second : ( invisible[0] || !get_map().has_graffiti_at( p ) ) ) {
2682         return false;
2683     }
2684     const lit_level lit = overridden ? lit_level::LIT : ll;
2685     return draw_from_id_string( "graffiti", C_NONE, empty_string, p, 0, 0, lit, false, height_3d );
2686 }
2687 
draw_field_or_item(const tripoint & p,const lit_level ll,int & height_3d,const bool (& invisible)[5])2688 bool cata_tiles::draw_field_or_item( const tripoint &p, const lit_level ll, int &height_3d,
2689                                      const bool ( &invisible )[5] )
2690 {
2691     const auto fld_override = field_override.find( p );
2692     const bool fld_overridden = fld_override != field_override.end();
2693     map &here = get_map();
2694     const field_type_id &fld = fld_overridden ?
2695                                fld_override->second : here.field_at( p ).displayed_field_type();
2696     bool ret_draw_field = false;
2697     bool ret_draw_items = false;
2698     if( ( fld_overridden || !invisible[0] ) && fld.obj().display_field ) {
2699         const lit_level lit = fld_overridden ? lit_level::LIT : ll;
2700         const bool nv = fld_overridden ? false : nv_goggles_activated;
2701 
2702         auto field_at = [&]( const tripoint & q, const bool invis ) -> field_type_id {
2703             const auto it = field_override.find( q );
2704             return it != field_override.end() ? it->second :
2705             ( !fld_overridden || !invis ) ? here.field_at( q ).displayed_field_type() : fd_null;
2706         };
2707         // for rotation information
2708         const int neighborhood[4] = {
2709             static_cast<int>( field_at( p + point_south, invisible[1] ) ),
2710             static_cast<int>( field_at( p + point_east, invisible[2] ) ),
2711             static_cast<int>( field_at( p + point_west, invisible[3] ) ),
2712             static_cast<int>( field_at( p + point_north, invisible[4] ) )
2713         };
2714 
2715         int subtile = 0;
2716         int rotation = 0;
2717         get_tile_values( fld.to_i(), neighborhood, subtile, rotation );
2718 
2719         ret_draw_field = draw_from_id_string( fld.id().str(), C_FIELD, empty_string, p, subtile,
2720                                               rotation, lit, nv );
2721     }
2722     if( fld.obj().display_items ) {
2723         const auto it_override = item_override.find( p );
2724         const bool it_overridden = it_override != item_override.end();
2725 
2726         itype_id it_id;
2727         mtype_id mon_id;
2728         bool hilite = false;
2729         const itype *it_type;
2730         if( it_overridden ) {
2731             it_id = std::get<0>( it_override->second );
2732             mon_id = std::get<1>( it_override->second );
2733             hilite = std::get<2>( it_override->second );
2734             it_type = item::find_type( it_id );
2735         } else if( !invisible[0] && here.sees_some_items( p, get_player_character() ) ) {
2736             const maptile &tile = here.maptile_at( p );
2737             const item &itm = tile.get_uppermost_item();
2738             const mtype *const mon = itm.get_mtype();
2739             it_id = itm.typeId();
2740             mon_id = mon ? mon->id : mtype_id::NULL_ID();
2741             hilite = tile.get_item_count() > 1;
2742             it_type = itm.type;
2743         } else {
2744             it_type = nullptr;
2745         }
2746         if( it_type && !it_id.is_null() ) {
2747             const std::string disp_id = it_id == itype_corpse && mon_id ?
2748                                         "corpse_" + mon_id.str() : it_id.str();
2749             const std::string it_category = it_type->get_item_type_string();
2750             const lit_level lit = it_overridden ? lit_level::LIT : ll;
2751             const bool nv = it_overridden ? false : nv_goggles_activated;
2752 
2753             ret_draw_items = draw_from_id_string( disp_id, C_ITEM, it_category, p, 0, 0, lit,
2754                                                   nv, height_3d );
2755             if( ret_draw_items && hilite ) {
2756                 draw_item_highlight( p );
2757             }
2758         }
2759     }
2760     return ret_draw_field && ret_draw_items;
2761 }
2762 
draw_vpart_below(const tripoint & p,const lit_level,int &,const bool (& invisible)[5])2763 bool cata_tiles::draw_vpart_below( const tripoint &p, const lit_level /*ll*/, int &/*height_3d*/,
2764                                    const bool ( &invisible )[5] )
2765 {
2766     const auto low_override = draw_below_override.find( p );
2767     const bool low_overridden = low_override != draw_below_override.end();
2768     if( low_overridden ? !low_override->second : ( invisible[0] ||
2769             get_map().dont_draw_lower_floor( p ) ) ) {
2770         return false;
2771     }
2772     tripoint pbelow( p.xy(), p.z - 1 );
2773     int height_3d_below = 0;
2774     bool below_invisible[5];
2775     std::fill_n( below_invisible, 5, false );
2776     return draw_vpart( pbelow, lit_level::LOW, height_3d_below, below_invisible );
2777 }
2778 
draw_vpart(const tripoint & p,lit_level ll,int & height_3d,const bool (& invisible)[5])2779 bool cata_tiles::draw_vpart( const tripoint &p, lit_level ll, int &height_3d,
2780                              const bool ( &invisible )[5] )
2781 {
2782     const auto override = vpart_override.find( p );
2783     const bool overridden = override != vpart_override.end();
2784     map &here = get_map();
2785     // first memorize the actual vpart
2786     const optional_vpart_position vp = here.veh_at( p );
2787     if( vp && !invisible[0] ) {
2788         const vehicle &veh = vp->vehicle();
2789         const int veh_part = vp->part_index();
2790         // Gets the visible part, should work fine once tileset vp_ids are updated to work
2791         // with the vehicle part json ids
2792         // get the vpart_id
2793         char part_mod = 0;
2794         const std::string &vp_id = veh.part_id_string( veh_part, part_mod );
2795         const int subtile = part_mod == 1 ? open_ : part_mod == 2 ? broken : 0;
2796         const int rotation = std::round( to_degrees( veh.face.dir() ) );
2797         const std::string vpname = "vp_" + vp_id;
2798         avatar &you = get_avatar();
2799         if( !veh.forward_velocity() && !veh.player_in_control( you )
2800             && !( you.get_grab_type() == object_type::VEHICLE
2801                   && veh.get_points().count( you.pos() + you.grab_point ) )
2802             && here.check_seen_cache( p ) ) {
2803             you.memorize_tile( here.getabs( p ), vpname, subtile, rotation );
2804         }
2805         if( !overridden ) {
2806             const cata::optional<vpart_reference> cargopart = vp.part_with_feature( "CARGO", true );
2807             const bool draw_highlight = cargopart &&
2808                                         !veh.get_items( cargopart->part_index() ).empty();
2809             const bool ret = draw_from_id_string( vpname, C_VEHICLE_PART, empty_string, p,
2810                                                   subtile, rotation, ll, nv_goggles_activated,
2811                                                   height_3d );
2812             if( ret && draw_highlight ) {
2813                 draw_item_highlight( p );
2814             }
2815             return ret;
2816         }
2817     }
2818     if( overridden ) {
2819         // and then draw the override vpart
2820         const vpart_id &vp2 = std::get<0>( override->second );
2821         if( vp2 ) {
2822             const char part_mod = std::get<1>( override->second );
2823             const int subtile = part_mod == 1 ? open_ : part_mod == 2 ? broken : 0;
2824             const units::angle rotation = std::get<2>( override->second );
2825             const int draw_highlight = std::get<3>( override->second );
2826             const std::string vpname = "vp_" + vp2.str();
2827             // tile overrides are never memorized
2828             // tile overrides are always shown with full visibility
2829             const bool ret = draw_from_id_string( vpname, C_VEHICLE_PART, empty_string, p,
2830                                                   subtile, to_degrees( rotation ), lit_level::LIT,
2831                                                   false, height_3d );
2832             if( ret && draw_highlight ) {
2833                 draw_item_highlight( p );
2834             }
2835             return ret;
2836         }
2837     } else if( invisible[0] && has_vpart_memory_at( p ) ) {
2838         // try drawing memory if invisible and not overridden
2839         const auto &t = get_vpart_memory_at( p );
2840         return draw_from_id_string( t.tile, C_VEHICLE_PART, empty_string, p, t.subtile, t.rotation,
2841                                     lit_level::MEMORIZED, nv_goggles_activated, height_3d );
2842     }
2843     return false;
2844 }
2845 
draw_critter_at_below(const tripoint & p,const lit_level,int &,const bool (& invisible)[5])2846 bool cata_tiles::draw_critter_at_below( const tripoint &p, const lit_level, int &,
2847                                         const bool ( &invisible )[5] )
2848 {
2849     // Check if we even need to draw below. If not, bail.
2850     const auto low_override = draw_below_override.find( p );
2851     const bool low_overridden = low_override != draw_below_override.end();
2852     if( low_overridden ? !low_override->second : ( invisible[0] ||
2853             get_map().dont_draw_lower_floor( p ) ) ) {
2854         return false;
2855     }
2856 
2857     tripoint pbelow( p.xy(), p.z - 1 );
2858 
2859     // Get the critter at the location below. If there isn't one,
2860     // we can bail.
2861     const Creature *critter = g->critter_at( pbelow, true );
2862     if( critter == nullptr ) {
2863         return false;
2864     }
2865 
2866     Character &you = get_player_character();
2867     // Check if the player can actually see the critter. We don't care if
2868     // it's via infrared or not, just whether or not they're seen. If not,
2869     // we can bail.
2870     if( !you.sees( *critter ) &&
2871         !( you.sees_with_infrared( *critter ) || you.sees_with_specials( *critter ) ) ) {
2872         return false;
2873     }
2874 
2875     const point screen_point = player_to_screen( pbelow.xy() );
2876 
2877     SDL_Color tercol = curses_color_to_SDL( c_red );
2878     const int sizefactor = 2;
2879 
2880     SDL_Rect belowRect;
2881     belowRect.h = tile_width / sizefactor;
2882     belowRect.w = tile_height / sizefactor;
2883 
2884     if( tile_iso ) {
2885         belowRect.h = ( belowRect.h * 2 ) / 3;
2886         belowRect.w = ( belowRect.w * 3 ) / 4;
2887     }
2888 
2889     belowRect.x = screen_point.x + ( tile_width - belowRect.w ) / 2;
2890     belowRect.y = screen_point.y + ( tile_height - belowRect.h ) / 2;
2891 
2892     if( tile_iso ) {
2893         belowRect.y += tile_height / 8;
2894     }
2895 
2896     geometry->rect( renderer, belowRect, tercol );
2897 
2898     return true;
2899 }
2900 
draw_critter_at(const tripoint & p,lit_level ll,int & height_3d,const bool (& invisible)[5])2901 bool cata_tiles::draw_critter_at( const tripoint &p, lit_level ll, int &height_3d,
2902                                   const bool ( &invisible )[5] )
2903 {
2904     bool result;
2905     bool is_player;
2906     bool sees_player;
2907     Creature::Attitude attitude;
2908     Character &you = get_player_character();
2909     const auto override = monster_override.find( p );
2910     if( override != monster_override.end() ) {
2911         const mtype_id id = std::get<0>( override->second );
2912         if( !id ) {
2913             return false;
2914         }
2915         is_player = false;
2916         sees_player = false;
2917         attitude = std::get<3>( override->second );
2918         const std::string &chosen_id = id.str();
2919         const std::string &ent_subcategory = id.obj().species.empty() ?
2920                                              empty_string : id.obj().species.begin()->str();
2921         result = draw_from_id_string( chosen_id, C_MONSTER, ent_subcategory, p, corner, 0,
2922                                       lit_level::LIT, false, height_3d );
2923     } else if( !invisible[0] ) {
2924         const Creature *pcritter = g->critter_at( p, true );
2925         if( pcritter == nullptr ) {
2926             return false;
2927         }
2928         const Creature &critter = *pcritter;
2929 
2930         if( !you.sees( critter ) ) {
2931             if( you.sees_with_infrared( critter ) ||
2932                 you.sees_with_specials( critter ) ) {
2933                 return draw_from_id_string( "infrared_creature", C_NONE, empty_string, p, 0, 0,
2934                                             lit_level::LIT, false, height_3d );
2935             }
2936             return false;
2937         }
2938         result = false;
2939         sees_player = false;
2940         is_player = false;
2941         attitude = Creature::Attitude::ANY;
2942         const monster *m = dynamic_cast<const monster *>( &critter );
2943         if( m != nullptr ) {
2944             const TILE_CATEGORY ent_category = C_MONSTER;
2945             std::string ent_subcategory = empty_string;
2946             if( !m->type->species.empty() ) {
2947                 ent_subcategory = m->type->species.begin()->str();
2948             }
2949             const int subtile = corner;
2950             // depending on the toggle flip sprite left or right
2951             int rot_facing = -1;
2952             if( m->facing == FacingDirection::RIGHT ) {
2953                 rot_facing = 0;
2954             } else if( m->facing == FacingDirection::LEFT ) {
2955                 rot_facing = 4;
2956             }
2957             if( rot_facing >= 0 ) {
2958                 const auto ent_name = m->type->id;
2959                 std::string chosen_id = ent_name.str();
2960                 if( m->has_effect( effect_ridden ) ) {
2961                     int pl_under_height = 6;
2962                     if( m->mounted_player ) {
2963                         draw_entity_with_overlays( *m->mounted_player, p, ll, pl_under_height );
2964                     }
2965                     const std::string prefix = "rid_";
2966                     std::string copy_id = chosen_id;
2967                     const std::string ridden_id = copy_id.insert( 0, prefix );
2968                     const tile_type *tt = tileset_ptr->find_tile_type( ridden_id );
2969                     if( tt ) {
2970                         chosen_id = ridden_id;
2971                     }
2972                 }
2973                 result = draw_from_id_string( chosen_id, ent_category, ent_subcategory, p,
2974                                               subtile, rot_facing, ll, false, height_3d );
2975                 sees_player = m->sees( you );
2976                 attitude = m->attitude_to( you );
2977             }
2978         }
2979         const player *pl = dynamic_cast<const player *>( &critter );
2980         if( pl != nullptr ) {
2981             draw_entity_with_overlays( *pl, p, ll, height_3d );
2982             result = true;
2983             if( pl->is_player() ) {
2984                 is_player = true;
2985             } else {
2986                 sees_player = pl->sees( you );
2987                 attitude = pl->attitude_to( you );
2988             }
2989         }
2990     } else {
2991         // invisible
2992         const Creature *critter = g->critter_at( p, true );
2993         if( critter && ( you.sees_with_infrared( *critter ) ||
2994                          you.sees_with_specials( *critter ) ) ) {
2995             // try drawing infrared creature if invisible and not overridden
2996             // return directly without drawing overlay
2997             return draw_from_id_string( "infrared_creature", C_NONE, empty_string, p, 0, 0,
2998                                         lit_level::LIT, false, height_3d );
2999         } else {
3000             return false;
3001         }
3002     }
3003 
3004     if( result && !is_player ) {
3005         std::string draw_id = "overlay_" + Creature::attitude_raw_string( attitude );
3006         if( sees_player ) {
3007             draw_id += "_sees_player";
3008         }
3009         if( tileset_ptr->find_tile_type( draw_id ) ) {
3010             draw_from_id_string( draw_id, C_NONE, empty_string, p, 0, 0, lit_level::LIT,
3011                                  false, height_3d );
3012         }
3013     }
3014     return result;
3015 }
3016 
draw_zone_mark(const tripoint & p,lit_level ll,int & height_3d,const bool (& invisible)[5])3017 bool cata_tiles::draw_zone_mark( const tripoint &p, lit_level ll, int &height_3d,
3018                                  const bool ( &invisible )[5] )
3019 {
3020     if( invisible[0] ) {
3021         return false;
3022     }
3023 
3024     if( !g->is_zones_manager_open() ) {
3025         return false;
3026     }
3027 
3028     const zone_manager &mgr = zone_manager::get_manager();
3029     const tripoint &abs = get_map().getabs( p );
3030     const zone_data *zone = mgr.get_bottom_zone( abs );
3031 
3032     if( zone && zone->has_options() ) {
3033         const mark_option *option = dynamic_cast<const mark_option *>( &zone->get_options() );
3034 
3035         if( option && !option->get_mark().empty() ) {
3036             return draw_from_id_string( option->get_mark(), C_NONE, empty_string, p, 0, 0, ll,
3037                                         nv_goggles_activated, height_3d );
3038         }
3039     }
3040 
3041     return false;
3042 }
3043 
draw_zombie_revival_indicators(const tripoint & pos,const lit_level,int &,const bool (& invisible)[5])3044 bool cata_tiles::draw_zombie_revival_indicators( const tripoint &pos, const lit_level /*ll*/,
3045         int &/*height_3d*/, const bool ( &invisible )[5] )
3046 {
3047     map &here = get_map();
3048     if( tileset_ptr->find_tile_type( ZOMBIE_REVIVAL_INDICATOR ) && !invisible[0] &&
3049         item_override.find( pos ) == item_override.end() &&
3050         here.could_see_items( pos, get_player_character() ) ) {
3051         for( item &i : here.i_at( pos ) ) {
3052             if( i.can_revive() ) {
3053                 return draw_from_id_string( ZOMBIE_REVIVAL_INDICATOR, C_NONE, empty_string,
3054                                             pos, 0, 0, lit_level::LIT, false );
3055             }
3056         }
3057     }
3058     return false;
3059 }
3060 
draw_entity_with_overlays(const Character & ch,const tripoint & p,lit_level ll,int & height_3d)3061 void cata_tiles::draw_entity_with_overlays( const Character &ch, const tripoint &p, lit_level ll,
3062         int &height_3d )
3063 {
3064     std::string ent_name;
3065 
3066     if( ch.is_npc() ) {
3067         ent_name = ch.male ? "npc_male" : "npc_female";
3068     } else {
3069         ent_name = ch.male ? "player_male" : "player_female";
3070     }
3071     // first draw the character itself(i guess this means a tileset that
3072     // takes this seriously needs a naked sprite)
3073     int prev_height_3d = height_3d;
3074 
3075     // depending on the toggle flip sprite left or right
3076     if( ch.facing == FacingDirection::RIGHT ) {
3077         draw_from_id_string( ent_name, C_NONE, "", p, corner, 0, ll, false, height_3d );
3078     } else if( ch.facing == FacingDirection::LEFT ) {
3079         draw_from_id_string( ent_name, C_NONE, "", p, corner, 4, ll, false, height_3d );
3080     }
3081 
3082     // next up, draw all the overlays
3083     std::vector<std::string> overlays = ch.get_overlay_ids();
3084     for( const std::string &overlay : overlays ) {
3085         std::string draw_id = overlay;
3086         if( find_overlay_looks_like( ch.male, overlay, draw_id ) ) {
3087             int overlay_height_3d = prev_height_3d;
3088             if( ch.facing == FacingDirection::RIGHT ) {
3089                 draw_from_id_string( draw_id, C_NONE, "", p, corner, /*rota:*/ 0, ll, false,
3090                                      overlay_height_3d );
3091             } else if( ch.facing == FacingDirection::LEFT ) {
3092                 draw_from_id_string( draw_id, C_NONE, "", p, corner, /*rota:*/ 4, ll, false,
3093                                      overlay_height_3d );
3094             }
3095             // the tallest height-having overlay is the one that counts
3096             height_3d = std::max( height_3d, overlay_height_3d );
3097         }
3098     }
3099 }
3100 
draw_item_highlight(const tripoint & pos)3101 bool cata_tiles::draw_item_highlight( const tripoint &pos )
3102 {
3103     return draw_from_id_string( ITEM_HIGHLIGHT, C_NONE, empty_string, pos, 0, 0,
3104                                 lit_level::LIT, false );
3105 }
3106 
ensure_default_item_highlight()3107 void tileset_loader::ensure_default_item_highlight()
3108 {
3109     if( ts.find_tile_type( ITEM_HIGHLIGHT ) ) {
3110         return;
3111     }
3112     const Uint8 highlight_alpha = 127;
3113 
3114     int index = ts.tile_values.size();
3115 
3116     const SDL_Surface_Ptr surface = create_surface_32( ts.tile_width, ts.tile_height );
3117     cata_assert( surface );
3118     throwErrorIf( SDL_FillRect( surface.get(), nullptr, SDL_MapRGBA( surface->format, 0, 0, 127,
3119                                 highlight_alpha ) ) != 0, "SDL_FillRect failed" );
3120     ts.tile_values.emplace_back( CreateTextureFromSurface( renderer, surface ),
3121                                  SDL_Rect{ 0, 0, ts.tile_width, ts.tile_height } );
3122     ts.tile_ids[ITEM_HIGHLIGHT].fg.add( std::vector<int>( {index} ), 1 );
3123 }
3124 
3125 /* Animation Functions */
3126 /* -- Inits */
init_explosion(const tripoint & p,int radius)3127 void cata_tiles::init_explosion( const tripoint &p, int radius )
3128 {
3129     do_draw_explosion = true;
3130     exp_pos = p;
3131     exp_rad = radius;
3132 }
init_custom_explosion_layer(const std::map<tripoint,explosion_tile> & layer)3133 void cata_tiles::init_custom_explosion_layer( const std::map<tripoint, explosion_tile> &layer )
3134 {
3135     do_draw_custom_explosion = true;
3136     custom_explosion_layer = layer;
3137 }
init_draw_bullet(const tripoint & p,std::string name)3138 void cata_tiles::init_draw_bullet( const tripoint &p, std::string name )
3139 {
3140     do_draw_bullet = true;
3141     bul_pos = p;
3142     bul_id = std::move( name );
3143 }
init_draw_hit(const tripoint & p,std::string name)3144 void cata_tiles::init_draw_hit( const tripoint &p, std::string name )
3145 {
3146     do_draw_hit = true;
3147     hit_pos = p;
3148     hit_entity_id = std::move( name );
3149 }
init_draw_line(const tripoint & p,std::vector<tripoint> trajectory,std::string name,bool target_line)3150 void cata_tiles::init_draw_line( const tripoint &p, std::vector<tripoint> trajectory,
3151                                  std::string name, bool target_line )
3152 {
3153     do_draw_line = true;
3154     is_target_line = target_line;
3155     line_pos = p;
3156     line_endpoint_id = std::move( name );
3157     line_trajectory = std::move( trajectory );
3158 }
init_draw_cursor(const tripoint & p)3159 void cata_tiles::init_draw_cursor( const tripoint &p )
3160 {
3161     do_draw_cursor = true;
3162     cursors.emplace_back( p );
3163 }
init_draw_highlight(const tripoint & p)3164 void cata_tiles::init_draw_highlight( const tripoint &p )
3165 {
3166     do_draw_highlight = true;
3167     highlights.emplace_back( p );
3168 }
init_draw_weather(weather_printable weather,std::string name)3169 void cata_tiles::init_draw_weather( weather_printable weather, std::string name )
3170 {
3171     do_draw_weather = true;
3172     weather_name = std::move( name );
3173     anim_weather = std::move( weather );
3174 }
init_draw_sct()3175 void cata_tiles::init_draw_sct()
3176 {
3177     do_draw_sct = true;
3178 }
init_draw_zones(const tripoint & _start,const tripoint & _end,const tripoint & _offset)3179 void cata_tiles::init_draw_zones( const tripoint &_start, const tripoint &_end,
3180                                   const tripoint &_offset )
3181 {
3182     do_draw_zones = true;
3183     zone_start = _start;
3184     zone_end = _end;
3185     zone_offset = _offset;
3186 }
init_draw_radiation_override(const tripoint & p,const int rad)3187 void cata_tiles::init_draw_radiation_override( const tripoint &p, const int rad )
3188 {
3189     radiation_override.emplace( p, rad );
3190 }
init_draw_terrain_override(const tripoint & p,const ter_id & id)3191 void cata_tiles::init_draw_terrain_override( const tripoint &p, const ter_id &id )
3192 {
3193     terrain_override.emplace( p, id );
3194 }
init_draw_furniture_override(const tripoint & p,const furn_id & id)3195 void cata_tiles::init_draw_furniture_override( const tripoint &p, const furn_id &id )
3196 {
3197     furniture_override.emplace( p, id );
3198 }
init_draw_graffiti_override(const tripoint & p,const bool has)3199 void cata_tiles::init_draw_graffiti_override( const tripoint &p, const bool has )
3200 {
3201     graffiti_override.emplace( p, has );
3202 }
init_draw_trap_override(const tripoint & p,const trap_id & id)3203 void cata_tiles::init_draw_trap_override( const tripoint &p, const trap_id &id )
3204 {
3205     trap_override.emplace( p, id );
3206 }
init_draw_field_override(const tripoint & p,const field_type_id & id)3207 void cata_tiles::init_draw_field_override( const tripoint &p, const field_type_id &id )
3208 {
3209     field_override.emplace( p, id );
3210 }
init_draw_item_override(const tripoint & p,const itype_id & id,const mtype_id & mid,const bool hilite)3211 void cata_tiles::init_draw_item_override( const tripoint &p, const itype_id &id,
3212         const mtype_id &mid, const bool hilite )
3213 {
3214     item_override.emplace( p, std::make_tuple( id, mid, hilite ) );
3215 }
init_draw_vpart_override(const tripoint & p,const vpart_id & id,const int part_mod,const units::angle & veh_dir,const bool hilite,const point & mount)3216 void cata_tiles::init_draw_vpart_override( const tripoint &p, const vpart_id &id,
3217         const int part_mod, const units::angle &veh_dir, const bool hilite, const point &mount )
3218 {
3219     vpart_override.emplace( p, std::make_tuple( id, part_mod, veh_dir, hilite, mount ) );
3220 }
init_draw_below_override(const tripoint & p,const bool draw)3221 void cata_tiles::init_draw_below_override( const tripoint &p, const bool draw )
3222 {
3223     draw_below_override.emplace( p, draw );
3224 }
init_draw_monster_override(const tripoint & p,const mtype_id & id,const int count,const bool more,const Creature::Attitude att)3225 void cata_tiles::init_draw_monster_override( const tripoint &p, const mtype_id &id, const int count,
3226         const bool more, const Creature::Attitude att )
3227 {
3228     monster_override.emplace( p, std::make_tuple( id, count, more, att ) );
3229 }
3230 
3231 /* -- Void Animators */
void_explosion()3232 void cata_tiles::void_explosion()
3233 {
3234     do_draw_explosion = false;
3235     exp_pos = {-1, -1, -1};
3236     exp_rad = -1;
3237 }
void_custom_explosion()3238 void cata_tiles::void_custom_explosion()
3239 {
3240     do_draw_custom_explosion = false;
3241     custom_explosion_layer.clear();
3242 }
void_bullet()3243 void cata_tiles::void_bullet()
3244 {
3245     do_draw_bullet = false;
3246     bul_pos = { -1, -1, -1 };
3247     bul_id.clear();
3248 }
void_hit()3249 void cata_tiles::void_hit()
3250 {
3251     do_draw_hit = false;
3252     hit_pos = { -1, -1, -1 };
3253     hit_entity_id.clear();
3254 }
void_line()3255 void cata_tiles::void_line()
3256 {
3257     do_draw_line = false;
3258     is_target_line = false;
3259     line_pos = { -1, -1, -1 };
3260     line_endpoint_id.clear();
3261     line_trajectory.clear();
3262 }
void_cursor()3263 void cata_tiles::void_cursor()
3264 {
3265     do_draw_cursor = false;
3266     cursors.clear();
3267 }
void_highlight()3268 void cata_tiles::void_highlight()
3269 {
3270     do_draw_highlight = false;
3271     highlights.clear();
3272 }
void_weather()3273 void cata_tiles::void_weather()
3274 {
3275     do_draw_weather = false;
3276     weather_name.clear();
3277     anim_weather.vdrops.clear();
3278 }
void_sct()3279 void cata_tiles::void_sct()
3280 {
3281     do_draw_sct = false;
3282 }
void_zones()3283 void cata_tiles::void_zones()
3284 {
3285     do_draw_zones = false;
3286 }
void_radiation_override()3287 void cata_tiles::void_radiation_override()
3288 {
3289     radiation_override.clear();
3290 }
void_terrain_override()3291 void cata_tiles::void_terrain_override()
3292 {
3293     terrain_override.clear();
3294 }
void_furniture_override()3295 void cata_tiles::void_furniture_override()
3296 {
3297     furniture_override.clear();
3298 }
void_graffiti_override()3299 void cata_tiles::void_graffiti_override()
3300 {
3301     graffiti_override.clear();
3302 }
void_trap_override()3303 void cata_tiles::void_trap_override()
3304 {
3305     trap_override.clear();
3306 }
void_field_override()3307 void cata_tiles::void_field_override()
3308 {
3309     field_override.clear();
3310 }
void_item_override()3311 void cata_tiles::void_item_override()
3312 {
3313     item_override.clear();
3314 }
void_vpart_override()3315 void cata_tiles::void_vpart_override()
3316 {
3317     vpart_override.clear();
3318 }
void_draw_below_override()3319 void cata_tiles::void_draw_below_override()
3320 {
3321     draw_below_override.clear();
3322 }
void_monster_override()3323 void cata_tiles::void_monster_override()
3324 {
3325     monster_override.clear();
3326 }
3327 
has_draw_override(const tripoint & p) const3328 bool cata_tiles::has_draw_override( const tripoint &p ) const
3329 {
3330     return radiation_override.find( p ) != radiation_override.end() ||
3331            terrain_override.find( p ) != terrain_override.end() ||
3332            furniture_override.find( p ) != furniture_override.end() ||
3333            graffiti_override.find( p ) != graffiti_override.end() ||
3334            trap_override.find( p ) != trap_override.end() ||
3335            field_override.find( p ) != field_override.end() ||
3336            item_override.find( p ) != item_override.end() ||
3337            vpart_override.find( p ) != vpart_override.end() ||
3338            draw_below_override.find( p ) != draw_below_override.end() ||
3339            monster_override.find( p ) != monster_override.end();
3340 }
3341 
3342 /* -- Animation Renders */
draw_explosion_frame()3343 void cata_tiles::draw_explosion_frame()
3344 {
3345     std::string exp_name = "explosion";
3346     int subtile = 0;
3347     int rotation = 0;
3348 
3349     for( int i = 1; i < exp_rad; ++i ) {
3350         subtile = corner;
3351         rotation = 0;
3352 
3353         draw_from_id_string( exp_name, exp_pos + point( -i, -i ),
3354                              subtile, rotation++, lit_level::LIT, nv_goggles_activated );
3355         draw_from_id_string( exp_name, exp_pos + point( -i, i ),
3356                              subtile, rotation++, lit_level::LIT, nv_goggles_activated );
3357         draw_from_id_string( exp_name, exp_pos + point( i, i ),
3358                              subtile, rotation++, lit_level::LIT, nv_goggles_activated );
3359         draw_from_id_string( exp_name, exp_pos + point( i, -i ),
3360                              subtile, rotation, lit_level::LIT, nv_goggles_activated );
3361 
3362         subtile = edge;
3363         for( int j = 1 - i; j < 0 + i; j++ ) {
3364             rotation = 0;
3365             draw_from_id_string( exp_name, exp_pos + point( j, -i ),
3366                                  subtile, rotation, lit_level::LIT, nv_goggles_activated );
3367             draw_from_id_string( exp_name, exp_pos + point( j, i ),
3368                                  subtile, rotation, lit_level::LIT, nv_goggles_activated );
3369 
3370             rotation = 1;
3371             draw_from_id_string( exp_name, exp_pos + point( -i, j ),
3372                                  subtile, rotation, lit_level::LIT, nv_goggles_activated );
3373             draw_from_id_string( exp_name, exp_pos + point( i, j ),
3374                                  subtile, rotation, lit_level::LIT, nv_goggles_activated );
3375         }
3376     }
3377 }
3378 
draw_custom_explosion_frame()3379 void cata_tiles::draw_custom_explosion_frame()
3380 {
3381     // TODO: Make the drawing code handle all the missing tiles: <^>v and *
3382     // TODO: Add more explosion tiles, like "strong explosion", so that it displays more info
3383     static const std::string exp_strong = "explosion";
3384     static const std::string exp_medium = "explosion_medium";
3385     static const std::string exp_weak = "explosion_weak";
3386     int subtile = 0;
3387     int rotation = 0;
3388 
3389     for( const auto &pr : custom_explosion_layer ) {
3390         const explosion_neighbors ngh = pr.second.neighborhood;
3391         const nc_color col = pr.second.color;
3392 
3393         switch( ngh ) {
3394             case N_NORTH:
3395             case N_SOUTH:
3396                 subtile = edge;
3397                 rotation = 1;
3398                 break;
3399             case N_WEST:
3400             case N_EAST:
3401                 subtile = edge;
3402                 rotation = 0;
3403                 break;
3404             case N_NORTH | N_SOUTH:
3405             case N_NORTH | N_SOUTH | N_WEST:
3406             case N_NORTH | N_SOUTH | N_EAST:
3407                 subtile = edge;
3408                 rotation = 1;
3409                 break;
3410             case N_WEST | N_EAST:
3411             case N_WEST | N_EAST | N_NORTH:
3412             case N_WEST | N_EAST | N_SOUTH:
3413                 subtile = edge;
3414                 rotation = 0;
3415                 break;
3416             case N_SOUTH | N_EAST:
3417                 subtile = corner;
3418                 rotation = 0;
3419                 break;
3420             case N_NORTH | N_EAST:
3421                 subtile = corner;
3422                 rotation = 1;
3423                 break;
3424             case N_NORTH | N_WEST:
3425                 subtile = corner;
3426                 rotation = 2;
3427                 break;
3428             case N_SOUTH | N_WEST:
3429                 subtile = corner;
3430                 rotation = 3;
3431                 break;
3432             case N_NO_NEIGHBORS:
3433                 subtile = edge;
3434                 break;
3435             case N_WEST | N_EAST | N_NORTH | N_SOUTH:
3436                 // Needs some special tile
3437                 subtile = edge;
3438                 break;
3439         }
3440 
3441         const tripoint &p = pr.first;
3442         std::string explosion_tile_id;
3443         if( pr.second.tile_name && find_tile_looks_like( *pr.second.tile_name, TILE_CATEGORY::C_NONE ) ) {
3444             explosion_tile_id = *pr.second.tile_name;
3445         } else if( col == c_red ) {
3446             explosion_tile_id = exp_strong;
3447         } else if( col == c_yellow ) {
3448             explosion_tile_id = exp_medium;
3449         } else {
3450             explosion_tile_id = exp_weak;
3451         }
3452 
3453         draw_from_id_string( explosion_tile_id, p, subtile, rotation, lit_level::LIT,
3454                              nv_goggles_activated );
3455     }
3456 }
draw_bullet_frame()3457 void cata_tiles::draw_bullet_frame()
3458 {
3459     draw_from_id_string( bul_id, C_BULLET, empty_string, bul_pos, 0, 0, lit_level::LIT, false );
3460 }
draw_hit_frame()3461 void cata_tiles::draw_hit_frame()
3462 {
3463     std::string hit_overlay = "animation_hit";
3464 
3465     draw_from_id_string( hit_entity_id, C_HIT_ENTITY, empty_string, hit_pos, 0, 0,
3466                          lit_level::LIT, false );
3467     draw_from_id_string( hit_overlay, hit_pos, 0, 0, lit_level::LIT, false );
3468 }
draw_line()3469 void cata_tiles::draw_line()
3470 {
3471     if( line_trajectory.empty() ) {
3472         return;
3473     }
3474     static std::string line_overlay = "animation_line";
3475     if( !is_target_line || get_player_view().sees( line_pos ) ) {
3476         for( auto it = line_trajectory.begin(); it != line_trajectory.end() - 1; ++it ) {
3477             draw_from_id_string( line_overlay, *it, 0, 0, lit_level::LIT, false );
3478         }
3479     }
3480 
3481     draw_from_id_string( line_endpoint_id, line_trajectory.back(), 0, 0, lit_level::LIT, false );
3482 }
draw_cursor()3483 void cata_tiles::draw_cursor()
3484 {
3485     for( const tripoint &p : cursors ) {
3486         draw_from_id_string( "cursor", p, 0, 0, lit_level::LIT, false );
3487     }
3488 }
draw_highlight()3489 void cata_tiles::draw_highlight()
3490 {
3491     for( const tripoint &p : highlights ) {
3492         draw_from_id_string( "highlight", p, 0, 0, lit_level::LIT, false );
3493     }
3494 }
draw_weather_frame()3495 void cata_tiles::draw_weather_frame()
3496 {
3497 
3498     for( auto &vdrop : anim_weather.vdrops ) {
3499         // TODO: Z-level awareness if weather ever happens on anything but z-level 0.
3500         tripoint p( vdrop.first, vdrop.second, 0 );
3501         if( !tile_iso ) {
3502             // currently in ASCII screen coordinates
3503             p += o;
3504         }
3505         draw_from_id_string( weather_name, C_WEATHER, empty_string, p, 0, 0,
3506                              lit_level::LIT, nv_goggles_activated );
3507     }
3508 }
3509 
draw_sct_frame(std::multimap<point,formatted_text> & overlay_strings)3510 void cata_tiles::draw_sct_frame( std::multimap<point, formatted_text> &overlay_strings )
3511 {
3512     const bool use_font = get_option<bool>( "ANIMATION_SCT_USE_FONT" );
3513     tripoint player_pos = get_player_location().pos();
3514 
3515     for( auto iter = SCT.vSCT.begin(); iter != SCT.vSCT.end(); ++iter ) {
3516         const point iD( iter->getPosX(), iter->getPosY() );
3517         const int full_text_length = utf8_width( iter->getText() );
3518 
3519         int iOffsetX = 0;
3520         int iOffsetY = 0;
3521 
3522         for( int j = 0; j < 2; ++j ) {
3523             std::string sText = iter->getText( ( j == 0 ) ? "first" : "second" );
3524             int FG = msgtype_to_tilecolor( iter->getMsgType( ( j == 0 ) ? "first" : "second" ),
3525                                            iter->getStep() >= SCT.iMaxSteps / 2 );
3526 
3527             if( use_font ) {
3528                 const direction direction = iter->getDirection();
3529                 // Compensate for string length offset added at SCT creation
3530                 // (it will be readded using font size and proper encoding later).
3531                 const int direction_offset = ( -direction_XY( direction ).x + 1 ) *
3532                                              full_text_length / 2;
3533 
3534                 overlay_strings.emplace(
3535                     player_to_screen( iD + point( direction_offset, 0 ) ),
3536                     formatted_text( sText, FG, direction ) );
3537             } else {
3538                 for( auto &it : sText ) {
3539                     const std::string generic_id = get_ascii_tile_id( it, FG, -1 );
3540 
3541                     if( tileset_ptr->find_tile_type( generic_id ) ) {
3542                         draw_from_id_string( generic_id, C_NONE, empty_string,
3543                                              iD + tripoint( iOffsetX, iOffsetY, player_pos.z ),
3544                                              0, 0, lit_level::LIT, false );
3545                     }
3546 
3547                     if( tile_iso ) {
3548                         iOffsetY++;
3549                     }
3550                     iOffsetX++;
3551                 }
3552             }
3553         }
3554     }
3555 }
3556 
draw_zones_frame()3557 void cata_tiles::draw_zones_frame()
3558 {
3559     tripoint player_pos = get_player_location().pos();
3560     for( int iY = zone_start.y; iY <= zone_end.y; ++ iY ) {
3561         for( int iX = zone_start.x; iX <= zone_end.x; ++iX ) {
3562             draw_from_id_string( "highlight", C_NONE, empty_string,
3563                                  zone_offset.xy() + tripoint( iX, iY, player_pos.z ),
3564                                  0, 0, lit_level::LIT, false );
3565         }
3566     }
3567 
3568 }
draw_footsteps_frame()3569 void cata_tiles::draw_footsteps_frame()
3570 {
3571     static const std::string footstep_tilestring = "footstep";
3572     for( const auto &footstep : sounds::get_footstep_markers() ) {
3573         draw_from_id_string( footstep_tilestring, footstep, 0, 0, lit_level::LIT, false );
3574     }
3575 }
3576 /* END OF ANIMATION FUNCTIONS */
3577 
init_light()3578 void cata_tiles::init_light()
3579 {
3580     g->reset_light_level();
3581 }
3582 
get_terrain_orientation(const tripoint & p,int & rota,int & subtile,const std::map<tripoint,ter_id> & ter_override,const bool (& invisible)[5])3583 void cata_tiles::get_terrain_orientation( const tripoint &p, int &rota, int &subtile,
3584         const std::map<tripoint, ter_id> &ter_override, const bool ( &invisible )[5] )
3585 {
3586     map &here = get_map();
3587     const bool overridden = ter_override.find( p ) != ter_override.end();
3588     const auto ter = [&]( const tripoint & q, const bool invis ) -> ter_id {
3589         const auto override = ter_override.find( q );
3590         return override != ter_override.end() ? override->second :
3591         ( !overridden || !invis ) ? here.ter( q ) : t_null;
3592     };
3593 
3594     // get terrain at x,y
3595     const ter_id tid = ter( p, invisible[0] );
3596     if( tid == t_null ) {
3597         subtile = 0;
3598         rota = 0;
3599         return;
3600     }
3601 
3602     // get terrain neighborhood
3603     const ter_id neighborhood[4] = {
3604         ter( p + point_south, invisible[1] ),
3605         ter( p + point_east, invisible[2] ),
3606         ter( p + point_west, invisible[3] ),
3607         ter( p + point_north, invisible[4] )
3608     };
3609 
3610     char val = 0;
3611 
3612     // populate connection information
3613     for( int i = 0; i < 4; ++i ) {
3614         if( neighborhood[i] == tid ) {
3615             val += 1 << i;
3616         }
3617     }
3618 
3619     get_rotation_and_subtile( val, rota, subtile );
3620 }
3621 
get_rotation_and_subtile(const char val,int & rotation,int & subtile)3622 void cata_tiles::get_rotation_and_subtile( const char val, int &rotation, int &subtile )
3623 {
3624     switch( val ) {
3625         // no connections
3626         case 0:
3627             subtile = unconnected;
3628             rotation = 0;
3629             break;
3630         // all connections
3631         case 15:
3632             subtile = center;
3633             rotation = 0;
3634             break;
3635         // end pieces
3636         case 8:
3637             subtile = end_piece;
3638             rotation = 2;
3639             break;
3640         case 4:
3641             subtile = end_piece;
3642             rotation = 3;
3643             break;
3644         case 2:
3645             subtile = end_piece;
3646             rotation = 1;
3647             break;
3648         case 1:
3649             subtile = end_piece;
3650             rotation = 0;
3651             break;
3652         // edges
3653         case 9:
3654             subtile = edge;
3655             rotation = 0;
3656             break;
3657         case 6:
3658             subtile = edge;
3659             rotation = 1;
3660             break;
3661         // corners
3662         case 12:
3663             subtile = corner;
3664             rotation = 2;
3665             break;
3666         case 10:
3667             subtile = corner;
3668             rotation = 1;
3669             break;
3670         case 3:
3671             subtile = corner;
3672             rotation = 0;
3673             break;
3674         case 5:
3675             subtile = corner;
3676             rotation = 3;
3677             break;
3678         // all t_connections
3679         case 14:
3680             subtile = t_connection;
3681             rotation = 2;
3682             break;
3683         case 11:
3684             subtile = t_connection;
3685             rotation = 1;
3686             break;
3687         case 7:
3688             subtile = t_connection;
3689             rotation = 0;
3690             break;
3691         case 13:
3692             subtile = t_connection;
3693             rotation = 3;
3694             break;
3695     }
3696 }
3697 
get_connect_values(const tripoint & p,int & subtile,int & rotation,const int connect_group,const std::map<tripoint,ter_id> & ter_override)3698 void cata_tiles::get_connect_values( const tripoint &p, int &subtile, int &rotation,
3699                                      const int connect_group,
3700                                      const std::map<tripoint, ter_id> &ter_override )
3701 {
3702     uint8_t connections = get_map().get_known_connections( p, connect_group, ter_override );
3703     get_rotation_and_subtile( connections, rotation, subtile );
3704 }
3705 
get_furn_connect_values(const tripoint & p,int & subtile,int & rotation,const int connect_group,const std::map<tripoint,furn_id> & furn_override)3706 void cata_tiles::get_furn_connect_values( const tripoint &p, int &subtile, int &rotation,
3707         const int connect_group, const std::map<tripoint,
3708         furn_id> &furn_override )
3709 {
3710     uint8_t connections = get_map().get_known_connections_f( p, connect_group, furn_override );
3711     get_rotation_and_subtile( connections, rotation, subtile );
3712 }
3713 
get_tile_values(const int t,const int * tn,int & subtile,int & rotation)3714 void cata_tiles::get_tile_values( const int t, const int *tn, int &subtile, int &rotation )
3715 {
3716     bool connects[4];
3717     char val = 0;
3718     for( int i = 0; i < 4; ++i ) {
3719         connects[i] = ( tn[i] == t );
3720         if( connects[i] ) {
3721             val += 1 << i;
3722         }
3723     }
3724     get_rotation_and_subtile( val, rotation, subtile );
3725 }
3726 
get_tile_values_with_ter(const tripoint & p,const int t,const int * tn,int & subtile,int & rotation)3727 void cata_tiles::get_tile_values_with_ter( const tripoint &p, const int t, const int *tn,
3728         int &subtile, int &rotation )
3729 {
3730     map &here = get_map();
3731     //check if furniture should connect to itself
3732     if( here.has_flag( "NO_SELF_CONNECT", p ) || here.has_flag( "ALIGN_WORKBENCH", p ) ) {
3733         //if we don't ever connect to ourself just return unconnected to be used further
3734         get_rotation_and_subtile( 0, rotation, subtile );
3735     } else {
3736         //if we do connect to ourself (tables, counters etc.) calculate based on neighbours
3737         get_tile_values( t, tn, subtile, rotation );
3738     }
3739     // calculate rotation for unconnected tiles based on surrounding walls
3740     if( subtile == unconnected ) {
3741         int val = 0;
3742         bool use_furniture = false;
3743 
3744         if( here.has_flag( "ALIGN_WORKBENCH", p ) ) {
3745             for( int i = 0; i < 4; ++i ) {
3746                 // align to furniture that has the workbench quality
3747                 const tripoint &pt = p + four_adjacent_offsets[i];
3748                 if( here.has_furn( pt ) && here.furn( pt ).obj().workbench ) {
3749                     val += 1 << i;
3750                     use_furniture = true;
3751                 }
3752             }
3753         }
3754         // if still unaligned, try aligning to walls
3755         if( val == 0 ) {
3756             for( int i = 0; i < 4; ++i ) {
3757                 const tripoint &pt = p + four_adjacent_offsets[i];
3758                 if( here.has_flag( "WALL", pt ) || here.has_flag( "WINDOW", pt ) ||
3759                     here.has_flag( "DOOR", pt ) ) {
3760                     val += 1 << i;
3761                 }
3762             }
3763         }
3764 
3765         switch( val ) {
3766             case 4:    // south wall
3767             case 14:   // north opening T
3768                 rotation = 2;
3769                 break;
3770             case 2:    // east wall
3771             case 6:    // southeast corner
3772             case 5:    // E/W corridor
3773             case 7:    // east opening T
3774                 rotation = 1;
3775                 break;
3776             case 8:    // west wall
3777             case 12:   // southwest corner
3778             case 13:   // west opening T
3779                 rotation = 3;
3780                 break;
3781             case 0:    // no walls
3782             case 1:    // north wall
3783             case 3:    // northeast corner
3784             case 9:    // northwest corner
3785             case 10:   // N/S corridor
3786             case 11:   // south opening T
3787             case 15:   // surrounded
3788             default:   // just in case
3789                 rotation = 0;
3790                 break;
3791         }
3792 
3793         //
3794         if( use_furniture ) {
3795             rotation = ( rotation + 2 ) % 4;
3796         }
3797     }
3798 }
3799 
do_tile_loading_report()3800 void cata_tiles::do_tile_loading_report()
3801 {
3802     DebugLog( D_INFO, DC_ALL ) << "Loaded tileset: " << get_option<std::string>( "TILES" );
3803 
3804     if( !g->is_core_data_loaded() ) {
3805         // There's nothing to do anymore without the core data.
3806         return;
3807     }
3808 
3809     tile_loading_report<ter_t>( ter_t::count(), C_TERRAIN, "" );
3810     tile_loading_report<furn_t>( furn_t::count(), C_FURNITURE, "" );
3811 
3812     std::map<itype_id, const itype *> items;
3813     for( const itype *e : item_controller->all() ) {
3814         items.emplace( e->get_id(), e );
3815     }
3816     tile_loading_report( items, C_ITEM, "" );
3817 
3818     auto mtypes = MonsterGenerator::generator().get_all_mtypes();
3819     lr_generic( mtypes.begin(), mtypes.end(), []( const std::vector<mtype>::iterator & m ) {
3820         return ( *m ).id.str();
3821     }, C_MONSTER, "" );
3822     tile_loading_report( vpart_info::all(), C_VEHICLE_PART, "vp_" );
3823     tile_loading_report<trap>( trap::count(), C_TRAP, "" );
3824     tile_loading_report<field_type>( field_type::count(), C_FIELD, "" );
3825 
3826     // needed until DebugLog ostream::flush bugfix lands
3827     DebugLog( D_INFO, DC_ALL );
3828 }
3829 
player_to_screen(const point & p) const3830 point cata_tiles::player_to_screen( const point &p ) const
3831 {
3832     point screen;
3833     if( tile_iso ) {
3834         screen.x = ( ( p.x - o.x ) - ( o.y - p.y ) + screentile_width - 2 ) * tile_width / 2 +
3835                    op.x;
3836         // y uses tile_width because width is definitive for iso tiles
3837         // tile footprints are half as tall as wide, arbitrarily tall
3838         screen.y = ( ( p.y - o.y ) - ( p.x - o.x ) - 4 ) * tile_width / 4 +
3839                    screentile_height * tile_height / 2 + // TODO: more obvious centering math
3840                    op.y;
3841     } else {
3842         screen.x = ( p.x - o.x ) * tile_width + op.x;
3843         screen.y = ( p.y - o.y ) * tile_height + op.y;
3844     }
3845     return {screen};
3846 }
3847 
3848 template<typename Iter, typename Func>
lr_generic(Iter begin,Iter end,Func id_func,TILE_CATEGORY category,const std::string & prefix)3849 void cata_tiles::lr_generic( Iter begin, Iter end, Func id_func, TILE_CATEGORY category,
3850                              const std::string &prefix )
3851 {
3852     std::string missing_list;
3853     std::string missing_with_looks_like_list;
3854     for( ; begin != end; ++begin ) {
3855         const std::string id_string = id_func( begin );
3856 
3857         if( !tileset_ptr->find_tile_type( prefix + id_string ) &&
3858             !find_tile_looks_like( id_string, category ) ) {
3859             missing_list.append( id_string + " " );
3860         } else if( !tileset_ptr->find_tile_type( prefix + id_string ) ) {
3861             missing_with_looks_like_list.append( id_string + " " );
3862         }
3863     }
3864     DebugLog( D_INFO, DC_ALL ) << "Missing " << TILE_CATEGORY_IDS[category] << ": " << missing_list;
3865     DebugLog( D_INFO, DC_ALL ) << "Missing " << TILE_CATEGORY_IDS[category] <<
3866                                " (but looks_like tile exists): " << missing_with_looks_like_list;
3867 }
3868 
3869 template <typename maptype>
tile_loading_report(const maptype & tiletypemap,TILE_CATEGORY category,const std::string & prefix)3870 void cata_tiles::tile_loading_report( const maptype &tiletypemap, TILE_CATEGORY category,
3871                                       const std::string &prefix )
3872 {
3873     lr_generic( tiletypemap.begin(), tiletypemap.end(),
3874     []( const decltype( tiletypemap.begin() ) & v ) {
3875         // c_str works for std::string and for string_id!
3876         return v->first.c_str();
3877     }, category, prefix );
3878 }
3879 
3880 template <typename base_type>
tile_loading_report(const size_t count,TILE_CATEGORY category,const std::string & prefix)3881 void cata_tiles::tile_loading_report( const size_t count, TILE_CATEGORY category,
3882                                       const std::string &prefix )
3883 {
3884     lr_generic( static_cast<size_t>( 0 ), count,
3885     []( const size_t i ) {
3886         return int_id<base_type>( i ).id().str();
3887     }, category, prefix );
3888 }
3889 
3890 template <typename arraytype>
tile_loading_report(const arraytype & array,int array_length,TILE_CATEGORY category,const std::string & prefix)3891 void cata_tiles::tile_loading_report( const arraytype &array, int array_length,
3892                                       TILE_CATEGORY category, const std::string &prefix )
3893 {
3894     const auto begin = &( array[0] );
3895     lr_generic( begin, begin + array_length,
3896     []( decltype( begin ) const v ) {
3897         return v->id;
3898     }, category, prefix );
3899 }
3900 
build_renderer_list()3901 std::vector<options_manager::id_and_option> cata_tiles::build_renderer_list()
3902 {
3903     std::vector<options_manager::id_and_option> renderer_names;
3904     std::vector<options_manager::id_and_option> default_renderer_names = {
3905 #   if defined(_WIN32)
3906         { "direct3d", to_translation( "direct3d" ) },
3907 #   endif
3908         { "software", to_translation( "software" ) },
3909         { "opengl", to_translation( "opengl" ) },
3910         { "opengles2", to_translation( "opengles2" ) },
3911     };
3912     int numRenderDrivers = SDL_GetNumRenderDrivers();
3913     DebugLog( D_INFO, DC_ALL ) << "Number of render drivers on your system: " << numRenderDrivers;
3914     for( int ii = 0; ii < numRenderDrivers; ii++ ) {
3915         SDL_RendererInfo ri;
3916         SDL_GetRenderDriverInfo( ii, &ri );
3917         DebugLog( D_INFO, DC_ALL ) << "Render driver: " << ii << "/" << ri.name;
3918         // First default renderer name we will put first on the list. We can use it later as
3919         // default value.
3920         if( ri.name == default_renderer_names.front().first ) {
3921             renderer_names.emplace( renderer_names.begin(), default_renderer_names.front() );
3922         } else {
3923             renderer_names.emplace_back( ri.name, no_translation( ri.name ) );
3924         }
3925 
3926     }
3927 
3928     return renderer_names.empty() ? default_renderer_names : renderer_names;
3929 }
3930 
build_display_list()3931 std::vector<options_manager::id_and_option> cata_tiles::build_display_list()
3932 {
3933     std::vector<options_manager::id_and_option> display_names;
3934     std::vector<options_manager::id_and_option> default_display_names = {
3935         { "0", to_translation( "Display 0" ) }
3936     };
3937 
3938     int numdisplays = SDL_GetNumVideoDisplays();
3939     for( int i = 0 ; i < numdisplays ; i++ ) {
3940         display_names.emplace_back( std::to_string( i ), no_translation( SDL_GetDisplayName( i ) ) );
3941     }
3942 
3943     return display_names.empty() ? default_display_names : display_names;
3944 }
3945 
3946 #endif // SDL_TILES
3947