module Natural20::MovementHelper

typed: true

Public Instance Methods

compute_actual_moves(entity, current_moves, map, battle, movement_budget, fixed_movement: false, test_placement: true, manual_jump: []) click to toggle source

@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
requires_squeeze?(entity, pos_x, pos_y, map, battle = nil) click to toggle source

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
retrieve_opportunity_attacks(entity, move_list, battle) click to toggle source

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
valid_move_path?(entity, path, battle, map, test_placement: true, manual_jump: []) click to toggle source

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

opportunity_attack_list(entity, current_moves, battle, map) click to toggle source
# 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