@@ -344,7 +344,7 @@ class Character : public Creature, public visitable<Character>
hp_part body_window( const std::string &menu_header,
bool show_all, bool precise,
int normal_bonus, int head_bonus, int torso_bonus,
bool bleed, bool bite, bool infect ) const;
bool bleed, bool bite, bool infect, bool is_bandage, bool is_disinfectant ) const;

// Returns color which this limb would have in healing menus
nc_color limb_color( body_part bp, bool bleed, bool bite, bool infect ) const;
@@ -629,6 +629,10 @@ class Character : public Creature, public visitable<Character>
* Average hit points healed per turn.
*/
float healing_rate( float at_rest_quality ) const;
/**
* Average hit points healed per turn from healing effects.
*/
float healing_rate_medicine( float at_rest_quality, const body_part bp ) const;

/**
* Goes over all mutations, gets min and max of a value with given name
@@ -667,7 +671,7 @@ class Character : public Creature, public visitable<Character>
bool male;

std::list<item> worn;
std::array<int, num_hp_parts> hp_cur, hp_max;
std::array<int, num_hp_parts> hp_cur, hp_max, damage_bandaged, damage_disinfected;
bool nv_cached;

inventory inv;
@@ -317,6 +317,11 @@ bool effect_type::load_mod_data( JsonObject &jsobj, const std::string &member )
extract_effect(j, mod_data, "vomit_chance_bot", member, "VOMIT", "chance_bot");
extract_effect(j, mod_data, "vomit_tick", member, "VOMIT", "tick");

// Then healing effects
extract_effect( j, mod_data, "healing_rate", member, "HEAL_RATE", "amount" );
extract_effect( j, mod_data, "healing_head", member, "HEAL_HEAD", "amount" );
extract_effect( j, mod_data, "healing_torso", member, "HEAL_TORSO", "amount" );

return true;
} else {
return false;
@@ -840,6 +845,7 @@ int effect::get_avg_mod(std::string arg, bool reduced) const

int effect::get_amount(std::string arg, bool reduced) const
{
int intensity_capped = ( ( eff_type->max_effective_intensity > 0 ) ? std::min( eff_type->max_effective_intensity, intensity ) : intensity );
auto &mod_data = eff_type->mod_data;
double ret = 0;
auto found = mod_data.find(std::make_tuple("base_mods", reduced, arg, "amount"));
@@ -848,7 +854,7 @@ int effect::get_amount(std::string arg, bool reduced) const
}
found = mod_data.find(std::make_tuple("scaling_mods", reduced, arg, "amount"));
if (found != mod_data.end()) {
ret += found->second * (intensity - 1);
ret += found->second * (intensity_capped - 1);
}
return int(ret);
}
@@ -1201,6 +1207,8 @@ void load_effect_type(JsonObject &jo)
new_etype.hurt_sizing = jo.get_bool("hurt_sizing", false);
new_etype.harmful_cough = jo.get_bool("harmful_cough", false);

new_etype.max_effective_intensity = jo.get_int( "max_effective_intensity", 0 );

new_etype.load_mod_data(jo, "base_mods");
new_etype.load_mod_data(jo, "scaling_mods");

@@ -81,6 +81,7 @@ class effect_type

protected:
int max_intensity;
int max_effective_intensity;
time_duration max_duration;

int dur_add_perc;
@@ -40,6 +40,7 @@
#include "string_input_popup.h"
#include "options.h"
#include "skill.h"
#include "effect.h"

#include <sstream>
#include <algorithm>
@@ -53,8 +54,10 @@ const skill_id skill_fabrication( "fabrication" );
const species_id ZOMBIE( "ZOMBIE" );
const species_id HUMAN( "HUMAN" );

const efftype_id effect_bandaged( "bandaged" );
const efftype_id effect_bite( "bite" );
const efftype_id effect_bleed( "bleed" );
const efftype_id effect_disinfected( "disinfected" );
const efftype_id effect_infected( "infected" );
const efftype_id effect_music( "music" );
const efftype_id effect_playing_instrument( "playing_instrument" );
@@ -497,6 +500,9 @@ void consume_drug_iuse::load( JsonObject &obj )
auto hi = vit.size() >= 3 ? vit.get_int( 2 ) : lo;
vitamins.emplace( vitamin_id( vit.get_string( 0 ) ), std::make_pair( lo, hi ) );
}

used_up_item = obj.get_string( "used_up_item", used_up_item );

}

void consume_drug_iuse::info( const item &, std::vector<iteminfo> &dump ) const
@@ -586,6 +592,12 @@ long consume_drug_iuse::use( player &p, item &it, bool, const tripoint & ) const
p.use_charges( consumable->first, consumable->second );
}
}

if( !used_up_item.empty() ) {
item used_up( used_up_item, it.birthday() );
p.i_add_or_drop( used_up );
}

p.moves -= moves;
return it.type->charges_to_use();
}
@@ -2769,9 +2781,14 @@ void heal_actor::load( JsonObject &obj )
{
// Mandatory
move_cost = obj.get_int( "move_cost" );
limb_power = obj.get_float( "limb_power" );
limb_power = obj.get_float( "limb_power", 0 );

// Optional
bandages_power = obj.get_float( "bandages_power", 0 );
bandages_scaling = obj.get_float( "bandages_scaling", 0.25f * bandages_power );
disinfectant_power = obj.get_float( "disinfectant_power", 0 );
disinfectant_scaling = obj.get_float( "disinfectant_scaling", 0.25f * disinfectant_power );

head_power = obj.get_float( "head_power", 0.8f * limb_power );
torso_power = obj.get_float( "torso_power", 1.5f * limb_power );

@@ -2889,9 +2906,29 @@ int heal_actor::get_heal_value( const player &healer, hp_part healed ) const
return heal_base;
}

int heal_actor::get_bandaged_level( const player &healer ) const
{
if( bandages_power > 0 ) {
/** @EFFECT_FIRSTAID increases healing item effects */
return bandages_power + bandages_scaling * healer.get_skill_level( skill_firstaid );
}

