Skip to content

Commit

Permalink
feat: Effects adding other effects on removal (#3695)
Browse files Browse the repository at this point in the history
* JSONized "effect from removal of other effect"

* Remove unused

* Rebalance adrenaline

* Body part selection for decayed effects

* style(autofix.ci): automated formatting

* Docs

* style(autofix.ci): automated formatting

* Add test file

* JSONized "effect from removal of other effect"

* Remove unused

* Rebalance adrenaline

* Body part selection for decayed effects

* Docs

* style(autofix.ci): automated formatting

* Add test file

* style(autofix.ci): automated formatting

* Remove unused comparator

Co-authored-by: scarf <[email protected]>

* fix: clang-tidy fixes

* Make blood filter filter adrenaline

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: scarf <[email protected]>
  • Loading branch information
3 people authored Dec 21, 2023
1 parent abdc6f7 commit 3abde26
Show file tree
Hide file tree
Showing 12 changed files with 327 additions and 37 deletions.
25 changes: 17 additions & 8 deletions data/json/effects.json
Original file line number Diff line number Diff line change
Expand Up @@ -1268,25 +1268,34 @@
{
"type": "effect_type",
"id": "adrenaline",
"name": [ "Adrenaline Comedown", "Adrenaline Rush" ],
"desc": [ "You feel completely drained.", "You feel the rush of adrenaline in your body!" ],
"name": [ "Adrenaline Rush" ],
"desc": [ "You feel the rush of adrenaline in your body!" ],
"apply_message": "You feel a surge of adrenaline!",
"rating": "good",
"removes_effects": [ "winded" ],
"max_duration": "5 m",
"base_mods": { "speed_mod": [ 20 ], "str_mod": [ 2 ], "dex_mod": [ 2 ], "int_mod": [ -8 ], "per_mod": [ 1 ], "stamina_min": [ 2 ] },
"blood_analysis_description": "Adrenaline Spike",
"effects_on_remove": [ { "allow_on_remove": true, "effect_type": "adrenaline_comedown", "duration": "1 m" } ]
},
{
"type": "effect_type",
"id": "adrenaline_comedown",
"name": [ "Adrenaline Comedown" ],
"desc": [ "You feel completely drained." ],
"apply_message": "Your adrenaline rush wears off. You feel AWFUL!",
"rating": "bad",
"decay_messages": [ [ "Your adrenaline rush wears off. You feel AWFUL!", "bad" ] ],
"miss_messages": [ [ "Your comedown throws you off.", 1 ] ],
"max_intensity": 2,
"int_dur_factor": "150 s",
"removes_effects": [ "winded" ],
"max_duration": "5 m",
"base_mods": {
"speed_mod": [ -10 ],
"str_mod": [ -2 ],
"dex_mod": [ -2 ],
"int_mod": [ -1 ],
"per_mod": [ -1 ],
"stamina_min": [ -2 ]
},
"scaling_mods": { "speed_mod": [ 30 ], "str_mod": [ 4 ], "dex_mod": [ 4 ], "int_mod": [ -7 ], "per_mod": [ 2 ], "stamina_min": [ 4 ] },
"blood_analysis_description": "Adrenaline Spike"
}
},
{
"type": "effect_type",
Expand Down
20 changes: 20 additions & 0 deletions data/mods/TEST_DATA/effects.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,25 @@
"id": "test_high",
"morale": "morale_test",
"base_mods": { "morale": [ 25 ] }
},
{
"type": "effect_type",
"id": "test_juggling_l1",
"effects_on_remove": [ { "effect_type": "test_juggling_r1", "duration": "0 s", "body_part": "hand_r" } ]
},
{
"type": "effect_type",
"id": "test_juggling_r1",
"effects_on_remove": [ { "effect_type": "test_juggling_r2", "duration": "0 s", "body_part": "hand_r" } ]
},
{
"type": "effect_type",
"id": "test_juggling_r2",
"effects_on_remove": [ { "effect_type": "test_juggling_l2", "duration": "0 s", "body_part": "hand_l" } ]
},
{
"type": "effect_type",
"id": "test_juggling_l2",
"effects_on_remove": [ { "effect_type": "test_juggling_l1", "duration": "0 s" } ]
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,31 @@ effect can hurt the player.
Type of morale effect provided. Mandatory if there is a morale effect, must not be specified
otherwise.

### Other effects on removal

```json
"effects_on_remove": [
{
"intensity_requirement": 0, - Defaults to 0
"effect_type": "cold", - (Mandatory) Effect that will be applied
"allow_on_decay": false, - Defaults to true
"allow_on_remove" true, - Defaults to false
"intensity": 5, - Defaults to 0
"inherit_intensity": false, - Defaults to false
"duration": "10 s", - Defaults to 0
"inherit_duration": true, - Defaults to true
"body_part": "hand_r, - Defaults to null
"inherit_body_part": false - Defaults to true
}
]
```

"intensity_requirement" will prevent adding the new effect if current effect has lower intensity.
"allow_on_decay" enables adding the effect if parent decayed (was removed due to 0 duration).
"allow_on_remove" enables adding the effect if parent was removed before 0 duration.
"inherit_duration", "inherit_intensity" and "inherit_body_part" cause the relevant variable to be
copied from parent effect.

### Effect effects

```json
Expand Down
4 changes: 2 additions & 2 deletions src/bionics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,7 @@ bool Character::activate_bionic( bionic &bio, bool eff_only, bool *close_bionics
} else if( bio.id == bio_blood_filter ) {
add_msg_activate();
static const std::vector<efftype_id> removable = {{
effect_adrenaline,
effect_fungus, effect_dermatik, effect_bloodworms,
effect_poison, effect_stung, effect_badpoison,
effect_pkill1, effect_pkill2, effect_pkill3, effect_pkill_l,
Expand All @@ -788,7 +789,6 @@ bool Character::activate_bionic( bionic &bio, bool eff_only, bool *close_bionics
remove_effect( eff );
}
// Purging the substance won't remove the fatigue it caused
force_comedown( get_effect( effect_adrenaline ) );
force_comedown( get_effect( effect_meth ) );
set_painkiller( 0 );
set_stim( 0 );
Expand Down Expand Up @@ -826,7 +826,7 @@ bool Character::activate_bionic( bionic &bio, bool eff_only, bool *close_bionics
return false;
} else {
add_msg_activate();
add_effect( effect_adrenaline, 20_minutes );
add_effect( effect_adrenaline, 3_minutes );
}
} else if( bio.id == bio_emp ) {
if( const std::optional<tripoint> pnt = choose_adjacent( _( "Create an EMP where?" ) ) ) {
Expand Down
10 changes: 3 additions & 7 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1140,7 +1140,7 @@ void Character::set_pain( int npain )

int Character::get_perceived_pain() const
{
if( get_effect_int( effect_adrenaline ) > 1 ) {
if( has_effect( effect_adrenaline ) ) {
return 0;
}

Expand Down Expand Up @@ -5514,10 +5514,6 @@ void Character::check_needs_extremes()
}
g->events().send<event_type::dies_from_drug_overdose>( getID(), effect_jetinjector );
set_part_hp_cur( bodypart_id( "torso" ), 0 );
} else if( get_effect_dur( effect_adrenaline ) > 50_minutes ) {
add_msg_if_player( m_bad, _( "Your heart spasms and stops." ) );
g->events().send<event_type::dies_from_drug_overdose>( getID(), effect_adrenaline );
set_part_hp_cur( bodypart_id( "torso" ), 0 );
} else if( get_effect_int( effect_drunk ) > 4 ) {
add_msg_if_player( m_bad, _( "Your breathing slows down to a stop." ) );
g->events().send<event_type::dies_from_drug_overdose>( getID(), effect_drunk );
Expand Down Expand Up @@ -9205,7 +9201,7 @@ void Character::on_hurt( Creature *source, bool disturb /*= true*/ )
if( has_trait( trait_ADRENALINE ) && !has_effect( effect_adrenaline ) &&
( get_part_hp_cur( bodypart_id( "head" ) ) < 25 ||
get_part_hp_cur( bodypart_id( "torso" ) ) < 15 ) ) {
add_effect( effect_adrenaline, 20_minutes );
add_effect( effect_adrenaline, 3_minutes );
}

if( disturb ) {
Expand Down Expand Up @@ -10451,7 +10447,7 @@ void Character::on_item_takeoff( const item &it )
void Character::on_effect_int_change( const efftype_id &effect_type, int intensity,
const bodypart_str_id &bp )
{
// Adrenaline can reduce perceived pain (or increase it when you enter comedown).
// Adrenaline can reduce perceived pain (or increase it when it times out).
// See @ref get_perceived_pain()
if( effect_type == effect_adrenaline ) {
// Note that calling this does no harm if it wasn't changed.
Expand Down
63 changes: 51 additions & 12 deletions src/creature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1361,31 +1361,44 @@ int Creature::get_effect_int( const efftype_id &eff_id, body_part bp ) const

return 0;
}

struct removed_effect {
public:
removed_effect( efftype_id type, bodypart_str_id bp, bool is_decayed ) :
type( type ), bp( bp ), is_decayed( is_decayed )
{}
efftype_id type;
bodypart_str_id bp;
bool is_decayed;
};

void Creature::process_effects()
{
process_effects_internal();

// id's and body_part's of all effects to be removed. If we ever get player or
// monster specific removals these will need to be moved down to that level and then
// passed in to this function.
std::vector<std::pair<efftype_id, bodypart_str_id>> to_remove;
std::vector<removed_effect> to_remove;

std::vector<effect> to_add;

// Decay/removal of effects
for( auto &elem : *effects ) {
for( auto &_it : elem.second ) {
if( _it.second.is_removed() ) {
to_remove.emplace_back( elem.first, _it.first );
to_remove.emplace_back( elem.first, _it.first, false );
continue;
}
// Add any effects that others remove to the removal list
for( const efftype_id &removed_effect : _it.second.get_removes_effects() ) {
to_remove.emplace_back( removed_effect, bodypart_str_id::NULL_ID() );
to_remove.emplace_back( removed_effect, bodypart_str_id::NULL_ID(), false );
}
effect &e = _it.second;
const int prev_int = e.get_intensity();
// Run decay effects, marking effects for removal as necessary.
if( e.decay( calendar::turn, is_player() ) ) {
to_remove.emplace_back( elem.first, _it.first );
to_remove.emplace_back( elem.first, _it.first, true );
}

if( e.get_intensity() != prev_int && e.get_duration() > 0_turns ) {
Expand All @@ -1395,21 +1408,47 @@ void Creature::process_effects()
}

// Run the on-remove effects
for( const std::pair<efftype_id, bodypart_str_id> &r : to_remove ) {
remove_effect( r.first, r.second );
for( const removed_effect &r : to_remove ) {
const auto &add_after = r.type->get_effects_on_remove();
if( !add_after.empty() ) {
bool found = false;
// Copypasted from get_effect, but without check for `removed` flag
auto got_outer = effects->find( r.type );
if( got_outer != effects->end() ) {
auto got_inner = got_outer->second.find( convert_bp( r.bp->token ) );
if( got_inner != got_outer->second.end() ) {
const auto &parent = got_inner->second;
const auto &decay_effects = r.is_decayed ?
parent.create_decay_effects() :
parent.create_removal_effects();
to_add.insert( to_add.end(), decay_effects.begin(), decay_effects.end() );
found = true;
}
}

if( !found ) {
debugmsg( "Couldn't find effect to remove %s", r.type.str() );
}
}

remove_effect( r.type, r.bp );
}
// Actually remove effects. This should be the last thing done in process_effects().
for( const std::pair<efftype_id, bodypart_str_id> &r : to_remove ) {
if( !r.second ) {
effects->erase( r.first );
for( const removed_effect &r : to_remove ) {
if( !r.bp ) {
effects->erase( r.type );
} else {
( *effects )[r.first].erase( r.second );
( *effects )[r.type].erase( r.bp );
// If there are no more effects of a given type remove the type map
if( ( *effects )[r.first].empty() ) {
effects->erase( r.first );
if( ( *effects )[r.type].empty() ) {
effects->erase( r.type );
}
}
}

for( const effect &eff : to_add ) {
add_effect( eff );
}
}

bool Creature::resists_effect( const effect &e ) const
Expand Down
75 changes: 75 additions & 0 deletions src/effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1420,6 +1420,14 @@ void load_effect_type( const JsonObject &jo )
}
}

if( jo.has_array( "effects_on_remove" ) ) {
JsonArray jarr = jo.get_array( "effects_on_remove" );
for( JsonObject jo_decay : jarr ) {
new_etype.effects_on_remove.emplace_back();
new_etype.effects_on_remove.back().load_decay( jo_decay );
}
}

effect_types[new_etype.id] = new_etype;
}

Expand Down Expand Up @@ -1515,3 +1523,70 @@ std::string texitify_healing_power( const int power )
}
return "";
}

void caused_effect::load_decay( const JsonObject &jo )
{
assign( jo, "allow_on_decay", allow_on_decay );
assign( jo, "allow_on_remove", allow_on_remove );
load( jo );
}

void caused_effect::load( const JsonObject &jo )
{
assign( jo, "effect_type", type );
assign( jo, "intensity_requirement", intensity_requirement );

if( assign( jo, "duration", duration ) ) {
// In case of copy-from
inherit_duration = false;
}
assign( jo, "inherit_duration", inherit_duration );
if( jo.has_member( "duration" ) && jo.has_member( "inherit_duration" ) ) {
jo.throw_error( R"("duration" and "inherit_duration" can't both be set at the same time.)" );
}

if( assign( jo, "intensity", intensity ) ) {
inherit_intensity = false;
}
assign( jo, "inherit_intensity", inherit_intensity );
if( jo.has_member( "intensity" ) && jo.has_member( "inherit_intensity" ) ) {
jo.throw_error( R"("intensity" and "inherit_intensity" can't both be set at the same time.)" );
}

if( assign( jo, "body_part", bp ) ) {
inherit_body_part = false;
}
assign( jo, "inherit_body_part", inherit_body_part );
if( jo.has_member( "intensity" ) && jo.has_member( "inherit_intensity" ) ) {
jo.throw_error( R"("body_part" and "inherit_body_part" can't both be set at the same time.)" );
}
}

std::vector<effect> effect::create_decay_effects() const
{
return create_child_effects( true );
}

std::vector<effect> effect::create_removal_effects() const
{
return create_child_effects( false );
}

std::vector<effect> effect::create_child_effects( bool decay ) const
{
std::vector<effect> ret;
for( const auto &new_effect : eff_type->effects_on_remove ) {
if( this->intensity < new_effect.intensity_requirement ||
( decay && !new_effect.allow_on_decay ) ||
( !decay && !new_effect.allow_on_remove ) ) {
continue;
}
const effect_type *new_effect_type = &*new_effect.type;
time_duration dur = new_effect.inherit_duration ? this->duration : new_effect.duration;
int intensity = new_effect.inherit_intensity ? this->intensity : new_effect.intensity;
bodypart_str_id bp = new_effect.inherit_body_part ? convert_bp( this->bp ) : new_effect.bp;
effect e = effect( new_effect_type, dur, bp, intensity, calendar::turn );
ret.emplace_back( e );
}
return ret;
}
Loading

0 comments on commit 3abde26

Please sign in to comment.