diff --git a/data/mods/TEST_DATA/EOC.json b/data/mods/TEST_DATA/EOC.json
index 028ddfb6ce459..86ad12387ddc9 100644
--- a/data/mods/TEST_DATA/EOC.json
+++ b/data/mods/TEST_DATA/EOC.json
@@ -16,7 +16,7 @@
{
"type": "effect_on_condition",
"id": "EOC_run_inv_test1",
- "effect": [ { "u_run_inv_eocs": [ "EOC_run_inv_test1_nested" ] } ]
+ "effect": [ { "u_run_inv_eocs": "all", "true_eocs": [ "EOC_run_inv_test1_nested" ] } ]
},
{
"type": "effect_on_condition",
@@ -26,13 +26,114 @@
{
"type": "effect_on_condition",
"id": "EOC_run_inv_test2",
- "effect": [ { "u_run_inv_eocs": [ "EOC_run_inv_test2_nested" ], "item_ids": [ "backpack" ], "worn_only": true } ]
+ "effect": [
+ {
+ "u_run_inv_eocs": "all",
+ "true_eocs": [ "EOC_run_inv_test2_nested" ],
+ "search_data": [ { "id": "backpack", "worn_only": true } ]
+ }
+ ]
},
{
"type": "effect_on_condition",
"id": "EOC_run_inv_test2_nested",
"effect": [ { "npc_add_var": "key2", "type": "general", "context": "run_inv_test", "value": "yes" } ]
},
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_run_inv_test3",
+ "effect": [
+ {
+ "u_run_inv_eocs": "all",
+ "true_eocs": [ "EOC_run_inv_test3_nested" ],
+ "search_data": [ { "material": "wood", "wielded_only": true } ]
+ }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_run_inv_test3_nested",
+ "effect": [ { "npc_add_var": "key3", "type": "general", "context": "run_inv_test", "value": "yes" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_run_inv_test4",
+ "effect": [
+ {
+ "u_run_inv_eocs": "all",
+ "true_eocs": [ "EOC_run_inv_test4_nested" ],
+ "search_data": [ { "category": "weapons", "flags": [ "BELT_CLIP" ] } ]
+ }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_run_inv_test4_nested",
+ "effect": [ { "npc_add_var": "key4", "type": "general", "context": "run_inv_test", "value": "yes" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_math_test",
+ "effect": [ { "u_run_inv_eocs": "all", "true_eocs": [ "EOC_item_math_test_nested" ] } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_math_test_nested",
+ "effect": [ { "math": [ "n_hp()", "=", "100" ] } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_teleport_test",
+ "effect": [
+ { "u_location_variable": { "u_val": "tele_test" }, "x_adjust": 1, "y_adjust": 1 },
+ { "u_run_inv_eocs": "all", "true_eocs": [ "EOC_item_teleport_test_nested" ] }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_teleport_test_nested",
+ "effect": [ { "npc_teleport": { "u_val": "tele_test" } } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_flag_test",
+ "effect": [
+ {
+ "u_run_inv_eocs": "all",
+ "true_eocs": [ "EOC_item_flag_test_nested1", "EOC_item_flag_test_nested2", "EOC_item_flag_test_nested3" ],
+ "search_data": [ { "worn_only": true } ]
+ }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_flag_test_nested1",
+ "effect": [ { "npc_set_flag": "FILTHY" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_flag_test_nested2",
+ "condition": { "npc_has_flag": "FILTHY" },
+ "effect": [ { "npc_add_var": "is_filthy", "type": "general", "context": "run_inv_test", "value": "yes" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_flag_test_nested3",
+ "effect": [ { "npc_unset_flag": "FILTHY" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_activate_test",
+ "effect": [
+ { "u_location_variable": { "u_val": "activate_pos" }, "x_adjust": 1, "y_adjust": 1 },
+ { "u_run_inv_eocs": "all", "true_eocs": [ "EOC_item_activate_test_nested" ] }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_item_activate_test_nested",
+ "effect": [ { "u_activate": "deploy_furn", "target_var": { "u_val": "activate_pos" } } ]
+ },
{
"type": "effect_on_condition",
"id": "EOC_run_until_test",
diff --git a/data/mods/TEST_DATA/items.json b/data/mods/TEST_DATA/items.json
index 4ba733c7822c8..cde2379f7a424 100644
--- a/data/mods/TEST_DATA/items.json
+++ b/data/mods/TEST_DATA/items.json
@@ -3770,6 +3770,7 @@
"mode_modifier": [ [ "REACH", "bayonet", 2, [ "MELEE", "REACH_ATTACK" ] ] ],
"install_time": "5 s"
},
+ "use_action": { "type": "deploy_furn", "furn_type": "f_cardboard_box" },
"min_skills": [ [ "weapon", 2 ], [ "melee", 1 ] ],
"techniques": [ "RAPID_TEST" ],
"qualities": [ [ "CUT", 2 ], [ "CUT_FINE", 1 ], [ "BUTCHER", 19 ] ],
diff --git a/doc/EFFECT_ON_CONDITION.md b/doc/EFFECT_ON_CONDITION.md
index c28a2499ede90..e5aa17346c72e 100644
--- a/doc/EFFECT_ON_CONDITION.md
+++ b/doc/EFFECT_ON_CONDITION.md
@@ -331,7 +331,7 @@ Checks do alpha talker has `FEATHERS` mutation
| Avatar | Character | NPC | Monster | Furniture | Item |
| ------ | --------- | --------- | ---- | ------- | --- |
-| ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ❌ |
+| ✔️ | ✔️ | ✔️ | ✔️ | ❌ | ✔️ |
#### Examples
Alpha talker has `GRAB` flag, and beta talker has `GRAB_FILTER` flag; monster uses it to perform grabs - the game checks do monster (alpha talker, `u_`) has GRAB flag (i.e. able to grab at all), and check is target able to be grabbed using `GRAB_FILTER` flag
@@ -2008,7 +2008,7 @@ You or NPC is teleported to `target_var` coordinates
| Avatar | Character | NPC | Monster | Furniture | Item |
| ------ | --------- | --------- | ---- | ------- | --- |
-| ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ |
+| ✔️ | ✔️ | ✔️ | ❌ | ❌ | ✔️ |
##### Examples
@@ -2093,6 +2093,66 @@ You and NPC both die
}
```
+## Item effects
+
+#### `u_set_flag`, `npc_set_flag`
+Give item a flag
+
+| Syntax | Optionality | Value | Info |
+| ------ | ----------- | ------ | ---- |
+| "u_set_flag" / "npc_set_flag" | **mandatory** | string or [variable object](##variable-object) | id of flag that should be given |
+
+##### Valid talkers:
+
+| Avatar | Character | NPC | Monster | Furniture | Item |
+| ------ | --------- | --------- | ---- | ------- | --- |
+| ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
+
+##### Examples
+Make item filthy
+```json
+{ "npc_set_flag": "FILTHY" }
+```
+
+#### `u_unset_flag`, `npc_unset_flag`
+Remove a flag from item
+
+| Syntax | Optionality | Value | Info |
+| ------ | ----------- | ------ | ---- |
+| "u_unset_flag" / "npc_unset_flag" | **mandatory** | string or [variable object](##variable-object) | id of flag that should be remove |
+
+##### Valid talkers:
+
+| Avatar | Character | NPC | Monster | Furniture | Item |
+| ------ | --------- | --------- | ---- | ------- | --- |
+| ❌ | ❌ | ❌ | ❌ | ❌ | ✔️ |
+
+##### Examples
+Make item clean
+```json
+{ "npc_unset_flag": "FILTHY" }
+```
+
+#### `u_activate`, `npc_activate`
+You activate beta talker / NPC activates alpha talker. One must be a Character and the other an item.
+
+| Syntax | Optionality | Value | Info |
+| ------ | ----------- | ------ | ---- |
+| "u_activate" / "npc_activate" | **mandatory** | string or [variable object](##variable-object) | use action id of item that activate |
+| "target_var" | optional | [variable object](##variable-object) | if set, target location is forced this variable's coordinates |
+
+##### Valid talkers:
+
+| Avatar | Character | NPC | Monster | Furniture | Item |
+| ------ | --------- | --------- | ---- | ------- | --- |
+| ✔️ | ✔️ | ✔️ | ❌ | ❌ | ❌ |
+
+##### Examples
+Force you consume drug item
+```json
+{ "u_activate": "consume_drug" }
+```
+
## Map Updates
Map updates are related to any change in the map, weather, or coordinates, and any talker can use them
diff --git a/doc/NPCs.md b/doc/NPCs.md
index 081c8110e5b52..92710698c47fe 100644
--- a/doc/NPCs.md
+++ b/doc/NPCs.md
@@ -925,7 +925,7 @@ Effect | Description
`u_roll_remainder, npc_roll_remainder : `array of strings and/or [variable objects](#variable-object), `type: `string or [variable object](#variable-object), (*optional* `true_eocs: eocs_array`), (*optional* `false_eocs: eocs_array`), (*optional* `message: ` string or [variable object](#variable-object) ) | Type must be either `bionic`, `mutation`, `spell` or `recipe`. If the u or npc does not have all of the listed bionics, mutations, spells, or recipes they will be given one randomly and and then all of the effect_on_conditions in `true_eocs` are run, otherwise all the effect_on_conditions in `false_eocs` are run. If `message` is provided and a result is given then the `message` will be displayed as a message with the first instance of `%s` in it replaced with the name of the result selected.
`switch : arithmetic/math_expression`, `cases: effect_array` | Will calculate value of `switch` and then run member of `cases` with the highest `case` that the `switch` is higher or equal to. `cases` is an array of objects with an int_or_var `case` and an `effect` field which is a dialog effect.
`u_run_npc_eocs or npc_run_npc_eocs : effect_on_condition_array`, (*optional* `unique_ids: `array of strings and/or [variable objects](#variable-object)), (*optional* `npcs_must_see: npcs_must_see_bool`), (*optional* `npc_range: `int or [variable object](#variable-object)), (*optional* `local: local_bool`) | Will run all members of the `effect_on_condition_array` on npcs. Members should either be the id of an effect_on_condition or an inline effect_on_condition. If `local`(default: false) is false, then regardless of location all npcs with unique ids in the array `unique_ids` will be affected. If `local` is true, only unique_ids listed in `unique_ids` will be affected, if it is empty all npcs in range will be effected. If a value is given for `npc_range` the npc must be that close to the source and if `npcs_must_see`(defaults to false) is true the npc must be able to see the source. For `u_run_npc_eocs` u is the source for `npc_run_npc_eocs` it is the npc.
-`u_run_inv_eocs or npc_run_inv_eocs : effect_on_condition_array`, (*optional* `item_ids: `array of strings and/or [variable objects](#variable-object)), (*optional* `worn_only: worn_only_bool`) | Will run all members of the `effect_on_condition_array` on items in inventory. Members should either be the id of an effect_on_condition or an inline effect_on_condition. If `item_ids` is given, only items with id listed in `item_ids` will be affected. If `worn_only`(default: false) is true, items u or npc wears are only affected. For `u_run_inv_eocs` u is the source for `npc_run_inv_eocs` it is the npc. Items will become the beta talker for the EOC to run.
+`u_run_inv_eocs or npc_run_inv_eocs : ` string or [variable object](#variable-object), (*optional* `true_eocs: effect_on_condition_array`), (*optional* `false_eocs: effect_on_condition_array`), (*optional* `search_data: search_data_array`) | Run EOCs on items in u/npc's inventory. For `u_run_inv_eocs` u is the source for `npc_run_inv_eocs` it is the npc. Items will become the beta talker for the EOC to run.
If value is `"all"`, this targets all items that match the condition. If value is `"random"`, this targets one item at random from that match the condition. `true_eocs` runs with items selected as target, `false_eocs` runs with the others. `search_data` sets the condition for the target item, if not given all items will be targeted.
`search_data` accepts one or more conditions such as:
"search_data": [ {
"id": "item_id",
"category": "item_category",
"flags": [ "FLAG_1", "FLAG_2" ],
"worn_only": false,
"wielded_only": true
} ]
All fields are optional. `id`, `category`, `material`, and `flags` set the id, category, material, flags of the target item respectively. `worn_only`, `wielded_only` limit taget to what you wear/wield.
`weighted_list_eocs: array_array` | Will choose one of a list of eocs to activate based on weight. Members should be an array of first the id of an effect_on_condition or an inline effect_on_condition and second an object that resolves to an integer weight.
Example: This will cause "EOC_SLEEP" 1/10 as often as it makes a test message appear. "effect": [
{
"weighted_list_eocs": [
[ "EOC_SLEEP", { "const": 1 } ],
[ {
"id": "eoc_test2",
"effect": [ { "u_message": "A test message appears!", "type": "bad" } ]
},
{ "const": 10 }
]
]
}
]
#### Detailed Info
@@ -1433,8 +1433,8 @@ _function arguments are `d`oubles (or sub-expressions), `s`trings, or `v`[ariabl
| armor(`s`/`v`,`s`/`v`) | ✅ | ❌ | u, n | Return the numerical value for a characters armor on a body part, for a damage type.
Variables are damagetype ID, bodypart ID.
Example:
`"condition": { "math": [ "u_armor('bash', 'torso')", ">=", "5"] }`|
| attack_speed() | ✅ | ❌ | u, n | Return the characters current adjusted attack speed with their current weapon.
Example:
`"condition": { "math": [ "u_attack_speed()", ">=", "10"] }`|
| effect_intensity(`s`/`v`,...) | ✅ | ❌ | u, n | Return the characters intensity of effect.
Variable is effect ID.
Optional kwargs:
`bodypart`: `s`/`v` - Specify the bodypart to get/set intensity of effect.
Example:
`"condition": { "math": [ "u_effect_intensity('bite', 'bodypart': 'torso')", ">", "1"] }`|
-| hp(`s`/`v`) | ✅ | ✅ | u, n | Return or set the characters hp.
Variable is bodypart ID. If omitted, get sum of all bodypart or set all bodypart.
Example:
`"condition": { "math": [ "hp('torso')", ">", "100"] }`|
-| hp_max(`s`/`v`) | ✅ | ❌ | u, n | Return the characters max amount of hp on a body part.
Variable is bodypart ID.
Example:
`"condition": { "math": [ "u_hp_max('torso')", ">=", "100"] }`|
+| hp(`s`/`v`) | ✅ | ✅ | u, n | Return or set the characters hp.
Variable is bodypart ID. If omitted, get sum of all bodypart or set all bodypart.
For items, returns current amount of damage required to destroy item. Variable is not required.
Example:
`"condition": { "math": [ "hp('torso')", ">", "100"] }`|
+| hp_max(`s`/`v`) | ✅ | ❌ | u, n | Return the characters max amount of hp on a body part.
Variable is bodypart ID.
For items, returns max amount of damage required to destroy item. Variable is not required.
Example:
`"condition": { "math": [ "u_hp_max('torso')", ">=", "100"] }`|
| game_option(`s`/`v`) | ✅ | ❌ | N/A
(global) | Return the numerical value of a game option
Example:
`"condition": { "math": [ "game_option('NPC_SPAWNTIME')", ">=", "5"] }`|
| monsters_nearby(`s`/`v`...) | ✅ | ❌ | u, n, global | Return the number of nearby monsters. Takes any number of `s`tring or `v`ariable positional parameters as monster IDs.
Optional kwargs:
`radius`: `d`/`v` - limit to radius (rl_dist)
`location`: `v` - center search on this location
The `location` kwarg is mandatory in the global scope.
Examples:
`"condition": { "math": [ "u_monsters_nearby('radius': u_search_radius * 3)", ">", "5" ] }`
`"condition": { "math": [ "monsters_nearby('mon_void_maw', 'mon_void_limb', mon_fotm_var, 'radius': u_search_radius * 3, 'location': u_search_loc)", ">", "5" ] }`|
| num_input(`s`/`v`,`d`/`v`) | ✅ | ❌ | N/A
(global) | Prompt the player for a number.
Variables are Prompt text, Default Value:
`"math": [ "u_value_to_set", "=", "num_input('Playstyle Perks Cost?', 4)" ]`|
diff --git a/src/dialogue_helpers.h b/src/dialogue_helpers.h
index 1c4297a717d97..621f364422e62 100644
--- a/src/dialogue_helpers.h
+++ b/src/dialogue_helpers.h
@@ -55,7 +55,7 @@ struct talk_effect_fun_t {
void set_run_eoc_until( const JsonObject &jo, std::string_view member );
void set_run_eoc_selector( const JsonObject &jo, const std::string &member );
void set_run_npc_eocs( const JsonObject &jo, std::string_view member, bool is_npc );
- void set_run_inv_eocs( const JsonObject &jo, std::string_view member, bool is_npc );
+ void set_run_inv_eocs( const JsonObject &jo, const std::string &member, bool is_npc );
void set_queue_eocs( const JsonObject &jo, std::string_view member );
void set_queue_eoc_with( const JsonObject &jo, std::string_view member );
void set_switch( const JsonObject &jo, std::string_view member );
@@ -127,6 +127,9 @@ struct talk_effect_fun_t {
void set_open_dialogue( const JsonObject &jo, std::string_view member );
void set_take_control( const JsonObject &jo );
void set_take_control_menu();
+ void set_set_flag( const JsonObject &jo, const std::string &member, bool is_npc );
+ void set_unset_flag( const JsonObject &jo, const std::string &member, bool is_npc );
+ void set_activate( const JsonObject &jo, const std::string &member, bool is_npc );
void operator()( dialogue &d ) const {
if( !function ) {
return;
diff --git a/src/item_location.cpp b/src/item_location.cpp
index 0d64a1bd4f3cb..b661ddd1fa430 100644
--- a/src/item_location.cpp
+++ b/src/item_location.cpp
@@ -1124,6 +1124,10 @@ std::unique_ptr get_talker_for( item_location &it )
{
return std::make_unique( &it );
}
+std::unique_ptr get_talker_for( const item_location &it )
+{
+ return std::make_unique( &it );
+}
std::unique_ptr get_talker_for( item_location *it )
{
return std::make_unique( it );
diff --git a/src/item_location.h b/src/item_location.h
index effa42dabfe76..b9ee33802ec29 100644
--- a/src/item_location.h
+++ b/src/item_location.h
@@ -159,5 +159,6 @@ class item_location
std::shared_ptr ptr;
};
std::unique_ptr get_talker_for( item_location &it );
+std::unique_ptr get_talker_for( const item_location &it );
std::unique_ptr get_talker_for( item_location *it );
#endif // CATA_SRC_ITEM_LOCATION_H
diff --git a/src/npctalk.cpp b/src/npctalk.cpp
index 91fb1b0f3d91f..aa75060751bea 100644
--- a/src/npctalk.cpp
+++ b/src/npctalk.cpp
@@ -121,6 +121,50 @@ static const zone_type_id zone_type_NPC_NO_INVESTIGATE( "NPC_NO_INVESTIGATE" );
static std::map json_talk_topics;
+struct item_search_data {
+ itype_id id;
+ item_category_id category;
+ material_id material;
+ std::vector flags;
+ bool worn_only;
+ bool wielded_only;
+
+ explicit item_search_data( const JsonObject &jo ) {
+ id = itype_id( jo.get_string( "id", "" ) );
+ category = item_category_id( jo.get_string( "category", "" ) );
+ material = material_id( jo.get_string( "material", "" ) );
+ for( std::string flag : jo.get_string_array( "flags" ) ) {
+ flags.emplace_back( flag );
+ }
+ worn_only = jo.get_bool( "worn_only", false );
+ wielded_only = jo.get_bool( "wielded_only", false );
+ }
+
+ bool check( const Character *guy, const item_location &loc ) {
+ if( !id.is_empty() && id != loc->typeId() ) {
+ return false;
+ }
+ if( !category.is_empty() && category != loc->get_category_shallow().id ) {
+ return false;
+ }
+ if( !material.is_empty() && loc->made_of( material ) == 0 ) {
+ return false;
+ }
+ for( flag_id flag : flags ) {
+ if( !loc->has_flag( flag ) ) {
+ return false;
+ }
+ }
+ if( worn_only && !guy->is_worn( *loc ) ) {
+ return false;
+ }
+ if( wielded_only && !guy->is_wielding( *loc ) ) {
+ return false;
+ }
+ return true;
+ }
+};
+
#define dbg(x) DebugLog((x),D_GAME) << __FILE__ << ":" << __LINE__ << ": "
static int topic_category( const talk_topic &the_topic );
@@ -4189,6 +4233,78 @@ void talk_effect_fun_t::set_offer_mission( const JsonObject &jo, const std::stri
};
}
+void talk_effect_fun_t::set_set_flag( const JsonObject &jo, const std::string &member,
+ bool is_npc )
+{
+ str_or_var flag = get_str_or_var( jo.get_member( member ), member, true );
+
+ function = [is_npc, flag]( dialogue & d ) {
+ item_location *it = d.actor( is_npc )->get_item();
+
+ if( it && it->get_item() ) {
+ flag_id f_id( flag.evaluate( d ) );
+ it->get_item()->set_flag( f_id );
+ } else {
+ debugmsg( "No valid %s talker.", is_npc ? "beta" : "alpha" );
+ }
+ };
+}
+
+void talk_effect_fun_t::set_unset_flag( const JsonObject &jo, const std::string &member,
+ bool is_npc )
+{
+ str_or_var flag = get_str_or_var( jo.get_member( member ), member, true );
+
+ function = [is_npc, flag]( dialogue & d ) {
+ item_location *it = d.actor( is_npc )->get_item();
+
+ if( it && it->get_item() ) {
+ flag_id f_id( flag.evaluate( d ) );
+ it->get_item()->unset_flag( f_id );
+ } else {
+ debugmsg( "No valid %s talker.", is_npc ? "beta" : "alpha" );
+ }
+ };
+}
+
+void talk_effect_fun_t::set_activate( const JsonObject &jo, const std::string &member,
+ bool is_npc )
+{
+ str_or_var method = get_str_or_var( jo.get_member( member ), member, true );
+ std::optional target_var;
+ if( jo.has_member( "target_var" ) ) {
+ target_var = read_var_info( jo.get_object( "target_var" ) );
+ }
+
+ function = [is_npc, method, target_var]( dialogue & d ) {
+ Character *guy = d.actor( is_npc )->get_character();
+ item_location *it = d.actor( !is_npc )->get_item();
+
+ if( guy ) {
+ const std::string method_str = method.evaluate( d );
+ if( it && it->get_item() ) {
+ if( !it->get_item()->get_usable_item( method_str ) ) {
+ add_msg_debug( debugmode::DF_NPC, "Invalid use action. %s", method_str );
+ return;
+ }
+ if( target_var.has_value() ) {
+ tripoint_abs_ms target_pos = get_tripoint_from_var( target_var, d );
+ if( get_map().inbounds( target_pos ) ) {
+ guy->invoke_item( it->get_item(), method_str, get_map().getlocal( target_pos ) );
+ return;
+ }
+ }
+ guy->invoke_item( it->get_item(), method.evaluate( d ) );
+
+ } else {
+ debugmsg( "%s talker must be Item.", is_npc ? "alpha" : "beta" );
+ }
+ } else {
+ debugmsg( "%s talker must be Character.", is_npc ? "beta" : "alpha" );
+ }
+ };
+}
+
void talk_effect_fun_t::set_make_sound( const JsonObject &jo, const std::string &member,
bool is_npc )
{
@@ -4537,40 +4653,67 @@ void talk_effect_fun_t::set_run_npc_eocs( const JsonObject &jo,
}
void talk_effect_fun_t::set_run_inv_eocs( const JsonObject &jo,
- const std::string_view member, bool is_npc )
+ const std::string &member, bool is_npc )
{
- std::vector eocs = load_eoc_vector( jo, member );
- std::vector item_ids;
- for( JsonValue jv : jo.get_array( "item_ids" ) ) {
- item_ids.emplace_back( get_str_or_var( jv, "item_ids" ) );
+ str_or_var option = get_str_or_var( jo.get_member( member ), member );
+ std::vector true_eocs = load_eoc_vector( jo, "true_eocs" );
+ std::vector false_eocs = load_eoc_vector( jo, "false_eocs" );
+ std::vector data;
+ for( const JsonValue &search_data_jo : jo.get_array( "search_data" ) ) {
+ data.emplace_back( search_data_jo );
}
- bool worn_only = jo.get_bool( "worn_only", false );
- function = [eocs, worn_only, item_ids, is_npc]( dialogue & d ) {
+ function = [option, true_eocs, false_eocs, data, is_npc]( dialogue & d ) {
Character *guy = d.actor( is_npc )->get_character();
if( guy ) {
- std::vector ids( item_ids.size() );
- for( const str_or_var &id : item_ids ) {
- ids.emplace_back( id.evaluate( d ) );
- }
- std::vector- target_items = d.actor( is_npc )->items_with( [worn_only, guy,
- ids]( const item & it ) {
- if( worn_only && !guy->is_worn( it ) ) {
- return false;
- }
- for( const std::string &id : ids ) {
- if( id == it.type->get_id().str() ) {
- return true;
+ std::vector true_items;
+ std::vector false_items;
+
+ for( item_location &loc : guy->all_items_loc() ) {
+ // Check if item matches any search_data.
+ bool true_tgt = data.empty();
+ for( item_search_data datum : data ) {
+ if( datum.check( guy, loc ) ) {
+ true_tgt = true;
+ break;
}
}
- return ids.empty();
- } );
- for( item *target : target_items ) {
+ if( true_tgt ) {
+ true_items.push_back( loc );
+ } else {
+ false_items.push_back( loc );
+ }
+ }
+
+ const auto run_eoc = [&d, is_npc]( item_location & loc,
+ const std::vector &eocs ) {
for( const effect_on_condition_id &eoc : eocs ) {
- item_location loc = item_location( *guy, target );
- dialogue newDialog = dialogue( d.actor( is_npc )->clone(), get_talker_for( loc ),
- d.get_conditionals(), d.get_context() );
- eoc->activate( newDialog );
+ // Check if item is outdated.
+ if( loc.get_item() ) {
+ dialogue newDialog = dialogue( d.actor( is_npc )->clone(), get_talker_for( loc ),
+ d.get_conditionals(), d.get_context() );
+ eoc->activate( newDialog );
+ }
+ }
+ };
+
+ if( option.evaluate( d ) == "all" ) {
+ for( item_location target : true_items ) {
+ run_eoc( target, true_eocs );
+ }
+ for( item_location target : false_items ) {
+ run_eoc( target, false_eocs );
+ }
+ } else if( option.evaluate( d ) == "random" ) {
+ std::shuffle( true_items.begin(), true_items.end(), rng_get_engine() );
+ run_eoc( true_items.back(), true_eocs );
+ true_items.pop_back();
+
+ for( item_location target : true_items ) {
+ run_eoc( target, false_eocs );
+ }
+ for( item_location target : false_items ) {
+ run_eoc( target, false_eocs );
}
}
}
@@ -5161,6 +5304,18 @@ void talk_effect_fun_t::set_teleport( const JsonObject &jo, const std::string_vi
teleporter->add_msg_if_player( _( fail_message.evaluate( d ) ) );
}
}
+ item_location *it = d.actor( is_npc )->get_item();
+ if( it && it->get_item() ) {
+ if( get_map().inbounds( target_pos ) ) {
+ get_map().add_item_or_charges( get_map().getlocal( target_pos ), *it->get_item() );
+ } else {
+ tinymap target_bay;
+ target_bay.load( project_to( target_pos ), false );
+ target_bay.add_item_or_charges( target_bay.getlocal( target_pos ), *it->get_item() );
+ }
+ add_msg( _( success_message.evaluate( d ) ) );
+ it->remove_item();
+ }
};
}
@@ -5464,9 +5619,9 @@ void talk_effect_t::parse_sub_effect( const JsonObject &jo )
subeffect_fun.set_run_npc_eocs( jo, "u_run_npc_eocs", false );
} else if( jo.has_array( "npc_run_npc_eocs" ) ) {
subeffect_fun.set_run_npc_eocs( jo, "npc_run_npc_eocs", true );
- } else if( jo.has_array( "u_run_inv_eocs" ) ) {
+ } else if( jo.has_member( "u_run_inv_eocs" ) ) {
subeffect_fun.set_run_inv_eocs( jo, "u_run_inv_eocs", false );
- } else if( jo.has_array( "npc_run_inv_eocs" ) ) {
+ } else if( jo.has_member( "npc_run_inv_eocs" ) ) {
subeffect_fun.set_run_inv_eocs( jo, "npc_run_inv_eocs", true );
} else if( jo.has_array( "weighted_list_eocs" ) ) {
subeffect_fun.set_weighted_list_eocs( jo, "weighted_list_eocs" );
@@ -5548,6 +5703,18 @@ void talk_effect_t::parse_sub_effect( const JsonObject &jo )
subeffect_fun.set_open_dialogue( jo, "open_dialogue" );
} else if( jo.has_member( "take_control" ) ) {
subeffect_fun.set_take_control( jo );
+ } else if( jo.has_member( "u_set_flag" ) ) {
+ subeffect_fun.set_set_flag( jo, "u_set_flag", false );
+ } else if( jo.has_member( "npc_set_flag" ) ) {
+ subeffect_fun.set_set_flag( jo, "npc_set_flag", true );
+ } else if( jo.has_member( "u_unset_flag" ) ) {
+ subeffect_fun.set_unset_flag( jo, "u_unset_flag", false );
+ } else if( jo.has_member( "npc_unset_flag" ) ) {
+ subeffect_fun.set_unset_flag( jo, "npc_unset_flag", true );
+ } else if( jo.has_member( "u_activate" ) ) {
+ subeffect_fun.set_activate( jo, "u_activate", false );
+ } else if( jo.has_member( "npc_activate" ) ) {
+ subeffect_fun.set_activate( jo, "npc_activate", true );
} else {
jo.throw_error( "invalid sub effect syntax: " + jo.str() );
}
diff --git a/src/talker_item.cpp b/src/talker_item.cpp
index 31171edaf74e0..885c0358ce051 100644
--- a/src/talker_item.cpp
+++ b/src/talker_item.cpp
@@ -10,49 +10,83 @@
#include "talker_item.h"
#include "vehicle.h"
-std::string talker_item::disp_name() const
+talker_item::talker_item( item_location *new_me )
{
- return me_it->get_item()->display_name();
+ me_it = new_me;
+ me_it_const = new_me;
}
-std::string talker_item::get_name() const
+std::string talker_item_const::disp_name() const
{
- return me_it->get_item()->type_name();
+ return me_it_const->get_item()->display_name();
}
-int talker_item::posx() const
+std::string talker_item_const::get_name() const
{
- return me_it->position().x;
+ return me_it_const->get_item()->type_name();
}
-int talker_item::posy() const
+int talker_item_const::posx() const
{
- return me_it->position().y;
+ return me_it_const->position().x;
}
-int talker_item::posz() const
+int talker_item_const::posy() const
{
- return me_it->position().z;
+ return me_it_const->position().y;
}
-tripoint talker_item::pos() const
+int talker_item_const::posz() const
{
- return me_it->position();
+ return me_it_const->position().z;
}
-tripoint_abs_ms talker_item::global_pos() const
+tripoint talker_item_const::pos() const
{
- return tripoint_abs_ms( get_map().getabs( me_it->position() ) );
+ return me_it_const->position();
}
-tripoint_abs_omt talker_item::global_omt_location() const
+tripoint_abs_ms talker_item_const::global_pos() const
+{
+ return tripoint_abs_ms( get_map().getabs( me_it_const->position() ) );
+}
+
+tripoint_abs_omt talker_item_const::global_omt_location() const
{
return get_player_character().global_omt_location();
}
-std::string talker_item::get_value( const std::string &var_name ) const
+std::string talker_item_const::get_value( const std::string &var_name ) const
+{
+ return me_it_const->get_item()->get_var( var_name );
+}
+
+bool talker_item_const::has_flag( const flag_id &f ) const
{
- return me_it->get_item()->get_var( var_name );
+ add_msg_debug( debugmode::DF_TALKER, "Item %s checked for flag %s",
+ me_it_const->get_item()->tname(),
+ f.c_str() );
+ return me_it_const->get_item()->has_flag( f );
+}
+
+std::vector talker_item_const::get_topics( bool )
+{
+ return me_it_const->get_item()->typeId()->chat_topics;
+}
+
+bool talker_item_const::will_talk_to_u( const Character &you, bool )
+{
+ return !you.is_dead_state();
+}
+
+int talker_item_const::get_cur_hp( const bodypart_id & ) const
+{
+ return me_it_const->get_item()->max_damage() - me_it_const->get_item()->damage();
+}
+
+int talker_item_const::get_hp_max( const bodypart_id & ) const
+{
+ return me_it_const->get_item()->max_damage();
}
void talker_item::set_value( const std::string &var_name, const std::string &value )
@@ -65,12 +99,7 @@ void talker_item::remove_value( const std::string &var_name )
me_it->get_item()->erase_var( var_name );
}
-std::vector talker_item::get_topics( bool )
+void talker_item::set_all_parts_hp_cur( int set ) const
{
- return me_it->get_item()->typeId()->chat_topics;
-}
-
-bool talker_item::will_talk_to_u( const Character &you, bool )
-{
- return !you.is_dead_state();
+ me_it->get_item()->set_damage( me_it->get_item()->max_damage() - set );
}
diff --git a/src/talker_item.h b/src/talker_item.h
index 1b579d8a9b153..5e2fec512527e 100644
--- a/src/talker_item.h
+++ b/src/talker_item.h
@@ -19,20 +19,13 @@ struct tripoint;
/*
* Talker wrapper class for item.
*/
-class talker_item: public talker_cloner
+class talker_item_const: public talker_cloner
{
public:
- explicit talker_item( item_location *new_me ): me_it( new_me ) {
+ explicit talker_item_const( const item_location *new_me ): me_it_const( new_me ) {
}
- ~talker_item() override = default;
+ ~talker_item_const() override = default;
- // underlying element accessor functions
- item_location *get_item() override {
- return me_it;
- }
- item_location *get_item() const override {
- return me_it;
- }
// identity and location
std::string disp_name() const override;
std::string get_name() const override;
@@ -44,12 +37,38 @@ class talker_item: public talker_cloner
tripoint_abs_omt global_omt_location() const override;
std::string get_value( const std::string &var_name ) const override;
- void set_value( const std::string &var_name, const std::string &value ) override;
- void remove_value( const std::string & ) override;
+
+ bool has_flag( const flag_id &f ) const override;
std::vector get_topics( bool radio_contact ) override;
bool will_talk_to_u( const Character &you, bool force ) override;
+ int get_cur_hp( const bodypart_id & ) const override;
+ int get_hp_max( const bodypart_id & ) const override;
+ protected:
+ talker_item_const() = default;
+ const item_location *me_it_const;
+};
+
+class talker_item: public talker_cloner
+{
+ public:
+ explicit talker_item( item_location *new_me );
+ ~talker_item() override = default;
+
+ // underlying element accessor functions
+ item_location *get_item() override {
+ return me_it;
+ }
+ item_location *get_item() const override {
+ return me_it;
+ }
+
+ void set_value( const std::string &var_name, const std::string &value ) override;
+ void remove_value( const std::string & ) override;
+
+ void set_all_parts_hp_cur( int ) const override;
+
protected:
talker_item() = default;
item_location *me_it;
diff --git a/tests/eoc_test.cpp b/tests/eoc_test.cpp
index 6b91f2870c2a4..42a987d148ccf 100644
--- a/tests/eoc_test.cpp
+++ b/tests/eoc_test.cpp
@@ -26,6 +26,14 @@ effect_on_condition_EOC_combat_mutator_test( "EOC_combat_mutator_test" );
static const effect_on_condition_id
effect_on_condition_EOC_increment_var_var( "EOC_increment_var_var" );
static const effect_on_condition_id
+effect_on_condition_EOC_item_activate_test( "EOC_item_activate_test" );
+static const effect_on_condition_id
+effect_on_condition_EOC_item_flag_test( "EOC_item_flag_test" );
+static const effect_on_condition_id
+effect_on_condition_EOC_item_math_test( "EOC_item_math_test" );
+static const effect_on_condition_id
+effect_on_condition_EOC_item_teleport_test( "EOC_item_teleport_test" );
+static const effect_on_condition_id
effect_on_condition_EOC_jmath_test( "EOC_jmath_test" );
static const effect_on_condition_id
effect_on_condition_EOC_math_armor( "EOC_math_armor" );
@@ -58,6 +66,8 @@ static const effect_on_condition_id effect_on_condition_EOC_recipe_test_1( "EOC_
static const effect_on_condition_id effect_on_condition_EOC_recipe_test_2( "EOC_recipe_test_2" );
static const effect_on_condition_id effect_on_condition_EOC_run_inv_test1( "EOC_run_inv_test1" );
static const effect_on_condition_id effect_on_condition_EOC_run_inv_test2( "EOC_run_inv_test2" );
+static const effect_on_condition_id effect_on_condition_EOC_run_inv_test3( "EOC_run_inv_test3" );
+static const effect_on_condition_id effect_on_condition_EOC_run_inv_test4( "EOC_run_inv_test4" );
static const effect_on_condition_id effect_on_condition_EOC_run_until_test( "EOC_run_until_test" );
static const effect_on_condition_id effect_on_condition_EOC_run_with_test( "EOC_run_with_test" );
static const effect_on_condition_id
@@ -73,7 +83,12 @@ effect_on_condition_EOC_string_var_var( "EOC_string_var_var" );
static const effect_on_condition_id effect_on_condition_EOC_teleport_test( "EOC_teleport_test" );
static const effect_on_condition_id effect_on_condition_EOC_try_kill( "EOC_try_kill" );
+static const flag_id json_flag_FILTHY( "FILTHY" );
+
+static const furn_str_id furn_f_cardboard_box( "f_cardboard_box" );
+
static const itype_id itype_backpack( "backpack" );
+static const itype_id itype_sword_wood( "sword_wood" );
static const itype_id itype_test_knife_combat( "test_knife_combat" );
static const mtype_id mon_zombie( "mon_zombie" );
@@ -691,6 +706,9 @@ TEST_CASE( "EOC_run_inv_test", "[eoc]" )
get_avatar().i_add( item( itype_test_knife_combat ) );
get_avatar().i_add( item( itype_backpack ) );
+ tripoint_abs_ms pos_before = get_avatar().get_location();
+ tripoint_abs_ms pos_after = pos_before + tripoint_south_east;
+
dialogue d( get_talker_for( get_avatar() ), std::make_unique() );
std::vector
- items_before = get_avatar().items_with( []( const item & it ) {
@@ -708,7 +726,7 @@ TEST_CASE( "EOC_run_inv_test", "[eoc]" )
CHECK( items_after.size() == 4 );
- // Worn backpack only
+ // Test search_data: id, worn_only
CHECK( effect_on_condition_EOC_run_inv_test2->activate( d ) );
items_after = get_avatar().items_with( []( const item & it ) {
@@ -716,6 +734,73 @@ TEST_CASE( "EOC_run_inv_test", "[eoc]" )
} );
CHECK( items_after.size() == 1 );
+
+ // Test search_data: material, wielded_only
+ CHECK( effect_on_condition_EOC_run_inv_test3->activate( d ) );
+
+ items_after = get_avatar().items_with( []( const item & it ) {
+ return it.get_var( "npctalk_var_general_run_inv_test_key3" ) == "yes";
+ } );
+
+ CHECK( items_after.empty() );
+
+ get_avatar().get_wielded_item().remove_item();
+ item weapon_wood( itype_sword_wood );
+ get_avatar().set_wielded_item( weapon_wood );
+
+ CHECK( effect_on_condition_EOC_run_inv_test3->activate( d ) );
+
+ items_after = get_avatar().items_with( []( const item & it ) {
+ return it.get_var( "npctalk_var_general_run_inv_test_key3" ) == "yes";
+ } );
+
+ CHECK( items_after.size() == 1 );
+
+ // Test search_data: category, flags
+ CHECK( effect_on_condition_EOC_run_inv_test4->activate( d ) );
+
+ items_after = get_avatar().items_with( []( const item & it ) {
+ return it.get_var( "npctalk_var_general_run_inv_test_key4" ) == "yes";
+ } );
+
+ CHECK( items_after.size() == 1 );
+
+ // Flag test for item
+ CHECK( effect_on_condition_EOC_item_flag_test->activate( d ) );
+
+ items_after = get_avatar().items_with( []( const item & it ) {
+ return it.get_var( "npctalk_var_general_run_inv_test_is_filthy" ) == "yes";
+ } );
+
+ CHECK( items_after.size() == 1 );
+
+ items_after = get_avatar().items_with( []( const item & it ) {
+ return it.has_flag( json_flag_FILTHY );
+ } );
+
+ CHECK( items_after.empty() );
+
+ // Math function test for item
+ items_before = get_avatar().items_with( []( const item & it ) {
+ return it.damage() == 0;
+ } );
+
+ REQUIRE( items_before.size() == 4 );
+ CHECK( effect_on_condition_EOC_item_math_test->activate( d ) );
+
+ items_after = get_avatar().items_with( []( const item & it ) {
+ return it.damage() == it.max_damage() - 100;
+ } );
+
+ CHECK( items_after.size() == 4 );
+
+ // Activate test for item
+ CHECK( effect_on_condition_EOC_item_activate_test->activate( d ) );
+ CHECK( get_map().furn( get_map().getlocal( pos_after ) ) == furn_f_cardboard_box );
+
+ // Teleport test for item
+ CHECK( effect_on_condition_EOC_item_teleport_test->activate( d ) );
+ CHECK( get_map().i_at( get_map().getlocal( pos_after ) ).size() == 3 );
}
TEST_CASE( "EOC_event_test", "[eoc]" )