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.
- checksums.yaml +4 -4
- data/LICENSE +23 -0
- data/Rakefile +1 -1
- data/lib/pfrpg_core/attack.rb +66 -0
- data/lib/pfrpg_core/attributes.rb +113 -0
- data/lib/pfrpg_core/avatar_url.rb +15 -0
- data/lib/pfrpg_core/bonus.rb +1 -1
- data/lib/pfrpg_core/character.rb +83 -0
- data/lib/pfrpg_core/class_feature.rb +31 -0
- data/lib/pfrpg_core/combat.rb +60 -0
- data/lib/pfrpg_core/demographics.rb +30 -0
- data/lib/pfrpg_core/derived/combat_bonuses.rb +78 -0
- data/lib/pfrpg_core/derived/defense.rb +29 -0
- data/lib/pfrpg_core/derived/magic.rb +145 -0
- data/lib/pfrpg_core/derived/misc.rb +114 -0
- data/lib/pfrpg_core/derived/offense.rb +49 -0
- data/lib/pfrpg_core/derived.rb +10 -0
- data/lib/pfrpg_core/effect_processor.rb +1 -1
- data/lib/pfrpg_core/effect_totaler.rb +29 -0
- data/lib/pfrpg_core/equipment_totaler.rb +14 -0
- data/lib/pfrpg_core/feat.rb +27 -0
- data/lib/pfrpg_core/feat_totaler.rb +61 -0
- data/lib/pfrpg_core/feature_duplicator.rb +26 -0
- data/lib/pfrpg_core/feature_totaler.rb +86 -0
- data/lib/pfrpg_core/filterable.rb +1 -1
- data/lib/pfrpg_core/filters/filter.rb +14 -0
- data/lib/pfrpg_core/filters/proficiency_mod.rb +34 -0
- data/lib/pfrpg_core/filters/prowess_mod.rb +20 -0
- data/lib/pfrpg_core/filters/ranged_attack_mod.rb +28 -0
- data/lib/pfrpg_core/filters/skill_focus_mod.rb +25 -0
- data/lib/pfrpg_core/filters/strength_mod.rb +21 -0
- data/lib/pfrpg_core/filters/weapon_finesse_mod.rb +24 -0
- data/lib/pfrpg_core/filters/weapon_focus_mod.rb +23 -0
- data/lib/pfrpg_core/filters/weapon_mastery_mod.rb +23 -0
- data/lib/pfrpg_core/filters/weapon_specialization_mod.rb +22 -0
- data/lib/pfrpg_core/filters/weapon_training_mod.rb +22 -0
- data/lib/pfrpg_core/inventory.rb +62 -0
- data/lib/pfrpg_core/item.rb +42 -0
- data/lib/pfrpg_core/level.rb +14 -0
- data/lib/pfrpg_core/level_parser.rb +9 -1
- data/lib/pfrpg_core/macros/macro.rb +62 -0
- data/lib/pfrpg_core/macros/manyshot_macro.rb +12 -0
- data/lib/pfrpg_core/macros/power_attack_macro.rb +12 -0
- data/lib/pfrpg_core/macros/rapid_shot_macro.rb +12 -0
- data/lib/pfrpg_core/macros/sneak_attack_macro.rb +33 -0
- data/lib/pfrpg_core/macros/vital_strike_macro.rb +12 -0
- data/lib/pfrpg_core/null_object.rb +31 -29
- data/lib/pfrpg_core/saving_throws.rb +43 -0
- data/lib/pfrpg_core/skills.rb +141 -0
- data/lib/pfrpg_core/specializer.rb +48 -0
- data/lib/pfrpg_core/spell.rb +37 -0
- data/lib/pfrpg_core/spell_books.rb +36 -0
- data/lib/pfrpg_core/spells_per_level.rb +53 -0
- data/lib/pfrpg_core/version.rb +1 -1
- data/lib/pfrpg_core/weapon_training_calculator.rb +36 -0
- data/lib/pfrpg_core.rb +13 -2
- data/test/alignment_test.rb +30 -0
- data/test/attributes_test.rb +50 -0
- data/test/bonus_test.rb +29 -0
- data/test/feat_totaler_test.rb +24 -7
- data/test/helper.rb +1 -1
- data/test/levels_test.rb +19 -0
- data/test/misc_test.rb +78 -0
- data/test/saves_test.rb +54 -0
- data/test/skills_test.rb +93 -0
- data/test/test_helper.rb +78 -0
- metadata +66 -35
- data/lib/pfrpg_core/affectable.rb +0 -7
- data/lib/pfrpg_core/effect.rb +0 -39
- data/lib/pfrpg_core/prerequisite.rb +0 -58
- data/lib/pfrpg_core/quick_classed.rb +0 -37
@@ -0,0 +1,145 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
module Derived
|
3
|
+
module Magic
|
4
|
+
class PfrpgCore::InvalidClassException < Exception; end
|
5
|
+
class PfrpgCore::SpellNotFoundException < Exception; end
|
6
|
+
class PfrpgCore::TooManySpellsException < Exception; end
|
7
|
+
class PfrpgCore::InvalidSpellException < Exception; end
|
8
|
+
class PfrpgCore::TooLowAttributeException < Exception; end
|
9
|
+
|
10
|
+
def spells_per_level(class_name, latest=nil)
|
11
|
+
latest ||= self.latest_levels
|
12
|
+
level = latest.find { |x| x.class_name == class_name }
|
13
|
+
return empty_spells unless level
|
14
|
+
return empty_spells if(['Paladin', 'Ranger'].include?(class_name) && level.class_number < 4)
|
15
|
+
base = level.heroclass.spells_per_day(level.class_number)
|
16
|
+
bonuses = PfrpgTables::Tables::Spells::SpellsPerDay.bonus(attr_mod(class_name))
|
17
|
+
total = []
|
18
|
+
for i in 0..9 do
|
19
|
+
total[i] = base[i] + bonuses[i]
|
20
|
+
end
|
21
|
+
total = filter_for_max_attribute(class_name, total)
|
22
|
+
total = filter_for_high_level(level.class_number, total)
|
23
|
+
return total
|
24
|
+
end
|
25
|
+
|
26
|
+
# you can only cast spells up to ($SPELLS_ATTRIBUTE - 10)th level
|
27
|
+
def filter_for_max_attribute(class_name, total)
|
28
|
+
max_spell_level = attr_static(class_name) - 10
|
29
|
+
return filter(total, max_spell_level)
|
30
|
+
end
|
31
|
+
|
32
|
+
def filter_for_high_level(level, total)
|
33
|
+
max_spell_level = ((level + 1)/2).floor
|
34
|
+
return filter(total, max_spell_level)
|
35
|
+
end
|
36
|
+
|
37
|
+
def filter(array, max)
|
38
|
+
for i in (max+1)..9 do
|
39
|
+
array[i] = 0
|
40
|
+
end
|
41
|
+
return array
|
42
|
+
end
|
43
|
+
|
44
|
+
def empty_spells
|
45
|
+
[0,0,0,0,0,0,0,0,0,0]
|
46
|
+
end
|
47
|
+
|
48
|
+
def known_sorcerer_spells
|
49
|
+
spells = []
|
50
|
+
sorc = class_features.select { |x| x.type == 'SorcererSpells' }
|
51
|
+
sorc.each do |x|
|
52
|
+
x.special.each do |spell|
|
53
|
+
spells << spell
|
54
|
+
end
|
55
|
+
end
|
56
|
+
return spells
|
57
|
+
end
|
58
|
+
|
59
|
+
def bloodline
|
60
|
+
class_features.find { |x| x.type == 'Bloodline' }
|
61
|
+
end
|
62
|
+
|
63
|
+
def arcane_school
|
64
|
+
class_features.find { |x| x.type == 'ArcaneSchool' }
|
65
|
+
end
|
66
|
+
|
67
|
+
def can_arcane?
|
68
|
+
arcane_classes = ['Bard', 'Sorcerer', 'Wizard']
|
69
|
+
return latest_levels.find { |x| arcane_classes.include? x.class_name }
|
70
|
+
end
|
71
|
+
|
72
|
+
def can_divine?
|
73
|
+
divine = false
|
74
|
+
latest_levels.each do |level|
|
75
|
+
if ['Cleric', 'Druid'].include? level.class_name
|
76
|
+
divine = true
|
77
|
+
elsif (['Paladin', 'Ranger'].include? level.class_name) && (level.class_number >= 4)
|
78
|
+
divine = true
|
79
|
+
end
|
80
|
+
end
|
81
|
+
return divine
|
82
|
+
end
|
83
|
+
|
84
|
+
def gets_familiar?
|
85
|
+
(class_features.find { |x| x.feature_type == 'Arcane Bond' && x.ability_name == 'Familiar' }) != nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def gets_companion?
|
89
|
+
(class_features.find { |x| x.feature_type == 'Nature Bond' && x.ability_name == 'Animal Companion' }) != nil
|
90
|
+
end
|
91
|
+
|
92
|
+
def valid_spell?(spell, class_name, level, spellbooks)
|
93
|
+
return has_class?(class_name) &&
|
94
|
+
appropriate_level?(spell, class_name, level) &&
|
95
|
+
spell_fits?(class_name, level, spellbooks)
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def attr_static(class_name)
|
101
|
+
heroclass = PfrpgClasses::Heroclass.by_name(class_name)
|
102
|
+
attr = heroclass.spells_bonus_attr
|
103
|
+
return self.attributes.send("modified_#{attr}")
|
104
|
+
end
|
105
|
+
|
106
|
+
def attr_mod(class_name)
|
107
|
+
heroclass = PfrpgClasses::Heroclass.by_name(class_name)
|
108
|
+
attr = heroclass.spells_bonus_attr
|
109
|
+
return self.attributes.send("#{attr}_mod")
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def has_class?(class_name)
|
114
|
+
latest_levels.each do |l|
|
115
|
+
return true if l.class_name == class_name
|
116
|
+
end
|
117
|
+
raise InvalidClassException
|
118
|
+
end
|
119
|
+
|
120
|
+
def spell_fits?(class_name, level, spellbooks)
|
121
|
+
max_spells = spells_per_level(class_name, latest_levels)[level]
|
122
|
+
if spellbooks[class_name] == nil || spellbooks[class_name][level] == nil
|
123
|
+
return true
|
124
|
+
end
|
125
|
+
raise TooManySpellsException unless spellbooks[class_name][level].size < max_spells
|
126
|
+
return true
|
127
|
+
end
|
128
|
+
|
129
|
+
def appropriate_level?(spell, class_name, level)
|
130
|
+
begin
|
131
|
+
spell_level = spell.send(class_name.downcase)
|
132
|
+
raise InvalidSpellException if spell_level == 'NULL'
|
133
|
+
spell_level_for_class = spell.send(class_name.downcase).to_i
|
134
|
+
raise InvalidSpellException if spell_level_for_class > level
|
135
|
+
raise TooLowSpellException if (attr_static(class_name)-10) < spell_level_for_class
|
136
|
+
return true
|
137
|
+
catch Exception
|
138
|
+
raise InvalidSpellException
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
module Derived
|
3
|
+
module Misc
|
4
|
+
|
5
|
+
def level_string
|
6
|
+
str = "#{@alignment} :"
|
7
|
+
@levels.each do |l|
|
8
|
+
str += "#{l.name}/#{l.rank}"
|
9
|
+
str += "*" if l.favored
|
10
|
+
end
|
11
|
+
str
|
12
|
+
end
|
13
|
+
|
14
|
+
def initiative
|
15
|
+
initiative = 0
|
16
|
+
initiative += @attributes.dex_mod
|
17
|
+
initiative += get_bonus("initiative").to_i
|
18
|
+
return initiative
|
19
|
+
end
|
20
|
+
|
21
|
+
def speed
|
22
|
+
base_speed = NullObject.maybe(@race).speed || 0
|
23
|
+
base_speed += parse_speed_bonuses(get_bonus("speed"))
|
24
|
+
base_speed -= armor_speed_penalty
|
25
|
+
speeds = {}
|
26
|
+
['land','fly','climb','swim'].each do |type|
|
27
|
+
speeds["#{type}_speed"] = calc_speed(type, base_speed)
|
28
|
+
end
|
29
|
+
speeds['speed'] = speeds['land_speed']
|
30
|
+
return speeds
|
31
|
+
end
|
32
|
+
|
33
|
+
def calc_speed(speed_type, base)
|
34
|
+
base + get_bonus("#{speed_type}_speed")
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_speed_bonuses(speed_bonuses)
|
38
|
+
speed_bonus = 0
|
39
|
+
speed_bonuses.each do |s|
|
40
|
+
speed_bonus += s.to_i if MathHelper.is_number(s)
|
41
|
+
end
|
42
|
+
speed_bonus
|
43
|
+
end
|
44
|
+
|
45
|
+
def hit_points
|
46
|
+
hp = @character.hit_points
|
47
|
+
# TODO : potentially move off this method when multiple hp affect stats exist
|
48
|
+
if (@feats.find { |x| x.name == 'Toughness' })
|
49
|
+
hp += 3
|
50
|
+
if (total_level > 3)
|
51
|
+
hp += total_level - 3
|
52
|
+
end
|
53
|
+
end
|
54
|
+
return hp
|
55
|
+
end
|
56
|
+
|
57
|
+
def size
|
58
|
+
return @race.size if @race
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def total_level
|
63
|
+
@levels.inject(0) {|sum, x| sum = sum + x.rank }
|
64
|
+
end
|
65
|
+
|
66
|
+
def hit_die
|
67
|
+
total_level
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_class_level(heroclass)
|
71
|
+
l = latest_levels.find { |x| x.class_name[heroclass.name] }
|
72
|
+
if l
|
73
|
+
return l.class_number
|
74
|
+
else
|
75
|
+
return 0
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def explode_levels
|
80
|
+
# TODO: implement me
|
81
|
+
[]
|
82
|
+
end
|
83
|
+
|
84
|
+
def hit_dice
|
85
|
+
hit_die
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_caster_level
|
89
|
+
# TODO constantize somewhere better
|
90
|
+
caster_classes = ['Bard', 'Cleric', 'Druid', 'Paladin', 'Ranger', 'Sorcerer', 'Wizard']
|
91
|
+
caster_levels = latest_levels.select { |x| caster_classes.include? x.class_name }
|
92
|
+
max_caster_level = caster_levels.map { |x| x.class_number }.max || 0
|
93
|
+
ek_level = get_class_level(PfrpgClasses::Heroclass.by_name('Eldritch Knight'))
|
94
|
+
return (max_caster_level + ek_level) || 0
|
95
|
+
end
|
96
|
+
|
97
|
+
def misc_json
|
98
|
+
{
|
99
|
+
:initiative => initiative,
|
100
|
+
:alignment => @alignment,
|
101
|
+
:speed => speed,
|
102
|
+
:hit_points => hit_points,
|
103
|
+
:feats => @feats,
|
104
|
+
:class_features => @class_features,
|
105
|
+
:levels => @levels,
|
106
|
+
:total_level => @total_level,
|
107
|
+
:size => size,
|
108
|
+
:level_string => level_string,
|
109
|
+
:temporary => @character.temp_values
|
110
|
+
}
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
module Derived
|
3
|
+
module Offense
|
4
|
+
def combat_maneuver_bonus
|
5
|
+
base = str_bonus + bab - size_modifier
|
6
|
+
misc = character.get_bonus('cmb').to_i
|
7
|
+
base += misc
|
8
|
+
return base
|
9
|
+
end
|
10
|
+
|
11
|
+
def attack_filters
|
12
|
+
standard_filters = [
|
13
|
+
PfrpgCore::Filters::StrengthMod.new(character),
|
14
|
+
PfrpgCore::Filters::WeaponFinesseMod.new(character),
|
15
|
+
PfrpgCore::Filters::RangedAttackMod.new(character),
|
16
|
+
PfrpgCore::Filters::WeaponFocusMod.new(character),
|
17
|
+
PfrpgCore::Filters::WeaponSpecializationMod.new(character),
|
18
|
+
PfrpgCore::Filters::ProficiencyMod.new(character)
|
19
|
+
]
|
20
|
+
standard_filters << character.get_attack_filters
|
21
|
+
standard_filters.flatten
|
22
|
+
end
|
23
|
+
|
24
|
+
def attacks
|
25
|
+
macros = Macro.find_available(character)
|
26
|
+
|
27
|
+
atk = character.inventory.equipped_weapons(character.levels).collect do |w|
|
28
|
+
Attack.new({ :weapon_name => w.get_best_name,
|
29
|
+
:range => w.range,
|
30
|
+
:weight_class => w.weight_class,
|
31
|
+
:damage => w.damage,
|
32
|
+
:weapon_type => w.weapon_type,
|
33
|
+
:critical_range => w.critical_range,
|
34
|
+
:critical_dmg => w.critical_dmg,
|
35
|
+
:bonus => w.bonus,
|
36
|
+
:strength_bonus => str_bonus,
|
37
|
+
:bab => bab,
|
38
|
+
:size => character.race.size,
|
39
|
+
:filters => attack_filters,
|
40
|
+
:filter_str => [],
|
41
|
+
:weapon => w,
|
42
|
+
:macros => macros.select { |m| m.applies_to? w }
|
43
|
+
})
|
44
|
+
end
|
45
|
+
return atk
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -10,7 +10,7 @@ module PfrpgCore
|
|
10
10
|
final = []
|
11
11
|
no_stacking = effects_hash(@effects)
|
12
12
|
no_stacking.keys.each do |key|
|
13
|
-
if Tables::Bonus.stackable?(key) || Tables::Bonus.special?(key)
|
13
|
+
if PfrpgTables::Tables::Bonus.stackable?(key) || PfrpgTables::Tables::Bonus.special?(key)
|
14
14
|
final << no_stacking[key]
|
15
15
|
else
|
16
16
|
final << find_max(no_stacking[key])
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
class EffectTotaler
|
3
|
+
attr_reader :character
|
4
|
+
def initialize(character)
|
5
|
+
@character = character
|
6
|
+
end
|
7
|
+
|
8
|
+
def effects
|
9
|
+
effects = []
|
10
|
+
[
|
11
|
+
NullObject.maybe(@character.feats),
|
12
|
+
NullObject.maybe(@character.inventory.equipment),
|
13
|
+
NullObject.maybe(@character.race).traits,
|
14
|
+
NullObject.maybe(@character.class_features)
|
15
|
+
].each do |r|
|
16
|
+
r.each do |x|
|
17
|
+
effects << x.get_effects
|
18
|
+
end
|
19
|
+
end
|
20
|
+
# TODO : calculate level based effects
|
21
|
+
NullObject.maybe(@character.explode_levels).each do |l|
|
22
|
+
l.class_features.each do |x|
|
23
|
+
effects << x.get_effects
|
24
|
+
end
|
25
|
+
end
|
26
|
+
return effects.flatten
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
class Feat
|
3
|
+
include PfrpgUtility::Affectable
|
4
|
+
attr_reader :name, :description, :effects, :prereqs,
|
5
|
+
:special, :type, :source
|
6
|
+
|
7
|
+
def initialize(params)
|
8
|
+
@name = params[:name]
|
9
|
+
@description = params[:description]
|
10
|
+
@effects = params[:effects] || []
|
11
|
+
@prereqs = params[:prereqs] || []
|
12
|
+
@special = params[:special]
|
13
|
+
@type = params[:type]
|
14
|
+
@source = params[:source]
|
15
|
+
end
|
16
|
+
|
17
|
+
def as_json(options={})
|
18
|
+
{
|
19
|
+
name: @name,
|
20
|
+
description: @description,
|
21
|
+
special: @special,
|
22
|
+
type: @type,
|
23
|
+
source: @source
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module PfrpgTables
|
2
|
+
class FeatTotaler
|
3
|
+
attr_reader :entity, :levels
|
4
|
+
def initialize(entity)
|
5
|
+
@entity = entity
|
6
|
+
@levels = PfrpgCore::LevelParser.new(entity.level)
|
7
|
+
end
|
8
|
+
|
9
|
+
def total
|
10
|
+
return level_granted + race_granted + feature_granted + choice_granted
|
11
|
+
end
|
12
|
+
|
13
|
+
def level_granted
|
14
|
+
i = (1..levels.total).inject(0) do |sum, x|
|
15
|
+
choices = PfrpgTables::Tables::LevelTable.for_level(x)[:choices]
|
16
|
+
sum = sum + (choices.select { |c| choice_is_feat(c) }).size
|
17
|
+
end
|
18
|
+
return i
|
19
|
+
end
|
20
|
+
|
21
|
+
def race_granted
|
22
|
+
i = entity.race.bonus_choices.inject(0) do |sum, x|
|
23
|
+
sum = sum + 1 if choice_is_feat(x)
|
24
|
+
end
|
25
|
+
return i
|
26
|
+
end
|
27
|
+
|
28
|
+
def feature_granted
|
29
|
+
i = entity.class_features.inject(0) do |sum, x|
|
30
|
+
if x.respond_to? 'granted_choice'
|
31
|
+
choices = [x.granted_choice].flatten
|
32
|
+
sum += (choices.select { |c| choice_is_feat(c) }).size
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return i || 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def choice_granted
|
39
|
+
choices = []
|
40
|
+
lvls = @levels.parse
|
41
|
+
lvls.keys.each do |hc|
|
42
|
+
clazz = PfrpgClasses::Heroclass.by_name(hc)
|
43
|
+
level = lvls[hc]
|
44
|
+
(1..level).each do |l|
|
45
|
+
clazz.bonuses_for_level(l)[:choices].each do |choice|
|
46
|
+
if (choice_is_feat(choice))
|
47
|
+
choice.npc_id = entity.id
|
48
|
+
choices << choice
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
@choose_features = choices.flatten.size
|
54
|
+
end
|
55
|
+
|
56
|
+
def choice_is_feat(choice)
|
57
|
+
choice.class.to_s['Feat'] != nil
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
class PfrpgCore::FeatureDuplicator
|
2
|
+
|
3
|
+
def self.dupe_words
|
4
|
+
['Sneak Attack', 'Channel Energy', 'Damage Reduction', 'Trap Sense']
|
5
|
+
end
|
6
|
+
|
7
|
+
# filter things with multiple bonuses, like sneak attack and channel energy
|
8
|
+
def self.filter_duplicates(features)
|
9
|
+
dupe_words.each do |w|
|
10
|
+
features = features - smallest(features, w)
|
11
|
+
end
|
12
|
+
features
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.smallest(features, phrase)
|
16
|
+
smallest = features.select { |x| x.name[phrase] != nil }
|
17
|
+
return [] if (smallest.empty? || smallest.size == 1)
|
18
|
+
smallest.sort_by! { |x| parse_number(x.name) }
|
19
|
+
smallest[0..smallest.size-2]
|
20
|
+
end
|
21
|
+
|
22
|
+
# of the form, 'Sneak Attack (10d6)'
|
23
|
+
def self.parse_number(description)
|
24
|
+
(description.split('d6')[0].split(' ').last.split('(')[1]).to_i
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module PfrpgTables
|
2
|
+
class FeatureTotaler
|
3
|
+
attr_reader :entity, :levels
|
4
|
+
def initialize(entity)
|
5
|
+
@entity = entity
|
6
|
+
@levels = PfrpgCore::LevelParser.new(entity.level)
|
7
|
+
@base_features = []
|
8
|
+
@choose_features = []
|
9
|
+
parse_features
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_base_features
|
13
|
+
@base_features
|
14
|
+
end
|
15
|
+
|
16
|
+
def get_choose_features
|
17
|
+
@choose_features
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse_features
|
21
|
+
choices = []
|
22
|
+
lvls = levels.parse
|
23
|
+
lvls.keys.each do |hc|
|
24
|
+
clazz = PfrpgClasses::Heroclass.by_name(hc)
|
25
|
+
level = lvls[hc]
|
26
|
+
(1..level).each do |l|
|
27
|
+
class_features = clazz.bonuses_for_level(l)[:granted_features]
|
28
|
+
class_features.each do |x|
|
29
|
+
ap "Creating granted feature:"
|
30
|
+
ap "\t#{x} (#{hc})"
|
31
|
+
@base_features << ClassFeature.granted_feature(x, hc)
|
32
|
+
end
|
33
|
+
clazz.bonuses_for_level(l)[:choices].each do |choice|
|
34
|
+
if !(choice_is_feat(choice))
|
35
|
+
choice.npc_id = entity.id
|
36
|
+
choices << flatten_options(choice)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
@choose_features = choices.flatten
|
42
|
+
end
|
43
|
+
|
44
|
+
def flatten_options(feature_choice)
|
45
|
+
ap "feature chioce:"
|
46
|
+
ap feature_choice.choices[:options][0]
|
47
|
+
if feature_choice.choices[:options][0].respond_to? 'name'
|
48
|
+
new_options = feature_choice.choices[:options].map { |x| x.name }
|
49
|
+
ap "New options:"
|
50
|
+
ap new_options
|
51
|
+
feature_choice.choices[:options] = new_options
|
52
|
+
end
|
53
|
+
return feature_choice
|
54
|
+
end
|
55
|
+
|
56
|
+
def level_granted
|
57
|
+
i = (1..levels.total).inject(0) do |sum, x|
|
58
|
+
choices = PfrpgTables::Tables::LevelTable.for_level(x)[:choices]
|
59
|
+
sum = sum + (choices.select { |c| choice_is_feat(c) }).size
|
60
|
+
end
|
61
|
+
return i
|
62
|
+
end
|
63
|
+
|
64
|
+
def race_granted
|
65
|
+
race = entity.race
|
66
|
+
i = race.bonus_choices.inject(0) do |sum, x|
|
67
|
+
sum = sum + 1 if choice_is_feat(x)
|
68
|
+
end
|
69
|
+
return i
|
70
|
+
end
|
71
|
+
|
72
|
+
def feature_granted
|
73
|
+
i = entity.class_features.inject(0) do |sum, x|
|
74
|
+
choices = x.bonus_choices
|
75
|
+
sum += (choices.select { |c| choice_is_feat(c) }).size
|
76
|
+
end
|
77
|
+
return i
|
78
|
+
end
|
79
|
+
|
80
|
+
def choice_is_feat(choice)
|
81
|
+
choice.class.to_s['Feat'] != nil
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
module Filters
|
3
|
+
end
|
4
|
+
end
|
5
|
+
|
6
|
+
require_relative './weapon_training_mod.rb'
|
7
|
+
require_relative './weapon_specialization_mod.rb'
|
8
|
+
require_relative './weapon_finesse_mod.rb'
|
9
|
+
require_relative './strength_mod.rb'
|
10
|
+
require_relative './proficiency_mod.rb'
|
11
|
+
require_relative './prowess_mod.rb'
|
12
|
+
require_relative './weapon_focus_mod.rb'
|
13
|
+
require_relative './weapon_mastery_mod.rb'
|
14
|
+
require_relative './ranged_attack_mod.rb'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
module Filters
|
3
|
+
class ProficiencyMod
|
4
|
+
attr_reader :character
|
5
|
+
def initialize(character)
|
6
|
+
@character = character
|
7
|
+
end
|
8
|
+
|
9
|
+
def filter(attack)
|
10
|
+
proficiency = PfrpgCore::Specializer.new(character)
|
11
|
+
unless proficiency.is_proficient_in_weapon? attack.weapon
|
12
|
+
modify_for_weapon(attack)
|
13
|
+
end
|
14
|
+
armor = character.inventory.armor_and_shield
|
15
|
+
armor.each do |a|
|
16
|
+
unless proficiency.is_proficient_in_armor? a
|
17
|
+
modify_for_armor(attack, a)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def modify_for_weapon(attack)
|
23
|
+
attack.filter_str << 'Not proficient with weapon, -4 atk'
|
24
|
+
attack.other_bonus -= 4
|
25
|
+
end
|
26
|
+
|
27
|
+
def modify_for_armor(attack, armor)
|
28
|
+
attack.filter_str << "Not proficient with armor: #{armor.name}"
|
29
|
+
# armor check penalties are negative
|
30
|
+
attack.other_bonus += NullObject.maybe(armor.armor_check_penalty)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
class Filters::ProwessMod
|
3
|
+
attr_reader :character
|
4
|
+
def initialize(character)
|
5
|
+
@character = character
|
6
|
+
end
|
7
|
+
|
8
|
+
def filter(skill)
|
9
|
+
if (skill.name.downcase == 'intimidate')
|
10
|
+
feat = character.char_feats.select { |x| x.pathfinder_feat.name == 'Intimidating Prowess' }
|
11
|
+
modify_skill_for_prowess(skill) if feat
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def modify_skill_for_prowess(skill)
|
16
|
+
skill.misc_bonus += character.attributes.str_mod
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module PfrpgCore
|
2
|
+
module Filters
|
3
|
+
class RangedAttackMod
|
4
|
+
attr_reader :character
|
5
|
+
def initialize(character)
|
6
|
+
@character = character
|
7
|
+
end
|
8
|
+
|
9
|
+
def filter(attack)
|
10
|
+
unless character.feats.find { |x| x.name == 'Weapon Finesse' }
|
11
|
+
modify_attack_for_ranged_attack(attack, character.attributes.dex_mod, character.attributes.str_mod)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def modify_attack_for_ranged_attack(attack, dex, str)
|
16
|
+
begin
|
17
|
+
ranged = attack.range.to_i
|
18
|
+
if !(ranged == nil || ranged == '' || ranged == 0)
|
19
|
+
attack.filter_str << "Range Bonus: -#{dex}, +#{str}"
|
20
|
+
attack.other_bonus += dex
|
21
|
+
attack.other_bonus -= str
|
22
|
+
end
|
23
|
+
rescue Exception => e
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|