Skip to content

Commit

Permalink
Merge pull request #73739 from irwiss/veh-change-shape
Browse files Browse the repository at this point in the history
Visual UI for changing vehicle part shapes
  • Loading branch information
I-am-Erk committed May 13, 2024
2 parents 81ede3c + b4cf46d commit dd42dbb
Show file tree
Hide file tree
Showing 8 changed files with 454 additions and 46 deletions.
12 changes: 12 additions & 0 deletions src/animation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -843,6 +843,18 @@ void game::draw_cursor( const tripoint &p ) const
}
#endif

void game::draw_cursor_unobscuring( const tripoint_bub_ms &p ) const
{
const tripoint_rel_ms rp = relative_view_pos( *this, p );
mvwputch_inv( w_terrain, ( rp.xy() + point_north_east ).raw(), c_cyan, "" );
mvwputch_inv( w_terrain, ( rp.xy() + point_south_east ).raw(), c_cyan, "" );
mvwputch_inv( w_terrain, ( rp.xy() + point_north_west ).raw(), c_cyan, "" );
mvwputch_inv( w_terrain, ( rp.xy() + point_south_west ).raw(), c_cyan, "" );
#if defined(TILES)
tilecontext->init_draw_cursor( p );
#endif
}

#if defined(TILES)
void game::draw_highlight( const tripoint_bub_ms &p )
{
Expand Down
3 changes: 3 additions & 0 deletions src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,9 @@ class game
// TODO: Get rid of untyped overload
void draw_cursor( const tripoint &p ) const;
void draw_cursor( const tripoint_bub_ms &p ) const;
// Tiles: equivalent to draw_cusor
// Curses: draws diagonal arrows pointing at the tile so the target tile isn't obscured
void draw_cursor_unobscuring( const tripoint_bub_ms &p ) const;
// Draw a highlight graphic at p, for example when examining something.
// TILES only, in curses this does nothing
// TODO: Get rid of untyped overload
Expand Down
53 changes: 13 additions & 40 deletions src/veh_interact.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
#include "units_utility.h"
#include "value_ptr.h"
#include "veh_type.h"
#include "veh_shape.h"
#include "veh_utils.h"
#include "vehicle.h"
#include "vehicle_selector.h"
Expand Down Expand Up @@ -129,8 +130,17 @@ static void act_vehicle_unload_fuel( vehicle *veh );

player_activity veh_interact::serialize_activity()
{
const auto *pt = sel_vehicle_part;
const auto *vp = sel_vpart_info;
const vehicle_part *pt = sel_vehicle_part;
const vpart_info *vp = sel_vpart_info;

if( sel_cmd == 'p' ) {
if( !parts_here.empty() ) {
const vpart_reference part_here( *veh, parts_here[0] );
const vpart_reference displayed_part( *veh, veh->part_displayed_at( part_here.mount() ) );
return veh_shape( *veh ).start( tripoint_bub_ms( displayed_part.pos() ) );
}
return player_activity();
}

if( sel_cmd == 'q' || sel_cmd == ' ' || !vp ) {
return player_activity();
Expand Down Expand Up @@ -550,8 +560,7 @@ void veh_interact::do_main_loop()
finish = do_unload();
}
} else if( action == "CHANGE_SHAPE" ) {
// purely comestic
do_change_shape();
sel_cmd = 'p';
} else if( action == "ASSIGN_CREW" ) {
if( owned_by_player ) {
do_assign_crew();
Expand Down Expand Up @@ -1984,42 +1993,6 @@ static void do_change_shape_menu( vehicle_part &vp )
}
}

void veh_interact::do_change_shape()
{
if( cant_do( 'p' ) == task_reason::INVALID_TARGET ) {
msg = _( "No valid vehicle parts here." );
return;
}

restore_on_out_of_scope<std::optional<std::string>> prev_title( title );
title = _( "Choose part to change shape:" );

shared_ptr_fast<ui_adaptor> current_ui = create_or_get_ui_adaptor();
restore_on_out_of_scope<int> prev_hilight_part( highlight_part );

int part_selected = 0;

while( true ) {
vehicle_part &vp = veh->part( parts_here[part_selected] );

highlight_part = part_selected;
overview_enable = [this, part_selected]( const vehicle_part & pt ) {
return &pt == &veh->part( part_selected );
};

ui_manager::redraw();
const std::string action = main_context.handle_input();

if( action == "QUIT" ) {
break;
} else if( action == "CONFIRM" || action == "CHANGE_SHAPE" ) {
do_change_shape_menu( vp );
} else {
move_in_list( part_selected, action, parts_here.size() );
}
}
}

void veh_interact::do_assign_crew()
{
if( cant_do( 'w' ) != task_reason::CAN_DO ) {
Expand Down
1 change: 0 additions & 1 deletion src/veh_interact.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,6 @@ class veh_interact
void do_siphon();
// Returns true if exiting the screen
bool do_unload();
void do_change_shape();
void do_assign_crew();
void do_relabel();
/*@}*/
Expand Down
254 changes: 254 additions & 0 deletions src/veh_shape.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#include "veh_shape.h"

#include "animation.h"
#include "avatar.h"
#include "cached_options.h"
#include "game.h"
#include "options.h"
#include "output.h"
#include "player_activity.h"
#include "veh_type.h"
#include "veh_utils.h"
#include "ui_manager.h"

veh_shape::veh_shape( vehicle &vehicle ): veh( vehicle ) { }

player_activity veh_shape::start( const tripoint_bub_ms &pos )
{
avatar &you = get_avatar();
on_out_of_scope cleanup( []() {
get_map().invalidate_map_cache( get_avatar().view_offset.z );
} );
restore_on_out_of_scope<tripoint> view_offset_prev( you.view_offset );

cursor_allowed.clear();
for( const vpart_reference &part : veh.get_all_parts() ) {
cursor_allowed.insert( tripoint_bub_ms( part.pos() ) );
}

if( !set_cursor_pos( pos ) ) {
debugmsg( "failed to set cursor at given part" );
set_cursor_pos( tripoint_bub_ms( veh.global_part_pos3( 0 ) ) );
}

const auto target_ui_cb = make_shared_fast<game::draw_callback_t>(
[&]() {
const avatar &you = get_avatar();
g->draw_cursor_unobscuring( tripoint_bub_ms( you.pos() + you.view_offset ) );
} );
g->add_draw_callback( target_ui_cb );
ui_adaptor ui;
ui.mark_resize();
init_input();

std::string action = "CONFIRM";

while( true ) {
g->invalidate_main_ui_adaptor();
ui_manager::redraw();

if( action.empty() ) {
action = ctxt.handle_input( get_option<int>( "EDGE_SCROLL" ) );
}

if( handle_cursor_movement( action ) || ( action == "HELP_KEYBINDINGS" ) ) {
action.clear();
continue;
} else if( action == "CONFIRM" ) {
std::optional<vpart_reference> part;
do {
part = select_part_at_cursor( _( "Choose part to change shape" ),
_( "Confirm to select or quit to select another tile." ),
[]( const vpart_reference & vp ) {
if( vp.info().variants.size() <= 1 ) {
return ret_val<void>::make_failure( _( "Only one shape" ) );
} else if( vp.part_displayed() && vp.part_displayed()->part_index() != vp.part_index() ) {
return ret_val<void>::make_success( _( "Part is obscured" ) );
}
return ret_val<void>::make_success();
}, part );
if( part.has_value() ) {
change_part_shape( part.value() );
}
} while( part.has_value() );
action.clear();
} else if( action == "QUIT" ) {
return player_activity();
} else {
debugmsg( "here be dragons" );
return player_activity();
}
}
}

std::vector<vpart_reference> veh_shape::parts_under_cursor() const
{
std::vector<vpart_reference> res;
// TODO: tons of methods getting parts from vehicle but all of them seem inadequate?
for( int part_idx = 0; part_idx < veh.part_count_real(); part_idx++ ) {
const vehicle_part &p = veh.part( part_idx );
if( veh.bub_part_pos( p ) == get_cursor_pos() && !p.is_fake ) {
res.emplace_back( veh, part_idx );
}
}
return res;
}

std::optional<vpart_reference> veh_shape::select_part_at_cursor(
const std::string &title, const std::string &extra_description,
const std::function<ret_val<void>( const vpart_reference & )> &predicate,
const std::optional<vpart_reference> &preselect ) const
{
const std::vector<vpart_reference> parts = parts_under_cursor();
if( parts.empty() ) {
return std::nullopt;
}

uilist menu;
menu.desc_enabled = !extra_description.empty();
menu.desc_lines_hint = 1;
menu.hilight_disabled = true;
menu.w_x_setup = TERMX / 8;

for( const vpart_reference &pt : parts ) {
ret_val<void> predicate_result = predicate( pt );
uilist_entry entry( -1, true, MENU_AUTOASSIGN, pt.part().name(), "", predicate_result.str() );
entry.desc = extra_description;
entry.enabled = predicate_result.success();
entry.retval = predicate_result.success() ? menu.entries.size() : -2;
if( preselect && preselect->part_index() == pt.part_index() ) {
menu.selected = menu.entries.size();
}
menu.entries.push_back( entry );
}
menu.text = title;
menu.query();

return menu.ret >= 0 ? std::optional<vpart_reference>( parts[menu.ret] ) : std::nullopt;
}

void veh_shape::change_part_shape( vpart_reference vpr ) const
{
vehicle_part &part = vpr.part();
const vpart_info &vpi = part.info();
veh_menu menu( this->veh, _( "Choose cosmetic variant:" ) );
std::string chosen_variant = part.variant; // support for cancel

do {
menu.reset( false );

for( const auto &[vvid, vv] : vpi.variants ) {
menu.add( vv.get_label() )
.text_color( ( part.variant == vvid ) ? c_light_green : c_light_gray )
.keep_menu_open()
.skip_locked_check()
.skip_theft_check()
.location( veh.global_part_pos3( part ) )
.select( part.variant == vvid )
.desc( _( "Confirm to save or exit to revert" ) )
.symbol( vv.get_symbol_curses( 0_degrees, false ) )
.symbol_color( vpi.color )
.on_select( [&part, variant_id = vvid]() {
part.variant = variant_id;
} )
.on_submit( [&chosen_variant, variant_id = vvid]() {
chosen_variant = variant_id;
} );
}

// An ordering of the line drawing symbols that does not result in
// connecting when placed adjacent to each other vertically.
menu.sort( []( const veh_menu_item & a, const veh_menu_item & b ) {
const static std::map<int, int> symbol_order = {
{ LINE_XOXO, 0 }, { LINE_OXOX, 1 }, { LINE_XOOX, 2 }, { LINE_XXOO, 3 },
{ LINE_XXXX, 4 }, { LINE_OXXO, 5 }, { LINE_OOXX, 6 },
};
const auto a_iter = symbol_order.find( a._symbol );
const auto b_iter = symbol_order.find( b._symbol );
if( a_iter != symbol_order.end() ) {
return ( b_iter == symbol_order.end() ) || ( a_iter->second < b_iter->second );
} else {
return ( b_iter == symbol_order.end() ) && ( a._symbol < b._symbol );
}
} );
} while( menu.query() );

part.variant = chosen_variant;
}

tripoint_bub_ms veh_shape::get_cursor_pos() const
{
return cursor_pos;
}

bool veh_shape::set_cursor_pos( const tripoint_bub_ms &new_pos )
{
avatar &you = get_avatar();

int z = std::max( { new_pos.z(), -fov_3d_z_range, -OVERMAP_DEPTH } );
z = std::min( {z, fov_3d_z_range, OVERMAP_HEIGHT } );

if( !allow_zlevel_shift ) {
z = cursor_pos.z();
}
tripoint_bub_ms target_pos( new_pos.xy(), z );

if( cursor_allowed.find( target_pos ) == cursor_allowed.cend() ) {
return false;
}

if( z != cursor_pos.z() ) {
get_map().invalidate_map_cache( z );
}
cursor_pos = target_pos;
you.view_offset = cursor_pos.raw() - you.pos();
return true;
}

bool veh_shape::handle_cursor_movement( const std::string &action )
{
if( action == "MOUSE_MOVE" || action == "TIMEOUT" ) {
tripoint edge_scroll = g->mouse_edge_scrolling_terrain( ctxt );
set_cursor_pos( get_cursor_pos() + edge_scroll );
} else if( const std::optional<tripoint> delta = ctxt.get_direction( action ) ) {
set_cursor_pos( get_cursor_pos() + *delta ); // move cursor with directional keys
} else if( action == "zoom_in" ) {
g->zoom_in();
} else if( action == "zoom_out" ) {
g->zoom_out();
} else if( action == "SELECT" ) {
const std::optional<tripoint> mouse_pos = ctxt.get_coordinates( g->w_terrain );
if( !mouse_pos ) {
return false;
}
const tripoint_bub_ms mp( *mouse_pos );
if( get_cursor_pos() != mp ) {
set_cursor_pos( mp );
}
} else if( action == "LEVEL_UP" ) {
set_cursor_pos( get_cursor_pos() + tripoint_above );
} else if( action == "LEVEL_DOWN" ) {
set_cursor_pos( get_cursor_pos() + tripoint_below );
} else {
return false;
}

return true;
}

void veh_shape::init_input()
{
ctxt = input_context( "VEH_SHAPES", keyboard_mode::keycode );
ctxt.set_iso( true );
ctxt.register_directions();
ctxt.register_action( "CONFIRM" );
ctxt.register_action( "SELECT" );
ctxt.register_action( "QUIT" );
ctxt.register_action( "HELP_KEYBINDINGS" );
ctxt.register_action( "MOUSE_MOVE" );
ctxt.register_action( "LEVEL_UP" );
ctxt.register_action( "LEVEL_DOWN" );
ctxt.register_action( "REMOVE" );
ctxt.register_action( "zoom_out" );
ctxt.register_action( "zoom_in" );
}

0 comments on commit dd42dbb

Please sign in to comment.