pfrpg_core 0.0.2 → 0.1.1

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.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +23 -0
  3. data/Rakefile +1 -1
  4. data/lib/pfrpg_core/attack.rb +66 -0
  5. data/lib/pfrpg_core/attributes.rb +113 -0
  6. data/lib/pfrpg_core/avatar_url.rb +15 -0
  7. data/lib/pfrpg_core/bonus.rb +1 -1
  8. data/lib/pfrpg_core/character.rb +83 -0
  9. data/lib/pfrpg_core/class_feature.rb +31 -0
  10. data/lib/pfrpg_core/combat.rb +60 -0
  11. data/lib/pfrpg_core/demographics.rb +30 -0
  12. data/lib/pfrpg_core/derived/combat_bonuses.rb +78 -0
  13. data/lib/pfrpg_core/derived/defense.rb +29 -0
  14. data/lib/pfrpg_core/derived/magic.rb +145 -0
  15. data/lib/pfrpg_core/derived/misc.rb +114 -0
  16. data/lib/pfrpg_core/derived/offense.rb +49 -0
  17. data/lib/pfrpg_core/derived.rb +10 -0
  18. data/lib/pfrpg_core/effect_processor.rb +1 -1
  19. data/lib/pfrpg_core/effect_totaler.rb +29 -0
  20. data/lib/pfrpg_core/equipment_totaler.rb +14 -0
  21. data/lib/pfrpg_core/feat.rb +27 -0
  22. data/lib/pfrpg_core/feat_totaler.rb +61 -0
  23. data/lib/pfrpg_core/feature_duplicator.rb +26 -0
  24. data/lib/pfrpg_core/feature_totaler.rb +86 -0
  25. data/lib/pfrpg_core/filterable.rb +1 -1
  26. data/lib/pfrpg_core/filters/filter.rb +14 -0
  27. data/lib/pfrpg_core/filters/proficiency_mod.rb +34 -0
  28. data/lib/pfrpg_core/filters/prowess_mod.rb +20 -0
  29. data/lib/pfrpg_core/filters/ranged_attack_mod.rb +28 -0
  30. data/lib/pfrpg_core/filters/skill_focus_mod.rb +25 -0
  31. data/lib/pfrpg_core/filters/strength_mod.rb +21 -0
  32. data/lib/pfrpg_core/filters/weapon_finesse_mod.rb +24 -0
  33. data/lib/pfrpg_core/filters/weapon_focus_mod.rb +23 -0
  34. data/lib/pfrpg_core/filters/weapon_mastery_mod.rb +23 -0
  35. data/lib/pfrpg_core/filters/weapon_specialization_mod.rb +22 -0
  36. data/lib/pfrpg_core/filters/weapon_training_mod.rb +22 -0
  37. data/lib/pfrpg_core/inventory.rb +62 -0
  38. data/lib/pfrpg_core/item.rb +42 -0
  39. data/lib/pfrpg_core/level.rb +14 -0
  40. data/lib/pfrpg_core/level_parser.rb +9 -1
  41. data/lib/pfrpg_core/macros/macro.rb +62 -0
  42. data/lib/pfrpg_core/macros/manyshot_macro.rb +12 -0
  43. data/lib/pfrpg_core/macros/power_attack_macro.rb +12 -0
  44. data/lib/pfrpg_core/macros/rapid_shot_macro.rb +12 -0
  45. data/lib/pfrpg_core/macros/sneak_attack_macro.rb +33 -0
  46. data/lib/pfrpg_core/macros/vital_strike_macro.rb +12 -0
  47. data/lib/pfrpg_core/null_object.rb +31 -29
  48. data/lib/pfrpg_core/saving_throws.rb +43 -0
  49. data/lib/pfrpg_core/skills.rb +141 -0
  50. data/lib/pfrpg_core/specializer.rb +48 -0
  51. data/lib/pfrpg_core/spell.rb +37 -0
  52. data/lib/pfrpg_core/spell_books.rb +36 -0
  53. data/lib/pfrpg_core/spells_per_level.rb +53 -0
  54. data/lib/pfrpg_core/version.rb +1 -1
  55. data/lib/pfrpg_core/weapon_training_calculator.rb +36 -0
  56. data/lib/pfrpg_core.rb +13 -2
  57. data/test/alignment_test.rb +30 -0
  58. data/test/attributes_test.rb +50 -0
  59. data/test/bonus_test.rb +29 -0
  60. data/test/feat_totaler_test.rb +24 -7
  61. data/test/helper.rb +1 -1
  62. data/test/levels_test.rb +19 -0
  63. data/test/misc_test.rb +78 -0
  64. data/test/saves_test.rb +54 -0
  65. data/test/skills_test.rb +93 -0
  66. data/test/test_helper.rb +78 -0
  67. metadata +66 -35
  68. data/lib/pfrpg_core/affectable.rb +0 -7
  69. data/lib/pfrpg_core/effect.rb +0 -39
  70. data/lib/pfrpg_core/prerequisite.rb +0 -58
  71. data/lib/pfrpg_core/quick_classed.rb +0 -37
@@ -0,0 +1,25 @@
1
+ module PfrpgCore
2
+ module Filters
3
+ class SkillFocusMod
4
+ attr_reader :character
5
+ def initialize(character)
6
+ @character = character
7
+ end
8
+
9
+ def filter(skill)
10
+ prof_feats = character.feats.select { |x| x.name == 'Skill Focus' }
11
+ skills = []
12
+ prof_feats.each do |f|
13
+ skills << f.feat_special
14
+ end
15
+ if skills.include? skill.name
16
+ modify_skill_for_skill_focus(skill)
17
+ end
18
+ end
19
+
20
+ def modify_skill_for_skill_focus(skill)
21
+ skill.misc_bonus += 3
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,21 @@
1
+ module PfrpgCore
2
+ module Filters
3
+ class StrengthMod
4
+ attr_reader :character
5
+ def initialize(character)
6
+ @character = character
7
+ end
8
+
9
+ def filter(attack)
10
+ if attack.weapon.applies_strength_dmg?
11
+ base_mod = character.attributes.str_mod
12
+ if(attack.weapon.weight_class == 'Two-handed')
13
+ base_mod = (base_mod * 1.5)
14
+ end
15
+ attack.filter_str << "Strength damage : #{base_mod}"
16
+ attack.damage.add_static(base_mod) if attack.weapon.applies_strength_dmg?
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,24 @@
1
+ module PfrpgCore
2
+ module Filters
3
+ class WeaponFinesseMod
4
+ attr_reader :character
5
+ def initialize(character)
6
+ @character = character
7
+ end
8
+
9
+ def filter(attack)
10
+ if character.feats.find { |x| x.name == 'Weapon Finesse' }
11
+ modify_attack_for_weapon_finesse(attack, character.attributes.dex_mod, character.attributes.str_mod)
12
+ end
13
+ end
14
+
15
+ def modify_attack_for_weapon_finesse(attack, dex, str)
16
+ if attack.weight_class == 'light' || attack.weight_class == 'natural'
17
+ attack.filter_str << "Weapon Finesse: -#{dex}, +#{str}"
18
+ attack.other_bonus += dex
19
+ attack.other_bonus -= str
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module PfrpgCore
2
+ module Filters
3
+ class WeaponFocusMod
4
+ attr_reader :character
5
+ def initialize(character)
6
+ @character = character
7
+ end
8
+
9
+ def filter(attack)
10
+ f = character.feats.find { |x| x.name == 'Weapon Focus' }
11
+ if f != nil && (attack.weapon.name.downcase == f.special.downcase)
12
+ modify(attack)
13
+ end
14
+ return attack
15
+ end
16
+
17
+ def modify(attack)
18
+ attack.filter_str << "Weapon Focus: attack bonus +1"
19
+ attack.other_bonus += 1
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ class PfrpgCore::Filters::WeaponMasteryMod
2
+ attr_reader :character, :special
3
+ def initialize(character, feature)
4
+ @character = character
5
+ @special = feature.special
6
+ end
7
+
8
+ def filter(attack)
9
+ if attack.weapon.name == @special
10
+ modify(attack)
11
+ end
12
+ end
13
+
14
+ def modify(attack)
15
+ attack.critical_damage = increase_crit(attack.critical_damage)
16
+ end
17
+
18
+ def increase_crit(crit_str)
19
+ crit_num = crit_str.split("x")[0].to_i
20
+ crit_num += 1
21
+ "#{crit_num}x"
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module PfrpgCore
2
+ module Filters
3
+ class WeaponSpecializationMod
4
+ attr_reader :character
5
+ def initialize(character)
6
+ @character = character
7
+ end
8
+
9
+ def filter(attack)
10
+ f = character.feats.find { |x| x.name == 'Weapon Specialization' }
11
+ if f != nil && attack.weapon.name == f.feat_special
12
+ modify(a)
13
+ end
14
+ end
15
+
16
+ def modify(attack)
17
+ attack.filter_str << "Weapon Specialization: +2 damage"
18
+ attack.damage.add_static(2)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ module PfrpgCore
2
+ module Filters
3
+ class WeaponTrainingMod
4
+ attr_reader :character, :feature
5
+ def initialize(character, feature)
6
+ @character = character
7
+ @feature = feature
8
+ end
9
+
10
+ def filter(attack)
11
+ trainer = PfrpgCore::WeaponTrainingCalculator.new(character)
12
+ trained = trainer.trained_value(attack.weapon, feature)
13
+ modify(attack, trained)
14
+ end
15
+
16
+ def modify(attack, val)
17
+ attack.other_bonus += val
18
+ attack.damage.add_static(val)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,62 @@
1
+ module PfrpgCore
2
+ class Inventory
3
+ attr_reader :equipment, :inventory
4
+ def initialize(equipment, inventory)
5
+ @equipment = equipment
6
+ @inventory = inventory
7
+ end
8
+
9
+ def get_armor_ac
10
+ equipped_armor.nil? ? 0 : equipped_armor.ac_bonus
11
+ end
12
+
13
+ def get_shield_ac
14
+ shield = equipment.find { |x| x.slot == 'shield' }
15
+ shield.nil? ? 0 : shield.ac_bonus
16
+ end
17
+
18
+ def equipped_defensive
19
+ equipment.select { |x| x.ac_bonus != nil }
20
+ end
21
+
22
+ def equipped_armor
23
+ equipment.find { |x| x.slot == 'armor' }
24
+ end
25
+
26
+ def armor_and_shield
27
+ e = [ equipped_armor ]
28
+ e << equipment.find { |x| x.slot == 'shield' }
29
+ e.delete_if { |x| x == nil }
30
+ e
31
+ end
32
+
33
+ # monk is obnoxious
34
+ def equipped_weapons(levels = [])
35
+ weapons = equipment.select { |x| x.type == 'Weapon' || x.type == 'MagicWeapon' }
36
+ monk = levels.find { |x| x.classname == 'Monk' }
37
+ weapons << unarmed_strike(monk.rank) if (monk != nil && weapons.empty?)
38
+ return weapons
39
+ end
40
+
41
+ def unarmed_strike(monk_level)
42
+ PfrpgClasses::Heroclass.by_name('Monk').get_unarmed_weapon(monk_level)
43
+ end
44
+
45
+ def ac_penalty(bonuses = Bonuses.new)
46
+ ac_penalty = 0
47
+ equipment.each do |e|
48
+ ac_penalty += NullObject.maybe(e.armor_check_penalty).to_i
49
+ end
50
+ ac_penalty += bonuses.get("ac_penalty").to_i
51
+ ac_penalty = 0 if ac_penalty > 0
52
+ ac_penalty
53
+ end
54
+
55
+ def as_json(options={})
56
+ {
57
+ equipment: get_equipment,
58
+ inventory: get_inventory
59
+ }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,42 @@
1
+ module PfrpgCore
2
+ class Item
3
+ include PfrpgUtility::Affectable
4
+ attr_reader :name, :description, :slot, :type, :equipped, :effects,
5
+ :cost, :dmg_s, :dmg_m, :critical_range, :critical_dmg,
6
+ :range, :weight, :dmg_type, :special, :source, :weapon_type,
7
+ :weight_class, :ac_bonus, :max_dex_bonus, :armor_check_penalty,
8
+ :arcane_spell_failure_chance, :speed_twenty, :speed_thirty
9
+
10
+ def initialize(item, equipped)
11
+ @name = item[:name]
12
+ @description = item[:description]
13
+ @slot = item[:slot]
14
+ @type = item[:type]
15
+ @effects = item[:effects]
16
+ @cost = item[:cost]
17
+ @dmg_m = item[:dmg_m]
18
+ @dmg_s = item[:dmg_s]
19
+ @critical_range = item[:critical_range]
20
+ @critical_dmg = item[:critical_dmg]
21
+ @range = item[:range]
22
+ @weight = item[:weight]
23
+ @dmg_type = item[:dmg_type]
24
+ @special = item[:special]
25
+ @source = item[:source]
26
+ @weapon_type = item[:weapon_type]
27
+ @weight_class = item[:weight_class]
28
+ @ac_bonus = item[:ac_bonus]
29
+ @max_dex_bonus = item[:max_dex_bonus]
30
+ @armor_check_penalty = item[:armor_check_penalty]
31
+ @arcane_spell_failure_chance = item[:arcane_spell_failure_chance]
32
+ @speed_twenty = item[:speed_twenty]
33
+ @speed_thirty = item[:speed_thirty]
34
+ @equipped = equipped
35
+ end
36
+
37
+ def applies_strength_dmg?
38
+ ['light', 'one-handed', 'two-handed'].include?(weight_class.downcase) ||
39
+ name == 'Sling'
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,14 @@
1
+ require 'pfrpg_classes'
2
+
3
+ module PfrpgCore
4
+ class Level
5
+ attr_reader :rank, :classname, :favored, :heroclass
6
+ def initialize(level)
7
+ @rank = level.class_number
8
+ @classname = level.class_name
9
+ @heroclass = PfrpgClasses::Heroclass.by_name(@classname)
10
+ @favored = level.favored
11
+ end
12
+ end
13
+
14
+ end
@@ -3,13 +3,21 @@ module PfrpgCore
3
3
  attr_reader :level_string, :levels
4
4
  def initialize(level_string)
5
5
  @level_string = level_string
6
- @levels = parse
6
+ begin
7
+ @levels = parse
8
+ rescue Exception
9
+ @levels = {}
10
+ end
7
11
  end
8
12
 
9
13
  def total
10
14
  return @levels.values.inject(0) { |sum, l| sum + l }
11
15
  end
12
16
 
17
+ def levelmap
18
+ @levels.map { |x| PfrpgCore::Level.new(x) }
19
+ end
20
+
13
21
  def parse
14
22
  levels = {}
15
23
  splits = @level_string.split(';')
@@ -0,0 +1,62 @@
1
+ module PfrpgCore
2
+ class Macro
3
+ attr_reader :character
4
+
5
+ def initialize(character)
6
+ @character = character
7
+ end
8
+
9
+ def available?
10
+ false
11
+ end
12
+
13
+ def applies_to?(_weapon)
14
+ false
15
+ end
16
+
17
+ def slug
18
+ name = self.class.name.split("PfrpgCore::")[1]
19
+ name.split('Macro').first
20
+ end
21
+
22
+ def name
23
+ slug.titleize
24
+ end
25
+
26
+ def info
27
+ nil
28
+ end
29
+
30
+ def as_json
31
+ {
32
+ slug: slug,
33
+ name: name,
34
+ info: info
35
+ }
36
+ end
37
+
38
+ def self.find_available(character)
39
+ macro_subclasses.map { |m| m.new(character) }.keep_if { |m| m.available? }
40
+ end
41
+
42
+ # TODO: This should not be needed - Rails provides #subclasses but there is
43
+ # crazy classloading stuff happening (or not happening actually) that
44
+ # is causing this to be empty...
45
+ def self.macro_subclasses
46
+ # Macro.subclasses
47
+ [
48
+ PowerAttackMacro,
49
+ SneakAttackMacro,
50
+ RapidShotMacro,
51
+ ManyshotMacro,
52
+ VitalStrikeMacro
53
+ ]
54
+ end
55
+ end
56
+ end
57
+
58
+ require 'pfrpg_core/macros/manyshot_macro'
59
+ require 'pfrpg_core/macros/power_attack_macro'
60
+ require 'pfrpg_core/macros/rapid_shot_macro'
61
+ require 'pfrpg_core/macros/sneak_attack_macro'
62
+ require 'pfrpg_core/macros/vital_strike_macro'
@@ -0,0 +1,12 @@
1
+ module PfrpgCore
2
+ class ManyshotMacro < Macro
3
+
4
+ def available?
5
+ character.feats.any? { |feat| feat.name == 'Manyshot' }
6
+ end
7
+
8
+ def applies_to?(weapon)
9
+ weapon.weight_class == 'ranged' && weapon.name.downcase.include?('bow')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module PfrpgCore
2
+ class PowerAttackMacro < Macro
3
+
4
+ def available?
5
+ character.feats.any? { |feat| feat.name == 'Power Attack' }
6
+ end
7
+
8
+ def applies_to?(weapon)
9
+ weapon.weight_class != 'ranged' && weapon.weight_class != 'ammunition'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module PfrpgCore
2
+ class RapidShotMacro < Macro
3
+
4
+ def available?
5
+ character.feats.any? { |feat| feat.name == 'Rapid Shot' }
6
+ end
7
+
8
+ def applies_to?(weapon)
9
+ weapon.weight_class == 'ranged'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,33 @@
1
+ module PfrpgCore
2
+ class SneakAttackMacro < Macro
3
+
4
+ def initialize(character)
5
+ super
6
+
7
+ sneak_attack_features = character.class_features.select do |feature|
8
+ feature.name.downcase.start_with? 'sneak a'
9
+ end
10
+
11
+ @sneak_attack_level = sneak_attack_features.map do |feature|
12
+ /Sneak Attack \((\d+)d6\)/.match(feature.name)[1].to_i
13
+ end.max || 0
14
+ end
15
+
16
+ def name
17
+ "Sneak Attack (level #{@sneak_attack_level})"
18
+ end
19
+
20
+ def available?
21
+ @sneak_attack_level.nonzero?
22
+ end
23
+
24
+ def applies_to?(weapon)
25
+ true
26
+ end
27
+
28
+ def info
29
+ { sneakAttackLevel: @sneak_attack_level }
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ module PfrpgCore
2
+ class VitalStrikeMacro < Macro
3
+
4
+ def available?
5
+ character.feats.any? { |feat| feat.name == 'Vital Strike' }
6
+ end
7
+
8
+ def applies_to?(weapon)
9
+ true
10
+ end
11
+ end
12
+ end
@@ -1,37 +1,39 @@
1
- class PfrpgCore::NullObject < Numeric
2
- def to_a; []; end
3
- def to_s; ""; end
4
- def to_str; ""; end
5
- def to_f; 0.0; end
6
- def to_i; 0; end
1
+ module PfrpgCore
2
+ class NullObject < Numeric
3
+ def to_a; []; end
4
+ def to_s; ""; end
5
+ def to_str; ""; end
6
+ def to_f; 0.0; end
7
+ def to_i; 0; end
7
8
 
8
- def empty?
9
- true
10
- end
9
+ def empty?
10
+ true
11
+ end
11
12
 
12
- def as_json(options={})
13
- ""
14
- end
13
+ def as_json(options={})
14
+ ""
15
+ end
15
16
 
16
- def method_missing(*args, &block)
17
- self
18
- end
17
+ def method_missing(*args, &block)
18
+ self
19
+ end
19
20
 
20
- def self.maybe(value)
21
- case value
22
- when nil then NullObject.new
23
- else value
21
+ def self.maybe(value)
22
+ case value
23
+ when nil then NullObject.new
24
+ else value
25
+ end
24
26
  end
25
- end
26
27
 
27
- def +(other)
28
- case other
29
- when String
30
- to_s + other
31
- when Fixnum
32
- to_i + other
33
- when Float
34
- to_f + other
28
+ def +(other)
29
+ case other
30
+ when String
31
+ to_s + other
32
+ when Fixnum
33
+ to_i + other
34
+ when Float
35
+ to_f + other
36
+ end
35
37
  end
36
38
  end
37
- end
39
+ end
@@ -0,0 +1,43 @@
1
+ module PfrpgCore
2
+ class SavingThrows
3
+
4
+ def initialize(saves, bonuses, attributes)
5
+ @attributes = attributes
6
+ @will = saves[:will]
7
+ @will_bonus = NullObject.maybe(bonuses.get('will')).to_i
8
+ @fortitude = saves[:fort]
9
+ @fort_bonus = NullObject.maybe(bonuses.get('fort')).to_i
10
+ @reflex = saves[:ref]
11
+ @ref_bonus = NullObject.maybe(bonuses.get('ref')).to_i
12
+ end
13
+
14
+ def fortitude
15
+ @attributes.con_mod + @fortitude + @fort_bonus
16
+ end
17
+
18
+ def reflex
19
+ @attributes.dex_mod + @reflex + @ref_bonus
20
+ end
21
+
22
+ def will
23
+ @attributes.wis_mod + @will + @will_bonus
24
+ end
25
+
26
+ def as_json(options={})
27
+ {
28
+ :con_modifier => @attributes.con_mod,
29
+ :wis_modifier => @attributes.wis_mod,
30
+ :dex_modifier => @attributes.dex_mod,
31
+ :base_ref => @reflex,
32
+ :base_fort => @fortitude,
33
+ :base_will => @will,
34
+ :bonus_ref => @ref_bonus,
35
+ :bonus_will => @will_bonus,
36
+ :bonus_fort => @fort_bonus,
37
+ :fortitude => fortitude,
38
+ :reflex => reflex,
39
+ :will => will
40
+ }
41
+ end
42
+ end
43
+ end