Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Monster item aware behaviour #38303

Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions data/json/effects.json
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,12 @@
"desc": [ "AI tag used for screecher sounds. This is a bug if you have it." ],
"show_in_info": true
},
{
"type": "effect_type",
"id": "looting",
"name": [ "Monster is looting" ],
"desc": [ "AI tag used for monster looting. This is a bug if you have it." ]
},
{
"type": "effect_type",
"id": "targeted",
Expand Down
2 changes: 1 addition & 1 deletion data/json/monsters/mammal.json
Original file line number Diff line number Diff line change
Expand Up @@ -1764,7 +1764,7 @@
"vision_night": 5,
"anger_triggers": [ "FRIEND_ATTACKED", "HURT" ],
"death_function": [ "NORMAL" ],
"flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "PATH_AVOID_DANGER_1", "WARM", "KEENNOSE", "BLEED" ]
"flags": [ "SEES", "HEARS", "SMELLS", "ANIMAL", "PATH_AVOID_DANGER_1", "WARM", "KEENNOSE", "BLEED", "STEALS_FOOD"]
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
},
{
"id": "mon_rat_king",
Expand Down
2 changes: 2 additions & 0 deletions doc/JSON_FLAGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -964,6 +964,8 @@ Multiple death functions can be used. Not all combinations make sense.
- ```SLUDGEPROOF``` Ignores the effect of sludge trails.
- ```SLUDGETRAIL``` Causes the monster to leave a sludge trap trail when moving.
- ```SMELLS``` It can smell you.
- ```STEALS_FOOD``` Will steal and eat food from the ground.
- ```STEALS_SHINY``` Will steal items shiny items with materials like gold, silver, diamond and platinum.
- ```STUMBLES``` Stumbles in its movement.
- ```SUNDEATH``` Dies in full sunlight.
- ```SWARMS``` Groups together and form loose packs.
Expand Down
1 change: 1 addition & 0 deletions src/cata_string_consts.h
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ static const efftype_id effect_jetinjector( "jetinjector" );
static const efftype_id effect_lack_sleep( "lack_sleep" );
static const efftype_id effect_laserlocked( "laserlocked" );
static const efftype_id effect_lightsnare( "lightsnare" );
static const efftype_id effect_looting("looting");
static const efftype_id effect_lying_down( "lying_down" );
static const efftype_id effect_masked_scent( "masked_scent" );
static const efftype_id effect_melatonin_supplements( "melatonin" );
Expand Down
215 changes: 215 additions & 0 deletions src/monmove.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,13 @@
#include "string_formatter.h"
#include "cata_string_consts.h"

//Related to monster loot
#include "item.h"
#include "itype.h"
#include "item_location.h"

#define MONSTER_FOLLOW_DIST 8
#define MONSTER_LOOT_DIST 16

bool monster::wander()
{
Expand Down Expand Up @@ -271,6 +277,109 @@ float monster::rate_target( Creature &c, float best, bool smart ) const
return INT_MAX;
}

std::map<item*, const tripoint> monster::find_loot_in_radius(const tripoint& target, int radius) {
bool steals_food = has_flag(MF_STEALS_FOOD);
bool steals_shiny = has_flag(MF_STEALS_SHINY);
bool suitable = false;
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
std::map<item*, const tripoint> lootable;

//Check all tiles nearby within radius
for (const tripoint &p : g->m.points_in_radius(target, radius)) {
//Check if we can see some items on the tile
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
if (g->m.sees_some_items(p, *this)) {
//A candidate tile with some items!
auto items = g->m.i_at(p);
//Check all items on tile
for (item &itm : items) {
suitable = false;

//Does monster try to steal food and is it actually food?
if (steals_food && (itm.is_comestible() && itm.get_comestible()->comesttype == "FOOD")) {
if (!itm.rotten())
suitable = true;

//TODO: Sanity checks here on what we're trying to loot.
}

//Does monster steal shiny items?
if (steals_shiny &&
(itm.made_of(material_id("gold")) ||
itm.made_of(material_id("silver")) ||
itm.made_of(material_id("diamond")) ||
itm.made_of(material_id("platinum")))) {

//TODO: Sanity checks on what we're trying to loot here

suitable = true;
}


if (suitable)
lootable.insert(std::make_pair(&itm, p));
}
}
}

return lootable;
}

bool monster::eat_from_inventory() {
auto it = inv.begin();
for (auto& itm : inv) {
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
//Is food?
if ((itm.is_comestible() && itm.get_comestible()->comesttype == "FOOD")) {
if (itm.charges > 1) {
itm.mod_charges(-1);
}

if (g->u.sees(*this)) {
add_msg(m_warning, "%1$s eats some of %2$s!", name().c_str(), itm.display_name());
}

//Remove item
inv.erase(it);
mod_moves(-50);
return true;
}

it++;
}


return false;
}

item_location monster::select_desired_loot(std::map<item*, const tripoint>& loot) {
std::map<item*, const tripoint>::iterator it;
item* desired_i = nullptr;
tripoint desired_p;
int desired_range = MONSTER_LOOT_DIST + 1;

item *i;
tripoint p;
int d;

//Iterate through the loot, find closest most viable item to loot
for (it = loot.begin(); it != loot.end(); it++)
{
i = it->first;
p = it->second;
d = rl_dist_fast(pos(), p);

//Closest is preferable here...
if (d < desired_range) {
//Ensure that can also see the target...
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
if (sees(p)) {
desired_i = i;
desired_p = p;
desired_range = d;
}
}
}

return item_location(map_cursor(desired_p),desired_i);
}

void monster::plan()
{
const auto &factions = g->critter_tracker->factions();
Expand All @@ -291,9 +400,15 @@ void monster::plan()
const int fears_hostile_near = type->has_fear_trigger( mon_trigger::HOSTILE_CLOSE ) ? 5 : 0;

bool group_morale = has_flag( MF_GROUP_MORALE ) && morale < type->morale;
bool steals_food = has_flag(MF_STEALS_FOOD);
bool will_steal = has_flag(MF_STEALS_FOOD) || has_flag(MF_STEALS_SHINY);

bool swarms = has_flag( MF_SWARMS );

auto mood = attitude();



// If we can see the player, move toward them or flee, simpleminded animals are too dumb to follow the player.
if( friendly == 0 && sees( g->u ) && !has_flag( MF_PET_WONT_FOLLOW ) ) {
dist = rate_target( g->u, dist, smart_planning );
Expand Down Expand Up @@ -344,6 +459,35 @@ void monster::plan()
}
}

//Check if steals, must be idle and have no target in sight.
if ((friendly >= 0 || target != nullptr) && !fleeing && !has_effect(effect_looting)) {
//If stealing monster, inventory is empty and with a bit of luck...we search for loot.
if (will_steal && inv.empty() && one_in(100)) {
//Find all lootable items within radius
auto lootable_items = find_loot_in_radius(pos(), MONSTER_LOOT_DIST);
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved

if (!lootable_items.empty()) {
item_goal = select_desired_loot(lootable_items);


set_dest(item_goal.position());
add_effect(effect_looting, 50_turns);
}
else //TODO: Get frustrated?
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
{

}
} //Monster has something in inventory, will steal and with some luck...
else if (will_steal && !inv.empty() && one_in(500)) {
//If a food stealer, then eat something.
if (steals_food) {
if (!eat_from_inventory()) {
//TODO: Do we need to do something if fails?
}
}
}
}

if( docile ) {
if( friendly != 0 && target != nullptr ) {
set_dest( target->pos() );
Expand Down Expand Up @@ -903,6 +1047,7 @@ void monster::move()
}
}
const bool can_open_doors = has_flag( MF_CAN_OPEN_DOORS );
const bool will_steal = has_flag ( MF_STEALS_FOOD ) || has_flag ( MF_STEALS_SHINY );
// Finished logic section. By this point, we should have chosen a square to
// move to (moved = true).
const tripoint local_next_step = g->m.getlocal( next_step );
Expand All @@ -911,6 +1056,7 @@ void monster::move()
( !pacified && attack_at( local_next_step ) ) ||
( !pacified && can_open_doors && g->m.open_door( local_next_step, !g->m.is_outside( pos() ) ) ) ||
( !pacified && bash_at( local_next_step ) ) ||
( !pacified && will_steal && pickup_at( local_next_step, item_goal ))||
( !pacified && push_to( local_next_step, 0, 0 ) ) ||
move_to( local_next_step, false, get_stagger_adjust( pos(), destination, local_next_step ) );

Expand Down Expand Up @@ -1381,6 +1527,75 @@ bool monster::attack_at( const tripoint &p )
return false;
}

