smash_and_grab 0.0.5alpha → 0.0.6alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. data/{CHANGELOG.txt → CHANGELOG.md} +0 -0
  2. data/Gemfile.lock +8 -4
  3. data/LICENSE.txt +20 -0
  4. data/README.md +30 -14
  5. data/Rakefile +2 -2
  6. data/config/lang/objects/entities/en.yml +134 -0
  7. data/config/lang/objects/static/en.yml +8 -0
  8. data/config/lang/objects/vehicles/en.yml +11 -0
  9. data/config/map/entities.yml +42 -38
  10. data/lib/smash_and_grab.rb +5 -0
  11. data/lib/smash_and_grab/abilities.rb +1 -1
  12. data/lib/smash_and_grab/abilities/ability.rb +45 -3
  13. data/lib/smash_and_grab/abilities/drop.rb +38 -0
  14. data/lib/smash_and_grab/abilities/melee.rb +4 -6
  15. data/lib/smash_and_grab/abilities/pick_up.rb +33 -0
  16. data/lib/smash_and_grab/abilities/ranged.rb +18 -12
  17. data/lib/smash_and_grab/abilities/sprint.rb +11 -7
  18. data/lib/smash_and_grab/chingu_ext/basic_game_object.rb +26 -0
  19. data/lib/smash_and_grab/game_window.rb +5 -1
  20. data/lib/smash_and_grab/gui/entity_panel.rb +26 -10
  21. data/lib/smash_and_grab/gui/entity_summary.rb +19 -11
  22. data/lib/smash_and_grab/gui/game_log.rb +6 -2
  23. data/lib/smash_and_grab/gui/info_panel.rb +7 -4
  24. data/lib/smash_and_grab/gui/object_panel.rb +6 -2
  25. data/lib/smash_and_grab/gui/scenario_panel.rb +4 -0
  26. data/lib/smash_and_grab/history/action_history.rb +1 -1
  27. data/lib/smash_and_grab/main.rb +7 -3
  28. data/lib/smash_and_grab/map/faction.rb +26 -8
  29. data/lib/smash_and_grab/map/map.rb +21 -12
  30. data/lib/smash_and_grab/map/tile.rb +29 -2
  31. data/lib/smash_and_grab/map/wall.rb +2 -2
  32. data/lib/smash_and_grab/mixins/has_contents.rb +38 -0
  33. data/lib/smash_and_grab/mixins/line_of_sight.rb +129 -0
  34. data/lib/smash_and_grab/mixins/pathfinding.rb +145 -0
  35. data/lib/smash_and_grab/mouse_selection.rb +107 -36
  36. data/lib/smash_and_grab/objects/entity.rb +311 -260
  37. data/lib/smash_and_grab/objects/floating_text.rb +0 -1
  38. data/lib/smash_and_grab/objects/static.rb +10 -3
  39. data/lib/smash_and_grab/objects/vehicle.rb +7 -2
  40. data/lib/smash_and_grab/objects/world_object.rb +10 -3
  41. data/lib/smash_and_grab/path.rb +38 -12
  42. data/lib/smash_and_grab/players/ai.rb +91 -0
  43. data/lib/smash_and_grab/players/human.rb +9 -0
  44. data/lib/smash_and_grab/players/player.rb +16 -65
  45. data/lib/smash_and_grab/players/remote.rb +9 -0
  46. data/lib/smash_and_grab/sprite_sheet.rb +9 -0
  47. data/lib/smash_and_grab/states/edit_level.rb +15 -4
  48. data/lib/smash_and_grab/states/main_menu.rb +16 -4
  49. data/lib/smash_and_grab/states/play_level.rb +88 -28
  50. data/lib/smash_and_grab/states/world.rb +17 -9
  51. data/lib/smash_and_grab/version.rb +1 -1
  52. data/lib/smash_and_grab/z_order.rb +6 -5
  53. data/media/images/path.png +0 -0
  54. data/media/images/tile_selection.png +0 -0
  55. data/media/images/tiles_selection.png +0 -0
  56. data/smash_and_grab.gemspec +2 -2
  57. data/tasks/create_portraits.rb +39 -0
  58. data/tasks/outline_images.rb +56 -0
  59. data/test/smash_and_grab/abilities/drop_test.rb +68 -0
  60. data/test/smash_and_grab/abilities/melee_test.rb +4 -4
  61. data/test/smash_and_grab/abilities/pick_up_test.rb +68 -0
  62. data/test/smash_and_grab/abilities/ranged_test.rb +105 -0
  63. data/test/smash_and_grab/abilities/sprint_test.rb +55 -25
  64. data/test/smash_and_grab/map/faction_test.rb +18 -16
  65. data/test/smash_and_grab/map/map_test.rb +9 -4
  66. metadata +51 -19
  67. data/lib/smash_and_grab/fidgit_ext/event.rb +0 -77
