split up old encounters namespace more fine-grained namespaces

This commit is contained in:
Gered 2016-02-28 15:04:09 -05:00
parent 0af2cadd06
commit 0c421165fb
5 changed files with 263 additions and 257 deletions

View file

@ -1,250 +1,14 @@
(ns vwowrla.core.encounters
(ns vwowrla.core.encounters.analysis
(:import
(java.util Date))
(:require
[clojure.java.io :as io]
[clojure.tools.logging :refer [info error]]
[schema.core :as s]
[cheshire.core :as json])
[clojure.tools.logging :refer [info warn error]]
[schema.core :as s])
(:use
vwowrla.core.encounters.core
vwowrla.core.schemas
vwowrla.core.utils))
(def defined-encounters (get-edn-resource "encounters.edn"))
(def non-combat-starting-auras (get-text-resource-as-lines "non_combat_starting_auras.txt"))
(def non-combat-starting-skills (get-text-resource-as-lines "non_combat_starting_skills.txt"))
(def wipe-or-timeout-period (* 60 1000))
(declare calculate-encounter-stats)
(declare count-currently-dead)
(declare get-entity-last-activity)
(s/defn find-defined-encounter-name :- (s/maybe s/Str)
"returns the name of a defined encounter which includes the given entity in it's
list of trigger entities. returns nil if there is no encounter which includes the
given entity"
[entity-name :- (s/maybe s/Str)]
(->> defined-encounters
(filter (fn [[_ {:keys [entities]}]]
(->> entities
(filter #(= (first %) entity-name))
(first))))
(ffirst)))
(s/defn find-past-encounters :- [Encounter]
"return a list of all previously parsed encounters (successful and not) matching the encounter name"
[encounter-name :- s/Str
data :- RaidAnalysis]
(->> (:encounters data)
(filter #(= (:name %) encounter-name))))
(s/defn any-successful-encounters? :- s/Bool
"returns true if there are any successful parsed encounters matching the encounter name"
[encounter-name :- s/Str
data :- RaidAnalysis]
(->> (find-past-encounters encounter-name data)
(map :wipe-or-timeout?)
(filter false?)
(empty?)
(not)))
(s/defn update-active-encounter :- RaidAnalysis
"updates the active encounter using function f which will take the current active
encounter and any supplied args, returning a new active encounter which is
'updated' in the original full parsed data and then finally returned."
[data :- RaidAnalysis
f & args]
(apply update-in data [:active-encounter] f args))
(s/defn update-all-entities :- Encounter
"updates all entities in the encounter using function f which takes the current
entity and any supplied args, returning a new entity which is 'updated' in the
original encounter. returns the encounter with the modified entity data."
[encounter :- Encounter
f & args]
(reduce
(fn [encounter [entity-name entity]]
(assoc-in encounter [:entities entity-name] (apply f entity args)))
encounter
(:entities encounter)))
(s/defn update-all-active-encounter-entities :- RaidAnalysis
"updates all entities in the current active encounter in the full parsed data
using function f which takes the current entity and any supplied args, returning
a new entity which is 'updated' in the original encounter. returns the updated
full parsed data."
[data :- RaidAnalysis
f & args]
(update-active-encounter data #(update-all-entities % f args)))
(s/defn update-entity :- RaidAnalysis
"updates an entity in the full parsed data's active encounter using function f
which takes the current entity and any supplied args, returning the new entity
which is 'updated' in the active encounter. returns the updated full parsed data."
[data :- RaidAnalysis
entity-name :- s/Str
f & args]
(apply update-in data [:active-encounter :entities entity-name] f args))
(s/defn update-entity-field :- RaidAnalysis
"updates a specific field within an entity pointed to by ks in the full parsed
data's active encounter using function f which takes the current entity and any
supplied args, returning the new entity which is 'updated' in the active encounter.
returns the updated full parsed data."
[data :- RaidAnalysis
entity-name :- s/Str
ks f & args]
(apply update-in data (concat [:active-encounter :entities entity-name] ks) f args))
(defn- ignore-interaction?
[entity-name ignore-entity-list {:keys [target-name source-name] :as parsed-line}]
(and (or (= entity-name target-name)
(= entity-name source-name))
(or (contained-in? target-name ignore-entity-list)
(contained-in? source-name ignore-entity-list))))
(s/defn ignored-interaction-event? :- s/Bool
"returns true if the given parsed combat log line is between entities that have
been specified to ignore interactions between for the purposes of detecting
an encounter trigger"
[encounter :- Encounter
parsed-line :- CombatEvent]
(->> (:entities encounter)
(filter
(fn [[entity-name entity-props]]
(seq (:ignore-interactions-with entity-props))))
(filter
(fn [[entity-name entity-props]]
(ignore-interaction? entity-name (:ignore-interactions-with entity-props) parsed-line)))
(seq)
(boolean)))
(defn- ignore-skill?
[entity-name ignore-skill-list {:keys [source-name skill] :as parsed-line}]
(and (= entity-name source-name)
(contained-in? skill ignore-skill-list)))
(s/defn ignored-skill-event? :- s/Bool
"returns true if the given parsed combat log line is for an encounter entity
that is using a skill that has been specifically indicated should be ignored
for the purposes of triggering an encounter"
[encounter :- Encounter
parsed-line :- CombatEvent]
(->> (:entities encounter)
(filter
(fn [[entity-name entity-props]]
(seq (:ignore-skills entity-props))))
(filter
(fn [[entity-name entity-props]]
(ignore-skill? entity-name (:ignore-skills entity-props) parsed-line)))
(seq)
(boolean)))
;;;
;;; encounter start/stop
;;;
(s/defn detect-encounter-triggered :- (s/maybe s/Str)
"determines if the parsed combat log line is for an event involving any specific encounter entities which
should cause an encounter to begin, returning the name of the encounter if it should begin, or nil if no
encounter begin was detected"
[{:keys [target-name source-name damage aura-name type skill] :as parsed-line} :- CombatEvent
data :- RaidAnalysis]
(if-let [encounter-name (or (find-defined-encounter-name target-name)
(find-defined-encounter-name source-name))]
(if (and (not (any-successful-encounters? encounter-name data))
(not (contained-in? aura-name non-combat-starting-auras))
(not (contained-in? skill non-combat-starting-skills)))
(let [encounter (get defined-encounters encounter-name)]
(cond
(ignored-interaction-event? encounter parsed-line)
nil
(ignored-skill-event? encounter parsed-line)
nil
; if either of these are defined, then their criteria MUST pass to
; trigger an encounter
(or (:trigger-on-damage? encounter)
(:trigger-on-aura? encounter)
(:trigger-on-buff? encounter)
(:trigger-on-debuff? encounter))
(cond
(and (:trigger-on-damage? encounter) damage) encounter-name
(and (:trigger-on-aura? encounter) aura-name) encounter-name
(and (:trigger-on-buff? encounter) (= :buff type)) encounter-name
(and (:trigger-on-debuff? encounter) (= :debuff type)) encounter-name)
:else
encounter-name)))))
(s/defn begin-encounter :- RaidAnalysis
"sets up a new active encounter in the parsed data, returning the new parsed data set ready to use for
parsing a new encounter."
[encounter-name :- s/Str
{:keys [timestamp line]} :- CombatEvent
data :- RaidAnalysis]
(info "Beginning encounter" (str "\"" encounter-name "\"") "detected on line:" line)
(assoc data :active-encounter
{:name encounter-name
:started-at timestamp
:entities {}
:skills {}
:trigger-entities (get-in defined-encounters [encounter-name :entities])}))
(s/defn detect-encounter-end :- (s/maybe s/Keyword)
"determines if the currently active encounter should end based on the active encounter parsed data.
returns :killed if the encounter should end due to a successful kill, :wipe-or-timeout if the
encounter was found to be over due to a raid wipe or other non-activity timeout, or nil if the
active encounter is not over yet."
[{:keys [^Date timestamp]} :- CombatEvent
data :- RaidAnalysis]
(let [trigger-entites (get-in data [:active-encounter :trigger-entities])]
(cond
(every?
(fn [[entity-name {:keys [count must-kill-count]}]]
(let [count-dead (count-currently-dead data entity-name)]
(>= count-dead (or must-kill-count count))))
trigger-entites)
:killed
(every?
(fn [[entity-name _]]
; HACK: what is the right thing to do when the entity we want to check the activity of hasn't even been
; added to the encounter's entity list yet? most likely because the encounter has probably just begun
; and there have been no combat log lines yet for one or more of the trigger entities.
; should we have a minimum encounter length time? something like 15-30 seconds? that also feels hacky...
(>= (- (.getTime timestamp)
(.getTime (or (get-entity-last-activity entity-name data)
timestamp)))
wipe-or-timeout-period))
trigger-entites)
:wipe-or-timeout)))
(s/defn end-encounter :- RaidAnalysis
"ends the current active encounter in the parsed data, moving it from :active-encounter and inserted it into the
end of the :encounters list. finalizes the encounter by performing various final entity statistic calculations and
marks the encounter as successful or not. returns the new parsed data set without any active encounter set."
[{:keys [timestamp line]} :- CombatEvent
encounter-end-cause :- s/Keyword
data :- RaidAnalysis]
(let [wipe-or-timeout? (= encounter-end-cause :wipe-or-timeout)]
(info "Ending encounter" (str "\"" (get-in data [:active-encounter :name]) "\"") "detected on line:" line)
(if wipe-or-timeout?
(info "Encounter ending due to wipe or trigger entity activity timeout (unsuccessful encounter kill attempt)."))
(let [data (-> data
(update-active-encounter assoc :ended-at timestamp)
(update-active-encounter assoc :wipe-or-timeout? wipe-or-timeout?)
(calculate-encounter-stats))]
(-> data
(assoc-in [:active-encounter] nil)
(update-in [:encounters] #(conj %1 (:active-encounter data)))))))
;;;
;;; entity manipulation
;;;
(s/defn touch-entity :- RaidAnalysis
"updates an entity within the current active encounter by resetting it's :last-activity-at timestamp
or adds a new entity under the given name to the active encounter if it does not already exist. returns
@ -505,3 +269,36 @@
timestamp :- Date
data :- RaidAnalysis]
data)
(s/defn begin-encounter :- RaidAnalysis
"sets up a new active encounter in the parsed data, returning the new parsed data set ready to use for
parsing a new encounter."
[encounter-name :- s/Str
{:keys [timestamp line]} :- CombatEvent
data :- RaidAnalysis]
(info "Beginning encounter" (str "\"" encounter-name "\"") "detected on line:" line)
(assoc data :active-encounter
{:name encounter-name
:started-at timestamp
:entities {}
:skills {}
:trigger-entities (get-in defined-encounters [encounter-name :entities])}))
(s/defn end-encounter :- RaidAnalysis
"ends the current active encounter in the parsed data, moving it from :active-encounter and inserted it into the
end of the :encounters list. finalizes the encounter by performing various final entity statistic calculations and
marks the encounter as successful or not. returns the new parsed data set without any active encounter set."
[{:keys [timestamp line]} :- CombatEvent
encounter-end-cause :- s/Keyword
data :- RaidAnalysis]
(let [wipe-or-timeout? (= encounter-end-cause :wipe-or-timeout)]
(info "Ending encounter" (str "\"" (get-in data [:active-encounter :name]) "\"") "detected on line:" line)
(if wipe-or-timeout?
(info "Encounter ending due to wipe or trigger entity activity timeout (unsuccessful encounter kill attempt)."))
(let [data (-> data
(update-active-encounter assoc :ended-at timestamp)
(update-active-encounter assoc :wipe-or-timeout? wipe-or-timeout?)
(calculate-encounter-stats))]
(-> data
(assoc-in [:active-encounter] nil)
(update-in [:encounters] #(conj %1 (:active-encounter data)))))))

View file

@ -0,0 +1,89 @@
(ns vwowrla.core.encounters.core
(:require
[schema.core :as s])
(:use
vwowrla.core.schemas
vwowrla.core.utils))
(def wipe-or-timeout-period (* 60 1000))
(def defined-encounters (get-edn-resource "encounters.edn"))
(def non-combat-starting-auras (get-text-resource-as-lines "non_combat_starting_auras.txt"))
(def non-combat-starting-skills (get-text-resource-as-lines "non_combat_starting_skills.txt"))
(s/defn find-defined-encounter-name :- (s/maybe s/Str)
"returns the name of a defined encounter which includes the given entity in it's
list of trigger entities. returns nil if there is no encounter which includes the
given entity"
[entity-name :- (s/maybe s/Str)]
(->> defined-encounters
(filter (fn [[_ {:keys [entities]}]]
(->> entities
(filter #(= (first %) entity-name))
(first))))
(ffirst)))
(s/defn find-past-encounters :- [Encounter]
"return a list of all previously parsed encounters (successful and not) matching the encounter name"
[encounter-name :- s/Str
data :- RaidAnalysis]
(->> (:encounters data)
(filter #(= (:name %) encounter-name))))
(s/defn any-successful-encounters? :- s/Bool
"returns true if there are any successful parsed encounters matching the encounter name"
[encounter-name :- s/Str
data :- RaidAnalysis]
(->> (find-past-encounters encounter-name data)
(map :wipe-or-timeout?)
(filter false?)
(empty?)
(not)))
(s/defn update-active-encounter :- RaidAnalysis
"updates the active encounter using function f which will take the current active
encounter and any supplied args, returning a new active encounter which is
'updated' in the original full parsed data and then finally returned."
[data :- RaidAnalysis
f & args]
(apply update-in data [:active-encounter] f args))
(s/defn update-all-entities :- Encounter
"updates all entities in the encounter using function f which takes the current
entity and any supplied args, returning a new entity which is 'updated' in the
original encounter. returns the encounter with the modified entity data."
[encounter :- Encounter
f & args]
(reduce
(fn [encounter [entity-name entity]]
(assoc-in encounter [:entities entity-name] (apply f entity args)))
encounter
(:entities encounter)))
(s/defn update-all-active-encounter-entities :- RaidAnalysis
"updates all entities in the current active encounter in the full parsed data
using function f which takes the current entity and any supplied args, returning
a new entity which is 'updated' in the original encounter. returns the updated
full parsed data."
[data :- RaidAnalysis
f & args]
(update-active-encounter data #(update-all-entities % f args)))
(s/defn update-entity :- RaidAnalysis
"updates an entity in the full parsed data's active encounter using function f
which takes the current entity and any supplied args, returning the new entity
which is 'updated' in the active encounter. returns the updated full parsed data."
[data :- RaidAnalysis
entity-name :- s/Str
f & args]
(apply update-in data [:active-encounter :entities entity-name] f args))
(s/defn update-entity-field :- RaidAnalysis
"updates a specific field within an entity pointed to by ks in the full parsed
data's active encounter using function f which takes the current entity and any
supplied args, returning the new entity which is 'updated' in the active encounter.
returns the updated full parsed data."
[data :- RaidAnalysis
entity-name :- s/Str
ks f & args]
(apply update-in data (concat [:active-encounter :entities entity-name] ks) f args))

View file

@ -0,0 +1,118 @@
(ns vwowrla.core.encounters.detection
(:import
(java.util Date))
(:require
[clojure.tools.logging :refer [info warn error]]
[schema.core :as s]
[vwowrla.core.encounters.analysis :refer [count-currently-dead get-entity-last-activity]])
(:use
vwowrla.core.encounters.core
vwowrla.core.schemas
vwowrla.core.utils))
(defn- ignore-interaction?
[entity-name ignore-entity-list {:keys [target-name source-name] :as parsed-line}]
(and (or (= entity-name target-name)
(= entity-name source-name))
(or (contained-in? target-name ignore-entity-list)
(contained-in? source-name ignore-entity-list))))
(s/defn ignored-interaction-event? :- s/Bool
"returns true if the given parsed combat log line is between entities that have
been specified to ignore interactions between for the purposes of detecting
an encounter trigger"
[encounter :- Encounter
parsed-line :- CombatEvent]
(->> (:entities encounter)
(filter
(fn [[entity-name entity-props]]
(seq (:ignore-interactions-with entity-props))))
(filter
(fn [[entity-name entity-props]]
(ignore-interaction? entity-name (:ignore-interactions-with entity-props) parsed-line)))
(seq)
(boolean)))
(defn- ignore-skill?
[entity-name ignore-skill-list {:keys [source-name skill] :as parsed-line}]
(and (= entity-name source-name)
(contained-in? skill ignore-skill-list)))
(s/defn ignored-skill-event? :- s/Bool
"returns true if the given parsed combat log line is for an encounter entity
that is using a skill that has been specifically indicated should be ignored
for the purposes of triggering an encounter"
[encounter :- Encounter
parsed-line :- CombatEvent]
(->> (:entities encounter)
(filter
(fn [[entity-name entity-props]]
(seq (:ignore-skills entity-props))))
(filter
(fn [[entity-name entity-props]]
(ignore-skill? entity-name (:ignore-skills entity-props) parsed-line)))
(seq)
(boolean)))
(s/defn detect-encounter-triggered :- (s/maybe s/Str)
"determines if the parsed combat log line is for an event involving any specific encounter entities which
should cause an encounter to begin, returning the name of the encounter if it should begin, or nil if no
encounter begin was detected"
[{:keys [target-name source-name damage aura-name type skill] :as parsed-line} :- CombatEvent
data :- RaidAnalysis]
(if-let [encounter-name (or (find-defined-encounter-name target-name)
(find-defined-encounter-name source-name))]
(if (and (not (any-successful-encounters? encounter-name data))
(not (contained-in? aura-name non-combat-starting-auras))
(not (contained-in? skill non-combat-starting-skills)))
(let [encounter (get defined-encounters encounter-name)]
(cond
(ignored-interaction-event? encounter parsed-line)
nil
(ignored-skill-event? encounter parsed-line)
nil
; if either of these are defined, then their criteria MUST pass to
; trigger an encounter
(or (:trigger-on-damage? encounter)
(:trigger-on-aura? encounter)
(:trigger-on-buff? encounter)
(:trigger-on-debuff? encounter))
(cond
(and (:trigger-on-damage? encounter) damage) encounter-name
(and (:trigger-on-aura? encounter) aura-name) encounter-name
(and (:trigger-on-buff? encounter) (= :buff type)) encounter-name
(and (:trigger-on-debuff? encounter) (= :debuff type)) encounter-name)
:else
encounter-name)))))
(s/defn detect-encounter-end :- (s/maybe s/Keyword)
"determines if the currently active encounter should end based on the active encounter parsed data.
returns :killed if the encounter should end due to a successful kill, :wipe-or-timeout if the
encounter was found to be over due to a raid wipe or other non-activity timeout, or nil if the
active encounter is not over yet."
[{:keys [^Date timestamp]} :- CombatEvent
data :- RaidAnalysis]
(let [trigger-entites (get-in data [:active-encounter :trigger-entities])]
(cond
(every?
(fn [[entity-name {:keys [count must-kill-count]}]]
(let [count-dead (count-currently-dead data entity-name)]
(>= count-dead (or must-kill-count count))))
trigger-entites)
:killed
(every?
(fn [[entity-name _]]
; HACK: what is the right thing to do when the entity we want to check the activity of hasn't even been
; added to the encounter's entity list yet? most likely because the encounter has probably just begun
; and there have been no combat log lines yet for one or more of the trigger entities.
; should we have a minimum encounter length time? something like 15-30 seconds? that also feels hacky...
(>= (- (.getTime timestamp)
(.getTime (or (get-entity-last-activity entity-name data)
timestamp)))
wipe-or-timeout-period))
trigger-entites)
:wipe-or-timeout)))

View file

@ -1,6 +1,6 @@
(ns vwowrla.core.handlers
(:require
[vwowrla.core.encounters :as encounters]))
[vwowrla.core.encounters.analysis :as analysis]))
(defmulti handle-event
(fn [{:keys [event]} _]
@ -8,7 +8,7 @@
(defmethod handle-event :skill-damage-to-target
[{:keys [source-name skill target-name damage damage-type absorbed resisted blocked crit? timestamp] :as parsed} data]
(encounters/process-source-to-target-damage
(analysis/process-source-to-target-damage
source-name
target-name
{:skill skill
@ -24,7 +24,7 @@
(defmethod handle-event :skill-avoided-by-target
[{:keys [source-name target-name skill avoidance-method timestamp] :as parsed} data]
(encounters/process-source-to-target-damage
(analysis/process-source-to-target-damage
source-name
target-name
{:skill skill
@ -35,7 +35,7 @@
(defmethod handle-event :damage-reflected
[{:keys [source-name target-name damage damage-type timestamp] :as parsed} data]
(encounters/process-source-to-target-damage
(analysis/process-source-to-target-damage
source-name
target-name
{:skill "Reflect"
@ -48,7 +48,7 @@
(defmethod handle-event :melee-damage-to-target
[{:keys [source-name target-name damage damage-type hit-type absorbed resisted blocked crit? timestamp] :as parsed} data]
(encounters/process-source-to-target-damage
(analysis/process-source-to-target-damage
source-name
target-name
{:skill "Melee"
@ -65,7 +65,7 @@
(defmethod handle-event :melee-avoided-by-target
[{:keys [source-name target-name avoidance-method timestamp] :as parsed} data]
(encounters/process-source-to-target-damage
(analysis/process-source-to-target-damage
source-name
target-name
{:skill "Melee"
@ -80,7 +80,7 @@
(defmethod handle-event :dot-damages-target
[{:keys [source-name skill target-name damage damage-type absorbed resisted timestamp] :as parsed} data]
(encounters/process-source-to-target-damage
(analysis/process-source-to-target-damage
source-name
target-name
{:skill skill
@ -100,11 +100,11 @@
(defmethod handle-event :skill-performed-on-target
[{:keys [source-name target-name skill spell? extra timestamp] :as parsed} data]
(encounters/process-source-to-target-cast source-name target-name skill timestamp data))
(analysis/process-source-to-target-cast source-name target-name skill timestamp data))
(defmethod handle-event :cast
[{:keys [source-name skill spell? timestamp] :as parsed} data]
(encounters/process-entity-cast source-name skill timestamp data))
(analysis/process-entity-cast source-name skill timestamp data))
(defmethod handle-event :skill-heals-target
[{:keys [source-name skill crit? target-name amount timestamp] :as parsed} data]
@ -117,7 +117,7 @@
(defmethod handle-event :resource-lost
[{:keys [target-name amount resource-type source-name skill timestamp] :as parsed} data]
(condp = resource-type
:health (encounters/process-source-to-target-damage
:health (analysis/process-source-to-target-damage
source-name
target-name
{:skill skill
@ -147,7 +147,7 @@
(defmethod handle-event :death
[{:keys [source-name timestamp] :as parsed} data]
(encounters/process-entity-death source-name timestamp data))
(analysis/process-entity-death source-name timestamp data))
(defmethod handle-event :ignored

View file

@ -1,10 +1,12 @@
(ns vwowrla.core.parser
(:import (java.util TimeZone))
(:import
(java.util TimeZone))
(:require
[clojure.tools.logging :refer [info error warn]]
[clojure.java.io :as io]
[schema.core :as s]
[vwowrla.core.encounters :as encounters]
[vwowrla.core.encounters.detection :refer [detect-encounter-end detect-encounter-triggered]]
[vwowrla.core.encounters.analysis :refer [begin-encounter end-encounter]]
[vwowrla.core.handlers :refer [handle-event]]
[vwowrla.core.matchers :refer [regex-matchers]])
(:use
@ -61,16 +63,16 @@
[parsed-line :- CombatEvent
data :- RaidAnalysis]
(let [data (handle-line parsed-line data)]
(if-let [encounter-end (encounters/detect-encounter-end parsed-line data)]
(encounters/end-encounter parsed-line encounter-end data)
(if-let [encounter-end (detect-encounter-end parsed-line data)]
(end-encounter parsed-line encounter-end data)
data)))
(s/defn ^:private out-of-encounter-processing
[parsed-line :- CombatEvent
data :- RaidAnalysis]
(if-let [encounter-name (encounters/detect-encounter-triggered parsed-line data)]
(if-let [encounter-name (detect-encounter-triggered parsed-line data)]
(->> data
(encounters/begin-encounter encounter-name parsed-line)
(begin-encounter encounter-name parsed-line)
(handle-line parsed-line))
data))