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.
- 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
|