natural_20 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +13 -0
  4. data/README.md +1 -0
  5. data/bin/nat20 +2 -1
  6. data/bin/nat20.cmd +0 -0
  7. data/char_classes/wizard.yml +89 -0
  8. data/characters/high_elf_mage.yml +27 -0
  9. data/fixtures/battle_sim_objects.yml +2 -2
  10. data/fixtures/high_elf_mage.yml +28 -0
  11. data/fixtures/large_map.yml +63 -0
  12. data/game.yml +2 -2
  13. data/items/equipment.yml +30 -0
  14. data/items/objects.yml +33 -29
  15. data/items/spells.yml +58 -0
  16. data/items/weapons.yml +78 -18
  17. data/lib/CHANGELOG.md +0 -0
  18. data/lib/natural_20.rb +9 -0
  19. data/lib/natural_20/actions/action.rb +2 -2
  20. data/lib/natural_20/actions/attack_action.rb +76 -67
  21. data/lib/natural_20/actions/concerns/action_damage.rb +3 -1
  22. data/lib/natural_20/actions/dash_action.rb +7 -10
  23. data/lib/natural_20/actions/disengage_action.rb +11 -12
  24. data/lib/natural_20/actions/dodge_action.rb +7 -8
  25. data/lib/natural_20/actions/escape_grapple_action.rb +16 -18
  26. data/lib/natural_20/actions/first_aid_action.rb +14 -16
  27. data/lib/natural_20/actions/grapple_action.rb +24 -28
  28. data/lib/natural_20/actions/ground_interact_action.rb +1 -3
  29. data/lib/natural_20/actions/help_action.rb +13 -16
  30. data/lib/natural_20/actions/hide_action.rb +7 -9
  31. data/lib/natural_20/actions/interact_action.rb +12 -14
  32. data/lib/natural_20/actions/look_action.rb +14 -15
  33. data/lib/natural_20/actions/move_action.rb +9 -9
  34. data/lib/natural_20/actions/multiattack_action.rb +8 -9
  35. data/lib/natural_20/actions/prone_action.rb +4 -6
  36. data/lib/natural_20/actions/short_rest_action.rb +7 -8
  37. data/lib/natural_20/actions/shove_action.rb +20 -24
  38. data/lib/natural_20/actions/spell_action.rb +89 -0
  39. data/lib/natural_20/actions/stand_action.rb +5 -7
  40. data/lib/natural_20/actions/use_item_action.rb +7 -9
  41. data/lib/natural_20/ai_controller/standard.rb +1 -1
  42. data/lib/natural_20/battle.rb +8 -3
  43. data/lib/natural_20/cli/action_ui.rb +180 -0
  44. data/lib/natural_20/cli/builder/fighter_builder.rb +1 -1
  45. data/lib/natural_20/cli/builder/rogue_builder.rb +10 -10
  46. data/lib/natural_20/cli/builder/wizard_builder.rb +77 -0
  47. data/lib/natural_20/cli/character_builder.rb +9 -4
  48. data/lib/natural_20/cli/commandline_ui.rb +55 -162
  49. data/lib/natural_20/cli/inventory_ui.rb +4 -0
  50. data/lib/natural_20/cli/map_renderer.rb +7 -1
  51. data/lib/natural_20/concerns/attack_helper.rb +53 -0
  52. data/lib/natural_20/concerns/entity.rb +170 -11
  53. data/lib/natural_20/concerns/fighter_actions/second_wind_action.rb +7 -9
  54. data/lib/natural_20/concerns/fighter_class.rb +2 -2
  55. data/lib/natural_20/concerns/spell_attack_helper.rb +33 -0
  56. data/lib/natural_20/concerns/wizard_class.rb +86 -0
  57. data/lib/natural_20/die_roll.rb +2 -2
  58. data/lib/natural_20/event_manager.rb +50 -44
  59. data/lib/natural_20/item_library/base_item.rb +1 -1
  60. data/lib/natural_20/npc.rb +4 -0
  61. data/lib/natural_20/player_character.rb +75 -12
  62. data/lib/natural_20/session.rb +14 -1
  63. data/lib/natural_20/spell_library/firebolt.rb +72 -0
  64. data/lib/natural_20/spell_library/mage_armor.rb +67 -0
  65. data/lib/natural_20/spell_library/mage_hand.rb +2 -0
  66. data/lib/natural_20/spell_library/magic_missile.rb +67 -0
  67. data/lib/natural_20/spell_library/shield.rb +69 -0
  68. data/lib/natural_20/spell_library/spell.rb +31 -0
  69. data/lib/natural_20/utils/weapons.rb +8 -6
  70. data/lib/natural_20/version.rb +1 -1
  71. data/locales/en.yml +44 -8
  72. data/maps/game_map.yml +12 -2
  73. metadata +22 -3