bool monster::pickup_at(const tripoint& p, item_location &target)
{
//If called through normal pathfinding, then clear looting effect
if (p == goal) {
remove_effect(effect_looting);

}

//Item has moved...
if (p != target.position()) {
return false;
}

inv.push_back(*target.get_item());
item *stored_item = &inv.back();
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved

//Get volumes and weight of the stack or item
units::volume vol = target->volume();
units::mass weight = target->weight();
units::mass capacity = this->weight_capacity();

int amount_taken = 1;

int charges = target->charges;

if (charges >= 1) {
//Adjust volume and weight for units in a stack
units::volume vol_each = vol / charges;
units::mass weigh_each = weight / charges;
amount_taken = charges + (weigh_each / capacity);

//Caps the amount taken to avoid taking large stacks.
//TODO: better way to cap the amount taken?
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
if (amount_taken > 2)
amount_taken = 2;

target->mod_charges(-amount_taken);
inv.back().charges = amount_taken;

//If we've taken all the stack, let's remove the item
if (amount_taken == charges)
target.remove_item();
}
else {
//Remove item from ground
target.remove_item();
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
}

//Successfully taken any?
if (amount_taken >= 1) {
//Notify the player
if (g->u.sees(*this)) {
add_msg(m_warning, "%1$s grabs %2$s!", name().c_str(), stored_item->display_name());
}


mod_moves(-100);
return true;
}
else {
//Failed to take any items, so remove the last item added to inventory.
inv.pop_back();
}
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved


add_msg(m_warning, "%1$s fails to grab %2$s!", name().c_str(), target->display_name());
return false;
}

