sc2ai 0.0.0.pre → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/data.json +1 -0
- data/data/data_readable.json +22842 -0
- data/data/sc2ai/protocol/common.proto +59 -0
- data/data/sc2ai/protocol/data.proto +120 -0
- data/data/sc2ai/protocol/debug.proto +127 -0
- data/data/sc2ai/protocol/error.proto +221 -0
- data/data/sc2ai/protocol/query.proto +55 -0
- data/data/sc2ai/protocol/raw.proto +202 -0
- data/data/sc2ai/protocol/sc2api.proto +718 -0
- data/data/sc2ai/protocol/score.proto +108 -0
- data/data/sc2ai/protocol/spatial.proto +115 -0
- data/data/sc2ai/protocol/ui.proto +145 -0
- data/data/setup/setup.SC2Map +0 -0
- data/data/setup/setup.SC2Replay +0 -0
- data/data/stableid.json +35730 -0
- data/data/versions.json +554 -0
- data/exe/sc2ai +35 -0
- data/lib/docker_build/Dockerfile.ruby +74 -0
- data/lib/docker_build/docker-compose-base-image.yml +10 -0
- data/lib/docker_build/docker-compose-ladderzip.yml +9 -0
- data/lib/sc2ai/api/ability_id.rb +1644 -0
- data/lib/sc2ai/api/buff_id.rb +306 -0
- data/lib/sc2ai/api/data.rb +101 -0
- data/lib/sc2ai/api/effect_id.rb +20 -0
- data/lib/sc2ai/api/tech_tree.rb +83 -0
- data/lib/sc2ai/api/tech_tree_data.rb +2338 -0
- data/lib/sc2ai/api/unit_type_id.rb +2022 -0
- data/lib/sc2ai/api/upgrade_id.rb +310 -0
- data/lib/sc2ai/cli/cli.rb +175 -0
- data/lib/sc2ai/cli/ladderzip.rb +154 -0
- data/lib/sc2ai/cli/new.rb +88 -0
- data/lib/sc2ai/configuration.rb +145 -0
- data/lib/sc2ai/connection/connection_listener.rb +30 -0
- data/lib/sc2ai/connection/requests.rb +417 -0
- data/lib/sc2ai/connection/status_listener.rb +15 -0
- data/lib/sc2ai/connection.rb +146 -0
- data/lib/sc2ai/local_play/client/configurable_options.rb +115 -0
- data/lib/sc2ai/local_play/client.rb +159 -0
- data/lib/sc2ai/local_play/client_manager.rb +70 -0
- data/lib/sc2ai/local_play/map_file.rb +48 -0
- data/lib/sc2ai/local_play/match.rb +184 -0
- data/lib/sc2ai/overrides/array.rb +14 -0
- data/lib/sc2ai/overrides/async/process/child.rb +31 -0
- data/lib/sc2ai/overrides/kernel.rb +33 -0
- data/lib/sc2ai/paths.rb +294 -0
- data/lib/sc2ai/player/actions.rb +386 -0
- data/lib/sc2ai/player/debug.rb +224 -0
- data/lib/sc2ai/player/game_state.rb +131 -0
- data/lib/sc2ai/player/geometry.rb +766 -0
- data/lib/sc2ai/player/previous_state.rb +49 -0
- data/lib/sc2ai/player/units.rb +337 -0
- data/lib/sc2ai/player.rb +661 -0
- data/lib/sc2ai/ports.rb +152 -0
- data/lib/sc2ai/protocol/_meta_documentation.rb +39 -0
- data/lib/sc2ai/protocol/common_pb.rb +43 -0
- data/lib/sc2ai/protocol/data_pb.rb +47 -0
- data/lib/sc2ai/protocol/debug_pb.rb +56 -0
- data/lib/sc2ai/protocol/error_pb.rb +36 -0
- data/lib/sc2ai/protocol/extensions/color.rb +20 -0
- data/lib/sc2ai/protocol/extensions/point.rb +23 -0
- data/lib/sc2ai/protocol/extensions/point_2_d.rb +26 -0
- data/lib/sc2ai/protocol/extensions/position.rb +202 -0
- data/lib/sc2ai/protocol/extensions/power_source.rb +19 -0
- data/lib/sc2ai/protocol/extensions/unit.rb +489 -0
- data/lib/sc2ai/protocol/query_pb.rb +47 -0
- data/lib/sc2ai/protocol/raw_pb.rb +57 -0
- data/lib/sc2ai/protocol/sc2api_pb.rb +130 -0
- data/lib/sc2ai/protocol/score_pb.rb +40 -0
- data/lib/sc2ai/protocol/spatial_pb.rb +48 -0
- data/lib/sc2ai/protocol/ui_pb.rb +56 -0
- data/lib/sc2ai/unit_group/action_ext.rb +74 -0
- data/lib/sc2ai/unit_group/filter_ext.rb +379 -0
- data/lib/sc2ai/unit_group.rb +277 -0
- data/lib/sc2ai/version.rb +2 -1
- data/lib/sc2ai.rb +93 -2
- data/lib/templates/ladderzip/bin/ladder.tt +23 -0
- data/lib/templates/new/.ladderignore +20 -0
- data/lib/templates/new/Gemfile.tt +7 -0
- data/lib/templates/new/api/common.proto +59 -0
- data/lib/templates/new/api/data.proto +120 -0
- data/lib/templates/new/api/debug.proto +127 -0
- data/lib/templates/new/api/error.proto +221 -0
- data/lib/templates/new/api/query.proto +55 -0
- data/lib/templates/new/api/raw.proto +202 -0
- data/lib/templates/new/api/sc2api.proto +718 -0
- data/lib/templates/new/api/score.proto +108 -0
- data/lib/templates/new/api/spatial.proto +115 -0
- data/lib/templates/new/api/ui.proto +145 -0
- data/lib/templates/new/boot.rb.tt +6 -0
- data/lib/templates/new/my_bot.rb.tt +23 -0
- data/lib/templates/new/run_example_match.rb.tt +14 -0
- data/sc2ai.gemspec +80 -0
- metadata +344 -13
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
class Player
|
5
|
+
# Container for the previous game state, based on current bot state
|
6
|
+
# Keep to one instance, but with variables to prevent large memory leaks
|
7
|
+
# @see Sc2::Player::GameState for current state
|
8
|
+
class PreviousState
|
9
|
+
include GameState
|
10
|
+
include Units
|
11
|
+
|
12
|
+
# Sets the previous state of the bot using the last frame
|
13
|
+
# @param bot [Sc2::Player::Bot]
|
14
|
+
def reset(bot)
|
15
|
+
before_reset(bot)
|
16
|
+
@all_units = bot.all_units
|
17
|
+
@units = bot.units
|
18
|
+
@structures = bot.structures
|
19
|
+
@neutral = bot.neutral
|
20
|
+
@effects = bot.effects
|
21
|
+
@power_sources = bot.power_sources
|
22
|
+
@radar_rings = bot.radar_rings
|
23
|
+
|
24
|
+
@status = bot.status
|
25
|
+
@game_info = bot.game_info
|
26
|
+
@observation = bot.observation
|
27
|
+
|
28
|
+
@spent_minerals = bot.spent_minerals
|
29
|
+
@spent_vespene = bot.spent_vespene
|
30
|
+
@spent_supply = bot.spent_supply
|
31
|
+
# Skipping unnecessary bloat: event_*, chats_received, ...
|
32
|
+
|
33
|
+
after_reset(bot)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Override to modify the previous frame before being set to current
|
37
|
+
# @param bot [Sc2::Player::Bot]
|
38
|
+
def before_reset(bot)
|
39
|
+
# pp "### before_reset"
|
40
|
+
end
|
41
|
+
|
42
|
+
# Override to modify previous frame after reset is complete
|
43
|
+
# @param bot [Sc2::Player::Bot]
|
44
|
+
def after_reset(bot)
|
45
|
+
# pp "### after_reset"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,337 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Sc2
|
4
|
+
class Player
|
5
|
+
# Helper methods for working with units
|
6
|
+
module Units
|
7
|
+
# @!attribute all_units
|
8
|
+
# @return [Sc2::UnitGroup]
|
9
|
+
attr_accessor :all_units
|
10
|
+
|
11
|
+
# A full list of all your units (non-structure)
|
12
|
+
# @!attribute units
|
13
|
+
# @return [Sc2::UnitGroup] a group of units
|
14
|
+
attr_accessor :units
|
15
|
+
|
16
|
+
# A full list of all your structures (non-units)
|
17
|
+
# @!attribute units
|
18
|
+
# @return [Sc2::UnitGroup] a group of units
|
19
|
+
attr_accessor :structures
|
20
|
+
|
21
|
+
# All units with alliance :Neutral
|
22
|
+
# @!attribute neutral
|
23
|
+
# @return [Sc2::UnitGroup] a group of neutral units
|
24
|
+
attr_accessor :neutral
|
25
|
+
|
26
|
+
# An array of effects such as Scan, Storm, Corrosive Bile, Lurker Spike, etc.
|
27
|
+
# They typically have a position on the map and may be persistent or temporary.
|
28
|
+
# Shorthand for observation.raw_data.effects
|
29
|
+
# @!attribute effects
|
30
|
+
# @return [Sc2::UnitGroup] a group of neutral units
|
31
|
+
attr_accessor :effects # not a unit
|
32
|
+
|
33
|
+
# An array of Protoss power sources, which have a point, radius and unit tag
|
34
|
+
# @!attribute power_sources
|
35
|
+
# @return [Array<Api::PowerSource>] an array of power sources
|
36
|
+
attr_accessor :power_sources # not a unit but has a tag
|
37
|
+
|
38
|
+
# An array of Sensor tower rings as per minimap. It has a `pos` and a `radius`
|
39
|
+
# @!attribute power_sources
|
40
|
+
# @return [Array<Api::RadarRing>] an array of power sources
|
41
|
+
attr_accessor :radar_rings # not a unit but has a tag
|
42
|
+
|
43
|
+
# @private
|
44
|
+
# @!attribute all_seen_unit_tags
|
45
|
+
# Privately keep track of all seen Unit tags (excl structures) in order to detect new created units
|
46
|
+
attr_accessor :_all_seen_unit_tags
|
47
|
+
|
48
|
+
# Event-driven unit groups ---
|
49
|
+
|
50
|
+
# Units created since last frame (visible only, units not structures)
|
51
|
+
# Read this on_step. Alternative to callback on_unit_created
|
52
|
+
# Note: Morphed units should watch #event_units_type_changed
|
53
|
+
# @!attribute event_units_created
|
54
|
+
# @return [Sc2::UnitGroup] group of created units
|
55
|
+
attr_accessor :event_units_created
|
56
|
+
|
57
|
+
# Units which had their type changed since last frame
|
58
|
+
# Read this on_step. Alternative to callback on_unit_type_changed
|
59
|
+
# @!attribute event_units_type_changed
|
60
|
+
# @return [Sc2::UnitGroup] group effected
|
61
|
+
attr_accessor :event_units_type_changed
|
62
|
+
|
63
|
+
# Structures seen since last frame with building not completed (< 1.0)
|
64
|
+
# Read this on_step. Alternative to callback on_structure_started
|
65
|
+
# @!attribute event_structures_started
|
66
|
+
# @return [Sc2::UnitGroup] a group of structures started
|
67
|
+
attr_accessor :event_structures_started
|
68
|
+
|
69
|
+
# Structures which had their building completed (==1.0) since last frame
|
70
|
+
# Read this on_step. Alternative to callback on_structure_completed
|
71
|
+
# @!attribute event_structures_completed
|
72
|
+
# @return [Sc2::UnitGroup] a group of structures started
|
73
|
+
attr_accessor :event_structures_completed
|
74
|
+
|
75
|
+
# Units and Structures which had their health/shields reduced since last frame
|
76
|
+
# Read this on_step. Alternative to callback on_unit_damaged
|
77
|
+
# @!attribute event_units_damaged
|
78
|
+
# @return [Sc2::UnitGroup] group of Units and Structures effected
|
79
|
+
attr_accessor :event_units_damaged
|
80
|
+
|
81
|
+
# Units destroyed since last frame (known units only, i.e. not projectiles)
|
82
|
+
# Read this on_step. Alternative to callback on_unit_destroyed
|
83
|
+
# @!attribute event_units_destroyed
|
84
|
+
# @return [Sc2::UnitGroup] group of dead units
|
85
|
+
attr_accessor :event_units_destroyed
|
86
|
+
|
87
|
+
# TODO: Unit buff disabled, because it calls back too often (mineral in hand). Put back if useful
|
88
|
+
# @private
|
89
|
+
# Units (Unit/Structure) on which a new buff_ids appeared this frame
|
90
|
+
# See which buffs via: unit.buff_ids - unit.previous.buff_ids
|
91
|
+
# Read this on_step. Alternative to callback on_unit_buffed
|
92
|
+
# @!attribute event_units_destroyed
|
93
|
+
# attr_accessor :event_units_buffed
|
94
|
+
|
95
|
+
# Returns static [Api::UnitTypeData] for a unit
|
96
|
+
# @param unit [Integer,Api::Unit] Api::UnitTypeId or Api::Unit
|
97
|
+
# @return [Api::UnitTypeData]
|
98
|
+
def unit_data(unit)
|
99
|
+
id = unit.is_a?(Integer) ? unit : unit.unit_type
|
100
|
+
data.units[id]
|
101
|
+
end
|
102
|
+
|
103
|
+
# Returns static [Api::AbilityData] for an ability
|
104
|
+
# @param ability_id [Integer] Api::AbilityId::*
|
105
|
+
# @return [Api::AbilityData]
|
106
|
+
def ability_data(ability_id)
|
107
|
+
data.abilities[ability_id]
|
108
|
+
end
|
109
|
+
|
110
|
+
# Checks unit data for an attribute value
|
111
|
+
# @param unit [Integer,Api::Unit] Api::UnitTypeId or Api::Unit
|
112
|
+
# @param attribute [Symbol] Api::Attribute, i.e. Api::Attribute::Mechanical or :Mechanical
|
113
|
+
# @return [Boolean] whether unit has attribute
|
114
|
+
# @example
|
115
|
+
# unit_has_attribute?(Api::UnitTypeId::SCV, Api::Attribute::Mechanical)
|
116
|
+
# unit_has_attribute?(units.workers.first, :Mechanical)
|
117
|
+
# unit_has_attribute?(Api::UnitTypeId::SCV, :Mechanical)
|
118
|
+
def unit_has_attribute?(unit, attribute)
|
119
|
+
unit_data(unit).attributes.include? attribute
|
120
|
+
end
|
121
|
+
|
122
|
+
# Creates a unit group from all_units with matching tag
|
123
|
+
# @param tags [Array<Integer>] array of unit tags
|
124
|
+
# @return [Sc2::UnitGroup]
|
125
|
+
def unit_group_from_tags(tags)
|
126
|
+
return unless tags.is_a? Array
|
127
|
+
|
128
|
+
ug = UnitGroup.new
|
129
|
+
tags.each do |tag|
|
130
|
+
ug.add(@all_units[tag])
|
131
|
+
end
|
132
|
+
ug
|
133
|
+
end
|
134
|
+
|
135
|
+
# Geo/Map/Macro ------
|
136
|
+
|
137
|
+
# @private
|
138
|
+
# Sums the cost (mineral/vespene/supply) of unit type used for internal spend trackers
|
139
|
+
# This is called internally when building/morphing/training
|
140
|
+
# @return [void]
|
141
|
+
def subtract_cost(unit_type_id)
|
142
|
+
unit_type_data = unit_data(unit_type_id)
|
143
|
+
|
144
|
+
# food_required is a float. ensure half units are counted as full
|
145
|
+
# TODO: Extend UnitTypeData message. def food_required = unit_id == Api::UnitTypeId::ZERGLING ? 1 : send("method_missing", :food_required)
|
146
|
+
supply_cost = unit_type_data.food_required
|
147
|
+
supply_cost = 1 if unit_type_id == Api::UnitTypeId::ZERGLING
|
148
|
+
|
149
|
+
@spent_minerals += unit_type_data.mineral_cost
|
150
|
+
@spent_vespene += unit_type_data.vespene_cost
|
151
|
+
@spent_supply += supply_cost
|
152
|
+
end
|
153
|
+
|
154
|
+
# Checks whether you have the resources to construct quantity of unit type
|
155
|
+
def can_afford?(unit_type_id:, quantity: 1)
|
156
|
+
unit_type_data = unit_data(unit_type_id)
|
157
|
+
return false if unit_type_data.nil?
|
158
|
+
|
159
|
+
mineral_cost = unit_type_data.mineral_cost * quantity
|
160
|
+
if common.minerals - spent_minerals < mineral_cost
|
161
|
+
return false # not enough minerals
|
162
|
+
end
|
163
|
+
|
164
|
+
vespene_cost = unit_type_data.vespene_cost * quantity
|
165
|
+
if common.vespene - spent_vespene < vespene_cost
|
166
|
+
return false # you require more vespene gas
|
167
|
+
end
|
168
|
+
|
169
|
+
supply_cost = unit_type_data.food_required
|
170
|
+
supply_cost = 1 if unit_type_id == Api::UnitTypeId::ZERGLING
|
171
|
+
supply_cost *= quantity
|
172
|
+
|
173
|
+
free_supply = common.food_cap - common.food_used
|
174
|
+
if free_supply - spent_supply < supply_cost
|
175
|
+
return false # you must construct additional pylons
|
176
|
+
end
|
177
|
+
|
178
|
+
true
|
179
|
+
end
|
180
|
+
|
181
|
+
private
|
182
|
+
|
183
|
+
# @private
|
184
|
+
# Divides raw data units into various attributes on every step
|
185
|
+
# Note, this needs to be fast.
|
186
|
+
# @param observation [Api::Observation]
|
187
|
+
def parse_observation_units(observation)
|
188
|
+
@all_units = UnitGroup.new(observation.raw_data.units)
|
189
|
+
# Clear previous units and prep for categorization
|
190
|
+
@units = UnitGroup.new
|
191
|
+
@structures = UnitGroup.new
|
192
|
+
@enemy.units = UnitGroup.new
|
193
|
+
@enemy.structures = UnitGroup.new
|
194
|
+
@neutral = UnitGroup.new
|
195
|
+
@effects = observation.raw_data.effects # not a unit
|
196
|
+
@power_sources = observation.raw_data.player.power_sources # not a unit
|
197
|
+
@radar_rings = observation.raw_data.radar
|
198
|
+
@blips = UnitGroup.new
|
199
|
+
|
200
|
+
# Unit tag tracking
|
201
|
+
@_all_seen_unit_tags ||= Set.new(@units.tags)
|
202
|
+
|
203
|
+
# Event-driven unit groups as callback alternatives
|
204
|
+
@event_units_created = UnitGroup.new
|
205
|
+
@event_structures_started = UnitGroup.new
|
206
|
+
@event_structures_completed = UnitGroup.new
|
207
|
+
@event_units_type_changed = UnitGroup.new
|
208
|
+
@event_units_damaged = UnitGroup.new
|
209
|
+
# @event_units_buffed = UnitGroup.new
|
210
|
+
|
211
|
+
# Categorization of self/enemy, structure/unit ---
|
212
|
+
own_alliance = self.own_alliance
|
213
|
+
enemy_alliance = self.enemy_alliance
|
214
|
+
|
215
|
+
# To prevent several loops over all units per frame, use this single loop for all checks
|
216
|
+
all_unit_size = observation.raw_data.units.size
|
217
|
+
i = 0
|
218
|
+
while i < all_unit_size
|
219
|
+
unit = observation.raw_data.units[i]
|
220
|
+
tag = unit.tag
|
221
|
+
tag = unit.tag = unit.hash if tag.zero?
|
222
|
+
# Reluctantly assigning player to unit
|
223
|
+
unit.bot = self
|
224
|
+
|
225
|
+
# Categorize own units/structures, enemy units/structures, neutral
|
226
|
+
if unit.is_blip
|
227
|
+
@blips[tag] = unit
|
228
|
+
elsif unit.alliance == own_alliance || unit.alliance == enemy_alliance
|
229
|
+
if unit.alliance == own_alliance
|
230
|
+
structure_collection = @structures
|
231
|
+
unit_collection = @units
|
232
|
+
else
|
233
|
+
structure_collection = @enemy.structures
|
234
|
+
unit_collection = @enemy.units
|
235
|
+
end
|
236
|
+
|
237
|
+
unit_data = unit_data(unit.unit_type)
|
238
|
+
if unit_data.attributes.include? :Structure
|
239
|
+
structure_collection[tag] = unit
|
240
|
+
else
|
241
|
+
unit_collection[tag] = unit
|
242
|
+
end
|
243
|
+
else
|
244
|
+
@neutral[tag] = unit
|
245
|
+
end
|
246
|
+
|
247
|
+
# Dont parse callbacks on first loop or for neutral units
|
248
|
+
if !game_loop.zero? &&
|
249
|
+
unit.alliance != :Neutral &&
|
250
|
+
unit.display_type != :Placeholder &&
|
251
|
+
unit.is_blip == false
|
252
|
+
|
253
|
+
previous_unit = @previous.all_units[unit.tag]
|
254
|
+
|
255
|
+
# Unit created/changed/damage modifiers ---
|
256
|
+
if previous_unit.nil?
|
257
|
+
issue_new_unit_callbacks(unit)
|
258
|
+
else
|
259
|
+
issue_existing_unit_callbacks(unit, previous_unit)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Allow user to fiddle with unit
|
264
|
+
on_parse_observation_unit(unit)
|
265
|
+
|
266
|
+
i += 1
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# @private
|
271
|
+
# Returns alliance based on whether you are a player or an enemy
|
272
|
+
# @return [:Symbol] :Self or :Enemy from Api::Alliance
|
273
|
+
def own_alliance
|
274
|
+
if is_a? Sc2::Player::Enemy
|
275
|
+
Api::Alliance.lookup(Api::Alliance::Enemy)
|
276
|
+
else
|
277
|
+
Api::Alliance.lookup(Api::Alliance::Self)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# @private
|
282
|
+
# Returns enemy alliance based on whether you are a player or an enemy
|
283
|
+
# @return [:Symbol] :Self or :Enemy from Api::Alliance
|
284
|
+
def enemy_alliance
|
285
|
+
if is_a? Sc2::Player::Enemy
|
286
|
+
Api::Alliance.lookup(Api::Alliance::Self)
|
287
|
+
else
|
288
|
+
Api::Alliance.lookup(Api::Alliance::Enemy)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
# @private
|
293
|
+
# Issues units/structure callbacks for units which are new
|
294
|
+
def issue_new_unit_callbacks(unit)
|
295
|
+
return if @_all_seen_unit_tags.include?(unit.tag)
|
296
|
+
|
297
|
+
if unit.is_structure?
|
298
|
+
if unit.build_progress < 1
|
299
|
+
@event_structures_started.add(unit)
|
300
|
+
on_structure_started(unit)
|
301
|
+
else
|
302
|
+
@event_structures_completed.add(unit)
|
303
|
+
on_structure_completed(unit)
|
304
|
+
end
|
305
|
+
else
|
306
|
+
@event_units_created.add(unit)
|
307
|
+
on_unit_created(unit)
|
308
|
+
end
|
309
|
+
@_all_seen_unit_tags.add(unit.tag)
|
310
|
+
end
|
311
|
+
|
312
|
+
# @private
|
313
|
+
# Issues callbacks for units over time, such as damaged or type changed
|
314
|
+
def issue_existing_unit_callbacks(unit, previous_unit)
|
315
|
+
# Check if a unit type has changed
|
316
|
+
if unit.unit_type != previous_unit.unit_type
|
317
|
+
@event_units_type_changed.add(unit)
|
318
|
+
on_unit_type_changed(unit, previous_unit.unit_type)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Check if a unit type has changed
|
322
|
+
if unit.health < previous_unit.health || unit.shield < previous_unit.shield
|
323
|
+
damage_amount = previous_unit.health - unit.health + previous_unit.shield - unit.shield
|
324
|
+
@event_units_damaged.add(unit)
|
325
|
+
on_unit_damaged(unit, damage_amount)
|
326
|
+
end
|
327
|
+
|
328
|
+
if unit.is_structure?
|
329
|
+
if previous_unit.build_progress < 1 && unit.build_progress == 1
|
330
|
+
@event_structures_completed.add(unit)
|
331
|
+
on_structure_completed(unit)
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|