@@ -15,6 +15,11 @@ module Natural20
15
15
  @session
16
16
  end
17
17
 
18
+ # @param session [Natural20::Session]
19
+ def self.set_session(session)
20
+ @session = session
21
+ end
22
+
18
23
  def initialize(root_path = nil)
19
24
  @root_path = root_path.presence || '.'
20
25
  @session_state = {}
@@ -23,6 +28,7 @@ module Natural20
23
28
  @objects = {}
24
29
  @thing = {}
25
30
  @char_classes = {}
31
+ @spells = {}
26
32
  @settings = {
27
33
  manual_dice_roll: false
28
34
  }
@@ -124,6 +130,13 @@ module Natural20
124
130
  end.to_h
125
131
  end
126
132
 
133
+ def load_spell(spell)
134
+ @spells[spell.to_sym] ||= begin
135
+ spells = YAML.load_file(File.join(@root_path, 'items', "spells.yml")).deep_symbolize_keys!
136
+ spells[spell.to_sym].merge(id: spell)
137
+ end
138
+ end
139
+
127
140
  def load_class(klass)
128
141
  @char_classes[klass.to_sym] ||= begin
129
142
  YAML.load_file(File.join(@root_path, 'char_classes', "#{klass}.yml")).deep_symbolize_keys!
@@ -162,7 +175,7 @@ module Natural20
162
175
  end
163
176
 
164
177
  def t(token, options = {})
165
- I18n.t(token, options)
178
+ I18n.t(token, **options)
166
179
  end
167
180
  end
168
181
  end
