1use super::{shogun::BARRACKS_SLOT_EMPTY, Barracks, DojosAccount};
6use steel::*;
7
8pub use crate::consts::{
9 BATTLE_CHAMPION_SLOT_UNSET as CHAMPION_SLOT_UNSET, BATTLE_DEFAULT_AP as DEFAULT_AP,
10 BATTLE_DEFAULT_BATTLE_POINTS as DEFAULT_BATTLE_POINTS, BATTLE_DEFAULT_DP as DEFAULT_DP,
11};
12
13#[repr(C)]
14#[derive(Clone, Copy, Debug, PartialEq, bytemuck::Pod, bytemuck::Zeroable)]
15pub struct Battle {
16 pub dojo: Pubkey,
17 pub attack_points: u64,
19 pub defense_points: u64,
21 pub battle_points: u64,
23 pub last_battle_slot: u64,
25 pub last_targeted_slot: u64,
27 pub duel_window_start_slot: u64,
29 pub duel_count_24h: u64,
31 pub champion_slot: u64,
33 pub last_champion_change_slot: u64,
35 pub buffer: [u64; 3],
36}
37
38account!(DojosAccount, Battle);
39
40impl Battle {
41 pub fn champion_sp(barracks: &Barracks, champion_slot: u64) -> Option<u64> {
43 if champion_slot == crate::consts::BATTLE_CHAMPION_SLOT_UNSET {
44 return None;
45 }
46 let i = champion_slot as usize;
47 if i >= crate::consts::MAX_BARRACKS_SLOTS {
48 return None;
49 }
50 if !barracks.is_slot_valid(champion_slot) {
51 return None;
52 }
53 if barracks.slots[i] == BARRACKS_SLOT_EMPTY {
54 return None;
55 }
56 Some(barracks.slot_cache[i].spirit_power.max(1))
57 }
58
59 pub fn champion_chakra_remaining(barracks: &Barracks, champion_slot: u64) -> Option<u64> {
61 if champion_slot == crate::consts::BATTLE_CHAMPION_SLOT_UNSET {
62 return None;
63 }
64 let i = champion_slot as usize;
65 if i >= crate::consts::MAX_BARRACKS_SLOTS {
66 return None;
67 }
68 if !barracks.is_slot_valid(champion_slot) {
69 return None;
70 }
71 if barracks.slots[i] == BARRACKS_SLOT_EMPTY {
72 return None;
73 }
74 Some(barracks.slot_cache[i].chakra_remaining)
75 }
76
77 pub fn win_probability_bps(attacker_ap: u64, defender_dp: u64) -> u64 {
79 let sum = attacker_ap.saturating_add(defender_dp);
80 if sum == 0 {
81 return 5000;
82 }
83 attacker_ap.saturating_mul(10_000) / sum
84 }
85
86 pub fn point_transfer_fren(
93 attacker_bp: u64,
94 defender_bp: u64,
95 win_bps: u64,
96 attacker_won: bool,
97 defender_cap_bps: u64,
98 ) -> u64 {
99 use crate::consts::{
100 BATTLE_LOW_WIN_CAP, BATTLE_POINTS_LOW_THRESHOLD, BATTLE_TRANSFER_BPS,
101 };
102 if attacker_bp == 0 || defender_bp == 0 {
103 return 0;
104 }
105
106 if attacker_bp < BATTLE_POINTS_LOW_THRESHOLD {
108 if attacker_won {
109 let max_def = defender_bp.saturating_mul(defender_cap_bps) / 10_000;
110 let t = BATTLE_LOW_WIN_CAP.min(max_def).min(defender_bp);
111 return t.max(1);
112 }
113 let max_att = attacker_bp.saturating_mul(BATTLE_TRANSFER_BPS) / 10_000;
114 let t = max_att.min(attacker_bp);
115 return t.max(1);
116 }
117
118 let max_att = attacker_bp.saturating_mul(BATTLE_TRANSFER_BPS) / 10_000;
119 let max_def = defender_bp.saturating_mul(defender_cap_bps) / 10_000;
120 if max_att == 0 || max_def == 0 {
121 return 1;
122 }
123
124 let raw = if win_bps < 5000 {
126 if attacker_won {
128 let pct = max_att.saturating_mul(5000u64.saturating_sub(win_bps)) / 10_000;
130 let sixty = max_att.saturating_mul(6000) / 10_000;
131 max_att
132 .saturating_add(pct)
133 .saturating_add(sixty)
134 .min(max_def)
135 } else {
136 let pct = max_def.saturating_mul(5000u64.saturating_sub(win_bps)) / 10_000;
138 let sixty = max_def.saturating_mul(6000) / 10_000;
139 max_def
140 .saturating_add(pct)
141 .saturating_add(sixty)
142 .min(max_att)
143 }
144 } else {
145 if attacker_won {
147 let t = max_att.saturating_add(max_att.saturating_mul(6000) / 10_000);
149 t.min(max_def)
150 } else {
151 let t = max_def.saturating_add(max_def.saturating_mul(win_bps) / 10_000);
153 t.min(max_att)
154 }
155 };
156
157 let loser_bp = if attacker_won {
158 defender_bp
159 } else {
160 attacker_bp
161 };
162 raw.max(1).min(loser_bp)
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::Battle;
169
170 #[test]
171 fn fren_stronger_opponent_examples() {
172 let a = 100_000u64;
174 let d = 200_000u64;
175 let win_bps = 2000;
176 assert_eq!(Battle::point_transfer_fren(a, d, win_bps, false, 50), 500);
177 assert_eq!(Battle::point_transfer_fren(a, d, win_bps, true, 50), 950);
178 }
179
180 #[test]
181 fn fren_low_attacker_under_10k() {
182 assert_eq!(Battle::point_transfer_fren(9_000, 100_000, 2000, true, 50), 100);
184 assert_eq!(Battle::point_transfer_fren(9_000, 100_000, 2000, false, 50), 45);
185 assert_eq!(Battle::point_transfer_fren(9_000, 5_000, 7000, true, 50), 25);
186 assert_eq!(Battle::point_transfer_fren(9_000, 5_000, 7000, true, 100), 50);
188 }
189
190 #[test]
191 fn fren_weaker_opponent_examples() {
192 let a = 200_000u64;
194 let d = 100_000u64;
195 let win_bps = 7000;
196 assert_eq!(Battle::point_transfer_fren(a, d, win_bps, false, 50), 850);
197 assert_eq!(Battle::point_transfer_fren(a, d, win_bps, true, 50), 500);
198
199 let a = 100_000u64;
201 let d = 200_000u64;
202 let win_bps = 7000;
203 assert_eq!(Battle::point_transfer_fren(a, d, win_bps, false, 50), 500);
204 assert_eq!(Battle::point_transfer_fren(a, d, win_bps, true, 50), 800);
205 }
206
207 #[test]
208 fn fren_defender_depleted_doubles_cap_when_binding() {
209 let a = 100_000u64;
211 let d = 200_000u64;
212 let win_bps = 0u64;
213 assert_eq!(Battle::point_transfer_fren(a, d, win_bps, true, 50), 1000);
214 assert_eq!(Battle::point_transfer_fren(a, d, win_bps, true, 100), 1050);
215 }
216}