aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--Cargo.lock4
-rw-r--r--Cargo.toml2
-rw-r--r--raidgrep.1.asciidoc27
-rw-r--r--src/fexpr/grammar.lalrpop17
-rw-r--r--src/fexpr/mod.rs2
-rw-r--r--src/filters/log.rs29
-rw-r--r--src/main.rs20
-rw-r--r--src/output/formats.rs59
-rw-r--r--src/output/sorting.rs7
10 files changed, 138 insertions, 34 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b13f1ca..a12805a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,6 +3,11 @@
All notable changes to this project will be documented in this file.
## Unreleased
+### Added
+- The `-gamemode` predicate as well as its `-raid`, `-fractal`, `-strike`,
+ `-golem` and `-wvw` shorthands.
+- The "trio", "river", "broken king", "eater" and "eyes" bosses.
+
### Changed
- Better error handling for situations in which the cache file could not be
found.
diff --git a/Cargo.lock b/Cargo.lock
index a837792..2e5fa97 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -349,9 +349,9 @@ dependencies = [
[[package]]
name = "evtclib"
-version = "0.5.0"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "538da1756f463fb78c5dab1cd641ac44f9de74bbf30b770c8d58b8b364dcf080"
+checksum = "72200b9f6de41c0f4134fffb49aa2f202f9b9c9094e0626b768faa3f7ae1a1a6"
dependencies = [
"byteorder",
"getset",
diff --git a/Cargo.toml b/Cargo.toml
index 43f7606..55d90f2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,7 +12,7 @@ repository = "https://gitlab.com/dunj3/raidgrep"
lto = true
[dependencies]
-evtclib = "0.5.0"
+evtclib = "0.6.0"
regex = "1.5.4"
structopt = "0.3.25"
walkdir = "2.3.2"
diff --git a/raidgrep.1.asciidoc b/raidgrep.1.asciidoc
index bad90f0..8068ce6 100644
--- a/raidgrep.1.asciidoc
+++ b/raidgrep.1.asciidoc
@@ -139,6 +139,28 @@ Those predicates can be used as-is in the filter:
+
Note that you have to quote the regex if it contains spaces or other special characters.
+*-gamemode* 'MODE'::
+ Include logs in the given game modes. Modes are 'raid', 'fractal', 'strike',
+ 'golem' and 'wvw'. Multiple game modes can be separated by commas.
+
+*-raid*::
+ Include logs that were in a raid (equivalent to *-gamemode raid*).
+
+*-fractal*::
+ Include logs that were in a fractal (equivalent to *-gamemode fractal*).
+
+*-strike*::
+ Include logs that were made in a strike mission (equivalent to *-gamemode
+ strike*).
+
+*-golem*::
+ Include logs that were made at the training golem (equivalent to *-gamemode
+ golem*).
+
+*-wvw*::
+ Include logs that were made in World-versus-World (equivalent to *-gamemode
+ wvw*).
+
*all(player:* 'PREDICATES' *)*::
Include logs in which all players match the given player predicates. See
below for a list of player predicates.
@@ -208,16 +230,17 @@ The following list is an inexhaustive list of all accepted boss names, with one
name per boss given:
* *Wing 1*: vg, gorseval, sabetha
-* *Wing 2*: slothasor, matthias
+* *Wing 2*: slothasor, trio, matthias
* *Wing 3*: kc, xera
* *Wing 4*: cairn, mo, samarog, deimos
-* *Wing 5*: desmina, dhuum
+* *Wing 5*: desmina, river, broken king, eater, eyes, dhuum
* *Wing 6*: ca, largos, qadim
* *Wing 7*: adina, sabir, qadimp
* *100 CM*: ai
* *99 CM*: skorvald, artsariiv, arkk
* *98 CM*: mama, siax, ensolyss
* *Strike Missions*: icebrood, fraenir, kodans, boneskinner, whisper
+* *Training Golems*: standard golem, medium golem, large golem
=== Regular Expressions
diff --git a/src/fexpr/grammar.lalrpop b/src/fexpr/grammar.lalrpop
index ce78396..bca7691 100644
--- a/src/fexpr/grammar.lalrpop
+++ b/src/fexpr/grammar.lalrpop
@@ -9,7 +9,7 @@ use super::{
DurationProducer,
CountProducer,
};
-use evtclib::Encounter;
+use evtclib::{Encounter, GameMode};
use std::collections::HashSet;
use lalrpop_util::ParseError;
@@ -64,6 +64,12 @@ LogPredicate: Box<dyn filters::log::LogFilter> = {
"-boss" <Comma<Encounter>> => filters::log::encounter(<>),
"-cm" => filters::log::challenge_mote(),
+ "-raid" => filters::log::game_mode([GameMode::Raid].into()),
+ "-fractal" => filters::log::game_mode([GameMode::Fractal].into()),
+ "-strike" => filters::log::game_mode([GameMode::Strike].into()),
+ "-wvw" => filters::log::game_mode([GameMode::WvW].into()),
+ "-gamemode" <Comma<GameMode>> => filters::log::game_mode(<>),
+
"-include" => filters::constant(true),
"-exclude" => filters::constant(false),
@@ -150,6 +156,15 @@ Encounter: Encounter = {
}),
}
+GameMode: GameMode = {
+ <l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User {
+ error: FError {
+ location: l,
+ kind: FErrorKind::GameMode,
+ }
+ }),
+}
+
PlayerClass: PlayerClass = {
<l:@L> <w:word> =>? w.parse().map_err(|_| ParseError::User {
error: FError {
diff --git a/src/fexpr/mod.rs b/src/fexpr/mod.rs
index 0fe2acf..6eeb753 100644
--- a/src/fexpr/mod.rs
+++ b/src/fexpr/mod.rs
@@ -51,6 +51,8 @@ pub enum FErrorKind {
Boss,
#[error("invalid class name")]
Class,
+ #[error("invalid game mode")]
+ GameMode,
}
/// Shortcut to create a new parser and parse the given input.
diff --git a/src/filters/log.rs b/src/filters/log.rs
index ac166e1..c878b78 100644
--- a/src/filters/log.rs
+++ b/src/filters/log.rs
@@ -9,7 +9,7 @@ use super::{
use std::collections::HashSet;
-use evtclib::Encounter;
+use evtclib::{Encounter, GameMode};
use chrono::{DateTime, Datelike, Utc, Weekday};
@@ -37,6 +37,33 @@ pub fn encounter(bosses: HashSet<Encounter>) -> Box<dyn LogFilter> {
}
#[derive(Debug, Clone)]
+struct GameModeFilter(HashSet<GameMode>);
+
+impl Filter<EarlyLogResult, LogResult> for GameModeFilter {
+ fn filter_early(&self, early_log: &EarlyLogResult) -> Inclusion {
+ let encounter_id = early_log.evtc.header.combat_id;
+ // Special WvW encounter
+ const GENERIC_ENCOUNTER_ID: u16 = 1;
+ let mode = if encounter_id == GENERIC_ENCOUNTER_ID {
+ Some(GameMode::WvW)
+ } else {
+ Encounter::from_header_id(encounter_id).map(Encounter::game_mode)
+ };
+ mode.map(|m| self.0.contains(&m).into())
+ .unwrap_or(Inclusion::Exclude)
+ }
+
+ fn filter(&self, log: &LogResult) -> bool {
+ log.game_mode.map(|m| self.0.contains(&m)).unwrap_or(false)
+ }
+}
+
+/// A [`LogFilter`] that only accepts logs with the given game mode.
+pub fn game_mode(game_modes: HashSet<GameMode>) -> Box<dyn LogFilter> {
+ Box::new(GameModeFilter(game_modes))
+}
+
+#[derive(Debug, Clone)]
struct OutcomeFilter(HashSet<FightOutcome>);
impl Filter<EarlyLogResult, LogResult> for OutcomeFilter {
diff --git a/src/main.rs b/src/main.rs
index b35f769..365a400 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -17,7 +17,7 @@ use structopt::StructOpt;
use walkdir::{DirEntry, WalkDir};
use evtclib::raw::parser::PartialEvtc;
-use evtclib::{Encounter, EventKind, Log, Outcome};
+use evtclib::{Encounter, EventKind, GameMode, Log, Outcome};
mod fexpr;
mod filters;
@@ -61,16 +61,23 @@ const RETCODE_ERROR: i32 = 2;
/// -after DATE Only include logs from after the given date.
/// -boss BOSSES Only include logs from the given bosses.
/// -cm Only include logs with challenge mote enabled.
+/// -gamemode MODE Only include logs from the given game modes.
+/// -raid Only include raid logs.
+/// -fractal Only include fractal logs.
+/// -strike Only include strike mission logs.
+/// -golem Only include golem logs.
+/// -wvw Only include WvW logs.
/// -player REGEX Shorthand to check if any player in the log has the given name.
/// -include Always evaluates to including the log.
/// -exclude Always evaluates to excluding the log.
///
/// BOSS NAMES:
/// The following names can be used with the -boss filter:
-/// vg, gorseval, sabetha, slothasor, matthias, kc, xera, cairn,
-/// mo, samarog, deimos, desmina, dhuum, ca, largos, qadim,
-/// adina, sabir, qadimp, ai, skorvald, artsariiv, arkk, mama, siax,
-/// ensolyss, icebrood, fraenir, kodans, boneskinner, whisper.
+/// vg, gorseval, sabetha, slothasor, trio, matthias, kc, xera, cairn,
+/// mo, samarog, deimos, desmina, river, broken king, eater, eyes,
+/// dhuum, ca, largos, qadim, adina, sabir, qadimp, ai, skorvald,
+/// artsariiv, arkk, mama, siax, ensolyss, icebrood, fraenir, kodans,
+/// boneskinner, whisper, standard golem, medium golem, large golem.
/// Names can also be comma separated.
#[derive(StructOpt, Debug)]
#[structopt(verbatim_doc_comment)]
@@ -180,6 +187,8 @@ pub struct LogResult {
outcome: FightOutcome,
/// Whether the fight had the Challenge Mote turned on.
is_cm: bool,
+ /// The game mode of the fight.
+ game_mode: Option<GameMode>,
}
/// A player.
@@ -578,6 +587,7 @@ fn extract_info(path: &Path, log: &Log) -> LogResult {
players,
outcome: get_fight_outcome(log),
is_cm: log.is_cm(),
+ game_mode: log.game_mode(),
}
}
diff --git a/src/output/formats.rs b/src/output/formats.rs
index d22ab19..47da57e 100644
--- a/src/output/formats.rs
+++ b/src/output/formats.rs
@@ -5,6 +5,7 @@ use super::super::guilds;
use super::{FightOutcome, LogResult};
use chrono::Local;
+use evtclib::GameMode;
/// An output format
pub trait Format: Sync + Send {
@@ -30,27 +31,43 @@ impl Format for HumanReadable {
item.log_file.to_string_lossy()
)
.expect("writing to String failed");
- let outcome = match item.outcome {
- FightOutcome::Success => "SUCCESS".green(),
- FightOutcome::Wipe => "WIPE".red(),
- };
- writeln!(
- result,
- "{}: {} - {}: {}{} {} after {}",
- "Date".green(),
- item.time
- .with_timezone(&Local)
- .format("%Y-%m-%d %H:%M:%S %a"),
- "Boss".green(),
- item.encounter
- .map(|x| x.to_string())
- .unwrap_or_else(|| "unknown".into()),
- if item.is_cm { " CM" } else { "" },
- outcome,
- humantime::Duration::from(item.duration.to_std().unwrap()),
- )
- .expect("writing to String failed");
- for player in &item.players {
+
+ if item.game_mode != Some(GameMode::WvW) {
+ let outcome = match item.outcome {
+ FightOutcome::Success => "SUCCESS".green(),
+ FightOutcome::Wipe => "WIPE".red(),
+ };
+ writeln!(
+ result,
+ "{}: {} - {}: {}{} {} after {}",
+ "Date".green(),
+ item.time
+ .with_timezone(&Local)
+ .format("%Y-%m-%d %H:%M:%S %a"),
+ "Boss".green(),
+ item.encounter
+ .map(|x| x.to_string())
+ .unwrap_or_else(|| "unknown".into()),
+ if item.is_cm { " CM" } else { "" },
+ outcome,
+ humantime::Duration::from(item.duration.to_std().unwrap()),
+ )
+ .expect("writing to String failed");
+ } else {
+ writeln!(
+ result,
+ "{}: {} - {} ended after {}",
+ "Date".green(),
+ item.time
+ .with_timezone(&Local)
+ .format("%Y-%m-%d %H:%M:%S %a"),
+ "World vs. World".green(),
+ humantime::Duration::from(item.duration.to_std().unwrap()),
+ )
+ .expect("writing to String failed");
+ }
+
+ for player in item.players.iter().filter(|p| !p.account_name.is_empty()) {
write!(
result,
" {:2} {:20} {:19} {:12}",
diff --git a/src/output/sorting.rs b/src/output/sorting.rs
index 234d804..27ea1e1 100644
--- a/src/output/sorting.rs
+++ b/src/output/sorting.rs
@@ -135,7 +135,7 @@ mod tests {
use super::*;
use chrono::prelude::*;
- use evtclib::Encounter as E;
+ use evtclib::{Encounter as E, GameMode};
#[test]
fn test_parse_component() {
@@ -179,6 +179,7 @@ mod tests {
players: vec![],
outcome: FightOutcome::Success,
is_cm: false,
+ game_mode: Some(GameMode::Raid),
},
&LogResult {
log_file: "".into(),
@@ -188,6 +189,7 @@ mod tests {
players: vec![],
outcome: FightOutcome::Success,
is_cm: false,
+ game_mode: Some(GameMode::Raid),
},
&LogResult {
log_file: "".into(),
@@ -197,6 +199,7 @@ mod tests {
players: vec![],
outcome: FightOutcome::Success,
is_cm: false,
+ game_mode: Some(GameMode::Raid),
},
&LogResult {
log_file: "".into(),
@@ -206,6 +209,7 @@ mod tests {
players: vec![],
outcome: FightOutcome::Success,
is_cm: false,
+ game_mode: Some(GameMode::Raid),
},
&LogResult {
log_file: "".into(),
@@ -215,6 +219,7 @@ mod tests {
players: vec![],
outcome: FightOutcome::Success,
is_cm: false,
+ game_mode: Some(GameMode::Raid),
},
];