Module: Lich::Gemstone::Armaments::ArmorStats

Defined in:
documented/gemstone/armaments/armor_stats.rb

Overview

Provides armor statistics and related functionalities.

This module contains a static array of armor stats indexed by armor identifiers. Each armor entry contains metadata such as category, alternative names, size and evade modifiers, and base weight.

Constant Summary collapse

@@armor_stats =

Static array of armor stats indexed by armor identifiers. Each armor entry contains metadata such as category, alternative names, size and evade modifiers, and base weight.

hindrances/Training Requirements Array: [0] - nil (Act Pen) [1] - Minor Spiritual [2] - Major Spiritual [3] - Cleric Base [4] - Minor Elemental [5] - Major Elemental [6] - Ranger Base [7] - Sorcerer Base [8] - Old Empath Base [9] - Wizard Base [10] - Bard Base [11] - Empath Base [12] - Minor Mental [13] - Major Mental [14] - Savant Base [15] - nil [16] - Paladin Base [17] - Arcane Spells [18] - nil [19] - Lost Arts

{
  :ag_1 => {
    :asg_1 => { # Cloth
      :type            => :cloth,
      :base_name       => :normal_clothing,
      :all_names       => ["normal clothing", "clothing", "clothes", "garb", "garments", "outfit", "attire", "ensemble"],
      :armor_group     => 1,
      :armor_sub_group => 1,
      :base_weight     => 0,
      :min_rt          => 0,
      :action_penalty  => 0,
      :normal_cva      => 25,
      :magical_cva     => 20,
      #                    AP 1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [0, 0, 0, 0, 0, 0, 0, 0, nil, 0, 0, 0, 0, 0, nil, nil, 0, nil, nil, nil],
      :hindrance_max   => 0,
      #                    AP 1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :training_reqs   => [0, 0, 0, 0, 0, 0, 0, 0, nil, 0, 0, 0, 0, 0, nil, nil, 0, nil, nil, nil],
    },
    :asg_2 => {
      :type            => :cloth,
      :base_name       => :robes,
      :all_names       => ["robes", "robe", "vestments", "tunic"],
      :armor_group     => 1,
      :armor_sub_group => 2,
      :base_weight     => 8,
      :min_rt          => 0,
      :action_penalty  => 0,
      :normal_cva      => 25,
      :magical_cva     => 20,
      #                    AP 1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [0, 0, 0, 0, 0, 0, 0, 0, nil, 0, 0, 0, 0, 0, nil, nil, 0, nil, nil, nil],
      :hindrance_max   => 0,
      #                    AP 1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :training_reqs   => [0, 0, 0, 0, 0, 0, 0, 0, nil, 0, 0, 0, 0, 0, nil, nil, 0, nil, nil, nil],
    },
    :asg_3 => nil, # not used
    :asg_4 => nil, # not used
  },
  :ag_2 => { # Leather
    :asg_5 => {
      :type            => :leather,
      :base_name       => :light_leather,
      :all_names       => ["light leather", "light leathers", "buffcoat", "casting leather", "casting leathers", "jack", "leather cyclas", "leather jerkin", "leather shirt", "leather tunic", "leather vest", "leather", "leathers", "hunts"],
      :armor_group     => 2,
      :armor_sub_group => 5,
      :base_weight     => 10,
      :min_rt          => 0,
      :action_penalty  => 0,
      :normal_cva      => 20,
      :magical_cva     => 15,
      #                    AP 1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [0, 0, 0, 0, 0, 0, 0, 0, nil, 0, 0, 0, 0, 0, nil, nil, 0, nil, nil, nil],
      :hindrance_max   => 0,
      #                    AP 1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :training_reqs   => [0, 0, 0, 0, 0, 0, 0, 0, nil, 0, 0, 0, 0, 0, nil, nil, 0, nil, nil, nil],
    },
    :asg_6 => {
      :type            => :leather,
      :base_name       => :full_leather,
      :all_names       => ["full leather", "full leathers", "arming doublet", "buffcoat", "casting leather", "casting leathers", "leather shirt", "leather pourpoint", "leather", "leathers", "hunts"],
      :armor_group     => 2,
      :armor_sub_group => 6,
      :base_weight     => 13,
      :min_rt          => 1,
      :action_penalty  => -1,
      :normal_cva      => 19,
      :magical_cva     => 14,
      #                    AP  1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [-1, 0, 0, 0, 0, 0, 0, 0, nil, 0, 0, 0, 0, 0, nil, nil, 0, nil, nil, nil],
      :hindrance_max   => 0,
      #                    AP 1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :training_reqs   => [2, 0, 0, 0, 0, 0, 0, 0, nil, 0, 0, 0, 0, 0, nil, nil, 0, nil, nil, nil],
    },
    :asg_7 => {
      :type            => :leather,
      :base_name       => :reinforced_leather,
      :all_names       => ["reinforced leather", "reinforced leathers", "aketon", "arming coat", "arming doublet", "gambeson", "quilted leather", "leather", "leathers", "hunts"],
      :armor_group     => 2,
      :armor_sub_group => 7,
      :base_weight     => 15,
      :min_rt          => 2,
      :action_penalty  => -5,
      :normal_cva      => 18,
      :magical_cva     => 13,
      #                    AP  1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [-5, 0, 0, 0, 0, 2, 0, 1, nil, 2, 0, 0, 0, 2, nil, nil, 0, nil, nil, nil],
      :hindrance_max   => 4,
      #                    AP 1  2  3  4  5  6  7  8    9  10 11 12 13 14 15   16 17   18   19
      :training_reqs   => [6, 0, 0, 0, 0, 6, 0, 2, nil, 6, 0, 0, 0, 6, 6, nil, 0, nil, nil, nil],
    },
    :asg_8 => {
      :type            => :leather,
      :base_name       => :double_leather,
      :all_names       => ["double leather", "double leathers", "aketon", "arming coat", "gambeson", "bodysuit", "leather", "leathers", "hunts"],
      :armor_group     => 2,
      :armor_sub_group => 8,
      :base_weight     => 16,
      :min_rt          => 2,
      :action_penalty  => -6,
      :normal_cva      => 17,
      :magical_cva     => 12,
      #                    AP  1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [-6, 0, 0, 0, 0, 4, 0, 2, nil, 4, 2, 0, 2, 4, nil, nil, 0, nil, nil, nil],
      :hindrance_max   => 6,
      #                    AP 1  2  3  4  5   6  7  8    9   10 11 12 13  14  15   16 17   18   19
      :training_reqs   => [6, 0, 0, 0, 0, 15, 0, 6, nil, 15, 6, 0, 6, 15, 15, nil, 0, nil, nil, nil],
    },
  },
  :ag_3 => { # Scale
    :asg_9  => {
      :type            => :scale,
      :base_name       => :leather_breastplate,
      :all_names       => ["leather breastplate", "breastplate", "brigandine shirt", "corslet/corselet", "cuirass", "jack", "jerkin", "lamellar shirt", "scale", "scalemail", "tunic", "armor"],
      :armor_group     => 3,
      :armor_sub_group => 9,
      :base_weight     => 16,
      :min_rt          => 3,
      :action_penalty  => -7,
      :normal_cva      => 11,
      :magical_cva     => 5,
      #                    AP  1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [-7, 3, 4, 4, 4, 6, 3, 5, nil, 6, 3, 4, 4, 6, nil, nil, 2, nil, nil, nil],
      :hindrance_max   => 16,
      #                    AP  1   2   3   4   5   6   7   8    9   10  11  12  13  14  15   16 17   18   19
      :training_reqs   => [10, 10, 15, 15, 15, 27, 10, 20, nil, 27, 10, 15, 15, 27, 27, nil, 6, nil, nil, nil],
    },
    :asg_10 => {
      :type            => :scale,
      :base_name       => :cuirboulli_leather,
      :all_names       => ["cuirboulli", "cuirboulli leather", "cuirboulli leathers", "brigandine shirt", "cuirass", "jerkin", "lamellar corslet/corselet", "lamellar shirt", "leather corslet/corselet", "scale", "scalemail", "tunic", "armor"],
      :armor_group     => 3,
      :armor_sub_group => 10,
      :base_weight     => 17,
      :min_rt          => 4,
      :action_penalty  => -8,
      :normal_cva      => 10,
      :magical_cva     => 4,
      #                    AP   1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [-8, 4, 5, 5, 5, 7, 4, 6, nil, 7, 3, 5, 5, 7, nil, nil, 3, nil, nil, nil],
      :hindrance_max   => 20,
      #                    AP  1   2   3   4   5   6   7   8    9   10  11  12  13  14  15   16  17   18   19
      :training_reqs   => [15, 15, 20, 20, 20, 35, 15, 27, nil, 35, 10, 20, 20, 35, 35, nil, 10, nil, nil, nil],
    },
    :asg_11 => {
      :type            => :scale,
      :base_name       => :studded_leather,
      :all_names       => ["studded leather", "studded leathers", "splint leather", "splinted leather", "lamellar leather", "armor"],
      :armor_group     => 3,
      :armor_sub_group => 11,
      :base_weight     => 20,
      :min_rt          => 5,
      :action_penalty  => -10,
      :normal_cva      => 9,
      :magical_cva     => 3,
      #                    AP   1  2  3  4  5  6  7  8    9  10 11 12 13 14   15   16 17   18   19
      :hindrances      => [-10, 5, 6, 6, 6, 9, 5, 8, nil, 9, 3, 6, 6, 9, nil, nil, 4, nil, nil, nil],
      :hindrance_max   => 24,
      #                    AP  1   2   3   4   5   6   7   8    9   10  11  12  13  14  15   16  17   18   19
      :training_reqs   => [20, 20, 27, 27, 27, 70, 20, 50, nil, 70, 10, 27, 27, 70, 70, nil, 15, nil, nil, nil],
    },
    :asg_12 => {
      :type            => :scale,
      :base_name       => :brigandine_armor,
      :all_names       => ["brigandine", "brigandine armor", "brigandine leather", "banded armor", "coat-of-plates", "jack-of-plates", "kuyak", "laminar armor", "lamellar armor", "scalemail", "splint armor", "splinted armor", "splint mail", "splinted mail", "armor"],
      :armor_group     => 3,
      :armor_sub_group => 12,
      :base_weight     => 25,
      :min_rt          => 6,
      :action_penalty  => -12,
      :normal_cva      => 8,
      :magical_cva     => 2,
      #                    AP   1  2  3  4  5   6  7   8    9   10 11 12 13  14   15   16 17   18   19
      :hindrances      => [-12, 6, 7, 7, 7, 12, 6, 11, nil, 12, 7, 7, 7, 12, nil, nil, 5, nil, nil, nil],
      :hindrance_max   => 28,
      #                    AP  1   2   3   4   5    6   7    8    9    10  11  12  13   14   15   16  17   18   19
      :training_reqs   => [27, 27, 35, 35, 35, 130, 27, 110, nil, 130, 35, 35, 35, 130, 130, nil, 20, nil, nil, nil],
    },
  },
  :ag_4 => { # Chain
    :asg_13 => {
      :type            => :chain,
      :base_name       => :chain_mail,
      :all_names       => ["chain", "chainmail", "chain armor", "mail", "ringmail", "byrnie", "chain corslet/corselet", "chain shirt", "chain tunic"],
      :armor_group     => 4,
      :armor_sub_group => 13,
      :base_weight     => 25,
      :min_rt          => 7,
      :action_penalty  => -13,
      :normal_cva      => 1,
      :magical_cva     => -6,
      #                    AP   1  2  3  4  5   6  7   8    9   10 11 12 13  14   15   16 17   18   19
      :hindrances      => [-13, 7, 8, 8, 8, 16, 7, 16, nil, 16, 8, 8, 6, 16, nil, nil, 6, nil, nil, nil],
      :hindrance_max   => 40,
      #                    AP  1   2   3   4   5    6   7    8    9    10  11  12   13   14   15   16  17   18   19
      :training_reqs   => [35, 35, 50, 50, 50, 210, 35, 210, nil, 210, 50, 50, 50, 210, 210, nil, 27, nil, nil, nil],
    },
    :asg_14 => {
      :type            => :chain,
      :base_name       => :double_chain,
      :all_names       => ["chain", "chainmail", "chain armor", "mail", "ringmail", "double chain", "double chainmail", "chain corslet/corselet", "chain shirt", "chain tunic", "haubergeon", "jazerant"],
      :armor_group     => 4,
      :armor_sub_group => 14,
      :base_weight     => 25,
      :min_rt          => 8,
      :action_penalty  => -14,
      :normal_cva      => 0,
      :magical_cva     => -7,
      #                    AP   1  2  3  4  5   6  7   8    9   10 11 12 13  14   15   16 17   18   19
      :hindrances      => [-14, 8, 9, 9, 9, 20, 8, 18, nil, 20, 8, 9, 9, 20, nil, nil, 7, nil, nil, nil],
      :hindrance_max   => 45,
      #                    AP  1   2   3   4   5    6   7    8    9    10  11  12  13   14   15   16  17   18   19
      :training_reqs   => [50, 50, 70, 70, 70, 290, 50, 250, nil, 290, 50, 70, 70, 290, 290, nil, 35, nil, nil, nil],
    },
    :asg_15 => {
      :type            => :chain,
      :base_name       => :augmented_chain,
      :all_names       => ["chain", "chainmail", "chain armor", "mail", "ringmail", "augmented chain", "augmented chainmail", "haubergeon", "jazerant"],
      :armor_group     => 4,
      :armor_sub_group => 15,
      :base_weight     => 26,
      :min_rt          => 8,
      :action_penalty  => -16,
      :normal_cva      => -1,
      :magical_cva     => -8,
      #                    AP   1  2   3   4   5   6  7   8    9   10 11  12  13  14   15   16 17   18   19
      :hindrances      => [-16, 9, 11, 11, 10, 25, 9, 22, nil, 25, 8, 11, 10, 25, nil, nil, 8, nil, nil, nil],
      :hindrance_max   => 55,
      #                    AP  1   2    3    4   5    6   7    8    9    10  11   12  13   14   15   16  17   18   19
      :training_reqs   => [50, 70, 110, 110, 90, 390, 70, 330, nil, 390, 50, 110, 90, 390, 390, nil, 50, nil, nil, nil],
    },
    :asg_16 => {
      :type            => :chain,
      :base_name       => :chain_hauberk,
      :all_names       => ["chain", "chainmail", "chain armor", "mail", "ringmail", "chain hauberk", "body armor", "hauberk", "jazerant hauberk"],
      :armor_group     => 4,
      :armor_sub_group => 16,
      :base_weight     => 27,
      :min_rt          => 9,
      :action_penalty  => -18,
      :normal_cva      => -2,
      :magical_cva     => -9,
      #                    AP   1   2   3   4   5   6   7   8    9   10  11  12  13  14   15   16 17   18   19
      :hindrances      => [-18, 11, 14, 14, 12, 30, 11, 26, nil, 30, 15, 14, 15, 30, nil, nil, 9, nil, nil, nil],
      :hindrance_max   => 60,
      #                    AP  1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16  17   18   19
      :training_reqs   => [70, 110, 170, 170, 130, 490, 110, 410, nil, 490, 190, 190, 190, 490, 490, nil, 70, nil, nil, nil],
    },
  },
  :ag_5 => { # Plate
    :asg_17 => {
      :type            => :plate,
      :base_name       => :metal_breastplate,
      :all_names       => ["plate armor", "plate-and-mail", "metal breastplate", "breastplate", "cuirass", "disc armor", "mirror armor", "plate corslet", "plate corselet"],
      :armor_group     => 5,
      :armor_sub_group => 17,
      :base_weight     => 23,
      :min_rt          => 9,
      :action_penalty  => -20,
      :normal_cva      => -10,
      :magical_cva     => -18,
      #                    AP   1   2   3   4   5   6   7   8    9   10  11  12  13 14   15   16 17   18   19
      :hindrances      => [-20, 16, 25, 25, 16, 35, 21, 29, nil, 35, 21, 25, 21, 35, nil, nil, 10, nil, nil, nil],
      :hindrance_max   => 90,
      #                    AP  1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16  17   18   19
      :training_reqs   => [70, 210, 390, 390, 210, 590, 310, 470, nil, 590, 310, 390, 310, 590, 590, nil, 90, nil, nil, nil],
    },
    :asg_18 => {
      :type            => :plate,
      :base_name       => :augmented_plate,
      :all_names       => ["plate armor", "plate-and-mail", "augmented breastplate", "breastplate", "coracia", "cuirass", "platemail", "plate corslet", "plate corselet"],
      :armor_group     => 5,
      :armor_sub_group => 18,
      :base_weight     => 25,
      :min_rt          => 10,
      :action_penalty  => -25,
      :normal_cva      => -11,
      :magical_cva     => -19,
      #                    AP   1   2   3   4   5   6   7   8    9   10  11  12  13  14   15   16 17   18   19
      :hindrances      => [-25, 17, 28, 28, 18, 40, 24, 33, nil, 40, 21, 28, 21, 40, nil, nil, 11, nil, nil, nil],
      :hindrance_max   => 92,
      #                    AP  1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16  17   18   19
      :training_reqs   => [90, 230, 450, 450, 250, 690, 370, 550, nil, 690, 310, 450, 310, 690, 690, nil, 110, nil, nil, nil],
    },
    :asg_19 => {
      :type            => :plate,
      :base_name       => :half_plate,
      :all_names       => ["plate armor", "plate-and-mail", "half plate", "half-plate", "plate", "platemail"],
      :armor_group     => 5,
      :armor_sub_group => 19,
      :base_weight     => 50,
      :min_rt          => 11,
      :action_penalty  => -30,
      :normal_cva      => -12,
      :magical_cva     => -20,
      #                    AP   1   2   3   4   5   6   7   8    9   10  11  12  13  14   15   16 17   18   19
      :hindrances      => [-30, 18, 32, 32, 20, 45, 27, 39, nil, 45, 21, 32, 21, 45, nil, nil, 12, nil, nil, nil],
      :hindrance_max   => 94,
      #                    AP   1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16   17   18   19
      :training_reqs   => [110, 250, 530, 530, 290, 790, 430, 570, nil, 790, 310, 530, 310, 790, 790, nil, 130, nil, nil, nil],
    },
    :asg_20 => {
      :type            => :plate,
      :base_name       => :full_plate,
      :all_names       => ["plate armor", "plate-and-mail", "full plate", "full platemail", "body armor", "field plate", "field platemail", "lasktol'zko", "plate", "platemail"],
      :armor_group     => 5,
      :armor_sub_group => 20,
      :base_weight     => 75,
      :min_rt          => 12,
      :action_penalty  => -35,
      :normal_cva      => -13,
      :magical_cva     => -21,
      #                    AP   1   2   3   4   5   6   7   8    9   10  11  12  13  14   15   16 17   18   19
      :hindrances      => [-35, 20, 45, 45, 22, 50, 30, 48, nil, 50, 50, 45, 50, 50, nil, nil, 13, nil, nil, nil],
      :hindrance_max   => 96,
      #                    AP   1    2    3    4    5    6    7    8    9    10   11   12   13   14   15   16   17   18   19
      :training_reqs   => [130, 290, 850, 850, 330, 890, 490, 850, nil, 890, 890, 790, 890, 890, 890, nil, 150, nil, nil, nil],
    },
  },
}