@@ -0,0 +1,129 @@
1
+ module SmashAndGrab
2
+ module Mixins
3
+ module LineOfSight
4
+ #
5
+ def line_of_sight?(target_tile)
6
+ !line_of_sight_blocked_by(target_tile)
7
+ end
8
+
9
+ # Returns the tile that blocks sight, otherwise nil.
10
+ # Implements 'Bresenham's line algorithm'
11
+ # @return [Tile, Wall, nil]
12
+ def line_of_sight_blocked_by(target_tile)
13
+ raise unless target_tile.is_a? Tile
14
+
15
+ # Check for the special case of looking diagonally.
16
+ x1, y1 = tile.grid_x, tile.grid_y
17
+ x2, y2 = target_tile.grid_x, target_tile.grid_y
18
+
19
+ step_x = x1 < x2 ? 1 : -1
20
+ step_y = y1 < y2 ? 1 : -1
21
+ dx, dy = (x2 - x1).abs, (y2 - y1).abs
22
+
23
+ if dx == dy
24
+ # Special case of the diagonal line, which has to run either
25
+ # .45 ..5
26
+ # 23. OR .34
27
+ # 1.. 12.
28
+ # Blocked only if BOTH are blocked - return blockage from just one if both are blocked.
29
+ blockage1 = zig_zag_blocked_by(tile, step_x, step_y, dx - 1, true)
30
+ blockage2 = zig_zag_blocked_by(tile, step_x, step_y, dx - 1, false)
31
+ if blockage1 && blockage2
32
+ # Choose the blockage that is closest to us, since the other is irrelevant.
33
+ [blockage1, blockage2].min_by do |blockage|
34
+ case blockage
35
+ when Wall
36
+ blockage.tiles.map {|t| manhattan_distance t }.min
37
+ when Tile
38
+ manhattan_distance blockage
39
+ end
40
+ end
41
+ elsif blockage1
42
+ blockage1
43
+ else
44
+ blockage2
45
+ end
46
+ else
47
+ ray_trace_blocked_by tile, step_x, step_y, dx, dy
48
+ end
49
+ end
50
+
51
+ protected
52
+ def zig_zag_blocked_by(from, step_x, step_y, length, x_first)
53
+ current = from
54
+ x, y = from.grid_x, from.grid_y
55
+
56
+ length.times do
57
+ if x_first
58
+ x += step_x
59
+ else
60
+ y += step_y
61
+ end
62
+
63
+ checking = @map.tile_at_grid(x, y)
64
+ blockage = tile_to_tile_blocked_by current, checking
65
+ return blockage if blockage
66
+ current = checking
67
+
68
+ if x_first
69
+ y += step_y
70
+ else
71
+ x += step_x
72
+ end
73
+
74
+ checking = @map.tile_at_grid(x, y)
75
+ blockage = tile_to_tile_blocked_by current, checking
76
+ return blockage if blockage
77
+ current = checking
78
+ end
79
+
80
+ nil
81
+ end
82
+
83
+ protected
84
+ def ray_trace_blocked_by(from, step_x, step_y, dx, dy)
85
+ current = from
86
+ x, y = from.grid_x, from.grid_y
87
+
88
+ # General case, ray-trace.
89
+ error = dx - dy
90
+
91
+ # Ensure that all tiles are visited that the sight-line passes over,
92
+ # not just those that create a "drawn" line.
93
+ dx *= 2
94
+ dy *= 2
95
+
96
+ length = ((dx + dy + 1) / 2)
97
+
98
+ (length - 1).times do
99
+ # Note that this ignores the special case of error == 0
100
+ if error > 0
101
+ error -= dy
102
+ x += step_x
103
+ else
104
+ error += dx
105
+ y += step_y
106
+ end
107
+
108
+ # Look at the next tile and see which wall is in the way.
109
+ checking = @map.tile_at_grid(x, y)
110
+ blockage = tile_to_tile_blocked_by current, checking
111
+ return blockage if blockage
112
+ current = checking
113
+ end
114
+
115
+ nil
116
+ end
117
+
118
+ protected
119
+ def tile_to_tile_blocked_by(from, to)
120
+ wall = from.wall_to to
121
+ if wall and wall.blocks_sight?
122
+ wall
123
+ elsif to.blocks_sight?
124
+ to
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,145 @@
1
+ require_relative "../path"
2
+
3
+ module SmashAndGrab
4
+ module Mixins
5
+ module Pathfinding
6
+ # Returns a list of tiles this entity could move to (including those they could melee at) [Set]
7
+ def potential_moves
8
+ destination_tile = tile # We are sort of working backwards here.
9
+
10
+ # Tiles we've already dealt with.
11
+ closed_tiles = Set.new
12
+ # Tiles we've looked at and that are in-range.
13
+ valid_tiles = Set.new
14
+ # Paths to check { tile => path_to_tile }.
15
+ open_paths = { destination_tile => Paths::Start.new(destination_tile, destination_tile) }
16
+
17
+ melee_cost = has_ability?(:melee) ? ability(:melee).action_cost : Float::INFINITY
18
+ pick_up_cost = has_ability?(:pick_up) ? ability(:pick_up).action_cost : Float::INFINITY
19
+
20
+ while open_paths.any?
21
+ path = open_paths.each_value.min_by(&:cost)
22
+ current_tile = path.last
23
+
24
+ open_paths.delete current_tile
25
+ closed_tiles << current_tile
26
+
27
+ exits = current_tile.exits(self).reject {|wall| closed_tiles.include? wall.destination(current_tile) }
28
+ exits.each do |wall|
29
+ testing_tile = wall.destination(current_tile)
30
+ object = testing_tile.object
31
+
32
+ if object and object.is_a?(Objects::Entity) and enemy?(object)
33
+ # Ensure that the current tile is somewhere we could launch an attack from and we could actually perform it.
34
+ if (current_tile.empty? or current_tile == tile) and ap >= melee_cost
35
+ valid_tiles << testing_tile
36
+ end
37
+
38
+ elsif object and object.is_a?(Objects::Static) and pick_up?(object)
39
+ if (current_tile.empty? or current_tile == tile) and ap >= pick_up_cost
40
+ valid_tiles << testing_tile
41
+ end
42
+
43
+ elsif testing_tile.passable?(self) and (object.nil? or object.passable?(self))
44
+ new_path = Paths::Move.new(path, testing_tile, wall.movement_cost, 0)
45
+
46
+ # If the path is shorter than one we've already calculated, then replace it. Otherwise just store it.
47
+ if new_path.move_distance <= movement_points
48
+ old_path = open_paths[testing_tile]
49
+ if old_path
50
+ if new_path.move_distance < old_path.move_distance
51
+ open_paths[testing_tile] = new_path
52
+ end
53
+ else
54
+ open_paths[testing_tile] = new_path
55
+ valid_tiles << testing_tile if testing_tile.empty?
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ valid_tiles
63
+ end
64
+
65
+ # A* path-finding.
66
+ def path_to(destination_tile)
67
+ return Paths::None.new if destination_tile == tile
68
+ return Paths::Inaccessible.new(destination_tile) unless destination_tile.passable?(self)
69
+
70
+ closed_tiles = Set.new # Tiles we've already dealt with.
71
+ open_paths = { tile => Paths::Start.new(tile, destination_tile) } # Paths to check { tile => path_to_tile }.
72
+
73
+ destination_object = destination_tile.object
74
+ destination_is_enemy = (destination_object and destination_object.is_a? Objects::Entity and destination_object.enemy?(self))
75
+
76
+ melee_cost = has_ability?(:melee) ? ability(:melee).action_cost : Float::INFINITY
77
+ pick_up_cost = has_ability?(:pick_up) ? ability(:pick_up).action_cost : Float::INFINITY
78
+
79
+ while open_paths.any?
80
+ # Check the (expected) shortest path and move it to closed, since we have considered it.
81
+ path = open_paths.each_value.min_by(&:cost)
82
+ current_tile = path.last
83
+
84
+ return path if current_tile == destination_tile
85
+
86
+ open_paths.delete current_tile
87
+ closed_tiles << current_tile
88
+
89
+ next if path.is_a? Paths::Melee
90
+
91
+ # Check adjacent tiles.
92
+ exits = current_tile.exits(self).reject {|wall| closed_tiles.include? wall.destination(current_tile) }
93
+ exits.each do |wall|
94
+ testing_tile = wall.destination(current_tile)
95
+
96
+ new_path = nil
97
+
98
+ object = testing_tile.object
99
+ if object and object.is_a?(Objects::Entity) and enemy?(object)
100
+ # Ensure that the current tile is somewhere we could launch an attack from and we could actually perform it.
101
+ if (current_tile.empty? or current_tile == tile) and ap >= melee_cost
102
+ new_path = Paths::Melee.new(path, testing_tile)
103
+ else
104
+ next
105
+ end
106
+
107
+ elsif object and object.is_a?(Objects::Static) and pick_up?(object)
108
+ if (current_tile.empty? or current_tile == tile) and ap >= pick_up_cost
109
+ new_path = Paths::PickUp.new(path, testing_tile)
110
+ else
111
+ next
112
+ end
113
+
114
+ elsif testing_tile.passable?(self)
115
+ if object.nil? or object.passable?(self)
116
+ disincentive = 0
117
+ disincentive += 1 if testing_tile.overwatched? faction
118
+ if testing_tile.object
119
+ disincentive += 1 # Try to avoid moving through objects, so try alternate routes in preference.
120
+ else
121
+ disincentive += 1 if testing_tile.zoc? faction
122
+ end
123
+ new_path = Paths::Move.new(path, testing_tile, wall.movement_cost, disincentive)
124
+ else
125
+ next
126
+ end
127
+ end
128
+
129
+ # If the path is shorter than one we've already calculated, then replace it. Otherwise just store it.
130
+ old_path = open_paths[testing_tile]
131
+ if old_path
132
+ if new_path.move_distance < old_path.move_distance
133
+ open_paths[testing_tile] = new_path
134
+ end
135
+ else
136
+ open_paths[testing_tile] = new_path
137
+ end
138
+ end
139
+ end
140
+
141
+ Paths::Inaccessible.new(destination_tile) # Failed to connect at all.
142
+ end
143
+ end
144
+ end
145
+ end
@@ -2,22 +2,34 @@ module SmashAndGrab
2
2
  class MouseSelection < GameObject
