smash_and_grab 0.0.5alpha → 0.0.6alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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