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.
- data/{CHANGELOG.txt → CHANGELOG.md} +0 -0
- data/Gemfile.lock +8 -4
- data/LICENSE.txt +20 -0
- data/README.md +30 -14
- data/Rakefile +2 -2
- data/config/lang/objects/entities/en.yml +134 -0
- data/config/lang/objects/static/en.yml +8 -0
- data/config/lang/objects/vehicles/en.yml +11 -0
- data/config/map/entities.yml +42 -38
- data/lib/smash_and_grab.rb +5 -0
- data/lib/smash_and_grab/abilities.rb +1 -1
- data/lib/smash_and_grab/abilities/ability.rb +45 -3
- data/lib/smash_and_grab/abilities/drop.rb +38 -0
- data/lib/smash_and_grab/abilities/melee.rb +4 -6
- data/lib/smash_and_grab/abilities/pick_up.rb +33 -0
- data/lib/smash_and_grab/abilities/ranged.rb +18 -12
- data/lib/smash_and_grab/abilities/sprint.rb +11 -7
- data/lib/smash_and_grab/chingu_ext/basic_game_object.rb +26 -0
- data/lib/smash_and_grab/game_window.rb +5 -1
- data/lib/smash_and_grab/gui/entity_panel.rb +26 -10
- data/lib/smash_and_grab/gui/entity_summary.rb +19 -11
- data/lib/smash_and_grab/gui/game_log.rb +6 -2
- data/lib/smash_and_grab/gui/info_panel.rb +7 -4
- data/lib/smash_and_grab/gui/object_panel.rb +6 -2
- data/lib/smash_and_grab/gui/scenario_panel.rb +4 -0
- data/lib/smash_and_grab/history/action_history.rb +1 -1
- data/lib/smash_and_grab/main.rb +7 -3
- data/lib/smash_and_grab/map/faction.rb +26 -8
- data/lib/smash_and_grab/map/map.rb +21 -12
- data/lib/smash_and_grab/map/tile.rb +29 -2
- data/lib/smash_and_grab/map/wall.rb +2 -2
- data/lib/smash_and_grab/mixins/has_contents.rb +38 -0
- data/lib/smash_and_grab/mixins/line_of_sight.rb +129 -0
- data/lib/smash_and_grab/mixins/pathfinding.rb +145 -0
- data/lib/smash_and_grab/mouse_selection.rb +107 -36
- data/lib/smash_and_grab/objects/entity.rb +311 -260
- data/lib/smash_and_grab/objects/floating_text.rb +0 -1
- data/lib/smash_and_grab/objects/static.rb +10 -3
- data/lib/smash_and_grab/objects/vehicle.rb +7 -2
- data/lib/smash_and_grab/objects/world_object.rb +10 -3
- data/lib/smash_and_grab/path.rb +38 -12
- data/lib/smash_and_grab/players/ai.rb +91 -0
- data/lib/smash_and_grab/players/human.rb +9 -0
- data/lib/smash_and_grab/players/player.rb +16 -65
- data/lib/smash_and_grab/players/remote.rb +9 -0
- data/lib/smash_and_grab/sprite_sheet.rb +9 -0
- data/lib/smash_and_grab/states/edit_level.rb +15 -4
- data/lib/smash_and_grab/states/main_menu.rb +16 -4
- data/lib/smash_and_grab/states/play_level.rb +88 -28
- data/lib/smash_and_grab/states/world.rb +17 -9
- data/lib/smash_and_grab/version.rb +1 -1
- data/lib/smash_and_grab/z_order.rb +6 -5
- data/media/images/path.png +0 -0
- data/media/images/tile_selection.png +0 -0
- data/media/images/tiles_selection.png +0 -0
- data/smash_and_grab.gemspec +2 -2
- data/tasks/create_portraits.rb +39 -0
- data/tasks/outline_images.rb +56 -0
- data/test/smash_and_grab/abilities/drop_test.rb +68 -0
- data/test/smash_and_grab/abilities/melee_test.rb +4 -4
- data/test/smash_and_grab/abilities/pick_up_test.rb +68 -0
- data/test/smash_and_grab/abilities/ranged_test.rb +105 -0
- data/test/smash_and_grab/abilities/sprint_test.rb +55 -25
- data/test/smash_and_grab/map/faction_test.rb +18 -16
- data/test/smash_and_grab/map/map_test.rb +9 -4
- metadata +51 -19
- 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(
|
6
|
-
|
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
|
-
|
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.
|
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
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
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
|
-
|
112
|
-
|
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
|
-
|
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
|
183
|
+
if @hover_tile
|
134
184
|
object = @hover_tile.object
|
135
|
-
object.draw_stat_bars
|
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 @
|
141
|
-
|
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
|
202
|
+
case saved_path
|
145
203
|
when Paths::Move
|
146
|
-
|
204
|
+
actor.use_ability :move, saved_path
|
205
|
+
|
147
206
|
when Paths::Melee
|
148
|
-
|
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
|
-
|
152
|
-
|
153
|
-
|
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
|