Class Method Summary collapse

Class Method Details

.aliases_for(name) ⇒ Array<String>

Retrieves all aliases for a given armor name.

Parameters:

  • name (String)

    the name of the armor

Returns:

  • (Array<String>)

    an array of aliases for the specified armor



612
613
614
615
# File 'documented/gemstone/armaments/armor_stats.rb', line 612

def self.aliases_for(name)
  armor = self.find(name)
  armor ? armor[:all_names] : []
end

.base_namesArray<Symbol>

Retrieves all unique base names of armor from the armor stats.

Returns:

  • (Array<Symbol>)

    an array of unique base names



423
424
425
426
427
# File 'documented/gemstone/armaments/armor_stats.rb', line 423

def self.base_names
  @@armor_stats.flat_map do |_, subgroups|
    subgroups.values.compact.map { |asg| asg[:base_name] }.uniq.compact
  end
end

.categoriesArray<Symbol>

Retrieves all unique armor categories from the armor stats.

Returns:

  • (Array<Symbol>)

    an array of unique armor categories



415
416
417
# File 'documented/gemstone/armaments/armor_stats.rb', line 415

def self.categories
  @@armor_stats.values.flat_map(&:values).map { _1[:base_name] }.uniq.compact
end

.category_for(name) ⇒ Symbol?