3
3
  attr_reader :selected, :hover_tile
4
4
 
5
- MOVE_COLOR = Color.rgba(0, 255, 0, 60)
6
- MELEE_COLOR = Color.rgba(255, 0, 0, 80)
5
+ MOVE_COLOR = Color.rgba(50, 50, 255, 60)
6
+ POTENTIAL_ATTACK_COLOR = Color.rgba(255, 255, 255, 100)
7
+ RANGED_EYE_COLOR = Color.rgba(255, 255, 255, 30)
7
8
  NO_MOVE_COLOR = Color.rgba(255, 0, 0, 30)
8
- ZOC_COLOR = Color.rgba(255, 0, 0, 100)
9
+ WARNING_COLOR = Color.rgba(255, 255, 255, 50) # ZOC/Overwatch warnings
9
10
 
10
11
  def initialize(map, options = {})
11
12
  @map = map
12
13
 
13
14
  @potential_moves = []
15
+ @potential_ranged = []
14
16
 
15
17
  @selected_image = Image["tile_selection.png"]
16
18
  @mouse_hover_image = Image["mouse_hover.png"]
17
19
 
20
+ path_images = SpriteSheet["path.png", 32, 16, 4]
21
+ @ranged_target_image = path_images[0, 4]
22
+ @ranged_eye_image = path_images[1, 4]
23
+ @ranged_dot_image = path_images[2, 4]
24
+ @melee_target_image = path_images[3, 1]
25
+ @pick_up_image = path_images[0, 5]
26
+ @zoc_image = path_images[3, 4]
27
+ @overwatch_image = path_images[2, 4]
28
+
18
29
  @selected = @hover_tile = nil
