Module: Natural20::MovementHelper

Included in:
AiController::Standard, CommandlineUI, MoveAction, BattleMap
Defined in:
lib/natural_20/concerns/movement_helper.rb

Overview

typed: true

Defined Under Namespace

Classes: Movement

Instance Method Summary collapse

Instance Method Details

#compute_actual_moves(entity, current_moves, map, battle, movement_budget, fixed_movement: false, test_placement: true, manual_jump: []) ⇒ Movement

Parameters:

  • entity (Natural20::Entity)
  • current_moves (Array)
  • map (Natural20::BattleMap)
  • battle (Natural20::Battle)
  • movement_budget (Integer)

    movement budget in number of squares (feet/5 by default)

  • test_placement (Boolean) (defaults to: true)

    If true tests if last move is placeable on the map

  • manual_jump (Array) (defaults to: [])

    Indices of moves that are supposed to be jumps

Returns:



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/natural_20/concerns/movement_helper.rb', line 87

def compute_actual_moves(entity, current_moves, map, battle, movement_budget, fixed_movement: false, test_placement: true, manual_jump: [])
  actual_moves = []
  provisional_moves = []
  jump_budget = (entity.standing_jump_distance / map.feet_per_grid).floor
  running_distance = 1
  jump_distance = 0
  jumped = false
  acrobatics_check_locations = []
  athletics_check_locations = []
  jump_start_locations = []
  land_locations = []
  jump_locations = []
  impediment = nil
  original_budget = movement_budget

  current_moves.each_with_index do |m, index|
    raise 'invalid move coordinate' unless m.size == 2 # assert move correctness

    unless index.positive?
      actual_moves << m
      next
    end

    unless map.passable?(entity, *m, battle)
      impediment = :path_blocked
      break
    end

    if fixed_movement
      movement_budget -= 1
    else
      movement_budget -= if !manual_jump.include?(index) && map.difficult_terrain?(entity, *m, battle)
                           2
                         else
                           1
                         end
      movement_budget -= 1 if requires_squeeze?(entity, *m, map, battle)
      movement_budget -= 1 if entity.prone?
      movement_budget -= 1 if entity.grappling?
    end

    if movement_budget.negative?
      impediment = :movement_budget
      break
    end

    if !fixed_movement && (map.jump_required?(entity, *m) || manual_jump.include?(index))
      if entity.prone? # can't jump if prone
        impediment = :prone_need_to_jump
        break
      end

      jump_start_locations << m unless jumped
      jump_locations << m
      jump_budget -= 1
      jump_distance += 1
      if !fixed_movement && jump_budget.negative?
        impediment = :jump_distance_not_enough
        break
      end

      running_distance = 0
      jumped = true
      provisional_moves << m

      entity_at_square = map.entity_at(*m)
      athletics_check_locations << m if entity_at_square&.conscious? && !entity_at_square&.prone?
    else
      actual_moves += provisional_moves
      provisional_moves.clear

      land_locations << m if jumped
      acrobatics_check_locations << m if jumped && map.difficult_terrain?(entity, *m,
                                                                          battle)
      running_distance += 1

      # if jump not required reset jump budgets
      jump_budget = if running_distance > 1
                      (entity.long_jump_distance / map.feet_per_grid).floor
                    else
                      (entity.standing_jump_distance / map.feet_per_grid).floor
                    end
      jumped = false
      jump_distance = 0
      actual_moves << m
    end
  end

  # handle case where end is a jump, in that case we land if this is possible
  unless provisional_moves.empty?
    actual_moves += provisional_moves
    m = actual_moves.last
    land_locations << m if jumped
    acrobatics_check_locations << m if jumped && map.difficult_terrain?(entity, *m,
                                                                        battle)
    jump_locations.delete(actual_moves.last)
  end

  while test_placement && !map.placeable?(entity, *actual_moves.last, battle)
    impediment = :not_placeable
    jump_locations.delete(actual_moves.last)
    actual_moves.pop
  end

  Movement.new(actual_moves, original_budget, acrobatics_check_locations, athletics_check_locations, jump_locations, jump_start_locations, land_locations, jump_budget,
               movement_budget, impediment)
end

#requires_squeeze?(entity, pos_x, pos_y, map, battle = nil) ⇒ Boolean

Determine if entity needs to squeeze to get through terrain

Parameters:

Returns:

  • (Boolean)


75
76
77
# File 'lib/natural_20/concerns/movement_helper.rb', line 75

def requires_squeeze?(entity, pos_x, pos_y, map, battle = nil)
  !map.passable?(entity, pos_x, pos_y, battle, false) && map.passable?(entity, pos_x, pos_y, battle, true)
end

#retrieve_opportunity_attacks(entity, move_list, battle) ⇒ Array<Hash>

Checks if a move provokes opportunity attacks

Parameters:

Returns:

  • (Array<Hash>)


200
201
202
203
204
205
206
207
# File 'lib/natural_20/concerns/movement_helper.rb', line 200

def retrieve_opportunity_attacks(entity, move_list, battle)
  return [] if entity.disengage?(battle)

  opportunity_attacks = opportunity_attack_list(entity, move_list, battle, battle.map)
  opportunity_attacks.select do |enemy_opporunity|
    enemy_opporunity[:source].has_reaction?(battle) && !entity.grappling_targets.include?(enemy_opporunity[:source])
  end
end

#valid_move_path?(entity, path, battle, map, test_placement: true, manual_jump: []) ⇒ Boolean

Checks if move path is valid

Parameters:

Returns:

  • (Boolean)


63
64
65
66
# File 'lib/natural_20/concerns/movement_helper.rb', line 63

def valid_move_path?(entity, path, battle, map, test_placement: true, manual_jump: [])
  path == compute_actual_moves(entity, path, map, battle, entity.available_movement(battle) / map.feet_per_grid,
                               test_placement: test_placement, manual_jump: manual_jump).movement
end