Skip to content

Commit

Permalink
Merge pull request #77825 from RenechCDDA/cannibals_are_pariahs_every…
Browse files Browse the repository at this point in the history
…where

Cannibals are pariahs 2
  • Loading branch information
Maleclypse authored Dec 27, 2024
2 parents 163ee18 + e970f2b commit 36bb280
Show file tree
Hide file tree
Showing 10 changed files with 141 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@
},
{
"text": "Let's trade.",
"topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading",
"condition": { "compare_string": [ "yes", { "u_val": "general_meeting_u_met_Gavin" } ] },
"effect": "start_trade"
"trial": {
"type": "CONDITION",
"condition": { "math": [ "faction_food_supply('free_merchants', 'vitamin':'human_flesh_vitamin')", "<", "1" ] }
},
"success": { "effect": "start_trade", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading" },
"failure": { "topic": "TALK_FREE_MERCHANTS_MERCHANT_CANNIBAL_GTFO" }
},
{
"text": "Just saying hello. Keep safe.",
Expand All @@ -55,7 +59,15 @@
"speaker_effect": [ { "effect": "distribute_food_auto" } ],
"responses": [
{ "text": "About other things…", "topic": "TALK_FREE_MERCHANTS_MERCHANT_Talk" },
{ "text": "Let's trade.", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading", "effect": "start_trade" },
{
"text": "Let's trade.",
"trial": {
"type": "CONDITION",
"condition": { "math": [ "faction_food_supply('free_merchants', 'vitamin':'human_flesh_vitamin')", "<", "1" ] }
},
"success": { "effect": "start_trade", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading" },
"failure": { "topic": "TALK_FREE_MERCHANTS_MERCHANT_CANNIBAL_GTFO" }
},
{ "text": "When will you have new stuff in stock?", "topic": "TALK_FREE_MERCHANTS_MERCHANT_AskedRestock" },
{ "text": "That's all for now. <end_talking_bye>", "topic": "TALK_DONE" }
]
Expand Down Expand Up @@ -94,7 +106,15 @@
],
"responses": [
{ "text": "Got some time to talk?", "topic": "TALK_FREE_MERCHANTS_MERCHANT_Talk" },
{ "text": "Let's trade.", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading", "effect": "start_trade" },
{
"text": "Let's trade.",
"trial": {
"type": "CONDITION",
"condition": { "math": [ "faction_food_supply('free_merchants', 'vitamin':'human_flesh_vitamin')", "<", "1" ] }
},
"success": { "effect": "start_trade", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading" },
"failure": { "topic": "TALK_FREE_MERCHANTS_MERCHANT_CANNIBAL_GTFO" }
},
{ "text": "I just wanted to look around. <end_talking_bye>", "topic": "TALK_DONE" }
]
},
Expand Down Expand Up @@ -162,7 +182,15 @@
"topic": "TALK_FREE_MERCHANTS_MERCHANT_OutsideWorld"
},
{ "text": "I'm looking for work. Can I do anything for the center?", "topic": "TALK_MISSION_LIST" },
{ "text": "Let's trade.", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading", "effect": "start_trade" },
{
"text": "Let's trade.",
"trial": {
"type": "CONDITION",
"condition": { "math": [ "faction_food_supply('free_merchants', 'vitamin':'human_flesh_vitamin')", "<", "1" ] }
},
"success": { "effect": "start_trade", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading" },
"failure": { "topic": "TALK_FREE_MERCHANTS_MERCHANT_CANNIBAL_GTFO" }
},
{
"text": "Are you interested in buying any processed lumber?",
"topic": "TALK_EVAC_MERCHANT_DEAL_NEGOTIATE",
Expand Down Expand Up @@ -303,8 +331,12 @@
},
{
"text": "Mind if I take a look at your stock?",
"topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading",
"effect": "start_trade"
"trial": {
"type": "CONDITION",
"condition": { "math": [ "faction_food_supply('free_merchants', 'vitamin':'human_flesh_vitamin')", "<", "1" ] }
},
"success": { "effect": "start_trade", "topic": "TALK_FREE_MERCHANTS_MERCHANT_DoneTrading" },
"failure": { "topic": "TALK_FREE_MERCHANTS_MERCHANT_CANNIBAL_GTFO" }
},
{ "text": "Radios? That seems awfully specific.", "topic": "TALK_FREE_MERCHANTS_MERCHANT_SellingHardware1" },
{ "text": "Good to know.", "topic": "TALK_FREE_MERCHANTS_MERCHANT_Talk" }
Expand All @@ -329,6 +361,17 @@
{ "text": "I'll keep that in mind.", "topic": "TALK_FREE_MERCHANTS_MERCHANT_Talk" }
]
},
{
"id": "TALK_FREE_MERCHANTS_MERCHANT_CANNIBAL_GTFO",
"type": "talk_topic",
"dynamic_line": "Listen, buddy. We were putting away some of the last food you gave us and noticed it was… off. Okay I'll cut the shit- It was people. I don't know how you got it, or what you did, but we aren't taking any chances. You are not welcome here anymore. <color_red>I'll give you one minute</color> to fuck off and never come back.",
"speaker_effect": [
{
"effect": [ "end_conversation", { "run_eocs": [ "EOC_GAVE_BEGGAR_CANNIBAL_PROOF" ], "time_in_future": "1 minutes" } ]
}
],
"responses": [ { "text": "But I-", "topic": "TALK_DONE" } ]
},
{
"id": "TALK_EVAC_MERCHANT_DEAL_NEGOTIATE",
"type": "talk_topic",
Expand Down
1 change: 1 addition & 0 deletions data/json/vitamin.json
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@
"type": "vitamin",
"vit_type": "counter",
"name": { "str": "Consumed human flesh" },
"flags": [ "NO_SELL" ],
"min": 0,
"max": 10000,
"rate": "1 h"
Expand Down
2 changes: 1 addition & 1 deletion doc/NPCs.md
Original file line number Diff line number Diff line change
Expand Up @@ -1335,7 +1335,7 @@ _some functions support array arguments or kwargs, denoted with square brackets
| encumbrance(`s`/`v`) ||| u, n | Return the characters total encumbrance of a body part.<br/>Argument is bodypart ID. <br/> For items, returns typical encumbrance of the item. <br/><br/>Example:<br/>`"condition": { "math": [ "u_encumbrance('torso') > 0"] }`|
| health(`d`/`v`) ||| u, n | Return character current health .<br/><br/>Example:<br/>`{ "math": [ "u_health() -= 1" ] }`|
| energy(`s`/`v`) ||| u, n | Return a numeric value (in millijoules) for an energy string (see [Units](JSON_INFO.md#units)).<br/><br/>Example:<br/>`{ "math": [ "u_val('power') -= energy('25 kJ')" ] }`|
| faction_like(`s`/`v`)<br/>faction_respect(`s`/`v`)<br/>faction_trust(`s`/`v`)<br/>faction_food_supply(`s`/`v`)<br/>faction_wealth(`s`/`v`)<br/>faction_power(`s`/`v`)<br/>faction_size(`s`/`v`) ||| N/A<br/>(global) | Return the like/respect/trust/fac_food_supply/wealth/power/size value a faction has for the avatar.<br/>Argument is faction ID.<br/><br/>Example:<br/>`"condition": { "math": [ "faction_like('hells_raiders') < -60" ] }`|
| faction_like(`s`/`v`)<br/>faction_respect(`s`/`v`)<br/>faction_trust(`s`/`v`)<br/>faction_food_supply(`s`/`v`)<br/>faction_wealth(`s`/`v`)<br/>faction_power(`s`/`v`)<br/>faction_size(`s`/`v`) ||| N/A<br/>(global) | Return the like/respect/trust/fac_food_supply/wealth/power/size value a faction has for the avatar.<br/>Argument is faction ID.<br/>`faction_food_supply` has an optional second argument(kwarg) for getting/setting vitamins.<br/><br/>Example:<br/>`"condition": { "math": [ "faction_like('hells_raiders') < -60" ] }`<br/><br/>`{ "math": [ "calcium_amount", "=", "faction_food_supply('your_followers', 'vitamin':'calcium')" ] },`<br/>`{ "u_message": "Calcium stored is <global_val:calcium_amount>", "type": "good" },`|
| field_strength(`s`/`v`) ||| u, n, global | Return the strength of a field on the tile.<br/>Argument is field ID.<br/><br/>Optional kwargs:<br/> `location`: `v` - center search on this location<br/><br/>The `location` kwarg is mandatory in the global scope.<br/><br/>Examples:<br/>`"condition": { "math": [ "u_field_strength('fd_blood') > 5" ] }`<br/><br/>`"condition": { "math": [ "field_strength('fd_blood_insect', 'location': u_search_loc) > 5" ] }`|
| has_flag(`s`/`v`) ||| u, n | Check whether the actor has a flag. Meant to be used as condition for ternaries. Argument is trait ID.<br/><br/> Example:<br/>`"condition": { "math": [ "u_blorg = u_has_flag('MUTATION_TRESHOLD') ? 100 : 15" ] }`|
| has_trait(`s`/`v`) ||| u, n | Check whether the actor has a trait. Meant to be used as condition for ternaries. Argument is trait ID.<br/><br/> Example:<br/>`"condition": { "math": [ "u_blorg = u_has_trait('FEEBLE') ? 100 : 15" ] }`|
Expand Down
42 changes: 31 additions & 11 deletions src/activity_handlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,7 @@ static const itype_id itype_pseudo_magazine( "pseudo_magazine" );

static const json_character_flag json_flag_ASOCIAL1( "ASOCIAL1" );
static const json_character_flag json_flag_ASOCIAL2( "ASOCIAL2" );
static const json_character_flag json_flag_CANNIBAL( "CANNIBAL" );
static const json_character_flag json_flag_PAIN_IMMUNE( "PAIN_IMMUNE" );
static const json_character_flag json_flag_PSYCHOPATH( "PSYCHOPATH" );
static const json_character_flag json_flag_SAPIOVORE( "SAPIOVORE" );
static const json_character_flag json_flag_SILENT_SPELL( "SILENT_SPELL" );
static const json_character_flag json_flag_SOCIAL1( "SOCIAL1" );
static const json_character_flag json_flag_SOCIAL2( "SOCIAL2" );
Expand Down Expand Up @@ -479,6 +476,32 @@ void activity_handlers::butcher_do_turn( player_activity *act, Character * )
corpse_item.set_var( butcher_progress_var( action ), progress );
}

static bool do_cannibalism_piss_people_off( Character &you )
{
if( !you.is_avatar() ) {
return true; // NPCs dont accidentally cause player hate
}

if( !query_yn(
_( "Really desecrate the mortal remains of a fellow human being by butchering them for meat?" ) ) ) {
return false; // player cancels
}

for( npc &guy : g->all_npcs() ) {
if( guy.is_active() && guy.sees( you ) && !guy.okay_with_eating_humans() ) {
guy.say( _( "<swear!>? Are you butchering them? That's not okay, <fuck_you>." ) );
// massive opinion penalty
guy.op_of_u.trust -= 5;
guy.op_of_u.value -= 5;
guy.op_of_u.anger += 5;
if( guy.turned_hostile() ) {
guy.make_angry();
}
}
}
return true;
}

static void set_up_butchery( player_activity &act, Character &you, butcher_type action )
{
const int factor = you.max_quality( action == butcher_type::DISSECT ? qual_CUT_FINE : qual_BUTCHER,
Expand Down Expand Up @@ -597,20 +620,18 @@ static void set_up_butchery( player_activity &act, Character &you, butcher_type
}
}


// TODO: Extract this bool into a function
const bool is_human = corpse.id == mtype_id::NULL_ID() || ( ( corpse.in_species( species_HUMAN ) ||
corpse.in_species( species_FERAL ) ) &&
!corpse.in_species( species_ZOMBIE ) );

// applies to all butchery actions except for dissections
if( is_human && action != butcher_type::DISSECT && !( you.has_flag( json_flag_CANNIBAL ) ||
you.has_flag( json_flag_PSYCHOPATH ) || you.has_flag( json_flag_SAPIOVORE ) ) ) {
if( is_human && action != butcher_type::DISSECT && !you.okay_with_eating_humans() ) {
//first determine if the butcherer has the dissect_humans proficiency.
if( you.has_proficiency( proficiency_prof_dissect_humans ) ) {
//if it's player doing the butchery, ask them first.
if( you.is_avatar() ) {
if( query_yn(
_( "Really desecrate the mortal remains of a fellow human being by butchering them for meat?" ) ) ) {
if( do_cannibalism_piss_people_off( you ) ) {
//give the player a random message showing their disgust and cause morale penalty.
switch( rng( 1, 3 ) ) {
case 1:
Expand Down Expand Up @@ -638,7 +659,7 @@ static void set_up_butchery( player_activity &act, Character &you, butcher_type
} else {
//this runs if the butcherer does NOT have prof_dissect_humans
if( you.is_avatar() ) {
if( query_yn( _( "Would you dare desecrate the mortal remains of a fellow human being?" ) ) ) {
if( do_cannibalism_piss_people_off( you ) ) {
//random message and morale penalty
switch( rng( 1, 3 ) ) {
case 1:
Expand Down Expand Up @@ -667,8 +688,7 @@ static void set_up_butchery( player_activity &act, Character &you, butcher_type
}

// applies to only dissections, so that dissect_humans training makes a difference.
if( is_human && action == butcher_type::DISSECT && !( you.has_flag( json_flag_CANNIBAL ) ||
you.has_flag( json_flag_PSYCHOPATH ) || you.has_flag( json_flag_SAPIOVORE ) ) ) {
if( is_human && action == butcher_type::DISSECT && !you.okay_with_eating_humans() ) {
if( you.has_proficiency( proficiency_prof_dissect_humans ) ) {
//you're either trained for this, densensitized, or both. doesn't bother you.
if( you.is_avatar() ) {
Expand Down
6 changes: 4 additions & 2 deletions src/activity_handlers.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ enum class do_activity_reason : int {
NEEDS_MOP, // This spot can be mopped, if a mop is present.
NEEDS_FISHING, // This spot can be fished, if the right tool is present.
NEEDS_CRAFT, // There is at least one item to craft.
NEEDS_DISASSEMBLE // There is at least one item to disassemble.
NEEDS_DISASSEMBLE, // There is at least one item to disassemble.
REFUSES_THIS_WORK // Character refuses to do this for some reason, maybe against their beliefs or needs danger prompt.

};

Expand Down Expand Up @@ -122,7 +123,8 @@ const std::vector<std::string> do_activity_reason_string = {
"NEEDS_MOP",
"NEEDS_FISHING",
"NEEDS_CRAFT",
"NEEDS_DISASSEMBLE"
"NEEDS_DISASSEMBLE",
"REFUSES_THIS_WORK"
};

struct activity_reason_info {
Expand Down
22 changes: 22 additions & 0 deletions src/activity_item_handling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,10 @@ static const quality_id qual_WELD( "WELD" );

static const requirement_id requirement_data_mining_standard( "mining_standard" );

static const species_id species_FERAL( "FERAL" );
static const species_id species_HUMAN( "HUMAN" );
static const species_id species_ZOMBIE( "ZOMBIE" );

static const ter_str_id ter_t_stump( "t_stump" );
static const ter_str_id ter_t_trunk( "t_trunk" );

Expand Down Expand Up @@ -1213,6 +1217,16 @@ static activity_reason_info can_do_activity_there( const activity_id &act, Chara
}
}
if( !corpses.empty() ) {
for( item &body : corpses ) {
const mtype &corpse = *body.get_mtype();
// TODO: Extract this bool into a function
const bool is_human = corpse.id == mtype_id::NULL_ID() || ( ( corpse.in_species( species_HUMAN ) ||
corpse.in_species( species_FERAL ) ) &&
!corpse.in_species( species_ZOMBIE ) );
if( is_human && !you.okay_with_eating_humans() ) {
return activity_reason_info::fail( do_activity_reason::REFUSES_THIS_WORK );
}
}
if( big_count > 0 && small_count == 0 ) {
if( !b_rack_present ) {
return activity_reason_info::fail( do_activity_reason::NO_ZONE );
Expand Down Expand Up @@ -2735,6 +2749,14 @@ static requirement_check_result generic_multi_activity_check_requirement(
if( can_do_it ) {
return requirement_check_result::CAN_DO_LOCATION;
}
if( reason == do_activity_reason::REFUSES_THIS_WORK ) {
you.add_msg_if_player( m_info,
_( "There's a human corpse there. You wouldn't want to butcher it by accident." ) );
if( you.is_npc() ) {
add_msg_if_player_sees( you, m_info, _( "%s refuses to butcher a human corpse." ),
you.disp_name() );
}
}
if( reason == do_activity_reason::DONT_HAVE_SKILL ||
reason == do_activity_reason::NO_ZONE ||
reason == do_activity_reason::ALREADY_DONE ||
Expand Down
2 changes: 2 additions & 0 deletions src/character.h
Original file line number Diff line number Diff line change
Expand Up @@ -3381,6 +3381,8 @@ class Character : public Creature, public visitable
int nutrition_for( const item &comest ) const;
/** Can the food be [theoretically] eaten no matter the consequences? */
ret_val<edible_rating> can_eat( const item &food ) const;
/** Would this character be normally willing to consume human flesh? */
bool okay_with_eating_humans() const;
/**
* Same as @ref can_eat, but takes consequences into account.
* Asks about them if @param interactive is true, refuses otherwise.
Expand Down
9 changes: 7 additions & 2 deletions src/consumption.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -963,6 +963,12 @@ ret_val<edible_rating> Character::can_eat( const item &food ) const
return ret_val<edible_rating>::make_success();
}

bool Character::okay_with_eating_humans() const
{
return has_flag( STATIC( json_character_flag( "CANNIBAL" ) ) ) ||
has_flag( json_flag_PSYCHOPATH ) || has_flag( json_flag_SAPIOVORE );
}

ret_val<edible_rating> Character::will_eat( const item &food, bool interactive ) const
{
ret_val<edible_rating> ret = can_eat( food );
Expand Down Expand Up @@ -997,8 +1003,7 @@ ret_val<edible_rating> Character::will_eat( const item &food, bool interactive )
const bool food_is_human_flesh = food.has_vitamin( vitamin_human_flesh_vitamin ) ||
( food.has_flag( flag_STRICT_HUMANITARIANISM ) &&
!has_flag( json_flag_STRICT_HUMANITARIAN ) );
if( ( food_is_human_flesh && !has_flag( STATIC( json_character_flag( "CANNIBAL" ) ) ) &&
!has_flag( json_flag_PSYCHOPATH ) && !has_flag( json_flag_SAPIOVORE ) ) &&
if( ( food_is_human_flesh && !okay_with_eating_humans() ) &&
( !food.has_flag( flag_HEMOVORE_FUN ) || ( !has_flag( json_flag_BLOODFEEDER ) ) ) ) {
add_consequence( _( "The thought of eating human flesh makes you feel sick." ), CANNIBALISM );
}
Expand Down
10 changes: 10 additions & 0 deletions src/item.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ static const vitamin_id vitamin_human_flesh_vitamin( "human_flesh_vitamin" );

// vitamin flags
static const std::string flag_NO_DISPLAY( "NO_DISPLAY" );
static const std::string flag_NO_SELL( "NO_SELL" );

// fault flags
static const std::string flag_BLACKPOWDER_FOULING_DAMAGE( "BLACKPOWDER_FOULING_DAMAGE" );
Expand Down Expand Up @@ -7170,6 +7171,15 @@ int item::price_no_contents( bool practical, std::optional<int> price_override )
price *= fault->price_mod();
}

if( is_food() && get_comestible() ) {
const nutrients &nutrients_value = default_character_compute_effective_nutrients( *this );
for( const std::pair<const vitamin_id, int> &vit_pair : nutrients_value.vitamins() ) {
if( vit_pair.second > 0 && vit_pair.first->has_flag( flag_NO_SELL ) ) {
price = 0.0;
}
}
}

return price;
}

Expand Down
17 changes: 13 additions & 4 deletions src/math_parser_diag.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,19 +285,28 @@ diag_assign_dbl_f faction_trust_ass( char /* scope */, std::vector<diag_value> c
}

diag_eval_dbl_f faction_food_supply_eval( char /* scope */,
std::vector<diag_value> const &params, diag_kwargs const &/* kwargs */ )
std::vector<diag_value> const &params, diag_kwargs const &kwargs )
{
return [fac_val = params[0]]( const_dialogue const & d ) {
diag_value vit_val = kwargs.kwarg_or( "vitamin" );
return [fac_val = params[0], vit_val]( const_dialogue const & d ) {
faction *fac = g->faction_manager_ptr->get( faction_id( fac_val.str( d ) ) );
if( !vit_val.is_empty() ) {
return static_cast<double>( fac->food_supply.get_vitamin( vitamin_id( vit_val.str( d ) ) ) );
}
return static_cast<double>( fac->food_supply.calories );
};
}

diag_assign_dbl_f faction_food_supply_ass( char /* scope */,
std::vector<diag_value> const &params, diag_kwargs const &/* kwargs */ )
std::vector<diag_value> const &params, diag_kwargs const &kwargs )
{
return [fac_val = params[0]]( dialogue const & d, double val ) {
diag_value vit_val = kwargs.kwarg_or( "vitamin" );
return [fac_val = params[0], vit_val]( dialogue const & d, double val ) {
faction *fac = g->faction_manager_ptr->get( faction_id( fac_val.str( d ) ) );
if( !vit_val.is_empty() ) {
fac->food_supply.add_vitamin( vitamin_id( vit_val.str( d ) ), val );
return;
}
fac->food_supply.calories = val;
};
}
Expand Down

0 comments on commit 36bb280

Please sign in to comment.