@@ -0,0 +1,72 @@
1
+ class Natural20::Firebolt < Natural20::Spell
2
+ include Natural20::Cover
3
+ include Natural20::Weapons
4
+ include Natural20::SpellAttackHelper
5
+
6
+ def build_map(action)
7
+ OpenStruct.new({
8
+ param: [
9
+ {
10
+ type: :select_target,
11
+ num: 1,
12
+ range: @properties[:range],
13
+ target_types: %i[enemies]
14
+ }
15
+ ],
16
+ next: lambda { |target|
17
+ action.target = target
18
+ OpenStruct.new({
19
+ param: nil,
20
+ next: lambda {
21
+ action
22
+ }
23
+ })
24
+ }
25
+ })
26
+ end
27
+
28
+ # @param entity [Natural20::Entity]
29
+ # @param battle [Natrual20::Battle]
30
+ # @param spell_action [Natural20::SpellAction]
31
+ def resolve(entity, battle, spell_action)
32
+ target = spell_action.target
33
+
34
+ hit, attack_roll, advantage_mod, cover_ac_adjustments = evaluate_spell_attack(battle, entity, target, @properties)
35
+
36
+ if hit
37
+ level = 1
38
+ level += 1 if entity.level >= 5
39
+ level += 1 if entity.level >= 11
40
+ level += 1 if entity.level >= 17
41
+
42
+ damage_roll = Natural20::DieRoll.roll("#{level}d10", crit: attack_roll.nat_20?, battle: battle, entity: entity,
43
+ description: t('dice_roll.spells.firebolt'))
44
+ [{
45
+ source: entity,
46
+ target: target,
47
+ attack_name: t('spell.firebolt'),
48
+ damage_type: @properties[:damage_type],
49
+ attack_roll: attack_roll,
50
+ damage_roll: damage_roll,
51
+ advantage_mod: advantage_mod,
52
+ damage: damage_roll,
53
+ cover_ac: cover_ac_adjustments,
54
+ type: :spell_damage,
55
+ spell: @properties
56
+ }]
57
+ else
58
+ [{
59
+ type: :spell_miss,
60
+ source: entity,
61
+ target: target,
62
+ attack_name: t('spell.firebolt'),
63
+ damage_type: @properties[:damage_type],
64
+ attack_roll: attack_roll,
65
+ damage_roll: damage_roll,
66
+ advantage_mod: advantage_mod,
67
+ cover_ac: cover_ac_adjustments,
68
+ spell: @properties
69
+ }]
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,67 @@
1
+ class Natural20::MageArmor < Natural20::Spell
2
+ def build_map(action)
3
+ OpenStruct.new({
4
+ param: [
5
+ {
6
+ type: :select_target,
7
+ num: 1,
8
+ range: 5,
9
+ target_types: %i[allies self]
10
+ }
11
+ ],
12
+ next: lambda { |target|
13
+ action.target = target
14
+ OpenStruct.new({
15
+ param: nil,
16
+ next: lambda {
17
+ action
18
+ }
19
+ })
20
+ }
21
+ })
22
+ end
23
+
24
+ def validate!(action)
25
+ @errors.clear
26
+ @errors << :wearing_armor if action.target.wearing_armor?
27
+ end
28
+
29
+ def self.apply!(battle, item)
30
+ case item[:type]
31
+ when :mage_armor
32
+
33
+ item[:source].add_casted_effect({ target: item[:target], effect: item[:effect], expiration: battle.session.game_time + 8.hours.to_i })
34
+ item[:target].register_effect(:ac_override, self, effect: item[:effect], source: item[:source],
35
+ duration: 8.hours.to_i)
36
+ item[:target].register_event_hook(:equip, self, effect: item[:effect], effect: item[:effect],
37
+ source: item[:source],
38
+ duration: 8.hours.to_i)
39
+ Natural20::EventManager.received_event(event: :spell_buf, spell: item[:effect], source: item[:source],
40
+ target: item[:target])
41
+ SpellAction.consume_resource(battle, item)
42
+ end
43
+ end
44
+
45
+ # @param entity [Natural20::Entity]
46
+ # @param effect [Object]
47
+ def self.ac_override(entity, _effect)
48
+ 13 + entity.dex_mod
49
+ end
50
+
51
+ def self.equip(entity, opts = {})
52
+ entity.dismiss_effect!(opts[:effect]) if entity.wearing_armor?
53
+ end
54
+
55
+ # @param entity [Natural20::Entity]
56
+ # @param battle [Natrual20::Battle]
57
+ # @param spell_action [Natural20::SpellAction]
58
+ def resolve(_entity, _battle, spell_action)
59
+ [{
60
+ type: :mage_armor,
61
+ target: spell_action.target,
62
+ source: spell_action.source,
63
+ effect: self,
64
+ spell: @properties
65
+ }]
66
+ end
67
+ end
@@ -0,0 +1,2 @@
1
+ class Natural20::MagicHand < Natural20::Spell
2
+ end
@@ -0,0 +1,67 @@
1
+ class Natural20::MagicMissile < Natural20::Spell
2
+ include Natural20::Cover
3
+ include Natural20::Weapons
4
+ include Natural20::AttackHelper
5
+
6
+ def build_map(action)
7
+ cast_level = action.at_level || 1
8
+ darts = 3 + (cast_level - 1)
9
+
10
+ OpenStruct.new({
11
+ param: [
12
+ {
13
+ type: :select_target,
14
+ num: darts,
15
+ range: @properties[:range],
16
+ target_types: %i[enemies]
17
+ }
18
+ ],
19
+ next: lambda { |target|
20
+ action.target = target
21
+ OpenStruct.new({
22
+ param: nil,
23
+ next: lambda {
24
+ action
25
+ }
26
+ })
27
+ }
28
+ })
29
+ end
30
+
31
+ # @param entity [Natural20::Entity]
32
+ # @param battle [Natrual20::Battle]
33
+ # @param spell_action [Natural20::SpellAction]
34
+ def resolve(entity, battle, spell_action)
35
+ targets = spell_action.target
36
+
37
+ targets.map do |target|
38
+ after_attack_roll_hook(battle, target,
39
+ entity, nil, nil, spell: @properties)
40
+
41
+ if target.has_spell_effect?(:shield)
42
+ {
43
+ source: entity,
44
+ target: target,
45
+ attack_name: t('spell.magic_missile'),
46
+ damage_type: @properties[:damage_type],
47
+ type: :spell_miss,
48
+ spell: @properties
49
+ }
50
+ else
51
+ damage_roll = Natural20::DieRoll.roll('1d4+1', battle: battle, entity: entity,
52
+ description: t('dice_roll.spells.magic_missile'))
53
+
54
+ {
55
+ source: entity,
56
+ target: target,
57
+ attack_name: t('spell.magic_missile'),
58
+ damage_type: @properties[:damage_type],
59
+ damage_roll: damage_roll,
60
+ damage: damage_roll,
61
+ type: :spell_damage,
62
+ spell: @properties
63
+ }
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,69 @@
1
+ class Natural20::Shield < Natural20::Spell
2
+ def build_map(action)
3
+ OpenStruct.new({
4
+ param: nil,
5
+ next: lambda {
6
+ action
7
+ }
8
+ })
9
+ end
10
+
11
+ def self.apply!(battle, item)
12
+ case item[:type]
13
+ when :shield
14
+
15
+ item[:source].add_casted_effect(effect: item[:effect])
16
+ item[:target].register_effect(:ac_bonus, self, effect: item[:effect], source: item[:source],
17
+ duration: 8.hours.to_i)
18
+
19
+ item[:target].register_event_hook(:start_of_turn, self, effect: item[:effect], effect: item[:effect],
20
+ source: item[:source])
21
+ Natural20::EventManager.received_event(event: :spell_buf, spell: item[:effect], source: item[:source],
22
+ target: item[:source])
23
+ SpellAction.consume_resource(battle, item)
24
+ end
25
+ end
26
+
27
+ # @param entity [Natural20::Entity]
28
+ # @param effect [Object]
29
+ def self.ac_bonus(_entity, _effect)
30
+ 5
31
+ end
32
+
33
+ def self.start_of_turn(entity, opts = {})
34
+ entity.dismiss_effect!(opts[:effect])
35
+ end
36
+
37
+ # @param battle [Natural20::Battle]
38
+ # @param entity [Natural20::Entity]
39
+ # @param attacker [Natural20::Entity]
40
+ # @param attack_roll [Natural20::DieRoll]
41
+ # @return [Array<Array<Hash>,Symbol>]
42
+ def self.after_attack_roll(battle, entity, _attacker, attack_roll, effective_ac, opts = {})
43
+ spell = battle.session.load_spell('shield')
44
+ if attack_roll.nil? || attack_roll.result.between?(entity.armor_class, effective_ac + 4)
45
+ [[{
46
+ type: :shield,
47
+ target: entity,
48
+ source: entity,
49
+ effect: Natural20::Shield.new(entity, 'shield', spell),
50
+ spell: spell
51
+ }], false]
52
+ else
53
+ [[], false]
54
+ end
55
+ end
56
+
57
+ # @param entity [Natural20::Entity]
58
+ # @param battle [Natrual20::Battle]
59
+ # @param spell_action [Natural20::SpellAction]
60
+ def resolve(_entity, _battle, spell_action)
61
+ [{
62
+ type: :shield,
63
+ target: spell_action.source,
64
+ source: spell_action.source,
65
+ effect: self,
66
+ spell: @properties
67
+ }]
68
+ end
69
+ end
@@ -0,0 +1,31 @@
1
+ class Natural20::Spell
2
+ attr_accessor :errors
3
+ attr_reader :properties, :source
4
+
5
+ def initialize(source, spell_name, details)
6
+ @name = spell_name
7
+ @properties = details
8
+ @source = source
9
+ @errors = []
10
+ end
11
+
12
+ def label
13
+ t(:"spell.#{@name}")
14
+ end
15
+
16
+ def id
17
+ @properties[:id]
18
+ end
19
+
20
+ def self.apply!(battle, item); end
21
+
22
+ def validate!(_action)
23
+ @errors.clear
24
+ end
25
+
26
+ protected
27
+
28
+ def t(token, options = {})
29
+ I18n.t(token, **options)
30
+ end
31
+ end
@@ -30,10 +30,10 @@ module Natural20::Weapons
30
30
  disadvantage << :armor_proficiency unless source.proficient_with_equipped_armor?
