diff --git a/data/raw/keybindings/keybindings.json b/data/raw/keybindings/keybindings.json index 1e4aadcf8d8c..0f005463f43b 100644 --- a/data/raw/keybindings/keybindings.json +++ b/data/raw/keybindings/keybindings.json @@ -3268,6 +3268,13 @@ "name": "Disassemble", "bindings": [ { "input_method": "keyboard", "key": "D" } ] }, + { + "id": "SHOW_KILL_LIST", + "type": "keybinding", + "category": "INVENTORY_ITEM", + "name": "Kills", + "bindings": [ { "input_method": "keyboard", "key": "K" } ] + }, { "id": "FAVORITE_ADD", "type": "keybinding", diff --git a/src/ballistics.cpp b/src/ballistics.cpp index dc657a86afe0..f571d9e2e43f 100644 --- a/src/ballistics.cpp +++ b/src/ballistics.cpp @@ -201,7 +201,7 @@ projectile_attack_aim projectile_attack_roll( const dispersion_sources &dispersi dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tripoint &source, const tripoint &target_arg, const dispersion_sources &dispersion, - Creature *origin, const vehicle *in_veh ) + Creature *origin, item *source_weapon, const vehicle *in_veh ) { const bool do_animation = get_option( "ANIMATION_PROJECTILES" ); @@ -432,7 +432,7 @@ dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tri continue; } attack.missed_by = cur_missed_by; - critter->deal_projectile_attack( null_source ? nullptr : origin, attack ); + critter->deal_projectile_attack( null_source ? nullptr : origin, source_weapon, attack ); // Critter can still dodge the projectile // In this case hit_critter won't be set if( attack.hit_critter != nullptr ) { @@ -501,7 +501,7 @@ dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tri Creature &z = *mon_ptr; add_msg( _( "The attack bounced to %s!" ), z.get_name() ); z.add_effect( effect_bounced, 1_turns ); - projectile_attack( proj, tp, z.pos(), dispersion, origin, in_veh ); + projectile_attack( proj, tp, z.pos(), dispersion, origin, source_weapon, in_veh ); sfx::play_variant_sound( "fire_gun", "bio_lightning_tail", sfx::get_heard_volume( z.pos() ), sfx::get_heard_angle( z.pos() ) ); } diff --git a/src/ballistics.h b/src/ballistics.h index 00936a77ac3d..78115dca529d 100644 --- a/src/ballistics.h +++ b/src/ballistics.h @@ -3,6 +3,7 @@ #define CATA_SRC_BALLISTICS_H class Creature; +class item; class dispersion_sources; class vehicle; struct dealt_projectile_attack; @@ -32,7 +33,7 @@ projectile_attack_aim projectile_attack_roll( const dispersion_sources &dispersi */ dealt_projectile_attack projectile_attack( const projectile &proj_arg, const tripoint &source, const tripoint &target_arg, const dispersion_sources &dispersion, - Creature *origin = nullptr, const vehicle *in_veh = nullptr ); + Creature *origin = nullptr, item *source_weapon = nullptr, const vehicle *in_veh = nullptr ); namespace ranged { diff --git a/src/character.cpp b/src/character.cpp index b40512571529..1d6ddbf52af5 100644 --- a/src/character.cpp +++ b/src/character.cpp @@ -8917,7 +8917,9 @@ void Character::on_hit( Creature *source, bodypart_id bp_hit, Where damage to character is actually applied to hit body parts Might be where to put bleed stuff rather than in player::deal_damage() */ -void Character::apply_damage( Creature *source, bodypart_id hurt, int dam, +void Character::apply_damage( Creature *source, item *source_weapon, item *source_projectile, + bodypart_id hurt, + int dam, const bool bypass_med ) { if( is_dead_state() || has_trait( trait_DEBUG_NODMG ) ) { @@ -8959,6 +8961,12 @@ void Character::apply_damage( Creature *source, bodypart_id hurt, int dam, get_name() ); } set_killer( source ); + if( source_weapon ) { + source_weapon->add_npc_kill( get_name() ); + } + if( source_projectile ) { + source_projectile->add_npc_kill( get_name() ); + } } if( !bypass_med ) { @@ -8972,9 +8980,21 @@ void Character::apply_damage( Creature *source, bodypart_id hurt, int dam, } } } +void Character::apply_damage( Creature *source, item *source_weapon, bodypart_id hurt, + int dam, + const bool bypass_med ) +{ + apply_damage( source, source_weapon, nullptr, hurt, dam, bypass_med ); +} +void Character::apply_damage( Creature *source, bodypart_id hurt, + int dam, + const bool bypass_med ) +{ + apply_damage( source, nullptr, nullptr, hurt, dam, bypass_med ); +} dealt_damage_instance Character::deal_damage( Creature *source, bodypart_id bp, - const damage_instance &d ) + const damage_instance &d, item *source_weapon, item *source_projectile ) { if( has_trait( trait_DEBUG_NODMG ) ) { return dealt_damage_instance(); @@ -8987,7 +9007,8 @@ dealt_damage_instance Character::deal_damage( Creature *source, bodypart_id bp, } //damage applied here - dealt_damage_instance dealt_dams = Creature::deal_damage( source, bp, d ); + dealt_damage_instance dealt_dams = Creature::deal_damage( source, bp, d, source_weapon, + source_projectile ); //block reduction should be by applied this point int dam = dealt_dams.total_damage(); @@ -9128,6 +9149,16 @@ dealt_damage_instance Character::deal_damage( Creature *source, bodypart_id bp, on_hurt( source ); return dealt_dams; } +dealt_damage_instance Character::deal_damage( Creature *source, bodypart_id bp, + const damage_instance &d, item *source_weapon ) +{ + return deal_damage( source, bp, d, source_weapon, nullptr ); +} +dealt_damage_instance Character::deal_damage( Creature *source, bodypart_id bp, + const damage_instance &d ) +{ + return deal_damage( source, bp, d, nullptr, nullptr ); +} int Character::reduce_healing_effect( const efftype_id &eff_id, int remove_med, const bodypart_id &hurt ) diff --git a/src/character.h b/src/character.h index 916918e141f4..2100e4dd7af1 100644 --- a/src/character.h +++ b/src/character.h @@ -671,9 +671,18 @@ class Character : public Creature, public location_visitable void did_hit( Creature &target ); /** Actually hurt the player, hurts a body_part directly, no armor reduction */ + void apply_damage( Creature *source, item *source_weapon, item *source_projectile, bodypart_id hurt, + int dam, + bool bypass_med = false ) override; + void apply_damage( Creature *source, item *source_weapon, bodypart_id hurt, int dam, + bool bypass_med = false ) override; void apply_damage( Creature *source, bodypart_id hurt, int dam, bool bypass_med = false ) override; /** Calls Creature::deal_damage and handles damaged effects (waking up, etc.) */ + dealt_damage_instance deal_damage( Creature *source, bodypart_id bp, + const damage_instance &d, item *source_weapon, item *source_projectile ) override; + dealt_damage_instance deal_damage( Creature *source, bodypart_id bp, + const damage_instance &d, item *source_weapon ) override; dealt_damage_instance deal_damage( Creature *source, bodypart_id bp, const damage_instance &d ) override; /** Reduce healing effect intensity, return initial intensity of the effect */ diff --git a/src/creature.cpp b/src/creature.cpp index cf0d6e46939f..3dae9822f169 100644 --- a/src/creature.cpp +++ b/src/creature.cpp @@ -578,7 +578,8 @@ int Creature::deal_melee_attack( Creature *source, int hitroll ) return hit_spread; } -void Creature::deal_melee_hit( Creature *source, int hit_spread, bool critical_hit, +void Creature::deal_melee_hit( Creature *source, item *source_weapon, int hit_spread, + bool critical_hit, const damage_instance &dam, dealt_damage_instance &dealt_dam ) { if( source == nullptr || source->is_hallucination() ) { @@ -592,7 +593,8 @@ void Creature::deal_melee_hit( Creature *source, int hit_spread, bool critical_h if( mons && mons->mounted_player ) { if( !mons->has_flag( MF_MECH_DEFENSIVE ) && one_in( std::max( 2, mons->get_size() - mons->mounted_player->get_size() ) ) ) { - mons->mounted_player->deal_melee_hit( source, hit_spread, critical_hit, dam, dealt_dam ); + mons->mounted_player->deal_melee_hit( source, source_weapon, hit_spread, critical_hit, dam, + dealt_dam ); return; } } @@ -603,9 +605,14 @@ void Creature::deal_melee_hit( Creature *source, int hit_spread, bool critical_h block_hit( source, bp_hit, d ); on_hit( source, bp_hit ); // trigger on-gethit events - dealt_dam = deal_damage( source, bp_hit, d ); + dealt_dam = deal_damage( source, bp_hit, d, source_weapon ); dealt_dam.bp_hit = bp_token; } +void Creature::deal_melee_hit( Creature *source, int hit_spread, bool critical_hit, + const damage_instance &dam, dealt_damage_instance &dealt_dam ) +{ + deal_melee_hit( source, nullptr, hit_spread, critical_hit, dam, dealt_dam ); +} namespace ranged { @@ -708,10 +715,12 @@ auto get_stun_srength( const projectile &proj, creature_size size ) -> int * Attempts to harm a creature with a projectile. * * @param source Pointer to the creature who shot the projectile. + * @param source_weapon Pointer to the weapon used to fire the projectile. * @param attack A structure describing the attack and its results. * @param print_messages enables message printing by default. */ -void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack &attack ) +void Creature::deal_projectile_attack( Creature *source, item *source_weapon, + dealt_projectile_attack &attack ) { const bool magic = attack.proj.has_effect( ammo_effect_magic ); const bool targetted_crit_allowed = !attack.proj.has_effect( ammo_effect_NO_CRIT ); @@ -726,7 +735,7 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack if( mons && mons->mounted_player ) { if( !mons->has_flag( MF_MECH_DEFENSIVE ) && one_in( std::max( 2, mons->get_size() - mons->mounted_player->get_size() ) ) ) { - mons->mounted_player->deal_projectile_attack( source, attack ); + mons->mounted_player->deal_projectile_attack( source, source_weapon, attack ); return; } } @@ -844,7 +853,8 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack // If we have a shield, it might passively block ranged impacts block_ranged_hit( source, bp_hit, impact ); - dealt_dam = deal_damage( source, bp_hit, impact ); + // If the projectile survives, both it and the launcher get credit for the kill. + dealt_dam = deal_damage( source, bp_hit, impact, source_weapon, attack.proj.get_drop() ); dealt_dam.bp_hit = bp_hit->token; // Apply ammo effects to target. @@ -926,8 +936,13 @@ void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack attack.missed_by = goodhit; } +void Creature::deal_projectile_attack( Creature *source, dealt_projectile_attack &attack ) +{ + deal_projectile_attack( source, nullptr, attack ); +} + dealt_damage_instance Creature::deal_damage( Creature *source, bodypart_id bp, - const damage_instance &dam ) + const damage_instance &dam, item *source_weapon, item *source_projectile ) { if( is_dead_state() ) { return dealt_damage_instance(); @@ -951,9 +966,19 @@ dealt_damage_instance Creature::deal_damage( Creature *source, bodypart_id bp, mod_pain( total_pain ); - apply_damage( source, bp, total_damage ); + apply_damage( source, source_weapon, source_projectile, bp, total_damage ); return dealt_dams; } +dealt_damage_instance Creature::deal_damage( Creature *source, bodypart_id bp, + const damage_instance &dam, item *source_weapon ) +{ + return deal_damage( source, bp, dam, source_weapon, nullptr ); +} +dealt_damage_instance Creature::deal_damage( Creature *source, bodypart_id bp, + const damage_instance &dam ) +{ + return deal_damage( source, bp, dam, nullptr, nullptr ); +} void Creature::deal_damage_handle_type( const damage_unit &du, bodypart_id bp, int &damage, int &pain ) { diff --git a/src/creature.h b/src/creature.h index 99a7cb9f5943..c3bfa008ce4b 100644 --- a/src/creature.h +++ b/src/creature.h @@ -356,11 +356,16 @@ class Creature // completes a melee attack against the creature // dealt_dam is overwritten with the values of the damage dealt + virtual void deal_melee_hit( Creature *source, item *source_weapon, int hit_spread, + bool critical_hit, + const damage_instance &dam, dealt_damage_instance &dealt_dam ); virtual void deal_melee_hit( Creature *source, int hit_spread, bool critical_hit, const damage_instance &dam, dealt_damage_instance &dealt_dam ); // Makes a ranged projectile attack against the creature // Sets relevant values in `attack`. + virtual void deal_projectile_attack( Creature *source, item *source_weapon, + dealt_projectile_attack &attack ); virtual void deal_projectile_attack( Creature *source, dealt_projectile_attack &attack ); /** @@ -374,15 +379,28 @@ class Creature * @param source The attacking creature, can be null. * @param bp The attacked body part * @param dam The damage dealt + * @param source_weapon The weapon used in the attack, optional + * @param source_projectile The projectile fired in the attack, optional */ + virtual dealt_damage_instance deal_damage( Creature *source, bodypart_id bp, + const damage_instance &dam, item *source_weapon, item *source_projectile ); + virtual dealt_damage_instance deal_damage( Creature *source, bodypart_id bp, + const damage_instance &dam, item *source_weapon ); virtual dealt_damage_instance deal_damage( Creature *source, bodypart_id bp, const damage_instance &dam ); + // for each damage type, how much gets through and how much pain do we // accrue? mutates damage and pain virtual void deal_damage_handle_type( const damage_unit &du, bodypart_id bp, int &damage, int &pain ); // directly decrements the damage. ONLY handles damage, doesn't // increase pain, apply effects, etc + virtual void apply_damage( Creature *source, item *source_weapon, item *source_projectile, + bodypart_id bp, + int amount, + bool bypass_med = false ) = 0; + virtual void apply_damage( Creature *source, item *source_weapon, bodypart_id bp, int amount, + bool bypass_med = false ) = 0; virtual void apply_damage( Creature *source, bodypart_id bp, int amount, bool bypass_med = false ) = 0; diff --git a/src/examine_item_menu.cpp b/src/examine_item_menu.cpp index 327337aa8f36..52615cf0b8a3 100644 --- a/src/examine_item_menu.cpp +++ b/src/examine_item_menu.cpp @@ -190,6 +190,13 @@ bool run( return true; } ); + if( itm.kill_count() > 0 ) { + add_entry( "SHOW_KILL_LIST", hint_rating::good, [&]() { + itm.show_kill_list(); + return true; + } ); + } + if( !itm.is_favorite ) { add_entry( "FAVORITE_ADD", hint_rating::good, [&]() { diff --git a/src/item.cpp b/src/item.cpp index e05164a45bc0..5c4f889b1d1e 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -93,6 +93,7 @@ #include "ret_val.h" #include "rot.h" #include "rng.h" +#include "scores_ui.h" #include "skill.h" #include "stomach.h" #include "string_formatter.h" @@ -10715,3 +10716,43 @@ location_vector &item::get_components() { return components; } + +bool item::init_kill_tracker() +{ + if( kills ) { + return true; + } else if( get_option( "ENABLE_EVENTS" ) ) { + kills = std::make_unique( false ); + return true; + } else { + return false; + } +} +void item::add_monster_kill( mtype_id mon ) +{ + if( init_kill_tracker() ) { + kills->add_monster( mon ); + } +} +void item::add_npc_kill( std::string npc ) +{ + if( init_kill_tracker() ) { + kills->add_npc( npc ); + } +} +void item::show_kill_list() +{ + if( !kills ) { + debugmsg( "Tried to display empty kill list" ); + return; + } + show_kills( *kills ); +} +int item::kill_count() +{ + if( !kills ) { + return 0; + } else { + return kills->monster_kill_count() + kills->npc_kill_count(); + } +} diff --git a/src/item.h b/src/item.h index 27d1fc3b1f13..d728fa337ef5 100644 --- a/src/item.h +++ b/src/item.h @@ -23,6 +23,7 @@ #include "gun_mode.h" #include "io_tags.h" #include "item_contents.h" +#include "kill_tracker.h" #include "location_vector.h" #include "pimpl.h" #include "safe_reference.h" @@ -2425,6 +2426,23 @@ class item : public location_visitable, public game_object * Ideally, this would be stored outside item class. */ pimpl drop_token; + + private: + /** Kill tracker */ + std::unique_ptr kills; + /** + * Check if there's a kill_tracker + * Make one if there isn't and if ENABLE_EVENTS option is toggled on + * @returns true if a kill_tracker exists, or if one was created + * false if there is no kill_tracker, and one wasn't created + */ + bool init_kill_tracker(); + + public: + void add_monster_kill( mtype_id ); + void add_npc_kill( std::string ); + void show_kill_list(); + int kill_count(); }; bool item_compare_by_charges( const item &left, const item &right ); diff --git a/src/kill_tracker.cpp b/src/kill_tracker.cpp index 5cee5f4cccd3..b9f92e6bc848 100644 --- a/src/kill_tracker.cpp +++ b/src/kill_tracker.cpp @@ -15,6 +15,11 @@ #include "string_id.h" #include "translations.h" +kill_tracker::kill_tracker( bool xp ) +{ + xp_allowed = xp; +} + void kill_tracker::reset( const std::map &kills_, const std::vector &npc_kills_ ) { @@ -66,6 +71,11 @@ int kill_tracker::kill_xp() const return ret; } +bool kill_tracker::option_xp() const +{ + return ( xp_allowed && get_option( "STATS_THROUGH_KILLS" ) ); +} + std::string kill_tracker::get_kills_text() const { std::vector data; @@ -98,7 +108,7 @@ std::string kill_tracker::get_kills_text() const buffer = _( "You haven't killed any monsters yet!" ); } else { buffer = string_format( _( "KILL COUNT: %d" ), totalkills ); - if( get_option( "STATS_THROUGH_KILLS" ) ) { + if( option_xp() ) { buffer += string_format( _( "\nExperience: %d (%d points available)" ), kill_xp(), get_avatar().free_upgrade_points() ); } @@ -142,3 +152,13 @@ void kill_tracker::notify( const cata::event &e ) break; } } + +void kill_tracker::add_monster( mtype_id victim ) +{ + kills[victim]++; +} + +void kill_tracker::add_npc( std::string victim ) +{ + npc_kills.push_back( victim ); +} diff --git a/src/kill_tracker.h b/src/kill_tracker.h index ddcd4cbc09a4..9cfbe98a294e 100644 --- a/src/kill_tracker.h +++ b/src/kill_tracker.h @@ -24,7 +24,11 @@ class kill_tracker : public event_subscriber */ friend class diary; public: - kill_tracker() = default; + /** + * @param xp_allowed can be set to false to disable Stats Through Kills regardless of game options. + * Intended to be used when creating multiple kill_trackers, so they don't all grant XP. + */ + kill_tracker( bool xp_allowed = true ); void reset( const std::map &kills, const std::vector &npc_kills ); /** returns the number of kills of the given mon_id by the player. */ @@ -41,10 +45,16 @@ class kill_tracker : public event_subscriber void clear(); void notify( const cata::event & ) override; + /** directly adds a monster kill to the tracker, bypassing the event bus. */ + void add_monster( mtype_id ); + /** directly adds an NPC kill to the tracker, bypassing the event bus. */ + void add_npc( std::string ); void serialize( JsonOut & ) const; void deserialize( JsonIn & ); private: + bool xp_allowed; + bool option_xp() const; std::map kills; // player's kill count std::vector npc_kills; // names of NPCs the player killed }; diff --git a/src/melee.cpp b/src/melee.cpp index 149eff20cc2c..2992da854b1a 100644 --- a/src/melee.cpp +++ b/src/melee.cpp @@ -562,7 +562,7 @@ void Character::melee_attack( Creature &t, bool allow_special, const matec_id *f if( allow_special ) { perform_special_attacks( t, dealt_special_dam ); } - t.deal_melee_hit( this, hit_spread, critical_hit, d, dealt_dam ); + t.deal_melee_hit( this, &cur_weapon, hit_spread, critical_hit, d, dealt_dam ); if( dealt_special_dam.type_damage( DT_CUT ) > 0 || dealt_special_dam.type_damage( DT_STAB ) > 0 || ( cur_weapon.is_null() && ( dealt_dam.type_damage( DT_CUT ) > 0 || diff --git a/src/monster.cpp b/src/monster.cpp index 5edd1f639e85..48640d0d0a9c 100644 --- a/src/monster.cpp +++ b/src/monster.cpp @@ -1905,7 +1905,9 @@ void monster::set_hp( const int hp ) this->hp = hp; } -void monster::apply_damage( Creature *source, bodypart_id /*bp*/, int dam, +void monster::apply_damage( Creature *source, item *source_weapon, item *source_projectile, + bodypart_id /*bp*/, + int dam, const bool /*bypass_med*/ ) { if( is_dead_state() ) { @@ -1914,10 +1916,26 @@ void monster::apply_damage( Creature *source, bodypart_id /*bp*/, int dam, hp -= dam; if( hp < 1 ) { set_killer( source ); + if( source_weapon ) { + source_weapon->add_monster_kill( type->id ); + } + if( source_projectile ) { + source_projectile->add_monster_kill( type->id ); + } } else if( dam > 0 ) { process_trigger( mon_trigger::HURT, 1 + static_cast( dam / 3 ) ); } } +void monster::apply_damage( Creature *source, item *source_weapon, bodypart_id bp, int dam, + const bool bypass_med ) +{ + return apply_damage( source, source_weapon, nullptr, bp, dam, bypass_med ); +} +void monster::apply_damage( Creature *source, bodypart_id bp, int dam, + const bool bypass_med ) +{ + return apply_damage( source, nullptr, nullptr, bp, dam, bypass_med ); +} void monster::die_in_explosion( Creature *source ) { diff --git a/src/monster.h b/src/monster.h index 53c484ef2d50..1d472faab9dc 100644 --- a/src/monster.h +++ b/src/monster.h @@ -335,7 +335,13 @@ class monster : public Creature, public location_visitable void melee_attack( Creature &target ); void melee_attack( Creature &target, float accuracy ); void melee_attack( Creature &p, bool ) = delete; + using Creature::deal_projectile_attack; void deal_projectile_attack( Creature *source, dealt_projectile_attack &attack ) override; + void apply_damage( Creature *source, item *source_weapon, item *source_projectile, bodypart_id bp, + int dam, + bool bypass_med = false ) override; + void apply_damage( Creature *source, item *source_weapon, bodypart_id bp, int dam, + bool bypass_med = false ) override; void apply_damage( Creature *source, bodypart_id bp, int dam, bool bypass_med = false ) override; // create gibs/meat chunks/blood etc all over the place, does not kill, can be called on a dead monster. diff --git a/src/ranged.cpp b/src/ranged.cpp index 9b9a4ac8300c..24a3216a7b43 100644 --- a/src/ranged.cpp +++ b/src/ranged.cpp @@ -924,7 +924,7 @@ int ranged::fire_gun( Character &who, const tripoint &target, int max_shots, ite projectile.add_effect( ammo_effect_NO_CRIT ); } if( !shape ) { - auto shot = projectile_attack( projectile, who.pos(), aim, dispersion, &who, in_veh ); + auto shot = projectile_attack( projectile, who.pos(), aim, dispersion, &who, &gun, in_veh ); if( shot.missed_by <= .1 ) { // TODO: check head existence for headshot g->events().send( who.getID() ); @@ -944,7 +944,7 @@ int ranged::fire_gun( Character &who, const tripoint &target, int max_shots, ite double length = trig_dist( who.pos(), aim ); rl_vec3d vec_pos( who.pos() ); rl_vec3d new_aim = vec_pos + rl_vec3d( length, 0, 0 ).rotated( new_angle ); - ranged::execute_shaped_attack( *shape->create( vec_pos, new_aim ), projectile, who ); + ranged::execute_shaped_attack( *shape->create( vec_pos, new_aim ), projectile, who, &gun ); } curshot++; diff --git a/src/ranged.h b/src/ranged.h index 017a2cf0cb60..42e3a00474c3 100644 --- a/src/ranged.h +++ b/src/ranged.h @@ -107,7 +107,8 @@ float str_draw_range_modifier( const item &it, const Character &p ); std::optional get_shape_factory( const item &gun ); /** AoE attack, with area given by shape */ -void execute_shaped_attack( const shape &sh, const projectile &proj, Creature &attacker ); +void execute_shaped_attack( const shape &sh, const projectile &proj, Creature &attacker, + item *source_weapon ); std::map expected_coverage( const shape &sh, const map &here, int bash_power ); diff --git a/src/ranged_aoe.cpp b/src/ranged_aoe.cpp index d74f1ac30c9d..8fe2f6498b3e 100644 --- a/src/ranged_aoe.cpp +++ b/src/ranged_aoe.cpp @@ -43,7 +43,8 @@ struct aoe_flood_node { namespace ranged { -void execute_shaped_attack( const shape &sh, const projectile &proj, Creature &attacker ) +void execute_shaped_attack( const shape &sh, const projectile &proj, Creature &attacker, + item *source_weapon ) { map &here = get_map(); const auto sigdist_to_coverage = []( const double sigdist ) { @@ -128,7 +129,7 @@ void execute_shaped_attack( const shape &sh, const projectile &proj, Creature &a atk.hit_critter = critter; atk.proj = proj; atk.missed_by = rng_float( 0.15, 0.45 ); - critter->deal_projectile_attack( &attacker, atk ); + critter->deal_projectile_attack( &attacker, source_weapon, atk ); } } } diff --git a/src/savegame_json.cpp b/src/savegame_json.cpp index 829f54589b82..ebe1eba3ecfa 100644 --- a/src/savegame_json.cpp +++ b/src/savegame_json.cpp @@ -2330,6 +2330,10 @@ void item::deserialize( JsonIn &jsin ) } else { data.read( "contents", contents ); } + if( data.has_member( "item_kill_tracker" ) ) { + kills = std::make_unique( false ); + data.read( "item_kill_tracker", kills ); + } if( data.has_member( "id" ) ) { safe_reference::id_type id; @@ -2350,6 +2354,10 @@ void item::serialize( JsonOut &json ) const if( !contents.empty() ) { json.member( "contents", contents ); } + if( kills ) { + json.member( "item_kill_tracker" ); + kills->serialize( json ); + } safe_reference::id_type id = safe_reference::lookup_id( this ); if( id != safe_reference::ID_NONE ) { diff --git a/src/scores_ui.cpp b/src/scores_ui.cpp index f26fafd976b4..4d591e93a005 100644 --- a/src/scores_ui.cpp +++ b/src/scores_ui.cpp @@ -165,3 +165,56 @@ void show_scores_ui( const achievements_tracker &achievements, stats_tracker &st } } } + +void show_kills( kill_tracker &kills ) +{ + catacurses::window w; + + input_context ctxt( "SCORES" ); + ctxt.register_cardinal(); + ctxt.register_action( "PAGE_UP" ); + ctxt.register_action( "PAGE_DOWN" ); + ctxt.register_action( "QUIT" ); + ctxt.register_action( "PREV_TAB" ); + ctxt.register_action( "NEXT_TAB" ); + ctxt.register_action( "HELP_KEYBINDINGS" ); + + catacurses::window w_view; + scrolling_text_view view( w_view ); + + ui_adaptor ui; + const auto &init_windows = [&]( ui_adaptor & ui ) { + w = new_centered_win( TERMY - 2, FULL_SCREEN_WIDTH ); + w_view = catacurses::newwin( getmaxy( w ) - 4, getmaxx( w ) - 1, + point( getbegx( w ), getbegy( w ) + 3 ) ); + ui.position_from_window( w ); + }; + ui.on_screen_resize( init_windows ); + // initialize explicitly here since w_view is used before first redraw + init_windows( ui ); + + ui.on_redraw( [&]( const ui_adaptor & ) { + werase( w ); + draw_border( w ); + wnoutrefresh( w ); + + view.set_text( kills.get_kills_text() ); + view.draw( c_white ); + } ); + + while( true ) { + ui_manager::redraw(); + const std::string action = ctxt.handle_input(); + if( action == "DOWN" ) { + view.scroll_down(); + } else if( action == "UP" ) { + view.scroll_up(); + } else if( action == "PAGE_DOWN" ) { + view.page_down(); + } else if( action == "PAGE_UP" ) { + view.page_up(); + } else if( action == "CONFIRM" || action == "QUIT" ) { + break; + } + } +} diff --git a/src/scores_ui.h b/src/scores_ui.h index 688bb6c2ce7f..aa7b4d8b2426 100644 --- a/src/scores_ui.h +++ b/src/scores_ui.h @@ -5,7 +5,10 @@ class achievements_tracker; class kill_tracker; class stats_tracker; +/** Display achievements, stats and kills in a scrollable window, each in their own tab */ void show_scores_ui( const achievements_tracker &achievements, stats_tracker &, const kill_tracker & ); +/** Display kills in a scrollable window */ +void show_kills( kill_tracker & ); #endif // CATA_SRC_SCORES_UI_H