Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion resources/public/srd/2014/conditions.edn
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
;; Generated by scripts/srd/emit-conditions.py (agents/develop) from open5e-api
;; srd-corrections 06ca28e. Do not edit: fix the source and regenerate.
;; srd-corrections 4480957. Do not edit: fix the source and regenerate.
{:srd/document "SRD 5.1" :srd/edition 2014 :srd/license "CC BY 4.0" :srd/attribution "This work includes material taken from the System Reference Document 5.1 (“SRD 5.1”) by Wizards of the Coast LLC and available at https://dnd.wizards.com/resources/systems-reference-document. The SRD 5.1 is licensed under the Creative Commons Attribution 4.0 International License available at https://creativecommons.org/licenses/by/4.0/legalcode."
:conditions
[{:key :blinded :name "Blinded" :page 358 :body [[:ul ["A blinded creature can’t see and automatically fails any ability check that requires sight."] ["Attack rolls against the creature have advantage, and the creature’s attack rolls have disadvantage."]]]}
Expand Down
22 changes: 11 additions & 11 deletions resources/public/srd/2014/equipment.edn
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
;; Generated by scripts/srd/emit-equipment.py (agents/develop) from open5e-api
;; srd-corrections b1a1bc5. Do not edit: fix the source and regenerate.
;; srd-corrections 4480957. Do not edit: fix the source and regenerate.
{:srd/document "SRD 5.1" :srd/edition 2014 :srd/license "CC BY 4.0"
:weapons
[{:key :battleaxe :name "Battleaxe" :category :martial-melee :cost {:num 10 :type :gp} :weight 4 :damage {:dice "1d8" :type :slashing} :properties [{:key :versatile :detail "1d10"}]}
Expand Down Expand Up @@ -30,16 +30,16 @@
{:key :light}]}
{:key :trident :name "Trident" :category :martial-melee :cost {:num 5 :type :gp} :weight 4 :damage {:dice "1d6" :type :piercing} :properties [{:key :thrown :detail "range 20/60"}
{:key :versatile :detail "1d8"}]}
{:key :war-pick :name "War pick" :category :martial-melee :cost {:num 5 :type :gp} :weight 2 :damage {:dice "1d8" :type :piercing} :properties []}
{:key :war-pick :name "War Pick" :category :martial-melee :cost {:num 5 :type :gp} :weight 2 :damage {:dice "1d8" :type :piercing} :properties []}
{:key :warhammer :name "Warhammer" :category :martial-melee :cost {:num 15 :type :gp} :weight 2 :damage {:dice "1d8" :type :bludgeoning} :properties [{:key :versatile :detail "1d10"}]}
{:key :whip :name "Whip" :category :martial-melee :cost {:num 2 :type :gp} :weight 3 :damage {:dice "1d4" :type :slashing} :properties [{:key :finesse}
{:key :reach}]}
{:key :blowgun :name "Blowgun" :category :martial-ranged :cost {:num 10 :type :gp} :weight 1 :damage {:dice "1" :type :piercing} :properties [{:key :ammunition :detail "range 25/100"}
{:key :loading}]}
{:key :crossbow-hand :name "Crossbow, hand" :category :martial-ranged :cost {:num 75 :type :gp} :weight 3 :damage {:dice "1d6" :type :piercing} :properties [{:key :ammunition :detail "range 30/120"}
{:key :crossbow-hand :name "Crossbow, Hand" :category :martial-ranged :cost {:num 75 :type :gp} :weight 3 :damage {:dice "1d6" :type :piercing} :properties [{:key :ammunition :detail "range 30/120"}
{:key :light}
{:key :loading}]}
{:key :crossbow-heavy :name "Crossbow, heavy" :category :martial-ranged :cost {:num 50 :type :gp} :weight 18 :damage {:dice "1d10" :type :piercing} :properties [{:key :ammunition :detail "range 100/400"}
{:key :crossbow-heavy :name "Crossbow, Heavy" :category :martial-ranged :cost {:num 50 :type :gp} :weight 18 :damage {:dice "1d10" :type :piercing} :properties [{:key :ammunition :detail "range 100/400"}
{:key :heavy}
{:key :loading}
{:key :two-handed}]}
Expand All @@ -56,14 +56,14 @@
{:key :handaxe :name "Handaxe" :category :simple-melee :cost {:num 5 :type :gp} :weight 2 :damage {:dice "1d6" :type :slashing} :properties [{:key :light}
{:key :thrown :detail "range 20/60"}]}
{:key :javelin :name "Javelin" :category :simple-melee :cost {:num 5 :type :sp} :weight 2 :damage {:dice "1d6" :type :piercing} :properties [{:key :thrown :detail "range 30/120"}]}
{:key :light-hammer :name "Light hammer" :category :simple-melee :cost {:num 2 :type :gp} :weight 2 :damage {:dice "1d4" :type :bludgeoning} :properties [{:key :light}
{:key :light-hammer :name "Light Hammer" :category :simple-melee :cost {:num 2 :type :gp} :weight 2 :damage {:dice "1d4" :type :bludgeoning} :properties [{:key :light}
{:key :thrown :detail "range 20/60"}]}
{:key :mace :name "Mace" :category :simple-melee :cost {:num 5 :type :gp} :weight 4 :damage {:dice "1d6" :type :bludgeoning} :properties []}
{:key :quarterstaff :name "Quarterstaff" :category :simple-melee :cost {:num 2 :type :sp} :weight 4 :damage {:dice "1d6" :type :bludgeoning} :properties [{:key :versatile :detail "1d8"}]}
{:key :sickle :name "Sickle" :category :simple-melee :cost {:num 1 :type :gp} :weight 2 :damage {:dice "1d4" :type :slashing} :properties [{:key :light}]}
{:key :spear :name "Spear" :category :simple-melee :cost {:num 1 :type :gp} :weight 3 :damage {:dice "1d6" :type :piercing} :properties [{:key :thrown :detail "range 20/60"}
{:key :versatile :detail "1d8"}]}
{:key :crossbow-light :name "Crossbow, light" :category :simple-ranged :cost {:num 25 :type :gp} :weight 5 :damage {:dice "1d8" :type :piercing} :properties [{:key :ammunition :detail "range 80/320"}
{:key :crossbow-light :name "Crossbow, Light" :category :simple-ranged :cost {:num 25 :type :gp} :weight 5 :damage {:dice "1d8" :type :piercing} :properties [{:key :ammunition :detail "range 80/320"}
{:key :loading}
{:key :two-handed}]}
{:key :dart :name "Dart" :category :simple-ranged :cost {:num 5 :type :cp} :weight 0.25 :damage {:dice "1d4" :type :piercing} :properties [{:key :finesse}
Expand All @@ -76,12 +76,12 @@
{:key :leather :name "Leather Armor" :category :light :cost {:num 10 :type :gp} :weight 10 :ac {:base 11 :dex? true} :strength nil :stealth-disadvantage? false}
{:key :studded-leather :name "Studded Leather Armor" :category :light :cost {:num 45 :type :gp} :weight 13 :ac {:base 12 :dex? true} :strength nil :stealth-disadvantage? false}
{:key :hide :name "Hide Armor" :category :medium :cost {:num 10 :type :gp} :weight 12 :ac {:base 12 :dex? true :max-dex 2} :strength nil :stealth-disadvantage? false}
{:key :chain-shirt :name "Chain shirt" :category :medium :cost {:num 50 :type :gp} :weight 20 :ac {:base 13 :dex? true :max-dex 2} :strength nil :stealth-disadvantage? false}
{:key :scale-mail :name "Scale mail" :category :medium :cost {:num 50 :type :gp} :weight 45 :ac {:base 14 :dex? true :max-dex 2} :strength nil :stealth-disadvantage? true}
{:key :chain-shirt :name "Chain Shirt" :category :medium :cost {:num 50 :type :gp} :weight 20 :ac {:base 13 :dex? true :max-dex 2} :strength nil :stealth-disadvantage? false}
{:key :scale-mail :name "Scale Mail" :category :medium :cost {:num 50 :type :gp} :weight 45 :ac {:base 14 :dex? true :max-dex 2} :strength nil :stealth-disadvantage? true}
{:key :breastplate :name "Breastplate" :category :medium :cost {:num 400 :type :gp} :weight 20 :ac {:base 14 :dex? true :max-dex 2} :strength nil :stealth-disadvantage? false}
{:key :half-plate :name "Half plate" :category :medium :cost {:num 750 :type :gp} :weight 40 :ac {:base 15 :dex? true :max-dex 2} :strength nil :stealth-disadvantage? true}
{:key :ring-mail :name "Ring mail" :category :heavy :cost {:num 30 :type :gp} :weight 40 :ac {:base 14 :dex? false} :strength nil :stealth-disadvantage? true}
{:key :chain-mail :name "Chain mail" :category :heavy :cost {:num 75 :type :gp} :weight 55 :ac {:base 16 :dex? false} :strength 13 :stealth-disadvantage? true}
{:key :half-plate :name "Half Plate" :category :medium :cost {:num 750 :type :gp} :weight 40 :ac {:base 15 :dex? true :max-dex 2} :strength nil :stealth-disadvantage? true}
{:key :ring-mail :name "Ring Mail" :category :heavy :cost {:num 30 :type :gp} :weight 40 :ac {:base 14 :dex? false} :strength nil :stealth-disadvantage? true}
{:key :chain-mail :name "Chain Mail" :category :heavy :cost {:num 75 :type :gp} :weight 55 :ac {:base 16 :dex? false} :strength 13 :stealth-disadvantage? true}
{:key :splint :name "Splint Armor" :category :heavy :cost {:num 200 :type :gp} :weight 60 :ac {:base 17 :dex? false} :strength 15 :stealth-disadvantage? true}
{:key :plate :name "Plate Armor" :category :heavy :cost {:num 1500 :type :gp} :weight 65 :ac {:base 18 :dex? false} :strength 15 :stealth-disadvantage? true}
{:key :shield :name "Shield" :category :shield :cost {:num 10 :type :gp} :weight 6 :ac {:bonus 2}}]}
18 changes: 9 additions & 9 deletions resources/public/srd/2014/rules.edn

Large diffs are not rendered by default.

34 changes: 21 additions & 13 deletions src/cljc/orcpub/dnd/e5/display.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -111,25 +111,33 @@
:ranged "ranged"
"melee"))))

(defn attack-description [{:keys [description summary attack-type area-type damage-type damage-die damage-die-count damage-modifier attack-modifier save save-dc page source] :as attack}]
(let [attack-mod-str (when attack-modifier (str (common/bonus-str attack-modifier) " to hit, "))]
(defn attack-description
"One-line text for an attack or weapon: summary, reach, to-hit, damage and save.
Args: attack, a map with plain keys (:damage-die ...) or a weapon's ::weapons/ keys, plus
:attack-type, :attack-modifier and :damage-modifier. Returns a string. With no damage die
(the Net) it says \"no damage\" and adds no modifier. A magic weapon's damage type (Sun Blade:
radiant) replaces its base type."
[{:keys [attack-type damage-modifier attack-modifier save save-dc] :as attack}]
(let [attack-mod-str (when attack-modifier (str (common/bonus-str attack-modifier) " to hit, "))
die (or (:damage-die attack) (::weapons/damage-die attack))
die-count (or (:damage-die-count attack) (::weapons/damage-die-count attack))
damage-type (or (:damage-type attack)
(:orcpub.dnd.e5.magic-items/magical-damage-type attack)
(::weapons/damage-type attack))]
(str
(attack-description-short attack)
(case attack-type
:area ", "
:ranged (str ", " attack-mod-str)
(str ", " attack-mod-str))
(or damage-die-count
(::weapons/damage-die-count attack))
"d"
(or damage-die
(::weapons/damage-die attack))
(when damage-modifier (common/mod-str damage-modifier))
" "
(when damage-type (common/safe-name damage-type))
" damage"
(when save (str ", DC" save-dc " " (common/safe-name save) " save"))
#_(when page (str " (" (source-description source page) ")")))))
(if die
(str die-count "d" die
(when damage-modifier (common/mod-str damage-modifier))
" "
(when damage-type (str (common/safe-name damage-type) " "))
"damage")
"no damage")
(when save (str ", DC" save-dc " " (common/safe-name save) " save")))))

(defn action-description [{:keys [description summary source page duration range frequency qualifier]}]
(str
Expand Down
3 changes: 3 additions & 0 deletions src/cljc/orcpub/dnd/e5/weapons.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@
::ranged? true,
::heavy? true,
::range {::min 150, ::max 600},
::ammunition? true
:key :longbow
::two-handed? true}
{:name "Net" :cost {:num 1 :type :gp} :weight "3 lb.",
Expand All @@ -374,6 +375,8 @@
::thrown true,
::range {::min 5, ::max 15},
:key :net
:summary "restrains"
::description "A Large or smaller creature hit by a net is restrained until it is freed. A net has no effect on creatures that are formless, or creatures that are Huge or larger. A creature can use its action to make a DC 10 Strength check, freeing itself or another creature within its reach on a success. Dealing 5 slashing damage to the net (AC 10) also frees the creature without harming it, ending the effect and destroying the net. When you use an action, bonus action, or reaction to attack with a net, you can make only one attack regardless of the number of attacks you can normally make."
::special? true}
;;;;
;; Community Designed Weapons
Expand Down
18 changes: 13 additions & 5 deletions src/cljc/orcpub/pdf_spec.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -674,10 +674,18 @@
(profs-paragraph armor-profs armor5e/armor-map "Armor")
(profs-paragraph languages language-map "Language")]))))

(defn damage-str [die die-count mod damage-type]
(str (dice/dice-string die-count die mod)
(when damage-type
(str " " (if (keyword? damage-type) (name damage-type) (str damage-type))))))
(defn damage-str
"A weapon's damage for the PDF, e.g. \"1d8+2 slashing\".
Args: die, die-count, mod (the damage modifier), damage-type (keyword or string), and
optionally summary, the weapon's short effect. Returns a string; with no die (the Net) it is
\"None\", or \"None (restrains)\" given a summary, and no modifier."
([die die-count mod damage-type] (damage-str die die-count mod damage-type nil))
([die die-count mod damage-type summary]
(if-not die
(str "None" (when summary (str " (" summary ")")))
(str (dice/dice-string die-count die mod)
(when damage-type
(str " " (if (keyword? damage-type) (name damage-type) (str damage-type))))))))

(defn attacks-and-spellcasting-fields
"The attack fields for `built-char`'s equipped weapons, ammunition excluded: the first three
Expand All @@ -696,7 +704,7 @@
finesse-damage-modifier (char5e/weapon-damage-modifier built-char weapon true)
normal {:name (:name weapon)
:attack-bonus (char5e/best-weapon-attack-modifier built-char weapon)
:damage (damage-str damage-die damage-die-count normal-damage-modifier damage-type)}]
:damage (damage-str damage-die damage-die-count normal-damage-modifier damage-type (:summary weapon))}]
(remove
nil?
[normal
Expand Down
27 changes: 19 additions & 8 deletions src/cljs/orcpub/dnd/e5/views.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -3642,7 +3642,12 @@
[{:keys [num type]}]
(str num " " (common/safe-name type)))

(defn weapon-details [{:keys [::weapon/description
(defn weapon-details
"The expanded details of a weapon row: type, damage type, magic bonuses, reach, range and
properties. Args: the weapon map; damage-modifier-fn, (fn [weapon off-hand?]) giving the
damage modifier for the versatile damage. Returns hiccup. Damage type reads \"None\" with
no damage die (the Net)."
[{:keys [::weapon/description
::weapon/type
::weapon/damage-type
::mi/magical-damage-bonus
Expand All @@ -3663,7 +3668,7 @@
(weapon-details-field "Type" (common/safe-name type))
(if magical-damage-type
(weapon-details-field "Damage Type" (common/safe-name magical-damage-type))
(weapon-details-field "Damage Type" (common/safe-name damage-type)))
(weapon-details-field "Damage Type" (if (::weapon/damage-die weapon) (common/safe-name damage-type) "None")))
(when magical-damage-bonus
(weapon-details-field "Magical Damage Bonus" magical-damage-bonus))
(when magical-attack-bonus
Expand Down Expand Up @@ -3805,7 +3810,11 @@
(svg-icon icon 32)
[:span.m-l-5.f-w-b.f-s-18 title]])

(defn weapons-section-2 []
(defn weapons-section-2
"The character sheet's weapons table: one row per weapon with proficiency, attack and damage
roll buttons, and expandable details. Returns a component fn of the character id. A weapon
with no damage die (the Net) shows \"None\" in place of a damage roll."
[]
(let [expanded-details (r/atom {})]
(fn [id]
(let [all-weapons @(subscribe [::char/all-weapons id])
Expand Down Expand Up @@ -3857,11 +3866,13 @@
(str name " attack: ")
(str "1d20" (common/mod-str (weapon-attack-modifier weapon)))
:text (str "1d20" (common/mod-str (weapon-attack-modifier weapon))))]
[:td (roll-button
(str name " damage: ")
(str damage-die-count "d" damage-die (common/mod-str (weapon-damage-modifier weapon)))
:text (str damage-die-count "d" damage-die (common/mod-str (weapon-damage-modifier weapon)))
:style {:width "100%"})
[:td (if damage-die
(roll-button
(str name " damage: ")
(str damage-die-count "d" damage-die (common/mod-str (weapon-damage-modifier weapon)))
:text (str damage-die-count "d" damage-die (common/mod-str (weapon-damage-modifier weapon)))
:style {:width "100%"})
[:span.p-10 "None"])
(when versatile
(roll-button
(str name " versatile damage: ")
Expand Down
46 changes: 44 additions & 2 deletions test/clj/orcpub/dnd/e5/srd_equipment_test.clj
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
(ns orcpub.dnd.e5.srd-equipment-test
"The app's SRD weapons and armor carry the costs and weights of the SRD 5.1 equipment tables,
as recorded in the served file resources/public/srd/2014/equipment.edn."
"The app's SRD weapons and armor match the SRD 5.1 equipment tables as recorded in the served
file resources/public/srd/2014/equipment.edn (costs, weights, ammunition flags), and a weapon
with no damage dice (the Net) shows as dealing no damage."
(:require [clojure.test :refer [deftest is testing]]
[clojure.edn :as edn]
[clojure.java.io :as io]
[clojure.string :as s]
[clojure.walk :as walk]
[orcpub.common :as common]
[orcpub.dnd.e5.display :as disp]
[orcpub.pdf-spec :as pdf]
[orcpub.dnd.e5.weapons :as weapons]
[orcpub.dnd.e5.armor :as armor]))

Expand Down Expand Up @@ -46,6 +50,44 @@
(is (= :martial-ranged (by-key :net)))
(is (= :simple-melee (by-key :javelin)) "a thrown melee weapon stays melee"))))

(deftest ammunition-weapons-are-flagged
(let [by-key (into {} (map (juxt :key identity)) weapons/weapons)]
(doseq [{:keys [key properties]} (:weapons (equipment-file))
:when (some #(= :ammunition (:key %)) properties)]
(testing (name key)
(is (::weapons/ammunition? (by-key key)) "an SRD ammunition weapon carries the app's ammunition flag")))))

(defn rules-text
"The served 2014 rules as one string: every text node, spaces collapsed, so a sentence split by
a link (\"is \" [:a ... \"restrained\"] \" until\") reads whole."
[]
(let [strs (atom [])]
(walk/postwalk #(do (when (string? %) (swap! strs conj %)) %)
(some-> (io/resource "public/srd/2014/rules.edn") slurp edn/read-string))
(s/replace (s/join " " @strs) #"\s+" " ")))

(deftest a-weapon-without-damage-shows-none
(let [net (weapons/weapons-map :net)
sword (weapons/weapons-map :longsword)
attack-text #(disp/attack-description (assoc % :attack-type :ranged :attack-modifier 5 :damage-modifier 2))
pdf-damage #(pdf/damage-str (::weapons/damage-die %) (::weapons/damage-die-count %) 2 (::weapons/damage-type %) (:summary %))]
(testing "the Net: no dice, so no damage and no modifier"
(is (not (s/includes? (attack-text net) "d+")))
(is (s/includes? (attack-text net) "no damage")))
(testing "small spaces get the short summary"
(is (<= (count (:summary net)) 12) "fits the table row and the PDF box")
(is (s/includes? (attack-text net) (:summary net)))
(is (= (str "None (" (:summary net) ")") (pdf-damage net))))
(testing "the details get the SRD's own words"
(is (seq (::weapons/description net)))
(is (s/includes? (rules-text) (::weapons/description net)) "verbatim from the served SRD rules"))
(testing "a weapon with dice is unchanged"
(is (s/includes? (attack-text sword) "1d8+2 slashing damage"))
(is (= "1d8+2 slashing" (pdf-damage sword))))
(testing "a magic weapon's own damage type replaces the base one (the Sun Blade)"
(let [sun-blade (assoc sword :orcpub.dnd.e5.magic-items/magical-damage-type :radiant)]
(is (s/includes? (attack-text sun-blade) "1d8+2 radiant damage"))))))

(deftest armor-carries-srd-cost-and-weight
(let [srd (by-name (map #(update % :name (fn [n] (s/replace n #"(?i) armor$" ""))) (:armor (equipment-file))))
app (by-name armor/armor)]
Expand Down
Loading
Loading