diff --git a/data/json/bionics.json b/data/json/bionics.json index f91a7b41ffe4a..f1508863256fe 100644 --- a/data/json/bionics.json +++ b/data/json/bionics.json @@ -261,6 +261,7 @@ "act_cost": "30 kJ", "react_cost": "30 kJ", "time": 1, + "enchantments": [ "ENCH_INVISIBILITY" ], "flags": [ "BIONIC_TOGGLED" ] }, { @@ -709,6 +710,7 @@ "description": "When active, this bionic eliminates all light within a 2 tile radius through destructive interference.", "occupied_bodyparts": [ [ "TORSO", 16 ] ], "flags": [ "BIONIC_TOGGLED" ], + "enchantments": [ "ENCH_INVISIBILITY" ], "act_cost": "9 kJ", "react_cost": "9 kJ", "time": 1 diff --git a/data/json/effects.json b/data/json/effects.json index 8c82ac522619d..0667194d405e5 100644 --- a/data/json/effects.json +++ b/data/json/effects.json @@ -629,6 +629,13 @@ "rating": "bad", "base_mods": { "vomit_chance": [ 500 ] } }, + { + "type": "effect_type", + "id": "invisibility", + "name": [ "Invisible" ], + "desc": [ "You are invisible." ], + "flags": [ "EFFECT_INVISIBLE" ] + }, { "type": "effect_type", "id": "took_flumed" diff --git a/data/json/enchantments.json b/data/json/enchantments.json new file mode 100644 index 0000000000000..f5635831ef01a --- /dev/null +++ b/data/json/enchantments.json @@ -0,0 +1,8 @@ +[ + { + "type": "enchantment", + "id": "ENCH_INVISIBILITY", + "condition": "ALWAYS", + "ench_effects": [ { "effect": "invisibility", "intensity": 1 } ] + } +] diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index a74af528e6197..395394331dd6c 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -452,6 +452,7 @@ This section describes each json file and their contents. Each json has their ow | coverage_power_gen_penalty | (_optional_) Fraction of coverage diminishing fuel_efficiency. Float between 0.0 and 1.0. (default: `nullopt`) | power_gen_emission | (_optional_) `emit_id` of the field emitted by this bionic when it produces energy. Emit_ids are defined in `emit.json`. | stat_bonus | (_optional_) List of passive stat bonus. Stat are designated as follow: "DEX", "INT", "STR", "PER". +| enchantments | (_optional_) List of enchantments applied by this CBM (see MAGIC.md for instructions on enchantment. NB: enchantments are not necessarily magic.) ```C++ { diff --git a/doc/MAGIC.md b/doc/MAGIC.md index 235af58e67476..2153c02d6856f 100644 --- a/doc/MAGIC.md +++ b/doc/MAGIC.md @@ -198,3 +198,122 @@ You can assign a spell as a special attack for a monster. * spell_id: the id for the spell being cast. * spell_level: the level at which the spell is cast. Spells cast by monsters do not gain levels like player spells. * cooldown: how often the monster can cast this spell + +### Enchantments +| Identifier | Description +|--- |--- +| id | Unique ID. Must be one continuous word, use underscores if necessary. +| has | How an enchantment determines if it is in the right location in order to qualify for being active. "WIELD" - when wielded in your hand * "WORN" - when worn as armor * "HELD" - when in your inventory +| condition | How an enchantment determines if you are in the right environments in order for the enchantment to qualify for being active. * "ALWAYS" - Always and forevermore * "UNDERGROUND" - When the owner of the item is below Z-level 0 * "UNDERWATER" - When the owner is in swimmable terrain +| hit_you_effect | A spell that activates when you melee_attack a creature. The spell is centered on the location of the creature unless self = true, then it is centered on your location. Follows the template for defining "fake_spell" +| hit_me_effect | A spell that activates when you are hit by a creature. The spell is centered on your location. Follows the template for defining "fake_spell" +| intermittent_activation | Spells that activate centered on you depending on the duration. The spells follow the "fake_spell" template. +| values | Anything that is a number that can be modified. The id field is required, and "add" and "multiply" are optional. A "multiply" value of -1 is -100% and a multiply of 2.5 is +250%. Add is always before multiply. See allowed id below. + + + +```C++ + { + "type": "enchantment", + "id": "MEP_INK_GLAND_SPRAY", + "hit_me_effect": [ + { + "id": "generic_blinding_spray_1", + "hit_self": false, + "once_in": 15, + "message": "Your ink glands spray some ink into %2$s's eyes.", + "npc_message": "%1$s's ink glands spay some ink into %2$s's eyes." + } + ] + }, + { + "type": "enchantment", + "id": "ENCH_INVISIBILITY", + "condition": "ALWAYS", + "ench_effects": [ { "effect": "invisibility", "intensity": 1 } ] + "has": "WIELD", + "hit_you_effect": [ { "id": "AEA_FIREBALL" } ], + "hit_me_effect": [ { "id": "AEA_HEAL" } ], + "values": [ { "value": "STRENGTH", "multiply": 1.1, "add": -5 } ], + "intermittent_activation": { + "effects": [ + { + "frequency": "1 hour", + "spell_effects": [ + { "id": "AEA_ADRENALINE" } + ] + } + ] + } + } + +``` + ### Allowed id for values +The allowed values are as follows: + +- Effects for the character that has the enchantment: +* STRENGTH +* DEXTERITY +* PERCEPTION +* INTELLIGENCE +* SPEED +* ATTACK_COST +* ATTACK_SPEED +* MOVE_COST +* METABOLISM +* MAX_MANA +* REGEN_MANA +* BIONIC_POWER +* MAX_STAMINA +* REGEN_STAMINA +* MAX_HP +* REGEN_HP +* THIRST +* FATIGUE +* PAIN +* BONUS_DODGE +* BONUS_BLOCK +* BONUS_DAMAGE +* ATTACK_NOISE +* SPELL_NOISE +* SHOUT_NOISE +* FOOTSTEP_NOISE +* SIGHT_RANGE +* CARRY_WEIGHT +* CARRY_VOLUME +* SOCIAL_LIE +* SOCIAL_PERSUADE +* SOCIAL_INTIMIDATE +* ARMOR_BASH +* ARMOR_CUT +* ARMOR_STAB +* ARMOR_HEAT +* ARMOR_COLD +* ARMOR_ELEC +* ARMOR_ACID +* ARMOR_BIO + +- Effects for the item that has the enchantment: +* ITEM_DAMAGE_BASH +* ITEM_DAMAGE_CUT +* ITEM_DAMAGE_STAB +* ITEM_DAMAGE_HEAT +* ITEM_DAMAGE_COLD +* ITEM_DAMAGE_ELEC +* ITEM_DAMAGE_ACID +* ITEM_DAMAGE_BIO +* ITEM_DAMAGE_AP +* ITEM_ARMOR_BASH +* ITEM_ARMOR_CUT +* ITEM_ARMOR_STAB +* ITEM_ARMOR_HEAT +* ITEM_ARMOR_COLD +* ITEM_ARMOR_ELEC +* ITEM_ARMOR_ACID +* ITEM_ARMOR_BIO +* ITEM_WEIGHT +* ITEM_ENCUMBRANCE +* ITEM_VOLUME +* ITEM_COVERAGE +* ITEM_ATTACK_SPEED +* ITEM_WET_PROTECTION diff --git a/src/bionics.cpp b/src/bionics.cpp index ef58a6505b2e5..5263578bc6237 100644 --- a/src/bionics.cpp +++ b/src/bionics.cpp @@ -377,6 +377,9 @@ bool Character::activate_bionic( int b, bool eff_only ) if( bio.info().charge_time > 0 ) { bio.charge_timer = bio.info().charge_time; } + if( !bio.id->enchantments.empty() ) { + recalculate_enchantment_cache(); + } } auto add_msg_activate = [&]() { @@ -977,6 +980,9 @@ bool Character::deactivate_bionic( int b, bool eff_only ) // Recalculate stats (strength, mods from pain etc.) that could have been affected reset_encumbrance(); reset(); + if( !bio.id->enchantments.empty() ) { + recalculate_enchantment_cache(); + } // Also reset crafting inventory cache if this bionic spawned a fake item if( !bio.info().fake_item.empty() ) { @@ -2436,6 +2442,9 @@ void Character::add_bionic( const bionic_id &b ) reset_encumbrance(); recalc_sight_limits(); + if( !b->enchantments.empty() ) { + recalculate_enchantment_cache(); + } } void Character::remove_bionic( const bionic_id &b ) @@ -2456,6 +2465,9 @@ void Character::remove_bionic( const bionic_id &b ) *my_bionics = new_my_bionics; reset_encumbrance(); recalc_sight_limits(); + if( !b->enchantments.empty() ) { + recalculate_enchantment_cache(); + } } int player::num_bionics() const @@ -2571,6 +2583,8 @@ void load_bionic( const JsonObject &jsobj ) new_bionic.weight_capacity_modifier = jsobj.get_float( "weight_capacity_modifier", 1.0 ); + assign( jsobj, "enchantments", new_bionic.enchantments ); + assign( jsobj, "weight_capacity_bonus", new_bionic.weight_capacity_bonus, false, 0_gram ); assign( jsobj, "exothermic_power_gen", new_bionic.exothermic_power_gen ); assign( jsobj, "power_gen_emission", new_bionic.power_gen_emission ); @@ -2640,6 +2654,11 @@ void check_bionics() bio.first.c_str(), mid.c_str() ); } } + for( const enchantment_id &eid : bio.first->enchantments ) { + if( !eid.is_valid() ) { + debugmsg( "Bionic %s uses undefined enchantment %s", bio.first.c_str(), eid.c_str() ); + } + } for( const bionic_id &bid : bio.second.included_bionics ) { if( !bid.is_valid() ) { debugmsg( "Bionic %s includes undefined bionic %s", diff --git a/src/bionics.h b/src/bionics.h index a8fa3425b8ba2..3bf1c9279ac93 100644 --- a/src/bionics.h +++ b/src/bionics.h @@ -109,6 +109,9 @@ struct bionic_data { /**Amount of cut protection offered by this bionic*/ std::map cut_protec; + /** bionic enchantments */ + std::vector enchantments; + /** * Body part slots used to install this bionic, mapped to the amount of space required. */ diff --git a/src/character.cpp b/src/character.cpp index f4cb8bbee5865..5982c99ecc52e 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -1548,6 +1548,8 @@ void Character::process_turn() } Creature::process_turn(); + + enchantment_cache.activate_passive( *this ); } void Character::recalc_hp() @@ -6311,8 +6313,6 @@ bool Character::is_invisible() const { return ( has_effect_with_flag( flag_EFFECT_INVISIBLE ) || - has_active_bionic( str_bio_cloak ) || - has_active_bionic( str_bio_night ) || is_wearing_active_optcloak() || has_trait( trait_DEBUG_CLOAK ) || has_artifact_with( AEP_INVISIBLE ) @@ -7961,6 +7961,20 @@ void Character::recalculate_enchantment_cache() } } } + + for( const bionic &bio : *my_bionics ) { + const bionic_id &bid = bio.id; + if( bid->toggled && !bio.powered ) { + continue; + } + + for( const enchantment_id &ench_id : bid->enchantments ) { + const enchantment &ench = ench_id.obj(); + if( ench.is_active( *this ) ) { + enchantment_cache.force_add( ench ); + } + } + } } double Character::calculate_by_enchantment( double modify, enchantment::mod value, diff --git a/src/magic_enchantment.cpp b/src/magic_enchantment.cpp index bbe8cddd75be6..05a401d88186d 100644 --- a/src/magic_enchantment.cpp +++ b/src/magic_enchantment.cpp @@ -240,6 +240,10 @@ void enchantment::load( const JsonObject &jo, const std::string & ) active_conditions.second = io::string_to_enum( jo.get_string( "condition", "ALWAYS" ) ); + for( JsonObject jsobj : jo.get_array( "ench_effects" ) ) { + ench_effects.emplace( efftype_id( jsobj.get_string( "effect" ) ), jsobj.get_int( "intensity" ) ); + } + if( jo.has_array( "values" ) ) { for( const JsonObject value_obj : jo.get_array( "values" ) ) { const enchantment::mod value = io::string_to_enum( value_obj.get_string( "value" ) ); @@ -345,6 +349,12 @@ void enchantment::force_add( const enchantment &rhs ) hit_you_effect.insert( hit_you_effect.end(), rhs.hit_you_effect.begin(), rhs.hit_you_effect.end() ); + ench_effects.insert( rhs.ench_effects.begin(), rhs.ench_effects.end() ); + + if( rhs.emitter ) { + emitter = rhs.emitter; + } + for( const std::pair> &act_pair : rhs.intermittent_activation ) { for( const fake_spell &fake : act_pair.second ) { @@ -399,6 +409,9 @@ void enchantment::activate_passive( Character &guy ) const if( emitter ) { g->m.emit_field( guy.pos(), *emitter ); } + for( const std::pair eff : ench_effects ) { + guy.add_effect( eff.first, 1_seconds, num_bp, false, eff.second ); + } for( const std::pair> &activation : intermittent_activation ) { // a random approximation! diff --git a/src/magic_enchantment.h b/src/magic_enchantment.h index 8ba26adc2b874..24d7a375fe38f 100644 --- a/src/magic_enchantment.h +++ b/src/magic_enchantment.h @@ -146,6 +146,7 @@ class enchantment void cast_hit_me( Character &caster, const Creature *target ) const; private: cata::optional emitter; + std::map ench_effects; // values that add to the base value std::map values_add; // values that get multiplied to the base value