31
31
  advantage << :squeezed if target.squeezed?
32
32
  advantage << :being_helped if battle.help_with?(target)
33
- disadvantage << :target_long_range if battle.map && battle.map.distance(source, target,
34
- entity_1_pos: source_pos) > weapon[:range]
33
+ disadvantage << :target_long_range if battle.map && weapon && battle.map.distance(source, target,
34
+ entity_1_pos: source_pos) > weapon[:range]
35
35
 
36
- if weapon[:type] == 'ranged_attack' && battle.map
36
+ if weapon && weapon[:type] == 'ranged_attack' && battle.map
37
37
  disadvantage << :ranged_with_enemy_in_melee if battle.enemy_in_melee_range?(source, source_pos: source_pos)
38
38
  disadvantage << :target_is_prone_range if target.prone?
39
39
  end
@@ -43,8 +43,10 @@ module Natural20::Weapons
43
43
  advantage << :pack_tactics
44
44
  end
45
45
 
46
- disadvantage << :small_creature_using_heavy if weapon[:properties]&.include?('heavy') && source.size == :small
47
- advantage << :target_is_prone if weapon[:type] == 'melee_attack' && target.prone?
46
+ if weapon && weapon[:properties]&.include?('heavy') && source.size == :small
47
+ disadvantage << :small_creature_using_heavy
48
+ end
49
+ advantage << :target_is_prone if weapon && weapon[:type] == 'melee_attack' && target.prone?
48
50
 