19
30
  @path = nil
20
31
  @moves_record = nil
32
+ @ranged_record = nil
21
33
  @selected_changed_handler = nil
22
34
 
23
35
  super(options)
@@ -37,11 +49,21 @@ class MouseSelection < GameObject
37
49
 
38
50
  def update
39
51
  self.selected = nil if selected and selected.tile.nil?
52
+
53
+ # Ensure the character faces the mouse, but don't flip out when moving the mouse directly above or
54
+ # below the character, so ensure that the cursor has to move a few pixels further across to trigger switch.
55
+ if @hover_tile and selected_can_be_controlled?
56
+ if (parent.cursor_world_x - selected.x > 2 and selected.factor_x < 0) or # Turn right
57
+ (parent.cursor_world_x - selected.x < -2 and selected.factor_x > 0) # Turn left
58
+ selected.face parent.cursor_world_x
59
+ end
60
+ end
61
+
40
62
  super
41
63
  end
42
64
 
43
65
  def selected_can_be_controlled?
44
- selected and selected.active? and selected.faction.player.is_a?(Players::Human) and not @map.busy?
66
+ selected and selected.active? and selected.faction.player.human? and not @map.busy?
45
67
  end
46
68
 
47
69
  def tile=(tile)
@@ -67,6 +89,8 @@ class MouseSelection < GameObject
67
89
  else
