diff --git a/data/json/effects.json b/data/json/effects.json
index f9783ea772ffe..5eec0174ca505 100644
--- a/data/json/effects.json
+++ b/data/json/effects.json
@@ -4885,6 +4885,48 @@
"flags": [ "FEATHER_FALL" ],
"max_duration": "1 s"
},
+ {
+ "type": "effect_type",
+ "id": "social_satisfied",
+ "name": [ "Good Company" ],
+ "desc": [ "It feels good to spend time around others." ],
+ "max_duration": "6 h",
+ "max_intensity": 1,
+ "rating": "good"
+ },
+ {
+ "type": "effect_type",
+ "id": "social_dissatisfied",
+ "name": [ "Lonely", "Very Lonely", "All Alone" ],
+ "desc": [
+ "You feel a little lonely.",
+ "You could really use a friend right now.",
+ "What's the point of surviving if you're all alone?"
+ ],
+ "max_intensity": 3,
+ "rating": "bad"
+ },
+ {
+ "type": "effect_type",
+ "id": "asocial_satisfied",
+ "name": [ "Alone" ],
+ "desc": [ "Being on your own makes you feel good." ],
+ "max_intensity": 1,
+ "max_duration": "6 h",
+ "rating": "good"
+ },
+ {
+ "type": "effect_type",
+ "id": "asocial_dissatisfied",
+ "name": [ "Irritable", "Annoyed", "Crowded" ],
+ "desc": [
+ "Being around others has left you feeling drained.",
+ "OK, that's enough social time. You'd really rather be alone now.",
+ "Your nerves are frayed from all this socializing. You need some alone time to recharge."
+ ],
+ "max_intensity": 3,
+ "rating": "bad"
+ },
{
"type": "effect_type",
"id": "quadruped_full",
diff --git a/data/json/effects_on_condition/effects_eocs.json b/data/json/effects_on_condition/effects_eocs.json
index 0da74fdccde14..3539252e21432 100644
--- a/data/json/effects_on_condition/effects_eocs.json
+++ b/data/json/effects_on_condition/effects_eocs.json
@@ -15,5 +15,53 @@
}
],
"false_effect": [ { "u_lose_morale": "morale_bad_protein_bar" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "eoc_social_satisfied",
+ "recurrence": [ "1 minutes", "5 minutes" ],
+ "condition": { "u_has_effect": "social_satisfied" },
+ "effect": [ { "u_add_morale": "morale_social", "bonus": 6, "max_bonus": 6, "duration": "1 days", "decay_start": "1 days" } ],
+ "false_effect": [ { "u_lose_morale": "morale_social" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "eoc_social_dissatisfied",
+ "recurrence": [ "1 minutes", "5 minutes" ],
+ "condition": { "u_has_effect": "social_dissatisfied" },
+ "effect": [
+ {
+ "u_add_morale": "morale_asocial",
+ "bonus": { "math": [ "u_effect_intensity('social_dissatisfied') * -7" ] },
+ "max_bonus": -21,
+ "duration": "1 days",
+ "decay_start": "1 days"
+ }
+ ],
+ "false_effect": [ { "u_lose_morale": "morale_social_dissatisfied" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "eoc_asocial_satisfied",
+ "recurrence": [ "1 minutes", "5 minutes" ],
+ "condition": { "u_has_effect": "asocial_satisfied" },
+ "effect": [ { "u_add_morale": "morale_asocial", "bonus": 10, "max_bonus": 10, "duration": "1 days", "decay_start": "1 days" } ],
+ "false_effect": [ { "u_lose_morale": "morale_asocial" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "eoc_asocial_dissatisfied",
+ "recurrence": [ "1 minutes", "5 minutes" ],
+ "condition": { "u_has_effect": "asocial_dissatisfied" },
+ "effect": [
+ {
+ "u_add_morale": "morale_social",
+ "bonus": { "math": [ "u_effect_intensity('asocial_dissatisfied') * -7" ] },
+ "max_bonus": -21,
+ "duration": "1 days",
+ "decay_start": "1 days"
+ }
+ ],
+ "false_effect": [ { "u_lose_morale": "morale_asocial_dissatisfied" } ]
}
]
diff --git a/data/json/effects_on_condition/mutation_eocs/mutation_effect_eocs.json b/data/json/effects_on_condition/mutation_eocs/mutation_effect_eocs.json
index 7f6daf18db519..5d77a3fe147a3 100644
--- a/data/json/effects_on_condition/mutation_eocs/mutation_effect_eocs.json
+++ b/data/json/effects_on_condition/mutation_eocs/mutation_effect_eocs.json
@@ -695,5 +695,149 @@
{ "u_activate_trait": "WINGS_INSECT_active" },
{ "u_message": "You don't have the stamina to keep buzzing.", "type": "bad" }
]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_social1_bonus",
+ "recurrence": [ "10 minutes", "30 minutes" ],
+ "//": "Alcohol and sedatives prevent the bad effects of these EOCs. Antidepressants prevent (but don't remove) both the good and bad effects, keeping you at a baseline state.",
+ "condition": {
+ "and": [
+ { "u_has_flag": "SOCIAL1" },
+ { "not": { "u_has_effect": "took_prozac" } },
+ { "math": [ "u_characters_nearby('radius': 30)", ">", "0" ] },
+ { "not": { "u_has_effect": "narcosis" } }
+ ]
+ },
+ "effect": [
+ { "u_lose_effect": "social_dissatisfied" },
+ { "u_lose_effect": "social_satisfied" },
+ { "u_add_effect": "social_satisfied", "duration": "2 hours" }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_social2_bonus",
+ "recurrence": [ "10 minutes", "30 minutes" ],
+ "condition": {
+ "and": [
+ { "u_has_flag": "SOCIAL2" },
+ { "not": { "u_has_effect": "took_prozac" } },
+ { "math": [ "u_characters_nearby('radius': 30, 'attitude': 'allies')", ">", "0" ] },
+ { "not": { "u_has_effect": "narcosis" } }
+ ]
+ },
+ "effect": [
+ { "u_lose_effect": "social_dissatisfied" },
+ { "u_lose_effect": "social_satisfied" },
+ { "u_add_effect": "social_satisfied", "duration": "6 hours" }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_social2_penalty",
+ "recurrence": [ "30 minutes", "90 minutes" ],
+ "condition": {
+ "and": [
+ { "u_has_flag": "SOCIAL2" },
+ { "math": [ "u_characters_nearby('radius': 30, 'attitude': 'allies')", "==", "0" ] },
+ { "not": { "u_has_effect": "took_prozac" } },
+ { "not": { "u_has_effect": "valium" } },
+ { "not": { "u_has_effect": "took_xanax" } },
+ { "not": { "u_has_effect": "drunk" } },
+ { "not": { "u_has_effect": "narcosis" } }
+ ]
+ },
+ "effect": [
+ { "u_lose_effect": "social_satisfied" },
+ {
+ "u_add_effect": "social_dissatisfied",
+ "duration": "PERMANENT",
+ "intensity": { "math": [ "u_effect_intensity('social_dissatisfied') + 1" ] }
+ },
+ { "u_message": "You could use some friendly company.", "type": "bad" }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_asocial1_bonus",
+ "recurrence": [ "10 minutes", "30 minutes" ],
+ "condition": {
+ "and": [
+ { "u_has_flag": "ASOCIAL1" },
+ { "not": { "u_has_effect": "took_prozac" } },
+ { "math": [ "u_characters_nearby('radius': 30)", "==", "0" ] },
+ { "not": { "u_has_effect": "narcosis" } }
+ ]
+ },
+ "effect": [
+ { "u_lose_effect": "asocial_dissatisfied" },
+ { "u_lose_effect": "asocial_satisfied" },
+ { "u_add_effect": "asocial_satisfied", "duration": "2 hours" }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_asocial2_bonus",
+ "recurrence": [ "10 minutes", "30 minutes" ],
+ "condition": {
+ "and": [
+ { "u_has_flag": "ASOCIAL2" },
+ { "math": [ "u_characters_nearby('radius': 30)", "==", "0" ] },
+ { "not": { "u_has_effect": "took_prozac" } },
+ { "not": { "u_has_effect": "narcosis" } }
+ ]
+ },
+ "effect": [ { "u_lose_effect": "asocial_dissatisfied" }, { "u_add_effect": "asocial_satisfied", "duration": "6 hours" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_asocial2_penalty",
+ "recurrence": [ "30 minutes", "90 minutes" ],
+ "condition": {
+ "and": [
+ { "u_has_flag": "ASOCIAL2" },
+ { "not": { "u_has_effect": "took_prozac" } },
+ { "not": { "u_has_effect": "valium" } },
+ { "not": { "u_has_effect": "took_xanax" } },
+ { "not": { "u_has_effect": "drunk" } },
+ { "math": [ "u_characters_nearby('radius': 30)", ">", "0" ] },
+ { "not": { "u_has_effect": "narcosis" } }
+ ]
+ },
+ "effect": [
+ { "u_lose_effect": "asocial_satisfied" },
+ {
+ "u_add_effect": "asocial_dissatisfied",
+ "duration": "PERMANENT",
+ "intensity": { "math": [ "u_effect_intensity('asocial_dissatisfied') + 1" ] }
+ },
+ { "u_message": "You'd really rather be by yourself.", "type": "bad" }
+ ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_social_reset_sleep",
+ "recurrence": [ "30 minutes", "45 minutes" ],
+ "condition": {
+ "and": [
+ { "or": [ { "u_has_effect": "narcosis" }, { "u_has_effect": "sleep" } ] },
+ { "or": [ { "u_has_effect": "social_dissatisfied" }, { "u_has_effect": "social_dissatisfied" } ] }
+ ]
+ },
+ "effect": [ { "u_lose_effect": "asocial_dissatisfied" }, { "u_lose_effect": "social_dissatisfied" } ]
+ },
+ {
+ "type": "effect_on_condition",
+ "id": "EOC_social_reset_drugs",
+ "recurrence": [ "1 seconds", "1 seconds" ],
+ "//": "Sedatives are effective at acutely treating social anxiety/autophobia. Enjoy your benzo addiction, survivors.",
+ "condition": {
+ "and": [
+ { "or": [ { "u_has_effect": "valium" }, { "u_has_effect": "took_xanax" } ] },
+ { "or": [ { "u_has_effect": "social_dissatisfied" }, { "u_has_effect": "social_dissatisfied" } ] }
+ ]
+ },
+ "effect": [ { "u_lose_effect": "asocial_dissatisfied" }, { "u_lose_effect": "social_dissatisfied" } ]
}
]
diff --git a/data/json/morale_types.json b/data/json/morale_types.json
index 2f5392ee282fd..008fc69d92e9b 100644
--- a/data/json/morale_types.json
+++ b/data/json/morale_types.json
@@ -428,5 +428,15 @@
"id": "morale_afs_drugs",
"type": "morale_type",
"text": "Chemical enhancement moodswing"
+ },
+ {
+ "id": "morale_social",
+ "type": "morale_type",
+ "text": "Spent time around allies"
+ },
+ {
+ "id": "morale_asocial",
+ "type": "morale_type",
+ "text": "Spent time alone"
}
]
diff --git a/data/json/mutations/mutation_type.json b/data/json/mutations/mutation_type.json
index 96eef015278b0..cbf1c5b19b743 100644
--- a/data/json/mutations/mutation_type.json
+++ b/data/json/mutations/mutation_type.json
@@ -266,5 +266,9 @@
{
"type": "mutation_type",
"id": "BODY_ARMOR_MOD"
+ },
+ {
+ "type": "mutation_type",
+ "id": "SOCIAL"
}
]
diff --git a/data/json/mutations/mutations.json b/data/json/mutations/mutations.json
index fdfaa4b656932..199af9ac7ad5d 100644
--- a/data/json/mutations/mutations.json
+++ b/data/json/mutations/mutations.json
@@ -253,7 +253,7 @@
"player_display": false,
"dummy": true,
"category": [ "HUMAN" ],
- "types": [ "ANIMAL_EMPATHY", "ATTITUDE", "HUMAN_EMPATHY", "MASOCHISM", "MEMORY", "SLEEPINESS", "WANDERLUST" ],
+ "types": [ "ANIMAL_EMPATHY", "SOCIAL", "ATTITUDE", "HUMAN_EMPATHY", "MASOCHISM", "MEMORY", "SLEEPINESS", "WANDERLUST" ],
"cancels": [
"ADDICTIVE",
"ADRENALINE",
@@ -9655,6 +9655,58 @@
"category": [ "ALPHA" ],
"starting_trait": true
},
+ {
+ "type": "mutation",
+ "id": "SOCIAL1",
+ "name": { "str": "Extrovert" },
+ "points": 1,
+ "types": [ "SOCIAL" ],
+ "cancels": [ "ASOCIAL1", "ASOCIAL2" ],
+ "changes_to": [ "SOCIAL2" ],
+ "description": "You don't mind being alone, but you get a slight mood boost from being around friends.",
+ "starting_trait": true,
+ "flags": [ "SOCIAL1" ],
+ "category": [ "CATTLE", "ELFA", "LUPINE", "MOUSE", "RAT", "CHIROPTERAN", "BIRD", "FISH" ]
+ },
+ {
+ "type": "mutation",
+ "id": "SOCIAL2",
+ "name": { "str": "Sociable" },
+ "points": 1,
+ "types": [ "SOCIAL" ],
+ "cancels": [ "ASOCIAL1", "ASOCIAL2" ],
+ "description": "You are a social creature. Spending time with friends will improve your mood, but if you're alone too long, you'll start to feel unhappy.",
+ "starting_trait": true,
+ "mixed_effect": true,
+ "flags": [ "SOCIAL2" ],
+ "category": [ "CATTLE", "LUPINE", "MOUSE", "RAT", "CHIROPTERAN", "ELFA" ]
+ },
+ {
+ "type": "mutation",
+ "id": "ASOCIAL1",
+ "name": { "str": "Introvert" },
+ "points": 1,
+ "types": [ "SOCIAL" ],
+ "cancels": [ "SOCIAL1", "SOCIAL2" ],
+ "changes_to": [ "ASOCIAL2" ],
+ "description": "You can tolerate others, but you prefer to be alone. Spending time by yourself will boost your mood slightly.",
+ "starting_trait": true,
+ "flags": [ "ASOCIAL1" ],
+ "category": [ "SPIDER", "URSINE", "FELINE", "LIZARD", "BEAST", "CHIMERA" ]
+ },
+ {
+ "type": "mutation",
+ "id": "ASOCIAL2",
+ "name": { "str": "Loner" },
+ "points": 1,
+ "types": [ "SOCIAL" ],
+ "cancels": [ "SOCIAL1", "SOCIAL2" ],
+ "description": "You'd really rather be by yourself. Being alone will improve your mood, but spending too much time around other people can grate on your nerves.",
+ "starting_trait": true,
+ "mixed_effect": true,
+ "flags": [ "ASOCIAL2" ],
+ "category": [ "SPIDER", "URSINE", "LIZARD", "BEAST" ]
+ },
{
"type": "mutation",
"id": "ALPHA_COMPLEX",
@@ -9662,7 +9714,7 @@
"points": 0,
"description": "You can't stand to prostrate yourself before your lessers and hate it when they don't immediately do what you say. Persuasion is much more difficult, but you're better at lying and intimidating.",
"purifiable": false,
- "types": [ "HUMAN_EMPATHY" ],
+ "types": [ "SOCIAL" ],
"social_modifiers": { "lie": 10, "persuade": -25, "intimidate": 10 },
"category": [ "ALPHA" ],
"prereqs": [ "INT_ALPHA", "PER_ALPHA" ],
diff --git a/doc/NPCs.md b/doc/NPCs.md
index b3e23965ca276..65a37b6f144c2 100644
--- a/doc/NPCs.md
+++ b/doc/NPCs.md
@@ -1315,6 +1315,7 @@ _some functions support array arguments or kwargs, denoted with square brackets
| attack_speed() | ✅ | ❌ | u, n | Return the characters current adjusted attack speed with their current weapon.
Example:
`"condition": { "math": [ "u_attack_speed()", ">=", "10"] }`|
| addiction_intensity(`s`/`v`) | ✅ | ❌ | u, n | Return the characters current intensity of the given addiction.
Argument is addiction type ID.
Example:
`"condition": { "math": [ "u_addiction_intensity('caffeine')", ">=", "1"] }`|
| addiction_turns(`s`/`v`) | ✅ | ✅ | u, n | Return the characters current duration left (in turns) for the given addiction.
Argument is addiction type ID.
Example:
`"condition": { "math": [ "u_addiction_turns('caffeine')", ">=", "3600"] }`|
+| characters_nearby() | ✅ | ❌ | u, n, global | Return the number of nearby characters (that is, the avatar and/or NPCs who are not hallucinations).
Optional kwargs:
`radius`: `d`/`v` - limit to radius (rl_dist)
`location`: `v` - center search on this location
`attitude`: `s`/`v` - attitude filter. Must be one of `hostile`, `allies`, `not_allies`, `any`. Assumes `any` if not specified
The `location` kwarg is mandatory in the global scope.
`allow_hallucinations`: `d`/`v` - False by default. If set to any non-zero number, all hallucinated NPCs in range will be counted as well.
Examples:
`"condition": { "math": [ "u_characters_nearby('radius': u_search_radius * 3, 'attitude': 'not_allies' )", ">", "0" ] }`
`"condition": { "math": [ "characters_nearby( 'radius': u_search_radius * 3, 'location': u_search_loc)", ">", "5" ] }`|
| charge_count(`s`/`v`) | ✅ | ❌ | u, n | Return the charges of a given item in the character's inventory.
Argument is item ID.
Example:
`"condition": { "math": [ "u_charge_count('light_plus_battery_cell')", ">=", "100"] }`|
| coverage(`s`/`v`) | ✅ | ❌ | u, n | Return the characters total coverage of a body part.
Argument is bodypart ID.
For items, returns typical coverage of the item.
Example:
`"condition": { "math": [ "u_coverage('torso')", ">", "0"] }`|
| distance(`s`/`v`,`s`/`v`) | ✅ | ❌ | g | Return distance between two targets.
Arguments are location variables or special strings (`u`, `npc`). `u` means your location. `npc` means NPC's location.
Example:
`"condition": { "math": [ "distance('u', loc)", "<=", "50"] }`|
@@ -1358,6 +1359,7 @@ _some functions support array arguments or kwargs, denoted with square brackets
| val(`s`) | ✅ | varies | u, n | Return or set a Character or item value. Argument is a [Character/item aspect](#list-of-character-and-item-aspects).
These are all in one function for legacy reasons and are generally poorly tested. If you need one of them, consider porting it to a native math function.
Example:
`{ "math": [ "u_val('strength')", "=", "2" ] }`|
| vitamin(`s`/`v`) | ✅ | ✅ | u, n | Return or set the characters vitamin level.
Argument is vitamin ID.
Example:
`{ "math": [ "u_vitamin('mutagen')", "=", "0" ] }`|
| warmth(`s`/`v`) | ✅ | ❌ | u, n | Return the characters warmth on a body part.
Argument is bodypart ID.
Example:
The value displayed in-game is calculated as follows.
`"{ "math": [ "u_warmth_in_game", "=", "(u_warmth('torso') / 100) * 2 - 100"] }`|
+| vision_range() | ✅ | ❌ | u, n | Return the character's visual range, adjusted by their mutations, effects, and other issues.
Example:
`"{ "math": [ "u_vision_range", "<", "30"] }`|
| weather(`s`) | ✅ | ✅ | N/A
(global) | Return or set a weather aspect
Aspect must be one of:
`temperature` (in Kelvin),
`humidity` (as percentage),
`pressure` (in millibar),
`windpower` (in mph).
`precipitation` (in mm / h) either 0.5 (very_light ), 1.5 (light), or 3 (heavy). Read only.
Temperature conversion functions are available: `celsius()`, `fahrenheit()`, `from_celsius()`, and `from_fahrenheit()`.
Examples:
`{ "math": [ "weather('temperature')", "<", "from_fahrenheit( 33 )" ] }`
`{ "math": [ "fahrenheit( weather('temperature') )", "==", "21" ] }`|
| damage_level() | ✅ | ❌ | u, n | Return the damage level of the talker, which must be an item.
Example:
`"condition": { "math": [ "n_damage_level()", "<", "1" ] }`|
diff --git a/src/activity_handlers.cpp b/src/activity_handlers.cpp
index d25ee3f612bd4..0b0e99e297d68 100644
--- a/src/activity_handlers.cpp
+++ b/src/activity_handlers.cpp
@@ -180,12 +180,15 @@ 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_asocial_dissatisfied( "asocial_dissatisfied" );
static const efftype_id effect_bleed( "bleed" );
static const efftype_id effect_blind( "blind" );
static const efftype_id effect_controlled( "controlled" );
static const efftype_id effect_narcosis( "narcosis" );
static const efftype_id effect_pet( "pet" );
static const efftype_id effect_sleep( "sleep" );
+static const efftype_id effect_social_dissatisfied( "social_dissatisfied" );
+static const efftype_id effect_social_satisfied( "social_satisfied" );
static const efftype_id effect_under_operation( "under_operation" );
static const harvest_drop_type_id harvest_drop_blood( "blood" );
@@ -200,11 +203,15 @@ static const itype_id itype_burnt_out_bionic( "burnt_out_bionic" );
static const itype_id itype_muscle( "muscle" );
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" );
static const mongroup_id GROUP_FISH( "GROUP_FISH" );
@@ -533,6 +540,7 @@ static void set_up_butchery( player_activity &act, Character &you, butcher_type
}
}
+
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 ) );
@@ -1612,6 +1620,19 @@ void activity_handlers::mutant_tree_communion_do_turn( player_activity *act, Cha
}
if( one_in( 128 ) ) {
communioncycles += 1;
+ if( one_in( 256 ) ) {
+ if( you->has_effect( effect_social_dissatisfied ) ) {
+ you->remove_effect( effect_social_dissatisfied );
+ }
+ if( ( you->has_flag( json_flag_SOCIAL1 ) || you->has_flag( json_flag_SOCIAL2 ) ) &&
+ !you->has_effect( effect_social_satisfied ) ) {
+ you->add_effect( effect_social_satisfied, 3_hours, false, 1 );
+ }
+ if( ( you->has_flag( json_flag_ASOCIAL1 ) || you->has_flag( json_flag_ASOCIAL2 ) ) &&
+ !you->has_effect( effect_asocial_dissatisfied ) ) {
+ you->add_effect( effect_asocial_dissatisfied, 3_hours, false, 1 );
+ }
+ }
you->add_msg_if_player( "%s", SNIPPET.random_from_category( "mutant_tree_communion" ).value_or(
translation() ) );
you->add_morale( MORALE_TREE_COMMUNION, 4, 30, 18_hours, 8_hours );
@@ -3781,6 +3802,19 @@ void activity_handlers::tree_communion_do_turn( player_activity *act, Character
you->add_morale( MORALE_TREE_COMMUNION, 1, 15, 2_hours, 1_hours );
}
if( one_in( 128 ) ) {
+ if( one_in( 256 ) ) {
+ if( you->has_effect( effect_social_dissatisfied ) ) {
+ you->remove_effect( effect_social_dissatisfied );
+ }
+ if( ( you->has_flag( json_flag_SOCIAL1 ) || you->has_flag( json_flag_SOCIAL2 ) ) &&
+ !you->has_effect( effect_social_satisfied ) ) {
+ you->add_effect( effect_social_satisfied, 3_hours, false, 1 );
+ }
+ if( ( you->has_flag( json_flag_ASOCIAL1 ) || you->has_flag( json_flag_ASOCIAL2 ) ) &&
+ !you->has_effect( effect_asocial_dissatisfied ) ) {
+ you->add_effect( effect_asocial_dissatisfied, 3_hours, false, 1 );
+ }
+ }
you->add_msg_if_player( "%s", SNIPPET.random_from_category( "tree_communion" ).value_or(
translation() ) );
}
diff --git a/src/math_parser_diag.cpp b/src/math_parser_diag.cpp
index 8c13059baaf92..6afbc8239cf50 100644
--- a/src/math_parser_diag.cpp
+++ b/src/math_parser_diag.cpp
@@ -565,6 +565,107 @@ std::function mod_order_eval( char /* scope */,
};
}
+enum class character_filter : int {
+ allies = 0,
+ not_allies,
+ hostile,
+ any,
+};
+
+bool _friend_match_filter_character( Character const &beta, Character const &guy,
+ character_filter filter )
+{
+ switch( filter ) {
+ case character_filter::allies:
+ return guy.is_ally( beta );
+ case character_filter::not_allies:
+ return !guy.is_ally( beta );
+ case character_filter::hostile:
+ return guy.attitude_to( beta ) == Creature::Attitude::HOSTILE ||
+ ( beta.is_avatar() && guy.is_npc() && guy.as_npc()->guaranteed_hostile() );
+ case character_filter::any:
+ return true;
+ }
+ return false;
+}
+
+bool _filter_character( Character const &beta, Character const &guy, int radius,
+ tripoint_abs_ms const &loc, character_filter filter, bool allow_hallucinations )
+{
+ if( ( !guy.is_hallucination() || allow_hallucinations ) && ( beta.getID() != guy.getID() ) ) {
+ return _friend_match_filter_character( beta, guy, filter ) &&
+ radius >= rl_dist( guy.get_location(), loc );
+ }
+ return false;
+}
+
+std::function _characters_nearby_eval( char scope,
+ std::vector const ¶ms, diag_kwargs const &kwargs )
+{
+ diag_value radius_val( 1000.0 );
+ diag_value filter_val( std::string{ "any" } );
+ diag_value allow_hallucinations_val( 0.0 );
+ std::optional loc_var;
+ if( kwargs.count( "radius" ) != 0 ) {
+ radius_val = *kwargs.at( "radius" );
+ }
+ if( kwargs.count( "attitude" ) != 0 ) {
+ filter_val = *kwargs.at( "attitude" );
+ }
+ if( kwargs.count( "allow_hallucinations" ) != 0 ) {
+ allow_hallucinations_val = *kwargs.at( "allow_hallucinations" );
+ }
+ if( kwargs.count( "location" ) != 0 ) {
+ loc_var = kwargs.at( "location" )->var();
+ } else if( scope == 'g' ) {
+ throw std::invalid_argument( string_format(
+ R"("characters_nearby" needs either an actor scope (u/n) or a 'location' kwarg)" ) );
+ }
+
+ return [beta = is_beta( scope ), params, loc_var, filter_val, radius_val,
+ allow_hallucinations_val ]( dialogue & d ) {
+ tripoint_abs_ms loc;
+ if( loc_var.has_value() ) {
+ loc = get_tripoint_from_var( loc_var, d );
+ } else {
+ loc = d.actor( beta )->global_pos();
+ }
+
+ int const radius = static_cast( radius_val.dbl( d ) );
+ std::string const filter_str = filter_val.str( d );
+ character_filter filter = character_filter::any;
+ if( filter_str == "allies" ) {
+ filter = character_filter::allies;
+ } else if( filter_str == "not_allies" ) {
+ filter = character_filter::not_allies;
+ } else if( filter_str == "hostile" ) {
+ filter = character_filter::hostile;
+ } else if( filter_str != "any" ) {
+ debugmsg( R"(Unknown attitude filter "%s" for characters_nearby(), counting all characters)",
+ filter_str );
+ }
+ bool allow_hallucinations = false;
+ int const hallucinations_int = static_cast( allow_hallucinations_val.dbl( d ) );
+ if( hallucinations_int != 0 ) {
+ allow_hallucinations = true;
+ }
+
+ std::vector const targets = g->get_characters_if( [ &beta, &d, &radius,
+ &loc, filter, allow_hallucinations ]( const Character & guy ) {
+ return _filter_character( *d.actor( beta )->get_character(), guy, radius, loc, filter,
+ allow_hallucinations );
+ } );
+ return static_cast( targets.size() );
+ };
+}
+
+std::function characters_nearby_eval( char scope,
+ std::vector const ¶ms, diag_kwargs const &kwargs )
+{
+ return _characters_nearby_eval( scope, params, kwargs );
+}
+
+
template
using f_monster_match = bool ( * )( Creature const &critter, ID const &id );
@@ -1143,6 +1244,20 @@ std::function value_or_eval( char /* scope */,
};
}
+std::function vision_range_eval( char scope,
+ std::vector const &/* params */, diag_kwargs const &/* kwargs */ )
+{
+ return[beta = is_beta( scope )]( dialogue const & d ) {
+ if( d.actor( beta )->get_character() ) {
+ return static_cast( d.actor( beta ) )
+ ->get_character()
+ ->unimpaired_range();
+ }
+ return 0;
+ };
+}
+
+
std::function vitamin_eval( char scope,
std::vector const ¶ms, diag_kwargs const &/* kwargs */ )
{
@@ -1246,6 +1361,7 @@ std::map const dialogue_eval_f{
{ "addiction_turns", { "un", 1, addiction_turns_eval } },
{ "armor", { "un", 2, armor_eval } },
{ "attack_speed", { "un", 0, attack_speed_eval } },
+ { "characters_nearby", { "ung", 0, characters_nearby_eval } },
{ "charge_count", { "un", 1, charge_count_eval } },
{ "coverage", { "un", 1, coverage_eval } },
{ "damage_level", { "un", 0, damage_level_eval } },
@@ -1289,6 +1405,7 @@ std::map const dialogue_eval_f{
{ "proficiency", { "un", 1, proficiency_eval } },
{ "val", { "un", 1, u_val } },
{ "value_or", { "g", 2, value_or_eval } },
+ { "vision_range", { "un", 0, vision_range_eval } },
{ "vitamin", { "un", 1, vitamin_eval } },
{ "warmth", { "un", 1, warmth_eval } },
{ "weather", { "g", 1, weather_eval } },
@@ -1324,4 +1441,4 @@ std::map const &get_all_diag_eval_funcs()
std::map const &get_all_diag_ass_funcs()
{
return dialogue_assign_f;
-}
+}
\ No newline at end of file
diff --git a/src/monattack.cpp b/src/monattack.cpp
index 315343aa2f4d0..64ffae9771e68 100644
--- a/src/monattack.cpp
+++ b/src/monattack.cpp
@@ -136,6 +136,7 @@ static const efftype_id effect_raising( "raising" );
static const efftype_id effect_rat( "rat" );
static const efftype_id effect_shrieking( "shrieking" );
static const efftype_id effect_slimed( "slimed" );
+static const efftype_id effect_social_dissatisfied( "social_dissatisfied" );
static const efftype_id effect_stunned( "stunned" );
static const efftype_id effect_targeted( "targeted" );
@@ -4616,6 +4617,11 @@ bool mattack::slimespring( monster *z )
add_msg( m_good, "%s", SNIPPET.random_from_category( "slime_cheers" ).value_or( translation() ) );
player_character.add_morale( MORALE_SUPPORT, 10, 50 );
}
+ // They will stave off loneliness, but aren't a substitute for real friends.
+ if( player_character.has_effect( effect_social_dissatisfied ) ) {
+ add_msg( m_good, "%s", SNIPPET.random_from_category( "slime_cheers" ).value_or( translation() ) );
+ player_character.remove_effect( effect_social_dissatisfied );
+ }
if( rl_dist( z->pos(), player_character.pos() ) <= 3 && z->sees( player_character ) ) {
if( player_character.has_effect( effect_bleed ) ||
player_character.has_effect( effect_bite ) ) {