Retrieves the category type for a given armor name.

Parameters:

  • name (String)

    the name of the armor

Returns:

  • (Symbol, nil)

    the armor type if found, otherwise nil



501
502
503
504
505
506
# File 'documented/gemstone/armaments/armor_stats.rb', line 501

def self.category_for(name)
  name = name.downcase.strip

  armor = self.find(name)
  armor ? armor[:type] : nil
end

.compare(name1, name2) ⇒ Hash?

Compares two armors and returns their attributes.

Parameters:

  • name1 (String)

    the name of the first armor

  • name2 (String)

    the name of the second armor

Returns:

  • (Hash, nil)

    a hash containing the comparison data or nil if either armor is not found



623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
# File 'documented/gemstone/armaments/armor_stats.rb', line 623

def self.compare(name1, name2)
  a1 = self.find(name1)
  a2 = self.find(name2)
  return nil unless a1 && a2

  {
    name1: a1[:base_name],
    name2: a2[:base_name],
    weight: [a1[:base_weight], a2[:base_weight]],
    min_rt: [a1[:min_rt], a2[:min_rt]],
    ap: [a1[:action_penalty], a2[:action_penalty]],
    normal_cva: [a1[:normal_cva], a2[:normal_cva]],
    magical_cva: [a1[:magical_cva], a2[:magical_cva]],
    type: [a1[:type], a2[:type]],
    coverage: [find_coverage(a1[:armor_sub_group]), find_coverage(a2[:armor_sub_group])],
    aliases: [a1[:all_names], a2[:all_names]]
  }
