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.
- 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
|