From d4cd8c978c87d83945a0c2d57d17a0e9b047acd9 Mon Sep 17 00:00:00 2001
From: Daniel Schadt <kingdread@gmx.de>
Date: Mon, 22 Nov 2021 23:10:17 +0100
Subject: add Twisted Castle

There's not many useful things we can do with this log, other than
providing a way for downstream applications to identify those logs.
---
 CHANGELOG.md                 |   2 ++
 src/analyzers/mod.rs         |   1 +
 src/analyzers/raids/mod.rs   |   2 +-
 src/analyzers/raids/w3.rs    |  30 ++++++++++++++++++++++++++++++
 src/gamedata.rs              |  22 +++++++++++++++++-----
 tests/logs/tc-20211122.zevtc | Bin 0 -> 425248 bytes
 tests/parsing.rs             |  19 +++++++++++++++++++
 7 files changed, 70 insertions(+), 6 deletions(-)
 create mode 100644 tests/logs/tc-20211122.zevtc

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 45e8aca..b366b8b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,8 @@
 All notable changes to this project will be documented in this file.
 
 ## Unreleased
+### Added
+- `Encounter::TwistedCastle` to identify twisted castle logs.
 
 ## 0.6.0 - 2021-11-19
 ### Added
diff --git a/src/analyzers/mod.rs b/src/analyzers/mod.rs
index fcec7cf..1d8caf7 100644
--- a/src/analyzers/mod.rs
+++ b/src/analyzers/mod.rs
@@ -92,6 +92,7 @@ pub fn for_log<'l>(log: &'l Log) -> Option<Box<dyn Analyzer + 'l>> {
         }
 
         Encounter::KeepConstruct => Some(Box::new(raids::GenericRaid::new(log))),
+        Encounter::TwistedCastle => Some(Box::new(raids::TwistedCastle::new(log))),
         Encounter::Xera => Some(Box::new(raids::Xera::new(log))),
 
         Encounter::Cairn => Some(Box::new(raids::Cairn::new(log))),
diff --git a/src/analyzers/raids/mod.rs b/src/analyzers/raids/mod.rs
index 824b957..1e0f09e 100644
--- a/src/analyzers/raids/mod.rs
+++ b/src/analyzers/raids/mod.rs
@@ -10,7 +10,7 @@ use crate::{
 };
 
 mod w3;
-pub use w3::Xera;
+pub use w3::{TwistedCastle, Xera};
 
 mod w4;
 pub use w4::{Cairn, Deimos, MursaatOverseer, Samarog};
diff --git a/src/analyzers/raids/w3.rs b/src/analyzers/raids/w3.rs
index 16a3d13..e46457f 100644
--- a/src/analyzers/raids/w3.rs
+++ b/src/analyzers/raids/w3.rs
@@ -3,6 +3,36 @@ use crate::{
     Log,
 };
 
+/// Analyzer for the final fight of Wing 3, Xera.
+#[derive(Debug, Clone, Copy)]
+pub struct TwistedCastle<'log> {
+    log: &'log Log,
+}
+
+impl<'log> TwistedCastle<'log> {
+    /// Create a new [`TwistedCastle`] analyzer for the given log.
+    ///
+    /// **Do not** use this method unless you know what you are doing. Instead, rely on
+    /// [`Log::analyzer`]!
+    pub fn new(log: &'log Log) -> Self {
+        TwistedCastle { log }
+    }
+}
+
+impl<'log> Analyzer for TwistedCastle<'log> {
+    fn log(&self) -> &Log {
+        self.log
+    }
+
+    fn is_cm(&self) -> bool {
+        false
+    }
+
+    fn outcome(&self) -> Option<Outcome> {
+        Outcome::from_bool(self.log.was_rewarded())
+    }
+}
+
 /// Analyzer for the final fight of Wing 3, Xera.
 #[derive(Debug, Clone, Copy)]
 pub struct Xera<'log> {