return bandages_power;
}

int heal_actor::get_disinfected_level( const player &healer ) const
{
if( disinfectant_power > 0 ) {
/** @EFFECT_FIRSTAID increases healing item effects */
return disinfectant_power + disinfectant_scaling * healer.get_skill_level( skill_firstaid );
}

return disinfectant_power;
}

long heal_actor::finish_using( player &healer, player &patient, item &it, hp_part healed ) const
{
float practice_amount = std::max( 9.0f, limb_power * 3.0f );
float practice_amount = limb_power * 3.0f;
const int dam = get_heal_value( healer, healed );

if( ( patient.hp_cur[healed] >= 1 ) && ( dam > 0 ) ) { // Prevent first-aid from mending limbs
@@ -2980,6 +3017,25 @@ long heal_actor::finish_using( player &healer, player &patient, item &it, hp_par
}
}

// apply healing over time effects
if( bandages_power > 0 ) {
int bandages_intensity = get_bandaged_level( healer );
patient.add_effect( effect_bandaged, 1_turns, bp_healed );
effect &e = patient.get_effect( effect_bandaged, bp_healed );
e.set_duration( e.get_int_dur_factor() * bandages_intensity );
patient.damage_bandaged[healed] = patient.hp_max[healed] - patient.hp_cur[healed];
practice_amount += 2 * bandages_intensity;
}
if( disinfectant_power > 0 ) {
int disinfectant_intensity = get_disinfected_level( healer );
patient.add_effect( effect_disinfected, 1_turns, bp_healed );
effect &e = patient.get_effect( effect_disinfected, bp_healed );
e.set_duration( e.get_int_dur_factor() * disinfectant_intensity );
patient.damage_disinfected[healed] = patient.hp_max[healed] - patient.hp_cur[healed];
practice_amount += 2 * disinfectant_intensity;
}
practice_amount = std::max( 9.0f, practice_amount );

healer.practice( skill_firstaid, ( int )practice_amount );
return it.type->charges_to_use();
}
@@ -2989,7 +3045,7 @@ hp_part pick_part_to_heal(
const std::string &menu_header,
int limb_power, int head_bonus, int torso_bonus,
float bleed_chance, float bite_chance, float infect_chance,
bool force )
bool force, bool is_bandage, bool is_disinfectant )
{
const bool bleed = bleed_chance > 0.0f;
const bool bite = bite_chance > 0.0f;
@@ -3003,7 +3059,7 @@ hp_part pick_part_to_heal(
while( true ) {
hp_part healed_part = patient.body_window( menu_header, force, precise,
limb_power, head_bonus, torso_bonus,
bleed, bite, infect );
bleed, bite, infect, is_bandage, is_disinfectant );
if( healed_part == num_hp_parts ) {
return num_hp_parts;
}
@@ -3069,9 +3125,11 @@ hp_part heal_actor::use_healing_item( player &healer, player &patient, item &it,
// Player healing self - let player select
if( healer.activity.id() != activity_id( "ACT_FIRSTAID" ) ) {
const std::string menu_header = it.tname();
bool is_bandages = bandages_power;
bool is_disinfectant = disinfectant_power;
healed = pick_part_to_heal( healer, patient, menu_header,
limb_power, head_bonus, torso_bonus,
bleed, bite, infect, force );
bleed, bite, infect, force, is_bandages, is_disinfectant );
if( healed == num_hp_parts ) {
return num_hp_parts; // canceled
}
@@ -3088,9 +3146,11 @@ hp_part heal_actor::use_healing_item( player &healer, player &patient, item &it,
// Player healing NPC
// TODO: Remove this hack, allow using activities on NPCs
const std::string menu_header = it.tname();
bool is_bandages = bandages_power;
bool is_disinfectant = disinfectant_power;
healed = pick_part_to_heal( healer, patient, menu_header,
limb_power, head_bonus, torso_bonus,
bleed, bite, infect, force );
bleed, bite, infect, force, is_bandages, is_disinfectant );
}

if( healed != num_hp_parts ) {
@@ -3116,6 +3176,24 @@ void heal_actor::info( const item &, std::vector<iteminfo> &dump ) const
}
}

if( bandages_power > 0 ) {
dump.emplace_back( "TOOL", _( "<bold>Base bandaging quality:</bold> " ), "", bandages_power, true,
"", true );
if( g != nullptr ) {
dump.emplace_back( "TOOL", _( "<bold>Actual bandaging quality:</bold> " ), "",
get_bandaged_level( g->u ), true, "", true );
}
}

if( disinfectant_power > 0 ) {
dump.emplace_back( "TOOL", _( "<bold>Base bandaging quality:</bold> " ), "", disinfectant_power,
true, "", true );
if( g != nullptr ) {
dump.emplace_back( "TOOL", _( "<bold>Actual bandaging quality:</bold> " ), "",
get_disinfected_level( g->u ), true, "", true );
}
}

if( bleed > 0.0f || bite > 0.0f || infect > 0.0f ) {
dump.emplace_back( "TOOL", _( "<bold>Chance to heal (percent):</bold> " ), "", -999, true, "",
true );
@@ -238,6 +238,9 @@ class consume_drug_iuse : public iuse_actor
long use( player &, item &, bool, const tripoint & ) const override;
iuse_actor *clone() const override;
void info( const item &, std::vector<iteminfo> & ) const override;

/** Item produced after using drugs. */
std::string used_up_item;
};

/**
@@ -813,6 +816,14 @@ class heal_actor : public iuse_actor
float head_power = 0;
/** How much hp to restore when healing torso? */
float torso_power = 0;
/** How many intensity levels will be applied when healing limbs? */
float bandages_power = 0;
/** Extra intensity levels gained per skill level when healing limbs. */
float bandages_scaling = 0;
/** How many intensity levels will be applied when healing limbs? */
float disinfectant_power = 0;
/** Extra intensity levels gained per skill level when healing limbs. */
float disinfectant_scaling = 0;
/** Chance to remove bleed effect. */
float bleed = 0;
/** Chance to remove bite effect. */
@@ -843,7 +854,10 @@ class heal_actor : public iuse_actor

/** How much hp would `healer` heal using this actor on `healed` body part. */
int get_heal_value( const player &healer, hp_part healed ) const;

/** How many intensity levels will be applied using this actor by `healer`. */
int get_bandaged_level( const player &healer ) const;
/** How many intensity levels will be applied using this actor by `healer`. */
int get_disinfected_level( const player &healer ) const;
/** Does the actual healing. Used by both long and short actions. Returns charges used. */
long finish_using( player &healer, player &patient, item &it, hp_part part ) const;

@@ -102,6 +102,7 @@ const efftype_id effect_adrenaline( "adrenaline" );
const efftype_id effect_alarm_clock( "alarm_clock" );
const efftype_id effect_asthma( "asthma" );
const efftype_id effect_attention( "attention" );
const efftype_id effect_bandaged( "bandaged" );
const efftype_id effect_bite( "bite" );
const efftype_id effect_blind( "blind" );
const efftype_id effect_blisters( "blisters" );
@@ -120,6 +121,7 @@ const efftype_id effect_deaf( "deaf" );
const efftype_id effect_depressants( "depressants" );
const efftype_id effect_dermatik( "dermatik" );
const efftype_id effect_disabled( "disabled" );
const efftype_id effect_disinfected( "disinfected" );
const efftype_id effect_downed( "downed" );
const efftype_id effect_drunk( "drunk" );
const efftype_id effect_earphones( "earphones" );
@@ -3592,6 +3594,26 @@ void player::react_to_felt_pain( int intensity )
}
}

