smash_and_grab 0.0.3alpha → 0.0.5alpha
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/Gemfile.lock +1 -1
- data/README.md +31 -16
- data/config/gui/schema.yml +3 -2
- data/config/map/entities.yml +36 -12
- data/lib/smash_and_grab/abilities/ability.rb +5 -0
- data/lib/smash_and_grab/abilities/melee.rb +11 -5
- data/lib/smash_and_grab/abilities/ranged.rb +20 -0
- data/lib/smash_and_grab/abilities/sprint.rb +4 -0
- data/lib/smash_and_grab/abilities.rb +3 -1
- data/lib/smash_and_grab/fidgit_ext/event.rb +77 -0
- data/lib/smash_and_grab/gui/editor_selector.rb +54 -73
- data/lib/smash_and_grab/gui/entity_panel.rb +110 -0
- data/lib/smash_and_grab/gui/entity_summary.rb +9 -8
- data/lib/smash_and_grab/gui/game_log.rb +44 -0
- data/lib/smash_and_grab/gui/info_panel.rb +39 -95
- data/lib/smash_and_grab/gui/object_panel.rb +37 -0
- data/lib/smash_and_grab/gui/scenario_panel.rb +21 -0
- data/lib/smash_and_grab/history/editor_actions/place_object.rb +1 -1
- data/lib/smash_and_grab/main.rb +11 -16
- data/lib/smash_and_grab/map/map.rb +1 -0
- data/lib/smash_and_grab/map/tile.rb +6 -3
- data/lib/smash_and_grab/map/wall.rb +4 -2
- data/lib/smash_and_grab/mouse_selection.rb +103 -46
- data/lib/smash_and_grab/objects/entity.rb +219 -30
- data/lib/smash_and_grab/objects/static.rb +7 -5
- data/lib/smash_and_grab/objects/vehicle.rb +7 -5
- data/lib/smash_and_grab/objects/world_object.rb +13 -3
- data/lib/smash_and_grab/path.rb +13 -7
- data/lib/smash_and_grab/players/player.rb +15 -10
- data/lib/smash_and_grab/states/edit_level.rb +17 -0
- data/lib/smash_and_grab/states/play_level.rb +20 -10
- data/lib/smash_and_grab/version.rb +1 -1
- data/lib/smash_and_grab.rb +18 -14
- data/test/smash_and_grab/abilities/melee_test.rb +37 -39
- data/test/teststrap.rb +3 -3
- metadata +21 -16
@@ -1,13 +1,11 @@
|
|
1
1
|
module SmashAndGrab
|
2
2
|
class MouseSelection < GameObject
|
3
|
-
attr_reader :
|
3
|
+
attr_reader :selected, :hover_tile
|
4
4
|
|
5
5
|
MOVE_COLOR = Color.rgba(0, 255, 0, 60)
|
6
6
|
MELEE_COLOR = Color.rgba(255, 0, 0, 80)
|
7
7
|
NO_MOVE_COLOR = Color.rgba(255, 0, 0, 30)
|
8
|
-
ZOC_COLOR = Color.rgba(255, 0, 0,
|
9
|
-
|
10
|
-
def selected; @selected_tile ? @selected_tile.object : nil; end
|
8
|
+
ZOC_COLOR = Color.rgba(255, 0, 0, 100)
|
11
9
|
|
12
10
|
def initialize(map, options = {})
|
13
11
|
@map = map
|
@@ -17,32 +15,51 @@ class MouseSelection < GameObject
|
|
17
15
|
@selected_image = Image["tile_selection.png"]
|
18
16
|
@mouse_hover_image = Image["mouse_hover.png"]
|
19
17
|
|
20
|
-
@
|
18
|
+
@selected = @hover_tile = nil
|
21
19
|
@path = nil
|
22
20
|
@moves_record = nil
|
21
|
+
@selected_changed_handler = nil
|
23
22
|
|
24
23
|
super(options)
|
25
24
|
|
26
25
|
add_inputs(released_left_mouse_button: :left_click,
|
27
26
|
released_right_mouse_button: :right_click)
|
27
|
+
|
28
|
+
@map.factions.each do |faction|
|
29
|
+
faction.subscribe :turn_ended do
|
30
|
+
recalculate
|
31
|
+
end
|
32
|
+
faction.subscribe :turn_started do
|
33
|
+
recalculate
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update
|
39
|
+
self.selected = nil if selected and selected.tile.nil?
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
def selected_can_be_controlled?
|
44
|
+
selected and selected.active? and selected.faction.player.is_a?(Players::Human) and not @map.busy?
|
28
45
|
end
|
29
46
|
|
30
47
|
def tile=(tile)
|
31
48
|
if tile != @hover_tile
|
32
|
-
modify_occlusions
|
49
|
+
modify_occlusions @hover_tile, -1 if @hover_tile
|
33
50
|
@hover_tile = tile
|
34
|
-
modify_occlusions
|
51
|
+
modify_occlusions @hover_tile, +1 if @hover_tile
|
35
52
|
calculate_path
|
36
53
|
end
|
37
54
|
end
|
38
55
|
|
39
56
|
def calculate_path
|
40
|
-
if
|
57
|
+
if selected_can_be_controlled?
|
41
58
|
modify_occlusions @path.tiles, -1 if @path
|
42
59
|
|
43
60
|
if @hover_tile
|
44
|
-
@path =
|
45
|
-
@path.prepare_for_drawing
|
61
|
+
@path = selected.path_to @hover_tile
|
62
|
+
@path.prepare_for_drawing @potential_moves
|
46
63
|
modify_occlusions @path.tiles, +1
|
47
64
|
else
|
48
65
|
@path = nil
|
@@ -55,16 +72,16 @@ class MouseSelection < GameObject
|
|
55
72
|
|
56
73
|
def calculate_potential_moves
|
57
74
|
modify_occlusions @potential_moves, -1
|
58
|
-
@potential_moves =
|
75
|
+
@potential_moves = selected.potential_moves
|
59
76
|
modify_occlusions @potential_moves, +1
|
60
77
|
|
61
|
-
|
62
78
|
@moves_record = if @potential_moves.empty?
|
63
79
|
nil
|
64
80
|
else
|
65
81
|
$window.record(1, 1) do
|
66
82
|
@potential_moves.each do |tile|
|
67
|
-
|
83
|
+
entity = tile.object
|
84
|
+
color = if entity and entity.enemy?(selected)
|
68
85
|
MELEE_COLOR
|
69
86
|
else
|
70
87
|
MOVE_COLOR
|
@@ -74,8 +91,8 @@ class MouseSelection < GameObject
|
|
74
91
|
Tile.blank.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 1, 1, color, :additive
|
75
92
|
|
76
93
|
# ZOC indicator.
|
77
|
-
if tile.entities_exerting_zoc(
|
78
|
-
Tile.blank.draw_rot tile.x, tile.y, 0, 0, 0.5, 0.5, 0.
|
94
|
+
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
|
79
96
|
end
|
80
97
|
end
|
81
98
|
end
|
@@ -83,16 +100,25 @@ class MouseSelection < GameObject
|
|
83
100
|
end
|
84
101
|
|
85
102
|
def modify_occlusions(tiles, amount)
|
86
|
-
tiles.each do |tile|
|
103
|
+
Array(tiles).each do |tile|
|
87
104
|
tile.modify_occlusions amount
|
88
105
|
end
|
89
106
|
end
|
90
107
|
|
91
108
|
def draw
|
92
109
|
# Draw a disc under the selected object.
|
93
|
-
if
|
94
|
-
selected_color =
|
95
|
-
|
110
|
+
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
|
96
122
|
|
97
123
|
# Highlight all squares that character can travel to.
|
98
124
|
@moves_record.draw 0, 0, ZOrder::TILE_SELECTION if @moves_record
|
@@ -102,36 +128,45 @@ class MouseSelection < GameObject
|
|
102
128
|
color = (@hover_tile.empty? or @hover_tile.object.inactive?) ? Color::BLUE : Color::CYAN
|
103
129
|
@mouse_hover_image.draw_rot @hover_tile.x, @hover_tile.y, ZOrder::TILE_SELECTION, 0, 0.5, 0.5, 1, 1, color
|
104
130
|
end
|
131
|
+
|
132
|
+
# Make the stat-bars visible when hovering over something else.
|
133
|
+
if @hover_tile and not (selected and @hover_tile == selected.tile)
|
134
|
+
object = @hover_tile.object
|
135
|
+
object.draw_stat_bars 10000 if object.is_a? Objects::Entity and object.alive?
|
136
|
+
end
|
105
137
|
end
|
106
138
|
|
107
139
|
def left_click
|
108
|
-
if @
|
109
|
-
path = @path
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
calculate_potential_moves
|
140
|
+
if @potential_moves.include? @hover_tile
|
141
|
+
path = @path # @path will change as we move.
|
142
|
+
|
143
|
+
# Move the character, perhaps with melee at the end.
|
144
|
+
case path
|
145
|
+
when Paths::Move
|
146
|
+
selected.use_ability :move, path
|
147
|
+
when Paths::Melee
|
148
|
+
selected.use_ability :move, path.previous_path if path.requires_movement?
|
149
|
+
|
150
|
+
# 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?
|
154
|
+
end
|
124
155
|
end
|
125
|
-
|
156
|
+
|
157
|
+
calculate_path
|
158
|
+
calculate_potential_moves
|
159
|
+
elsif @hover_tile and @hover_tile.object
|
126
160
|
# Select a character to move.
|
127
|
-
|
161
|
+
self.selected = @hover_tile.object
|
128
162
|
end
|
129
163
|
end
|
130
164
|
|
131
|
-
def
|
132
|
-
|
133
|
-
|
134
|
-
|
165
|
+
def recalculate
|
166
|
+
@moves_record = nil
|
167
|
+
|
168
|
+
if selected and selected_can_be_controlled?
|
169
|
+
calculate_path
|
135
170
|
calculate_potential_moves
|
136
171
|
else
|
137
172
|
modify_occlusions @potential_moves, -1
|
@@ -139,16 +174,38 @@ class MouseSelection < GameObject
|
|
139
174
|
|
140
175
|
modify_occlusions @path.tiles, -1 if @path
|
141
176
|
@path = nil
|
177
|
+
end
|
178
|
+
end
|
142
179
|
|
143
|
-
|
180
|
+
def selected=(object)
|
181
|
+
# Make sure we learn of any changes made to the selected object so we can move.
|
182
|
+
if selected
|
183
|
+
@selected_changed_handler.unsubscribe
|
184
|
+
@selected_changed_handler = nil
|
185
|
+
end
|
186
|
+
|
187
|
+
@selected = object
|
188
|
+
recalculate
|
189
|
+
|
190
|
+
if selected
|
191
|
+
tile, path, potential_moves = selected.tile, @path, @potential_moves.dup
|
192
|
+
|
193
|
+
@selected_changed_handler = selected.subscribe :changed do |entity|
|
194
|
+
recalculate
|
195
|
+
|
196
|
+
# Create a partial path while we move.
|
197
|
+
if path and entity.tile != tile
|
198
|
+
tile = entity.tile
|
199
|
+
path.prepare_for_drawing potential_moves, from: entity.tile
|
200
|
+
modify_occlusions path.tiles, +1
|
201
|
+
@path = path
|
202
|
+
end
|
203
|
+
end
|
144
204
|
end
|
145
205
|
end
|
146
206
|
|
147
207
|
def right_click
|
148
|
-
|
149
|
-
if @selected_tile
|
150
|
-
select nil
|
151
|
-
end
|
208
|
+
self.selected = nil if selected
|
152
209
|
end
|
153
210
|
end
|
154
211
|
end
|
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'set'
|
2
|
+
require 'fiber'
|
3
|
+
|
2
4
|
require_relative "../path"
|
3
5
|
require_relative "../abilities"
|
4
6
|
require_relative "world_object"
|
@@ -14,14 +16,29 @@ class Entity < WorldObject
|
|
14
16
|
SPRITE_WIDTH, SPRITE_HEIGHT = 66, 66
|
15
17
|
PORTRAIT_WIDTH, PORTRAIT_HEIGHT = 36, 36
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
+
STATS_BACKGROUND_COLOR = Color::BLACK
|
20
|
+
STATS_HP_COLOR = Color.rgb(0, 200, 0)
|
21
|
+
STATS_MP_COLOR = Color.rgb(100, 100, 255)
|
22
|
+
STATS_AP_COLOR = Color::YELLOW
|
23
|
+
STATS_USED_COLOR = Color.rgb(100, 100, 100)
|
24
|
+
STATS_WIDTH = 12.0
|
25
|
+
STATS_HALF_WIDTH = STATS_WIDTH / 2
|
26
|
+
|
27
|
+
class << self
|
28
|
+
def config; @config ||= YAML.load_file(File.expand_path("config/map/entities.yml", EXTRACT_PATH)); end
|
29
|
+
def types; config.keys; end
|
30
|
+
def sprites; @sprites ||= SpriteSheet.new("entities.png", SPRITE_WIDTH, SPRITE_HEIGHT, 8); end
|
31
|
+
def portraits; @portraits ||= SpriteSheet.new("entity_portraits.png", PORTRAIT_WIDTH, PORTRAIT_HEIGHT, 8); end
|
32
|
+
end
|
19
33
|
|
20
34
|
def_delegators :@faction, :minimap_color, :active?, :inactive?
|
21
35
|
|
22
36
|
attr_reader :faction, :movement_points, :action_points, :health, :type, :portrait,
|
23
37
|
:max_movement_points, :max_action_points, :max_health
|
24
38
|
|
39
|
+
alias_method :hp, :health
|
40
|
+
alias_method :max_hp, :max_health
|
41
|
+
|
25
42
|
attr_writer :movement_points, :action_points
|
26
43
|
|
27
44
|
alias_method :max_mp, :max_movement_points
|
@@ -37,11 +54,6 @@ class Entity < WorldObject
|
|
37
54
|
def name; @type.to_s.split("_").map(&:capitalize).join(" "); end
|
38
55
|
def alive?; @health > 0 and @tile; end
|
39
56
|
|
40
|
-
def self.config; @@config ||= YAML.load_file(File.expand_path("config/map/entities.yml", EXTRACT_PATH)); end
|
41
|
-
def self.types; config.keys; end
|
42
|
-
def self.sprites; @@sprites ||= SpriteSheet.new("entities.png", SPRITE_WIDTH, SPRITE_HEIGHT, 8); end
|
43
|
-
def self.portraits; @@portraits ||= SpriteSheet.new("entity_portraits.png", PORTRAIT_WIDTH, PORTRAIT_HEIGHT, 8); end
|
44
|
-
|
45
57
|
def initialize(map, data)
|
46
58
|
@type = data[:type]
|
47
59
|
config = self.class.config[data[:type]]
|
@@ -76,12 +88,18 @@ class Entity < WorldObject
|
|
76
88
|
|
77
89
|
if config[:abilities]
|
78
90
|
config[:abilities].each do |ability_data|
|
79
|
-
ability_data = ability_data
|
80
91
|
@abilities[ability_data[:type]] = Abilities.ability(self, ability_data)
|
81
92
|
end
|
82
93
|
end
|
83
94
|
|
95
|
+
@queued_activities = []
|
96
|
+
|
84
97
|
@faction << self
|
98
|
+
|
99
|
+
@stat_bars_record = nil
|
100
|
+
subscribe :changed do
|
101
|
+
@stat_bars_record = nil
|
102
|
+
end
|
85
103
|
end
|
86
104
|
|
87
105
|
def has_ability?(type); @abilities.has_key? type; end
|
@@ -100,21 +118,55 @@ class Entity < WorldObject
|
|
100
118
|
end
|
101
119
|
|
102
120
|
FloatingText.new(text, color: color, x: x, y: y - height / 3, zorder: y - 0.01)
|
121
|
+
publish :changed
|
103
122
|
end
|
104
123
|
|
105
124
|
if @health == 0 and @tile
|
125
|
+
parent.publish :game_info, "#{name} was vanquished!"
|
106
126
|
self.tile = nil
|
127
|
+
@queued_activities.empty?
|
107
128
|
end
|
108
129
|
end
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
130
|
+
alias_method :hp=, :health=
|
131
|
+
|
132
|
+
# Called from GameAction::Ability
|
133
|
+
# Also used to un-melee :)
|
134
|
+
def melee(target, damage)
|
135
|
+
add_activity do
|
136
|
+
if damage > 0 # do => wound
|
137
|
+
face target
|
138
|
+
self.z += 10
|
139
|
+
delay 0.1
|
140
|
+
self.z -= 10
|
141
|
+
|
142
|
+
# Can be dead at this point if there were 2-3 attackers of opportunity!
|
143
|
+
if target.alive?
|
144
|
+
parent.publish :game_info, "#{name} smashed #{target.name} for {#{damage}}"
|
145
|
+
target.health -= damage
|
146
|
+
|
147
|
+
target.color = Color.rgb(255, 100, 100)
|
148
|
+
delay 0.1
|
149
|
+
target.color = Color::WHITE
|
150
|
+
else
|
151
|
+
parent.publish :game_info, "#{name} kicked #{target.name} while they were down"
|
152
|
+
end
|
153
|
+
else # undo => heal
|
154
|
+
target.color = Color.rgb(255, 100, 100)
|
155
|
+
delay 0.1
|
156
|
+
target.health -= damage
|
157
|
+
target.color = Color::WHITE
|
158
|
+
|
159
|
+
self.z += 10
|
160
|
+
delay 0.1
|
161
|
+
self.z -= 10
|
162
|
+
end
|
163
|
+
end
|
113
164
|
end
|
114
165
|
|
115
166
|
def start_turn
|
116
167
|
@movement_points = @max_movement_points
|
117
168
|
@action_points = @max_action_points
|
169
|
+
publish :changed
|
118
170
|
end
|
119
171
|
|
120
172
|
def end_turn
|
@@ -122,7 +174,39 @@ class Entity < WorldObject
|
|
122
174
|
end
|
123
175
|
|
124
176
|
def draw
|
125
|
-
|
177
|
+
return unless alive?
|
178
|
+
|
179
|
+
super()
|
180
|
+
|
181
|
+
draw_stat_bars @y
|
182
|
+
end
|
183
|
+
|
184
|
+
def draw_stat_bars(zorder)
|
185
|
+
@stat_bars_record ||= $window.record 1, 1 do
|
186
|
+
# Draw background shadow.
|
187
|
+
height = 1
|
188
|
+
height += 1 if active?
|
189
|
+
height += 1 if max_ap > 0
|
190
|
+
|
191
|
+
$window.pixel.draw -0.5, -0.5, 0, STATS_WIDTH + 1, height + 1, STATS_BACKGROUND_COLOR
|
192
|
+
|
193
|
+
# Health.
|
194
|
+
$window.pixel.draw 0, 0, 0, STATS_WIDTH * health / max_health, 1, STATS_HP_COLOR
|
195
|
+
|
196
|
+
# Action points.
|
197
|
+
if max_ap > 0
|
198
|
+
pip_width = (STATS_WIDTH + 1 - max_ap) / max_ap
|
199
|
+
max_ap.times do |i|
|
200
|
+
color = i < ap ? STATS_AP_COLOR : STATS_USED_COLOR
|
201
|
+
$window.pixel.draw i * pip_width + i, 1, 0, pip_width, 1, color
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
# Movement points.
|
206
|
+
$window.pixel.draw 0, 2, 0, 12.0 * mp / max_mp, 1, STATS_MP_COLOR if active?
|
207
|
+
end
|
208
|
+
|
209
|
+
@stat_bars_record.draw @x - STATS_HALF_WIDTH, @y - 4, zorder
|
126
210
|
end
|
127
211
|
|
128
212
|
def friend?(character); @faction.friend? character.faction; end
|
@@ -151,6 +235,8 @@ class Entity < WorldObject
|
|
151
235
|
# Paths to check { tile => path_to_tile }.
|
152
236
|
open_paths = { destination_tile => Paths::Start.new(destination_tile, destination_tile) }
|
153
237
|
|
238
|
+
melee_cost = has_ability?(:melee) ? ability(:melee).action_cost : Float::INFINITY
|
239
|
+
|
154
240
|
while open_paths.any?
|
155
241
|
path = open_paths.each_value.min_by(&:cost)
|
156
242
|
current_tile = path.last
|
@@ -165,7 +251,7 @@ class Entity < WorldObject
|
|
165
251
|
|
166
252
|
if object and object.is_a?(Objects::Entity) and enemy?(object)
|
167
253
|
# Ensure that the current tile is somewhere we could launch an attack from and we could actually perform it.
|
168
|
-
if (current_tile.empty? or current_tile == tile) and ap >=
|
254
|
+
if (current_tile.empty? or current_tile == tile) and ap >= melee_cost
|
169
255
|
valid_tiles << testing_tile
|
170
256
|
end
|
171
257
|
|
@@ -174,7 +260,8 @@ class Entity < WorldObject
|
|
174
260
|
|
175
261
|
# If the path is shorter than one we've already calculated, then replace it. Otherwise just store it.
|
176
262
|
if new_path.move_distance <= movement_points
|
177
|
-
|
263
|
+
old_path = open_paths[testing_tile]
|
264
|
+
if old_path
|
178
265
|
if new_path.move_distance < old_path.move_distance
|
179
266
|
open_paths[testing_tile] = new_path
|
180
267
|
end
|
@@ -198,6 +285,11 @@ class Entity < WorldObject
|
|
198
285
|
closed_tiles = Set.new # Tiles we've already dealt with.
|
199
286
|
open_paths = { tile => Paths::Start.new(tile, destination_tile) } # Paths to check { tile => path_to_tile }.
|
200
287
|
|
288
|
+
destination_object = destination_tile.object
|
289
|
+
destination_is_enemy = (destination_object and destination_object.is_a? Entity and destination_object.enemy?(self))
|
290
|
+
|
291
|
+
melee_cost = has_ability?(:melee) ? ability(:melee).action_cost : Float::INFINITY
|
292
|
+
|
201
293
|
while open_paths.any?
|
202
294
|
# Check the (expected) shortest path and move it to closed, since we have considered it.
|
203
295
|
path = open_paths.each_value.min_by(&:cost)
|
@@ -218,15 +310,18 @@ class Entity < WorldObject
|
|
218
310
|
new_path = nil
|
219
311
|
|
220
312
|
object = testing_tile.object
|
221
|
-
if
|
313
|
+
if testing_tile.zoc?(faction) and not (testing_tile == destination_tile or destination_is_enemy)
|
314
|
+
# Avoid tiles that have zoc, unless at the end of the path. You have to MANUALLY enter.
|
315
|
+
next
|
316
|
+
elsif object and object.is_a?(Objects::Entity) and enemy?(object)
|
222
317
|
# Ensure that the current tile is somewhere we could launch an attack from and we could actually perform it.
|
223
|
-
if (current_tile.empty? or current_tile == tile) and ap >=
|
318
|
+
if (current_tile.empty? or current_tile == tile) and ap >= melee_cost
|
224
319
|
new_path = Paths::Melee.new(path, testing_tile)
|
225
320
|
else
|
226
321
|
next
|
227
322
|
end
|
228
323
|
elsif testing_tile.passable?(self)
|
229
|
-
if
|
324
|
+
if object.nil? or object.passable?(self)
|
230
325
|
new_path = Paths::Move.new(path, testing_tile, wall.movement_cost)
|
231
326
|
else
|
232
327
|
next
|
@@ -234,7 +329,8 @@ class Entity < WorldObject
|
|
234
329
|
end
|
235
330
|
|
236
331
|
# If the path is shorter than one we've already calculated, then replace it. Otherwise just store it.
|
237
|
-
|
332
|
+
old_path = open_paths[testing_tile]
|
333
|
+
if old_path
|
238
334
|
if new_path.move_distance < old_path.move_distance
|
239
335
|
open_paths[testing_tile] = new_path
|
240
336
|
end
|
@@ -247,28 +343,111 @@ class Entity < WorldObject
|
|
247
343
|
Paths::Inaccessible.new(destination_tile) # Failed to connect at all.
|
248
344
|
end
|
249
345
|
|
250
|
-
|
346
|
+
def update
|
347
|
+
super
|
348
|
+
|
349
|
+
unless @queued_activities.empty?
|
350
|
+
@queued_activities.first.resume if @queued_activities.first.alive?
|
351
|
+
unless @queued_activities.first.alive?
|
352
|
+
@queued_activities.shift
|
353
|
+
publish :changed if @queued_activities.empty?
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
def add_activity(&action)
|
359
|
+
@queued_activities << Fiber.new(&action)
|
360
|
+
publish :changed if @queued_activities.size == 1 # Means busy? changed from false to true.
|
361
|
+
end
|
362
|
+
|
363
|
+
def prepend_activity(&action)
|
364
|
+
@queued_activities.unshift Fiber.new(&action)
|
365
|
+
publish :changed if @queued_activities.size == 1 # Means busy? changed from false to true.
|
366
|
+
end
|
367
|
+
|
368
|
+
def clear_activities
|
369
|
+
has_activities = @queued_activities.any?
|
370
|
+
@queued_activities.clear
|
371
|
+
publish :changed if has_activities # Means busy? changed from true to false.
|
372
|
+
end
|
373
|
+
|
374
|
+
def busy?
|
375
|
+
@queued_activities.any?
|
376
|
+
end
|
377
|
+
|
378
|
+
# @overload delay(duration)
|
379
|
+
# Wait for duration (Called from an activity ONLY!)
|
380
|
+
# @param duration [Number]
|
381
|
+
#
|
382
|
+
# @overload delay
|
383
|
+
# Wait until next frame (Called from an activity ONLY!)
|
384
|
+
def delay(duration = 0)
|
385
|
+
raise if duration < 0
|
386
|
+
|
387
|
+
if duration == 0
|
388
|
+
Fiber.yield
|
389
|
+
else
|
390
|
+
finish = Time.now + duration
|
391
|
+
Fiber.yield until Time.now >= finish
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# @param target [Tile, Objects::WorldObject]
|
396
|
+
def face(target)
|
397
|
+
change_in_x = target.x - x
|
398
|
+
self.factor_x = change_in_x > 0 ? 1 : -1
|
399
|
+
end
|
400
|
+
|
401
|
+
# Actually perform movement (called from GameAction::Ability).
|
251
402
|
def move(tiles, movement_cost)
|
252
403
|
raise "Not enough movement points (#{self} tried to move #{movement_cost} with #{@movement_points} left #{tiles} )" unless movement_cost <= @movement_points
|
253
404
|
|
254
405
|
tiles = tiles.map {|pos| @map.tile_at_grid *pos } unless tiles.first.is_a? Tile
|
255
406
|
|
256
|
-
destination = tiles.last
|
257
407
|
@movement_points -= movement_cost
|
258
408
|
|
259
|
-
|
409
|
+
add_activity do
|
410
|
+
tiles.each_cons(2) do |from, to|
|
411
|
+
face to
|
260
412
|
|
261
|
-
|
262
|
-
|
413
|
+
delay 0.1
|
414
|
+
|
415
|
+
# TODO: this will be triggered _every_ time you move, even when redoing is done!
|
416
|
+
trigger_zoc_melee from
|
417
|
+
break unless alive?
|
418
|
+
|
419
|
+
# Skip through a tile if we are moving through something else!
|
420
|
+
if to.object
|
421
|
+
self.z = 20
|
422
|
+
self.x, self.y = to.x, to.y
|
423
|
+
else
|
424
|
+
self.tile = to
|
425
|
+
self.z = 0
|
426
|
+
end
|
263
427
|
|
264
|
-
|
428
|
+
# TODO: this will be triggered _every_ time you move, even when redoing is done!
|
429
|
+
trigger_zoc_melee to
|
430
|
+
break unless alive?
|
431
|
+
end
|
432
|
+
end
|
433
|
+
|
434
|
+
nil
|
265
435
|
end
|
266
436
|
|
267
437
|
# TODO: Need to think of the best way to trigger this. It should only happen once, when you actually "first" move.
|
268
|
-
def
|
269
|
-
|
438
|
+
def trigger_zoc_melee(tile)
|
439
|
+
entities = tile.entities_exerting_zoc(self)
|
440
|
+
enemies = entities.find_all {|e| e.enemy?(self) and e.has_ability?(:melee) and e.use_ability?(:melee) }
|
270
441
|
enemies.each do |enemy|
|
271
|
-
|
442
|
+
break unless alive? and enemy.alive?
|
443
|
+
|
444
|
+
parent.publish :game_info, "#{enemy.name} got an attack of opportunity!"
|
445
|
+
|
446
|
+
enemy.use_ability :melee, self
|
447
|
+
|
448
|
+
prepend_activity do
|
449
|
+
delay while enemy.busy?
|
450
|
+
end
|
272
451
|
end
|
273
452
|
end
|
274
453
|
|
@@ -339,9 +518,19 @@ class Entity < WorldObject
|
|
339
518
|
nil # Didn't hit anything.
|
340
519
|
end
|
341
520
|
|
521
|
+
def use_ability(name, *args)
|
522
|
+
raise "#{self} does not have ability: #{name.inspect}" unless has_ability? name
|
523
|
+
map.actions.do :ability, ability(name).action_data(*args)
|
524
|
+
publish :changed
|
525
|
+
end
|
526
|
+
|
527
|
+
def use_ability?(name)
|
528
|
+
alive? and has_ability?(name) and ap >= ability(name).action_cost
|
529
|
+
end
|
530
|
+
|
342
531
|
def to_json(*a)
|
343
532
|
data = {
|
344
|
-
class
|
533
|
+
:class => CLASS,
|
345
534
|
type: @type,
|
346
535
|
id: id,
|
347
536
|
health: @health,
|
@@ -356,4 +545,4 @@ class Entity < WorldObject
|
|
356
545
|
end
|
357
546
|
end
|
358
547
|
end
|
359
|
-
end
|
548
|
+
end
|
@@ -12,11 +12,13 @@ class Static < WorldObject
|
|
12
12
|
def inactive?; true; end
|
13
13
|
|
14
14
|
def to_s; "<#{self.class.name}/#{@type}##{id} #{tile ? grid_position : "[off-map]"}>"; end
|
15
|
-
def name; @type.split("_").map(&:capitalize).join(" "); end
|
15
|
+
def name; @type.to_s.split("_").map(&:capitalize).join(" "); end
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
class << self
|
18
|
+
def config; @config ||= YAML.load_file(File.expand_path("config/map/objects.yml", EXTRACT_PATH)); end
|
19
|
+
def types; config.keys; end
|
20
|
+
def sprites; @sprites ||= SpriteSheet.new("objects.png", 64 + 2, 64 + 2, 8); end
|
21
|
+
end
|
20
22
|
|
21
23
|
def initialize(map, data)
|
22
24
|
@type = data[:type]
|
@@ -36,7 +38,7 @@ class Static < WorldObject
|
|
36
38
|
|
37
39
|
def to_json(*a)
|
38
40
|
{
|
39
|
-
class
|
41
|
+
:class => CLASS,
|
40
42
|
type: @type,
|
41
43
|
id: id,
|
42
44
|
tile: grid_position,
|
@@ -21,11 +21,13 @@ class Vehicle < WorldObject
|
|
21
21
|
def inactive?; true; end
|
22
22
|
|
23
23
|
def to_s; "<#{self.class.name}/#{@type}##{id} #{tile ? grid_position : "[off-map]"}>"; end
|
24
|
-
def name; @type.split("_").map(&:capitalize).join(" "); end
|
24
|
+
def name; @type.to_s.split("_").map(&:capitalize).join(" "); end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
class << self
|
27
|
+
def config; @config ||= YAML.load_file(File.expand_path("config/map/vehicles.yml", EXTRACT_PATH)); end
|
28
|
+
def types; config.keys; end
|
29
|
+
def sprites; @sprites ||= SpriteSheet.new("vehicles.png", (128 * 2) + 2, (128 * 2) + 2, 3); end
|
30
|
+
end
|
29
31
|
|
30
32
|
def fills_tile_on_minimap?; true; end
|
31
33
|
|
@@ -89,7 +91,7 @@ class Vehicle < WorldObject
|
|
89
91
|
|
90
92
|
def to_json(*a)
|
91
93
|
{
|
92
|
-
class
|
94
|
+
:class => CLASS,
|
93
95
|
type: @type,
|
94
96
|
id: id,
|
95
97
|
tile: grid_position,
|