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 ); }