Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate ACT_AIM to the activity actor system #40684

Merged
merged 10 commits into from
May 20, 2020
7 changes: 4 additions & 3 deletions doc/PLAYER_ACTIVITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ new activity.
functions needed for the new actor as well as the required serialization
functions. Don't forget to add the deserialization function of your new
activity actor to the `deserialize_functions` map towards the bottom of
`activity_actor.cpp`.
`activity_actor.cpp`. Define `canceled` function if activity modifies
some complex state that should be restored upon cancellation / interruption.

4. If this activity is resumable, `override`
`activity_actor::can_resume_with_internal`
Expand Down Expand Up @@ -87,8 +88,8 @@ There are several ways an activity can be ended:

Canceling an activity prevents the `activity_actor::finish`
function from running, and the activity does therefore not yield a
result. A copy of the activity is written to `Character::backlog`
if it's suspendable.
result. Instead, `activity_actor::canceled` is called. If activity is
suspendable, a copy of it is written to `Character::backlog`.

## Notes

Expand Down
285 changes: 285 additions & 0 deletions src/activity_actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@
#include "map.h"
#include "map_iterator.h"
#include "npc.h"
#include "options.h"
#include "output.h"
#include "pickup.h"
#include "player.h"
#include "player_activity.h"
#include "point.h"
#include "ranged.h"
#include "timed_event.h"
#include "uistate.h"

Expand All @@ -36,6 +38,7 @@ static const skill_id skill_computer( "computer" );

static const trait_id trait_ILLITERATE( "ILLITERATE" );

static const std::string flag_RELOAD_AND_SHOOT( "RELOAD_AND_SHOOT" );
static const std::string flag_USE_EAT_VERB( "USE_EAT_VERB" );

static const mtype_id mon_zombie( "mon_zombie" );
Expand All @@ -44,6 +47,287 @@ static const mtype_id mon_zombie_rot( "mon_zombie_rot" );
static const mtype_id mon_skeleton( "mon_skeleton" );
static const mtype_id mon_zombie_crawler( "mon_zombie_crawler" );

template<>
struct enum_traits<aim_activity_actor::WeaponSource> {
static constexpr aim_activity_actor::WeaponSource last =
aim_activity_actor::WeaponSource::NumWeaponSources;
};

namespace io
{
using WS = aim_activity_actor::WeaponSource;

template<>
std::string enum_to_string<WS>( WS data )
{
switch( data ) {
// *INDENT-OFF*
case WS::Wielded: return "Wielded";
case WS::Bionic: return "Bionic";
case WS::Mutation: return "Mutation";
// *INDENT-ON*
case WS::NumWeaponSources:
break;
}
debugmsg( "Invalid weapon source" );
abort();
}
} // namespace io

aim_activity_actor::aim_activity_actor()
{
initial_view_offset = g->u.view_offset;
}

aim_activity_actor aim_activity_actor::use_wielded()
{
return aim_activity_actor();
}

aim_activity_actor aim_activity_actor::use_bionic( const item &fake_gun,
const units::energy &cost_per_shot )
{
aim_activity_actor act = aim_activity_actor();
act.weapon_source = WeaponSource::Bionic;
act.bp_cost_per_shot = cost_per_shot;
act.fake_weapon = shared_ptr_fast<item>( new item( fake_gun ) );
return act;
}

aim_activity_actor aim_activity_actor::use_mutation( const item &fake_gun )
{
aim_activity_actor act = aim_activity_actor();
act.weapon_source = WeaponSource::Mutation;
act.fake_weapon = shared_ptr_fast<item>( new item( fake_gun ) );
return act;
}

void aim_activity_actor::start( player_activity &act, Character &/*who*/ )
{
// Time spent on aiming is determined on the go by the player
act.moves_total = 1;
act.moves_left = 1;
act.interruptable_with_kb = false;
}

void aim_activity_actor::do_turn( player_activity &act, Character &who )
{
if( aborted || finished ) {
// A shortcut that allows terminating this activity by setting 'aborted' or 'finished'
act.moves_left = 0;
return;
}
if( !who.is_avatar() ) {
debugmsg( "ACT_AIM not implemented for NPCs" );
aborted = true;
return;
}
avatar &you = g->u;

item *weapon = get_weapon();
if( !weapon || !avatar_action::can_fire_weapon( you, g->m, *weapon ) ) {
aborted = true;
return;
}

gun_mode gun = weapon->gun_current_mode();
if( first_turn && gun->has_flag( flag_RELOAD_AND_SHOOT ) && !gun->ammo_remaining() ) {
if( !load_RAS_weapon() ) {
aborted = true;
return;
}
}

g->temp_exit_fullscreen();
g->m.draw( g->w_terrain, you.pos() );
target_handler::trajectory trajectory = target_handler::mode_fire( you, *this );
g->reenter_fullscreen();

if( !aborted ) {
if( !trajectory.empty() ) {
finished = true;
fin_trajectory = trajectory;
}
// If aborting on the first turn, keep 'first_turn' as 'true'.
// This allows refunding moves spent on unloading RELOAD_AND_SHOOT weapons
// to simulate avatar not loading them in the first place
first_turn = false;
}
}

void aim_activity_actor::finish( player_activity &act, Character &who )
{
act.set_to_null();
restore_view();
if( aborted ) {
unload_RAS_weapon();
if( reload_requested ) {
// Reload the gun / select different arrows
// May assign ACT_RELOAD
g->reload_wielded( true );
}
return;
}

// Recenter our view
g->draw_ter();
wrefresh( g->w_terrain );
g->draw_panels();

// Fire!
item *weapon = get_weapon();
gun_mode gun = weapon->gun_current_mode();
int shots_fired = static_cast<player *>( &who )->fire_gun( fin_trajectory.back(), gun.qty, *gun );

// TODO: bionic power cost of firing should be derived from a value of the relevant weapon.
if( shots_fired && ( bp_cost_per_shot > 0_J ) ) {
who.mod_power_level( -bp_cost_per_shot * shots_fired );
}
}

