overlord_event_system/behaviors/
arena.rs1use configs::fighting::ArenaSettings;
14
15pub struct RatingChangeCtx<'a> {
19 pub player_power: i64,
20 pub player_rating: i64,
21 pub player_win_streak: i64,
22 pub opponent_power: i64,
23 pub settings: &'a ArenaSettings,
24}
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum OpponentKind {
29 Prey,
30 Peer,
31 Stretch,
32}
33
34pub fn opponent_kind(
35 player_power: i64,
36 opponent_power: i64,
37 settings: &ArenaSettings,
38) -> OpponentKind {
39 if player_power <= 0 {
40 return OpponentKind::Peer;
41 }
42 let ratio = opponent_power as f64 / player_power as f64;
43 if ratio < settings.rating_prey_power_ratio {
44 OpponentKind::Prey
45 } else if ratio > settings.rating_stretch_power_ratio {
46 OpponentKind::Stretch
47 } else {
48 OpponentKind::Peer
49 }
50}
51
52pub fn win_rating_increase(ctx: &RatingChangeCtx) -> i64 {
54 let s = ctx.settings;
55 let base = match opponent_kind(ctx.player_power, ctx.opponent_power, s) {
56 OpponentKind::Prey => s.win_points_prey,
57 OpponentKind::Peer => s.win_points_peer,
58 OpponentKind::Stretch => s.win_points_stretch,
59 };
60 let streak_bonus = (ctx.player_win_streak.max(0) * s.win_streak_bonus_step)
61 .min(s.win_streak_bonus_max)
62 .max(0);
63 base + streak_bonus
64}
65
66pub fn lose_rating_decrease(ctx: &RatingChangeCtx) -> i64 {
70 let s = ctx.settings;
71 if ctx.player_rating < s.lose_protection_rating {
72 return 0;
73 }
74 let cost = match opponent_kind(ctx.player_power, ctx.opponent_power, s) {
75 OpponentKind::Prey => s.lose_points_prey,
76 OpponentKind::Peer => s.lose_points_peer,
77 OpponentKind::Stretch => s.lose_points_stretch,
78 };
79 -cost.max(0)
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 fn settings() -> ArenaSettings {
87 serde_json::from_value(serde_json::json!({
90 "base_rating": 1000,
91 "matches_history_size": 10,
92 "leaderboard_size": 100,
93 "arena_tickets_currency_id": "0199958f-61b0-72f3-97ba-16dff38a93d0",
94 "matchmaking_character_rating_delta": 100,
95 "pvp_cooldown_secs": 60,
96 "win_reward_bundle_id": "019f2358-e971-7d1f-827d-f722bea66b8c",
97 "lose_reward_bundle_id": "019f2358-e971-7d1f-827d-f722bea66b8c",
98 "arena_ticket_buy_currency_id": "0194d64e-2162-76d3-8449-3e850f6e39e9",
99 "arena_ticket_price": {"currency_id": "0194d64e-2162-76d3-8449-3e850f6e39e9", "amount": 20},
100 "arena_matches_ttl_days": 30,
101 "rematch_max_rating_increase": 10,
102 "rematch_max_rating_decrease": 10
103 }))
104 .expect("arena settings from defaults")
105 }
106
107 fn ctx(
108 s: &ArenaSettings,
109 power: i64,
110 opp: i64,
111 rating: i64,
112 streak: i64,
113 ) -> RatingChangeCtx<'_> {
114 RatingChangeCtx {
115 player_power: power,
116 player_rating: rating,
117 player_win_streak: streak,
118 opponent_power: opp,
119 settings: s,
120 }
121 }
122
123 #[test]
124 fn asymmetric_points_by_power_kind() {
125 let s = settings();
126 assert_eq!(
128 win_rating_increase(&ctx(&s, 1000, 700, 1500, 0)),
129 s.win_points_prey
130 );
131 assert_eq!(
132 win_rating_increase(&ctx(&s, 1000, 1000, 1500, 0)),
133 s.win_points_peer
134 );
135 assert_eq!(
136 win_rating_increase(&ctx(&s, 1000, 1300, 1500, 0)),
137 s.win_points_stretch
138 );
139 }
140
141 #[test]
142 fn win_streak_bonus_is_capped() {
143 let s = settings();
144 let base = s.win_points_peer;
145 assert_eq!(win_rating_increase(&ctx(&s, 1000, 1000, 1500, 1)), base + 1);
146 assert_eq!(
147 win_rating_increase(&ctx(&s, 1000, 1000, 1500, 50)),
148 base + s.win_streak_bonus_max
149 );
150 }
151
152 #[test]
153 fn loss_is_free_under_protection_rating() {
154 let s = settings();
155 assert_eq!(
156 lose_rating_decrease(&ctx(&s, 1000, 1000, s.lose_protection_rating - 1, 0)),
157 0
158 );
159 assert_eq!(
160 lose_rating_decrease(&ctx(&s, 1000, 1000, s.lose_protection_rating, 0)),
161 -s.lose_points_peer
162 );
163 }
164
165 #[test]
166 fn asymmetric_loss_by_power_kind() {
167 let s = settings();
168 assert_eq!(
171 lose_rating_decrease(&ctx(&s, 1000, 700, 1500, 0)),
172 -s.lose_points_prey
173 );
174 assert_eq!(
175 lose_rating_decrease(&ctx(&s, 1000, 1000, 1500, 0)),
176 -s.lose_points_peer
177 );
178 assert_eq!(
179 lose_rating_decrease(&ctx(&s, 1000, 1300, 1500, 0)),
180 -s.lose_points_stretch
181 );
182 assert!(s.lose_points_prey > s.lose_points_peer);
183 assert!(s.lose_points_peer > s.lose_points_stretch);
184 }
185
186 #[test]
187 fn zero_power_player_defaults_to_peer() {
188 let s = settings();
189 assert_eq!(opponent_kind(0, 500, &s), OpponentKind::Peer);
190 }
191}