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

Apex Predator is on top of the food chain #74390

Closed
18 changes: 18 additions & 0 deletions data/json/enchantments.json
Original file line number Diff line number Diff line change
Expand Up @@ -216,5 +216,23 @@
{ "value": "CARRY_WEIGHT", "multiply": 0.4 },
{ "value": "CLIMATE_CONTROL_CHILL", "add": 50 }
]
},
{
"id": "PRED2_PROF",
"type": "enchantment",
"condition": "ALWAYS",
"prof_boost": [ { "value": "prof_weakpoint", "multiply": 1.0 } ]
},
{
"id": "PRED3_PROF",
"type": "enchantment",
"condition": "ALWAYS",
"prof_boost": [ { "value": "prof_weakpoint", "multiply": 2.0 } ]
},
{
"id": "PRED4_PROF",
"type": "enchantment",
"condition": "ALWAYS",
"prof_boost": [ { "value": "prof_weakpoint", "multiply": 3.0 } ]
}
]
12 changes: 9 additions & 3 deletions data/json/mutations/mutations.json
Original file line number Diff line number Diff line change
Expand Up @@ -5383,7 +5383,7 @@
],
"category": [ "BEAST", "RAPTOR", "CHIMERA", "LUPINE", "FELINE", "URSINE", "LIZARD", "SPIDER" ],
"flags": [ "PRED2" ],
"enchantments": [ { "condition": "ALWAYS", "values": [ { "value": "COMBAT_CATCHUP", "multiply": 2.0 } ] } ]
"enchantments": [ "PRED2_PROF", { "values": [ { "value": "COMBAT_CATCHUP", "multiply": 2.0 } ] } ]
},
{
"type": "mutation",
Expand Down Expand Up @@ -5412,7 +5412,10 @@
"cancels": [ "PACIFIST" ],
"category": [ "BEAST", "RAPTOR", "CHIMERA", "LUPINE", "FELINE", "URSINE", "LIZARD", "SPIDER" ],
"flags": [ "PRED3" ],
"enchantments": [ { "values": [ { "value": "COMBAT_CATCHUP", "multiply": 2.0 }, { "value": "INTELLIGENCE", "add": -1 } ] } ]
"enchantments": [
"PRED3_PROF",
{ "values": [ { "value": "COMBAT_CATCHUP", "multiply": 2.0 }, { "value": "INTELLIGENCE", "add": -1 } ] }
]
},
{
"type": "mutation",
Expand All @@ -5429,7 +5432,10 @@
"threshreq": [ "THRESH_BEAST", "THRESH_RAPTOR", "THRESH_CHIMERA", "THRESH_URSINE" ],
"category": [ "BEAST", "RAPTOR", "CHIMERA", "URSINE" ],
"flags": [ "PRED4" ],
"enchantments": [ { "values": [ { "value": "COMBAT_CATCHUP", "multiply": 3.0 }, { "value": "INTELLIGENCE", "add": -3 } ] } ]
"enchantments": [
"PRED4_PROF",
{ "values": [ { "value": "COMBAT_CATCHUP", "multiply": 3.0 }, { "value": "INTELLIGENCE", "add": -3 } ] }
]
},
{
"type": "mutation",
Expand Down
8 changes: 4 additions & 4 deletions doc/JSON_FLAGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -400,10 +400,10 @@ Character flags can be `trait_id`, `json_flag_id` or `flag_id`. Some of these a
- ```PARAIMMUNE``` You are immune to parasites.
- ```PLANTBLOOD``` Your body drip veggy blood if wounded.
- ```PORTAL_PROOF``` You are immune to personal portal storm effects.
- ```PRED1``` Small morale bonus from foods with the `PREDATOR_FUN` flag. Lower morale penalty from the guilt mondeath effect.
- ```PRED2``` Learn combat skills with double catchup modifier. Resist skill rust on combat skills. Small morale bonus from foods with the `PREDATOR_FUN` flag. Lower morale penalty from the guilt mondeath effect.
- ```PRED3``` Learn combat skills with double catchup modifier. Resist skill rust on combat skills. Medium morale bonus from foods with the `PREDATOR_FUN` flag. Immune to the guilt mondeath effect.
- ```PRED4``` Learn combat skills with triple catchup modifier. Learn combat skills without spending focus. Resist skill rust on combat skills. Large morale bonus from foods with the `PREDATOR_FUN` flag. Immune to the `guilt` mondeath effect.
- ```PRED1``` Less penalty from unknown weakpoint proficiencies. Small morale bonus from foods with the `PREDATOR_FUN` flag. Lower morale penalty from the guilt mondeath effect.
- ```PRED2``` Learn weakpoint proficiencies with double speed. More bonus from weakpoint proficiencies, and less penalty from unknown weakpoint proficiencies. Learn combat skills with double catchup modifier. Resist skill rust on combat skills. Small morale bonus from foods with the `PREDATOR_FUN` flag. Lower morale penalty from the guilt mondeath effect.
- ```PRED3``` Learn weakpoint proficiencies with triple speed. More bonus from weakpoint proficiencies, and less penalty from unknown weakpoint proficiencies. Learn combat skills with double catchup modifier. Resist skill rust on combat skills. Medium morale bonus from foods with the `PREDATOR_FUN` flag. Immune to the guilt mondeath effect.
- ```PRED4``` Learn weakpoint proficiencies four times as fast. More bonus from weakpoint proficiencies, and no penalty from unknown weakpoint proficiencies. Learn combat skills with triple catchup modifier. Learn combat skills without spending focus. Resist skill rust on combat skills. Large morale bonus from foods with the `PREDATOR_FUN` flag. Immune to the `guilt` mondeath effect.
- ```PSYCHOPATH``` Butcher humans without a morale penalty.
- ```ROOTS2``` Gain enhanced effects from the Mycorrhizal Communion mutation.
- ```ROOTS3``` Gain enhanced effects from the Mycorrhizal Communion mutation (slightly faster than `ROOTS2`).
Expand Down
2 changes: 2 additions & 0 deletions doc/MAGIC.md
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,7 @@ Identifier | Description
`intermittent_activation` | Spells that activate centered on you depending on the duration. The spells follow the `fake_spell` template.
`values` | Numbers that can be modified (see [list](#id-values)). `add` is added to the base value, `multiply` is **also added** and treated as percentage: 2.5 is +250% and -1 is -100%. `add` is always applied before `multiply`. Either `add` or `multiply` can be a variable_object/math expression (see [below](#variables) for syntax and application, and [NPCs](NPCs.md) for the in depth explanation).
`skills` | A bonus or penalty to skills. Syntax is the same as for values, using the id of the skill name.
`prof_boost` | A bonus for learning speed of proficiencies in certain proficiency category. `value` is the proficiency category affected, `multiply` will reduce time_to_learn to 1 / ( multiply + 1 ).
`emitter` | Grants the emit_id.
`modified_bodyparts` | Modifies the body plan (standard is human). `gain` adds body_part_id, `lose` removes body_part_id. Note: changes done this way stay even after the item/effect/mutation carrying the enchantment is removed.
`mutations` | Grants the mutation/trait ID. Note: enchantments effects added this way won't stack, due how mutations work.
Expand All @@ -645,6 +646,7 @@ There are two possible syntaxes. The first is by defining an enchantment object
"hit_me_effect": [ { "id": "AEA_HEAL" } ],
"values": [ { "value": "STRENGTH", "multiply": 1.1, "add": -5 } ],
"skills": [ { "value": "computer", "add": 3 } ],
"prof_boost": [ { "value": "prof_weakpoint", "multiply": 2.0 } ],
"emitter": "emit_AEP_SMOKE",
"modified_bodyparts": [ { "gain": "test_corvid_beak" }, { "lose": "torso" } ],
"mutations": [ "GILLS", "MEMBRANE", "AMPHIBIAN", "WAYFARER", "WILDSHAPE:FISH" ],
Expand Down
4 changes: 4 additions & 0 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,7 @@ Character::Character() :
male = true;
prof = profession::has_initialized() ? profession::generic() :
nullptr; //workaround for a potential structural limitation, see player::create
prof_boosts.clear();
start_location = start_location_sloc_shelter_a;
moves = 100;
oxygen = 0;
Expand Down Expand Up @@ -3857,6 +3858,9 @@ void Character::reset_stats()
if( int_cur < 0 ) {
int_cur = 0;
}

prof_boosts.clear();
set_prof_boost();
}

void Character::reset()
Expand Down
6 changes: 6 additions & 0 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,9 @@ class Character : public Creature, public visitable
float get_arms_stam_mult() const;
float get_legs_stam_mult() const;

/** prof boost */
std::map<proficiency_category_id, float> prof_boosts;

private:
/** Modifiers to character speed, with descriptions */
std::vector<speed_bonus_effect> speed_bonus_effects;
Expand Down Expand Up @@ -2465,6 +2468,9 @@ class Character : public Creature, public visitable

// --------------- Proficiency Stuff ----------------
bool has_proficiency( const proficiency_id &prof ) const;
// boost learning speed in certain category
void set_prof_boost();
float get_prof_boost( const proficiency_id &prof ) const;
float get_proficiency_practice( const proficiency_id &prof ) const;
time_duration get_proficiency_practiced_time( const proficiency_id &prof ) const;
bool has_prof_prereqs( const proficiency_id &prof ) const;
Expand Down
26 changes: 23 additions & 3 deletions src/character_proficiency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,24 @@ bool Character::has_proficiency( const proficiency_id &prof ) const
return _proficiencies->has_learned( prof );
}

void Character::set_prof_boost()
{
for( const proficiency_category &prof_cat : proficiency_category::get_all() ) {
float value = enchantment_cache->modify_value( prof_cat.id, 1.0 );
if( value > 1.0 ) {
prof_boosts.emplace( prof_cat.id, value );
}
}
}

float Character::get_prof_boost( const proficiency_id &prof ) const
{
float prof_boost = prof_boosts.find( prof->prof_category() ) != prof_boosts.end()
? prof_boosts.at( prof->prof_category() )
: 1.0;
return prof_boost;
}

float Character::get_proficiency_practice( const proficiency_id &prof ) const
{
return _proficiencies->pct_practiced( prof );
Expand Down Expand Up @@ -75,8 +93,9 @@ bool Character::practice_proficiency( const proficiency_id &prof, const time_dur
// Proficiencies can ignore focus using the `ignore_focus` JSON property
const bool ignore_focus = prof->ignore_focus();
float focus_adjusted = adjust_for_focus( to_seconds<float>( amount ) );
const time_duration &focused_amount = ignore_focus ? amount : time_duration::from_seconds(
focus_adjusted );
float boost = this->get_prof_boost( prof );
const time_duration &focused_amount = ignore_focus ? amount * boost
: time_duration::from_seconds( focus_adjusted ) * boost;

const float pct_before = _proficiencies->pct_practiced( prof );
const bool learned = _proficiencies->practice( prof, focused_amount,
Expand All @@ -97,7 +116,8 @@ bool Character::practice_proficiency( const proficiency_id &prof, const time_dur

time_duration Character::proficiency_training_needed( const proficiency_id &prof ) const
{
return _proficiencies->training_time_needed( prof );
float boost = this->get_prof_boost( prof );
return _proficiencies->training_time_needed( prof ) / boost;
}

std::vector<proficiency_id> Character::known_proficiencies() const
Expand Down
60 changes: 60 additions & 0 deletions src/magic_enchantment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,21 @@ void enchantment::load( const JsonObject &jo, const std::string_view,
}
}
}

if( !is_child && jo.has_array( "prof_boost" ) ) {
for( const JsonObject value_obj : jo.get_array( "prof_boost" ) ) {
const proficiency_category_id value = proficiency_category_id( value_obj.get_string( "value" ) );
if( value_obj.has_member( "multiply" ) ) {
dbl_or_var mult;
if( value_obj.has_float( "multiply" ) ) {
mult.max.dbl_val = mult.min.dbl_val = value_obj.get_float( "multiply" );
} else {
mult = get_dbl_or_var( value_obj, "multiply", false );
}
prof_boost_multiply.emplace( value, mult );
}
}
}
}

void enchant_cache::load( const JsonObject &jo, const std::string_view,
Expand Down Expand Up @@ -514,6 +529,16 @@ void enchant_cache::load( const JsonObject &jo, const std::string_view,
}
}
}

if( jo.has_array( "prof_boost" ) ) {
for( const JsonObject value_obj : jo.get_array( "prof_boost" ) ) {
const proficiency_category_id value = proficiency_category_id( value_obj.get_string( "value" ) );
const double mult = value_obj.get_float( "multiply", 0.0 );
if( mult != 0.0 ) {
prof_boost_multiply.emplace( value, static_cast<int>( mult ) );
}
}
}
}