end

.find(name) ⇒ Hash?

Finds armor data by name.

Parameters:

  • name (String)

    the name of the armor to find

Returns:

  • (Hash, nil)

    the armor data if found, otherwise nil



434
435
436
437
438
439
440
441
442
443
444
445
446
447
# File 'documented/gemstone/armaments/armor_stats.rb', line 434

def self.find(name)
  name = name.downcase.strip

  matches = []
  @@armor_stats.each_value do |subgroups|
    subgroups.each_value do |asg_data|
      next unless asg_data.is_a?(Hash)
      matches << asg_data if asg_data[:all_names].include?(name)
    end
  end

  return nil if matches.empty?
  matches.min_by { |asg| asg[:armor_sub_group] }
end

.find_all(name) ⇒ Array<Hash>?

Finds all armor data matching the given name.

Parameters:

  • name (String)

    the name of the armor to find

Returns:

  • (Array<Hash>, nil)

    an array of matching armor data or nil if none found



454
455
456
457
458
459
460
461
462
463
464
465
466
# File 'documented/gemstone/armaments/armor_stats.rb', line 454

def self.find_all(name)
  name = name.downcase.strip

  matches = []
  @@armor_stats.each_value do |subgroups|
    subgroups.each_value do |asg_data|
      next unless asg_data.is_a?(Hash)
      matches << asg_data if asg_data[:all_names].include?(name)
    end
  end

  return matches.empty? ? nil : matches.uniq
end

.find_by_asg(asg_number) ⇒ Hash?

Finds armor data by armor sub-group number.

Parameters:

  • asg_number (Integer)

    the armor sub-group number

Returns:

  • (Hash, nil)

    the armor data if found, otherwise nil



388
389
390
391
392
393
394
395
396
397
398
399
# File 'documented/gemstone/armaments/armor_stats.rb', line 388

def self.find_by_asg(asg_number)
  return nil unless asg_number.is_a?(Integer) && asg_number.between?(1, 20)

  @@armor_stats.each_value do |subgroups|
    subgroups.each do |_, asg_data|
      next unless asg_data.is_a?(Hash)
      return asg_data if asg_data[:armor_sub_group] == asg_number
    end
  end

  nil
end

.find_coverage(asg) ⇒ Symbol?

Finds the coverage type for a given armor sub-group number.

Parameters:

  • asg (Integer)

    the armor sub-group number

Returns:

  • (Symbol, nil)

    the coverage type (e.g., :torso, :torso_and_arms) or nil if not found



367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'documented/gemstone/armaments/armor_stats.rb', line 367

def self.find_coverage(asg)
  return nil unless asg.is_a?(Integer) && asg.between?(1, 20)
  coverage = {
    torso: [1, 5, 9, 13, 17],
    torso_and_arms: [6, 10, 14, 18],
    torso_arms_and_legs: [7, 11, 15, 19],
    torso_arms_legs_and_head: [2, 8, 12, 16, 20],
  }

  coverage.each do |key, asgs|
    return key if asgs.include?(asg)
  end

  nil