49
51
  advantage << :unseen_attacker if battle.map && !battle.can_see?(target, source, entity_2_pos: source_pos)
50
52
  disadvantage << :invisible_attacker if battle.map && !battle.can_see?(source, target, entity_1_pos: source_pos)
@@ -73,6 +75,6 @@ module Natural20::Weapons
73
75
  damage_mod += 2
74
76
  end
75
77
 
76
- "#{damage_roll}+#{damage_mod}"
78
+ "#{damage_roll}#{damage_mod >= 0 ? "+#{damage_mod}" : damage_mod}"
77
79
  end
78
80
  end
@@ -1,4 +1,4 @@
1
1
  # typed: strict
2
2
  module Natural20
3
- VERSION = "0.1.1"
3
+ VERSION = "0.2.0"
4
4
  end
data/locales/en.yml CHANGED
@@ -1,8 +1,9 @@
1
1
  ---
2
2
  en:
3
3
  action:
4
- attack_action: '%{name} with %{weapon_name} -> Hit: +%{mod} Dmg: %{dmg}'
5
- attack_action_throw: '%{name} with %{weapon_name} (Throw) -> Hit: +%{mod} Dmg: %{dmg}'
4
+ arcane_recovery: Arcane Recovery for %{name}
5
+ attack_action: '%{name} with %{weapon_name} -> Hit: %{mod} Dmg: %{dmg}'
6
+ attack_action_throw: '%{name} with %{weapon_name} (Throw) -> Hit: %{mod} Dmg: %{dmg}'
6
7
  dash: Dash
