natural_20 0.1.0
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 +7 -0
- data/.gitignore +14 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +7 -0
- data/LICENSE.txt +21 -0
- data/README.md +99 -0
- data/Rakefile +6 -0
- data/bin/compute_lights +19 -0
- data/bin/console +19 -0
- data/bin/nat20 +135 -0
- data/bin/nat20.cmd +3 -0
- data/bin/nat20author +104 -0
- data/bin/setup +8 -0
- data/char_classes/fighter.yml +45 -0
- data/char_classes/rogue.yml +54 -0
- data/characters/halfling_rogue.yml +46 -0
- data/characters/high_elf_fighter.yml +49 -0
- data/fixtures/battle_sim.yml +58 -0
- data/fixtures/battle_sim_2.yml +30 -0
- data/fixtures/battle_sim_3.yml +26 -0
- data/fixtures/battle_sim_4.yml +26 -0
- data/fixtures/battle_sim_objects.yml +101 -0
- data/fixtures/corridors.yml +24 -0
- data/fixtures/elf_rogue.yml +39 -0
- data/fixtures/halfling_rogue.yml +41 -0
- data/fixtures/high_elf_fighter.yml +49 -0
- data/fixtures/human_fighter.yml +48 -0
- data/fixtures/path_finding_test.yml +11 -0
- data/fixtures/path_finding_test_2.yml +15 -0
- data/fixtures/path_finding_test_3.yml +26 -0
- data/fixtures/thin_walls.yml +53 -0
- data/fixtures/traps.yml +25 -0
- data/game.yml +20 -0
- data/items/equipment.yml +101 -0
- data/items/objects.yml +73 -0
- data/items/weapons.yml +297 -0
- data/lib/natural_20.rb +68 -0
- data/lib/natural_20/actions/action.rb +40 -0
- data/lib/natural_20/actions/attack_action.rb +372 -0
- data/lib/natural_20/actions/concerns/action_damage.rb +14 -0
- data/lib/natural_20/actions/dash_action.rb +46 -0
- data/lib/natural_20/actions/disengage_action.rb +53 -0
- data/lib/natural_20/actions/dodge_action.rb +45 -0
- data/lib/natural_20/actions/escape_grapple_action.rb +97 -0
- data/lib/natural_20/actions/first_aid_action.rb +109 -0
- data/lib/natural_20/actions/grapple_action.rb +185 -0
- data/lib/natural_20/actions/ground_interact_action.rb +74 -0
- data/lib/natural_20/actions/help_action.rb +56 -0
- data/lib/natural_20/actions/hide_action.rb +53 -0
- data/lib/natural_20/actions/interact_action.rb +91 -0
- data/lib/natural_20/actions/inventory_action.rb +23 -0
- data/lib/natural_20/actions/look_action.rb +63 -0
- data/lib/natural_20/actions/move_action.rb +254 -0
- data/lib/natural_20/actions/multiattack_action.rb +41 -0
- data/lib/natural_20/actions/prone_action.rb +38 -0
- data/lib/natural_20/actions/short_rest_action.rb +53 -0
- data/lib/natural_20/actions/shove_action.rb +142 -0
- data/lib/natural_20/actions/stand_action.rb +47 -0
- data/lib/natural_20/actions/use_item_action.rb +57 -0
- data/lib/natural_20/ai_controller/path_compute.rb +140 -0
- data/lib/natural_20/ai_controller/standard.rb +288 -0
- data/lib/natural_20/battle.rb +544 -0
- data/lib/natural_20/battle_map.rb +843 -0
- data/lib/natural_20/cli/builder/fighter_builder.rb +104 -0
- data/lib/natural_20/cli/builder/rogue_builder.rb +62 -0
- data/lib/natural_20/cli/character_builder.rb +210 -0
- data/lib/natural_20/cli/commandline_ui.rb +612 -0
- data/lib/natural_20/cli/inventory_ui.rb +136 -0
- data/lib/natural_20/cli/map_renderer.rb +165 -0
- data/lib/natural_20/concerns/container.rb +32 -0
- data/lib/natural_20/concerns/entity.rb +1213 -0
- data/lib/natural_20/concerns/evaluator/entity_state_evaluator.rb +59 -0
- data/lib/natural_20/concerns/fighter_actions/second_wind_action.rb +51 -0
- data/lib/natural_20/concerns/fighter_class.rb +35 -0
- data/lib/natural_20/concerns/health_flavor.rb +27 -0
- data/lib/natural_20/concerns/lootable.rb +94 -0
- data/lib/natural_20/concerns/movement_helper.rb +195 -0
- data/lib/natural_20/concerns/multiattack.rb +54 -0
- data/lib/natural_20/concerns/navigation.rb +87 -0
- data/lib/natural_20/concerns/notable.rb +37 -0
- data/lib/natural_20/concerns/rogue_class.rb +26 -0
- data/lib/natural_20/controller.rb +11 -0
- data/lib/natural_20/die_roll.rb +331 -0
- data/lib/natural_20/event_manager.rb +288 -0
- data/lib/natural_20/item_library/base_item.rb +27 -0
- data/lib/natural_20/item_library/chest.rb +230 -0
- data/lib/natural_20/item_library/door_object.rb +189 -0
- data/lib/natural_20/item_library/ground.rb +124 -0
- data/lib/natural_20/item_library/healing_potion.rb +51 -0
- data/lib/natural_20/item_library/object.rb +153 -0
- data/lib/natural_20/item_library/pit_trap.rb +69 -0
- data/lib/natural_20/item_library/stone_wall.rb +18 -0
- data/lib/natural_20/npc.rb +173 -0
- data/lib/natural_20/player_character.rb +414 -0
- data/lib/natural_20/session.rb +168 -0
- data/lib/natural_20/utils/cover.rb +35 -0
- data/lib/natural_20/utils/ray_tracer.rb +90 -0
- data/lib/natural_20/utils/static_light_builder.rb +72 -0
- data/lib/natural_20/utils/weapons.rb +78 -0
- data/lib/natural_20/version.rb +4 -0
- data/locales/en.yml +304 -0
- data/maps/game_map.yml +168 -0
- data/natural_20.gemspec +46 -0
- data/npcs/goblin.yml +64 -0
- data/npcs/human_guard.yml +48 -0
- data/npcs/ogre.yml +61 -0
- data/npcs/owlbear.yml +55 -0
- data/npcs/wolf.yml +46 -0
- data/races/elf.yml +44 -0
- data/races/halfling.yml +22 -0
- data/races/human.yml +13 -0
- metadata +373 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module Natural20
|
2
|
+
module EntityStateEvaluator
|
3
|
+
# Safely evaluates a DSL to return a boolean expression
|
4
|
+
# @param conditions [String]
|
5
|
+
# @param context [Hash]
|
6
|
+
# @return [Boolean]
|
7
|
+
def eval_if(conditions, context = {})
|
8
|
+
or_groups = conditions.split('|')
|
9
|
+
!!or_groups.detect do |g|
|
10
|
+
and_groups = g.split('&')
|
11
|
+
and_groups.detect do |and_g|
|
12
|
+
cmd, test_expression = and_g.strip.split(':')
|
13
|
+
invert = test_expression[0] == '!'
|
14
|
+
test_expression = test_expression[1..test_expression.size - 1] if test_expression[0] == '!'
|
15
|
+
result = case cmd.to_sym
|
16
|
+
when :inventory
|
17
|
+
item_count(test_expression).positive?
|
18
|
+
when :equipped
|
19
|
+
equipped_items.map(&:name).include?(test_expression.to_sym)
|
20
|
+
when :object_type
|
21
|
+
context[:item_type].to_s.downcase == test_expression.to_s.downcase
|
22
|
+
when :target
|
23
|
+
(test_expression == 'object' && context[:target].object?)
|
24
|
+
when :entity
|
25
|
+
(test_expression == 'pc' && pc?) ||
|
26
|
+
(test_expression == 'npc' && npc?)
|
27
|
+
when :state
|
28
|
+
case test_expression
|
29
|
+
when 'unconscious'
|
30
|
+
unconscious?
|
31
|
+
when 'stable'
|
32
|
+
stable?
|
33
|
+
when 'dead'
|
34
|
+
dead?
|
35
|
+
when 'conscious'
|
36
|
+
conscious?
|
37
|
+
end
|
38
|
+
else
|
39
|
+
raise "Invalid expression #{cmd} #{test_expression}"
|
40
|
+
end
|
41
|
+
|
42
|
+
invert ? result : !result
|
43
|
+
end.nil?
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def apply_effect(expression, context = {})
|
48
|
+
action, value = expression.split(':')
|
49
|
+
case action
|
50
|
+
when 'status'
|
51
|
+
{
|
52
|
+
source: self,
|
53
|
+
type: value.to_sym,
|
54
|
+
battle: context[:battle]
|
55
|
+
}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# typed: true
|
2
|
+
class SecondWindAction < Natural20::Action
|
3
|
+
def self.can?(entity, battle)
|
4
|
+
(battle.nil? || entity.total_bonus_actions(battle).positive?) && entity.second_wind_count.positive?
|
5
|
+
end
|
6
|
+
|
7
|
+
def label
|
8
|
+
'Second Wind'
|
9
|
+
end
|
10
|
+
|
11
|
+
def build_map
|
12
|
+
OpenStruct.new({
|
13
|
+
action: self,
|
14
|
+
param: nil,
|
15
|
+
next: -> { self }
|
16
|
+
})
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.build(session, source)
|
20
|
+
action = SecondWindAction.new(session, source, :second_wind)
|
21
|
+
action.build_map
|
22
|
+
end
|
23
|
+
|
24
|
+
def resolve(_session, _map, opts = {})
|
25
|
+
second_wind_roll = Natural20::DieRoll.roll(@source.second_wind_die, description: t('dice_roll.second_wind'),
|
26
|
+
entity: @source, battle: opts[:battle])
|
27
|
+
@result = [{
|
28
|
+
source: @source,
|
29
|
+
roll: second_wind_roll,
|
30
|
+
type: :second_wind,
|
31
|
+
battle: opts[:battle]
|
32
|
+
}]
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def apply!(battle)
|
37
|
+
@result.each do |item|
|
38
|
+
case (item[:type])
|
39
|
+
when :second_wind
|
40
|
+
Natural20::EventManager.received_event(action: self.class, source: item[:source], roll: item[:roll],
|
41
|
+
event: :second_wind)
|
42
|
+
item[:source].second_wind!(item[:roll].result)
|
43
|
+
battle.entity_state_for(item[:source])[:bonus_action] -= 1
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.describe(event)
|
49
|
+
"#{event[:source].name.colorize(:green)} uses " + 'Second Wind'.colorize(:blue) + " with #{event[:roll]} healing"
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# typed: false
|
2
|
+
require "natural_20/concerns/fighter_actions/second_wind_action"
|
3
|
+
|
4
|
+
module Natural20::FighterClass
|
5
|
+
attr_accessor :fighter_level, :second_wind_count
|
6
|
+
|
7
|
+
def initialize_fighter
|
8
|
+
@second_wind_count = 1
|
9
|
+
end
|
10
|
+
|
11
|
+
def second_wind_die
|
12
|
+
"1d10+#{@fighter_level}"
|
13
|
+
end
|
14
|
+
|
15
|
+
def second_wind!(amt)
|
16
|
+
@second_wind_count -= 1
|
17
|
+
heal!(amt)
|
18
|
+
end
|
19
|
+
|
20
|
+
def special_actions_for_fighter(session, battle)
|
21
|
+
%i[second_wind].map do |type|
|
22
|
+
next unless "#{type.to_s.camelize}Action".constantize.can?(self, battle)
|
23
|
+
|
24
|
+
case type
|
25
|
+
when :second_wind
|
26
|
+
SecondWindAction.new(session, self, :second_wind)
|
27
|
+
end
|
28
|
+
end.compact
|
29
|
+
end
|
30
|
+
|
31
|
+
# hooks for the fighter class during a short rest
|
32
|
+
def short_rest_for_fighter
|
33
|
+
@second_wind_count = 1
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# typed: false
|
2
|
+
module Natural20::HealthFlavor
|
3
|
+
def describe_health
|
4
|
+
return '' if hp.zero? || hp.negative?
|
5
|
+
|
6
|
+
percentage = (hp.to_f / max_hp) * 100
|
7
|
+
|
8
|
+
token = if dead?
|
9
|
+
'dead'
|
10
|
+
elsif unconscious?
|
11
|
+
'unconscious'
|
12
|
+
elsif percentage > 90
|
13
|
+
'max'
|
14
|
+
elsif percentage > 75
|
15
|
+
'over_75'
|
16
|
+
elsif percentage > 50
|
17
|
+
'over_50'
|
18
|
+
elsif percentage > 25
|
19
|
+
'over_25'
|
20
|
+
elsif percentage > 10
|
21
|
+
'over_10'
|
22
|
+
else
|
23
|
+
'almost_dead'
|
24
|
+
end
|
25
|
+
t("entity.health_flavor.#{token}")
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module Natural20::Lootable
|
2
|
+
include Natural20::Container
|
3
|
+
|
4
|
+
# Builds a custom UI map
|
5
|
+
# @param action [Symbol] The item specific action
|
6
|
+
# @param action_object [InteractAction]
|
7
|
+
# @return [OpenStruct]
|
8
|
+
def build_map(action, action_object)
|
9
|
+
case action
|
10
|
+
when :give
|
11
|
+
OpenStruct.new({
|
12
|
+
action: action_object,
|
13
|
+
param: [
|
14
|
+
{
|
15
|
+
type: :select_items,
|
16
|
+
label: action_object.source.items_label,
|
17
|
+
items: action_object.source.inventory
|
18
|
+
}
|
19
|
+
],
|
20
|
+
next: lambda { |items|
|
21
|
+
action_object.other_params = items
|
22
|
+
OpenStruct.new({
|
23
|
+
param: nil,
|
24
|
+
next: lambda {
|
25
|
+
action_object
|
26
|
+
}
|
27
|
+
})
|
28
|
+
}
|
29
|
+
})
|
30
|
+
when :loot
|
31
|
+
OpenStruct.new({
|
32
|
+
action: action_object,
|
33
|
+
param: [
|
34
|
+
{
|
35
|
+
type: :select_items,
|
36
|
+
label: items_label,
|
37
|
+
items: inventory + equipped_items
|
38
|
+
}
|
39
|
+
],
|
40
|
+
next: lambda { |items|
|
41
|
+
action_object.other_params = items
|
42
|
+
OpenStruct.new({
|
43
|
+
param: nil,
|
44
|
+
next: lambda {
|
45
|
+
action_object
|
46
|
+
}
|
47
|
+
})
|
48
|
+
}
|
49
|
+
})
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Lists default available interactions for an entity
|
54
|
+
# @param entity [Natural20::Entity]
|
55
|
+
# @param battle [Natural20::Battle]
|
56
|
+
# @return [Array] List of availabel actions
|
57
|
+
def available_interactions(entity, battle = nil)
|
58
|
+
other_interactions = super entity, battle
|
59
|
+
other_interactions << :give if !npc? || object?
|
60
|
+
# other_interactions << :pickpocket if !unconscious && npc?
|
61
|
+
other_interactions << :loot if (dead? || unconscious? || opened?) && inventory_count.positive?
|
62
|
+
|
63
|
+
other_interactions
|
64
|
+
end
|
65
|
+
|
66
|
+
def interactable?
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
# @param entity [Natural20::Entity]
|
71
|
+
# @param action [InteractAction]
|
72
|
+
# @param other_params [Hash]
|
73
|
+
def resolve(entity, action, other_params, opts = {})
|
74
|
+
return if action.nil?
|
75
|
+
|
76
|
+
case action
|
77
|
+
when :give, :loot
|
78
|
+
{ action: action, items: other_params, source: entity, target: self, battle: opts[:battle] }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# @param entity [Natural20::Entity]
|
83
|
+
# @option result action [Symbol]
|
84
|
+
# @option result items [Array]
|
85
|
+
# @option result source [Natural20::Entity]
|
86
|
+
def use!(_entity, result)
|
87
|
+
case (result[:action])
|
88
|
+
when :give
|
89
|
+
store(result[:battle], result[:source], result[:target], result[:items])
|
90
|
+
when :loot
|
91
|
+
retrieve(result[:battle], result[:source], result[:target], result[:items])
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# typed: true
|
2
|
+
module Natural20::MovementHelper
|
3
|
+
class Movement
|
4
|
+
def initialize(movement, original_budget, acrobatics_check_locations, athletics_check_locations, jump_locations, jump_start_locations, land_locations, jump_budget, budget, impediment)
|
5
|
+
@jump_start_locations = jump_start_locations
|
6
|
+
@athletics_check_locations = athletics_check_locations
|
7
|
+
@jump_locations = jump_locations
|
8
|
+
@land_locations = land_locations
|
9
|
+
@jump_budget = jump_budget
|
10
|
+
@movement = movement
|
11
|
+
@original_budget = original_budget
|
12
|
+
@acrobatics_check_locations = acrobatics_check_locations
|
13
|
+
@impediment = impediment
|
14
|
+
@budget = budget
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Natural20::Movement]
|
18
|
+
def self.empty
|
19
|
+
Movement.new([], 0, [], [], [], [], [], 0, 0, :nil)
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [Integer]
|
23
|
+
attr_reader :budget
|
24
|
+
|
25
|
+
# @return [Symbol]
|
26
|
+
attr_reader :impediment
|
27
|
+
|
28
|
+
# @return [Array]
|
29
|
+
attr_reader :movement
|
30
|
+
|
31
|
+
# @return [Integer]
|
32
|
+
attr_reader :jump_budget
|
33
|
+
|
34
|
+
# @return [Array]
|
35
|
+
attr_reader :jump_start_locations
|
36
|
+
|
37
|
+
# @return [Array]
|
38
|
+
attr_reader :jump_locations
|
39
|
+
|
40
|
+
# @return [Array]
|
41
|
+
attr_reader :land_locations
|
42
|
+
|
43
|
+
# @return [Array]
|
44
|
+
attr_reader :acrobatics_check_locations
|
45
|
+
|
46
|
+
# @return [Array]
|
47
|
+
attr_reader :athletics_check_locations
|
48
|
+
|
49
|
+
# @return [Integer]
|
50
|
+
def cost
|
51
|
+
(@original_budget - @budget)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Checks if move path is valid
|
56
|
+
# @param entity [Natural20::Entity]
|
57
|
+
# @param path [Array]
|
58
|
+
# @param battle [Natural20::Battle]
|
59
|
+
# @param map [Natural20::BattleMap]
|
60
|
+
# @param test_placement [Boolean]
|
61
|
+
# @param manual_jump [Array]
|
62
|
+
# @return [Boolean]
|
63
|
+
def valid_move_path?(entity, path, battle, map, test_placement: true, manual_jump: [])
|
64
|
+
path == compute_actual_moves(entity, path, map, battle, entity.available_movement(battle) / map.feet_per_grid,
|
65
|
+
test_placement: test_placement, manual_jump: manual_jump).movement
|
66
|
+
end
|
67
|
+
|
68
|
+
# Determine if entity needs to squeeze to get through terrain
|
69
|
+
# @param entity [Natural20::Entity]
|
70
|
+
# @param pos_x [Integer]
|
71
|
+
# @param pos_y [Integer]
|
72
|
+
# @param map [Natural20::BattleMap]
|
73
|
+
# @param battle [Natural20::Battle]
|
74
|
+
# @return [Boolean]
|
75
|
+
def requires_squeeze?(entity, pos_x, pos_y, map, battle = nil)
|
76
|
+
!map.passable?(entity, pos_x, pos_y, battle, false) && map.passable?(entity, pos_x, pos_y, battle, true)
|
77
|
+
end
|
78
|
+
|
79
|
+
# @param entity [Natural20::Entity]
|
80
|
+
# @param current_moves [Array]
|
81
|
+
# @param map [Natural20::BattleMap]
|
82
|
+
# @param battle [Natural20::Battle]
|
83
|
+
# @param movement_budget [Integer] movement budget in number of squares (feet/5 by default)
|
84
|
+
# @param test_placement [Boolean] If true tests if last move is placeable on the map
|
85
|
+
# @param manual_jump [Array] Indices of moves that are supposed to be jumps
|
86
|
+
# @return [Movement]
|
87
|
+
def compute_actual_moves(entity, current_moves, map, battle, movement_budget, fixed_movement: false, test_placement: true, manual_jump: [])
|
88
|
+
actual_moves = []
|
89
|
+
provisional_moves = []
|
90
|
+
jump_budget = (entity.standing_jump_distance / map.feet_per_grid).floor
|
91
|
+
running_distance = 1
|
92
|
+
jump_distance = 0
|
93
|
+
jumped = false
|
94
|
+
acrobatics_check_locations = []
|
95
|
+
athletics_check_locations = []
|
96
|
+
jump_start_locations = []
|
97
|
+
land_locations = []
|
98
|
+
jump_locations = []
|
99
|
+
impediment = nil
|
100
|
+
original_budget = movement_budget
|
101
|
+
|
102
|
+
current_moves.each_with_index do |m, index|
|
103
|
+
raise "invalid move coordinate" unless m.size == 2 # assert move correctness
|
104
|
+
|
105
|
+
unless index.positive?
|
106
|
+
actual_moves << m
|
107
|
+
next
|
108
|
+
end
|
109
|
+
|
110
|
+
unless map.passable?(entity, *m, battle)
|
111
|
+
impediment = :path_blocked
|
112
|
+
break
|
113
|
+
end
|
114
|
+
|
115
|
+
if fixed_movement
|
116
|
+
movement_budget -= 1
|
117
|
+
else
|
118
|
+
movement_budget -= if !manual_jump.include?(index) && map.difficult_terrain?(entity, *m, battle)
|
119
|
+
2
|
120
|
+
else
|
121
|
+
1
|
122
|
+
end
|
123
|
+
movement_budget -= 1 if requires_squeeze?(entity, *m, map, battle)
|
124
|
+
movement_budget -= 1 if entity.prone?
|
125
|
+
movement_budget -= 1 if entity.grappling?
|
126
|
+
end
|
127
|
+
|
128
|
+
if movement_budget.negative?
|
129
|
+
impediment = :movement_budget
|
130
|
+
break
|
131
|
+
end
|
132
|
+
|
133
|
+
if !fixed_movement && (map.jump_required?(entity, *m) || manual_jump.include?(index))
|
134
|
+
if entity.prone? # can't jump if prone
|
135
|
+
impediment = :prone_need_to_jump
|
136
|
+
break
|
137
|
+
end
|
138
|
+
|
139
|
+
jump_start_locations << m unless jumped
|
140
|
+
jump_locations << m
|
141
|
+
jump_budget -= 1
|
142
|
+
jump_distance += 1
|
143
|
+
if !fixed_movement && jump_budget.negative?
|
144
|
+
impediment = :jump_distance_not_enough
|
145
|
+
break
|
146
|
+
end
|
147
|
+
|
148
|
+
running_distance = 0
|
149
|
+
jumped = true
|
150
|
+
provisional_moves << m
|
151
|
+
|
152
|
+
entity_at_square = map.entity_at(*m)
|
153
|
+
athletics_check_locations << m if entity_at_square&.conscious? && !entity_at_square&.prone?
|
154
|
+
else
|
155
|
+
actual_moves += provisional_moves
|
156
|
+
provisional_moves.clear
|
157
|
+
|
158
|
+
land_locations << m if jumped
|
159
|
+
acrobatics_check_locations << m if jumped && map.difficult_terrain?(entity, *m,
|
160
|
+
battle)
|
161
|
+
running_distance += 1
|
162
|
+
|
163
|
+
# if jump not required reset jump budgets
|
164
|
+
jump_budget = if running_distance > 1
|
165
|
+
(entity.long_jump_distance / map.feet_per_grid).floor
|
166
|
+
else
|
167
|
+
(entity.standing_jump_distance / map.feet_per_grid).floor
|
168
|
+
end
|
169
|
+
jumped = false
|
170
|
+
jump_distance = 0
|
171
|
+
actual_moves << m
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# handle case where end is a jump, in that case we land if this is possible
|
176
|
+
unless provisional_moves.empty?
|
177
|
+
actual_moves += provisional_moves
|
178
|
+
m = actual_moves.last
|
179
|
+
land_locations << m if jumped
|
180
|
+
acrobatics_check_locations << m if jumped && map.difficult_terrain?(entity, *m,
|
181
|
+
battle)
|
182
|
+
jump_locations.delete(actual_moves.last)
|
183
|
+
end
|
184
|
+
|
185
|
+
while test_placement && !map.placeable?(entity, *actual_moves.last, battle)
|
186
|
+
impediment = :not_placeable
|
187
|
+
jump_locations.delete(actual_moves.last)
|
188
|
+
actual_moves.pop
|
189
|
+
end
|
190
|
+
|
191
|
+
Movement.new(actual_moves, original_budget, acrobatics_check_locations, athletics_check_locations, jump_locations, jump_start_locations, land_locations, jump_budget,
|
192
|
+
movement_budget, impediment)
|
193
|
+
end
|
194
|
+
|
195
|
+
end
|