Skip to content

Commit

Permalink
Shoggoths can't eat anomalous material (#72957)
Browse files Browse the repository at this point in the history
* no_absorb_material

* no_absorb_material

* get_no_absorb_material

* update items_available

* get_no_absorb_material

* get_

* adjust absorb logic for blacklist

* doc no_absorb_material

* document no_absorb_materials

* Apply suggestions from code review

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* typofix

* shoggoths can't eat artifacts

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
anoobindisguise and github-actions[bot] authored Apr 12, 2024
1 parent 0bcda8a commit 36578d7
Show file tree
Hide file tree
Showing 9 changed files with 97 additions and 11 deletions.
3 changes: 2 additions & 1 deletion data/json/monsters/nether.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions doc/MONSTERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion doc/MONSTER_SPECIAL_ATTACKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
32 changes: 30 additions & 2 deletions src/monattack.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,19 +422,47 @@ bool mattack::absorb_items( monster *z )

std::vector<item *> consumed_items;
std::vector<material_id> absorb_material = z->get_absorb_material();
std::vector<material_id> 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
Expand Down
5 changes: 5 additions & 0 deletions src/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1342,6 +1342,11 @@ std::vector<material_id> monster::get_absorb_material() const
return type->absorb_material;
}

std::vector<material_id> monster::get_no_absorb_material() const
{
return type->no_absorb_material;
}

void monster::set_patrol_route( const std::vector<point> &patrol_pts_rel_ms )
{
const tripoint_abs_ms base_abs_ms = project_to<coords::ms>( global_omt_location() );
Expand Down
1 change: 1 addition & 0 deletions src/monster.h
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ class monster : public Creature
void spawn( const tripoint &p );
void spawn( const tripoint_abs_ms &loc );
std::vector<material_id> get_absorb_material() const;
std::vector<material_id> get_no_absorb_material() const;
creature_size get_size() const override;
units::mass get_weight() const override;
units::mass weight_capacity() const override;
Expand Down
51 changes: 44 additions & 7 deletions src/monster_oracle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<material_id> absorb_material = subject->get_absorb_material();
std::vector<material_id> 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;
}
}
}
}
}
Expand Down
11 changes: 11 additions & 0 deletions src/monstergenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
2 changes: 2 additions & 0 deletions src/mtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,8 @@ struct mtype {

// The type of material this monster can absorb. Leave unspecified for all materials.
std::vector<material_id> absorb_material;
// The type of material this monster cannot absorb. Leave unspecified for no materials (blacklist none).
std::vector<material_id> no_absorb_material;
damage_instance melee_damage; // Basic melee attack damage
std::vector<std::string> special_attacks_names; // names of attacks, in json load order
std::vector<std::string> chat_topics; // What it has to say.
Expand Down

0 comments on commit 36578d7

Please sign in to comment.