void enchant_cache::serialize( JsonOut &jsout ) const
Expand Down Expand Up @@ -600,6 +625,18 @@ void enchant_cache::serialize( JsonOut &jsout ) const
}
jsout.end_array();

jsout.member( "prof_boost" );
jsout.start_array();
for( const proficiency_category &prof_cat : proficiency_category::get_all() ) {
jsout.start_object();
jsout.member( "value", prof_cat.id );
if( get_prof_boost_multiply( prof_cat.id ) != 0 ) {
jsout.member( "multiply", get_prof_boost_multiply( prof_cat.id ) );
}
jsout.end_object();
}
jsout.end_array();

jsout.end_object();
}

Expand Down Expand Up @@ -700,6 +737,13 @@ void enchant_cache::force_add( const enchantment &rhs, const Character &guy )
skill_values_multiply[pair_values.first] += pair_values.second.evaluate( d );
}

for( const std::pair<const proficiency_category_id, dbl_or_var> &pair_values :
rhs.prof_boost_multiply ) {
// values do not multiply against each other, they add.
// so +10% and -10% will add to 0%
prof_boost_multiply[pair_values.first] += pair_values.second.evaluate( d );
}

hit_me_effect.insert( hit_me_effect.end(), rhs.hit_me_effect.begin(), rhs.hit_me_effect.end() );

