Class: Natural20::PlayerCharacter
Constant Summary
collapse
- ACTION_LIST =
%i[spell first_aid look attack move dash hide help dodge disengage use_item interact ground_interact inventory disengage_bonus
dash_bonus hide_bonus grapple escape_grapple drop_grapple shove push prone stand short_rest two_weapon_attack].freeze
Constants included
from WizardClass
WizardClass::WIZARD_SPELL_SLOT_TABLE
Constants included
from Entity
Entity::ALL_SKILLS, Entity::ATTRIBUTE_TYPES, Entity::ATTRIBUTE_TYPES_ABBV, Entity::SKILL_AND_ABILITY_MAP
Instance Attribute Summary collapse
Attributes included from WizardClass
#arcane_recovery, #wizard_level, #wizard_spell_slots
Attributes included from FighterClass
#fighter_level, #second_wind_count
Attributes included from RogueClass
#rogue_level
Attributes included from Entity
#casted_effects, #color, #current_hit_die, #death_fails, #death_saves, #effects, #entity_event_hooks, #entity_uid, #max_hit_die, #session, #statuses
Class Method Summary
collapse
Instance Method Summary
collapse
#clear_multiattack!, #multi_attack_actions, #multiattack?, #reset_turn!
#describe_health
#initialize_wizard, #max_slots_for_wizard, #short_rest_for_wizard, #special_actions_for_wizard, #spell_attack_modifier
#initialize_fighter, #second_wind!, #second_wind_die, #short_rest_for_fighter, #special_actions_for_fighter
Methods included from RogueClass
#initialize_rogue, #sneak_attack_level, #special_actions_for_rogue
Methods included from Entity
#ability_mod, #acrobatics_proficient?, #action?, #active_effects, #add_casted_effect, #add_item, #all_ability_mods, #all_ability_scores, #any_class_feature?, #athletics_proficient?, #attach_handler, #attack_ability_mod, #attack_roll_mod, #available_movement, #break_stealth!, #can_see?, #carry_capacity, #cha_mod, #check_equip, #con_mod, #conscious!, #conscious?, #dead!, #dead?, #death_saving_throw!, #deduct_item, #description, #dex_mod, #dexterity_check!, #disengage!, #disengage?, #dismiss_effect!, #dodge?, #dodging!, #drop_grapple!, #drop_items!, #entered_melee?, #equip, #equipped?, #equipped_items, #equipped_weapons, #escape_grapple_from!, #expertise, #expertise?, #free_object_interaction?, #grappled?, #grappled_by!, #grapples, #grappling, #grappling?, #grappling_targets, #hand_slots_required, #has_reaction?, #has_spell_effect?, #has_spells?, #heal!, #help!, #help?, #hiding!, #hiding?, #hit_die, #incapacitated?, #initiative!, #insight_proficient?, #int_mod, #inventory, #inventory_count, #inventory_weight, #investigation_proficient?, #item_count, #items_label, #label, #light_properties, #locate_melee_positions, #lockpick!, #long_jump_distance, #medicine_check!, #medicine_proficient?, #melee_squares, #object?, #opened?, #perception_proficient?, #proficient_with_armor?, #proficient_with_equipped_armor?, #prone!, #prone?, #push_from, #ranged_spell_attack!, #register_effect, #register_event_hook, #reset_turn!, #resistant_to?, #saving_throw!, #sentient?, #shield_equipped?, #size_identifier, #squeezed!, #squeezed?, #stable!, #stable?, #stand!, #standing_jump_distance, #stealth_proficient?, #str_mod, #strength_check!, #take_damage!, #to_item, #token_size, #total_actions, #total_bonus_actions, #total_reactions, #trigger_event, #unconscious!, #unconscious?, #unequip, #unequip_all, #ungrapple, #unsqueeze, #usable_items, #usable_objects, #use_hit_die!, #used_hand_slots, #wearing_armor?, #wis_mod, #wisdom_check!
#apply_effect, #eval_if
Methods included from Lootable
#build_map, #interactable?, #resolve, #use!
Methods included from Container
#retrieve, #store
Constructor Details
#initialize(session, properties) ⇒ PlayerCharacter
Returns a new instance of PlayerCharacter.
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
# File 'lib/natural_20/player_character.rb', line 18
def initialize(session, properties)
@session = session
@properties = properties.deep_symbolize_keys!
@spell_slots = {}
@ability_scores = @properties[:ability]
@equipped = @properties[:equipped]
@race_properties = YAML.load_file(File.join(session.root_path, 'races',
"#{@properties[:race]}.yml")).deep_symbolize_keys!
@inventory = {}
@color = @properties[:color]
@properties[:inventory]&.each do |inventory|
@inventory[inventory[:type].to_sym] ||= OpenStruct.new({ type: inventory[:type], qty: 0 })
@inventory[inventory[:type].to_sym].qty += inventory[:qty]
end
@statuses = Set.new
@resistances = []
entity_uid = SecureRandom.uuid
@max_hit_die = {}
@current_hit_die = {}
@class_properties = @properties[:classes].map do |klass, level|
send(:"#{klass}_level=", level)
send(:"initialize_#{klass}")
@max_hit_die[klass] = level
character_class_properties =
YAML.load_file(File.join(session.root_path, 'char_classes', "#{klass}.yml")).deep_symbolize_keys!
hit_die_details = DieRoll.parse(character_class_properties[:hit_die])
@current_hit_die[hit_die_details.die_type.to_i] = level
[klass.to_sym, character_class_properties]
end.to_h
setup_attributes
end
|
Instance Attribute Details
#class_properties ⇒ Object
Returns the value of attribute class_properties.
12
13
14
|
# File 'lib/natural_20/player_character.rb', line 12
def class_properties
@class_properties
end
|
#experience_points ⇒ Object
Returns the value of attribute experience_points.
12
13
14
|
# File 'lib/natural_20/player_character.rb', line 12
def experience_points
@experience_points
end
|
#hp ⇒ Object
Returns the value of attribute hp.
12
13
14
|
# File 'lib/natural_20/player_character.rb', line 12
def hp
@hp
end
|
#other_counters ⇒ Object
Returns the value of attribute other_counters.
12
13
14
|
# File 'lib/natural_20/player_character.rb', line 12
def other_counters
@other_counters
end
|
#resistances ⇒ Object
Returns the value of attribute resistances.
12
13
14
|
# File 'lib/natural_20/player_character.rb', line 12
def resistances
@resistances
end
|
#spell_slots(level, character_class = nil) ⇒ Integer
Returns the number of spell slots
391
392
393
|
# File 'lib/natural_20/player_character.rb', line 391
def spell_slots
@spell_slots
end
|
Class Method Details
Loads a pregen character from path
378
379
380
|
# File 'lib/natural_20/player_character.rb', line 378
def self.load(session, path, override = {})
Natural20::PlayerCharacter.new(session, YAML.load_file(path).deep_symbolize_keys!.merge(override))
end
|
Instance Method Details
#armor_class ⇒ Object
68
69
70
71
72
73
74
75
76
77
78
79
|
# File 'lib/natural_20/player_character.rb', line 68
def armor_class
current_ac = if has_effect?(:ac_override)
eval_effect(:ac_override, armor_class: equipped_ac)
else
equipped_ac
end
if has_effect?(:ac_bonus)
current_ac + eval_effect(:ac_bonus)
else
current_ac
end
end
|
#available_actions(session, battle, opportunity_attack: false) ⇒ Object
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
|
# File 'lib/natural_20/player_character.rb', line 259
def available_actions(session, battle, opportunity_attack: false)
return [] if unconscious?
if opportunity_attack
if AttackAction.can?(self, battle, opportunity_attack: true)
return player_character_attack_actions(battle, opportunity_attack: true)
else
return []
end
end
ACTION_LIST.map do |type|
next unless "#{type.to_s.camelize}Action".constantize.can?(self, battle)
case type
when :look
LookAction.new(session, self, :look)
when :attack
player_character_attack_actions(battle)
when :dodge
DodgeAction.new(session, self, :dodge)
when :help
action = HelpAction.new(session, self, :help)
action
when :hide
HideAction.new(session, self, :hide)
when :hide_bonus
action = HideBonusAction.new(session, self, :hide_bonus)
action.as_bonus_action = true
action
when :disengage_bonus
action = DisengageAction.new(session, self, :disengage_bonus)
action.as_bonus_action = true
action
when :disengage
DisengageAction.new(session, self, :disengage)
when :drop_grapple
DropGrappleAction.new(session, self, :drop_grapple)
when :grapple
GrappleAction.new(session, self, :grapple)
when :escape_grapple
EscapeGrappleAction.new(session, self, :escape_grapple)
when :move
MoveAction.new(session, self, type)
when :prone
ProneAction.new(session, self, type)
when :stand
StandAction.new(session, self, type)
when :short_rest
ShortRestAction.new(session, self, type)
when :dash_bonus
action = DashBonusAction.new(session, self, :dash_bonus)
action.as_bonus_action = true
action
when :dash
action = DashAction.new(session, self, type)
action
when :use_item
UseItemAction.new(session, self, type)
when :interact
InteractAction.new(session, self, type)
when :ground_interact
GroundInteractAction.new(session, self, type)
when :inventory
InventoryAction.new(session, self, type)
when :first_aid
FirstAidAction.new(session, self, type)
when :shove
action = ShoveAction.new(session, self, type)
action.knock_prone = true
action
when :spell
SpellAction.new(session, self, type)
when :two_weapon_attack
two_weapon_attack_actions(battle)
when :push
ShoveAction.new(session, self, type)
else
Natural20::Action.new(session, self, type)
end
end.compact.flatten + c_class.keys.map { |c| send(:"special_actions_for_#{c}", session, battle) }.flatten
end
|
#available_interactions(_entity, _battle) ⇒ Object
359
360
361
|
# File 'lib/natural_20/player_character.rb', line 359
def available_interactions(_entity, _battle)
[]
end
|
#available_spells(battle) ⇒ Hash
Returns the available spells for the current user
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
|
# File 'lib/natural_20/player_character.rb', line 426
def available_spells(battle)
prepared_spells.map do |spell|
details = session.load_spell(spell)
next unless details
_qty, resource = details[:casting_time].split(':')
disable_reason = []
disable_reason << :no_action if resource == 'action' && battle && battle.ongoing? && total_actions(battle).zero?
if resource == 'bonus_action' && battle.ongoing? && total_bonus_actions(battle).zero?
disable_reason << :no_bonus_action
end
disable_reason << :no_spell_slot if details[:level].positive? && spell_slots(details[:level]).zero?
[spell, details.merge(disabled: disable_reason)]
end.compact.to_h
end
|
#c_class ⇒ Object
120
121
122
|
# File 'lib/natural_20/player_character.rb', line 120
def c_class
@properties[:classes]
end
|
#class_feature?(feature) ⇒ Boolean
363
364
365
366
367
368
369
370
371
|
# File 'lib/natural_20/player_character.rb', line 363
def class_feature?(feature)
return true if @properties[:class_features]&.include?(feature)
return true if @properties[:attributes]&.include?(feature)
return true if @race_properties[:race_features]&.include?(feature)
return true if subrace && @race_properties.dig(:subrace, subrace.to_sym, :class_features)&.include?(feature)
return true if subrace && @race_properties.dig(:subrace, subrace.to_sym, :race_features)&.include?(feature)
@class_properties.values.detect { |p| p[:class_features]&.include?(feature) }
end
|
#consume_spell_slot!(level, character_class = nil, qty = 1) ⇒ Object
Consumes a characters spell slot
408
409
410
411
412
413
|
# File 'lib/natural_20/player_character.rb', line 408
def consume_spell_slot!(level, character_class = nil, qty = 1)
character_class = @spell_slots.keys.first if character_class.nil?
if @spell_slots[character_class][level]
@spell_slots[character_class][level] = [@spell_slots[character_class][level] - qty, 0].max
end
end
|
#darkvision?(distance) ⇒ Boolean
217
218
219
220
221
|
# File 'lib/natural_20/player_character.rb', line 217
def darkvision?(distance)
return true if super
!!(@race_properties[:darkvision] && @race_properties[:darkvision] >= distance)
end
|
#insight_proficiency ⇒ Object
144
145
146
|
# File 'lib/natural_20/player_character.rb', line 144
def insight_proficiency
insight_proficient? ? proficiency_bonus : 0
end
|
#investigation_proficiency ⇒ Object
140
141
142
|
# File 'lib/natural_20/player_character.rb', line 140
def investigation_proficiency
investigation_proficient? ? proficiency_bonus : 0
end
|
#languages ⇒ Object
109
110
111
112
113
114
115
116
117
118
|
# File 'lib/natural_20/player_character.rb', line 109
def languages
class_languages = []
@class_properties.values.each do |prop|
class_languages += prop[:languages] || []
end
racial_languages = @race_properties[:languages] || []
(super + class_languages + racial_languages).sort
end
|
#level ⇒ Object
81
82
83
|
# File 'lib/natural_20/player_character.rb', line 81
def level
@properties[:level]
end
|
#max_hp ⇒ Object
60
61
62
63
64
65
66
|
# File 'lib/natural_20/player_character.rb', line 60
def max_hp
if class_feature?('dwarven_toughness')
@properties[:max_hp] + level
else
@properties[:max_hp]
end
end
|
#max_spell_slots(level, character_class = nil) ⇒ Integer
Returns the number of spell slots
399
400
401
402
403
404
405
|
# File 'lib/natural_20/player_character.rb', line 399
def max_spell_slots(level, character_class = nil)
character_class = @spell_slots.keys.first if character_class.nil?
return send(:"max_slots_for_#{character_class}", level) if respond_to?(:"max_slots_for_#{character_class}")
0
end
|
#melee_distance ⇒ Object
207
208
209
210
211
212
213
214
215
|
# File 'lib/natural_20/player_character.rb', line 207
def melee_distance
(@properties[:equipped].map do |item|
weapon_detail = session.load_weapon(item)
next if weapon_detail.nil?
next unless weapon_detail[:type] == 'melee_attack'
weapon_detail[:range]
end.compact + [5]).max
end
|
#name ⇒ Object
56
57
58
|
# File 'lib/natural_20/player_character.rb', line 56
def name
@properties[:name]
end
|
#npc? ⇒ Boolean
returns if an npc or a player character
384
385
386
|
# File 'lib/natural_20/player_character.rb', line 384
def npc?
false
end
|
#passive_insight ⇒ Object
132
133
134
|
# File 'lib/natural_20/player_character.rb', line 132
def passive_insight
10 + wis_mod + insight_proficiency
end
|
#passive_investigation ⇒ Object
128
129
130
|
# File 'lib/natural_20/player_character.rb', line 128
def passive_investigation
10 + int_mod + investigation_proficiency
end
|
#passive_perception ⇒ Object
124
125
126
|
# File 'lib/natural_20/player_character.rb', line 124
def passive_perception
10 + wis_mod + wisdom_proficiency
end
|
#pc? ⇒ Boolean
415
416
417
|
# File 'lib/natural_20/player_character.rb', line 415
def pc?
true
end
|
#player_character_attack_actions(_battle, opportunity_attack: false) ⇒ Object
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
# File 'lib/natural_20/player_character.rb', line 223
def player_character_attack_actions(_battle, opportunity_attack: false)
valid_weapon_types = if opportunity_attack
%w[melee_attack]
else
%w[ranged_attack melee_attack]
end
weapon_attacks = @properties[:equipped]&.map do |item|
weapon_detail = session.load_weapon(item)
next if weapon_detail.nil?
next unless valid_weapon_types.include?(weapon_detail[:type])
next if weapon_detail[:ammo] && !item_count(weapon_detail[:ammo]).positive?
attacks = []
action = AttackAction.new(session, self, :attack)
action.using = item
attacks << action
if !opportunity_attack && weapon_detail[:properties] && weapon_detail[:properties].include?('thrown')
action = AttackAction.new(session, self, :attack)
action.using = item
action.thrown = true
attacks << action
end
attacks
end&.flatten&.compact || []
unarmed_attack = AttackAction.new(session, self, :attack)
unarmed_attack.using = 'unarmed_attack'
weapon_attacks + [unarmed_attack]
end
|
#prepared_spells ⇒ Object
419
420
421
|
# File 'lib/natural_20/player_character.rb', line 419
def prepared_spells
@properties.fetch(:cantrips, []) + @properties.fetch(:prepared_spells, [])
end
|
#proficiency_bonus ⇒ Object
148
149
150
|
# File 'lib/natural_20/player_character.rb', line 148
def proficiency_bonus
proficiency_bonus_table[level - 1]
end
|
#proficient?(prof) ⇒ Boolean
152
153
154
155
156
157
158
|
# File 'lib/natural_20/player_character.rb', line 152
def proficient?(prof)
return true if @class_properties.values.detect { |c| c[:proficiencies]&.include?(prof) }
return true if @race_properties[:skills]&.include?(prof)
return true if weapon_proficiencies.include?(prof)
super
end
|
#proficient_with_weapon?(weapon) ⇒ Boolean
160
161
162
163
164
165
166
167
168
169
170
|
# File 'lib/natural_20/player_character.rb', line 160
def proficient_with_weapon?(weapon)
weapon = @session.load_thing weapon if weapon.is_a?(String)
all_weapon_proficiencies = weapon_proficiencies
return true if all_weapon_proficiencies.include?(weapon[:name].to_s.underscore)
all_weapon_proficiencies&.detect do |prof|
weapon[:proficiency_type]&.include?(prof)
end
end
|
#race ⇒ Object
101
102
103
|
# File 'lib/natural_20/player_character.rb', line 101
def race
@properties[:race]
end
|
#short_rest!(battle, prompt: false) ⇒ Object
445
446
447
448
449
450
|
# File 'lib/natural_20/player_character.rb', line 445
def short_rest!(battle, prompt: false)
super
@class_properties.keys.each do |klass|
send(:"short_rest_for_#{klass}", battle) if respond_to?(:"short_rest_for_#{klass}")
end
end
|
#size ⇒ Object
85
86
87
|
# File 'lib/natural_20/player_character.rb', line 85
def size
@properties[:size] || @race_properties[:size]
end
|
#speed ⇒ Object
93
94
95
96
97
98
99
|
# File 'lib/natural_20/player_character.rb', line 93
def speed
if subrace
return (@race_properties.dig(:subrace, subrace.to_sym,
:base_speed) || @race_properties[:base_speed])
end
@race_properties[:base_speed]
end
|
#subrace ⇒ Object
105
106
107
|
# File 'lib/natural_20/player_character.rb', line 105
def subrace
@properties[:subrace]
end
|
#to_h ⇒ Object
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
# File 'lib/natural_20/player_character.rb', line 186
def to_h
{
name: name,
classes: c_class,
hp: hp,
ability: {
str: @ability_scores.fetch(:str),
dex: @ability_scores.fetch(:dex),
con: @ability_scores.fetch(:con),
int: @ability_scores.fetch(:int),
wis: @ability_scores.fetch(:wis),
cha: @ability_scores.fetch(:cha)
},
passive: {
perception: passive_perception,
investigation: passive_investigation,
insight: passive_insight
}
}
end
|
#token ⇒ Object
89
90
91
|
# File 'lib/natural_20/player_character.rb', line 89
def token
@properties[:token]
end
|
#two_weapon_attack_actions(battle) ⇒ Object
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
|
# File 'lib/natural_20/player_character.rb', line 342
def two_weapon_attack_actions(battle)
@properties[:equipped]&.each do |item|
weapon_detail = session.load_weapon(item)
next if weapon_detail.nil?
next unless weapon_detail[:type] == 'melee_attack'
next unless weapon_detail[:properties] && weapon_detail[:properties].include?('light') && TwoWeaponAttackAction.can?(
self, battle, weapon: item
)
action = TwoWeaponAttackAction.new(session, self, :attack, weapon: item)
action.using = item
return action
end
nil
end
|
#weapon_proficiencies ⇒ Object
172
173
174
175
176
177
178
179
180
181
182
183
184
|
# File 'lib/natural_20/player_character.rb', line 172
def weapon_proficiencies
all_weapon_proficiencies = @class_properties.values.map do |p|
p[:weapon_proficiencies]
end.compact.flatten + @properties.fetch(:weapon_proficiencies, [])
all_weapon_proficiencies += @race_properties.fetch(:weapon_proficiencies, [])
if subrace
all_weapon_proficiencies += (@race_properties.dig(:subrace, subrace.to_sym,
:weapon_proficiencies) || [])
end
all_weapon_proficiencies
end
|
#wisdom_proficiency ⇒ Object
136
137
138
|
# File 'lib/natural_20/player_character.rb', line 136
def wisdom_proficiency
perception_proficient? ? proficiency_bonus : 0
end
|