Skip to content

Commit 364d971

Browse files
authored
feat: auto-generate character names + fix grappler SRD detection
Auto-save summary uses descriptive label from race/class/level when character name is blank (e.g., "High Elf Ranger 3"). Manual save generates a random race-appropriate name via random.cljc generators and persists it to the entity. Display fallback in character lists, party dropdowns, and encounter selectors. Also adds Grappler to builtin-feats in content reconciliation - the only SRD feat was missing from the exclusion set, causing a false "missing content" warning.
1 parent 4133dd1 commit 364d971

3 files changed

Lines changed: 86 additions & 28 deletions

File tree

src/cljs/orcpub/dnd/e5/content_reconciliation.cljs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,9 @@
178178
#{:champion :berserker :lore :life :land :open-hand
179179
:devotion :hunter :thief :draconic :fiend :evocation})
180180

181+
;; Grappler is the only SRD feat (feats5e/feats-plugin, hardcoded).
182+
(def ^:private builtin-feats #{:grappler})
183+
181184
(defn- builtin?
182185
"True if this key is SRD built-in content that won't appear in plugin subs."
183186
[k content-type]
@@ -187,6 +190,7 @@
187190
:race (contains? builtin-races k)
188191
:subrace (contains? builtin-races k)
189192
:background (contains? builtin-backgrounds k)
193+
:feat (contains? builtin-feats k)
190194
false))
191195

192196
;; ============================================================================

src/cljs/orcpub/dnd/e5/events.cljs

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,23 @@
360360
[:set-character character]
361361
[::char5e/set-character id character]]})))
362362

363+
(defn descriptive-character-label
364+
"Build a descriptive label like 'High Elf Ranger 3' from character properties.
365+
Used as the summary name when the user hasn't set a character name."
366+
[race subrace classes levels]
367+
(let [race-part (or subrace race)
368+
class-parts (when (seq classes)
369+
(s/join "/"
370+
(map (fn [cls]
371+
(let [{:keys [class-name class-level]} (levels cls)]
372+
(str class-name " " class-level)))
373+
classes)))]
374+
(cond
375+
(and race-part class-parts) (str race-part " " class-parts)
376+
race-part race-part
377+
class-parts class-parts
378+
:else "Adventurer")))
379+
363380
(defn make-summary [built-char]
364381
(let [classes (char5e/classes built-char)
365382
levels (char5e/levels built-char)
@@ -375,10 +392,12 @@
375392
hair (char5e/hair built-char)
376393
eyes (char5e/eyes built-char)
377394
skin (char5e/skin built-char)
378-
;alignment (char5e/get-prop built-char ::alignment) ;This is not available?
379-
;background (char5e/get-prop built-char ::background) ;This is not available?
380-
]
381-
(cond-> {::char5e/character-name (or character-name "")}
395+
;; When user hasn't set a name, auto-generate a descriptive label
396+
;; for the summary (what lists/parties display).
397+
display-name (if (s/blank? character-name)
398+
(descriptive-character-label race subrace classes levels)
399+
character-name)]
400+
(cond-> {::char5e/character-name display-name}
382401
image-url (assoc ::char5e/image-url image-url)
383402
faction-image-url (assoc ::char5e/faction-image-url faction-image-url)
384403
race (assoc ::char5e/race-name race)
@@ -433,18 +452,31 @@
433452
{:dispatch [:show-error-message "You must provide values for all ability scores"]}))))))
434453

