Class: AiController::Standard
- Inherits:
-
Natural20::Controller
- Object
- Natural20::Controller
- AiController::Standard
- Includes:
- Natural20::MovementHelper, Natural20::Navigation
- Defined in:
- lib/natural_20/ai_controller/standard.rb
Overview
Class used for handling “Standard” NPC AI
Instance Attribute Summary collapse
-
#battle_data ⇒ Object
readonly
Returns the value of attribute battle_data.
Instance Method Summary collapse
- #action_listener(battle, action, _opt) ⇒ Object
- #attack_listener(battle, target) ⇒ Object
-
#has_appropriate_weapon? ⇒ Boolean
tests if npc has an appropriate weapon to at least one visible enemy.
-
#initialize ⇒ Standard
constructor
A new instance of Standard.
-
#move_for(entity, battle) ⇒ Array
Tells AI to compute moves for an entity.
- #movement_listener(battle, entity, position) ⇒ Object
- #opportunity_attack_listener(battle, session, entity, map, event) ⇒ Object
- #register_battle_listeners(battle) ⇒ Object
- #register_handlers_on(entity) ⇒ Object
- #sound_listener(battle, entity, position, stealth) ⇒ Object
Methods included from Natural20::Navigation
#candidate_squares, #evaluate_square
Methods included from Natural20::Weapons
#compute_advantages_and_disadvantages, #damage_modifier, #target_advantage_condition
Methods included from Natural20::MovementHelper
#compute_actual_moves, #requires_squeeze?, #retrieve_opportunity_attacks, #valid_move_path?
Methods inherited from Natural20::Controller
Constructor Details
#initialize ⇒ Standard
Returns a new instance of Standard.
10 11 12 |
# File 'lib/natural_20/ai_controller/standard.rb', line 10 def initialize @battle_data = {} end |
Instance Attribute Details
#battle_data ⇒ Object (readonly)
Returns the value of attribute battle_data.
8 9 10 |
# File 'lib/natural_20/ai_controller/standard.rb', line 8 def battle_data @battle_data end |
Instance Method Details
#action_listener(battle, action, _opt) ⇒ Object
60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
# File 'lib/natural_20/ai_controller/standard.rb', line 60 def action_listener(battle, action, _opt) # handle unaware npcs new_npcs = battle.map.unaware_npcs.map do |unaware_npc_info| npc = unaware_npc_info[:entity] next unless npc.conscious? next unless battle.map.can_see?(npc, action.source) register_handlers_on(npc) # attach this AI controller to this NPC battle.add(npc, unaware_npc_info[:group]) update_enemy_known_position(battle, npc, action.source, battle.map.entity_squares(action.source).first) npc end new_npcs.each { |npc| battle.map.unaware_npcs.delete(npc) } end |
#attack_listener(battle, target) ⇒ Object
49 50 51 52 53 54 55 56 |
# File 'lib/natural_20/ai_controller/standard.rb', line 49 def attack_listener(battle, target) unaware_npc_info = battle.map.unaware_npcs.detect { |n| n[:entity] == target } return unless unaware_npc_info register_handlers_on(target) # attach this AI controller to this NPC battle.add(target, unaware_npc_info[:group]) battle.map.unaware_npcs.delete(target) end |
#has_appropriate_weapon? ⇒ Boolean
tests if npc has an appropriate weapon to at least one visible enemy
109 |
# File 'lib/natural_20/ai_controller/standard.rb', line 109 def has_appropriate_weapon?; end |
#move_for(entity, battle) ⇒ Array
Tells AI to compute moves for an entity
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 |
# File 'lib/natural_20/ai_controller/standard.rb', line 115 def move_for(entity, battle) initialize_battle_data(battle, entity) known_enemy_positions = @battle_data[battle][entity][:known_enemy_positions] hiding_spots = @battle_data[battle][entity][:hiding_spots] investigate_location = @battle_data[battle][entity][:investigate_location] enemy_positions = {} observe_enemies(battle, entity, enemy_positions) available_actions = entity.available_actions(@session, battle) # generate available targets valid_actions = [] if enemy_positions.empty? && investigate_location.empty? && LookAction.can?(entity, battle) action = LookAction.new(battle.session, entity, :look) return action end # try to stand if prone valid_actions << StandAction.new(@session, entity, :stand) if entity.prone? && StandAction.can?(entity, battle) available_actions.select { |a| a.action_type == :attack }.each do |action| next unless action.npc_action valid_targets = battle.valid_targets_for(entity, action) unless valid_targets.first.nil? action.target = valid_targets.first valid_actions << action end end # movement planner if no more attack options and enemies are in sight if valid_actions.empty? && !enemy_positions.empty? valid_actions += generate_moves_for_positions(battle, entity, enemy_positions) end # attempt to investigate last seen positions if enemy_positions.empty? my_group = battle.entity_group_for(entity) investigate_location = known_enemy_positions.map do |enemy, position| group = battle.entity_group_for(enemy) next if my_group == group [enemy, position] end.compact.to_h valid_actions += generate_moves_for_positions(battle, entity, investigate_location) end if HideBonusAction.can?(entity, battle) # bonus action hide if able hide_action = HideBonusAction.new(battle.session, entity, :hide_bonus) hide_action.as_bonus_action = true valid_actions << hide_action end if valid_actions.first&.action_type == :move && DisengageBonusAction.can?(entity, battle) && !retrieve_opportunity_attacks( entity, valid_actions.first.move_path, battle ).empty? return DisengageBonusAction.new(battle.session, entity, :disengage_bonus) end valid_actions << DodgeAction.new(battle.session, entity, :dodge) if entity.action?(battle) return valid_actions.first unless valid_actions.empty? end |
#movement_listener(battle, entity, position) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/natural_20/ai_controller/standard.rb', line 19 def movement_listener(battle, entity, position) move_path = position[:move_path] return if move_path.nil? # handle unaware npcs new_npcs = battle.map.unaware_npcs.map do |unaware_npc_info| npc = unaware_npc_info[:entity] seen = !!move_path.reverse.detect do |path| npc.conscious? && battle.map.can_see?(npc, entity, entity_2_pos: path) end next unless seen register_handlers_on(npc) # attach this AI controller to this NPC battle.add(npc, unaware_npc_info[:group]) npc end.compact new_npcs.each { |npc| battle.map.unaware_npcs.delete(npc) } # update seen info for each npc battle.entities.each_key do |e| loc = move_path.reverse.detect do |location| battle.map.can_see?(entity, e, entity_2_pos: location) end # include friendlies as well since they can turn on us at one point in time :) update_enemy_known_position(e, entity, *loc) if loc end end |
#opportunity_attack_listener(battle, session, entity, map, event) ⇒ Object
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/natural_20/ai_controller/standard.rb', line 75 def opportunity_attack_listener(battle, session, entity, map, event) entity_x, entity_y = map.position_of(entity) target_x, target_y = event[:position] distance = Math.sqrt((target_x - entity_x)**2 + (target_y - entity_y)**2).ceil action = entity.available_actions(session, battle, opportunity_attack: true).select do |s| distance <= s.npc_action[:range] end.first if action action.target = event[:target] action.as_reaction = true end action end |
#register_battle_listeners(battle) ⇒ Object
92 93 94 95 96 97 98 99 100 101 |
# File 'lib/natural_20/ai_controller/standard.rb', line 92 def register_battle_listeners(battle) # detects noisy things battle.add_battlefield_event_listener(:sound, self, :sound_listener) # detects line of sight movement battle.add_battlefield_event_listener(:movement, self, :movement_listener) # actions listener (check if doors opened etc) battle.add_battlefield_event_listener(:interact, self, :action_listener) end |
#register_handlers_on(entity) ⇒ Object
104 105 106 |
# File 'lib/natural_20/ai_controller/standard.rb', line 104 def register_handlers_on(entity) entity.attach_handler(:opportunity_attack, self, :opportunity_attack_listener) end |
#sound_listener(battle, entity, position, stealth) ⇒ Object
14 |
# File 'lib/natural_20/ai_controller/standard.rb', line 14 def sound_listener(battle, entity, position, stealth); end |