1use crate::scheme::DDSTATS_BASE_URL;
2use crate::util::prelude::{encode, seconds_to_hours, slugify2};
3use chrono::{NaiveDate, NaiveDateTime};
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
8pub struct Player {
9 pub points_graph: Vec<PointsGraph>,
10 pub recent_finishes: Vec<RecentFinish>,
11 pub favourite_teammates: Vec<FavouriteTeammate>,
12 pub profile: Profile,
13 pub is_mapper: bool,
14 pub finishes: Vec<Finish>,
15 pub unfinished_maps: Vec<UnfinishedMap>,
16 pub points: Points,
17 #[serde(default)]
18 pub completion_progress: Vec<CompletionProgress>,
19 pub recent_activity: Vec<RecentActivity>,
20 pub recent_player_info: Vec<RecentPlayerInfo>,
21 pub most_played_maps: Vec<MostPlayedMap>,
22 pub most_played_gametypes: Vec<MostPlayed>,
23 pub most_played_categories: Vec<MostPlayed>,
24 pub most_played_locations: Vec<MostPlayed>,
25 pub playtime_per_month: Vec<PlaytimePerMonth>,
26 pub general_activity: Option<GeneralActivity>,
27 pub favourite_rank1s_teammates: Vec<FavouriteRank1sTeammates>,
28 pub all_top_10s: Vec<AllTop10>,
29 pub recent_top_10s: Vec<RecentTop10>,
30}
31
32#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub struct CompletionProgress {
34 pub category: String,
35 pub maps_finished: i64,
36 pub maps_total: i64,
37}
38
39impl Player {
40 #[must_use]
41 pub fn url(&self) -> String {
42 format!(
43 "https://{}/player/{}",
44 DDSTATS_BASE_URL,
45 encode(&self.profile.name)
46 )
47 }
48
49 #[must_use]
50 pub fn url_with_name(player: &str) -> String {
51 format!("https://{}/player/{}", DDSTATS_BASE_URL, encode(player))
52 }
53
54 #[must_use]
55 pub fn api(player: &str) -> String {
56 format!(
57 "https://{}/player/json?player={}",
58 DDSTATS_BASE_URL,
59 encode(player)
60 )
61 }
62}
63
64#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct PointsGraph {
66 pub date: NaiveDate,
67 pub points: i64,
68 pub rank_points: i64,
69 pub team_points: i64,
70}
71
72#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
73pub struct RecentFinish {
74 pub map: StatsMap,
75 pub name: String,
76 pub time: f64,
77 pub timestamp: NaiveDateTime,
78 pub server: String,
79 pub cp1: f64,
80 pub cp2: f64,
81 pub cp3: f64,
82 pub cp4: f64,
83 pub cp5: f64,
84 pub cp6: f64,
85 pub cp7: f64,
86 pub cp8: f64,
87 pub cp9: f64,
88 pub cp10: f64,
89 pub cp11: f64,
90 pub cp12: f64,
91 pub cp13: f64,
92 pub cp14: f64,
93 pub cp15: f64,
94 pub cp16: f64,
95 pub cp17: f64,
96 pub cp18: f64,
97 pub cp19: f64,
98 pub cp20: f64,
99 pub cp21: f64,
100 pub cp22: f64,
101 pub cp23: f64,
102 pub cp24: f64,
103 pub cp25: f64,
104 pub team_rank: Option<TeamRankingSMap>,
105 pub rank: Option<RankingSMap>,
106}
107
108#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
109pub struct StatsMap {
110 pub map: String,
111 pub server: String,
112 pub points: i32,
113 pub stars: i32,
114 pub mapper: String,
115 pub timestamp: Option<NaiveDateTime>,
116}
117
118impl StatsMap {
119 #[must_use]
120 pub fn url(&self) -> String {
121 format!(
122 "https://{}/map/{}",
123 DDSTATS_BASE_URL,
124 encode(&slugify2(&self.map))
125 )
126 }
127
128 #[must_use]
129 pub fn url_with_name(map: &str) -> String {
130 format!(
131 "https://{}/map/{}",
132 DDSTATS_BASE_URL,
133 encode(&slugify2(map))
134 )
135 }
136
137 #[must_use]
138 pub fn api() -> String {
139 format!("https://{DDSTATS_BASE_URL}/maps/json")
140 }
141}
142
143#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
144pub struct FavouriteTeammate {
145 pub name: String,
146 pub ranks_together: i64,
147}
148
149#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
150pub struct Profile {
151 pub name: String,
152 pub points: i32,
153 pub clan: Option<String>,
154 pub country: Option<i32>,
155 pub skin_name: Option<String>,
156 pub skin_color_body: Option<i32>,
157 pub skin_color_feet: Option<i32>,
158 pub most_played_location: Option<String>,
159}
160
161impl Profile {
162 #[must_use]
163 pub fn url(&self) -> String {
164 format!("https://{}/player/{}", DDSTATS_BASE_URL, encode(&self.name))
165 }
166
167 #[must_use]
168 pub fn url_with_name(player: &str) -> String {
169 format!("https://{}/player/{}", DDSTATS_BASE_URL, encode(player))
170 }
171
172 #[must_use]
173 pub fn api(player: &str) -> String {
174 format!(
175 "https://{}/profile/json?player={}",
176 DDSTATS_BASE_URL,
177 encode(player)
178 )
179 }
180}
181
182#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
183pub struct Finish {
184 pub map: StatsMap,
185 pub name: String,
186 pub time: f64,
187 pub timestamp: NaiveDateTime,
188 pub server: String,
189 pub rank: i32,
190 pub team_rank: Option<i32>,
191 pub seconds_played: Option<i32>,
192}
193
194#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
195pub struct UnfinishedMap {
196 pub map: StatsMap,
197 pub finishes: Option<i32>,
198 pub finishes_rank: Option<i32>,
199 pub median_time: Option<f64>,
200}
201
202#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
203pub struct Points {
204 pub weekly_points: Option<LeaderboardRank>,
205 pub monthly_points: Option<LeaderboardRank>,
206 pub yearly_points: Option<LeaderboardRank>,
207 pub points: HashMap<Category, Option<LeaderboardRank>>,
208 pub rank_points: HashMap<Category, Option<LeaderboardRank>>,
209 pub team_points: HashMap<Category, Option<LeaderboardRank>>,
210}
211
212#[derive(Default, Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
213pub struct LeaderboardRank {
214 pub points: u64,
215 pub rank: u64,
216}
217
218#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
219pub enum Category {
220 All,
221 Total,
222 Novice,
223 Moderate,
224 Brutal,
225 Insane,
226 Dummy,
227 #[serde(rename = "DDmaX.Easy")]
228 DDmaXEasy,
229 #[serde(rename = "DDmaX.Next")]
230 DDmaXNext,
231 #[serde(rename = "DDmaX.Pro")]
232 DDmaXPro,
233 #[serde(rename = "DDmaX.Nut")]
234 DDmaXNut,
235 Oldschool,
236 Solo,
237 Race,
238 Fun,
239 Event,
240}
241
242#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
243pub struct RecentActivity {
244 pub name: String,
245 pub date: NaiveDate,
246 pub map_name: String,
247 pub map: Option<StatsMap>,
248 pub seconds_played: i64,
249}
250
251#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
252pub struct RecentPlayerInfo {
253 pub name: String,
254 pub clan: String,
255 pub country: i32,
256 pub skin_name: String,
257 pub skin_color_body: Option<i32>,
258 pub skin_color_feet: Option<i32>,
259 pub last_seen: NaiveDate,
260 pub seconds_played: i64,
261}
262
263#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
264pub struct MostPlayedMap {
265 pub map_name: String,
266 pub seconds_played: i64,
267 pub map: Option<StatsMap>,
268}
269
270#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
271pub struct MostPlayedMapsGametype {
272 pub map_name: String,
273 pub seconds_played: i64,
274 pub gametype: String,
275}
276
277#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub struct Teero {
279 pub most_played_maps_gametype: Vec<MostPlayedMapsGametype>,
280}
281
282impl Teero {
283 #[must_use]
284 pub fn api(player: &str) -> String {
285 format!(
286 "https://{}/teero/json?player={}",
287 DDSTATS_BASE_URL,
288 encode(player)
289 )
290 }
291}
292
293#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
294pub struct MostPlayed {
295 pub key: String,
296 pub seconds_played: i64,
297}
298
299#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
300pub struct PlaytimePerMonth {
301 pub year_month: String,
302 pub month: String,
303 pub seconds_played: i64,
304}
305
306#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
307pub struct GeneralActivity {
308 pub total_seconds_played: i64,
309 pub start_of_playtime: NaiveDate,
310 pub average_seconds_played: i64,
311}
312
313impl GeneralActivity {
314 #[must_use]
315 pub fn total_seconds_played_to_hours(&self) -> f64 {
316 #[allow(clippy::cast_precision_loss)]
319 let seconds = self.total_seconds_played as f64;
320 seconds_to_hours(seconds)
321 }
322
323 #[must_use]
324 pub fn average_seconds_played_to_hours(&self) -> f64 {
325 #[allow(clippy::cast_precision_loss)]
326 let seconds = self.average_seconds_played as f64;
327 seconds_to_hours(seconds)
328 }
329}
330
331#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
332pub struct FavouriteRank1sTeammates {
333 pub name: String,
334 pub ranks_together: i64,
335}
336
337#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
338pub struct AllTop10 {
339 pub map: StatsMap,
340 pub name: String,
341 pub time: f64,
342 pub rank: i32,
343 pub team_rank: Option<i32>,
344 pub team_time: Option<f64>,
345}
346
347#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
348pub struct RecentTop10 {
349 pub rank_type: String,
350 pub map: String,
351 pub time: f64,
352 pub rank: i32,
353 pub timestamp: NaiveDateTime,
354 pub server: String,
355}
356
357#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
358pub struct InfoSMap {
359 pub map: StatsMap,
360 pub finishes: Option<i32>,
361 pub finishes_rank: Option<i32>,
362 pub median_time: Option<f64>,
363 pub total_playtime: Option<i64>,
364 pub total_playtime_rank: Option<i64>,
365}
366
367#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
368pub struct RankingSMap {
369 pub rank: i32,
370 pub timestamp: Option<NaiveDateTime>,
371 pub name: String,
372 pub time: f64,
373 pub map: String,
374 pub server: String,
375}
376
377#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
378pub struct TeamRankingSMap {
379 pub rank: i32,
380 pub timestamp: Option<NaiveDateTime>,
381 pub id: Vec<u8>,
382 pub players: Vec<String>,
383 pub time: f64,
384 pub map: String,
385 pub server: String,
386}
387
388#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
389pub struct TimeCpsSMap {
390 pub name: String,
391 pub cp1: f64,
392 pub cp2: f64,
393 pub cp3: f64,
394 pub cp4: f64,
395 pub cp5: f64,
396 pub cp6: f64,
397 pub cp7: f64,
398 pub cp8: f64,
399 pub cp9: f64,
400 pub cp10: f64,
401 pub cp11: f64,
402 pub cp12: f64,
403 pub cp13: f64,
404 pub cp14: f64,
405 pub cp15: f64,
406 pub cp16: f64,
407 pub cp17: f64,
408 pub cp18: f64,
409 pub cp19: f64,
410 pub cp20: f64,
411 pub cp21: f64,
412 pub cp22: f64,
413 pub cp23: f64,
414 pub cp24: f64,
415 pub cp25: f64,
416 pub time: f64,
417}
418
419#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
420pub struct PlaytimeSMap {
421 pub rank: i64,
422 pub name: String,
423 pub seconds_played: i64,
424}
425
426#[derive(Default, Debug, Clone, PartialEq, Serialize, Deserialize)]
427pub struct Map {
428 pub info: InfoSMap,
429 pub rankings: Vec<RankingSMap>,
430 pub team_rankings: Vec<TeamRankingSMap>,
431 pub time_cps: Vec<TimeCpsSMap>,
432 pub playtime: Vec<PlaytimeSMap>,
433}
434
435impl Map {
436 #[must_use]
437 pub fn url(&self) -> String {
438 format!(
439 "https://{}/map/{}",
440 DDSTATS_BASE_URL,
441 encode(&self.info.map.map)
442 )
443 }
444
445 #[must_use]
446 pub fn url_with_name(map: &str) -> String {
447 format!("https://{}/map/{}", DDSTATS_BASE_URL, encode(map))
448 }
449
450 #[must_use]
451 pub fn api(map: &str) -> String {
452 format!("https://{}/map/json?map={}", DDSTATS_BASE_URL, encode(map))
453 }
454}