68
90
  modify_occlusions @potential_moves, +1
69
91
  @potential_moves.clear
92
+ modify_occlusions @potential_ranged, +1
93
+ @potential_ranged.clear
70
94
  end
71
95
  end
72
96
 
@@ -80,19 +104,46 @@ class MouseSelection < GameObject
80
104
  else
81
105
  $window.record(1, 1) do
82
106
  @potential_moves.each do |tile|
83
- entity = tile.object
84
- color = if entity and entity.enemy?(selected)
85
- MELEE_COLOR
86
- else
87
- MOVE_COLOR
107
+ case tile.object
108
+ when Objects::Entity
109
+ if tile.object.enemy?(selected)
110
+ @melee_target_image.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, POTENTIAL_ATTACK_COLOR
111
+ end
112
+ when Objects::Static
113
+ @pick_up_image.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, POTENTIAL_ATTACK_COLOR
114
+
115
+ else
116
+ Tile.blank.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, MOVE_COLOR, :additive
88
117
  end
89
118
 
90
- # Tile background
91
- Tile.blank.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, color, :additive
92
-
93
119
  # ZOC indicator.
94
120
  if tile.entities_exerting_zoc(selected.tile.object.faction).any? and tile.empty?
95
- Tile.blank.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 0.7, 0.7, ZOC_COLOR
121
+ @zoc_image.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, WARNING_COLOR
122
+ end
123
+
124
+ if tile.overwatched? selected.faction
125
+ @overwatch_image.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, WARNING_COLOR
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+
132
+ def calculate_potential_ranged
133
+ modify_occlusions @potential_ranged, -1
134
+ @potential_ranged = selected.potential_ranged
135
+ modify_occlusions @potential_ranged, +1
136
+
137
+ # Draw a mark on all tiles that could be affected by ranged attack.
138
+ @ranged_record = if @potential_ranged.empty?
139
+ nil
140
+ else
141
+ $window.record 1, 1 do
142
+ @potential_ranged.each do |tile|
143
+ if tile.object and tile.object.is_a?(Objects::Entity) and selected.enemy?(tile.object)
144
+ @ranged_target_image.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, POTENTIAL_ATTACK_COLOR
145
+ else
146
+ @ranged_eye_image.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, RANGED_EYE_COLOR
96
147
  end
97
148
  end
98
149
  end
@@ -106,23 +157,22 @@ class MouseSelection < GameObject
106
157
  end
107
158
 
108
159
  def draw
109
- # Draw a disc under the selected object.
110
160
  if selected and selected.tile
111
- selected_color = if selected.is_a? Objects::Entity
112
- if selected_can_be_controlled? and selected.move?
113
- Color::GREEN
114
- else
115
- Color::BLACK
116
- end
117
- else
118
- Color::BLUE
119
- end
120
-
121
- @selected_image.draw_rot selected.tile.x, selected.tile.y, ZOrder::TILE_SELECTION, 0, 0.5, 0.5, 1, 1, selected_color
161
+ # Draw a disc/box under the selected object.
162
+ selected.draw_base
122
163
 
123
164
  # Highlight all squares that character can travel to.
