Repository: doukutsu-rs/doukutsu-rs (language: rust)

Issue:
Damage/Exp number popup inaccuracies
- [x] When taking fatal damage (e.g. from a death spike), the damage amount is not displayed
- [x] The rising animation of damage numbers can be interrupted by inflicting more damage: (https://clips.twitch.tv/SuperLittleAsparagusVoteNay-z4REadr9A0TqpyID)
- [x] When taking damage and also collecting exp drops, the damage/exp numbers replace each other: (https://clips.twitch.tv/CrispyCrowdedStrawberryRickroll-2k73zE3C00chEECs)
- [x] Damage numbers display instantly for map bosses (Balfrog, Omega, Monster X, etc). Normally they should not appear until after a period of not taking damage



Discussion / hints:
github please

Fix patch:
diff --git a/src/components/number_popup.rs b/src/components/number_popup.rs
index 9ab56266..29f55b25 100644
--- a/src/components/number_popup.rs
+++ b/src/components/number_popup.rs
@@ -13,20 +13,15 @@ pub struct NumberPopup {
     pub prev_x: i32,
     pub prev_y: i32,
     counter: u16,
-    throttle: u16,
     value_display: i16,
 }
 
 impl NumberPopup {
     pub fn new() -> NumberPopup {
-        NumberPopup { value: 0, x: 0, y: 0, prev_x: 0, prev_y: 0, counter: 0, throttle: 0, value_display: 0 }
+        NumberPopup { value: 0, x: 0, y: 0, prev_x: 0, prev_y: 0, counter: 0, value_display: 0 }
     }
 
     pub fn set_value(&mut self, value: i16) {
-        if self.counter > 32 {
-            self.counter = 32;
-        }
-
         self.value = value;
     }
 
@@ -34,32 +29,24 @@ impl NumberPopup {
         self.set_value(self.value + value);
     }
 
-    pub fn set_value_throttled(&mut self, value: i16) {
-        self.throttle = 16;
-        self.set_value(value);
-    }
-
-    pub fn add_value_throttled(&mut self, value: i16) {
-        self.set_value_throttled(self.value + value);
+    pub fn update_displayed_value(&mut self) {
+        if self.counter > 32 {
+            self.counter = 32;
+        }
+        self.value_display += self.value;
+        self.value = 0;
     }
 }
 
 impl GameEntity<()> for NumberPopup {
     fn tick(&mut self, _state: &mut SharedGameState, _custom: ()) -> GameResult<()> {
-        if self.throttle > 0 {
-            self.throttle = self.throttle.saturating_sub(1);
-        }
-
-        if self.value == 0 || self.throttle > 0 {
+        if self.value_display == 0 {
             return Ok(());
         }
 
-        self.value_display = self.value;
-
         self.counter += 1;
         if self.counter == 80 {
             self.counter = 0;
-            self.value = 0;
             self.value_display = 0;
         }
 
@@ -81,7 +68,8 @@ impl GameEntity<()> for NumberPopup {
 
         let (frame_x, frame_y) = frame.xy_interpolated(state.frame_time);
         let x = interpolate_fix9_scale(self.prev_x, self.x, state.frame_time) - frame_x;
-        let y = interpolate_fix9_scale(self.prev_y, self.y, state.frame_time) - frame_y - y_offset;
+        let y = interpolate_fix9_scale(self.prev_y, self.y, state.frame_time) - frame_y - y_offset
+                     - 3.0f32; // This is supposed to be -4, but for some reason -3 looks more accurate
 
         let n = format!("{:+}", self.value_display);
 
diff --git a/src/game/npc/boss/mod.rs b/src/game/npc/boss/mod.rs
index 20f86e34..86022862 100644
--- a/src/game/npc/boss/mod.rs
+++ b/src/game/npc/boss/mod.rs
@@ -100,6 +100,9 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &BulletManager, &mut Fl
         for part in &mut self.parts {
             if part.shock > 0 {
                 part.shock -= 1;
+            } else if part.npc_flags.show_damage() && part.popup.value != 0 {
+                // I don't know where the best place to put this is, but let's try putting it here
+                part.popup.update_displayed_value();
             }
             part.popup.x = part.x;
             part.popup.y = part.y;
diff --git a/src/game/npc/mod.rs b/src/game/npc/mod.rs
index f7c08118..defd8821 100644
--- a/src/game/npc/mod.rs
+++ b/src/game/npc/mod.rs
@@ -625,6 +625,10 @@ impl GameEntity<([&mut Player; 2], &NPCList, &mut Stage, &mut BulletManager, &mu
             _ => Ok(()),
         }?;
 
+        // I don't know where the best place to put this is, but let's try putting it here
+        if self.shock == 0 && self.npc_flags.show_damage() && self.popup.value != 0 {
+            self.popup.update_displayed_value();
+        }
         self.popup.x = self.x;
         self.popup.y = self.y;
         self.popup.tick(state, ())?;
diff --git a/src/game/npc/utils.rs b/src/game/npc/utils.rs
index bf8e9b6b..e4f1550a 100644
--- a/src/game/npc/utils.rs
+++ b/src/game/npc/utils.rs
@@ -332,6 +332,9 @@ impl NPCList {
             state.set_flag(npc.flag_num as usize, true);
 
             if npc.npc_flags.show_damage() {
+                if npc.popup.value != 0 {
+                    npc.popup.update_displayed_value();
+                }
                 if vanish {
                     npc.vanish(state);
                 }
diff --git a/src/game/player/mod.rs b/src/game/player/mod.rs
index a469fb74..ada99737 100644
--- a/src/game/player/mod.rs
+++ b/src/game/player/mod.rs
@@ -108,14 +108,13 @@ pub struct Player {
     pub air: u16,
     pub skin: Box<dyn PlayerSkin>,
     pub controller: Box<dyn PlayerController>,
-    pub popup: NumberPopup,
+    pub damage_popup: NumberPopup,
+    pub exp_popup: NumberPopup,
     strafe_up: bool,
     weapon_offset_y: i8,
     splash: bool,
     tick: u8,
     booster_switch: BoosterSwitch,
-    damage_counter: u16,
-    damage_taken: i16,
     pub anim_num: u16,
     anim_counter: u16,
     anim_rect: Rect<u16>,
@@ -167,10 +166,9 @@ impl Player {
             air: 0,
             skin,
             controller: Box::new(DummyPlayerController::new()),
-            popup: NumberPopup::new(),
+            damage_popup: NumberPopup::new(),
+            exp_popup: NumberPopup::new(),
             strafe_up: false,
-            damage_counter: 0,
-            damage_taken: 0,
             anim_num: 0,
             anim_counter: 0,
             anim_rect: constants.player.frames_right[0],
@@ -896,11 +894,8 @@ impl Player {
         self.controller.set_rumble(rumble_intensity, rumble_intensity, 20);
 
         self.damage = self.damage.saturating_add(final_hp as u16);
-        if self.popup.value > 0 {
-            self.popup.set_value(-(self.damage as i16));
-        } else {
-            self.popup.add_value(-(self.damage as i16));
-        }
+        self.damage_popup.add_value(-(self.damage as i16));
+        self.damage_popup.update_displayed_value();
 
         if self.life == 0 {
             state.sound_manager.play_sfx(17);
@@ -937,9 +932,9 @@ impl GameEntity<&NPCList> for Player {
     fn tick(&mut self, state: &mut SharedGameState, npc_list: &NPCList) -> GameResult {
         if !self.cond.alive() {
             if self.life == 0 {
-                self.popup.x = self.x;
-                self.popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
-                self.popup.tick(state, ())?;
+                self.damage_popup.x = self.x;
+                self.damage_popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
+                self.damage_popup.tick(state, ())?;
             }
 
             return Ok(());
@@ -949,18 +944,14 @@ impl GameEntity<&NPCList> for Player {
             self.shock_counter = 0;
         }
 
-        if self.damage_counter != 0 {
-            self.damage_counter -= 1;
-        }
-
         if self.xp_counter != 0 {
             self.xp_counter -= 1;
         }
 
         if self.shock_counter != 0 {
             self.shock_counter -= 1;
-        } else if self.damage_taken != 0 {
-            self.damage_taken = 0;
+        } else if self.exp_popup.value != 0 {
+            self.exp_popup.update_displayed_value();
         }
 
         match (self.control_mode, state.settings.noclip) {
@@ -969,9 +960,12 @@ impl GameEntity<&NPCList> for Player {
             (ControlMode::IronHead, _) => self.tick_ironhead(state)?,
         }
 
-        self.popup.x = self.x;
-        self.popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
-        self.popup.tick(state, ())?;
+        self.damage_popup.x = self.x;
+        self.damage_popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
+        self.damage_popup.tick(state, ())?;
+        self.exp_popup.x = self.x;
+        self.exp_popup.y = self.y - self.display_bounds.top as i32 + 0x1000;
+        self.exp_popup.tick(state, ())?;
 
         self.cond.set_increase_acceleration(false);
         self.tick_animation(state);
diff --git a/src/game/player/player_hit.rs b/src/game/player/player_hit.rs
index fb79d9ca..690eddf0 100644
--- a/src/game/player/player_hit.rs
+++ b/src/game/player/player_hit.rs
@@ -287,16 +287,8 @@ impl Player {
                     inventory.add_xp(npc.exp, self, state);
 
                     if let Some(weapon) = inventory.get_current_weapon() {
-                        if weapon.wtype == WeaponType::Spur {
-                            npc.exp = 0;
-                        } else {
-                            if self.shock_counter == 0 {
-                                if self.popup.value > 0 {
-                                    self.popup.add_value(npc.exp as i16);
-                                } else {
-                                    self.popup.set_value(npc.exp as i16);
-                                }
-                            }
+                        if weapon.wtype != WeaponType::Spur {
+                            self.exp_popup.add_value(npc.exp as i16);
                         }
                     }
 
diff --git a/src/scene/game_scene.rs b/src/scene/game_scene.rs
index 73ea93e0..2a5c041d 100644
--- a/src/scene/game_scene.rs
+++ b/src/scene/game_scene.rs
@@ -1197,7 +1197,7 @@ impl GameScene {
                         }
 
                         if npc.npc_flags.show_damage() {
-                            npc.popup.add_value_throttled(-bullet.damage);
+                            npc.popup.add_value(-bullet.damage);
                         }
                     }
                 } else if !bullet.weapon_flags.no_proj_dissipation()
@@ -1261,6 +1261,7 @@ impl GameScene {
                 }
 
                 if npc.npc_flags.shootable() {
+                    let shock = npc.shock;
                     if npc.cond.damage_boss() {
                         idx = 0;
                         npc = unsafe { self.boss.parts.get_unchecked_mut(0) };
@@ -1268,10 +1269,6 @@ impl GameScene {
 
                     npc.life = (npc.life as i32).saturating_sub(bullet.damage as i32).clamp(0, u16::MAX as i32) as u16;
 
-                    if npc.npc_flags.show_damage() {
-                        npc.popup.add_value(-bullet.damage);
-                    }
-
                     if npc.life == 0 {
                         npc.life = npc.id;
 
@@ -1295,7 +1292,7 @@ impl GameScene {
                             npc.cond.set_alive(false);
                         }
                     } else {
-                        if npc.shock < 14 {
+                        if shock < 14 {
                             for _ in 0..3 {
                                 state.create_caret(bullet.x, bullet.y, CaretType::HurtParticles, Direction::Left);
                             }
@@ -1303,6 +1300,9 @@ impl GameScene {
                         }
 
                         npc.shock = 8;
+                        if npc.npc_flags.show_damage() {
+                            npc.popup.add_value(-bullet.damage);
+                        }
 
                         npc = unsafe { self.boss.parts.get_unchecked_mut(i) };
                         npc.shock = 8;
@@ -1919,12 +1919,16 @@ impl Scene for GameScene {
         self.frame.prev_y = self.frame.y;
         self.player1.prev_x = self.player1.x;
         self.player1.prev_y = self.player1.y;
-        self.player1.popup.prev_x = self.player1.popup.x;
-        self.player1.popup.prev_y = self.player1.popup.y;
+        self.player1.damage_popup.prev_x = self.player1.damage_popup.x;
+        self.player1.damage_popup.prev_y = self.player1.damage_popup.y;
+        self.player1.exp_popup.prev_x = self.player1.exp_popup.x;
+        self.player1.exp_popup.prev_y = self.player1.exp_popup.y;
         self.player2.prev_x = self.player2.x;
         self.player2.prev_y = self.player2.y;
-        self.player2.popup.prev_x = self.player2.popup.x;
-        self.player2.popup.prev_y = self.player2.popup.y;
+        self.player2.damage_popup.prev_x = self.player2.damage_popup.x;
+        self.player2.damage_popup.prev_y = self.player2.damage_popup.y;
+        self.player2.exp_popup.prev_x = self.player2.exp_popup.x;
+        self.player2.exp_popup.prev_y = self.player2.exp_popup.y;
 
         for npc in self.npc_list.iter_alive() {
             npc.prev_x = npc.x;
@@ -2010,8 +2014,10 @@ impl Scene for GameScene {
         self.water_renderer.draw(state, ctx, &self.frame, WaterLayer::Front)?;
 
         self.draw_carets(state, ctx)?;
-        self.player1.popup.draw(state, ctx, &self.frame)?;
-        self.player2.popup.draw(state, ctx, &self.frame)?;
+        self.player1.exp_popup.draw(state, ctx, &self.frame)?;
+        self.player1.damage_popup.draw(state, ctx, &self.frame)?;
+        self.player2.exp_popup.draw(state, ctx, &self.frame)?;
+        self.player2.damage_popup.draw(state, ctx, &self.frame)?;
         self.draw_npc_popup(state, ctx)?;
         self.draw_boss_popup(state, ctx)?;
 


Test patch: