Skip to content

Commit

Permalink
Merge pull request #75467 from RenechCDDA/npc_driving_infra
Browse files Browse the repository at this point in the history
Genericize vehicle handling for characters (no special avatar stuff)
  • Loading branch information
dseguin authored Aug 18, 2024
2 parents 0050bb0 + 77e0dd9 commit b60b8ec
Show file tree
Hide file tree
Showing 8 changed files with 131 additions and 77 deletions.
23 changes: 14 additions & 9 deletions src/activity_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2273,30 +2273,35 @@ void activity_handlers::start_engines_finish( player_activity *act, Character *y
sfx::do_vehicle_engine_sfx();

if( attempted == 0 ) {
add_msg( m_info, _( "The %s doesn't have an engine!" ), veh->name );
you->add_msg_if_player( m_info, _( "The %s doesn't have an engine!" ), veh->name );
} else if( non_muscle_attempted > 0 ) {
//Some non-muscle engines tried to start
if( non_muscle_attempted == non_muscle_started ) {
//All of the non-muscle engines started
add_msg( n_gettext( "The %s's engine starts up.",
"The %s's engines start up.", non_muscle_started ), veh->name );
add_msg_if_player_sees( you->pos_bub(), n_gettext( "The %s's engine starts up.",
"The %s's engines start up.", non_muscle_started ), veh->name );
} else if( non_muscle_started > 0 ) {
//Only some of the non-muscle engines started
add_msg( n_gettext( "One of the %s's engines start up.",
"Some of the %s's engines start up.", non_muscle_started ), veh->name );
add_msg_if_player_sees( you->pos_bub(), n_gettext( "One of the %s's engines start up.",
"Some of the %s's engines start up.", non_muscle_started ), veh->name );
} else if( non_combustion_started > 0 ) {
//Non-combustions "engines" started
add_msg( _( "The %s is ready for movement." ), veh->name );
you->add_msg_if_player( _( "The %s is ready for movement." ), veh->name );
} else {
//All of the non-muscle engines failed
add_msg( m_bad, n_gettext( "The %s's engine fails to start.",
"The %s's engines fail to start.", non_muscle_attempted ), veh->name );
if( you->is_avatar() ) {
add_msg( m_bad, n_gettext( "The %s's engine fails to start.",
"The %s's engines fail to start.", non_muscle_attempted ), veh->name );
} else {
add_msg_if_player_sees( you->pos_bub(), n_gettext( "The %s's engine fails to start.",
"The %s's engines fail to start.", non_muscle_attempted ), veh->name );
}
}
}

if( take_control && !veh->engine_on && !veh->velocity ) {
you->controlling_vehicle = false;
add_msg( _( "You let go of the controls." ) );
you->add_msg_if_player( _( "You let go of the controls." ) );
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5541,7 +5541,7 @@ void game::control_vehicle()
u.controlling_vehicle = true;
add_msg( _( "You take control of the %s." ), veh->name );
} else {
veh->start_engines( true );
veh->start_engines( &u, true );
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/iuse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7365,7 +7365,7 @@ std::optional<int> iuse::remoteveh( Character *p, item *it, const tripoint &pos
g->setremoteveh( veh );
p->add_msg_if_player( m_good, _( "You take control of the vehicle." ) );
if( !veh->engine_on ) {
veh->start_engines();
veh->start_engines( p );
}
}
} else if( choice == 1 ) {
Expand Down
117 changes: 84 additions & 33 deletions src/vehicle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,28 @@ bool vehicle::player_in_control( const Character &p ) const
return remote_controlled( p );
}

bool vehicle::player_is_driving_this_veh() const
{
// Unfortunate code duplication for tests
// Debug switch to prevent vehicles from skidding
// without having to place the player in them.
if( tags.count( "IN_CONTROL_OVERRIDE" ) ) {
return true;
}
// Early out, nobody's driving
if( !get_driver() ) {
return false;
}
Character &player_character = get_player_character();
// Another easy out, just check if the player is controlling any vehicle
if( !player_character.controlling_vehicle ) {
return false;
}

// Lastly check if the player is controlling *this* vehicle
return player_in_control( player_character );
}

bool vehicle::remote_controlled( const Character &p ) const
{
vehicle *veh = g->remoteveh();
Expand Down Expand Up @@ -3418,6 +3440,23 @@ Character *vehicle::get_passenger( int you ) const
return nullptr;
}

bool vehicle::has_driver() const
{
return get_driver();
}

Character *vehicle::get_driver() const
{
// TODO: Gotta be a better way than this...
for( const vpart_reference &vp : get_all_parts() ) {
Character *occupant = vp.get_passenger();
if( occupant && player_in_control( *occupant ) ) {
return occupant;
}
}
return nullptr;
}

monster *vehicle::get_monster( int p ) const
{
p = part_with_feature( p, VPFLAG_BOARDABLE, false );
Expand Down Expand Up @@ -3570,21 +3609,24 @@ int64_t vehicle::fuel_left( const itype_id &ftype,

//muscle engines have infinite fuel
if( ftype == fuel_type_muscle ) {
Character &player_character = get_player_character();
// TODO: Allow NPCs to power those
const optional_vpart_position vp = get_map().veh_at( player_character.pos_bub() );
bool player_controlling = player_in_control( player_character );
Character *driver = get_driver();
if( !driver ) {
// FIXME: Should return fl, not arbitrarily assume player
driver = &get_player_character();
}
const optional_vpart_position vp = get_map().veh_at( driver->pos_bub() );

//if the engine in the player tile is a muscle engine, and player is controlling vehicle
if( vp && &vp->vehicle() == this && player_controlling ) {
//if the engine in the tile is a muscle engine, and someone is ready to control this vehicle
// TODO: Allow NPCs to power those
if( vp && &vp->vehicle() == this && driver ) {
const int p = avail_part_with_feature( vp->part_index(), VPFLAG_ENGINE );
if( p >= 0 ) {
const vehicle_part &vp = parts[p];
const vpart_info &vpi = vp.info();
if( vp.enabled && vpi.fuel_type == fuel_type_muscle ) {
// intact limbs allow using muscle engines from working
if( ( vpi.has_flag( "MUSCLE_LEGS" ) && player_character.get_working_leg_count() >= 2 ) ||
( vpi.has_flag( "MUSCLE_ARMS" ) && player_character.has_two_arms_lifting() ) ) {
if( ( vpi.has_flag( "MUSCLE_LEGS" ) && driver->get_working_leg_count() >= 2 ) ||
( vpi.has_flag( "MUSCLE_ARMS" ) && driver->has_two_arms_lifting() ) ) {
fl += 10;
}
}
Expand Down Expand Up @@ -4415,7 +4457,7 @@ bool vehicle::has_sufficient_rotorlift() const

bool vehicle::is_rotorcraft() const
{
return !rotors.empty() && player_in_control( get_player_character() ) &&
return !rotors.empty() && has_driver() &&
has_sufficient_rotorlift();
}

Expand Down Expand Up @@ -4912,63 +4954,71 @@ void vehicle::consume_fuel( int load, bool idling )
if( idling ) {
return;
}
Character &player_character = get_player_character();
Character *driver = get_driver();

// Only process muscle power and training things when someone is actually driving.
// Note that the vehicle can be moving even if nobody is driving it.
if( !driver ) {
// FIXME: Should return early, not arbitrarily assume player
// Make our pointer valid so we don't dereference nullptr and crash in the upcoming code
driver = &get_player_character();
}

// if engine is under load, player is actively piloting a vehicle, so train appropriate vehicle proficiency
if( load > 0 ) {
practice_pilot_proficiencies( player_character, in_deep_water );
practice_pilot_proficiencies( *driver, in_deep_water );
}

if( load > 0 && fuel_left( fuel_type_muscle ) > 0 &&
player_character.has_effect( effect_winded ) ) {
driver->has_effect( effect_winded ) ) {
cruise_velocity = 0;
if( velocity == 0 ) {
stop();
}
}
// we want this to update the activity level whenever we're using muscle power to move
if( load > 0 && fuel_left( fuel_type_muscle ) > 0 ) {
player_character.set_activity_level( ACTIVE_EXERCISE );
driver->set_activity_level( ACTIVE_EXERCISE );
//do this as a function of current load
// But only if the player is actually there!
int eff_load = load / 10;
int mod = 4 * st; // strain
const int base_staminaRegen = static_cast<int>
( get_option<float>( "PLAYER_BASE_STAMINA_REGEN_RATE" ) );
const int actual_staminaRegen = static_cast<int>( base_staminaRegen *
player_character.get_cardiofit() / player_character.get_cardio_acc_base() );
driver->get_cardiofit() / driver->get_cardio_acc_base() );
int base_burn = actual_staminaRegen - 3;
base_burn = std::max( eff_load / 3, base_burn );
//charge bionics when using muscle engine
const item muscle( "muscle" );
for( const bionic_id &bid : player_character.get_bionic_fueled_with_muscle() ) {
if( player_character.has_active_bionic( bid ) ) { // active power gen
for( const bionic_id &bid : driver->get_bionic_fueled_with_muscle() ) {
if( driver->has_active_bionic( bid ) ) { // active power gen
// more pedaling = more power
player_character.mod_power_level( muscle.fuel_energy() *
bid->fuel_efficiency *
load / 1000 );
driver->mod_power_level( muscle.fuel_energy() *
bid->fuel_efficiency *
load / 1000 );
mod += eff_load / 5;
} else { // passive power gen
player_character.mod_power_level( muscle.fuel_energy() *
bid->passive_fuel_efficiency *
load / 1000 );
driver->mod_power_level( muscle.fuel_energy() *
bid->passive_fuel_efficiency *
load / 1000 );
mod += eff_load / 10;
}
}
// decreased stamina burn scalable with load
if( player_character.has_active_bionic( bio_jointservo ) ) {
player_character.mod_power_level( units::from_kilojoule( static_cast<std::int64_t>( -std::max(
eff_load / 20, 1 ) ) ) );
if( driver->has_active_bionic( bio_jointservo ) ) {
driver->mod_power_level( units::from_kilojoule( static_cast<std::int64_t>( -std::max(
eff_load / 20, 1 ) ) ) );
mod -= std::max( eff_load / 5, 5 );
}

player_character.mod_stamina( -( base_burn + mod ) );
driver->mod_stamina( -( base_burn + mod ) );
add_msg_debug( debugmode::DF_VEHICLE, "Load: %d", load );
add_msg_debug( debugmode::DF_VEHICLE, "Mod: %d", mod );
add_msg_debug( debugmode::DF_VEHICLE, "Burn: %d", -( base_burn + mod ) );

// player is actively powering a muscle engine, so train cycling proficiency
practice_athletic_proficiency( player_character );
// character is actively powering a muscle engine, so train cycling proficiency
practice_athletic_proficiency( *driver );
}
}

Expand Down Expand Up @@ -5318,7 +5368,7 @@ void vehicle::power_parts()
for( int elem : reactors ) {
parts[ elem ].enabled = false;
}
if( player_in_control( player_character ) || player_character.sees( global_pos3() ) ) {
if( player_is_driving_this_veh() || player_character.sees( global_pos3() ) ) {
add_msg( _( "The %s's reactor dies!" ), name );
}
}
Expand Down Expand Up @@ -5353,13 +5403,13 @@ void vehicle::power_parts()

is_alarm_on = false;
camera_on = false;
if( player_in_control( player_character ) || player_character.sees( global_pos3() ) ) {
if( player_is_driving_this_veh() || player_character.sees( global_pos3() ) ) {
add_msg( _( "The %s's battery dies!" ), name );
}
if( engine_epower < 0_W ) {
// Not enough epower to run gas engine ignition system
engine_on = false;
if( player_in_control( player_character ) || player_character.sees( global_pos3() ) ) {
if( player_is_driving_this_veh() || player_character.sees( global_pos3() ) ) {
add_msg( _( "The %s's engine dies!" ), name );
}
}
Expand Down Expand Up @@ -5796,8 +5846,9 @@ void vehicle::on_move()
}

Character &pc = get_player_character();
// TODO: Send ID of driver
get_event_bus().send<event_type::vehicle_moves>(
is_passenger( pc ), player_in_control( pc ), remote_controlled( pc ),
is_passenger( pc ), player_is_driving_this_veh(), remote_controlled( pc ),
is_flying_in_air(), is_watercraft() && can_float(), can_use_rails(),
is_falling, is_in_water( true ) && !can_float(),
skidding, velocity, sm_pos.z
Expand Down Expand Up @@ -6109,7 +6160,7 @@ void vehicle::gain_moves()
{
fuel_used_last_turn.clear();
check_falling_or_floating();
const bool pl_control = player_in_control( get_player_character() );
const bool pl_control = player_is_driving_this_veh();
if( is_moving() || is_falling ) {
if( !loose_parts.empty() ) {
shed_loose_parts();
Expand Down
12 changes: 9 additions & 3 deletions src/vehicle.h
Original file line number Diff line number Diff line change
Expand Up @@ -928,9 +928,11 @@ class vehicle
*/
bool mod_hp( vehicle_part &pt, int qty );

// check if given player controls this vehicle
// check if given character controls this vehicle
bool player_in_control( const Character &p ) const;
// check if player controls this vehicle remotely
// check if the *player* character controls this vehicle
bool player_is_driving_this_veh() const;
// check if the given character controls this vehicle remotely
bool remote_controlled( const Character &p ) const;

// initializes parts and fuel state for randomly generated vehicle and calls refresh()
Expand Down Expand Up @@ -1015,7 +1017,8 @@ class vehicle
// stop all engines
void stop_engines();
// Attempt to start the vehicle's active engines
void start_engines( bool take_control = false, bool autodrive = false );
void start_engines( Character *driver = nullptr, bool take_control = false,
bool autodrive = false );

// Engine backfire, making a loud noise
void backfire( const vehicle_part &vp ) const;
Expand Down Expand Up @@ -1394,6 +1397,9 @@ class vehicle
bool is_passenger( Character &c ) const;
// get passenger at part p
Character *get_passenger( int you ) const;
bool has_driver() const;
// get character that is currently controlling the vehicle's motion
Character *get_driver() const;
// get monster on a boardable part at p
monster *get_monster( int p ) const;

Expand Down
Loading

0 comments on commit b60b8ec

Please sign in to comment.