void aim_activity_actor::canceled( player_activity &/*act*/, Character &/*who*/ )
{
restore_view();
unload_RAS_weapon();
}

void aim_activity_actor::serialize( JsonOut &jsout ) const
{
jsout.start_object();

jsout.member( "weapon_source", weapon_source );
if( weapon_source == WeaponSource::Bionic || weapon_source == WeaponSource::Mutation ) {
jsout.member( "fake_weapon", *fake_weapon );
}
jsout.member( "bp_cost_per_shot", bp_cost_per_shot );
jsout.member( "first_turn", first_turn );
jsout.member( "action", action );
jsout.member( "snap_to_target", snap_to_target );
jsout.member( "shifting_view", shifting_view );
jsout.member( "initial_view_offset", initial_view_offset );

jsout.end_object();
}

std::unique_ptr<activity_actor> aim_activity_actor::deserialize( JsonIn &jsin )
{
aim_activity_actor actor = aim_activity_actor();

JsonObject data = jsin.get_object();

data.read( "weapon_source", actor.weapon_source );
if( actor.weapon_source == WeaponSource::Bionic || actor.weapon_source == WeaponSource::Mutation ) {
actor.fake_weapon = shared_ptr_fast<item>( new item() );
data.read( "fake_weapon", *actor.fake_weapon );
}
data.read( "bp_cost_per_shot", actor.bp_cost_per_shot );
data.read( "first_turn", actor.first_turn );
data.read( "action", actor.action );
data.read( "snap_to_target", actor.snap_to_target );
data.read( "shifting_view", actor.shifting_view );
data.read( "initial_view_offset", actor.initial_view_offset );

return actor.clone();
}

item *aim_activity_actor::get_weapon()
{
switch( weapon_source ) {
case WeaponSource::Wielded:
// Check for lost gun (e.g. yanked by zombie technician)
// TODO: check that this is the same gun that was used to start aiming
return g->u.weapon.is_null() ? nullptr : &g->u.weapon;
case WeaponSource::Bionic:
case WeaponSource::Mutation:
// TODO: check if the player lost relevant bionic/mutation
return fake_weapon.get();
default:
debugmsg( "Invalid weapon source value" );
return nullptr;
}
}

void aim_activity_actor::restore_view()
{
bool changed_z = g->u.view_offset.z != initial_view_offset.z;
g->u.view_offset = initial_view_offset;
if( changed_z ) {
g->m.invalidate_map_cache( g->u.view_offset.z );
g->refresh_all();
}
}

bool aim_activity_actor::load_RAS_weapon()
{
// TODO: use activity for fetching ammo and loading weapon
player &you = g->u;
item *weapon = get_weapon();
gun_mode gun = weapon->gun_current_mode();
const auto ammo_location_is_valid = [&]() -> bool {
if( !you.ammo_location )
{
return false;
}
if( !gun->can_reload_with( you.ammo_location->typeId() ) )
{
return false;
}
if( square_dist( you.pos(), you.ammo_location.position() ) > 1 )
{
return false;
}
return true;
};
item::reload_option opt = ammo_location_is_valid() ? item::reload_option( &you, weapon,
weapon, you.ammo_location ) : you.select_ammo( *gun );
if( !opt ) {
// Menu canceled
return false;
}
int reload_time = 0;
reload_time += opt.moves();
if( !gun->reload( you, std::move( opt.ammo ), 1 ) ) {
// Reload not allowed
return false;
}

// Burn 0.2% max base stamina x the strength required to fire.
you.mod_stamina( gun->get_min_str() * static_cast<int>( 0.002f *
get_option<int>( "PLAYER_MAX_STAMINA" ) ) );
// At low stamina levels, firing starts getting slow.
int sta_percent = ( 100 * you.get_stamina() ) / you.get_stamina_max();
reload_time += ( sta_percent < 25 ) ? ( ( 25 - sta_percent ) * 2 ) : 0;

you.moves -= reload_time;
g->refresh_all();
return true;
}

void aim_activity_actor::unload_RAS_weapon()
{
// Unload reload-and-shoot weapons to avoid leaving bows pre-loaded with arrows
avatar &you = g->u;
item *weapon = get_weapon();
if( !weapon ) {
return;
}

gun_mode gun = weapon->gun_current_mode();
if( gun->has_flag( flag_RELOAD_AND_SHOOT ) ) {
int moves_before_unload = you.moves;

// Note: this code works only for avatar
item_location loc = item_location( you, gun.target );
g->unload( loc );

// Give back time for unloading as essentially nothing has been done.
if( first_turn ) {
you.moves = moves_before_unload;
}
g->refresh_all();
}
}

void dig_activity_actor::start( player_activity &act, Character & )
{
act.moves_total = moves_total;
Expand Down Expand Up @@ -671,6 +955,7 @@ namespace activity_actors
// Please keep this alphabetically sorted
const std::unordered_map<activity_id, std::unique_ptr<activity_actor>( * )( JsonIn & )>
deserialize_functions = {
{ activity_id( "ACT_AIM" ), &aim_activity_actor::deserialize },
{ activity_id( "ACT_CONSUME" ), &consume_activity_actor::deserialize },
{ activity_id( "ACT_DIG" ), &dig_activity_actor::deserialize },
{ activity_id( "ACT_DIG_CHANNEL" ), &dig_channel_activity_actor::deserialize },
Expand Down
Loading