sc2ai 0.0.0.pre → 0.0.2
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.
- 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
|