class Natural20::Npc

Attributes

battle_defaults[RW]
hp[RW]
max_hp[R]
name[R]
npc_actions[RW]
npc_type[RW]
resistances[RW]

Public Class Methods

new(session, type, opt = {}) click to toggle source

@param session [Session] @param type [String,Symbol] @option opt rand_life [Boolean] Determines if will use die for npc HP instead of fixed value

# File lib/natural_20/npc.rb, line 17
def initialize(session, type, opt = {})
  @properties = YAML.load_file(File.join("npcs", "#{type}.yml")).deep_symbolize_keys!
  @properties.merge!(opt[:overrides].presence || {})
  @ability_scores = @properties[:ability]
  @color = @properties[:color]
  @session = session
  @npc_type = type
  @inventory = @properties[:default_inventory]&.map do |inventory|
    [inventory[:type].to_sym, OpenStruct.new({ qty: inventory[:qty] })]
  end.to_h || {}

  @properties[:inventory]&.each do |inventory|
    @inventory[inventory[:type].to_sym] = OpenStruct.new({ qty: inventory[:qty] })
  end

  @npc_actions = @properties[:actions]
  @battle_defaults = @properties[:battle_defaults]
  @opt = opt
  @resistances = []
  @statuses = Set.new

  @properties[:statuses]&.each do |stat|
    @statuses.add(stat.to_sym)
  end

  name = case type
    when "goblin"
      RandomNameGenerator.new(RandomNameGenerator::GOBLIN).compose(1)
    when "ogre"
      %w[Guzar Irth Grukurg Zoduk].sample(1).first
    else
      type.to_s.humanize
    end
  @name = opt.fetch(:name, name)
  @entity_uid = SecureRandom.uuid
  setup_attributes
end

Public Instance Methods

armor_class() click to toggle source
# File lib/natural_20/npc.rb, line 77
def armor_class
  @properties[:default_ac]
end
available_actions(session, battle, opportunity_attack: false) click to toggle source
# File lib/natural_20/npc.rb, line 85
def available_actions(session, battle, opportunity_attack: false)
  return %i[end] if unconscious?

  if opportunity_attack
    return generate_npc_attack_actions(battle, opportunity_attack: true).select do |s|
      s.action_type == :attack && s.npc_action[:type] == 'melee_attack'
    end
  end
  
  [ generate_npc_attack_actions(battle) +

  %i[hide dodge look stand move dash grapple escape_grapple].map do |type|
    next unless "#{type.to_s.camelize}Action".constantize.can?(self, battle)
    case type
    when :dodge
      DodgeAction.new(session, self, :dodge)
    when :hide
      HideAction.new(session, self, :hide)
    when :disengage
      action = DisengageAction.new(session, self, :disengage)
      action
    when :move
      MoveAction.new(session, self, type)
    when :stand
      StandAction.new(session, self, type)
    when :dash
      action = DashAction.new(session, self, type)
      action
    when :help
      action = HelpAction.new(session, self, :help)
      action
    else
      Natural20::Action.new(session, self, type)
    end
  end.compact].flatten
end
available_interactions(entity, battle) click to toggle source
# File lib/natural_20/npc.rb, line 132
def available_interactions(entity, battle)
  []
end
class_feature?(feature) click to toggle source
# File lib/natural_20/npc.rb, line 128
def class_feature?(feature)
  @properties[:attributes]&.include?(feature)
end
generate_npc_attack_actions(battle, opportunity_attack: false) click to toggle source
# File lib/natural_20/npc.rb, line 144
def generate_npc_attack_actions(battle, opportunity_attack: false)
  actions = []

  actions += npc_actions.map do |npc_action|
    next if npc_action[:ammo] && item_count(npc_action[:ammo]) <= 0
    next if npc_action[:if] && !eval_if(npc_action[:if])
    next unless AttackAction.can?(self, battle, npc_action: npc_action, opportunity_attack: opportunity_attack)

    action = AttackAction.new(session, self, :attack)

    action.npc_action = npc_action
    action
  end.compact

  actions
end
kind() click to toggle source
# File lib/natural_20/npc.rb, line 57
def kind
  @properties[:kind]
end
melee_distance() click to toggle source
# File lib/natural_20/npc.rb, line 122
def melee_distance
  @properties[:actions].select { |a| a[:type] == "melee_attack" }.map do |action|
    action[:range]
  end&.max
end
npc?() click to toggle source
# File lib/natural_20/npc.rb, line 73
def npc?
  true
end
prepared_spells() click to toggle source
# File lib/natural_20/npc.rb, line 140
def prepared_spells
  @properties.fetch(:prepared_spells, [])
end
proficient_with_equipped_armor?() click to toggle source
# File lib/natural_20/npc.rb, line 136
def proficient_with_equipped_armor?
  true
end
size() click to toggle source
# File lib/natural_20/npc.rb, line 61
def size
  @properties[:size]
end
speed() click to toggle source
# File lib/natural_20/npc.rb, line 81
def speed
  @properties[:speed]
end
token() click to toggle source
# File lib/natural_20/npc.rb, line 65
def token
  @properties[:token]
end

Private Instance Methods

setup_attributes() click to toggle source
Calls superclass method Multiattack#setup_attributes
# File lib/natural_20/npc.rb, line 163
def setup_attributes
  super

  @max_hp = @opt[:rand_life] ? Natural20::DieRoll.roll(@properties[:hp_die]).result : @properties[:max_hp]
  @hp = [@properties.fetch(:override_hp, @max_hp), @max_hp].min

  # parse hit die details
  hp_details = Natural20::DieRoll.parse(@properties[:hp_die] || "1d6")
  @max_hit_die = {}
  @current_hit_die = {}
  @max_hit_die[npc_type] = hp_details.die_count
  @current_hit_die[hp_details.die_type.to_i] = hp_details.die_count
end