sc2ai 0.0.5 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/data/stableid.json +424 -2562
  3. data/data/versions.json +8 -0
  4. data/lib/docker_build/Dockerfile.ruby +1 -1
  5. data/lib/sc2ai/api/ability_id.rb +12 -314
  6. data/lib/sc2ai/api/buff_id.rb +9 -16
  7. data/lib/sc2ai/api/data.rb +1 -4
  8. data/lib/sc2ai/api/tech_tree.rb +35 -0
  9. data/lib/sc2ai/api/tech_tree_data.rb +14 -9
  10. data/lib/sc2ai/api/unit_type_id.rb +6 -58
  11. data/lib/sc2ai/api/upgrade_id.rb +9 -9
  12. data/lib/sc2ai/connection/requests.rb +34 -16
  13. data/lib/sc2ai/data.rb +101 -0
  14. data/lib/sc2ai/local_play/match.rb +1 -3
  15. data/lib/sc2ai/player/actions.rb +8 -4
  16. data/lib/sc2ai/player/debug.rb +2 -3
  17. data/lib/sc2ai/player/game_state.rb +36 -5
  18. data/lib/sc2ai/player/geometry.rb +138 -55
  19. data/lib/sc2ai/player/previous_state.rb +2 -1
  20. data/lib/sc2ai/player/units.rb +66 -3
  21. data/lib/sc2ai/player.rb +5 -3
  22. data/lib/sc2ai/protocol/_meta_documentation.rb +18 -0
  23. data/lib/sc2ai/protocol/extensions/ability_remapable.rb +22 -0
  24. data/lib/sc2ai/protocol/extensions/point.rb +3 -1
  25. data/lib/sc2ai/protocol/extensions/point_2_d.rb +1 -1
  26. data/lib/sc2ai/protocol/extensions/point_distance.rb +11 -0
  27. data/lib/sc2ai/protocol/extensions/position.rb +48 -13
  28. data/lib/sc2ai/protocol/extensions/unit.rb +54 -3
  29. data/lib/sc2ai/protocol/extensions/unit_type.rb +9 -0
  30. data/lib/sc2ai/unit_group/action_ext.rb +3 -3
  31. data/lib/sc2ai/unit_group/filter_ext.rb +33 -7
  32. data/lib/sc2ai/unit_group/geo_ext.rb +28 -0
  33. data/lib/sc2ai/unit_group.rb +3 -0
  34. data/lib/sc2ai/version.rb +1 -1
  35. data/lib/templates/new/run_example_match.rb.tt +1 -1
  36. data/sig/sc2ai.rbs +653 -567
  37. metadata +22 -31
@@ -20,7 +20,7 @@ module Sc2
20
20
  # @return [Integer]
21
21
  def map_width
22
22
  # bot.bot.game_info
23
- bot.game_info.start_raw.map_size.x
23
+ @map_width ||= bot.game_info.start_raw.map_size.x
24
24
  end
25
25
 
26
26
  # Gets the map tile height. Range is 1-255.
@@ -28,7 +28,13 @@ module Sc2
28
28
  # @return [Integer]
29
29
  def map_height
30
30
  # bot.bot.game_info
31
- bot.game_info.start_raw.map_size.y
31
+ @map_height ||= bot.game_info.start_raw.map_size.y
32
+ end
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]
32
38
  end
33
39
 
34
40
  # Returns zero to map_width as range
@@ -83,6 +89,15 @@ module Sc2
83
89
  @parsed_placement_grid
84
90
  end
85
91
 
92
+ # Whether this tile is where an expansion is supposed to be placed.
93
+ # To see if a unit/structure is blocking an expansion, pass their coordinates to this method.
94
+ # @param x [Float, Integer]
95
+ # @param y [Float, Integer]
96
+ # @return [Boolean] true if location has creep on it
97
+ def expo_placement?(x:, y:)
98
+ expo_placement_grid[y.to_i, x.to_i] == 1
99
+ end
100
+
86
101
  # Returns a grid where ony the expo locations are marked
87
102
  # @return [Numo::Bit]
88
103
  def expo_placement_grid
