527 lines
20 KiB
C++
527 lines
20 KiB
C++
#include "vehicle.h" // IWYU pragma: associated
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdlib>
|
|
#include <set>
|
|
#include <string>
|
|
|
|
#include "calendar.h"
|
|
#include "cata_utility.h"
|
|
#include "catacharset.h"
|
|
#include "color.h"
|
|
#include "cursesdef.h"
|
|
#include "debug.h"
|
|
#include "itype.h"
|
|
#include "optional.h"
|
|
#include "options.h"
|
|
#include "output.h"
|
|
#include "string_formatter.h"
|
|
#include "translations.h"
|
|
#include "units.h"
|
|
#include "units_utility.h"
|
|
#include "veh_type.h"
|
|
#include "vpart_position.h"
|
|
|
|
static const std::string part_location_structure( "structure" );
|
|
static const ammotype ammo_battery( "battery" );
|
|
|
|
static const itype_id fuel_type_muscle( "muscle" );
|
|
static const itype_id itype_battery( "battery" );
|
|
|
|
std::string vehicle::disp_name() const
|
|
{
|
|
return string_format( _( "the %s" ), name );
|
|
}
|
|
|
|
char vehicle::part_sym( const int p, const bool exact, const bool include_fake ) const
|
|
{
|
|
if( p < 0 || p >= static_cast<int>( parts.size() ) || parts[p].removed ) {
|
|
return ' ';
|
|
}
|
|
|
|
int displayed_part = exact ? p : part_displayed_at( parts[p].mount, include_fake );
|
|
if( displayed_part == -1 ) {
|
|
displayed_part = p;
|
|
}
|
|
|
|
const vehicle_part &vp = parts.at( displayed_part );
|
|
const vpart_info &vp_info = part_info( displayed_part );
|
|
|
|
if( part_flag( displayed_part, VPFLAG_OPENABLE ) && vp.open ) {
|
|
// open door
|
|
return '\'';
|
|
} else {
|
|
if( vp.is_broken() ) {
|
|
return vp_info.sym_broken;
|
|
} else if( vp.variant.empty() ) {
|
|
return vp_info.sym;
|
|
} else {
|
|
const auto vp_symbol = vp_info.symbols.find( vp.variant );
|
|
if( vp_symbol == vp_info.symbols.end() ) {
|
|
return vp_info.sym;
|
|
} else {
|
|
return vp_symbol->second;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// similar to part_sym(int p) but for use when drawing SDL tiles. Called only by cata_tiles
|
|
// during draw_vpart vector returns at least 1 element, max of 2 elements. If 2 elements the
|
|
// second denotes if it is open or damaged
|
|
std::string vehicle::part_id_string( const int p, char &part_mod ) const
|
|
{
|
|
part_mod = 0;
|
|
if( p < 0 || p >= static_cast<int>( parts.size() ) || parts[p].removed ) {
|
|
return "";
|
|
}
|
|
|
|
int displayed_part = part_displayed_at( parts[p].mount, true );
|
|
if( displayed_part < 0 || displayed_part >= static_cast<int>( parts.size() ) ||
|
|
parts[ displayed_part ].removed ) {
|
|
return "";
|
|
}
|
|
|
|
const vehicle_part &vp = parts.at( displayed_part );
|
|
|
|
if( part_flag( displayed_part, VPFLAG_OPENABLE ) && vp.open ) {
|
|
// open
|
|
part_mod = 1;
|
|
} else if( vp.is_broken() ) {
|
|
// broken
|
|
part_mod = 2;
|
|
}
|
|
|
|
return vp.id.str() + ( vp.variant.empty() ? "" : "_" + vp.variant );
|
|
}
|
|
|
|
nc_color vehicle::part_color( const int p, const bool exact, const bool include_fake ) const
|
|
{
|
|
if( p < 0 || p >= static_cast<int>( parts.size() ) ) {
|
|
return c_black;
|
|
}
|
|
|
|
nc_color col;
|
|
|
|
int parm = -1;
|
|
|
|
//If armoring is present and the option is set, it colors the visible part
|
|
if( get_option<bool>( "VEHICLE_ARMOR_COLOR" ) ) {
|
|
parm = part_with_feature( p, VPFLAG_ARMOR, false );
|
|
}
|
|
|
|
if( parm >= 0 ) {
|
|
col = part_info( parm ).color;
|
|
} else {
|
|
const int displayed_part = exact ? p : part_displayed_at( parts[p].mount, include_fake );
|
|
|
|
if( displayed_part < 0 || displayed_part >= static_cast<int>( parts.size() ) ) {
|
|
return c_black;
|
|
}
|
|
if( parts[displayed_part].blood > 200 ) {
|
|
col = c_red;
|
|
} else if( parts[displayed_part].blood > 0 ) {
|
|
col = c_light_red;
|
|
} else if( parts[displayed_part].is_broken() ) {
|
|
col = part_info( displayed_part ).color_broken;
|
|
} else {
|
|
col = part_info( displayed_part ).color;
|
|
}
|
|
|
|
}
|
|
|
|
if( exact ) {
|
|
return col;
|
|
}
|
|
|
|
// curtains turn windshields gray
|
|
int curtains = part_with_feature( p, VPFLAG_CURTAIN, false );
|
|
if( curtains >= 0 ) {
|
|
if( part_with_feature( p, VPFLAG_WINDOW, true ) >= 0 && !parts[curtains].open ) {
|
|
col = part_info( curtains ).color;
|
|
}
|
|
}
|
|
|
|
//Invert colors for cargo parts with stuff in them
|
|
int cargo_part = part_with_feature( p, VPFLAG_CARGO, true );
|
|
if( cargo_part > 0 && !get_items( cargo_part ).empty() ) {
|
|
return invert_color( col );
|
|
} else {
|
|
return col;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints a list of all parts to the screen inside of a boxed window, possibly
|
|
* highlighting a selected one.
|
|
* @param win The window to draw in.
|
|
* @param y1 The y-coordinate to start drawing at.
|
|
* @param max_y Draw no further than this y-coordinate.
|
|
* @param width The width of the window.
|
|
* @param p The index of the part being examined.
|
|
* @param hl The index of the part to highlight (if any).
|
|
* @param detail Whether or not to show detailed contents for fuel components.
|
|
*/
|
|
int vehicle::print_part_list( const catacurses::window &win, int y1, const int max_y, int width,
|
|
int p, int hl /*= -1*/, bool detail, bool include_fakes ) const
|
|
{
|
|
if( p < 0 || p >= static_cast<int>( parts.size() ) ) {
|
|
return y1;
|
|
}
|
|
std::vector<int> pl = this->parts_at_relative( parts[p].mount, true, include_fakes );
|
|
int y = y1;
|
|
for( size_t i = 0; i < pl.size(); i++ ) {
|
|
const vehicle_part &vp = parts[ pl [ i ] ];
|
|
if( vp.is_fake && !vp.is_active_fake ) {
|
|
continue;
|
|
}
|
|
if( y >= max_y ) {
|
|
mvwprintz( win, point( 1, y ), c_yellow, _( "More parts here…" ) );
|
|
++y;
|
|
break;
|
|
}
|
|
|
|
|
|
std::string partname = vp.name();
|
|
|
|
if( vp.is_fuel_store() && !vp.ammo_current().is_null() ) {
|
|
if( detail ) {
|
|
if( vp.ammo_current() == itype_battery ) {
|
|
partname += string_format( _( " (%s/%s charge)" ), vp.ammo_remaining(),
|
|
vp.ammo_capacity( ammo_battery ) );
|
|
} else if( vp.ammo_current()->stack_size > 0 ) {
|
|
const itype *pt_ammo_cur = item::find_type( vp.ammo_current() );
|
|
auto stack = units::legacy_volume_factor / pt_ammo_cur->stack_size;
|
|
partname += string_format( _( " (%.1fL %s)" ),
|
|
round_up( units::to_liter( vp.ammo_remaining() * stack ), 1 ),
|
|
item::nname( vp.ammo_current() ) );
|
|
}
|
|
} else {
|
|
partname += string_format( " (%s)", item::nname( vp.ammo_current() ) );
|
|
}
|
|
}
|
|
|
|
if( part_flag( pl[i], "CARGO" ) ) {
|
|
//~ used/total volume of a cargo vehicle part
|
|
partname += string_format( _( " (vol: %s/%s %s)" ),
|
|
format_volume( stored_volume( pl[i] ) ),
|
|
format_volume( max_volume( pl[i] ) ),
|
|
volume_units_abbr() );
|
|
}
|
|
|
|
bool armor = part_flag( pl[i], "ARMOR" );
|
|
std::string left_sym;
|
|
std::string right_sym;
|
|
if( armor ) {
|
|
left_sym = "(";
|
|
right_sym = ")";
|
|
} else if( part_info( pl[i] ).location == part_location_structure ) {
|
|
left_sym = "[";
|
|
right_sym = "]";
|
|
} else {
|
|
left_sym = "-";
|
|
right_sym = "-";
|
|
}
|
|
nc_color sym_color = static_cast<int>( i ) == hl ? hilite( c_light_gray ) : c_light_gray;
|
|
mvwprintz( win, point( 1, y ), sym_color, left_sym );
|
|
trim_and_print( win, point( 2, y ), getmaxx( win ) - 4,
|
|
static_cast<int>( i ) == hl ? hilite( c_light_gray ) : c_light_gray, partname );
|
|
wprintz( win, sym_color, right_sym );
|
|
|
|
if( i == 0 && vpart_position( const_cast<vehicle &>( *this ), pl[i] ).is_inside() ) {
|
|
//~ indicates that a vehicle part is inside
|
|
mvwprintz( win, point( width - 2 - utf8_width( _( "Interior" ) ), y ), c_light_gray,
|
|
_( "Interior" ) );
|
|
} else if( i == 0 ) {
|
|
//~ indicates that a vehicle part is outside
|
|
mvwprintz( win, point( width - 2 - utf8_width( _( "Exterior" ) ), y ), c_light_gray,
|
|
_( "Exterior" ) );
|
|
}
|
|
y++;
|
|
}
|
|
|
|
// print the label for this location
|
|
const cata::optional<std::string> label = vpart_position( const_cast<vehicle &>( *this ),
|
|
p ).get_label();
|
|
if( label && y <= max_y ) {
|
|
mvwprintz( win, point( 1, y++ ), c_light_red, _( "Label: %s" ), label->c_str() );
|
|
}
|
|
|
|
return y;
|
|
}
|
|
|
|
/**
|
|
* Prints a list of descriptions for all parts to the screen inside of a boxed window
|
|
* @param win The window to draw in.
|
|
* @param max_y Draw no further than this y-coordinate.
|
|
* @param width The width of the window.
|
|
* @param p The index of the part being examined.
|
|
* @param start_at Which vehicle part to start printing at.
|
|
* @param start_limit the part index beyond which the display is full
|
|
*/
|
|
void vehicle::print_vparts_descs( const catacurses::window &win, int max_y, int width, int p,
|
|
int &start_at, int &start_limit ) const
|
|
{
|
|
if( p < 0 || p >= static_cast<int>( parts.size() ) ) {
|
|
return;
|
|
}
|
|
|
|
std::vector<int> pl = this->parts_at_relative( parts[p].mount, true );
|
|
std::string msg;
|
|
|
|
int lines = 0;
|
|
/*
|
|
* start_at and start_limit interaction is little tricky
|
|
* start_at and start_limit start at 0 when moving to a new frame
|
|
* if all the descriptions are displayed in the window, start_limit stays at 0 and
|
|
* start_at is capped at 0 - so no scrolling at all.
|
|
* if all the descriptions aren't displayed, start_limit jumps to the last displayed part
|
|
* and the next scrollthrough can start there - so scrolling down happens.
|
|
* when the scroll reaches the point where all the remaining descriptions are displayed in
|
|
* the window, start_limit is set to start_at again.
|
|
* on the next attempted scrolldown, start_limit is set to the nth item, and start_at is
|
|
* capped to the nth item, so no more scrolling down.
|
|
* start_at can always go down, but never below 0, so scrolling up is only possible after
|
|
* some scrolling down has occurred.
|
|
* important! the calling function needs to track p, start_at, and start_limit, and set
|
|
* start_limit to 0 if p changes.
|
|
*/
|
|
start_at = std::max( 0, std::min( start_at, start_limit ) );
|
|
if( start_at ) {
|
|
msg += std::string( "<color_yellow>" ) + "< " + _( "More parts here…" ) + "</color>\n";
|
|
lines += 1;
|
|
}
|
|
for( size_t i = start_at; i < pl.size(); i++ ) {
|
|
const vehicle_part &vp = parts[ pl [ i ] ];
|
|
std::string possible_msg;
|
|
const nc_color name_color = vp.is_broken() ? c_dark_gray : c_light_green;
|
|
possible_msg += colorize( vp.name(), name_color ) + "\n";
|
|
const nc_color desc_color = vp.is_broken() ? c_dark_gray : c_light_gray;
|
|
// -4 = -2 for left & right padding + -2 for "> "
|
|
int new_lines = 2 + vp.info().format_description( possible_msg, desc_color, width - 4 );
|
|
if( vp.has_flag( vehicle_part::carrying_flag ) ) {
|
|
possible_msg += _( " Carrying a vehicle on a rack.\n" );
|
|
new_lines += 1;
|
|
}
|
|
if( vp.has_flag( vehicle_part::carried_flag ) ) {
|
|
possible_msg += string_format( _( " Part of a %s carried on a rack.\n" ),
|
|
vp.carried_name() );
|
|
new_lines += 1;
|
|
}
|
|
|
|
possible_msg += "</color>\n";
|
|
if( lines + new_lines <= max_y ) {
|
|
msg += possible_msg;
|
|
lines += new_lines;
|
|
start_limit = start_at;
|
|
} else {
|
|
msg += std::string( "<color_yellow>" ) + _( "More parts here…" ) + " >" + "</color>\n";
|
|
start_limit = i;
|
|
break;
|
|
}
|
|
}
|
|
werase( win );
|
|
// -2 for left & right padding
|
|
// NOLINTNEXTLINE(cata-use-named-point-constants)
|
|
fold_and_print( win, point( 1, 0 ), width - 2, c_light_gray, msg );
|
|
wnoutrefresh( win );
|
|
}
|
|
|
|
/**
|
|
* Returns an array of fuel types that can be printed
|
|
* @return An array of printable fuel type ids
|
|
*/
|
|
std::vector<itype_id> vehicle::get_printable_fuel_types() const
|
|
{
|
|
std::set<itype_id> opts;
|
|
for( const vpart_reference &vpr : get_all_parts() ) {
|
|
const vehicle_part &pt = vpr.part();
|
|
if( !pt.has_flag( vehicle_part::carried_flag ) && pt.is_fuel_store() &&
|
|
!pt.ammo_current().is_null() ) {
|
|
opts.emplace( pt.ammo_current() );
|
|
}
|
|
}
|
|
|
|
std::vector<itype_id> res( opts.begin(), opts.end() );
|
|
|
|
std::sort( res.begin(), res.end(), [&]( const itype_id & lhs, const itype_id & rhs ) {
|
|
return basic_consumption( rhs ) < basic_consumption( lhs );
|
|
} );
|
|
|
|
return res;
|
|
}
|
|
|
|
/**
|
|
* Prints all of the fuel indicators of the vehicle
|
|
* @param win Pointer to the window to draw in.
|
|
* @param p location to draw at.
|
|
* @param start_index Starting index in array of fuel gauges to start reading from
|
|
* @param fullsize true if it's expected to print multiple rows
|
|
* @param verbose true if there should be anything after the gauge (either the %, or number)
|
|
* @param desc true if the name of the fuel should be at the end
|
|
* @param isHorizontal true if the menu is not vertical
|
|
*/
|
|
void vehicle::print_fuel_indicators( const catacurses::window &win, const point &p, int start_index,
|
|
bool fullsize, bool verbose, bool desc, bool isHorizontal )
|
|
{
|
|
auto fuels = get_printable_fuel_types();
|
|
if( fuels.empty() ) {
|
|
return;
|
|
}
|
|
if( !fullsize ) {
|
|
for( size_t e = 0; e < engines.size(); e++ ) {
|
|
// if only one display, print the first engine that's on and consumes power
|
|
if( is_engine_on( e ) &&
|
|
!( is_perpetual_type( e ) || is_engine_type( e, fuel_type_muscle ) ) ) {
|
|
print_fuel_indicator( win, p, parts[ engines [ e ] ].fuel_current(), verbose,
|
|
desc );
|
|
return;
|
|
}
|
|
}
|
|
// or print the first fuel if no engines
|
|
print_fuel_indicator( win, p, fuels.front(), verbose, desc );
|
|
return;
|
|
}
|
|
|
|
int yofs = 0;
|
|
int max_gauge = ( isHorizontal ? 12 : 5 ) + start_index;
|
|
int max_size = std::min( static_cast<int>( fuels.size() ), max_gauge );
|
|
|
|
for( int i = start_index; i < max_size; i++ ) {
|
|
const itype_id &f = fuels[i];
|
|
print_fuel_indicator( win, p + point( 0, yofs ), f, fuel_used_last_turn, verbose, desc );
|
|
yofs++;
|
|
}
|
|
|
|
// check if the current index is less than the max size minus 12 or 5, to indicate that there's more
|
|
if( start_index < static_cast<int>( fuels.size() ) - ( isHorizontal ? 12 : 5 ) ) {
|
|
mvwprintz( win, p + point( 0, yofs ), c_light_green, ">" );
|
|
wprintz( win, c_light_gray, " for more" );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prints a fuel gauge for a vehicle
|
|
* @param win Pointer to the window to draw in.
|
|
* @param p location to draw at.
|
|
* @param fuel_type ID of the fuel type to draw
|
|
* @param verbose true if there should be anything after the gauge (either the %, or number)
|
|
* @param desc true if the name of the fuel should be at the end
|
|
* @param fuel_usages map of fuel types to consumption for verbose
|
|
*/
|
|
void vehicle::print_fuel_indicator( const catacurses::window &win, const point &p,
|
|
const itype_id &fuel_type, bool verbose, bool desc )
|
|
{
|
|
std::map<itype_id, float> fuel_usages;
|
|
print_fuel_indicator( win, p, fuel_type, fuel_usages, verbose, desc );
|
|
}
|
|
|
|
void vehicle::print_fuel_indicator( const catacurses::window &win, const point &p,
|
|
const itype_id &fuel_type,
|
|
std::map<itype_id, float> fuel_usages,
|
|
bool verbose, bool desc )
|
|
{
|
|
static constexpr std::array<char, 5> fsyms = { 'E', '\\', '|', '/', 'F' };
|
|
nc_color col_indf1 = c_light_gray;
|
|
int cap = fuel_capacity( fuel_type );
|
|
int f_left = fuel_left( fuel_type );
|
|
nc_color f_color = item::find_type( fuel_type )->color;
|
|
// NOLINTNEXTLINE(cata-text-style): not an ellipsis
|
|
mvwprintz( win, p, col_indf1, "E...F" );
|
|
int amnt = cap > 0 ? f_left * 99 / cap : 0;
|
|
int indf = ( amnt / 20 ) % 5;
|
|
mvwprintz( win, p + point( indf, 0 ), f_color, "%c", fsyms[indf] );
|
|
if( verbose ) {
|
|
if( debug_mode ) {
|
|
mvwprintz( win, p + point( 6, 0 ), f_color, "%d/%d", f_left, cap );
|
|
} else {
|
|
mvwprintz( win, p + point( 6, 0 ), f_color, "%d", f_left * 100 / cap );
|
|
wprintz( win, c_light_gray, "%c", 045 );
|
|
}
|
|
}
|
|
if( desc ) {
|
|
wprintz( win, c_light_gray, " - %s", item::nname( fuel_type ) );
|
|
}
|
|
if( verbose ) {
|
|
auto fuel_data = fuel_usages.find( fuel_type );
|
|
int rate = 0;
|
|
std::string units;
|
|
if( fuel_data != fuel_usages.end() ) {
|
|
rate = consumption_per_hour( fuel_type, fuel_data->second );
|
|
units = _( "mL" );
|
|
}
|
|
if( fuel_type == itype_battery ) {
|
|
rate += power_to_energy_bat( net_battery_charge_rate_w(), 1_hours );
|
|
units = _( "kJ" );
|
|
}
|
|
if( rate != 0 ) {
|
|
int tank_use = 0;
|
|
nc_color tank_color = c_light_green;
|
|
std::string tank_goal = _( "full" );
|
|
if( rate > 0 ) {
|
|
tank_use = cap - f_left;
|
|
if( !tank_use ) {
|
|
return;
|
|
}
|
|
} else {
|
|
if( !f_left ) {
|
|
return;
|
|
}
|
|
tank_use = f_left;
|
|
tank_color = c_light_red;
|
|
tank_goal = _( "empty" );
|
|
}
|
|
|
|
// promote to double so esimate doesn't overflow for high fuel values
|
|
// 3600 * tank_use overflows signed 32 bit when tank_use is over ~596523
|
|
double turns = to_turns<double>( 60_minutes );
|
|
time_duration estimate = time_duration::from_turns( turns * tank_use / std::abs( rate ) );
|
|
|
|
if( debug_mode ) {
|
|
wprintz( win, tank_color, _( ", %d %s(%4.2f%%)/hour, %s until %s" ),
|
|
rate, units, 100.0 * rate / cap, to_string_clipped( estimate ), tank_goal );
|
|
} else {
|
|
wprintz( win, tank_color, _( ", %3.1f%% / hour, %s until %s" ),
|
|
100.0 * rate / cap, to_string_clipped( estimate ), tank_goal );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void vehicle::print_speed_gauge( const catacurses::window &win, const point &p, int spacing ) const
|
|
{
|
|
if( spacing < 0 ) {
|
|
spacing = 0;
|
|
}
|
|
if( !cruise_on ) {
|
|
return;
|
|
}
|
|
|
|
// Color is based on how much vehicle is straining beyond its safe velocity
|
|
const float strain = this->strain();
|
|
nc_color col_vel = strain <= 0 ? c_light_blue :
|
|
( strain <= 0.2 ? c_yellow :
|
|
( strain <= 0.4 ? c_light_red : c_red ) );
|
|
// Get cruising (target) velocity, and current (actual) velocity
|
|
int t_speed = static_cast<int>( convert_velocity( cruise_velocity, VU_VEHICLE ) );
|
|
int c_speed = static_cast<int>( convert_velocity( velocity, VU_VEHICLE ) );
|
|
auto ndigits = []( int value ) {
|
|
return value == 0 ? 1 :
|
|
( value > 0 ?
|
|
static_cast<int>( std::log10( static_cast<double>( std::abs( value ) ) ) ) + 1 :
|
|
static_cast<int>( std::log10( static_cast<double>( std::abs( value ) ) ) ) + 2 );
|
|
};
|
|
const std::string type = get_option<std::string> ( "USE_METRIC_SPEEDS" );
|
|
int t_offset = ndigits( t_speed );
|
|
int c_offset = ndigits( c_speed );
|
|
|
|
// Target cruising velocity in green
|
|
mvwprintz( win, p, c_light_green, "%d", t_speed );
|
|
mvwprintz( win, p + point( t_offset + spacing, 0 ), c_light_gray, "<" );
|
|
// Current velocity in color indicating engine strain
|
|
mvwprintz( win, p + point( t_offset + 1 + 2 * spacing, 0 ), col_vel, "%d", c_speed );
|
|
// Units of speed (mph, km/h, t/t)
|
|
mvwprintz( win, p + point( t_offset + c_offset + 1 + 3 * spacing, 0 ), c_light_gray, type );
|
|
}
|