end

.find_crit_divisor(type: nil, ag: nil, asg: nil) ⇒ Integer?

Finds the critical divisor based on armor type, armor group, or armor sub-group.

Parameters:

  • type (Symbol, nil) (defaults to: nil)

    the type of armor (e.g., :cloth, :leather)

  • ag (Integer, nil) (defaults to: nil)

    the armor group number

  • asg (Integer, nil) (defaults to: nil)

    the armor sub-group number

Returns:

  • (Integer, nil)

    the critical divisor for the specified parameters

Raises:

  • (ArgumentError)

    if none of the parameters are provided



355
356
357
358
359
360
# File 'documented/gemstone/armaments/armor_stats.rb', line 355

def self.find_crit_divisor(type: nil, ag: nil, asg: nil)
  return { cloth: 5, leather: 6, scale: 7, chain: 9, plate: 11 }[type] unless type.nil?
  return { 1 => 5, 2 => 6, 3 => 7, 4 => 9, 5 => 11 }[ag] unless ag.nil?
  return ({ 1..4 => 5, 5..8 => 6, 9..12 => 7, 13..16 => 9, 17..20 => 11 }.find { |range, _| range.include?(asg) }&.last) unless asg.nil?
  raise ArgumentError, "Must provide either type, ag (armor group), or asg (armor sub-group) to find_crit_divisor"
