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
@@ -1,9 +1,9 @@
|
|
1
1
|
module Natural20::RogueBuilder
|
2
|
-
def rogue_builder
|
2
|
+
def rogue_builder(_build_values)
|
3
3
|
@class_values ||= {
|
4
4
|
attributes: [],
|
5
5
|
saving_throw_proficiencies: %w[dexterity intelligence],
|
6
|
-
equipped: [
|
6
|
+
equipped: %w[leather dagger dagger],
|
7
7
|
inventory: [],
|
8
8
|
tools: ['thieves_tools'],
|
9
9
|
expertise: []
|
@@ -13,7 +13,7 @@ module Natural20::RogueBuilder
|
|
13
13
|
@values[:skills].each do |skill|
|
14
14
|
q.choice t("builder.skill.#{skill}"), skill
|
15
15
|
end
|
16
|
-
q.choice t(
|
16
|
+
q.choice t('builder.skill.thieves_tools'), 'thieves_tools'
|
17
17
|
end
|
18
18
|
|
19
19
|
starting_equipment = []
|
@@ -36,17 +36,17 @@ module Natural20::RogueBuilder
|
|
36
36
|
}
|
37
37
|
when :shortbow_and_quiver
|
38
38
|
@class_values[:inventory] += [{
|
39
|
-
type:
|
39
|
+
type: 'shortbow',
|
40
40
|
qty: 1
|
41
41
|
},
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
42
|
+
{
|
43
|
+
type: 'arrows',
|
44
|
+
qty: 20
|
45
|
+
}]
|
46
46
|
end
|
47
47
|
end
|
48
48
|
|
49
|
-
shortswords = starting_equipment.select { |a| a == :shortword}.size
|
49
|
+
shortswords = starting_equipment.select { |a| a == :shortword }.size
|
50
50
|
if shortswords > 0
|
51
51
|
@class_values[:inventory] << {
|
52
52
|
type: 'shortsword',
|
@@ -59,4 +59,4 @@ module Natural20::RogueBuilder
|
|
59
59
|
|
60
60
|
@class_values
|
61
61
|
end
|
62
|
-
end
|
62
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Natural20::WizardBuilder
|
2
|
+
def wizard_builder(build_values)
|
3
|
+
@wizard_info = session.load_class('wizard')
|
4
|
+
@class_values ||= {
|
5
|
+
attributes: [],
|
6
|
+
saving_throw_proficiencies: %w[intelligence wisdom],
|
7
|
+
equipped: [],
|
8
|
+
inventory: [{
|
9
|
+
type: 'spellbook',
|
10
|
+
qty: 1
|
11
|
+
}],
|
12
|
+
prepared_spells: [],
|
13
|
+
spellbook: []
|
14
|
+
}
|
15
|
+
|
16
|
+
starting_equipment = []
|
17
|
+
|
18
|
+
starting_equipment << prompt.select(t('builder.wizard.select_starting_weapon')) do |q|
|
19
|
+
q.choice t('object.weapons.quarterstaff'), :quarterstaff
|
20
|
+
q.choice t('object.weapons.dagger'), :dagger
|
21
|
+
end
|
22
|
+
|
23
|
+
starting_equipment << prompt.select(t('builder.wizard.select_starting_weapon_2')) do |q|
|
24
|
+
q.choice t('object.component_pouch'), :component_pouch
|
25
|
+
q.choice t('object.arcane_focus'), :arcane_focus
|
26
|
+
end
|
27
|
+
|
28
|
+
starting_equipment.each do |equip|
|
29
|
+
case equip
|
30
|
+
when :quarterstaff
|
31
|
+
@class_values[:equipped] << 'quarterstaff'
|
32
|
+
when :dagger
|
33
|
+
@class_values[:equipped] << 'dagger'
|
34
|
+
when :component_pouch
|
35
|
+
@class_values[:inventory] << {
|
36
|
+
type: 'component_pouch',
|
37
|
+
qty: 1
|
38
|
+
}
|
39
|
+
when :arcane_focus
|
40
|
+
arcane_focus = prompt.select(t('builder.wizard.select_arcane_focus')) do |q|
|
41
|
+
%w[crytal orb rod staff wand].each do |equip|
|
42
|
+
q.choice t(:"object.#{equip}"), equip
|
43
|
+
end
|
44
|
+
end
|
45
|
+
@class_values[:inventory] << {
|
46
|
+
type: arcane_focus,
|
47
|
+
qty: 1
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
@class_values[:prepared_spells] = prompt.multi_select(t('builder.wizard.select_cantrip'), min: 3, max: 3) do |q|
|
53
|
+
@wizard_info.dig(:spell_list, :cantrip).each do |cantrip|
|
54
|
+
next unless session.load_spell(cantrip)
|
55
|
+
|
56
|
+
q.choice t(:"spell.#{cantrip}"), cantrip
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
@class_values[:spellbook] = prompt.multi_select(t('builder.wizard.select_spells'), min: 6, max: 6) do |q|
|
61
|
+
@wizard_info.dig(:spell_list, :level_1).each do |spell|
|
62
|
+
next unless session.load_spell(spell)
|
63
|
+
|
64
|
+
q.choice t(:"spell.#{spell}"), spell
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
prepared_spells_count = modifier_table(build_values[:ability][:int]) + 1
|
69
|
+
|
70
|
+
@class_values[:prepared_spells] = prompt.multi_select(t('builder.wizard.select_prepared_spells'), min: prepared_spells_count, max: prepared_spells_count) do |q|
|
71
|
+
@class_values[:spellbook].each do |spell|
|
72
|
+
q.choice t(:"spell.#{spell}"), spell
|
73
|
+
end
|
74
|
+
end
|
75
|
+
@class_values
|
76
|
+
end
|
77
|
+
end
|
@@ -1,9 +1,11 @@
|
|
1
1
|
require 'natural_20/cli/builder/fighter_builder'
|
2
2
|
require 'natural_20/cli/builder/rogue_builder'
|
3
|
+
require 'natural_20/cli/builder/wizard_builder'
|
3
4
|
module Natural20
|
4
5
|
class CharacterBuilder
|
5
6
|
include Natural20::FighterBuilder
|
6
7
|
include Natural20::RogueBuilder
|
8
|
+
include Natural20::WizardBuilder
|
7
9
|
include Natural20::InventoryUI
|
8
10
|
|
9
11
|
attr_reader :session, :battle
|
@@ -47,9 +49,11 @@ module Natural20
|
|
47
49
|
end
|
48
50
|
end
|
49
51
|
|
50
|
-
|
51
|
-
q.default t('
|
52
|
-
end
|
52
|
+
description = prompt.multiline(t('builder.description')) do |q|
|
53
|
+
q.default t('builder.default_description')
|
54
|
+
end
|
55
|
+
|
56
|
+
@values[:description] = description.is_a?(Array) ? description.join("\n") : description
|
53
57
|
|
54
58
|
races = session.load_races
|
55
59
|
@values[:race] = prompt.select(t('builder.select_race')) do |q|
|
@@ -140,7 +144,8 @@ module Natural20
|
|
140
144
|
|
141
145
|
class_skills_selector
|
142
146
|
|
143
|
-
send(:"#{k}_builder")
|
147
|
+
send(:"#{k}_builder", @values)
|
148
|
+
|
144
149
|
@values.merge!(@class_values)
|
145
150
|
@pc = Natural20::PlayerCharacter.new(session, @values)
|
146
151
|
character_sheet(@pc)
|
@@ -1,10 +1,12 @@
|
|
1
1
|
require 'natural_20/cli/inventory_ui'
|
2
2
|
require 'natural_20/cli/character_builder'
|
3
|
+
require 'natural_20/cli/action_ui'
|
3
4
|
|
4
5
|
class CommandlineUI < Natural20::Controller
|
5
6
|
include Natural20::InventoryUI
|
6
7
|
include Natural20::MovementHelper
|
7
8
|
include Natural20::Cover
|
9
|
+
include Natural20::ActionUI
|
8
10
|
|
9
11
|
TTY_PROMPT_PER_PAGE = 20
|
10
12
|
attr_reader :battle, :map, :session, :test_mode
|
@@ -54,33 +56,37 @@ class CommandlineUI < Natural20::Controller
|
|
54
56
|
selected_targets = []
|
55
57
|
valid_targets = options[:targets] || battle.valid_targets_for(entity, action, target_types: options[:target_types],
|
56
58
|
range: options[:range], filter: options[:filter])
|
57
|
-
|
58
|
-
|
59
|
-
|
59
|
+
total_targets = options[:num] || 1
|
60
|
+
puts t(:"multiple_targets", total_targets: total_targets) if total_targets > 1
|
61
|
+
total_targets.times.each do |index|
|
62
|
+
target = prompt.select("Target #{index + 1}: #{entity.name} targets") do |menu|
|
63
|
+
valid_targets.each do |t|
|
64
|
+
menu.choice target_name(entity, t, weapon: weapon_details), t
|
65
|
+
end
|
66
|
+
menu.choice 'Manual - Use cursor to select a target instead', :manual
|
67
|
+
menu.choice 'Back', nil
|
60
68
|
end
|
61
|
-
menu.choice 'Manual - Use cursor to select a target instead', :manual
|
62
|
-
menu.choice 'Back', nil
|
63
|
-
end
|
64
69
|
|
65
|
-
|
70
|
+
return nil if target == 'Back'
|
66
71
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
72
|
+
if target == :manual
|
73
|
+
valid_targets = options[:targets] || battle.valid_targets_for(entity, action,
|
74
|
+
target_types: options[:target_types], range: options[:range],
|
75
|
+
filter: options[:filter],
|
76
|
+
include_objects: true)
|
77
|
+
selected_targets += target_ui(entity, weapon: weapon_details, validation: lambda { |selected|
|
78
|
+
selected_entities = map.thing_at(*selected)
|
74
79
|
|
75
|
-
|
76
|
-
|
77
|
-
|
80
|
+
if selected_entities.empty?
|
81
|
+
return false
|
82
|
+
end
|
83
|
+
|
84
|
+
selected_entities.detect do |selected_entity|
|
85
|
+
valid_targets.include?(selected_entity)
|
86
|
+
end
|
87
|
+
})
|
88
|
+
end
|
78
89
|
|
79
|
-
selected_entities.detect do |selected_entity|
|
80
|
-
valid_targets.include?(selected_entity)
|
81
|
-
end
|
82
|
-
})
|
83
|
-
else
|
84
90
|
selected_targets << target
|
85
91
|
end
|
86
92
|
|
@@ -184,7 +190,7 @@ class CommandlineUI < Natural20::Controller
|
|
184
190
|
selected_targets = []
|
185
191
|
targets = selected.flatten.select { |t| t.hp && t.hp.positive? }.flatten.uniq
|
186
192
|
|
187
|
-
if targets.size >
|
193
|
+
if targets.size > 1
|
188
194
|
loop do
|
189
195
|
target = prompt.select(t('multiple_target_prompt')) do |menu|
|
190
196
|
targets.flatten.uniq.each do |t|
|
@@ -320,149 +326,34 @@ class CommandlineUI < Natural20::Controller
|
|
320
326
|
end
|
321
327
|
end
|
322
328
|
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
param = cont.param&.map do |p|
|
332
|
-
case (p[:type])
|
333
|
-
when :look
|
334
|
-
self
|
335
|
-
when :movement
|
336
|
-
move_path, jump_index = move_ui(entity, p)
|
337
|
-
return nil if move_path.nil?
|
338
|
-
|
339
|
-
[move_path, jump_index]
|
340
|
-
when :target, :select_target
|
341
|
-
targets = attack_ui(entity, action, p)
|
342
|
-
return nil if targets.nil? || targets.empty?
|
343
|
-
|
344
|
-
targets.first
|
345
|
-
when :select_weapon
|
346
|
-
action.using || action.npc_action
|
347
|
-
when :select_item
|
348
|
-
item = prompt.select("#{entity.name} use item", per_page: TTY_PROMPT_PER_PAGE) do |menu|
|
349
|
-
entity.usable_items.each do |d|
|
350
|
-
if d[:consumable]
|
351
|
-
menu.choice "#{d[:label].colorize(:blue)} (#{d[:qty]})", d[:name]
|
352
|
-
else
|
353
|
-
menu.choice d[:label].colorize(:blue).to_s, d[:name]
|
354
|
-
end
|
355
|
-
end
|
356
|
-
menu.choice t(:back).colorize(:blue), :back
|
357
|
-
end
|
358
|
-
|
359
|
-
return nil if item == :back
|
360
|
-
|
361
|
-
item
|
362
|
-
when :select_ground_items
|
363
|
-
selected_items = prompt.multi_select("Items on the ground around #{entity.name}") do |menu|
|
364
|
-
map.items_on_the_ground(entity).each do |ground_item|
|
365
|
-
ground, items = ground_item
|
366
|
-
items.each do |t|
|
367
|
-
item_label = t("object.#{t.label}", default: t.label)
|
368
|
-
menu.choice t('inventory.inventory_items', name: item_label, qty: t.qty), [ground, t]
|
369
|
-
end
|
370
|
-
end
|
371
|
-
end
|
372
|
-
|
373
|
-
return nil if selected_items.empty?
|
374
|
-
|
375
|
-
item_selection = {}
|
376
|
-
selected_items.each do |s_item|
|
377
|
-
ground, item = s_item
|
378
|
-
|
379
|
-
qty = how_many?(item)
|
380
|
-
item_selection[ground] ||= []
|
381
|
-
item_selection[ground] << [item, qty]
|
382
|
-
end
|
383
|
-
|
384
|
-
item_selection.map do |k, v|
|
385
|
-
[k, v]
|
386
|
-
end
|
387
|
-
when :select_object
|
388
|
-
target_objects = entity.usable_objects(map, battle)
|
389
|
-
item = prompt.select("#{entity.name} interact with") do |menu|
|
390
|
-
target_objects.each do |d|
|
391
|
-
menu.choice d.name.humanize.to_s, d
|
392
|
-
end
|
393
|
-
menu.choice t(:manual_target), :manual_target
|
394
|
-
menu.choice t(:back).colorize(:blue), :back
|
395
|
-
end
|
396
|
-
|
397
|
-
return nil if item == :back
|
398
|
-
|
399
|
-
if item == :manual_target
|
400
|
-
item = target_ui(entity, num_select: 1, validation: lambda { |selected|
|
401
|
-
selected_entities = map.thing_at(*selected)
|
402
|
-
|
403
|
-
return false if selected_entities.empty?
|
404
|
-
|
405
|
-
selected_entities.detect do |selected_entity|
|
406
|
-
target_objects.include?(selected_entity)
|
407
|
-
end
|
408
|
-
}).first
|
409
|
-
end
|
410
|
-
|
411
|
-
item
|
412
|
-
when :select_items
|
413
|
-
selected_items = prompt.multi_select(p[:label], per_page: TTY_PROMPT_PER_PAGE) do |menu|
|
414
|
-
p[:items].each do |m|
|
415
|
-
item_label = t("object.#{m.label}", default: m.label)
|
416
|
-
if m.try(:equipped)
|
417
|
-
menu.choice t('inventory.equiped_items', name: item_label), m
|
418
|
-
else
|
419
|
-
menu.choice t('inventory.inventory_items', name: item_label, qty: m.qty), m
|
420
|
-
end
|
421
|
-
end
|
422
|
-
menu.choice t(:back).colorize(:blue), :back
|
423
|
-
end
|
329
|
+
def arcane_recovery_ui(entity, spell_levels)
|
330
|
+
choice = prompt.select(t('action.arcane_recovery', name: entity.name)) do |q|
|
331
|
+
spell_levels.sort.each do |level|
|
332
|
+
q.choice t(:spell_level, level: level), level
|
333
|
+
end
|
334
|
+
q.choice t('action.waive_arcane_recovery'), :waive
|
335
|
+
end
|
336
|
+
return nil if choice == :waive
|
424
337
|
|
425
|
-
|
338
|
+
choice
|
339
|
+
end
|
426
340
|
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
selected_items
|
432
|
-
when :interact
|
433
|
-
object_action = prompt.select("#{entity.name} will") do |menu|
|
434
|
-
interactions = p[:target].available_interactions(entity)
|
435
|
-
class_key = p[:target].class.to_s
|
436
|
-
if interactions.is_a?(Array)
|
437
|
-
interactions.each do |k|
|
438
|
-
menu.choice t(:"object.#{class_key}.#{k}", default: k.to_s.humanize), k
|
439
|
-
end
|
440
|
-
else
|
441
|
-
interactions.each do |k, options|
|
442
|
-
label = options[:label] || t(:"object.#{class_key}.#{k}", default: k.to_s.humanize)
|
443
|
-
if options[:disabled]
|
444
|
-
menu.choice label, k, disabled: options[:disabled_text]
|
445
|
-
else
|
446
|
-
menu.choice label, k
|
447
|
-
end
|
448
|
-
end
|
449
|
-
end
|
450
|
-
menu.choice 'Back', :back
|
451
|
-
end
|
341
|
+
def spell_slots_ui(entity)
|
342
|
+
puts t(:spell_slots)
|
343
|
+
(1..9).each do |level|
|
344
|
+
next unless entity.max_spell_slots(level).positive?
|
452
345
|
|
453
|
-
|
346
|
+
used_slots = entity.max_spell_slots(level) - entity.spell_slots(level)
|
347
|
+
used = used_slots.times.map do
|
348
|
+
'■'
|
349
|
+
end
|
454
350
|
|
455
|
-
|
456
|
-
|
457
|
-
inventory_ui(entity)
|
458
|
-
else
|
459
|
-
raise "unknown #{p[:type]}"
|
460
|
-
end
|
351
|
+
avail = entity.spell_slots(level).times.map do
|
352
|
+
'°'
|
461
353
|
end
|
462
|
-
|
463
|
-
|
354
|
+
|
355
|
+
puts t(:spell_level_slots, level: level, slots: (used + avail).join(' '))
|
464
356
|
end
|
465
|
-
@action = cont
|
466
357
|
end
|
467
358
|
|
468
359
|
def describe_map(map, line_of_sight: [])
|
@@ -482,9 +373,11 @@ class CommandlineUI < Natural20::Controller
|
|
482
373
|
loop do
|
483
374
|
describe_map(battle.map, line_of_sight: entity)
|
484
375
|
puts @renderer.render(line_of_sight: entity)
|
485
|
-
puts t(:character_status_line, hp: entity.hp, max_hp: entity.max_hp, total_actions: entity.total_actions(battle), bonus_action: entity.total_bonus_actions(battle),
|
376
|
+
puts t(:character_status_line, ac: entity.armor_class, hp: entity.hp, max_hp: entity.max_hp, total_actions: entity.total_actions(battle), bonus_action: entity.total_bonus_actions(battle),
|
486
377
|
available_movement: entity.available_movement(battle), statuses: entity.statuses.to_a.join(','))
|
487
|
-
|
378
|
+
entity.active_effects.each do |effect|
|
379
|
+
puts t(:effect_line, effect_name: effect[:effect].label, source: effect[:source].name)
|
380
|
+
end
|
488
381
|
action = prompt.select(t('character_action_prompt', name: entity.name, token: entity.token&.first), per_page: TTY_PROMPT_PER_PAGE,
|
489
382
|
filter: true) do |menu|
|
490
383
|
entity.available_actions(@session, battle).each do |a|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
module Natural20::InventoryUI
|
2
2
|
include Natural20::Weapons
|
3
3
|
|
4
|
+
# Displays the character sheet of the specified entity
|
5
|
+
# @param entity [Natural20::Entity]
|
4
6
|
def character_sheet(entity)
|
5
7
|
puts t('character_sheet.name', name: entity.name)
|
6
8
|
puts t('character_sheet.level', level: entity.level)
|
@@ -15,6 +17,8 @@ module Natural20::InventoryUI
|
|
15
17
|
puts t('character_sheet.hp', current: entity.hp, max: entity.max_hp)
|
16
18
|
puts t('character_sheet.ac', ac: entity.armor_class)
|
17
19
|
puts t('character_sheet.speed', speed: entity.speed)
|
20
|
+
puts t('character_sheet.proficiency_bonus', bonus: entity.proficiency_bonus)
|
21
|
+
puts t('character_sheet.spell_attack', spell_attack: entity.spell_attack_modifier) if entity.has_spells?
|
18
22
|
puts t('character_sheet.languages')
|
19
23
|
entity.languages.each do |lang|
|
20
24
|
puts " #{t("language.#{lang}")}"
|