hit_you_effect.insert( hit_you_effect.end(), rhs.hit_you_effect.begin(), rhs.hit_you_effect.end() );
Expand Down Expand Up @@ -914,6 +958,15 @@ double enchant_cache::get_skill_value_multiply( const skill_id &value ) const
return found->second;
}

double enchant_cache::get_prof_boost_multiply( const proficiency_category_id &value ) const
{
const auto found = prof_boost_multiply.find( value );
if( found == prof_boost_multiply.cend() ) {
return 0;
}
return found->second;
}

double enchant_cache::modify_value( const enchant_vals::mod mod_val, double value ) const
{
value += get_value_add( mod_val );
Expand All @@ -928,6 +981,12 @@ double enchant_cache::modify_value( const skill_id &mod_val, double value ) cons
return value;
}

double enchant_cache::modify_value( const proficiency_category_id &mod_val, double value ) const
{
value *= 1.0 + get_prof_boost_multiply( mod_val );
return value;
}

units::energy enchant_cache::modify_value( const enchant_vals::mod mod_val,
units::energy value ) const
{
Expand Down Expand Up @@ -1088,6 +1147,7 @@ void enchant_cache::clear()
values_multiply.clear();
skill_values_add.clear();
skill_values_multiply.clear();
prof_boost_multiply.clear();
hit_me_effect.clear();
hit_you_effect.clear();
ench_effects.clear();
Expand Down
5 changes: 5 additions & 0 deletions src/magic_enchantment.h
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,8 @@ class enchantment
std::map<skill_id, dbl_or_var> skill_values_add; // NOLINT(cata-serialize)
std::map<skill_id, dbl_or_var> skill_values_multiply; // NOLINT(cata-serialize)

std::map<proficiency_category_id, dbl_or_var> prof_boost_multiply; // NOLINT(cata-serialize)

std::vector<fake_spell> hit_me_effect;
std::vector<fake_spell> hit_you_effect;

Expand All @@ -284,6 +286,7 @@ class enchant_cache : public enchantment

double modify_value( enchant_vals::mod mod_val, double value ) const;
double modify_value( const skill_id &mod_val, double value ) const;
double modify_value( const proficiency_category_id &mod_val, double value ) const;
units::energy modify_value( enchant_vals::mod mod_val, units::energy value ) const;
units::mass modify_value( enchant_vals::mod mod_val, units::mass value ) const;
units::volume modify_value( enchant_vals::mod mod_val, units::volume value ) const;
Expand All @@ -305,6 +308,7 @@ class enchant_cache : public enchantment
int get_skill_value_add( const skill_id &value ) const;
double get_skill_value_multiply( const skill_id &value ) const;
int skill_mult_bonus( const skill_id &value_type, int base_value ) const;
double get_prof_boost_multiply( const proficiency_category_id &value ) const;
// attempts to add two like enchantments together.
// if their conditions don't match, return false. else true.
bool add( const enchantment &rhs, Character &you );
Expand Down Expand Up @@ -344,6 +348,7 @@ class enchant_cache : public enchantment
// the exact same as above, though specifically for skills
std::map<skill_id, int> skill_values_add; // NOLINT(cata-serialize)
std::map<skill_id, int> skill_values_multiply; // NOLINT(cata-serialize)
std::map<proficiency_category_id, int> prof_boost_multiply; // NOLINT(cata-serialize)
};

