From a77429daf122bcb894f1b284bdc9c5d1324e56e2 Mon Sep 17 00:00:00 2001 From: Mark Langsdorf Date: Fri, 21 Dec 2018 12:27:11 -0600 Subject: [PATCH] vehicles: enable functional boats phase 1 add a function vehicle::is_in_water() and an underlying variable is_floating to replace ad-hoc calls to !floating.empty (with the long term plan of dynamically determining if a vehicle is in water to allow amphibious vehicles). rework the boat movement equations to use the same kind of math as the ground vehicle movement equations. Approximate boat draft and freeboard based on vehicle weight and percentage of structure that have boat boards. Add two more lines to the stats pane in the vehicle interaction menu and use the extra space to display water speed and acceleration and draft. Rework that section of the code to be more C++ and less C. --- src/game.cpp | 4 +- src/map.cpp | 2 +- src/veh_interact.cpp | 197 +++++++++++++++++++++++++++---------------- src/veh_interact.h | 8 +- src/vehicle.cpp | 151 ++++++++++++++++++++++++++++----- src/vehicle.h | 53 ++++++++++-- src/vehicle_move.cpp | 15 ++-- 7 files changed, 315 insertions(+), 115 deletions(-) diff --git a/src/game.cpp b/src/game.cpp index a1e0b0ab9aa2a..e167a29c4742e 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -10413,8 +10413,8 @@ bool game::plmove( int dx, int dy, int dz ) bool toDeepWater = m.has_flag( TFLAG_DEEP_WATER, dest_loc ); bool fromSwimmable = m.has_flag( "SWIMMABLE", u.pos() ); bool fromDeepWater = m.has_flag( TFLAG_DEEP_WATER, u.pos() ); - bool fromBoat = veh0 != nullptr && !veh0->floating.empty(); - bool toBoat = veh1 != nullptr && !veh1->floating.empty(); + bool fromBoat = veh0 != nullptr && veh0->is_in_water(); + bool toBoat = veh1 != nullptr && veh1->is_in_water(); if( toSwimmable && toDeepWater && !toBoat ) { // Dive into water! // Requires confirmation if we were on dry land previously diff --git a/src/map.cpp b/src/map.cpp index 1125b6d67113a..f7ef40036428b 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -511,7 +511,7 @@ void map::move_vehicle( vehicle &veh, const tripoint &dp, const tileray &facing } // If not enough wheels, mess up the ground a bit. - if( !vertical && !veh.valid_wheel_config( !veh.floating.empty() ) ) { + if( !vertical && !veh.valid_wheel_config( veh.is_in_water() ) ) { veh.velocity += veh.velocity < 0 ? 2000 : -2000; for( const auto &p : veh.get_points() ) { const ter_id &pter = ter( p ); diff --git a/src/veh_interact.cpp b/src/veh_interact.cpp index a215f02019c0c..c5b3010268fcb 100644 --- a/src/veh_interact.cpp +++ b/src/veh_interact.cpp @@ -198,7 +198,7 @@ veh_interact::veh_interact( vehicle &veh, int x, int y ) main_context.register_action( "HELP_KEYBINDINGS" ); main_context.register_action( "FILTER" ); - countDurability(); + count_durability(); cache_tool_availability(); allocate_windows(); } @@ -214,7 +214,6 @@ void veh_interact::allocate_windows() int mode_h = 1; int name_h = 1; - int stats_h = 6; page_size = grid_h - ( mode_h + stats_h + name_h ) - 2; @@ -2002,7 +2001,8 @@ void veh_interact::display_stats() werase(w_stats); const int extraw = ((TERMX - FULL_SCREEN_WIDTH) / 4) * 2; // see exec() - int x[18], y[18], w[18]; // 3 columns * 6 rows = 18 slots max + const int slots = 3 * stats_h; + int x[slots], y[slots], w[slots]; units::volume total_cargo = 0; units::volume free_cargo = 0; @@ -2014,66 +2014,91 @@ void veh_interact::display_stats() const int second_column = 33 + (extraw / 4); const int third_column = 65 + (extraw / 2); - for (int i = 0; i < 18; i++) { - if (i < 6) { // First column + for( int i = 0; i < slots; i++ ) { + if (i < stats_h ) { // First column x[i] = 1; y[i] = i; w[i] = second_column - 2; - } else if (i < 13) { // Second column + } else if( i < ( 2 * stats_h ) ) { // Second column x[i] = second_column; - y[i] = i - 6; + y[i] = i - stats_h; w[i] = third_column - second_column - 1; } else { // Third column x[i] = third_column; - y[i] = i - 13; + y[i] = i - 2 * stats_h; w[i] = extraw - third_column - 2; } } - fold_and_print( w_stats, y[0], x[0], w[0], c_light_gray, - _( "Safe/Top Speed: %3d/%3d %s" ), - int( convert_velocity( veh->safe_velocity( false ), VU_VEHICLE ) ), - int( convert_velocity( veh->max_velocity( false ), VU_VEHICLE ) ), - velocity_units( VU_VEHICLE ) ); - //TODO: extract accelerations units to its own function + bool is_boat = !veh->floating.empty(); + + const auto vel_to_int = []( const double vel ) { + return static_cast( convert_velocity( vel, VU_VEHICLE ) ); + }; - fold_and_print( w_stats, y[1], x[1], w[1], c_light_gray, - //~ /t means per turn - _( "Acceleration: %3d %s/t" ), - int( convert_velocity( veh->acceleration( false ), VU_VEHICLE ) ), - velocity_units( VU_VEHICLE ) ); - fold_and_print( w_stats, y[2], x[2], w[2], c_light_gray, + int i = 0; + if( !is_boat ) { + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, + _( "Safe/Top Speed: %3d/%3d %s" ), + vel_to_int( veh->safe_ground_velocity( false ) ), + vel_to_int( veh->max_ground_velocity( false ) ), + velocity_units( VU_VEHICLE ) ); + i += 1; + //TODO: extract accelerations units to its own function + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, + //~ /t means per turn + _( "Acceleration: %3d %s/t" ), + vel_to_int( veh->ground_acceleration( false ) ), + velocity_units( VU_VEHICLE ) ); + i += 1; + } else { + i += 2; + } + if( is_boat ) { + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, + _( "Water Safe/Top Speed: %3d/%3d %s" ), + vel_to_int( veh->safe_water_velocity( false ) ), + vel_to_int( veh->max_water_velocity( false ) ), + velocity_units( VU_VEHICLE ) ); + i += 1; + //TODO: extract accelerations units to its own function + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, + //~ /t means per turn + _( "Water Acceleration: %3d %s/t" ), + vel_to_int( veh->water_acceleration( false ) ), + velocity_units( VU_VEHICLE ) ); + i += 1; + } else { + i += 2; + } + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, _( "Mass: %5.0f %s" ), convert_weight( veh->total_mass() ), weight_units() ); - fold_and_print( w_stats, y[3], x[3], w[3], c_light_gray, + i += 1; + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, _( "Cargo Volume: %s/%s %s" ), - format_volume( total_cargo - free_cargo ).c_str(), - format_volume( total_cargo ).c_str(), - volume_units_abbr() ); + format_volume( total_cargo - free_cargo ), + format_volume( total_cargo ), volume_units_abbr() ); + i += 1; // Write the overall damage - mvwprintz(w_stats, y[4], x[4], c_light_gray, _("Status:")); - x[4] += utf8_width(_("Status:")) + 1; - fold_and_print(w_stats, y[4], x[4], w[4], totalDurabilityColor, totalDurabilityText); + mvwprintz( w_stats, y[i], x[i], c_light_gray, _( "Status:") ); + x[i] += utf8_width( _("Status:") ) + 1; + fold_and_print( w_stats, y[i], x[i], w[i], total_durability_color, total_durability_text ); + i += 1; - fold_and_print( w_stats, y[5], x[5], w[5], c_light_gray, wheel_state_description( *veh ).c_str() ); + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, + wheel_state_description( *veh ) ); + i += 1; //This lambda handles printing parts in the "Most damaged" and "Needs repair" cases //for the veh_interact ui - auto print_part = [&]( const char * str, int slot, vehicle_part *pt ) + const auto print_part = [&]( const std::string &str, int slot, vehicle_part *pt ) { mvwprintz( w_stats, y[slot], x[slot], c_light_gray, str); - auto iw = utf8_width( str ) + 1; - x[slot] += iw; - w[slot] -= iw; - - const auto hoff = fold_and_print( w_stats, y[slot], x[slot], w[slot], - pt->is_broken() ? c_dark_gray : pt->base.damage_color(), pt->name() ); - - // If fold_and_print did write on the next line(s), shift the following entries, - // hoff == 1 is already implied and expected - one line is consumed at least. - for( size_t i = slot + 1; i < sizeof( y ) / sizeof( y[0] ); ++i ) { - y[i] += hoff - 1; - } + int iw = utf8_width( str ) + 1; + return fold_and_print( w_stats, y[slot], x[slot] + iw, w[slot], + pt->is_broken() ? c_dark_gray : pt->base.damage_color(), + pt->name() ); }; vehicle_part *mostDamagedPart = get_most_damaged_part(); @@ -2081,41 +2106,65 @@ void veh_interact::display_stats() // Write the most damaged part if( mostDamagedPart ) { - const char *damaged_header = mostDamagedPart == most_repairable ? - _( "Most damaged:" ) : _( "Most damaged (can't repair):" ); - print_part( damaged_header, 6, mostDamagedPart ); + const std::string damaged_header = mostDamagedPart == most_repairable ? + _( "Most damaged:" ) : + _( "Most damaged (can't repair):" ); + i += print_part( damaged_header, i, mostDamagedPart ); + } else { + i += 1; } + // Write the part that needs repair the most. if( most_repairable && most_repairable != mostDamagedPart ) { - const char * needsRepair = _( "Needs repair:" ); - print_part( needsRepair, 7, most_repairable ); + const std::string needsRepair = _( "Needs repair:" ); + i += print_part( needsRepair, i, most_repairable ); + } else { + i += 1; } - bool is_boat = !veh->floating.empty(); - - fold_and_print( w_stats, y[8], x[8], w[8], c_light_gray, + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, _( "Air drag: %5.2f" ), veh->coeff_air_drag() ); + i += 1; + if( is_boat ) { - fold_and_print( w_stats, y[9], x[9], w[9], c_light_gray, + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, _( "Water drag: %5.2f"), veh->coeff_water_drag() ); - } else { - fold_and_print( w_stats, y[9], x[9], w[9], c_light_gray, + } + i += 1; + + if( !is_boat ) { + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, _( "Rolling drag: %5.2f"), veh->coeff_rolling_drag() ); } - fold_and_print( w_stats, y[10], x[10], w[10], c_light_gray, + i += 1; + + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, _( "Static drag: %5d"), veh->static_drag( false ) ); - fold_and_print( w_stats, y[11], x[11], w[11], c_light_gray, + i += 1; + + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, _( "Offroad: %4d%%" ), - int( veh->k_traction( veh->wheel_area( is_boat ) * 0.5f ) * 100 ) ); + static_cast( veh->k_traction( veh->wheel_area( is_boat ) * + 0.5f ) * 100 ) ); + i += 1; + + if( is_boat ) { + fold_and_print( w_stats, y[i], x[i], w[i], c_light_gray, + _( "Draft: %4.2fm" ), + veh->water_draft() ); + i += 1; + } + + i = std::max( i, 2 * stats_h ); // Print fuel percentage & type name only if it fits in the window, 10 is width of "E...F 100%" - veh->print_fuel_indicators (w_stats, y[13], x[13], fuel_index, true, - ( x[ 13 ] + 10 < getmaxx( w_stats ) ), - ( x[ 13 ] + 10 < getmaxx( w_stats ) ) ); + veh->print_fuel_indicators( w_stats, y[i], x[i], fuel_index, true, + ( x[ i ] + 10 < getmaxx( w_stats ) ), + ( x[ i ] + 10 < getmaxx( w_stats ) ) ); wrefresh(w_stats); } @@ -2384,7 +2433,7 @@ void veh_interact::display_details( const vpart_info *part ) wrefresh(w_details); } -void veh_interact::countDurability() +void veh_interact::count_durability() { int qty = std::accumulate( veh->parts.begin(), veh->parts.end(), 0, []( int lhs, const vehicle_part &rhs ) { @@ -2396,27 +2445,27 @@ void veh_interact::countDurability() return lhs + rhs.base.max_damage(); } ); - double pct = double( qty ) / double( total ); + int pct = 100 * qty / total; - if( pct < 0.05 ) { - totalDurabilityText = _( "like new" ); - totalDurabilityColor = c_light_green; + if( pct < 5 ) { + total_durability_text = _( "like new" ); + total_durability_color = c_light_green; - } else if( pct < 0.33 ) { - totalDurabilityText = _( "dented" ); - totalDurabilityColor = c_yellow; + } else if( pct < 33 ) { + total_durability_text = _( "dented" ); + total_durability_color = c_yellow; - } else if( pct < 0.66 ) { - totalDurabilityText = _( "battered" ); - totalDurabilityColor = c_magenta; + } else if( pct < 66 ) { + total_durability_text = _( "battered" ); + total_durability_color = c_magenta; - } else if( pct < 1.00 ) { - totalDurabilityText = _( "wrecked" ); - totalDurabilityColor = c_red; + } else if( pct < 100 ) { + total_durability_text = _( "wrecked" ); + total_durability_color = c_red; } else { - totalDurabilityText = _( "destroyed" ); - totalDurabilityColor = c_dark_gray; + total_durability_text = _( "destroyed" ); + total_durability_color = c_dark_gray; } } diff --git a/src/veh_interact.h b/src/veh_interact.h index 66b0b5af118bc..e68eac0951482 100644 --- a/src/veh_interact.h +++ b/src/veh_interact.h @@ -68,6 +68,8 @@ class veh_interact int cpart = -1; int page_size; int fuel_index = 0; /** Starting index of where to start printing fuels from */ + // height of the stats window + const int stats_h = 8; catacurses::window w_grid; catacurses::window w_mode; catacurses::window w_msg; @@ -156,10 +158,10 @@ class veh_interact std::function action = {} ); void move_overview_line( int ); - void countDurability(); + void count_durability(); - std::string totalDurabilityText; - nc_color totalDurabilityColor; + std::string total_durability_text; + nc_color total_durability_color; /** Returns the most damaged part's index, or -1 if they're all healthy. */ vehicle_part *get_most_damaged_part() const; diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 425c578f13489..27e534e300f33 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -1252,7 +1252,6 @@ int vehicle::install_part( const point dp, const vehicle_part &new_part ) refresh(); coeff_air_changed = true; - coeff_water_changed = true; return parts.size() - 1; } @@ -1522,7 +1521,6 @@ bool vehicle::remove_part( int p ) g->m.dirty_vehicle_list.insert( this ); refresh(); coeff_air_changed = true; - coeff_water_changed = true; return shift_if_needed(); } @@ -1554,9 +1552,7 @@ void vehicle::part_removal_cleanup() shift_if_needed(); refresh(); // Rebuild cached indices coeff_air_dirty = coeff_air_changed; - coeff_water_dirty = coeff_water_changed; coeff_air_changed = false; - coeff_water_changed = false; } void vehicle::remove_carried_flag() @@ -2804,7 +2800,7 @@ bool vehicle::is_moving() const return velocity != 0; } -int vehicle::acceleration( const bool fueled, int at_vel_in_vmi ) const +int vehicle::ground_acceleration( const bool fueled, int at_vel_in_vmi ) const { if( !( engine_on || skidding ) ) { return 0; @@ -2818,6 +2814,22 @@ int vehicle::acceleration( const bool fueled, int at_vel_in_vmi ) const return cmps_to_vmiph( accel_at_vel ); } +int vehicle::water_acceleration( const bool fueled, int at_vel_in_vmi ) const +{ + if( !( engine_on || skidding ) ) { + return 0; + } + int target_vmiph = std::max( at_vel_in_vmi, std::max( 1000, + max_water_velocity( fueled ) / 4 ) ); + int cmps = vmiph_to_cmps( target_vmiph ); + int engine_power_ratio = total_power_w( fueled ) / to_kilogram( total_mass() ); + int accel_at_vel = 100 * 100 * engine_power_ratio / cmps; + add_msg( m_debug, "%s: water accel at %d vimph is %d", name, target_vmiph, + cmps_to_vmiph( accel_at_vel ) ); + return cmps_to_vmiph( accel_at_vel ); +} + + // cubic equation solution // don't use complex numbers unless necessary and it's usually not // see https://math.vanderbilt.edu/schectex/courses/cubic/ for the gory details @@ -2852,6 +2864,14 @@ double simple_cubic_solution( double a, double b, double c, double d ) } } +int vehicle::acceleration( const bool fueled, int at_vel_in_vmi ) const +{ + if( is_floating ) { + return water_acceleration( fueled, at_vel_in_vmi ); + } + return ground_acceleration( fueled, at_vel_in_vmi ); +} + int vehicle::current_acceleration( const bool fueled ) const { return acceleration( fueled, std::abs( velocity ) ); @@ -2882,7 +2902,7 @@ int vehicle::current_acceleration( const bool fueled ) const // c_air_drag * v^3 + c_rolling_drag * v^2 + c_rolling_drag * 33.3 * v - engine power = 0 // solve for v with the simplified cubic equation solver // got it? quiz on Wednesday. -int vehicle::max_velocity( const bool fueled ) const +int vehicle::max_ground_velocity( const bool fueled ) const { int total_engine_w = total_power_w( fueled ); double c_rolling_drag = coeff_rolling_drag(); @@ -2894,8 +2914,33 @@ int vehicle::max_velocity( const bool fueled ) const return mps_to_vmiph( max_in_mps ); } -// the same physics as max_velocity, but with a smaller engine power -int vehicle::safe_velocity( const bool fueled ) const +// the same physics as ground velocity, but there's no rolling resistance so the math is easy +// F_drag = F_water_drag + F_air_drag +// F_drag = c_water_drag * velocity^2 + c_air_drag * velocity^2 +// F_drag = ( c_water_drag + c_air_drag ) * velocity^2 +// F_prop = engine_power / velocity +// F_prop = F_drag +// engine_power / velocity = ( c_water_drag + c_air_drag ) * velocity^2 +// engine_power = ( c_water_drag + c_air_drag ) * velocity^3 +// velocity^3 = engine_power / ( c_water_drag + c_air_drag ) +// velocity = cube root( engine_power / ( c_water_drag + c_air_drag ) ) +int vehicle::max_water_velocity( const bool fueled ) const +{ + int total_engine_w = total_power_w( fueled ); + double total_drag = coeff_water_drag() + coeff_air_drag(); + double max_in_mps = std::cbrt( total_engine_w / total_drag ); + add_msg( m_debug, "%s: power %d, c_air %3.2f, c_water %3.2f, water max_in_mps %3.2f", + name, total_engine_w, coeff_air_drag(), coeff_water_drag(), max_in_mps ); + return mps_to_vmiph( max_in_mps ); +} + +int vehicle::max_velocity( const bool fueled ) const +{ + return is_floating ? max_water_velocity( fueled ) : max_ground_velocity( fueled ); +} + +// the same physics as max_ground_velocity, but with a smaller engine power +int vehicle::safe_ground_velocity( const bool fueled ) const { int effective_engine_w = total_power_w( fueled, true ); double c_rolling_drag = coeff_rolling_drag(); @@ -2905,6 +2950,20 @@ int vehicle::safe_velocity( const bool fueled ) const return mps_to_vmiph( safe_in_mps ); } +// the same physics as max_water_velocity, but with a smaller engine power +int vehicle::safe_water_velocity( const bool fueled ) const +{ + int total_engine_w = total_power_w( fueled, true ); + double total_drag = coeff_water_drag() + coeff_air_drag(); + double safe_in_mps = std::cbrt( total_engine_w / total_drag ); + return mps_to_vmiph( safe_in_mps ); +} + +int vehicle::safe_velocity( const bool fueled ) const +{ + return is_floating ? safe_water_velocity( fueled ) : safe_ground_velocity( fueled ); +} + bool vehicle::do_environmental_effects() { bool needed = false; @@ -3216,28 +3275,81 @@ double vehicle::coeff_rolling_drag() const return coefficient_rolling_resistance; } +double vehicle::water_draft() const +{ + if( coeff_water_dirty ) { + coeff_water_drag(); + } + return draft_m; +} + +bool vehicle::can_float() const +{ + if( coeff_water_dirty ) { + coeff_water_drag(); + } + // Someday I'll deal with submarines, but now, you can only float if you have freeboard + return draft_m < hull_height; +} + +bool vehicle::is_in_water() const +{ + return is_floating; +} + double vehicle::coeff_water_drag() const { if( !coeff_water_dirty ) { return coefficient_water_resistance; } std::vector structure_indices = all_parts_at_location( part_location_structure ); + if( structure_indices.empty() ) { + // huh? + coeff_water_dirty = false; + hull_height = 0.3; + draft_m = 1.0; + return 1250.0; + } + double hull_coverage = floating.size() / structure_indices.size(); + + int min_x = 0; + int max_x = 0; int min_y = 0; int max_y = 0; + // find how many rows and columns the vehicle has for( int p : structure_indices ) { + min_x = std::min( min_x, parts[p].mount.x ); + max_x = std::max( max_x, parts[p].mount.x ); min_y = std::min( min_y, parts[p].mount.y ); max_y = std::max( max_y, parts[p].mount.y ); } - int width = max_y - min_y; - // todo: calculate actual coefficent of water drag - // todo: calculate actual draft - double draft = 1; - constexpr double water_density = 1000; // kg/m^3 - double c_water_drag = 0.45; + // assume a rectangular footprint instead of doing a stepwise integration by row + double width = tile_to_width( max_y - min_y + 1 ); + // only count board board tiles to determine area. + double area = width * ( max_x - min_x + 1 ) * std::max( 0.1, hull_coverage ); + // treat the hullform as a tetrahedron for half it's length, and a rectangular block + // for the rest. the mass of the water displaced by those shapes is equal to the mass + // of the vehicle (Archimedes principle, eh?) and the volume of that water is the volume + // of the hull below the waterline divided by the density of water. apply math to get + // depth. + // volume of the block = width * length / 2 * depth + // volume of the tetrahedron = 1/3 * area of the triangle * depth + // area of the triangle = 1/2 triangle length * width = 1/2 * length/2 * width + // volume of the tetrahedron = 1/3 * 1/4 * length * width * depth + // hull volume underwater = 1/2 * width * length * depth + 1/12 * length * width * depth + // 7/12 * length * width * depth = hull_volume = water_mass / water density + // water_mass = vehicle_mass + // 7/12 * length * width * depth = vehicle_mass / water_density + // depth = 12/7 * vehicle_mass / water_density / ( length * width ) + constexpr double water_density = 1000.0; // kg/m^3 + draft_m = 12 / 7 * to_kilogram( total_mass() ) / water_density / area; + // increase the streamlining as more of the boat is covered in boat boards + double c_water_drag = 1.25 - hull_coverage; + // hull height starts at 0.3m and goes up as you add more boat boards + hull_height = 0.3 + 0.5 * hull_coverage; // F_water_drag = c_water_drag * cross_area * 1/2 * water_density * v^2 // coeff_water_resistance = c_water_drag * cross_area * 1/2 * water_density - coefficient_water_resistance = c_water_drag * tile_to_width( width ) * draft * - 0.5 * water_density; + coefficient_water_resistance = c_water_drag * width * draft_m * 0.5 * water_density; coeff_water_dirty = false; return coefficient_water_resistance; } @@ -3278,7 +3390,7 @@ float vehicle::strain() const bool vehicle::sufficient_wheel_config( bool boat ) const { // @todo: Remove the limitations that boats can't move on land - if( boat || !floating.empty() ) { + if( boat || is_floating ) { return boat && floating.size() > 2; } if( wheelcache.empty() ) { @@ -3325,7 +3437,7 @@ bool vehicle::valid_wheel_config( bool boat ) const float vehicle::steering_effectiveness() const { - if( !floating.empty() ) { + if( is_floating ) { // I'M ON A BOAT return 1.0; } @@ -4239,6 +4351,7 @@ void vehicle::refresh() check_environmental_effects = true; insides_dirty = true; invalidate_mass(); + is_floating = !floating.empty(); } const point &vehicle::pivot_point() const @@ -4744,7 +4857,6 @@ int vehicle::damage_direct( int p, int dmg, damage_type type ) invalidate_mass(); coeff_air_changed = true; - coeff_water_changed = true; } if( parts[p].is_fuel_store() ) { @@ -4995,6 +5107,7 @@ void vehicle::invalidate_mass() // Anything that affects mass will also affect the pivot pivot_dirty = true; coeff_rolling_dirty = true; + coeff_water_dirty = true; } void vehicle::refresh_mass() const diff --git a/src/vehicle.h b/src/vehicle.h index 91f3479329b19..f9d0d8f8eb314 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -1034,19 +1034,36 @@ class vehicle // their safe power. int total_power_w( bool fueled = true, bool safe = false ) const; - // Get acceleration gained by combined power of all engines. If fueled == true, then only engines which - // vehicle have fuel for are accounted + // Get ground acceleration gained by combined power of all engines. If fueled == true, + // then only engines which the vehicle has fuel for are included + int ground_acceleration( bool fueled = true, int at_vel_in_vmi = -1 ) const; + // Get water acceleration gained by combined power of all engines. If fueled == true, + // then only engines which the vehicle has fuel for are included + int water_acceleration( bool fueled = true, int at_vel_in_vmi = -1 ) const; + // Get acceleration for the current movement mode int acceleration( bool fueled = true, int at_vel_in_vmi = -1 ) const; + + // Get the vehicle's actual current acceleration int current_acceleration( bool fueled = true ) const; // is the vehicle currently moving? bool is_moving() const; - // Get maximum velocity gained by combined power of all engines. If fueled == true, then only engines which - // vehicle have fuel for are accounted + // Get maximum ground velocity gained by combined power of all engines. + // If fueled == true, then only the engines which the vehicle has fuel for are included + int max_ground_velocity( bool fueled = true ) const; + // Get maximum water velocity gained by combined power of all engines. + // If fueled == true, then only the engines which the vehicle has fuel for are included + int max_water_velocity( bool fueled = true ) const; + // Get maximum velocity for the current movement mode int max_velocity( bool fueled = true ) const; - // Get safe velocity gained by combined power of all engines. If fueled == true, then only engines which - // vehicle have fuel for are accounted + // Get safe ground velocity gained by combined power of all engines. + // If fueled == true, then only the engines which the vehicle has fuel for are included + int safe_ground_velocity( bool fueled = true ) const; + // Get safe water velocity gained by combined power of all engines. + // If fueled == true, then only the engines which the vehicle has fuel for are included + int safe_water_velocity( bool fueled = true ) const; + // Get maximum velocity for the current movement mode int safe_velocity( bool fueled = true ) const; // Generate smoke from a part, either at front or back of vehicle depending on velocity. @@ -1089,6 +1106,23 @@ class vehicle */ double coeff_water_drag() const; + /** + * water draft in meters - how much of the vehicle's body is under water + * must be less than the hull height or the boat will sink + * at some point, also add boats with deep draft running around + */ + double water_draft() const; + + /** + * can_float + * does the vehicle have freeboard or does it overflow with whater? + */ + bool can_float() const; + /** + * is the vehicle mostly in water or mostly on fairly dry land? + */ + bool is_in_water() const; + /** * Traction coefficient of the vehicle. * 1.0 on road. Outside roads, depends on mass divided by wheel area @@ -1550,16 +1584,19 @@ class vehicle mutable bool coeff_rolling_dirty = true; mutable bool coeff_air_dirty = true; mutable bool coeff_water_dirty = true; - // air and water use a two stage dirty check: one dirty bit gets set on part install, + // air uses a two stage dirty check: one dirty bit gets set on part install, // removal, or breakage. The other dirty bit only gets set during part_removal_cleanup, // and that's the bit that controls recalculation. The intent is to only recalculate // the coeffs once per turn, even if multiple parts are destroyed in a collision mutable bool coeff_air_changed = true; - mutable bool coeff_water_changed = true; mutable double coefficient_air_resistance; mutable double coefficient_rolling_resistance; mutable double coefficient_water_resistance; + mutable double draft_m; + mutable double hull_height; + // is the vehicle currently mostly in water + mutable bool is_floating; }; #endif diff --git a/src/vehicle_move.cpp b/src/vehicle_move.cpp index ab3e481172db3..b39eeb714c5f8 100644 --- a/src/vehicle_move.cpp +++ b/src/vehicle_move.cpp @@ -66,7 +66,6 @@ int vehicle::slowdown( int at_velocity ) const // slowdown due to air resistance is proportional to square of speed double f_total_drag = coeff_air_drag() * mps * mps; - bool is_floating = !floating.empty(); if( is_floating ) { // same with water resistance f_total_drag += coeff_water_drag() * mps * mps; @@ -105,9 +104,9 @@ void vehicle::thrust( int thd ) bool pl_ctrl = player_in_control( g->u ); // No need to change velocity if there are no wheels - if( !valid_wheel_config( !floating.empty() ) && velocity == 0 ) { + if( !valid_wheel_config( is_floating ) && velocity == 0 ) { if( pl_ctrl ) { - if( floating.empty() ) { + if( !is_floating ) { add_msg( _( "The %s doesn't have enough wheels to move!" ), name ); } else { add_msg( _( "The %s is too leaky!" ), name ); @@ -132,7 +131,7 @@ void vehicle::thrust( int thd ) } return; } - int max_vel = max_velocity() * traction; + int max_vel = traction * max_velocity(); // Get braking power int brk = std::max( 1000, abs( max_vel ) * 3 / 10 ); @@ -188,7 +187,7 @@ void vehicle::thrust( int thd ) consume_fuel( load, 1 ); //break the engines a bit, if going too fast. - int strn = static_cast( ( strain() * strain() * 100 ) ); + int strn = static_cast( strain() * strain() * 100 ); for( size_t e = 0; e < engines.size(); e++ ) { do_engine_damage( e, strn ); } @@ -1034,7 +1033,7 @@ bool vehicle::act_on_map() } const float wheel_traction_area = g->m.vehicle_wheel_traction( *this ); - const float traction = k_traction( wheel_traction_area ); + const float traction = is_floating ? 1.0f : k_traction( wheel_traction_area ); // TODO: Remove this hack, have vehicle sink a z-level if( wheel_traction_area < 0 ) { add_msg( m_bad, _( "Your %s sank." ), name ); @@ -1055,7 +1054,7 @@ bool vehicle::act_on_map() stop(); // TODO: Remove this hack // TODO: Amphibious vehicles - if( floating.empty() ) { + if( !is_floating ) { add_msg( m_info, _( "Your %s can't move on this terrain." ), name ); } else { add_msg( m_info, _( "Your %s is beached." ), name ); @@ -1180,7 +1179,7 @@ float map::vehicle_wheel_traction( const vehicle &veh ) const { const tripoint pt = veh.global_pos3(); // TODO: Remove this and allow amphibious vehicles - if( !veh.floating.empty() ) { + if( veh.is_in_water() ) { return vehicle_buoyancy( veh ); }