sc2ai 0.0.6 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -299,14 +299,14 @@ module Api
299
299
  DIGGINGCLAWS = 293
300
300
  CARRIERCARRIERCAPACITY = 294
301
301
  CARRIERLEASHRANGEUPGRADE = 295
302
- TEMPESTGROUNDATTACKUPGRADE = 296
303
- MICROBIALSHROUD = 297
304
- SUNDERINGIMPACT = 298
305
- AMPLIFIEDSHIELDING = 299
306
- PSIONICAMPLIFIERS = 300
307
- SECRETEDCOATING = 301
308
- ENHANCEDSHOCKWAVES = 302
309
- HURRICANETHRUSTERS = 303
310
- INTERFERENCEMATRIX = 304
302
+ HURRICANETHRUSTERS = 296
303
+ TEMPESTGROUNDATTACKUPGRADE = 297
304
+ MICROBIALSHROUD = 298
305
+ INTERFERENCEMATRIX = 299
306
+ SUNDERINGIMPACT = 300
307
+ AMPLIFIEDSHIELDING = 301
308
+ PSIONICAMPLIFIERS = 302
309
+ SECRETEDCOATING = 303
310
+ ENHANCEDSHOCKWAVES = 304
311
311
  end
312
312
  end
@@ -279,10 +279,11 @@ module Sc2
279
279
  # Advances the game simulation by step_count. Not used in realtime mode.
280
280
  # Only constant step size supported - subsequent requests use cache.
281
281
  def step(step_count = 1)