end

.list_by_type(type) ⇒ Array<Hash>

Lists all armor data of a specific type.

Parameters:

  • type (Symbol)

    the type of armor to filter by

Returns:

  • (Array<Hash>)

    an array of armor data of the specified type



473
474
475
476
477
# File 'documented/gemstone/armaments/armor_stats.rb', line 473

def self.list_by_type(type)
  @@armor_stats.flat_map do |_, subgroups|
    subgroups.values.compact.select { |asg| asg[:type] == type }
  end
end

.namesArray<String>

Retrieves all unique armor names from the armor stats.

Returns:

  • (Array<String>)

    an array of unique armor names



405
406
407
408
409
# File 'documented/gemstone/armaments/armor_stats.rb', line 405

def self.names
  @@armor_stats.flat_map do |_, subgroups|
    subgroups.values.compact.map { |asg| asg[:all_names] }
  end.flatten.compact.uniq
end

.names_in_asg(asg) ⇒ Array<String>

Retrieves all names for a specific armor sub-group.

Parameters:

  • asg (Integer)

    the armor sub-group number

Returns:

  • (Array<String>)

    an array of names for the specified armor sub-group



484
485
486
487
488
489
490
491
492
493
494
# File 'documented/gemstone/armaments/armor_stats.rb', line 484

def self.names_in_asg(asg)
  asg_sym = asg.is_a?(Integer) ? :"asg_#{asg}" : asg.to_sym

  @@armor_stats.each_value do |subgroups|
    if subgroups.key?(asg_sym)
      return subgroups[asg_sym][:all_names] || []
    end
  end

  []
end

.pretty(name) ⇒ String

Formats and returns a pretty string representation of the armor data.

Parameters:

  • name (String)

    the name of the armor to format

Returns:

  • (String)

    a formatted string representation of the armor data



513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
# File 'documented/gemstone/armaments/armor_stats.rb', line 513

def self.pretty(name)
  armor = self.find(name)
  return "\n(no data)\n" unless armor.is_a?(Hash)

  lines = []
  lines << "" # leading blank

  asg = armor[:armor_sub_group]
  ag  = armor[:armor_group]
  type = armor[:type].to_s.capitalize

  lines << "Armor: #{armor[:all_names].first} (ASG #{asg}, AG #{ag}, #{type})"
  lines << "Weight: #{armor[:base_weight]} lbs    RT: #{armor[:min_rt]}s    AP: #{armor[:action_penalty]}    CVA: Norm #{armor[:normal_cva]} / Mag #{armor[:magical_cva]}"
  lines << "Hindrance Max: #{armor[:hindrance_max]}    Coverage: #{find_coverage(asg)}"
  lines << "Alternate Names: #{armor[:all_names].join(', ')}"

  # Determine column width and spacing
  labels = Armaments::SPELL_CIRCLE_INDEX_TO_NAME.map { |_, data| data[:abbr] }
  col_width = labels.map(&:length).max + 1 # Add space between columns
  label_pad = 19
  format_cell = ->(str) { "%#{col_width}s" % str }

  # Build rows
  label_row     = ' ' * label_pad + labels.map(&format_cell).join
  underline_row = ' ' * label_pad + labels.map { format_cell.call('---') }.join
  hind_row      = 'Hindrances:'.ljust(label_pad) + armor[:hindrances].map { |v| v.nil? ? '-' : v }.map(&format_cell).join
  train_row     = 'Training Reqs:'.ljust(label_pad) + armor[:training_reqs].map { |v| v.nil? ? '-' : v }.map(&format_cell).join

  lines << label_row
  lines << underline_row
  lines << hind_row
  lines << train_row
  lines << "" # trailing blank

  lines.join("\n")
