diff options
-rw-r--r-- | CHANGELOG.md | 5 | ||||
-rw-r--r-- | Cargo.lock | 4 | ||||
-rw-r--r-- | Cargo.toml | 2 | ||||
-rw-r--r-- | raidgrep.1.asciidoc | 27 | ||||
-rw-r--r-- | src/fexpr/grammar.lalrpop | 17 | ||||
-rw-r--r-- | src/fexpr/mod.rs | 2 | ||||
-rw-r--r-- | src/filters/log.rs | 29 | ||||
-rw-r--r-- | src/main.rs | 20 | ||||
-rw-r--r-- | src/output/formats.rs | 59 | ||||
-rw-r--r-- | src/output/sorting.rs | 7 |
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. @@ -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", @@ -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), }, ]; |