static tripoint find_closest_stair( const tripoint &near_this, const ter_bitflags stair_type )
{
for( const tripoint &candidate : closest_tripoints_first( near_this, 10 ) ) {
Expand Down
32 changes: 28 additions & 4 deletions src/monster.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ static const std::map<monster_attitude, std::pair<std::string, color_id>> attitu
{monster_attitude::MATT_IGNORE, {translate_marker( "Ignoring." ), def_c_light_gray}},
{monster_attitude::MATT_ZLAVE, {translate_marker( "Zombie slave." ), def_c_green}},
{monster_attitude::MATT_ATTACK, {translate_marker( "Hostile!" ), def_c_red}},
{monster_attitude::MATT_LOOT,{translate_marker("Looting!"),def_c_blue}},
{monster_attitude::MATT_NULL, {translate_marker( "BUG: Behavior unnamed." ), def_h_red}},
};

Expand Down Expand Up @@ -539,6 +540,10 @@ int monster::print_info( const catacurses::window &w, int vStart, int vLines, in
mvwprintz( w, point( column, ++vStart ), c_yellow, _( "Aware of your presence!" ) );
}

if (!inv.empty()) {
mvwprintz(w, point(column, ++vStart), c_white, _("It is carrying something!"));
}

std::string effects = get_effect_status();
if( !effects.empty() ) {
trim_and_print( w, point( column, ++vStart ), getmaxx( w ) - 2, h_white, effects );
Expand Down Expand Up @@ -655,7 +660,9 @@ std::string monster::extended_description() const
{swims(), pgettext( "Swim as an action", "swim" )},
{flies(), pgettext( "Fly as an action", "fly" )},
{can_dig(), pgettext( "Dig as an action", "dig" )},
{climbs(), pgettext( "Climb as an action", "climb" )}
{climbs(), pgettext( "Climb as an action", "climb" )},
{m_flag::MF_STEALS_FOOD, pgettext("Steal food as an action", "steal")},
{m_flag::MF_STEALS_SHINY, pgettext("Steal shiny items as an action", "steal")}
} );

describe_flags( _( "<bad>In fight it can %s.</bad>" ), {
Expand Down Expand Up @@ -901,6 +908,7 @@ Creature::Attitude monster::attitude_to( const Creature &other ) const
case MATT_FPASSIVE:
case MATT_FLEE:
case MATT_IGNORE:
case MATT_LOOT:
case MATT_FOLLOW:
return A_NEUTRAL;
case MATT_ATTACK:
Expand Down Expand Up @@ -1024,7 +1032,10 @@ monster_attitude monster::attitude( const Character *u ) const
if( get_hp() != get_hp_max() ) {
return MATT_FLEE;
} else {
return MATT_IGNORE;
if (has_effect(effect_looting))
return MATT_LOOT;
else
return MATT_IGNORE;
}
}

Expand Down Expand Up @@ -2213,11 +2224,15 @@ void monster::drop_items_on_death()
if( is_hallucination() ) {
return;
}
if( type->death_drops.empty() ) {

if (type->death_drops.empty() && inv.empty()) {
return;
}

std::vector<item> items = item_group::items_from( type->death_drops, calendar::start_of_cataclysm );
std::vector<item> items;

if (!type->death_drops.empty())
items = item_group::items_from(type->death_drops, calendar::start_of_cataclysm);

// This block removes some items, according to item spawn scaling factor
const float spawn_rate = get_option<float>( "ITEM_SPAWNRATE" );
Expand All @@ -2236,6 +2251,15 @@ void monster::drop_items_on_death()
items = remaining;
}

// Adds all items in the monsters inventory to be dropped.
Diabolus-Engi marked this conversation as resolved.
Show resolved Hide resolved
// TODO: Apply random damage to items ? Modify them in some way ?
if (!inv.empty())
for (const item &itm : inv) {
items.push_back(itm);
}

inv.erase(inv.begin(), inv.end());

const auto dropped = g->m.spawn_items( pos(), items );

if( has_flag( MF_FILTHY ) && get_option<bool>( "FILTHY_CLOTHES" ) ) {
Expand Down
Loading