sc2ai 0.0.5 → 0.0.7

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