Skip to main content

steel_core/player/
tick_state.rs

1/// Per-player tick counters and once-per-tick packet state.
2#[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}