diff --git a/data/json/hobbies.json b/data/json/hobbies.json index a341cd90bb4b8..013e2e70c8f42 100644 --- a/data/json/hobbies.json +++ b/data/json/hobbies.json @@ -836,7 +836,7 @@ "points": -3, "traits": [ "ANTIFRUIT", "MEATARIAN" ], "skills": [ { "level": 2, "name": "survival" }, { "level": 2, "name": "cooking" } ], - "proficiencies": [ "prof_knives_familiar", "prof_knife_skills" ] + "proficiencies": [ "prof_knives_familiar", "prof_knife_skills", "prof_butchering_basic", "prof_skinning_basic" ] }, { "type": "profession", diff --git a/data/json/professions.json b/data/json/professions.json index 78ebc1a35665c..adff68052bdc9 100644 --- a/data/json/professions.json +++ b/data/json/professions.json @@ -905,7 +905,9 @@ "prof_baking", "prof_baking_desserts_1", "prof_frying", - "prof_knives_familiar" + "prof_knives_familiar", + "prof_butchering_basic", + "prof_skinning_basic" ], "items": { "both": { @@ -931,7 +933,17 @@ "description": "You spent most of your adult life in a butcher shop. Your trusty knife has seen many different creatures and you know how to butcher them.", "points": 2, "skills": [ { "name": "cutting", "level": 2 }, { "name": "cooking", "level": 2 }, { "name": "survival", "level": 2 } ], - "proficiencies": [ "prof_food_prep", "prof_knife_skills", "prof_intro_biology", "prof_wp_basic_bird", "prof_knives_familiar" ], + "proficiencies": [ + "prof_food_prep", + "prof_knife_skills", + "prof_intro_biology", + "prof_wp_basic_bird", + "prof_knives_familiar", + "prof_butchering_basic", + "prof_butchering_adv", + "prof_skinning_basic", + "prof_skinning_adv" + ], "items": { "both": { "entries": [ @@ -5212,7 +5224,9 @@ "prof_bow_expert", "prof_fletching", "prof_carving", - "prof_knives_familiar" + "prof_knives_familiar", + "prof_butchering_basic", + "prof_skinning_basic" ], "items": { "both": { diff --git a/data/json/proficiencies/butchering.json b/data/json/proficiencies/butchering.json new file mode 100644 index 0000000000000..57b6ff4fba2a1 --- /dev/null +++ b/data/json/proficiencies/butchering.json @@ -0,0 +1,40 @@ +[ + { + "type": "proficiency", + "id": "prof_butchering_basic", + "category": "prof_butchering", + "name": { "str": "Principles of Butchering" }, + "description": "You know how to pick up most of the meat from the animal body.", + "can_learn": true, + "time_to_learn": "2 h" + }, + { + "type": "proficiency", + "id": "prof_butchering_adv", + "category": "prof_butchering", + "name": { "str": "Butchering Expert" }, + "description": "There is very little meat left after your skillful knife movements.", + "can_learn": true, + "time_to_learn": "25 h", + "required_proficiencies": [ "prof_butchering_basic" ] + }, + { + "type": "proficiency", + "id": "prof_skinning_basic", + "category": "prof_butchering", + "name": { "str": "Principles of Skinning" }, + "description": "You stopped making holes in the skin of animals you butcher.", + "can_learn": true, + "time_to_learn": "2 h" + }, + { + "type": "proficiency", + "id": "prof_skinning_adv", + "category": "prof_butchering", + "name": { "str": "Skinning Expert" }, + "description": "You really know how to skin the animal.", + "can_learn": true, + "time_to_learn": "25 h", + "required_proficiencies": [ "prof_skinning_basic" ] + } +] diff --git a/data/json/proficiencies/proficiency_categories.json b/data/json/proficiencies/proficiency_categories.json index 5d95e0d26a2ce..3eabe88e57fc2 100644 --- a/data/json/proficiencies/proficiency_categories.json +++ b/data/json/proficiencies/proficiency_categories.json @@ -23,6 +23,12 @@ "name": "Food Handling", "description": "Proficiencies for cooking and food preparation, as well as experience with food handling tools." }, + { + "type": "proficiency_category", + "id": "prof_butchering", + "name": "Butchering", + "description": "Proficiencies for carving and proper dressing of meat and skin of animals." + }, { "type": "proficiency_category", "id": "prof_electronic", diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp index 989825905925d..6a4927b8b945d 100644 --- a/src/activity_handlers.cpp +++ b/src/activity_handlers.cpp @@ -231,7 +231,11 @@ static const morale_type morale_feeling_good( "morale_feeling_good" ); static const morale_type morale_game( "morale_game" ); static const morale_type morale_tree_communion( "morale_tree_communion" ); +static const proficiency_id proficiency_prof_butchering_adv( "prof_butchering_adv" ); +static const proficiency_id proficiency_prof_butchering_basic( "prof_butchering_basic" ); static const proficiency_id proficiency_prof_dissect_humans( "prof_dissect_humans" ); +static const proficiency_id proficiency_prof_skinning_adv( "prof_skinning_adv" ); +static const proficiency_id proficiency_prof_skinning_basic( "prof_skinning_basic" ); static const quality_id qual_BUTCHER( "BUTCHER" ); static const quality_id qual_CUT_FINE( "CUT_FINE" ); @@ -811,6 +815,36 @@ int butcher_time_to_cut( Character &you, const item &corpse_item, const butcher_ if( corpse_item.has_flag( flag_QUARTERED ) ) { time_to_cut /= 4; } + + double butch_basic = you.get_proficiency_practice( proficiency_prof_butchering_basic ); + double butch_adv = you.get_proficiency_practice( proficiency_prof_butchering_adv ); + double skin_basic = you.get_proficiency_practice( proficiency_prof_skinning_basic ); + double penalty_small = 0.5; + double penalty_big = 1.5; + + int prof_butch_penalty = penalty_big * ( 1 - butch_basic ) + penalty_small * ( 1 - butch_adv ); + int prof_skin_penalty = penalty_small * ( 1 - skin_basic ); + + // there supposed to be a code for book mitigation, but we don't have any book fitting for this + + if( action == butcher_type::FULL ) { + // 40% of butchering and gutting, 40% of skinning, 20% another activities + time_to_cut *= 0.4 * ( 1 + prof_butch_penalty ) + 0.4 * ( 1 + prof_skin_penalty ) + 0.2; + } + + if( action == butcher_type::QUICK ) { + // 70% of butchery, 15% skinning, 15% another activities + time_to_cut *= 0.7 * ( 1 + prof_butch_penalty ) + 0.15 * ( 1 + prof_skin_penalty ) + 0.15; + } + + if( action == butcher_type::FIELD_DRESS ) { + time_to_cut *= 1 + prof_butch_penalty; + } + + if( action == butcher_type::SKIN ) { + time_to_cut *= 1 + prof_skin_penalty; + } + time_to_cut *= ( 1.0f - ( get_player_character().get_num_crafting_helpers( 3 ) / 10.0f ) ); return time_to_cut; } @@ -950,7 +984,7 @@ static std::vector create_charge_items( const itype *drop, int count, // Returns false if the calling function should abort static bool butchery_drops_harvest( item *corpse_item, const mtype &mt, Character &you, - butcher_type action ) + butcher_type action, int moves_total ) { const int tool_quality = you.max_quality( action == butcher_type::DISSECT ? qual_CUT_FINE : qual_BUTCHER, PICKUP_RANGE ); @@ -1017,6 +1051,7 @@ static bool butchery_drops_harvest( item *corpse_item, const mtype &mt, Characte } map &here = get_map(); + for( const harvest_entry &entry : ( action == butcher_type::DISSECT && !mt.dissect.is_empty() ) ? *mt.dissect : *mt.harvest ) { const int skill_level = butchery_dissect_skill_level( you, tool_quality, entry.type ); @@ -1057,6 +1092,21 @@ static bool butchery_drops_harvest( item *corpse_item, const mtype &mt, Characte roll = 0; } + const double butch_basic = you.get_proficiency_practice( proficiency_prof_butchering_basic ); + const double skin_basic = you.get_proficiency_practice( proficiency_prof_skinning_basic ); + const double skin_adv = you.get_proficiency_practice( proficiency_prof_skinning_adv ); + const double penalty_small = 0.15; + const double penalty_big = 2; + + if( entry.type == harvest_drop_flesh || entry.type == harvest_drop_offal ) { + roll /= 1 + ( penalty_small * ( 1 - butch_basic ) ); + } + + if( entry.type == harvest_drop_skin ) { + roll /= 1 + ( penalty_big * ( 1 - skin_basic ) ); + roll /= 1 + ( penalty_small * ( 1 - skin_adv ) ); + } + // QUICK BUTCHERY if( action == butcher_type::QUICK ) { if( entry.type == harvest_drop_flesh ) { @@ -1267,6 +1317,75 @@ static bool butchery_drops_harvest( item *corpse_item, const mtype &mt, Characte 0 ) + 4 ); } + // handle our prof training + if( action == butcher_type::FULL && ( mt.harvest->has_entry_type( harvest_drop_flesh ) || + mt.harvest->has_entry_type( harvest_drop_offal ) ) ) { + // 40% of butchering and gutting, 40% of skinning, 20% another activities + if( you.has_proficiency( proficiency_prof_butchering_basic ) ) { + you.practice_proficiency( proficiency_prof_butchering_adv, + time_duration::from_moves( moves_total * 0.4 ) ); + } else { + you.practice_proficiency( proficiency_prof_butchering_basic, + time_duration::from_moves( moves_total * 0.4 ) ); + } + } + + if( action == butcher_type::FULL && mt.harvest->has_entry_type( harvest_drop_skin ) ) { + // 40% of butchering and gutting, 40% of skinning, 20% another activities + if( you.has_proficiency( proficiency_prof_skinning_basic ) ) { + you.practice_proficiency( proficiency_prof_skinning_adv, + time_duration::from_moves( moves_total * 0.4 ) ); + } else { + you.practice_proficiency( proficiency_prof_skinning_basic, + time_duration::from_moves( moves_total * 0.4 ) ); + } + } + + if( action == butcher_type::QUICK && ( mt.harvest->has_entry_type( harvest_drop_flesh ) || + mt.harvest->has_entry_type( harvest_drop_offal ) ) ) { + // 70% of butchery, 15% skinning, 15% another activities + if( you.has_proficiency( proficiency_prof_butchering_basic ) ) { + you.practice_proficiency( proficiency_prof_butchering_adv, + time_duration::from_moves( moves_total * 0.7 ) ); + } else { + you.practice_proficiency( proficiency_prof_butchering_basic, + time_duration::from_moves( moves_total * 0.7 ) ); + } + } + + if( action == butcher_type::QUICK && mt.harvest->has_entry_type( harvest_drop_skin ) ) { + // 70% of butchery, 15% skinning, 15% another activities + if( you.has_proficiency( proficiency_prof_skinning_basic ) ) { + you.practice_proficiency( proficiency_prof_skinning_adv, + time_duration::from_moves( moves_total * 0.15 ) ); + } else { + you.practice_proficiency( proficiency_prof_skinning_basic, + time_duration::from_moves( moves_total * 0.15 ) ); + } + } + + if( action == butcher_type::FIELD_DRESS && ( mt.harvest->has_entry_type( harvest_drop_flesh ) || + mt.harvest->has_entry_type( harvest_drop_offal ) ) ) { + if( you.has_proficiency( proficiency_prof_butchering_basic ) ) { + you.practice_proficiency( proficiency_prof_butchering_adv, + time_duration::from_moves( moves_total ) ); + } else { + you.practice_proficiency( proficiency_prof_butchering_basic, + time_duration::from_moves( moves_total ) ); + } + } + + if( action == butcher_type::SKIN && mt.harvest->has_entry_type( harvest_drop_skin ) ) { + // 70% of butchery, 15% skinning, 15% another activities + if( you.has_proficiency( proficiency_prof_skinning_basic ) ) { + you.practice_proficiency( proficiency_prof_skinning_adv, + time_duration::from_moves( moves_total ) ); + } else { + you.practice_proficiency( proficiency_prof_skinning_basic, + time_duration::from_moves( moves_total ) ); + } + } + // after this point, if there was a liquid handling from the harvest, // and the liquid handling was interrupted, then the activity was canceled, // therefore operations on this activity's targets and values may be invalidated. @@ -1353,7 +1472,7 @@ void activity_handlers::butcher_finish( player_activity *act, Character *you ) } // all action types - yields - if( !butchery_drops_harvest( &corpse_item, *corpse, *you, action ) ) { + if( !butchery_drops_harvest( &corpse_item, *corpse, *you, action, act->moves_total ) ) { // FATAL FAILURE add_msg( m_warning, SNIPPET.random_from_category( "harvest_drop_default_dissect_failed" ).value_or( translation() ).translated() );