435454
;; Manual save — dispatched from character builder UI with built-char in scope.
455+
;; If the user hasn't set a name, generates a random one and persists it.
436456
(reg-event-fx
437457
:save-character
438458
(fn [{:keys [db]} [_ built-character]]
439-
(let [{:keys [:db/id] :as strict} (char5e/to-strict (:character db))
440-
summary (make-summary built-character)]
459+
(let [character-name (char5e/character-name built-character)
460+
needs-name? (s/blank? character-name)
461+
;; Generate a random name for unnamed characters on manual save
462+
rand-name (when needs-name? (generate-random-name built-character))
463+
;; Update entity in db so the name persists across future edits
464+
db' (if needs-name?
465+
(assoc-in db [:character ::entity/values ::char5e/character-name] rand-name)
466+
db)
467+
{:keys [:db/id] :as strict} (char5e/to-strict (:character db'))
468+
summary (cond-> (make-summary built-character)
469+
;; Override summary name with the generated name
470+
;; (make-summary produced a descriptive label since entity was blank)
471+
needs-name? (assoc ::char5e/character-name rand-name))]
441472
(if (every?
442473
(fn [ability-kw]
443474
(nat-int? (get-in built-character [:base-abilities ability-kw])))
444475
char5e/ability-keys)
445-
{:dispatch [:set-loading true]
476+
{:db db'
477+
:dispatch [:set-loading true]
446478
:http {:method :post
447-
:headers (authorization-headers db)
479+
:headers (authorization-headers db')
448480
:url (url-for-route routes/dnd-e5-char-list-route)
449481
:transit-params (assoc strict :orcpub.entity.strict/summary summary)
450482
:on-success [:character-save-success]}}
@@ -1191,22 +1223,25 @@
11911223
character-interceptors
11921224
update-value-field)
11931225

1226+
(defn generate-random-name
1227+
"Generate a random name from the built character's race/subrace/sex.
1228+
Falls back to a random human name for unsupported or custom races."
1229+
[built-char]
1230+
(let [race-kw (common/name-to-kw (char5e/race built-char) "orcpub.dnd.e5.character.random")
1231+
subrace-kw (common/name-to-kw (char5e/subrace built-char) "orcpub.dnd.e5.character.random")
1232+
sex-kw (common/name-to-kw (char5e/sex built-char) "orcpub.dnd.e5.character.random")]
1233+
(:name (char-rand5e/random-name-result
1234+
{:race race-kw
1235+
:subrace (when (= ::char-rand5e/human race-kw) subrace-kw)
1236+
:sex sex-kw}))))
1237+
11941238
;; Generate a random name based on character's race/subrace/sex.
11951239
;; built-char passed from component (description-fields).
11961240
(reg-event-fx
11971241
::char5e/set-random-name
11981242
(fn [_ [_ built-char]]
1199-
(let [race-name (char5e/race built-char)
1200-
race-kw (common/name-to-kw race-name "orcpub.dnd.e5.character.random")
1201-
subrace-name (char5e/subrace built-char)
1202-
subrace-kw (common/name-to-kw subrace-name "orcpub.dnd.e5.character.random")
1203-
sex (char5e/sex built-char)
1204-
sex-kw (common/name-to-kw sex "orcpub.dnd.e5.character.random")]
1205-
{:dispatch [:update-value-field ::char5e/character-name (:name
1206-
(char-rand5e/random-name-result
1207-
{:race race-kw
1208-
:subrace (when (= ::char-rand5e/human race-kw) subrace-kw)
1209-
:sex sex-kw}))]})))
1243+
{:dispatch [:update-value-field ::char5e/character-name
1244+
(generate-random-name built-char)]}))
12101245
(reg-event-db
12111246
:select-option
12121247
character-interceptors

src/cljs/orcpub/dnd/e5/views.cljs

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,24 @@
15011501
"unfollow"
15021502
"follow")])]))
15031503

1504+
(defn character-display-name
1505+
"Return the character's name, or a descriptive fallback like 'High Elf Ranger 3'
1506+
built from race/class/level when the name is blank. Works on summary maps."
1507+
[{:keys [::char/character-name ::char/race-name ::char/subrace-name ::char/classes]}]
1508+
(if (s/blank? character-name)
1509+
(let [race-part (or subrace-name race-name)
1510+
class-str (when (seq classes)
1511+
(s/join "/"
1512+
(map (fn [{:keys [::char/class-name ::char/level]}]
1513+
(str class-name " " level))
1514+
classes)))]
1515+
(cond
1516+
(and race-part class-str) (str race-part " " class-str)
1517+
race-part race-part
1518+
class-str class-str
1519+
:else "New Character"))
1520+
character-name))
1521+
15041522
(defn character-summary-2 [{:keys [::char/character-name
15051523
::char/image-url
15061524
::char/race-name
@@ -1514,19 +1532,21 @@
15141532
::char/skin
15151533
::char/classes
15161534
::char/alignment
1517-
::char/background]}
1535+
::char/background]
1536+
:as summary}
15181537
include-name?
15191538
owner
15201539
show-owner?
15211540
show-follow?]
1522-
(let [username @(subscribe [:username])]
1541+
(let [username @(subscribe [:username])
1542+
display-name (when include-name? (character-display-name summary))]
15231543
[:div.flex.justify-cont-s-b.w-100-p.align-items-c
15241544
[:div.flex.align-items-c.align-items-t
15251545
(when image-url
15261546
[:img.m-r-20.m-t-10.m-b-10.image-character-thumbnail {:src image-url }])
15271547
[:div.flex.character-summary.m-t-20.m-b-20
1528-
(when (and character-name include-name?) [:span.m-r-20.m-b-5
1529-
[:span.character-name character-name]
1548+
(when display-name [:span.m-r-20.m-b-5
1549+
[:span.character-name display-name]
15301550
[:div.f-s-12.m-t-5.opacity-6.character-background background]
15311551
[:div.f-s-12.m-t-5.opacity-6.character-alignment alignment]
15321552
(when (not (s/blank? age)) [:div.f-s-12.m-t-5.opacity-6.character-age "Age: " age])
@@ -6643,10 +6663,9 @@
66436663
{:items (cons
66446664
{:title "<select character>"}
66456665
(map
6646-
(fn [{:keys [::char/character-name
6647-
::char/race-name
6666+
(fn [{:keys [::char/race-name
66486667
::char/classes] :as character-summary}]
6649-
{:title (str character-name
6668+
{:title (str (character-display-name character-summary)
66506669
" - "
66516670
race-name
66526671
" "
@@ -8298,8 +8317,8 @@
82988317
(fn [{:keys [:db/id]}]
82998318
(character-ids id)))
83008319
(map
8301-
(fn [{:keys [:db/id ::char/character-name]}]
8302-
{:name character-name
8320+
(fn [{:keys [:db/id] :as char-summary}]
8321+
{:name (character-display-name char-summary)
83038322
:key id})))
83048323
@(subscribe [::char/characters]))
83058324
(fn [e]

0 commit comments

Comments
 (0)