sc2ai 0.0.6 → 0.0.8

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