7
8
  dash_bonus: Bonus Action -> Dash
8
9
  disengage: Disengage
@@ -26,10 +27,14 @@ en:
26
27
  push: Shove (Push Away)
27
28
  short_rest: Short Rest
28
29
  shove: Shove (Knock Prone)
30
+ spell: Spellcasting
31
+ spell_choice: '%{spell} (%{level})'
29
32
  stand: Stand Up
30
33
  use_item: Use Item
34
+ waive_arcane_recovery: Skip Arcane Recovery
31
35
  attack_status:
32
36
  being_helped: Being being_helped
37
+ invisible_attacker: Target is hidden/invisible
33
38
  prone: Target Prone
34
39
  ranged_with_enemy_in_melee: Ranged weapon with enemy in melee range
35
40
  small_creature_using_heavy: Heavy weapon used by small creature
@@ -42,8 +47,6 @@ en:
42
47
  back: Back
43
48
  battle_end: Battle ended in %{num} rounds
44
49
  battle_simulator: Battle Simulator
45
- map_description: 'Battle Map (%{width}x%{length}) %{feet_per_grid}ft per square, pov %{pov}:'
46
- character_action_prompt: '%{name} (%{token}) will'
47
50
  builder:
48
51
  ability_score:
49
52
  fixed: Fixed ability score 15, 14, 13, 12, 10, 8
@@ -84,21 +87,22 @@ en:
84
87
  select_starting_weapon: Select a starting weapon
85
88
  select_starting_weapon_2: Select second weapon set
86
89
  select_languages: Select languages
87
- select_race: Please select a race
88
- select_skill: null
89
- select_subrace: Please select a sub-race
90
+ select_race: Please select a racetargets.first -race
90
91
  skill:
91
92
  acrobatics: Acrobatics
92
93
  animal_handling: Animal Handling
94
+ arcana: Arcana
93
95
  athletics: Athletics
94
96
  deception: Deception
95
97
  history: History
96
98
  insight: Insight
97
99
  intimidation: Intimidation
98
100
  investigation: Investigation
101
+ medicine: Medicine
99
102
  perception: Perception
100
103
  performance: Performance
101
104
  persuasion: Persuasion
105
+ religion: Religion
102
106
  sleight_of_hand: Sleight of Hand
103
107
  stealth: Stealth
104
108
  survival: Survival
@@ -111,6 +115,14 @@ en:
111
115
  masons_tools: Mason's Tools
112
116
  smiths_tools: Smith's Tools
113
117
  wis: Select Wisdom ability score
118
+ wizard:
119
+ select_cantrip: Select Cantrip
120
+ select_prepared_spells: Select prepared spells
121
+ select_skill: Select a wizard skill
122
+ select_spells: Select starting spells in your spellbook
123
+ select_starting_weapon: Select starting weapon
124
+ select_starting_weapon_2: Select starting equipment
125
+ character_action_prompt: '%{name} (%{token}) will'
114
126
  character_builder: Character Builder ...
115
127
  character_sheet:
116
128
  ac: 'AC: %{ac}'
@@ -121,12 +133,14 @@ en:
121
133
  languages: 'Languages:'
122
134
  level: 'Level: %{level}'
123
135
  name: 'Name: %{name}'
136
+ proficiency_bonus: 'Proficiency Bonus: +%{bonus}'
124
137
  race: 'Race: %{race}'
125
138
  skill_mod: '%{prefix} %{skill} %{bonus}'
126
139
  skills: Skills
127
140
  speed: 'Speed: %{speed}ft.'
141
+ spell_attack: 'Spell Attack: +%{spell_attack}'
128
142
  subrace: 'Subrace: %{race}'
129
- character_status_line: 'HP: %{hp}/%{max_hp} actions: %{total_actions} bonus action: %{bonus_action}, movement: %{available_movement} ft. statuses: %{statuses}'
143
+ character_status_line: 'AC: %{ac} HP: %{hp}/%{max_hp} actions: %{total_actions} bonus action: %{bonus_action}, movement: %{available_movement} ft. statuses: %{statuses}'
130
144
  dead_goblin_items: Dead Goblin's items