diff --git a/src/gamedata.rs b/src/gamedata.rs
index bbe6e74..f19f871 100644
--- a/src/gamedata.rs
+++ b/src/gamedata.rs
@@ -86,6 +86,10 @@ pub enum Encounter {
 
     // Wing 3
     KeepConstruct = Boss::KeepConstruct as u16,
+    /// The "Traverse the Twisted Castle" encounter, between Keep Construct and Xera.
+    ///
+    /// [Guild Wars 2 Wiki](https://wiki.guildwars2.com/wiki/Traverse_the_Twisted_Castle)
+    TwistedCastle = 0x3F77,
     Xera = Boss::Xera as u16,
 
     // Wing 4
@@ -165,6 +169,7 @@ impl Encounter {
             Encounter::BanditTrio => &[Boss::Berg, Boss::Zane, Boss::Narella],
             Encounter::Matthias => &[Boss::Matthias],
             Encounter::KeepConstruct => &[Boss::KeepConstruct],
+            Encounter::TwistedCastle => &[],
             Encounter::Xera => &[Boss::Xera, Boss::Xera2],
             Encounter::Cairn => &[Boss::Cairn],
             Encounter::MursaatOverseer => &[Boss::MursaatOverseer],
@@ -212,11 +217,12 @@ impl Encounter {
     /// ```
     #[inline]
     pub fn from_header_id(id: u16) -> Option<Encounter> {
-        // For the encounter without boss, we do it manually.
-        if id == Encounter::RiverOfSouls as u16 {
-            return Some(Encounter::RiverOfSouls);
+        // For the encounters without boss, we do it manually.
+        match id {
+            _ if id == Encounter::TwistedCastle as u16 => Some(Encounter::TwistedCastle),
+            _ if id == Encounter::RiverOfSouls as u16 => Some(Encounter::RiverOfSouls),
+            _ => Boss::from_u16(id).map(Boss::encounter),
         }
-        Boss::from_u16(id).map(Boss::encounter)
     }
 
     /// Returns the game mode of the encounter.
@@ -229,7 +235,7 @@ impl Encounter {
             MAMA | Siax | Ensolyss | Skorvald | Artsariiv | Arkk | Ai => GameMode::Fractal,
 
             ValeGuardian | Gorseval | Sabetha | Slothasor | BanditTrio | Matthias
-            | KeepConstruct | Xera | Cairn | MursaatOverseer | Samarog | Deimos
+            | KeepConstruct | TwistedCastle | Xera | Cairn | MursaatOverseer | Samarog | Deimos
             | SoullessHorror | RiverOfSouls | BrokenKing | EaterOfSouls | StatueOfDarkness
             | VoiceInTheVoid | ConjuredAmalgamate | TwinLargos | Qadim | CardinalAdina
             | CardinalSabir | QadimThePeerless => GameMode::Raid,
@@ -262,6 +268,7 @@ impl FromStr for Encounter {
         let lower = s.to_lowercase();
         match &lower as &str {
             "trio" | "bandit trio" => Ok(Encounter::BanditTrio),
+            "tc" | "twisted castle" => Ok(Encounter::TwistedCastle),
             "river" | "river of souls" => Ok(Encounter::RiverOfSouls),
             "eyes" | "statue of darkness" => Ok(Encounter::StatueOfDarkness),
             "largos" | "twins" | "largos twins" | "twin largos" => Ok(Encounter::TwinLargos),
@@ -282,6 +289,7 @@ impl Display for Encounter {
             Encounter::BanditTrio => "Bandit Trio",
             Encounter::Matthias => "Matthias Gabrel",
             Encounter::KeepConstruct => "Keep Construct",
+            Encounter::TwistedCastle => "Twisted Castle",
             Encounter::Xera => "Xera",
             Encounter::Cairn => "Cairn the Indomitable",
             Encounter::MursaatOverseer => "Mursaat Overseer",
@@ -946,6 +954,10 @@ mod tests {
             ("KC", KeepConstruct),
             ("keep construct", KeepConstruct),
             ("Keep Construct", KeepConstruct),
+            ("tc", TwistedCastle),
+            ("TC", TwistedCastle),
+            ("twisted castle", TwistedCastle),
+            ("Twisted Castle", TwistedCastle),
             ("xera", Xera),
             ("Xera", Xera),
             ("cairn", Cairn),
diff --git a/tests/logs/tc-20211122.zevtc b/tests/logs/tc-20211122.zevtc
new file mode 100644
index 0000000..cafef09
Binary files /dev/null and b/tests/logs/tc-20211122.zevtc differ
diff --git a/tests/parsing.rs b/tests/parsing.rs
index 598bafd..1e9f0ef 100644
--- a/tests/parsing.rs
+++ b/tests/parsing.rs
@@ -178,6 +178,25 @@ test! {
     ],
 }
 
+test! {
+    name: parse_twisted_castle,
+    log: "logs/tc-20211122.zevtc",
+    boss: Encounter::TwistedCastle,
+    mode: Raid,
+    players: &[
+        (1, ":Kiki.9576", "Sir Kiki", Warrior, Some(Berserker)),
+        (1, ":Timothy.5829", "Arwen Elrondsdottir", Warrior, Some(Berserker)),
+        (1, ":Straimer.1093", "Deepfreeze Myself", Elementalist, Some(Weaver)),
+        (1, ":xyoz.6710", "Xaphwen", Mesmer, Some(Mirage)),
+        (1, ":Dracia.5287", "Dracia", Ranger, Some(Druid)),
+        (2, ":Taniniver BlindDragon.9503", "Dragon Kills You", Necromancer, Some(Scourge)),
+        (2, ":Rajnesh.4526", "I Rajnesh I", Revenant, Some(Renegade)),
+        (2, ":Dunje.4863", "Irodo", Elementalist, Some(Weaver)),
+        (2, ":neko.9741", "Mordrem Cat", Ranger, Some(Soulbeast)),
+        (2, ":Jupp.4570", "Aldwor", Guardian, Some(Firebrand)),
+    ],
+}
+
 test! {
     name: parse_xera,
     log: "logs/xera-20200415.zevtc",
-- 
cgit v1.2.3