diff --git a/src/game.cpp b/src/game.cpp index fcb92ae80dde5..f39722f608af6 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -5621,7 +5621,7 @@ void game::control_vehicle() const bool controls_ok = controls_idx >= 0; // controls available to "drive" const bool reins_ok = reins_idx >= 0 // reins + animal available to "drive" && veh->has_engine_type( fuel_type_animal, false ) - && veh->has_harnessed_animal(); + && veh->get_harnessed_animal(); if( veh->player_in_control( u ) ) { // player already "driving" - offer ways to leave if( controls_ok ) { diff --git a/src/grab.cpp b/src/grab.cpp index 265127d4c9cab..3dc58eee71cb0 100644 --- a/src/grab.cpp +++ b/src/grab.cpp @@ -16,8 +16,6 @@ #include "vpart_position.h" #include "vpart_range.h" -static const efftype_id effect_harnessed( "harnessed" ); - bool game::grabbed_veh_move( const tripoint &dp ) { const optional_vpart_position grabbed_vehicle_vp = m.veh_at( u.pos() + u.grab_point ); @@ -32,14 +30,11 @@ bool game::grabbed_veh_move( const tripoint &dp ) return false; } const int grabbed_part = grabbed_vehicle_vp->part_index(); - for( const vpart_reference &vpr : grabbed_vehicle->get_all_parts() ) { - monster *mon = grabbed_vehicle->get_monster( vpr.part_index() ); - if( mon != nullptr && mon->has_effect( effect_harnessed ) ) { - add_msg( m_info, _( "You cannot move this vehicle whilst your %s is harnessed!" ), - mon->get_name() ); - u.grab( object_type::NONE ); - return false; - } + if( monster *mon = grabbed_vehicle->get_harnessed_animal() ) { + add_msg( m_info, _( "You cannot move this vehicle whilst your %s is harnessed!" ), + mon->get_name() ); + u.grab( object_type::NONE ); + return false; } const vehicle *veh_under_player = veh_pointer_or_null( m.veh_at( u.pos() ) ); if( grabbed_vehicle == veh_under_player ) { diff --git a/src/handle_action.cpp b/src/handle_action.cpp index 5669b2e5bb915..ceebaee45847f 100644 --- a/src/handle_action.cpp +++ b/src/handle_action.cpp @@ -469,7 +469,7 @@ static void pldrive( const tripoint &p ) const bool has_animal_controls = veh->part_with_feature( vp.mount, "CONTROL_ANIMAL", true ) >= 0; const bool has_controls = veh->part_with_feature( vp.mount, "CONTROLS", true ) >= 0; const bool has_animal = veh->has_engine_type( fuel_type_animal, false ) && - veh->has_harnessed_animal(); + veh->get_harnessed_animal(); if( !has_controls && !has_animal_controls ) { add_msg( m_info, _( "You can't drive the vehicle from here. You need controls!" ) ); player_character.controlling_vehicle = false; diff --git a/src/monmove.cpp b/src/monmove.cpp index f44754336da56..6a96c08f1030a 100644 --- a/src/monmove.cpp +++ b/src/monmove.cpp @@ -894,20 +894,22 @@ void monster::move() if( friendly > 0 ) { --friendly; } - - // don't move if a passenger in a moving vehicle - optional_vpart_position vp = here.veh_at( pos() ); - bool harness_part = static_cast( here.veh_at( pos() ).part_with_feature( "ANIMAL_CTRL", - true ) ); - if( vp && ( ( friendly != 0 && vp->vehicle().is_moving() && - vp->vehicle().get_monster( vp->part_index() ) ) || - // Don't move if harnessed, even if vehicle is stationary - has_effect( effect_harnessed ) ) ) { - moves = 0; - return; - // If harnessed monster finds itself moved from the harness point, the harness probably broke! - } else if( !harness_part && has_effect( effect_harnessed ) ) { - remove_effect( effect_harnessed ); + const optional_vpart_position ovp = here.veh_at( pos() ); + if( has_effect( effect_harnessed ) ) { + if( !ovp.part_with_feature( "ANIMAL_CTRL", true ) ) { + remove_effect( effect_harnessed ); // the harness part probably broke + } else { + moves = 0; + return; // don't move if harnessed + } + } + const std::optional vp_boardable = ovp.part_with_feature( "BOARDABLE", true ); + if( vp_boardable && friendly != 0 ) { + const vehicle &veh = vp_boardable->vehicle(); + if( veh.is_moving() && veh.get_monster( vp_boardable->part_index() ) ) { + moves = 0; + return; // don't move if friendly and passenger in a moving vehicle + } } // Set attitude to attitude to our current target monster_attitude current_attitude = attitude( nullptr ); diff --git a/src/veh_type.cpp b/src/veh_type.cpp index 282a47d4b167c..bd2455a475bb8 100644 --- a/src/veh_type.cpp +++ b/src/veh_type.cpp @@ -41,6 +41,8 @@ class npc; static const ammotype ammo_battery( "battery" ); +static const itype_id fuel_type_animal( "animal" ); + static const itype_id itype_null( "null" ); static const quality_id qual_JACK( "JACK" ); @@ -1000,6 +1002,15 @@ void vpart_info::check() if( part.has_flag( VPFLAG_ENABLED_DRAINS_EPOWER ) && part.epower == 0_W ) { debugmsg( "%s is set to drain epower, but has epower == 0", part.id.c_str() ); } + if( part.has_flag( VPFLAG_ENGINE ) ) { + if( part.power != 0_W && part.fuel_type == fuel_type_animal ) { + debugmsg( "engine part '%s' powered by '%s' can't define non-zero 'power'", + part.id.c_str(), part.fuel_type.str() ); + } else if( part.power == 0_W && part.fuel_type != fuel_type_animal ) { + debugmsg( "engine part '%s' powered by '%s' must define non zero 'power'", + part.id.c_str(), part.fuel_type.str() ); + } + } // Parts with non-zero epower must have a flag that affects epower usage static const std::vector handled = {{ "ENABLED_DRAINS_EPOWER", "SECURITY", "ENGINE", diff --git a/src/veh_type.h b/src/veh_type.h index a3e437559e16d..7054b36bfa5b3 100644 --- a/src/veh_type.h +++ b/src/veh_type.h @@ -493,9 +493,6 @@ class vpart_info int z_order = 0; // Display order in vehicle interact display int list_order = 0; - - /** Legacy parts don't specify installation requirements */ - bool legacy = true; }; struct vehicle_item_spawn { diff --git a/src/vehicle.cpp b/src/vehicle.cpp index 710f087554365..edca0ad081152 100644 --- a/src/vehicle.cpp +++ b/src/vehicle.cpp @@ -218,7 +218,7 @@ bool vehicle::player_in_control( const Character &p ) const if( vp && &vp->vehicle() == this && p.controlling_vehicle && ( ( part_with_feature( vp->mount(), "CONTROL_ANIMAL", true ) >= 0 && - has_engine_type( fuel_type_animal, false ) && has_harnessed_animal() ) || + has_engine_type( fuel_type_animal, false ) && get_harnessed_animal() ) || ( part_with_feature( vp->part_index(), VPFLAG_CONTROLS, false ) >= 0 ) ) ) { return true; @@ -1011,43 +1011,32 @@ const vpart_info &vehicle::part_info( int index, bool include_removed ) const units::power vehicle::part_vpower_w( const vehicle_part &vp, const bool at_full_hp ) const { const vpart_info &vpi = vp.info(); + const int vp_index = index_of_part( &vp ); units::power pwr = vpi.power; if( vpi.has_flag( VPFLAG_ENGINE ) ) { - if( pwr == 0_W ) { - pwr = vp.base.engine_displacement() * 373_W; - } if( vpi.fuel_type == fuel_type_animal ) { - monster *mon = get_monster( index_of_part( &vp ) ); - if( mon != nullptr && mon->has_effect( effect_harnessed ) ) { - // An animal that can carry twice as much weight, can pull a cart twice as hard. - pwr = units::from_watt( mon->get_speed() * ( mon->get_size() - 1 ) * 3 - * ( mon->get_mountable_weight_ratio() * 5 ) ); - } else { - pwr = 0_W; - } - } - // Weary multiplier - const float weary_mult = get_player_character().exertion_adjusted_move_multiplier(); - ///\EFFECT_STR increases power produced for MUSCLE_* vehicles - pwr += units::from_watt( get_player_character().str_cur - 8 ) * vpi.engine_muscle_power_factor() * - weary_mult; - /// wind-powered vehicles have differing power depending on wind direction - if( vpi.fuel_type == fuel_type_wind ) { - weather_manager &weather = get_weather(); - int windpower = weather.windspeed; - rl_vec2d windvec; - double raddir = ( ( weather.winddirection + 180 ) % 360 ) * ( M_PI / 180 ); - windvec = windvec.normalized(); - windvec.y = -std::cos( raddir ); - windvec.x = std::sin( raddir ); - rl_vec2d fv = face_vec(); - double dot = windvec.dot_product( fv ); - if( dot <= 0 ) { - dot = std::min( -0.1, dot ); - } else { - dot = std::max( 0.1, dot ); - } - units::power windeffectint = units::from_watt( ( windpower * dot ) * 200 ); + int factor = 0; + if( const monster *mon = get_monster( vp_index ); mon && mon->has_effect( effect_harnessed ) ) { + factor += mon->get_size() - 1; + factor *= mon->get_speed(); + factor *= mon->get_mountable_weight_ratio(); + } + pwr = 15_W * factor; + } else if( vpi.fuel_type == fuel_type_muscle ) { + if( const Character *muscle_user = get_passenger( vp_index ) ) { + ///\EFFECT_STR increases power produced for MUSCLE_* vehicles + const float muscle_multiplier = muscle_user->str_cur - 8; + const float weary_multiplier = muscle_user->exertion_adjusted_move_multiplier(); + const float engine_multiplier = vpi.engine_muscle_power_factor(); + pwr += units::from_watt( muscle_multiplier * weary_multiplier * engine_multiplier ); + } + } else if( vpi.fuel_type == fuel_type_wind ) { + /// wind-powered vehicles have differing power depending on wind direction + const weather_manager &weather = get_weather(); + const double raddir = ( ( weather.winddirection + 180 ) % 360 ) * ( M_PI / 180 ); + const rl_vec2d windvec( std::sin( raddir ), -std::cos( raddir ) ); + const double dot = windvec.dot_product( face_vec() ); + const units::power windeffectint = units::from_watt( weather.windspeed * dot * 200 ); pwr = std::max( 1_W, pwr + windeffectint ); } } @@ -2852,10 +2841,8 @@ std::vector> vehicle::find_lines_of_parts( std::vector x_parts; std::vector y_parts; - if( parts[part].is_fake ) { - // start from the real part, otherwise it fails in certain orientations - part = parts[part].fake_part_to; - } + // start from the real part, otherwise it fails in certain orientations + part = get_non_fake_part( part ); vpart_id part_id = part_info( part ).get_id(); // create vectors of parts on the same X or Y axis @@ -2961,25 +2948,14 @@ int vehicle::part_at( const point &dp ) const return -1; } -/** - * Given a vehicle part which is inside of this vehicle, returns the index of - * that part. This exists solely because activities relating to vehicle editing - * require the index of the vehicle part to be passed around. - * @param part The part to find. - * @param check_removed Check whether this part can be removed - * @return The part index, -1 if it is not part of this vehicle. - */ -int vehicle::index_of_part( const vehicle_part *const part, const bool check_removed ) const +int vehicle::index_of_part( const vehicle_part *part, bool include_removed ) const { - if( part != nullptr ) { - for( const vpart_reference &vp : get_all_parts() ) { - const vehicle_part &next_part = vp.part(); - if( !check_removed && next_part.removed ) { - continue; - } - if( part->id == next_part.id && part->mount == vp.mount() ) { - return vp.part_index(); - } + if( !part || ( !include_removed && part->removed ) ) { + return -1; + } + for( size_t i = 0; i < parts.size(); i++ ) { + if( &parts[i] == part ) { + return static_cast( i ); } } return -1; @@ -4485,21 +4461,18 @@ float vehicle::steering_effectiveness() const if( steering.empty() ) { return -1.0f; // No steering installed } - // If the only steering part is an animal harness, with no animal in, it - // is not steerable. - const vehicle_part &vp = parts[ steering[0] ]; - if( steering.size() == 1 && vp.info().fuel_type == fuel_type_animal ) { - monster *mon = get_monster( steering[0] ); - if( mon == nullptr || !mon->has_effect( effect_harnessed ) ) { - return -2.0f; - } + // no steering if only steering part is animal harness without animal in it + const vehicle_part &vp = parts[steering[0]]; + if( steering.size() == 1 && vp.info().fuel_type == fuel_type_animal && !get_harnessed_animal() ) { + return -2.0f; } // For now, you just need one wheel working for 100% effective steering. // TODO: return something less than 1.0 if the steering isn't so good // (unbalanced, long wheelbase, back-heavy vehicle with front wheel steering, // etc) - for( int p : steering ) { - if( parts[ p ].is_available() ) { + for( const int p : steering ) { + const vehicle_part &vp = parts[p]; + if( vp.is_available() ) { return 1.0f; } } @@ -5747,25 +5720,20 @@ void vehicle::gain_moves() } } -void vehicle::dump_items_from_part( const size_t index ) -{ - map &here = get_map(); - vehicle_part &vp = parts[ index ]; - for( item &e : vp.items ) { - here.add_item_or_charges( global_part_pos3( vp ), e ); - } - vp.items.clear(); -} - bool vehicle::decrement_summon_timer() { if( !summon_time_limit ) { return false; } if( *summon_time_limit <= 0_turns ) { - for( const vpart_reference &vp : get_all_parts() ) { - const size_t p = vp.part_index(); - dump_items_from_part( p ); + map &here = get_map(); + for( const vpart_reference &vpr : get_all_parts() ) { + vehicle_part &vp = vpr.part(); + const tripoint_bub_ms pos = bub_part_pos( vp ); + for( item &it : vp.items ) { + here.add_item_or_charges( pos, it ); + } + vp.items.clear(); } add_msg_if_player_sees( global_pos3(), m_info, _( "Your %s winks out of existence." ), name ); get_map().destroy_vehicle( this ); @@ -7654,19 +7622,15 @@ const vehicle_part &vehicle::part( int part_num ) const return parts[part_num]; } -int vehicle::get_non_fake_part( const int part_num ) +int vehicle::get_non_fake_part( const int part_num ) const { - if( part_num != -1 && part_num < part_count() ) { - const vehicle_part &vp = parts.at( part_num ); - if( vp.is_fake ) { - return vp.fake_part_to; - } else { - return part_num; - } + if( part_num < 0 || part_num >= part_count() ) { + debugmsg( "get_non_fake_part(%d) returning -1 on '%s', which has %d parts.", + part_num, disp_name(), parts.size() ); + return -1; } - debugmsg( "Returning -1 for get_non_fake_part on part_num %d on %s, which has %d parts.", part_num, - disp_name(), parts.size() ); - return -1; + const vehicle_part &vp = parts[part_num]; + return vp.is_fake ? vp.fake_part_to : part_num; } vehicle_part_range vehicle::get_all_parts() const diff --git a/src/vehicle.h b/src/vehicle.h index 095d9e51d7667..61457f9cdacbc 100644 --- a/src/vehicle.h +++ b/src/vehicle.h @@ -1220,8 +1220,9 @@ class vehicle bool below_roof = true, bool roof = true ) const; int roof_at_part( int p ) const; - // Given a part, finds its index in the vehicle - int index_of_part( const vehicle_part *part, bool check_removed = false ) const; + // Finds index of a given vehicle_part in parts vector, compared by address + // @return index or -1 if part is nullptr, not found or removed and include_removed is false. + int index_of_part( const vehicle_part *part, bool include_removed = false ) const; // get symbol for map char part_sym( int p, bool exact = false, bool include_fake = true ) const; @@ -1703,7 +1704,6 @@ class vehicle vehicle_stack get_items( int part ); std::vector &get_tools( vehicle_part &vp ); const std::vector &get_tools( const vehicle_part &vp ) const; - void dump_items_from_part( size_t index ); // Generates starting items in the car, should only be called when placed on the map void place_spawn_items(); @@ -1845,8 +1845,6 @@ class vehicle // @returns true if succeeded bool restore_folded_parts( const item &it ); - //handles locked vehicles interaction - bool interact_vehicle_locked(); //true if an alarm part is installed on the vehicle bool has_security_working() const; /** @@ -1890,7 +1888,7 @@ class vehicle //true if an engine exists with specified type //If enabled true, this engine must be enabled to return true bool has_engine_type( const itype_id &ft, bool enabled ) const; - bool has_harnessed_animal() const; + monster *get_harnessed_animal() const; //true if an engine exists without the specified type //If enabled true, this engine must be enabled to return true bool has_engine_type_not( const itype_id &ft, bool enabled ) const; @@ -2008,7 +2006,7 @@ class vehicle vehicle_part &part( int part_num ); const vehicle_part &part( int part_num ) const; // get the parent part of a fake part or return part_num otherwise - int get_non_fake_part( int part_num ); + int get_non_fake_part( int part_num ) const; // Updates active state on all fake_mounts based on whether they can fill a gap // map.cpp calls this in displace_vehicle void update_active_fakes(); diff --git a/src/vehicle_move.cpp b/src/vehicle_move.cpp index 91823b1582853..5b73847756600 100644 --- a/src/vehicle_move.cpp +++ b/src/vehicle_move.cpp @@ -1270,54 +1270,35 @@ void vehicle::handle_trap( const tripoint &p, int part ) } } -bool vehicle::has_harnessed_animal() const +monster *vehicle::get_harnessed_animal() const { for( size_t e = 0; e < parts.size(); e++ ) { const vehicle_part &vp = parts[ e ]; if( vp.info().fuel_type == fuel_type_animal ) { monster *mon = get_monster( e ); if( mon && mon->has_effect( effect_harnessed ) && mon->has_effect( effect_pet ) ) { - return true; + return mon; } } } - return false; + return nullptr; } void vehicle::selfdrive( const point &p ) { - if( !is_towed() && !magic ) { - for( size_t e = 0; e < parts.size(); e++ ) { - const vehicle_part &vp = parts[ e ]; - if( vp.info().fuel_type == fuel_type_animal ) { - monster *mon = get_monster( e ); - if( !mon || !mon->has_effect( effect_harnessed ) || !mon->has_effect( effect_pet ) ) { - is_following = false; - return; - } - } - } + if( !is_towed() && !magic && !get_harnessed_animal() ) { + is_following = false; + return; } - units::angle turn_delta = vehicles::steer_increment * p.x; - const float handling_diff = handling_difficulty(); - if( turn_delta != 0_degrees ) { - float eff = steering_effectiveness(); - if( eff == -2 ) { - return; + if( p.x != 0 ) { + if( steering_effectiveness() <= 0 ) { + return; // no steering } - - if( eff < 0 ) { - return; - } - - if( eff == 0 ) { - return; - } - turn( turn_delta ); + turn( p.x * vehicles::steer_increment ); } if( p.y != 0 ) { - int thr_amount = 100 * ( std::abs( velocity ) < 2000 ? 4 : 5 ); if( !is_towed() ) { + const int thr_amount = std::abs( velocity ) < 2000 ? 400 : 500; cruise_thrust( -p.y * thr_amount ); } else { thrust( -p.y ); @@ -1325,9 +1306,7 @@ void vehicle::selfdrive( const point &p ) } // TODO: Actually check if we're on land on water (or disable water-skidding) if( skidding && valid_wheel_config() ) { - ///\EFFECT_DEX increases chance of regaining control of a vehicle - - ///\EFFECT_DRIVING increases chance of regaining control of a vehicle + const float handling_diff = handling_difficulty(); if( handling_diff * rng( 1, 10 ) < 15 ) { velocity = static_cast( forward_velocity() ); skidding = false;