module Natural20::Weapons

reusable utility methods for weapon calculations

Public Instance Methods

compute_advantages_and_disadvantages(battle, source, target, weapon, source_pos: nil) click to toggle source

Compute all advantages and disadvantages @param battle [Natural20::Battle] @param source [Natural20::Entity] @param target [Natural20::Entity] @option weapon type [String] @return [Array]

# File lib/natural_20/utils/weapons.rb, line 22
def compute_advantages_and_disadvantages(battle, source, target, weapon, source_pos: nil)
  weapon = battle.session.load_weapon(weapon) if weapon.is_a?(String) || weapon.is_a?(Symbol)
  advantage = []
  disadvantage = []

  disadvantage << :prone if source.prone?
  disadvantage << :squeezed if source.squeezed?
  disadvantage << :target_dodge if target.dodge?(battle)
  disadvantage << :armor_proficiency unless source.proficient_with_equipped_armor?
  advantage << :squeezed if target.squeezed?
  advantage << :being_helped if battle.help_with?(target)
  disadvantage << :target_long_range if battle.map && weapon && battle.map.distance(source, target,
                                                                                    entity_1_pos: source_pos) > weapon[:range]

  if weapon && weapon[:type] == 'ranged_attack' && battle.map
    disadvantage << :ranged_with_enemy_in_melee if battle.enemy_in_melee_range?(source, source_pos: source_pos)
    disadvantage << :target_is_prone_range if target.prone?
  end

  if source.class_feature?('pack_tactics') && battle.ally_within_enemey_melee_range?(source, target,
                                                                                     source_pos: source_pos)
    advantage << :pack_tactics
  end

  if weapon && weapon[:properties]&.include?('heavy') && source.size == :small
    disadvantage << :small_creature_using_heavy
  end
  advantage << :target_is_prone if weapon && weapon[:type] == 'melee_attack' && target.prone?

  advantage << :unseen_attacker if battle.map && !battle.can_see?(target, source, entity_2_pos: source_pos)
  disadvantage << :invisible_attacker if battle.map && !battle.can_see?(source, target, entity_1_pos: source_pos)
  [advantage, disadvantage]
end
damage_modifier(entity, weapon, second_hand: false) click to toggle source

Calculates weapon damage roll @param entity [Natural20::Entity] @param weapon [Hash] weapon descriptor @param second_hand [Boolean] Second hand to be used for two weapon fighting @return [String]

# File lib/natural_20/utils/weapons.rb, line 61
def damage_modifier(entity, weapon, second_hand: false)
  damage_mod = entity.attack_ability_mod(weapon)

  damage_mod = [damage_mod, 0].min if second_hand && !entity.class_feature?('two_weapon_fighting')

  # compute damage roll using versatile weapon property
  damage_roll = if weapon[:properties]&.include?('versatile') && entity.used_hand_slots <= 1.0
                  weapon[:damage_2]
                else
                  weapon[:damage]
                end

  # duelist class feature
  if entity.class_feature?('dueling') && weapon[:type] == 'melee_attack' && entity.used_hand_slots(weapon_only: true) <= 1.0
    damage_mod += 2
  end

  "#{damage_roll}#{damage_mod >= 0 ? "+#{damage_mod}" : damage_mod}"
end
target_advantage_condition(battle, source, target, weapon, source_pos: nil) click to toggle source

Check all the factors that affect advantage/disadvantage in attack rolls

# File lib/natural_20/utils/weapons.rb, line 4
def target_advantage_condition(battle, source, target, weapon, source_pos: nil)
  advantages, disadvantages = compute_advantages_and_disadvantages(battle, source, target, weapon,
                                                                   source_pos: source_pos)

  return [0, [advantages, disadvantages]] if advantages.empty? && disadvantages.empty?
  return [0, [advantages, disadvantages]] if !advantages.empty? && !disadvantages.empty?

  return [1, [advantages, disadvantages]] unless advantages.empty?

  [-1, [advantages, disadvantages]]
end