Skip to content

Commit

Permalink
Adds ability to run effect_on_conditions on either avatar or npc deat…
Browse files Browse the repository at this point in the history
…h. (#51890)
  • Loading branch information
Ramza13 committed Sep 26, 2021
1 parent 7bbf943 commit eba01f0
Show file tree
Hide file tree
Showing 14 changed files with 102 additions and 28 deletions.
6 changes: 3 additions & 3 deletions data/json/effect_on_condition.json
Expand Up @@ -2,7 +2,7 @@
{
"type": "effect_on_condition",
"id": "scenario_bad_day",
"scenario_specific": true,
"eoc_type": "SCENARIO_SPECIFIC",
"effect": [
{ "u_add_effect": "flu", "intensity": 1, "duration": "1000 minutes" },
{ "u_add_effect": "drunk", "intensity": 1, "duration": "270 minutes" },
Expand All @@ -18,7 +18,7 @@
{
"type": "effect_on_condition",
"id": "scenario_infected",
"scenario_specific": true,
"eoc_type": "SCENARIO_SPECIFIC",
"effect": [
{ "u_add_effect": "infected", "intensity": 1, "duration": "PERMANENT", "target_part": "RANDOM" },
{ "assign_mission": "MISSION_INFECTED_START_FIND_ANTIBIOTICS" }
Expand All @@ -27,7 +27,7 @@
{
"type": "effect_on_condition",
"id": "scenario_fungal_infection",
"scenario_specific": true,
"eoc_type": "SCENARIO_SPECIFIC",
"effect": [ { "u_add_effect": "effect_fungus", "intensity": 1, "duration": "PERMANENT", "target_part": "RANDOM" } ]
},
{
Expand Down
3 changes: 2 additions & 1 deletion doc/EFFECT_ON_CONDITION.md
Expand Up @@ -5,7 +5,6 @@ An effect_on_condition is an object allowing the combination of dialog condition

|Identifier|Type|Description|
|-|-|-|
| `scenario_specific`| int | The effect_on_condition is automatically invoked once on scenario start.
| `recurrence_min`| int | The effect_on_condition is automatically invoked (activated) with at least this many seconds in-between.
| `recurrence_max`| int | The effect_on_condition is automatically invoked (activated) at least once this many seconds.
| `condition`| condition | The condition(s) under which this effect_on_condition, upon activation, will cause its effect. See the "Dialogue conditions" section of [NPCs](NPCs.md) for the full syntax.
Expand All @@ -14,6 +13,8 @@ An effect_on_condition is an object allowing the combination of dialog condition
| `false_effect`| effect | The effect(s) caused if `condition` returns false upon activation. See the "Dialogue Effects" section of [NPCs](NPCs.md) for the full syntax.
| `global`| bool | If this is true, this recurring eoc will be run on the player and every npc from a global queue. Deactivate conditions will work based on the avatar. If it is false the avatar and every character will have their own copy and their own deactivated list. Defaults to false.
| `run_on_npcs`| bool | Can only be true if global is true. If false the eoc will only be run against the avatar. If true the eoc will be run against the avatar and all npcs. Defaults to false.
| `EOC_TYPE`| string | The effect_on_condition is automatically invoked once on scenario start.

This comment has been minimized.

Copy link
@ZhilkinSerg

ZhilkinSerg Jun 29, 2023

Contributor

The effect_on_condition is automatically invoked once on scenario start. does not make sense here and can be removed (it was copied from scenario_specific where it made sense).

Can be any of:ACTIVATION, RECURRING, SCENARIO_SPECIFIC, AVATAR_DEATH, NPC_DEATH. It defaults to ACTIVATION unless a `recurrence_min` and `recurrence_max` are provided in which case it defaults to RECURRING. If it is SCENARIO_SPECIFIC it is automatically invoked once on scenario start. If it is AVATAR_DEATH whenever the current avatar dies it will be run with the avatar as u and the killer as npc. NPC_DEATH eocs can only be assigned to run on the death of an npc.

## Examples:
```JSON
Expand Down
4 changes: 3 additions & 1 deletion doc/NPCs.md
Expand Up @@ -41,14 +41,16 @@ Format:
"attitude": 0,
"mission": 7,
"chat": "TALK_EXAMPLE",
"faction": "no_faction"
"faction": "no_faction",
"death_eocs": [ "EOC_DEATH_NPC_TEST" ]
}
```
This is the JSON that creates the NPC ID that is used to spawn an NPC in "mapgen" (map generation).
Attitude is based on the enum in npc.h. The important ones are 0=NPCATT_NULL, 1=NPCATT_TALK", 3=NPCATT_FOLLOW, 7=NPCATT_DEFEND, 10=NPCATT_KILL, and 11=NPCATT_FLEE.
Mission is based on the enum in npc.h. The important ones are 0=NPC_MISSION_NUL, 3=NPC_MISSION_SHOPKEEP", and 7=NPC_MISSION_GUARD", 8 = NPC_MISSION_GUARD_PATROL will actively investigate noises".
Chat is covered in the dialogue examples below.
Faction determines what faction, if any, the NPC belongs to. Some examples are the Free Traders, Old Guard, Marloss Evangelists, and Hell's raiders but could include a brand new faction you create!
death_eocs are string effect_on_condition ids and or inline effect_on_conditions (see [EFFECT_ON_CONDITION.md](EFFECT_ON_CONDITION.md)). When the npc dies all of these eocs are run with the victim as u and the killer as npc.

# Age and Height
You can define the age and height of the NPC in the `age` or `height` fields in `"type": "npc"`.
Expand Down
10 changes: 10 additions & 0 deletions src/character.cpp
Expand Up @@ -2899,6 +2899,16 @@ void Character::die( Creature *nkiller )
is_dead = true;
set_killer( nkiller );
set_time_died( calendar::turn );

dialogue d( get_talker_for( this ), nkiller == nullptr ? nullptr : get_talker_for( nkiller ) );
for( effect_on_condition_id &eoc : death_eocs ) {
if( eoc->type == eoc_type::NPC_DEATH ) {
eoc->activate( d );
} else {
debugmsg( "Tried to use non NPC_DEATH eoc_type %s for an npc death.", eoc.c_str() );
}
}

if( has_effect( effect_lightsnare ) ) {
inv->add_item( item( "string_36", calendar::turn_zero ) );
inv->add_item( item( "snare_trigger", calendar::turn_zero ) );
Expand Down
2 changes: 1 addition & 1 deletion src/character.h
Expand Up @@ -2223,7 +2223,7 @@ class Character : public Creature, public visitable
bool male = false;

bool is_dead = false;

std::vector<effect_on_condition_id> death_eocs;
std::list<item> worn;
bool nv_cached = false;
// Means player sit inside vehicle on the tile he is now
Expand Down
6 changes: 5 additions & 1 deletion src/creature.cpp
Expand Up @@ -2749,7 +2749,11 @@ std::unique_ptr<talker> get_talker_for( const Creature &me )

std::unique_ptr<talker> get_talker_for( Creature *me )
{
if( me->is_monster() ) {
if( !me ) {
debugmsg( "Null creature type." );
standard_npc default_npc( "Default" );
return get_talker_for( default_npc );
} else if( me->is_monster() ) {
return std::make_unique<talker_monster>( static_cast<monster *>( me ) );
} else if( me->is_npc() ) {
return std::make_unique<talker_npc>( static_cast<npc *>( me ) );
Expand Down
53 changes: 45 additions & 8 deletions src/effect_on_condition.cpp
Expand Up @@ -10,6 +10,26 @@
#include "talker.h"
#include "type_id.h"

namespace io
{
// *INDENT-OFF*
template<>
std::string enum_to_string<eoc_type>( eoc_type data )
{
switch ( data ) {
case eoc_type::ACTIVATION: return "ACTIVATION";
case eoc_type::RECURRING: return "RECURRING";
case eoc_type::SCENARIO_SPECIFIC: return "SCENARIO_SPECIFIC";
case eoc_type::AVATAR_DEATH: return "AVATAR_DEATH";
case eoc_type::NPC_DEATH: return "NPC_DEATH";
case eoc_type::NUM_EOC_TYPES: break;
}
cata_fatal( "Invalid eoc_type" );
}
// *INDENT-ON*
} // namespace io


namespace
{
generic_factory<effect_on_condition>
Expand All @@ -36,15 +56,21 @@ void effect_on_conditions::check_consistency()
void effect_on_condition::load( const JsonObject &jo, const std::string & )
{
mandatory( jo, was_loaded, "id", id );
activate_only = true;
optional( jo, was_loaded, "eoc_type", type, eoc_type::NUM_EOC_TYPES );
if( jo.has_member( "recurrence_min" ) || jo.has_member( "recurrence_max" ) ) {
activate_only = false;
if( type != eoc_type::NUM_EOC_TYPES && type != eoc_type::RECURRING ) {
jo.throw_error( "A recurring effect_on_condition must be of type RECURRING." );
}
type = eoc_type::RECURRING;
mandatory( jo, was_loaded, "recurrence_min", recurrence_min );
mandatory( jo, was_loaded, "recurrence_max", recurrence_max );
if( recurrence_max < recurrence_min ) {
jo.throw_error( "recurrence_max cannot be smaller than recurrence_min." );
}
}
if( type == eoc_type::NUM_EOC_TYPES ) {
type = eoc_type::ACTIVATION;
}

if( jo.has_member( "deactivate_condition" ) ) {
read_condition<dialogue>( jo, "deactivate_condition", deactivate_condition, false );
Expand All @@ -61,11 +87,10 @@ void effect_on_condition::load( const JsonObject &jo, const std::string & )
has_false_effect = true;
}

optional( jo, was_loaded, "scenario_specific", scenario_specific );
optional( jo, was_loaded, "run_for_npcs", run_for_npcs, false );
optional( jo, was_loaded, "global", global, false );
if( activate_only && global ) {
jo.throw_error( "global should only be true for recurring effect_on_conditions." );
if( type != eoc_type::RECURRING && ( global || run_for_npcs ) ) {
jo.throw_error( "run_for_npcs and global should only be true for RECURRING effect_on_conditions." );
} else if( !global && run_for_npcs ) {
jo.throw_error( "run_for_npcs should only be true for global effect_on_conditions." );
}
Expand Down Expand Up @@ -96,7 +121,7 @@ void effect_on_conditions::load_new_character( Character &you )
bool is_avatar = you.is_avatar();
for( const effect_on_condition_id &eoc_id : get_scenario()->eoc() ) {
effect_on_condition eoc = eoc_id.obj();
if( eoc.scenario_specific && ( is_avatar || eoc.run_for_npcs ) ) {
if( eoc.type == eoc_type::SCENARIO_SPECIFIC && ( is_avatar || eoc.run_for_npcs ) ) {
queued_eoc new_eoc = queued_eoc{ eoc.id, true, calendar::turn + next_recurrence( eoc.id ) };
you.queued_effect_on_conditions.push( new_eoc );
}
Expand All @@ -105,7 +130,7 @@ void effect_on_conditions::load_new_character( Character &you )
effect_on_conditions::clear( you );

for( const effect_on_condition &eoc : effect_on_conditions::get_all() ) {
if( !eoc.activate_only && !eoc.scenario_specific && ( is_avatar || !eoc.global ) ) {
if( eoc.type == eoc_type::RECURRING && ( is_avatar || eoc.run_for_npcs ) ) {
queued_eoc new_eoc = queued_eoc{ eoc.id, true, calendar::turn + next_recurrence( eoc.id ) };
if( eoc.global ) {
g->queued_global_effect_on_conditions.push( new_eoc );
Expand Down Expand Up @@ -147,7 +172,7 @@ void effect_on_conditions::load_existing_character( Character &you )
bool is_avatar = you.is_avatar();
std::map<effect_on_condition_id, bool> new_eocs;
for( const effect_on_condition &eoc : effect_on_conditions::get_all() ) {
if( !eoc.activate_only && !eoc.scenario_specific && ( is_avatar || !eoc.global ) ) {
if( eoc.type == eoc_type::RECURRING && ( is_avatar || !eoc.global ) ) {
new_eocs[eoc.id] = true;
}
}
Expand Down Expand Up @@ -335,6 +360,18 @@ void effect_on_conditions::write_global_eocs_to_file( )

}, "eocs test file" );
}
void effect_on_conditions::avatar_death()
{
avatar &player_character = get_avatar();
dialogue d( get_talker_for( get_avatar() ),
player_character.get_killer() == nullptr ? nullptr : get_talker_for(
player_character.get_killer() ) );
for( const effect_on_condition &eoc : effect_on_conditions::get_all() ) {
if( eoc.type == eoc_type::AVATAR_DEATH ) {
eoc.activate( d );
}
}
}

void effect_on_condition::finalize()
{
Expand Down
21 changes: 16 additions & 5 deletions src/effect_on_condition.h
Expand Up @@ -13,7 +13,14 @@

template<typename T>
class generic_factory;

enum eoc_type {
ACTIVATION,
RECURRING,
SCENARIO_SPECIFIC,
AVATAR_DEATH,
NPC_DEATH,
NUM_EOC_TYPES
};
struct effect_on_condition {
public:
friend class generic_factory<effect_on_condition>;
Expand All @@ -23,17 +30,15 @@ struct effect_on_condition {
/* If this is true it will be run on the player and every npc. Deactivate conditions will work based on the player. */
bool global = false;
effect_on_condition_id id;

eoc_type type;
std::function<bool( const dialogue & )> condition;
std::function<bool( const dialogue & )> deactivate_condition;
talk_effect_t true_effect;
talk_effect_t false_effect;
bool has_deactivate_condition = false;
bool has_condition = false;
bool has_false_effect = false;
bool activate_only = true;
bool scenario_specific = false;

//TODO duration or var these
time_duration recurrence_min = 1_seconds;
time_duration recurrence_max = 1_seconds;
bool activate( dialogue &d ) const;
Expand Down Expand Up @@ -72,6 +77,12 @@ void clear( Character &you );
/** write out all queued eocs and inactive eocs to a file for testing */
void write_eocs_to_file( Character &you );
void write_global_eocs_to_file();
/** Run all avatar death eocs */
void avatar_death();
} // namespace effect_on_conditions

template<>
struct enum_traits<eoc_type> {
static constexpr eoc_type last = eoc_type::NUM_EOC_TYPES;
};
#endif // CATA_SRC_EFFECT_ON_CONDITION_H
4 changes: 4 additions & 0 deletions src/game.cpp
Expand Up @@ -2312,6 +2312,10 @@ bool game::is_game_over()
}
// is_dead_state() already checks hp_torso && hp_head, no need to for loop it
if( u.is_dead_state() ) {
effect_on_conditions::avatar_death();
if( !u.is_dead_state() ) {
return false;
}
Messages::deactivate();
if( get_option<std::string>( "DEATHCAM" ) == "always" ) {
uquit = QUIT_WATCH;
Expand Down
4 changes: 2 additions & 2 deletions src/iuse_actor.cpp
Expand Up @@ -4703,10 +4703,10 @@ cata::optional<int> effect_on_conditons_actor::use( Character &p, item &it, bool
item_location loc( *( p.as_character() ), &it );
dialogue d( get_talker_for( char_ptr ), get_talker_for( loc ) );
for( const effect_on_condition_id &eoc : eocs ) {
if( eoc->activate_only ) {
if( eoc->type == eoc_type::ACTIVATION ) {
eoc->activate( d );
} else {
debugmsg( "Cannot use a recurring effect_on_condition in an item. If you don't want the effect_on_condition to happen on its own (without the item's involvement), remove the recurrence min and max. Otherwise, create a non-recurring effect_on_condition for this item with its condition and effects, then have a recurring one queue it." );
debugmsg( "Must use an activation eoc for activation. If you don't want the effect_on_condition to happen on its own (without the item's involvement), remove the recurrence min and max. Otherwise, create a non-recurring effect_on_condition for this item with its condition and effects, then have a recurring one queue it." );
}
}
return it.type->charges_to_use();
Expand Down
4 changes: 2 additions & 2 deletions src/magic_spell_effect.cpp
Expand Up @@ -1528,10 +1528,10 @@ void spell_effect::effect_on_condition( const spell &sp, Creature &caster, const
dialogue d( get_talker_for( creatures.creature_at<Creature>( potential_target ) ),
get_talker_for( caster ) );
effect_on_condition_id eoc = effect_on_condition_id( sp.effect_data() );
if( eoc->activate_only ) {
if( eoc->type == eoc_type::ACTIVATION ) {
eoc->activate( d );
} else {
debugmsg( "Cannot use a recurring effect_on_condition in a spell. If you don't want the effect_on_condition to happen on its own (without the spell being cast), remove the recurrence min and max. Otherwise, create a non-recurring effect_on_condition for this spell with its condition and effects, then have a recurring one queue it." );
debugmsg( "Must use an activation eoc for a spell. If you don't want the effect_on_condition to happen on its own (without the spell being cast), remove the recurrence min and max. Otherwise, create a non-recurring effect_on_condition for this spell with its condition and effects, then have a recurring one queue it." );
}
}
}
5 changes: 5 additions & 0 deletions src/npc.cpp
Expand Up @@ -282,6 +282,10 @@ void npc_template::load( const JsonObject &jsobj )
if( jsobj.has_int( "height" ) ) {
guy.set_base_height( jsobj.get_int( "height" ) );
}
for( JsonValue jv : jsobj.get_array( "death_eocs" ) ) {
guy.death_eocs.emplace_back( effect_on_conditions::load_inline_eoc( jv, "" ) );
}

npc_templates.emplace( string_id<npc_template>( guy.idz ), std::move( tem ) );
}

Expand Down Expand Up @@ -376,6 +380,7 @@ void npc::load_npc_template( const string_id<npc_template> &ident )
for( const mission_type_id &miss_id : tguy.miss_ids ) {
add_new_mission( mission::reserve_new( miss_id, getID() ) );
}
death_eocs = tguy.death_eocs;
}

npc::~npc() = default;
Expand Down
4 changes: 2 additions & 2 deletions src/npctalk.cpp
Expand Up @@ -2833,10 +2833,10 @@ void talk_effect_fun_t::set_queue_effect_on_condition( const JsonObject &jo,
if( max > 0_seconds ) {
time_duration time_in_future = rng( dov_time_in_future_min.evaluate( d.actor( false ) ), max );
for( const effect_on_condition_id &eoc : eocs ) {
if( eoc->activate_only ) {
if( eoc->type == eoc_type::ACTIVATION ) {
effect_on_conditions::queue_effect_on_condition( time_in_future, eoc );
} else {
debugmsg( "Cannot queue a recurring effect_on_condition." );
debugmsg( "Cannot queue a non activation effect_on_condition." );
}
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/savegame_json.cpp
Expand Up @@ -730,7 +730,7 @@ void Character::load( const JsonObject &data )

data.read( "my_bionics", *my_bionics );
invalidate_pseudo_items();

data.read( "death_eocs", death_eocs );
for( auto &w : worn ) {
w.on_takeoff( *this );
}
Expand Down Expand Up @@ -1210,7 +1210,7 @@ void Character::store( JsonOut &json ) const

// "Looks like I picked the wrong week to quit smoking." - Steve McCroskey
json.member( "addictions", addictions );

json.member( "death_eocs", death_eocs );
json.member( "worn", worn ); // also saves contents
json.member( "inv" );
inv->json_save_items( json );
Expand Down

0 comments on commit eba01f0

Please sign in to comment.