1use crate::{KillEaterScoreType, Attributes};
2use strum_macros::{Display, EnumString, EnumIter, EnumCount};
3use num_enum::{TryFromPrimitive, IntoPrimitive};
4use serde_repr::{Serialize_repr, Deserialize_repr};
5
6#[derive(
9 Serialize_repr,
10 Deserialize_repr,
11 Debug,
12 Hash,
13 Eq,
14 PartialEq,
15 Ord,
16 PartialOrd,
17 Display,
18 EnumString,
19 EnumIter,
20 EnumCount,
21 TryFromPrimitive,
22 IntoPrimitive,
23 Clone,
24 Copy,
25)]
26#[repr(u32)]
27pub enum StrangePart {
28 #[strum(serialize = "Scouts Killed")]
29 ScoutsKilled = 10,
30 #[strum(serialize = "Snipers Killed")]
31 SnipersKilled = 11,
32 #[strum(serialize = "Demomen Killed")]
33 DemomenKilled = 13,
34 #[strum(serialize = "Heavies Killed")]
35 HeaviesKilled = 14,
36 #[strum(serialize = "Pyros Killed")]
37 PyrosKilled = 15,
38 #[strum(serialize = "Spies Killed")]
39 SpiesKilled = 16,
40 #[strum(serialize = "Engineers Killed")]
41 EngineersKilled = 17,
42 #[strum(serialize = "Medics Killed")]
43 MedicsKilled = 18,
44 #[strum(serialize = "Buildings Destroyed")]
45 BuildingsDestroyed = 19,
46 #[strum(serialize = "Projectiles Reflected")]
47 ProjectilesReflected = 20,
48 #[strum(serialize = "Headshot Kills")]
49 HeadshotKills = 21,
50 #[strum(serialize = "Airborne Enemy Kills")]
51 AirborneEnemyKills = 22,
52 #[strum(serialize = "Gib Kills")]
53 GibKills = 23,
54 #[strum(serialize = "Kills Under A Full Moon")]
55 KillsUnderAFullMoon = 27,
56 #[strum(serialize = "Dominations")]
57 Dominations = 28,
58 #[strum(serialize = "Revenges")]
59 Revenges = 30,
60 #[strum(serialize = "Posthumous Kills")]
61 PosthumousKills = 31,
62 #[strum(serialize = "Teammates Extinguished")]
63 TeammatesExtinguished = 32,
64 #[strum(serialize = "Critical Kills")]
65 CriticalKills = 33,
66 #[strum(serialize = "Kills While Explosive-Jumping")]
67 KillsWhileExplosiveJumping = 34,
68 #[strum(serialize = "Sappers Removed")]
69 SappersRemoved = 36,
70 #[strum(serialize = "Cloaked Spies Killed")]
71 CloakedSpiesKilled = 37,
72 #[strum(serialize = "Medics Killed That Have Full ÜberCharge")]
73 MedicsKilledThatHaveFullUberCharge = 38,
74 #[strum(serialize = "Robots Destroyed")]
75 RobotsDestroyed = 39,
76 #[strum(serialize = "Defender Kills")]
77 #[serde(alias = "Defenders Killed")]
78 DefenderKills = 47,
79 #[strum(serialize = "Submerged Enemy Kills")]
80 SubmergedEnemyKills = 48,
81 #[strum(serialize = "Kills While Invuln ÜberCharged")]
82 KillsWhileInvulnUberCharged = 49,
83 #[strum(serialize = "Tanks Destroyed")]
84 TanksDestroyed = 61,
85 #[strum(serialize = "Long-Distance Kills")]
86 LongDistanceKills = 62,
87 #[strum(serialize = "Kills during Victory Time")]
88 KillsDuringVictoryTime = 67,
89 #[strum(serialize = "Robot Spies Destroyed")]
90 RobotSpiesDestroyed = 74,
91 #[strum(serialize = "Taunt Kills")]
92 TauntKills = 77,
93 #[strum(serialize = "Unusual-Wearing Player Kills")]
94 UnusualWearingPlayerKills = 78,
95 #[strum(serialize = "Burning Player Kills")]
96 BurningPlayerKills = 79,
97 #[strum(serialize = "Killstreaks Ended")]
98 KillstreaksEnded = 80,
99 #[strum(serialize = "Freezecam Taunt Appearances")]
100 FreezecamTauntAppearances = 81,
101 #[strum(serialize = "Damage Dealt")]
102 DamageDealt = 82,
103 #[strum(serialize = "Fires Survived")]
104 FiresSurvived = 83,
105 #[strum(serialize = "Allied Healing Done")]
106 AlliedHealingDone = 84,
107 #[strum(serialize = "Point Blank Kills")]
108 PointBlankKills = 85,
109 #[strum(serialize = "Robots Killed During Halloween")]
110 RobotsKilledDuringHalloween = 46,
111 #[strum(serialize = "Kills During Halloween")]
112 KillsDuringHalloween = 45,
113 #[strum(serialize = "Kills While Low Health")]
114 KillsWhileLowHealth = 44,
115 #[strum(serialize = "Giant Robots Destroyed")]
116 GiantRobotsDestroyed = 40,
117 #[strum(serialize = "Kills")]
118 Kills = 87,
119 #[strum(serialize = "Full Health Kills")]
120 FullHealthKills = 88,
121 #[strum(serialize = "Soldiers Killed")]
122 SoldiersKilled = 12,
123 #[strum(serialize = "Robot Scouts Destroyed")]
124 RobotScoutsDestroyed = 68,
125 #[strum(serialize = "Taunting Player Kills")]
126 TauntingPlayerKills = 89,
127 #[strum(serialize = "Assists")]
128 Assists = 95,
129 #[strum(serialize = "Not Crit nor MiniCrit Kills")]
130 NotCritNorMiniCritKills = 93,
131 #[strum(serialize = "Player Hits")]
132 #[serde(alias = "Players Hit")]
133 PlayerHits = 94,
134}
135
136impl StrangePart {
137 pub fn score_type(&self) -> u32 {
139 u32::from(*self)
140 }
141
142 pub fn from_score_type(score_type: u32) -> Option<Self> {
144 Self::try_from(score_type).ok()
145 }
146
147 pub fn strange_part_name(&self) -> &'static str {
149 match self {
150 Self::ScoutsKilled => "Strange Part: Scouts Killed",
151 Self::SnipersKilled => "Strange Part: Snipers Killed",
152 Self::DemomenKilled => "Strange Part: Demomen Killed",
153 Self::HeaviesKilled => "Strange Part: Heavies Killed",
154 Self::PyrosKilled => "Strange Part: Pyros Killed",
155 Self::SpiesKilled => "Strange Part: Spies Killed",
156 Self::EngineersKilled => "Strange Part: Engineers Killed",
157 Self::MedicsKilled => "Strange Part: Medics Killed",
158 Self::BuildingsDestroyed => "Strange Part: Buildings Destroyed",
159 Self::ProjectilesReflected => "Strange Part: Projectiles Reflected",
160 Self::HeadshotKills => "Strange Part: Headshot Kills",
161 Self::AirborneEnemyKills => "Strange Part: Airborne Enemies Killed",
162 Self::GibKills => "Strange Part: Gib Kills",
163 Self::KillsUnderAFullMoon => "Strange Part: Full Moon Kills",
164 Self::Dominations => "Strange Part: Domination Kills",
165 Self::Revenges => "Strange Part: Revenge Kills",
166 Self::PosthumousKills => "Strange Part: Posthumous Kills",
167 Self::TeammatesExtinguished => "Strange Part: Teammates Extinguished",
168 Self::CriticalKills => "Strange Part: Critical Kills",
169 Self::KillsWhileExplosiveJumping => "Strange Part: Kills While Explosive Jumping",
170 Self::SappersRemoved => "Strange Part: Sappers Destroyed",
171 Self::CloakedSpiesKilled => "Strange Part: Cloaked Spies Killed",
172 Self::MedicsKilledThatHaveFullUberCharge => "Strange Part: Medics Killed That Have Full ÜberCharge",
173 Self::RobotsDestroyed => "Strange Part: Robots Destroyed",
174 Self::DefenderKills => "Strange Part: Defender Kills",
175 Self::SubmergedEnemyKills => "Strange Part: Underwater Kills",
176 Self::KillsWhileInvulnUberCharged => "Strange Part: Kills While Übercharged",
177 Self::TanksDestroyed => "Strange Part: Tanks Destroyed",
178 Self::LongDistanceKills => "Strange Part: Long-Distance Kills",
179 Self::KillsDuringVictoryTime => "Strange Part: Kills During Victory Time",
180 Self::RobotSpiesDestroyed => "Strange Part: Robot Spies Destroyed",
181 Self::TauntKills => "Strange Part: Kills with a Taunt Attack",
182 Self::UnusualWearingPlayerKills => "Strange Part: Unusual-Wearing Player Kills",
183 Self::BurningPlayerKills => "Strange Part: Burning Enemy Kills",
184 Self::KillstreaksEnded => "Strange Part: Killstreaks Ended",
185 Self::FreezecamTauntAppearances => "Strange Cosmetic Part: Freezecam Taunt Appearances",
186 Self::DamageDealt => "Strange Part: Damage Dealt",
187 Self::FiresSurvived => "Strange Cosmetic Part: Fires Survived",
188 Self::AlliedHealingDone => "Strange Part: Allied Healing Done",
189 Self::PointBlankKills => "Strange Part: Point-Blank Kills",
190 Self::RobotsKilledDuringHalloween => "Strange Part: Robots Destroyed During Halloween",
191 Self::KillsDuringHalloween => "Strange Part: Halloween Kills",
192 Self::KillsWhileLowHealth => "Strange Part: Low-Health Kills",
193 Self::GiantRobotsDestroyed => "Strange Part: Giant Robots Destroyed",
194 Self::Kills => "Strange Cosmetic Part: Kills",
195 Self::FullHealthKills => "Strange Part: Full Health Kills",
196 Self::SoldiersKilled => "Strange Part: Soldiers Killed",
197 Self::RobotScoutsDestroyed => "Strange Part: Robot Scouts Destroyed",
198 Self::TauntingPlayerKills => "Strange Part: Taunting Player Kills",
199 Self::Assists => "Strange Cosmetic Part: Assists",
200 Self::NotCritNorMiniCritKills => "Strange Part: Not Crit nor MiniCrit Kills",
201 Self::PlayerHits => "Strange Part: Player Hits",
202 }
203 }
204
205 pub fn from_strange_part_name(name: &str) -> Option<StrangePart> {
207 match name {
208 "Strange Part: Scouts Killed" => Some(Self::ScoutsKilled),
209 "Strange Part: Snipers Killed" => Some(Self::SnipersKilled),
210 "Strange Part: Demomen Killed" => Some(Self::DemomenKilled),
211 "Strange Part: Heavies Killed" => Some(Self::HeaviesKilled),
212 "Strange Part: Pyros Killed" => Some(Self::PyrosKilled),
213 "Strange Part: Spies Killed" => Some(Self::SpiesKilled),
214 "Strange Part: Engineers Killed" => Some(Self::EngineersKilled),
215 "Strange Part: Medics Killed" => Some(Self::MedicsKilled),
216 "Strange Part: Buildings Destroyed" => Some(Self::BuildingsDestroyed),
217 "Strange Part: Projectiles Reflected" => Some(Self::ProjectilesReflected),
218 "Strange Part: Headshot Kills" => Some(Self::HeadshotKills),
219 "Strange Part: Airborne Enemies Killed" => Some(Self::AirborneEnemyKills),
220 "Strange Part: Gib Kills" => Some(Self::GibKills),
221 "Strange Part: Full Moon Kills" => Some(Self::KillsUnderAFullMoon),
222 "Strange Part: Domination Kills" => Some(Self::Dominations),
223 "Strange Part: Revenge Kills" => Some(Self::Revenges),
224 "Strange Part: Posthumous Kills" => Some(Self::PosthumousKills),
225 "Strange Part: Teammates Extinguished" => Some(Self::TeammatesExtinguished),
226 "Strange Part: Critical Kills" => Some(Self::CriticalKills),
227 "Strange Part: Kills While Explosive Jumping" => Some(Self::KillsWhileExplosiveJumping),
228 "Strange Part: Sappers Destroyed" => Some(Self::SappersRemoved),
229 "Strange Part: Cloaked Spies Killed" => Some(Self::CloakedSpiesKilled),
230 "Strange Part: Medics Killed That Have Full ÜberCharge" => Some(Self::MedicsKilledThatHaveFullUberCharge),
231 "Strange Part: Robots Destroyed" => Some(Self::RobotsDestroyed),
232 "Strange Part: Defender Kills" => Some(Self::DefenderKills),
233 "Strange Part: Underwater Kills" => Some(Self::SubmergedEnemyKills),
234 "Strange Part: Kills While Übercharged" => Some(Self::KillsWhileInvulnUberCharged),
235 "Strange Part: Tanks Destroyed" => Some(Self::TanksDestroyed),
236 "Strange Part: Long-Distance Kills" => Some(Self::LongDistanceKills),
237 "Strange Part: Kills During Victory Time" => Some(Self::KillsDuringVictoryTime),
238 "Strange Part: Robot Spies Destroyed" => Some(Self::RobotSpiesDestroyed),
239 "Strange Part: Kills with a Taunt Attack" => Some(Self::TauntKills),
240 "Strange Part: Unusual-Wearing Player Kills" => Some(Self::UnusualWearingPlayerKills),
241 "Strange Part: Burning Enemy Kills" => Some(Self::BurningPlayerKills),
242 "Strange Part: Killstreaks Ended" => Some(Self::KillstreaksEnded),
243 "Strange Cosmetic Part: Freezecam Taunt Appearances" => Some(Self::FreezecamTauntAppearances),
244 "Strange Part: Damage Dealt" => Some(Self::DamageDealt),
245 "Strange Cosmetic Part: Fires Survived" => Some(Self::FiresSurvived),
246 "Strange Part: Allied Healing Done" => Some(Self::AlliedHealingDone),
247 "Strange Part: Point-Blank Kills" => Some(Self::PointBlankKills),
248 "Strange Part: Robots Destroyed During Halloween" => Some(Self::RobotsKilledDuringHalloween),
249 "Strange Part: Halloween Kills" => Some(Self::KillsDuringHalloween),
250 "Strange Part: Low-Health Kills" => Some(Self::KillsWhileLowHealth),
251 "Strange Part: Giant Robots Destroyed" => Some(Self::GiantRobotsDestroyed),
252 "Strange Cosmetic Part: Kills" => Some(Self::Kills),
253 "Strange Part: Full Health Kills" => Some(Self::FullHealthKills),
254 "Strange Part: Soldiers Killed" => Some(Self::SoldiersKilled),
255 "Strange Part: Robot Scouts Destroyed" => Some(Self::RobotScoutsDestroyed),
256 "Strange Part: Taunting Player Kills" => Some(Self::TauntingPlayerKills),
257 "Strange Cosmetic Part: Assists" => Some(Self::Assists),
258 "Strange Part: Not Crit nor MiniCrit Kills" => Some(Self::NotCritNorMiniCritKills),
259 "Strange Part: Player Hits" => Some(Self::PlayerHits),
260 _ => None,
261 }
262 }
263
264 pub fn is_cosmetic_part(&self) -> bool {
266 matches!(self, Self::Kills | Self::FullHealthKills | Self::Assists | Self::FreezecamTauntAppearances)
267 }
268
269 pub fn defindex(&self) -> u32 {
271 match self {
272 Self::ScoutsKilled => 6003,
273 Self::SnipersKilled => 6005,
274 Self::DemomenKilled => 6001,
275 Self::HeaviesKilled => 6000,
276 Self::PyrosKilled => 6006,
277 Self::SpiesKilled => 6008,
278 Self::EngineersKilled => 6004,
279 Self::MedicsKilled => 6007,
280 Self::BuildingsDestroyed => 6009,
281 Self::ProjectilesReflected => 6010,
282 Self::HeadshotKills => 6011,
283 Self::AirborneEnemyKills => 6012,
284 Self::GibKills => 6013,
285 Self::KillsUnderAFullMoon => 6015,
286 Self::Dominations => 6016,
287 Self::Revenges => 6018,
288 Self::PosthumousKills => 6019,
289 Self::TeammatesExtinguished => 6020,
290 Self::CriticalKills => 6021,
291 Self::KillsWhileExplosiveJumping => 6022,
292 Self::SappersRemoved => 6025,
293 Self::CloakedSpiesKilled => 6024,
294 Self::MedicsKilledThatHaveFullUberCharge => 6023,
295 Self::RobotsDestroyed => 6026,
296 Self::DefenderKills => 6035,
297 Self::SubmergedEnemyKills => 6036,
298 Self::KillsWhileInvulnUberCharged => 6037,
299 Self::TanksDestroyed => 6038,
300 Self::LongDistanceKills => 6039,
301 Self::KillsDuringVictoryTime => 6041,
302 Self::RobotSpiesDestroyed => 6048,
303 Self::TauntKills => 6051,
304 Self::UnusualWearingPlayerKills => 6052,
305 Self::BurningPlayerKills => 6053,
306 Self::KillstreaksEnded => 6054,
307 Self::FreezecamTauntAppearances => 6055,
308 Self::DamageDealt => 6056,
309 Self::FiresSurvived => 6057,
310 Self::AlliedHealingDone => 6058,
311 Self::PointBlankKills => 6059,
312 Self::RobotsKilledDuringHalloween => 6034,
313 Self::KillsDuringHalloween => 6033,
314 Self::KillsWhileLowHealth => 6032,
315 Self::GiantRobotsDestroyed => 6028,
316 Self::Kills => 6060,
317 Self::FullHealthKills => 6061,
318 Self::SoldiersKilled => 6002,
319 Self::RobotScoutsDestroyed => 6042,
320 Self::TauntingPlayerKills => 6062,
321 Self::Assists => 6065,
322 Self::NotCritNorMiniCritKills => 6063,
323 Self::PlayerHits => 6064,
324 }
325 }
326
327 pub fn from_defindex(defindex: u32) -> Option<Self> {
329 match defindex {
330 6003 => Some(Self::ScoutsKilled),
331 6005 => Some(Self::SnipersKilled),
332 6001 => Some(Self::DemomenKilled),
333 6000 => Some(Self::HeaviesKilled),
334 6006 => Some(Self::PyrosKilled),
335 6008 => Some(Self::SpiesKilled),
336 6004 => Some(Self::EngineersKilled),
337 6007 => Some(Self::MedicsKilled),
338 6009 => Some(Self::BuildingsDestroyed),
339 6010 => Some(Self::ProjectilesReflected),
340 6011 => Some(Self::HeadshotKills),
341 6012 => Some(Self::AirborneEnemyKills),
342 6013 => Some(Self::GibKills),
343 6015 => Some(Self::KillsUnderAFullMoon),
344 6016 => Some(Self::Dominations),
345 6018 => Some(Self::Revenges),
346 6019 => Some(Self::PosthumousKills),
347 6020 => Some(Self::TeammatesExtinguished),
348 6021 => Some(Self::CriticalKills),
349 6022 => Some(Self::KillsWhileExplosiveJumping),
350 6025 => Some(Self::SappersRemoved),
351 6024 => Some(Self::CloakedSpiesKilled),
352 6023 => Some(Self::MedicsKilledThatHaveFullUberCharge),
353 6026 => Some(Self::RobotsDestroyed),
354 6035 => Some(Self::DefenderKills),
355 6036 => Some(Self::SubmergedEnemyKills),
356 6037 => Some(Self::KillsWhileInvulnUberCharged),
357 6038 => Some(Self::TanksDestroyed),
358 6039 => Some(Self::LongDistanceKills),
359 6041 => Some(Self::KillsDuringVictoryTime),
360 6048 => Some(Self::RobotSpiesDestroyed),
361 6051 => Some(Self::TauntKills),
362 6052 => Some(Self::UnusualWearingPlayerKills),
363 6053 => Some(Self::BurningPlayerKills),
364 6054 => Some(Self::KillstreaksEnded),
365 6055 => Some(Self::FreezecamTauntAppearances),
366 6056 => Some(Self::DamageDealt),
367 6057 => Some(Self::FiresSurvived),
368 6058 => Some(Self::AlliedHealingDone),
369 6059 => Some(Self::PointBlankKills),
370 6034 => Some(Self::RobotsKilledDuringHalloween),
371 6033 => Some(Self::KillsDuringHalloween),
372 6032 => Some(Self::KillsWhileLowHealth),
373 6028 => Some(Self::GiantRobotsDestroyed),
374 6060 => Some(Self::Kills),
375 6061 => Some(Self::FullHealthKills),
376 6002 => Some(Self::SoldiersKilled),
377 6042 => Some(Self::RobotScoutsDestroyed),
378 6062 => Some(Self::TauntingPlayerKills),
379 6065 => Some(Self::Assists),
380 6063 => Some(Self::NotCritNorMiniCritKills),
381 6064 => Some(Self::PlayerHits),
382 _ => None,
383 }
384 }
385
386 pub fn defindex_is_strange_part(defindex: u32) -> bool {
388 Self::from_defindex(defindex).is_some()
389 }
390
391 pub fn kill_eater_score_type(&self) -> Option<KillEaterScoreType> {
393 KillEaterScoreType::try_from(*self).ok()
394 }
395}
396
397impl Attributes for StrangePart {
401 const DEFINDEX: &[u32] = &[
402 380,
403 382,
404 384,
405 ];
406 const NAME: &[&str] = &[
407 "kill eater user score type 1",
408 "kill eater user score type 2",
409 "kill eater user score type 3",
410 ];
411 const ATTRIBUTE_CLASS: &[&str] = &[
412 "kill_eater_user_score_type_1",
413 "kill_eater_user_score_type_2",
414 "kill_eater_user_score_type_3",
415 ];
416 const DESCRIPTION_STRING: &[Option<&str>] = &[
417 None,
418 None,
419 None,
420 ];
421 const DESCRIPTION_FORMAT: &[Option<&str>] = &[
422 None,
423 None,
424 None,
425 ];
426 const EFFECT_TYPE: &[&str] = &[
427 "positive",
428 "positive",
429 "positive",
430 ];
431 const HIDDEN: &[bool] = &[
432 true,
433 true,
434 true,
435 ];
436 const STORED_AS_INTEGER: &[bool] = &[
437 false,
438 false,
439 false,
440 ];
441}
442
443impl TryFrom<KillEaterScoreType> for StrangePart {
444 type Error = num_enum::TryFromPrimitiveError<StrangePart>;
445
446 fn try_from(part: KillEaterScoreType) -> Result<Self, Self::Error> {
447 StrangePart::try_from(part as u32)
448 }
449}
450
451impl TryFrom<&KillEaterScoreType> for StrangePart {
452 type Error = num_enum::TryFromPrimitiveError<StrangePart>;
453
454 fn try_from(part: &KillEaterScoreType) -> Result<Self, Self::Error> {
455 StrangePart::try_from(*part as u32)
456 }
457}
458
459#[cfg(test)]
460mod tests {
461 use super::*;
462 use std::str::FromStr;
463
464 #[test]
465 fn from_str_converts() {
466 assert_eq!(
467 StrangePart::MedicsKilledThatHaveFullUberCharge,
468 StrangePart::from_str("Medics Killed That Have Full ÜberCharge").unwrap()
469 );
470 }
471
472 #[test]
473 fn serializes_player_hits_correctly() {
474 let player_hits = StrangePart::PlayerHits;
475 let serialized = player_hits.to_string();
476
477 assert_eq!(serialized, "Player Hits");
478 assert_eq!(StrangePart::from_str("Player Hits").unwrap(), StrangePart::PlayerHits);
479 }
480
481 #[test]
482 fn serializes_defender_kills_correctly() {
483 let defender_kills = StrangePart::DefenderKills;
484 let serialized = defender_kills.to_string();
485
486 assert_eq!(serialized, "Defender Kills");
487 assert_eq!(StrangePart::from_str("Defender Kills").unwrap(), StrangePart::DefenderKills);
488 }
489}