steel_core/player/
tick_state.rs1#[derive(Debug, Clone, Copy)]
3pub(super) struct PlayerTickState {
4 tick_count: i32,
5 attack_strength_ticker: i32,
6 take_xp_delay: i32,
7 last_level_up_time: i32,
8 ack_block_changes_up_to: i32,
9}
10
11impl PlayerTickState {
12 #[must_use]
13 pub(super) const fn new() -> Self {
14 Self {
15 tick_count: 0,
16 attack_strength_ticker: 0,
17 take_xp_delay: 0,
18 last_level_up_time: 0,
19 ack_block_changes_up_to: -1,
20 }
21 }
22
23 #[must_use]
24 pub(super) const fn tick_count(self) -> i32 {
25 self.tick_count
26 }
27
28 #[must_use]
29 pub(super) const fn attack_strength_ticker(self) -> i32 {
30 self.attack_strength_ticker
31 }
32
33 #[must_use]
34 pub(super) const fn take_xp_delay(self) -> i32 {
35 self.take_xp_delay
36 }
37
38 pub(super) const fn set_take_xp_delay(&mut self, delay: i32) {
39 self.take_xp_delay = delay;
40 }
41
42 pub(super) fn mark_level_up_sound_if_due(&mut self) -> bool {
43 if self.last_level_up_time as f32 >= self.tick_count as f32 - 100.0 {
44 return false;
45 }
46 self.last_level_up_time = self.tick_count;
47 true
48 }
49
50 pub(super) const fn advance_tick(&mut self) {
51 self.tick_count = self.tick_count.wrapping_add(1);
52 if self.take_xp_delay > 0 {
53 self.take_xp_delay -= 1;
54 }
55 }
56
57 pub(super) const fn advance_attack_strength_ticker(&mut self) {
58 self.attack_strength_ticker = self.attack_strength_ticker.wrapping_add(1);
59 }
60
61 pub(super) const fn reset_attack_strength_ticker(&mut self) {
62 self.attack_strength_ticker = 0;
63 }
64
65 pub(super) const fn ack_block_changes_up_to(&mut self, sequence: i32) {
66 if sequence > self.ack_block_changes_up_to {
67 self.ack_block_changes_up_to = sequence;
68 }
69 }
70
71 pub(super) const fn take_ack_block_changes_up_to(&mut self) -> i32 {
72 let sequence = self.ack_block_changes_up_to;
73 self.ack_block_changes_up_to = -1;
74 sequence
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::PlayerTickState;
81
82 #[test]
83 fn tick_count_advances_with_wrapping_semantics() {
84 let mut state = PlayerTickState::new();
85
86 state.advance_tick();
87 state.advance_tick();
88
89 assert_eq!(state.tick_count(), 2);
90 }
91
92 #[test]
93 fn attack_strength_ticker_advances_and_resets() {
94 let mut state = PlayerTickState::new();
95
96 state.advance_attack_strength_ticker();
97 state.advance_attack_strength_ticker();
98 assert_eq!(state.attack_strength_ticker(), 2);
99
100 state.reset_attack_strength_ticker();
101 assert_eq!(state.attack_strength_ticker(), 0);
102 }
103
104 #[test]
105 fn xp_pickup_delay_decrements_during_tick() {
106 let mut state = PlayerTickState::new();
107 state.set_take_xp_delay(2);
108
109 state.advance_tick();
110 assert_eq!(state.take_xp_delay(), 1);
111
112 state.advance_tick();
113 assert_eq!(state.take_xp_delay(), 0);
114
115 state.advance_tick();
116 assert_eq!(state.take_xp_delay(), 0);
117 }
118
119 #[test]
120 fn level_up_sound_uses_vanillas_strict_hundred_tick_cooldown() {
121 let mut state = PlayerTickState::new();
122 for _ in 0..100 {
123 state.advance_tick();
124 }
125 assert!(!state.mark_level_up_sound_if_due());
126
127 state.advance_tick();
128 assert!(state.mark_level_up_sound_if_due());
129
130 for _ in 0..100 {
131 state.advance_tick();
132 }
133 assert!(!state.mark_level_up_sound_if_due());
134 state.advance_tick();
135 assert!(state.mark_level_up_sound_if_due());
136 }
137
138 #[test]
139 fn block_ack_keeps_highest_sequence_until_taken() {
140 let mut state = PlayerTickState::new();
141
142 state.ack_block_changes_up_to(3);
143 state.ack_block_changes_up_to(1);
144 state.ack_block_changes_up_to(5);
145
146 assert_eq!(state.take_ack_block_changes_up_to(), 5);
147 assert_eq!(state.take_ack_block_changes_up_to(), -1);
148 }
149}