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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +13 -0
- data/README.md +1 -0
- data/bin/nat20 +2 -1
- data/bin/nat20.cmd +0 -0
- data/char_classes/wizard.yml +89 -0
- data/characters/high_elf_mage.yml +27 -0
- data/fixtures/battle_sim_objects.yml +2 -2
- data/fixtures/high_elf_mage.yml +28 -0
- data/fixtures/large_map.yml +63 -0
- data/game.yml +2 -2
- data/items/equipment.yml +30 -0
- data/items/objects.yml +33 -29
- data/items/spells.yml +58 -0
- data/items/weapons.yml +78 -18
- data/lib/CHANGELOG.md +0 -0
- data/lib/natural_20.rb +9 -0
- data/lib/natural_20/actions/action.rb +2 -2
- data/lib/natural_20/actions/attack_action.rb +76 -67
- data/lib/natural_20/actions/concerns/action_damage.rb +3 -1
- data/lib/natural_20/actions/dash_action.rb +7 -10
- data/lib/natural_20/actions/disengage_action.rb +11 -12
- data/lib/natural_20/actions/dodge_action.rb +7 -8
- data/lib/natural_20/actions/escape_grapple_action.rb +16 -18
- data/lib/natural_20/actions/first_aid_action.rb +14 -16
- data/lib/natural_20/actions/grapple_action.rb +24 -28
- data/lib/natural_20/actions/ground_interact_action.rb +1 -3
- data/lib/natural_20/actions/help_action.rb +13 -16
- data/lib/natural_20/actions/hide_action.rb +7 -9
- data/lib/natural_20/actions/interact_action.rb +12 -14
- data/lib/natural_20/actions/look_action.rb +14 -15
- data/lib/natural_20/actions/move_action.rb +9 -9
- data/lib/natural_20/actions/multiattack_action.rb +8 -9
- data/lib/natural_20/actions/prone_action.rb +4 -6
- data/lib/natural_20/actions/short_rest_action.rb +7 -8
- data/lib/natural_20/actions/shove_action.rb +20 -24
- data/lib/natural_20/actions/spell_action.rb +89 -0
- data/lib/natural_20/actions/stand_action.rb +5 -7
- data/lib/natural_20/actions/use_item_action.rb +7 -9
- data/lib/natural_20/ai_controller/standard.rb +1 -1
- data/lib/natural_20/battle.rb +8 -3
- data/lib/natural_20/cli/action_ui.rb +180 -0
- data/lib/natural_20/cli/builder/fighter_builder.rb +1 -1
- data/lib/natural_20/cli/builder/rogue_builder.rb +10 -10
- data/lib/natural_20/cli/builder/wizard_builder.rb +77 -0
- data/lib/natural_20/cli/character_builder.rb +9 -4
- data/lib/natural_20/cli/commandline_ui.rb +55 -162
- data/lib/natural_20/cli/inventory_ui.rb +4 -0
- data/lib/natural_20/cli/map_renderer.rb +7 -1
- data/lib/natural_20/concerns/attack_helper.rb +53 -0
- data/lib/natural_20/concerns/entity.rb +170 -11
- data/lib/natural_20/concerns/fighter_actions/second_wind_action.rb +7 -9
- data/lib/natural_20/concerns/fighter_class.rb +2 -2
- data/lib/natural_20/concerns/spell_attack_helper.rb +33 -0
- data/lib/natural_20/concerns/wizard_class.rb +86 -0
- data/lib/natural_20/die_roll.rb +2 -2
- data/lib/natural_20/event_manager.rb +50 -44
- data/lib/natural_20/item_library/base_item.rb +1 -1
- data/lib/natural_20/npc.rb +4 -0
- data/lib/natural_20/player_character.rb +75 -12
- data/lib/natural_20/session.rb +14 -1
- data/lib/natural_20/spell_library/firebolt.rb +72 -0
- data/lib/natural_20/spell_library/mage_armor.rb +67 -0
- data/lib/natural_20/spell_library/mage_hand.rb +2 -0
- data/lib/natural_20/spell_library/magic_missile.rb +67 -0
- data/lib/natural_20/spell_library/shield.rb +69 -0
- data/lib/natural_20/spell_library/spell.rb +31 -0
- data/lib/natural_20/utils/weapons.rb +8 -6
- data/lib/natural_20/version.rb +1 -1
- data/locales/en.yml +44 -8
- data/maps/game_map.yml +12 -2
- metadata +22 -3
data/lib/natural_20/die_roll.rb
CHANGED
@@ -69,7 +69,7 @@ module Natural20
|
|
69
69
|
end
|
70
70
|
|
71
71
|
def t(key, options = {})
|
72
|
-
I18n.t(key, options)
|
72
|
+
I18n.t(key, **options)
|
73
73
|
end
|
74
74
|
end
|
75
75
|
|
@@ -325,7 +325,7 @@ module Natural20
|
|
325
325
|
end
|
326
326
|
|
327
327
|
def self.t(key, options = {})
|
328
|
-
I18n.t(key, options)
|
328
|
+
I18n.t(key, **options)
|
329
329
|
end
|
330
330
|
end
|
331
331
|
end
|
@@ -53,10 +53,10 @@ module Natural20
|
|
53
53
|
def self.standard_cli
|
54
54
|
Natural20::EventManager.clear
|
55
55
|
event_handlers = { died: lambda { |event|
|
56
|
-
|
56
|
+
output "#{show_name(event)} died."
|
57
57
|
},
|
58
58
|
unconscious: lambda { |event|
|
59
|
-
|
59
|
+
output "#{show_name(event)} unconscious."
|
60
60
|
},
|
61
61
|
attacked: lambda { |event|
|
62
62
|
advantage_mod = event[:advantage_mod]
|
@@ -68,7 +68,7 @@ module Natural20
|
|
68
68
|
if event[:cover_ac].try(:positive?)
|
69
69
|
cover_str = " (behind cover +#{event[:cover_ac]} ac)"
|
70
70
|
end
|
71
|
-
|
71
|
+
Time.at(82800).utc.strftime("%I:%M%p")
|
72
72
|
advantage_str = if advantage_mod&.positive?
|
73
73
|
' with advantage'.colorize(:green)
|
74
74
|
elsif advantage_mod&.negative?
|
@@ -77,7 +77,7 @@ module Natural20
|
|
77
77
|
''
|
78
78
|
end
|
79
79
|
str_token = event[:attack_roll] ? 'event.attack' : 'event.attack_no_roll'
|
80
|
-
|
80
|
+
output t(str_token, opportunity: event[:as_reaction] ? 'Opportunity Attack: ' : '',
|
81
81
|
source: show_name(event),
|
82
82
|
target: "#{event[:target].name}#{cover_str}",
|
83
83
|
attack_name: event[:attack_name],
|
@@ -87,126 +87,128 @@ module Natural20
|
|
87
87
|
damage: damage_str)
|
88
88
|
},
|
89
89
|
damage: lambda { |event|
|
90
|
-
|
90
|
+
output "#{show_name(event)} #{event[:source].describe_health}"
|
91
91
|
},
|
92
92
|
miss: lambda { |event|
|
93
93
|
advantage_mod = event[:advantage_mod]
|
94
|
-
advantage_str = if advantage_mod
|
94
|
+
advantage_str = if advantage_mod&.positive?
|
95
95
|
' with advantage'.colorize(:green)
|
96
|
-
elsif advantage_mod
|
96
|
+
elsif advantage_mod&.negative?
|
97
97
|
' with disadvantage'.colorize(:red)
|
98
98
|
else
|
99
99
|
''
|
100
100
|
end
|
101
|
-
|
101
|
+
output "#{event[:as_reaction] ? 'Opportunity Attack: ' : ''} rolled #{advantage_str} #{event[:attack_roll]} ... #{event[:source].name&.colorize(:blue)} missed his attack #{event[:attack_name].colorize(:red)} on #{event[:target].name.colorize(:green)}"
|
102
102
|
},
|
103
103
|
initiative: lambda { |event|
|
104
|
-
|
104
|
+
output "#{show_name(event)} rolled a #{event[:roll]} = (#{event[:value]}) with dex tie break for initiative."
|
105
105
|
},
|
106
106
|
move: lambda { |event|
|
107
|
-
|
107
|
+
output "#{show_name(event)} moved #{(event[:path].size - 1) * event[:feet_per_grid]}ft."
|
108
108
|
},
|
109
109
|
dodge: lambda { |event|
|
110
|
-
|
110
|
+
output t('event.dodge', name: show_name(event))
|
111
111
|
},
|
112
112
|
help: lambda { |event|
|
113
|
-
|
113
|
+
output "#{show_name(event)} is helping to attack #{event[:target].name&.colorize(:red)}"
|
114
114
|
},
|
115
115
|
second_wind: lambda { |event|
|
116
|
-
|
116
|
+
output SecondWindAction.describe(event)
|
117
117
|
},
|
118
118
|
heal: lambda { |event|
|
119
|
-
|
119
|
+
output "#{show_name(event)} heals for #{event[:value]}hp"
|
120
120
|
},
|
121
121
|
hit_die: lambda { |event|
|
122
|
-
|
122
|
+
output t('event.hit_die', source: show_name(event), roll: event[:roll].to_s,
|
123
123
|
value: event[:roll].result)
|
124
124
|
},
|
125
125
|
object_interaction: lambda { |event|
|
126
126
|
if event[:roll]
|
127
|
-
|
127
|
+
output "#{event[:roll]} = #{event[:roll].result} -> #{event[:reason]}"
|
128
128
|
else
|
129
|
-
|
129
|
+
output (event[:reason]).to_s
|
130
130
|
end
|
131
131
|
},
|
132
132
|
perception: lambda { |event|
|
133
|
-
|
133
|
+
output "#{show_name(event)} rolls #{event[:perception_roll]} on perception"
|
134
134
|
},
|
135
135
|
|
136
136
|
death_save: lambda { |event|
|
137
|
-
|
137
|
+
output t('event.death_save', name: show_name(event), roll: event[:roll].to_s, value: event[:roll].result,
|
138
138
|
saves: event[:saves], fails: event[:fails]).colorize(:blue)
|
139
139
|
},
|
140
140
|
|
141
141
|
death_fail: lambda { |event|
|
142
142
|
if event[:roll]
|
143
|
-
|
143
|
+
output t('event.death_fail', name: show_name(event), roll: event[:roll].to_s, value: event[:roll].result,
|
144
144
|
saves: event[:saves], fails: event[:fails]).colorize(:red)
|
145
145
|
else
|
146
|
-
|
146
|
+
output t('event.death_fail_hit', name: show_name(event), saves: event[:saves],
|
147
147
|
fails: event[:fails]).colorize(:red)
|
148
148
|
end
|
149
149
|
},
|
150
150
|
great_weapon_fighting_roll: lambda { |event|
|
151
|
-
|
151
|
+
output t('event.great_weapon_fighting_roll', name: show_name(event),
|
152
152
|
roll: event[:roll], prev_roll: event[:prev_roll])
|
153
153
|
},
|
154
154
|
feature_protection: lambda { |event|
|
155
|
-
|
155
|
+
output t('event.feature_protection', source: event[:source]&.name,
|
156
156
|
target: event[:target]&.name, attacker: event[:attacker]&.name)
|
157
157
|
},
|
158
158
|
prone: lambda { |event|
|
159
|
-
|
159
|
+
output t('event.status.prone', name: show_name(event))
|
160
160
|
},
|
161
161
|
|
162
162
|
stand: lambda { |event|
|
163
|
-
|
163
|
+
output t('event.status.stand', name: show_name(event))
|
164
164
|
},
|
165
165
|
|
166
166
|
%i[acrobatics athletics] => lambda { |event|
|
167
167
|
if event[:success]
|
168
|
-
|
168
|
+
output t("event.#{event[:event]}.success", name: show_name(event), roll: event[:roll],
|
169
169
|
value: event[:roll].result)
|
170
170
|
else
|
171
|
-
|
171
|
+
output t("event.#{event[:event]}.failure", name: show_name(event), roll: event[:roll],
|
172
172
|
value: event[:roll].result)
|
173
173
|
end
|
174
174
|
},
|
175
175
|
|
176
176
|
start_of_combat: lambda { |event|
|
177
|
-
|
177
|
+
output t('event.combat_start')
|
178
178
|
event[:combat_order].each_with_index do |entity_and_initiative, index|
|
179
179
|
entity, initiative = entity_and_initiative
|
180
|
-
|
180
|
+
output "#{index + 1}. #{decorate_name(entity)} #{initiative}"
|
181
181
|
end
|
182
182
|
},
|
183
|
-
|
183
|
+
spell_buff: lambda { |event|
|
184
|
+
output t('event.spell_buff', source: show_name(event), spell: event[:spell].label, target: event[:target]&.name)
|
185
|
+
},
|
184
186
|
end_of_combat: lambda { |_event|
|
185
|
-
|
187
|
+
output t('event.combat_end')
|
186
188
|
},
|
187
189
|
grapple_success: lambda { |event|
|
188
190
|
if event[:target_roll]
|
189
|
-
|
191
|
+
output t('event.grapple_success',
|
190
192
|
source: show_name(event), target: event[:target]&.name,
|
191
193
|
source_roll: event[:source_roll],
|
192
194
|
source_roll_value: event[:source_roll].result,
|
193
195
|
target_roll: event[:target_roll],
|
194
196
|
target_roll_value: event[:target_roll].result)
|
195
197
|
else
|
196
|
-
|
198
|
+
output t('event.grapple_success_no_roll',
|
197
199
|
source: show_name(event), target: event[:target]&.name)
|
198
200
|
end
|
199
201
|
},
|
200
202
|
first_aid: lambda { |event|
|
201
|
-
|
203
|
+
output t('event.first_aid', name: show_name(event),
|
202
204
|
target: event[:target]&.name, roll: event[:roll], value: event[:roll].result)
|
203
205
|
},
|
204
206
|
first_aid_failure: lambda { |event|
|
205
|
-
|
207
|
+
output t('event.first_aid_failure', name: show_name(event),
|
206
208
|
target: event[:target]&.name, roll: event[:roll], value: event[:roll].result)
|
207
209
|
},
|
208
210
|
grapple_failure: lambda { |event|
|
209
|
-
|
211
|
+
output t('event.grapple_failure',
|
210
212
|
source: show_name(event), target: event[:target]&.name,
|
211
213
|
source_roll: event[:source_roll],
|
212
214
|
source_roll_value: event[:source_roll].result,
|
@@ -214,11 +216,11 @@ module Natural20
|
|
214
216
|
target_roll_value: event[:target_roll_value].result)
|
215
217
|
},
|
216
218
|
drop_grapple: lambda { |event|
|
217
|
-
|
219
|
+
output t('event.drop_grapple',
|
218
220
|
source: show_name(event), target: event[:target]&.name)
|
219
221
|
},
|
220
222
|
flavor: lambda { |event|
|
221
|
-
|
223
|
+
output t("event.flavor.#{event[:text]}", source: event[:source]&.name,
|
222
224
|
target: event[:target]&.name)
|
223
225
|
},
|
224
226
|
shove_success: lambda do |event|
|
@@ -230,9 +232,9 @@ module Natural20
|
|
230
232
|
target_roll_value: event[:target_roll].result
|
231
233
|
}
|
232
234
|
if event[:knock_prone]
|
233
|
-
|
235
|
+
output t('event.knock_prone_success', opts)
|
234
236
|
else
|
235
|
-
|
237
|
+
output t('event.shove_success', opts)
|
236
238
|
end
|
237
239
|
end,
|
238
240
|
shove_failure: lambda do |event|
|
@@ -244,13 +246,13 @@ module Natural20
|
|
244
246
|
target_roll_value: event[:target_roll].result
|
245
247
|
}
|
246
248
|
if event[:knock_prone]
|
247
|
-
|
249
|
+
output t('event.knock_prone_failure', opts)
|
248
250
|
else
|
249
|
-
|
251
|
+
output t('event.shove_failure', opts)
|
250
252
|
end
|
251
253
|
end,
|
252
254
|
%i[escape_grapple_success escape_grapple_failure] => lambda { |event|
|
253
|
-
|
255
|
+
output t("event.#{event[:event]}",
|
254
256
|
source: show_name(event), target: event[:target]&.name,
|
255
257
|
source_roll: event[:source_roll],
|
256
258
|
source_roll_value: event[:source_roll].result,
|
@@ -267,6 +269,10 @@ module Natural20
|
|
267
269
|
decorate_name(event[:source])
|
268
270
|
end
|
269
271
|
|
272
|
+
def self.output(string)
|
273
|
+
puts "[#{Session.current_session&.game_time || 0}] #{string}"
|
274
|
+
end
|
275
|
+
|
270
276
|
# @param event [Hash]
|
271
277
|
def self.decorate_name(entity)
|
272
278
|
if @battle && @current_entity_context && entity
|
@@ -282,7 +288,7 @@ module Natural20
|
|
282
288
|
end
|
283
289
|
|
284
290
|
def self.t(token, options = {})
|
285
|
-
I18n.t(token, options)
|
291
|
+
I18n.t(token, **options)
|
286
292
|
end
|
287
293
|
end
|
288
294
|
end
|
data/lib/natural_20/npc.rb
CHANGED
@@ -4,20 +4,21 @@ module Natural20
|
|
4
4
|
include Natural20::Entity
|
5
5
|
include Natural20::RogueClass
|
6
6
|
include Natural20::FighterClass
|
7
|
+
include Natural20::WizardClass
|
7
8
|
include Natural20::HealthFlavor
|
8
9
|
prepend Natural20::Lootable
|
9
10
|
include Multiattack
|
10
11
|
|
11
|
-
attr_accessor :hp, :other_counters, :resistances, :experience_points, :class_properties
|
12
|
+
attr_accessor :hp, :other_counters, :resistances, :experience_points, :class_properties, :spell_slots
|
12
13
|
|
13
|
-
ACTION_LIST = %i[first_aid look attack move dash hide help dodge disengage use_item interact ground_interact inventory disengage_bonus
|
14
|
+
ACTION_LIST = %i[spell first_aid look attack move dash hide help dodge disengage use_item interact ground_interact inventory disengage_bonus
|
14
15
|
dash_bonus hide_bonus grapple escape_grapple drop_grapple shove push prone stand short_rest two_weapon_attack].freeze
|
15
16
|
|
16
17
|
# @param session [Natural20::Session]
|
17
18
|
def initialize(session, properties)
|
18
19
|
@session = session
|
19
20
|
@properties = properties.deep_symbolize_keys!
|
20
|
-
|
21
|
+
@spell_slots = {}
|
21
22
|
@ability_scores = @properties[:ability]
|
22
23
|
@equipped = @properties[:equipped]
|
23
24
|
@race_properties = YAML.load_file(File.join(session.root_path, 'races',
|
@@ -65,7 +66,16 @@ module Natural20
|
|
65
66
|
end
|
66
67
|
|
67
68
|
def armor_class
|
68
|
-
|
69
|
+
current_ac = if has_effect?(:ac_override)
|
70
|
+
eval_effect(:ac_override, armor_class: equipped_ac)
|
71
|
+
else
|
72
|
+
equipped_ac
|
73
|
+
end
|
74
|
+
if has_effect?(:ac_bonus)
|
75
|
+
current_ac + eval_effect(:ac_bonus)
|
76
|
+
else
|
77
|
+
current_ac
|
78
|
+
end
|
69
79
|
end
|
70
80
|
|
71
81
|
def level
|
@@ -152,7 +162,7 @@ module Natural20
|
|
152
162
|
|
153
163
|
all_weapon_proficiencies = weapon_proficiencies
|
154
164
|
|
155
|
-
return true if all_weapon_proficiencies.include?(weapon[:name])
|
165
|
+
return true if all_weapon_proficiencies.include?(weapon[:name].to_s.underscore)
|
156
166
|
|
157
167
|
all_weapon_proficiencies&.detect do |prof|
|
158
168
|
weapon[:proficiency_type]&.include?(prof)
|
@@ -218,7 +228,7 @@ module Natural20
|
|
218
228
|
%w[ranged_attack melee_attack]
|
219
229
|
end
|
220
230
|
|
221
|
-
weapon_attacks = @properties[:equipped]
|
231
|
+
weapon_attacks = @properties[:equipped]&.map do |item|
|
222
232
|
weapon_detail = session.load_weapon(item)
|
223
233
|
next if weapon_detail.nil?
|
224
234
|
next unless valid_weapon_types.include?(weapon_detail[:type])
|
@@ -238,7 +248,7 @@ module Natural20
|
|
238
248
|
end
|
239
249
|
|
240
250
|
attacks
|
241
|
-
end
|
251
|
+
end&.flatten&.compact || []
|
242
252
|
|
243
253
|
unarmed_attack = AttackAction.new(session, self, :attack)
|
244
254
|
unarmed_attack.using = 'unarmed_attack'
|
@@ -317,6 +327,8 @@ module Natural20
|
|
317
327
|
action = ShoveAction.new(session, self, type)
|
318
328
|
action.knock_prone = true
|
319
329
|
action
|
330
|
+
when :spell
|
331
|
+
SpellAction.new(session, self, type)
|
320
332
|
when :two_weapon_attack
|
321
333
|
two_weapon_attack_actions(battle)
|
322
334
|
when :push
|
@@ -328,7 +340,7 @@ module Natural20
|
|
328
340
|
end
|
329
341
|
|
330
342
|
def two_weapon_attack_actions(battle)
|
331
|
-
@properties[:equipped]
|
343
|
+
@properties[:equipped]&.each do |item|
|
332
344
|
weapon_detail = session.load_weapon(item)
|
333
345
|
next if weapon_detail.nil?
|
334
346
|
next unless weapon_detail[:type] == 'melee_attack'
|
@@ -373,16 +385,67 @@ module Natural20
|
|
373
385
|
false
|
374
386
|
end
|
375
387
|
|
388
|
+
# Returns the number of spell slots
|
389
|
+
# @param level [Integer]
|
390
|
+
# @return [Integer]
|
391
|
+
def spell_slots(level, character_class = nil)
|
392
|
+
character_class = @spell_slots.keys.first if character_class.nil?
|
393
|
+
@spell_slots[character_class].fetch(level, 0)
|
394
|
+
end
|
395
|
+
|
396
|
+
# Returns the number of spell slots
|
397
|
+
# @param level [Integer]
|
398
|
+
# @return [Integer]
|
399
|
+
def max_spell_slots(level, character_class = nil)
|
400
|
+
character_class = @spell_slots.keys.first if character_class.nil?
|
401
|
+
|
402
|
+
return send(:"max_slots_for_#{character_class}", level) if respond_to?(:"max_slots_for_#{character_class}")
|
403
|
+
|
404
|
+
0
|
405
|
+
end
|
406
|
+
|
407
|
+
# Consumes a characters spell slot
|
408
|
+
def consume_spell_slot!(level, character_class = nil, qty = 1)
|
409
|
+
character_class = @spell_slots.keys.first if character_class.nil?
|
410
|
+
if @spell_slots[character_class][level]
|
411
|
+
@spell_slots[character_class][level] = [@spell_slots[character_class][level] - qty, 0].max
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
376
415
|
def pc?
|
377
416
|
true
|
378
417
|
end
|
379
418
|
|
419
|
+
def prepared_spells
|
420
|
+
@properties.fetch(:cantrips, []) + @properties.fetch(:prepared_spells, [])
|
421
|
+
end
|
422
|
+
|
423
|
+
# Returns the available spells for the current user
|
424
|
+
# @param battle [Natural20::Battle]
|
425
|
+
# @return [Hash]
|
426
|
+
def available_spells(battle)
|
427
|
+
prepared_spells.map do |spell|
|
428
|
+
details = session.load_spell(spell)
|
429
|
+
next unless details
|
430
|
+
|
431
|
+
_qty, resource = details[:casting_time].split(':')
|
432
|
+
|
433
|
+
disable_reason = []
|
434
|
+
disable_reason << :no_action if resource == 'action' && battle && battle.ongoing? && total_actions(battle).zero?
|
435
|
+
if resource == 'bonus_action' && battle.ongoing? && total_bonus_actions(battle).zero?
|
436
|
+
disable_reason << :no_bonus_action
|
437
|
+
end
|
438
|
+
disable_reason << :no_spell_slot if details[:level].positive? && spell_slots(details[:level]).zero?
|
439
|
+
|
440
|
+
[spell, details.merge(disabled: disable_reason)]
|
441
|
+
end.compact.to_h
|
442
|
+
end
|
443
|
+
|
380
444
|
# @param hit_die_num [Integer] number of hit die to use
|
381
445
|
def short_rest!(battle, prompt: false)
|
382
446
|
super
|
383
|
-
|
384
|
-
|
385
|
-
send(:"short_rest_for_#{klass}") if respond_to?(:"short_rest_for_#{klass}")
|
447
|
+
@class_properties.keys.each do |klass|
|
448
|
+
send(:"short_rest_for_#{klass}", battle) if respond_to?(:"short_rest_for_#{klass}")
|
386
449
|
end
|
387
450
|
end
|
388
451
|
|
@@ -400,7 +463,7 @@ module Natural20
|
|
400
463
|
def equipped_ac
|
401
464
|
@equipments ||= YAML.load_file(File.join(session.root_path, 'items', 'equipment.yml')).deep_symbolize_keys!
|
402
465
|
|
403
|
-
equipped_meta = @equipped
|
466
|
+
equipped_meta = @equipped&.map { |e| @equipments[e.to_sym] }&.compact || []
|
404
467
|
armor = equipped_meta.detect do |equipment|
|
405
468
|
equipment[:type] == 'armor'
|
406
469
|
end
|