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 ¢er, 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 ¢er, 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