diff --git a/doc/TILESET.md b/doc/TILESET.md index 2617ae85b430f..0b973857e8282 100644 --- a/doc/TILESET.md +++ b/doc/TILESET.md @@ -82,6 +82,9 @@ Bashing animations (handle_action.cpp): `bash_effective` Bash effective but target not yet destroyed. `bash_ineffective` Bash not effective. +Shadows (cata_tiles.cpp): +`shadow` Drawn when creature present in tiles above. + #### Complex IDs Special prefixes that are used include: diff --git a/src/cata_tiles.cpp b/src/cata_tiles.cpp index 68c6e437834a5..16282adf63d3e 100644 --- a/src/cata_tiles.cpp +++ b/src/cata_tiles.cpp @@ -1695,6 +1695,12 @@ void cata_tiles::draw( const point &dest, const tripoint ¢er, int width, int } }; + // Skip drawing shadow of critters above if there is no shadow sprite + bool do_draw_shadow = false; + if( find_tile_looks_like( "shadow", TILE_CATEGORY::NONE, "" ) ) { + do_draw_shadow = true; + } + if( max_draw_depth <= 0 ) { // Legacy draw mode for( int row = min_row; row < max_row; row ++ ) { @@ -1762,6 +1768,13 @@ void cata_tiles::draw( const point &dest, const tripoint ¢er, int width, int // If no vpart drawn, revert height_3d changes p.com.height_3d = temp_height_3d; } + } else if( f == &cata_tiles::draw_critter_at ) { + // Draw + if( !( this->*f )( draw_loc, var->ll, p.com.height_3d, var->invisible, false ) && do_draw_shadow && + cur_zlevel == p.com.draw_min_z ) { + // Draw shadow of flying critters on bottom-most tile if no other critter drawn + draw_critter_above( draw_loc, var->ll, p.com.height_3d, var->invisible ); + } } else { // Draw ( this->*f )( draw_loc, var->ll, p.com.height_3d, var->invisible, false ); @@ -4042,6 +4055,69 @@ bool cata_tiles::draw_critter_at( const tripoint &p, lit_level ll, int &height_3 return result; } +bool cata_tiles::draw_critter_above( const tripoint &p, lit_level ll, int &height_3d, + const std::array & ) +{ + tripoint scan_p( p.xy(), p.z + 1 ); + map &here = get_map(); + Character &you = get_player_character(); + const Creature *pcritter = nullptr; + // Search for a creature above + while( pcritter == nullptr && !here.dont_draw_lower_floor( scan_p ) && + scan_p.z - you.pos().z <= fov_3d_z_range ) { + pcritter = get_creature_tracker().creature_at( scan_p, true ); + scan_p.z++; + } + + // Abort if no creature found + if( pcritter == nullptr ) { + return false; + } + const Creature &critter = *pcritter; + + // Draw shadow + if( draw_from_id_string( "shadow", TILE_CATEGORY::NONE, empty_string, p, + 0, 0, ll, false, height_3d ) && scan_p.z - 1 > you.pos().z && you.sees( critter ) ) { + + bool is_player = false; + bool sees_player = false; + Creature::Attitude attitude = Creature::Attitude::ANY; + + // Get critter status disposition if monster + const monster *m = dynamic_cast( &critter ); + if( m != nullptr ) { + sees_player = m->sees( you ); + attitude = m->attitude_to( you ); + } + + // Get critter status disposition if character + const Character *pl = dynamic_cast( &critter ); + if( pl != nullptr ) { + if( pl->is_avatar() ) { + is_player = true; + } else { + sees_player = pl->sees( you ); + attitude = pl->attitude_to( you ); + } + } + + // Draw overlay for shadow owner + if( !is_player ) { + std::string draw_id = "overlay_" + Creature::attitude_raw_string( attitude ); + if( sees_player && !you.has_trait( trait_INATTENTIVE ) ) { + draw_id += "_sees_player"; + } + if( tileset_ptr->find_tile_type( draw_id ) ) { + draw_from_id_string( draw_id, TILE_CATEGORY::NONE, empty_string, p, 0, 0, + lit_level::LIT, false, height_3d ); + } + } + return true; + } else { + return false; + } +} + bool cata_tiles::draw_zone_mark( const tripoint &p, lit_level ll, int &height_3d, const std::array &invisible, const bool memorize_only ) { diff --git a/src/cata_tiles.h b/src/cata_tiles.h index 01b0876b48546..fcd99681dd864 100644 --- a/src/cata_tiles.h +++ b/src/cata_tiles.h @@ -549,6 +549,8 @@ class cata_tiles const std::array &invisible, bool memorize_only ); bool draw_critter_at_below( const tripoint &p, lit_level ll, int &height_3d, const std::array &invisible, bool memorize_only ); + bool draw_critter_above( const tripoint &p, lit_level ll, int &height_3d, + const std::array &invisible ); bool draw_zone_mark( const tripoint &p, lit_level ll, int &height_3d, const std::array &invisible, bool memorize_only ); bool draw_zombie_revival_indicators( const tripoint &pos, lit_level ll, int &height_3d, diff --git a/src/game.cpp b/src/game.cpp index 0242acbffe765..3d999420ceb52 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -3799,8 +3799,6 @@ void game::draw_async_anim_curses() const nc_color nccol = anim.second.second; mvwprintz( w_terrain, p.xy(), nccol, ncstr ); - //shared_ptr_fast hit_cb = make_shared_fast( [&]() { mvwprintz( w_terrain, p.xy(), nccol, ncstr ); } ); - //g->add_draw_callback( hit_cb ); } } @@ -3809,6 +3807,37 @@ void game::void_async_anim_curses() async_anim_layer_curses.clear(); } +void game::init_draw_blink_curses( const tripoint &p, const std::string &ncstr, + const nc_color &nccol ) +{ + std::pair anim( ncstr, nccol ); + blink_layer_curses[p] = anim; +} + +void game::draw_blink_curses() +{ + // game::draw_blink_curses can be called multiple times, storing each animation to be played in blink_layer_curses + // Iterate through every animation in async_anim_layer + for( const auto &anim : blink_layer_curses ) { + const tripoint p = anim.first - u.view_offset + tripoint( POSX - u.posx(), POSY - u.posy(), + -u.posz() ); + const std::string ncstr = anim.second.first; + const nc_color nccol = anim.second.second; + + mvwprintz( w_terrain, p.xy(), nccol, ncstr ); + } +} + +void game::void_blink_curses() +{ + blink_layer_curses.clear(); +} + +bool game::has_blink_curses() +{ + return !blink_layer_curses.empty(); +} + void game::draw( ui_adaptor &ui ) { if( test_mode ) { @@ -3821,6 +3850,7 @@ void game::draw( ui_adaptor &ui ) m.update_visibility_cache( ter_view_p.z ); werase( w_terrain ); + void_blink_curses(); draw_ter(); for( auto it = draw_callbacks.begin(); it != draw_callbacks.end(); ) { shared_ptr_fast cb = it->lock(); @@ -3832,6 +3862,10 @@ void game::draw( ui_adaptor &ui ) } } draw_async_anim_curses(); + // Only draw blinking symbols when in active phase + if( blink_active_phase ) { + draw_blink_curses(); + } wnoutrefresh( w_terrain ); draw_panels( true ); @@ -3938,8 +3972,13 @@ void game::draw_critter( const Creature &critter, const tripoint ¢er ) // Monster is below // TODO: Make this show something more informative than just green 'v' // TODO: Allow looking at this mon with look command - // TODO: Redraw this after weather etc. animations - mvwputch( w_terrain, point( mx, my ), c_green_cyan, 'v' ); + init_draw_blink_curses( tripoint( critter.pos().xy(), center.z ), "v", c_green_cyan ); + } + if( critter.posz() == center.z + 1 && + ( debug_mode || u.sees( critter ) ) && + m.valid_move( critter.pos(), critter.pos() + tripoint_below, false, true ) ) { + // Monster is above + init_draw_blink_curses( tripoint( critter.pos().xy(), center.z ), "^", c_green_cyan ); } return; } diff --git a/src/game.h b/src/game.h index 5b5ce3a5cde29..e631a35800078 100644 --- a/src/game.h +++ b/src/game.h @@ -257,6 +257,17 @@ class game std::map> async_anim_layer_curses; // NOLINT(cata-serialize) + public: + void init_draw_blink_curses( const tripoint &p, const std::string &ncstr, + const nc_color &nccol ); + void draw_blink_curses(); + void void_blink_curses(); + bool has_blink_curses(); + bool blink_active_phase = true; // NOLINT(cata-serialize) + protected: + std::map> + blink_layer_curses; // NOLINT(cata-serialize) + public: // when force_redraw is true, redraw all panel instead of just animated panels // mostly used after UI updates diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 33d53a08af417..2e1a166db92b4 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -216,6 +216,18 @@ class user_turn return elapsed_ms.count() > get_option( "ANIMATION_DELAY" ); } + std::chrono::steady_clock::time_point last_blink_transition = std::chrono::steady_clock::now(); + bool blink_timeout() { + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + std::chrono::milliseconds elapsed_ms = + std::chrono::duration_cast( now - last_blink_transition ); + if( elapsed_ms.count() > get_option( "BLINK_SPEED" ) ) { + last_blink_transition = now; + return true; + } + return false; + } + }; input_context game::get_player_input( std::string &action ) @@ -397,6 +409,12 @@ input_context game::get_player_input( std::string &action ) #endif } + if( g->has_blink_curses() && current_turn.blink_timeout() ) { + // Toggle blink phase and redraw + g->blink_active_phase = !g->blink_active_phase; + g->invalidate_main_ui_adaptor(); + } + ui_manager::redraw_invalidated(); } while( handle_mouseview( ctxt, action ) && uquit != QUIT_WATCH && ( action != "TIMEOUT" || !current_turn.has_timeout_elapsed() ) ); diff --git a/tools/json_tools/generate_overlay_ids.py b/tools/json_tools/generate_overlay_ids.py index 63c1e9cb778a2..3bbb6ec42186c 100755 --- a/tools/json_tools/generate_overlay_ids.py +++ b/tools/json_tools/generate_overlay_ids.py @@ -33,6 +33,7 @@ 'infrared_creature', 'run_nw', 'run_n', 'run_ne', 'run_w', 'run_e', 'run_sw', 'run_s', 'run_se', 'bash_complete', 'bash_effective', 'bash_ineffective', + 'shadow', ) ATTITUDES = ('hostile', 'neutral', 'friendly', 'other')