int player::reduce_healing_effect( const efftype_id &eff_id, int remove_med, body_part hurt ){
effect &e = get_effect( eff_id, hurt );
int intensity = e.get_intensity();
if( remove_med < intensity ) {
if( eff_id == effect_bandaged ){
add_msg_if_player( m_bad, _( "Bandages on your %s was damaged!" ), body_part_name( hurt ) );
} else if( eff_id == effect_disinfected ){
add_msg_if_player( m_bad, _( "You got some filth on you disinfected %s!" ), body_part_name( hurt ) );
}
} else {
if( eff_id == effect_bandaged ){
add_msg_if_player( m_bad, _( "Bandages on your %s was destroyed!" ), body_part_name( hurt ) );
} else if( eff_id == effect_disinfected ){
add_msg_if_player( m_bad, _( "Your %s is no longer disinfected!" ), body_part_name( hurt ) );
}
}
e.mod_duration( - time_duration::from_hours( 6 * remove_med ) );
return intensity;
}

/*
Where damage to player is actually applied to hit body parts
Might be where to put bleed stuff rather than in player::deal_damage()
@@ -3618,15 +3640,27 @@ void player::apply_damage(Creature *source, body_part hurt, int dam)
hp_cur[hurtpart] = 0;
}

if( hp_cur[hurtpart] <= 0 && ( source == nullptr || !source->is_hallucination() )) {
remove_effect( effect_mending, hurt );
add_effect( effect_disabled, 1_turns, hurt, true );
if( hp_cur[hurtpart] <= 0 && ( source == nullptr || !source->is_hallucination() ) ) {
if( has_effect( effect_mending, hurt ) ) {
effect &e = get_effect( effect_mending, hurt );
float remove_mend = dam / 20.0f;
e.mod_duration( -e.get_max_duration() * remove_mend );
}
}

lifetime_stats.damage_taken += dam;
if( dam > get_painkiller() ) {
on_hurt( source );
}

// remove healing effects if damaged
int remove_med = roll_remainder( dam / 5.0f );
if( remove_med > 0 && has_effect( effect_bandaged, hurt ) ) {
remove_med -= reduce_healing_effect( effect_bandaged , remove_med, hurt );
}
if( remove_med > 0 && has_effect( effect_disinfected, hurt ) ) {
remove_med -= reduce_healing_effect( effect_disinfected , remove_med, hurt );
}
}

void player::heal(body_part healed, int dam)
@@ -4332,6 +4366,43 @@ void player::regen( int rate_multiplier )
}
}

// include healing effects
for( int i = 0; i < num_hp_parts; i++ ) {
body_part bp = hp_to_bp( static_cast<hp_part>( i ) );
float healing = healing_rate_medicine( rest, bp ) * MINUTES( 5 ) ;

int healing_apply = roll_remainder( healing );
heal( bp, healing_apply );
if( damage_bandaged[i] > 0 ) {
damage_bandaged[i] -= healing_apply;
if( damage_bandaged[i] <= 0 ) {
damage_bandaged[i] = 0;
remove_effect( effect_bandaged, bp );
add_msg_if_player( _( "Bandaged wounds on your %s was healed." ), body_part_name( bp ) );
}
}
if( damage_disinfected[i] > 0 ) {
damage_disinfected[i] -= healing_apply;
if( damage_disinfected[i] <= 0 ) {
damage_disinfected[i] = 0;
remove_effect( effect_disinfected, bp );
add_msg_if_player( _( "Disinfected wounds on your %s was healed." ), body_part_name( bp ) );
}
}

// remove effects if the limb was healed by other way
if( has_effect( effect_bandaged, bp ) && ( hp_cur[i] == hp_max[i] ) ) {
damage_bandaged[i] = 0;
remove_effect( effect_bandaged, bp );
add_msg_if_player( _( "Bandaged wounds on your %s was healed." ), body_part_name( bp ) );
}
if( has_effect( effect_disinfected, bp ) && ( hp_cur[i] == hp_max[i] ) ) {
damage_disinfected[i] = 0;
remove_effect( effect_disinfected, bp );
add_msg_if_player( _( "Disinfected wounds on your %s was healed." ), body_part_name( bp ) );
}
}

if( radiation > 0 ) {
radiation = std::max( 0, radiation - roll_remainder( rate_multiplier / 50.0f ) );
}
@@ -653,6 +653,8 @@ class player : public Character
bool immune_to( body_part bp, damage_unit dam ) const;
/** Calls Creature::deal_damage and handles damaged effects (waking up, etc.) */
dealt_damage_instance deal_damage(Creature *source, body_part bp, const damage_instance &d) override;
/** Reduce healing effect intensity, return initial intensity of the effect */
int reduce_healing_effect( const efftype_id &eff_id, int remove_med, body_part hurt );
/** Actually hurt the player, hurts a body_part directly, no armor reduction */
void apply_damage(Creature *source, body_part bp, int amount) override;
/** Modifies a pain value by player traits before passing it to Creature::mod_pain() */
@@ -270,6 +270,9 @@ void Character::load(JsonObject &data)
data.read( "healthy", healthy );
data.read( "healthy_mod", healthy_mod );

data.read( "damage_bandaged", damage_bandaged );
data.read( "damage_disinfected", damage_disinfected );

JsonArray parray;

data.read("underwater", underwater);
@@ -546,6 +549,8 @@ void player::store(JsonOut &json) const
// @todo: consider ["parts"]["head"]["hp_cur"] instead of ["hp_cur"][head_enum_value]
json.member( "hp_cur", hp_cur );
json.member( "hp_max", hp_max );
json.member( "damage_bandaged", damage_bandaged );
json.member( "damage_disinfected", damage_disinfected );

// npc; unimplemented
json.member( "power_level", power_level );