diff --git a/data/json/effects.json b/data/json/effects.json index 73ac7be9566f..86b53ce63a48 100644 --- a/data/json/effects.json +++ b/data/json/effects.json @@ -180,6 +180,7 @@ "id": "blind", "name": ["Blind"], "desc": ["Range of Sight: 0"], + "removes_effects": ["glare", "darkness"], "apply_message": "You're blinded!", "remove_message": "Your sight returns!", "rating": "bad" @@ -848,7 +849,8 @@ }, { "type": "effect_type", - "id": "flushot" + "id": "flushot", + "blocks_effects": ["flu"] }, { "type": "effect_type", @@ -917,7 +919,7 @@ "max_intensity": 2, "int_dur_factor": 150, "dur_add_perc": 15, - "removes_effect": "winded", + "removes_effects": ["winded"], "base_mods": { "speed_mod": [-10], "str_mod": [-2], diff --git a/doc/EFFECTS_JSON.md b/doc/EFFECTS_JSON.md index e604e3163389..798cdf25f391 100644 --- a/doc/EFFECTS_JSON.md +++ b/doc/EFFECTS_JSON.md @@ -183,13 +183,21 @@ These fields are used to determine if an effect is being resisted or not. If the matching trait or effect then they are "resisting" the effect, which changes its effects and description. Effects can only have one "resist_trait" and one "resist_effect" at a time. -### Removes effect +### Removes effects ```C++ - "removes_effect": "bite" + "removes_effects": ["bite", "flu"] ``` -This field will cause an effect to automatically remove any other copies of the listed effect if it is present. -In the example above the placed effect would automatically cure any bite wounds the player had. An effect can only -have one "removes_effect" field at a time. +This field will cause an effect to automatically remove any other copies of the listed effects if they are present. +In the example above the placed effect would automatically cure any bite wounds or flu the player had. Any values here +automatically count for "blocks_effects" as well, no need to duplicate them there. + +### Blocks effects +```C++ + "blocks_effects": ["cold", "flu"] +``` +This field will cause an effect to prevent the placement of the listed effects. In the example above the effect would +prevent the player from catching the cold or the flu (BUT WOULD NOT CURE ANY ONGOING COLDS OR FLUS). Any effects present +in "removes_effects" are automatically added to "blocks_effects", no need for manual duplication. ### Effect limiters ```C++ diff --git a/src/creature.cpp b/src/creature.cpp index e79816ffdbb5..a78c65ff1156 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -719,7 +719,7 @@ void Creature::add_eff_effects(effect e, bool reduced) (void)reduced; return; } - + void Creature::add_effect(efftype_id eff_id, int dur, body_part bp, bool permanent, int intensity) { // Mutate to a main (HP'd) body_part if necessary. @@ -752,7 +752,7 @@ void Creature::add_effect(efftype_id eff_id, int dur, body_part bp, bool permane } else if (e.get_int_add_val() != 0) { e.mod_intensity(e.get_int_add_val()); } - + // Bound intensity by [1, max intensity] if (e.get_intensity() < 1) { add_msg( m_debug, "Bad intensity, ID: %s", e.get_id().c_str() ); @@ -762,12 +762,27 @@ void Creature::add_effect(efftype_id eff_id, int dur, body_part bp, bool permane } } } - + if (found == false) { // If we don't already have it then add a new one + + // First make sure it's a valid effect if (effect_types.find(eff_id) == effect_types.end()) { return; } + // Then check if the effect is blocked by another + for( auto &elem : effects ) { + for( auto &_effect_it : elem.second ) { + for( const auto blocked_effect : _effect_it.second.get_blocks_effects() ) { + if (blocked_effect == eff_id) { + // The effect is blocked by another, return + return; + } + } + } + } + + // Now we can make the new effect for application effect new_eff(&effect_types[eff_id], dur, bp, permanent, intensity); effect &e = new_eff; // Bound to max duration @@ -819,7 +834,7 @@ bool Creature::remove_effect(efftype_id eff_id, body_part bp) //Effect doesn't exist, so do nothing return false; } - + if (is_player()) { // Print the removal message and add the memorial log if needed if(effect_types[eff_id].get_remove_message() != "") { @@ -831,7 +846,7 @@ bool Creature::remove_effect(efftype_id eff_id, body_part bp) pgettext("memorial_female", effect_types[eff_id].get_remove_memorial_log().c_str())); } - + // num_bp means remove all of a given effect id if (bp == num_bp) { effects.erase(eff_id); @@ -894,7 +909,7 @@ void Creature::process_effects() // passed in to this function. std::vector rem_ids; std::vector rem_bps; - + // Decay/removal of effects for( auto &elem : effects ) { for( auto &_it : elem.second ) { @@ -907,7 +922,7 @@ void Creature::process_effects() _it.second.decay( rem_ids, rem_bps, calendar::turn, is_player() ); } } - + // Actually remove effects. This should be the last thing done in process_effects(). for (size_t i = 0; i < rem_ids.size(); ++i) { remove_effect( rem_ids[i], rem_bps[i] ); diff --git a/src/effect.cpp b/src/effect.cpp index 10d1d54f3a0c..2d332dcec977 100644 --- a/src/effect.cpp +++ b/src/effect.cpp @@ -747,6 +747,12 @@ const std::vector &effect::get_removes_effects() const { return eff_type->removes_effects; } +const std::vector effect::get_blocks_effects() const +{ + std::vector ret = eff_type->removes_effects; + ret.insert(ret.end(), eff_type->blocks_effects.begin(), eff_type->blocks_effects.end()); + return ret; +} int effect::get_mod(std::string arg, bool reduced) const { @@ -1122,6 +1128,7 @@ void load_effect_type(JsonObject &jo) new_etype.resist_trait = jo.get_string("resist_trait", ""); new_etype.resist_effect = jo.get_string("resist_effect", ""); new_etype.removes_effects = jo.get_string_array("removes_effects"); + new_etype.blocks_effects = jo.get_string_array("blocks_effects"); new_etype.max_intensity = jo.get_int("max_intensity", 1); new_etype.max_duration = jo.get_int("max_duration", 0); diff --git a/src/effect.h b/src/effect.h index 505794923801..2f557cf33956 100644 --- a/src/effect.h +++ b/src/effect.h @@ -36,7 +36,7 @@ class effect_type /** Returns if an effect is good or bad for message display. */ effect_rating get_rating() const; - + /** Returns true if there is a listed name in the JSON entry for each intensity from * 1 to max_intensity. */ bool use_name_ints() const; @@ -71,26 +71,27 @@ class effect_type protected: int max_intensity; int max_duration; - + int dur_add_perc; int int_add_val; - + int int_decay_step; int int_decay_tick; int int_dur_factor; - + bool main_parts_only; - + std::string resist_trait; std::string resist_effect; std::vector removes_effects; - + std::vector blocks_effects; + std::vector> miss_msgs; - + bool pain_sizing; bool hurt_sizing; bool harmful_cough; - // TODO: Once addictions are JSON-ized it should be trivial to convert this to a + // TODO: Once addictions are JSON-ized it should be trivial to convert this to a // "generic" addiction reduces value bool pkill_addict_reduces; @@ -99,7 +100,7 @@ class effect_type std::vector desc; std::vector reduced_desc; bool part_descs; - + std::vector> decay_msgs; effect_rating rating; @@ -108,7 +109,7 @@ class effect_type std::string apply_memorial_log; std::string remove_message; std::string remove_memorial_log; - + /** Key tuple order is:("base_mods"/"scaling_mods", reduced: bool, type of mod: "STR", desired argument: "tick") */ std::unordered_map, double> mod_data; }; @@ -142,7 +143,7 @@ class effect : public JsonSerializer, public JsonDeserializer /** Returns the effect's matching effect_type. */ effect_type *get_effect_type() const; - + /** Decays effect durations, pushing their id and bp's back to rem_ids and rem_bps for removal later * if their duration is <= 0. This is called in the middle of a loop through all effects, which is * why we aren't allowed to remove the effects here. */ @@ -158,7 +159,7 @@ class effect : public JsonSerializer, public JsonDeserializer void mod_duration(int dur); /** Multiplies the duration, capping at max_duration if it exists. */ void mult_duration(double dur); - + /** Returns the targeted body_part of the effect. This is num_bp for untargeted effects. */ body_part get_bp() const; /** Sets the targeted body_part of an effect. */ @@ -179,14 +180,16 @@ class effect : public JsonSerializer, public JsonDeserializer void set_intensity(int nintensity); /** Mods an effect's intensity, capping at max_intensity. */ void mod_intensity(int nintensity); - + /** Returns the string id of the resist trait to be used in has_trait("id"). */ std::string get_resist_trait() const; /** Returns the string id of the resist effect to be used in has_effect("id"). */ std::string get_resist_effect() const; /** Returns the string ids of the effects removed by this effect to be used in remove_effect("id"). */ const std::vector &get_removes_effects() const; - + /** Returns the string ids of the effects blocked by this effect to be used in add_effect("id"). */ + const std::vector get_blocks_effects() const; + /** Returns the matching modifier type from an effect, used for getting actual effect effects. */ int get_mod(std::string arg, bool reduced = false) const; /** Returns the average return of get_mod for a modifier type. Used in effect description displays. */ @@ -204,7 +207,7 @@ class effect : public JsonSerializer, public JsonDeserializer /** Checks to see if a given modifier type can activate, and performs any rolls required to do so. mod is a direct * multiplier on the overall chance of a modifier type activating. */ bool activated(unsigned int turn, std::string arg, int val, bool reduced = false, double mod = 1) const; - + /** Returns the modifier caused by addictions. Currently only handles painkiller addictions. */ double get_addict_mod(std::string arg, int addict_level) const; /** Returns true if the coughs caused by an effect can harm the player directly. */ @@ -213,10 +216,10 @@ class effect : public JsonSerializer, public JsonDeserializer int get_dur_add_perc() const; /** Returns the amount an already existing effect intensity is modified by further applications of the same effect. */ int get_int_add_val() const; - + /** Returns a vector of the miss message messages and chances for use in add_miss_reason() while the effect is in effect. */ std::vector> get_miss_msgs() const; - + /** Returns the value used for display on the speed modifier window in the player status menu. */ std::string get_speed_name() const; diff --git a/src/player.cpp b/src/player.cpp index ba2941dcf326..3c421a807d9a 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1,4 +1,4 @@ -#include "player.h" +#include "player.h" #include "profession.h" #include "bionics.h" #include "mission.h" @@ -816,7 +816,7 @@ void player::update_bodytemp() } else if( furn_at_pos == f_makeshift_bed || furn_at_pos == f_armchair || furn_at_pos == f_sofa ) { floor_bedding_warmth += 500; - } else if( veh && veh->part_with_feature (vpart, "BED") >= 0 && + } else if( veh && veh->part_with_feature (vpart, "BED") >= 0 && veh->part_with_feature (vpart, "SEAT") >= 0) { floor_bedding_warmth += 250; // BED+SEAT is intentionally worse than just BED } else if( veh && veh->part_with_feature (vpart, "BED") >= 0 ) { @@ -1897,7 +1897,7 @@ void player::memorial( std::ofstream &memorial_file, std::string epitaph ) const auto closest_city = overmap_buffer.closest_city( point( global_sm_pos.x, global_sm_pos.y ) ); std::string kill_place; if( !closest_city ) { - //~ First parameter is a pronoun (“He”/“She”), second parameter is a terrain name. + //~ First parameter is a pronoun ("He"/"She"), second parameter is a terrain name. kill_place = string_format(_("%s was killed in a %s in the middle of nowhere."), pronoun.c_str(), tername.c_str()); } else { @@ -1905,16 +1905,16 @@ void player::memorial( std::ofstream &memorial_file, std::string epitaph ) //Give slightly different messages based on how far we are from the middle const int distance_from_city = closest_city.distance - nearest_city.s; if(distance_from_city > nearest_city.s + 4) { - //~ First parameter is a pronoun (“He”/“She”), second parameter is a terrain name. + //~ First parameter is a pronoun ("He"/"She"), second parameter is a terrain name. kill_place = string_format(_("%s was killed in a %s in the wilderness."), pronoun.c_str(), tername.c_str()); } else if(distance_from_city >= nearest_city.s) { - //~ First parameter is a pronoun (“He”/“She”), second parameter is a terrain name, third parameter is a city name. + //~ First parameter is a pronoun ("He"/"She"), second parameter is a terrain name, third parameter is a city name. kill_place = string_format(_("%s was killed in a %s on the outskirts of %s."), pronoun.c_str(), tername.c_str(), nearest_city.name.c_str()); } else { - //~ First parameter is a pronoun (“He”/“She”), second parameter is a terrain name, third parameter is a city name. + //~ First parameter is a pronoun ("He"/"She"), second parameter is a terrain name, third parameter is a city name. kill_place = string_format(_("%s was killed in a %s in %s."), pronoun.c_str(), tername.c_str(), nearest_city.name.c_str()); } @@ -1926,7 +1926,7 @@ void player::memorial( std::ofstream &memorial_file, std::string epitaph ) memorial_file << "\n"; memorial_file << string_format(_("In memory of: %s"), name.c_str()) << "\n"; if(epitaph.length() > 0) { //Don't record empty epitaphs - //~ The “%s” will be replaced by an epitaph as displyed in the memorial files. Replace the quotation marks as appropriate for your language. + //~ The "%s" will be replaced by an epitaph as displyed in the memorial files. Replace the quotation marks as appropriate for your language. memorial_file << string_format(pgettext("epitaph","\"%s\""), epitaph.c_str()) << "\n\n"; } //~ First parameter: Pronoun, second parameter: a profession name (with article) @@ -4270,23 +4270,23 @@ bool player::has_pda() bool player::has_alarm_clock() { return ( has_item_with_flag("ALARMCLOCK") || - ( - ( g->m.veh_at( posx(), posy() ) != nullptr ) && + ( + ( g->m.veh_at( posx(), posy() ) != nullptr ) && !g->m.veh_at( posx(), posy() )->all_parts_with_feature( "ALARMCLOCK", true ).empty() ) || has_bionic("bio_watch") - ); + ); } bool player::has_watch() { return ( has_item_with_flag("WATCH") || - ( - ( g->m.veh_at( posx(), posy() ) != nullptr ) && + ( + ( g->m.veh_at( posx(), posy() ) != nullptr ) && !g->m.veh_at( posx(), posy() )->all_parts_with_feature( "WATCH", true ).empty() ) || has_bionic("bio_watch") - ); + ); } void player::pause() @@ -5139,7 +5139,7 @@ void player::get_sick() if (!has_effect("flu") && !has_effect("common_cold") && one_in(900 + get_healthy() + (has_trait("DISRESISTANT") ? 300 : 0))) { - if (one_in(6) && !has_effect("flushot")) { + if (one_in(6)) { add_env_effect("flu", bp_mouth, 3, rng(40000, 80000)); } else { add_env_effect("common_cold", bp_mouth, 3, rng(20000, 60000)); @@ -9553,7 +9553,7 @@ bool player::eat(item *eaten, it_comest *comest) return true; } -int player::nutrition_for(const it_comest *comest) +int player::nutrition_for(const it_comest *comest) { /* thresholds: ** 100 : 1x @@ -12609,7 +12609,7 @@ void player::practice( const Skill* s, int amount, int cap ) amount /= 2; } - + if (skillLevel(s) > cap) { //blunt grinding cap implementation for crafting amount = 0; @@ -12619,7 +12619,7 @@ void player::practice( const Skill* s, int amount, int cap ) s->name().c_str(), curLevel); } } - + if (amount > 0 && level.isTraining()) { int oldLevel = skillLevel(s); skillLevel(s).train(amount); @@ -12632,7 +12632,7 @@ void player::practice( const Skill* s, int amount, int cap ) add_msg(m_info, _("You feel that %s tasks of this level are becoming trivial."), s->name().c_str()); } - + int chance_to_drop = focus_pool; focus_pool -= chance_to_drop / 100;