feat(dr): player-supplied custom prep/cast/invoke messages - #1528
feat(dr): player-supplied custom prep/cast/invoke messages#1528MahtraDR wants to merge 3 commits into
Conversation
Let players teach Lich the flavor text of altered/unusual spells, cambrinth
foci, and runestones so DRCA's prepare?/cast?/invoke stop waiting out the bput
timeout on messages that aren't in base-spells.yaml.
Adds three optional, case-insensitive (regex-capable) settings, merged on top of
the built-in message lists (never replacing them):
- custom_invoke_message per-character (ritual focus / cambrinth) and per-spell
in a waggle entry (runestone "prepare my <rune>")
- custom_prep_message per-spell (falls back to the existing custom_spell_prep)
- custom_cast_message per-spell
A shared guard (custom_message_pattern) trims and compiles each entry with
IGNORECASE, dropping non-strings, blanks (which would compile to // and match
every line), and invalid regexes (which would raise in bput). Merging flows
through a single with_custom_messages helper. Patterns are unanchored, matching
as substrings like the built-ins.
Backward compatible: prepare? gains an optional custom_invoke_message: keyword and
cast? an optional trailing custom_cast_message positional, both defaulted; all
existing callers and the custom_spell_prep setting are unchanged. Change is
contained to common-arcana.rb; no core files or base-spells.yaml touched.
Tests: adversarial + DAMP specs for the guard/merge (hostile inputs, whitespace,
invalid regex, list-under-singular-key) plus end-to-end wiring through the real
prepare?/cast?/invoke. Rubocop clean; full suite green.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review in full |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== PR metadata =='
gh pr view 1528 --repo elanthia-online/lich-5 --json number,title,author,baseRefName,headRefName,changedFiles,additions,deletions,mergeable,state,url
echo '== Changed files =='
git diff --name-status "origin/$(gh pr view 1528 --repo elanthia-online/lich-5 --json baseRefName --jq .baseRefName)"...HEAD
echo '== Diff (no context truncation) =='
git diff --find-renames --find-copies --unified=80 "origin/$(gh pr view 1528 --repo elanthia-online/lich-5 --json baseRefName --jq .baseRefName)"...HEAD
echo '== Ruby outlines for changed files =='
git diff --name-only "origin/$(gh pr view 1528 --repo elanthia-online/lich-5 --json baseRefName --jq .baseRefName)"...HEAD \
| while IFS= read -r f; do
case "$f" in
*.rb)
echo "--- $f ---"
ast-grep outline "$f" --items all --type class,module,function,method || true
;;
esac
doneLength of output: 67191 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== prepare? call sites =='
rg -n -P '\bprepare\?\s*\(' -g '*.rb' -C 3 .
echo '== cast? call sites =='
rg -n -P '\bcast\?\s*\(' -g '*.rb' -C 3 .
echo '== invoke_messages/custom_invoke_message/custom_spell_prep usages =='
rg -n -P '\b(invoke_messages|custom_invoke_message|custom_prep_message|custom_cast_message|custom_spell_prep)\b' -g '*.rb' -g '*.yaml' -C 3 .
echo '== bput definitions and pattern normalization =='
rg -n -P '^\s*def\s+bput\b|\bbput\s*=' -g '*.rb' -C 30 .
echo '== Settings retrieval and settings object definitions =='
rg -n -P '^\s*def\s+get_settings\b|^\s*(class|struct)\s+\w*Settings|def\s+method_missing\b' -g '*.rb' -C 15 .
echo '== Relevant source and spec setup =='
sed -n '1,130p' lib/dragonrealms/commons/common-arcana.rb
sed -n '1,180p' spec/lib/dragonrealms/commons/common_arcana_spec.rbLength of output: 120725 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== Ruby version declarations =='
for f in .ruby-version Gemfile gemspec *.gemspec; do
if [ -f "$f" ]; then
echo "--- $f ---"
rg -n -P 'ruby|required_ruby_version|filter_map' "$f" -C 2 || true
fi
done
echo '== SetupFiles settings loading and OpenStruct conversion =='
ast-grep outline lib/common/setup_files.rb --items all --type method,function
rg -n -P 'OpenStruct|transform|deep_merge|merge|to_h|settings' lib/common/setup_files.rb -C 10
echo '== Exact changed regions and line numbers =='
nl -ba lib/dragonrealms/commons/common-arcana.rb | sed -n '340,415p;435,470p;505,555p;860,910p;990,1025p'
echo '== Existing use of filter_map =='
rg -n -P '\.filter_map\b' -g '*.rb' -C 2 .Length of output: 8638 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '== Ruby version declarations =='
if [ -f .ruby-version ]; then
echo '--- .ruby-version ---'
cat .ruby-version
fi
if [ -f Gemfile ]; then
echo '--- Gemfile Ruby declarations ---'
rg -n -i -P '^\s*ruby\s+|required_ruby_version|ruby_version' Gemfile -C 2 || true
fi
find . -maxdepth 2 -type f \( -name '*.gemspec' -o -name 'gemspec' \) -print | while IFS= read -r f; do
echo "--- $f ---"
rg -n -i -P 'required_ruby_version|ruby_version' "$f" -C 2 || true
done
echo '== Existing filter_map usage =='
rg -n -P '\.filter_map\b' -g '*.rb' -C 2 . || true
echo '== DragonRealms settings transform configuration =='
fd -i 'settings*config*.rb' lib . || true
rg -n -P 'TRANSFORM_CONFIG|custom_spell_prep|SettingsTransformer' -g '*.rb' -C 12 lib
echo '== SettingsTransformer unknown-key behavior =='
fd -i 'settings_transformer.rb' lib . -x sh -c 'echo "--- $1 ---"; sed -n "1,280p" "$1"' sh {} || true
echo '== Changed implementation regions =='
sed -n '350,415p;440,470p;510,555p;865,905p;1008,1025p' lib/dragonrealms/commons/common-arcana.rbLength of output: 57907
Findings
I found no other correctness issues in the changed behavior. 🧠 Learnings usedYou are interacting with an AI system. |
…fallback Addresses CodeRabbit's P2 review on PR elanthia-online#1528. The prepare? call sites used `data['custom_prep_message'] || settings['custom_spell_prep']`, which selects by truthiness: a blank/invalid/non-string per-spell value (e.g. " " or "bad(") is truthy, so || picked it and shadowed the valid global custom_spell_prep; the guard then dropped the bad per-spell value, leaving no prep message and a full bput timeout for a player who had a valid global set. Pass both the per-spell and the global value to with_custom_messages via a new custom_spell_prep: keyword on prepare?; each is validated independently, so a bad per-spell value no longer suppresses a valid global. This also restores the pre-PR behavior where the global custom_spell_prep applied to every spell. Tests: regression coverage for a blank and an invalid-regex per-spell message alongside a valid global custom_spell_prep. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@coderabbitai Valid finding — confirmed and fixed in 2bd6dbc. The three Fix: both the per-spell and the global value are now passed to I took your "pass both to the merge helper" option (additive) rather than validity-based replace, since these are additive Added regression specs: a blank per-spell message, and an invalid-regex per-spell message, each alongside a valid global |
|
Tip For best results, initiate chat on the files or code changes.
The regression coverage addresses the reported failure mode. ✏️ Learnings added
If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit! You are interacting with an AI system. |
mrhoribu
left a comment
There was a problem hiding this comment.
PR Review: feat(dr): player-supplied custom prep/cast/invoke messages (lich-5#1528)
Reviewed: 2 files, +379/-15, at d127e21f (head of feature/dr-custom-spell-messages, merged with main @ 4ed650ba) on 2026-08-21. Based on: full clone (history, blame, working tree, test suite, rubocop).
Verdict: Approve
Summary: Adds three new optional, regex-capable, case-insensitive player settings (custom_invoke_message, custom_prep_message, custom_cast_message) so DRCA's prepare?/cast?/invoke stop timing out on altered/flavor-text game responses that aren't in base-spells.yaml. The new messages are always additive to the built-in lists via a shared, well-guarded merge helper, and every one of the four internal call sites was updated consistently. 142 specs and rubocop pass clean.
Verification performed
- Traced all four internal call sites of
prepare?/cast?(ritual,cast_spell,crafting_cast_spell,crafting_prepare_spell) — all pass the new params consistently; confirmed viagrepthat no caller ofprepare?/cast?/invokeexists outsidecommon-arcana.rb, so there's no external blast radius to check. - Confirmed the new per-spell keys (
custom_prep_message,custom_cast_message,custom_invoke_message) actually survive the settings pipeline:SettingsTransformer.enrich_spells(lib/common/settings_transformer.rb:60) merges basespell_dataunder the player'sspell_settingfor everywaggle_set_keysentry (waggle_sets, perlib/dragonrealms/dependency/settings_config.rb:21), with the player's hash winning the merge — so these new keys aren't stripped or overwritten by base-spell enrichment. - Confirmed
get_settingsreturns anOpenStruct(default) or aSettingsTransformer-builtOpenStruct(game-specific), soget_settings.custom_invoke_messagereturnsnil, not an exception, when unset — matching the test doubles in the spec (OpenStruct.new(...)). - Ran the full spec file: 142 examples, 0 failures. Ran rubocop on both changed files: no offenses.
- Manually traced the regression the second commit fixes: with
custom_prep_message: ' 'andcustom_spell_prep: 'Valid global prep',with_custom_messagesnow validates each independently viafilter_map, so the blank per-spell value is dropped and the valid global still gets appended — confirmed by the spec atspec/lib/dragonrealms/commons/common_arcana_spec.rb:422.
What this PR gets right
- The guard is genuinely defensive and tested adversarially:
custom_message_pattern(lib/dragonrealms/commons/common-arcana.rb:349) rejects non-strings, blank/whitespace-only strings (which would otherwise compile to//and match every line — a real footgun caught before it could ship), and invalid regex (caught viarescue RegexpErrorrather than raising insidebput). All three failure modes have dedicated specs. - Independent validation of per-spell vs. global prep message (the second commit) is a real fix for a real bug that would have silently broken existing
custom_spell_prepusers the moment they set any (even blank) per-spell value — good catch, and it shipped with regression tests. - Non-mutation of the base list is explicitly tested (
with_custom_messages (merge helper)→does not mutate the base list it was given), which matters sinceget_data('spells').prep_messagesetc. are shared, cached data. - Consistent parameter threading across all four call sites, verified by direct inspection rather than trust.
Coverage notes
Full clone with history (git blame, git log -S), working tree at head, full test suite run, and rubocop run — no gaps in access. Not independently verified: in-game behavior of bput with a mixed string/Regexp pattern array (matches existing behavior for other Regexp entries already used elsewhere in base-spells.yaml, e.g. cast message list, so this isn't a new code path for bput itself) — this is a runtime confirmation the PR author is better placed to do than a static review.
Problem
DRCA's
prepare?,cast?, andinvokematch game responses against the message lists inbase-spells.yaml. Players routinely get spells altered to have unique-to-them flavor text (and use unusual cambrinth foci / runestones) whose lines aren't in the shared lists. When a message isn't recognized,bputwaits out its full timeout on every prep/cast/invoke — slow and spammy — with no way for a player to fix it short of a Lich release.Solution
Three optional player settings, merged on top of the built-in lists (they never replace them):
custom_invoke_messageprepare my <rune>custom_prep_messagecustom_spell_prepsetting)custom_cast_messageEach entry is a single string, matched case-insensitively and regex-capable.
Safety
A shared guard (
custom_message_pattern) trims and compiles each entry withRegexp::IGNORECASE, and drops it (rather than applying it) when it is://and would match every line), orbput).Patterns are unanchored, so they match as substrings the way the built-ins do.
Backward compatibility
prepare?gains an optionalcustom_invoke_message:keyword;cast?gains an optional trailingcustom_cast_messagepositional. Both default tonil; existing internal and external callers are unaffected.custom_spell_prepsetting still works and is the fallback forcustom_prep_message.base-spells.yamlor any core file — the whole change lives incommon-arcana.rb.Tests
Adversarial + DAMP specs covering the guard and merge helper (hostile inputs — non-string types, empty/whitespace, invalid regex, list-under-a-singular-key), the per-character/per-spell invoke merge, runestone vs non-runestone behavior, and end-to-end wiring through the real
invoke/prepare?/cast?. Rubocop clean; full suite green.Notes
validate.lic); a duplicate here is merely redundant, not harmful.🤖 Generated with Claude Code