module Natural20::MovementHelper
typed: true
Public Instance Methods
@param entity [Natural20::Entity] @param current_moves [Array] @param map [Natural20::BattleMap] @param battle [Natural20::Battle] @param movement_budget [Integer] movement budget in number of squares (feet/5 by default) @param test_placement [Boolean] If true tests if last move is placeable on the map @param manual_jump [Array] Indices of moves that are supposed to be jumps @return [Movement]
# 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
Determine if entity needs to squeeze to get through terrain @param entity [Natural20::Entity] @param pos_x [Integer] @param pos_y [Integer] @param map [Natural20::BattleMap] @param battle [Natural20::Battle] @return [Boolean]
# 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
Checks if a move provokes opportunity attacks @param entity [Natural20::Entity] @param move_list [Array<Array<Integer,Integer>>] @param battle [Natural20::Battle] @return [Array<Hash>]
# 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
Checks if move path is valid @param entity [Natural20::Entity] @param path [Array] @param battle [Natural20::Battle] @param map [Natural20::BattleMap] @param test_placement [Boolean] @param manual_jump [Array] @return [Boolean]
# 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
Protected Instance Methods
# File lib/natural_20/concerns/movement_helper.rb, line 211 def opportunity_attack_list(entity, current_moves, battle, map) # get opposing forces opponents = battle.opponents_of?(entity) entered_melee_range = Set.new left_melee_range = [] current_moves.each_with_index do |path, index| opponents.each do |enemy| entered_melee_range.add(enemy) if enemy.entered_melee?(map, entity, *path) if !left_melee_range.include?(enemy) && entered_melee_range.include?(enemy) && !enemy.entered_melee?(map, entity, *path) left_melee_range << { source: enemy, path: index } end end end left_melee_range end