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.
- 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
|