Skip to content

Commit

Permalink
Merge pull request CleverRaven#79804 from GuardianDll/pulp_test
Browse files Browse the repository at this point in the history
Pulping tweaks (+CI)
  • Loading branch information
GuardianDll authored Mar 1, 2025
1 parent 1271c8b commit 0d12391
Show file tree
Hide file tree
Showing 9 changed files with 1,126 additions and 62 deletions.
930 changes: 930 additions & 0 deletions data/mods/TEST_DATA/pulping_test.json

Large diffs are not rendered by default.

20 changes: 10 additions & 10 deletions src/activity_actor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8525,11 +8525,13 @@ bool pulp_activity_actor::punch_corpse_once( item &corpse, Character &you,

pd = g->calculate_pulpability( you, *corpse_mtype, pd );

if( !g->can_pulp_corpse( pd ) ) {
if( !g->can_pulp_corpse( you, *corpse_mtype, pd ) ) {
way_too_long_to_pulp = true;
return false;
}

add_msg_debug( debugmode::DF_ACTIVITY, "time to pulp: %s", pd.time_to_pulp );

// 10 minutes
if( pd.time_to_pulp > 600 && !too_long_to_pulp ) {
if( you.query_yn( "Pulping one or more corpses in this pile would take too much time. Continue?" ) ) {
Expand All @@ -8551,12 +8553,12 @@ bool pulp_activity_actor::punch_corpse_once( item &corpse, Character &you,
corpse.set_flag( flag_PULPED );
}

// 19 splatters on average, no matter of the corpse size
if( one_in( pd.time_to_pulp / 19 ) ) {
const float corpse_volume = units::to_liter( corpse_mtype->volume );

if( one_in( corpse_volume / pd.pulp_power ) ) {
// Splatter some blood around
// Splatter a bit more randomly, so that it looks cooler
const int radius = pd.acid_corpse ? 0
: pd.mess_radius + x_in_y( pd.pulp_power, 500 ) + x_in_y( pd.pulp_power, 1000 );
const int radius = pd.mess_radius + x_in_y( pd.pulp_power, 500 ) + x_in_y( pd.pulp_power, 1000 );
const tripoint_bub_ms dest( pos + point( rng( -radius, radius ), rng( -radius, radius ) ) );
const field_type_id type_blood = ( pd.mess_radius > 1 && x_in_y( pd.pulp_power, 10000 ) ) ?
corpse.get_mtype()->gibType() :
Expand Down Expand Up @@ -8629,7 +8631,7 @@ void pulp_activity_actor::send_final_message( Character &you ) const
} else {
tools = string_format( " mixing your %s with powerful stomps", pd.bash_tool );
}
if( pd.can_severe_cutting ) {
if( pd.can_cut_precisely ) {
tools += string_format( " and a trusty %s", pd.cut_tool );
}

Expand Down Expand Up @@ -8693,10 +8695,9 @@ void pulp_activity_actor::serialize( JsonOut &jsout ) const
jsout.member( "too_long_to_pulp", too_long_to_pulp );
jsout.member( "too_long_to_pulp_interrupted", too_long_to_pulp_interrupted );
jsout.member( "way_too_long_to_pulp", way_too_long_to_pulp );
jsout.member( "cut_quality", pd.cut_quality );
jsout.member( "stomps_only", pd.stomps_only );
jsout.member( "weapon_only", pd.weapon_only );
jsout.member( "can_severe_cutting", pd.can_severe_cutting );
jsout.member( "can_cut_precisely", pd.can_cut_precisely );
jsout.member( "used_pry", pd.used_pry );
jsout.member( "couldnt_use_pry", pd.couldnt_use_pry );
jsout.member( "bash_tool", pd.bash_tool );
Expand All @@ -8722,10 +8723,9 @@ std::unique_ptr<activity_actor> pulp_activity_actor::deserialize( JsonValue &jsi
data.read( "too_long_to_pulp", actor.too_long_to_pulp );
data.read( "too_long_to_pulp_interrupted", actor.too_long_to_pulp_interrupted );
data.read( "way_too_long_to_pulp", actor.way_too_long_to_pulp );
data.read( "cut_quality", actor.pd.cut_quality );
data.read( "stomps_only", actor.pd.stomps_only );
data.read( "weapon_only", actor.pd.weapon_only );
data.read( "can_severe_cutting", actor.pd.can_severe_cutting );
data.read( "can_cut_precisely", actor.pd.can_cut_precisely );
data.read( "used_pry", actor.pd.used_pry );
data.read( "couldnt_use_pry", actor.pd.couldnt_use_pry );
data.read( "bash_tool", actor.pd.bash_tool );
Expand Down
34 changes: 17 additions & 17 deletions src/character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9723,23 +9723,6 @@ const
return best_weapon;
}

std::pair<int, const item *> Character::get_best_tool( const quality_id quality ) const
{
// todo: check edge case of you having bionic tool/mutation,
// something that max_quality() is aware but max_quality() is not?

const int max_qual = max_quality( quality );

std::vector<const item *> nit = cache_get_items_with( "best_quality_" + quality.str(), {},
[quality, max_qual]( const item & i ) {
return i.get_quality( quality ) == max_qual;
} );

const item *it = random_entry( nit );
const std::pair<int, const item *> best_tool = std::make_pair( max_qual, it );
return best_tool;
}

units::energy Character::available_ups() const
{
units::energy available_charges = 0_kJ;
Expand Down Expand Up @@ -10322,6 +10305,23 @@ item &Character::best_item_with_quality( const quality_id &qid )
return null_item_reference();
}

const item &Character::best_item_with_quality( const quality_id &qid ) const
{
int max_lvl_found = INT_MIN;
std::vector<const item *> items = items_with( [qid, &max_lvl_found]( const item & it ) {
int qlvl = it.get_quality_nonrecursive( qid );
if( qlvl > max_lvl_found ) {
max_lvl_found = qlvl;
return true;
}
return false;
} );
if( max_lvl_found > INT_MIN ) {
return *items.back();
}
return null_item_reference();
}

int Character::run_cost( int base_cost, bool diag ) const
{
float movecost = static_cast<float>( base_cost );
Expand Down
3 changes: 1 addition & 2 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -2980,8 +2980,6 @@ class Character : public Creature, public visitable

std::pair<float, item> get_best_weapon_by_damage_type( damage_type_id dmg_type ) const;

std::pair<int, const item *> get_best_tool( quality_id quality ) const;

/**
* Available ups from all sources
* Sum of mech, bionic UPS and UPS
Expand Down Expand Up @@ -3868,6 +3866,7 @@ class Character : public Creature, public visitable
* @param qual_id The quality to search
*/
item &best_item_with_quality( const quality_id &qid );
const item &best_item_with_quality( const quality_id &qid ) const;

// inherited from visitable
bool has_quality( const quality_id &qual, int level = 1, int qty = 1 ) const override;
Expand Down
97 changes: 69 additions & 28 deletions src/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ static const climbing_aid_id climbing_aid_furn_CLIMBABLE( "furn_CLIMBABLE" );
static const damage_type_id damage_acid( "acid" );
static const damage_type_id damage_bash( "bash" );
static const damage_type_id damage_cut( "cut" );
static const damage_type_id damage_stab( "stab" );

static const efftype_id effect_adrenaline_mycus( "adrenaline_mycus" );
static const efftype_id effect_asked_to_train( "asked_to_train" );
Expand Down Expand Up @@ -14259,6 +14260,7 @@ pulp_data game::calculate_character_ability_to_pulp( const Character &you )

double pulp_power_bash = 1;

// the idea was that roll_damage() gonna handle melee skills, but it doesn't care for skill, apparently?
const std::pair<float, item> pair_bash = you.get_best_weapon_by_damage_type( damage_bash );
pulp_power_bash = pair_bash.first;
pd.bash_tool = pair_bash.second.display_name();
Expand All @@ -14267,6 +14269,23 @@ pulp_data game::calculate_character_ability_to_pulp( const Character &you )
pd.mess_radius = 2;
}

// bash is good, but the rest physical damages can be useful also
damage_instance di;
u.roll_damage( damage_cut, false, di, true, pair_bash.second, attack_vector_id::NULL_ID(),
sub_bodypart_str_id::NULL_ID(), 1.f );

u.roll_damage( damage_stab, false, di, true, pair_bash.second, attack_vector_id::NULL_ID(),
sub_bodypart_str_id::NULL_ID(), 1.f );

for( const damage_unit &du : di ) {
// potentially move it to json, if someone find necrotic mace +3 should pulp faster for some reason
if( du.type == damage_cut ) {
pulp_power_bash += du.amount / 3;
} else if( du.type == damage_stab ) {
pulp_power_bash += du.amount / 6;
}
}

const double weight_factor = units::to_kilogram( you.get_weight() ) / 10;
const double athletic_factor = std::min( 9.0f, you.get_skill_level( skill_swimming ) + 3 );
const int strength_factor = you.get_str() / 2;
Expand All @@ -14289,32 +14308,33 @@ pulp_data game::calculate_character_ability_to_pulp( const Character &you )
}

add_msg_debug( debugmode::DF_ACTIVITY,
"you: %s, bash weapon: %s, bash damage: %s, pulp_power_bash: %s, pulp_power_stomps: %s",
"you: %s, bash weapon: %s, final pulp_power_bash: %s, pulp_power_stomps: %s",
you.name.c_str(), pair_bash.second.display_name().c_str(), pair_bash.first, pulp_power_bash,
pulp_power_stomps, bash_factor );

bash_factor = std::pow( bash_factor, 1.8f );
std::pair<int, const item *> pair_cut = you.get_best_tool( qual_BUTCHER );
pd.cut_quality = pair_cut.first;
if( pd.cut_quality > 5 ) {
pd.can_severe_cutting = true;
pd.cut_tool = item::nname( pair_cut.second->typeId() );

const item &best_cut = you.best_item_with_quality( qual_BUTCHER );
if( best_cut.get_quality( qual_BUTCHER ) > 5 ) {
pd.can_cut_precisely = true;
pd.cut_tool = item::nname( best_cut.typeId() );
}

std::pair<int, const item *> pair_pry;
if( you.max_quality( qual_PRY ) > 0 ) {
const item &best_pry = you.best_item_with_quality( qual_PRY );
if( best_pry.get_quality( qual_PRY ) > 0 ) {
pd.can_pry_armor = true;
pair_pry = you.get_best_tool( qual_PRY );
pd.pry_tool = item::nname( pair_pry.second->typeId() );
pd.pry_tool = item::nname( best_pry.typeId() );
}

add_msg_debug( debugmode::DF_ACTIVITY,
"final bash factor: %s, butcher tool name: %s, butcher tool quality: %s, prying tool name (if used): %s",
bash_factor, pd.cut_tool, pd.cut_quality, pd.pry_tool );
"final bash factor: %s, butcher tool name: %s, prying tool name (if used): %s",
bash_factor, pd.cut_tool, pd.pry_tool );

const float skill_factor = std::sqrt( 2 + std::max(
you.get_skill_level( skill_survival ),
you.get_skill_level( skill_firstaid ) ) );

pd.pulp_power = bash_factor
* std::sqrt( you.get_skill_level( skill_survival ) + 2 )
* ( pd.can_severe_cutting ? 1 : 0.85 );
pd.pulp_power = bash_factor * skill_factor * ( pd.can_cut_precisely ? 1 : 0.85 );

add_msg_debug( debugmode::DF_ACTIVITY, "final pulp_power: %s", pd.pulp_power );

Expand All @@ -14337,6 +14357,8 @@ pulp_data game::calculate_pulpability( const Character &you, const mtype &corpse
pulp_data game::calculate_pulpability( const Character &you, const mtype &corpse_mtype,
pulp_data pd )
{
// potentially make a new var and stash all this calculations in corpse

double pow_factor;
if( corpse_mtype.size == creature_size::huge ) {
pow_factor = 1.2;
Expand All @@ -14346,9 +14368,13 @@ pulp_data game::calculate_pulpability( const Character &you, const mtype &corpse
pow_factor = 1;
}

float corpse_volume = units::to_liter( corpse_mtype.volume );
// in seconds
int time_to_pulp =
( std::pow( units::to_liter( corpse_mtype.volume ), pow_factor ) * 1000 ) / pd.pulp_power;
int time_to_pulp = ( std::pow( corpse_volume, pow_factor ) * 1000 ) / pd.pulp_power;

// in seconds also
// 30 seconds for human body volume of 62.5L, scale from this
int min_time_to_pulp = 30 * corpse_volume / 62.5;

// +25% to pulp time if char knows no weakpoints of monster
// -25% if knows all of them
Expand All @@ -14364,16 +14390,6 @@ pulp_data game::calculate_pulpability( const Character &you, const mtype &corpse
time_to_pulp *= 1.25 - 0.5 * wp_known / corpse_mtype.families.families.size();
}

const bool acid_immune = you.is_immune_damage( damage_acid ) ||
you.is_immune_field( fd_acid );
// this corpse is acid, and you are not immune to it
pd.acid_corpse = corpse_mtype.bloodType().obj().has_acid && !acid_immune;

// if acid, you prefer to mainly cut corpse instead of bashing it, to not spray acid in your eyes
if( pd.acid_corpse ) {
time_to_pulp *= ( 300 - pd.cut_quality * 2 ) / 100;
}

// you have a hard time pulling armor to reach important parts of this monster
if( corpse_mtype.has_flag( mon_flag_PULP_PRYING ) ) {
if( pd.can_pry_armor ) {
Expand All @@ -14383,13 +14399,23 @@ pulp_data game::calculate_pulpability( const Character &you, const mtype &corpse
}
}

pd.time_to_pulp = time_to_pulp;
pd.time_to_pulp = std::max( min_time_to_pulp, time_to_pulp );

return pd;
}

bool game::can_pulp_corpse( const Character &you, const mtype &corpse_mtype )
{

const bool acid_immune = you.is_immune_damage( damage_acid ) ||
you.is_immune_field( fd_acid );
// this corpse is acid, and you are not immune to it
const bool acid_corpse = corpse_mtype.bloodType().obj().has_acid && !acid_immune;

if( acid_corpse ) {
return false;
}

pulp_data pd = calculate_pulpability( you, corpse_mtype );

return can_pulp_corpse( pd );
Expand All @@ -14401,6 +14427,21 @@ bool game::can_pulp_corpse( const pulp_data &pd )
return pd.time_to_pulp < 3600;
}

bool game::can_pulp_corpse( Character &you, const mtype &corpse_mtype, const pulp_data &pd )
{
const bool acid_immune = you.is_immune_damage( damage_acid ) ||
you.is_immune_field( fd_acid );
// this corpse is acid, and you are not immune to it
const bool acid_corpse = corpse_mtype.bloodType().obj().has_acid && !acid_immune;

if( acid_corpse ) {
return false;
}

// if pulping is longer than an hour, this is a hard no
return pd.time_to_pulp < 3600;
}

namespace cata_event_dispatch
{
void avatar_moves( const tripoint_abs_ms &old_abs_pos, const avatar &u, const map &m )
Expand Down
10 changes: 5 additions & 5 deletions src/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,20 +133,19 @@ struct w_map {
struct pulp_data {
// how far the splatter goes
int mess_radius = 1;
int cut_quality;
// how much damage you deal to corpse every second, average of multiple values
float pulp_power;
// how much stamina is consumed after each punch
float pulp_effort;
// time to pulp the corpse
int time_to_pulp;
// potential prof we can learn by pulping
std::optional<proficiency_id> unknown_prof;
// for monsters with PULP_PRYING flag
// if monster has PULP_PRYING flag, can you pry armor faster using tool
bool can_pry_armor = false;
// if acid corpse, we start to cut really slow
bool acid_corpse = false;
// do we have a good tool to cut specific parts faster
bool can_cut_precisely = false;
// all used in ending messages
bool can_severe_cutting = false;
bool stomps_only = false;
bool weapon_only = false;
bool used_pry = false;
Expand Down Expand Up @@ -1339,6 +1338,7 @@ class game
pulp_data calculate_pulpability( const Character &you, const mtype &corpse_mtype, pulp_data pd );
bool can_pulp_corpse( const Character &you, const mtype &corpse_mtype );
bool can_pulp_corpse( const pulp_data &pd );
bool can_pulp_corpse( Character &you, const mtype &corpse_mtype, const pulp_data &pd );
};

// Returns temperature modifier from direct heat radiation of nearby sources
Expand Down
27 changes: 27 additions & 0 deletions src/test_data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// Define the static class varaibles
std::set<itype_id> test_data::legacy_to_hit;
std::set<itype_id> test_data::known_bad;
std::vector<pulp_test_data> test_data::pulp_test;
std::vector<std::regex> test_data::overmap_terrain_coverage_whitelist;
std::map<vproto_id, std::vector<double>> test_data::drag_data;
std::map<vproto_id, efficiency_data> test_data::eff_data;
Expand Down Expand Up @@ -126,6 +127,32 @@ void test_data::load( const JsonObject &jo )
known_bad.insert( new_known_bad.begin(), new_known_bad.end() );
}

if( jo.has_array( "pulp_testing_data" ) ) {
for( JsonObject job : jo.get_array( "pulp_testing_data" ) ) {

std::string name;
int pulp_time = 0;
std::vector<itype_id> items;
std::map<skill_id, int> skills;
bool profs;
mtype_id corpse;

job.read( "name", name );
job.read( "pulp_time", pulp_time );
job.read( "items", items );
profs = job.get_bool( "proficiencies", false );
job.read( "corpse", corpse );

if( job.has_array( "skills" ) ) {
for( JsonObject job_skills : job.get_array( "skills" ) ) {
skills.emplace( job_skills.get_string( "skill" ), job_skills.get_int( "level" ) );
}
}

pulp_test.push_back( { name, pulp_time, items, skills, profs, corpse } );
}
}

if( jo.has_array( "overmap_terrain_coverage_whitelist" ) ) {
std::vector<std::string> new_overmap_terrain_coverage_whitelist;
jo.read( "overmap_terrain_coverage_whitelist", new_overmap_terrain_coverage_whitelist );
Expand Down
Loading

0 comments on commit 0d12391

Please sign in to comment.