124
165
  @moves_record.draw 0, 0, ZOrder::TILE_SELECTION if @moves_record
125
- @path.draw if @path
166
+
167
+ @ranged_record.draw 0, 0, ZOrder::TILE_SELECTION if @ranged_record
168
+
169
+ if @ranged_record and @potential_ranged.include?(@hover_tile) and @hover_tile.object and
170
+ @hover_tile.object.is_a?(Objects::Entity) and selected.enemy?(@hover_tile.object) and
171
+ selected.line_of_sight?(@hover_tile)
172
+ @ranged_target_image.draw_rot @hover_tile.x, @hover_tile.y, 0, 0, 0.5, 0.5, 1, 1
173
+ elsif @path
174
+ @path.draw selected.mp
175
+ end
126
176
 
127
177
  elsif @hover_tile
128
178
  color = (@hover_tile.empty? or @hover_tile.object.inactive?) ? Color::BLUE : Color::CYAN
@@ -130,32 +180,49 @@ class MouseSelection < GameObject
130
180
  end
131
181
 
132
182
  # Make the stat-bars visible when hovering over something else.
133
- if @hover_tile and not (selected and @hover_tile == selected.tile)
183
+ if @hover_tile
134
184
  object = @hover_tile.object
135
- object.draw_stat_bars 10000 if object.is_a? Objects::Entity and object.alive?
185
+ object.draw_stat_bars zorder: ZOrder::BEHIND_GUI if object.is_a? Objects::Entity and object.alive?
136
186
  end
137
187
  end
138
188
 
139
189
  def left_click
140
- if @potential_moves.include? @hover_tile
141
- path = @path # @path will change as we move.
190
+ if @ranged_record and @hover_tile and @hover_tile.object and
191
+ @hover_tile.object.is_a?(Objects::Entity) and selected.enemy?(@hover_tile.object) and
192
+ selected.line_of_sight?(@hover_tile)
193
+
194
+ selected.use_ability :ranged, @hover_tile.object
195
+ calculate_potential_ranged unless selected.use_ability? :ranged
196
+
197
+ elsif @potential_moves.include? @hover_tile
198
+ actor = selected
199
+ saved_path = @path # @path will change as we move.
142
200
 
143
201
  # Move the character, perhaps with melee at the end.
144
- case path
202
+ case saved_path
145
203
  when Paths::Move
146
- selected.use_ability :move, path
204
+ actor.use_ability :move, saved_path
205
+
147
206
  when Paths::Melee
148
- selected.use_ability :move, path.previous_path if path.requires_movement?
207
+ actor.use_ability :move, saved_path.previous_path if saved_path.requires_movement?
149
208
 
150
209
  # Only perform melee if you weren't killed by attacks of opportunity.
151
- attacker, target = selected, path.last.object
152
- attacker.add_activity do
153
- attacker.use_ability :melee, target if attacker.alive?
210
+ actor.add_activity do
211
+ actor.use_ability :melee, saved_path.defender if actor.alive?
212
+ end
213
+
214
+ when Paths::PickUp
215
+ actor.use_ability :move, saved_path.previous_path if saved_path.requires_movement?
216
+
217
+ # Only pick up if you weren't killed by attacks of opportunity.
218
+ actor.add_activity do
219
+ actor.use_ability :pick_up, saved_path.object if actor.alive?
154
220
  end
155
221
  end
156
222
 
157
223
  calculate_path
158
224
  calculate_potential_moves
225
+ calculate_potential_ranged
159
226
  elsif @hover_tile and @hover_tile.object
160
227
  # Select a character to move.
161
228
  self.selected = @hover_tile.object
@@ -164,13 +231,17 @@ class MouseSelection < GameObject
164
231
 
165
232
  def recalculate
166
233
  @moves_record = nil
234
+ @ranged_record = nil
167
235
 
168
236
  if selected and selected_can_be_controlled?
169
237
  calculate_path
170
238
  calculate_potential_moves
239
+ calculate_potential_ranged
171
240
  else
172
241
  modify_occlusions @potential_moves, -1
173
242
  @potential_moves.clear
243
+ modify_occlusions @potential_ranged, -1
244
+ @potential_ranged.clear
174
245
 
175
246
  modify_occlusions @path.tiles, -1 if @path
176
247
  @path = nil