diff --git a/data/json/artifact/relic_procgen_data.json b/data/json/artifact/relic_procgen_data.json new file mode 100644 index 000000000000..b9c8593b5ea8 --- /dev/null +++ b/data/json/artifact/relic_procgen_data.json @@ -0,0 +1,125 @@ +[ + { + "type": "relic_procgen_data", + "id": "cult", + "passive_add_procgen_values": [ + { "weight": 100, "min_value": -1, "max_value": 1, "type": "STRENGTH", "increment": 1, "power_per_increment": 250 }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "DEXTERITY", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "PERCEPTION", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "INTELLIGENCE", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -100, + "max_value": 25, + "type": "SPEED", + "increment": 5, + "power_per_increment": 200 + } + ], + "type_weights": [ { "weight": 100, "value": "passive_enchantment_add" } ], + "items": [ { "weight": 100, "item": "spoon" } ] + }, + { + "type": "relic_procgen_data", + "id": "netherum_tunnels", + "passive_add_procgen_values": [ + { "weight": 100, "min_value": -1, "max_value": 1, "type": "STRENGTH", "increment": 1, "power_per_increment": 250 }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "DEXTERITY", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "PERCEPTION", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "INTELLIGENCE", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -100, + "max_value": 25, + "type": "SPEED", + "increment": 5, + "power_per_increment": 200 + } + ], + "type_weights": [ { "weight": 100, "value": "passive_enchantment_add" } ], + "items": [ { "weight": 100, "item": "spoon" } ] + }, + { + "type": "relic_procgen_data", + "id": "alien_reality", + "passive_add_procgen_values": [ + { "weight": 100, "min_value": -1, "max_value": 1, "type": "STRENGTH", "increment": 1, "power_per_increment": 250 }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "DEXTERITY", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "PERCEPTION", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "INTELLIGENCE", + "increment": 1, + "power_per_increment": 250 + }, + { + "weight": 100, + "min_value": -100, + "max_value": 25, + "type": "SPEED", + "increment": 5, + "power_per_increment": 200 + } + ], + "type_weights": [ { "weight": 100, "value": "passive_enchantment_add" } ], + "items": [ { "weight": 100, "item": "spoon" } ] + } +] diff --git a/doc/src/content/docs/en/mod/json/reference/items/relics.md b/doc/src/content/docs/en/mod/json/reference/items/relics.md index 742d1ca98cc6..268aa6e587e8 100644 --- a/doc/src/content/docs/en/mod/json/reference/items/relics.md +++ b/doc/src/content/docs/en/mod/json/reference/items/relics.md @@ -64,3 +64,79 @@ optional): | `rad` | Character or map tile must be irradiated | | `wet` | Character must be wet, or it's raining | | `sky` | Character must be above z=0 | + +# Generation + +The procedural generation of artifacts is defined in Json. The object looks like the following: + +```json +{ + "type": "relic_procgen_data", + "id": "cult", + "passive_add_procgen_values": [ + { + "weight": 100, + "min_value": -1, + "max_value": 1, + "type": "STRENGTH", + "increment": 1, + "power_per_increment": 250 + } + ], + "passive_mult_procgen_values": [ + { + "weight": 100, + "min_value": -1.5, + "max_value": 1.5, + "type": "STRENGTH", + "increment": 0.1, + "power_per_increment": 250 + } + ], + "type_weights": [{ "weight": 100, "value": "passive_enchantment_add" }], + "items": [{ "weight": 100, "item": "spoon" }] +} +``` + +## passive_add_procgen_values and passive_mult_procgen_values + +As the names suggest, these are _passive_ benefits/penalties to having the artifact (ie. always +present without activating the artifact's abilities). **Add** values add or subtract from existing +scores, and **mult** values multiply them. These are entered as a list of possible 'abilities' the +artifact could get. It does not by default get all these abilities, rather when it spawns it selects +from the list provided. + +- **weight:** the weight of this value in the list, to be chosen randomly +- **min_value:** the minimum possible value for this value type. for add must be an integer, for + mult it can be a float +- **max_value:** the maximum possible value for this value type. for add must be an integer, for + mult it can be a float +- **type:** the type of enchantment value. see MAGIC.md for detailed documentation on enchantment + values +- **increment:** the increment that is used for the power multiplier +- **power_per_increment:** the power value per increment + +## type_weights + +This determines the relative weight of the 'add' and 'mult' types. When generated, an artifact first +decides if it is going to apply an 'add' or a 'mult' ability based on the type_weights of each. Then +it uses the weights of the entries under the selected type to pick an ability. This continues +cycling until the artifact reaches the defined power level. Possible values right now that are +functional are: + +- passive_enchantment_add +- passive_enchantment_mult + +This must be included in a dataset or it could cause a crash. + +## items + +This provides a list of possible items that this artifact can spawn as, if it appears randomly in a +hard-coded map extra. + +## Power Level + +An artifact's power level is a summation of its attributes. For example, each point of strength +addition in the above object, the artifact is a +250 power, so an artifact with +2 strength would +have a power level of 500. similarly, if an artifact had a strength multiplier of 0.8, it would have +a power level of -500. diff --git a/src/debug_menu.cpp b/src/debug_menu.cpp index 1381cfcc319e..2eed47c4fd07 100644 --- a/src/debug_menu.cpp +++ b/src/debug_menu.cpp @@ -1639,7 +1639,7 @@ void debug() artifact_natural_property prop = static_cast( rng( ARTPROP_NULL + 1, ARTPROP_MAX - 1 ) ); m.create_anomaly( *center, prop ); - m.spawn_natural_artifact( *center, prop ); + m.spawn_artifact( *center, relic_procgen_id( "alien_reality" ) ); } break; diff --git a/src/init.cpp b/src/init.cpp index 5e7e38371f69..ebfb6df18b20 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -254,6 +254,7 @@ void DynamicDataLoader::initialize() add( "json_flag", &json_flag::load_all ); add( "mutation_flag", &json_trait_flag::load_all ); add( "fault", &fault::load_fault ); + add( "relic_procgen_data", &relic_procgen_data::load_relic_procgen_data ); add( "field_type", &field_types::load ); add( "weather_type", &weather_types::load ); add( "ammo_effect", &ammo_effects::load ); diff --git a/src/item.cpp b/src/item.cpp index 4850df785ea5..9aba97837950 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -9232,6 +9232,11 @@ std::vector item::mutations_from_wearing( const Character &guy ) const return muts; } +void item::overwrite_relic( const relic &nrelic ) +{ + this->relic_data = cata::make_value( nrelic ); +} + void item::process_relic( Character &carrier ) { if( !is_relic() ) { diff --git a/src/item.h b/src/item.h index 0869959e4005..436d5b74d705 100644 --- a/src/item.h +++ b/src/item.h @@ -1214,6 +1214,7 @@ class item : public location_visitable, public game_object * @param pos The location of the artifact (should be the player location if carried). */ void process_artifact( player *carrier, const tripoint &pos ); + void overwrite_relic( const relic &nrelic ); void process_relic( Character &carrier ); bool destroyed_at_zero_charges() const; diff --git a/src/magic_enchantment.cpp b/src/magic_enchantment.cpp index 5db1246b5315..3fa12f764d0a 100644 --- a/src/magic_enchantment.cpp +++ b/src/magic_enchantment.cpp @@ -20,23 +20,6 @@ #include "string_id.h" #include "units.h" -template struct enum_traits; - -template<> -struct enum_traits { - static constexpr enchantment::has last = enchantment::has::NUM_HAS; -}; - -template<> -struct enum_traits { - static constexpr enchantment::condition last = enchantment::condition::NUM_CONDITION; -}; - -template<> -struct enum_traits { - static constexpr enchant_vals::mod last = enchant_vals::mod::NUM_MOD; -}; - namespace io { // *INDENT-OFF* @@ -401,6 +384,31 @@ void enchantment::force_add( const enchantment &rhs ) } } +void enchantment::set_has( enchantment::has value ) +{ + active_conditions.first = value; +} + +void enchantment::add_value_add( enchant_vals::mod value, int add_value ) +{ + values_add[value] = add_value; +} + +void enchantment::add_value_mult( enchant_vals::mod value, float mult_value ) +{ + values_multiply[value] = mult_value; +} + +void enchantment::add_hit_me( const fake_spell &sp ) +{ + hit_me_effect.push_back( sp ); +} + +void enchantment::add_hit_you( const fake_spell &sp ) +{ + hit_you_effect.push_back( sp ); +} + int enchantment::get_value_add( const enchant_vals::mod value ) const { const auto found = values_add.find( value ); diff --git a/src/magic_enchantment.h b/src/magic_enchantment.h index b2b465f53995..d65666a00f5d 100644 --- a/src/magic_enchantment.h +++ b/src/magic_enchantment.h @@ -9,6 +9,7 @@ #include #include "calendar.h" +#include "enum_traits.h" #include "magic.h" #include "type_id.h" @@ -99,6 +100,14 @@ class enchantment // adds two enchantments together and ignores their conditions void force_add( const enchantment &rhs ); + void set_has( has value ); + + void add_value_add( enchant_vals::mod value, int add_value ); + void add_value_mult( enchant_vals::mod value, float mult_value ); + + void add_hit_me( const fake_spell &sp ); + void add_hit_you( const fake_spell &sp ); + int get_value_add( enchant_vals::mod value ) const; double get_value_multiply( enchant_vals::mod value ) const; @@ -172,4 +181,21 @@ class enchantment const fake_spell &sp ) const; }; +template struct enum_traits; + +template<> +struct enum_traits { + static constexpr enchantment::has last = enchantment::has::NUM_HAS; +}; + +template<> +struct enum_traits { + static constexpr enchantment::condition last = enchantment::condition::NUM_CONDITION; +}; + +template<> +struct enum_traits { + static constexpr enchant_vals::mod last = enchant_vals::mod::NUM_MOD; +}; + #endif // CATA_SRC_MAGIC_ENCHANTMENT_H diff --git a/src/map.cpp b/src/map.cpp index 282412a2a8a8..fe783542f9db 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -4350,15 +4350,14 @@ std::vector> map::spawn_items( const tripoint &p, return ret; } -void map::spawn_artifact( const tripoint &p ) +void map::spawn_artifact( const tripoint &p, const relic_procgen_id &id ) { - add_item_or_charges( p, item::spawn( new_artifact(), calendar::start_of_cataclysm ) ); -} + relic_procgen_data::generation_rules rules; + rules.max_attributes = 5; + rules.power_level = 1000; + rules.max_negative_power = -2000; -void map::spawn_natural_artifact( const tripoint &p, artifact_natural_property prop ) -{ - add_item_or_charges( p, item::spawn( new_natural_artifact( prop ), - calendar::start_of_cataclysm ) ); + add_item_or_charges( p, id->create_item( rules ) ); } void map::spawn_item( const tripoint &p, const itype_id &type_id, diff --git a/src/map.h b/src/map.h index ec38116d1512..9fc081554f83 100644 --- a/src/map.h +++ b/src/map.h @@ -56,6 +56,7 @@ class mapgendata; class monster; class optional_vpart_position; class player; +class relic_procgen_data; class submap; template class tripoint_range; @@ -99,6 +100,8 @@ namespace cata template class poly_serialized; } // namespace cata +using relic_procgen_id = string_id; + class map_stack : public item_stack { private: @@ -1200,8 +1203,7 @@ class map detached_ptr i_rem( point p, item *it ) { return i_rem( tripoint( p, abs_sub.z ), it ); } - void spawn_artifact( const tripoint &p ); - void spawn_natural_artifact( const tripoint &p, artifact_natural_property prop ); + void spawn_artifact( const tripoint &p, const relic_procgen_id &id ); void spawn_item( const tripoint &p, const itype_id &type_id, unsigned quantity = 1, int charges = 0, const time_point &birthday = calendar::start_of_cataclysm, int damlevel = 0 ); diff --git a/src/map_extras.cpp b/src/map_extras.cpp index b444c472536a..a80fe4b52c4a 100644 --- a/src/map_extras.cpp +++ b/src/map_extras.cpp @@ -1753,7 +1753,8 @@ static bool mx_portal_in( map &m, const tripoint &abs_sub ) artifact_natural_property prop = static_cast( rng( ARTPROP_NULL + 1, ARTPROP_MAX - 1 ) ); m.create_anomaly( portal_location, prop ); - m.spawn_natural_artifact( p + tripoint{ rng( -1, 1 ), rng( -1, 1 ), abs_sub.z }, prop ); + m.spawn_artifact( p + tripoint( rng( -1, 1 ), rng( -1, 1 ), abs_sub.z ), + relic_procgen_id( "alien_reality" ) ); break; } } diff --git a/src/mapgen.cpp b/src/mapgen.cpp index 4d49e6d49219..bf934fa02a27 100644 --- a/src/mapgen.cpp +++ b/src/mapgen.cpp @@ -5628,8 +5628,10 @@ void map::draw_temple( const mapgendata &dat ) square( this, t_rock_floor, point( SEEX - 1, 1 ), point( SEEX + 2, 4 ) ); square( this, t_rock_floor, point( SEEX, 5 ), point( SEEX + 1, SOUTH_EDGE ) ); line( this, t_stairs_up, point( SEEX, SOUTH_EDGE ), point( SEEX + 1, SOUTH_EDGE ) ); - spawn_artifact( tripoint( rng( SEEX, SEEX + 1 ), rng( 2, 3 ), abs_sub.z ) ); - spawn_artifact( tripoint( rng( SEEX, SEEX + 1 ), rng( 2, 3 ), abs_sub.z ) ); + spawn_artifact( tripoint( rng( SEEX, SEEX + 1 ), rng( 2, 3 ), abs_sub.z ), + relic_procgen_id( "cult" ) ); + spawn_artifact( tripoint( rng( SEEX, SEEX + 1 ), rng( 2, 3 ), abs_sub.z ), + relic_procgen_id( "cult" ) ); return; } @@ -5902,7 +5904,8 @@ void map::draw_mine( mapgendata &dat ) p3, false, calendar::start_of_cataclysm ); } place_spawns( GROUP_DOG_THING, 1, point( SEEX, SEEX ), point( SEEX + 1, SEEX + 1 ), 1, true, true ); - spawn_artifact( tripoint( rng( SEEX, SEEX + 1 ), rng( SEEY, SEEY + 1 ), abs_sub.z ) ); + spawn_artifact( tripoint( rng( SEEX, SEEX + 1 ), rng( SEEY, SEEY + 1 ), abs_sub.z ), + relic_procgen_id( "netherum_tunnels" ) ); } } diff --git a/src/mondeath.cpp b/src/mondeath.cpp index 220a4ef06338..850e7a5f2f54 100644 --- a/src/mondeath.cpp +++ b/src/mondeath.cpp @@ -575,7 +575,7 @@ void mdeath::amigara( monster &z ) add_msg( _( "Your obsession with the fault fades away…" ) ); } - g->m.spawn_artifact( z.pos() ); + g->m.spawn_artifact( z.pos(), relic_procgen_id( "netherum_tunnels" ) ); } void mdeath::thing( monster &z ) diff --git a/src/relic.cpp b/src/relic.cpp index c2fefdf5b62b..36ed6e94fc29 100644 --- a/src/relic.cpp +++ b/src/relic.cpp @@ -6,8 +6,10 @@ #include "cata_unreachable.h" #include "creature.h" #include "character.h" +#include "enum_traits.h" #include "field.h" #include "game.h" +#include "generic_factory.h" #include "json.h" #include "magic.h" #include "magic_enchantment.h" @@ -63,6 +65,23 @@ std::string enum_to_string( relic_recharge_req data ) debugmsg( "Invalid relic_recharge_req" ); abort(); } + +template<> +std::string enum_to_string( relic_procgen_data::type data ) +{ + switch( data ) { + // *INDENT-OFF* + case relic_procgen_data::type::active_enchantment: return "active_enchantment"; + case relic_procgen_data::type::hit_me: return "hit_me"; + case relic_procgen_data::type::hit_you: return "hit_you"; + case relic_procgen_data::type::passive_enchantment_add: return "passive_enchantment_add"; + case relic_procgen_data::type::passive_enchantment_mult: return "passive_enchantment_mult"; + case relic_procgen_data::type::last: break; + // *INDENT-ON* + } + debugmsg( "Invalid enchantment::has" ); + abort(); +} } // namespace io bool relic_recharge::operator==( const relic_recharge &rhs ) const @@ -140,6 +159,28 @@ void relic_recharge::check() const } } +namespace +{ +generic_factory relic_procgen_data_factory( "relic_procgen_data" ); +} // namespace + +template<> +const relic_procgen_data &string_id::obj() const +{ + return relic_procgen_data_factory.obj( *this ); +} + +template<> +bool string_id::is_valid() const +{ + return relic_procgen_data_factory.is_valid( *this ); +} + +void relic_procgen_data::load_relic_procgen_data( const JsonObject &jo, const std::string &src ) +{ + relic_procgen_data_factory.load( jo, src ); +} + void relic::add_active_effect( const fake_spell &sp ) { active_effects.emplace_back( sp ); @@ -160,6 +201,85 @@ void relic::add_recharge_scheme( const relic_recharge &r ) recharge_scheme.emplace_back( r ); } + +template +void relic_procgen_data::enchantment_value_passive::load( const JsonObject &jo ) +{ + mandatory( jo, was_loaded, "type", type ); + optional( jo, was_loaded, "power_per_increment", power_per_increment, 1 ); + optional( jo, was_loaded, "increment", increment, 1 ); + optional( jo, was_loaded, "min_value", min_value, 0 ); + optional( jo, was_loaded, "max_value", max_value, 0 ); +} + +template +void relic_procgen_data::enchantment_value_passive::deserialize( JsonIn &jsin ) +{ + JsonObject jobj = jsin.get_object(); + load( jobj ); +} + +void relic_procgen_data::enchantment_active::load( const JsonObject &jo ) +{ + mandatory( jo, was_loaded, "spell_id", activated_spell ); + optional( jo, was_loaded, "base_power", base_power, 0 ); + optional( jo, was_loaded, "power_per_increment", power_per_increment, 1 ); + optional( jo, was_loaded, "increment", increment, 1 ); + optional( jo, was_loaded, "min_level", min_level, 0 ); + optional( jo, was_loaded, "max_level", max_level, 0 ); +} + +void relic_procgen_data::enchantment_active::deserialize( JsonIn &jsin ) +{ + JsonObject jobj = jsin.get_object(); + load( jobj ); +} + +void relic_procgen_data::load( const JsonObject &jo, const std::string & ) +{ + for( const JsonObject &jo_inner : jo.get_array( "passive_add_procgen_values" ) ) { + int weight = 0; + mandatory( jo_inner, was_loaded, "weight", weight ); + relic_procgen_data::enchantment_value_passive val; + val.load( jo_inner ); + + passive_add_procgen_values.add( val, weight ); + } + + for( const JsonObject &jo_inner : jo.get_array( "passive_mult_procgen_values" ) ) { + int weight = 0; + mandatory( jo_inner, was_loaded, "weight", weight ); + relic_procgen_data::enchantment_value_passive val; + val.load( jo_inner ); + + passive_mult_procgen_values.add( val, weight ); + } + + for( const JsonObject &jo_inner : jo.get_array( "type_weights" ) ) { + int weight = 0; + mandatory( jo_inner, was_loaded, "weight", weight ); + relic_procgen_data::type val; + mandatory( jo_inner, was_loaded, "value", val ); + + type_weights.add( val, weight ); + } + + for( const JsonObject &jo_inner : jo.get_array( "items" ) ) { + int weight = 0; + mandatory( jo_inner, was_loaded, "weight", weight ); + itype_id it; + mandatory( jo_inner, was_loaded, "item", it ); + + item_weights.add( it, weight ); + } +} + +void relic_procgen_data::deserialize( JsonIn &jsin ) +{ + JsonObject jobj = jsin.get_object(); + load( jobj ); +} + void relic::load( const JsonObject &jo ) { if( jo.has_array( "active_effects" ) ) { @@ -458,4 +578,207 @@ void process_recharge( item &itm, Character &carrier ) process_recharge_entry( itm, rech, carrier ); } } + } // namespace relic_funcs + +int relic::power_level( const relic_procgen_id &ruleset ) const +{ + int total_power_level = 0; + for( const enchantment &ench : passive_effects ) { + total_power_level += ruleset->power_level( ench ); + } + for( const fake_spell &sp : active_effects ) { + total_power_level += ruleset->power_level( sp ); + } + return total_power_level; +} + +int relic_procgen_data::power_level( const enchantment &ench ) const +{ + int power = 0; + + for( const weighted_object> + &add_val_passive : passive_add_procgen_values ) { + int val = ench.get_value_add( add_val_passive.obj.type ); + if( val != 0 ) { + power += static_cast( add_val_passive.obj.power_per_increment ) / + static_cast( add_val_passive.obj.increment ) * val; + } + } + + for( const weighted_object> + &mult_val_passive : passive_mult_procgen_values ) { + float val = ench.get_value_multiply( mult_val_passive.obj.type ); + if( val != 0.0f ) { + power += mult_val_passive.obj.power_per_increment / mult_val_passive.obj.increment * val; + } + } + + return power; +} + +int relic_procgen_data::power_level( const fake_spell &sp ) const +{ + for( const weighted_object &vals : + active_procgen_values ) { + if( vals.obj.activated_spell == sp.id ) { + return vals.obj.calc_power( sp.level ); + } + } + return 0; +} + +detached_ptr relic_procgen_data::create_item( const relic_procgen_data::generation_rules + &rules ) const +{ + const itype_id *it_id = item_weights.pick(); + + if( it_id->is_empty() ) { + debugmsg( "ERROR: %s procgen data does not have items", id.c_str() ); + return detached_ptr(); + } + + detached_ptr it = item::spawn( *it_id, calendar::turn ); + + it->overwrite_relic( generate( rules, *it_id ) ); + + return it; +} + +relic relic_procgen_data::generate( const relic_procgen_data::generation_rules &rules, + const itype_id &it_id ) const +{ + relic ret; + int num_attributes = 0; + int negative_attribute_power = 0; + const bool is_armor = item( it_id ).is_armor(); + + while( rules.max_attributes > num_attributes && rules.power_level > ret.power_level( id ) ) { + switch( *type_weights.pick() ) { + case relic_procgen_data::type::active_enchantment: { + const relic_procgen_data::enchantment_active *active = active_procgen_values.pick(); + if( active != nullptr ) { + fake_spell active_sp; + active_sp.id = active->activated_spell; + active_sp.level = rng( active->min_level, active->max_level ); + num_attributes++; + int power = power_level( active_sp ); + if( power < 0 ) { + if( rules.max_negative_power > negative_attribute_power ) { + break; + } + negative_attribute_power += power; + } + ret.add_active_effect( active_sp ); + } + break; + } + case relic_procgen_data::type::passive_enchantment_add: { + const relic_procgen_data::enchantment_value_passive *add = passive_add_procgen_values.pick(); + if( add != nullptr ) { + enchantment ench; + int value = rng( add->min_value, add->max_value ); + if( value == 0 ) { + break; + } + ench.add_value_add( add->type, value ); + num_attributes++; + int negative_ench_attribute = power_level( ench ); + if( negative_ench_attribute < 0 ) { + if( rules.max_negative_power > negative_attribute_power ) { + break; + } + negative_attribute_power += negative_ench_attribute; + } + if( is_armor ) { + ench.set_has( enchantment::has::WORN ); + } else { + ench.set_has( enchantment::has::WIELD ); + } + ret.add_passive_effect( ench ); + } + break; + } + case relic_procgen_data::type::passive_enchantment_mult: { + const relic_procgen_data::enchantment_value_passive *mult = + passive_mult_procgen_values.pick(); + if( mult != nullptr ) { + enchantment ench; + float value = rng( mult->min_value, mult->max_value ); + ench.add_value_mult( mult->type, value ); + num_attributes++; + int negative_ench_attribute = power_level( ench ); + if( negative_ench_attribute < 0 ) { + if( rules.max_negative_power > negative_attribute_power ) { + break; + } + negative_attribute_power += negative_ench_attribute; + } + if( is_armor ) { + ench.set_has( enchantment::has::WORN ); + } else { + ench.set_has( enchantment::has::WIELD ); + } + ret.add_passive_effect( ench ); + } + break; + } + case relic_procgen_data::type::hit_me: { + const relic_procgen_data::enchantment_active *active = passive_hit_me.pick(); + if( active != nullptr ) { + fake_spell active_sp; + active_sp.id = active->activated_spell; + active_sp.level = rng( active->min_level, active->max_level ); + num_attributes++; + enchantment ench; + ench.add_hit_me( active_sp ); + int power = power_level( ench ); + if( power < 0 ) { + if( rules.max_negative_power > negative_attribute_power ) { + break; + } + negative_attribute_power += power; + } + if( is_armor ) { + ench.set_has( enchantment::has::WORN ); + } else { + ench.set_has( enchantment::has::WIELD ); + } + ret.add_passive_effect( ench ); + } + break; + } + case relic_procgen_data::type::hit_you: { + const relic_procgen_data::enchantment_active *active = passive_hit_you.pick(); + if( active != nullptr ) { + fake_spell active_sp; + active_sp.id = active->activated_spell; + active_sp.level = rng( active->min_level, active->max_level ); + num_attributes++; + enchantment ench; + ench.add_hit_you( active_sp ); + int power = power_level( ench ); + if( power < 0 ) { + if( rules.max_negative_power > negative_attribute_power ) { + break; + } + negative_attribute_power += power; + } + if( is_armor ) { + ench.set_has( enchantment::has::WORN ); + } else { + ench.set_has( enchantment::has::WIELD ); + } + ret.add_passive_effect( ench ); + } + break; + } + case relic_procgen_data::type::last: { + debugmsg( "ERROR: invalid relic procgen type" ); + break; + } + } + } + + return ret; +} diff --git a/src/relic.h b/src/relic.h index 55a1658f109f..a457c58b6fe1 100644 --- a/src/relic.h +++ b/src/relic.h @@ -9,11 +9,14 @@ #include "magic.h" #include "magic_enchantment.h" #include "translations.h" +#include "weighted_list.h" class Creature; class JsonIn; class JsonObject; class JsonOut; +class relic; +class relic_procgen_data; struct tripoint; enum class relic_recharge_type { @@ -99,6 +102,107 @@ class relic_recharge void check() const; }; +using relic_procgen_id = string_id; + +class relic_procgen_data +{ + public: + + /* + * various procgen values for passive enchantment values + * this is a template for the ability to write a little bit + * less code and easier maintainability for additional values + */ + template + struct enchantment_value_passive { + enchant_vals::mod type; + // THIS CANNOT BE 0 + int power_per_increment = 1; + // whatever increment is used for the point values + // THIS CANNOT BE 0 + T increment = 1; + T min_value = 0; + T max_value = 0; + + int calc_power( T level ) const { + return std::round( level * static_cast( power_per_increment ) / + static_cast( increment ) ); + } + + bool was_loaded = false; + + void load( const JsonObject &jo ); + void deserialize( JsonIn &jsin ); + }; + + struct enchantment_active { + spell_id activated_spell; + // power cost of spell at level 0 + int base_power = 0; + // power cost increment per spell level increment + int power_per_increment = 1; + // number of spell levels that give the power per increment at + int increment = 1; + // min level of the spell allowed + int min_level = 0; + // max level of the spell allowed + int max_level = 0; + + int calc_power( int level ) const { + return base_power + std::round( level * + static_cast( power_per_increment ) / static_cast( increment ) ); + } + + bool was_loaded = false; + + void load( const JsonObject &jo ); + void deserialize( JsonIn &jsin ); + }; + + struct generation_rules { + // the desired power level for the generated artifact + int power_level = 0; + // the most negative (total) attributes a generated artifact can have + int max_negative_power = 0; + // the maximum number of attributes a generated artifact can have + int max_attributes = INT_MAX; + }; + + enum type { + passive_enchantment_add, + passive_enchantment_mult, + hit_you, + hit_me, + active_enchantment, + last + }; + private: + + weighted_int_list> passive_add_procgen_values; + weighted_int_list> passive_mult_procgen_values; + weighted_int_list passive_hit_you; + weighted_int_list passive_hit_me; + weighted_int_list active_procgen_values; + weighted_int_list type_weights; + weighted_int_list item_weights; + + public: + relic_procgen_id id; + + int power_level( const enchantment &ench ) const; + // power level of the active spell + int power_level( const fake_spell &sp ) const; + + detached_ptr create_item( const relic_procgen_data::generation_rules &rules ) const; + relic generate( const generation_rules &rules, const itype_id &it_id ) const; + + bool was_loaded; + + static void load_relic_procgen_data( const JsonObject &jo, const std::string &src ); + void load( const JsonObject &jo, const std::string & = "" ); + void deserialize( JsonIn &jsin ); +}; + class relic { private: @@ -135,6 +239,9 @@ class relic return recharge_scheme; } + // what is the power level of this artifact, given a specific ruleset + int power_level( const relic_procgen_id &ruleset ) const; + void check() const; }; @@ -148,4 +255,11 @@ void process_recharge( item &itm, Character &carrier ); } // namespace relic_funcs +template struct enum_traits; + +template<> +struct enum_traits { + static constexpr relic_procgen_data::type last = relic_procgen_data::type::last; +}; + #endif // CATA_SRC_RELIC_H