end

.pretty_long(name) ⇒ String

Formats and returns a detailed string representation of the armor data.

Parameters:

  • name (String)

    the name of the armor to format

Returns:

  • (String)

    a detailed formatted string representation of the armor data



555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
# File 'documented/gemstone/armaments/armor_stats.rb', line 555

def self.pretty_long(name)
  armor = self.find(name)
  return "\n(no data)\n" unless armor.is_a?(Hash)

  lines = []
  max_field_label_len = [
    "Cast vs Armor", "Alternate Names", "Hindrance Max", "Base Weight"
  ].concat(Armaments::SPELL_CIRCLE_INDEX_TO_NAME.values.map { |v| v[:name] }).map(&:length).max

  col_width = 12
  training_label_indent = max_field_label_len + 3 + col_width + 2

  fields = {
    "Type"            => armor[:type].to_s.capitalize,
    "ASG"             => armor[:armor_sub_group],
    "AG"              => armor[:armor_group],
    "Weight"          => "#{armor[:base_weight]} lbs",
    "RT"              => "#{armor[:min_rt]}s",
    "AP"              => armor[:action_penalty],
    "Hindrance Max"   => armor[:hindrance_max],
    "Coverage"        => find_coverage(armor[:armor_sub_group]),
    "Alternate Names" => armor[:all_names].join(", ")
  }

  fields.each do |label, value|
    lines << "%#{max_field_label_len}s: %s" % [label, value]
  end

  # CvA values
  lines << "%#{max_field_label_len}s:    Normal: %s" % ["Cast vs Armor", armor[:normal_cva]]
  lines << "%#{max_field_label_len}s     Magical: %s" % ["", armor[:magical_cva]]

  # Headers
  lines << " " * training_label_indent + "Training"
  lines << " " * (max_field_label_len + 3) + "%-#{col_width}s  %-#{col_width}s" % ["Hindrance", "Requirements"]
  lines << " " * (max_field_label_len + 3) + "%-#{col_width}s  %-#{col_width}s" % ["-" * col_width, "-" * col_width]

  # Values
  Armaments::SPELL_CIRCLE_INDEX_TO_NAME.each do |i, meta|
    label = meta[:name]
    hindrance = armor[:hindrances][i] if armor[:hindrances]
    training  = armor[:training_reqs][i] if armor[:training_reqs]

    hindrance_str = hindrance.nil? ? "-" : hindrance.to_s
    training_str  = training.nil? ? "-" : training.to_s

    lines << "%#{max_field_label_len}s: %#{col_width}s  %#{col_width}s" % [label, hindrance_str, training_str]
  end

  lines.join("\n")
end

.search(filters = {}) ⇒ Array<Hash>

Searches for armor based on provided filters.

Parameters:

  • filters (Hash) (defaults to: {})

    a hash of filters to apply to the search

Returns:

  • (Array<Hash>)

    an array of armor data matching the filters



647
648
649
650
651
652
653
654
655
656
657
658
659
# File 'documented/gemstone/armaments/armor_stats.rb', line 647

def self.search(filters = {})
  @@armor_stats.values.flat_map(&:values).compact.select do |armor|
    next if filters[:name] && !armor[:all_names].include?(filters[:name].downcase.strip)
    next if filters[:type] && armor[:type] != filters[:type]
    next if filters[:max_weight] && armor[:base_weight] > filters[:max_weight]
    next if filters[:min_cva] && armor[:normal_cva] < filters[:min_cva]
    next if filters[:max_rt] && armor[:min_rt] > filters[:max_rt]
    next if filters[:min_ap] && armor[:action_penalty] < filters[:min_ap]
    next if filters[:coverage] && find_coverage(armor[:armor_sub_group]) != filters[:coverage]

    true
  end
end

.valid_name?(name) ⇒ Boolean

Validates if the provided name corresponds to an existing armor name.

Parameters:

  • name (String)

    the name to validate

Returns:

  • (Boolean)

    true if the name is valid, false otherwise



666
667
668
669
# File 'documented/gemstone/armaments/armor_stats.rb', line 666

def self.valid_name?(name)
  name = name.downcase.strip
  self.names.include?(name)
end