diff --git a/data/mods/Aftershock/items/comestibles.json b/data/mods/Aftershock/items/comestibles.json index a73f11e34e478..127bf14320281 100644 --- a/data/mods/Aftershock/items/comestibles.json +++ b/data/mods/Aftershock/items/comestibles.json @@ -90,5 +90,28 @@ { "id": "downed", "duration": 1 } ] } + }, + { + "id": "panacea", + "type": "COMESTIBLE", + "comestible_type": "MED", + "name": { "str": "Panaceus", "str_pl": "Panaceii" }, + "symbol": "!", + "color": "red", + "copy-from": "panacea", + "use_action": { + "type": "consume_drug", + "activation_message": "You feel AMAZING!", + "effects": [ { "id": "panacea", "duration": "1 m" }, { "id": "cureall" } ], + "damage_over_time": [ + { + "damage_type": "true", + "duration": "1 m", + "amount": -10, + "bodyparts": [ "torso", "head", "arm_l", "leg_l", "arm_r", "leg_r" ] + } + ] + }, + "flags": [ "NPC_SAFE", "IRREPLACEABLE_CONSUMABLE" ] } ] diff --git a/doc/JSON_INFO.md b/doc/JSON_INFO.md index 55fadb0dd8148..fb136425b0add 100644 --- a/doc/JSON_INFO.md +++ b/doc/JSON_INFO.md @@ -2270,6 +2270,14 @@ The contents of use_action fields can either be a string indicating a built-in f "type" : "consume_drug", // A drug the player can consume. "activation_message" : "You smoke your crack rocks. Mother would be proud.", // Message, ayup. "effects" : { "high": 15 }, // Effects and their duration. + "damage_over_time": [ + { + "damage_type": "true", // Type of damage + "duration": "1 m", // For how long this damage will be applied + "amount": -10, // Amount of damage applied every turn, negative damage heals + "bodyparts": [ "torso", "head", "arm_l", "leg_l", "arm_r", "leg_r" ] // Body parts hit by the damage + } + ] "stat_adjustments": {"hunger" : -10}, // Adjustment to make to player stats. "fields_produced" : {"cracksmoke" : 2}, // Fields to produce, mostly used for smoke. "charges_needed" : { "fire" : 1 }, // Charges to use in the process of consuming the drug. diff --git a/src/character.cpp b/src/character.cpp index 1d1b1a9172319..4e650b50f388b 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -8584,6 +8584,11 @@ int Character::reduce_healing_effect( const efftype_id &eff_id, int remove_med, return intensity; } +void Character::heal_bp( bodypart_id bp, int dam ) +{ + heal( bp->token, dam ); +} + void Character::heal( body_part healed, int dam ) { hp_part healpart; diff --git a/src/character.h b/src/character.h index 6a72dc603cd0e..2a5fee27125cb 100644 --- a/src/character.h +++ b/src/character.h @@ -794,6 +794,7 @@ class Character : public Creature, public visitable /** Handles effects that happen when the player is damaged and aware of the fact. */ void on_hurt( Creature *source, bool disturb = true ); /** Heals a body_part for dam */ + void heal_bp( bodypart_id bp, int dam ) override; void heal( body_part healed, int dam ); /** Heals an hp_part for dam */ void heal( hp_part healed, int dam ); diff --git a/src/creature.cpp b/src/creature.cpp index 4e1ae88311d5f..37ce60e768da7 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -152,6 +152,8 @@ void Creature::process_turn() process_effects(); + process_damage_over_time(); + // Call this in case any effects have changed our stats reset_stats(); @@ -897,6 +899,10 @@ void Creature::deal_damage_handle_type( const damage_unit &du, bodypart_id bp, i pain += roll_remainder( adjusted_damage / div ); } +void Creature::heal_bp( bodypart_id /* bp */, int /* dam */ ) +{ +} + /* * State check functions */ @@ -1666,6 +1672,31 @@ body_part Creature::select_body_part( Creature *source, int hit_roll ) const return human_anatomy->select_body_part( szdif, hit_roll )->token; } +void Creature::add_damage_over_time( const damage_over_time_data &DoT ) +{ + damage_over_time_map.emplace_back( DoT ); +} + +void Creature::process_damage_over_time() +{ + for( auto DoT = damage_over_time_map.begin(); DoT != damage_over_time_map.end(); ) { + if( DoT->duration > 0_turns ) { + for( const bodypart_str_id &bp : DoT->bps ) { + const int dmg_amount = DoT->amount; + if( dmg_amount < 0 ) { + heal_bp( bp.id(), -dmg_amount ); + } else { + deal_damage( nullptr, bp.id(), damage_instance( DoT->type, dmg_amount ) ); + } + } + DoT->duration -= 1_turns; + ++DoT; + } else { + damage_over_time_map.erase( DoT ); + } + } +} + void Creature::check_dead_state() { if( is_dead_state() ) { diff --git a/src/creature.h b/src/creature.h index 5394393946fd2..82e3cef395e9c 100644 --- a/src/creature.h +++ b/src/creature.h @@ -12,6 +12,7 @@ #include "anatomy.h" #include "bodypart.h" +#include "damage.h" #include "pimpl.h" #include "string_formatter.h" #include "translations.h" @@ -265,6 +266,8 @@ class Creature virtual void apply_damage( Creature *source, bodypart_id bp, int amount, bool bypass_med = false ) = 0; + virtual void heal_bp( bodypart_id bp, int dam ); + /** * This creature just dodged an attack - possibly special/ranged attack - from source. * Players should train dodge, monsters may use some special defenses. @@ -750,6 +753,9 @@ class Creature virtual void process_one_effect( effect &e, bool is_new ) = 0; pimpl effects; + + std::vector damage_over_time_map; + // Miscellaneous key/value pairs. std::unordered_map values; @@ -795,6 +801,9 @@ class Creature public: body_part select_body_part( Creature *source, int hit_roll ) const; + void add_damage_over_time( const damage_over_time_data &DoT ); + void process_damage_over_time(); + static void load_hit_range( const JsonObject & ); // Empirically determined by "synthetic_range_test" in tests/ranged_balance.cpp. static std::vector dispersion_for_even_chance_of_good_hit; diff --git a/src/damage.cpp b/src/damage.cpp index 38ffe715ecd21..7fa9a2deffd27 100644 --- a/src/damage.cpp +++ b/src/damage.cpp @@ -7,6 +7,7 @@ #include #include "debug.h" +#include "generic_factory.h" #include "item.h" #include "json.h" #include "monster.h" @@ -423,3 +424,37 @@ resistances load_resistances_instance( const JsonObject &jo ) ret.resist_vals = load_damage_array( jo ); return ret; } + +void damage_over_time_data::load( const JsonObject &obj ) +{ + std::string tmp_string; + mandatory( obj, was_loaded, "damage_type", tmp_string ); + type = dt_by_name( tmp_string ); + mandatory( obj, was_loaded, "amount", amount ); + mandatory( obj, was_loaded, "bodyparts", bps ); + + if( obj.has_string( "duration" ) ) { + duration = read_from_json_string( *obj.get_raw( "duration" ), time_duration::units ); + } else { + duration = time_duration::from_turns( obj.get_int( "duration", 0 ) ); + } +} + +void damage_over_time_data::serialize( JsonOut &jsout ) const +{ + jsout.start_object(); + jsout.member( "damage_type", name_by_dt( type ) ); + jsout.member( "duration", duration ); + jsout.member( "amount", amount ); + jsout.member( "bodyparts", bps ); + jsout.end_object(); +} + +void damage_over_time_data::deserialize( JsonIn &jsin ) +{ + const JsonObject &jo = jsin.get_object(); + type = dt_by_name( jo.get_string( "damage_type" ) ); + jo.read( "amount", amount ); + jo.read( "duration", duration ); + jo.read( "bodyparts", bps ); +} diff --git a/src/damage.h b/src/damage.h index 6302bd3eed6ca..dc795dd4bb772 100644 --- a/src/damage.h +++ b/src/damage.h @@ -8,6 +8,7 @@ #include #include "type_id.h" +#include "calendar.h" class item; class monster; @@ -85,6 +86,22 @@ struct damage_instance { void deserialize( JsonIn & ); }; +class damage_over_time_data +{ + public: + damage_type type; + time_duration duration; + std::vector bps; + int amount; + + bool was_loaded; + + void load( const JsonObject &obj ); + + void serialize( JsonOut &jsout ) const; + void deserialize( JsonIn &jsin ); +}; + struct dealt_damage_instance { std::array dealt_dams; body_part bp_hit; diff --git a/src/iuse_actor.cpp b/src/iuse_actor.cpp index 14ea87d28e584..1a3ce5571d7c7 100644 --- a/src/iuse_actor.cpp +++ b/src/iuse_actor.cpp @@ -25,6 +25,7 @@ #include "clothing_mod.h" #include "crafting.h" #include "creature.h" +#include "damage.h" #include "debug.h" #include "effect.h" #include "enum_conversions.h" @@ -34,6 +35,7 @@ #include "flat_set.h" #include "game.h" #include "game_inventory.h" +#include "generic_factory.h" #include "int_id.h" #include "inventory.h" #include "item.h" @@ -701,6 +703,7 @@ void consume_drug_iuse::load( const JsonObject &obj ) effects.push_back( load_effect_data( e ) ); } } + optional( obj, false, "damage_over_time", damage_over_time ); obj.read( "stat_adjustments", stat_adjustments ); obj.read( "fields_produced", fields_produced ); obj.read( "moves", moves ); @@ -777,6 +780,10 @@ int consume_drug_iuse::use( player &p, item &it, bool, const tripoint & ) const } p.add_effect( eff.id, dur, eff.bp, eff.permanent ); } + //Apply the various damage_over_time + for( const damage_over_time_data &Dot : damage_over_time ) { + p.add_damage_over_time( Dot ); + } for( const auto &stat_adjustment : stat_adjustments ) { p.mod_stat( stat_adjustment.first, stat_adjustment.second ); } diff --git a/src/iuse_actor.h b/src/iuse_actor.h index 2aaf591fa434c..7f450397ed483 100644 --- a/src/iuse_actor.h +++ b/src/iuse_actor.h @@ -10,8 +10,8 @@ #include #include -#include "calendar.h" #include "color.h" +#include "damage.h" #include "enums.h" #include "explosion.h" #include "game_constants.h" @@ -261,6 +261,9 @@ class consume_drug_iuse : public iuse_actor /** Modify player vitamin_levels by random amount between min (first) and max (second) */ std::map> vitamins; + /**List of damage over time applyed by this drug, negative damage heals*/ + std::vector damage_over_time; + /** How many move points this action takes. */ int moves = 100; diff --git a/src/monster.cpp b/src/monster.cpp index 68fb6488c8470..d0b3f1743f867 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1624,6 +1624,11 @@ void monster::die_in_explosion( Creature *source ) die( source ); } +void monster::heal_bp( bodypart_id, int dam ) +{ + heal( dam ); +} + bool monster::movement_impaired() { return effect_cache[MOVEMENT_IMPAIRED]; diff --git a/src/monster.h b/src/monster.h index 61a258014c1dc..1d2abe257155f 100644 --- a/src/monster.h +++ b/src/monster.h @@ -320,6 +320,8 @@ class monster : public Creature void explode(); // Let the monster die and let its body explode into gibs void die_in_explosion( Creature *source ); + + void heal_bp( bodypart_id bp, int dam ) override; /** * Flat addition to the monsters @ref hp. If `overheal` is true, this is not capped by max hp. * Returns actually healed hp. diff --git a/src/player_hardcoded_effects.cpp b/src/player_hardcoded_effects.cpp index 90e745e920536..849e21169ac3a 100644 --- a/src/player_hardcoded_effects.cpp +++ b/src/player_hardcoded_effects.cpp @@ -1307,12 +1307,6 @@ void player::hardcoded_effects( effect &it ) // Just unpause, in case someone added it as a temporary effect (numbing poison etc.) it.unpause_effect(); } - } else if( id == effect_panacea ) { - // restore health all body parts, dramatically reduce pain - for( int i = 0; i < num_hp_parts; i++ ) { - hp_cur[i] += 10; - } - mod_pain( -10 ); } else if( id == effect_toxin_buildup ) { // Loosely based on toxic man-made compounds (mostly pesticides) which don't degrade // easily, leading to build-up in muscle and fat tissue through bioaccumulation. diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 6b6ec6ca635ac..cd71758d4e8fd 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -48,6 +48,7 @@ #include "craft_command.h" #include "creature.h" #include "creature_tracker.h" +#include "damage.h" #include "debug.h" #include "effect.h" #include "enum_conversions.h" @@ -2981,6 +2982,7 @@ void Creature::store( JsonOut &jsout ) const } jsout.member( "effects", tmp_map ); + jsout.member( "damage_over_time_map", damage_over_time_map ); jsout.member( "values", values ); jsout.member( "blocks_left", num_blocks ); @@ -3046,6 +3048,8 @@ void Creature::load( const JsonObject &jsin ) } jsin.read( "values", values ); + jsin.read( "damage_over_time_map", damage_over_time_map ); + jsin.read( "blocks_left", num_blocks ); jsin.read( "dodges_left", num_dodges ); jsin.read( "num_blocks_bonus", num_blocks_bonus );