diff --git a/doc/MONSTERS.md b/doc/MONSTERS.md index ed4c2fe604b01..2867fe7888c3e 100644 --- a/doc/MONSTERS.md +++ b/doc/MONSTERS.md @@ -94,6 +94,8 @@ Property | Description `path_settings` | (object) How monster may find a path, open doors, avoid traps, or bash obstacles `biosignature` | (object) Droppings or feces left by the animal or monster `harvest` | (string) ID of a "harvest" type describing what can be harvested from the corpse +`dissect` | (string) (Optional) ID of a "harvest" type describing what is returned when a corpse of this monster is dissected +`decay` | (string) (Optional) ID of a "harvest" type describing what is left when a corpse of this monster rots away `zombify_into` | (string) mtype_id this monster zombifies into after it's death `fungalize_into` | (string) mtype_id this monster turns into when fungalized by spores `shearing` | (array of objects) Items produced when the monster is sheared diff --git a/src/item.cpp b/src/item.cpp index be7de797c7ecc..023a1c459ab46 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -14234,6 +14234,9 @@ bool item::process_internal( map &here, Character *carrier, const tripoint &pos, if( is_comestible() ) { here.rotten_item_spawn( *this, pos ); } + if( is_corpse() ) { + here.handle_decayed_corpse( *this, here.getglobal( pos ) ); + } return true; } } else { @@ -14620,6 +14623,11 @@ std::string item::type_name( unsigned int quantity, bool use_variant, bool use_c return ret_name; } +const mtype *item::get_corpse_mon() const +{ + return corpse; +} + std::string item::get_corpse_name() const { return corpse_name; diff --git a/src/item.h b/src/item.h index 6b089ab0b847e..b62183adf45ad 100644 --- a/src/item.h +++ b/src/item.h @@ -3081,6 +3081,7 @@ class item : public visitable item_components components; /** What faults (if any) currently apply to this item */ cata::heap> faults; + const mtype *get_corpse_mon() const; private: item_contents contents; diff --git a/src/map.cpp b/src/map.cpp index fef25c1fdd78d..ef57a7053e116 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -8414,6 +8414,56 @@ void map::loadn( const point &grid, bool update_vehicles ) } } +void map::handle_decayed_corpse( const item &it, const tripoint_abs_ms &pnt ) +{ + if( !it.is_corpse() ) { + debugmsg( "Tried to decay a non-corpse item %s. Aborted", it.tname() ); + return; + } + const mtype *dead_monster = it.get_corpse_mon(); + if( !dead_monster ) { + debugmsg( "Corpse at abs_ms %s has no associated monster?!", pnt.to_string() ); + return; + } + + if( dead_monster->decay.is_null() ) { + return; + } + int decayed_weight_grams = to_gram( dead_monster->weight ); // corpse might have stuff in it! + decayed_weight_grams += std::round( decayed_weight_grams * rng_float( -0.1, 0.1 ) ); + bool notify_player = false; + if( calendar::once_every( 30_minutes ) ) { + //one corpse max in 30 minutes will notify if seen, for *all* the items it left + notify_player = true; + } + + bool anything_left = false; + for( const harvest_entry &entry : dead_monster->decay.obj() ) { + item harvest = item( entry.drop ); + const float random_decay_modifier = rng_float( 0.0f, static_cast( MAX_SKILL ) ); + const float min_num = entry.scale_num.first * random_decay_modifier + entry.base_num.first; + const float max_num = entry.scale_num.second * random_decay_modifier + entry.base_num.second; + int roll = 0; + if( entry.mass_ratio != 0.00f ) { + roll = static_cast( std::round( entry.mass_ratio * decayed_weight_grams ) ); + roll = std::ceil( static_cast( roll ) / to_gram( harvest.type->weight ) ); + } else { + roll = std::min( entry.max, std::round( rng_float( min_num, max_num ) ) ); + } + anything_left = roll > 0; + for( int i = 0; i < roll; i++ ) { + if( harvest.has_temperature() ) { + harvest.set_item_temperature( get_weather().get_temperature( project_to( pnt ) ) ); + } + add_item_or_charges( bub_from_abs( pnt ), harvest, false ); + if( anything_left && notify_player ) { + add_msg_if_player_sees( getlocal( pnt ), _( "You notice a %1$s has rotted away, leaving a %2$s." ), + it.tname(), harvest.tname() ); + } + } + } +} + void map::rotten_item_spawn( const item &item, const tripoint &pnt ) { if( get_creature_tracker().creature_at( pnt ) != nullptr ) { diff --git a/src/map.h b/src/map.h index c03f4e5bf2825..281bd7bd297e8 100644 --- a/src/map.h +++ b/src/map.h @@ -2033,6 +2033,13 @@ class map */ void spawn_monsters( bool ignore_sight, bool spawn_nonlocal = false ); + /** + * Checks to see if the corpse that is rotting away generates items when it does. + * @param it item that is spawning creatures + * @param pnt The point on this map where the item is and where bones/etc will be + */ + void handle_decayed_corpse( const item &it, const tripoint_abs_ms &pnt ); + /** * Checks to see if the item that is rotting away generates a creature when it does. * @param item item that is spawning creatures diff --git a/src/monstergenerator.cpp b/src/monstergenerator.cpp index 8bd0e3a7ca0be..5404cb62f3f9e 100644 --- a/src/monstergenerator.cpp +++ b/src/monstergenerator.cpp @@ -1080,6 +1080,8 @@ void mtype::load( const JsonObject &jo, const std::string &src ) optional( jo, was_loaded, "dissect", dissect ); + optional( jo, was_loaded, "decay", decay ); + if( jo.has_array( "shearing" ) ) { std::vector entries; for( JsonObject shearing_entry : jo.get_array( "shearing" ) ) { diff --git a/src/mtype.cpp b/src/mtype.cpp index e366b54677d70..1172906373162 100644 --- a/src/mtype.cpp +++ b/src/mtype.cpp @@ -19,6 +19,7 @@ #include "weakpoint.h" static const harvest_id harvest_list_human( "human" ); +static const harvest_id harvest_list_null( "null" ); static const itype_id itype_bone( "bone" ); static const itype_id itype_bone_tainted( "bone_tainted" ); @@ -319,6 +320,7 @@ mtype::mtype() sp_defense = nullptr; melee_training_cap = MAX_SKILL; harvest = harvest_list_human; + decay = harvest_list_null; luminance = 0; bash_skill = 0; diff --git a/src/mtype.h b/src/mtype.h index 4d8a1b8de31df..9f4f01bd4b501 100644 --- a/src/mtype.h +++ b/src/mtype.h @@ -302,6 +302,7 @@ struct mtype { mfaction_str_id default_faction; harvest_id harvest; harvest_id dissect; + harvest_id decay; speed_description_id speed_desc; // Monster upgrade variables mtype_id upgrade_into;