template <typename E> struct enum_traits;
Expand Down
42 changes: 35 additions & 7 deletions src/weakpoint.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@
#include "rng.h"
#include "translations.h"

static const json_character_flag json_flag_PRED1( "PRED1" );
static const json_character_flag json_flag_PRED2( "PRED2" );
static const json_character_flag json_flag_PRED3( "PRED3" );
static const json_character_flag json_flag_PRED4( "PRED4" );

static const limb_score_id limb_score_reaction( "reaction" );
static const limb_score_id limb_score_vision( "vision" );

Expand Down Expand Up @@ -102,9 +107,25 @@ float Character::throw_weakpoint_skill() const

float weakpoint_family::modifier( const Character &attacker ) const
{
float prof_bonus = bonus.value_or( proficiency.obj().default_weakpoint_bonus() );
float prof_penalty = penalty.value_or( proficiency.obj().default_weakpoint_penalty() );

if( attacker.has_flag( json_flag_PRED4 ) ) {
prof_bonus += 3;
prof_penalty = std::max( 0.0f, prof_penalty );
} else if( attacker.has_flag( json_flag_PRED3 ) ) {
prof_bonus += 2;
prof_penalty = prof_penalty < - 1 ? prof_penalty + 2 : std::max( 0.0f, prof_penalty );
} else if( attacker.has_flag( json_flag_PRED2 ) ) {
prof_bonus += 1;
prof_penalty = prof_penalty < 0 ? prof_penalty + 1 : prof_penalty;
} else if( attacker.has_flag( json_flag_PRED1 ) ) {
prof_penalty = prof_penalty < 0 ? prof_penalty + 1 : prof_penalty;
}

return attacker.has_proficiency( proficiency )
? bonus.value_or( proficiency.obj().default_weakpoint_bonus() )
: penalty.value_or( proficiency.obj().default_weakpoint_penalty() );
? prof_bonus
: prof_penalty;
}

void weakpoint_family::load( const JsonValue &jsin )
Expand Down Expand Up @@ -150,11 +171,18 @@ bool weakpoint_families::practice_kill( Character &learner ) const

bool weakpoint_families::practice_dissect( Character &learner ) const
{
// Proficiency experience is capped at 1000 seconds (~16 minutes), so we split it into two
// instances. This should be refactored when butchering becomes an `activity_actor`.
bool p1 = practice( learner, time_duration::from_minutes( 15 ) );
bool p2 = practice( learner, time_duration::from_minutes( 15 ) );
bool learned = p1 || p2;
// Proficiency experience is no longer capped at 1000 seconds,
// but this still needs to be refactored, see #74426.
int progress = 1800;
// A work around to cut xp for predators to half of normals.
if( learner.has_flag( json_flag_PRED4 ) ) {
progress /= 8;
} else if( learner.has_flag( json_flag_PRED3 ) ) {
progress /= 6;
} else if( learner.has_flag( json_flag_PRED2 ) ) {
progress /= 4;
}
bool learned = practice( learner, time_duration::from_seconds( progress ) );
if( learned ) {
learner.add_msg_if_player(
m_good, _( "You carefully record the creature's vulnerabilities." ) );
Expand Down
Loading