@@ -91,8 +106,16 @@ module Sc2
91
106
  expansion_points.each do |point|
92
107
  x = point.x.floor
93
108
  y = point.y.floor
94
- @expo_placement_grid[(y - 2).clamp(map_tile_range_y)..(y + 2).clamp(map_tile_range_y),
95
- (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
96
119
  end
97
120
  end
98
121
  @expo_placement_grid
@@ -112,40 +135,11 @@ module Sc2
112
135
  return result
113
136
  end
114
137
 
115
- radius = power_source.radius
116
- radius_tile = radius.ceil
117
-
118
- # Keep this code-block, should we need to make power sources dynamic again:
119
- # START: Dynamic blueprint
120
- # # Build a blueprint and mark it everywhere we need to
121
- # # Lets mark everything as powered with 1 and then disable non-powered with a 0
122
- # blueprint = Numo::Bit.ones(radius.ceil * 2, radius.ceil * 2)
123
- # #blueprint[radius_tile, radius_tile] = 0
124
- # blueprint[(radius_tile - 1)..radius_tile, (radius_tile - 1)..radius_tile] = 0
125
- # # Loop over top-right quadrant of a circle, so we don't have to +/- for distance calcs.
126
- # # Additionally, we only measure if in the upper triangle, since the inner is all inside the circle.
127
- # # Then apply to all four quadrants.
128
- # quadrant_size = radius_tile - 1
129
- # point_search_offsets = (0..quadrant_size).to_a.product((0..quadrant_size).to_a)
130
- # point_search_offsets.each do |y, x|
131
- # next if x < quadrant_size - y # Only upper Triangle
132
- #
133
- # dist = Math.hypot(x, y)
134
- # if dist >= radius
135
- # # Mark as outside x4
136
- # blueprint[radius_tile + y, radius_tile + x] = 0
137
- # blueprint[radius_tile + y, radius_tile - 1 - x] = 0
138
- # blueprint[radius_tile - 1 - y, radius_tile + x] = 0
139
- # blueprint[radius_tile - 1 - y, radius_tile - 1 - x] = 0
140
- # end
141
- # end
142
- # END: Dynamic blueprint ---
143
-
144
138
  # Hard-coding this shape for pylon power
145
139
  # 00001111110000
140
+ # 00011111111000
146
141
  # 00111111111100
147
142
  # 01111111111110
148
- # 01111111111110
149
143
  # 11111111111111
150
144
  # 11111111111111
151
145
  # 11111100111111
@@ -153,15 +147,35 @@ module Sc2
153
147
  # 11111111111111
154
148
  # 11111111111111
155
149
  # 01111111111110
156
- # 01111111111110
157
150
  # 00111111111100
151
+ # 00011111111000
158
152
  # 00001111110000
159
-
160
153
  # perf: Saving pre-created shape for speed (0.5ms saved) by using hardcode from .to_binary.unpack("C*")
161
- 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*")
162
- blueprint = ::Numo::Bit.from_binary(blueprint_data, [radius_tile * 2, radius_tile * 2])
163
-
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
164
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
+
165
179
  x_tile = ps.pos.x.floor
166
180
  y_tile = ps.pos.y.floor
167
181
  replace_start_x = (x_tile - radius_tile)
@@ -260,6 +274,7 @@ module Sc2
260
274
  end
261
275
 
262
276
  # Returns the terrain height (z) at position x and y for a point
277
+ # @param position [Sc2::Position]
263
278
  # @return [Float] z axis position between -16 and 16
264
279
  def terrain_height_for_pos(position)
265
280
  terrain_height(x: position.x, y: position.y)
@@ -340,15 +355,17 @@ module Sc2
340
355
  end
341
356
 
342
357
  # Provides parsed minimap representation of creep spread
358
+ # Caches for 4 frames
343
359
  # @return [Numo::Bit] Numo array
344
360
  def parsed_creep
345
- if @parsed_creep.nil?
361
+ if @parsed_creep.nil? || @parsed_creep[1] + 4 < bot.game_loop
346
362
  image_data = bot.observation.raw_data.map_state.creep
347
363
  # Fix endian for Numo bit parser
348
364
  data = image_data.data.unpack("b*").pack("B*")
349
- @parsed_creep = ::Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
365
+ result = ::Numo::Bit.from_binary(data, [image_data.size.y, image_data.size.x])
366
+ @parsed_creep = [result, bot.game_loop]
350
367
  end
351
- @parsed_creep
368
+ @parsed_creep[0]
352
369
  end
353
370
 
354
371
  # TODO: Removing. Better name or more features for this? Maybe check nearest units.
@@ -412,7 +429,7 @@ module Sc2
412
429
  # This differs from position of first base structure
413
430
  # @return [Api::Point2D]
414
431
  def start_position
415
- @start_position ||= bot.observation.raw_data.player.camera
432
+ @start_position ||= bot.observation.raw_data.player.camera.to_p2d
416
433
  end
417
434
 
418
435
  # Returns the enemy 2d start position
@@ -543,16 +560,75 @@ module Sc2
543
560
  # @param base [Api::Unit, Sc2::Position] base Unit or Position
544
561
  # @return [Sc2::UnitGroup] UnitGroup of minerals for the base
545
562
  def minerals_for_base(base)
546
- # resources_for_base contains what we need, but slice neutral.minerals,
547
- # so that only active patches remain
548
- bot.neutral.minerals.slice(*resources_for_base(base).minerals.tags)
563
+ base_resources = resources_for_base(base)
564
+ cached_tags = base_resources.minerals.tags
565
+ observed_tags = bot.neutral.minerals.tags
566
+
567
+ # BACK-STORY: Mineral id's are fixed when in vision.
568
+ # Snapshots get random id's every time an object leaves vision.
569
+ # At game launch when we calculate and save minerals, which are mostly snapshot.
570
+
571
+ # Currently, we might have moved vision over minerals, so that their id's have changed.
572
+ # The alive object share a Position with our cached one, so we can get the correct id and update our cache.
573
+
574
+ # PERF: Fix takes 0.70ms, cache takes 0.10ms - we mostly call cached. This is the way.
575
+ # PERF: In contrast, repeated calls to neutral.minerals.units_in_circle? always costs 0.22ms
576
+
577
+ missing_tags = cached_tags - observed_tags
578
+ unless missing_tags.empty?
579
+ other_alive_minerals = bot.neutral.minerals.slice(*(observed_tags - cached_tags))
580
+ # For each missing calculated mineral patch...
581
+ missing_tags.each do |tag|
582
+ missing_resource = base_resources.delete(tag)
583
+
584
+ # Find an alive mineral at that position
585
+ new_resource = other_alive_minerals.find { |live_mineral| live_mineral.pos == missing_resource.pos }
586
+ base_resources.add(new_resource) unless new_resource.nil?
587
+ end
588
+ end
589
+
590
+ base_resources.minerals
549
591
  end
550
592
 
551
593
  # Gets geysers for a base or base position
552
594
  # @param base [Api::Unit, Sc2::Position] base Unit or Position
553
595
  # @return [Sc2::UnitGroup] UnitGroup of geysers for the base
554
596
  def geysers_for_base(base)
555
- resources_for_base(base).geysers
597
+ # @see #minerals_for_base for backstory on these fixes
598
+ base_resources = resources_for_base(base)
599
+ cached_tags = base_resources.geysers.tags
600
+ observed_tags = bot.neutral.geysers.tags
601
+
602
+ missing_tags = cached_tags - observed_tags
603
+ unless missing_tags.empty?
604
+ other_alive_geysers = bot.neutral.geysers.slice(*(observed_tags - cached_tags))
605
+ # For each missing calculated geyser patch...
606
+ missing_tags.each do |tag|
607
+ missing_resource = base_resources.delete(tag)
608
+
609
+ # Find an alive geyser at that position
610
+ new_resource = other_alive_geysers.find { |live_geyser| live_geyser.pos == missing_resource.pos }
611
+ base_resources.add(new_resource) unless new_resource.nil?
612
+ end
613
+ end
614
+
615
+ base_resources.geysers
616
+ end
617
+
618
+ # Gets geysers which have not been taken for a base or base position
619
+ # @param base [Api::Unit, Sc2::Position] base Unit or Position
620
+ # @return [Sc2::UnitGroup] UnitGroup of geysers for the base
621
+ def geysers_open_for_base(base)
622
+ geysers = geysers_for_base(base)
623
+
624
+ # Mineral-only base, return nothing
625
+ return UnitGroup.new if geysers.size == 0
626
+
627
+ # Reject all which have a gas structure on-top
628
+ gas_positions = bot.structures.gas.map { |gas| gas.pos }
629
+ geysers.reject do |geyser|
630
+ gas_positions.include?(geyser.pos)
631
+ end
556
632
  end
557
633
 
558
634
  # @private
@@ -560,6 +636,7 @@ module Sc2
560
636
  # @return [Sc2::UnitGroup] UnitGroup of resources (minerals+geysers)
561
637
  private def resources_for_base(base)
562
638
  pos = base.is_a?(Api::Unit) ? base.pos : base
639
+ pos = pos.to_p2d if base.is_a?(Api::Point)
563
640
 
564
641
  # If we have a base setup for this exact position, use it
565
642
  if expansions.has_key?(pos)
@@ -599,12 +676,17 @@ module Sc2
599
676
  def build_coordinates(length:, on_creep: false, in_power: false)
600
677
  length = 1 if length < 1
601
678
  @_build_coordinates ||= {}
602
- cache_key = [length, on_creep].hash
679
+ cache_key = [length, on_creep, in_power].hash
603
680
  return @_build_coordinates[cache_key] if !@_build_coordinates[cache_key].nil? && !bot.game_info_stale?
604
681
 
605
682
  result = []
606
683
  input_grid = parsed_pathing_grid & parsed_placement_grid & ~expo_placement_grid
607
- input_grid = parsed_creep & input_grid if on_creep
684
+ input_grid = if on_creep
685
+ parsed_creep & input_grid
686
+ else
687
+ ~parsed_creep & input_grid
688
+ end
689
+
608
690
  input_grid = parsed_power_grid & input_grid if in_power
609
691
 
610
692
  # Dimensions
@@ -657,23 +739,24 @@ module Sc2
657
739
  # @param length [Integer] length of the building, 2 for depot/pylon, 3 for rax/gate
658
740
  # @param target [Api::Unit, Sc2::Position] near where to find a placement
659
741
  # @param random [Integer] number of nearest points to randomly choose from. 1 for nearest point.
742
+ # @param in_power [Boolean] whether this must be on a power field
660
743
  # @return [Api::Point2D, nil] buildable location, nil if no buildable location found
661
- def build_placement_near(length:, target:, random: 1)
744
+ def build_placement_near(length:, target:, random: 1, in_power: false)
662
745
  target = target.pos if target.is_a? Api::Unit
663
746
  random = 1 if random.to_i.negative?
664
747
  length = 1 if length < 1
665
748
  on_creep = bot.race == Api::Race::Zerg
666
749
 
667
- coordinates = build_coordinates(length:, on_creep:)
750
+ coordinates = build_coordinates(length:, on_creep:, in_power:)
751
+ cache_key = coordinates.hash
668
752
  @_build_coordinate_tree ||= {}
669
- cache_key = [length, on_creep].hash
670
753
  if @_build_coordinate_tree[cache_key].nil?
671
754
  @_build_coordinate_tree[cache_key] = Kdtree.new(
672
755
  coordinates.each_with_index.map { |coords, index| coords + [index] }
673
756
  )
674
757
  end
675
758
  nearest = @_build_coordinate_tree[cache_key].nearestk(target.x, target.y, random)
676
- return nil if nearest.nil?
759
+ return nil if nearest.nil? || nearest.empty?
677
760
 
678
761
  coordinates[nearest.sample].to_p2d
679
762
  end
@@ -819,14 +902,14 @@ module Sc2
819
902
  # @example
820
903
  # Randomly randomly adjust both x and y by a range of -3.5 or +3.5
821
904
  # geo.point_random_near(point: structures.hq.first, offset: 3.5)
822
- # @param pos [Sc2::Location]
905
+ # @param pos [Sc2::Position]
823
906
  # @param offset [Float]
824
907
  # @return [Api::Point2D]
825
908
  def point_random_near(pos:, offset: 1.0)
826
909
  pos.random_offset(offset)
827
910
  end
828
911
 
829
- # @param pos [Sc2::Location]
912
+ # @param pos [Sc2::Position]
830
913
  # @param radius [Float]
831
914
  # @return [Api::Point2D]
832
915
  def point_random_on_circle(pos:, radius: 1.0)
@@ -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
@@ -24,7 +25,7 @@ module Sc2
24
25
  @status = bot.status
25
26
  @game_info = bot.game_info
26
27
  @observation = bot.observation
27
-
28
+ @game_loop = bot.observation.game_loop
28
29
  @spent_minerals = bot.spent_minerals
29
30
  @spent_vespene = bot.spent_vespene
30
31
  @spent_supply = bot.spent_supply
@@ -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
@@ -77,6 +82,47 @@ module Sc2
77
82
  false
78
83
  end
79
84
 
85
+ # For this unit type, tells you how many are in progress by checking orders for all it's sources.
86
+ # @return [Integer]
87
+ def units_in_progress(unit_type_id)
88
+ source_unit_types = Api::TechTree.unit_created_from(unit_type_id: unit_type_id)
89
+
90
+ # When building from LARVA, check the intermediate models
91
+ if source_unit_types.include?(Api::UnitTypeId::LARVA)
92
+ source_unit_types << Api::UnitTypeId::EGG
93
+ elsif source_unit_types.include?(Api::UnitTypeId::BANELING)
94
+ # For certain Zerg types, return the count of specific intermediate egg/cocoon
95
+ return units.select_type(Api::UnitTypeId::BANELINGCOCOON).size
96
+ elsif source_unit_types.include?(Api::UnitTypeId::RAVAGER)
97
+ return units.select_type(Api::UnitTypeId::RAVAGERCOCOON).size
98
+ elsif source_unit_types.include?(Api::UnitTypeId::OVERSEER)
99
+ return units.select_type(Api::UnitTypeId::OVERLORDCOCOON).size
100
+ elsif source_unit_types.include?(Api::UnitTypeId::LURKERMP)
101
+ return units.select_type(Api::UnitTypeId::LURKERMPEGG).size
102
+ elsif source_unit_types.include?(Api::UnitTypeId::BROODLORD)
103
+ return units.select_type(Api::UnitTypeId::BROODLORDCOCOON).size
104
+ end
105
+
106
+ unit_create_ability = Api::TechTree.unit_type_creation_ability_id(
107
+ source: source_unit_types.first,
108
+ target: unit_type_id
109
+ )
110
+
111
+ origin = if unit_data(source_unit_types.first).attributes.include?(:Structure)
112
+ structures
113
+ else
114
+ units
115
+ end
116
+ total_in_progress = origin.select_type(source_unit_types).sum do |source|
117
+ source.orders.count do |order|
118
+ true if order.ability_id == unit_create_ability
119
+ end
120
+ end
121
+ total_in_progress *= 2 if unit_type_id == Api::UnitTypeId::ZERGLING
122
+
123
+ total_in_progress
124
+ end
125
+
80
126
  # An array of Protoss power sources, which have a point, radius and unit tag
81
127
  # @!attribute power_sources
82
128
  # @return [Array<Api::PowerSource>] an array of power sources
@@ -84,13 +130,14 @@ module Sc2
84
130
 
85
131
  # An array of Sensor tower rings as per minimap. It has a `pos` and a `radius`
86
132
  # @!attribute power_sources
87
- # @return [Array<Api::RadarRing>] an array of power sources
133
+ # @return [Array<Api::RadarRing>] an array of radar rings sources
88
134
  attr_accessor :radar_rings # not a unit but has a tag
89
135
 
90
136
  # @private
91
137
  # @!attribute all_seen_unit_tags
92
138
  # Privately keep track of all seen Unit tags (excl structures) in order to detect new created units
93
139
  attr_accessor :_all_seen_unit_tags
140
+ private :_all_seen_unit_tags
94
141
 
95
142
  # Event-driven unit groups ---
96
143
 
@@ -247,6 +294,17 @@ module Sc2
247
294
  true
248
295
  end
249
296
 
297
+ # Micro/Unit-Specific ------
298
+
299
+ # Returns whether Query Available Ability is true for unit and tag
300
+ # Queries API if necessary. Uses batching in the background.
301
+ # @param [Integer] unit_tag
302
+ # @param [Integer] ability_id
303
+ # @return [Boolean]
304
+ def unit_ability_available?(unit_tag:, ability_id:)
305
+ !!available_abilities[unit_tag]&.include?(ability_id)
306
+ end
307
+
250
308
  private
251
309
 
252
310
  # @private
@@ -258,8 +316,10 @@ module Sc2
258
316
  # Clear previous units and prep for categorization
259
317
  @units = UnitGroup.new
260
318
  @structures = UnitGroup.new
319
+ @placeholders = UnitGroup.new
261
320
  @enemy.units = UnitGroup.new
262
321
  @enemy.structures = UnitGroup.new
322
+ @enemy.all_units = UnitGroup.new
263
323
  @neutral = UnitGroup.new
264
324
  @effects = observation.raw_data.effects # not a unit
265
325
  @power_sources = observation.raw_data.player.power_sources # not a unit
@@ -294,6 +354,8 @@ module Sc2
294
354
  # Categorize own units/structures, enemy units/structures, neutral
295
355
  if unit.is_blip
296
356
  @blips[tag] = unit
357
+ elsif unit.display_type == :Placeholder
358
+ @placeholders[tag] = unit
297
359
  elsif unit.alliance == own_alliance || unit.alliance == enemy_alliance
298
360
  if unit.alliance == own_alliance
299
361
  structure_collection = @structures
@@ -301,6 +363,7 @@ module Sc2
301
363
  else
302
364
  structure_collection = @enemy.structures
303
365
  unit_collection = @enemy.units
366
+ @enemy.all_units[tag] = unit
304
367
  end
305
368
 
306
369
  unit_data = unit_data(unit.unit_type)
@@ -314,7 +377,7 @@ module Sc2
314
377
  end
315
378
 
316
379
  # Dont parse callbacks on first loop or for neutral units
317
- if !game_loop.zero? &&
380
+ if !@previous.all_units.nil? &&
318
381
  unit.alliance != :Neutral &&
319
382
  unit.display_type != :Placeholder &&
320
383
  unit.is_blip == false
data/lib/sc2ai/player.rb CHANGED
@@ -226,13 +226,14 @@ module Sc2
226
226
  # Callback for step 0
227
227
  on_step
228
228
 
229
+ puts ""
229
230
  # Step 1 to n
230
231
  loop do
231
232
  r = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
232
233
  perform_actions
233
234
  perform_debug_commands # TODO: Detect IS_LADDER? -> unless IS_LADDER?
234
235
  step_forward
235
- puts (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000
236
+ print "\e[2K#{game_loop - @previous.game_loop} Steps Took (ms): #{(::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - r) * 1000}\n\e[1A\r"
236
237
  return @result unless @result.nil?
237
238
  break if @status != :in_game
238
239
  end
@@ -525,6 +526,7 @@ module Sc2
525
526
  @previous.reset(self)
526
527
  # Reset
527
528
  self.observation = response_observation.observation
529
+ self.game_loop = observation.game_loop
528
530
  self.chats_received = response_observation.chat
529
531
  self.spent_minerals = 0
530
532
  self.spent_vespene = 0
@@ -547,7 +549,7 @@ module Sc2
547
549
  # Having loaded all the necessities for the current state...
548
550
  # If we're on the first frame of the game, say previous state and current are the same
549
551
  # This is better than having a bunch of random zero and nil values
550
- @previous.reset(self) if game_loop.zero?
552
+ @previous.reset(self) if @previous.all_units.nil?
551
553
 
552
554
  # TODO: remove @events attributes if we don't use them for performance gains
553
555
  # Actions performed and errors (only if implemented)
@@ -584,7 +586,7 @@ module Sc2
584
586
  self.game_info = @api.game_info
585
587
  end
586
588
 
587
- # Data Parsing -----------------------
589
+ # Enemy -----------------------
588
590
 
589
591
  # If you're random, best to set #race to match after launched
590
592
  def set_race_for_random
@@ -23,8 +23,26 @@
23
23
  # class Unit < Google::Protobuf::AbstractMessage; end;
24
24
  # # Protobuf virtual class.
25
25
  # class UnitTypeData < Google::Protobuf::AbstractMessage; end;
26
+ # # Protobuf virtual class.
27
+ # class AvailableAbility < Google::Protobuf::AbstractMessage; end;
28
+ # # Protobuf virtual class.
29
+ # class UnitOrder < Google::Protobuf::AbstractMessage; end;
30
+ # # Protobuf virtual class.
31
+ # class ActionRawUnitCommand < Google::Protobuf::AbstractMessage; end;
32
+ # # Protobuf virtual class.
33
+ # class ActionRawToggleAutocast < Google::Protobuf::AbstractMessage; end;
34
+ # # Protobuf virtual class.
35
+ # class ActionError < Google::Protobuf::AbstractMessage; end;
36
+ # # Protobuf virtual class.
37
+ # class ActionSpatialUnitCommand < Google::Protobuf::AbstractMessage; end;
38
+ # # Protobuf virtual class.
39
+ # class BuildItem < Google::Protobuf::AbstractMessage; end;
40
+ # # Protobuf virtual class.
41
+ # class ActionToggleAutocast < Google::Protobuf::AbstractMessage; end;
26
42
  # end
27
43
 
44
+
45
+
28
46
  # Protobuf enums ---
29
47
  # Protobuf classes
30
48
  # @!parse
@@ -0,0 +1,22 @@
1
+ module Api
2
+ # This module make sure that a read from method ability_id always returns the proper source id
3
+ module AbilityRemapable
4
+ # Ability Id. The generic id or "remapid".
5
+ # i.e. Api::AbilityId::ATTACK_BATTLECRUISER returns generic Api::AbilityId::ATTACK
6
+ # @return [Integer]
7
+ def ability_id
8
+ @ability_id ||= Api::AbilityId.generic_id(send(:method_missing, :ability_id))
9
+ end
10
+ end
11
+ end
12
+
13
+ # AbilityData should not include, since it holds exact info and contains the remap id as attr
14
+ # Similarly Request* methods only do requests and ew supply correct id's.
15
+ Api::AvailableAbility.include Api::AbilityRemapable
16
+ Api::UnitOrder.include Api::AbilityRemapable
17
+ Api::ActionRawUnitCommand.include Api::AbilityRemapable
18
+ Api::ActionRawToggleAutocast.include Api::AbilityRemapable
19
+ Api::ActionError.include Api::AbilityRemapable
20
+ Api::ActionSpatialUnitCommand.include Api::AbilityRemapable
21
+ Api::BuildItem.include Api::AbilityRemapable
22
+ Api::ActionToggleAutocast.include Api::AbilityRemapable
@@ -3,7 +3,9 @@ module Api
3
3
  module PointExtension
4
4
  # @private
5
5
  def hash
6
- [x, y, z].hash
6
+ # Only one plane is ever used. Ignore hashing on z
7
+ # [x, y, z].hash
8
+ [x, y].hash
7
9
  end
8
10
 
9
11
  def eql?(other)
@@ -12,7 +12,7 @@ module Api
12
12
 
13
13
  # Create a new 3d Point, by adding a y axis.
14
14
  # @return [Api::Point]
15
- def to_3d(z:)
15
+ def to_3d(z: 0)
16
16
  Api::Point[x, y, z]
17
17
  end
18
18
 
@@ -0,0 +1,11 @@
1
+ # This class was partially generated with the help of AI.
2
+
3
+ module Api
4
+ # Adds additional functionality to message object Api::Unit
5
+ module PointDistanceExtension
6
+ end
7
+ end
8
+ Api::Point.include Api::PointDistanceExtension
9
+ Api::Point2D.include Api::PointDistanceExtension
10
+ Api::PointI.include Api::PointDistanceExtension
11
+ Api::Size2DI.include Api::PointDistanceExtension