natural_20 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: