pfrpg_core 0.0.2 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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