282
- @_cached_request_step ||= Api::Request.new(
282
+ @_cached_request_step ||= {}
283
+ @_cached_request_step[step_count] ||= Api::Request.new(
283
284
  step: Api::RequestStep.new(count: step_count)
284
285
  ).to_proto
285
- send_request_and_ignore(@_cached_request_step)
286
+ send_request_and_ignore(@_cached_request_step[step_count])
286
287
  end
287
288
 
288
289
  # Additional methods for inspecting game state. Synchronous and must wait on response
@@ -291,7 +292,7 @@ module Sc2
291
292
  # @param placements [Array<Api::RequestQueryBuildingPlacement>]
292
293
  # @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
293
294
  # @return [Api::ResponseQuery]
294
- def query(pathing: nil, abilities: nil, placements: nil, ignore_resource_requirements: true)
295
+ def query(pathing: nil, abilities: nil, placements: nil, ignore_resource_requirements: false)
295
296
  send_request_for query: Api::RequestQuery.new(
296
297
  pathing:,
297
298
  abilities:,
@@ -316,7 +317,7 @@ module Sc2
316
317
  # @param queries [Array<Api::RequestQueryAvailableAbilities>] one or more pathing queries
317
318
  # @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
318
319
  # @return [Array<Api::ResponseQueryAvailableAbilities>] one or more results depending on input size
319
- def query_abilities(queries, ignore_resource_requirements: true)
320
+ def query_abilities(queries, ignore_resource_requirements: false)
320
321
  arr_queries = queries.is_a?(Array) ? queries : [queries]
321
322
 
322
323
  response = send_request_for query: Api::RequestQuery.new(
@@ -330,7 +331,8 @@ module Sc2
330
331
  # @param unit_tags [Array<Integer>] an array of unit tags or a single tag
331
332
  # @param ignore_resource_requirements [Boolean] Ignores requirements like food, minerals and so on.
332
333
  # @return [Array<Api::ResponseQueryAvailableAbilities>] one or more results depending on input size
333
- def query_abilities_for_unit_tags(unit_tags, ignore_resource_requirements: true)
334
+ def query_abilities_for_unit_tags(unit_tags, ignore_resource_requirements: false)
335
+ return [] if unit_tags.nil?
334
336
  queries = []
335
337
  unit_tags = [unit_tags] unless unit_tags.is_a? Array
336
338
  unit_tags.each do |unit_tag|
@@ -345,13 +347,14 @@ module Sc2
345
347
  # and can just return an array of ability ids.
346
348
  # Note: Querying single units are expensive and should be batched with #query_abilities_for_unit_tags
347
349
  # @param unit [Api::Unit, Integer] a unit or a tag.
348
- def query_ability_ids_for_unit(unit, ignore_resource_requirements: true)
350
+ # @return [Array<Integer>] array of ability ids
351
+ def query_ability_ids_for_unit(unit, ignore_resource_requirements: false)
349
352
  tag = unit.is_a?(Api::Unit) ? unit.tag : unit
350
353
  result = query_abilities_for_unit_tags([tag], ignore_resource_requirements:)
351
354
  if result.nil?
352
355
  []
353
356
  else
354
- result.first.abilities
357
+ result.first.abilities.map(&:ability_id)
355
358
  end
356
359
  end
357
360
 
data/lib/sc2ai/data.rb ADDED
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "api/ability_id"
4
+ require_relative "api/unit_type_id"
5
+ require_relative "api/upgrade_id"
6
+ require_relative "api/buff_id"
7
+ require_relative "api/effect_id"
8
+ require_relative "api/tech_tree"
9
+
10
+ module Sc2
11
+ # Holds game data from tech tree and Api::ResponseData
12
+ # Called once on game start
13
+ class Data
14
+ # @!attribute abilities
15
+ # @return [Hash<Integer, Api::AbilityData]>] AbilitId => AbilityData
16
+ attr_accessor :abilities
17
+ # @!attribute units
18
+ # @return [Hash<Integer, Api::UnitTypeData>] UnitId => UnitData
19
+ attr_accessor :units
20
+ # @!attribute upgrades
21
+ # @return [Hash<Integer, Api::UpgradeData>] UpgradeId => UpgradeData
22
+ attr_accessor :upgrades
23
+ # @!attribute buffs
24
+ # Not particularly useful data. Just use BuffId directly
25
+ # @return [Hash<Integer, Api::UpgradeData>] BuffId => BuffData
26
+ attr_accessor :buffs
27
+ # @!attribute effects
28
+ # Not particularly useful data. Just use EffectId directly
29
+ # @return [Hash<Integer, Api::UpgradeData>] EffectId => EffectData
30
+ attr_accessor :effects
31
+
32
+ # @param data [Api::ResponseData]
33
+ def initialize(data)
34
+ return unless data
35
+
36
+ @abilities = abilities_from_proto(data.abilities)
37
+ @units = units_from_proto(data.units)
38
+ @upgrades = upgrades_from_proto(data.upgrades)
39
+ @buffs = buffs_from_proto(data.buffs)
40
+ @effects = effects_from_proto(data.effects)
41
+ end
42
+
43
+ private
44
+
45
+ # Indexes ability data by ability id
46
+ # @param abilities [Array<Api::AbilityData>]
47
+ # @return [Hash<Integer, Api::AbilityData] indexed data
48
+ def abilities_from_proto(abilities)
49
+ result = {}
50
+
51
+ ability_ids = Api::AbilityId.constants.map { |c| Api::AbilityId.const_get(c) }
52
+ abilities.each do |a|
53
+ next if a.ability_id.zero?
54
+ next if ability_ids.delete(a.ability_id).nil?
55
+
56
+ result[a.ability_id] = a
57
+ end
58
+ result
59
+ end
60
+
61
+ # Indexes unit data by id
62
+ # @param units [Array<Api::UnitTypeData>]
63
+ # @return [Hash<Integer, Api::UnitTypeData>] indexed data
64
+ def units_from_proto(units)
65
+ result = {}
66
+ units.each do |u|
67
+ next unless u.available
68
+
69
+ result[u.unit_id] = u
70
+ end
71
+ result
72
+ end
73
+
74
+ # Indexes upgrades data by id
75
+ # @param upgrades [Array<Api::UpgradeData>]
76
+ # @return [Hash<Integer, Api::UpgradeData] indexed data
77
+ def upgrades_from_proto(upgrades)
78
+ result = {}
79
+ upgrades.each do |u|
80
+ result[u.upgrade_id] = u
81
+ end
82
+ result
83
+ end
84
+
85
+ def effects_from_proto(effects)
86
+ result = {}
87
+ effects.each do |e|
88
+ result[e.effect_id] = e
89
+ end
90
+ result
91
+ end
92
+
93
+ def buffs_from_proto(buffs)
94
+ result = {}
95
+ buffs.each do |b|
96
+ result[b.buff_id] = b
97
+ end
98
+ result
99
+ end
100
+ end
101
+ end
@@ -23,8 +23,6 @@ module Sc2
23
23
  # end
24
24
  end
25
25
 
26
- # TODO: DEFINE REALTIME AS A PARAM
27
-
28
26
  # @!attribute players Sets the Player(s) for the match
29
27
  # @return [Array<Sc2::Player>] an array of assigned players (ai,bots,humans,observers)
30
28
  attr_accessor :players
@@ -66,7 +64,7 @@ module Sc2
66
64
  connect_players
67
65
  setup_player_hooks
68
66
 
69
- player_host.create_game(map:, players: @players)
67
+ player_host.create_game(map:, players: @players, realtime: player_host.realtime)
70
68
 
71
69
  api_players.each_with_index do |player, player_index|
72
70
  run_task.async do
@@ -45,9 +45,12 @@ module Sc2
45
45
 
46
46
  target_pos = nil
47
47
  target_unit_tag = nil
48
- if target.is_a? Api::Point2D
48
+ case target
49
+ when Api::Point2D
49
50
  target_pos = target
50
- elsif target.is_a? Api::Unit
51
+ when Api::Point
52
+ target_pos = target.to_p2d
53
+ when Api::Unit
51
54
  target_unit_tag = target.tag
52
55
  else
53
56
  target_unit_tag = target
@@ -65,7 +68,7 @@ module Sc2
65
68
 
66
69
  # Builds target unit type using units as source at optional target
67
70
  # @param units [Array<Integer>,Integer,Api::Unit] can be an Api::Unit, array of Api::Unit#tag or single tag
68
- # @param unit_type_id [Integer] Api::UnitTypeId the unit type which will do the creation
71
+ # @param unit_type_id [Integer] Api::UnitTypeId the unit type you wish to build
69
72
  # @param target [Api::Point2D, Integer, nil] is a unit tag or a Api::Point2D. Nil for addons/orbital
70
73
  # @param queue_command [Boolean] shift+command
71
74
  def build(units:, unit_type_id:, target: nil, queue_command: false)
@@ -79,9 +82,10 @@ module Sc2
79
82
 
80
83
  # Warps in unit type at target (location or pylon) with optional source units (warp gates)
81
84
  # When not specifying the specific warp gate(s), all warpgates will be used
82
- # @param unit_type_id [Integer] Api::UnitTypeId the unit type which will do the creation
85
+ # @param unit_type_id [Integer] Api::UnitTypeId the unit type you wish to build
83
86
  # @param queue_command [Boolean] shift+command
84
87
  # @param target [Api::Point2D, Integer] is a unit tag or a Api::Point2D
88
+ # @param units [Array<Integer>, Integer, Api::Unit]
85
89
  def warp(unit_type_id:, target:, queue_command:, units: nil)
86
90
  warp_ability = Api::TechTree.unit_type_creation_abilities(
87
91
  source: Api::UnitTypeId::WARPGATE,
@@ -133,7 +133,7 @@ module Sc2
133
133
  # Debug draws a sphere at position with a radius in color
134
134
  # @param point [Api::Point]
135
135
  # @param radius [Float] default one tile wide, 1.0
136
- # @param color [Api::Color] default white. min(r,b) is used for both r&b
136
+ # @param color [Api::Color] default white
137
137
  # @return [void]
138
138
  def debug_draw_sphere(point:, radius: 1.0, color: nil)
139
139
  queue_debug_command Api::DebugCommand.new(
@@ -151,14 +151,13 @@ module Sc2
151
151
 
152
152
  # Other Commands ---
153
153
 
154
- # Toggles cheat commands on/off (send only once to enable)
155
154
  # @param command [Integer] one of Api::DebugGameState::*
156
155
  # Possible values:
157
156
  # Api::DebugGameState::Show_map
158
157
  # Api::DebugGameState::Control_enemy
159
158
  # Api::DebugGameState::Food
160
159
  # Api::DebugGameState::Free
161
- # Api::DebugGameState::all_resources
160
+ # Api::DebugGameState::All_resources
162
161
  # Api::DebugGameState::God
163
162
  # Api::DebugGameState::Minerals
164
163
  # Api::DebugGameState::Gas
@@ -106,6 +106,34 @@ module Sc2
106
106
  @chats_received || []
107
107
  end
108
108
 
109
+ # @private
110
+ # @!attribute available_abilities_loop
111
+ # This is the last loop at which available_abilities was queried.
112
+ # Used to determine staleness.
113
+ # @return [Integer]
114
+ attr_accessor :available_abilities_loop
115
+ private :available_abilities_loop
116
+
117
+ # A Hash by unit tag, holding an array of available ability ids
118
+ # Synchronously calls RequestQueryAvailableAbilities and caches for this game loop.
119
+ # @return [Hash<Integer, Array<Integer>>] { unit_tag => [ability_id, ...], ... }
120
+ def available_abilities
121
+ # Save/check when last we refreshed abilities
122
+ if @available_abilities_loop != game_loop
123
+
124
+ # Query abilities for all our units + structure tags combined
125
+ abilities = api.query_abilities_for_unit_tags(units.tags + structures.tags, ignore_resource_requirements: false)
126
+ # Build the hash by unit tag
127
+ fresh_available_abilities = {}
128
+ abilities.each do |row|
129
+ fresh_available_abilities[row.unit_tag] = row.abilities.map(&:ability_id)
130
+ end
131
+ @available_abilities = fresh_available_abilities
132
+ @available_abilities_loop = game_loop
133
+ end
134
+ @available_abilities
135
+ end
136
+
109
137
  # An alias for observation.player_common to allow easier access to i.e. common.minerals
110
138
  # @return [Api::PlayerCommon] common info such as minerals, vespene, supply
111
139
  def common
@@ -31,6 +31,12 @@ module Sc2
31
31
  @map_height ||= bot.game_info.start_raw.map_size.y
32
32
  end
33
33
 
34
+ # Center of the map
35
+ # @return [Api::Point2D]
36
+ def map_center
37
+ @map_center ||= Api::Point2D[map_width / 2, map_height / 2]
38
+ end
39
+
34
40
  # Returns zero to map_width as range
35
41
  # @return [Range] 0 to map_width
36
42
  def map_range_x
@@ -100,8 +106,16 @@ module Sc2
100
106
  expansion_points.each do |point|
101
107
  x = point.x.floor
102
108
  y = point.y.floor
103
- @expo_placement_grid[(y - 2).clamp(map_tile_range_y)..(y + 2).clamp(map_tile_range_y),
104
- (x - 2).clamp(map_tile_range_y)..(x + 2).clamp(map_tile_range_y)] = 1
109
+
110
+ # For zerg, reserve a layer at the bottom for larva->egg
111
+ if bot.race == Api::Race::Zerg
112
+ # Reserve one row lower, meaning (y-3) instead of (y-2)
113
+ @expo_placement_grid[(y - 3).clamp(map_tile_range_y)..(y + 2).clamp(map_tile_range_y),
114
+ (x - 2).clamp(map_tile_range_y)..(x + 2).clamp(map_tile_range_y)] = 1
115
+ else
116
+ @expo_placement_grid[(y - 2).clamp(map_tile_range_y)..(y + 2).clamp(map_tile_range_y),
117
+ (x - 2).clamp(map_tile_range_y)..(x + 2).clamp(map_tile_range_y)] = 1
118
+ end
105
119
  end
106
120
  end
107
121
  @expo_placement_grid
@@ -121,40 +135,11 @@ module Sc2
121
135
  return result
122
136
  end
123
137
 
124
- radius = power_source.radius
125
- radius_tile = radius.ceil
126
-
127
- # Keep this code-block, should we need to make power sources dynamic again:
128
- # START: Dynamic blueprint
129
- # # Build a blueprint and mark it everywhere we need to
130
- # # Lets mark everything as powered with 1 and then disable non-powered with a 0
131
- # blueprint = Numo::Bit.ones(radius.ceil * 2, radius.ceil * 2)
132
- # #blueprint[radius_tile, radius_tile] = 0
133
- # blueprint[(radius_tile - 1)..radius_tile, (radius_tile - 1)..radius_tile] = 0
134
- # # Loop over top-right quadrant of a circle, so we don't have to +/- for distance calcs.
135
- # # Additionally, we only measure if in the upper triangle, since the inner is all inside the circle.
136
- # # Then apply to all four quadrants.
137
- # quadrant_size = radius_tile - 1
138
- # point_search_offsets = (0..quadrant_size).to_a.product((0..quadrant_size).to_a)
139
- # point_search_offsets.each do |y, x|
140
- # next if x < quadrant_size - y # Only upper Triangle
141
- #
142
- # dist = Math.hypot(x, y)
143
- # if dist >= radius
144
- # # Mark as outside x4
145
- # blueprint[radius_tile + y, radius_tile + x] = 0
146
- # blueprint[radius_tile + y, radius_tile - 1 - x] = 0
147
- # blueprint[radius_tile - 1 - y, radius_tile + x] = 0
148
- # blueprint[radius_tile - 1 - y, radius_tile - 1 - x] = 0
149
- # end
150
- # end
151
- # END: Dynamic blueprint ---
152
-
153
138
  # Hard-coding this shape for pylon power
154
139
  # 00001111110000
140
+ # 00011111111000
155
141
  # 00111111111100
156
142
  # 01111111111110
157
- # 01111111111110
158
143
  # 11111111111111
159
144
  # 11111111111111
160
145
  # 11111100111111
@@ -162,15 +147,35 @@ module Sc2
162
147
  # 11111111111111
163
148
  # 11111111111111
164
149
  # 01111111111110
165
- # 01111111111110
166
150
  # 00111111111100
151
+ # 00011111111000
167
152
  # 00001111110000
168
-
169
153
  # perf: Saving pre-created shape for speed (0.5ms saved) by using hardcode from .to_binary.unpack("C*")
170
- blueprint_data = [240, 3, 255, 227, 255, 249, 127, 255, 255, 255, 255, 243, 255, 252, 255, 255, 255, 239, 255, 249, 127, 252, 15, 252, 0].pack("C*")
171
- blueprint = ::Numo::Bit.from_binary(blueprint_data, [radius_tile * 2, radius_tile * 2])
172
-
154
+ blueprint_data = [0, 0, 254, 193, 255, 248, 127, 254, 159, 255, 231, 243, 249, 124, 254, 159, 255, 231, 255, 241, 63, 248, 7, 0, 0].pack("C*")
155
+ blueprint_pylon = ::Numo::Bit.from_binary(blueprint_data, [14, 14])
156
+
157
+ # Warp Prism
158
+ # 00011000
159
+ # 01111110
160
+ # 01111110
161
+ # 11111111
162
+ # 11111111
163
+ # 01111110
164
+ # 01111110
165
+ # 00011000
166
+ blueprint_data = [24, 126, 126, 255, 255, 126, 126, 24].pack("C*")
167
+ blueprint_prism = ::Numo::Bit.from_binary(blueprint_data, [8, 8])
168
+
169
+ # Print each power-source on map using shape above
173
170
  bot.power_sources.each do |ps|
171
+ radius_tile = ps.radius.ceil
172
+ # Select blueprint for 7-tile radius (Pylon) or 4-tile radius (Prism)
173
+ blueprint = if radius_tile == 4
174
+ blueprint_prism
175
+ else
176
+ blueprint_pylon
177
+ end
178
+
174
179
  x_tile = ps.pos.x.floor
175
180
  y_tile = ps.pos.y.floor
176
181
  replace_start_x = (x_tile - radius_tile)
@@ -424,7 +429,7 @@ module Sc2
424
429
  # This differs from position of first base structure
425
430
  # @return [Api::Point2D]
426
431
  def start_position
427
- @start_position ||= bot.observation.raw_data.player.camera
432
+ @start_position ||= bot.observation.raw_data.player.camera.to_p2d
428
433
  end
429
434
 
430
435
  # Returns the enemy 2d start position
@@ -751,7 +756,7 @@ module Sc2
751
756
  )
752
757
  end
753
758
  nearest = @_build_coordinate_tree[cache_key].nearestk(target.x, target.y, random)
754
- return nil if nearest.nil?
759
+ return nil if nearest.nil? || nearest.empty?
755
760
 
756
761
  coordinates[nearest.sample].to_p2d
757
762
  end
@@ -16,6 +16,7 @@ module Sc2
16
16
  @all_units = bot.all_units
17
17
  @units = bot.units
18
18
  @structures = bot.structures
19
+ @placeholders = bot.placeholders
19
20
  @neutral = bot.neutral
20
21
  @effects = bot.effects
21
22
  @power_sources = bot.power_sources
@@ -15,9 +15,14 @@ module Sc2
15
15
 
16
16
  # A full list of all your structures (non-units)
17
17
  # @!attribute units
18
- # @return [Sc2::UnitGroup] a group of units
18
+ # @return [Sc2::UnitGroup] a group of structures
19
19
  attr_accessor :structures
20
20
 
21
+ # A list of structures which haven't started
22
+ # @!attribute units
23
+ # @return [Sc2::UnitGroup] a group of placeholder structures
24
+ attr_accessor :placeholders
25
+
21
26
  # All units with alliance :Neutral
22
27
  # @!attribute neutral
23
28
  # @return [Sc2::UnitGroup] a group of neutral units
@@ -125,41 +130,17 @@ module Sc2
125
130
 
126
131
  # An array of Sensor tower rings as per minimap. It has a `pos` and a `radius`
127
132
  # @!attribute power_sources
128
- # @return [Array<Api::RadarRing>] an array of power sources
133
+ # @return [Array<Api::RadarRing>] an array of radar rings sources
129
134
  attr_accessor :radar_rings # not a unit but has a tag
130
135
 
131
136
  # @private
132
137
  # @!attribute all_seen_unit_tags
133
138
  # Privately keep track of all seen Unit tags (excl structures) in order to detect new created units
134
139
  attr_accessor :_all_seen_unit_tags
140
+ private :_all_seen_unit_tags
135
141
 
136
142
  # Event-driven unit groups ---
137
143
 
138
- # @!attribute event_units_created
139
- # Units created since last frame (visible only, units not structures)
140
- # Read this on_step. Alternative to callback on_unit_created
141
- # @note Morphed units should watch #event_units_type_changed
142
- # @return [Sc2::UnitGroup] group of created units
143
- attr_accessor :event_units_created
144
-
145
- # Units which had their type changed since last frame
146
- # Read this on_step. Alternative to callback on_unit_type_changed
147
- # @!attribute event_units_type_changed
148
- # @return [Sc2::UnitGroup] group effected
149
- attr_accessor :event_units_type_changed
150
-
151
- # Structures seen since last frame with building not completed (< 1.0)
152
- # Read this on_step. Alternative to callback on_structure_started
153
- # @!attribute event_structures_started
154
- # @return [Sc2::UnitGroup] a group of structures started
155
- attr_accessor :event_structures_started
156
-
157
- # Structures which had their building completed (==1.0) since last frame
158
- # Read this on_step. Alternative to callback on_structure_completed
159
- # @!attribute event_structures_completed
160
- # @return [Sc2::UnitGroup] a group of structures started
161
- attr_accessor :event_structures_completed
162
-
163
144
  # Units and Structures which had their health/shields reduced since last frame
164
145
  # Read this on_step. Alternative to callback on_unit_damaged
165
146
  # @!attribute event_units_damaged
@@ -288,6 +269,17 @@ module Sc2
288
269
  true
289
270
  end
290
271
 
272
+ # Micro/Unit-Specific ------
273
+
274
+ # Returns whether Query Available Ability is true for unit and tag
275
+ # Queries API if necessary. Uses batching in the background.
276
+ # @param [Integer] unit_tag
277
+ # @param [Integer] ability_id
278
+ # @return [Boolean]
279
+ def unit_ability_available?(unit_tag:, ability_id:)
280
+ !!available_abilities[unit_tag]&.include?(ability_id)
281
+ end
282
+
291
283
  private
292
284
 
293
285
  # @private
@@ -299,8 +291,10 @@ module Sc2
299
291
  # Clear previous units and prep for categorization
300
292
  @units = UnitGroup.new
301
293
  @structures = UnitGroup.new
294
+ @placeholders = UnitGroup.new
302
295
  @enemy.units = UnitGroup.new
303
296
  @enemy.structures = UnitGroup.new
297
+ @enemy.all_units = UnitGroup.new
304
298
  @neutral = UnitGroup.new
305
299
  @effects = observation.raw_data.effects # not a unit
306
300
  @power_sources = observation.raw_data.player.power_sources # not a unit
@@ -311,10 +305,6 @@ module Sc2
311
305
  @_all_seen_unit_tags ||= Set.new(@units.tags)
312
306
 
313
307
  # Event-driven unit groups as callback alternatives
314
- @event_units_created = UnitGroup.new
315
- @event_structures_started = UnitGroup.new
316
- @event_structures_completed = UnitGroup.new
317
- @event_units_type_changed = UnitGroup.new
318
308
  @event_units_damaged = UnitGroup.new
319
309
  # @event_units_buffed = UnitGroup.new
320
310
 
@@ -335,6 +325,8 @@ module Sc2
335
325
  # Categorize own units/structures, enemy units/structures, neutral
336
326
  if unit.is_blip
337
327
  @blips[tag] = unit
328
+ elsif unit.display_type == :Placeholder
329
+ @placeholders[tag] = unit
338
330
  elsif unit.alliance == own_alliance || unit.alliance == enemy_alliance
339
331
  if unit.alliance == own_alliance
340
332
  structure_collection = @structures
@@ -342,6 +334,7 @@ module Sc2
342
334
  else
343
335
  structure_collection = @enemy.structures
344
336
  unit_collection = @enemy.units
337
+ @enemy.all_units[tag] = unit
345
338
  end
346
339
 
347
340
  unit_data = unit_data(unit.unit_type)
@@ -355,7 +348,7 @@ module Sc2
355
348
  end
356
349
 
357
350
  # Dont parse callbacks on first loop or for neutral units
358
- if !game_loop.zero? &&
351
+ if !@previous.all_units.nil? &&
359
352
  unit.alliance != :Neutral &&
360
353
  unit.display_type != :Placeholder &&
361
354
  unit.is_blip == false
@@ -406,14 +399,11 @@ module Sc2
406
399
 
407
400
  if unit.is_structure?
408
401
  if unit.build_progress < 1
409
- @event_structures_started.add(unit)
410
402
  on_structure_started(unit)
411
403
  else
412
- @event_structures_completed.add(unit)
413
404
  on_structure_completed(unit)
414
405
  end
415
406
  else
416
- @event_units_created.add(unit)
417
407
  on_unit_created(unit)
418
408
  end
419
409
  @_all_seen_unit_tags.add(unit.tag)
@@ -424,7 +414,6 @@ module Sc2
424
414
  def issue_existing_unit_callbacks(unit, previous_unit)
425
415
  # Check if a unit type has changed
426
416
  if unit.unit_type != previous_unit.unit_type
427
- @event_units_type_changed.add(unit)
428
417
  on_unit_type_changed(unit, previous_unit.unit_type)
429
418
  end
430
419
 
@@ -437,7 +426,6 @@ module Sc2
437
426
 
438
427
  if unit.is_structure?
439
428
  if previous_unit.build_progress < 1 && unit.build_progress == 1
440
- @event_structures_completed.add(unit)
441
429
  on_structure_completed(unit)
442
430
  end
443
431
  end
data/lib/sc2ai/player.rb CHANGED
@@ -233,7 +233,7 @@ module Sc2
233
233
  perform_actions
234
234
  perform_debug_commands # TODO: Detect IS_LADDER? -> unless IS_LADDER?
235
235
  step_forward
236
- print "\e[2K#{@step_count} Steps Took (ms): #{(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000}\n\e[1A\r"
236
+ print "\e[2K#{game_loop - @previous.game_loop} Steps Took (ms): #{(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000}\n\e[1A\r"
237
237
  return @result unless @result.nil?
238
238
  break if @status != :in_game
239
239
  end
@@ -329,7 +329,7 @@ module Sc2
329
329
 
330
330
  # Callback for unit destroyed. Tags might be found in `previous.all_units`
331
331
  # This excludes unknown objects, like projectiles and only shows things the API has "seen" as a unit
332
- # Override to use in your bot class or use Player.event_units_destroyed
332
+ # Override to use in your bot class or use Player.
333
333
  # @param unit [Api::Unit]
334
334
  # @see Sc2::Player::Units#units_destroyed
335
335
  def on_unit_destroyed(unit)
@@ -343,7 +343,7 @@ module Sc2
343
343
 
344
344
  # Callback for unit type changing.
345
345
  # To detect certain unit creations, you should use this method to watch morphs.
346
- # Override to use in your bot class or use Player.event_structures_started
346
+ # Override to use in your bot class or use Player.
347
347
  # @param unit [Api::Unit]
348
348
  # @param previous_unit_type_id [Integer] Api::UnitTypeId::*
349
349
  def on_unit_type_changed(unit, previous_unit_type_id)
@@ -356,13 +356,13 @@ module Sc2
356
356
  end
357
357
 
358
358
  # Callback for structure building is completed
359
- # Override to use in your bot class or use Player.event_structures_completed
359
+ # Override to use in your bot class or use Player.
360
360
  # @param unit [Api::Unit]
361
361
  def on_structure_completed(unit)
362
362
  end
363
363
 
364
364
  # Callback for unit (Unit/Structure) taking damage
365
- # Override to use in your bot class or use Player.event_units_damaged
365
+ # Override to use in your bot class or use Player.
366
366
  # @param unit [Api::Unit]
367
367
  # @param amount [Integer] of damage
368
368
  def on_unit_damaged(unit, amount)
@@ -549,7 +549,7 @@ module Sc2
549
549
  # Having loaded all the necessities for the current state...
550
550
  # If we're on the first frame of the game, say previous state and current are the same
551
551
  # This is better than having a bunch of random zero and nil values
552
- @previous.reset(self) if game_loop.zero?
552
+ @previous.reset(self) if @previous.all_units.nil?
553
553
 
554
554
  # TODO: remove @events attributes if we don't use them for performance gains
555
555
  # Actions performed and errors (only if implemented)
@@ -586,7 +586,7 @@ module Sc2
586
586
  self.game_info = @api.game_info
587
587
  end
588
588
 
589
- # Data Parsing -----------------------
589
+ # Enemy -----------------------
590
590
 
591
591
  # If you're random, best to set #race to match after launched
592
592
  def set_race_for_random