diff --git a/data/json/monsters/nether.json b/data/json/monsters/nether.json index 1f8aeba429432..5eaba60a0b0a3 100644 --- a/data/json/monsters/nether.json +++ b/data/json/monsters/nether.json @@ -1177,7 +1177,8 @@ "absorb_move_cost_max": 300, "absorb_move_cost_per_ml": 0.025, "flags": [ "SEES", "SMELLS", "HAS_MIND", "SWIMS", "PLASTIC", "SLUDGEPROOF", "ACID_BLOOD", "ACIDPROOF", "NOHEAD", "NOGIB" ], - "armor": { "bash": 10, "cut": 30, "bullet": 24, "electric": 4 } + "armor": { "bash": 10, "cut": 30, "bullet": 24, "electric": 4 }, + "no_absorb_material": [ "anomaly", "monolith", "monolith_heavy" ] }, { "id": "mon_thing", diff --git a/doc/MONSTERS.md b/doc/MONSTERS.md index 2d51586155ea0..e97f37a9c7a93 100644 --- a/doc/MONSTERS.md +++ b/doc/MONSTERS.md @@ -104,6 +104,7 @@ Property | Description `absorb_move_cost_min` | (int) For monsters with the `ABSORB_ITEMS` special attack. Sets a minimum movement cost for absorbing items regardless of the volume of the consumed item. Default 1. `absorb_move_cost_max` | (int) For monsters with the `ABSORB_ITEMS` special attack. Sets a maximum movement cost for absorbing items regardless of the volume of the consumed item. -1 for no limit. Default -1. `absorb_material` | (array of string) For monsters with the `ABSORB_ITEMS` special attack. Specifies the types of materials that the monster will seek to absorb. Items with multiple materials will be matched as long as it is made of at least one of the materials in this list. If not specified the monster will absorb all materials. +`no_absorb_material` | (array of string) For monsters with the `ABSORB_ITEMS` special attack. Specifies the types of materials that the monster is unable to absorb. This takes precedence over absorb_material; even if the monster is whitelisted for this material, it cannot do so if any of its materials are found here. If not specified, there are no limits placed on what was whitelisted. `split_move_cost` | (int) For monsters with the `SPLIT` special attack. Determines the move cost when splitting into a copy of itself. Properties in the above tables are explained in more detail in the sections below. diff --git a/doc/MONSTER_SPECIAL_ATTACKS.md b/doc/MONSTER_SPECIAL_ATTACKS.md index b920dd5c5aedb..6aaca31aed6c8 100644 --- a/doc/MONSTER_SPECIAL_ATTACKS.md +++ b/doc/MONSTER_SPECIAL_ATTACKS.md @@ -80,7 +80,7 @@ This monster can attempt a grab every ten turns, a leap with a maximum range of These special attacks are mostly hardcoded in C++ and are generally not configurable with JSON (unless otherwise documented). JSON-declared replacements are mentioned when available. -- ```ABSORB_ITEMS``` Consumes objects it moves over to gain HP. A movement cost per ml consumed can be enforced with `absorb_move_cost_per_ml` (default 0.025). The movement cost can have a minimum and maximum value specified by `absorb_move_cost_min` (default 1) and `absorb_move_cost_max` (default -1 for no limit). The volume in milliliters the monster must consume to gain 1 HP can be specified with `absorb_ml_per_hp` (default 250 ml). A list of materials that the monster can absorb can be specified with `absorb_materials` (can be string or string array, not specified = absorb all materials). +- ```ABSORB_ITEMS``` Consumes objects it moves over to gain HP. A movement cost per ml consumed can be enforced with `absorb_move_cost_per_ml` (default 0.025). The movement cost can have a minimum and maximum value specified by `absorb_move_cost_min` (default 1) and `absorb_move_cost_max` (default -1 for no limit). The volume in milliliters the monster must consume to gain 1 HP can be specified with `absorb_ml_per_hp` (default 250 ml). A list of materials that the monster can absorb can be specified with `absorb_materials` (can be string or string array, not specified = absorb all materials) and forbidden with `no_absorb_materials` (can be string or string array, not specified = absorb all materials specified by absorb_materials). - ```ABSORB_MEAT``` Absorbs adjacent meat items (maximum absorbable item volume depends on the monster's volume), regenerating health in the process. - ```ACID``` Spits acid. - ```ACID_ACCURATE``` Shoots acid that is accurate at long ranges, but less so up close. diff --git a/src/monattack.cpp b/src/monattack.cpp index cd659412b6fb2..b526fdfa715b7 100644 --- a/src/monattack.cpp +++ b/src/monattack.cpp @@ -422,19 +422,47 @@ bool mattack::absorb_items( monster *z ) std::vector consumed_items; std::vector absorb_material = z->get_absorb_material(); + std::vector no_absorb_material = z->get_no_absorb_material(); for( item &elem : here.i_at( z->pos() ) ) { bool any_materials_match = false; - if( absorb_material.empty() ) { + // there is no whitelist or blacklist so allow anything. + if( absorb_material.empty() && no_absorb_material.empty() ) { any_materials_match = true; - } else { + // there is a whitelist but no blacklist + } else if( !absorb_material.empty() && no_absorb_material.empty() ) { for( const material_type *mat_type : elem.made_of_types() ) { if( std::find( absorb_material.begin(), absorb_material.end(), mat_type->id ) != absorb_material.end() ) { any_materials_match = true; } } + // there is no whitelist but there is a blacklist + } else if( absorb_material.empty() && !no_absorb_material.empty() ) { + bool found = false; + for( const material_type *mat_type : elem.made_of_types() ) { + if( std::find( no_absorb_material.begin(), no_absorb_material.end(), + mat_type->id ) != no_absorb_material.end() ) { + found = true; + } + } + // if the item wasn't on the blacklist, it's allowed + if( !found ) { + any_materials_match = true; + } + // there is a whitelist and a blacklist + } else if( !absorb_material.empty() && !no_absorb_material.empty() ) { + for( const material_type *mat_type : elem.made_of_types() ) { + if( !( std::find( no_absorb_material.begin(), no_absorb_material.end(), + mat_type->id ) != no_absorb_material.end() ) ) { + // the item wasn't found on the blacklist so check the whitelist + if( std::find( absorb_material.begin(), absorb_material.end(), + mat_type->id ) != absorb_material.end() ) { + any_materials_match = true; + } + } + } } // make sure we don't absorb the wrong types of items diff --git a/src/monster.cpp b/src/monster.cpp index f3692b9e44291..eabd301e27bc6 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1342,6 +1342,11 @@ std::vector monster::get_absorb_material() const return type->absorb_material; } +std::vector monster::get_no_absorb_material() const +{ + return type->no_absorb_material; +} + void monster::set_patrol_route( const std::vector &patrol_pts_rel_ms ) { const tripoint_abs_ms base_abs_ms = project_to( global_omt_location() ); diff --git a/src/monster.h b/src/monster.h index e6e528e8212ae..e3a72f52160ed 100644 --- a/src/monster.h +++ b/src/monster.h @@ -122,6 +122,7 @@ class monster : public Creature void spawn( const tripoint &p ); void spawn( const tripoint_abs_ms &loc ); std::vector get_absorb_material() const; + std::vector get_no_absorb_material() const; creature_size get_size() const override; units::mass get_weight() const override; units::mass weight_capacity() const override; diff --git a/src/monster_oracle.cpp b/src/monster_oracle.cpp index a9cf4d7eadda5..6f0404f3fdabf 100644 --- a/src/monster_oracle.cpp +++ b/src/monster_oracle.cpp @@ -29,16 +29,53 @@ status_t monster_oracle_t::items_available( const std::string_view ) const if( !get_map().has_flag( ter_furn_flag::TFLAG_SEALED, subject->pos() ) && get_map().has_items( subject->pos() ) ) { std::vector absorb_material = subject->get_absorb_material(); + std::vector no_absorb_material = subject->get_no_absorb_material(); - if( absorb_material.empty() ) { + // base case: there are no specified conditions for what it can eat + if( absorb_material.empty() && no_absorb_material.empty() ) { return status_t::running; } - - for( item &it : get_map().i_at( subject->pos() ) ) { - for( const material_type *mat_type : it.made_of_types() ) { - if( std::find( absorb_material.begin(), absorb_material.end(), - mat_type->id ) != absorb_material.end() ) { - return status_t::running; + // case 2: no whitelist specified (it is approved for everything) but a blacklist specified + if( absorb_material.empty() && !no_absorb_material.empty() ) { + bool found = false; + for( item &it : get_map().i_at( subject->pos() ) ) { + for( const material_type *mat_type : it.made_of_types() ) { + if( !( std::find( no_absorb_material.begin(), no_absorb_material.end(), + mat_type->id ) != no_absorb_material.end() ) ) { + //we found something that wasn't on the blacklist + found = true; + } + } + } + if( found ) { + return status_t::running; + } else { + return status_t::failure; + } + } + // Case 3: there is a whitelist but no blacklist, so only allow the whitelisted ones + if( !absorb_material.empty() && no_absorb_material.empty() ) { + for( item &it : get_map().i_at( subject->pos() ) ) { + for( const material_type *mat_type : it.made_of_types() ) { + if( std::find( absorb_material.begin(), absorb_material.end(), + mat_type->id ) != absorb_material.end() ) { + return status_t::running; + } + } + } + } + // case 4: no whitelist specified (it is approved for everything) but a blacklist specified + if( !absorb_material.empty() && !no_absorb_material.empty() ) { + for( item &it : get_map().i_at( subject->pos() ) ) { + for( const material_type *mat_type : it.made_of_types() ) { + if( !( std::find( no_absorb_material.begin(), no_absorb_material.end(), + mat_type->id ) != no_absorb_material.end() ) ) { + //we found something that wasn't on the blacklist, so now check the whitelist + if( std::find( absorb_material.begin(), absorb_material.end(), + mat_type->id ) != absorb_material.end() ) { + return status_t::running; + } + } } } } diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index bc16d4eb1188e..e4bffa0df3299 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -944,6 +944,17 @@ void mtype::load( const JsonObject &jo, const std::string &src ) } } + if( jo.has_member( "no_absorb_material" ) ) { + no_absorb_material.clear(); + if( jo.has_array( "no_absorb_material" ) ) { + for( std::string mat : jo.get_string_array( "no_absorb_material" ) ) { + no_absorb_material.emplace_back( mat ); + } + } else { + no_absorb_material.emplace_back( jo.get_string( "no_absorb_material" ) ); + } + } + optional( jo, was_loaded, "bleed_rate", bleed_rate, 100 ); optional( jo, was_loaded, "petfood", petfood ); diff --git a/src/mtype.h b/src/mtype.h index 179b5e3c1967d..12fa4c43db5e8 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -341,6 +341,8 @@ struct mtype { // The type of material this monster can absorb. Leave unspecified for all materials. std::vector absorb_material; + // The type of material this monster cannot absorb. Leave unspecified for no materials (blacklist none). + std::vector no_absorb_material; damage_instance melee_damage; // Basic melee attack damage std::vector special_attacks_names; // names of attacks, in json load order std::vector chat_topics; // What it has to say.