131
145
  dice_roll:
132
146
  ability_score: 'Rolling for ability score #%{roll_num}'
@@ -151,12 +165,15 @@ en:
151
165
  second_wind: Roll for second wind hp gain
152
166
  sneak_attack: Roll for sneak attack damage
153
167
  special_weapon_damage: Roll for special weapon additional damage
168
+ spells:
169
+ firebolt: Roll for Firebot spell damage
154
170
  stealth: Rolling for stealth check
155
171
  strength_check: Roll for strength (skill check)
156
172
  thieves_tools: Roll for thieves tools (skill check)
157
173
  wisdom_check: Roll for wisdom check
158
174
  dice_roller: Dice Roller
159
175
  drop: Drop Item
176
+ effect_line: '%{effect_name} (%{source})'
160
177
  end_turn: '%{name}: End turn.'
161
178
  engine_title: Welcome to Wizards and Goblins (DnD 5e Adventure Engine)
162
179
  entity:
@@ -203,6 +220,7 @@ en:
203
220
  shove_failure: '%{source_roll} < %{target_roll}. ${source} tried but failed to shove %{target} 5ft away'
204
221
  shove_success: '%{source_roll} >= %{target_roll}. ${source} shoves %{target} 5ft away'
205
222
  something: (something)
223
+ spell_buff: '%{source} casts %{spell} on %{target}.'
206
224
  status:
207
225
  prone: '%{name} is now prone.'
208
226
  stand: '%{name} stands up.'
@@ -232,18 +250,22 @@ en:
232
250
  thieves_cant: Thieves Cant
233
251
  undercommon: Undercommon
234
252
  manual_target: Target object using the map
253
+ map_description: 'Battle Map (%{width}x%{length}) %{feet_per_grid}ft per square, pov %{pov}:'
235
254
  missing_game: missing game.yml in the current folder
236
255
  multiple_target_prompt: Multiple targets at location(s) please select specific targets
256
+ multiple_targets: 'Total Targets: %{total_targets}'
237
257
  'no': 'No'
238
258
  object:
239
259
  Arrows: Arrows
240
260
  ItemLibrary::Chest:
241
261
  loot: Retreive Contents
242
262
  Wooden door key: Wooden Door Key
263
+ arcane_focus: Arcane Focus
243
264
  chain_mail: Chain Mail
244
265
  chest:
245
266
  lock: clink. chest is now locked
246
267
  unlock: Ka-chink! chest is now unlocked
268
+ component_pouch: Component Pouch
247
269
  door:
248
270
  door_blocked: (door is blocked)
249
271
  key_required: (missing key)
@@ -278,6 +300,7 @@ en:
278
300
  longbow: Longbow
279
301
  longbow_and_arrows: Longbow and Arrows
280
302
  longsword: Longsword
303
+ quarterstaff: Quarterstaff
281
304
  rapier: Rapier
282
305
  scimitar: Scimitar
283
306
  shortbow: Shortbow
@@ -303,6 +326,19 @@ en:
303
326
  select_npcs: Select NPCs
304
327
  select_party_member: Select Party Member %{index}
305
328
  skip_hit_die: Skip Hit Die
329
+ spell:
330
+ firebolt: Firebolt
331
+ mage_armor: Mage Armor
332
+ magic_missile: Magic Missile
333
+ shield: Shield
334
+ spell_level: Recover Spell Level %{level}
335
+ spell_level_slots: 'level %{level}: %{slots}'
336
+ spell_slots: Spell Slots
337
+ spells:
338
+ disabled:
339
+ no_action: '- No more actions'
340
+ no_bonus_action: '- No more bonus actions'
341
+ no_spell_slot: '- No more spell slots'
306
342
  tpk: The entire party has fallen. Game Over!
307
343
  unequip: Unequip
308
344
  validation: