diff --git a/data/core/sentinels.json b/data/core/sentinels.json index 9189e86e1288d..06a74c091b5f8 100644 --- a/data/core/sentinels.json +++ b/data/core/sentinels.json @@ -44,6 +44,12 @@ "legacy_enum_id": 0, "intensity_levels": [ { "name": "nothing" } ] }, + { + "id": "null", + "type": "fault", + "name": { "str": "This is a bug" }, + "description": "This fault id is a reserved sentinel." + }, { "type": "SPELL", "id": "null", diff --git a/src/avatar_action.cpp b/src/avatar_action.cpp index 0d9e8fc93a6a3..999258b8f4c9c 100644 --- a/src/avatar_action.cpp +++ b/src/avatar_action.cpp @@ -1136,10 +1136,10 @@ void avatar_action::use_item( avatar &you, item_location &loc, std::string const if( loc->wetness && loc->has_flag( flag_WATER_BREAK_ACTIVE ) ) { if( query_yn( _( "This item is still wet and it will break if you turn it on. Proceed?" ) ) ) { loc->deactivate(); - loc.get_item()->set_fault( random_entry( fault::get_by_type( std::string( "wet" ) ) ) ); + loc.get_item()->set_fault( faults::random_of_type( "wet" ) ); // An electronic item in water is also shorted. if( loc->has_flag( flag_ELECTRONIC ) ) { - loc.get_item()->set_fault( random_entry( fault::get_by_type( std::string( "shorted" ) ) ) ); + loc.get_item()->set_fault( faults::random_of_type( "shorted" ) ); } } else { return; diff --git a/src/character.cpp b/src/character.cpp index 3d6e662ba3688..24afe64be6ec0 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -6736,7 +6736,7 @@ void Character::mend_item( item_location &&obj, bool interactive ) const fault_fix &fix = opt.fix; assign_activity( ACT_MEND_ITEM, to_moves( opt.time_to_fix ) ); activity.name = opt.fault.str(); - activity.str_values.emplace_back( fix.id_ ); + activity.str_values.emplace_back( fix.id.str() ); activity.targets.push_back( std::move( obj ) ); } } diff --git a/src/explosion.cpp b/src/explosion.cpp index 77dd8c2ddc7d7..23e98a30cf1f0 100644 --- a/src/explosion.cpp +++ b/src/explosion.cpp @@ -782,7 +782,7 @@ void emp_blast( const tripoint &p ) !player_character.has_flag( json_flag_EMP_IMMUNE ) ) { add_msg( m_bad, _( "The EMP blast fries your %s!" ), it->tname() ); it->deactivate(); - it->faults.insert( random_entry( fault::get_by_type( "shorted" ) ) ); + it->faults.insert( faults::random_of_type( "shorted" ) ); } } } @@ -795,7 +795,7 @@ void emp_blast( const tripoint &p ) add_msg( _( "The EMP blast fries the %s!" ), it.tname() ); } it.deactivate(); - it.set_fault( random_entry( fault::get_by_type( "shorted" ) ) ); + it.set_fault( faults::random_of_type( "shorted" ) ); } } // TODO: Drain NPC energy reserves diff --git a/src/fault.cpp b/src/fault.cpp index e754b84fd275e..e5d3a9697f1a4 100644 --- a/src/fault.cpp +++ b/src/fault.cpp @@ -2,63 +2,103 @@ #include #include +#include -#include "assign.h" #include "debug.h" -#include "flexbuffer_json-inl.h" -#include "flexbuffer_json.h" #include "generic_factory.h" -#include "json_error.h" #include "requirements.h" -static std::map faults_all; -static std::map fault_fixes_all; +namespace +{ + +generic_factory fault_factory( "fault", "id" ); +generic_factory fault_fixes_factory( "fault_fix", "id" ); + +// we'll store requirement_ids here and wait for requirements to load in, then we can actualize them +std::multimap> reqs_temp_storage; + // Have a list of faults by type, the type right now is item prefix to avoid adding more JSON data -static std::map> faults_by_type; +std::map> faults_by_type; + +} // namespace + +const fault_id &faults::random_of_type( const std::string &type ) +{ + const auto &typed = faults_by_type.find( type ); + if( typed == faults_by_type.end() ) { + debugmsg( "there are no faults with type '%s'", type ); + return fault_id::NULL_ID(); + } + return random_entry_ref( typed->second ); +} + +void faults::load_fault( const JsonObject &jo, const std::string &src ) +{ + fault_factory.load( jo, src ); +} + +void faults::load_fix( const JsonObject &jo, const std::string &src ) +{ + fault_fixes_factory.load( jo, src ); +} + +void faults::reset() +{ + fault_factory.reset(); + fault_fixes_factory.reset(); + faults_by_type.clear(); +} + +void faults::finalize() +{ + fault_factory.finalize(); + fault_fixes_factory.finalize(); + + // actualize the requirements + for( const fault_fix &const_fix : fault_fixes_factory.get_all() ) { + fault_fix &fix = const_cast( const_fix ); + fix.finalize(); + } + for( const fault &f : fault_factory.get_all() ) { + if( !f.type().empty() ) { + faults_by_type[f.type()].emplace_back( f.id.str() ); + } + } + reqs_temp_storage.clear(); +} + +void faults::check_consistency() +{ + fault_factory.check(); + fault_fixes_factory.check(); +} /** @relates string_id */ template<> bool string_id::is_valid() const { - return faults_all.count( *this ); + return fault_factory.is_valid( *this ); } /** @relates string_id */ template<> const fault &string_id::obj() const { - const auto found = faults_all.find( *this ); - if( found == faults_all.end() ) { - debugmsg( "Tried to get invalid fault: %s", c_str() ); - static const fault null_fault{}; - return null_fault; - } - return found->second; + return fault_factory.obj( *this ); } /** @relates string_id */ template<> bool string_id::is_valid() const { - return fault_fixes_all.count( *this ); + return fault_fixes_factory.is_valid( *this ); } /** @relates string_id */ template<> const fault_fix &string_id::obj() const { - const auto found = fault_fixes_all.find( *this ); - if( found == fault_fixes_all.end() ) { - debugmsg( "Tried to get invalid fault fix: %s", c_str() ); - static const fault_fix null_fault_fix{}; - return null_fault_fix; - } - return found->second; -} - -const fault_id &fault::id() const -{ - return id_; + return fault_fixes_factory.obj( *this ); } std::string fault::name() const @@ -97,56 +137,24 @@ const std::set &fault::get_fixes() const return fixes; } -void fault::load( const JsonObject &jo ) -{ - fault f; - - mandatory( jo, false, "id", f.id_ ); - mandatory( jo, false, "name", f.name_ ); - mandatory( jo, false, "description", f.description_ ); - optional( jo, false, "item_prefix", f.item_prefix_ ); - optional( jo, false, "fault_type", f.type_ ); - optional( jo, false, "flags", f.flags ); - optional( jo, false, "price_modifier", f.price_modifier, 1.0 ); - - if( !faults_all.emplace( f.id_, f ).second ) { - jo.throw_error_at( "id", "parsed fault overwrites existing definition" ); - } - if( !f.type_.empty() ) { - faults_by_type[ std::string( f.type_ ) ].push_back( f.id() ); - } -} - -const std::map &fault::all() -{ - return faults_all; -} - -void fault::reset() +void fault::load( const JsonObject &jo, std::string_view ) { - faults_all.clear(); + mandatory( jo, was_loaded, "name", name_ ); + mandatory( jo, was_loaded, "description", description_ ); + optional( jo, was_loaded, "item_prefix", item_prefix_ ); + optional( jo, was_loaded, "fault_type", type_ ); + optional( jo, was_loaded, "flags", flags ); + optional( jo, was_loaded, "price_modifier", price_modifier, 1.0 ); } -void fault::check_consistency() +void fault::check() const { - for( const auto& [fault_id, fault] : faults_all ) { - if( fault.name_.empty() ) { - debugmsg( "fault '%s' has empty name", fault_id.str() ); - } - if( fault.description_.empty() ) { - debugmsg( "fault '%s' has empty description", fault_id.str() ); - } + if( name_.empty() ) { + debugmsg( "fault '%s' has empty name", id.str() ); + } + if( description_.empty() ) { + debugmsg( "fault '%s' has empty description", id.str() ); } -} - -const std::map &fault_fix::all() -{ - return fault_fixes_all; -} - -const std::list &fault::get_by_type( const std::string &type ) -{ - return faults_by_type.at( type ); } const requirement_data &fault_fix::get_requirements() const @@ -154,25 +162,19 @@ const requirement_data &fault_fix::get_requirements() const return *requirements; } -// we'll store requirement_ids here and wait for requirements to load in, then we can actualize them -static std::multimap> reqs_temp_storage; - -void fault_fix::load( const JsonObject &jo ) +void fault_fix::load( const JsonObject &jo, std::string_view ) { - fault_fix f; - - assign( jo, "id", f.id_, true ); - assign( jo, "name", f.name, true ); - assign( jo, "success_msg", f.success_msg, true ); - assign( jo, "time", f.time, false ); - assign( jo, "set_variables", f.set_variables, true ); - assign( jo, "skills", f.skills, true ); - assign( jo, "faults_removed", f.faults_removed ); - assign( jo, "faults_added", f.faults_added ); - assign( jo, "mod_damage", f.mod_damage ); - assign( jo, "mod_degradation", f.mod_degradation ); - assign( jo, "time_save_profs", f.time_save_profs, false ); - assign( jo, "time_save_flags", f.time_save_flags, false ); + mandatory( jo, was_loaded, "name", name ); + optional( jo, was_loaded, "success_msg", success_msg ); + optional( jo, was_loaded, "time", time ); + optional( jo, was_loaded, "set_variables", set_variables ); + optional( jo, was_loaded, "skills", skills ); + optional( jo, was_loaded, "faults_removed", faults_removed ); + optional( jo, was_loaded, "faults_added", faults_added ); + optional( jo, was_loaded, "mod_damage", mod_damage ); + optional( jo, was_loaded, "mod_degradation", mod_degradation ); + optional( jo, was_loaded, "time_save_profs", time_save_profs ); + optional( jo, was_loaded, "time_save_flags", time_save_flags ); if( jo.has_array( "requirements" ) ) { for( const JsonValue &jv : jo.get_array( "requirements" ) ) { @@ -181,94 +183,81 @@ void fault_fix::load( const JsonObject &jo ) const JsonArray &req = static_cast( jv ); const std::string req_id = req.get_string( 0 ); const int req_amount = req.get_int( 1 ); - reqs_temp_storage.emplace( f.id_, std::make_pair( req_id, req_amount ) ); + reqs_temp_storage.emplace( id, std::make_pair( req_id, req_amount ) ); } else if( jv.test_object() ) { // defining single requirement inline const JsonObject &req = static_cast( jv ); - const requirement_id req_id( "fault_fix_" + f.id_.str() + "_inline_req" ); + const requirement_id req_id( "fault_fix_" + id.str() + "_inline_req" ); requirement_data::load_requirement( req, req_id ); - reqs_temp_storage.emplace( f.id_, std::make_pair( req_id.str(), 1 ) ); + reqs_temp_storage.emplace( id, std::make_pair( req_id.str(), 1 ) ); } else { - debugmsg( "fault_fix '%s' has has invalid requirement element", f.id_.str() ); + debugmsg( "fault_fix '%s' has has invalid requirement element", id.str() ); } } } - fault_fixes_all.emplace( f.id_, f ); -} - -void fault_fix::reset() -{ - fault_fixes_all.clear(); } void fault_fix::finalize() { - for( auto& [fix_id, fix] : fault_fixes_all ) { - const auto range = reqs_temp_storage.equal_range( fix_id ); - for( auto it = range.first; it != range.second; ++it ) { - const requirement_id req_id( it->second.first ); - const int amount = it->second.second; - if( !req_id.is_valid() ) { - debugmsg( "fault_fix '%s' has invalid requirement_id '%s'", fix_id.str(), req_id.str() ); - continue; - } - *fix.requirements = *fix.requirements + ( *req_id ) * amount; - } - fix.requirements->consolidate(); - for( const fault_id &fid : fix.faults_removed ) { - const_cast( *fid ).fixes.emplace( fix_id ); + const auto range = reqs_temp_storage.equal_range( id ); + for( auto it = range.first; it != range.second; ++it ) { + const requirement_id req_id( it->second.first ); + const int amount = it->second.second; + if( !req_id.is_valid() ) { + debugmsg( "fault_fix '%s' has invalid requirement_id '%s'", id.str(), req_id.str() ); + continue; } + *requirements = *requirements + ( *req_id ) * amount; + } + requirements->consolidate(); + for( const fault_id &fid : faults_removed ) { + const_cast( *fid ).fixes.emplace( id ); } - reqs_temp_storage.clear(); } -void fault_fix::check_consistency() +void fault_fix::check() const { - for( const auto &[fix_id, fix] : fault_fixes_all ) { - if( fix.time < 0_turns ) { - debugmsg( "fault_fix '%s' has negative time", fix_id.str() ); + if( time < 0_turns ) { + debugmsg( "fault_fix '%s' has negative time", id.str() ); + } + for( const auto &[skill_id, lvl] : skills ) { + if( !skill_id.is_valid() ) { + debugmsg( "fault_fix %s requires unknown skill '%s'", id.str(), skill_id.str() ); } - for( const auto &[skill_id, lvl] : fix.skills ) { - if( !skill_id.is_valid() ) { - debugmsg( "fault_fix %s requires unknown skill '%s'", - fix_id.str(), skill_id.str() ); - } - if( lvl <= 0 ) { - debugmsg( "fault_fix '%s' requires negative level of skill '%s'", - fix_id.str(), skill_id.str() ); - } + if( lvl <= 0 ) { + debugmsg( "fault_fix '%s' requires negative level of skill '%s'", id.str(), skill_id.str() ); } - for( const fault_id &fault_id : fix.faults_removed ) { - if( !fault_id.is_valid() ) { - debugmsg( "fault_fix '%s' has invalid fault_id '%s' in 'faults_removed' field", - fix_id.str(), fault_id.str() ); - } + } + for( const fault_id &fault_id : faults_removed ) { + if( !fault_id.is_valid() ) { + debugmsg( "fault_fix '%s' has invalid fault_id '%s' in 'faults_removed' field", + id.str(), fault_id.str() ); } - for( const fault_id &fault_id : fix.faults_added ) { - if( !fault_id.is_valid() ) { - debugmsg( "fault_fix '%s' has invalid fault_id '%s' in 'faults_added' field", - fix_id.str(), fault_id.str() ); - } + } + for( const fault_id &fault_id : faults_added ) { + if( !fault_id.is_valid() ) { + debugmsg( "fault_fix '%s' has invalid fault_id '%s' in 'faults_added' field", + id.str(), fault_id.str() ); } - for( const auto &[proficiency_id, mult] : fix.time_save_profs ) { - if( !proficiency_id.is_valid() ) { - debugmsg( "fault_fix %s has unknown proficiency_id '%s'", - fix_id.str(), proficiency_id.str() ); - } - if( mult < 0 ) { - debugmsg( "fault_fix '%s' has negative mend time if possessing proficiency '%s'", - fix_id.str(), proficiency_id.str() ); - } + } + for( const auto &[proficiency_id, mult] : time_save_profs ) { + if( !proficiency_id.is_valid() ) { + debugmsg( "fault_fix %s has unknown proficiency_id '%s'", + id.str(), proficiency_id.str() ); } - for( const auto &[flag_id, mult] : fix.time_save_flags ) { - if( !flag_id.is_valid() ) { - debugmsg( "fault_fix %s has unknown flag_id '%s'", - fix_id.str(), flag_id.str() ); - } - if( mult < 0 ) { - debugmsg( "fault_fix '%s' has negative mend time if item possesses flag '%s'", - fix_id.str(), flag_id.str() ); - } + if( mult < 0 ) { + debugmsg( "fault_fix '%s' has negative mend time if possessing proficiency '%s'", + id.str(), proficiency_id.str() ); + } + } + for( const auto &[flag_id, mult] : time_save_flags ) { + if( !flag_id.is_valid() ) { + debugmsg( "fault_fix %s has unknown flag_id '%s'", + id.str(), flag_id.str() ); + } + if( mult < 0 ) { + debugmsg( "fault_fix '%s' has negative mend time if item possesses flag '%s'", + id.str(), flag_id.str() ); } } } diff --git a/src/fault.h b/src/fault.h index a0054375f1285..b2512d66d306b 100644 --- a/src/fault.h +++ b/src/fault.h @@ -2,7 +2,6 @@ #ifndef CATA_SRC_FAULT_H #define CATA_SRC_FAULT_H -#include #include #include #include @@ -14,15 +13,32 @@ #include "translation.h" #include "type_id.h" +template class generic_factory; + +class fault; +class fault_fix; class JsonObject; +struct requirement_data; + +namespace faults +{ +void load_fault( const JsonObject &jo, const std::string &src ); +void load_fix( const JsonObject &jo, const std::string &src ); + +void reset(); +void finalize(); +void check_consistency(); + +const fault_id &random_of_type( const std::string &type ); +} // namespace faults class fault_fix { public: - fault_fix_id id_ = fault_fix_id::NULL_ID(); + fault_fix_id id = fault_fix_id::NULL_ID(); translation name; translation success_msg; // message to print on applying successfully - time_duration time; + time_duration time = 0_seconds; std::map set_variables; // item vars applied to item std::map skills; // map of skill_id to required level std::set faults_removed; // which faults are removed on applying @@ -36,12 +52,12 @@ class fault_fix const requirement_data &get_requirements() const; - static void load( const JsonObject &jo ); - static const std::map &all(); - static void reset(); - static void finalize(); - static void check_consistency(); + void finalize(); private: + void load( const JsonObject &jo, std::string_view src ); + void check() const; + bool was_loaded = false; // used by generic_factory + friend class generic_factory; friend class fault; shared_ptr_fast requirements = make_shared_fast(); }; @@ -49,7 +65,7 @@ class fault_fix class fault { public: - const fault_id &id() const; + fault_id id = fault_id::NULL_ID(); std::string name() const; std::string type() const; // use a set of types? std::string description() const; @@ -58,16 +74,12 @@ class fault bool has_flag( const std::string &flag ) const; const std::set &get_fixes() const; - - static const std::map &all(); - static const std::list &get_by_type( const std::string &type ); - static void load( const JsonObject &jo ); - static void reset(); - static void check_consistency(); - private: + void load( const JsonObject &jo, std::string_view ); + void check() const; + bool was_loaded = false; // used by generic_factory + friend class generic_factory; friend class fault_fix; - fault_id id_ = fault_id::NULL_ID(); std::string type_; translation name_; translation description_; diff --git a/src/game.cpp b/src/game.cpp index 34b9be63b7d98..5e3d3b3b974af 100644 --- a/src/game.cpp +++ b/src/game.cpp @@ -11795,10 +11795,10 @@ void game::water_affect_items( Character &ch ) const loc->deactivate(); // TODO: Maybe different types of wet faults? But I can't think of any. // This just means it's still too wet to use. - loc->set_fault( random_entry( fault::get_by_type( std::string( "wet" ) ) ) ); + loc->set_fault( faults::random_of_type( "wet" ) ) ; // An electronic item in water is also shorted. if( loc->has_flag( flag_ELECTRONIC ) ) { - loc->set_fault( random_entry( fault::get_by_type( std::string( "shorted" ) ) ) ); + loc->set_fault( faults::random_of_type( "shorted" ) ); } } else if( loc->has_flag( flag_WATER_BREAK_ACTIVE ) && !loc->is_broken() && !loc.protected_from_liquids() ) { diff --git a/src/init.cpp b/src/init.cpp index c65e6be9b599c..beda2c8d1ba87 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -249,8 +249,8 @@ void DynamicDataLoader::initialize() add( "jmath_function", &jmath_func::load_func ); add( "var_migration", &global_variables::load_migrations ); add( "connect_group", &connect_group::load ); - add( "fault", &fault::load ); - add( "fault_fix", &fault_fix::load ); + add( "fault", &faults::load_fault ); + add( "fault_fix", &faults::load_fix ); add( "relic_procgen_data", &relic_procgen_data::load_relic_procgen_data ); add( "effect_on_condition", &effect_on_conditions::load ); add( "field_type", &field_types::load ); @@ -579,8 +579,7 @@ void DynamicDataLoader::unload_data() effect_on_conditions::reset(); event_transformation::reset(); faction_template::reset(); - fault_fix::reset(); - fault::reset(); + faults::reset(); field_types::reset(); gates::reset(); harvest_drop_type::reset(); @@ -757,7 +756,7 @@ void DynamicDataLoader::finalize_loaded_data( loading_ui &ui ) { _( "Achievements" ), &achievement::finalize }, { _( "Damage info orders" ), &damage_info_order::finalize_all }, { _( "Widgets" ), &widget::finalize }, - { _( "Fault fixes" ), &fault_fix::finalize }, + { _( "Faults" ), &faults::finalize }, #if defined(TILES) { _( "Tileset" ), &load_tileset }, #endif @@ -811,8 +810,7 @@ void DynamicDataLoader::check_consistency( loading_ui &ui ) } }, { _( "Materials" ), &materials::check }, - { _( "Faults" ), &fault::check_consistency }, - { _( "Fault fixes" ), &fault_fix::check_consistency }, + { _( "Faults" ), &faults::check_consistency }, { _( "Vehicle parts" ), &vehicles::parts::check }, { _( "Vehicle part migrations" ), &vpart_migration::check }, { _( "Mapgen definitions" ), &check_mapgen_definitions }, diff --git a/src/item.cpp b/src/item.cpp index 9d3850ee9f9ca..270153fe01a50 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -14132,9 +14132,9 @@ bool item::process_internal( map &here, Character *carrier, const tripoint &pos, if( wetness && has_flag( flag_WATER_BREAK ) ) { deactivate(); - set_fault( random_entry( fault::get_by_type( std::string( "wet" ) ) ) ); + set_fault( faults::random_of_type( "wet" ) ); if( has_flag( flag_ELECTRONIC ) ) { - set_fault( random_entry( fault::get_by_type( std::string( "shorted" ) ) ) ); + set_fault( faults::random_of_type( "shorted" ) ); } }