diff --git a/data/json/disease.json b/data/json/disease.json new file mode 100644 index 0000000000000..d7434b8ae333a --- /dev/null +++ b/data/json/disease.json @@ -0,0 +1,12 @@ +[ + { + "type": "disease_type", + "id": "bad_food", + "min_duration": "6 m", + "max_duration": "1 h", + "min_intensity": 1, + "max_intensity": 1, + "health_threshold": 100, + "symptoms": "foodpoison" + } +] diff --git a/data/json/items/comestibles/dairy.json b/data/json/items/comestibles/dairy.json index 071a84e080f3a..b530eedd864ff 100644 --- a/data/json/items/comestibles/dairy.json +++ b/data/json/items/comestibles/dairy.json @@ -6,7 +6,7 @@ "copy-from": "milk", "spoils_in": "12 hours", "price": 19, - "contamination": 5, + "contamination": [ { "disease": "bad_food", "probability": 5 } ], "description": "This is raw, unhomogenized and unpasteurized milk from a cow. It couldn't be any fresher unless you drank it straight from the cow, which might upset it. Depending on your dietary sensibilities, you might want to pasteurize or even boil this before drinking." }, { diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index 9551fcaa0c7fe..62ac30a254e16 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -19,6 +19,7 @@ Use the `Home` key to return to the top. * [`data/json/` JSONs](#datajson-jsons) + [Bionics](#bionics) + [Dreams](#dreams) + + [Disease](#disease_type) + [Item Groups](#item-groups) + [Item Category](#item-category) + [Materials](#materials) @@ -188,6 +189,7 @@ Here's a quick summary of what each of the JSON files contain, broken down by fo | default_blacklist.json | a standard blacklist of joke monsters | doll_speech.json | talk doll speech messages | dreams.json | dream text and linked mutation categories +| disease.json | disease definitions | effects.json | common effects and their effects | emit.json | smoke and gas emissions | flags.json | common flags and their descriptions @@ -457,6 +459,34 @@ When adding a new bionic, if it's not included with another one, you must also a } ``` +### Disease + +| Identifier | Description +|--- |--- +| id | Unique ID. Must be one continuous word, use underscores if necessary. +| min_duration | The minimum duration the disease can last. Uses strings "x m", "x s","x d". +| max_duration | The maximum duration the disease can last. +| min_intensity | The minimum intensity of the effect applied by the disease +| max_intensity | The maximum intensity of the effect. +| health_threshold | The amount of health above which one is immune to the disease. Must be between -200 and 200. (optional ) +| symptoms | The effect applied by the disease. +| affected_bodyparts | The list of bodyparts on which the effect is applied. (optional, default to num_bp) + + +```json + { + "type": "disease_type", + "id": "bad_food", + "min_duration": "6 m", + "max_duration": "1 h", + "min_intensity": 1, + "max_intensity": 1, + "affected_bodyparts": [ "TORSO" ], + "health_threshold": 100, + "symptoms": "foodpoison" + } +``` + ### Item Groups Item groups have been expanded, look at [the detailed docs](ITEM_SPAWN.md) to their new description. @@ -1603,7 +1633,7 @@ CBMs can be defined like this: "freezing_point": 32, // (Optional) Temperature in F at which item freezes, default is water (32F/0C) "cooks_like": "meat_cooked" // (Optional) If the item is used in a recipe, replaces it with its cooks_like "parasites": 10, // (Optional) Probability of becoming parasitised when eating -"contamination": 5, // (Optional) Probability to get food poisoning from this comestible. Values must be in the [0, 100] range. +"contamination": [ { "disease": "bad_food", "probability": 5 } ], // (Optional) List of diseases carried by this comestible and their associated probability. Values must be in the [0, 100] range. ``` ### Containers diff --git a/src/character.cpp b/src/character.cpp index 987579772823b..b18cca5b6b169 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -16,6 +16,7 @@ #include "construction.h" #include "coordinate_conversions.h" #include "debug.h" +#include "disease.h" #include "effect.h" #include "event_bus.h" #include "field.h" @@ -1482,6 +1483,25 @@ void Character::add_effect( const efftype_id &eff_id, const time_duration &dur, Creature::add_effect( eff_id, dur, bp, permanent, intensity, force, deferred ); } +void Character::expose_to_disease( const diseasetype_id dis_type ) +{ + const cata::optional &healt_thresh = dis_type->health_threshold; + if( healt_thresh && healt_thresh.value() < get_healthy() ) { + return; + } + const std::set &bps = dis_type->affected_bodyparts; + if( !bps.empty() ) { + for( const body_part &bp : bps ) { + add_effect( dis_type->symptoms, rng( dis_type->min_duration, dis_type->max_duration ), bp, false, + rng( dis_type->min_intensity, dis_type->max_intensity ) ); + } + } else { + add_effect( dis_type->symptoms, rng( dis_type->min_duration, dis_type->max_duration ), num_bp, + false, + rng( dis_type->min_intensity, dis_type->max_intensity ) ); + } +} + void Character::process_turn() { for( bionic &i : *my_bionics ) { diff --git a/src/character.h b/src/character.h index c4706fc13d908..49f21047172fb 100644 --- a/src/character.h +++ b/src/character.h @@ -565,6 +565,9 @@ class Character : public Creature, public visitable void add_effect( const efftype_id &eff_id, const time_duration &dur, body_part bp = num_bp, bool permanent = false, int intensity = 0, bool force = false, bool deferred = false ) override; + + /**Determine if character is susceptible to dis_type and if so apply the symptoms*/ + void expose_to_disease( const diseasetype_id dis_type ); /** * Handles end-of-turn processing. */ diff --git a/src/consumption.cpp b/src/consumption.cpp index d15c9f18b9ce8..f314af0a4a4ff 100644 --- a/src/consumption.cpp +++ b/src/consumption.cpp @@ -13,6 +13,7 @@ #include "calendar.h" #include "cata_utility.h" #include "debug.h" +#include "disease.h" #include "game.h" #include "itype.h" #include "map.h" @@ -1001,11 +1002,9 @@ bool player::eat( item &food, bool force ) } } - // Chance to get food poisoning from bacterial contamination - if( !will_vomit && !has_bionic( bio_digestion ) ) { - const int contamination = food.get_comestible()->contamination; - if( rng( 1, 100 ) <= contamination ) { - add_effect( effect_foodpoison, rng( 6_minutes, ( nutr + 1 ) * 6_minutes ) ); + for( const std::pair &elem : food.get_comestible()->contamination ) { + if( rng( 1, 100 ) <= elem.second ) { + expose_to_disease( elem.first ); } } diff --git a/src/disease.cpp b/src/disease.cpp new file mode 100644 index 0000000000000..e728bd8fa7ed1 --- /dev/null +++ b/src/disease.cpp @@ -0,0 +1,61 @@ +#include "disease.h" + +#include "assign.h" +#include "generic_factory.h" + +namespace +{ +generic_factory disease_factory( "disease_type" ); +} // namespace + +template<> +const disease_type &string_id::obj() const +{ + return disease_factory.obj( *this ); +} + +template<> +bool string_id::is_valid() const +{ + return disease_factory.is_valid( *this ); +} + +void disease_type::load_disease_type( const JsonObject &jo, const std::string &src ) +{ + disease_factory.load( jo, src ); +} + +void disease_type::load( const JsonObject &jo, const std::string & ) +{ + disease_type new_disease; + + assign( jo, "id", id ); + assign( jo, "min_duration", min_duration ); + assign( jo, "max_duration", max_duration ); + assign( jo, "min_intensity", min_intensity ); + assign( jo, "max_intensity", max_intensity ); + assign( jo, "health_threshold", health_threshold ); + assign( jo, "symptoms", symptoms ); + + JsonArray jsr = jo.get_array( "affected_bodyparts" ); + while( jsr.has_more() ) { + new_disease.affected_bodyparts.emplace( get_body_part_token( jsr.next_string() ) ); + } + +} + +const std::vector &disease_type::get_all() +{ + return disease_factory.get_all(); +} + +void disease_type::check_disease_consistency() +{ + for( const disease_type &dis : get_all() ) { + const efftype_id &symp = dis.symptoms; + if( !symp.is_valid() ) { + debugmsg( "disease_type %s has invalid efftype_id %s in symptoms", dis.id.c_str(), symp.c_str() ); + } + } +} + diff --git a/src/disease.h b/src/disease.h new file mode 100644 index 0000000000000..aa8b474182b1b --- /dev/null +++ b/src/disease.h @@ -0,0 +1,33 @@ +#pragma once +#ifndef DISEASE_H +#define DISEASE_H + +#include "bodypart.h" +#include "effect.h" +#include "type_id.h" +#include "json.h" + +class disease_type +{ + public: + static void load_disease_type( const JsonObject &jo, const std::string &src ); + void load( const JsonObject &jo, const std::string & ); + static const std::vector &get_all(); + static void check_disease_consistency(); + bool was_loaded; + + diseasetype_id id; + time_duration min_duration = 1_turns; + time_duration max_duration = 1_turns; + int min_intensity = 1; + int max_intensity = 1; + /**Affected body parts*/ + std::set affected_bodyparts; + /**If not empty this sets the health threshold above which you're immune to the disease*/ + cata::optional health_threshold; + /**effect applied by this disease*/ + efftype_id symptoms; + +}; +#endif + diff --git a/src/init.cpp b/src/init.cpp index d697304ae19a4..8b81dd44ec20b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -22,6 +22,7 @@ #include "creature.h" #include "debug.h" #include "dialogue.h" +#include "disease.h" #include "effect.h" #include "emit.h" #include "event_statistics.h" @@ -221,6 +222,7 @@ void DynamicDataLoader::initialize() add( "enchantment", &enchantment::load_enchantment ); add( "hit_range", &Creature::load_hit_range ); add( "scent_type", &scent_type::load_scent_type ); + add( "disease_type", &disease_type::load_disease_type ); // json/colors.json would be listed here, but it's loaded before the others (see init_colors()) // Non Static Function Access @@ -696,6 +698,7 @@ void DynamicDataLoader::check_consistency( loading_ui &ui ) { _( "Statistics" ), &event_statistic::check_consistency }, { _( "Scent types" ), &scent_type::check_scent_consistency }, { _( "Scores" ), &score::check_consistency }, + { _( "Disease types" ), &disease_type::check_disease_consistency }, { _( "Factions" ), &faction_template::check_consistency }, } }; diff --git a/src/item_factory.cpp b/src/item_factory.cpp index dd0e7a7fed803..86b216a2a61af 100644 --- a/src/item_factory.cpp +++ b/src/item_factory.cpp @@ -514,6 +514,14 @@ void Item_factory::finalize_post( itype &obj ) } } + if( obj.comestible ) { + for( const std::pair elem : obj.comestible->contamination ) { + const diseasetype_id dtype = elem.first; + if( !dtype.is_valid() ) { + debugmsg( "contamination in %s contains invalid diseasetype_id %s.", obj.id, dtype.c_str() ); + } + } + } for( std::string &line : obj.ascii_picture ) { if( utf8_width( remove_color_tags( line ) ) > ascii_art_width ) { line = trim_by_length( line, ascii_art_width ); @@ -1834,12 +1842,16 @@ void Item_factory::load( islot_comestible &slot, const JsonObject &jo, const std assign( jo, "fatigue_mod", slot.fatigue_mod, strict ); assign( jo, "healthy", slot.healthy, strict ); assign( jo, "parasites", slot.parasites, strict, 0 ); - assign( jo, "contamination", slot.contamination, strict, 0, 100 ); assign( jo, "radiation", slot.radiation, strict ); assign( jo, "freezing_point", slot.freeze_point, strict ); assign( jo, "spoils_in", slot.spoils, strict, 1_hours ); assign( jo, "cooks_like", slot.cooks_like, strict ); assign( jo, "smoking_result", slot.smoking_result, strict ); + + for( const JsonObject &jsobj : jo.get_array( "contamination" ) ) { + slot.contamination.emplace( diseasetype_id( jsobj.get_string( "disease" ) ), + jsobj.get_int( "probability" ) ); + } if( jo.has_member( "primary_material" ) ) { std::string mat = jo.get_string( "primary_material" ); diff --git a/src/itype.h b/src/itype.h index ec30924c8de6c..fe49507a219d7 100644 --- a/src/itype.h +++ b/src/itype.h @@ -155,15 +155,15 @@ struct islot_comestible { /** chance (odds) of becoming parasitised when eating (zero if never occurs) */ int parasites = 0; - /** probability [0, 100] to get food poisoning from this comestible */ - int contamination = 0; - /**Amount of radiation you get from this comestible*/ int radiation = 0; /** freezing point in degrees Fahrenheit, below this temperature item can freeze */ int freeze_point = temperatures::freezing; + /**List of diseases carried by this comestible and their associated probability*/ + std::map contamination; + //** specific heats in J/(g K) and latent heat in J/g */ float specific_heat_liquid = 4.186; float specific_heat_solid = 2.108; diff --git a/src/type_id.h b/src/type_id.h index 065f5041d690e..af103421a65d7 100644 --- a/src/type_id.h +++ b/src/type_id.h @@ -31,6 +31,9 @@ using efftype_id = string_id; class scent_type; using scenttype_id = string_id; +class disease_type; +using diseasetype_id = string_id; + class emit; using emit_id = string_id;