Cataclysm-DDA/src/mutation_data.cpp

1153 lines
42 KiB
C++

#include "mutation.h" // IWYU pragma: associated
#include <cstdlib>
#include <map>
#include <memory>
#include <set>
#include <stdexcept>
#include <type_traits>
#include <vector>
#include "assign.h"
#include "color.h"
#include "condition.h"
#include "debug.h"
#include "effect_on_condition.h"
#include "enum_conversions.h"
#include "enums.h"
#include "generic_factory.h"
#include "json.h"
#include "localized_comparator.h"
#include "make_static.h"
#include "memory_fast.h"
#include "string_formatter.h"
#include "trait_group.h"
#include "translations.h"
static const mutation_category_id mutation_category_ANY( "ANY" );
static const trait_group::Trait_group_tag Trait_group_EMPTY_GROUP( "EMPTY_GROUP" );
using TraitGroupMap =
std::map<trait_group::Trait_group_tag, shared_ptr_fast<Trait_group>>;
using TraitSet = std::set<trait_id>;
using trait_reader = string_id_reader<::mutation_branch>;
using flag_reader = string_id_reader<::json_flag>;
static TraitSet trait_blacklist;
static TraitGroupMap trait_groups;
static std::map<trait_id, trait_replacement> trait_migrations;
static trait_replacement trait_migration_remove{ cata::nullopt, cata::nullopt, true };
namespace
{
generic_factory<mutation_branch> trait_factory( "trait" );
} // namespace
std::vector<dream> dreams;
std::map<mutation_category_id, std::vector<trait_id> > mutations_category;
static std::map<mutation_category_id, mutation_category_trait> mutation_category_traits;
template<>
const mutation_branch &string_id<mutation_branch>::obj() const
{
return trait_factory.obj( *this );
}
template<>
bool string_id<mutation_branch>::is_valid() const
{
return trait_factory.is_valid( *this );
}
template<>
bool string_id<Trait_group>::is_valid() const
{
return trait_groups.count( *this );
}
static void extract_mod(
const JsonObject &j, std::unordered_map<std::pair<bool, std::string>, int, cata::tuple_hash> &data,
const std::string &mod_type, bool active, const std::string &type_key )
{
int val = j.get_int( mod_type, 0 );
if( val != 0 ) {
data[std::make_pair( active, type_key )] = val;
}
}
static void load_mutation_mods(
const JsonObject &jsobj, const std::string &member,
std::unordered_map<std::pair<bool, std::string>, int, cata::tuple_hash> &mods )
{
if( jsobj.has_object( member ) ) {
JsonObject j = jsobj.get_object( member );
bool active = false;
if( member == "active_mods" ) {
active = true;
}
// json field type key
extract_mod( j, mods, "str_mod", active, "STR" );
extract_mod( j, mods, "dex_mod", active, "DEX" );
extract_mod( j, mods, "per_mod", active, "PER" );
extract_mod( j, mods, "int_mod", active, "INT" );
}
}
void mutation_category_trait::load( const JsonObject &jsobj )
{
mutation_category_trait new_category;
jsobj.read( "id", new_category.id, true );
jsobj.get_member( "name" ).read( new_category.raw_name );
new_category.threshold_mut = trait_id( jsobj.get_string( "threshold_mut" ) );
jsobj.get_member( "mutagen_message" ).read( new_category.raw_mutagen_message );
new_category.wip = jsobj.get_bool( "wip", false );
new_category.skip_test = jsobj.get_bool( "skip_test", false );
static_cast<void>( translate_marker_context( "memorial_male", "Crossed a threshold" ) );
static_cast<void>( translate_marker_context( "memorial_female", "Crossed a threshold" ) );
optional( jsobj, false, "memorial_message", new_category.raw_memorial_message,
text_style_check_reader(), "Crossed a threshold" );
new_category.vitamin = vitamin_id( jsobj.get_string( "vitamin", "null" ) );
mutation_category_traits[new_category.id] = new_category;
}
std::string mutation_category_trait::name() const
{
return raw_name.translated();
}
std::string mutation_category_trait::mutagen_message() const
{
return raw_mutagen_message.translated();
}
std::string mutation_category_trait::memorial_message_male() const
{
return pgettext( "memorial_male", raw_memorial_message.c_str() );
}
std::string mutation_category_trait::memorial_message_female() const
{
return pgettext( "memorial_female", raw_memorial_message.c_str() );
}
const std::map<mutation_category_id, mutation_category_trait> &mutation_category_trait::get_all()
{
return mutation_category_traits;
}
const mutation_category_trait &mutation_category_trait::get_category(
const mutation_category_id &category_id )
{
return mutation_category_traits.find( category_id )->second;
}
void mutation_category_trait::reset()
{
mutation_category_traits.clear();
}
void mutation_category_trait::check_consistency()
{
for( const auto &pr : mutation_category_traits ) {
const mutation_category_trait &cat = pr.second;
if( !cat.threshold_mut.is_empty() && !cat.threshold_mut.is_valid() ) {
debugmsg( "Mutation category %s has threshold mutation %s, which does not exist",
cat.id.c_str(), cat.threshold_mut.c_str() );
}
}
}
static mut_attack load_mutation_attack( const JsonObject &jo )
{
mut_attack ret;
jo.read( "attack_text_u", ret.attack_text_u );
jo.read( "attack_text_npc", ret.attack_text_npc );
jo.read( "required_mutations", ret.required_mutations );
jo.read( "blocker_mutations", ret.blocker_mutations );
jo.read( "hardcoded_effect", ret.hardcoded_effect );
if( jo.has_string( "body_part" ) ) {
ret.bp = bodypart_str_id( jo.get_string( "body_part" ) );
}
jo.read( "chance", ret.chance );
if( jo.has_array( "base_damage" ) ) {
ret.base_damage = load_damage_instance( jo.get_array( "base_damage" ) );
} else if( jo.has_object( "base_damage" ) ) {
JsonObject jo_dam = jo.get_object( "base_damage" );
ret.base_damage = load_damage_instance( jo_dam );
}
if( jo.has_array( "strength_damage" ) ) {
ret.strength_damage = load_damage_instance( jo.get_array( "strength_damage" ) );
} else if( jo.has_object( "strength_damage" ) ) {
JsonObject jo_dam = jo.get_object( "strength_damage" );
ret.strength_damage = load_damage_instance( jo_dam );
}
if( ret.attack_text_u.empty() || ret.attack_text_npc.empty() ) {
jo.throw_error( "Attack message unset" );
}
if( !ret.hardcoded_effect && ret.base_damage.empty() && ret.strength_damage.empty() ) {
jo.throw_error( "Damage unset" );
} else if( ret.hardcoded_effect && ( !ret.base_damage.empty() || !ret.strength_damage.empty() ) ) {
jo.throw_error( "Damage and hardcoded effect are both set (must be one, not both)" );
}
if( ret.chance <= 0 ) {
jo.throw_error( "Chance of procing must be set and positive" );
}
return ret;
}
static social_modifiers load_mutation_social_mods( const JsonObject &jo )
{
social_modifiers ret;
jo.read( "lie", ret.lie );
jo.read( "persuade", ret.persuade );
jo.read( "intimidate", ret.intimidate );
return ret;
}
void mutation_branch::load_trait( const JsonObject &jo, const std::string &src )
{
trait_factory.load( jo, src );
}
mut_transform::mut_transform() = default;
bool mut_transform::load( const JsonObject &jsobj, const std::string &member )
{
JsonObject j = jsobj.get_object( member );
assign( j, "target", target );
assign( j, "msg_transform", msg_transform );
assign( j, "active", active );
assign( j, "moves", moves );
return true;
}
void reflex_activation_data::load( const JsonObject &jsobj )
{
read_condition<dialogue>( jsobj, "condition", trigger, false );
if( jsobj.has_object( "msg_on" ) ) {
JsonObject jo = jsobj.get_object( "msg_on" );
optional( jo, was_loaded, "text", msg_on.first );
std::string tmp_rating;
optional( jo, was_loaded, "rating", tmp_rating, "neutral" );
msg_on.second = io::string_to_enum<game_message_type>( tmp_rating );
}
if( jsobj.has_object( "msg_off" ) ) {
JsonObject jo = jsobj.get_object( "msg_off" );
optional( jo, was_loaded, "text", msg_off.first );
std::string tmp_rating;
optional( jo, was_loaded, "rating", tmp_rating, "neutral" );
msg_off.second = io::string_to_enum<game_message_type>( tmp_rating );
}
}
void reflex_activation_data::deserialize( const JsonObject &jo )
{
load( jo );
}
void trait_and_var::deserialize( const JsonValue &jv )
{
if( jv.test_string() ) {
jv.read( trait );
} else {
JsonObject jo = jv.get_object();
jo.read( "trait", trait );
jo.read( "variant", variant );
}
}
void trait_and_var::serialize( JsonOut &jsout ) const
{
jsout.start_object();
jsout.member( "trait", trait );
jsout.member( "variant", variant );
jsout.end_object();
}
std::string trait_and_var::name() const
{
return trait->name( variant );
}
std::string trait_and_var::desc() const
{
return trait->desc( variant );
}
void mutation_variant::load( const JsonObject &jo )
{
mandatory( jo, false, "id", id );
mandatory( jo, false, "name", alt_name );
mandatory( jo, false, "description", alt_description );
optional( jo, false, "append_desc", append_desc );
optional( jo, false, "weight", weight );
}
void mutation_variant::deserialize( const JsonObject &jo )
{
load( jo );
}
void mutation_branch::load( const JsonObject &jo, const std::string & )
{
mandatory( jo, was_loaded, "name", raw_name );
mandatory( jo, was_loaded, "description", raw_desc );
mandatory( jo, was_loaded, "points", points );
optional( jo, was_loaded, "vitamin_cost", vitamin_cost, 100 );
optional( jo, was_loaded, "visibility", visibility, 0 );
optional( jo, was_loaded, "ugliness", ugliness, 0 );
optional( jo, was_loaded, "starting_trait", startingtrait, false );
optional( jo, was_loaded, "mixed_effect", mixed_effect, false );
optional( jo, was_loaded, "active", activated, false );
optional( jo, was_loaded, "starts_active", starts_active, false );
optional( jo, was_loaded, "destroys_gear", destroys_gear, false );
optional( jo, was_loaded, "allow_soft_gear", allow_soft_gear, false );
optional( jo, was_loaded, "cost", cost, 0 );
optional( jo, was_loaded, "time", cooldown, 0_turns );
optional( jo, was_loaded, "kcal", hunger, false );
optional( jo, was_loaded, "thirst", thirst, false );
optional( jo, was_loaded, "fatigue", fatigue, false );
optional( jo, was_loaded, "valid", valid, true );
optional( jo, was_loaded, "purifiable", purifiable, true );
std::vector<mutation_variant> _variants;
optional( jo, was_loaded, "variants", _variants );
for( mutation_variant &var : _variants ) {
var.parent = id;
variants.emplace( var.id, var );
}
if( jo.has_object( "spawn_item" ) ) {
JsonObject si = jo.get_object( "spawn_item" );
optional( si, was_loaded, "type", spawn_item );
optional( si, was_loaded, "message", raw_spawn_item_message );
}
if( jo.has_object( "ranged_mutation" ) ) {
JsonObject si = jo.get_object( "ranged_mutation" );
optional( si, was_loaded, "type", ranged_mutation );
optional( si, was_loaded, "message", raw_ranged_mutation_message );
}
if( jo.has_object( "transform" ) ) {
transform = cata::make_value<mut_transform>();
transform->load( jo, "transform" );
}
optional( jo, was_loaded, "triggers", trigger_list );
optional( jo, was_loaded, "initial_ma_styles", initial_ma_styles );
if( jo.has_array( "bodytemp_modifiers" ) ) {
JsonArray bodytemp_array = jo.get_array( "bodytemp_modifiers" );
bodytemp_min = bodytemp_array.get_int( 0 );
bodytemp_max = bodytemp_array.get_int( 1 );
}
optional( jo, was_loaded, "bodytemp_sleep", bodytemp_sleep, 0 );
optional( jo, was_loaded, "threshold", threshold, false );
optional( jo, was_loaded, "profession", profession, false );
optional( jo, was_loaded, "debug", debug, false );
optional( jo, was_loaded, "player_display", player_display, true );
optional( jo, was_loaded, "vanity", vanity, false );
optional( jo, was_loaded, "dummy", dummy, false );
for( JsonArray pair : jo.get_array( "vitamin_rates" ) ) {
vitamin_rates.emplace( vitamin_id( pair.get_string( 0 ) ),
time_duration::from_turns( pair.get_int( 1 ) ) );
}
for( JsonArray pair : jo.get_array( "vitamins_absorb_multi" ) ) {
std::map<vitamin_id, double> vit;
// fill the inner map with vitamins
for( JsonArray vitamins : pair.get_array( 1 ) ) {
vit.emplace( vitamin_id( vitamins.get_string( 0 ) ), vitamins.get_float( 1 ) );
}
// assign the inner vitamin map to the material_id key
vitamin_absorb_multi.emplace( material_id( pair.get_string( 0 ) ), vit );
}
optional( jo, was_loaded, "healing_awake", healing_awake, cata::nullopt );
optional( jo, was_loaded, "healing_resting", healing_resting, cata::nullopt );
optional( jo, was_loaded, "mending_modifier", mending_modifier, cata::nullopt );
optional( jo, was_loaded, "hp_modifier", hp_modifier, cata::nullopt );
optional( jo, was_loaded, "hp_modifier_secondary", hp_modifier_secondary, cata::nullopt );
optional( jo, was_loaded, "hp_adjustment", hp_adjustment, cata::nullopt );
optional( jo, was_loaded, "stealth_modifier", stealth_modifier, cata::nullopt );
optional( jo, was_loaded, "str_modifier", str_modifier, cata::nullopt );
optional( jo, was_loaded, "cut_dmg_bonus", cut_dmg_bonus, 0 );
optional( jo, was_loaded, "pierce_dmg_bonus", pierce_dmg_bonus, 0.0f );
optional( jo, was_loaded, "bash_dmg_bonus", bash_dmg_bonus, 0 );
optional( jo, was_loaded, "dodge_modifier", dodge_modifier, cata::nullopt );
optional( jo, was_loaded, "movecost_modifier", movecost_modifier, cata::nullopt );
optional( jo, was_loaded, "movecost_flatground_modifier", movecost_flatground_modifier,
cata::nullopt );
optional( jo, was_loaded, "movecost_obstacle_modifier", movecost_obstacle_modifier, cata::nullopt );
optional( jo, was_loaded, "movecost_swim_modifier", movecost_swim_modifier, cata::nullopt );
optional( jo, was_loaded, "attackcost_modifier", attackcost_modifier, cata::nullopt );
optional( jo, was_loaded, "cardio_multiplier", cardio_multiplier, cata::nullopt );
optional( jo, was_loaded, "weight_capacity_modifier", weight_capacity_modifier, cata::nullopt );
optional( jo, was_loaded, "hearing_modifier", hearing_modifier, cata::nullopt );
optional( jo, was_loaded, "noise_modifier", noise_modifier, cata::nullopt );
optional( jo, was_loaded, "temperature_speed_modifier", temperature_speed_modifier, cata::nullopt );
optional( jo, was_loaded, "metabolism_modifier", metabolism_modifier, cata::nullopt );
optional( jo, was_loaded, "thirst_modifier", thirst_modifier, cata::nullopt );
optional( jo, was_loaded, "fatigue_modifier", fatigue_modifier, cata::nullopt );
optional( jo, was_loaded, "fatigue_regen_modifier", fatigue_regen_modifier, cata::nullopt );
optional( jo, was_loaded, "stamina_regen_modifier", stamina_regen_modifier, cata::nullopt );
optional( jo, was_loaded, "obtain_cost_multiplier", obtain_cost_multiplier, cata::nullopt );
optional( jo, was_loaded, "stomach_size_multiplier", stomach_size_multiplier, cata::nullopt );
optional( jo, was_loaded, "vomit_multiplier", vomit_multiplier, cata::nullopt );
optional( jo, was_loaded, "sweat_multiplier", sweat_multiplier, cata::nullopt );
optional( jo, was_loaded, "overmap_sight", overmap_sight, cata::nullopt );
optional( jo, was_loaded, "overmap_multiplier", overmap_multiplier, cata::nullopt );
optional( jo, was_loaded, "reading_speed_multiplier", reading_speed_multiplier, cata::nullopt );
optional( jo, was_loaded, "skill_rust_multiplier", skill_rust_multiplier, cata::nullopt );
optional( jo, was_loaded, "consume_time_modifier", consume_time_modifier, cata::nullopt );
optional( jo, was_loaded, "scent_modifier", scent_modifier, 1.0f );
optional( jo, was_loaded, "scent_intensity", scent_intensity, cata::nullopt );
optional( jo, was_loaded, "scent_mask", scent_mask, cata::nullopt );
optional( jo, was_loaded, "scent_type", scent_typeid, cata::nullopt );
optional( jo, was_loaded, "bleed_resist", bleed_resist, 0 );
optional( jo, was_loaded, "healthy_rate", healthy_rate, 1.0f );
optional( jo, was_loaded, "fat_to_max_hp", fat_to_max_hp, 0.0f );
optional( jo, was_loaded, "weakness_to_water", weakness_to_water, 0 );
optional( jo, was_loaded, "ignored_by", ignored_by );
optional( jo, was_loaded, "can_only_eat", can_only_eat );
optional( jo, was_loaded, "can_only_heal_with", can_only_heal_with );
optional( jo, was_loaded, "can_heal_with", can_heal_with );
optional( jo, was_loaded, "butchering_quality", butchering_quality, 0 );
optional( jo, was_loaded, "allowed_category", allowed_category );
optional( jo, was_loaded, "crafting_speed_multiplier", crafting_speed_multiplier );
optional( jo, was_loaded, "mana_modifier", mana_modifier, cata::nullopt );
optional( jo, was_loaded, "mana_multiplier", mana_multiplier, cata::nullopt );
optional( jo, was_loaded, "mana_regen_multiplier", mana_regen_multiplier, cata::nullopt );
optional( jo, was_loaded, "bionic_mana_penalty", bionic_mana_penalty, cata::nullopt );
optional( jo, was_loaded, "casting_time_multiplier", casting_time_multiplier, cata::nullopt );
if( jo.has_object( "rand_cut_bonus" ) ) {
JsonObject sm = jo.get_object( "rand_cut_bonus" );
rand_cut_bonus.first = sm.get_int( "min" );
rand_cut_bonus.second = sm.get_int( "max" );
}
if( jo.has_object( "rand_bash_bonus" ) ) {
JsonObject sm = jo.get_object( "rand_bash_bonus" );
rand_bash_bonus.first = sm.get_int( "min" );
rand_bash_bonus.second = sm.get_int( "max" );
}
if( jo.has_object( "social_modifiers" ) ) {
JsonObject sm = jo.get_object( "social_modifiers" );
social_mods = load_mutation_social_mods( sm );
}
load_mutation_mods( jo, "passive_mods", mods );
/* Not currently supported due to inability to save active mutation state
load_mutation_mods(jsobj, "active_mods", new_mut.mods); */
optional( jo, was_loaded, "prereqs", prereqs, trait_reader{} );
optional( jo, was_loaded, "prereqs2", prereqs2, trait_reader{} );
optional( jo, was_loaded, "threshreq", threshreq, trait_reader{} );
optional( jo, was_loaded, "cancels", cancels, trait_reader{} );
optional( jo, was_loaded, "changes_to", replacements, trait_reader{} );
optional( jo, was_loaded, "leads_to", additions, trait_reader{} );
optional( jo, was_loaded, "flags", flags, flag_reader{} );
optional( jo, was_loaded, "active_flags", active_flags, flag_reader{} );
optional( jo, was_loaded, "inactive_flags", inactive_flags, flag_reader{} );
optional( jo, was_loaded, "types", types, string_reader{} );
for( JsonArray pair : jo.get_array( "moncams" ) ) {
moncams.insert( std::pair<mtype_id, int>( mtype_id( pair.get_string( 0 ) ), pair.get_int( 1 ) ) );
}
for( JsonValue jv : jo.get_array( "activated_eocs" ) ) {
activated_eocs.push_back( effect_on_conditions::load_inline_eoc( jv, "" ) );
}
for( JsonValue jv : jo.get_array( "deactivated_eocs" ) ) {
deactivated_eocs.push_back( effect_on_conditions::load_inline_eoc( jv, "" ) );
}
int enchant_num = 0;
for( JsonValue jv : jo.get_array( "enchantments" ) ) {
std::string enchant_name = "INLINE_ENCH_" + raw_name + "_" + std::to_string( enchant_num++ );
enchantments.push_back( enchantment::load_inline_enchantment( jv, "", enchant_name ) );
}
for( const std::string s : jo.get_array( "no_cbm_on_bp" ) ) {
no_cbm_on_bp.emplace( bodypart_str_id( s ) );
}
optional( jo, was_loaded, "category", category, string_id_reader<mutation_category_trait> {} );
for( JsonArray ja : jo.get_array( "spells_learned" ) ) {
const spell_id sp( ja.next_string() );
spells_learned.emplace( sp, ja.next_int() );
}
for( JsonArray ja : jo.get_array( "craft_skill_bonus" ) ) {
const skill_id skid( ja.next_string() );
if( skid.is_valid() ) {
craft_skill_bonus.emplace( skid, ja.next_int() );
} else {
jo.throw_error( "invalid skill_id" );
}
}
for( JsonArray ja : jo.get_array( "lumination" ) ) {
const bodypart_str_id bp = bodypart_str_id( ja.next_string() );
lumination.emplace( bp, static_cast<float>( ja.next_float() ) );
}
for( JsonArray ja : jo.get_array( "anger_relations" ) ) {
const species_id spe = species_id( ja.next_string() );
anger_relations.emplace( spe, ja.next_int() );
}
for( JsonObject wp : jo.get_array( "wet_protection" ) ) {
int ignored = wp.get_int( "ignored", 0 );
int neutral = wp.get_int( "neutral", 0 );
int good = wp.get_int( "good", 0 );
tripoint protect = tripoint( ignored, neutral, good );
protection[bodypart_str_id( wp.get_string( "part" ) )] = protect;
}
for( JsonArray ea : jo.get_array( "encumbrance_always" ) ) {
const bodypart_str_id bp = bodypart_str_id( ea.next_string() );
const int enc = ea.next_int();
encumbrance_always[bp] = enc;
}
for( JsonArray ec : jo.get_array( "encumbrance_covered" ) ) {
const bodypart_str_id bp = bodypart_str_id( ec.next_string() );
int enc = ec.next_int();
encumbrance_covered[bp] = enc;
}
for( JsonMember ema : jo.get_object( "encumbrance_multiplier_always" ) ) {
const bodypart_str_id &bp = bodypart_str_id( ema.name() );
encumbrance_multiplier_always[bp] = ema.get_float();
}
for( const std::string line : jo.get_array( "restricts_gear" ) ) {
bodypart_str_id bp( line );
if( bp.is_valid() ) {
restricts_gear.insert( bp );
} else {
restricts_gear_subparts.insert( sub_bodypart_str_id( line ) );
}
}
for( const std::string line : jo.get_array( "allowed_items" ) ) {
allowed_items.insert( flag_id( line ) );
}
for( JsonObject ao : jo.get_array( "armor" ) ) {
const resistances res = load_resistances_instance( ao );
// Set damage resistances for all body parts of the specified type(s)
for( const std::string &type_string : ao.get_tags( "part_types" ) ) {
for( const body_part_type &bp : body_part_type::get_all() ) {
if( type_string == "ALL" ) {
armor[bp.id] += res;
} else if( bp.limbtypes.count( io::string_to_enum<body_part_type::type>( type_string ) ) > 0 ) {
armor[bp.id] += res * bp.limbtypes.at( io::string_to_enum<body_part_type::type>( type_string ) );
}
}
}
// Set damage resistances for specific body parts
for( const std::string &part_string : ao.get_tags( "parts" ) ) {
armor[bodypart_str_id( part_string )] += res;
}
}
for( const JsonValue jv : jo.get_array( "integrated_armor" ) ) {
integrated_armor.emplace_back( itype_id( jv ) );
}
for( JsonMember member : jo.get_object( "bionic_slot_bonuses" ) ) {
bionic_slot_bonuses[bodypart_str_id( member.name() )] = member.get_int();
}
if( jo.has_array( "attacks" ) ) {
for( JsonObject ao : jo.get_array( "attacks" ) ) {
attacks_granted.emplace_back( load_mutation_attack( ao ) );
}
} else if( jo.has_object( "attacks" ) ) {
JsonObject attack = jo.get_object( "attacks" );
attacks_granted.emplace_back( load_mutation_attack( attack ) );
}
}
int mutation_branch::bionic_slot_bonus( const bodypart_str_id &part ) const
{
const auto iter = bionic_slot_bonuses.find( part );
if( iter == bionic_slot_bonuses.end() ) {
return 0;
} else {
return iter->second;
}
}
std::string mutation_branch::spawn_item_message() const
{
return raw_spawn_item_message.translated();
}
std::string mutation_branch::ranged_mutation_message() const
{
return raw_ranged_mutation_message.translated();
}
std::string mutation_branch::name( const std::string &variant ) const
{
auto variter = variants.find( variant );
if( variant.empty() || variter == variants.end() ) {
return raw_name.translated();
}
return variter->second.alt_name.translated();
}
std::string mutation_branch::desc( const std::string &variant ) const
{
auto variter = variants.find( variant );
if( variant.empty() || variter == variants.end() ) {
return raw_desc.translated();
}
if( variter->second.append_desc ) {
return string_format( "%s %s", raw_desc.translated(),
variter->second.alt_description.translated() );
}
return variter->second.alt_description.translated();
}
static bool has_cyclic_dependency( const trait_id &mid, std::vector<trait_id> already_visited )
{
if( contains_trait( already_visited, mid ) ) {
return true; // Circular dependency was found.
}
already_visited.push_back( mid );
// Perform Depth-first search
for( const trait_id &current_mutation : mid->prereqs ) {
if( has_cyclic_dependency( current_mutation->id, already_visited ) ) {
return true;
}
}
for( const trait_id &current_mutation : mid->prereqs2 ) {
if( has_cyclic_dependency( current_mutation->id, already_visited ) ) {
return true;
}
}
return false;
}
static void check_has_cyclic_dependency( const trait_id &mid )
{
std::vector<trait_id> already_visited;
if( has_cyclic_dependency( mid, already_visited ) ) {
debugmsg( "mutation %s references itself in either of its prerequsition branches. The program will crash if the player gains this mutation.",
mid.c_str() );
}
}
static void check_consistency( const std::vector<trait_id> &mvec, const trait_id &mid,
const std::string &what )
{
for( const auto &m : mvec ) {
if( !m.is_valid() ) {
debugmsg( "mutation %s refers to undefined %s %s", mid.c_str(), what.c_str(), m.c_str() );
}
if( m == mid ) {
debugmsg( "mutation %s refers to itself in %s context. The program will crash if the player gains this mutation.",
mid.c_str(), what.c_str() );
}
}
}
void mutation_branch::check_consistency()
{
for( const mutation_branch &mdata : get_all() ) {
const trait_id &mid = mdata.id;
const cata::optional<scenttype_id> &s_id = mdata.scent_typeid;
const std::map<species_id, int> &an_id = mdata.anger_relations;
for( const auto &style : mdata.initial_ma_styles ) {
if( !style.is_valid() ) {
debugmsg( "mutation %s refers to undefined martial art style %s", mid.c_str(), style.c_str() );
}
}
for( const std::string &type : mdata.types ) {
if( !mutation_type_exists( type ) ) {
debugmsg( "mutation %s refers to undefined mutation type %s", mid.c_str(), type );
}
}
if( mid->transform ) {
const trait_id tid = mid->transform->target;
if( !tid.is_valid() ) {
debugmsg( "mutation %s transform uses undefined target %s", mid.c_str(), tid.c_str() );
}
}
for( const std::pair<species_id, int> elem : an_id ) {
if( !elem.first.is_valid() ) {
debugmsg( "mutation %s refers to undefined species id %s", mid.c_str(), elem.first.c_str() );
}
}
if( s_id && !s_id.value().is_valid() ) {
debugmsg( "mutation %s refers to undefined scent type %s", mid.c_str(), s_id.value().c_str() );
}
for( const trait_id &replacement : mdata.replacements ) {
const mutation_branch &rdata = replacement.obj();
for( const mutation_category_id &cat : rdata.category ) {
if( std::find( mdata.category.begin(), mdata.category.end(), cat ) == mdata.category.end() ) {
debugmsg( "mutation %s lacks category %s present in replacement mutation %s", mid.c_str(),
cat.c_str(), replacement.c_str() );
}
}
}
for( const trait_id &addition : mdata.additions ) {
if( mdata.category.empty() ) {
break;
}
const mutation_branch &adata = addition.obj();
bool found = false;
for( const mutation_category_id &cat : adata.category ) {
found = found ||
std::find( mdata.category.begin(), mdata.category.end(), cat ) != mdata.category.end();
}
if( !found ) {
debugmsg( "categories in mutation %s don't match any category present in additive mutation %s",
mid.c_str(), addition.c_str() );
}
}
for( const mutation_category_id &cat_id : mdata.category ) {
if( !mdata.prereqs.empty() ) {
bool found = false;
for( const trait_id &prereq_id : mdata.prereqs ) {
const mutation_branch &prereq = prereq_id.obj();
found = found ||
std::find( prereq.category.begin(), prereq.category.end(), cat_id ) != prereq.category.end();
}
if( !found ) {
debugmsg( "mutation %s is in category %s but none of its slot 1 prereqs have this category",
mid.c_str(), cat_id.c_str() );
}
}
if( !mdata.prereqs2.empty() ) {
bool found = false;
for( const trait_id &prereq_id : mdata.prereqs2 ) {
const mutation_branch &prereq = prereq_id.obj();
found = found ||
std::find( prereq.category.begin(), prereq.category.end(), cat_id ) != prereq.category.end();
}
if( !found ) {
debugmsg( "mutation %s is in category %s but none of its slot 2 prereqs have this category",
mid.c_str(), cat_id.c_str() );
}
}
}
// We need to display active mutations in the UI.
if( mdata.activated && !mdata.player_display ) {
debugmsg( "mutation %s is not displayed but set as active" );
}
check_has_cyclic_dependency( mid );
::check_consistency( mdata.prereqs, mid, "prereqs" );
::check_consistency( mdata.prereqs2, mid, "prereqs2" );
::check_consistency( mdata.threshreq, mid, "threshreq" );
::check_consistency( mdata.cancels, mid, "cancels" );
::check_consistency( mdata.replacements, mid, "replacements" );
::check_consistency( mdata.additions, mid, "additions" );
}
}
nc_color mutation_branch::get_display_color() const
{
if( flags.count( STATIC( json_character_flag( "ATTUNEMENT" ) ) ) ) {
return c_green;
} else if( threshold || profession ) {
return c_white;
} else if( debug ) {
return c_light_cyan;
} else if( mixed_effect ) {
return c_pink;
} else if( points > 0 ) {
return c_light_green;
} else if( points < 0 ) {
return c_light_red;
} else {
return c_yellow;
}
}
const mutation_variant *mutation_branch::pick_variant() const
{
weighted_int_list<const mutation_variant *> options;
for( const std::pair<const std::string, mutation_variant> &cur : variants ) {
options.add( &cur.second, cur.second.weight );
}
const mutation_variant **picked = options.pick();
if( picked == nullptr ) {
return nullptr;
}
return *picked;
}
const mutation_variant *mutation_branch::variant( const std::string &id ) const
{
auto iter = variants.find( id );
if( id.empty() || iter == variants.end() ) {
return nullptr;
}
return &iter->second;
}
const mutation_variant *mutation_branch::pick_variant_menu() const
{
if( variants.empty() ) {
return nullptr;
}
uilist menu;
menu.allow_cancel = false;
menu.desc_enabled = true;
menu.text = string_format( _( "Pick variant for: %s" ), name() );
std::vector<const mutation_variant *> options;
for( const std::pair<const std::string, mutation_variant> &var : variants ) {
options.emplace_back( &var.second );
}
for( size_t i = 0; i < options.size(); ++i ) {
menu.addentry_desc( i, true, MENU_AUTOASSIGN, name( options[i]->id ), desc( options[i]->id ) );
}
menu.query();
return options[menu.ret];
}
const std::vector<mutation_branch> &mutation_branch::get_all()
{
return trait_factory.get_all();
}
void mutation_branch::reset_all()
{
mutations_category.clear();
trait_factory.reset();
trait_blacklist.clear();
trait_groups.clear();
trait_groups.emplace( Trait_group_EMPTY_GROUP,
make_shared_fast<Trait_group_collection>( 100 ) );
}
std::vector<std::string> dream::messages() const
{
std::vector<std::string> ret;
for( const translation &msg : raw_messages ) {
ret.push_back( msg.translated() );
}
return ret;
}
void dream::load( const JsonObject &jsobj )
{
dream newdream;
newdream.strength = jsobj.get_int( "strength" );
jsobj.read( "category", newdream.category, true );
jsobj.read( "messages", newdream.raw_messages );
dreams.push_back( newdream );
}
bool trait_display_sort( const trait_and_var &a, const trait_and_var &b ) noexcept
{
auto trait_sort_key = []( const trait_and_var & t ) {
return std::make_pair( -t.trait->get_display_color().to_int(), t.name() );
};
return localized_compare( trait_sort_key( a ), trait_sort_key( b ) );
}
bool trait_display_nocolor_sort( const trait_and_var &a, const trait_and_var &b ) noexcept
{
return localized_compare( a.name(), b.name() );
}
void mutation_branch::load_trait_blacklist( const JsonObject &jsobj )
{
for( const std::string line : jsobj.get_array( "traits" ) ) {
trait_blacklist.insert( trait_id( line ) );
}
}
bool mutation_branch::trait_is_blacklisted( const trait_id &tid )
{
return trait_blacklist.count( tid );
}
void mutation_branch::load_trait_migration( const JsonObject &jo )
{
// First, load the target
trait_id target;
jo.read( "id", target );
// Then, populate the replacement
trait_replacement replacement;
if( jo.has_string( "proficiency" ) ) {
jo.read( "proficiency", replacement.prof );
} else if( jo.has_string( "trait" ) ) {
trait_and_var dat;
jo.read( "trait", dat.trait );
jo.read( "variant", dat.variant, "" );
replacement.trait = dat;
} else if( !jo.has_bool( "remove" ) || !jo.get_bool( "remove" ) ) {
jo.throw_error( "Specified migration for trait " + target.str() +
", but did not specify how to migrate!" );
}
trait_migrations.emplace( target, replacement );
}
const trait_replacement &mutation_branch::trait_migration( const trait_id &tid )
{
auto it = trait_migrations.find( tid );
if( it != trait_migrations.end() ) {
return it->second;
}
return trait_migration_remove;
}
void mutation_branch::finalize()
{
for( const mutation_branch &branch : get_all() ) {
for( const mutation_category_id &cat : branch.category ) {
mutations_category[cat].push_back( trait_id( branch.id ) );
}
// Don't include dummy mutations for the ANY category, since they have a very specific use case
// Otherwise, the system will prioritize them
if( !branch.dummy ) {
mutations_category[mutation_category_ANY].push_back( trait_id( branch.id ) );
}
}
finalize_trait_blacklist();
}
void mutation_branch::finalize_trait_blacklist()
{
for( const auto &trait : trait_blacklist ) {
if( !trait.is_valid() ) {
debugmsg( "trait on blacklist %s does not exist", trait.c_str() );
}
}
}
void mutation_branch::load_trait_group( const JsonObject &jsobj )
{
const trait_group::Trait_group_tag group_id( jsobj.get_string( "id" ) );
const std::string subtype = jsobj.get_string( "subtype", "old" );
load_trait_group( jsobj, group_id, subtype );
}
static Trait_group &make_group_or_throw( const trait_group::Trait_group_tag &gid,
bool is_collection )
{
// NOTE: If the gid is already in the map, emplace will just return an iterator to it
auto found = ( is_collection
? trait_groups.emplace( gid, make_shared_fast<Trait_group_collection>( 100 ) )
: trait_groups.emplace( gid, make_shared_fast<Trait_group_distribution>( 100 ) ) ).first;
// Evidently, making the collection/distribution separation better has made the code for this check worse.
if( is_collection ) {
if( dynamic_cast<Trait_group_distribution *>( found->second.get() ) ) {
throw std::runtime_error( string_format(
R"("mutation group "%s" already defined with type "distribution")", gid.str() ) );
}
} else {
if( dynamic_cast<Trait_group_collection *>( found->second.get() ) ) {
throw std::runtime_error( string_format(
R"("mutation group "%s" already defined with type "collection")", gid.str() ) );
}
}
return *found->second;
}
void mutation_branch::load_trait_group( const JsonArray &entries,
const trait_group::Trait_group_tag &gid, const bool is_collection )
{
Trait_group &tg = make_group_or_throw( gid, is_collection );
for( const JsonValue entry : entries ) {
// Backwards-compatibility with old format ["TRAIT", 100]
if( entry.test_array() ) {
JsonArray subarr = entry.get_array();
trait_id id( subarr.get_string( 0 ) );
tg.add_entry( std::make_unique<Single_trait_creator>( id, "", subarr.get_int( 1 ) ) );
// Otherwise load new format {"trait": ... } or {"group": ...}
} else {
JsonObject subobj = entry.get_object();
add_entry( tg, subobj );
}
}
}
void mutation_branch::load_trait_group( const JsonObject &jsobj,
const trait_group::Trait_group_tag &gid,
const std::string &subtype )
{
if( subtype != "distribution" && subtype != "collection" && subtype != "old" ) {
jsobj.throw_error_at( "subtype", "unknown trait group type" );
}
Trait_group &tg = make_group_or_throw( gid, subtype == "collection" || subtype == "old" );
// TODO: (sm) Looks like this makes the new code backwards-compatible with the old format. Great if so!
if( subtype == "old" ) {
for( JsonArray pair : jsobj.get_array( "traits" ) ) {
tg.add_trait_entry( trait_id( pair.get_string( 0 ) ), "", pair.get_int( 1 ) );
}
return;
}
// TODO: (sm) Taken from item_factory.cpp almost verbatim. Ensure that these work!
if( jsobj.has_member( "entries" ) ) {
for( JsonObject subobj : jsobj.get_array( "entries" ) ) {
add_entry( tg, subobj );
}
}
if( jsobj.has_member( "traits" ) ) {
for( const JsonValue entry : jsobj.get_array( "traits" ) ) {
if( entry.test_string() ) {
tg.add_trait_entry( trait_id( entry.get_string() ), "", 100 );
} else if( entry.test_array() ) {
JsonArray subtrait = entry.get_array();
tg.add_trait_entry( trait_id( subtrait.get_string( 0 ) ), "", subtrait.get_int( 1 ) );
} else {
JsonObject subobj = entry.get_object();
add_entry( tg, subobj );
}
}
}
if( jsobj.has_member( "groups" ) ) {
for( const JsonValue entry : jsobj.get_array( "groups" ) ) {
if( entry.test_string() ) {
tg.add_group_entry( trait_group::Trait_group_tag( entry.get_string() ), 100 );
} else if( entry.test_array() ) {
JsonArray subtrait = entry.get_array();
tg.add_group_entry( trait_group::Trait_group_tag( subtrait.get_string( 0 ) ),
subtrait.get_int( 1 ) );
} else {
JsonObject subobj = entry.get_object();
add_entry( tg, subobj );
}
}
}
}
void mutation_branch::add_entry( Trait_group &tg, const JsonObject &obj )
{
std::unique_ptr<Trait_creation_data> ptr;
int probability = obj.get_int( "prob", 100 );
JsonArray jarr;
if( obj.has_member( "collection" ) ) {
ptr = std::make_unique<Trait_group_collection>( probability );
jarr = obj.get_array( "collection" );
} else if( obj.has_member( "distribution" ) ) {
ptr = std::make_unique<Trait_group_distribution>( probability );
jarr = obj.get_array( "distribution" );
}
if( ptr ) {
Trait_group &tg2 = dynamic_cast<Trait_group &>( *ptr );
for( const JsonObject job2 : jarr ) {
add_entry( tg2, job2 );
}
tg.add_entry( std::move( ptr ) );
return;
}
if( obj.has_member( "trait" ) ) {
trait_id id( obj.get_string( "trait" ) );
std::string var;
if( obj.has_member( "variant" ) ) {
var = obj.get_string( "variant" );
}
ptr = std::make_unique<Single_trait_creator>( id, var, probability );
} else if( obj.has_member( "group" ) ) {
ptr = std::make_unique<Trait_group_creator>( trait_group::Trait_group_tag(
obj.get_string( "group" ) ),
probability );
}
if( !ptr ) {
return;
}
tg.add_entry( std::move( ptr ) );
}
shared_ptr_fast<Trait_group> mutation_branch::get_group( const
trait_group::Trait_group_tag &gid )
{
auto found = trait_groups.find( gid );
return found != trait_groups.end() ? found->second : nullptr;
}
std::vector<trait_group::Trait_group_tag> mutation_branch::get_all_group_names()
{
std::vector<trait_group::Trait_group_tag> rval;
for( auto &group : trait_groups ) {
rval.push_back( group.first );
}
return rval;
}
bool mutation_category_is_valid( const mutation_category_id &cat )
{
return mutation_category_traits.count( cat );
}
template<>
bool string_id<mutation_category_trait>::is_valid() const
{
return mutation_category_traits.count( *this );
}
bool mutation_is_in_category( const trait_id &mut, const mutation_category_id &cat )
{
return std::find( mutations_category[cat].begin(), mutations_category[cat].end(),
mut ) != mutations_category[cat].end();
}