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
@@ -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}")}"
|