summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author0xNul <57599792+0xNul@users.noreply.github.com>2023-07-22 13:11:54 -0700
committer0xNul <57599792+0xNul@users.noreply.github.com>2023-07-22 13:11:54 -0700
commit9c601ef72f6c44e73c862f4efed0041463b8a257 (patch)
treea2bd678da0e0d8ebea170c2ea77bd15c2d2c5f28
parentbf1f9ed2f636b6374e32c8261d06f9942a064044 (diff)
added battle simulator
-rw-r--r--src/votann/battle_simulator_widget.clj140
-rw-r--r--src/votann/codex.clj5
-rw-r--r--src/votann/event_handler.clj155
-rw-r--r--src/votann/tab_widget.clj8
-rw-r--r--src/votann/util.clj24
-rw-r--r--src/votann_battle_simulator/battle_round.clj87
-rw-r--r--src/votann_battle_simulator/util.clj46
-rw-r--r--src/votann_battle_simulator/weapon_abilities.clj185
8 files changed, 624 insertions, 26 deletions
diff --git a/src/votann/battle_simulator_widget.clj b/src/votann/battle_simulator_widget.clj
new file mode 100644
index 0000000..cfc383d
--- /dev/null
+++ b/src/votann/battle_simulator_widget.clj
@@ -0,0 +1,140 @@
+(ns votann.battle-simulator-widget
+ (:require [votann.codex :refer [kin-models kin-bodyguards]]
+ [votann.util :as util]
+ [cljfx.api :as fx]))
+
+(def models-widget-select
+ {:fx/type :choice-box
+ :on-action {:event/type :event/choice-select-model}
+ :value (:name (first (apply conj kin-models kin-bodyguards)))
+ :items (mapv #(:name %) (apply conj kin-models kin-bodyguards))})
+
+(def models-widget-target
+ {:fx/type :choice-box
+ :on-action {:event/type :event/choice-select-target}
+ :value (:name (first (apply conj kin-models kin-bodyguards)))
+ :items (mapv #(:name %) (apply conj kin-models kin-bodyguards))})
+
+(defn chart-data [data]
+ (vec (for [d data]
+ {:fx/type :xy-chart-data :x-value (:damage d) :y-value (:weapon d)})))
+
+(defn damage-data [data type]
+ (vec (for [[weapon damage] data]
+ (cond (= "Mean" type)
+ {:fx/type :xy-chart-data :x-value (util/mean damage) :y-value weapon}
+
+ (= "Median" type)
+ {:fx/type :xy-chart-data :x-value (util/median damage) :y-value weapon}
+
+ (= "Total" type)
+ {:fx/type :xy-chart-data :x-value (util/total damage) :y-value weapon}))))
+
+(defn battle-simulator-widget [{:keys [data total-damage data-type count weapon-type rolls target-count tokens disabled]}]
+ {:fx/type :v-box
+ :children [{:fx/type :h-box
+ :alignment :center
+ :padding 12
+ :spacing 12
+ :children [
+ {:fx/type :v-box
+ :children [
+ {:fx/type :label
+ :text "Weapon Type"}
+ {:fx/type :choice-box
+ :on-action {:event/type :event/choice-select-weapon}
+ :value weapon-type
+ :items ["Ranged"
+ "Melee"
+ ]}]}
+ {:fx/type :v-box
+ :children [
+ {:fx/type :label
+ :text "Model"}
+ models-widget-select]}
+ {:fx/type :v-box
+ :children [
+ {:fx/type :label
+ :text "Model Count"}
+ {:fx/type :text-field
+ :on-key-typed {:event/type :event/choice-select-rolls}
+ :max-width 80
+ :text rolls}
+ ]}
+ {:fx/type :v-box
+ :children [
+ {:fx/type :label
+ :text "Target"}
+ models-widget-target]}
+ {:fx/type :v-box
+ :children [
+ {:fx/type :label
+ :text "Target Count"}
+ {:fx/type :text-field
+ :on-key-typed {:event/type :event/choice-select-target-size}
+ :max-width 82
+ :text target-count}
+ ]}
+ {:fx/type :v-box
+ :children [
+ {:fx/type :label
+ :text "Judgment Tokens"}
+ {:fx/type :choice-box
+ :min-width 108
+ :on-action {:event/type :event/choice-select-tokens}
+ :value tokens
+ :items ["0 "
+ "1 "
+ "2 "]}]}
+ {:fx/type :v-box
+ :children [
+ {:fx/type :label
+ :text ""}
+ {:fx/type :button
+ :disable disabled
+ :on-action {:event/type :event/choice-select-start}
+ :text "Roll"}
+ ]}
+ {:fx/type :v-box
+ :children [
+ {:fx/type :label
+ :text ""}
+ {:fx/type :button
+ :on-action {:event/type :event/choice-select-reset}
+ :text "Reset"}
+ ]}
+ ]}
+ {:fx/type util/ext-recreate-on-key-changed
+ :key data
+ :desc
+ {:fx/type :bar-chart
+ :title (:dialog (first data))
+ :legend-visible false
+ :x-axis {:fx/type :number-axis :label "Damage"}
+ :y-axis {:fx/type :category-axis}
+ :data [{:fx/type :xy-chart-series
+ :data (chart-data data)}]}}
+
+ {:fx/type :h-box
+ :alignment :center
+ :spacing 12
+ :children [{:fx/type :label
+ :style "-fx-font-size: 18px;"
+ :text (str "Weapon damage after " count " rolls")}
+ {:fx/type :choice-box
+ :on-action {:event/type :event/choice-select-stats}
+ :value data-type
+ :items ["Total"
+ "Mean"
+ "Median"]}]
+ }
+
+ {:fx/type util/ext-recreate-on-key-changed
+ :key total-damage
+ :desc
+ {:fx/type :bar-chart
+ :legend-visible false
+ :x-axis {:fx/type :number-axis :label "Damage"}
+ :y-axis {:fx/type :category-axis}
+ :data [{:fx/type :xy-chart-series
+ :data (damage-data total-damage data-type)}]}}]})
diff --git a/src/votann/codex.clj b/src/votann/codex.clj
index 6e2cd17..96768f5 100644
--- a/src/votann/codex.clj
+++ b/src/votann/codex.clj
@@ -17,6 +17,11 @@
lov/brokyr-thunderkyn
lov/hekaton-land-fortress])
+(def kin-bodyguards
+ [lov/corv
+ lov/ironkin-assistant
+ lov/e-cog])
+
(def kin-enhancements
[{:name "A Long List"
:points (Points. "x1" 15)}
diff --git a/src/votann/event_handler.clj b/src/votann/event_handler.clj
index 8558762..e521724 100644
--- a/src/votann/event_handler.clj
+++ b/src/votann/event_handler.clj
@@ -1,5 +1,7 @@
(ns votann.event-handler
- (:require [votann.codex :refer [kin-models]])
+ (:require [votann.codex :refer [kin-models]]
+ [votann.util :as util]
+ [votann-battle-simulator.battle-round :refer [shooting-phase fight-phase]])
(:import [javafx.scene.input KeyCode KeyEvent]))
(def *state (atom {:list
@@ -18,6 +20,29 @@
:tokens "0 "
:disabled false}}))
+;; TODO find a better place for this
+(defn battle-modifiers [state]
+ (let [judgment-count (clojure.string/trim (get-in @state [:battle-simulator :tokens]))]
+ (cond
+ (= "0" judgment-count)
+ {:hit []
+ :wound []}
+
+ (= "1" judgment-count)
+ {:hit ["Judgment Token"]
+ :wound []}
+
+ (= "2" judgment-count)
+ {:hit ["Judgment Token"]
+ :wound ["Judgment Token"]})))
+
+;; TODO find a better place for
+(defn total-damage [state]
+ (doseq [data (get-in @state [:battle-simulator :data])]
+ (if-not (nil? (get-in @state [:battle-simulator :total-damage (:weapon data)]))
+ (swap! state assoc-in [:battle-simulator :total-damage (:weapon data)] (conj (get-in @state [:battle-simulator :total-damage (:weapon data)]) (:damage data)))
+ (swap! state assoc-in [:battle-simulator :total-damage (:weapon data)] [(:damage data)]))))
+
(defn enter-event [function e]
(if (= KeyCode/ENTER (.getCode ^KeyEvent (:fx/event e)))
(function e)))
@@ -48,32 +73,120 @@
(spit (str (.toString (java.time.Instant/now)) ".list") (:list @*state))))
(defn map-event-handler [e]
- (cond (= :event/undo-unit-click (:event/type e))
- (undo-points e)
+ (cond
+ ;; list events
+ (= :event/undo-unit-click (:event/type e))
+ (undo-points e)
+
+ (= :event/undo-unit-enter (:event/type e))
+ (enter-event undo-points e)
+
+ (= :event/restart-click (:event/type e))
+ (restart-list e)
+
+ (= :event/restart-enter (:event/type e))
+ (enter-event restart-list e)
- (= :event/undo-unit-enter (:event/type e))
- (enter-event undo-points e)
+ (= :event/remove-unit-click (:event/type e))
+ (remove-unit e)
- (= :event/restart-click (:event/type e))
- (restart-list e)
+ (= :event/remove-unit-enter (:event/type e))
+ (enter-event remove-unit e)
- (= :event/restart-enter (:event/type e))
- (enter-event restart-list e)
+ (= :event/add-unit-click (:event/type e))
+ (add-unit e)
- (= :event/remove-unit-click (:event/type e))
- (remove-unit e)
+ (= :event/add-unit-enter (:event/type e))
+ (enter-event add-unit e)
- (= :event/remove-unit-enter (:event/type e))
- (enter-event remove-unit e)
+ (= :event/export-list-click (:event/type e))
+ (export-list e)
- (= :event/add-unit-click (:event/type e))
- (add-unit e)
+ (= :event/export-list-enter (:event/type e))
+ (enter-event export-list e)
- (= :event/add-unit-enter (:event/type e))
- (enter-event add-unit e)
+ ;;battle-simulator events
+ (= :event/choice-select-weapon (:event/type e))
+ (swap! *state assoc-in [:battle-simulator :weapon-type] (.getValue (.getTarget (:fx/event e))))
- (= :event/export-list-click (:event/type e))
- (export-list e)
+ (= :event/choice-select-model (:event/type e))
+ (do
+ (swap! *state assoc-in [:battle-simulator :model] (.getValue (.getTarget (:fx/event e))))
+ (swap! *state assoc-in [:battle-simulator :count] "0")
+ (swap! *state assoc-in [:battle-simulator :rolls] "1")
+ (swap! *state assoc-in [:battle-simulator :data] [])
+ (swap! *state assoc-in [:battle-simulator :total-damage] {}))
+
+ (= :event/choice-select-reset (:event/type e))
+ (do
+ (swap! *state assoc-in [:battle-simulator :count] "0")
+ (swap! *state assoc-in [:battle-simulator :data] [])
+ (swap! *state assoc-in [:battle-simulator :total-damage] {}))
- (= :event/export-list-enter (:event/type e))
- (enter-event export-list e)))
+ (= :event/choice-select-target (:event/type e))
+ (do
+ (swap! *state assoc-in [:battle-simulator :target] (.getValue (.getTarget (:fx/event e))))
+ (swap! *state assoc-in [:battle-simulator :count] "0")
+ (swap! *state assoc-in [:battle-simulator :data] [])
+ (swap! *state assoc-in [:battle-simulator :total-damage] {}))
+
+ (= :event/choice-select-rolls (:event/type e))
+ (try
+ (let [roll (Integer/parseInt (.getText (.getTarget (:fx/event e))))]
+ (if (< roll 1)
+ (do
+ (swap! *state assoc-in [:battle-simulator :rolls] "")
+ (.setText (.getTarget (:fx/event e)) "")
+ (swap! *state assoc-in [:battle-simulator :disabled] true))
+ (do
+ (swap! *state assoc-in [:battle-simulator :rolls] (str roll))
+ (swap! *state assoc-in [:battle-simulator :disabled] false))))
+ (catch java.lang.NumberFormatException | java.lang.NullPointerException e
+ (swap! *state assoc-in [:battle-simulator :rolls] "")
+ (.setText (.getTarget (:fx/event e)) "")
+ (swap! *state assoc-in [:battle-simulator :disabled] true)))
+
+ (= :event/choice-select-target-size (:event/type e))
+ (try
+ (let [count (Integer/parseInt (.getText (.getTarget (:fx/event e))))]
+ (if (< count 1)
+ (do
+ (swap! *state assoc-in [:battle-simulator :target-count] "")
+ (.setText (.getTarget (:fx/event e)) "")
+ (swap! *state assoc-in [:battle-simulator :disabled] true))
+ (do
+ (swap! *state assoc-in [:battle-simulator :target-count] (str count))
+ (swap! *state assoc-in [:battle-simulator :disabled] false))))
+ (catch java.lang.NumberFormatException | java.lang.NullPointerException e
+ (swap! *state assoc-in [:battle-simulator :target-count] "")
+ (.setText (.getTarget (:fx/event e)) "")
+ (swap! *state assoc-in [:battle-simulator :disabled] true)))
+
+ (= :event/choice-select-tokens (:event/type e))
+ (swap! *state assoc-in [:battle-simulator :tokens] (.getValue (.getTarget (:fx/event e))))
+
+ (= :event/choice-select-stats (:event/type e))
+ (swap! *state assoc-in [:battle-simulator :data-type] (.getValue (.getTarget (:fx/event e))))
+
+ (= :event/choice-select-start (:event/type e))
+ (cond (= "Ranged" (get-in @*state [:battle-simulator :weapon-type]))
+ (do
+ (swap! *state assoc-in [:battle-simulator :data]
+ (shooting-phase (Integer/parseInt (get-in @*state [:battle-simulator :rolls]))
+ (util/get-model (get-in @*state [:battle-simulator :model]))
+ (Integer/parseInt (get-in @*state [:battle-simulator :target-count]))
+ (util/get-model (get-in @*state [:battle-simulator :target]))
+ (battle-modifiers *state)))
+ (swap! *state assoc-in [:battle-simulator :count] (str (+ (Integer/parseInt (get-in @*state [:battle-simulator :count])) 1)))
+ (total-damage *state))
+
+ (= "Melee" (get-in @*state [:battle-simulator :weapon-type]))
+ (do
+ (swap! *state assoc-in [:battle-simulator :data]
+ (fight-phase (Integer/parseInt (get-in @*state [:battle-simulator :rolls]))
+ (util/get-model (get-in @*state [:battle-simulator :model]))
+ (Integer/parseInt (get-in @*state [:battle-simulator :target-count]))
+ (util/get-model (get-in @*state [:battle-simulator :target]))
+ (battle-modifiers *state)))
+ (swap! *state assoc-in [:battle-simulator :count] (str (+ (Integer/parseInt (get-in @*state [:battle-simulator :count])) 1)))
+ (total-damage *state)))))
diff --git a/src/votann/tab_widget.clj b/src/votann/tab_widget.clj
index 85bb770..4b6cbe7 100644
--- a/src/votann/tab_widget.clj
+++ b/src/votann/tab_widget.clj
@@ -4,7 +4,7 @@
[votann.unit-widget :refer [unit-view-widget]]
[votann.enhancements-widget :refer [enhancements-view-widget]]
[votann.codex-view :refer [codex-page]]
- [votann.battle-simulator-widget :refer [place-holder]]
+ [votann.battle-simulator-widget :refer [battle-simulator-widget]]
[cljfx.api :as fx]
[cljfx.ext.web-view :as fx.ext.web-view]))
@@ -34,11 +34,11 @@
:content {:fx/type :tab-pane
:tabs unit-view-tab}}])
-(def battle-simulator-view-tab
+(defn battle-simulator-view-tab [data]
[{:fx/type :tab
:text "Battle Simulator"
:closable false
- :content place-holder}])
+ :content (battle-simulator-widget data)}])
(defn tab-widget [data]
- (vec (apply concat [(list-view-tab (:list data)) codex-view-tab battle-simulator-view-tab])))
+ (vec (apply concat [(list-view-tab (:list data)) codex-view-tab (battle-simulator-view-tab (:battle-simulator data))])))
diff --git a/src/votann/util.clj b/src/votann/util.clj
index 307f8a7..bb84624 100644
--- a/src/votann/util.clj
+++ b/src/votann/util.clj
@@ -1,8 +1,30 @@
(ns votann.util
- (:require [cljfx.lifecycle :as fx.lifecycle]
+ (:require [votann.codex :refer [kin-models kin-bodyguards]]
+ [cljfx.lifecycle :as fx.lifecycle]
[cljfx.component :as fx.component]
+ [clojure.math :as math]
[clojure.string :as string]))
+(defn get-model [name]
+ (->>
+ (filter #(= (:name %) name) (apply conj kin-models kin-bodyguards))
+ first))
+
+(defn total [collection]
+ (apply + collection))
+
+(defn mean [collection]
+ (double (/ (total collection) (count collection))))
+
+(defn median [collection]
+ (let [c (count collection)
+ m (- (int (math/ceil (double (/ c 2)))) 1)
+ collection (vec (sort collection))]
+ (if (= 0 (mod c 2))
+ (get collection
+ (int (math/ceil (double (/ (+ m (+ m 1)) 2)))))
+ (get collection m))))
+
(defn get-resource-path [file]
(.toExternalForm (clojure.java.io/resource file)))
diff --git a/src/votann_battle_simulator/battle_round.clj b/src/votann_battle_simulator/battle_round.clj
new file mode 100644
index 0000000..f5e55b2
--- /dev/null
+++ b/src/votann_battle_simulator/battle_round.clj
@@ -0,0 +1,87 @@
+(ns votann-battle-simulator.battle-round
+ (:require [votann-battle-simulator.util :as util]
+ [votann-battle-simulator.weapon-abilities :as weapon-abilities])
+ (:import [votann.records.model Model]))
+
+(defn resolve-hit-roll [rolls skill]
+ (vec (filter #(>= % skill) rolls)))
+
+(defn resolve-wound-roll [rolls strength toughness]
+ (vec (cond (>= strength (* toughness 2))
+ (filter #(>= % 2) rolls)
+
+ (> strength toughness)
+ (filter #(>= % 3) rolls)
+
+ (= strength toughness)
+ (filter #(>= % 4) rolls)
+
+ (< strength toughness)
+ (filter #(>= % 5) rolls)
+
+ (<= strength (double (/ toughness 2)))
+ (filter #(>= % 6) rolls))))
+
+(defn resolve-saving-throw-roll [rolls ap save]
+ (vec (filter #(< % (+ ap save)) rolls)))
+
+(defn command-phase [])
+
+(defn movement-phase [])
+
+(defn shooting-phase [^Integer unit-size ^Model model ^Integer target-size ^Model target battle-modifiers]
+ (println "Starting shooting-phase ")
+ (println (str (:name model) " x" unit-size " target " (:name target)
+ " W: " (:w target)
+ " T: " (:t target)
+ " SV: " (:sv target)))
+ (for [weapon (:ranged-weapons model)]
+ (do
+ (println (str "\nUsing weapon: " (:name weapon)))
+ (let [rolls (util/roll-d6 (weapon-abilities/resolve-attack-abilities weapon unit-size target-size))
+ rolls (weapon-abilities/resolve-hit-dice-abilities weapon rolls)
+ rolls (weapon-abilities/resolve-hit-modifier-abilities weapon (:hit battle-modifiers) rolls)
+ hits (resolve-hit-roll rolls (:bs weapon))
+ wounds (weapon-abilities/resolve-wound-dice-abilites weapon (util/roll-d6 (count hits)) (:s weapon) (:t target))
+ mortal-wounds (count (filter #(= 99 %) wounds))
+ wounds (vec (filter #(not= 99 %) wounds))
+ wounds (weapon-abilities/resolve-wound-modifier-abilites weapon (:wound battle-modifiers) wounds)
+ wounds (resolve-wound-roll (util/roll-d6 (count wounds)) (:s weapon) (:t target))
+ non-saves (count (resolve-saving-throw-roll (util/roll-d6 (count wounds)) (:ap weapon) (:sv target)))
+ damage (* (util/resolve-stat-count (:d weapon)) (+ mortal-wounds non-saves))]
+ (println (str "Total damage: " damage))
+ {:dialog (str (:name model) " x" unit-size " target " (:name target)
+ " W: " (:w target)
+ " T: " (:t target)
+ " SV: " (:sv target))
+ :weapon (:name weapon) :damage damage}
+ ))))
+
+(defn charge-phase [])
+
+(defn fight-phase [^Integer unit-size ^Model model ^Integer target-size ^Model target battle-modifiers]
+ (println "Starting fight-phase ")
+ (println (str (:name model) " x" unit-size " target " (:name target)
+ " W: " (:w target)
+ " T: " (:t target)
+ " SV: " (:sv target)))
+ (for [weapon (:melee-weapons model)]
+ (do
+ (println (str "\nUsing weapon: " (:name weapon)))
+ (let [rolls (util/roll-d6 (weapon-abilities/resolve-attack-abilities weapon unit-size target-size))
+ rolls (weapon-abilities/resolve-hit-dice-abilities weapon rolls)
+ rolls (weapon-abilities/resolve-hit-modifier-abilities weapon (:hit battle-modifiers) rolls)
+ hits (resolve-hit-roll rolls (:bs weapon))
+ wounds (weapon-abilities/resolve-wound-dice-abilites weapon (util/roll-d6 (count hits)) (:s weapon) (:t target))
+ mortal-wounds (count (filter #(= 99 %) wounds))
+ wounds (vec (filter #(not= 99 %) wounds))
+ wounds (weapon-abilities/resolve-wound-modifier-abilites weapon (:wound battle-modifiers) wounds)
+ wounds (resolve-wound-roll (util/roll-d6 (count wounds)) (:s weapon) (:t target))
+ non-saves (count (resolve-saving-throw-roll (util/roll-d6 (count wounds)) (:ap weapon) (:sv target)))
+ damage (* (util/resolve-stat-count (:d weapon)) (+ mortal-wounds non-saves))]
+ (println (str "Total damage: " damage))
+ {:dialog (str (:name model) " x" unit-size " target " (:name target)
+ " W: " (:w target)
+ " T: " (:t target)
+ " SV: " (:sv target))
+ :weapon (:name weapon) :damage damage}))))
diff --git a/src/votann_battle_simulator/util.clj b/src/votann_battle_simulator/util.clj
new file mode 100644
index 0000000..1292bf2
--- /dev/null
+++ b/src/votann_battle_simulator/util.clj
@@ -0,0 +1,46 @@
+(ns votann-battle-simulator.util)
+
+(defn roll-d6 [n]
+ (loop [i 0
+ rolls ()]
+ (if (>= i n)
+ (vec rolls)
+ (recur (+ i 1) (conj rolls (+ 1 (rand-int 6)))))))
+
+(defn roll-d3 [n]
+ (mapv #(int (Math/ceil (double (/ % 2)))) (roll-d6 n)))
+
+(defn d+ [n rolls]
+ (vec (filter #(>= % n) rolls)))
+
+(defn resolve-stat-count [stat]
+ (if (int? stat)
+ stat
+ (let [add-num (re-find #"\+\d+" stat)
+ num (if (nil? add-num)
+ 0
+ (Integer/parseInt add-num))]
+ (cond
+ (re-find #"D6" stat)
+ (+ num (first (roll-d6 1)))
+
+ (re-find #"D3" stat)
+ (+ num (first (roll-d3 1)))))))
+
+(defn modifier-cap [modifier]
+ (cond
+ (< modifier 0) -1
+ (> modifier 0) 1
+ :else 0))
+
+(defn dice-cap [dice]
+ (cond
+ (< dice 1) 1
+ (> dice 6) 6
+ :else dice))
+
+(defn mod+1 [modifier]
+ (+ modifier 1))
+
+(defn mod-1 [modifier]
+ (- modifier 1))
diff --git a/src/votann_battle_simulator/weapon_abilities.clj b/src/votann_battle_simulator/weapon_abilities.clj
new file mode 100644
index 0000000..a0b37b5
--- /dev/null
+++ b/src/votann_battle_simulator/weapon_abilities.clj
@@ -0,0 +1,185 @@
+(ns votann-battle-simulator.weapon-abilities
+ (:require [votann-battle-simulator.util :as util])
+ (:import [votann.records.weapon Weapon]))
+
+(defn blast
+ ([target-size]
+ (int (/ target-size 5)))
+ ([dice unit-size target-size]
+ (let [blast-added (+ dice (* unit-size (blast target-size)))]
+ (println "Dice count before " dice " after " blast-added)
+ blast-added)))
+
+(defn devastating-wounds [dice]
+ (let [mortal-added (vec (map #(if (= 6 %)
+ 99
+ %) dice))]
+ (println "Hit rolls before " dice " after " mortal-added)
+ mortal-added))
+
+(defn twin-linked [dice strength toughness]
+ (let [re-rolls (vec (cond (>= strength (* toughness 2))
+ (filter #(< % 2) dice)
+
+ (> strength toughness)
+ (filter #(< % 3) dice)
+
+ (= strength toughness)
+ (filter #(< % 4) dice)
+
+ (< strength toughness)
+ (filter #(< % 5) dice)
+
+ (<= strength (double (/ toughness 2)))
+ (filter #(< % 6) dice)))
+ dice-rolls (vec (filter (complement (set re-rolls)) dice))
+ twin-linked-rolls (apply conj dice-rolls (util/roll-d6 (count re-rolls)))]
+ (println "Wounds before " dice " after " twin-linked-rolls)
+ twin-linked-rolls))
+
+(defn rapid-fire [dice modifier]
+ (cond
+ (re-find #"D\d" modifier)
+ (let [rapid-fire-modifier (util/resolve-stat-count modifier)
+ rapid-fire-added (+ dice rapid-fire-modifier)]
+ (println "Dice count before " dice " after " rapid-fire-added)
+ rapid-fire-added)
+ :else
+ (let [rapid-fire-added (+ dice (Integer/parseInt (re-find #"\d+" modifier)))]
+ (println "Dice count before " dice " after " rapid-fire-added)
+ rapid-fire-added)))
+
+(defn sustained-hits [modifier dice]
+ (let [critical (count (filter #(= 6 %) dice))]
+ (cond
+ (re-find #"D\d" modifier)
+ (let [crit-added (apply conj dice
+ (vec (repeat (*
+ critical
+ (util/resolve-stat-count modifier))
+ 7)))]
+ (println "Hit count before " dice " after " crit-added)
+ crit-added)
+
+ :else
+ (let [crit-added (apply conj dice
+ (vec (repeat (*
+ critical
+ (Integer/parseInt (re-find #"\d+" modifier)))
+ 7)))]
+ (println "Hit count after " dice " after " crit-added)
+ crit-added)
+ )))
+
+(defn resolve-attack-abilities [^Weapon weapon unit-size target-size]
+ (loop [abilities (:abilities weapon)
+ dice (* unit-size (util/resolve-stat-count (:a weapon)))]
+ (if (empty? abilities)
+ dice
+ (let [ability (first abilities)]
+ (cond
+ (not (nil? (re-find #"Blast" ability)))
+ (do
+ (println "Applied Blast attack ability for " (:name weapon))
+ (recur (rest abilities)
+ (blast dice unit-size target-size)))
+
+ (not (nil? (re-find #"Rapid Fire" ability)))
+ (do
+ (println "Applied Rapid fire attack ability for " (:name weapon))
+ (recur (rest abilities)
+ (rapid-fire dice ability)))
+
+ :else (recur (rest abilities)
+ dice))))))
+
+(defn resolve-hit-dice-abilities [^Weapon weapon dice]
+ (loop [abilities (:abilities weapon)
+ dice-rolls dice]
+ (if (empty? abilities)
+ dice-rolls
+ (let [ability (first abilities)]
+ (cond
+ (not (nil? (re-find #"Sustained Hits" ability)))
+ (do
+ (println "Applied Sustained Hits hit ability for " (:name weapon))
+ (recur (rest abilities)
+ (sustained-hits ability dice-rolls)))
+
+ :else
+ (recur (rest abilities)
+ dice-rolls))))))
+
+(defn resolve-hit-modifier-abilities [^Weapon weapon modifier-abilities dice]
+ (loop [abilities (apply conj (:abilities weapon) modifier-abilities)
+ modifier 0]
+ (if (empty? abilities)
+ (let [modifier-added (vec (map #(if (or (= 1 %) (= 6 %))
+ %
+ (util/dice-cap (+ (util/modifier-cap modifier) %))) dice))]
+ (println "Resolve hit Before " dice " after " modifier-added)
+ modifier-added)
+ (let [ability (first abilities)]
+ (cond
+ (not (nil? (re-find #"Heavy" ability)))
+ (do
+ (println "Applied Heavy hit modifier for " (:name weapon))
+ (recur (rest abilities)
+ (util/mod+1 modifier)))
+
+ (not (nil? (re-find #"Judgment Token" ability)))
+ (do
+ (println "Applied Judgment Token hit modifier for " (:name weapon))
+ (recur (rest abilities)
+ (util/mod+1 modifier)))
+ :else
+ (recur (rest abilities)
+ modifier))))))
+
+(defn resolve-wound-dice-abilites [^Weapon weapon dice strength toughness]
+ (loop [abilities (:abilities weapon)
+ dice-rolls dice
+ modifier 0]
+ (if (empty? abilities)
+ dice-rolls
+ (let [ability (first abilities)]
+ (cond
+ (not (nil? (re-find #"Devastating Wounds" ability)))
+ (do
+ (println "Applied Devastating Wounds wound ability for " (:name weapon))
+ (recur (rest abilities)
+ (devastating-wounds dice)
+ modifier))
+
+ (not (nil? (re-find #"Twin-Linked" ability)))
+ (do
+ (println "Twin-Linked wound ability for " (:name weapon))
+ (recur (rest abilities)
+ (twin-linked dice strength toughness)
+ modifier))
+
+ :else
+ (recur (rest abilities)
+ dice-rolls
+ modifier))))))
+
+(defn resolve-wound-modifier-abilites [^Weapon weapon modifier-abilities dice]
+ (loop [abilities (apply conj (:abilities weapon) modifier-abilities)
+ modifier 0]
+ (if (empty? abilities)
+ (let [modifier-added (vec (map #(if (or (= 1 %) (= 6 %))
+ %
+ (util/dice-cap (+ (util/modifier-cap modifier) %))) dice))]
+ (println "Resolve wound Before " dice " after " modifier-added)
+ modifier-added)
+ (let [ability (first abilities)]
+ (cond
+ (not (nil? (re-find #"Judgment Token" ability)))
+ (do
+ (println "Applied Judgment Token wound modifier for " (:name weapon))
+ (recur (rest abilities)
+ (util/mod+1 modifier)))
+
+ :else
+ (recur (rest abilities)
+ modifier))))))