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

Archery Proficiencies and Proficiency Bonus Infrastructure #50064

Merged
merged 12 commits into from
Jul 21, 2021
10 changes: 6 additions & 4 deletions data/json/professions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1282,7 +1282,9 @@
"prof_fibers_rope",
"prof_leatherworking_basic",
"prof_metalworking",
"prof_lockpicking"
"prof_lockpicking",
"prof_bow_basic",
"prof_bow_expert"
],
"skills": [
{ "level": 4, "name": "survival" },
Expand Down Expand Up @@ -1694,7 +1696,7 @@
"name": "Bow Hunter",
"description": "Ever since you were a child you loved hunting, and quickly developed a talent for archery. Why, if the world ended, there's nothing you'd want at your side more than your trusty bow. So, when it did, you made sure to bring it along.",
"points": 2,
"proficiencies": [ "prof_bowyery" ],
"proficiencies": [ "prof_bow_basic", "prof_bow_expert", "prof_bowyery" ],
"skills": [ { "level": 2, "name": "archery" } ],
"items": {
"both": {
Expand Down Expand Up @@ -1725,7 +1727,7 @@
"name": "Crossbow Hunter",
"description": "Ever since you were a child you loved hunting, and crossbow hunting was always your favorite. Why, if the world ended, there's nothing you'd want at your side more than your trusty crossbow. So, when it did, you made sure to bring it along.",
"points": 2,
"proficiencies": [ "prof_gunsmithing_spring" ],
"proficiencies": [ "prof_bow_basic", "prof_gunsmithing_spring" ],
"skills": [ { "level": 2, "name": "rifle" } ],
"items": {
"both": {
Expand Down Expand Up @@ -3102,7 +3104,7 @@
{ "level": 2, "name": "chemistry" },
{ "level": 3, "name": "cooking" }
],
"proficiencies": [ "prof_fibers", "prof_fibers_rope", "prof_knapping", "prof_leatherworking_basic" ],
"proficiencies": [ "prof_fibers", "prof_fibers_rope", "prof_knapping", "prof_leatherworking_basic", "prof_bow_basic" ],
"items": {
"both": {
"items": [
Expand Down
35 changes: 35 additions & 0 deletions data/json/proficiencies/wilderness.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,40 @@
"time_to_learn": "8 h",
"default_time_multiplier": 2,
"default_fail_multiplier": 1.2
},
{
"type": "proficiency",
"id": "prof_bow_basic",
"name": { "str": "Basic Archer's Form" },
"description": "You have grasped some basic understanding on archery, and find handling bows easier.",
"can_learn": true,
"time_to_learn": "5 h",
"default_time_multiplier": 1.5,
"default_fail_multiplier": 1.2,
"bonuses": [ { "category": "archery", "type": "strength", "value": 1 } ]
},
{
"type": "proficiency",
"id": "prof_bow_expert",
"name": { "str": "Expert Archer's Form" },
"description": "After significant practice, you have become an expert in archery.",
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
"can_learn": true,
"time_to_learn": "10 h",
"default_time_multiplier": 1.5,
"default_fail_multiplier": 1.2,
"required_proficiencies": [ "prof_bow_basic" ],
"bonuses": [ { "category": "archery", "type": "strength", "value": 1 } ]
},
{
"type": "proficiency",
"id": "prof_bow_master",
"name": { "str": "Master Archer's Form" },
"description": "You are a master at the art of Archery.",
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
"can_learn": true,
"time_to_learn": "20 h",
"default_time_multiplier": 1.5,
"default_fail_multiplier": 1.2,
"required_proficiencies": [ "prof_bow_expert" ],
"bonuses": [ { "category": "archery", "type": "strength", "value": 1 } ]
}
]
20 changes: 14 additions & 6 deletions doc/PROFICIENCY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@

```JSON
{
"id": "prof_knapping",
"type": "proficiency",
"name": { "str": "Knapping" },
"description": "The ability to turn stones into usable tools.",
"id": "prof_bow_master",
"name": { "str": "Master Archer's Form" },
"description": "You are a master at the art of Archery.",
"can_learn": true,
"time_to_learn": "10 h",
"required_proficiencies": [ "prof_foo" ]
},
"time_to_learn": "20 h",
"default_time_multiplier": 1.5,
"default_fail_multiplier": 1.2,
"required_proficiencies": [ "prof_bow_expert" ],
"bonuses": [ { "category": "archery", "type": "strength", "value": 1 } ]
}
```
### `id`
Mandatory. String
Expand Down Expand Up @@ -48,3 +51,8 @@ The (optimal) time required to learn this proficiency.
### `required_proficiencies`
Optional. Array of strings
The proficiencies that must be obtained before this one can. You cannot gain experience in a proficiency without the necessary prerequisites.

### `bonuses`
Optional. Array of objects
This member is used to apply bonuses to certain activities given the player has a particular proficiency. The bonuses applied must be hardcoded to the activity in question.
A category, type, and value must be specified. Current valid types are strength, dexterity, intelligence, and perception.
6 changes: 6 additions & 0 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12843,6 +12843,12 @@ std::vector<proficiency_id> Character::learning_proficiencies() const
return _proficiencies->learning_profs();
}

int Character::get_proficiency_bonus( std::string category,
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
proficiency_bonus_type prof_bonus ) const
{
return _proficiencies->get_proficiency_bonus( category, prof_bonus );
}

void Character::set_proficiency_practice( const proficiency_id &id, const time_duration &amount )
{
if( !test_mode ) {
Expand Down
2 changes: 2 additions & 0 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include "player_activity.h"
#include "pldata.h"
#include "point.h"
#include "proficiency.h"
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
#include "recipe.h"
#include "ret_val.h"
#include "stomach.h"
Expand Down Expand Up @@ -1841,6 +1842,7 @@ class Character : public Creature, public visitable
std::vector<display_proficiency> display_proficiencies() const;
std::vector<proficiency_id> known_proficiencies() const;
std::vector<proficiency_id> learning_proficiencies() const;
int get_proficiency_bonus( std::string category, proficiency_bonus_type prof_bonus ) const;
Saelfen marked this conversation as resolved.
Show resolved Hide resolved

// tests only!
void set_proficiency_practice( const proficiency_id &id, const time_duration &amount );
Expand Down
4 changes: 4 additions & 0 deletions src/creature.h
Original file line number Diff line number Diff line change
Expand Up @@ -1162,6 +1162,10 @@ class Creature : public location, public viewer
virtual void on_damage_of_type( int, damage_type, const bodypart_id & ) {}

public:
// Keep a count of moves passed in which resets every 100 turns as a result of practicing archery proficiency
// This is done this way in order to not destroy focus since `do_aim` is on a per-move basis.
int archery_aim_counter = 0;

bodypart_id select_body_part( Creature *source, int hit_roll ) const;
bodypart_id random_body_part( bool main_parts_only = false ) const;

Expand Down
3 changes: 3 additions & 0 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10884,8 +10884,11 @@ bool item::is_upgrade() const

int item::get_min_str() const
{
Character &p = get_player_character();
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
if( type->gun ) {
int min_str = type->min_str;
min_str -= p.get_proficiency_bonus( "archery", proficiency_bonus_type::strength );

for( const item *mod : gunmods() ) {
min_str += mod->type->gunmod->min_str_required_mod;
}
Expand Down
2 changes: 1 addition & 1 deletion src/monmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1755,7 +1755,7 @@ bool monster::move_to( const tripoint &p, bool force, bool step_on_critter,
}
}

if( has_flag ( MF_SMALLSLUDGETRAIL ) ) {
if( has_flag( MF_SMALLSLUDGETRAIL ) ) {
if( one_in( 2 ) ) {
here.add_field( pos(), fd_sludge, 1 );
}
Expand Down
73 changes: 73 additions & 0 deletions src/proficiency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "debug.h"
#include "generic_factory.h"
#include "json.h"
#include "enums.h"

const float book_proficiency_bonus::default_time_factor = 0.5f;
const float book_proficiency_bonus::default_fail_factor = 0.5f;
Expand All @@ -31,11 +32,58 @@ bool proficiency_id::is_valid() const
return proficiency_factory.is_valid( *this );
}

namespace io
{
template<>
std::string enum_to_string<proficiency_bonus_type>( const proficiency_bonus_type data )
{
// *INDENT-OFF*
switch( data ) {
case proficiency_bonus_type::strength:
return "strength";
case proficiency_bonus_type::dexterity:
return "dexterity";
case proficiency_bonus_type::intelligence:
return "intelligence";
case proficiency_bonus_type::perception:
return "perception";
case proficiency_bonus_type::last:
break;
}
// *INDENT-ON*
Saelfen marked this conversation as resolved.
Show resolved Hide resolved

debugmsg( "Invalid proficiency bonus type" );
return "";
}
} // namespace io

void proficiency::load_proficiencies( const JsonObject &jo, const std::string &src )
{
proficiency_factory.load( jo, src );
}

void proficiency::load_proficiency_bonus( const JsonObject &jo )
{
std::string member = "bonuses";
if( jo.has_member( member ) ) {
if( jo.has_array( member ) ) {
for( const JsonValue entry : jo.get_array( member ) ) {
if( entry.test_object() ) {
JsonObject obj = entry.get_object();

const std::string category = obj.get_string( "category", "" );
if( category != "" ) {
std::string type_str = obj.get_string( "type", "last" );
proficiency_bonus_type prof_bonus_type = io::string_to_enum<proficiency_bonus_type>( type_str );
int value = obj.get_int( "value", 0 );
_bonuses[category].push_back( proficiency_bonus( prof_bonus_type, value ) );
}
}
}
}
}
}

Saelfen marked this conversation as resolved.
Show resolved Hide resolved
void proficiency::reset()
{
proficiency_factory.reset();
Expand All @@ -51,6 +99,8 @@ void proficiency::load( const JsonObject &jo, const std::string & )
optional( jo, was_loaded, "default_fail_multiplier", _default_fail_multiplier );
optional( jo, was_loaded, "time_to_learn", _time_to_learn );
optional( jo, was_loaded, "required_proficiencies", _required );

load_proficiency_bonus( jo );
}

const std::vector<proficiency> &proficiency::get_all()
Expand Down Expand Up @@ -98,6 +148,11 @@ std::set<proficiency_id> proficiency::required_proficiencies() const
return _required;
}

std::map<std::string, std::vector<proficiency_bonus>> proficiency::get_bonuses() const
{
return _bonuses;
}

Saelfen marked this conversation as resolved.
Show resolved Hide resolved
learning_proficiency &proficiency_set::fetch_learning( const proficiency_id &target )
{
for( learning_proficiency &cursor : learning ) {
Expand Down Expand Up @@ -332,6 +387,24 @@ std::vector<proficiency_id> proficiency_set::learning_profs() const
return ret;
}

float proficiency_set::get_proficiency_bonus( std::string category,
proficiency_bonus_type prof_bonus ) const
{
int stat_bonus = 0;
for( const proficiency_id &knows : known ) {
proficiency prof = knows.obj();

std::map<std::string, std::vector<proficiency_bonus>> bonuses = prof.get_bonuses();

for( size_t index = 0; index < bonuses[category].size(); index++ ) {
if( bonuses[category][index].type == prof_bonus ) {
stat_bonus += bonuses[category][index].value;
}
}
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
}
return stat_bonus;
}

void proficiency_set::serialize( JsonOut &jsout ) const
{
jsout.start_object();
Expand Down
31 changes: 31 additions & 0 deletions src/proficiency.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <set>
#include <vector>

#include <string>
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
#include "calendar.h"
#include "color.h"
#include "flat_set.h"
Expand All @@ -19,9 +20,31 @@ class JsonObject;
class JsonOut;
struct display_proficiency;
struct learning_proficiency;
template<typename E> struct enum_traits;
template<typename T>
class generic_factory;

enum class proficiency_bonus_type : int {
strength,
dexterity,
intelligence,
perception,
last
};

template<>
struct enum_traits<proficiency_bonus_type> {
static constexpr proficiency_bonus_type last = proficiency_bonus_type::last;
};

struct proficiency_bonus {
proficiency_bonus_type type = proficiency_bonus_type::last;
float value = 0;

Saelfen marked this conversation as resolved.
Show resolved Hide resolved
proficiency_bonus() {}
proficiency_bonus( proficiency_bonus_type type, float value ) : type( type ), value( value ) {}
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
};

class proficiency
{
friend class generic_factory<proficiency>;
Expand All @@ -40,10 +63,13 @@ class proficiency
time_duration _time_to_learn = 9999_hours;
std::set<proficiency_id> _required;

std::map<std::string, std::vector<proficiency_bonus>> _bonuses;

public:
static void load_proficiencies( const JsonObject &jo, const std::string &src );
static void reset();
void load( const JsonObject &jo, const std::string &src );

static const std::vector<proficiency> &get_all();

bool can_learn() const;
Expand All @@ -56,6 +82,9 @@ class proficiency

time_duration time_to_learn() const;
std::set<proficiency_id> required_proficiencies() const;

void load_proficiency_bonus( const JsonObject &jo );
std::map<std::string, std::vector<proficiency_bonus>> get_bonuses() const;
Saelfen marked this conversation as resolved.
Show resolved Hide resolved
};

// The proficiencies you know, and the ones you're learning.
Expand Down Expand Up @@ -91,6 +120,8 @@ class proficiency_set
std::vector<proficiency_id> known_profs() const;
std::vector<proficiency_id> learning_profs() const;

float get_proficiency_bonus( std::string category, proficiency_bonus_type proficiency_bonus ) const;
Saelfen marked this conversation as resolved.
Show resolved Hide resolved

void serialize( JsonOut &jsout ) const;
void deserialize( JsonIn &jsin );
void deserialize_legacy( const JsonArray &jo );
Expand Down
Loading