diff --git a/vwowrla.core/src/vwowrla/core/encounters.clj b/vwowrla.core/src/vwowrla/core/encounters.clj index 01e62de..a10b1cd 100644 --- a/vwowrla.core/src/vwowrla/core/encounters.clj +++ b/vwowrla.core/src/vwowrla/core/encounters.clj @@ -4,8 +4,10 @@ (:require [clojure.java.io :as io] [clojure.tools.logging :refer [info error]] + [schema.core :as s] [cheshire.core :as json]) (:use + vwowrla.core.schemas vwowrla.core.utils)) (def defined-encounters (get-edn-resource "encounters.edn")) @@ -18,11 +20,11 @@ (declare count-currently-dead) (declare get-entity-last-activity) -(defn find-defined-encounter-name +(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] + [entity-name :- (s/maybe s/Str)] (->> defined-encounters (filter (fn [[_ {:keys [entities]}]] (->> entities @@ -30,60 +32,69 @@ (first)))) (ffirst))) -(defn find-past-encounters +(s/defn find-past-encounters :- [Encounter] "return a list of all previously parsed encounters (successful and not) matching the encounter name" - [encounter-name data] + [encounter-name :- s/Str + data :- RaidAnalysis] (->> (:encounters data) (filter #(= (:name %) encounter-name)))) -(defn any-successful-encounters? +(s/defn any-successful-encounters? :- s/Bool "returns true if there are any successful parsed encounters matching the encounter name" - [encounter-name data] + [encounter-name :- s/Str + data :- RaidAnalysis] (->> (find-past-encounters encounter-name data) (map :wipe-or-timeout?) (filter false?) (empty?) (not))) -(defn update-active-encounter +(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 f & args] + [data :- RaidAnalysis + f & args] (apply update-in data [:active-encounter] f args)) -(defn update-all-entities +(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 f & args] + [encounter :- Encounter + f & args] (reduce (fn [encounter [entity-name entity]] (assoc-in encounter [:entities entity-name] (apply f entity args))) encounter (:entities encounter))) -(defn update-all-active-encounter-entities +(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 f & args] + [data :- RaidAnalysis + f & args] (update-active-encounter data #(update-all-entities % f args))) -(defn update-entity +(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 entity-name f & args] + [data :- RaidAnalysis + entity-name :- s/Str + f & args] (apply update-in data [:active-encounter :entities entity-name] f args)) -(defn update-entity-field +(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 entity-name ks f & args] + [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? @@ -93,11 +104,12 @@ (or (contained-in? target-name ignore-entity-list) (contained-in? source-name ignore-entity-list)))) -(defn ignored-interaction-event? +(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 parsed-line] + [encounter :- Encounter + parsed-line :- CombatEvent] (->> (:entities encounter) (filter (fn [[entity-name entity-props]] @@ -105,18 +117,20 @@ (filter (fn [[entity-name entity-props]] (ignore-interaction? entity-name (:ignore-interactions-with entity-props) parsed-line))) - (seq))) + (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))) -(defn ignored-skill-event? +(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 parsed-line] + [encounter :- Encounter + parsed-line :- CombatEvent] (->> (:entities encounter) (filter (fn [[entity-name entity-props]] @@ -124,17 +138,19 @@ (filter (fn [[entity-name entity-props]] (ignore-skill? entity-name (:ignore-skills entity-props) parsed-line))) - (seq))) + (seq) + (boolean))) ;;; ;;; encounter start/stop ;;; -(defn detect-encounter-triggered +(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} data] + [{: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)) @@ -163,10 +179,12 @@ :else encounter-name))))) -(defn begin-encounter +(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 {:keys [timestamp line] :as parsed-line} data] + [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 @@ -175,12 +193,13 @@ :skills {} :trigger-entities (get-in defined-encounters [encounter-name :entities])})) -(defn detect-encounter-end +(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] :as parsed-line} data] + [{:keys [^Date timestamp]} :- CombatEvent + data :- RaidAnalysis] (let [trigger-entites (get-in data [:active-encounter :trigger-entities])] (cond (every? @@ -203,11 +222,13 @@ trigger-entites) :wipe-or-timeout))) -(defn end-encounter +(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] :as parsed-line} encounter-end-cause data] + [{: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? @@ -224,11 +245,13 @@ ;;; entity manipulation ;;; -(defn touch-entity +(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 the new parsed data set with the updated entity information." - [data entity-name timestamp] + [data :- RaidAnalysis + entity-name :- s/Str + timestamp :- Date] (if-not (get-in data [:active-encounter :entities entity-name]) (assoc-in data [:active-encounter :entities entity-name] {:name entity-name @@ -252,18 +275,15 @@ :alive-duration 0}) (assoc-in data [:active-encounter :entities entity-name :last-activity-at] timestamp))) -(defn get-entity-last-activity - [entity-name data] +(s/defn get-entity-last-activity :- (s/maybe Date) + [entity-name :- s/Str + data :- RaidAnalysis] (get-in data [:active-encounter :entities entity-name :last-activity-at])) -(defn get-entity-alive-time +(s/defn get-entity-alive-time :- Long "returns the number of milliseconds of the encounter that the entity was alive for" - [{:keys [deaths resurrections] :as entity} {:keys [started-at ended-at] :as encounter}] - ;(println (:name entity)) - ;(println "started-at" started-at) - ;(println "ended-at " ended-at) - ;(println "deaths " deaths) - ;(println "resurrects" resurrections) + [{:keys [deaths resurrections]} :- Entity + {:keys [started-at ended-at] :as encounter} :- Encounter] (if (and (= 0 (count deaths)) (= 0 (count resurrections))) (:duration encounter) @@ -273,10 +293,8 @@ [{:status :end :at ended-at}]) x (remove empty? x) (sort-by :at x))] - ;(println ">" segments) (reduce (fn [{:keys [total current-status from] :as result} {:keys [status at]}] - ;(println ">>" current-status from status at) (cond ; is the first state change we find a resurrect? (e.g. they were dead when the fight began) (and (nil? current-status) @@ -320,13 +338,14 @@ :from started-at} segments)))) -(defn finalize-entity-auras - [entity timestamp] +(s/defn finalize-entity-auras :- Entity + [entity :- Entity + timestamp :- Date] ; TODO entity) -(defn finalize-entities - [data] +(s/defn finalize-entities :- RaidAnalysis + [data :- RaidAnalysis] (update-active-encounter data (fn [encounter] @@ -345,31 +364,32 @@ 1000))))) (update-all-entities finalize-entity-auras (:ended-at encounter)))))) -(defn calculate-encounter-stats - [data] +(s/defn calculate-encounter-stats :- RaidAnalysis + [data :- RaidAnalysis] (-> data (update-active-encounter (fn [{:keys [started-at ended-at] :as encounter}] (assoc encounter :duration (time-between started-at ended-at)))) (finalize-entities))) -(defn count-currently-dead - [data entity-name] +(s/defn count-currently-dead :- s/Num + [data :- RaidAnalysis + entity-name :- s/Str] (if-let [entity (get-in data [:active-encounter :entities entity-name])] (let [num-deaths (count (:deaths entity)) num-resurrects (count (:resurrections entity))] (- num-deaths num-resurrects)) 0)) -(defn update-damage-averages - [{:keys [num-hits total-hit-damage total-crit-damage num-crits] :as totals}] +(s/defn update-damage-averages :- SkillStatistics + [{:keys [num-hits total-hit-damage total-crit-damage num-crits] :as totals} :- SkillStatistics] (-> totals (update-in [:average-hit] #(if (> num-hits 0) (int (/ total-hit-damage num-hits)) %)) (update-in [:average-crit] #(if (> num-crits 0) (int (/ total-crit-damage num-crits)) %)))) -(defn add-from-damage-properties - [totals - {:keys [damage damage-type hit-type crit? partial-absorb partial-resist partial-block avoidance-method] :as damage-properties}] +(s/defn add-from-damage-properties :- SkillStatistics + [totals :- (s/maybe SkillStatistics) + {:keys [damage damage-type hit-type crit? partial-absorb partial-resist partial-block avoidance-method]} :- DamageProperties] (let [damage (or damage 0)] (-> (or totals {:damage 0 :max-hit 0 @@ -419,16 +439,26 @@ (update-in [:num-immune] #(if (= avoidance-method :immune) (inc %) %)) (update-damage-averages)))) -(defn entity-takes-damage - [data entity-name from-entity-name {:keys [skill damage damage-type] :as damage-properties} timestamp] +(s/defn entity-takes-damage :- RaidAnalysis + [data :- RaidAnalysis + entity-name :- s/Str + from-entity-name :- s/Str + {:keys [skill damage damage-type] + :as damage-properties} :- DamageProperties + timestamp :- Date] (-> data (update-entity-field entity-name [:damage-in-total] #(if damage (+ (or % 0) damage) %)) (update-entity-field entity-name [:damage-in-totals damage-type] #(if damage (+ (or % 0) damage) %)) (update-entity-field entity-name [:damage-in skill] #(add-from-damage-properties % damage-properties)) (update-entity-field entity-name [:damage-in-by-entity from-entity-name skill] #(add-from-damage-properties % damage-properties)))) -(defn entity-deals-damage - [data entity-name to-entity-name {:keys [skill damage damage-type] :as damage-properties} timestamp] +(s/defn entity-deals-damage :- RaidAnalysis + [data :- RaidAnalysis + entity-name :- s/Str + to-entity-name :- s/Str + {:keys [skill damage damage-type] + :as damage-properties} :- DamageProperties + timestamp :- Date] (-> data (update-entity-field entity-name [:damage-out-total] #(if damage (+ (or % 0) damage) %)) (update-entity-field entity-name [:damage-out-totals damage-type] #(if damage (+ (or % 0) damage) %)) @@ -439,8 +469,12 @@ ;;; main combat log entry processing entry points ;;; -(defn process-source-to-target-damage - [source-name target-name damage-properties timestamp data] +(s/defn process-source-to-target-damage :- RaidAnalysis + [source-name :- s/Str + target-name :- s/Str + damage-properties :- DamageProperties + timestamp :- Date + data :- RaidAnalysis] (-> data (touch-entity source-name timestamp) (touch-entity target-name timestamp) @@ -448,17 +482,26 @@ (entity-deals-damage source-name target-name damage-properties timestamp)) ) -(defn process-entity-death - [entity-name timestamp data] +(s/defn process-entity-death :- RaidAnalysis + [entity-name :- s/Str + timestamp :- Date + data :- RaidAnalysis] (-> data (touch-entity entity-name timestamp) (update-entity-field entity-name [:deaths] #(conj % {:timestamp timestamp})) (update-entity entity-name finalize-entity-auras timestamp))) -(defn process-source-to-target-cast - [source-name target-name skill-name timestamp data] +(s/defn process-source-to-target-cast :- RaidAnalysis + [source-name :- s/Str + target-name :- s/Str + skill-name :- s/Str + timestamp :- Date + data :- RaidAnalysis] data) -(defn process-entity-cast - [entity-name skill-name timestamp data] +(s/defn process-entity-cast :- RaidAnalysis + [entity-name :- s/Str + skill-name :- s/Str + timestamp :- Date + data :- RaidAnalysis] data) diff --git a/vwowrla.core/src/vwowrla/core/preparsing.clj b/vwowrla.core/src/vwowrla/core/preparsing.clj index 7b14f1a..6112f46 100644 --- a/vwowrla.core/src/vwowrla/core/preparsing.clj +++ b/vwowrla.core/src/vwowrla/core/preparsing.clj @@ -1,10 +1,12 @@ (ns vwowrla.core.preparsing (:import - (java.util Calendar GregorianCalendar TimeZone)) + (java.util Calendar Date GregorianCalendar TimeZone)) (:require [clojure.string :as string] - [clojure.java.io :as io]) + [clojure.java.io :as io] + [schema.core :as s]) (:use + vwowrla.core.schemas vwowrla.core.utils)) (def problem-entity-names (get-text-resource-as-lines "problem_entity_names.txt")) @@ -20,43 +22,45 @@ :fixed-to-problem {}} problem-entity-names)) -(defn sanitize-entity-name - [^String entity-name] +(s/defn sanitize-entity-name :- s/Str + [entity-name :- s/Str] (get-in problem-entity-name-to-fixed-name [:problem-to-fixed entity-name] entity-name)) -(defn get-original-entity-name - [^String potentially-sanitized-entity-name] +(s/defn get-original-entity-name :- s/Str + [potentially-sanitized-entity-name :- s/Str] (get-in problem-entity-name-to-fixed-name [:fixed-to-problem potentially-sanitized-entity-name] potentially-sanitized-entity-name)) -(defn sanitize-entity-names - [^String line] +(s/defn sanitize-entity-names :- s/Str + [line :- s/Str] (reduce (fn [^String line [^String problem-name ^String fixed-name]] (.replace line problem-name fixed-name)) line (:problem-to-fixed problem-entity-name-to-fixed-name))) -(defn undo-swstats-fixlogstring - [^String line] - (.replace line " 's" "'s")) +(s/defn undo-swstats-fixlogstring :- s/Str + [line :- s/Str] + (.replace ^String line " 's" "'s")) -(defn parse-log-timestamp - [^String timestamp {:keys [^long year ^TimeZone timezone windows?] :as options}] +(s/defn parse-log-timestamp :- Date + [timestamp :- s/Str + options :- ParserOptions] (if-let [matches (re-matches #"^(\d{1,2})\/(\d{1,2}) (\d{1,2}):(\d{2}):(\d{2})\.(\d{3})$" timestamp)] (let [c (GregorianCalendar.) [month day hour minute second millis] (rest matches)] (.clear c) - (.setTimeZone c timezone) - (.set c year (if windows? (dec (->int month)) (->int month)) (->int day) (->int hour) (->int minute) (->int second)) + (.setTimeZone c (:timezone options)) + (.set c (:year options) (if (:windows? options) (dec (->int month)) (->int month)) (->int day) (->int hour) (->int minute) (->int second)) (.set c Calendar/MILLISECOND (->int millis)) (.getTime c)))) -(defn split-log-timestamp-and-content - [^String line] - (clojure.string/split line #" " 2)) +(s/defn split-log-timestamp-and-content :- [s/Str] + [line :- s/Str] + (string/split line #" " 2)) -(defn process-parsed-line - [{:keys [source-name target-name source] :as parsed-line} ^String log-owner-char-name] +(s/defn process-parsed-line :- CombatEvent + [{:keys [source-name target-name source] :as parsed-line} :- CombatEvent + log-owner-char-name :- s/Str] (merge parsed-line (if source-name diff --git a/vwowrla.core/src/vwowrla/core/schemas.clj b/vwowrla.core/src/vwowrla/core/schemas.clj index d5814ae..737b915 100644 --- a/vwowrla.core/src/vwowrla/core/schemas.clj +++ b/vwowrla.core/src/vwowrla/core/schemas.clj @@ -14,6 +14,15 @@ ;; model schemas +(def Milliseconds java.lang.Long) +(def UnixTimestamp java.lang.Long) + +(def ParserOptions + {:log-owner-char-name s/Str + :year s/Int + :timezone java.util.TimeZone + (s/optional-key :windows?) s/Bool}) + (def CombatEvent {:id s/Keyword :logfmt s/Keyword @@ -42,15 +51,23 @@ (s/optional-key :stacks) (s/maybe s/Num) (s/optional-key :faded?) s/Bool}) +; TODO +(def SkillStatistics + {s/Any s/Any}) + +; TODO +(def Entity + {s/Any s/Any}) + +; TODO (def Encounter {s/Any s/Any}) +; TODO (def RaidAnalysis {:encounters [Encounter] :active-encounter (s/maybe Encounter)}) -(def ParserOptions - {:log-owner-char-name s/Str - :year s/Int - :timezone java.util.TimeZone - (s/optional-key :windows?) s/Bool}) +; TODO +(def DamageProperties + {s/Any s/Any}) \ No newline at end of file